• Redis事务+watch+RDB+AOF+发布订阅+主从复制+哨兵模式+缓存穿透和雪崩及解决方案+使用watch实现秒杀相关


    redis高级

    redis事务

    redis事务的本质:一组命令的集合,一个事务中所有命令都会被序列化,事务执行的过程中,会按照顺序执行!

    一次性,顺序性,排他性,执行命令!

    redis单条命令是保证原子性的,但是事务不保证原子性。

    redis事务没有隔离性

    redis事务中,所有命令不是直接执行,只有发起执行命令的时候才会执行。

    事务执行三个阶段:

    • 开启事务(multi)
    • 命令入队(…)
    • 执行,取消事务(exec,discard)

    一组事务执行或者取消后,就已经结束了,下次使用必须要从开启事务开始

    例子

    # 开启事务
    multi
    
    # 命令入列
    set key1 k1
    set key2 k2
    get key1
    
    # 事务执行/取消事务
    exec/discard
    

    监控 watch实现redis乐观锁

    锁类型:

    • 悲观锁

    效率低下,做什么都加锁

    • 乐观锁

    效率高,视情况加锁

    获取version,修改的时候比较version

    模拟一个场景:a监听某个key并且开启了事务没有执行,b事务也在监听这个key,b中执行了事务,那么a未执行的事务将不会执行成功。

    要注意:在事务中是不支持watch监听key的,所以要在事务的外面执行,乐观锁一定要在事务开启之前加上(watch key)

    a中,监听key且事务中命令入列,先不执行

    set key 1000
    watch key
    MULTI
    DECRBY key 200
    get key
    

    新开一个redis连接,b中,对这个key进行修改

    DECRBY key 200
    

    a中,再执行

    exec
    

    结果为

    (nil)
    

    例子说明:A和B秒杀商品,现在只剩一件了,A在浏览这个商品准备下单,B直接下单了,这时候A再下单发现商品已经没了。

    Java使用原生Jedis实现事务

    是官方推荐的java连接redis的开发工具,导入依赖,jedis

    依赖

    <dependency>
        <groupId>redis.clientsgroupId>
        <artifactId>jedisartifactId>
        <version>3.6.3version>
    dependency>
    

    简单实现

    public static void main(String[] args) {
        Jedis j=new Jedis("127.0.0.1",6379);
        Transaction t = j.multi();
        try{
            t.set("user","1");
            t.exec();
        } catch(Exception e){
            t.discard();
        } finally {
            j.close();
        }
    }
    

    redis.conf配置文件详解

    • 对unit单位大小写,不敏感
    • 配置文件中可用include引入其他配置文件
    • bind 绑定ip,指定哪个ip可以访问redis,注释掉后表示所有ip可访问
    • protected-mode保护模式,值yes/no,是否开启保护模式(开启后不支持远程访问)
    • daemonize yes ,取值yes/no,是否以守护进程方式运行
    • pidfile /var/run/redis_6379.pid,如果以守护进程方式运行,则需要指定pidfile
    • loglevel notice日志级别,默认notice,生产相关,一般不用修改
    • logfile ,日志输出文件路径
    • 快照

    900s内,执行1次修改操作,进行持久化,其他策略略同

    save 900 1

    save 300 10

    save 60 10000

    在规定时间内,执行多少次操作,会把数据持久化到.rdb,.aof文件中

    • rdbcompression yes rdb文件进行持久化(默认,会消耗一些cpu资源)
    • dbfilename dump.rdb rdp持久化文件名
    • requirepass 111111 设置redis密码
    • maxmemory-policy 内存到上限之后处理策略
    • appendonly no 持久化AOF模式,默认no,默认使用rdb持久化方式,足够
    • appendfilename 持久化文件名
    • appendfsync everysec 每秒执行一次持久化数据
    • no-appendfsync-on-rewrite no,重写机制

    redis持久化之RDB(Redis DataBase)

    执行原理

    redis会单独创建(fork)一个子进程来进行数据的持久化,会先将数据写入到一个临时文件中,临时文件持久化结束后,再去替换掉上次持久化的文件。整个进程中,主进程不进行任何io操作,确保了极高的性能,缺点是最后一次持久化可能会造成数据丢失。

    触发机制

    触发redis持久化机制后,会产生rdb文件

    • 配置文件中,满足save规则时,触发
    • flushall命令,清除所有数据库数据时触发
    • 关闭redis服务时,触发rdb
    • 手动bgsave命令

    恢复持久化数据

    只需要把备份文件dump.rdb放入redis.conf配置文件同级目录下即可

    redis启动时会去读取配置文件,由于配置文件指定了dump.rdb文件路径,redis会去自动扫描dump.rdb中的数据,并且进行恢复。

    RDB优缺点

    优:

    • 适用于大量数据的恢复
    • 对数据完整性要求不高

    缺:

    • redis宕机,可能导致最后一次修改的数据丢失
    • fork进程执行的时候,可能占用一定的空间

    持久化之AOF(Append Only File)

    redis默认使用rdb的持久化方式,但是如果aof模式开启了,那么会去同时使用rdb和aof作为持久化,但是数据恢复时只会执行aof来恢复数据(aof模式的数据完整性更高)

    执行原理

    实现方式,fork一个子进程,去记录每次执行的命令追加到aof文件中,恢复数据时,直接执行aof文件来恢复数据

    appendonly yes 即可开启aof模式

    appendfilename appendonly.aof 指定文件名

    当aof文件损坏时

    假如aof文件损坏了(模拟:在aof文件中随便加了一些字符),那么启动redis时就会失败,由于aof文件损坏,redis启动时读取aof文件时出现问题,造成无法启动redis服务

    解决方案:

    使用redis目录下redis-check-aof工具修复aof文件

    redis-check-aof --fix appendonly.aof
    

    AOF优缺点

    优:

    • 每一次修改都会同步(命令记录入aof文件),数据完整性更高
    • 每秒同步一次,只丢失一秒的数据
    • 不开启时,从不同步,效率最高

    缺:

    • aof文件远大于rdb文件,修复速度也比rdb文件慢很多
    • aof运行效率比rdb慢

    redis发布订阅

    redis发布订阅(pub/sub),是一种消息通信模式,发送者发送(pub)消息,订阅者订阅(sub)消息

    redis客户端可以订阅任意数量的频道

    三个角色

    • 消息生产者
    • 频道
    • 消息订阅者

    订阅者订阅频道,订阅后会自动监听

    # 订阅一个频道wlhme
    subscribe wlhme
    

    发布者发布消息到频道

    publish wlhme helloworld
    

    Redis主从复制

    指将一台服务器的数据复制给其他服务器,前者是主机,后者从机,数据复制是单向的,只能是从主机到从机。master以写为主,slave以读为主。

    主从复制,读写分离。减缓服务器压力,一主二从。

    作用:

    • 数据冗余,主从复制实现了数据备份
    • 故障恢复,主节点出问题,从节点可以提供服务,实现快速故障修复
    • 负载均衡,主从复制基础上,配合读写分离,分担服务器负载,提高redis并发量
    • 高可用基石:主从复制是哨兵模式和集群的基础

    单台redis服务器,最大使用内存不应超过20G,超了就整从机。

    默认情况下,每台redis服务器都是主节点,可以修改配置文件变成从节点。一个主节点可以有多个从节点,但是一个从节点只能有一个主节点。

    Redis集群

    环境配置:只需要配置从节点,主节点不用配置。

    # 查看集群配置信息
    info replication
    

    从节点如何配置?

    • 方案一:使用命令方式配置(不是永久的)

      # 成为本机6379端口服务的从机(认主)
      slaveof 127.0.0.1 6379
      

      要点:

      如果主节点存在密码,那么需要在从机配置文件中中加上masterauth 主密码,配置主节点的密码才可以。

    • 方案二:直接在从节点配置文件中加上主节点的配置

      replicaof 127.0.0.1 6379
      masterauth 111111
      

      细节

      主写从读。主节点存入数据后,复制数据到从节点,从节点可以读到数据。

      从机只能读数据,不能写入数据。

      主节点宕机后,从节点仍然可以提供服务,过一段时间主节点上线,从节点仍然可以和主节点保持连接(获取到主机写的数据)。

      如果是使用命令行配置的从机,那么从机挂掉后再启动就会变成主机(最好配置在文件中,可以永久保存),当从机再次连接到主机时,主机的数据仍会同步到从机中,仍然可以读取到主机中的数据。

    主从复制的实现原理

    slave启动服务,连接到master后会发送一个sync同步命令。master接到命令,启动后台存盘进程,同时收集所有用于修改数据的命令,后台进程执行完毕以后,master将整个数据文件发送到slave,完成同步。

    • 全量复制

    slave接收到master的数据文件后,直接同步数据加载到内存中。

    • 增量复制

    master继续将修改数据命令依次发送给slave,实际上就是,master和slave保持连接时,master修改一条数据,slave同步一次数据。

    要点:

    只要是重新连接master,那么全量复制将自动执行。

    哨兵模式

    可参考文章

    Redis的哨兵模式

    手动谋权篡位过于费时费力,采用自动选举老大的哨兵模式。

    哨兵模式是一种特殊模式,redis提供了哨兵模式的命令,哨兵作为独立的进程执行,原理:哨兵通过发送命令到redis服务器,等待redis服务器响应,从而监控运行的多个redis实例。

    当主节点挂掉后,哨兵1先发现主服务不可用(主观下线),后面的哨兵也发现了,并且达到一定数量后,哨兵们会进行投票选举。选举完成后,通过发布订阅模式,让各个哨兵通知自己监控的从节点切换主节点(换老大),这个过程是客观下线。

    实现

    在redis目录下,创建sentinel.conf配置文件

    内容

    # 监控本机6379的redis,投票机制是1
    sentinel monitor red1 127.0.0.1 6379 1
    

    启动哨兵

    redis-sentinel /sentinel.conf
    

    当我们的redis主节点挂了之后,哨兵会选举老大,使之成为主节点。当原主节点恢复了,他也只能当从机!!!

    优点

    • 基于主从复制模式,所有主从配置优点它都有
    • 主从可以切换,故障可以转移,可用性好
    • 主从模式的升级,手动改为自动选举老大

    缺点

    • redis不好在线扩容,集群容量一旦达到上限,在线扩容非常麻烦
    • 实现哨兵模式配置很麻烦,里面有很多选择

    缓存穿透和雪崩

    缓存穿透(数据找不到)

    用户发起请求查询一个数据,发现redis中没有,也就是缓存没有命中,于是去mysql中查询,但是持久化数据库也没有数据,于是本次查询失败。当用户量很多,都去发起请求,都没有缓存命中,都去请求持久化数据库,给持久层数据库造成很大的压力,就是缓存穿透。

    解决方案

    • 布隆过滤器
    • 缓存空对象

    当缓存未命中,且持久层数据库也无数据,就在缓存中加一个空对象返回

    缓存击穿(热点key过期)

    指一个key非常热点,扛着大量的请求压力,并发集中对一个点进行访问。放这个key在失效的瞬间,持续的大并发击穿缓存,直接访问持久层数据库,给数据库造成很大压力。

    解决方案

    • 设置热点数据永不过期
    • 加互斥锁

    分布式锁,保证每个key每次只有一个线程去进行访问

    缓存雪崩(大量key同时过期)

    指在某一时间段,缓存key集中失效,或者redis突然宕机。

    所有的访问压力全部落在了持久层数据库的身上,造成很大的访问压力。

    解决方案

    • redis集群
    • 限流降级

    方案思想:redis的大部分key失效后,进行加锁或者队列,限制持久层数据库的访问线程数量

    • 数据预热

    正式部署之前,先访问一遍,把可能大量访问的数据加载到缓存中,在大量访问前,手动加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

    使用watch实现秒杀相关业务代码

    参考自redis的watch机制及应用

    String redisKey = "miaosha";
    ExecutorService executorService = Executors.newFixedThreadPool(20);//20个线程
    try {//初始化
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        // 初始值
        jedis.set(redisKey, "0");
        jedis.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
    for (int i = 0; i < 1000; i++) {//尝试1000次
        executorService.execute(() -> {
            try (Jedis jedis1 = new Jedis("127.0.0.1", 6379)) {
                jedis1.watch(redisKey);
                String redisValue = jedis1.get(redisKey);
                int valInteger = Integer.valueOf(redisValue);
                String userInfo = UUID.randomUUID().toString();
                // 没有秒完
                if (valInteger < 20) {//redisKey
                    Transaction tx = jedis1.multi();//开启事务
                    tx.incr(redisKey);//自增
                    List list = tx.exec();//提交事务,如果返回nil则说明执行失败,因为我watch了的,只要执行失败,则
                    // 进来发现东西还有,秒杀成功
                    if (list != null && list.size() > 0) {
                        System.out.println("用户:" + userInfo + ",秒杀成功!当前成功人数:" + (valInteger + 1));
                    } else {//执行结果不是OK,说明被修改了,被别人抢了
                        System.out.println("用户:" + userInfo + ",秒杀失败");
                    }
                } else {//东西秒完了
                    System.out.println("已经有20人秒杀成功,秒杀结束");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }//关闭redis
        });
    }
    executorService.shutdown();//关闭线程池
    

    参考

    哨兵模式可参考Redis的哨兵模式
    布隆过滤器参考SpringBoot+Redis布隆过滤器防恶意流量击穿缓存的正确姿势
    redis分布式锁redis分布式锁的实现(setNx命令和Lua脚本)
  • 相关阅读:
    kafka日志策略
    【1】请问什么是 AQS?
    QJsonValue的学习
    程序验证之Dafny--证明霍尔逻辑的半自动化利器
    【web-解析目标】(1.2.4)解析应用程序:解析受攻击面
    JavaWeb、Maven与Mybatis框架
    4G RTU水文数据采集终端应用在水文监测终端系统
    RabbitMQ备份交换机与优先级队列
    检索系统设计方案(重构)
    Nginx基本知识
  • 原文地址:https://blog.csdn.net/weixin_45248492/article/details/126957794