Redis 架构重构


7/19/2018 Redis

使用 Redis Sentinel 重构现有架构

redis-proxy 重构


[toc]

对于搭建高可用 Redis 服务, 网上已有了很多方案, 例如 Keepalived, Codis, Twemproxy, Redis Sentinel. 其中 Codis 和 Twemproxy 主要是用于大规模的 Redis 集群中, 也是在 Redis 官方发布 Redis Sentinel 之前 豌豆荚 和 twitter 提供的开源解决方案. Redis Sentinel可以理解为一个监控 Redis Server 服务是否正常的进程, 并且一旦检测到不正常, 可以自动地将备份(slave)Redis Server启用, 使得外部用户对Redis服务内部出现的异常无感知.

原有架构

3915800D-B858-4AB4-BABD-23CCC8E72DFB

存在的问题

  1. 配置部署复杂
  2. 不稳定

Redis Sentinel 高可用

1F89EC1A-910B-4F58-B1BA-8B4ED8F1B1A8

下面以1个主节点、2个从节点、3个Sentinel节点组成的Redis Sentinel为例子

故障转移处理逻辑:

  1. 主节点出现故障, 此时两个从节点与主节点时区连接, 主从复制失败;

    85406409-6B92-4CE0-A282-6E67A5DBA68D

  2. 每个 Sentinel 节点通过定期监控发现主节点出现故障;

    00FDFC3B-4DD0-4324-812B-B7FC4ED1D211

  3. 多个 Sentinel 节点对主节点的故障达成一致, 选举出 sentinel-3 节点作为领导者负责故障转移;

    212E5E3C-AB42-41B9-8DAF-61BFB3EDFC6B

    1. 原来的从节点 slave-1 成为新的主节点后, 更新应用方的主节点信息;
    2. 客户端命令另一个从节点 slave-2 去复制新的主节点;
    3. 待原来的主节点恢复后, 让它去复制新的主节点;

    0151B897-8FC5-4700-8DD7-E596BAFAF002

  4. 故障转移后的结构图

    19036242-6602-47DF-85DA-042602A80A11

Redis Sentinel 功能

  1. 监控: Sentinel 节点会定期检测 Redis 数据节点和其余 Sentinel 节点是否可达;
  2. 通知: Sentinel 节点会将故障转移的结果通知给应用方;
  3. 主节点故障转移: 实现从节点晋升为主节点并维护后续正确的主从关系;
  4. 配置提供者: 客户端在初始化时, 连接 Sentinel 节点集群, 从中获取主节点信息;

采用多个 Sentinel 节点的优点:

  1. 对于节点故障判断由多个 Sentinel 节点共同完成, 有效防止误判;
  2. 避免单点故障;

几个概念

三个定时监控任务

  1. 每隔 10 秒. 每个 Sentinel 节点会向主节点和从节点发送 info 命令获取最新的拓扑结构; -w400

  2. 每隔 2 秒, 每个 Sentinel 节点向 sentinel:hello 频道上发送该 Sentinel 节点对于主节点的判断一级当前 Sentinel 节点的信息, 同时每个 Sentinel 节点也会订阅该频道; -w400

  3. 每隔 1 秒, 每个 Sentine 会向主从节点, 其他 Sentinel 节点发送 ping 命令做心跳检测, 来确保节点当前是否可达 -w400

主观下线(Subjectively Down, 简称 SDOWN)

指的是单个 Sentinel 实例对服务器做出的下线判断.

每个 Sentinel 节点会每隔1秒对主节 点、从节点、其他 Sentinel 节点发送 ping 命令做心跳检测, 当这些节点超过 down-after-milliseconds 没有进行有效回复, Sentinel 节点就会对该节点做失败 判定, 这个行为叫做主观下线

-w400

客观下线(Objectively Down, 简称 ODOWN)

指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断. (一个 Sentinel 可以通过向另一个 Sentinel 发送 SENTINEL is-master-down-by-addr 命令来询问对方是否认为给定的服务器已下线. )

Redis Sentinel 安装与部署

下面将以3个Sentinel节点、1个主节点、2个从节点组成一个Redis Sentinel进行说明

109FCB47-B219-49C7-B36E-6E234765

具体的物理部署:

角色 ip port 别名
master 127.0.0.1 6379 主节点
slave-1 127.0.0.1 6380 slave-1
slave-2 127.0.0.1 6381 slave-2
sentinel-1 127.0.0.1 26379 sentinel-1
sentinel-2 127.0.0.1 26380 sentinel-2
sentinel-3 127.0.0.1 26381 sentinel-3

1. master 配置

daemonize yes
dbfilename "6379.db"
dir "/Users/codeai/Develop/logs/redis/db/"
logfile "/Users/codeai/Develop/logs/redis/log/6379.log"
port 6379
requirepass 1234
1
2
3
4
5
6

2. slave-1 配置

daemonize yes
dbfilename "6380.db"
dir "/Users/codeai/Develop/logs/redis/db/"
logfile "/Users/codeai/Develop/logs/redis/log/6380.log"
port 6380
slaveof 127.0.0.1 6379
# 设置 master 验证密码
masterauth 1234
# 设置 slave 密码
requirepass 1234
1
2
3
4
5
6
7
8
9
10

3. slave-2 配置

daemonize yes
dbfilename "6381.db"
dir "/Users/codeai/Develop/logs/redis/db/"
logfile "/Users/codeai/Develop/logs/redis/log/6381.log"
port 6381
slaveof 127.0.0.1 6379
# 设置 master 验证密码
masterauth 1234
# 设置 slave 密码
requirepass 1234
1
2
3
4
5
6
7
8
9
10

4. 启动 redis 服务

redis-server redis-6379.conf; redis-server redis-6380.conf; redis-server redis-6381.conf
1

A62CC675-0FA4-4A6A-B432-5CAD116B95

5. 确认主从关系

主节点视角

redis-cli -h 127.0.0.1 -p 6379 info replication
# Replication
# 主节点
role:master
#2 个 从节点
connected_slaves:2
# 从节点 1 信息
slave0:ip=127.0.0.1,port=6380,state=online,offset=112,lag=0
# 从节点 2 信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=112,lag=0
master_replid:8f43dc48cca779f46fa1516a38e24fb8c5423d94
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:112
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:112
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

从节点视角

redis-cli -h 127.0.0.1 -p 6380 info replication
# Replication
# 从节点
role:slave
# 主节点信息
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:2
master_sync_in_progress:0
slave_repl_offset:238
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8f43dc48cca779f46fa1516a38e24fb8c5423d94
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:238
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:238
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

6. 部署 Sentinel 节点

port 26379
daemonize yes
logfile "/Users/codeai/Develop/logs/redis/log/26379.log"
dir "/Users/codeai/Develop/logs/redis/db/"
# 监控 127.0.0.1:6379 主节点; 2 表示判断主节点失败至少需要 2 个 sentinel 节点同意
sentinel monitor mymaster 127.0.0.1 6379 2
# 设置监听的 master 密码
sentinel auth-pass mymaster 1234
# 30 秒内 ping 失败, sentinel 则认为 master 不可用
sentinel down-after-milliseconds mymaster 30000
# 在发生failover主备切换时, 这个选项指定了最多可以有多少个slave同时对新的master进行同步
sentinel parallel-syncs mymaster 1
# 如果在该时间(ms)内未能完成failover操作, 则认为该failover失败
sentinel failover-timeout mymaster 180000
1
2
3
4
5
6
7
8
9
10
11
12
13
14

其他节点只是端口不同

7. 启动 sentinel 节点

# 方式一
redis-sentinel redis-sentinel-26379.conf; redis-sentinel redis-sentinel-26380.conf; redis-sentinel redis-sentinel-26381.conf
# 方式二
redis-server redis-sentinel-26379.conf --sentinel; redis-server redis-sentinel-26380.conf --sentinel; redis-server redis-sentinel-26381.conf --sentinel; 
1
2
3
4

4B44AF64-E724-4A23-A09A-297489A6F834

8. 确认关系

redis-cli -h 127.0.0.1 -p 26379 info sentinel
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3
1
2
3
4
5
6
7
8

部署方案

  1. Sentinel 部署在不同物理机上;
  2. 部署至少三个且奇数个的 Sentinel 节点;

一套 Sentinel 监控所有主节点

-w500

优点:

  1. 维护成本低

缺点:

  1. 集群出现异常, 将导致服务不可用
  2. 过多的网络连接

每个主节点各一套 Sentinel

-w500

优点:

  1. 某个 Sentinel 集群出现故障, 不会影响其他业务
  2. 网络连接少

缺点:

  1. 维护成本高

如果监控同一个业务的多个主节点集合, 推荐使用方案一 如果是多个业务不同主节点集合, 推荐方案二 (推荐)

Redis 连接数太多导致

原因分析

Redis 默认最大连接数为 10000个

  1. 网络通信差, 按照 TCP 协议, 客户端断开连接时, 向服务器端发送 FIN 信号, 但是服务端未接收到, 客户端超时后放弃等待, 直接断开, 服务端由于通信故障, 保持了 ESTABLISHED 状态;
  2. 客户端异常, 客户端连接之后, 由于代码运行过程中产生异常, 导致未正常释放或者关闭连接;
  3. client 设置不合理 (client 数 * maxTotal 是不能超过redis的最大连接数)

解决方案

1. 修改 redis.config 配置

# 连接的空闲实现超过 360s, 则主动关闭连接;默认配置为 0 ,导致所有空闲 idle 连接未被释放, 服务端连接泄漏
timeout 360
# 默认关闭, 导致服务端不知客户端连接状态; 开启长连接, 服务端主动(60s)探测客户端 socket 状态
tcp-keeplive 60
1
2
3
4

2. 完善代码

客户端每次执行完 jedis 里面的方法之后必须关闭链接, 释放资源

3. redis-proxy 服务化


重构方案

使用 Redis Sentinel 代替 Redis + Keepalived

架构

重构 redis-proxy

提供 6 种 连接模式

0652C208-B9C1-4505-92BB-C1BFBC09E9

component-redis 介绍

项目中有单独使用 Redis 的, 也有使用分片方式连接的, 也有使用 redis-proxy 组件来连接 Redis 的. 造成代码不好管理, 因此使用 component-redis 组件为其他模块提供连接 Redis 的功能, 统一管理 Redis 相关代码.

作为连接 Redis 的工具模块, 为其他模块提供操作 Redis 的功能, 具有多种模式选择. 客户端不需要再写连接 Redis 相关代码, 只需要按照要求配置即可,减少了冗余代码;

兼容原有代码, 只需要将 redis-proxy 依赖替换成 component-redis 即可使用.

功能

  1. 多种模式任君选择 (standalone, sentinel, shard, shard-sentinel, cluster, hybrid);
  2. 使用简单, 引入 jar 即可使用;
  3. 扩展方便, 如果 RedisService 满足不了现有业务需要, 可直接使用各种 Pool 获取 jedis, shardedJedis, jedisCluster 自由发挥;

使用方式

1. 引入依赖
<dependency>
    <groupId>com.iflytek.musicsearch</groupId>
    <artifactId>component-redis</artifactId>
    <version>最新版本</version>
</dependency>
1
2
3
4
5
2. 配置
# jedis pool 配置
# 连接超时时间(毫秒)
redis.connectionTimeout=2000
# 等待Response超时时间 (新增)
redis.soTimeout=5000
redis.pool.maxActive=5000
redis.pool.maxWait=5000
redis.pool.maxIdle=200
redis.pool.minIdleTime=120
redis.pool.testOnBorrow=true
redis.pool.testOnReturn=false
# Redis 配置
redis.model=standalone
redis.node=redis://127.0.0.1:6379
1
2
3
4
5
6
7
8
9
10
11
12
13
14
3. 注入 service
@Autowired
private RedisService redisService;
@Test
public void testRedisService() throws Exception {
    redisService.set("redisServiceTest", "redisServiceTest");
}
1
2
3
4
5
6

component-redis 配置规范

由于 component-redis 组件需要支持多种模式, 配置需要规范的格式才能避免出错.

  1. 节点使用 , 分隔, 模式分组使用 ; 分隔;
  2. 使用某个 Redis 时, 不要把所有配置全部加上;

jedisPool 配置是固定配置, 每种模式都会使用到, 只需要根据业务调整即可.

## 连接超时时间(毫秒)
redis.connectionTimeout=2000
# 等待Response超时时间 (新增)
redis.soTimeout=5000
# 连接池最大连接数(使用负值表示没有限制)
redis.pool.maxActive=5000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
redis.pool.maxWait=5000
# 连接池中的最大空闲连接
redis.pool.maxIdle=200
# 连接池中的最小空闲连接
redis.pool.minIdleTime=120
# 当调用borrow Object方法时, 是否进行有效性检查
redis.pool.testOnBorrow=true
# 调用return 一个对象方法时, 是否检查其有效性
redis.pool.testOnReturn=false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
redis.model=模式名
redis.node=业务名#模式名://[:password@]ip:port[/database];...
1
2

这里以现有业务为例子:

redis.model=hybrid
# callout 使用单机模式, biz 使用哨兵模式
redis.node=callout#standalone://:1234@127.0.0.1:6382;biz#sentinel://:5678@127.0.0.1:26379,sentinel://127.0.0.1:26380,sentinel://127.0.0.1:26381
1
2
3

配置优化

使用标准的 uri 协议代替 host 和 port

避免手动解析出错

格式如下:

# 完整格式
redis://user:password@ip:port/database
# 不需要用户名的格式
redis://:password@ip:port/database
# 不需要密码的格式
redis://ip:port/database
# 不需要 database 的配置, 将默认使用 0 db
redis://ip:port
1
2
3
4
5
6
7
8

密码设置

redis的查询速度是非常快的, 外部用户一秒内可以尝试多大150K个密码;所以密码要尽量长;

建议设置为 64 位长度密码

standalone (单机)模式配置
redis.model=standalone
# password 前面的 : 不能少
redis.node=redis://:password@127.0.0.1:6382
1
2
3

demo:

redis.model=standalone
redis.node=redis://127.0.0.1:6379
1
2
sentinel

哨兵模式是对单机模式高可用的一种实现方式, 可以实现故障主从自动切换

哨兵模式需要配置 master name, 和 redis-sentinel.conf 中的 sentinel monitor masterName xxx 保持一致

哨兵模式只能接收一个密码, 密码设置在任意节点即可(第一个最好了)

redis.model=sentinel
redis.node=mymaster#redis://:1234@127.0.0.1:26379,redis://127.0.0.1:26380,redis://127.0.0.1:26381
1
2

哨兵模式就是单机模式的增强版, 需要配置多个哨兵节点(避免造成主从切换失败, 最少3组哨兵), 节点之间使用 , 分隔

demo:

redis.model=sentinel
redis.node=mymaster#redis://:s4jRcLhAcUdKrNmqv9XQxwbEUZ6p4sK3kTFE4k9ts3PLahnswEzE4aPgXEQ6QdMa@127.0.0.1:26379,redis://127.0.0.1:26380,redis://127.0.0.1:26381
1
2
sharding (分片)模式配置

每组 redis 实例可以设置不同的密码

分片实例之间使用 , 分隔

redis.model=sharding
redis.node=redis://:1234@127.0.0.1:6382,redis://:5678@127.0.0.1:6382
1
2

demo:

# redis://:password@ip:port/database 没有密码则使用 redis://ip:port/database
redis.model=sharding
redis.node=redis://:1234@127.0.0.1:6382,redis://:s4jRcLhAcUdKrNmqv9XQxwbEUZ6p4sK3kTFE4k9ts3PLahnswEzE4aPgXEQ6QdMa@127.0.0.1:6379
1
2
3
sharding-sentinel

分片哨兵模式是对分片模式高可用的一种实现方式, 可以实现分片模式下, 故障主从自动切换

分片哨兵模式是哨兵模式和分片模式的结合, 配置可

demo:

redis.model=sharding-sentinel
redis.node=mymaster#redis://127.0.0.1:26379,redis://127.0.0.1:26380,redis://127.0.0.1:26381;mymaster1#redis://127.0.0.1:26379,redis://127.0.0.1:26380,redis://127.0.0.1:26381
1
2
cluster

redis 3.x 远程集群模式

demo:

redis.model=cluster
redis.node=redis://127.0.0.1:6379,redis://127.0.0.1:6380,redis://127.0.0.1:6381
1
2
hybrid

混合模式, 为了兼容现有 Redis 环境, 一种临时的解决方案, 改造完成后, 将配置修改为 sentinel 即可

demo:

redis.model=hybrid
redis.node=callout#standalone://:1234@127.0.0.1:6382;mymaster#sentinel://:s4jRcLhAcUdKrNmqv9XQxwbEUZ6p4sK3kTFE4k9ts3PLahnswEzE4aPgXEQ6QdMa@127.0.0.1:26379,sentinel://127.0.0.1:26380,sentinel://127.0.0.1:26381
1
2

混合模式实现了 standalone, sentinel, sharding 模式的混合使用

代码重构

  1. 使用 @Value 实现自动配置, 代替 xml 中的 bean 标签;
  2. 使用 logbok 简化代码;
  3. 不在使用 RedisServiceFacotry 获取 redisService, 某个模块需要使用该组件时, import xml 即可 (多容器问题);
  4. 使用 log4j2 代替 log4j;
  5. 编译版本由 jdk1.6 改为 jdk1.7;
  6. 使用代理类重构安全关闭 jedis 连接的方式;

重构前:

@Override
public String set(String flag, final String key, final String value) throws Exception {
	return new RedisCallBack<String>() {
		@Override
		public String doCallback(Jedis jedis) {
			return jedis.set(key, value);
		}
	}.callback(getJedisPoolByFlag(flag));
}
1
2
3
4
5
6
7
8
9

重构后:

@Override
public String set(String flag, final String key, final String value) {
    return RedisUtil.jedisProxy(model, flag).set(key, value);
}
1
2
3
4
Last Updated: 7/3/2019, 6:17:56 PM