- 为什么用缓存?
- 高性能:减少查询同一个数据时的响应速度
- 高并发:减少数据库的承载压力(2000/s),缓存走内存,天然支撑高并发
- 缓存的不良后果:
- 缓存与数据库的双写不一致
- 缓存雪崩
- 缓存穿透
- 缓存并发竞争
- Redis和memcached区别(单线程、NIO、异步)
- Redis支持服务器端数据操作:数据类型更多,功能更全
- 内存使用效率对比:简单key-value时memcached效率高,hash结构存储Redis高
- 性能对比:Redis单线程,小数据时Redis高;大数据时多线程的memcached高
- 集群模式:memcached不支持集群,Redis支持
- Redis单线程模型:
- 文件事件处理器,单线程,通过IO多路复用程序同时监听多个socket,通过socket上的事件选择对应的事件处理器处理事件
- AE_READABLE:socket可读(连接操作、客户端对Redis执行write操作,close操作)
- AE_WRITABLE:socket可写(客户端对Redis执行read操作)

- Redis单线程模型为什么效率高?
- 纯内存操作(事件处理器在内存操作,性能高1ms)
- 核心基于非阻塞IO多路复用机制(不处理、直接压队列)
- 单线程避免了多线程的频繁上下文切换问题
- Redis有哪些数据类型?
- string
- hash
- 结构化数据,存放对象
- key=150
- value={
- “id”: 150,
- “name”: “zhangsan”,
- “age”: 20
- }
- list
- 列表性数据结构(微博粉丝)
- key=某大v
- value=[zhangsan, lisi, wangwu]
- set
- 无需集合,自动去重
- sorted set
- 去重&排序
- Redis的过期策略、内存淘汰机制
- 定期删除+惰性删除
- 每隔100ms抽取设置过期时间的key,检查是否过期后删除(定期)
- 获取某个key时,redis检查是否过期(惰性)
- 内存淘汰
1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了
2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊
4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)
5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key
6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除
- 手写LRU算法
- Redis高并发、高可用、缓存一致性:
- Redis高并发:主从架构,一主多从,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒10W的QPS。如果需要缓存容纳数据量大,用Redis集群。
- Redis高可用:如果你做主从架构部署,其实就是加上哨兵就可以了,就可以实现,任何一个实例宕机,自动会进行主备切换。
- 高并发
- 主从架构,实现读写分离(master写,slave读),水平扩容
- redis replication->主从架构->读写分离->水平扩容支撑读高并发
- master开启持久化
- 主从架构核心原理:
- master启动线程,生成RDB快照,发送给slave
- 断点续传,记录offset继续复制
- 无磁盘复制:在内存创建rdb,发送给slave
- Redis replication(复制)的完整流程
- 数据同步的核心机制:
- master和slave都会维护一个offset
- backlog:用来全量复制中断后的增量复制
- master run id:区分master(重启或者数据发生变化)
- psync:psync runid offset,从节点使用此命令从master node进行复制
- 全量复制
- master生成快照
- 发送给slave node
- master在生成快照时,将所有新的写命令缓存在内存中,slave保存rdb后,将新的写命令复制
- slave在接收到rdb后,清空旧数据,加载rdb
- 如slave开启aof,立即重写aof
- 增量复制
- 全量复制中断
- master用runid和offset从backlog中获取到丢失数据,发送给slave
- heartbeat
- master默认每隔10秒发送一次heartbeat,salve node每隔1秒发送一个heartbeat
- 异步复制
- master每次接收到写命令之后,现在内部写入数据,然后异步发送给slave node
- 高可用性
- 基于哨兵(sentinal)的高可用,故障转移(failover),主备切换
- 哨兵:
- 哨兵的作用:
- 集群监控,负责监控redis master和slave进程是否正常工作
- 消息通知,如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
- 故障转移,如果master node挂掉了,会自动转移到slave node上
- 配置中心,如果故障转移发生了,通知client客户端新的master地址
- 分布式,哨兵集群运行
- 主备切换数据丢失问题:
- 异步复制导致:
- master->slave复制是异步的,可能没复制完,master宕机
- 集群脑裂导致:
- master主节点,出现异常性的相同工作的两个节点
- client可以和旧master写数据,恢复后旧master成为slave后数据丢失
- 解决:
- min-slaves-to-write 1
- min-slaves-max-lag 10
- 要求至少有1个slave,数据复制和同步的延迟不能超过10秒
- 如果说一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了
- 哨兵的核心原理:
- sdown和odown转换:
- sdown主观宕机
- odown客观宕机
- quorum指定数量
- 哨兵的自动发现机制:
- __sentinel__:hello这个channel里发送一个消息
- slave配置的自动纠正
- slave->master选举算法
- 跟master断开连接的时长
- slave优先级
- 复制offset
- run id
- Redis重启数据恢复(数据持久化)
- 持久化的意义:数据备份、故障恢复
- 两种持久化:RDB、AOF
- RDB:对Redis中数据执行周期性的持久化
- AOF:对于每条写入命令作为日志,以append-only模式写入一个日志文件中(更加完整)
- Redis重启后,通过回放AOF日志中的指令重新构建数据集
- AOF大到一等程度后,rewrite操作,构建更小的AOF文件
- RDB优劣势:
- 优点:(冷备)
- 生成多个数据文件,适合冷备。AOF需要写脚本处理。
- RDB对Redis性能影响小,fork子进行进行RDB持久化
- RDB重启恢复速度快
- 缺点:
- 恢复效果差,数据丢失
- fork子进程时,数据文件特别大,会对客户端服务暂停数毫秒
- AOF优劣势:
- 优点:
- 保护数据不丢失
- append-only写入,没有磁盘寻址开销,写入性能高,文件不易损坏
- AOF日志文件过大rewrite对客户端读写性能影响小
- 日志文件可读
- 缺点:
- 日志文件大
- 支持的写QPS低
- 健壮性低
- 结合RDB和AOF同时使用
- Redis集群模式
- 分布式数据的核心算法,数据分布的算法:
- redis cluster——hash slot算法
- 每个master持有部分slot
- 节点间的内部通信机制
- 集群的元数据维护:集中式(ZooKeeper)、gossip
- gossip协议:元数据分散,降低压力,更新有延迟(meet ping pong fail)
- 10000端口:用于节点间通信,为服务接口+10000,ping->pong
- 交换信息的内容:故障信息、节点的增删、hash slot信息。。。
- jedis: Redis的java client客户端
- smart jedis:本地维护hashslot->node的映射表,缓存,不需要节点moved重定向
- hash tag手动指定对应slot,set key1:{100}
- 高可用与主备切换:
- pfail(主观宕机) fail(客观宕机)
- 与哨兵类似
- Redis缓存雪崩与缓存穿透
- 缓存雪崩:
- 事前:Redis高可用(主从+哨兵、Redis Cluster)
- 事中:本地ehcache缓存(系统内部小缓存)+hystrix限流(限流降级组件)
- 事后:Redis持久化,快速恢复缓存数据
- 缓存穿透:
- 没查到,写空值到缓存中(set -999 UNKNOWN)
- 使用布隆过滤器或者压缩filter提前拦截
- 缓存与数据库双写数据一致性(读写串行化):
- 缓存+数据库读写模式:cache aside pattern
- 读。先缓存,再数据库,读完放入缓存
- 更新,先删除缓存(缓存复杂计算得到、不会频繁访问),再更新数据库
- 简单场景:先删缓存,在修改数据库
- 复杂场景(删除缓存,修改数据库之前,读后缓存旧数据,数据库新修改)
- 数据库与缓存更新读取操作进行异步(线程)串行化
- 内存队列放请求,一个队列对应一个线程,串行进行操作
- 读请求长时阻塞
- 队列里写请求过多(部署多个服务,分摊数据更新)
- 加多机器,每个机器上部署的服务实例处理更少的数据
- 多服务实例部署的请求路由
- 同一个商品的读写请求,路由到同一台机器上
- nginx,hash路由功能
- 热点商品的路由问题(读写请求高)
- 商品更新清空缓存,导致读写并发,更新频率不高影响不大
- Redis并发竞争问题:
- 多客户端并发写一个key
- CAS类的乐观锁方案
- zookeeper提供分布式锁
- 每个value的时间戳要比缓存中的时间戳更新,才可以写
- 生产环境中的Redis怎么部署