Redis基础
2023年2月2日
Redis特点
- 快。基于内存,IO多路复用机制。读11W/s,写8W/s,读写操作都在内存中完成(异步写入磁盘)
- 支持事务。一个事务是一个原子操作。但是事务内的错误不能回滚,只能是语法问题,开发时可发现。
- 可持久化。可以持久化到磁盘,且掉电后可根据AOF或RDB恢复
- 丰富的数据结构
为什么快?
- 基于内存。
- IO多路复用。非阻塞IO,多个IO操作可以在一个线程内并发处理。
单线程与多线程
- Redis的瓶颈不在CPU,而是内存和网络。 所以一直是单线程处理,能避免并发加锁和线程切换的损耗等复杂问题。
- Redis4.0 主线程处理命令读写、解析、执行。 也有其他线程 清理脏数据、释放无用连接 等。
- Redis6.0 增加了多线程功能,但默认不开启。(开启io-threads-do-reads yes,设置数量io-threads小于CPU数)
- 多线程是优化处理网络读写解析等,在执行上仍然是单线程,所以不会产生线程安全问题。
数据类型
- string 字符串
- 数据结构:SDS(简单动态字符串)实现,redis用C实现,C中字符串是char数组,但是长度需要遍历获取。redis在基础上封装了长度len,申请的内存长度alloc,sds的类型flags(分别用于存储不同长度字符串),字符串的值buf[]。内存分配,小于1M时每次扩容一倍,大于1M时每次扩容1M,最大512M。
- 应用:缓存,分布式锁,计数器
- hash 哈希
- 数据结构:ziplist(小于512个键值对且键和值长度都小于64字节) 或 hashtable
- 应用:缓存对象,计数器
- list 列表
- 数据结构:双向链表实现,具体点是quicklist快速链表(ziplist+链表),分成多个段,段之间指针连接,每一段是一个压缩链表,元素连续内存
- 应用:队列
- set 集合
- 数据结构:intset(元素都是整数且数量小于512) 或 hashtable
- 应用:唯一性相关
- zset 有序集合
- 数据结构:ziplist(小于128个元素且每个元素小于64字节) 或 字典+跳跃表。 字典用来member找score
- 应用:排行相关
- hyperloglog 基数统计
- 应用:统计数量(只占用12KB,允许0.81%的误差)
- bitmap 位图 (字符串类型的位操作)
- 应用:标记
- geo 地理位置
- 应用:经纬度坐标相关
- stream
- 应用:消息队列
- 概念:
- stream。存储消息的集合
- group。 分组,
持久化机制
默认RDB,对数据实时性要求比较高时建议结合两种。即开启AOF,同时也自动会定期RDB
RDB: 生成数据快照二进制。体积小,恢复速度快。适合数据冷备和复制传输。频繁执行成本高。
- 手动触发:
- 阻塞式。备份过程中阻塞正常读写。主从时master不要这种方式,警惕。(save命令)
- 非阻塞。fork一个子进程备份 (bgsave命令)
- 自动触发
- 全量复制节点数据时bgsave
- shutdown未使用AOF模式时bgsave
- 执行debug reload时会执行save
- 配置
save m n 每m秒发生n个改动,则保存一次。 应该多设置几个,m和n成反比。
- 手动触发:
AOF: 按照特定格式记录更改命令行。 体积大,恢复速度慢。有AOF缓冲区和.aof文件。
- 配置
appendonly yes 开启
appendfsync (everysec默认的,每秒1次|always每次更改|no依赖OS的写入,一般30s一次)
- 配置
过期键的删除策略
- 定时。对每一个设置过期时间的键添加一个定时器,准时删除,消耗资源,不考虑。
- 定期。周期性检测。(默认开启,配置文件:hz 10,即每秒执行10次)
- 惰性。获取时判断是否过期。
默认的策略是定期+惰性。
内存满了时key的淘汰策略
- 不淘汰,返回错误 noeviction
- 有过期时间的,最近最少使用 volatile-lru
- 所有的,最近最少使用 allkeys-lru
- 有过期时间的,最少使用 volatile-lfu
- 所有的,最少使用 allkeys-lfu
- 有过期时间的,随机淘汰 volatile-random
- 所有的,随机淘汰 allkeys-random
- 有过期时间的,先到期的 volatile-ttl
默认不淘汰。 配置 maxmemory 最大内存 和 maxmemory-policy
缓存问题
缓存雪崩:大批键同时过期,数据库瞬间承受压力。
- 永不过期。(废话)
- 使过期时间不一。加上随机数
缓存穿透:访问不存在的数据,缓存没有,打到数据库
- 判断合法性。(防止恶意请求参数)
- 缓存空值。 防止第二次仍然穿透,过期时间设置小一点。
- 布隆过滤器
缓存击穿:高并发访问,单键到期时,大量访问打到数据库。(区分于雪崩)
- 缓存永不过期。
- 互斥锁。第一个请求查询数据库前设置锁,其他请求等待,第一个请求生成缓存后释放锁,其他请求查询缓存。
并发缓存更新
- 更新数据库-删除缓存。 间隙存在短暂的数据不一致,但这种最简单也可接受。
- 延时双删。 删除缓存-更新数据库-延迟再删除缓存。 最终一致性是没问题的,但也存在短暂不一致问题。
分布式
- CAP原理
C一致性,A可用性,P分区容错性。分布式系统无法同时满足这3个条件,架构应该在三者间取其二。 - hash槽
Redis 集群有 16384 个哈希槽,每个 key 通过CRC16 校验后对 16384 取模来决定放置哪个槽, 集群的每个节点负责一部分 hash 槽。 - 分布式锁
利用setnx命令,lua脚本保证原子性。## 加锁。 参数 key id expire 。 id是唯一标识,expire是过期时间 if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) end ## 解锁。 参数 key id 。 if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) end