第06课:分布式环境下

第06课:分布式环境下 Redis 如何正确使用

本节将介绍在分布式和高并发环境下在使用 Redis 的时候,我们需要考虑的东西。

第一部分 Redis 本身的高可用

在分布式环境中,Redis 本身的高可用,都是来突破单个机器的极限的,无非分为两种:

  • 备份/Master:Slave 模式
  • 切片模式

1.官方 Sentinel 模式

1)发展

期初,Redis 官方只提供了 Master 和 Slave,用于提供主存和备份的模式,可以很好的做读写分离,但是官方没有提供故障转移的功能,所以当我们使用的时候不仅要考虑读写分离,也应该要考虑发生故障的时候如何切换到 Slave,并且把它选成 Master,后来 Redis 官方推出来了 Sentinel 哨兵的模式,帮我们解决了如下的四个问题。

  • 监控(Monitoring):Sentinel 不断检查你的主从实例是否运转正常。
  • 通知(Notification):Sentinel 可以通过 API 来通知系统管理员,或者其他计算机程序,被监控的 Redis 实例出了问题。
  • 自动故障转移(Automatic failover):如果一台主服务器运行不正常,Sentinel 会开始一个故障转移过程,将从服务器提升为主服务器,配置其他的从服务器使用新的主服务器,使用 Redis 服务器的应用程序在连接时会收到新的服务器地址通知。
  • 配置提供者(Configuration provider):Sentinel 充当客户端服务发现的权威来源,客户端连接到 Sentinel 来询问某个服务的当前 Redis 主服务器的地址。当故障转移发生时,Sentinel 会报告新地址。
2)配置方法
3)原理
  • 客户端其实使用了代理的思路,将直接操作 Redis 的命令交给了 Sentinel 给我们提供的连接地址。
  • 而 Sentinel 利用心跳检查,来实时监听原本的 master/slave,实现服务端内部的切换。

2.官方 cluster 模式

1)Redis 集群帮我们解决的问题
  • 自动分割数据到不同的节点上。
  • 整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
2)原理大概介绍

Redis 集群有16384个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,从而可以放到不同的切片服务器上。

3)配置使用方法
  • Redis Server 端的 cluster 的配置请参考官方的配置方式: Redis 官方英文文档 / Redis 官方中文文档
  • Spring Data Redis 里面的 cluster 配置也在前面讲过:请参考02课
  • 还有一种选择:可以按照不同的业务线拆分,分别连不同的 Redis 的 master/slave 组合。

3.业内集群解决不错的开源产品

4.成熟云的选择

如果当公司的技术实力跟不上的时候也可以选择一些成熟的云平台的产品。

  • 阿里云的云数据库 Redis 版。
  • AWS(亚马逊云)的 elasticache 中添加 Redis。

第二部分:缓存雪崩&缓存击穿&缓存穿透

高并发的环境下,我们使用 Redis 的时候最可能碰到的的问题缓存雪崩、缓存击穿、缓存穿透三大问题。

1.缓存雪崩

缓存雪崩有些小伙伴又称缓存并发。其实是指在我们设置缓存时,大多数的 Key 采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB,DB 瞬时压力过重雪崩。

解决方法:不同的 key 设置不同的过期时间,尽量不要通过后台批量创建缓存数据。具体方法将在第04课中介绍,通过 RedisCacheManager 设置不同的 cacheName 的过期时间即可。

2. 缓存击穿

于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key 缓存,前者则是很多 key。

缓存在某个时间点过期的时候,恰好在这个时间点对这个 Key 有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端 DB 压垮。

解决方法:这个时候用同步就行了,@Cacheable(key = "test",sync = true)。

3.缓存穿透

我们在项目中使用缓存通常都是先检查缓存中是否存在,如果存在直接返回缓存内容,如果不存在就直接查询数据库然后再缓存查询结果返回。这个时候如果我们查询的某一个数据在缓存中一直不存在,就会造成每一次请求都查询 DB,这样缓存就失去了意义,在流量大时,可能 DB 就挂掉了,要是有人利用不存在的 key 频繁攻击我们的应用,这就是漏洞。

解决方法:

  • 一般是配置一个 @Aspect,针对某些方法,不符合规则的参数,就直接返回找不到结果了。
  • 有一个比较巧妙的作法是,可以将这个不存在的 key 预先设定一个值,如“key”、“&&”。在返回这个 && 值的时候,我们的应用就可以认为这是不存在的 key,那我们的应用就可以决定是否继续等待继续访问,还是放弃掉这次操作。如果继续等待访问,过一个时间轮询点后,再次请求这个 key,如果取到的值不再是 &&,则可以认为这时候 key 有值了,从而避免了透传到数据库,从而把大量的类似请求挡在了缓存之中。

第三部分:在分布式环境中的应用

在分布式的系统的,还可以用 Redis 帮我们解决一些分布式环境下的问题。

1.分布式锁的问题

在分布上的环境中,Redis 还可以很好的帮我们解决分布式锁的问题,要用到 Redis 里面的 SETNX 命令,命令格式 SETNX key value。

  • SETNX 是 SET If Not eXists 的简写。
  • 将 key 的值设为 value,当且仅当 key 不存在。
  • 若给定的 key 已经存在,则 SETNX 不做任何动作。
redis> SETNX mykey “hello”     
(integer) 1    
redis> SETNX mykey “hello” 
(integer) 0 

RedisTemplate 的用法如下:

@Autowired
private RedisTemplate redisTemplate;
public void someMethod(Object param){
   ......
   redisTemplate.opsForValue().setIfAbsent(key,lockValue);
}

2.分布式计数器

在分布上的环境中,如果是分布式数据库,经常会用到自增的 ID,各个库里面不能保持有序自增。Redis 里面帮我们提供了 INCR 的命令,命令格式 INCR key。

  • 将 key 中储存的数字值增一。
  • 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。
redis> SET page_view 20
OK
redis> INCR page_view
(integer) 21
redis> GET page_view    # 数字值在 Redis 中以字符串的形式保存
"21"

RedisTemplate 的用法如下:

@Autowired
private RedisTemplate redisTemplate;
public void someMethod(Object param){
   ......
   redisTemplate.opsForValue().increment(key,delta);
}

总结

希望大家在使用 Redis 的时候可以根据本公司的情况,随机应变的调整一下细节,但是整体自己要有个概念,这样不至于遇到实际生产情况,手忙脚乱。

上一篇
下一篇
目录