SpringBoot集成Redis哨兵集群實(shí)現(xiàn)讀寫分離的完整教程
一、redis安裝
# 安裝包存放目錄 cd /opt/software/ # 下載最新穩(wěn)定版 wget https://download.redis.io/releases/redis-6.2.6.tar.gz # 解壓 tar -zxvf redis-6.2.6.tar.gz # 進(jìn)入解壓后的目錄 cd /opt/software/redis-6.2.6/ # 編譯 make # 執(zhí)行 "make install" 默認(rèn)會(huì)安裝到 /usr/local/bin,可通過PREFIX指定安裝路徑 make install PREFIX=/usr/local/redis # 測(cè)試是否安裝成功,執(zhí)行下面命令 /usr/local/redis/bin/redis-server
二、主從及哨兵配置
三個(gè)redis實(shí)例都運(yùn)行在192.168.162.10服務(wù)器上,端口分別是7001、7002、7003,默認(rèn)啟動(dòng)時(shí)已7001作為主節(jié)點(diǎn),7002與7003作為從節(jié)點(diǎn),下面是主從與哨兵的配置文件需要修改的地方。
1、redis配置
原始配置文件可在解壓后的源碼文件根目錄中找到,這里以從節(jié)點(diǎn) 7002 配置文件為例,其余兩個(gè)配置文件幾乎一致。
首先將配置文件拷貝到/opt/software/redis-cluster/redis-7002.conf,然后進(jìn)行下面的修改。
# (1)設(shè)置允許外部ip訪問,需要注釋掉bind配置,并關(guān)掉保護(hù)模式 # bind 127.0.0.1 -::1 protected-mode no # (2)修改端口號(hào) port 7002 # (3)修改為以守護(hù)進(jìn)程模式后臺(tái)運(yùn)行 daemonize yes # (4)修改pid文件名,以守護(hù)進(jìn)程運(yùn)行的時(shí)候,會(huì)產(chǎn)生pid文件,默認(rèn)位置為 /run/redis.pid # 因?yàn)檫@里在同一臺(tái)機(jī)器上運(yùn)行多個(gè)實(shí)例,所以需要指定 pidfile /opt/software/redis-cluster/redis_7002.pid # (5)修改日志文件位置 logfile /opt/software/redis-cluster/redis_7002.log # (6)修改rdb快照文件位置 dir /opt/software/redis-cluster dbfilename dump_7002.rdb # (7)修改主節(jié)點(diǎn)地址,在部分舊版本中是slaveof命令,主節(jié)點(diǎn)7001配置文件中不要加這一行 replicaof 192.168.162.10 7001 # (8)aof可按需要開啟 appendonly yes appendfilename appendonly_7002.aof
在上面的配置中,7001文件與7003一致,改一下其中的端口及地址就可以了,其中,7001作為主節(jié)點(diǎn),沒有第(7)點(diǎn)。
建議三個(gè)實(shí)例運(yùn)行在不同的文件夾下,我為了省去切換文件目錄的時(shí)間,都放在一個(gè)文件夾下了。

配置完成后,按三個(gè)端口號(hào)的順序啟動(dòng)分別啟動(dòng)三個(gè)實(shí)例。
/usr/local/redis/bin/redis-server /opt/software/redis-cluster/redis-7001.conf /usr/local/redis/bin/redis-server /opt/software/redis-cluster/redis-7002.conf /usr/local/redis/bin/redis-server /opt/software/redis-cluster/redis-7003.conf
啟動(dòng)后,產(chǎn)生的文件如下所示

進(jìn)入主節(jié)點(diǎn),查看實(shí)例主從狀況

進(jìn)入從節(jié)點(diǎn),查看實(shí)例主從狀況

測(cè)試主從復(fù)制,在主節(jié)點(diǎn)中添加一個(gè)緩存,然后從節(jié)點(diǎn)中查詢


至此,主從復(fù)制基本上差不多了,接下來就是哨兵的配置了。
2、sentinel配置
共計(jì)啟動(dòng)三個(gè)實(shí)例,分別運(yùn)行于27001、27002、27003三個(gè)端口,以sentinel-27001.conf為例,配置信息如下,其余兩個(gè)配置文件基本上一致,改一下端口以及pidfile、logfile即可。
port 27001 daemonize yes pidfile /opt/software/redis-cluster/sentinel-27001.pid logfile /opt/software/redis-cluster/sentinel-27001.log # 監(jiān)控192.168.162.10:7001實(shí)例,實(shí)例取名為mymaster,當(dāng)有兩個(gè)哨兵認(rèn)為實(shí)例下線后,自動(dòng)進(jìn)行故障轉(zhuǎn)移 sentinel monitor mymaster 192.168.162.10 7001 2 # 服務(wù)不可達(dá)時(shí)間,心跳超過這個(gè)時(shí)間,sentinel將認(rèn)為節(jié)點(diǎn)掛了 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1

分別啟動(dòng)三個(gè)哨兵實(shí)例


隨意連接一個(gè)哨兵,查看哨兵監(jiān)控信息

查看哨兵日志

關(guān)閉主節(jié)點(diǎn),再看哨兵日志


從上面的日志文件中,我們可以看到哨兵投票選舉leader以及切換主節(jié)點(diǎn)的大概過程,這時(shí)候,主節(jié)點(diǎn)已經(jīng)切換到7003節(jié)點(diǎn)了。

這時(shí)候,再重新啟動(dòng)7001節(jié)點(diǎn),也就是之前的主節(jié)點(diǎn),這個(gè)節(jié)點(diǎn)會(huì)被哨兵自動(dòng)加入到集群中作為從節(jié)點(diǎn),sentinel會(huì)打印如下日志
+convert-to-slave slave 192.168.162.10:7001 192.168.162.10 7001 @ mymaster 192.168.162.10 7003

至此,哨兵集群也OK了。接下來就是springboot中配置哨兵集群了。
三、springboot配置哨兵集群及讀寫分離
創(chuàng)建springboot測(cè)試項(xiàng)目,pom.xml如下所示
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>sentinel-cluster</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-sentinel-cluster</name>
<description>spring-boot-sentinel-cluster</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>配置文件application.yml如下
spring:
redis:
sentinel:
master: mymaster
nodes:
- 192.168.162.10:27001
- 192.168.162.10:27002
- 192.168.162.10:27003
logging:
pattern:
console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'
level:
root: info創(chuàng)建配置類,配置RedisTemplate
package com.example.config;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* @author ygr
* @date 2022-02-15 16:30
*/
@Slf4j
@Configuration
public class RedisConfig {
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
objectMapper.configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
return objectMapper;
}
@Bean
@ConditionalOnMissingBean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 創(chuàng)建RedisTemplate<String, Object>對(duì)象
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 定義Jackson2JsonRedisSerializer序列化對(duì)象
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
StringRedisSerializer stringSerial = new StringRedisSerializer();
// redis key 序列化方式使用stringSerial
template.setKeySerializer(stringSerial);
// redis value 序列化方式使用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// redis hash key 序列化方式使用stringSerial
template.setHashKeySerializer(stringSerial);
// redis hash value 序列化方式使用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
新建一個(gè)RedisInit類來進(jìn)行測(cè)試,該類實(shí)現(xiàn)了ApplicationRunner接口,在應(yīng)用啟動(dòng)后自動(dòng)運(yùn)行
package com.example.init;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author ygr
* @date 2022-02-15 16:32
*/
@Slf4j
@RequiredArgsConstructor
@Component
public class RedisInit implements ApplicationRunner {
private final RedisTemplate<String, Object> redisTemplate;
@Override
public void run(ApplicationArguments args) throws Exception {
for (int i = 0; i < 300; i++) {
try {
redisTemplate.opsForValue().set("k" + i, "v" + i);
log.info("set value success: {}", i);
Object val = redisTemplate.opsForValue().get("k" + i);
log.info("get value success: {}", val);
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
log.error("error: {}", e.getMessage());
}
}
log.info("finished...");
}
}
項(xiàng)目結(jié)構(gòu)特別簡(jiǎn)單

啟動(dòng)項(xiàng)目,查看日志,可以看到讀寫一切正常。

途中嘗試將主節(jié)點(diǎn)干掉,接著看日志,從日志中可以看到,主從切換過來后,一切ok

但從info級(jí)別日志中,我們是看不出具體的讀寫連接信息的。將剛剛干掉的主節(jié)點(diǎn)重新啟動(dòng)起來,保持一主二從的模式,并修改一下部分包的日志級(jí)別為debug,然后再次啟動(dòng)看日志
logging:
pattern:
console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'
level:
root: info
io.lettuce.core: debug
org.springframework.data.redis: debug這部分日志有點(diǎn)長(zhǎng),我直接截取了一次讀寫的日志,如下
2022-02-28 15:43:04.962 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 143 -| Fetching Redis Connection from RedisConnectionFactory 2022-02-28 15:43:04.962 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.962 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 430 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1] write() writeAndFlush command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.962 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 207 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1] write() done 2022-02-28 15:43:04.963 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 383 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] write(ctx, AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise) 2022-02-28 15:43:04.963 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandEncoder 101 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001] writing command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.964 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 577 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Received: 5 bytes, 1 commands in the stack 2022-02-28 15:43:04.964 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 651 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Stack contains: 1 commands 2022-02-28 15:43:04.964 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.RedisStateMachine 298 -| Decode done, empty stack: true 2022-02-28 15:43:04.964 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 679 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Completing command AsyncCommand [type=SET, output=StatusOutput [output=OK, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.964 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 389 -| Closing Redis Connection. 2022-02-28 15:43:04.965 | INFO [ main] com.example.init.RedisInit 28 -| set value success: 4 2022-02-28 15:43:04.965 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 143 -| Fetching Redis Connection from RedisConnectionFactory 2022-02-28 15:43:04.965 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.965 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 430 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.965 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 207 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1] write() done 2022-02-28 15:43:04.965 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 383 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise) 2022-02-28 15:43:04.966 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandEncoder 101 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.966 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 577 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Received: 10 bytes, 1 commands in the stack 2022-02-28 15:43:04.966 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 651 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Stack contains: 1 commands 2022-02-28 15:43:04.966 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.RedisStateMachine 298 -| Decode done, empty stack: true 2022-02-28 15:43:04.966 | DEBUG [nioEventLoop-6-2] io.lettuce.core.protocol.CommandHandler 679 -| [channel=0x72e65475, /192.168.162.1:61674 -> /192.168.162.10:7001, epid=0x1, chid=0x2] Completing command AsyncCommand [type=GET, output=ValueOutput [output=[B@393ff2f4, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:43:04.967 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 389 -| Closing Redis Connection. 2022-02-28 15:43:04.967 | INFO [ main] com.example.init.RedisInit 31 -| get value success: v4
從日志中可以看到,讀與寫都是走的主節(jié)點(diǎn)(目前7001是主)。
如果想做讀寫分離,也很簡(jiǎn)單,修改RedisConfig類,加入如下Bean的配置代碼
@Bean
public RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
);
LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
// 讀寫分離,若主節(jié)點(diǎn)能抗住讀寫并發(fā),則不需要設(shè)置,全都走主節(jié)點(diǎn)即可
.readFrom(ReadFrom.ANY_REPLICA)
.build();
return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
}
ReadFrom的取值及讀取方式的對(duì)應(yīng)關(guān)系如下,其中,REPLICA會(huì)一直讀取的同一個(gè)從節(jié)點(diǎn),ANY_REPLICA則會(huì)隨機(jī)選擇
| ReadFrom | 讀取方式 |
|---|---|
| MASTER / UPSTREAM | 僅讀取主節(jié)點(diǎn) |
| MASTER_PREFERRED / UPSTREAM_PREFERRED | 優(yōu)先讀取主節(jié)點(diǎn),如果主節(jié)點(diǎn)不可用,則讀取從節(jié)點(diǎn) |
| REPLICA/ SLAVE(已廢棄) | 僅讀取從節(jié)點(diǎn) |
| REPLICA_PREFERRED / SLAVE_PREFERRED(已廢棄) | 優(yōu)先讀取從節(jié)點(diǎn),如果從節(jié)點(diǎn)不可用,則讀取主節(jié)點(diǎn) |
| NEAREST | 從最近節(jié)點(diǎn)讀取 |
| ANY | 從任何節(jié)點(diǎn)讀取 |
| ANY_REPLICA | 從任意一個(gè)從節(jié)點(diǎn)讀取 |
再重啟查看日志
2022-02-28 15:40:58.971 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 143 -| Fetching Redis Connection from RedisConnectionFactory 2022-02-28 15:40:58.971 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.971 | DEBUG [ main] i.l.c.m.MasterReplicaConnectionProvider 112 -| getConnectionAsync(WRITE) 2022-02-28 15:40:58.971 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.971 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 430 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7] write() writeAndFlush command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.972 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.CommandHandler 383 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7, chid=0x7] write(ctx, AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise) 2022-02-28 15:40:58.972 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 207 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7] write() done 2022-02-28 15:40:58.973 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.CommandEncoder 101 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001] writing command AsyncCommand [type=SET, output=StatusOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.974 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.CommandHandler 577 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7, chid=0x7] Received: 5 bytes, 1 commands in the stack 2022-02-28 15:40:58.974 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.CommandHandler 651 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7, chid=0x7] Stack contains: 1 commands 2022-02-28 15:40:58.974 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.RedisStateMachine 298 -| Decode done, empty stack: true 2022-02-28 15:40:58.974 | DEBUG [nioEventLoop-6-7] io.lettuce.core.protocol.CommandHandler 679 -| [channel=0x4c2f55eb, /192.168.162.1:61317 -> /192.168.162.10:7001, epid=0x7, chid=0x7] Completing command AsyncCommand [type=SET, output=StatusOutput [output=OK, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.974 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 389 -| Closing Redis Connection. 2022-02-28 15:40:58.974 | INFO [ main] com.example.init.RedisInit 28 -| set value success: 4 2022-02-28 15:40:58.974 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 143 -| Fetching Redis Connection from RedisConnectionFactory 2022-02-28 15:40:58.975 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.975 | DEBUG [ main] i.l.c.m.MasterReplicaConnectionProvider 112 -| getConnectionAsync(READ) 2022-02-28 15:40:58.975 | DEBUG [ main] io.lettuce.core.RedisChannelHandler 175 -| dispatching command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.975 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 430 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8] write() writeAndFlush command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.975 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.CommandHandler 383 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8, chid=0x8] write(ctx, AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command], promise) 2022-02-28 15:40:58.975 | DEBUG [ main] io.lettuce.core.protocol.DefaultEndpoint 207 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8] write() done 2022-02-28 15:40:58.976 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.CommandEncoder 101 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002] writing command AsyncCommand [type=GET, output=ValueOutput [output=null, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.976 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.CommandHandler 577 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8, chid=0x8] Received: 10 bytes, 1 commands in the stack 2022-02-28 15:40:58.976 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.CommandHandler 651 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8, chid=0x8] Stack contains: 1 commands 2022-02-28 15:40:58.977 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.RedisStateMachine 298 -| Decode done, empty stack: true 2022-02-28 15:40:58.977 | DEBUG [nioEventLoop-6-8] io.lettuce.core.protocol.CommandHandler 679 -| [channel=0x83e97184, /192.168.162.1:61318 -> /192.168.162.10:7002, epid=0x8, chid=0x8] Completing command AsyncCommand [type=GET, output=ValueOutput [output=[B@75c09ac4, error='null'], commandType=io.lettuce.core.protocol.Command] 2022-02-28 15:40:58.977 | DEBUG [ main] o.s.data.redis.core.RedisConnectionUtils 389 -| Closing Redis Connection. 2022-02-28 15:40:58.977 | INFO [ main] com.example.init.RedisInit 31 -| get value success: v4
從日志中可以看到,寫操作走的是主節(jié)點(diǎn)(7001),讀操作走的是從節(jié)點(diǎn)(7002),日志太長(zhǎng),沒有粘貼其他的,從完整的日志中可以看到,從節(jié)點(diǎn)其實(shí)是一直隨機(jī)選擇的。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Spring 中@Validated 分組校驗(yàn)的使用解析
這篇文章主要介紹了Spring 中@Validated 分組校驗(yàn)的使用解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java的Jackson庫的使用及其樹模型的入門學(xué)習(xí)教程
這篇文章主要介紹了Java的Jackson庫的使用及其樹模型入門學(xué)習(xí)教程,Jackson庫通常被用來作Java對(duì)象和JSON的互相轉(zhuǎn)換,需要的朋友可以參考下2016-01-01
IDEA2020.1啟動(dòng)SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在
這篇文章主要介紹了IDEA2020.1啟動(dòng)SpringBoot項(xiàng)目出現(xiàn)java程序包:xxx不存在,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
java實(shí)現(xiàn)微信企業(yè)付款到個(gè)人
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)微信企業(yè)付款到個(gè)人功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
spring security認(rèn)證異常后返回中文提示的問題
這篇文章主要介紹了spring security認(rèn)證異常后返回中文提示的問題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
maven打包時(shí)候修改包名稱帶上git版本號(hào)和打包時(shí)間方式
這篇文章主要介紹了maven打包時(shí)候修改包名稱帶上git版本號(hào)和打包時(shí)間方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
詳解Java數(shù)據(jù)結(jié)構(gòu)之平衡二叉樹
平衡二叉樹(Balanced?Binary?Tree)又被稱為AVL樹(有別于AVL算法),且具有以下性質(zhì):它是一?棵空樹或它的左右兩個(gè)子樹的高度差的絕對(duì)值不超過1,并且左右兩個(gè)子樹都是一棵平衡二叉樹。本文將詳解介紹一下平衡二叉樹的原理與實(shí)現(xiàn),需要的可以參考一下2022-02-02

