Redis(Remote Dictionary Server ),即远程字典服务
redis的起源是因为大佬觉得MySql慢,所以就自己弄了一个新数据库,所以有些MySql相似的地方也很正常。
一个有趣的发现,mysql缓存优化上说,最佳的情况就是数据全在缓存中;这样看redis就很符合这点。
既然是缓存数据库,那设计上和那些要写入磁盘的数据库就有很大的区别。
没有使用BTree
跳表:变种二叉树,空间换时间的典型,直接多了一组(或几组)数据。
字典hash(招牌):和hashMap很像,hashMap套hashMap
K-V结构,使用数组和链表保存数据(不会变二叉树);虽然查询快,但是也导致扩容的时候会卡,需要重新hash分配数据;所以注定它的数据量不能很多。
redis中是hashmap套hashmap,这样可以减弱一个过大的hashmap扩容导致的顿卡。
压缩表(ziplist):数组格式
特殊编码的连续内存块组成的顺序型数据结构;
压缩了什么?
数组需要预先申请空间,空间大小是类型的最大值;而压缩表是先知道要存什么,所以空间大小就是数据大小。那它没有修改功能吗?
sorted-set和hash元素个数少且是小整数或短字符串(直接使用);小规模的数据,可能修改的时候会重新生成新数组。
快速列表(quicklist):链表套套数组,和hashMap正好相反
quicklist是一个双向链表,链表中的每个节点时一个ziplist结构。
内容大小不同,储存类型不同会
根据储存内容的大小,自己切换储存类型。
比如:
当元素的个数比较多或元素不是小整数或短字符串时。使用字典
当元素的个数比较少,且元素都是小整数或短字符串时。使用压缩列表
单线程,但是现在有多线程特性
单线程为什么会快呢?第一个想到的是内存数据库不用读磁盘,省去了读盘阻塞时间;但是分布式环境更多的是网络等待。
RedisCluster官方集群解决方案,集群使用传染病协议
Redis3.0之后,Redis官方提供了完整的集群解决方案。
方案采用去中心化的方式,包括:sharding(分区)、replication(复制)、failover(故障转移)。称为RedisCluster。
Gossip(流行病)协议:特点是去中心化。感觉是一个弱一致性,又耗资源的协议。
那么RedisCluster是收到新数据后,马上向其他服务器传播,还是定时传播?
Gossip协议基本思想就是:一个节点周期性(每秒)随机选择一些节点,并把信息传递给这些节点。收到信息的节点会把信息继续传下去。
所以是定时传播。
传播哪些信息呢,是增量还是全量?
全量的话明显太慢了,我猜是增量。
节点A收到publish命令,节点A执行该命令,并向集群广播publish命令,收到publish命令的节点都会执行相同的publish命令。
这里看出是增量,只有新增节点会需要全量更新。
传播什么时候停止?
每个节点都会随机选一些节点传播,什么时候知道传播完成了?
Gossip协议并没有说最完美的解决方案;会停止会导致有些节点没更新(因为是随机选),不停止会消耗很多资源。
可以把以传播的节点剔除随机节点池,同时更新信息版本号,版本相同就不用执行更新但可以传播。解决的办法还是有很多的。
节点接收的数据怎么分配?
这里用到了一致性hash;一致性hash环的好处就是增减节点的时候不需要所有的节点都rehash,只需要附件的节点rehash就可以了。
redis-cluster把所有的物理节点映射到[0-16383]个slot上,基本上采用平均分配和连续分配的方式。
每个节点负责一部分slot,比如Redis1 负责接收 0-4000的信息;
哨兵其实就是租期模式
redis不会自己替换坏掉的master节点,需要借助哨兵(sentinel),哨兵会定期用心跳包检查master状态,如果master坏了,就换一个顶上。
没有操作日志
和MySql完全不一样。
不会每一个操作都保持日志,类似的东西是RBD(Redis DataBase),用来数据持久化的东西;定期保存数据库的快照,就是很不安全的样子。
持久化只是恢复备份。
事务和LUA脚本语言操作
redis的事物是弱事务,不能回滚(说白了就是命令集);不推荐用redis来处理事务。
推荐使用脚本来操作redis,脚本操作就等同于Redis的事务了。
Redisson分布式锁
分布式锁说白了就是在Redis中插入一条数据,标明是哪个客户端插入的,这个客户端就拥有了锁;其他客户端要做这个事之前都要去redis看看能不能拿到锁。
一般只有拿锁的客户端可以归还,但是考虑到分布式环境,客户端可能出了意外,所以锁都有超时时间。
本着来都来了的原则,可重入锁出现了,既然这个客户端已经拿到锁了而且还没还,那他还要继续再办一件事也是可以的,锁的计数+1,还锁就变成了锁的计数-1。
但是啊但是,redis集群并不是强一致性啊,我找的redis和你找的redis不是同一个,并且他们还没有同步到数据,以为锁空闲,结果我和你都拿到了锁。根据上面的流行病协议弱一致性AP,在redis集群下分布式锁就不那么好用了。所以还是用zookeeper吧。
淘汰策略
内存总是有限的,这时候需要删除一些不常用的数据,这里的策略和mysql挺像的;
LRU (Least recently used) 最近最少使用:使用链表,新增,更新和查看都会把数据移动到链表头部;超过容量就删除队尾数据。
LFU (Least frequently used) 最不经常使用:这个和次数有关(上面的是跟时间有关),根据使用次数排序,最少的删除。
三个缓存问题
缓存穿透
就是穿过缓存去数据库拿数据;好像也没什么不对呀。
如果一万个人同时要拿这个数据,但是这个数据刚好缓存里没有,这样数据库就有一万个访问了;其实有一个就行了,拿到数据存进缓存,然后其他9999个访问从缓存拿。怎么解决这个问题呢?
使用布隆过滤器:基于hash算法,把查询key散列后会得到一个值,如果数组中这个值的下标值为1,说明缓存中有这个key要查的数据。
看上去不靠谱啊,hash很容易冲突的;所以我们多使用几种hash算法,比如:hashA(key)=1,hashB(key)=5,hashC(key)=8,如果下标1,5,8的值都为1,那这个key要查的数据就在缓存中;根据数据大小可以增加hash算法,虽然做不到100%精确。
怎么使用布隆过滤器呢?
在缓存之前在加一层布隆过滤器,在查询的时候先去布隆过滤器查询 key 是否存在,如果不存在就直接返回,存在再查缓存和DB。虽然解决了缓存穿透的问题,但是查不到数据了!不解决问题反而把提出问题的人解决了。
当然只需要简单设计一下就行了,比如设置同一个查询可以穿透的访问数量。
缓存雪崩
突然间大量的key失效了或redis重启;
和上面的危害是一样的,都是大量访问数据库,不过原因不一样,处理手段就不一样了。
解决方案:
1、 key的失效期分散开 不同的key设置不同的有效期(这个比较科学)
2、设置二级缓存(数据不一定一致)
3、高可用(脏读)
缓存击穿
如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题;
热key过期导致的;热key为什么会过期?淘汰策略里怎么也轮不到热key啊!设计有问题才会触发这种事吧。
首先这个布隆过滤器拦不住,因为本来有,但是访问的时候过期了。
1.设置key没有过期时间。(末位淘汰就好了,干嘛要过期时间)
2.设置互斥锁,让其他线程等待
其实这些缓存问题加上熔断器就可以解决了,熔断器的舱壁机制,可以给每个请求设置每秒访问数量;这样无论如何都不会崩溃了。
还有就是幂等处理,穿透和击穿查询的都是同一个key,如果要查询一个缓存里没有的key可以让查询先等待,然后起个线程去数据库里拿,并插入一条带这个查询参数的数据状态是已经在查了,这时候如果还有相同查询进来,检查查询状态,一起等待数据进入缓存,然后在缓存中查询。
延迟双删
保证数据的最终一致性(延时双删),解决缓存和DB的数据不一致
1、先更新数据库同时删除缓存项(key),等读的时候再填充缓存
2、2秒后再删除一次缓存项(key)
3、设置缓存过期时间 Expired Time 比如 10秒 或1小时
4、将缓存删除失败记录到日志中,利用脚本提取失败记录再次删除(缓存失效期过长 7*24)
订阅监听
Redis客户端通过执行MONITOR命令可以将自己变为一个监视器,实时地接受并打印出服务器当前处理的命令请求的相关信息。
127.0.0.1:6379> monitor
可以监听到别的客户端的操作。
Redis怎么做MySql的缓存?
导入maven包,SpringBoot直接配置即可;mybatis二级缓存兼容redis,默认开启,使用redis;redis以查询条件为key,mysql缓存也是以查询条件为key。
网友评论