
「查缺补漏」巩固你的Redis知识体系
作者 |柯小贤
来源 | 是Kerwin啊(ID:KerwinRoad)
Windows Redis
安装
链接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取码: 2c6w 复制这段内容后打开百度网盘手机App,操作更方便哦
无脑下一步即可
使用
出现错误:
creating server tcp listening socket 127.0.0.1:6379: bind No error
解决方案:
- redis-cli.exe
- shutdown
- exit
- redis-server.exe redis.windows.conf
启动:redis-server.exe redis.windows.conf
客户端启动:redis-cli.exe (不修改配置的话默认即可)
redis-cli.exe -h 127.0.0.1 -p 6379 -a password
基本文件说明
基础命令
字符串命令
哈希(Hash)命令
编码: field value 值由 ziplist 及 hashtable 两种编码格式
字段较少的时候采用ziplist,字段较多的时候会变成hashtable编码
列表(List)命令
Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)
一个列表最多可以包含 232 - 1 个元素 (4294967295, 每个列表超过40亿个元素)
容量 -> 集合,有序集合也是如此
集合(Set)命令
Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据
有序集合(sorted set)命令
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
有序集合的成员是唯一的,但分数(score)却可以重复。
发布订阅
开启两个客户端
A客户端订阅频道:subscribe redisChat (频道名字为 redisChat)
B客户端发布内容:publish redisChat "Hello, this is my wor" (内容是 hello....)
A客户端即为自动收到内容, 原理图如下:
Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
一个事务从开始到执行会经历以下三个阶段:
- 开始事务
- 命令入队
- 执行事务
注意:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚
Redis 服务器命令
Redis 数据备份与恢复
Redis SAVE 命令用于创建当前数据库的备份
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令
Redis 性能测试
redis 性能测试的基本命令如下:
Java Redis
Jedis
Jedis配置
JedisConfig
基础使用
SpringBoot redis staeter RedisTemplate
Redis使用场景或者简单消息队列,发布订阅实施消息系统等等
String - 缓存
String - 限流 | 计数器
String - 分布式锁 (重点)
String - 分布式Session(重点)
List 简单队列-栈
List 社交类APP - 好友列表
Set 抽奖 | 好友关系(合,并,交集)
Zset 排行榜
常见面试题
针对第四点进行说明 ->
常见的IO模型有四种:
- 同步阻塞IO(Blocking IO):即传统的IO模型。
- 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
- IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
- 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO
同步异步,阻塞非阻塞的概念:
假设Redis采用同步阻塞IO:
Redis主程序(服务端 单线程)-> 多个客户端连接(真实情况是如开发人员连接redis,程序 redispool连接redis),这每一个都对应着一个客户端,假设为100个客户端,其中一个进行交互时候,如果采用同步阻塞式,那么剩下的99个都需要原地等待,这势必是不科学的。
IO多路复用
Redis 采用 I/O 多路复用模型
I/O 多路复用模型中,最重要的函数调用就是 select,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select 方法就会返回可读以及可写的文件描述符个数
注:redis默认使用的是更加优化的算法:epoll
所以我们可以说Redis是这样的:服务端单线程毫无疑问,多客户端连接时候,如果客户端没有发起任何动作,则服务端会把其视为不活跃的IO流,将其挂起,当有真正的动作时,会通过回调的方式执行相应的事件
Q2:从海量Key里查询出某一个固定前缀的Key
A. 笨办法:KEYS [pattern] 注意key很多的话,这样做肯定会出问题,造成redis崩溃
B. SCAN cursor [MATCH pattern] [COUNT count] 游标方式查找
Q3:如何通过Redis实现分布式锁
见上文
Q4:如何实现异步队列
上文说到利用 redis-list 实现队列
假设场景:A服务生产数据 - B服务消费数据,即可利用此种模型构造-生产消费者模型
1. 使用Redis中的List作为队列
2.使用BLPOP key [key...] timeout -> LPOP key [key ...] timeout:阻塞直到队列有消息或者超时
(方案二:解决方案一中,拿数据的时,生产者尚未生产的情况)
3.pub/sub:主题订阅者模式
基于reds的终极方案,上文有介绍,基于发布/订阅模式
缺点:消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的,此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列
Q5:Redis支持的数据类型?
见上文
Q6:什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 提供了两种持久化方式:RDB(默认) 和AOF
RDB:
rdb是Redis DataBase缩写
功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数RDB: 把当前进程数据生成快照文件保存到硬盘的过程。分为手动触发和自动触发
手动触发 -> save (不推荐,阻塞严重) bgsave -> (save的优化版,微秒级阻塞)
shutdowm 关闭服务时,如果没有配置AOF,则会使用bgsave持久化数据
bgsave - 工作原理
会从当前父进程fork一个子进程,然后生成rdb文件
缺点:频率低,无法做到实时持久化
AOF:
Aof是Append-only file缩写,AOF文件存储的也是RESP协议每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof写入保存:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
存储结构:
内容是redis通讯协议(RESP )格式的命令文本存储
原理:
相当于存储了redis的执行命令(类似mysql的sql语句日志),数据的完整性和一致性更高
比较:
1、aof文件比rdb更新频率高
2、aof比rdb更安全
3、rdb性能更好
PS:正确停止redis服务 应该基于连接命令 加再上 shutdown -> 否则数据持久化会出现问题
Q7:redis通讯协议(RESP)
Redis 即 REmote Dictionary Server (远程字典服务);
而Redis的协议规范是 Redis Serialization Protocol (Redis序列化协议)
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
协议如下:
客户端以规定格式的形式发送命令给服务器
服务器在执行最后一条命令后,返回结果,返回格式如下:
For Simple Strings the first byte of the reply is "+" 回复
For Errors the first byte of the reply is "-" 错误
For Integers the first byte of the reply is ":" 整数
For Bulk Strings the first byte of the reply is "$" 字符串
For Arrays the first byte of the reply is "*" 数组
Q8:redis架构有哪些
单节点
主从复制
哨兵
集群
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
Q9:Redis集群-如何从海量数据里快速找到所需?
分片
按照某种规则去划分数据,分散存储在多个节点上。通过将数据分到多个Redis服务器上,来减轻单个Redis服务器的压力。
一致性Hash算法
既然要将数据进行分片,那么通常的做法就是获取节点的Hash值,然后根据节点数求模,但这样的方法有明显的弊端,当Redis节点数需要动态增加或减少的时候,会造成大量的Key无法被命中。所以Redis中引入了一致性Hash算法。该算法对2^32 取模,将Hash值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为0、1、2...2^32-1,之后将每个服务器进行Hash运算,确定服务器在这个Hash环上的地址,确定了服务器地址后,对数据使用同样的Hash算法,将数据定位到特定的Redis服务器上。如果定位到的地方没有Redis服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。
一致性Hash算法
Hash环的数据倾斜问题
Hash环在服务器节点很少的时候,容易遇到服务器节点不均匀的问题,这会造成数据倾斜,数据倾斜指的是被缓存的对象大部分集中在Redis集群的其中一台或几台服务器上。如上图,一致性Hash算法运算后的数据大部分被存放在A节点上,而B节点只存放了少量的数据,久而久之A节点将被撑爆。引入虚拟节点
例如上图:将NodeA和NodeB两个节点分为Node A#1-A#3 NodeB#1-B#3。
Q10:什么是缓存穿透?如何避免?什么是缓存雪崩?如何避免?什么是缓存击穿?如何避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
3:由于请求参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(Bloomfilter)或压缩filter提前进行拦截,不合法就不让这个请求进入到数据库层
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4:启用限流策略,尽量避免数据库被干掉
缓存击穿
概念 一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案 A. 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key
B. 服务层处理 - 方法加锁 + 双重校验:
Q11:缓存与数据库双写一致
如果仅仅是读数据,没有此类问题
如果是新增数据,也没有此类问题
当数据需要更新时,如何保证缓存与数据库的双写一致性?
三种更新策略:
- 先更新数据库,再更新缓存 ->
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
方案一:并发的时候,执行顺序无法保证,可能A先更新数据库,但B后更新数据库但先更新缓存
加锁的话,确实可以避免,但这样吞吐量会下降,可以根据业务场景考虑
方案二:该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形: (1)请求A进行写操作,删除缓存 (2)请求B查询发现缓存不存在 (3)请求B去数据库查询得到旧值 (4)请求B将旧值写入缓存 (5)请求A将新值写入数据库
因此采用:采用延时双删策略 即进入逻辑就删除Key,执行完操作,延时再删除key
方案三:更新数据库 - 删除缓存 可能出现问题的场景:
(1)缓存刚好失效 (2)请求A查询数据库,得一个旧值 (3)请求B将新值写入数据库 (4)请求B删除缓存 (5)请求A将查到的旧值写入缓存
先天条件要求:请求第二步的读取操作耗时要大于更新操作,条件较为苛刻
但如果真的发生怎么处理?
A. 给键设置合理的过期时间
B. 异步延时删除key
Q12:何保证Redis中的数据都是热点数据
A. 可以通过手工或者主动方式,去加载热点数据
B. Redis有其自己的数据淘汰策略:
redis 内存数据集大小上升到一定大小的时候,就会施行数据淘汰策略(回收策略)。redis 提供 6种数据淘汰策略:
- volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-enviction(驱逐):禁止驱逐数据
Q13:Redis的并发竞争问题如何解决?
Q14:Redis回收进程如何工作的? Redis回收使用的是什么算法?
Q12 中提到过,当所需内存超过配置的最大内存时,redis会启用数据淘汰规则
默认规则是:# maxmemory-policy noeviction
即只允许读,无法继续添加key
因此常需要配置淘汰策略,比如LRU算法
LRU算法最为精典的实现,就是HashMap+Double LinkedList,时间复杂度为O(1)
Q15:Redis大批量增加数据
参考文章:https://www.cnblogs.com/PatrickLiu/p/8548580.html
使用管道模式,运行的命令如下所示:
data.txt文本:
这将产生类似于这样的输出:
redis-cli实用程序还将确保只将从Redis实例收到的错误重定向到标准输出
演示:
mysql数据快速导入到redis 实战: 文件详情:可见Redis-通道实战
博文:https://www.cnblogs.com/tommy-huang/p/4703514.html
Q16:延申:布隆过滤器
数据结构及算法篇 / 布隆过滤器
Redis 实现
redis 4.X 以上 提供 布隆过滤器插件
centos中安装redis插件bloom-filter:https://blog.csdn.net/u013030276/article/details/88350641
语法:[bf.add key options]
语法:[bf.exists key options]
注意: redis 布隆过滤器提供的是 最大内存512M,2亿数据,万分之一的误差率
Q17:Lua脚本相关
使用Lua脚本的好处:
- 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延
- 原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务
- 复用,客户端发送的脚本会永久存在redis中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑
Q18:性能相关 - Redis慢查询分析
redis 命令会放在redis内置队列中,然后主线程一个个执行,因此 其中一个 命令执行时间过长,会造成成批量的阻塞
命令:slowlog get 获取慢查询记录 slowlog len 获取慢查询记录量 (慢查询队列是先进先出的,因此新的值在满载的时候,旧的会出去)
Redis 慢查询 -> 执行阶段耗时过长
conf文件设置:slowlog-low-slower-than 10000 -> 10000微秒,10毫秒 (默认) 0 -> 记录所有命令 -1 -> 不记录命令 slow-max-len 存放的最大条数
慢查询导致原因: value 值过大,解决办法:数据分段(更细颗粒度存放数据)
Q19:如何提高Redis处理效率? 基于Jedis 的批量操作 Pipelined
