Redis基础
Redis数据结构
- String:字符串。实现方式:动态字符串(SDS, Simple Dynamic String)
- SDS 通过预分配空间和惰性释放来优化内存使用
- List:列表。实现方式:压缩列表(ziplist)、双向链表、quicklist
- 双向链表:内存不连续,内存开销大,无法利用CPU缓存
- 压缩列表:不能保存过多的元素,新增或修改时内存空间重新分配,可能引发连锁更新
prevlen:前一个节点的长度。如果前一个节点长度小于254个字节,则该字段保存使用1字节,如果大于等于254字节,则使用5字节保存- 连锁更新:如果插入或修改一个元素,导致其大小大于等于
254字节,则其后面节点的prevlen需使用5字节保存,可能也导致再后面的节点长度扩大,从而导致连锁更新
- quicklist:双向链表 + 压缩列表的组合
- 控制每个压缩列表的元素数量避免连锁更新
- 插入时,先判断是否能直接保存在压缩列表,如果不能,再新建一个节点
- 并没有彻底解决连锁更新问题
- Hash:哈希表。压缩列表(ziplist)、哈希表、listpack
- 哈希冲突严重触发渐进式
rehash- rehash期间,在访问一个哈希桶时,也将该哈希桶的所有key value迁移到hash2,以避免一次性操作;新增时,直接添加到hash2,最终hash1表变成空表,然后交换
- 触发时机:负载因子=保存的节点数/哈希桶大小,大于
1时,如果没有bgsave和bgrewriteaof则触发,大于5时,强制触发
- listpack:不再记录前一个节点长度,彻底解决连锁更新问题
- 使用:计数布隆过滤器,支持元素删除
- 优化:
- 对于field比较多的大key,使用
hscan和hmget代替hgetall:需要注意,hscan的count参数是否有效,取决于此时hash的实现方式,如果field数量比较小,则count参数不生效 https://redis.io/docs/latest/commands/scan/
- 对于field比较多的大key,使用
- 哈希冲突严重触发渐进式
- Set:实现方式:哈希表、整数集合
- 整数集合:元素为整数且数量不大
- ZSet:有序Set。实现方式:压缩列表(ziplist)、跳表(skiplist)、listpack
- 每个元素有一个
score属性,按score从小到大排序 - 当键值对少于128个且元素长度小于64时使用
ziplist ZADD命令:ZADD key score memberZINCRBY命令:ZINCRBY key increment member- 应用:
- 延时队列(score为时间戳):
zadd q 100 a 200 b 300 czrange q 100 250 byscore=>a b
- 语义前缀匹配(bylex):
zadd s 0 aaa 0 aab 0 abc- 匹配aa开头的:
zrange s "[aa" "[aa\xff" bylex=>aaa aab,redis对中文支持不太好,建议转成Unicode后处理
- 延时队列(score为时间戳):
- 每个元素有一个
- bitmap:位图。实现方式:字符串
- 使用:布隆过滤器。需配合代码里的k个hash函数。具体来说,对于长度为m的bitmap b,每个hash函数计算后的值对m取模得到的一个位置坐标,将bitmap对应位置标记为1。布隆过滤器可以判断可能存在和一定不存在,不支持元素删除。
- Geo:地理位置。实现方式:有序列表(ZSet)
- zset里每个member是地点的名称或id,score是经纬度经过GeoHash编码后的值
- 增加:
GEOADD cities 116.405285 39.904989 "Beijing" - 查找附近:
GEORADIUS cities 116.405285 39.904989 100 km - 计算距离:
GEODIST cities "Beijing" "Shanghai" km
Redis过期策略
- 定时删除
- 默认每秒执行10次
- 贪心+随机删除
- 惰性删除
- 请求key时,发现过期,则删除,返回
null
- 请求key时,发现过期,则删除,返回
Redis内存淘汰策略
- LRU:最少最近使用,删除访问时间最早的元素
- LFU:最少使用频率,删除访问频率最低的元素
noeviction:只返回错误,不会删除任何key。该策略是Redis的默认淘汰策略,一般不会选用volatile-ttl:将设置了过期时间的key中即将过期(剩余存活时间最短)的key删除掉volatile-random:在设置了过期时间的key中,随机删除某个keyallkeys-random:从所有key中随机删除某个keyvolatile-lru:基于LRU算法,从设置了过期时间的key中,删除掉最近最少使用的keyallkeys-lru:基于LRU算法,从所有key中,删除掉最近最少使用的key。该策略是最常使用的策略volatile-lfu:基于LFU算法,从设置了过期时间的key中,删除掉最不经常使用(使用次数最少)的keyallkeys-lfu:基于LFU算法,从所有key中,删除掉最不经常使用(使用次数最少)的key
Redis持久化
- RDB:周期性保存快照。宕机时可以会丢数据
- 适合冷备,体积小,恢复速度快
- fork时如果数据量过大,则可能导致服务暂停或OOM
- 耗时与实例大小有关,控制redis内存在10G以内
- 虚拟机上 fork 的耗时比物理机更久
- 二进制文件,不可读
- AOF:将命令写入os cache,定时刷盘。数据更完整
- rewirte:将大文件构造为小文件
- 不影响服务
- 同步磁盘策略:
everysec、always、no(由操作系统执行)
- rewirte:将大文件构造为小文件
Redis高可用方案
1. 主从架构
- 仅数据同步
- 没有故障迁移功能,需手动迁移
2. 哨兵模式(sentinel,基于主从架构)
client连接哨兵,由哨兵返回master节点
当master不可用,sentinel返回给客户端slave地址
sentinel也可以是集群部署,当需要故障迁移时,需要先选出leader节点再由leader节点进行操作
主观下线:redis节点不能及时正常响应sentinel的
PING命令客观下线:redis master节点被足够多的sentinel标记为主观下线
3. Redis Cluster
由多个redis节点组成,数据通过分片的形式保存在各个redis节点上
- 当其中一个分片不可用时,整个集群将不可用
- 每个分片可以是主从架构,主节点故障时从节点可以接管,提高可用性。
- 至少部署6个节点(3主3从)
哈希槽
Redis集群有16384个哈希槽,进行set操作时,每个key会通过CRC16校验后再对16384取模来决定放置在哪个槽,搭建Redis集群时会先给集群中每个master节点分配一部分哈希槽
- 每个节点都需要知道其他所有节点的状态信息
重定向MOVED
由于每个节点都知道所有节点信息,所以client可以向任意redis master节点发送命令
如果key不在本机,则返回MOVED错误,格式为MOVED [哈希槽] [IP:port](key所属的哈希槽和能处理这个请求的Redis节点的IP和端口号),客户端根据此重新发送命令
Redis命令
- expire [key] [second]:当timeout为非正数时,key将被删除且发出key被删除事件
Note that calling EXPIRE/PEXPIRE with a non-positive timeout or EXPIREAT/PEXPIREAT with a time in the past will result in the key being deleted rather than expired (accordingly, the emitted key event will be del, not expired). https://redis.io/commands/expire/
Redis常见问题
热点Key问题
- 本地缓存,使用Caffine等库。适用于不要求强一致性的场景
- 拆分key。需要提前知道哪些时热点key,并且客户端需要特殊处理
- 主从架构,从slave节点读取。同步延迟
大key删除
- 4.0.0以后,使用
unlink代替del - 6.0.0以后,开启
lazyfree-lazy-user-del=yes,这样del命令也会放到后台线程执行 - 设置过期时间,让redis在后台进行删除
- 将大 Key 分成多个小批次删除。也可以将下面的执行写成lua脚本执行
- 对于Hash,使用hscan和hdel删除
- 对于Set,使用sscan和srem删除
- 对于List,使用lpop或rpop删除
- 对于ZSet,使用zscan和zrem删除
Redis 7的ACL权限控制
- 开启ACL: 编辑
redis.conf文件,打开aclfile /etc/redis/users.acl,该文件需存在,否则启动报错 - 查看所有用户:
acl list,默认有default用户 - 新增/修改用户:
acl setuser [username] [on/off] >[password] [~*] [+@all]~*表示有权限key的正则表达式+@all表示操作权限
- 保存修改:
acl save - 使用: 连接后,默认是
default用户,需先调用auth [username] [password]命令进行认证。