Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,和Memcached类似;它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型);同时,还支持地理位置GEO和Bitmap扩展类型。
Redis之所以这么受欢迎,得益于它的性能,说到性能又不得不提它的单线程设计。那为什么redis采用单线程还这么高效呢?主要由以下原因:
- 减少上下文切换时间
- 纯内存访问
- 非阻塞IO,使用epoll
数据类型
字符串
1 | set key xx ex[seconds] | px [milliseconds] [nx |xx] # 如何实现分布式共享锁 |
哈希
1 | hset key field value |
列表
特征:
- 列表中元素有序
- 列表中元素可重复
1 | lpush key value [value …] |
集合
1 | sadd key element [element …] |
有序集合
特征:
- 保留不可重复特征
- 给每个元素设置一个score作为排序的依据
1 | zadd key score member [score member ...] |
其他
包括用于计算地理位置的GEO 和 BITMAP 两种扩展类型。
键管理
1 | dbsize |
使用场景
- 分布式锁(setnx ex)
- 消息队列(list)
- 延时队列(zset)
- 计数(bitmap)
- 统计UV(HyperLogLog pfadd/pfcount)
- 大规模数据集判断是否存在(布隆过滤器 bf.add/bf.exists)
- 限流(cl.throttle)
- 计算距离(GeoHash)
管道
本质客户端行为,是将多个操作放到一个命令里面发送到服务器端处理。
事务
multi -> exec
是客户端将多个命令发送到服务器端缓存,然后执行exec的时候再统一执行。
它不具有原子性,无法保障里面的多个命令都成功或者都失败。一般管道和事务和结合使用,在multi之后,将多个操作命令压如管道中,一次发送到服务器端执行。
watch
watch 会在事务开始之前盯住一个或多个关键变 量,当事务执行时,也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后 是否被修改了(包括当前事务所在的客户端)。如果关键变量被人动过了, exec 指令就会返回 NULL 回复告知客户端事务执行失败,这个时候客户端一般会选择重试。
Redis 禁止在 multi 和 exec 之间执行 watch 指令,而必须在 multi 之前盯住关键 变量,否则会出错。
日常运维
慢查询
1 | config set slowlog-log-slower-than 20000 # 慢查询标准为慢于20000微秒 |
事务
1 | multi |
run id
redis的run id在重启之后会发生变化,这样slave就能基于该id来判断需要重新全量同步master的数据,因为重启过程可能发生数据恢复等操作。
如果希望不改变run id的情况下重新加载配置,需要执行 redis-cli debug reload
数据持久化
- RDB快照手动触发时,save和bgsave的差异?
- AOF以独立日志方式记录每次写操作,重启时重放以恢复数据。
触发方法
- 手动bgrewriteaof
- 自动根据auto-aof-rewrite-min-size 和 auto-aof-rewrite-percentage确定自动触发机制
持久化流程
命令写入 -> AOF缓冲区 -> AOF文件 -> rewrite -> 重启(load)
执行重写后,为什么AOF文件会变小?
- 干掉一些中间状态的数据,比如set之后又del的数据;
- expire已经timeout的数据,不再写入;
- 多条命令可以合并为一条;
三种AOF缓冲区同步文件策略
- always
- everysec
- none
复制相关
1 | slave of host port |
默认情况下, 从节点使用 slave-read-only=yes
配置为只读模式。
哨兵模式
redis sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。
如果被标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数 Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。
哨兵的流程:
- 监控并发送报文给各自的节点;
- 发现主观下线;
- 询问,并投票,客观下线;
- 领导者节点选举->raft(基本为谁最早发现某个redis节点主观下线,就会发起投票,然后的票就会最多,然后成为切换该主redis的领导者);
- 故障转移;选出新的老大(在从节点中,过滤不健康的,然后基于优先级->复制偏移量->runid
集群方案
集群方案在这里有详细介绍。
redis cluser采用虚拟槽分区,所有的键根据哈希函数映射到0~16383整数槽内,计算公式: slot = CRC16(key)& 16383
集群限制
- 不支持多个db
- Key的批量操作有限,比如mset
节点握手
1 | cluster meet {ip} {port} |
分配槽
1 | cluster addslots {0…5555} |
从节点设置
1 | cluster replicate {master node id} |
缓存穿透解决办法
- 缓存空对象
- 布隆过滤器
值得深入思考的问题
- redis如何实现消息队列?
- 如何使用redis实现每分钟获取验证码的次数不超过5次?
- redis的订阅与发布适合用来做消息队列吗?为什么?
- 如果一个key设置了expire时间,get操作之后,expire会发生变化吗?
- 关系型数据库存储与hash散列存储的差异性在哪儿?
- 将数据直接序列化后使用string存到redis和将数据按照hash存到redis两者的优缺点?
- 如何通过redis快速计算出具有共同兴趣爱好的一类人?
- 如何通过redis计算被点赞数的用户排名?
- 当系统中同时存在AOF和RDB文件时,系统重启默认有限加载哪一个?
- 哨兵模式下,客户端连接的是redis节点,还是哨兵节点?
- 缓存雪崩后,重建缓存可能会被很多程序调用到,这个时候如何采用较好的方法避免出现大家都去重建缓存呢?