Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理

笔记内容来自于 B站 编程不良人 up主的视频
文章内容比较长,需要的小伙伴可以根据目录自行跳转到指定位置
笔记内容参考了很多大佬的文章,在此表示感谢

1. 基础知识

1.基于内存的key-value数据库
2.基于c语言编写的,可以支持多种语言的api //set每秒11万次,取get 81000次
3.支持数据持久化
4.value可以是string,hash, list, set, sorted set

1.1 使用场景

  1. 取最新n个数据的操作
  2. 排行榜,取top n个数据 //最佳人气前10条
  3. 精确的设置过期时间
  4. 计数器
  5. 实时系统, 反垃圾系统
  6. pub, sub发布订阅构建实时消息系统
  7. 构建消息队列
  8. 缓存

1.2 不同类型的Value在互联网中应用

  • String:缓存、限流、计数器、分布式锁、分布式session
  • hash:存储用户信息、用户主页访问量、组合查询
  • List:微博关注人时间轴列表、简单队列
  • Set:赞、踩、标签、好友关系
  • ZSet:排行榜

1.3 常用操作指令

cmd访问redis
redis-cli.exe -h 127.0.0.1 -p 6379 如果访问的Redis服务部署在本地,则不需要 -h … -p … 参数

select 0 选择第一个库

move mykey 1 将当前的数据库key移动到某个数据库,目标库有,则不能移动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jv9ytc2p-1605670346894)(C:\Users\MI\AppData\Roaming\Typora\typora-user-images\image-20201105113524018.png)]

key

​ keys * 获取所有的key
​ flush db 清除指定库
​ randomkey 随机key
​ type key 获取key对应的value的类型

​ set key1 value1 设置key
​ get key1 获取key对应的value
​ mset key1 value1 key2 value2 key3 value3 批量设置key value
​ mget key1 key2 key3 批量获取key对应的value
​ del key1 删除key
​ exists key 判断是否存在key
​ expire key 10 对指定的key设置10秒过期
​ pexpire key 1000 对指定的key设置1000毫秒过期
​ persist key 对指定的key移出设置的过期时间,使其永不过期

string

​ getrange name 0 -1 字符串截取,范围内不存在子串则返回空
​ getset name new_cxx 设置值,返回旧值
​ mset key1 key2 批量设置
​ mget key1 key2 批量获取
​ setnx key value 不存在就插入(not exists)
​ setex key time value 过期时间(expire)
​ setrange key index value 从index开始替换value
​ incr age 递增
​ incrby age 10 递增
​ decr age 递减
​ decrby age 10 递减
​ incrbyfloat 增减浮点数
​ append 追加
​ strlen 长度
​ getbit/setbit/bitcount/bitop 位操作

hash

​ hset myhash name cxx
​ hget myhash name
​ hmset myhash name cxx age 25 note “i am notes”
​ hmget myhash name age note
​ hgetall myhash 获取所有的
​ hexists myhash name 是否存在
​ hsetnx myhash score 100 设置不存在的
​ hincrby myhash id 1 递增
​ hdel myhash name 删除
​ hkeys myhash 只取key
​ hvals myhash 只取value
​ hlen myhash 长度

list

​ lpush mylist a b c 左插入
​ rpush mylist x y z 右插入
​ lrange mylist 0 -1 输出指定范围的数据
​ lpop mylist 从左侧弹出元素
​ rpop mylist 从右侧弹出元素
​ llen mylist 长度
​ lrem mylist count value 删除指定个数的value值
​ lindex mylist 2 指定索引的值
​ lset mylist 2 n 索引设值(前提是索引必须存在,不能越界)
​ ltrim mylist 0 4 截取并保留子串
​ linsert mylist before a 插入(从左往右匹配到第一个,就执行该操作)
​ linsert mylist after a 插入
​ rpoplpush list list2 转移列表的数据

​ lpushx mylist value 判断该列表是否存在,如果不存在取消push,存在则执行push

set(不可重复)

​ sadd myset redis 插入数据到set集合
​ smembers myset 查看数据集合
​ srem myset set1 删除指定元素
​ sismember myset set1 判断元素是否在集合中(返回0或者1)
​ scard key_name 查看集合数据的个数
​ sdiff | sinter | sunion 操作:集合间运算:差集 | 交集 | 并集
​ srandmember 随机获取集合中的元素(返回但不删除)
​ spop 从集合中弹出一个元素(无序,所以弹出元素是随机的,且会删除集合中该元素)

​ smove 原集合 目标集合 数据 (原集合和目的集合必须都是set)

zset(可排序的set集合,不可重复)

​ zadd zset 1 one
​ zadd zset 2 two
​ zadd zset 3 three
​ zincrby zset 1 one 增长分数
​ zscore zset two 获取分数
​ zrange zset 0 -1 withscores 升序方式列出所有key(加上withscores会显示分数)

​ zrevrange zset 0 -1 withscores 降序方式列出所有key(加上withscores会显示分数)

​ zrangebyscore zset 10 25 withscores 指定 范围的值
​ zrangebyscore zset 10 25 withscores limit 1 2 分页
​ Zrevrangebyscore zset 10 25 withscores 指定范围的值
​ zcard zset 元素数量
​ Zcount zset 获得指定分数范围内的元素个数
​ Zrem zset one two 删除一个或多个元素
​ Zremrangebyrank zset 0 1 按照排名范围删除元素
​ Zremrangebyscore zset 0 1 按照分数范围删除元素
​ Zrank zset member 查询元素在升序set中的位置(index从0开始)
​ Zrevrank zset member 查询元素在降序set中的位置
​ Zinterstore
​ zunionstore rank:last_week 7 rank:20150323 rank:20150324 rank:20150325 weights 1 1 1 1 1 1 1

排序:
sort mylist 排序
sort mylist alpha desc limit 0 2 字母排序
sort list by it:* desc by命令
sort list by it:* desc get it:* get参数
sort list by it:* desc get it:* store sorc:result sort命令之store参数:表示把sort查询的结果集保存起来

订阅与发布:
订阅频道:subscribe chat1
发布消息:publish chat1 “hell0 ni hao”
查看频道:pubsub channels
查看某个频道的订阅者数量: pubsub numsub chat1
退订指定频道: unsubscrible chat1 , punsubscribe java.*
订阅一组频道: psubscribe java.*

redis事物:
隔离性,原子性,
步骤: 开始事务,执行命令,提交事务
multi //开启事务
sadd myset a b c
sadd myset e f g
lpush mylist aa bb cc
lpush mylist dd ff gg

服务器管理
dump.rdb
appendonly.aof
//BgRewriteAof 异步执行一个aop(appendOnly file)文件重写
会创建当前一个AOF文件体积的优化版本

//BgSave 后台异步保存数据到磁盘,会在当前目录下创建文件dump.rdb
//save同步保存数据到磁盘,会阻塞主进程,别的客户端无法连接

//client kill 关闭客户端连接
//client list 列出所有的客户端

//给客户端设置一个名称
client setname myclient1
client getname

config get port
//configRewrite 对redis的配置文件进行改写

rdb save 900 1save 300 10save 60 10000

aop备份处理appendonly yes 开启持久化appendfsync everysec 每秒备份一次

命令:bgsave异步保存数据到磁盘(快照保存)lastsave返回上次成功保存到磁盘的unix的时间戳shutdown同步保存到服务器并关闭redis服务器bgrewriteaof文件压缩处理(命令)

2. 持久化机制

2.1 Snapshot 快照机制

  1. 这种快照方式将某一时刻的所有数据写入硬盘中,当然这也是redis的默认开启持久化的方式,保存的文件以.rdb形式结尾。因此这种方式也称之为RDB方式

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第1张图片
2. 快照生成方式:

  • 客户端: BGSAVE 和 SAVE 指令

    1. 客户端使用BGSAVE(Background Save)指令时,redis会调用fork来创建一个子进程,然后子进程负责将快照写入磁盘当中,而父进程可以继续处理命令请求。这也就意味着,在BGSAVE指令下,redis的命令请求不会被阻塞。

    关于fork:当一个进程创建子进程的时候,底层的操作系统会创建该进程的一个副本,在类unix系统中创建子进程的操作会进行优化:在刚开始的时候,父子进程会共享相同内存,直到其中一方对内存进行写操作后,对被写入的内存共享才会结束。

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第2张图片

​ 2. 当使用SAVE命令时,Redis服务器会使用主进程去执行创建快照的任务,对于快照之外的任务将不会响应。
Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第3张图片

  • 服务器配置自动触发(redis.config 中配置

    1. 服务器配置方式,满足条件自动触发

      # 以下条件满足任意一次就会触发BGSAVE执行快照
      save 900 1
      save 300 10
      save 60 10000
      
    2. 服务器接收客户端shutdown命令

      服务器在接收到客户端shutdown指令以后,会调用save执行进行一次快照。对于其他命令将不会响应。

2.2 AOF Append Only File

默认配置中AOF处于关闭状态,开启AOF:在redis.config配置文件中找到Append Only Model, 设置为yes

appendonly yes

同步频率:

  • always【不推荐】

    每一条写命令都会同步到硬盘

    虽然这种同步方式可以最大程度保障数据丢失问题,但是由于硬盘的io性能有限,很可能会影响redis的性能。其中机械硬盘大概能同步200个命令/s,而固态硬盘能达到几百万条命令/s。而这种持续写入少量数据的操作可能会导致写入放大问题,极大缩短硬盘的寿命,因此SSD用户更应该慎用。

  • everysec【推荐】

    每秒执行一次同步,显式的将多条写命令同步到磁盘。

    每秒执行一次同步和不执行同步对于redis的性能影响相差无几,而且通过这种方式可以保证最多丢失一秒之间的数据。

  • no 【不推荐】

    不执行同步,由操作系统决定合适同步。

两种持久化方式可以同时开启或者同时关闭,如果同时开启,redis将会优先使用AOF,因为AOF相对更安全。

虽然AOF是比较安全的一种持久化方式,但是AOF会存在冗余记录。该记录会导致AOF文件越来越大。

2.3 AOF重写机制

重写原因:AOF文件不断冗余,导致文件过大难以维护。

重写目的:减少冗余指令,缩小AOF文件的体积。

重写的设置:

  • 客户端命令执行

    BGREWRITEAOF 命令 不会阻塞redis命令

  • 服务器配置方式自动触发

    在redis.config配置文件中配置auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size, 在启用AOF持久化时,会根据配置自动触发AOF文件重写

    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    # 该设置意义为:当AOF文件大于64Mb时,并且AOF文件比上次重写后大小增长100%时会自动触发重写
    

重写原理:重写实际上是新AOF文件覆盖旧AOF文件,并不会去读取旧AOF文件

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第4张图片

3. Java操作Redis

3.1 环境准备

    
    <dependency>
      <groupId>jedis.clientsgroupId>
      <artifactId>jedisartifactId>
      <version>3.0.0version>
    dependency>

3.2 使用Jedis对象进行操作

        //创建jedis客户端对象
        Jedis jedis = new Jedis("localhost",6379);
        //选择使用一个库 默认使用0号库
        jedis.select(0);
		        //获取redis所有key信息
//        Set keys = jedis.keys("*");
//        keys.forEach(key-> System.out.println("key: " + key));
        //清空所有的key
//        jedis.flushAll();
        //删除key
//        Long name = jedis.del("name");
//        System.out.println(name);
        //设置超时时间
//        Long age = jedis.expire("addr", 10);
//        System.out.println(age);
        //随机获取一个key
//        String s = jedis.randomKey();
//        System.out.println(s);
        //重命名一个key
//        String rename = jedis.rename("age", "newage");
//        System.out.println(rename);
        //查看值类型
        String addr = jedis.type("class");
        System.out.println(addr);
        //释放连接
        jedis.close();

4. Spring Boot 整合redis

在Spring Boot Data Redis 中提供了RedisTemplateStringRedisTemplate,StringRedisTemplate是RedisTemplate的子类,两者在方法上面基本相同,不同的是RedisTemplate中key和value的泛型是ObjectStringRedisTemplate的泛型是String

这也就意味着,RedisTemplate中可以对任意实现了序列化和反序列化对象的redis存取等操作,而StringTemplate只能支持String类型的操作。

4.1 导入依赖

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-redisartifactId>
            <version>2.3.4.RELEASEversion>
        dependency>

4.2 配置参数

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

4.3 构建测试类

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.util.Set;

@SpringBootTest(classes = SpringBootRedisApplication.class)
public class TestRedis {
     
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @BeforeEach
    public void setRedisTemplate(){
     
        this.redisTemplate.setKeySerializer(new StringRedisSerializer());
        this.redisTemplate.setValueSerializer(new StringRedisSerializer());
    }
    @Test
    public void test(){
     
        redisTemplate.opsForValue().set("fromSpringBootTest","hello redis");
        String fromSpringBootTest = (String) redisTemplate.opsForValue().get("fromSpringBootTest");
        System.out.println(fromSpringBootTest);
        Boolean age = redisTemplate.hasKey("addr");
        System.out.println(age);
        Set<String> keys = stringRedisTemplate.keys("*");
        keys.forEach(key-> System.out.println("key:" + key + ",value:" + 				  stringRedisTemplate.opsForValue().get(key)));
    }
}

测试过程中两个问题总结:

  1. 启动测试方法IDEA提示:Failed to resolve org.junit.platform:junit-platform-launcher

    解决方法:在pom中添加相关依赖

    <dependency>
                <groupId>org.junit.platformgroupId>
                <artifactId>junit-platform-launcherartifactId>
                <scope>testscope>
    dependency>
    
  2. 使用RedisTemplate写入redis时key的前部分会出现乱码

    解决方法:该错误是因为没有给redisTemplate指定KeySerializer,导致redisTemplate调用jedis SDK默认的Serializer,而默认的Serializer中使用的Outputstream使用的是ISO-8859-1编码

   this.redisTemplate.setKeySerializer(new StringRedisSerializer());

注意,如果操作的是Hash类型,则还需要设置hashKey序列化方案

   this.redisTemplate.setHashKeySerializer(new StringRedisSerializer());

4.4 绑定API

bound api:在Spring boot中使用绑定API操作Redis,这是一种对Redis更友好的操作

​ 解释:如果需要对一个key进行多次操作,则可以通过绑定的方式简化写法。

//没有绑定之前
redisTemplate.opsForValue().set("name","huangwuping");
redisTemplate.opsForValue().append("name","is a kind person");
String name = (String) redisTemplate.opsForValue().get("name");
System.out.println(name);

//绑定后
BoundValueOperations name1 = redisTemplate.boundValueOps("name");
name1.set("pingwuhuang");
name1.append("is a good person");
String object = (String) name1.get();
System.out.println(object);

总结

  • 针对key和value都是String类型的数据,可以使用StringRedisTemplate

  • 使用Redistemplate对自定义对象进行存储时,首先要对该对象实现序列化

  • 在RedisTemplate中使用String作为key和HashKey时,要指定序列化方案,推荐使用StringRedisSerializer

  • 对一个key进行多次操作的情景,可以使用绑定api简化代码

5. Redis应用场景

5.1 使用mybatis本地缓存和redis实现分布式缓存

Mybatis实现的本地缓存:默认是PerpetualCache,它实现Cache接口。其内部使用的Java自带的HashMap,通过HashMap的get和put操作实现本地缓存。PerpetualCache在HashMap基础上实现了CacheKey,以适应更复杂的缓存场景。

开启本地缓存只需要在mapper.xml文件中引入单标签



<mapper namespace="im.hwp.spring_boot_redis_cache.DAO.UserDao">
    <cache/>
    <select id="findAll" resultType="User">
        select name,age,birthday,addr from user
    select>
mapper>
  1. 自定义RedisCache类,实现Cache接口,分别实现RedisCache类中putObject,getObject和clear方法
   package im.hwp.spring_boot_redis_cache.cache;
   
   import im.hwp.spring_boot_redis_cache.utils.ApplicationContextUtils;
   import org.apache.ibatis.cache.Cache;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.data.redis.core.RedisTemplate;
   import org.springframework.data.redis.serializer.StringRedisSerializer;
   import java.util.concurrent.locks.ReadWriteLock;
   
   public class RedisCache implements Cache {
     
   
       private final String id;
       //必须存在构造方法
       public RedisCache(String id){
     
           System.out.println("=====id:" + id + " =======");
           this.id = id;
       }
       //返回cache的唯一标识
       @Override
       public String getId() {
     
           return this.id;
       }
   
       //放入缓存
       @Override
       public void putObject(Object key, Object value) {
     
           RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
           redisTemplate.setKeySerializer(new StringRedisSerializer());
           redisTemplate.setHashKeySerializer(new StringRedisSerializer());
           redisTemplate.opsForHash().put(id.toString(),key.toString(),value);
       }
   
       @Override
       public Object getObject(Object key) {
     
           RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
           Object value = redisTemplate.opsForHash().get(id.toString(), key.toString());
           return value;
       }
   	//该方法是mybatis的保留方法,可能会在后续版本中使用
       @Override
       public Object removeObject(Object o) {
     
           return null;
       }
   	//为什么要实现clear方法,是因为如果发生增删改其中任意一个,redis缓存中的信息是不会刷新的。
       //因此在进行增删改操作时,对redis缓存中的信息进行清空处理。
       @Override
       public void clear() {
     
           //当发生增删改其中任意一个时,需要对redis缓存进行清空操作
           RedisTemplate redistemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
           redistemplate.setKeySerializer(new StringRedisSerializer());
           redistemplate.setHashKeySerializer(new StringRedisSerializer());
           redistemplate.delete(id.toString());
           System.out.println("清除id:" + id.toString() + " 对应的缓存执行完毕");
   
       }
   
       @Override
       public int getSize() {
     
           return 0;
       }
   
       @Override
       public ReadWriteLock getReadWriteLock() {
     
           return null;
       }
   }
  1. 在xml文件中指定cache的type为自定义的RedisCache类全路径类名
   
   
   <mapper namespace="im.hwp.spring_boot_redis_cache.DAO.UserDao">
       <cache type="im.hwp.spring_boot_redis_cache.cache.RedisCache"/>
       <select id="findAll" resultType="User">
           select name,age,birthday,addr from user
       select>
       <select id="findById" parameterType="String" resultType="User">
           select name,age,birthday,addr from user where name = #{name}
       select>
       <delete id="deleteByName" parameterType="String">
           delete from user where name = #{name}
       delete>
   mapper>
  1. 构建测试类,测试redis缓存
   package im.hwp.spring_boot_redis_cache;
   
   import im.hwp.spring_boot_redis_cache.entity.User;
   import im.hwp.spring_boot_redis_cache.service.UserService;
   import org.junit.jupiter.api.Test;
   import org.springframework.beans.factory.annotation.Autowired;
   import org.springframework.boot.test.context.SpringBootTest;
   import java.util.List;
   
   @SpringBootTest(classes = SpringBootRedisCacheApplication.class)
   public class TestSql {
     
       @Autowired
       private UserService userService;
   
       @Test
       public void findAll(){
     
   //        List all = userService.findAll();
   //        all.forEach(user-> System.out.println(user));
   //        System.out.println("=====第二次查询======");
   //        all = userService.findAll();
   //        all.forEach(user-> System.out.println(user));
   //
           User user = userService.findById("pingwuhuang");
           System.out.println(user);
           System.out.println("========第二次查询========");
           user = userService.findById("pingwuhuang");
           System.out.println(user);
           userService.deleteByName("pingwuhuang");
       }
   }

为了清楚的看到redis缓存的作用,需要在application.proerties中开启日志

   logging.level.im.hwp.spring_boot_redis_cache.DAO=debug
  1. 问题:

    由于clear机制是以DAO接口为单位,发生增删改时会自动清除一个DAO下的所有缓存。比如当增加一个user时,UserDao对应的缓存会被清除。但是如果两个表之前存在关联关系,那么缓存中具有关联关系的缓存也应该被清除。

    因此需要引入cache-ref来保证具有关联关系的缓存能够得到清除。

   
   
   <mapper namespace="im.hwp.spring_boot_redis_cache.DAO.JobDao">
       <cache-ref namespace="im.hwp.spring_boot_redis_cache.DAO.UserDao"/>
       <select id="findAll" resultType="Job">
           select company,title from job
       select>
   mapper>

引入该cache-ref以后,Job下面的Hash缓存key将不是im.hwp.spring_boot_redis_cache.DAO.JobDao,而是im.hwp.spring_boot_redis_cache.DAO.UserDao,因此当清除Job或者User时,都会清除与之关联的缓存。

没有实现cache-ref的存储情况:

在这里插入图片描述

实现cache-ref后存储的情况:

在这里插入图片描述

  1. 缓存优化策略

    对放入redis中的key进行优化:key的长度不能太长。尽可能将key设计的简介一些。

   # key: -609484135:5256359200:im.hwp.spring_boot_redis_cache.DAO.JobDao.findAll:0:2147483647:select company,title from job:SqlSessionFactoryBean

算法:MD5 (本身用于加密处理)

特点:

  1. 一切的文件字符串等经过md5处理之后,都会生成32位16进制字符串
  2. 不同内容文件经过md5进行加密,加密结果一定不一致
  3. 相同文件的md5值始终相同(稳定性)

推荐:在redis整合mybatis过程中对key进行md5优化处理。

  1. 面试问题

    1)什么是缓存穿透

    ​ 定义:客户端多次提交一个无效的查询,该查询在redis缓存中没有,而数据库中也没有对应结果。这种情况下,每次请求进来检查redis发现没有匹配记录,于是每次都会向数据库发起查询。

    mybatis针对该情况的解决方法:在向数据库发起第一次查询以后,redis中会缓存一个对应的key以及一个值为null的记录。下次的查询直接从redis返回null。

    2)什么是缓存雪崩

    ​ 定义:在系统运行的某一时刻,系统中的缓存全部失效,恰好在这一时刻涌入大量的客户端请求,导致所有模块缓存无法利用。大量的请求涌向数据库,导致极端情况下数据库阻塞挂起。

    ​ 缓存存储时,由于业务系统非常大,模块非常多,业务数据不同,不同模块在放入缓存时会设置一个超时时间。如果大量的缓存数据在同一时刻失效,就会导致大量的请求涌向数据库导致数据库挂起。

    解决方案:

    ​ 1) 永久存储:不推荐。因为有些的数据不需要长期缓存,永久存储在redis会导致存储空间利用不高。

    ​ 2)针对不同的业务数据设置不同的超时时间,具体的设置方案要根据用户场景和需求确定。但是一定不要把所有业务模块的数据设置为同一个超时时间。

5.2 Redis主从复制

主节点对外提供服务,多个从节点只能同步主节点的数据,提供冗余备份功能。从节点无法进行自动故障转移,成为主节点对外提供服务。

原理分析: redis主从复制三大步骤:连接建立 ——> 数据同步 ——>命令传播

  1. 连接建立

    从节点内部维护 masterhostmasterport 信息,分别表示主节点ip地址和端口号

    1)从节点通过定时函数replicationCron检查主节点是否可以连接,如果能连接,通过socket创建连接。

    ​ socket连接成功以后,从节点专门为该连接创建处理复制工作的进程,负责后续的RDB文件接收,命令传播接收等。

    2)主节点接收到从节点socket连接请求后,该从节点作为连接到主节点的客户端之一,后续操作由从节点向主节点发起请求命令的 形式运行。

    root3)主节点成为客户端以后,会首次发送ping请求,目的是检测socket连接是否可用以及主节点当前能否处理请求。如果收到主节点 pong响应,则表示连接正常,否则进行重连。

    4) 如果从节点配置文件中开启了masterauth选项,则会向主节点进行身份验证。该操作通过从节点向主节点下发auth命令进行, 参数为从节点配置文件中masterauth的值。

    5) 从节点发送其监听端口号给主节点。主节点将该信息保存至对应的从节点slave_listening_port字段中。该端口号可以在主节点客 户端使用info Replication命令查看。

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第5张图片

  1. 数据同步

    1)从节点向主节点发送psync2,请求同步数据。

    2)master节点在接收到指令后,执行BGSAVE指令生成RDB文件。在生成RDB文件的同时,master节点还可以继续接收客户端的指 令。因此该部分指令需要放入命令缓冲区。

    3)master节点创建RDB文件完成后,通过Socket连接发送文件给slave节点。

    4)slave节点接收到RDB文件后,清空原有数据,从RDB文件恢复。

    5)slave节点完成RDB文件的恢复之后,告知master节点恢复完成。

    6)master节点发送缓冲区内容给slave节点(AOF)

    7)slave节点接收信息,执行bgrewriteaof,完成最后的数据同步。

    # 补充:Redis 2.8 psync1 和 Redis 4.0 psync2
    # psync1:
    # 在2.8版本之前,redis复制的秒级中断,都会触发slave节点进行全量复制(fullsync),而这种复制会影响集群的性能。
    # 为解决这种复制闪断触发全量复制的情况,redis 2.8 使用复制积压缓冲区-Replication backlog buffer,引入psync1。
    # 复制积压缓冲区大小由redis维护,默认大小为1M。一个master节点只有一个复制积压缓冲区。所有slave共享这一个。
    	1.当发生复制中断时,slave会尝试用master runid + 当前复制偏移量(offset)发送给master。
    	2.如果master runid匹配且offset值在复制积压缓冲区内,则不需要进行全量复制,否则进行全量复制。
    --------但是在slave重启,master故障切换时,还是会触发全量复制--------
    # psync2:
    # 引入master_replid1 + offset 和 master_replid2 + offset
    # master_replid1: 长度为41字节,是一个随机串,生成格式和runid相同
    # master_replid2: 初始化默认为全0,用于存储上一次master的replid1,offset默认为-1
    主要步骤
    	1.redis关闭时,把复制信息作为辅助字段(AUX Field)存储到RDB文件中
    	2.redis启动时,会把复制信息重新赋值给相关字段
    	3.redis重新同步时,会上报replid 和 offset,如果和master信息一致,且offset还在复制积压缓冲区内,则进行部分复制。
    # 注意事项
    	作为从节点的redis在重启时,主节点需要动态调整复制积压缓冲区的大小。如果不调整缓冲区大小,则当从节点在重启时,由于主节点仍然在接收指令,有可能导致从节点记录的offset被挤出缓冲区。如此,当从节点重启完成时,由于offset不在复制缓冲区,导致触发全量复制。因此需要合理计算从节点重启时间以及当前主节点每秒接收的指令情况。该操作需要在重启实例之前完成。
    #	config set动态调整redis的repl-backlog-size
    	
    
  2. 命令传播

    该阶段主要是在全量复制完成以后,同步主节点接收的每一条指令。主要思想是AOF

  3. 搭建主从复制

    1) 主节点配置文件

    # bind指允许哪些ip地址连接到该节点,多个ip用空格隔开
    # 0.0.0.0 表示允许任意ip连接
    bind 0.0.0.0
    

    2) 从节点配置文件

    bind 0.0.0.0
    # slaveof 主节点ip 主节点端口
    slaveof 127.0.0.1 6379
    
  4. 配置文件处理完成以后,需要通过配置文件启动主从节点。

    主节点启动:

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第6张图片

从节点启动:

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第7张图片

5.3 哨兵机制

  1. 名词解释

    Sentinel(哨兵)是Redis的高可用性方案:由一个或者多个Sentinel实例组成的系统可以监视任意多个主服务器,以及这些主服务器下面的所有从服务器,并且在主服务器进入下线状态时,自动将下线服务器的任意从服务器升级为主服务器。简而言之,哨兵机制就是带有自动故障转移的主从复制架构

  2. 哨兵架构示意图

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第8张图片

  1. 搭建哨兵架构

    1)首先在sentinel节点所在服务器创建一个sentinel.conf

    # sentinel monitor    
    sentinel monitor mymaster 127.0.0.1 6379 1
    

    这个配置表达的是 哨兵节点定期监控 名字叫做 并且 IP 为 端口号为 的主节点。

    表示认为主节点已经故障的票数,一般该参数设置为sentinel节点数量的一半+1

    2)启动对应的redis主从节点群

    3)启动sentinel节点,使用命令如下:

       # 方式1
       redis-server s:/redis/sentinel/sentinel.conf --sentinel
       # 方式2,需要有redis-sentinel程序才行,该程序可以从redis源码中拷贝
       redis-sentinel s:/redis/sentinel/sentinel.conf
    

    Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第9张图片

    通过以上3步即可完成哨兵节架构的搭建。

  2. 哨兵机制工作原理

    当前主从架构可能遇到的问题,当主节点宕机时,为了保证系统能够继续工作,我们需要执行以下步骤:

    1)选择一台从节点,执行slave no one指令,将其由从节点提升为主节点。

    2)将其他从节点修改为新的主节点的从节点。

    3)告知redis服务调用者新的主节点的信息。

    4)原来宕机的主节点恢复以后,将其设置为新主节点的从节点。

    由此可见,上述步骤比较繁琐,手动操作难度较大且不能保证系统的高可用性和时效性。因此哨兵机制可用于解决该类问题,简化操作。

    • 哨兵sentinel的基础配置

      sentinel monitor mymaster 127.0.0.1 6379 2
      # 除了第一行,其他配置的通用格式 sentinel [option_name] [master_name] [option_value]
      
      sentinel down-after-milliseconds mymaster 60000 
      # sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应PONG 或者是回复了一个错误消息,那么这个sentinel会主观地认为这个master已经不可用了。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒。
      
      sentinel failover-timeout mymaster 180000
      # sentinel执行failover超过设置时间时会被认为失败。将由其他sentinel继续执行failover,单位毫秒
      
      sentinel parallel-syncs mymaster 1
      # 在发生failover主从切换时,这个选项指定了最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成主从故障转移所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为主从同步而不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。
      
    • 哨兵的定时监控

      1)哨兵每隔10秒钟会向主节点和从节点发送info命令获取最新的拓扑图。因此在哨兵配置时只需要配置主节点信息,从节点信息可以通过对主节点下达info命令获取。

      2)哨兵每隔2秒会向redis主从节点的指定频道(—Sentinel—:hello)上发送该节点对于主节点的判断以及自身sentinel的信息。每个哨兵都会订阅该频道,获取其他哨兵的消息。因此,初始化哨兵时不需要在哨兵配置文件中显式定义其他的哨兵节点的ip。

      3)每个哨兵每隔1秒中就会向其他主从节点和哨兵节点发送一次ping命令做心跳检测,以判断其他节点是否正常工作。

      主观下线(SDOWN):当哨兵在心跳检测时发现在down-after-milliseconds毫秒内都是无效回复或者超时未回复,则该哨兵认为该节点是主观下线。如果该节点是主节点,则该哨兵会通过sentinel is-masterdown-by-addr指令寻求其他哨兵节点的判断。当认为主观下线的哨兵数量超过quorum设定数量时,该节点被认为是客观下线(ODOWN)

5.4 Redis Cluster 集群

  1. 原理解析

    架构图

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第10张图片

架构解析:

  1. 每个master节点负责存储数据,集群状态以及slots与nodes的对应关系。

  2. master节点具有失败检测机制,当集群中半数以上的节点认为某个节点fail,该节点就会被fail

    投票机制:集群中所有的master节点都会参与,如果半数以上的master节点与某个master节点通信超时(cluster-node-timeout),认为当前master节点挂掉。

  3. 所有的redis节点之间彼此互联(PING-PONG机制),redis使用二进制协议优化传输速度和带宽。

  4. 客户端可以与集群中任意一个节点(master或者slave)直接连接,就可以实现对集群的操作。

  5. cluster把16384个slot平均映射到所有的物理节点(master节点)上。

  6. 如果某一个master节点宕机,集群的自动故障转移将使其slave节点成为主节点,如果它并没有slave节点。那么就会导致整个集群不可用。

    整个集群fail的条件:1.某个master节点fail且没有slave节点 or 2.集群中半数以上的master宕机

2. 集群搭建

安装Ruby环境依赖(要求版本>2.3.0)

在线安装:

# 如果不能执行在线下载,则需要去官网手动下载压缩文件,然后上传至服务器进行安装
wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.5.tar.gz
tar zxvf  ruby-2.3.5.tar.gz
cd ruby-2.3.5
./configure  --prefix=/opt/ruby
ln -s /opt/ruby/bin/ruby /usr/bin/ruby
ln -s /opt/ruby/bin/gem /usr/bin/gem
# 最后检查安装是否成功
ruby -v 

安装rubygem redis依赖

# 如果无法执行下载则需要手动下载后上传至服务器
wget http://rubygems.org/downloads/redis-3.3.0.gem
gem install -l redis-3.3.0.gem
# 执行该命令时可能会报错:
ERROR:  Loading command: install (LoadError)
        cannot load such file -- zlib
ERROR:  While executing gem ... (NoMethodError)
    undefined method `invoke_with_build_args' for nil:NilClass
# 针对上述问题解决方法如下
yum -y install zlib-devel
cd ruby-2.3.5/ext/zlib
ruby ./extconf.rb
make
make install
# 继续gem的安装
gem install -l redis-4.2.2.gem

创建节点文件夹,并修改对应的配置文件

mkdir 7000 7001 7002 7003 7004 7005 7006
cp redis-5.0.0/redis.conf 7000/redis.conf
cp redis-5.0.0/redis.conf 7001/redis.conf
cp redis-5.0.0/redis.conf 7002/redis.conf
cp redis-5.0.0/redis.conf 7003/redis.conf
cp redis-5.0.0/redis.conf 7004/redis.conf
cp redis-5.0.0/redis.conf 7005/redis.conf
cp redis-5.0.0/redis.conf 7006/redis.conf

复制完成以后,修改对应的配置文件参数,以7000端口号对应节点为例

port 7000 # 端口号
bind 0.0.0.0 # 开启外部访问
appendonly yes #开启aof模式
appendfilename "appendonly-7000.aof" # 文件名加上端口号用于区分
dbfilename dump-7000.rdb # rdb快照机制文件名
daemonize yes # 后台运行,不需要保留前端窗口

启动7个节点

bin/redis-server 7000/redis.conf
bin/redis-server 7001/redis.conf
bin/redis-server 7002/redis.conf
bin/redis-server 7003/redis.conf
bin/redis-server 7004/redis.conf
bin/redis-server 7005/redis.conf
bin/redis-server 7006/redis.conf

启动完成后使用命令检查redis启动情况

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第11张图片

创建集群

  • 首先复制redis源码包下的redis-trib.rb文件到bin目录下

    [root@localhost src]# cp redis-trib.rb ../../bin/redis-trib.rb
    
  • 然后在bin目录下执行创建集群的命令

    注意:redis 5.x版本以后不支持使用redis-trib.rb创建集群,直接使用redis-cli即可

    redis-cli --cluster help
    Cluster Manager Commands:
      create         host1:port1 ... hostN:portN   #创建集群
                     --cluster-replicas       #从节点个数
      check          host:port                     #检查集群
                     --cluster-search-multiple-owners #检查是否有槽同时被分配给了多个节点
      info           host:port                     #查看集群状态
      fix            host:port                     #修复集群
                     --cluster-search-multiple-owners #修复槽的重复分配问题
      reshard        host:port                     #指定集群的任意一节点进行迁移slot,重新分slots
                     --cluster-from           #需要从哪些源节点上迁移slot,可从多个源节点完成迁移,以逗号隔开,传递的是节点的node id,还可以直接传递--from all,这样源节点就是集群的所有节点,不传递该参数的话,则会在迁移过程中提示用户输入
                     --cluster-to             #slot需要迁移的目的节点的node id,目的节点只能填写一个,不传递该参数的话,则会在迁移过程中提示用户输入
                     --cluster-slots          #需要迁移的slot数量,不传递该参数的话,则会在迁移过程中提示用户输入。
                     --cluster-yes                 #指定迁移时的确认输入
                     --cluster-timeout        #设置migrate命令的超时时间
                     --cluster-pipeline       #定义cluster getkeysinslot命令一次取出的key数量,不传的话使用默认值为10
                     --cluster-replace             #是否直接replace到目标节点
      rebalance      host:port                                      #指定集群的任意一节点进行平衡集群节点slot数量 
                     --cluster-weight          #指定集群节点的权重
                     --cluster-use-empty-masters                    #设置可以让没有分配slot的主节点参与,默认不允许
                     --cluster-timeout                         #设置migrate命令的超时时间
                     --cluster-simulate                             #模拟rebalance操作,不会真正执行迁移操作
                     --cluster-pipeline                        #定义cluster getkeysinslot命令一次取出的key数量,默认值为10
                     --cluster-threshold                       #迁移的slot阈值超过threshold,执行rebalance操作
                     --cluster-replace                              #是否直接replace到目标节点
      add-node       new_host:new_port existing_host:existing_port  #添加节点,把新节点加入到指定的集群,默认添加主节点
                     --cluster-slave                                #新节点作为从节点,默认随机一个主节点
                     --cluster-master-id                       #给新节点指定主节点
      del-node       host:port node_id                              #删除给定的一个节点,成功后关闭该节点服务
      call           host:port command arg arg .. arg               #在集群的所有节点执行相关命令
      set-timeout    host:port milliseconds                         #设置cluster-node-timeout
      import         host:port                                      #将外部redis数据导入集群
                     --cluster-from                            #将指定实例的数据导入到集群
                     --cluster-copy                                 #migrate时指定copy
                     --cluster-replace                              #migrate时指定replace
      help           
    
    For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.
    
    # redis-cli --cluster create 
    redis-cli --cluster create 192.168.1.188:7000 192.168.1.188:7001 192.168.1.188:7002 192.168.1.188:7003 192.168.1.188:7004 192.168.1.188:7005 --cluster-replicas 1
    

    创建结束,结果如下:

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第12张图片

  1. 集群使用

    查看集群状态–任意选择集群中一个节点即可

    redis-cli --cluster check 192.168.1.188:7000
    

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第13张图片
连接集群:使用redis-cli连接集群的任意节点即可, 需要使用 -c 声明是集群模式

redis-cli -h 192.168.1.188 -p 7000 -c

在存取值时,会使用CRC16算法对key进行计算slot,根据计算得到的slot值重定向到指定的节点进行相关操作。

Redis学习笔记:Redis主从复制,哨兵机制(Sentinel),集群(Cluster),Spring Boot 整合Redis集群实现分布式session管理_第14张图片

重启redis cluster且保留原有集群配置和数据

# 首先杀掉全部redis进程
pkill -9 redis
# 通过配置文件重新启动每个redis节点
bin/redis-server 7000/redis.conf
# 每个节点都启动完成后,集群状态会自动恢复
bin/redis-cli -c -h 172.31.7.188 -p 7000
172.31.7.188:7000> hset abc aaa bbb
-> Redirected to slot [7638] located at 172.31.7.188:7001
(integer) 1

5.5 Spring Boot整合redis cluster

  1. 导入依赖

          
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-data-redisartifactId>
                
                <exclusions>
                    <exclusion>
                        <groupId>io.lettucegroupId>
                        <artifactId>lettuce-coreartifactId>
                    exclusion>
                exclusions>
            dependency>
    		
            <dependency>
                <groupId>redis.clientsgroupId>
                <artifactId>jedisartifactId>
            dependency>
            
            <dependency>
                <groupId>org.springframework.sessiongroupId>
                <artifactId>spring-session-data-redisartifactId>
            dependency>
            <dependency>
                <groupId>org.apache.commonsgroupId>
                <artifactId>commons-pool2artifactId>
            dependency>
    
  2. 在启动类添加**@EnableRedisHttpSession**注释,该注释表明session将由redis进行管理

    @SpringBootApplication
    @EnableRedisHttpSession
    public class RedisSessionManageApplication {
           
    
        public static void main(String[] args) {
           
            SpringApplication.run(RedisSessionManageApplication.class, args);
        }
    
    }
    
    
  3. 编写配置文件

    server.port=8080
    server.servlet.context-path=/
    
    # host:port形式,节点直接以 , 隔开。
    spring.redis.cluster.nodes=172.31.7.188:7000,172.31.7.188:7001,172.31.7.188:7002,172.31.7.188:7003,172.31.7.188:7004,172.31.7.188:7005
    
  4. 编写测试类

    @Controller
    @RequestMapping("test")
    public class TestController {
           
        @RequestMapping("test")
        public void test(HttpServletRequest httpServletRequest,HttpServletResponse response) throws IOException {
           
            List<String> list = (List<String>) httpServletRequest.getSession().getAttribute("list");
            if(list==null){
           
                list = new ArrayList<String>();
            }
            list.add("xxxxxxxxx");
            httpServletRequest.getSession().setAttribute("list",list);
            response.getWriter().println("size:" + list.size());
            response.getWriter().print("sessionId:"+httpServletRequest.getSession().getId());
        }
    }
    
  5. 进行测试

    • session的管理完全交给redis,每次对session内的attribute进行修改时,需要进行set操作保证其刷新到redis中去。
    • 查看redis节点,发现不同的session保存在不同的节点上,即session的分布式存储。

你可能感兴趣的:(redis,redis)