Redis分享
Redis分享数据结构集群模式优点缺点基本原理主从切换主从数据同步数据丢失问题针对cluster模式的坑其他模式主从模式哨兵模式故障恢复代理模式持久化机制Redis使用场景缓存一致性缓存击穿缓存穿透缓存雪崩运维注意问题排查常见的运维故障规范冷热数据区分业务数据分离消息大小限制连接数限制缓存Key设置失效时间缓存不能有中间态扩展方式首选客户端hashKey规范操作限制严禁使用Keys严禁使用Flush严禁作为消息队列使用严禁不设置范围的批量操作禁用select函数禁用事务禁用lua脚本扩展禁止长时间monitorRedis为什么这么快
数据结构
一共有10种,常用的有5种
String字符串
Hash字典
List列表
Set集合
ZSet有序集合
Pubsub发布订阅(不推荐使用,坑很多)
Bitmap位图
GEO地理位置(有限使用,附近的人)
Stream流(5.0)(与Kafka非常像)
Hyperloglog基数统计,HyperLogLog提供不精确的去重计数方案
集群模式
redis提供了非常丰富的集群模式:主从、哨兵、cluster,满足服务高可用的需求。
其中cluster模式为目前大多数公司所采用的方式。rediscluster是redis亲生的集群方案,它的主要特点就是去中心化,无需proxy代理。其中一个主要设计目标就是达到线性可扩展性。
优点
不再需要额外的Sentinel集群,为使用者提供了一致的方案,减少了学习成本。
去中心架构,节点对等,集群可支持上千个节点。
副本功能能够实现自动故障转移,大部分情况下无需人工介入。
可水平线性扩展
缺点
从库是完全的冷备,无法分担读操作
数据是通过异步复制的,不能保证数据的强一致性。(其实不单是cluster模式的问题)
资源隔离困难,经常流量不均衡,尤其是多个业务共用集群的时候。可能存在热点数据问题。
数据迁移是基于key而不是基于slot的,过程较慢。
一些批量操作。由于key被hash到多台机器上,所以mget、hmset、sunion等操作就非常的不友好,经常发生性能问题。
基本原理
rediscluster采用虚拟槽的概念,把所有的key映射到0~一共个整数槽内,当需要在其中存取一个key时,redis客户端会首先对这个key采用crc16算法算出一个值,然后对这个值进行mod操作。
crc16(key)mod
rediscluster所有的集群操作都是围绕slot进行。所以必须存储slot的存储关系。
redis节点发送心跳包时,需要把所有的槽信息放在这个心跳包里,所以必须优化使数据量最小。
使用使用bitmap来存储是最节省空间的。这个数组的长度为/8=Byte,2K字节。
请求随机落到一个主节点,该节点检查key是否在自己负责的slot上,是就处理。不是就根据信息将请求转发到对应的节点上。所以,客户端连接集群中的任意一台机器,都能够完成操作。
当数据库中的个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。(可以配置,使集群强制可读)
主从切换
redis-cluster可以自动完成一定程度的故障转移。
集群中的每个节点都会定期地向集群中的其他节点发送ping消息,以此来检测对方是否在线,如果接收ping消息的节点没有在规定的时间内返回pong消息,那么发送ping消息的节点就会将接收ping消息的节点标记为疑似下线(PFAIL)。
如果在一个集群里面,半数以上节点都将某个主节点x报告为疑似下线,那么这个主节点x将被标记为已下线(FAIL),将x标记为FAIL的节点会向集群广播一条关于x的FAIL消息,所有收到这条FAIL消息的节点都会立即将x标记为FAIL。
大家可以注意到这个过程,与es和zk的节点判断类似,都是半数以上才进行判断,所以主节点的数量一般都是奇数。
当一个节点发现自己的主节点进入fail状态,将会从这个主节点的从节点当中,选出一台,执行slaveofnoone命令,变身为主节点。
新的节点完成自己的槽指派以后,会向集群广播一条pong消息,以便让其他节点立即知道自己的这些变化。它告诉别人:我已经是主节点了,我已经接管了有问题的节点,成为了新的主节点。
主从数据同步
当一台从机连接到master之后,会发送一个sync指令。master在收到这个指令后,会在后台启动存盘进程。执行完毕后,master将整个数据库文件传输到slave,这样就完成了第一次全量同步。
接下来,master会把自己收到的变更指令,依次传送给slave,从而达到数据的最终同步。从redis2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断掉了,那么可以接着上次复制的地方,继续复制下去,而不是从头开始复制一份。
数据丢失问题
rediscluster中节点之间使用异步复制,并没有类似kafka这种ack的概念。节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升,完成这个过程注定了需要时间。在发生故障的过程中就容易存在窗口,导致丢失写入的数据。比如以下两种情况。
一、命令已经到到master,此时数据并没有同步到slave,master会对客户端回复ok。如果这个时候主节点宕机,那么这条数据将会丢失。redis这样做会避免很多问题,但对一个对数据可靠性要求较高的系统,是不可忍受的。
二、由于路由表是在客户端存放的,存在一个时效问题。如果分区导致一个节点不可达,提升了某个从节点,但原来的主节点在这个时候又可以用了(并未完成failover)。这个时候一旦客户端的路由表并没有更新,那么它将会把数据写到错误的节点,造成数据丢失。
所以rediscluster在通常情况下运行的很好,在极端情况下某些值丢失问题,目前无解。
针对cluster模式的坑
1、rediscluster号称能够支持1k个节点,但你最好不要这么做。当节点数量增加到10,就能够感受到集群的一些抖动。
2、一定要避免产生热点,如果流量全部打到了某个节点,后果一般很严重。
3、大key不要放redis,它会产生大量的慢查询,影响正常的查询。
4、如果你不是作为存储,缓存一定要设置过期时间。占着茅坑不拉屎的感觉是非常讨厌的。
5、大流量,不要开aof,开rdb即可。
6、rediscluster的操作,少用pipeline,少用multi-key,它们会产生大量不可预料的结果。
其他模式
主从模式
redis最早支持的,就是M-S模式,也就是一主多从。redis单机qps可达到10w+,但是在某些高访问量场景下,依然不太够用。一般通过读写分离来增加slave,减少主机的压力。
数据同步问题
先进行全量同步,再进行增量同步。主从同步是异步进行的。
要注意的一个点是:
run_id是master唯一标示,slave连接master时会传runid,master每次重启runid都发生变化,当slave发现master的runid变化时都会触发全量复制流程。
哨兵模式
哨兵模式是主从的升级版,因为主从的出现故障后,不会自动恢复,需要人为干预。所以在主从的基础上,实现哨兵模式就是为了监控主从的运行状况,对主从的健壮进行监控。并且当master出现故障的时候,会自动选举一个slave作为master顶上去。
故障恢复
当master被认为客观下线后,首先需要在哨兵中选出一个老大哨兵进行故障恢复。选举老大哨兵的算法还是Raft算法
选出大佬哨兵后,大佬哨兵就会对故障进行自动恢复,即从slave中选出一名slave作为master
所有的slave中slave-priority优先级最高的会被选中。
若是优先级相同,会选择偏移量最大的,因为偏移量记录着数据的复制的增量,越大表示数据越完整。
若是以上两者都相同,选择RunID最小的。
代理模式
代理模式在rediscluster出现之前,非常流行,比如codis,predixy。
持久化机制
redis提供了两种持久化方式:aof和rdb,常用的是rdb。RDB和AOF两种方式也可以同时使用,也可以都不用,也可以只用一个。
RDB
RedisDataBase。简而言之,就是在不同的时间点,将redis存储的数据生成快照并存储到磁盘上;
AOF
则是换了一个角度来实现持久化,那就是将redis执行过的所有写指令记录下来,在下次redis重新启动时,只要把这些写指令从前到后再重复执行一遍,就可以实现数据恢复了
对于RDB方式,redis会单独创建(fork)一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。
RDB有不少优点,但它的缺点也是不容忽视的。
即使你每5分钟都持久化一次,当redis故障时,仍然会有近5分钟的数据丢失
默认的AOF持久化策略是每秒钟fsync一次(fsync是指把缓存中的写指令记录到磁盘中),
因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。
也可以配置每次执行命令都fsync一次。
因为采用了追加方式,如果不做任何处理的话,AOF文件会变得越来越大,为此,redis提供了AOF文件重写(rewrite)机制,即当AOF文件的大小超过所设定的阈值时,redis就会启动AOF文件的内容压缩
rdb的优点是文件小,恢复快。但是存在数据丢失较多的风险。
aof的有点是数据丢失风险较小,缺点是文件较大。
Redis使用场景
缓存(缓存一致性缓存穿透缓存击穿缓存雪崩)
分布式锁(redlock)
分布式限流
Session共享
缓存一致性
为什么有一致性问题?
写入。缓存和数据库是两个不同的组件,只要涉及到双写,就存在只有一个写成功的可能性,造成数据不一致。
更新。更新的情况类似,需要更新两个不同的组件。
读取。读取要保证从缓存中读到的信息是最新的,是和数据库中的是一致的。
删除。当删除数据库记录的时候,如何把缓存中的数据也删掉?
建议使用:CacheAsidePattern
读请求:
先读cache,再读db
变更操作:
不要更新缓存,直接淘汰缓存
先操作数据库,再淘汰缓存
涉及到复杂的事务和回滚操作,可以把淘汰放在finally里。
缓存击穿
影响,轻微。
高流量下大量请求读取一个失效的Key-RedisMiss-穿透到DB
解决方式:采用分布式锁,只有拿到锁的第一个线程去请求数据库,然后插入缓存
缓存穿透
影响,一般。
故意访问一个不存在的Key(恶意攻击)-RedisMiss-穿透到DB
解决方式:
给相应的Key设置一个Null值,放在缓存中
BloomFilter预先判断。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
缓存雪崩
影响:严重。
大量Key同时失效|Redis当机-RedisMiss-压力打到DB
解决方式:
给失效时间加上相对的随机数
保证Redis的高可用
运维注意
问题排查
monitor指令回显所有执行的指令。可以使用grep配合过滤
keyspace-events订阅某些Key的事件。比如,删除某条数据的事件,底层实现基于pubsub
slowlog顾名思义,慢查询,非常有用
--bigkeys启动参数Redis大Key健康检查。使用的是scan的方式执行,不用担心阻塞
memoryusagekey、memorystats指令
info指令,