• Redis升级




    学会了Redis的基本操作还不够,再来看看升级部分


    1. 数据删除策略

    惰性删除+定期删除(默认)

    定期删除:默认是每隔 100ms 就轮询各个库随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。每隔100ms就遍历所有的设置过期时间的 key 的话,是个损耗。

    惰性删除:定期删除会导致很多过期 key 到了时间并没有被删除掉。除非系统去查询才会删除。如果靠定期删除,和没有走惰性删除的话会导致一大部分过期数据没有删除,这时候就出现了内存淘汰机制





    2. 内存淘汰机制

    在数据进入内存的时候发现内存不够了,就采用内存淘汰机制,不一定淘汰过期的

    其配置有:

    • maxmemory:最大可用内存
    • maxmemory-samples:每次选取删除数据的个数
    • maxmemory-policy:删除策略
      • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)最近最久未使用
      • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)最近最少使用
      • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)将要过期的数据淘汰
      • volatile-random:从已设置过期时间的数据集(server.db[i].expires)随机淘汰
      • allkeys-lru:在全库数据中(server.db[i].dict),最近最久未使用(这个是最常用的)
      • allkeys-lfu:在全库数据中(server.db[i].dict),最近最少使用
      • allkeys-random:在全库数据中(server.db[i].dict)随机淘汰
      • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用




    3. 限制登录次数功能

    • 判断用户是否被限制登录
      • 有:做相应的提示
      • 没有
        • 登录成功:清除失败错误次数
        • 登录不成功(查询key是否存在,即是否第一次 错误)
          • 第一次错误:设次数为1,user:loginCount:fail:用户名进行赋值,同时设置失效期
          • 不是第一次
            • (判断是否4次,是的话这次加1等于5,限制1小时),user:loginCount:fail:用户名+1
            • 小于第四次,失败次数加1
    // 这里笔者用了不规范的返回值,返回数值大于10表示被限制登录
    public long login(String username, String password) {
    
        // 先判断是否被限制,
        if (jedis.exists("user:loginCount:limit:" + username)) {
            return jedis.ttl("user:loginCount:limit:" + username);
        }
    
        // 然后查询数据库返回结果,这里模拟查询设数据库,密码输入错误
        if (!username.equals(password)) {
            long count = 0;
            // 第一次错误,键不存在,设置过期时间,10秒内可以错误5次
            if (!jedis.exists("user:loginCount:fail:" + username)) {
                jedis.setex("user:loginCount:fail:" + username, 10, "1");
                return 1;
            } else {
                count = jedis.incr("user:loginCount:fail:" + username);
                if (count == 5) {
                    jedis.setex("user:loginCount:limit:" + username, 10, ""); // 设置登录限制时间
                    jedis.del("user:loginCount:fail:" + username);  // 删除登录次数,因为被登录限制
                }
                return count; // 返回已尝试次数
            }
        }
        // 密码正确,清除错误次数
        jedis.del("user:loginCount:fail:" + username);
        return 0L; // 表示密码正确
    }
    




    4. 消息订阅

    subscribe channel[channel]  订阅频道
    psubscribe pattern[pattern]  订阅匹配的频道
    
    publish channel message  将消息发送到指定频道
    
    unsubscribe [channel | channel]  退订频道
    punsubscribe [pattern | pattern]  退订匹配的频道
    

    应用场景:

    • 构建实时消息系统
      • 普通的即时聊天,群聊
      • 粉丝订阅之后,发布新文章的消息推送,公众号模式




    5. 缓存雪崩

    Redis过期是惰性删除+定期删除,如果缓存数据设置的过期时间相同,那么当这些数据全部过期时,就会在这段时间全部请求走数据库中。简单就是Redis某段时间,或直接挂了,请求全走数据库,那么导致数据库支持不住而宕机。

    解决方法:

    • 给过期时间加上一个随机值(数据分类过期),减少大幅度同一时间过期问题
    • 事前:可以用集群或高可用来尽量避免
    • 事发中:使用本地缓存+限流(比如验证码)
    • 事发后:redis的持久化,从硬盘上恢复数据




    6. 缓存穿透

    大量查询不存在的数据,导致每次返回空,Redis不起作用,相当于直接访问数据库。

    解决方法:

    • 请求参数的校验,使之不能进入到redis,更不要说数据库了
    • 查询不存在数据时,也将这个数据放入Redis,下次访问可以从里面获取,当然要设置过期时间
    • 布隆过滤器、限流算法、令牌桶




    7. 缓存与数据库的读写一致

    读:

    • 如果查询数据缓存里有,直接返回
    • 缓存里没有,去数据库查询,将查询结果放入缓存,并返回给客户端

    对于更新时:会导致缓存数据和数据库不一致,可以先修改数据库,再修改缓存。或者先修改缓存,再修改数据库,重点在于我们要是这两个操作突显原子性,这样数据才不会出错


    操作缓存:可以选择更新和删除,但一般采取删除操作。因为删除相对比更新更直接简单,如果每次更新数据库都要更新缓存,如果频繁更新的话,会频繁修改一定程度损耗性能,不如直接删除,再次读取时缓存没有就到数据库查找


    先更新数据库再删除缓存:也有概率出错但很低,比如缓存失效,线程A查询数据库得到旧值,期间线程B将新值写入数据库,线程B删除缓存,然后线程A才将旧址写入缓存。删除缓存失败策略是,不断重试删除,直到成功。

    先删除缓存,再更新数据库:如果原子性被破坏了,第一步成功删除缓存,第二步更新数据库失败,那么数据库数据是一致的,如果第一步删除缓存失败了,可以直接返回错误,数据库数据和缓存还是一致。

    但是:线程A删除了缓存,期间线程B查询会走数据库得到旧值,并把旧值写入缓存,然后线程A才将新值写入数据库,导致数据不一致,解决方法:将删除缓存,修改数据库,读取缓存等操作挤压到队列里,实现串行化。


    二者对比:

    前者:高并发下表现优异,原子性破坏时不好

    后者:高并发下串行,原子性破坏时优异






    8. 持久化

    Redis是基于内存的,万一遇到宕机那么内存中的数据则会丢失,而持久化则是将内存中的数据保存到硬盘防止丢失。Redis支持两种方式的持久化方式:RDB、AOF


    1. RDB

    创建内存中数据的二进制快照来实现持久化,可对快照备份或把快照复制到其他服务器使之成为服务器副本,还可以将快照留在原地以便重启服务器加载使用,默认持久化文件为dump.rdb


    save命令执行一次就保存一次,若数据量过大,加入单线程任务执行会阻塞任务,所以不建议使用

    bgsave命令后台运行,fork子进程来进行持久化,成功后记录到日志中

    自动执行持久化:需在redis.conf中配置,执行多少次非查询操作就保存

    • save 900 1
    • save 300 10
    • save 60 10000

    优点:

    • 紧凑压缩的二进制文件,存储效率高
    • 存储的是某个时间点的快照,适合数据备份,全量复制
    • 恢复数据速度比AOF快很多
    • 应用:每隔一段时间执行bgsave备份,用于灾难恢复

    缺点:

    • 不能实时持久化,间隔时间段的数据可能丢失
    • fork子进程,内存额外消耗
    • 数据量大时,持久化速度慢,全部数据持久化


    2. AOF

    将除查询外的命令追加保存到AOF文件中,重启时重新执行AOF文件中的命令达到恢复数据的目的,是主流的持久化方式,默认没有开启,持久化文件为appendonly.aof


    持久化数据的三种策略(写命令刷新到aof命令缓冲区)

    • always 每次
    • everysec 每秒
    • no 系统控制

    配置文件

    • appendonly yes|no
    • appendfsync always|everysec|no

    AOF重写机制

    将Redis进程内的数据转化为写命令同步到新的AOF文件的过程,即将对同一个数据的若干命令的执行结果合并成一条操作指令(忽略超时数据,忽略无效指令删除等,合并重复指令),可降低文件大小,提高持久化与恢复效率,其也有重写缓冲区,下面是重写命令:

    • bgrewriteaof 手动重写
    • auto-aof-rewrite-min-size size 配置自动重写(当aof缓存了多少)
    • auto-aof-rewrite-percentage percentage 配置自动重写(%)

    参考黑马教程


    优点:

    • AOF持久化的实时性更好
    • 持久化速度快,追加命令

    缺点:

    • 因为记录命令,持久化文件大
    • 恢复数据慢,要执行命令




    9. 事务

    Redis 通过 MULTI、EXEC命令来实现事务(transaction)功能,其事务实质是将多个命令打包后一次性地按顺序执行,期间不会执行其他客户端的命令请求,简单来说是命令串行化执行功能,没有回滚功能。关系型数据库用 ACID 检验事务功能的可靠性和安全性。而 Redis 中,事务总是具有原子性、一致性、隔离性,当持久化时,事务也具有持久性


    MULTI:开启事务,创建队列,命令来了加入队列
    EXEC:执行事务,队列中执行命令,完后销毁队列
    DISCARD:取消事务,销毁队列
    

    流程:

    • 开始事务
    • 命令入队,命令不会立即执行
    • 执行事务,按上面入队顺序执行

    举例转账:multi开始事务,exec执行事务

    set account:a 100
    set account:b 100
    
    multi
    
    get account:a
    "QUEUED"
    get account:b
    "QUEUED"
    decrby account:a 10
    "QUEUED"
    incrby account:b 10
    "QUEUED"
    
    exec
     1)  "100"
     2)  "100"
     3)  "90"
     4)  "110"
    

    Redis的事务是没有回滚功能的,在进行事务的时候,只有报错的命令不会执行(例外:语法错误整个队列都不会执行,类型错误会执行),其他命令都会执行。只是单纯的执行事务的时候不会有其他命令加塞


    场景:动物园给熊猫投喂竹子,这里有很多个饲养员,只要其中一个投喂了,其他饲养员就不用再投喂,使用watch解决

    WATCH:执行事务前,监视Key是否被修改,若有则取消事务,返回nil(针对同时修改用处大)
    UNWATCH:取消监视
    
    watch eat
    // 中间可以执行其他命令,必须在开启事务前watch
    multi
    set panda 1
    exec
    




    10. 主从复制

    repl-backlog-size 设置指令缓冲区
    slave-server-stale-data yes|no  slave关闭写功能
    

    • 建立连接

    方式1:
    客户端发送 slaveof <masterip> <masterport>
    		  auth <password>
    
    方式2:
    启动式服务器参数 redis-server -slavveof <masterip> <masterport>
    
    方式3
    slave配置文件:slaveof <masterip> <masterport>
    masterauth 123456
    

    主从复制低版本不能复制高版本的数据,笔者在这里花了挺久时间才找出问题所在


    • 数据同步


    • 命令传播


    • 心跳机制

    进入命令传播阶段时,master和slave的信息交换使用心跳机制维护,实现双方连接保持在线


    主从复制的作用

    • master写,slave读,提高读写负载能力
    • 负载均衡,基于主从结构,配合读写分离
    • 故障恢复,当master故障时,由slave提供服务,实现快速恢复
    • 数据冗余,实现数据热备份
    • 高可用基础




    11. 哨兵模式Sentinel(主备切换)

    哨兵是一个分布式系统,也是一台redis服务器,对于主从结构中的每台服务器进行监控,出现故障时投票机制选择新的master并将所有slave连接到新的master,演示搭建三个哨兵和1主2从


    sentinel.conf的配置文件

    monitor mymaster 127.0.0.1 6379 2  // 监听主服务器,自定义名字,后面2表示多少个哨兵认为宕机才有效
    down-after-millisecoinds mymaster 30000  // 多久才认为宕机
    parallel-syncs mymaster 1  // 命令传播
    failover-timeout mymaster 180000  // 复制超时时间
    

    先启动1主2从,再启动哨兵

    redis-sentinel sentinel-26379.conf
    redis-sentinel sentinel-26380.conf
    redis-sentinel sentinel-26381.conf
    

    启动哨兵后,每台服务器的配置都会有对应的修改


    哨兵模式的流程:

    • 1.监控阶段
      • 获取各sentinel的状态(是否在线)
      • 获取master的状态
        • master属性
          • runId
          • role:master
        • 各个slave的详细信息
      • 获取所有slave的状态(根据master中的slave信息)
        • slave属性
          • runId
          • role:slave
          • master_host、master_port
          • offset
    • 2.通知阶段
      • 不停地用ping去测试
    • 3.故障转移
      • 发现问题
      • 竞选负责人
      • 优选新master
        • 在线的
        • 响应快的
        • 与原master断开时间最短的
        • 优先原则:优先级、offset、runId
      • 新master上线,其他slave切换master,原master作为slave故障恢复后连接





    12. 集群

    分散单台服务器的访问压力,即负载均衡

    其底层存储原理:

    • 将key进行两次算法运算得key应该保存的位置(CRC16(key) % 16384)
    • 将所有Redis服务器的总存储空间计划切割成16384份,每台主机保存一部分
    • 加Redis服务器的话,原本服务器将槽分给新的服务器、删除服务器则相反
    • 集群内部通讯:记录各服务器槽范围,一次命中OK,否则服务器查询通讯录让请求去对应槽服务器(最多2次命中)
    • 内部通讯这样就不用虚拟IP了

    配置3主3从(官方自带,每个服务器都要配置)

    cluster-enabled yes  // 开启集群节点
    cluster-config-file nodes-6379 // 集群配置文件
    cluster-node-timeout 10000  // 宕机时间
    

    src下有redis-trib.rb(需要Ruby、Gem支持)

    ./redis-trib.rb create --replicas 1 // 其中1表示1主拖1从
    127.0.0.1:6379 127.0.0.1:6380 127.0.0.1:6381
    127.0.0.1:6382 127.0.0.1:6383 127.0.0.1:6384
    

    客户端启动

    redis-cli -c
    // 不然key和服务器没有对应上会报错,让你去连对应的服务器。加了配置会帮你重定向
    

    故障处理:

    • 从服务器下线,各个节点能收到通知,对应master节点会标记一下宕机从服务器
    • 主服务器下线,对应从服务器重试,失败就执行上面的主从切换,切换的从顶替了主集群。原主上线变成slave




    13. 并发竞争Key问题

    所谓 Redis 的并发竞争 Key 的问题也就是多个系统同时对一个 key 进行操作,但是最后执行的顺序和我们期望的顺序不同,这样也就导致了结果的不同

    推荐一种方案:分布式锁(zookeeper 和 redis 都可以实现分布式锁)。(如果不存在 Redis 的并发竞争 Key 问题,不要使用分布式锁,这样会影响性能)






    参考

    https://www.bilibili.com/video/BV1CJ411m7Gc?p=101



  • 相关阅读:
    运动员排成绩————————对多个数组进行排序
    关于指针 用字符数组,字符指针变量输入字符串 动态为字符型指针变量分配内存
    axel 原来求中点我想复杂了 两个结果一样
    Bezier曲线绘制 B样条绘制
    最大的回文子串
    字符数组
    按位与、或、异或等运算方法
    uniapp mixin 使用
    vue mixin执行覆盖
    windows10 安装NASM
  • 原文地址:https://www.cnblogs.com/Howlet/p/12686522.html
Copyright © 2020-2023  润新知