Redis
数据类型
- string:
- list:有序列表,比如微博大V粉丝,lrange可以实现分页
- hash:比如存对象
- set:无序,自动去重,微博大V共同粉丝
- sorted set(zset):有序,自动去重,可排序,排行榜
问题
redis和memcached区别
redis更丰富的数据结构,更强大的数据操作;redis官方是支持cluster模式,memcached官方不支持分布式。
为什么redis是单线程但是还可以支持高并发?
- 纯内存操作
- 核心是基于非阻塞的IO多路复用机制
- 单线程反而避免了多线程频繁的上下文切换的问题
redis缓存后果
-
缓存与数据库双写不一致
对于并发不多的系统,初级的不一致现象可能是这样的:修改一个数据,先修改数据库然后去删除缓存,但是删除缓存的时候失败了,这个时候缓存的数据是旧数据,就有问题。
解决办法:先删除缓存数据,然后修改数据库数据,下次读取数据的时候缓存拿不到就去数据库取,取出来后顺便放入缓存中。
-
缓存雪崩:当缓存机器宕机后大量请求落在了数据库上,导致数据库压力过大而崩溃
事发前:采用redis高可用架构(主从架构+sentinel)避免redis挂掉
事发时:本地缓存+限流,避免数据库挂掉
事发后:重启redis,让redis通过持久哈恢复旧数据
-
缓存穿透:假如某表id为正数,黑客针对系统故意发送id为负数的请求,先去缓存中取数据,取不到然后去数据库拿数据,大量请求就会直接让数据库挂掉
解决办法:第一次查询后如果没有数据,就在redis缓存中用key设置空的结果并设置较短的过期时间
-
并发竞争
分布式锁的机制
redis过期策略
由于redis是单线程,如果占用太多时间处理过期key救护导致线上读写的卡顿。
定时删除是集中处理,惰性删除是零散处理。
-
定时扫描策略
redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,默认每秒进行十次的过期扫描,每次扫描随机抽取20个key,然后删除过期的key,如果超过1/4,就循环继续删除,为了避免循环过渡默认不会超过25ms。所以避免有大批过期时间集中的key,可以设置随机数来分散过期处理的压力
-
惰性策略
客户端访问这个 key 的时候,redis 对 key 的过期时间进行检查,如果过期了就立即删除。
内存淘汰机制
当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换 (swap)。交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率基本上等于不可用。
当实际内存超出最大内存时,redis提供了几种可选策略
- noeviction:停止写的请求,读请求可以继续进行。默认策略
- volatile-lru:尝试淘汰设置了过期时间的key,优先淘汰最少使用的key
- volatile-ttl:尝试淘汰设置了过期时间的key,优先淘汰剩余寿命最少的key
- volatile-random:随机淘汰设置了过期时间的key
- allkeys-lru:尝试淘汰所有的key,优先淘汰最少使用的key
- allkeys-random:随机淘汰所有的key
redis持久化
-
RDB快照
快照是一次全量备份,它是内存数据的二进制序列化形式,在存储上非常紧凑。
-
AOF日志
AOF 日志是连续的增量备份,记录的是内存数据修改的指令文本,在长期的运行中会变得很庞大,所以要定期进行AOF重写来进行瘦身,重写是先将新的AOF文件重新整理序列化,然后将序列化期间发生的增量AOF追加到新的文件中
-
redis4.0混合持久化
将RDB和AOF文件放在一起,这里的AOF是自最后一次持久化开始到持久化结束的这段时间发生的增量AOF日志
redis主从同步
-
增量同步
主节点会将对自己的状态产生修改的指令记录到内存buffer中,然后异步将指令同步到从节点,从节点一边执行节点流来保证自己和主节点一致,一边向主节点反馈自己同步到了哪里。
但是buffer长度有限,如果满了会覆盖前面的内容;如果网络状况不好同步出现问题。
-
快照同步
主节点先进行一次bgsave将当前内存数据全部快照到磁盘文件中,然后将文件传送到从节点,从节点接受并执行全量加载,加载前会将数据全部清空。加载完后通知主节点继续进行增量同步。
-
注意事项:
-
使用主从架构必须给主节点开启持久化,否则主节点丢失数据重启后,会将空数据同步到所有从节点
-
当新增一个从节点后,必须先进行一次快照同步,完成后再进行增量同步
-
Redis分布式锁
-
加锁占茅坑
可以加锁,当别的进程来调用时发现有锁只能放弃
占坑一般使用setnx(set if not exists)指令,先来先占,用完使用del释放锁
> setnx lock:codehole true ok ... do something critical ... > del lock:codehole (integer) 1
-
但是上面的指令在加锁和释放锁中间出现异常时会导致del指令没有执行,使锁永远得不到释放。此时有个办法就是可以加个过期时间,保证即使遇到异常也能释放锁
> setnx lock:codehole true OK > expire lock:codehole 5 ... do something critical ... > del lock:codehole (integer) 1
-
但是上面还有个问题是。加锁和设置过期时间不是原子性操作,如果加锁和设置过期时间中间遇到异常,锁依然得不到释放。最终redis2.8版本加入了set指令的扩展参数,使得setnx和expire一起执行
> set lock:codehole true ex 5 nx OK ... do something critical ... > del lock:codehole
redis高可用架构
redis主从架构中可以增加sentinal节点,sentinal来监控主从节点的健康状况,客户端每次请求会先去链接sentinal,sentinal会告诉client主节点进行数据交互。当主节点挂掉后,client会向sentinal重新查询主节点地址,这时候sentinal会投票表决最优的一个从节点来作为主节点,然后将地址返回给client,旧的master恢复后会变为新的从节点。
sentinel node负责集群的监控、消息通知、故障转移、配置中心,sentinel本身也是分布式的,至少需要三个实例。
redis部署情况
redis集群:6台机器,3台机器部署了redis主实例,每个主实例挂一个从实例,3个节点对外提供读写服务,每个节点QPS最多5w,每个主实例宕机后会自动故障转移,从实例会转变成主实例继续提供读写服务。机器配置:32G内存8核cpu1T磁盘,reids占用10G。