上几次说了 Redis 的主从, 哨兵, 集群配置, 但是内部的选举一直没说, 先来简单说一下选举吧.
集群选举
Redis cluster 节点间采取 gossip 协议进行通信, 也就是说, 在每一个节点间, 无论主节点还是从节点, 他们之间都是存在相互通信的. 例如你的 Redis 端口号是 6379, 那么你的 gossip 协议端口号就是 16379.
gossip 协议包含多种消息, 包括 ping,pong,meet,fail 等等.
ping: 每个节点都会频繁给其他节点发送 ping, 其中包含自己的状态还有自己维护的集群元数据, 互相通过 ping 交换元 数据;
pong: 返回 ping 和 meet, 包含自己的状态和其他信息, 也可以用于信息广播和更新;
fail: 某个节点判断另一个节点 fail 之后, 就发送 fail 给其他节点, 通知其他节点, 指定的节点宕机了.
meet: 某个节点发送 meet 给新加入的节点, 让新节点加入集群中, 然后新节点就会开始与其他节点进行通信, 不需要 发送形成网络的所需的所有 CLUSTER MEET 命令. 发送 CLUSTER MEET 消息以便每个节点能够达到其他每个节点只需通 过一条已知的节点链就够了. 由于在心跳包中会交换 gossip 信息, 将会创建节点间缺失的链接.
当我们的 master 节点和其 slave 节点中断, 或和其它节点中断时, 也就是连接超过了我们设置的 cluster-node-timeout 的值, 这时就会认为我们的当前的 master 是不可用的, 需要选举了, 这时将自己记录的集群 currentEpoch 加 1, 并广播 FAILOVER_AUTH_REQUEST 信息到所有节点上 (包括其他主从的从节点), 其它主节点收到 FAILOVER_AUTH_REQUEST 信息会给与一个 FAILOVER_AUTH_ACK 反馈, 其它从节点不会有任何反应, 当我们的 slave 收到 ACK 反馈达到半数以上时, 会当选当前选举内的 master 节点, 其它 slave 节点不在进行选举, 作为该新 master 的 slave 节点. 广播 Pong 消息通知其他集群节点.
流程大概就是这样的, 还有可能每个正在选举的 slave 节点收到的 ACK 反馈是一样的, 这时再次触发一次选举, currentEpoch 再加 1, 流程和上面一样. 这里要注意的是, 并不是每个 slave 都在同一时刻向外发送 FAILOVER_AUTH_REQUEST 信息的, 一般数据较新的节点会先发, 数据的新旧由 SLAVE_RANK 来判断, SLAVE_RANK 越小, 代表数据越新.
Redis 的优化与实际场景
缓存穿透
我们在一般大型的互联网项目查询到的数据, 都是查询的缓存内的数据, 也就是我们的 Redis 内的数据, 但是第一次查询或者说根本不存在的数据, 会穿过缓存到达我们的数据库去查询, 如果大量这样的请求过来, 我们的数据库是扛不住的. 这就是我们常说的缓存穿透. 处理思路很简单, 只要是请求过来的, 没有结果. 存入缓存设置超时时间, 再返回. 设置时间是为了保证现在没用到, 现在没缓存结果, 不代表永远没有缓存结果.
- @GetMapping(value = "/")
- public String getIndex(String goods_id){
- // 优先从缓存去拿
- String goods = stringRedisTemplate.opsForValue().get(goods_id);
- if (goods == null){
- // 如果拿不到去数据库拿
- goods = goodsService.getGoodsById(goods_id);
- // 存入缓存, 设置超时时间
- stringRedisTemplate.opsForValue().set(goods_id,goods,300);
- }
- return goods;
- }
如果是黑客来了, 一直拿不同的缓存来请求我们的项目, 这样的思路是不可取的, 我们可以使用布隆过滤器来实现阻止缓存击穿问题.
缓存预热
双 11 要来了, 每次双 11 的 0 点, 会有大批的商品进行交易, 如果这些商品不是存在缓存内的, 超高的并发 (都不用双 11, 平时的秒杀就够受的), 大量的线程会涌入数据库, 给数据库造成超大的压力, 我们这时应该提前将这些要秒杀的商品, 提前存入到 Redis 当中去, 防止大批量的请求直接冲进数据库. 这就是我们提到的缓存预热.
缓存失效
刚才我们的说了预热, 但是我还是需要设置超时时间的时间的, 不设置超时时间的话, 你的数据库更新了, 而我们的缓存还是我们的最开始的数据, 造成数据的不一致. 假设我们在预热的时候将大量的商品设置为 300 秒超时的时间, 开始秒杀.... 过了 300 秒了. 还是有一定的并发量, 这时所有的缓存都失效了, 还是会有大量的请求进入到我们的数据库的, 所以说我在设置缓存预热时, 不要设置同一个时间结束. 会造成大量的缓存在同一时间失效, 给我们的后台服务造成巨大压力.
缓存雪崩
有很多项目还是在停留在使用 Redis 单机的状态, 如果说 Redis 不在对我们的项目服务了, 大量的请求会涌入我们的数据访问层, 造成我们的数据库压力超大, 甚至数据库宕机, 从导致整个服务的不可用状态. 或者说我们的并发量远远超过我们的 Redis 吞吐量. 也会早成 Redis 的拥塞, 其它线程请求 Redis 超时, 早成 Redis 假死现象, 造成我们的 Redis 雪崩. 这时我们应该尽力采用高可用的缓存层架构, 比如哨兵, 比如集群架构, 对于并发量超大的情况我们可以使用限流的方式来控制.
热点缓存重建
如果说, 我们的设置了一个缓存, 失效时间为 300 毫秒, 但在失效那一刻, 还是高并发的状态, 我们的服务器压力还是巨大的, 这些高并发的请求进入我们的数据库, 后果可想而知, 所以我们要在这个热点 key 的重建过程中, 避免大量的请求进入我们的数据库. 我们可以这样来做, 尝试加一把简单的锁.
- @GetMapping(value = "/")
- public String getIndex(String goods_id) throws InterruptedException {
- // 优先从缓存去拿
- String goods = stringRedisTemplate.opsForValue().get(goods_id);
- if (goods == null){
- // 如果拿不到去数据库拿
- // 设置只有一个请求可以进入数据库, 其余的线程自旋等待
- Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("lock" + goods_id, goods_id, Duration.ofMinutes(3));
- if(aBoolean){
- goods = goodsService.getGoodsById(goods_id);
- // 存入缓存, 设置超时时间
- stringRedisTemplate.opsForValue().set(goods_id,goods,300);
- }else{
- // 自旋等待 50 毫秒
- Thread.sleep(50);
- // 再次调用该方法, 尝试获取数据
- getIndex(goods_id);
- }
- }
- return goods;
- }
一些 Redis 的使用建议
1. 建议 key 设置为服务名: 表名或者模块名: 表名作为 key, 便于后期的查找和使用.
2. 保证能识别语义的前提下, 尽力设置 key 要简洁, 不要过长.
3. 不要在 key 中设置特殊字符, 比如空格, 换行等字符.
4.Redis 中不要设置过大的值, 一个字符串最大限制 512M, 但建议一般是要超过 10kb 大小, list,set,hash,zset 不建议超过 5000 个元素, 视情况而定.
5. 不要使用 keys 命令, 建议使用 scan 命令进行替换.
6. 建议多使用原生命令, 管道等操作尽力减少使用, 推荐使用 mget,mset 这样的命令.
这些优化其实都是围绕着我们 Redis 的特性, 单线程来说的, 如果说我们存了一个 bigKey 或者是一次性塞入了超多的命令, 很可能阻塞后面的命令, 造成我们的 Redis 假死现象, 也会造成我们的网络拥塞, 占有了更多的带宽.
Redis 的清除策略
1. 被动删除: 当读 / 写一个已经过期的 key 时, 会触发惰性删除策略, 直接删除掉这个过期 key
2. 主动删除: 由于惰性删除策略无法保证冷数据被及时删掉, 所以 Redis 会定期主动淘汰一批已过期的 key
3. 当前已用内存超过 maxmemory 限定时, 触发主动删除策略.
在 Redis 启动前, 我们就配置了, 最大的内存使用 maxmemory, 当前已用内存超过 maxmemory 限定时, 会触发主动清理策略.
默认策略是 volatile-lru, 即超过最大内存后, 在过期键中使用 lru 算法进行 key 的剔除, 保证不过 期数据不被删除, 但是可能会出现 OOM 问题.
其他策略如下:
allkeys-lru: 根据 LRU 算法删除键, 不管数据有没有设置超时属性, 直到腾出足够空间 为止.
allkeys-random: 随机删除所有键, 直到腾出足够空间为止.
volatile-random: 随机删除过期键, 直到腾出足够空间为止.
volatile-ttl: 根据键值对象的 ttl 属性, 删除最近将要过期数据. 如果没有, 回退到
noeviction 策略. noeviction: 不会剔除任何数据, 拒绝所有写入操作并返回客户端错误信息 "(error)OOM command not allowed when used memory", 此时 Redis 只响应读操作.
注意: 如果没有配置我们的 maxmemory 属性, 当我们的内存写满以后, 不会触发任何清除策略, 会直接将我们的数据存放在磁盘上, 极具降低我们的 Redis 性能.
总结:
Redis 差不多就说这么多了, 再深入的 c 语言代码, 我也不懂了, 我们大概简单使用, 基础的搭建主从, 哨兵, 集群, java 链接 Redis,Redis 的优化这几个角度来讲解我们的 Redis, 后面我会弄一篇 Redis 的面试题, 也是围绕这些来讲解的, 还是那句话, 真正懂得了内部的原理, 什么样的面试题都不在话下了...
最进弄了一个公众号, 小菜技术, 欢迎大家的加入
来源: https://www.cnblogs.com/cxiaocai/p/11746326.html