redis需要通过主从复制和哨兵模式来实现读写分离,节点设置为一主二从。下面以mall4j-aof-redis为例
配置主节点
redis主节点默认不需要额外配置,只需要确保其网络端口(系统配置为6389)对外可访问。 使用系统部署文档中的配置文件redis.conf即可
配置从节点
从节点需要配置主节点的IP、端口和密码,可使用主节点的conf文件配置,配置两个从节点,补充如下
replicaof <masterip><masterport>
masterauth <master-passwd>
对应为
replicaof 192.168.1.72 6389
masterauth hn02le.34lkdLKD
在docker-compose.yml中根据mall4j-aof-redis添加相同配置,分别修改端口为7001,7002启动
mall4j-7001-redis:
image: redis:7.0
container_name: mall4cloud-7001-redis
restart: always
network_mode: "host"
expose:
- 7001
volumes:
- ./redis/redis7001.conf:/etc/redis/redis7001.conf
command: redis-server /etc/redis/redis7001.conf --requirepass hn02le.34lkdLKD
启动docker-compose.yml
docker-compose up -d
可进入容器查看节点状态,以主节点为例
docker exec -it mall4j-aof-redis redis-cli -p 6389
auth hn02le.34lkdLKD
info replication
主节点输出以下内容即正常
// 主节点输出
role:master
connected_slaves:2
slave0:ip=192.168.1.72,port=7001,state=online,offset=249031856,lag=1
slave1:ip=192.168.1.72,port=7002,state=online,offset=249031995,lag=1
// 从节点输出
role:slave
master_host:192.168.1.72
master_port:6389
master_link_status:up
启动成功后,主从节点会自动进行同步,主节点会自动将数据同步到从节点。
共计启动三个实例,分别运行于27001、27002、27003三个端口,以sentinel-27001.conf为例,配置信息如下,其余两个配置文件基本上一致,改一下端口以及pidfile、logfile即可。
port 27001
daemonize no
pidfile /root/mall4j-shop/redis/sentinel-27001.pid
logfile /root/mall4j-shop/redis/sentinel-27001.log
# 监控192.168.1.72:6389实例,实例取名为mymaster,当有两个哨兵认为实例下线后,自动进行故障转移
sentinel monitor mymaster 192.168.1.72 6389 2
sentinel auth-pass mymaster hn02le.34lkdLKD
# 服务不可达时间,心跳超过这个时间,sentinel将认为节点挂了
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
在docker-compose.yml配置中添加三个实例
sentinel-27001-redis:
image: redis:7.0
container_name: sentinel-27001-redis
restart: always
network_mode: "host"
expose:
- 27001
volumes:
- ./redis/sentinel-27001.conf:/etc/redis/sentinel-27001.conf
- /root/mall4j-shop/redis:/root/mall4j-shop/redis
command: redis-sentinel /etc/redis/sentinel-27001.
启动docker-compose.yml,启动后进入容器查看
docker exec -it sentinel-27001-redis redis-cli -p 27001
info sentinel
输入结果为:
sentinel_masters:1
sentinel_tilt:0
sentinel_tilt_since_seconds:-1
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
sentinel_simulate_failure_flags:0
master0:name=mymaster,status=ok,address=192.168.1.72:7002,slaves=2,sentinels=3
启动成功后,三个实例会自动进行监控,当主节点挂掉后,会自动进行故障转移。
在项目pom.yml中引入依赖
<commons-pool2.version>2.12.1</commons-pool2.version>
<lettuce.version>6.2.6.RELEASE</lettuce.version>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>${commons-pool2.version}</version>
</dependency>
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>${lettuce.version}</version>
</dependency>
在yami-shop-common模块中的pom.yml添加依赖
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
修改RedisCacheConfig类,并添加以下代码
@Bean
public RedisConnectionFactory createRedisConnectionFactory(RedisProperties redisProperties) {
// 判断是否配置了哨兵模式
if (redisProperties.getSentinel() != null && redisProperties.getSentinel().getMaster() != null) {
// 哨兵模式配置
var sentinelConfig = new RedisSentinelConfiguration()
.master(redisProperties.getSentinel().getMaster());
// 添加哨兵节点(使用 Java 17 的 Stream API 优化)
redisProperties.getSentinel().getNodes().stream()
.map(node -> node.split(":"))
.forEach(parts -> sentinelConfig.sentinel(parts[0], Integer.parseInt(parts[1])));
if (Objects.nonNull(redisProperties.getDatabase())) {
sentinelConfig.setDatabase(redisProperties.getDatabase());
} else {
sentinelConfig.setDatabase(0);
}
if (Objects.nonNull(redisProperties.getPassword())) {
sentinelConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
}
// 配置读写分离策略:优先从副本读取
var clientConfig = LettuceClientConfiguration.builder()
.readFrom(ReadFrom.REPLICA_PREFERRED)
.build();
return new LettuceConnectionFactory(sentinelConfig, clientConfig);
} else {
// 单节点模式配置
var standaloneConfig = new RedisStandaloneConfiguration();
standaloneConfig.setHostName(redisProperties.getHost());
standaloneConfig.setPort(redisProperties.getPort());
standaloneConfig.setPassword(RedisPassword.of(redisProperties.getPassword()));
standaloneConfig.setDatabase(redisProperties.getDatabase());
var clientConfig = LettuceClientConfiguration.builder()
.build();
return new LettuceConnectionFactory(standaloneConfig, clientConfig);
}
}
修改AofRedisConfig类的AofRedisConfig方法,添加以下代码
public AofRedisConfig(AofRedisBO aofRedisBO) {
if (StrUtil.isBlank(aofRedisBO.getRedisAddr())) {
throw new YamiShopBindException("请在yml配置好 aofRedis 的配置,见 com.yami.shop.common.bo.AofRedisBO");
}
Config config = new Config();
if (BooleanUtil.isTrue(aofRedisBO.getSentinel())) {
// 哨兵模式
SentinelServersConfig sentinelServersConfig = config.useSentinelServers();
sentinelServersConfig.setMasterName(aofRedisBO.getMasterName());
List<String> list = Arrays.stream(aofRedisBO.getRedisAddr().split(",")).map(ip -> "redis://" + ip).toList();
if (aofRedisBO.getDatabase() != null) {
sentinelServersConfig.setDatabase(aofRedisBO.getDatabase());
} else {
sentinelServersConfig.setDatabase(0);
}
if (StrUtil.isNotBlank(aofRedisBO.getPassword())) {
sentinelServersConfig.setPassword(aofRedisBO.getPassword());
}
sentinelServersConfig.setSentinelAddresses(list);
} else {
// 单机
SingleServerConfig singleServerConfig = config.useSingleServer();
if (StrUtil.isNotBlank(aofRedisBO.getPassword())) {
singleServerConfig.setPassword(aofRedisBO.getPassword());
}
if (aofRedisBO.getDatabase() != null) {
singleServerConfig.setDatabase(aofRedisBO.getDatabase());
} else {
singleServerConfig.setDatabase(0);
}
singleServerConfig.setAddress("redis://" + aofRedisBO.getRedisAddr());
}
redissonClient = Redisson.create(config);
LOGGER.info("创建redisson, redisson={}", redissonClient);
stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setValueSerializer(StringRedisSerializer.UTF_8);
stringRedisTemplate.setKeySerializer(StringRedisSerializer.UTF_8);
stringRedisTemplate.setConnectionFactory(new RedissonConnectionFactory(redissonClient));
stringRedisTemplate.afterPropertiesSet();
}
修改服务的application-dev.yml配置文件中的redis连接配置,端口和密码配置根据实际情况配置
data:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.1.72:27001
- 192.168.1.72:27002
- 192.168.1.72:27003
password: hn02le.34lkdLKD
database: 7
aofredis的配置
redis:
aof:
sentinel: true
master-name: mymaster
database: 8
redis-addr: 192.168.1.72:27001,192.168.1.72:27002,192.168.1.72:27003
password: hn02le.34lkdLKD
如果为java服务由docker-compose启动,yml环境为docker,修改项目中的application-docker.yml文件
data:
redis:
sentinel:
master: ${SENTINEL_MASTER:mymaster}
nodes:
- ${MONGO_NODE_1:-192.168.1.72:27001} # 默认值为192.168.1.72:27001,可通过环境变量MONGO_NODE_1覆盖
- ${MONGO_NODE_2:-192.168.1.72:27002} # 默认值为192.168.1.72:27002,可通过环境变量MONGO_NODE_2覆盖
- ${MONGO_NODE_3:-192.168.1.72:27003} # 默认值为192.168.1.72:27003,可通过环境变量MONGO_NODE_3覆盖
database: ${REDIS_DATABASE:0}
password: ${REDIS_PASSWORD:}
redis:
aof:
sentinel: true
master-name: ${REDIS_AOF_MASTER_NAME:mymaster}
database: ${REDIS_AOF_DATABASE:0}
redis-addr: ${REDIS_AOF_ADDR:192.168.1.72:27001,192.168.1.72:27002,192.168.1.72:27003}
password: ${REDIS_AOF_PASSWORD:}
添加对应环境变量,重新打包代码。 在docker-compose.yml文件中添加mall4j-api、mall4j-multishop、mall4j-platform中对应配置名称的环境变量,重启服务。