首页 > 技术文章 > 《redis设计与实现》

chenwenjun 2020-09-18 14:52 原文

提高系统架构的性能,增加缓存层是常见的优化方式,redis和memcache是当前采用较多的缓存组件,redis被称为数据结构数据库
redis和memcache的区别:
redis和memcahce都是基于内存的key-value存储
memcache的事件模型是多线程reactor模型, 性能更好,但是只支持一种key-value类型,复杂的数据结构需要用户自己做序列化
redis是单线程,但是支持更多的数据结构,并且支持持久化和主从复制
 
redis常作为互联网高并发请时的中间件,但是会有以下常见的三种问题:
1.缓存穿透:查询的数据不存在,缓存和数据库中都没有,大量的这种请求就会对数据库造成压力
解决方案:
  a.使用布隆过滤器,即位图bitmap,尽可能将所有可能的不存在数据都保存下来,过滤掉不存在的请求
  b.即使数据不存在,也要添加到redis缓存里,将value设成空,避免频繁访问数据库
2.缓存击穿:查询的某一条热点数据在缓存中已过期,此时有大量针对此条数据的访问,由于缓存查询不到会造成都到数据库中查询,导致数据库压力飙升
解决方案:
  a.利用redis自带的setnx实现分布式锁,当key已过期时,加上分布式锁,获取到锁就到数据库中查询并更新缓存,获取不到锁说明有其他线程已经在查询数据库,睡眠一会尝试再次从redis缓存中查询
3.缓存雪崩:当redis重启或者大量key同时全部过期,此时若有大量请求,称为缓存雪崩,会对数据库造成更大的压力
解决方案:
  a.和缓存击穿一样,使用分布式锁或者队列减轻数据库压力,但是会造成请求堵塞
  b.针对大量key全部过期这种,可以在设置过期时间设置随机值,防止同时过期
 
以下是redis的部分实现:
 
1.独有字符串结构封装,大概类似std::string的实现,自动扩展,动态伸缩
2.redis字典使用哈希表,两张哈希表支持rehash重新散列,采用渐进rehash,大量数据不能一次性重新散列完毕
3.redis使用的数据结构有动态字符串、双端链表、哈希表、跳跃表、整数集合、压缩列表
4.redis为键值存储,键都是字符串,值有五大对象,字符串对象(SET)、列表对象(RPUSH)、哈希对象(HSET)、集合对象(SADD)、有序集合对象(ZADD)
5.每种对象都有2种编码方式,有序集合列表中可以使用整数集合或者ZSET结构,ZSET结构是由跳跃表加字典方式实现,跳跃表保证集合有序,插入logn,字典保证访问O1
6.过期删除支持毫秒、秒、具体时间,过期键删除方式是惰性删除加定期删除
7.rdb支持手动备份,save阻塞,bgsave子进程创建不阻塞,rdb保存数据结构,aof保存命令可能重复,所以支持aof重写采用子进程加重写缓冲区去重
  两种持久化方式的区别:
  rdb方式为定时快照方式,一次性保存内存中所有数据,采用子进程不阻塞主进程,无数据重复,体积小,重新加载恢复也快,缺点是定时保存过程中出现宕机,相比aof会丢失更多数据
  aof方式每条命令都会立刻刷新到aof文件里,数据保存更完整,缺点是宕机后重新加载文件相比rdb较慢,另外aof开启了强制同步磁盘的话redis性能相较rdb稍微有所降低
8.redis采用事件驱动,分为文件事件和时间事件,其中文件事件就是reactor模型,为单线程reactor模型(I/O多路复用+非阻塞I/O)
为什么redis是单线程?
因为redis是纯内存操作,并且内部数据结构设计的比较好,且内部耗时操作都采用子进程操作,所以cpu不是redis的瓶颈,既然cpu不是redis瓶颈,单线程又能减少锁竞争消耗,所以使用单线程
8.redis支持主从复制、哨兵集群、集群节点部署
主从复制:提供数据备份功能,从节点复制主节点数据,但是只有主节点提供服务,主节点宕机,从节点立刻转主,选主算法是raft算法,最先获得其他半数以上节点支持的成为主节点
哨兵:redis高可用方案,负责主从复制故障转移
集群节点:每个节点都提供服务,每个节点协商分别处理不同的槽,也可以和主从复制结合使用,因为redis是单线程,当数据量大的时候,可以采用redis集群多进程的方式加大吞吐量
9.redis也支持发布与订阅,可以用作消息中间件,但是效率应该没有kafka好
10.redis支持事务,具有原子性、一致性、持久性,但是redis不支持事务回滚,如果事务处理过程中某一条指令报错,后续指令会继续执行
11.redis还支持lua语言、排序、二进制数组、慢查询、监视器

推荐阅读