## redis读写分离 redis需要通过主从复制和哨兵模式来实现读写分离,节点设置为一主二从。下面以mall4j-aof-redis为例 ## 1. 配置主从复制 配置主节点 redis主节点默认不需要额外配置,只需要确保其网络端口(系统配置为6389)对外可访问。 使用系统部署文档中的配置文件redis.conf即可 配置从节点 从节点需要配置主节点的IP、端口和密码,可使用主节点的conf文件配置,配置两个从节点,补充如下 ```json replicaof masterauth ``` 对应为 ```json replicaof 192.168.1.72 6389 masterauth hn02le.34lkdLKD ``` ![](../img/读写分离/从节点配置主节点地址.png) 在docker-compose.yml中根据mall4j-aof-redis添加相同配置,分别修改端口为7001,7002启动 ```json 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 ```shell docker-compose up -d ``` 可进入容器查看节点状态,以主节点为例 ```shell docker exec -it mall4j-aof-redis redis-cli -p 6389 auth hn02le.34lkdLKD info replication ``` 主节点输出以下内容即正常 ```json // 主节点输出 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 ``` 启动成功后,主从节点会自动进行同步,主节点会自动将数据同步到从节点。 ## 2. 配置哨兵模式 sentinel 共计启动三个实例,分别运行于27001、27002、27003三个端口,以sentinel-27001.conf为例,配置信息如下,其余两个配置文件基本上一致,改一下端口以及pidfile、logfile即可。 ```json 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配置中添加三个实例 ```json 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,启动后进入容器查看 ```shell docker exec -it sentinel-27001-redis redis-cli -p 27001 info sentinel ``` 输入结果为: ```json 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中引入依赖 ```json 2.12.1 6.2.6.RELEASE org.apache.commons commons-pool2 ${commons-pool2.version} io.lettuce lettuce-core ${lettuce.version} ``` 在yami-shop-common模块中的pom.yml添加依赖 ```json io.lettuce lettuce-core org.apache.commons commons-pool2 ``` 修改RedisCacheConfig类,并添加以下代码 ```java @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方法,添加以下代码 ```java 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 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连接配置,端口和密码配置根据实际情况配置 ```yml 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的配置 ```yml 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文件 ```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中对应配置名称的环境变量,重启服务。