Redis回顾2
为什么需要集群这样的功能?
- 并发量
- 数据量的问题,如果机器内存只有256G,业务需要500G呢?
- 网络流量的需求
数据分布-将全量数据进行拆分-拆分成一个个子集(分区)
顺序分区和哈希分区
对比
还是跟你业务需求有关吧,比如你的键值业务相关就用顺序分布法,但是顺序分布有个很严重问题,就是数据倾斜,例如按照用户id进行区分,可能早期用户访问量较高,后期用户访问较低,导致访问不均匀
hash分区带来的问题
如果扩容一个,那么迁移约80%,如果是翻倍扩容,那么迁移丢失的数据大约是50%,丢失数据就导致回大量的回写操作(缓存失效)。综上,建议翻倍扩容
一致性hash
有啥好处呢?
比如我在node1和node2之间添加一个节点,那么影响的就只有node2,因为访问node1到新节点部分的数据会被转移到新节点上,丢失数据也只丢失了一小部分,而node1,node3,node4完全没有被影响
最好是翻倍伸缩,这样的话别的节点的压力也会变小,不会造成负载不均衡的情况
->虚拟槽分区
这个其实就是redis-cluster的实现方式了,其实要做hash槽分区有两种方式,一种我们通过中间件代理的方式,判断key输入哪个槽,槽属于哪个节点。另外一种就是在每个节点都保留hash算法,这样每个节点都直到key会被划分到哪个槽,槽属于哪个节点,如果不属于我我进行命令转发就可以了。
集群伸缩
原理
扩展其实就是启动一个节点,进行meet加入集群然后进行槽指派
这样会更好,可以检测新节点是否是孤立节点,否则如果这个节点在别的集群中就会造成节点混合
集群收缩
一样的,就是将槽迁移,然后脱离出集群,对节点进行下线
故障发现
通过ping/pong消息实现故障发现,不需要sentinel
也分为主观下线和客观下线
故障恢复
1)资格检查
每个从节点检查与故障主节点的断线时间
超过cluster-node-timeout*cluster-slave-validity-factory则取消资格,默认150秒
2)准备选举时间
更新触发选举的时间,这一步是为了保障偏移量较大的节点有更小的延迟达到选举时间,就是保障数据一致性更高
3)选举投票
4)替换主节点
slave no one…将主节点槽分配给自己,向集群广播pong消息,表名自己已替换从节点
如何实现批量操作?
mget mset必须在一个槽,但是这个要求太高了
解决
1)串行mget
写一个for循环不断get->n次网络时间
2)串行IO
通过本地CRC(16)%16383,就可以将key按照节点进行分组,分出一个个子集,然后只需要执行节点个数的pipeline就可以了
3)并行IO
并行IO就是分组后就是启动多个线程分别进行执行操作
4)hash_tag
用tag包装,保证每个key都落在同一个节点
集群倾斜
1)数据倾斜:内存不均
可能前两个节点使用内存较少,别的使用较高
*节点和槽分配不均
*不同槽对应的键值数量差异较大
*包含bigkey比如hash结构,list,set
*内存相关配置不一致
2)请求倾斜:热点问题,热点key
*避免bigkey
*热键不要用hash_tag
*一致性不高时,可以用本地缓存+MQ中间件
带宽消耗
定期交换gossip消息和心跳检测
建议节点不要超过1000个
三个方面的带宽消耗
1)消息的发送频率:节点发现与其他节点最后通信时间超过cluster-node-timeout/2时会直接发送ping消息
2)消息数据量:slots槽数组和整个集群的状态数据
3)节点部署的集群规模
优化:
1)避免大集群,避免多个业务使用一个集群,大业务可以多集群
数据迁移
缓存的收益和成本
- 加速读写
- 降低后端MYSQL负载
比如高消耗的SQL,join结果集,我们不需要每次都去数据库join,用缓存存起来直接取也行
成本: - 数据不一致
- 代码维护成本,多了一层缓存逻辑
更新策略
1)低一致性:最大内存和淘汰策略(LRU-FIFO什么的)结合
2)高一致性:超时剔除和主动更新结合,最大内存和淘汰策略兜底
来个小问题:同时来了两个更新请求,同时失效掉缓存,然后第一个请求导致数据更新了,并将缓存状态设置为有效,此时第二个请求更新数据库后,还未更新缓存此时系统挂了。数据库与缓存数据不一致导致读脏数据怎么办?
数据库字段加版本号,缓存value中也加版本号,每次更新将数据库版本号+1,然后异步数据到缓存,并将缓存版本号+1,读取缓存时对比缓存中的两个版本号,如果一直则返回,不一致则读库
缓存粒度问题
到底缓存select * 还是仅仅一些部分属性字段
缓存穿透问题
缓存空对象是一个方法,但是如果很多无效key,那么空对象对内存的占用也会比较严重,所以会设计过期时间
布隆过滤器
1个元素,1个hash函数,任意一个比特为1的概率为1/m,依然为0的概率为1-1/m
k个函数,依然为0概率(1-1/m)k,n个元素为0的概率(1-1/m)nk
为1个概率1-(1-1/m)nk,全中概率(1-(1-1/m)nk)^k
缓存雪崩
保证缓存高可用,不要宕机后流量全压到数据库
为后端限流,降级等
提前演练,进行压测
无底洞问题
当机器节点增加,性能没有提升,反而下降,加的越多,下降约明显,2010年Facebook 3000 Memcache
热点key重建优化
如果重建的key是热点key,那么会导致很多的无用功,并且对数据库造成很大压力
解决
1)互斥锁
2)为每个value添加过期时间,然后用单独线程完成缓存重建
本地布隆过滤器存在的问题guava
- 容量受限,受限于JVM
2)多个应用存在多个布隆过滤器,构建同步复杂
这样的话,布隆过滤器的同步是有问题的
使用集中式的布隆过滤器,redis实现
RedisCluster实现分布式布隆过滤器
多个布隆过滤器:二次路由,假设有10个布隆过滤器
找到bigkey
- 应用方异常
- redis-cli --bigkeys
- scan+debug object
- 主动报警:网络流量监控,客户端监控
bigkey删除
删除可能很耗费时间,特别是hash需要一个个删除,而且删除不记录在慢查询日志里,只记录客户端的耗时操作
但是会将删除命令同步给从节点,就相当于主节点是客户端执行慢查询操作,会记录下来,可以在从节点慢查询中找到记录
最好还是别出现bigkey问题,对数据进行拆分
选择合适的数据结构
方案一
方案二
方案三
内存使用统计
info
内存溢出策略
默认策略:不会删除任何数据,拒绝所有写入操作并返回端错误信息。此时只响应读操作
Volatile-lru:根据LRU算法删除设置了超时时间属性的键,直到腾出足够的空间为止。如果没有可删除的键对象,回退到默认策略
Allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止
Allkeys-random:随机删除所有键,直到腾出空间为止
volatile-ttl:根据简直对象的ttl,删除最近将要过期的数据。如果没有,回退到默认策略
Redis安全法则
容易被攻击的redis有哪些特征?
1)Redis所在机器有外网IP
2)Redis以默认端口号6379为启动端口,并且是对外网开放的
3)Redis是以Root用户启动的
4)Redis没有设置密码
5)Redis的bind设置为0.0.0.0或者“”
ssh [email protected]… 不通,因为没有密码
那我们直接就直接redis -cli -h ip -p 6379 ping直接测试连接
redis -cli -h ip -p 6379 flushall直接就可以操作
将公钥作为value添加到Redis中
cat my.pub | redis-cli -h ip -p 6379 -x set crackit
将Redis的dir设置为/root/.ssh目录,dbfilename设置为authorized_keys,再次通过ssh就可以登陆了
这里再补充一下为什么?SSH是怎么实现免密登陆的
一般我们是先在自己的PC端,通过ssh-keygenerate生成公钥和私钥,公钥文件是id_rsa.pub,私钥文件是id_rsa
然后将公钥上传到服务器的用户/.ssh/authorized-keys这个文件中作为一行
免密登陆的时候就会将本地私钥传过去,进行公私匹配
Redis如何扫描指定模式的数据?
redis 127.0.0.1:6379> KEYS w3c*
- “w3c3”
- “w3c1”
- “w3c2”
但是会有阻塞
这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。
Redis做异步队列如何生产一次,消费多次?
使用pub/sub主题订阅者模式,可以实现1:N的消息队列。
如果对方追问pub/sub有什么缺点?在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。
如果对方追问redis如何实现延时队列?我估计现在你很想把面试官一棒打死如果你手上有一根棒球棍的话,怎么问的这么详细。但是你很克制,然后神态自若的回答道:使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
(1)发布消息
发布者发布消息的命令是 publish,用法是 publish channel message,如向 channel1.1说一声hi
127.0.0.1:6379> publish channel:1 hi
(integer) 0
这样消息就发出去了。返回值表示接收这条消息的订阅者数量。发出去的消息不会被持久化,也就是有客户端订阅channel:1后只能接收到后续发布到该频道的消息,之前的就接收不到了。
(2)订阅频道
订阅频道的命令是 subscribe,可以同时订阅多个频道,用法是 subscribe channel1 [channel2 …],例如新开一个客户端订阅上面频道:(不会收到消息,因为不会收到订阅之前就发布到该频道的消息)
127.0.0.1:6379> subscribe channel:1
Reading messages… (press Ctrl-C to quit)
- “subscribe”
- “channel:1”
- (integer) 1
执行上面命令客户端会进入订阅状态,处于此状态下客户端不能使用除subscribe、unsubscribe、psubscribe和punsubscribe这四个属于"发布/订阅"之外的命令,否则会报错。
进入订阅状态后客户端可能收到3种类型的回复。每种类型的回复都包含3个值,第一个值是消息的类型,根据消类型的不同,第二个和第三个参数的含义可能不同。
消息类型的取值可能是以下3个:
(1)subscribe。表示订阅成功的反馈信息。第二个值是订阅成功的频道名称,第三个是当前客户端订阅的频道数量。
(2)message。表示接收到的消息,第二个值表示产生消息的频道名称,第三个值是消息的内容。
(3)unsubscribe。表示成功取消订阅某个频道。第二个值是对应的频道名称,第三个值是当前客户端订阅的频道数量,当此值为0时客户端会退出订阅状态,之后就可以执行其他非"发布/订阅"模式的命令了。
如果再问aof文件过大恢复时间过长怎么办?
Redis会定期做aof重写,压缩aof文件日志大小。Redis4.0之后有了混合持久化的功能,将bgsave的全量和aof的增量做了融合处理,这样既保证了恢复的效率又兼顾了数据的安全性。
那如果突然机器掉电会怎样?取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。
看到一个关于槽的好的解释
https://www.cnblogs.com/rjzheng/p/11430592.html