前言

文章介绍Ubuntu系统搭建redis,并完成主从的读写分离配置,为主节点搭建三台哨兵服务,并集成到springboot中。
本篇文章是通过其他优秀博文学习后,用作学习记录使用。

1.Ubuntu安装redis

(1)工具安装
sudo apt-get install -y gcc
sudo apt-get install -y g++
sudo apt-get install -y gcc automake autoconf libtool make
(2)安装redis

a. 到redis官网(链接: https://redis.io/download/)下载redis安装包,然后上传到服务器,解压安装包

# 切换到安装包存放目录
cd /data/redis-colony
# 解压安装包
tar -zxvf redis-6.2.12.tar.gz

解压成功后,进入解压目录可以看到下面的内容,其中,redis.conf就是redis的配置文件,sentinel.conf就是redis哨兵的配置文件,待会会用这两个

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring

b. 编译Makefile,安装redis

# 进入解压目录
cd redis-6.2.12/
# 编译Makefile
make
# 编译完成后会看到最下面的一行字 Hint: It's a good idea to run 'make test' ; )
# 安装redis,使用PREFIX指定安装目录,默认在/usr/local/bin下
make install PREFIX=/data/redis-colony/redis

c.安装完成后可以在安装目录的bin目录下看到下面内容

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring_02


这个时候就可以用redis-server启动了,redis安装就到此完成了。

2.redis主从复制,读写分离

我这先在redis-colony目录下创建一个redis-cluster文件夹,统一管理主从redis的相关文件。

# 切换到目标目录,创建redis-cluster文件夹
cd /data/redis-colony
mkdir redis-cluster
# 切换到redis解压目录
cd /data/redis-colony/redis-6.2.12
# 复制redis配置文件到这个路径下面并改名
cp redis.conf /data/redis-colony/redis-cluster/redis-6380.conf
cp redis.conf /data/redis-colony/redis-cluster/redis-6381.conf
cp redis.conf /data/redis-colony/redis-cluster/redis-6382.conf

修改三个配置文件,我这里将6380作为主节点,6381和6382作为从节点

# bind 127.0.0.1 注释表示允许外机访问,不然就只能本机访问
protected-mode no # 关闭保护模式
port 6381 # 指定端口为6381

redis哨兵是否默认读写分离 redis 哨兵 读写分离_redis_03

# 启动方式从前台启动改成后台启动(前台启动的话,终端关闭就会自动停止)
daemonize yes 
# 后续就是一些相关文件路径,文件名配置
# aof相关配置,这里没有配,可以根据需要进行配置
appendonly yes
appendfilename appendonly_6381.aof
# 连接密码配置,这里也没有配置,可以根据需要进行配置
requirepass <password>

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring_04


redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_05


redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_06

# 从节点进行这项配置,主节点不用
replicaof <ip> 6380 # 指定当前从节点的主节点redis

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_07

# 主节点可以加一下配置
replica-read-only no # 标记当前节点可写

配置完成后就可以用redis-server启动了,我这里是想写的三个对应的启动脚本,这里拿6380示例

/data/redis-colony/redis/bin/redis-server /data/redis-colony/redis-cluster/redis-6380.conf

启动完成后,可以使用redis-cli进入主节点,查看当前主从情况

/data/redis-colony/redis/bin/redis-cli -p 6380
info replication

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_08

3.redis哨兵配置

前面提到了,sentinel.conf就是redis哨兵的配置,我们这里新建一个redis-sentinel文件夹,管理redis哨兵相关的文件。然后复制三个哨兵配置文件到这个路径下。

# 切换到解压文件夹下
cd /data/redis-colony/redis-6.2.12
# 复制哨兵配置文件到redis-sentinel路径下
cp sentinel.conf /data/redis-colony/redis-sentinel/sentinel-26380.conf
cp sentinel.conf /data/redis-colony/redis-sentinel/sentinel-26381.conf
cp sentinel.conf /data/redis-colony/redis-sentinel/sentinel-26382.conf

修改哨兵配置文件,三个哨兵配置文件一样,都是为主节点做哨兵配置,只是修改一下端口和相关文件即可,这里还是拿其中一个举例。

# 指定端口 26380
port 26380
# 改为后台启动
daemonize yes
# 指定文件路径
# ...
# 监控<ip> 6380 节点(将这台机器作为<ip> 6380的哨兵),命名为mymaster
# 这里的 1 代表优先级,我这给26380配置的1,其余两台分别是2和3,代表主节点宕机后,优先26380代替成为主节点
sentinel monitor mymaster <ip> 6380 1
# 设置主节点无响应时间代表挂了,默认是30s
sentinel down-after-milliseconds mymaster 30000
# 故障转移时间上限,默认是三分钟
sentinel failover-timeout mymaster 180000
# 发生故障时最多几台从节点对新master进行同步,值越小,故障响应处理越快
sentinel parallel-syncs mymaster 1
# 在监控节点配置下方的配置,这里的master-name指代的是前面配置的主节点名称,这里指mymaster,密码指主节点配置文件中的密码
# 当密码正确时,才能和主节点建立连接,监控主节点状态信息
sentinel auth-pass <master-name> <password>
# 这里为哨兵配置连接密码,按需要配置
requirepass <password>

redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_09


redis哨兵是否默认读写分离 redis 哨兵 读写分离_redis_10


redis哨兵是否默认读写分离 redis 哨兵 读写分离_redis哨兵是否默认读写分离_11


redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring boot_12


redis哨兵是否默认读写分离 redis 哨兵 读写分离_spring_13


完成配置后可以使用安装目录下的redis-sentinel启动哨兵服务

# 路径根据自己的来定
/data/redis-colony/redis/bin/redis-sentinel /data/redis-colony/redis-sentinel/sentinel-26380.con

4.springboot集成redis完成读写分离配置

(1)导入依赖

引入依赖(我这只引入了redis依赖,其余依赖看着来)

<!--spirngboot springdata对redis支持-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
(2)yml配置

编写yml配置文件,我这里的就是无密码配置,主节点,从节点,哨兵这些都没有密码,如果有配置了密码,还需要进行密码配置。通过无密码配置中的日志配置方式可以看到redis操作时的读写操作从哪个节点进行。

a.无密码yml配置
spring:
  redis:
    # 哨兵模式配置
    sentinel:
      # 这里就是前面哨兵配置文件中指定的主节点名称
      master: mymaster
      nodes:
        - <ip>:26380
        - <ip>:26381
        - <ip>:26382
# 日志配置
logging:
  level:
    # 表示根 logger 的日志级别为 info
    root: info
    # 表示 io.lettuce.core 包(redis的核心驱动包)下的日志级别为 debug
    io.lettuce.core: debug
    org.springframework.data.redis: debug
b. 主从节点密码相同,哨兵密码相同yml配置
spring:
  redis:
    sentinel:
      master: mymaster
      nodes: sentinel-host1:26380,sentinel-host2:26381
      password: sentinel-password
    cluster:
      nodes: redis-host1:6380,redis-host2:6381,redis-host3:6382
      password: cluster-password
c. 主从节点,哨兵密码各不相同yml配置
spring:
  redis:
    sentinel:
      master: mymaster
      nodes: sentinel1:26380,sentinel2:26381
      node-passwords:
        - host: sentinel1
          port: 26380
          password: sentinel1-password
        - host: sentinel2
          port: 26381
          password: sentinel2-password
    cluster:
      nodes: cluster1:6380,cluster2:6381,cluster3:6382
      node-passwords:
        - host: cluster1
          port: 6380
          password: cluster1-password
        - host: cluster2
          port: 6381
          password: cluster2-password
        - host: cluster3
          port: 6382
          password: cluster3-password
(3)RedisConfig配置类

编写redis配置类(我这直接把大佬的粘过来,写得太详细了)

@Configuration
public class RedisConfiguration {

    /**
     *  配置redis序列化json
     * @param redisConnectionFactory
     * @return
     */
    @Bean
    @Primary    //若有相同类型的Bean时,优先使用此注解标注的Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 为了开发方便,一般直接使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        // 配置具体的序列化方式
        // JSON解析任意对象
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        // 设置日期格式
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // String的序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();


        //key采用String的序列化
        template.setKeySerializer(stringRedisSerializer);
        //hash的key也采用String的序列化
        template.setHashKeySerializer(stringRedisSerializer);
        //value的序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        //设置所有配置
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 配置读写分离
     * @param redisProperties
     * @return
     */
    @Bean
    public RedisConnectionFactory lettuceConnectionFactory(RedisProperties redisProperties) {
        // 配置哨兵节点以及主节点
        RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration(
                redisProperties.getSentinel().getMaster(), new HashSet<>(redisProperties.getSentinel().getNodes())
        );

        // 配置读写分离
        LettucePoolingClientConfiguration lettuceClientConfiguration = LettucePoolingClientConfiguration.builder()
                // 读写分离,这里的ReadFrom是配置Redis的读取策略,是一个枚举,包括下面选择
                // MASTER   仅读取主节点
                // MASTER_PREFERRED   优先读取主节点,如果主节点不可用,则读取从节点
                // REPLICA_PREFERRED   优先读取从节点,如果从节点不可用,则读取主节点
                // REPLICA   仅读取从节点
                // NEAREST   从最近节点读取
                // ANY   从任意一个从节点读取
                .readFrom(ReadFrom.REPLICA_PREFERRED)
                .build();

        return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
    }

}
(4)测试

我个人习惯使用接口进行测试,不习惯用springboot测试方法,所以这里写的接口进行测试

@RequestMapping("/test/redis")
@RestController
@Api(tags = "redis测试")
@Slf4j
public class RedisTest {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @PostMapping("/save")
    @ApiOperation("保存")
    public Result save(@ApiParam("key") @RequestParam("key") String key, @ApiParam("value") @RequestParam("value") String value) {
        log.info("key-> {}, value -> {}", key, value);
        redisTemplate.opsForValue().set(key, value, 30, TimeUnit.SECONDS);
        return Result.success();
    }

    @GetMapping("/get")
    @ApiOperation("取值")
    public Result<String> get(@RequestParam("key") String key) {
        log.info("取值:key -> {}", key);
        Object o = redisTemplate.opsForValue().get(key);
        log.info("redisTemplate取值:value -> {}", o);
        Object o1 = redisUtils.get(key);
        log.info("redisUtils取值:value -> {}", o1);
        return Result.success(o);
    }
}

然后通过swagger进行接口测试,查看日志(这里就不放日志截图了),可以看到调用存值接口的时候,使用的6380,调用取值接口的时候,使用的6381,至此完成redis主从配置,读写分离,哨兵配置,并集成到springboot项目中。