SpringBoot对接Redis高可用集群

SpringBoot对接高可用Redis的情况和配置方式

Redis 高可用分两个方式:主从哨兵模式或者Cluster集群

SpringBoot 版本 2.6.8

主从哨兵模式

常见的部署是一主二从三哨兵模式,一个redis节点主节点,承担写+读的责任。两个redis节点,作为只读节点,不承担写入责任,三个redis节点,通过特殊的启动模式,作为监控节点,监控主节点的实例状态。

特性:高可用、部分高并发,主从节点数据完全一致

缺点:高并发特性不明显

优点:支持性好,可无缝衔接具体项目,完美支持lua脚本

Spring Boot对接

1
2
3
4
5
6
7
8
#主节点的名称
spring.redis.sentinel.master=mymaster
# 三个哨兵的地址
spring.redis.sentinel.nodes=127.0.0.1:26663,127.0.0.1:26662,127.0.0.1:26661
# 哨兵如果有密码,配置哨兵密码
spring.redis.sentinel.password=redis
# 主节点的密码
spring.redis.password=redis

Cluster集群

常见的部署是三主三从,三个redis节点,作为主节点,承担写+读的责任。三个redis节点,从节点提供数据读能力,不承担写入责任。Cluster集群式无中心的架构,六者之间相互监控,当有主节点实例状态出问题,从节点自动顶上

特性:高可用、高并发,数据分片,三个主节点数据不一致

优点:高并发

缺点:数据分片,导致原生不支持mset,mget,pipeline等批量操作,部分支持lua脚本

Spring Boot对接

  • 对接尽量使用lettuce,不要使用jedis

    • cluster集群原生不支持批量操作

    • lettucejedis都对其进行二次封装,能在cluster集群中使用mset,mget

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      mset mget
      //redis.clients.jedis.JedisCluster 2013
      @Override
      public String mset(final String... keysvalues) {
      String[] keys = new String[keysvalues.length / 2];

      for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) {
      keys[keyIdx] = keysvalues[keyIdx * 2];
      }

      return new JedisClusterCommand<String>(connectionHandler, maxAttempts, maxTotalRetriesDuration) {
      @Override
      public String execute(Jedis connection) {
      return connection.mset(keysvalues);
      }
      }.run(keys.length, keys);
      }
      // io.lettuce.core.cluster.RedisAdvancedClusterAsyncCommandsImpl<K, V> 350

      @Override
      public RedisFuture<String> mset(Map<K, V> map) {

      Map<Integer, List<K>> partitioned = SlotHash.partition(codec, map.keySet());

      if (partitioned.size() < 2) {
      return super.mset(map);
      }

      Map<Integer, RedisFuture<String>> executions = new HashMap<>();

      for (Map.Entry<Integer, List<K>> entry : partitioned.entrySet()) {

      Map<K, V> op = new HashMap<>();
      entry.getValue().forEach(k -> op.put(k, map.get(k)));

      RedisFuture<String> mset = super.mset(op);
      executions.put(entry.getKey(), mset);
      }

      return MultiNodeExecution.firstOfAsync(executions);
      }
  • lettuce还支持pipeline,jedis不支持,会直接抛出异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // pipeline 
    // org.springframework.data.redis.connection.lettuce.LettuceConnection 490
    @Override
    public void openPipeline() {

    if (!isPipelined) {
    isPipelined = true;
    ppline = new ArrayList<>();
    flushState = this.pipeliningFlushPolicy.newPipeline();
    flushState.onOpen(this.getOrCreateDedicatedConnection());
    }
    }
    // org.springframework.data.redis.connection.jedis.JedisClusterConnection 849

    @Override
    public void openPipeline() {
    throw new UnsupportedOperationException("Pipeline is currently not supported for JedisClusterConnection.");
    }
  • lua脚本,如果不是批量操作,或者此次批量操作涉及的key能分片到同一台redis上,可以执行
1
2
3
4
5
6
7
8
9
10
#六个节点的地址
spring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006
#拓扑动态感应即客户端能够根据 redis cluster 集群的变化,动态改变客户端的节点情况,完成故障转移。
spring.redis.lettuce.cluster.refresh.adaptive=true
#集群拓扑刷新周期 单位毫秒
spring.redis.lettuce.cluster.refresh.period=10000
#是否发现和查询所有群集节点以获取群集拓扑。设置为false时,仅将初始种子节点用作拓扑发现的源
spring.redis.lettuce.cluster.refresh.dynamic-refresh-sources=true
# 节点的密码
spring.redis.password=redis