原创

    Redis 高级教程【三】

    第一章:三种特殊数据类型

    一、geospatial-地理位置

    有没有想过,微信定位,附近的人,打车距离计算等功能 是如何实现的?用 geospatial 地理位置就可以实现。这种数据类型是 根据经纬度计算两地之间的距离 【直线距离,两个极点除外】


    那我们该如何去使用呢?先查询好经纬度:经纬度查询 ,然后进行各种操作。 如下图:

    小提示: 我们 至少需要两个点才能进行操作, 越多越好。以下是我准备的几个城市的经纬度:

    北京:  经度: 116.23128      纬度: 40.22077
    上海:  经度: 121.48941      纬度: 31.40527
    广州:  经度: 113.27324      纬度: 23.15792
    深圳:  经度: 113.88308      纬度: 22.55329
    
    佛山:  经度: 112.89262      纬度: 22.90026
    成都:  经度: 104.10194      纬度: 30.65984
    重庆:  经度: 106.54041      纬度: 29.40268
    

    有了上述的经纬度之后,我们就可以使用命令进行操作了。 其实手动添加数据是真的烦,如果要添加 1000 个城市甚至更多,那我们岂不是要累死了。所以,我们可以自己通过写 Java 程序,去读取一个相关的配置文件,然后把多个城市或者全国的城市的坐标添加进去。【这个就自己实现了哈!!!】接下来开始命令操作吧!!!

    1、GEOADD

    官方介绍: GEOADD 命令 将指定的地理空间位置 (经度、纬度、名称) 添加到指定的 key 中。这些数据将会存储到 sorted set 集合【所以 geospatial 它的底层就是有序集合】 。这样的目的是为了方便使用 GEORADIUS 命令 或者 GEORADIUSBYMEMBER 命令 对数据进行半径查询等操作。

    注意事项:

    1、有效的经度 从 -180 度到 180 度
    2、有效的纬度 从 -85.05112878 度到 85.05112878 度【所以两个极点是没有坐标的】
    3、当坐标位置超出上述指定范围时,该命令将会返回一个错误

    # 使用 【GEOADD 命令】添加地理位置
    
    127.0.0.1:6379> geoadd ChinaCity 116.23 40.22 BeiJing
    127.0.0.1:6379> geoadd ChinaCity 121.48 31.41 ShangHai
    127.0.0.1:6379> geoadd ChinaCity 112.89 22.90 FoShan
    127.0.0.1:6379> geoadd ChinaCity 113.27 23.15 GuangZhou
    127.0.0.1:6379> geoadd ChinaCity 113.88 22.55 ShenZhen
    127.0.0.1:6379> geoadd ChinaCity 104.10 30.66 ChengDu
    127.0.0.1:6379> geoadd ChinaCity 106.54 29.40 ChongQing
    

    通过上述命令,添加操作完成了。【 GEOADD 命令 是不是挺简单的,哈哈】 操作如下图:

    2、GEOPOS

    官方介绍: GEOPOS 命令 从 key 里返回所有给定位置元素的位置(经度和纬度)。返回值:该命令返回一个数组。 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。当给定的位置元素不存在时, 对应的数组项为空值。

    # 【GEOPOS命令】获取位置
    
    127.0.0.1:6379> geopos ChinaCity BeiJing
    127.0.0.1:6379> geopos ChinaCity ShangHai FoShan
    

    到这为止,获取操作完成了。【 GEOPOS 命令 是不是也挺简单的,哈哈】如下图:

    3、GEODIST

    官方介绍:GEODIST 命令 返回两个给定位置之间的距离。如果两个位置之间的其中一个不存在, 那么命令返回空值。 指定的单位如下:

    # 指定单位的 unit 参数必须是以下单位的其中一个:
    m   ==>  表示单位为米
    km  ==>  表示单位为千米
    mi  ==>  表示单位为英里
    ft  ==>  表示单位为英尺
    
    # 如果用户没有显式地指定单位参数, 那么【GEODIST 命令】默认使用米作为单位
    # 【GEODIST 命令】在计算距离时会假设地球为完美的球形。在极限情况下, 这一假设最大会造成 0.5% 的误差
    

    GEODIST 命令 的具体操作如下图:

    4、GEORADIUS

    GEORADIUS 命令 以给定的经纬度为中心, 然后返回与中心的距离不超过给定最大距离的所有的位置元素。 更多详细介绍如下:

    # 范围可以使用以下其中一个单位:
    m   ==>  表示单位为米
    km  ==>  表示单位为千米
    mi  ==>  表示单位为英里
    ft  ==>  表示单位为英尺
    
    # 在给定以下可选项时, 命令会返回额外的信息:
    WITHDIST:  在返回位置元素的同时,将该元素与中心之间的距离也一并返回。距离的单位和用户给定的范围单位保持一致
    WITHCOORD: 将位置元素的经度和维度也一并返回
    WITHHASH:  以 52 位有符号整数的形式,返回位置元素经过原始 geohash 编码的有序集合分值。 
               这个选项主要用于底层应用或者调试, 实际中的作用并不大
    
    # 命令默认返回未排序的位置元素。 通过以下两个参数, 用户可以指定被返回位置元素的排序方式:
    ASC:  根据中心的位置, 按照【从近到远】的方式返回位置元素
    DESC: 根据中心的位置, 按照【从远到近】的方式返回位置元素
    
    # 该命令可以用于查找附近的人,通过半径查询,即圆形搜索。Over!!!
    

    在默认情况下, GEORADIUS 命令 会返回所有匹配的位置元素 。 虽然用户可以使用 COUNT <count> 选项去获取前 N 个匹配元素, 但是因为命令在内部可能会需要对所有被匹配的元素进行处理, 所以在对一个非常大的区域进行搜索时, 即使只使用 COUNT 选项去获取少量元素, 命令的执行速度也可能会非常慢。 但是从另一方面来说, 使用 COUNT 选项去减少需要返回的元素数量, 对于减少带宽来说仍然是非常有用的

    # 【GEORADIUS 命令】使用举例如下:
    
    127.0.0.1:6379> georadius ChinaCity 110 30 1000 km
    127.0.0.1:6379> georadius ChinaCity 110 30 1000 km withdist
    127.0.0.1:6379> georadius ChinaCity 110 30 1000 km withcoord
    127.0.0.1:6379> georadius ChinaCity 110 30 1000 km count 1
    127.0.0.1:6379> georadius ChinaCity 110 30 1000 km count 2
    

    上述命令对应的结果如下图:

    5、GEORADIUSBYMEMBER

    GEORADIUSBYMEMBER 这个命令和 GEORADIUS 命令 一样, 都可以找出位于指定范围内的元素 。 但是 GEORADIUSBYMEMBER 命令 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点。 简而言之,GEORADIUSBYMEMBER 命令就是指定成员的位置被用作查询的中心。

    # 【GEORADIUSBYMEMBER 命令】使用举例如下:
    
    127.0.0.1:6379> georadiusbymember ChinaCity FoShan 1000 km
    127.0.0.1:6379> georadiusbymember ChinaCity FoShan 100 km
    

    上述命令操作d的结果如下图:

    6、GEOHASH

    GEOHASH 命令 返回一个或多个位置元素的 Geohash 表示【了解一下即可】。

    命令和结果

    特别提示:GEOADD 命令 中有这么一句话: 这些数据将会存储到 sorted set 集合。 由此可知,geospatial 类型的底层是 sorted set ,所以我们可以使用有序集合的命令来操作 geospatial 类型 。具体操作如下图:

    二、HyperLogLog 类型

    官方介绍: HyperLogLog 是用来做基数统计的算法,它的的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。


    那么问题来了,什么是基数? 比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为 5 。 基数估计就是在误差可接受的范围内,快速计算基数。

    # HyperLogLog 应用场景如下:
    
    1、网站用户访问量(一个用户访问多次,但还是算作一个人)
    2、统计注册 IP 数
    3、统计每日访问 IP 数
    4、统计页面实时 UV 数
    5、统计在线用户数
    6、统计用户每天搜索不同词条的个数
    

    HyperLogLog 类型 的命令只有三个,比较少【详情参阅官网】。 具体介绍如下:

    # HyperLogLog 类型的【三个命令】如下:
    
    # 添加指定元素到 HyperLogLog 中
    PFADD key element [element ...]
    
    # 返回给定 HyperLogLog 的基数估算值
    PFCOUNT key [key ...]
    
    # 将多个 HyperLogLog 合并为一个 HyperLogLog
    PFMERGE destkey sourcekey [sourcekey ...]
    

    TIPS: 如果允许容错, 那么 统计功能 的实现建议使用 HyperLogLog 类型 ^_^

    三、Bitmaps 位图类型

    Bitmaps 的底层是 字符串类型,所以它的命令是在 字符串类型命令 里面【详情参阅官网】 。其部分命令如下:

    # Bitmaps 部分命令
    
    # 【Setbit 命令】用于对 key 所储存的字符串值设置或清除指定偏移量上的位(bit)
    SETBIT KEY_NAME OFFSET VALUE
    
    # 【Getbit 命令】用于对 key 所储存的字符串值,获取指定偏移量上的位(bit)
    GETBIT KEY_NAME OFFSET
    
    # 【Bitcount 命令】用于获取 Bitmaps 指定范围值为 1 的个数
    BITCOUNT KEY [start end]
    
    # Bitmaps 相关命令举例如下【一周 7 天的打卡情况。1 表示打卡了,0 表示未打卡】:
    127.0.0.1:6379> setbit sign 1 0
    127.0.0.1:6379> setbit sign 2 1
    127.0.0.1:6379> setbit sign 3 0
    127.0.0.1:6379> setbit sign 4 1
    127.0.0.1:6379> setbit sign 5 0
    127.0.0.1:6379> setbit sign 6 1
    127.0.0.1:6379> setbit sign 7 0         # SETBIT 命令
    127.0.0.1:6379> getbit sign 4           # GETBIT 命令
    127.0.0.1:6379> bitcount sign           # BITCOUNT 命令
    

    TIPS: 到此为止,三种特殊的数据类型 就全部学习完了 ~ ~ ~

    第二章:Redis 事务

    一、Redis 事务介绍

    Redis 事务本质 是一组命令的集合!事务支持一次执行多个命令,一个事务中所有命令都会被序列化。 在事务执行过程中,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。总结地说: Redis 事务就是 一次性、顺序性、排他性 的执行一个队列中的一系列命令。


    Redis 事务没有隔离级别的概念: 批量操作在发送 EXEC 命令前被放入队列缓存,并不会被实际执行,也就不存在事务内的查询要看到事务里的更新,事务外查询不能看到。


    Redis 不保证原子性: 单个 Redis 命令的执行是原子性的,但事务不保证原子性,且没有回滚。事务中任意命令执行失败,其余的命令仍会被执行。也就是说, Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。

    # Redis 事务的三个阶段如下:
    
    1、开始事务【multi】
    2、命令入队【……】
    3、执行事务【exec】
    

    二、Redis 事务相关命令

    # Redis 事务相关命令如下:
    
    multi      ==>    标记一个事务块的开始( queued )
    exec       ==>    执行所有事务块的命令( 一旦执行 exec 后,之前加的监控锁都会被取消掉)
    discard    ==>    取消事务,放弃事务块中的所有命令
    unwatch    ==>    取消 watch 对所有 key 的监控
    
    watch key1 key2 ...   ==>     监视一或多个 key
    # 注意:watch 命令如果在事务执行之前,被监视的 key 被其他命令改动,则事务被打断 ( 类似乐观锁 )
    

    编译型异常: 这个会导致提交失败。 失败原因:命令错误或者语法错误。 如下图:

    运行时异常: 事务提交能够成功。具体原因详见下方 TIPS 。如下图:

    TIPS: 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚 ,也不会造成后续的指令不做。

    三、Redis 实现乐观锁

    乐观锁: 顾名思义,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁。 但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和 CAS 算法实现。 乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。


    悲观锁: 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java 中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。


    两种锁的使用场景: 从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。


    Redis 监视测试, 相关命令如下:

    # Redis 监视测试
    
    127.0.0.1:6379> set money 1000
    127.0.0.1:6379> set spend 0
    127.0.0.1:6379> watch money
    127.0.0.1:6379> multi
    127.0.0.1:6379> decrby money 200
    127.0.0.1:6379> incrby spend 200
    127.0.0.1:6379> exec
    

    在这种单线程的操作下,结果没有出现问题【等会我们开多个线程,结果就不一样了,哈哈】。 如下图:

    接下来,我们需要再开启一条线程,即线程二。 如下图:

    线程一 线程二

    此时,我们的 watch 命令 就充当了 Redis 的乐观锁 操作。最终结果是线程一的事务提交失败。 结果如下图:

    线程二

    那么我们该如何来解决这个问题嘞? 首先 如果发现事务提交失败,那么就先解除监控。第二 既然修改失败,那就获取最新的值再次进行监控。最后 执行相应的操作。 请看下图:

    四、Spring Boot 整合 Redis

    Spring Boot 是未来的主流,所以使用它 集成 Redis 是非常有必要的。接下来就逐步介绍集成过程及其使用。【这部分的代码都安排好了,点击右侧即可下载: 代码资源下载


    第一步: 创建 Spring Boot 项目。【这个就自行创建,这里不过多介绍了】 接着我们导入依赖如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId><!--netty-->
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-test</artifactId>
        <version>2.2.9.RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>RELEASE</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.8.RELEASE</version>
        <scope>test</scope>
    </dependency>
    

    注意事项:

    1、在 Spring Boot 2.x 之后,原来使用的 Jedis 被替换成了 lettuce
    2、Jedis 采用直连方式连接,多个线程操作是不安全的。如需避免这种问题,就需要使用 Jedis 连接池技术。【类似 BIO 模式】
    3、lettuce 的底层采用 netty ,实例可以在多个线程中共享,所以不存在线程安全问题了。【类似 NIO 模式】

    第二步: 编写我们的 application.properties 配置文件。 代码如下:

    # 由于当时 8080 端口被占用,所以就把端口设置为了 8082
    server.port=8082
    
    # 设置 Redis 的主机
    spring.redis.host=127.0.0.1
    
    # 设置 Redis 的端口号
    spring.redis.port=6379
    

    第三步: 编写我们的单元测试代码,并进行测试。 代码和测试结果如下:

    package club.guoshizhan;
    
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.test.context.junit4.SpringRunner;
    
    import javax.annotation.Resource;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 16:53
     * @Description: 测试类
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    class MyRedisApplicationTest {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Test
        void contextLoads() {
    
            /**
             * RedisTemplate    模板的相关介绍
             * 1、opsForValue   用于操作 String
             * 2、opsForList    用于操作 List
             * 3、opsForset     用于操作 Set
             * ……
             */
            redisTemplate.opsForValue().set("Language", "Java");
            System.out.println(redisTemplate.opsForValue().get("Language"));
    
        }
    
    }
    

    Redis 的测试结果 如下:

    五、自定义 redisTemplate

    由上图可知,我们存储的键乱码了。为什么呢?这个是序列化的问题。 先看下图:

    那我们如何来解决这个问题呢? 这个时候我们需要 自定义 redisTemplate ,这样便可解决序列化问题。先按下图把对应的包和类创建好,然后再开始编写我们自定义的类。 如下图:

    接着在 guoshizhan 包下新建 entity 包,然后在 entity 包里新建 User 类。【还未实现序列化】 代码如下:

    package club.guoshizhan.entity;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 16:56
     * @Description: User 实体类
     */
    @Component
    public class User {
    
        private String name;
        private int age;
        private String address;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        public User(String name, int age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    
        public User() {
        }
    
        @Override
        public String toString() {
            return "User{" + "name='" + name + '\'' + ", age=" + age + ", address='" + address + '\'' + '}';
        }
    
    }
    

    此时我们去编写一个测试方法,把这个方法放到 MyRedisApplicationTest 类 中。 代码如下:

    @Test
    public void testUser() {
        // 开发中一般使用 json 来传递对象
        User user = new User("Java", 25, "China");
        String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user", jsonUser);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }
    

    测试结果以及 后续的一点儿小改动 如下图:

    到这里为止,我们控制台输出的信息是不会乱码的,但是我们的 Redis 客户端却会出现乱码 。因为我们还使用的是系统默认的序列化方式,我们还没有自定义我们的 redisTemplate 。 所以,我们现在就来自定义我们的 redisTemplate 吧!!!


    第一步: 编写我们的 RedisConfig 类。 代码如下:

    package club.guoshizhan.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    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.net.UnknownHostException;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 16:59
     * @Description: Redis 配置类
     */
    @Configuration
    public class RedisConfig {
    
        // 编写我们自己的 redisTemplate 【代码来源于:RedisAutoConfiguration 类】
        @Bean
        @SuppressWarnings("all")
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) throws UnknownHostException {
    
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(factory);    // 连接工厂
    
            Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);    // json 序列化配置
            ObjectMapper objectMapper = new ObjectMapper();
            objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            serializer.setObjectMapper(objectMapper);
    
            StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();    // String 的序列化
    
            template.setKeySerializer(stringRedisSerializer);        // key 采用 string 的序列化方式
            template.setHashKeySerializer(stringRedisSerializer);    // hash 的 key 采用 String 的序列化方式
            template.setValueSerializer(serializer);                 // value 序列化方式采用 jackson
            template.setHashValueSerializer(serializer);             // hash 的 value 序列化方式采用 jackson
            template.afterPropertiesSet();
    
            return template;
    
        }
    
    }
    

    第二步: 编写我们的 MyRedisApplicationTest 类。 代码如下:

    package club.guoshizhan;
    
    import club.guoshizhan.entity.User;
    import org.junit.jupiter.api.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.test.context.junit4.SpringRunner;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 17:03
     * @Description: 测试类
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    class MyRedisApplicationTest {
    
        @Autowired
        @Qualifier("redisTemplate")  // 指定到我们自己编写的 redisTemplate
        private RedisTemplate redisTemplate;
    
        @Test
        public void testUser() {
            // 开发中一般使用 json 来传递对象
            User user = new User("Java", 25, "China");
            redisTemplate.opsForValue().set("user", user);
            System.out.println(redisTemplate.opsForValue().get("user"));
        }
    
    }
    

    第三步: 点击测试方法进行测试 结果如下图:

    六、自定义 Redis 工具类

    我们发现 使用系统默认的命令来操作 Redis 比较麻烦,我们想要自己定义一个 Redis 工具类来方便我们的操作。安排!!!


    第一步: 我们在 guoshizhan 包下新建 utils 包,然后在 utils 包里新建 RedisUtils 工具类。 代码如下:

    package club.guoshizhan.utils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    
    import java.util.List;
    import java.util.Map;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 17:13
     * @Description: 自定义 Redis 工具类
     */
    @Component
    public class RedisUtils {
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        @Autowired
        private StringRedisTemplate stringRedisTemplate;
    
        /**
         * 指定缓存失效时间
         *
         * @param key  键
         * @param time 时间(秒)
         * @return
         */
        public boolean expire(String key, long time) {
            try {
                if (time > 0) {
                    redisTemplate.expire(key, time, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 根据key 获取过期时间
         *
         * @param key 键 不能为null
         * @return 时间(秒) 返回0代表为永久有效
         */
        public long getExpire(String key) {
            return redisTemplate.getExpire(key, TimeUnit.SECONDS);
        }
    
        /**
         * 判断key是否存在
         *
         * @param key 键
         * @return true 存在 false不存在
         */
        public boolean hasKey(String key) {
            try {
                return redisTemplate.hasKey(key);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 删除缓存
         *
         * @param key 可以传一个值 或多个
         */
        @SuppressWarnings("unchecked")
        public void del(String... key) {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        }
    
        // ============================String=============================
    
        /**
         * 普通缓存获取
         *
         * @param key 键
         * @return 值
         */
        public Object get(String key) {
            return key == null ? null : redisTemplate.opsForValue().get(key);
        }
    
        /**
         * 普通缓存放入
         *
         * @param key   键
         * @param value 值
         * @return true成功 false失败
         */
        public boolean set(String key, Object value) {
            try {
                redisTemplate.opsForValue().set(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
    
        }
    
        /**
         * 普通缓存放入并设置时间
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long time) {
            try {
                if (time > 0) {
                    redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
                } else {
                    set(key, value);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 递增
         *
         * @param key 键
         * @return
         */
        public long incr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递增因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, delta);
        }
    
        /**
         * 递减
         *
         * @param key 键
         * @return
         */
        public long decr(String key, long delta) {
            if (delta < 0) {
                throw new RuntimeException("递减因子必须大于0");
            }
            return redisTemplate.opsForValue().increment(key, -delta);
        }
    
        // ================================Map=================================
    
        /**
         * HashGet
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return 值
         */
        public Object hget(String key, String item) {
            return redisTemplate.opsForHash().get(key, item);
        }
    
        /**
         * 获取hashKey对应的所有键值
         *
         * @param key 键
         * @return 对应的多个键值
         */
        public Map<Object, Object> hmget(String key) {
            return redisTemplate.opsForHash().entries(key);
        }
    
        /**
         * HashSet
         *
         * @param key 键
         * @param map 对应多个键值
         * @return true 成功 false 失败
         */
        public boolean hmset(String key, Map<String, Object> map) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * HashSet 并设置时间
         *
         * @param key  键
         * @param map  对应多个键值
         * @param time 时间(秒)
         * @return true成功 false失败
         */
        public boolean hmset(String key, Map<String, Object> map, long time) {
            try {
                redisTemplate.opsForHash().putAll(key, map);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         *
         * @param key   键
         * @param item  项
         * @param value 值
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 向一张hash表中放入数据,如果不存在将创建
         *
         * @param key   键
         * @param item  项
         * @param value 值
         * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
         * @return true 成功 false失败
         */
        public boolean hset(String key, String item, Object value, long time) {
            try {
                redisTemplate.opsForHash().put(key, item, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 删除hash表中的值
         *
         * @param key  键 不能为null
         * @param item 项 可以使多个 不能为null
         */
        public void hdel(String key, Object... item) {
            redisTemplate.opsForHash().delete(key, item);
        }
    
        /**
         * 判断hash表中是否有该项的值
         *
         * @param key  键 不能为null
         * @param item 项 不能为null
         * @return true 存在 false不存在
         */
        public boolean hHasKey(String key, String item) {
            return redisTemplate.opsForHash().hasKey(key, item);
        }
    
        /**
         * hash递增 如果不存在,就会创建一个 并把新增后的值返回
         *
         * @param key  键
         * @param item 项
         * @param by   要增加几(大于0)
         * @return
         */
        public double hincr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, by);
        }
    
        /**
         * hash递减
         *
         * @param key  键
         * @param item 项
         * @param by   要减少记(小于0)
         * @return
         */
        public double hdecr(String key, String item, double by) {
            return redisTemplate.opsForHash().increment(key, item, -by);
        }
    
        // ============================set=============================
    
        /**
         * 根据key获取Set中的所有值
         *
         * @param key 键
         * @return
         */
        public Set<Object> sGet(String key) {
            try {
                return redisTemplate.opsForSet().members(key);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 根据value从一个set中查询,是否存在
         *
         * @param key   键
         * @param value 值
         * @return true 存在 false不存在
         */
        public boolean sHasKey(String key, Object value) {
            try {
                return redisTemplate.opsForSet().isMember(key, value);
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将数据放入set缓存
         *
         * @param key    键
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSet(String key, Object... values) {
            try {
                return redisTemplate.opsForSet().add(key, values);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 将set数据放入缓存
         *
         * @param key    键
         * @param time   时间(秒)
         * @param values 值 可以是多个
         * @return 成功个数
         */
        public long sSetAndTime(String key, long time, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().add(key, values);
                if (time > 0) {
                    expire(key, time);
                }
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 获取set缓存的长度
         *
         * @param key 键
         * @return
         */
        public long sGetSetSize(String key) {
            try {
                return redisTemplate.opsForSet().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 移除值为value的
         *
         * @param key    键
         * @param values 值 可以是多个
         * @return 移除的个数
         */
        public long setRemove(String key, Object... values) {
            try {
                Long count = redisTemplate.opsForSet().remove(key, values);
                return count;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
        // ===============================list=================================
    
        /**
         * 获取list缓存的内容
         *
         * @param key   键
         * @param start 开始
         * @param end   结束 0 到 -1代表所有值
         * @return
         */
        public List<Object> lGet(String key, long start, long end) {
            try {
                return redisTemplate.opsForList().range(key, start, end);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 获取list缓存的长度
         *
         * @param key 键
         * @return
         */
        public long lGetListSize(String key) {
            try {
                return redisTemplate.opsForList().size(key);
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    
        /**
         * 通过索引 获取list中的值
         *
         * @param key   键
         * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
         * @return
         */
        public Object lGetIndex(String key, long index) {
            try {
                return redisTemplate.opsForList().index(key, index);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, Object value) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        public boolean lSet(String key, Object value, long time) {
            try {
                redisTemplate.opsForList().rightPush(key, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @return
         */
        public boolean lSet(String key, List<Object> value) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 将list放入缓存
         *
         * @param key   键
         * @param value 值
         * @param time  时间(秒)
         * @return
         */
        public boolean lSet(String key, List<Object> value, long time) {
            try {
                redisTemplate.opsForList().rightPushAll(key, value);
                if (time > 0) {
                    expire(key, time);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 根据索引修改list中的某条数据
         *
         * @param key   键
         * @param index 索引
         * @param value 值
         * @return
         */
        public boolean lUpdateIndex(String key, long index, Object value) {
            try {
                redisTemplate.opsForList().set(key, index, value);
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 移除N个值为value
         *
         * @param key   键
         * @param count 移除多少个
         * @param value 值
         * @return 移除的个数
         */
        public long lRemove(String key, long count, Object value) {
            try {
                Long remove = redisTemplate.opsForList().remove(key, count, value);
                return remove;
            } catch (Exception e) {
                e.printStackTrace();
                return 0;
            }
        }
    }
    

    第二步: 编写我们的 MyRedisApplicationTest 类。 代码如下:

    package club.guoshizhan;
    
    import club.guoshizhan.utils.RedisUtils;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.junit4.SpringRunner;
    
    /**
     * @Author: guoshizhan
     * @Create: 2021/08/13 17:26
     * @Description: 测试类
     */
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class MyRedisApplicationTest {
    
        @Autowired
        private RedisUtils redisUtils;
    
        @Test
        public void redisUtilsTest() {
            redisUtils.set("name", "Java");
            System.out.println(redisUtils.get("name"));
        }
    
    }
    

    第三步: 点击测试方法进行测试。 结果如下图:

    第三章:Redis 配置文件详解

    一、基础配置

    1、内存配置

    当需要配置内存大小时,可以使用 1k、5gb、4m 等类似的格式进行配置。 其转换方式如下 【不区分大小写】

    # Note on units: when memory size is needed, it is possible to specify
    # it in the usual form of 1k 5GB 4M and so forth:
    #
    # 1k => 1000 bytes
    # 1kb => 1024 bytes
    # 1m => 1000000 bytes
    # 1mb => 1024*1024 bytes
    # 1g => 1000000000 bytes
    # 1gb => 1024*1024*1024 bytes
    #
    # units are case insensitive so 1GB 1Gb 1gB are all the same.(对大小写不敏感)
    

    2、文件引入

    引入其他的配置文件【做集群需要多个配置文件,这个时候此处就有用处了】。 具体配置如下:

    ################################## INCLUDES ###################################
    
    # Include one or more other config files here.  This is useful if you
    # have a standard template that goes to all Redis servers but also need
    # to customize a few per-server settings.  Include files can include
    # other files, so use this wisely.
    #
    # Notice option "include" won't be rewritten by command "CONFIG REWRITE"
    # from admin or Redis Sentinel. Since Redis always uses the last processed
    # line as value of a configuration directive, you'd better put includes
    # at the beginning of this file to avoid overwriting config change at runtime.
    #
    # If instead you are interested in using includes to override configuration
    # options, it is better to use include as the last line.
    #
    # include /path/to/local.conf
    # include /path/to/other.conf
    

    3、模块加载

    Redis 在启动时加载哪些模块。 具体配置如下:

    ################################## MODULES #####################################
    
    # Load modules at startup. If the server is not able to load modules
    # it will abort. It is possible to use multiple loadmodule directives.
    #
    # loadmodule /path/to/my_module.so
    # loadmodule /path/to/other_module.so
    

    4、网络配置

    配置 Redis 的主机、端口等等。 常用配置如下:

    # 指定 Redis 只能接受来自此 IP 绑定的网卡的请求,注意此默认值默认外网是不可访问的
    bind 127.0.0.1
    
    # 是否开启保护模式。如果没有指定 bind 和密码,Redis 只会本地进行访问,拒绝外部访问。
    protected-mode yes
    
    # 默认端口,建议生产环境不要使用默认端口避免被恶意扫描到
    port 6379
    
    # TCP 连接中已完成队列(完成三次握手之后)的长度
    tcp-backlog 511
    
    # 配置 unix socket 来让 Redis 支持监听本地连接。【此项默认是被注释掉了的,只因介绍从而把它放开】
    unixsocket /tmp/redis.sock
    
    # 配置 unix socket 使用文件的权限。【此项默认是被注释掉了的,只因介绍从而把它放开】
    unixsocketperm 700
    
    # 客户端连接空闲超过 timeout 将会被断开,为 0 则断开
    timeout 0
    
    # 配置 tcp keepalive 参数
    tcp-keepalive 300
    
    ################################## NETWORK #####################################
    
    # By default, if no "bind" configuration directive is specified, Redis listens
    # for connections from all the network interfaces available on the server.
    # It is possible to listen to just one or multiple selected interfaces using
    # the "bind" configuration directive, followed by one or more IP addresses.
    #
    # Examples:
    #
    # bind 192.168.1.100 10.0.0.1
    # bind 127.0.0.1 ::1
    #
    # ~~~ WARNING ~~~ If the computer running Redis is directly exposed to the
    # internet, binding to all the interfaces is dangerous and will expose the
    # instance to everybody on the internet. So by default we uncomment the
    # following bind directive, that will force Redis to listen only into
    # the IPv4 loopback interface address (this means Redis will be able to
    # accept connections only from clients running into the same computer it
    # is running).
    #
    # IF YOU ARE SURE YOU WANT YOUR INSTANCE TO LISTEN TO ALL THE INTERFACES
    # JUST COMMENT THE FOLLOWING LINE.
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    bind 127.0.0.1
    
    # Protected mode is a layer of security protection, in order to avoid that
    # Redis instances left open on the internet are accessed and exploited.
    #
    # When protected mode is on and if:
    #
    # 1) The server is not binding explicitly to a set of addresses using the
    #    "bind" directive.
    # 2) No password is configured.
    #
    # The server only accepts connections from clients connecting from the
    # IPv4 and IPv6 loopback addresses 127.0.0.1 and ::1, and from Unix domain
    # sockets.
    #
    # By default protected mode is enabled. You should disable it only if
    # you are sure you want clients from other hosts to connect to Redis
    # even if no authentication is configured, nor a specific set of interfaces
    # are explicitly listed using the "bind" directive.
    protected-mode yes
    
    # Accept connections on the specified port, default is 6379 (IANA #815344).
    # If port 0 is specified Redis will not listen on a TCP socket.
    port 6379
    
    # TCP listen() backlog.
    #
    # In high requests-per-second environments you need an high backlog in order
    # to avoid slow clients connections issues. Note that the Linux kernel
    # will silently truncate it to the value of /proc/sys/net/core/somaxconn so
    # make sure to raise both the value of somaxconn and tcp_max_syn_backlog
    # in order to get the desired effect.
    tcp-backlog 511
    
    # Unix socket.
    #
    # Specify the path for the Unix socket that will be used to listen for
    # incoming connections. There is no default, so Redis will not listen
    # on a unix socket when not specified.
    #
    # unixsocket /tmp/redis.sock
    # unixsocketperm 700
    
    # Close the connection after a client is idle for N seconds (0 to disable)
    timeout 0
    
    # TCP keepalive.
    #
    # If non-zero, use SO_KEEPALIVE to send TCP ACKs to clients in absence
    # of communication. This is useful for two reasons:
    #
    # 1) Detect dead peers.
    # 2) Take the connection alive from the point of view of network
    #    equipment in the middle.
    #
    # On Linux, the specified value (in seconds) is the period used to send ACKs.
    # Note that to close the connection the double of the time is needed.
    # On other kernels the period depends on the kernel configuration.
    #
    # A reasonable value for this option is 300 seconds, which is the new
    # Redis default starting with Redis 3.2.1.
    tcp-keepalive 300
    

    5、通用配置

    这个是我们基本的配置项。能配置后台启动、日志级别等等。 配置如下:

    # 是否后台启动,默认是 no ,我们需要自己把它该为 yes
    daemonize no
    
    # 可以通过 upstart 和 systemd 来管理 Redis 的守护进程。选项如下:
    # supervised no            - 没有监督互动
    # supervised upstart       - 通过将 Redis 置于 SIGSTOP 式来启动信号
    # supervised systemd       - signal systemd 将 READY = 1 写入 $ NOTIFY_SOCKET
    # supervised auto          - 检测 upstart 或 systemd 方法基于 UPSTART_JOB 或 NOTIFY_SOCKET 环境变量
    supervised no
    
    # 如果以后台的方式运行 Redis ,我们需要指定一个 PID 文件
    pidfile /var/run/redis_6379.pid
    
    # 配置日志级别,默认是 notice 。参数如下:
    # debug
    # verbose
    # notice
    # warning
    loglevel notice
    
    # 配置日志文件的位置,就是把日志输出到哪一个文件
    logfile ""
    
    # 是否打开记录 syslog 功能【此项默认是被注释掉了的,只因介绍从而把它放开】
    syslog-enabled no
    
    # syslog 标识符。【此项默认是被注释掉了的,只因介绍从而把它放开】
    syslog-ident redis
    
    # 配置日志的来源。【此项默认是被注释掉了的,只因介绍从而把它放开】
    syslog-facility local0
    
    # 数据库的数量,默认值为 16 。默认使用的数据库是 DB 0,数据库范围在 0 -(database-1)之间
    # 可以通过 SELECT 命令选择一个 db
    # 集群环境默认只有 DB 0
    databases 16
    
    # 是否一直显示 logo ,就是启动 Redis 数据库有无那个立方体的图形显示
    always-show-logo yes
    
    ################################# GENERAL #####################################
    
    # By default Redis does not run as a daemon. Use 'yes' if you need it.
    # Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
    daemonize yes
    
    # If you run Redis from upstart or systemd, Redis can interact with your
    # supervision tree. Options:
    #   supervised no      - no supervision interaction
    #   supervised upstart - signal upstart by putting Redis into SIGSTOP mode
    #   supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET
    #   supervised auto    - detect upstart or systemd method based on
    #                        UPSTART_JOB or NOTIFY_SOCKET environment variables
    # Note: these supervision methods only signal "process is ready."
    #       They do not enable continuous liveness pings back to your supervisor.
    supervised no
    
    # If a pid file is specified, Redis writes it where specified at startup
    # and removes it at exit.
    #
    # When the server runs non daemonized, no pid file is created if none is
    # specified in the configuration. When the server is daemonized, the pid file
    # is used even if not specified, defaulting to "/var/run/redis.pid".
    #
    # Creating a pid file is best effort: if Redis is not able to create it
    # nothing bad happens, the server will start and run normally.
    pidfile /var/run/redis_6379.pid
    
    # Specify the server verbosity level.
    # This can be one of:
    # debug (a lot of information, useful for development/testing)
    # verbose (many rarely useful info, but not a mess like the debug level)
    # notice (moderately verbose, what you want in production probably)
    # warning (only very important / critical messages are logged)
    loglevel notice
    
    # Specify the log file name. Also the empty string can be used to force
    # Redis to log on the standard output. Note that if you use standard
    # output for logging but daemonize, logs will be sent to /dev/null
    logfile ""
    
    # To enable logging to the system logger, just set 'syslog-enabled' to yes,
    # and optionally update the other syslog parameters to suit your needs.
    # syslog-enabled no
    
    # Specify the syslog identity.
    # syslog-ident redis
    
    # Specify the syslog facility. Must be USER or between LOCAL0-LOCAL7.
    # syslog-facility local0
    
    # Set the number of databases. The default database is DB 0, you can select
    # a different one on a per-connection basis using SELECT <dbid> where
    # dbid is a number between 0 and 'databases'-1
    databases 16
    
    # By default Redis shows an ASCII art logo only when started to log to the
    # standard output and if the standard output is a TTY. Basically this means
    # that normally a logo is displayed only in interactive sessions.
    #
    # However it is possible to force the pre-4.0 behavior and always show a
    # ASCII art logo in startup logs by setting the following option to yes.
    always-show-logo yes
    

    二、高级配置

    1、快照配置

    快照配置与 Redis 的数据持久化 RDB 有关。【一定时间内执行了多少次,则会持久化到文件】 配置如下:

    # 保存数据到磁盘 【RDB 方式】 :
    # after 900 sec (15 min) if at least 1 key changed    【15 分钟(900 秒)内至少有一个 key 发生变化就保存数据到磁盘】
    # after 300 sec (5 min) if at least 10 keys changed   【5 分钟(300 秒)至少有 10 个 key 发生变化就保存数据到磁盘】
    # after 60 sec if at least 10000 keys changed         【1 分钟(60 秒)至少有 10000 个 key 发生变化就保存数据到磁盘】
    
    save 900 1
    save 300 10
    save 60 10000
    
    # 持久化出现错误后,是否依然进行继续进行工作
    stop-writes-on-bgsave-error yes
    
    # 存储至本地数据库时(持久化到 rdb 文件)是否压缩数据【这需要消耗一些 CPU 资源】,默认为 yes
    rdbcompression yes
    
    # 是否开启RC64校验,默认是 yes
    rdbchecksum yes
    
    # rdb 文件名称
    dbfilename dump.rdb
    
    # rdb 使用上面的 dbfilename 配置指令的文件名保存到这个目录
    dir ./
    
    ################################ SNAPSHOTTING  ################################
    #
    # Save the DB on disk:
    #
    #   save <seconds> <changes>
    #
    #   Will save the DB if both the given number of seconds and the given
    #   number of write operations against the DB occurred.
    #
    #   In the example below the behaviour will be to save:
    #   after 900 sec (15 min) if at least 1 key changed
    #   after 300 sec (5 min) if at least 10 keys changed
    #   after 60 sec if at least 10000 keys changed
    #
    #   Note: you can disable saving completely by commenting out all "save" lines.
    #
    #   It is also possible to remove all the previously configured save
    #   points by adding a save directive with a single empty string argument
    #   like in the following example:
    #
    #   save ""
    
    save 900 1
    save 300 10
    save 60 10000
    
    # By default Redis will stop accepting writes if RDB snapshots are enabled
    # (at least one save point) and the latest background save failed.
    # This will make the user aware (in a hard way) that data is not persisting
    # on disk properly, otherwise chances are that no one will notice and some
    # disaster will happen.
    #
    # If the background saving process will start working again Redis will
    # automatically allow writes again.
    #
    # However if you have setup your proper monitoring of the Redis server
    # and persistence, you may want to disable this feature so that Redis will
    # continue to work as usual even if there are problems with disk,
    # permissions, and so forth.
    stop-writes-on-bgsave-error yes
    
    # Compress string objects using LZF when dump .rdb databases?
    # For default that's set to 'yes' as it's almost always a win.
    # If you want to save some CPU in the saving child set it to 'no' but
    # the dataset will likely be bigger if you have compressible values or keys.
    rdbcompression yes
    
    # Since version 5 of RDB a CRC64 checksum is placed at the end of the file.
    # This makes the format more resistant to corruption but there is a performance
    # hit to pay (around 10%) when saving and loading RDB files, so you can disable it
    # for maximum performances.
    #
    # RDB files created with checksum disabled have a checksum of zero that will
    # tell the loading code to skip the check.
    rdbchecksum yes
    
    # The filename where to dump the DB
    dbfilename dump.rdb
    
    # The working directory.
    #
    # The DB will be written inside this directory, with the filename specified
    # above using the 'dbfilename' configuration directive.
    #
    # The Append Only File will also be created inside this directory.
    #
    # Note that you must specify a directory here, not a file name.
    dir ./
    

    2、主从复制配置

    主从复制: 主从复制配置。 配置如下:

    # 指定主节点。旧版本是:slaveof 。【此项默认是被注释掉了的,只因介绍从而把它放开】
    replicaof
    
    # master 的密码。【此项默认是被注释掉了的,只因介绍从而把它放开】
    masterauth
    
    # 当一个 slave 失去和 master 的连接,或者同步正在进行中,slave 的行为有两种可能:
    # 如果 replica-serve-stale-data 设置为 “yes” (默认值),slave 会继续响应客户端请求,可能是正常数据,也可能是还没获得值的空数据。
    # 如果 replica-serve-stale-data 设置为 “no” ,slave 会回复"正在从 master 同步(SYNC with master in progress)" 来处理各种请求,除了 INFO 和 SLAVEOF 命令。
    replica-serve-stale-data yes
    
    # 配置从是否为只读,开启后从则不能写入数据,旧版本是:slave-read-only yes
    replica-read-only yes
    
    # 同步策略: 磁盘或 socket,默认磁盘方式
    repl-diskless-sync no
    
    # 如果非磁盘同步方式开启,可以配置同步延迟时间,以等待 master 产生子进程通过 socket 传输 RDB 数据给 slave 。
    # 默认值为 5 秒,设置为 0 秒则每次传输无延迟。
    repl-diskless-sync-delay 5
    
    # slave 根据指定的时间间隔向 master 发送 ping 请求。默认 10 秒。【此项默认是被注释掉了的,只因介绍从而把它放开】
    repl-ping-replica-period 10
    
    # 同步的超时时间:slave 在与 master SYNC 期间有大量数据传输,造成超时
    # 在 slave 角度,master 超时,包括数据、ping 等
    # 在 master 角度,slave 超时,当 master 发送 REPLCONF ACK pings ,确保这个值大于指定的 repl-ping-slave-period ,否则在主从间流量不高时每次都会检测到超时
    repl-timeout 60
    
    # 是否在 slave 套接字发送 SYNC 之后禁用 TCP_NODELAY
    # 如果选择 yes,Redis 将使用更少的 TCP 包和带宽来向 slaves 发送数据。但是这将使数据传输到 slave 上有延迟,Linux 内核的默认配置会达到 40 毫秒。
    # 如果选择 no ,数据传输到 salve 的延迟将会减少但要使用更多的带宽。
    # 默认我们会为低延迟做优化,但高流量情况或主从之间的跳数过多时,可以设置为 “yes” 。
    repl-disable-tcp-nodelay no
    
    # 设置数据备份的 backlog 大小。同步的超时时间
    repl-backlog-size 1mb
    
    # 从最后一个 slave 断开开始计时多少秒后,backlog 缓冲将会释放。同步的超时时间
    repl-backlog-ttl 3600
    
    # 优先级
    replica-priority 100
    
    # 如果 master 少于 N 个延时小于等于M秒的已连接slave,就可以停止接收写操作。N 个 slave 需要是 “oneline” 状态。
    # 延时是以秒为单位,并且必须小于等于指定值,是从最后一个从slave接收到的ping(通常每秒发送)开始计数。
    # 该选项不保证N个slave正确同步写操作,但是限制数据丢失的窗口期。
    # 例如至少需要3个延时小于等于10秒的slave用下面的指令:【此项默认是被注释掉了的,只因介绍从而把它放开】
    min-replicas-to-write 3
    min-replicas-max-lag 10
    
    ################################# REPLICATION #################################
    
    # Master-Replica replication. Use replicaof to make a Redis instance a copy of
    # another Redis server. A few things to understand ASAP about Redis replication.
    #
    #   +------------------+      +---------------+
    #   |      Master      | ---> |    Replica    |
    #   | (receive writes) |      |  (exact copy) |
    #   +------------------+      +---------------+
    #
    # 1) Redis replication is asynchronous, but you can configure a master to
    #    stop accepting writes if it appears to be not connected with at least
    #    a given number of replicas.
    # 2) Redis replicas are able to perform a partial resynchronization with the
    #    master if the replication link is lost for a relatively small amount of
    #    time. You may want to configure the replication backlog size (see the next
    #    sections of this file) with a sensible value depending on your needs.
    # 3) Replication is automatic and does not need user intervention. After a
    #    network partition replicas automatically try to reconnect to masters
    #    and resynchronize with them.
    #
    # replicaof <masterip> <masterport>
    
    # If the master is password protected (using the "requirepass" configuration
    # directive below) it is possible to tell the replica to authenticate before
    # starting the replication synchronization process, otherwise the master will
    # refuse the replica request.
    #
    # masterauth <master-password>
    
    # When a replica loses its connection with the master, or when the replication
    # is still in progress, the replica can act in two different ways:
    #
    # 1) if replica-serve-stale-data is set to 'yes' (the default) the replica will
    #    still reply to client requests, possibly with out of date data, or the
    #    data set may just be empty if this is the first synchronization.
    #
    # 2) if replica-serve-stale-data is set to 'no' the replica will reply with
    #    an error "SYNC with master in progress" to all the kind of commands
    #    but to INFO, replicaOF, AUTH, PING, SHUTDOWN, REPLCONF, ROLE, CONFIG,
    #    SUBSCRIBE, UNSUBSCRIBE, PSUBSCRIBE, PUNSUBSCRIBE, PUBLISH, PUBSUB,
    #    COMMAND, POST, HOST: and LATENCY.
    #
    replica-serve-stale-data yes
    
    # You can configure a replica instance to accept writes or not. Writing against
    # a replica instance may be useful to store some ephemeral data (because data
    # written on a replica will be easily deleted after resync with the master) but
    # may also cause problems if clients are writing to it because of a
    # misconfiguration.
    #
    # Since Redis 2.6 by default replicas are read-only.
    #
    # Note: read only replicas are not designed to be exposed to untrusted clients
    # on the internet. It's just a protection layer against misuse of the instance.
    # Still a read only replica exports by default all the administrative commands
    # such as CONFIG, DEBUG, and so forth. To a limited extent you can improve
    # security of read only replicas using 'rename-command' to shadow all the
    # administrative / dangerous commands.
    replica-read-only yes
    
    # Replication SYNC strategy: disk or socket.
    #
    # -------------------------------------------------------
    # WARNING: DISKLESS REPLICATION IS EXPERIMENTAL CURRENTLY
    # -------------------------------------------------------
    #
    # New replicas and reconnecting replicas that are not able to continue the replication
    # process just receiving differences, need to do what is called a "full
    # synchronization". An RDB file is transmitted from the master to the replicas.
    # The transmission can happen in two different ways:
    #
    # 1) Disk-backed: The Redis master creates a new process that writes the RDB
    #                 file on disk. Later the file is transferred by the parent
    #                 process to the replicas incrementally.
    # 2) Diskless: The Redis master creates a new process that directly writes the
    #              RDB file to replica sockets, without touching the disk at all.
    #
    # With disk-backed replication, while the RDB file is generated, more replicas
    # can be queued and served with the RDB file as soon as the current child producing
    # the RDB file finishes its work. With diskless replication instead once
    # the transfer starts, new replicas arriving will be queued and a new transfer
    # will start when the current one terminates.
    #
    # When diskless replication is used, the master waits a configurable amount of
    # time (in seconds) before starting the transfer in the hope that multiple replicas
    # will arrive and the transfer can be parallelized.
    #
    # With slow disks and fast (large bandwidth) networks, diskless replication
    # works better.
    repl-diskless-sync no
    
    # When diskless replication is enabled, it is possible to configure the delay
    # the server waits in order to spawn the child that transfers the RDB via socket
    # to the replicas.
    #
    # This is important since once the transfer starts, it is not possible to serve
    # new replicas arriving, that will be queued for the next RDB transfer, so the server
    # waits a delay in order to let more replicas arrive.
    #
    # The delay is specified in seconds, and by default is 5 seconds. To disable
    # it entirely just set it to 0 seconds and the transfer will start ASAP.
    repl-diskless-sync-delay 5
    
    # Replicas send PINGs to server in a predefined interval. It's possible to change
    # this interval with the repl_ping_replica_period option. The default value is 10
    # seconds.
    #
    # repl-ping-replica-period 10
    
    # The following option sets the replication timeout for:
    #
    # 1) Bulk transfer I/O during SYNC, from the point of view of replica.
    # 2) Master timeout from the point of view of replicas (data, pings).
    # 3) Replica timeout from the point of view of masters (REPLCONF ACK pings).
    #
    # It is important to make sure that this value is greater than the value
    # specified for repl-ping-replica-period otherwise a timeout will be detected
    # every time there is low traffic between the master and the replica.
    #
    # repl-timeout 60
    
    # Disable TCP_NODELAY on the replica socket after SYNC?
    #
    # If you select "yes" Redis will use a smaller number of TCP packets and
    # less bandwidth to send data to replicas. But this can add a delay for
    # the data to appear on the replica side, up to 40 milliseconds with
    # Linux kernels using a default configuration.
    #
    # If you select "no" the delay for data to appear on the replica side will
    # be reduced but more bandwidth will be used for replication.
    #
    # By default we optimize for low latency, but in very high traffic conditions
    # or when the master and replicas are many hops away, turning this to "yes" may
    # be a good idea.
    repl-disable-tcp-nodelay no
    
    # Set the replication backlog size. The backlog is a buffer that accumulates
    # replica data when replicas are disconnected for some time, so that when a replica
    # wants to reconnect again, often a full resync is not needed, but a partial
    # resync is enough, just passing the portion of data the replica missed while
    # disconnected.
    #
    # The bigger the replication backlog, the longer the time the replica can be
    # disconnected and later be able to perform a partial resynchronization.
    #
    # The backlog is only allocated once there is at least a replica connected.
    #
    # repl-backlog-size 1mb
    
    # After a master has no longer connected replicas for some time, the backlog
    # will be freed. The following option configures the amount of seconds that
    # need to elapse, starting from the time the last replica disconnected, for
    # the backlog buffer to be freed.
    #
    # Note that replicas never free the backlog for timeout, since they may be
    # promoted to masters later, and should be able to correctly "partially
    # resynchronize" with the replicas: hence they should always accumulate backlog.
    #
    # A value of 0 means to never release the backlog.
    #
    # repl-backlog-ttl 3600
    
    # The replica priority is an integer number published by Redis in the INFO output.
    # It is used by Redis Sentinel in order to select a replica to promote into a
    # master if the master is no longer working correctly.
    #
    # A replica with a low priority number is considered better for promotion, so
    # for instance if there are three replicas with priority 10, 100, 25 Sentinel will
    # pick the one with priority 10, that is the lowest.
    #
    # However a special priority of 0 marks the replica as not able to perform the
    # role of master, so a replica with priority of 0 will never be selected by
    # Redis Sentinel for promotion.
    #
    # By default the priority is 100.
    replica-priority 100
    
    # It is possible for a master to stop accepting writes if there are less than
    # N replicas connected, having a lag less or equal than M seconds.
    #
    # The N replicas need to be in "online" state.
    #
    # The lag in seconds, that must be <= the specified value, is calculated from
    # the last ping received from the replica, that is usually sent every second.
    #
    # This option does not GUARANTEE that N replicas will accept the write, but
    # will limit the window of exposure for lost writes in case not enough replicas
    # are available, to the specified number of seconds.
    #
    # For example to require at least 3 replicas with a lag <= 10 seconds use:
    #
    # min-replicas-to-write 3
    # min-replicas-max-lag 10
    #
    # Setting one or the other to 0 disables the feature.
    #
    # By default min-replicas-to-write is set to 0 (feature disabled) and
    # min-replicas-max-lag is set to 10.
    
    # A Redis master is able to list the address and port of the attached
    # replicas in different ways. For example the "INFO replication" section
    # offers this information, which is used, among other tools, by
    # Redis Sentinel in order to discover replica instances.
    # Another place where this info is available is in the output of the
    # "ROLE" command of a master.
    #
    # The listed IP and address normally reported by a replica is obtained
    # in the following way:
    #
    #   IP: The address is auto detected by checking the peer address
    #   of the socket used by the replica to connect with the master.
    #
    #   Port: The port is communicated by the replica during the replication
    #   handshake, and is normally the port that the replica is using to
    #   listen for connections.
    #
    # However when port forwarding or Network Address Translation (NAT) is
    # used, the replica may be actually reachable via different IP and port
    # pairs. The following two options can be used by a replica in order to
    # report to its master a specific set of IP and port, so that both INFO
    # and ROLE will report those values.
    #
    # There is no need to use both the options if you need to override just
    # the port or the IP address.
    #
    # replica-announce-ip 5.5.5.5
    # replica-announce-port 1234
    

    3、安全设置

    安全设置: 可在此处设置 Redis 的密码。 配置如下:

    # 设置密码。举个例子: requirepass 123456 。【此项默认是被注释掉了的,只因介绍从而把它放开】
    requirepass foobared
    
    #命令重命名。设置命令为空时禁用命令。【此项默认是被注释掉了的,只因介绍从而把它放开】
    rename-command CONFIG ""
    
    
    
    # 在 Redis 客户端查看是否有密码以及设置密码等
    # 查看是否有密码
    config get requirepass
    
    # 设置密码
    config set requirepass "123456"
    
    # 登陆验证密码
    auth 123456
    
    ################################## SECURITY ###################################
    
    # Require clients to issue AUTH <PASSWORD> before processing any other
    # commands.  This might be useful in environments in which you do not trust
    # others with access to the host running redis-server.
    #
    # This should stay commented out for backward compatibility and because most
    # people do not need auth (e.g. they run their own servers).
    #
    # Warning: since Redis is pretty fast an outside user can try up to
    # 150k passwords per second against a good box. This means that you should
    # use a very strong password otherwise it will be very easy to break.
    #
    # requirepass foobared
    
    # Command renaming.
    #
    # It is possible to change the name of dangerous commands in a shared
    # environment. For instance the CONFIG command may be renamed into something
    # hard to guess so that it will still be available for internal-use tools
    # but not available for general clients.
    #
    # Example:
    #
    # rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
    #
    # It is also possible to completely kill a command by renaming it into
    # an empty string:
    #
    # rename-command CONFIG ""
    #
    # Please note that changing the name of commands that are logged into the
    # AOF file or transmitted to replicas may cause problems.
    

    4、客户端配置

    客户端设置: 设置能同时连接到 Redis 的最大客户端的数量。 配置如下:

    ################################### CLIENTS ####################################
    
    # Set the max number of connected clients at the same time. By default
    # this limit is set to 10000 clients, however if the Redis server is not
    # able to configure the process file limit to allow for the specified limit
    # the max number of allowed clients is set to the current file limit
    # minus 32 (as Redis reserves a few file descriptors for internal uses).
    #
    # Once the limit is reached Redis will close all the new connections sending
    # an error 'max number of clients reached'.
    # 设置能同时连接到 Redis 的最大客户端的数量
    # maxclients 10000
    

    5、内存限制

    内存限制: 当内存已经使用完了,那么需要配置算法将一些不必要的 key 移除。 配置如下:

    # 内存限制:能够配置最大内存容量【此项默认是被注释掉了的,只因介绍从而把它放开】
    maxmemory <bytes>
    
    # 如果达到上方最大的内存限制,Redis 如何选择删除 key
    # volatile-lru     ->   根据 LRU 算法删除设置过期时间的 key
    # allkeys-lru      ->   根据 LRU 算法删除任何 key
    # volatile-random  ->   随机移除设置过过期时间的 key
    # allkeys-random   ->   随机移除任何 key
    # volatile-ttl     ->   移除即将过期的 key(minor TTL)
    # noeviction       ->   不移除任何 key,只返回一个写错误
    # 注意:对所有策略来说,如果 Redis 找不到合适的可以删除的 key 都会在写操作时返回一个错误。
    # 目前为止涉及的命令:set setnx setex append incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby getset mset msetnx exec sort
    maxmemory-policy noeviction
    
    # LRU 和最小 TTL 算法的样本个数【此项默认是被注释掉了的,只因介绍从而把它放开】
    maxmemory-samples 5
    
    ############################## MEMORY MANAGEMENT ################################
    
    # Set a memory usage limit to the specified amount of bytes.
    # When the memory limit is reached Redis will try to remove keys
    # according to the eviction policy selected (see maxmemory-policy).
    #
    # If Redis can't remove keys according to the policy, or if the policy is
    # set to 'noeviction', Redis will start to reply with errors to commands
    # that would use more memory, like SET, LPUSH, and so on, and will continue
    # to reply to read-only commands like GET.
    #
    # This option is usually useful when using Redis as an LRU or LFU cache, or to
    # set a hard memory limit for an instance (using the 'noeviction' policy).
    #
    # WARNING: If you have replicas attached to an instance with maxmemory on,
    # the size of the output buffers needed to feed the replicas are subtracted
    # from the used memory count, so that network problems / resyncs will
    # not trigger a loop where keys are evicted, and in turn the output
    # buffer of replicas is full with DELs of keys evicted triggering the deletion
    # of more keys, and so forth until the database is completely emptied.
    #
    # In short... if you have replicas attached it is suggested that you set a lower
    # limit for maxmemory so that there is some free RAM on the system for replica
    # output buffers (but this is not needed if the policy is 'noeviction').
    #
    # maxmemory <bytes>
    
    # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
    # is reached. You can select among five behaviors:
    #
    # volatile-lru -> Evict using approximated LRU among the keys with an expire set.
    # allkeys-lru -> Evict any key using approximated LRU.
    # volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
    # allkeys-lfu -> Evict any key using approximated LFU.
    # volatile-random -> Remove a random key among the ones with an expire set.
    # allkeys-random -> Remove a random key, any key.
    # volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
    # noeviction -> Don't evict anything, just return an error on write operations.
    #
    # LRU means Least Recently Used
    # LFU means Least Frequently Used
    #
    # Both LRU, LFU and volatile-ttl are implemented using approximated
    # randomized algorithms.
    #
    # Note: with any of the above policies, Redis will return an error on write
    #       operations, when there are no suitable keys for eviction.
    #
    #       At the date of writing these commands are: set setnx setex append
    #       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
    #       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
    #       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
    #       getset mset msetnx exec sort
    #
    # The default is:
    #
    # maxmemory-policy noeviction
    
    # LRU, LFU and minimal TTL algorithms are not precise algorithms but approximated
    # algorithms (in order to save memory), so you can tune it for speed or
    # accuracy. For default Redis will check five keys and pick the one that was
    # used less recently, you can change the sample size using the following
    # configuration directive.
    #
    # The default of 5 produces good enough results. 10 Approximates very closely
    # true LRU but costs more CPU. 3 is faster but not very accurate.
    #
    # maxmemory-samples 5
    
    # Starting from Redis 5, by default a replica will ignore its maxmemory setting
    # (unless it is promoted to master after a failover or manually). It means
    # that the eviction of keys will be just handled by the master, sending the
    # DEL commands to the replica as keys evict in the master side.
    #
    # This behavior ensures that masters and replicas stay consistent, and is usually
    # what you want, however if your replica is writable, or you want the replica to have
    # a different memory setting, and you are sure all the writes performed to the
    # replica are idempotent, then you may change this default (but be sure to understand
    # what you are doing).
    #
    # Note that since the replica by default does not evict, it may end using more
    # memory than the one set via maxmemory (there are certain buffers that may
    # be larger on the replica, or data structures may sometimes take more memory and so
    # forth). So make sure you monitor your replicas and make sure they have enough
    # memory to never hit a real out-of-memory condition before the master hits
    # the configured maxmemory setting.
    #
    # replica-ignore-maxmemory yes
    

    三、持久化触发机制

    RDB 触发机制:

    1、执行 save 方法会生成 dump.rdb 文件
    2、执行 flushall 命令生成 dump.rdb 文件
    3、退出 Redis 也会生成 dump.rdb 文件

    AOF 配置: 持久化方式 AOF 的配置。 配置如下:

    # 每次启动时 Redis 都会先把这个文件的数据读入内存里,先忽略 RDB 文件【默认是不开启 AOF 模式的】
    appendonly no
    
    # 配置生成的 AOF 文件的名称
    appendfilename “appendonly.aof”
    
    # fsync() 系统调用告诉操作系统把数据写到磁盘上,而不是等更多的数据进入输出缓冲区。有些操作系统会真的把数据马上刷到磁盘上;有些则会尽快去尝试这么做。
    # Redis 支持三种不同的模式:
    # no:           不要立刻刷,只有在操作系统需要刷的时候再刷。比较快。
    # always:      每次写操作都立刻写入到 aof 文件。慢,但是最安全。
    # everysec:    每秒写一次。折中方案。
    # 默认的 everysec 通常来说能在速度和数据安全性之间取得比较好的平衡。
    appendfsync everysec
    
    # 如果 AOF 的同步策略设置成 “always” 或者 “everysec” ,并且后台的存储进程(后台存储或写入 AOF 日志)会产生很多磁盘 I/O 开销。某些 Linux 的配置下会使 Redis 因为 fsync() 系统调用而阻塞很久。
    # 注意,目前对这个情况还没有完美修正,甚至不同线程的 fsync() 会阻塞我们同步的 write(2) 调用。
    # 为了缓解这个问题,可以用下面这个选项。它可以在 BGSAVE 或 BGREWRITEAOF 处理时阻止 fsync()。
    # 这就意味着如果有子进程在进行保存操作,那么 Redis 就处于"不可同步"的状态。
    # 这实际上是说,在最差的情况下可能会丢掉 30 秒钟的日志数据。(默认 Linux 设定)
    # 如果把这个设置成 "yes" 带来了延迟问题,就保持 "no" ,这是保存持久数据的最安全的方式。
    no-appendfsync-on-rewrite no
    
    #自动重写AOF文件。如果AOF日志文件增大到指定百分比,Redis能够通过 BGREWRITEAOF 自动重写AOF日志文件。
    #工作原理:Redis记住上次重写时AOF文件的大小(如果重启后还没有写操作,就直接用启动时的AOF大小)
    #这个基准大小和当前大小做比较。如果当前大小超过指定比例,就会触发重写操作。
    #你还需要指定被重写日志的最小尺寸,这样避免了达到指定百分比但尺寸仍然很小的情况还要重写。
    #指定百分比为0会禁用AOF自动重写特性。
    
    auto-aof-rewrite-percentage 100
    # 文件达到大小阈值的时候进行重写
    auto-aof-rewrite-min-size 64mb
    
    #如果设置为yes,如果一个因异常被截断的AOF文件被redis启动时加载进内存,redis将会发送日志通知用户
    #如果设置为no,erdis将会拒绝启动。此时需要用"redis-check-aof"工具修复文件。
    
    aof-load-truncated yes
    
    #加载时Redis识别出AOF文件以“REDIS”开头字符串,
    #并加载带此前缀的RDB文件,然后继续加载AOF
    aof-use-rdb-preamble yes
    
    ############################## APPEND ONLY MODE ###############################
    
    # By default Redis asynchronously dumps the dataset on disk. This mode is
    # good enough in many applications, but an issue with the Redis process or
    # a power outage may result into a few minutes of writes lost (depending on
    # the configured save points).
    #
    # The Append Only File is an alternative persistence mode that provides
    # much better durability. For instance using the default data fsync policy
    # (see later in the config file) Redis can lose just one second of writes in a
    # dramatic event like a server power outage, or a single write if something
    # wrong with the Redis process itself happens, but the operating system is
    # still running correctly.
    #
    # AOF and RDB persistence can be enabled at the same time without problems.
    # If the AOF is enabled on startup Redis will load the AOF, that is the file
    # with the better durability guarantees.
    #
    # Please check http://redis.io/topics/persistence for more information.
    
    appendonly no
    
    # The name of the append only file (default: "appendonly.aof")
    
    appendfilename "appendonly.aof"
    
    # The fsync() call tells the Operating System to actually write data on disk
    # instead of waiting for more data in the output buffer. Some OS will really flush
    # data on disk, some other OS will just try to do it ASAP.
    #
    # Redis supports three different modes:
    #
    # no: don't fsync, just let the OS flush the data when it wants. Faster.
    # always: fsync after every write to the append only log. Slow, Safest.
    # everysec: fsync only one time every second. Compromise.
    #
    # The default is "everysec", as that's usually the right compromise between
    # speed and data safety. It's up to you to understand if you can relax this to
    # "no" that will let the operating system flush the output buffer when
    # it wants, for better performances (but if you can live with the idea of
    # some data loss consider the default persistence mode that's snapshotting),
    # or on the contrary, use "always" that's very slow but a bit safer than
    # everysec.
    #
    # More details please check the following article:
    # http://antirez.com/post/redis-persistence-demystified.html
    #
    # If unsure, use "everysec".
    
    # appendfsync always
    appendfsync everysec
    # appendfsync no
    
    # When the AOF fsync policy is set to always or everysec, and a background
    # saving process (a background save or AOF log background rewriting) is
    # performing a lot of I/O against the disk, in some Linux configurations
    # Redis may block too long on the fsync() call. Note that there is no fix for
    # this currently, as even performing fsync in a different thread will block
    # our synchronous write(2) call.
    #
    # In order to mitigate this problem it's possible to use the following option
    # that will prevent fsync() from being called in the main process while a
    # BGSAVE or BGREWRITEAOF is in progress.
    #
    # This means that while another child is saving, the durability of Redis is
    # the same as "appendfsync none". In practical terms, this means that it is
    # possible to lose up to 30 seconds of log in the worst scenario (with the
    # default Linux settings).
    #
    # If you have latency problems turn this to "yes". Otherwise leave it as
    # "no" that is the safest pick from the point of view of durability.
    
    no-appendfsync-on-rewrite no
    
    # Automatic rewrite of the append only file.
    # Redis is able to automatically rewrite the log file implicitly calling
    # BGREWRITEAOF when the AOF log size grows by the specified percentage.
    #
    # This is how it works: Redis remembers the size of the AOF file after the
    # latest rewrite (if no rewrite has happened since the restart, the size of
    # the AOF at startup is used).
    #
    # This base size is compared to the current size. If the current size is
    # bigger than the specified percentage, the rewrite is triggered. Also
    # you need to specify a minimal size for the AOF file to be rewritten, this
    # is useful to avoid rewriting the AOF file even if the percentage increase
    # is reached but it is still pretty small.
    #
    # Specify a percentage of zero in order to disable the automatic AOF
    # rewrite feature.
    
    auto-aof-rewrite-percentage 100
    auto-aof-rewrite-min-size 64mb
    
    # An AOF file may be found to be truncated at the end during the Redis
    # startup process, when the AOF data gets loaded back into memory.
    # This may happen when the system where Redis is running
    # crashes, especially when an ext4 filesystem is mounted without the
    # data=ordered option (however this can't happen when Redis itself
    # crashes or aborts but the operating system still works correctly).
    #
    # Redis can either exit with an error when this happens, or load as much
    # data as possible (the default now) and start if the AOF file is found
    # to be truncated at the end. The following option controls this behavior.
    #
    # If aof-load-truncated is set to yes, a truncated AOF file is loaded and
    # the Redis server starts emitting a log to inform the user of the event.
    # Otherwise if the option is set to no, the server aborts with an error
    # and refuses to start. When the option is set to no, the user requires
    # to fix the AOF file using the "redis-check-aof" utility before to restart
    # the server.
    #
    # Note that if the AOF file will be found to be corrupted in the middle
    # the server will still exit with an error. This option only applies when
    # Redis will try to read more data from the AOF file but not enough bytes
    # will be found.
    aof-load-truncated yes
    
    # When rewriting the AOF file, Redis is able to use an RDB preamble in the
    # AOF file for faster rewrites and recoveries. When this option is turned
    # on the rewritten AOF file is composed of two different stanzas:
    #
    #   [RDB file][AOF tail]
    #
    # When loading Redis recognizes that the AOF file starts with the "REDIS"
    # string and loads the prefixed RDB file, and continues loading the AOF
    # tail.
    aof-use-rdb-preamble yes
    

    注意啦!!!

    1、如果我们的 AOF 文件有错误,我们的 Redis 文件时启动不起来的。因此,我们需要修复 AOF 文件
    2、Redis 给我们提供了一个工具: redis-check-aof ,用于修复受损的 AOF 文件
    3、修复命令为: redis-check-aof --fix appendonly.aof

    第四章:Redis 高级

    一、Redis 的发布订阅

    Redis 发布订阅(pub/sub) 是一种 消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。 举个常用的例子:我们微信关注的公众号会给我们发送消息就属于这种通信模式。【Redis 客户端可以订阅任意数量的频道】

    发布订阅消息图

    下表列出了关于 Redis 发布订阅常用命令:

    # 订阅一个或多个符合给定模式的频道
    PSUBSCRIBE pattern [pattern ...]
    
    # 查看订阅与发布系统状态
    PUBSUB subcommand [argument [argument ...]]
    
    # 将信息发送到指定的频道
    PUBLISH channel message
    
    # 退订所有给定模式的频道
    PUNSUBSCRIBE [pattern [pattern ...]]
    
    # 订阅给定的一个或多个频道的信息
    SUBSCRIBE channel [channel ...]
    
    # 指退订给定的频道
    UNSUBSCRIBE [channel [channel ...]]
    

    使用 Redis 客户端来实现订阅发布。 如下图:

    TIPS: 使用 Java 来实现订阅发布先放一放,以后有空来完善。

    应用场景: Redis 的 发布订阅应用场景 在我们生活中还是挺常见的。如下:

    # Redis 订阅发布应用场景如下:
    
    1、构建实时消息系统,比如普通的即时聊天,群聊等功能
    2、微信的公共号订阅消息推送等
    

    二、Redis 的集群搭建

    1、集群简介

    Redis 是一个开源的 key value 存储系统,受到了广大互联网公司的青睐。但在 Redis3.0 版本之前,它只支持单例模式,在 3.0 版本及以后才支持集群。 Redis 集群采用 P2P 模式,是完全去中心化的,不存在中心节点或者代理节点。并且 Redis 集群是没有统一的入口的,客户端(client)连接集群的时候连接集群中的任意节点(node)即可。集群内部的节点是相互通信的(PING-PONG 机制),每个节点都是一个 Redis 实例。


    为了实现 Redis 集群的高可用,即判断节点是否健康(能否正常使用),redis-cluster 有这么一个投票容错机制: 如果集群中超过半数的节点投票认为某个节点挂了,那么这个节点就挂了(fail)。这是判断节点是否挂了的方法。那么如何判断集群是否挂了呢? -> 如果集群中任意一个节点挂了,而且该节点没有从节点(备份节点),那么这个集群就挂了。这是判断集群是否挂了的方法。


    那么为什么任意一个节点挂了(没有从节点)这个集群就挂了呢? 因为集群内置了 16384 个 slot(哈希槽),并且把所有的物理节点映射到了这 16384[0-16383] 个 slot 上,或者说把这些 slot 均等的分配给了各个节点。当需要在 Redis 集群存放一个数据(key-value)时,Redis 会先对这个 key 进行 crc16 算法,然后得到一个结果。再把这个结果对 16384 进行求余,这个余数会对应 [0-16383] 其中一个槽,进而决定 key-value 存储到哪个节点中。所以一旦某个节点挂了,该节点对应的 slot 就无法使用,那么就会导致集群无法正常工作。 综上所述,每个 Redis 集群理论上最多可以有 16384 个节点。


    集群所需环境: 1、Redis 集群至少需要 3 个节点, 因为投票容错机制要求超过半数节点认为某个节点挂了该节点才是挂了,所以 2 个节点无法构成集群。2、要保证集群的高可用,需要每个节点都有从节点,也就是备份节点,所以 Redis 集群至少需要 6 台服务器。 因为我没有那么多服务器,也启动不了那么多虚拟机,所在这里搭建的是伪分布式集群,即一台服务器虚拟运行 6 个 redis 实例,修改端口号为(7001-7006),当然实际生产环境的 Redis 集群搭建和这里是一样的。

    简介来源: redis集群搭建(非常详细,适合新手)

    2、集群搭建

    第一步: 开启虚拟机,打开四个窗口连接我们的同一台虚拟机。 如下图:

    第二步: 复制 Redis 配置文件,为每一台从机进行配置。 如下图:

    第三步: 修改 redis.confredis-1.confredis-2.confredis-3.conf 配置文件。 具体配置如下:

    # redis.conf 配置文件需修改的地方
    logfile "6379.log"                   # 这个是修改后的内容
    dbfilename dump6379.rdb              # 这个是修改后的内容
    
    # redis-1.conf 配置文件需修改的地方
    port 6380                            # 这个是修改后的内容
    pidfile /var/run/redis_6380.pid      # 这个是修改后的内容
    logfile "6380.log"                   # 这个是修改后的内容
    dbfilename dump6380.rdb              # 这个是修改后的内容
    
    # redis-2.conf 配置文件需修改的地方
    port 6381                            # 这个是修改后的内容
    pidfile /var/run/redis_6381.pid      # 这个是修改后的内容
    logfile "6381.log"                   # 这个是修改后的内容
    dbfilename dump6381.rdb              # 这个是修改后的内容
    
    # redis-3.conf 配置文件需修改的地方
    port 6382                            # 这个是修改后的内容
    pidfile /var/run/redis_6382.pid      # 这个是修改后的内容
    logfile "6382.log"                   # 这个是修改后的内容
    dbfilename dump6382.rdb              # 这个是修改后的内容
    

    第四步: 开启我们的四个 Redis 服务器,并进行查看。 具体操作如下图:

    开启 Redis 主机 开启 Redis 1 号从机 开启 Redis 2 号从机 开启 Redis 3 号从机查看 Redis 服务

    说明一下:
    这个部分的操作是为下面的 Redis 的主从复制 服务的。所以,上述的搭建过程并非真正意义上的集群搭建

    三、Redis 的主从复制

    1、主从复制简介

    通过持久化功能,Redis 保证了即使在服务器重启的情况下也不会丢失(或少量丢失)数据,但是由于数据是存储在一台服务器上的,如果这台服务器出现故障,比如硬盘坏了,也会导致数据丢失。为了避免单点故障,我们需要将数据复制多份部署在多台不同的服务器上,即使有一台服务器出现故障其他服务器依然可以继续提供服务。 这就要求当一台服务器上的数据更新后,自动将更新的数据同步到其他服务器上,这时候就用到了 Redis 的主从复制。

    Redis 提供了复制(replication)功能来自动实现多台 Redis 服务器的数据同步。 我们可以通过部署多台 Redis ,并在配置文件中指定这几台 Redis 之间的主从关系,主服务器负责写入数据,同时把写入的数据实时同步到从服务器,这种模式叫做 主从复制 ,即 master/slave ,并且 Redis 默认 master 用于写,slave 用于读,向 slave 写数据会导致错误。

    注意: 默认情况下,每一台 Redis 服务器都是主节点 【如果还没有进行相应的配置】

    2、主从复制搭建

    主从复制会使用到的命令如下:

    # 主从复制部分命令如下:
    
    127.0.0.1:6379> info replication    # 查看当前库信息
    127.0.0.1:6379> slaveof host port   # 配置主机【就是去认老大】
    127.0.0.1:6379> slaveof no one      # 如果主机宕机了,从机可使用这条命令让自己成为主机【谋朝篡位】
    

    第一步: 开启四台 Redis 服务器的客户端。 如下图所示:





    第二步: 使用 1 号机(6380 端口)去配置主机,配置之后 1 号机将由主机变为从机。 配置如下图:

    第三步: 配置完成后,我们去看看我们的主机状态(6379 端口)。 如下图:

    第四步: 注意事项与故障测试。 这部分使用文字描述,具体操作自行实现。描述如下:

    # 注意事项与故障测试
    
    1、真正的主从复制的配置应该在配置文件中配置,这样配置才是永久的,而我们使用命令配置只是暂时的
    2、主机负责写,从机负责读且从机不能写。
    # 注意:从机如果去写,会报错: (error) READONLY You can't write against a read only replica.
    
    3、如果主机服务器(6379 端口)突然宕机了(我们把它断开连接),那么从机依旧会连接到主机,但是没有写操作
    4、如果此时主机又恢复正常了,从机依旧可以获取到主机写的信息
    
    5、如果是使用命令行来配置的主从复制,这个时候从机如果重启,就会变会到主机,就无法获取到他原来主机的值
    6、这个时候如果把它变回从机,那么它又可以获取到原来主机的值。
    

    3、宕机后手动配置主机

    如果我们的 6379 机子宕机了,那怎么办呢?【现在我们先手动配置,稍后讲哨兵模式(自动配置)】 如下图:

    我们先把集群配置成上图的第二种方式。【注意观察 6380 机子的信息】 如下图:

    6381 机子
    6382 机子
    6380 机子
    6379 主机

    此时,我们把 6379 主机断开(使用 shutdown + exit 命令) ,那就没有主机了。那么 6380 机子可能成为主机吗?(需手动选择) 系统能不能在主机宕机之后自动选择主机呢?(哨兵模式,稍后讲) 我们先来手动配置,把 6380 机子设置为主机。如下图:

    那么问题来了,如果 6379 又恢复正常了,谁会是主机呢?【6380 仍是主机,否则怎么叫谋朝篡位呢】 如下图:

    温馨提示:

    主机宕机的手动解决方案就是这样了。接下来介绍 自动解决方案: 哨兵模式 ^_^

    四、哨兵模式详解

    1、哨兵模式简介

    主从切换技术的方法是: 当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要 人工干预,费事费力,还会造成一段时间内服务不可用, 这不是一种推荐的方式。


    更多时候, 我们优先考虑 哨兵模式从 Redis2.8 开始正式提供了 Sentinel(哨兵)架构来解决这个问题。 哨兵模式 就是谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。


    哨兵模式 是一种特殊的模式, 首先 Redis 提供了哨兵的命令, 哨兵是一个独立的进程, 作为进程, 它会独立运行。 其原理是哨兵通过发送命令, 等待 Redis 服务器响应, 从而监控运行的多个 Redis 实例。 哨兵模式如下图:

    这里的哨兵有两个作用:

    1、通过发送命令,让 Redis 服务器返回监控其运行状态的信息,包括主服务器和从服务器
    2、当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从服务器,并修改配置文件,让它们切换主机

    然而,一个哨兵进程对 Redis 服务器进行监控, 可能会出现问题【可能哨兵挂了】。 为此, 我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了 多哨兵模式 。如下图:

    假设主服务器宕机,【哨兵 1】先检测到这个结果, 系统并不会马上进行 failover 过程, 仅仅是【哨兵 1】主观的认为主服务器不可用, 这个现象称为 主观下线 。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票, 投票的结果由一个哨兵发起, 进行 failover[故障转移] 操作。切换成功后, 就会通过 发布订阅模式 , 让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为 客观下线

    2、哨兵模式配置

    第一步: 明确我们的主从配置模式。* 现在的配置模式 【一主三从】 如下图:

    第二步: 配置我们的哨兵配置文件 sentinel.conf 配置文件内容及截图如下:

    # 哨兵模式配置如下:
    
    # sentinel【固定的】  monitor【固定的】  监控的名称  主机  端口号  是否选举
    sentinel monitor myredis 127.0.0.1 6379 1
    
    # 上述的数字 1 表示:当主机挂了,哨兵进行投票给从机,哪个从机票数多,谁就能够成为主机
    

    第三步: 启动哨兵模式。 如下图:

    第四步: 关闭主机,查看哨兵模式是否起作用。 如下图:

    那么问题来了,如果 6379 机子恢复了,会出现什么情况呢? 如下图:

    3、优缺点及问题

    # 哨兵模式的优缺点如下:
    
    # 优点
    1、哨兵集群,基于主从复制模式,所有的主从配置优点,它全有
    2、主从可以切换,故障可以转移,系统的可用性就会更好
    3、哨兵模式就是主从模式的升级,手动到自动,更加健壮
    
    # 缺点
    1、Redis 不好在线扩容, 集群容量一旦到达上限, 在线扩容就十分麻烦
    2、实现哨兵模式的配置其实是很麻烦的,里面有很多选择
    

    问题如下图:就是我恢复 6379 机子时,主机发生了变化,不是 6381 了, 而是 6382。由于时间原因,暂未查明,以后看到把它搞定。

    4、哨兵模式配置文件

    哨兵模式配置文件 详解如下:

    #Example sentinel.conf
    
    #哨兵sentinel实例运行的端口 默认26379
    port 26379
    
    #哨兵sentinel的工作目录
    dir /tmp
    
    #哨兵sentinel监控的redis主节点的 ip port
    #master-name可以自己命名的主节点名字,只能由字母A-z、数字0-9、这三个字符".-_"组成。
    #quorum 配置多少个sentinel哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
    #sentinel monitor <master-name> <ip> <redis-port> <quorum>
    sentinel monitor mymaster 127.0.0.1 6379 2
     
    #当在Redis实例中开启了requirepass foobared授权密码,这样所有连接Redis实例的客户端都要提供密码 
    #设置哨兵sentinel连接主从的密码,注意必须为主从设置一样的验证密码
    # sentinel auth-pass <master-name> <password>
    sentinel auth-pass mymaster XXX
    #指定多少毫秒之后主节点没有应答哨兵,此时哨兵主观上认为主节点下线,默认30秒
    # sentinel down-after-milliseconds <master-name> <milliseconds>
    sentinel down-after-milliseconds mymaster 30000
    
    # 这个配置项指定了在发生failover主备切换(选举)时多可以有多少个slave同时对新的master进行同步,数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越多的slave因为replication而不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态。
    # sentinel parallel-syncs <master-name> <numslaves>
    sentinel parallel-syncs mymaster 1
    
    #故障转移的超时时间failover-timeout可以用在以下这些方面:
    #1. 同一个sentinel对同一个master两次failover之间的间隔时间。
    #2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
    #3.当想要取消一个正在进行的failover所需要的时间。
    #4.当进行failover时,配置所有slaves指向新的master所需的大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了#默认三分钟
    # sentinel failover-timeout <master-name> <milliseconds>
    sentinel failover-timeout mymaster 180000
    
    #SCRIPTS EXECUTION
    
    #配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
    #对于脚本的运行结果有以下规则:
    #若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
    #若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
    #如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。#一个脚本的大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
    
    #通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,一个是事件的类型,一个是事件的描述。如果sentinel.conf配 置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无 法正常启动成功。
    #通知脚本
    # sentinel notification-script <master-name> <script-path>
    sentinel notification-script mymaster /var/redis/notify.sh
    
    #客户端重新配置主节点参数脚本
    # 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
    #以下参数将会在调用脚本时传给脚本:
    # <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
    # 目前<state>总是“failover”, # <role>是“leader”或者“observer”中的一个。
    #参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
    # 这个脚本应该是通用的,能被多次调用,不是针对性的。
    # sentinel client-reconfig-script <master-name> <script-path>
    sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
    

    第五章:Redis 高频问答

    一、缓存穿透问题

    缓存穿透的概念: 用户想要查询一个数据, 发现 Redis 内存数据库没有, 也就是缓存没有命中, 于是向持久层数据库查询。发现也没有,于是本次查询失败。 当用户很多的时候,缓存都没有命中(秒杀!),于是都去请求了持久层数据库,这会给持久层数据库造成很大的压力,这时候就相当于出现了 缓存穿透


    解决方案: 布隆过滤器 是一种数据结构, 对所有可能查询的参数以 hash 形式存储, 在控制层先进行校验, 不符合则丢弃, 从而避免了对底层存储系统的查询压力;

    缓存空对象:当存储层不命中后,即使返回的空对象也将其缓存起来,同时会设置一个过期时间,之后再访问这个数据将会从缓存中获取,保护了后端数据源。


    但是这种方法会存在两个问题:

    • 1、如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。
    • 2、即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。

    二、缓存击穿问题

    这里需要注意缓存穿透和缓存击穿的区别, 缓存击穿 是指一个 key 非常热点(如微博热搜), 在不停的扛着大并发, 大并发集中对这一个点进行访问, 当这个 key 在失效的瞬间, 持续的大并发就穿破缓存, 直接请求数据库, 就像在一个屏障上凿开了一个洞。


    当某个 key 在过期的瞬间, 有大量的请求并发访问, 这类数据一般是热点数据, 由于缓存过期, 会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大。


    解决方案 1、设置热点数据永不过期:从缓存层面来看, 没有设置过期时间, 所以不会出现热点 key 过期后产生的问题。 2、加互斥锁分布式锁:使用分布式锁, 保证对于每个key同时只有一个线程去查询后端服务, 其他线程没有获得分布式锁的权限, 因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。

    三、缓存雪崩问题

    缓存雪崩 是指在某一个时间段工缓存集中过期失效。产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了缓存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。

    其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。11!I


    解决方案 1、Redis 高可用 这个思想的含义是, 既然red is有可能挂掉, 那我多增设几台red is, 这样一台挂掉之群。(异地多活!) 2、限流降级 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的询数据和写缓存,其他线程等待。 3、数据预热 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分将发生大并发访问前手动触发加载缓存不同的key, 设置不同的过期时间, 让缓存失后其他的还可以继续工作,其实就是搭建的集的线程数量。比如对某个key只允许一个线程查分可能大量访问的数据就会加载到缓存中。在即效的时间点尽量均匀。

    Redis
    • 文章作者:GuoShiZhan
    • 创建时间:2021-08-10 14:31:52
    • 更新时间:2021-08-17 11:10:52
    • 版权声明:本文为博主原创文章,未经博主允许不得转载!
    请 在 评 论 区 留 言 哦 ~~~
    1024