• Redis6笔记04 主从复制,集群,应用问题,Redis6新功能


    Redis主从复制

    主机数据更新后根据配置和策略,自动同步到备机的master/slave机制,Master是以写为主,Slave以读为主

    能做什么?

    读写分离,性能扩展

    容灾快速恢复

    怎么用

    创建/myredis文件夹

    1. mkdir /myredis
    2. cd /myredis

    复制redis.conf配置文件到该文件夹中

    cp /etc/redis.conf /myredis/redis.conf
    

    修改/myredis/redis.conf

    关闭AOF


    配置一主两从,创建三个配置文件

    新建redis6379.conf,填写如下

    新建redis6380.conf,redis6381.conf内容一致把数字6379改一下


    启动三台redis服务器

    连接指定redis服务器

    redis-cli -p 6379
    

    查看三台主机运行情况

    当前三台都是主服务器


    配从不配主

    格式:slaveof ip prot

    在6380和6381上执行如下命令

    配置完成

    测试

    此时在主机上设置一个数据,在从机上查看该数据

    但在从机中不能做写操作

    常用方式

    一主二仆

    切入点问题,slave1,slave2是从头开始复制还是从切入点开始复制?

    当一个从服务器出现问题宕机,那么在他重启后它就与原来的主服务器没有了主从关系

    在此时在主服务器中设置一些键

    重新添加主从关系

    查看键的情况

    可以看到所有的键

    结论,从头开始复制


    主机shutdown后情况如何?

    主机挂掉后,从机就是原地待命,不会上位也不会叛变

    主机重启后,新增记录,从机还能顺利复制

    主从复制原理

    从服务器连接上主服务器之后,从服务器向主服务器发送进行数据同步消息(sync命令)(从服务器主动)

    主服务器接到从服务器发送过来的同步消息,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令, 在后台进程执行完毕之后,master将传送整个数据文件(rdb文件)发送从服务器,从服务器拿到rdb进行读取,完成一次全量复制

    每次主服务器进行写操作之后,和从服务器进行数据同步(增量复制)(主服务器主动)

    只要重新连接主服务器就自动执行一次全量复制

    增量复制:主服务器将新收集到的修改命令依次传给从服务器

    薪火相传

    从服务器也可以下一个从服务器的主人,它同样可以接收其他从服务器的连接和同步请求,那么该从服务器作为链条中下一个从服务器的主人,可以有效减轻主服务器的写压力,去中心化降低风险

    使用命令将6380作为6381的主服务器

    这样6379的从服务器只有一台

    中途变更转向会清除之前的数据,重新建立拷贝最新的

    缺点是某一从服务器宕机,它的从服务器都没法备份

    反客为主

    当一个主服务器宕机后,后面的从服务器立刻升为主服务器

    使用slaveof no one命令将从机变成主机

    哨兵模式

    反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库

    先将刚才的几个服务器重新调整为一主二仆模式

    自定义的/myredis目录下新建sentinel.conf文件

    vi sentinel.conf
    

    配置哨兵,编写配置文件

    sentinel monitor mymaster 127.0.0.1 6379 1
    

    mymaster是为监控对象起的服务器名称,1为至少1个哨兵同意才更换主机

    启动哨兵

    redis-sentinel sentinel.conf
    

    关闭主服务器

     可以看到哨兵窗口日志,切换了新的主机

     原主机重启后会变成从机

    故障恢复

    哨兵是怎么投票的

    优先级在redis.conf中默认:replica-priority 100,值越小优先级越高

    如果replica-priority值相同,就选择偏移量大的。偏移量是指获得原主机数据最全的

    如果前两个条件一样,那么每个redis实例启动后都会随机生成一个40位的runid,选择runid小的

    挑选出新的主服务器后sentinel向原主服务器发送slaveof新主服务器的命令.

    Java实现

    主从复制的缺点

    复制延时

    当所有的写操作都是先在Master上操作,然后同步更新到Slava上,所有从Master同步到Slave机器有一定的延时,当系统很繁忙的时候,延迟问题就会更加严重,Slave机器数量的增加也会使得这个问题更加严重。

    Redis集群

    问题

    容量不够,redis如何进行扩容

    并发写操作,redis如何分摊

    另外,主从模式,薪火相传模式,主机宕机导致ip地址发生变化,应用程序中配置需要修改对应的主机地址,端口号等信息

    之前通过代理主机来解决,但是redis3.0中提供了解决方案,就是无中心化集群配置

    什么是集群

    Redis集群实现了对Redis的水平扩容,即启动N个redis节点,讲整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。

    Redis集群通过分区(partition)来提供一定程度的可用性(availability):即使集群中有一部分节点失效或者无法进行通讯,集群也可以继续处理命令请求。

    搭建Redis集群

    删除持久化数据,将rdb,aof文件都删除掉

    制作6个实例,6379,6380,6381,6389,6390,6391(三主三从)

    修改redis6379.conf

    vi redis6379.conf
    

    开启集群模式,设置节点配置文件名,节点超时时间(超时,集群进行主从切换)

     

    保存文件,将redis6380.conf和redis6381.conf删除

    复制文件

    ps -ef | grep redis
    

    修改复制而来的五个配置文件的内容(以6380为例)

    在编辑页面使用替换命令将6379变为指定数字

    :%s/6379/6380
    

    启动6个redis服务(这里是使用6个不同端口号模拟6个服务器)

    使用以下命令查看进程

    ps -ef | grep redis
    

    将六个节点合成一个集群

    先切换到redis最开始安装的目录中

    cd /opt/redis-6.2.7/src
    

    执行命令

    redis-cli --cluster create --cluster-replicas 1 192.168.199.129:6379 192.168.199.129:6380 192.168.199.129:6381 192.168.199.129:6389 192.168.199.129:6390 192.168.199.129:6391
    

    这里使用自己的ip

    replicas 1表示采用最简单的方式配置集群,一台主机,一台从机,正好三组

    输入yes

    完成搭建

    任何一个节点可以作为集群的入口

    集群方式连接

    redis cluster如何分配这六个节点

    一个集群至少要有三个主节点

    选项--cluster-replicas 1表示我们希望集群中的每个主节点创建一个从节点

    分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上

    什么是slots

    一个Redis集群包含16384个插槽(hash slot),数据库每个键都属于这16384个插槽的其中一个

    集群使用公式CRC16(key)%16384来计算键key属于哪个槽,其中CRC16(key)语句用于计算键key的CRC16校验和。

    集群中的每个节点负责处理一部分插槽,举个例子,如果一个集群可以有主节点,其中:

    集群操作

    向集群中添加数据

    会根据算得的槽,切换到对应的主机

    不在同一个slot下的键值,是不能使用mget,mset等多键操作

    可以通过{} 来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中

    查询集群中的值   找其所对应的插槽,查看插槽键数量,获得键值

    只能查看当前主机所负责的插槽的值

    故障恢复

    如果主节点下线,从节点是否能自动升为主节点

    使用shutdown将6379停掉

    从其他节点登入,查看节点状态

    发现6389将其替代了,在6379重启后,6379作为它的从机


    如果某一段插槽的主从节点都挂掉,而cluster-required-full-coverage为yes,那么整个集群都挂掉

    如果某一段插槽的主从节点都挂掉,而cluster-required-full-coverage为no,那么该插槽数据全都不能使用,也无法存储

    集群的jedis开发

    即使连接的不是主机,集群会自动切换到主机存储,主机写,从机读

    无中心化主从集群,无论哪台主机写的数据,其他主机都能读到数据

    Redis集群的好处和不足

    好处:实现扩容,分摊压力,无中心配置相对简单

    不足:不支持多键操作

    多建的Redis事务是不支持的,lua脚本不支持

    由于集群方案出现比较晚,很多公司采用了其他的集群方案,而代理或客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大

    应用问题的解决

    缓存穿透

            key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源,比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。

    应用服务器的压力突然变大(双十一),redis命中率降低,一直查询数据库(数据库崩溃)

    解决方案

            一个一定不存在缓存及查询不到的数据,由于缓存是不命中时被动写入的,并且出于容错考虑,如果存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。

    对空值缓存:如果一个查询返回的数据为空(不管数据存在不存在),我们仍然会把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过5分钟

    设置可访问的名单(白名单):使用bitmaps类型定义一个可访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。

    采用布隆过滤器(Bloom Filter):是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。(布隆过滤器可以用来检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难)将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。

    进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务。

    黑客攻击,报警解决!!!

    缓存击穿

            key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    某个key过期,大量访问(并发)使用这个key。

    此时redis正常运行,也并没有大量key过期,而数据库的访问压力瞬时增加(可能就垮了)

    解决方案

            key可能会在某个时间点被超高并发地访问,是一种非常“热点”的数据。这个时候需要考虑缓存击穿的问题

    预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据的key的时长

    实时调整:现场监控哪些数据热门,实时调整key的过期时长

    使用锁:        

            就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db

            先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key

            当操作成功返回时,再进行load db的操作,并回设缓存,最后删除mutex key(互斥锁)

            当操作返回成功,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法

    缓存雪崩

            key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

            缓存学崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

    解决方案

    缓存失效时的雪崩效应对底层系统的冲击非常可怕!

    构建多级缓存架构:nginx缓存+reids缓存+其他缓存(ehcache等)

    使用锁或队列:用加锁或队列的方式保证不会有大量线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上,不适用高并发情况

    设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存

    将缓存失效时间分隔开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效事件

    分布式锁

    随着业务发展的需要,原单体单机部署的系统被演化成分布式集群系统后,由于分布式系统多线程,多进程并且分布在不同的机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API不能提供分布式锁的能力。为解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题!

    分布式锁主流的实现方案:

    基于数据库实现分布式锁(乐观锁)

    基于缓存(Redis等)

    基于Zookeeper

    每一种分布式锁解决方案都有各自的优缺点:

    性能:redis最高

    可靠性:zookeeper最高

    这里我们就急于redis实现分布式锁

    分布式锁的特点

    首先,为了确保分布式锁可用,我们至少需要确保锁的实现同时满足以下四个条件

    1、互斥性:任意时刻,只能有一个客户端获取锁,不能同时有两个客户端获取到锁。

    2、安全性:锁只能被持有该锁的客户端删除,不能由其它客户端删除。

    3、死锁:获取锁的客户端因为某些原因(如down机等)而未能释放锁,其它客户端再也无法获取到该锁。

    4、容错:当部分节点(redis节点等)down机时,客户端仍然能够获取锁和释放锁。

    加锁和解锁必须具有原子性

    使用redis实现分布式锁

    设置锁和过期时间

    setnx命令,设置锁

    第一次执行setnx命令,给这个key加上了锁,后面再添加无效

    将其删除掉(释放锁),再次设置即可

    但此时如果锁一直没有释放就死锁了

    可以为锁设置过期时间,到时间自动释放

    但是如果在手动设置过期时间前服务器宕机了咋整

    那么就需要在上锁的同时设置过期时间    补充:2.6.12版本以后可以通过set命令设置锁

    格式:SET key value NX EX max-lock-time

    Jedis操作

    先设置下值

    测试代码如下

    1. @GetMapping("/testLock")
    2. public void testLock() {
    3. //获取锁
    4. Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "110", 3, TimeUnit.SECONDS);
    5. //获取锁成功,查询num的值
    6. if (lock) {
    7. Object value = redisTemplate.opsForValue().get("num");
    8. //判断num为空return
    9. if (StringUtils.isEmpty(value)) {
    10. return;
    11. }
    12. //有值就转成int
    13. int num = Integer.parseInt(value + "");
    14. //把redis的num加1
    15. redisTemplate.opsForValue().set("num", ++num);
    16. //释放锁,del
    17. redisTemplate.delete("lock");
    18. } else {
    19. //获取锁失败,每隔0.1s在获取
    20. try {
    21. Thread.sleep(100);
    22. testLock();
    23. } catch (InterruptedException e) {
    24. e.printStackTrace();
    25. }
    26. }
    27. }

    使用ab进行测试

    命令如下

    ab -n 1000 -c 100 -k http://项目运行ip:8080/app/redis/testLock
    

     查看结果


    UUID防止误删

    当服务器a上了一个锁,正在执行具体操作时出现卡顿,锁时间到自动释放,然后服务器b抢到了锁,执行具体操作时,服务器a又继续进行操作,然后还把锁释放了,此时它释放的就是b的锁

    使用uuid表示不同的操作

    释放锁前判断当前uuid和要释放锁的uuid是否相同,代码如下

    1. @ResponseBody
    2. @GetMapping("/testLock")
    3. public void testLock() {
    4. String uuid = UUID.randomUUID().toString();
    5. //获取锁
    6. Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
    7. //获取锁成功,查询num的值
    8. if (lock) {
    9. Object value = redisTemplate.opsForValue().get("num");
    10. //判断num为空return
    11. if (StringUtils.isEmpty(value)) {
    12. return;
    13. }
    14. //有值就转成int
    15. int num = Integer.parseInt(value + "");
    16. //把redis的num加1
    17. redisTemplate.opsForValue().set("num", ++num);
    18. //释放锁,del
    19. String lockUuid = (String) redisTemplate.opsForValue().get(lock);
    20. if (uuid.equals(lockUuid)) {
    21. redisTemplate.delete("lock");
    22. }
    23. } else {
    24. //获取锁失败,每隔0.1s在获取
    25. try {
    26. Thread.sleep(100);
    27. testLock();
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }
    31. }
    32. }

    LUA保证删除原子性

    上面代码如果服务器a在比较中结果true,进入了其中,正准备删除但还没删除,锁到了过期时间自动释放了,那么如果有别的服务器拿到了锁,服务器a再进行释放的就是别人的锁。

    1. /*使用lua脚本来锁*/
    2. // 定义lua 脚本
    3. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    4. // 使用redis执行lua执行
    5. DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
    6. redisScript.setScriptText(script);
    7. // 设置一下返回值类型 为Long
    8. // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
    9. // 那么返回字符串与0 会有发生错误。
    10. redisScript.setResultType(Long.class);
    11. // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
    12. redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);

    Redis6.0新功能

    ACL

            Redis ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

            在Redis5版本之前,Redis安全规则只有密码控制还有通过rename来调整高危命令比如flushdb,keys *,shutdown等,Redis6则提供ACL功能对用户进行更细粒度的权限控制:

    接入权限:用户名和密码

    可以执行的命令

    可以操作的KEY

    参考官网:https://redis.io/topics/acl

    命令

    acl list展现用户权限列表

    查看当前用户

     

    通过命令创建新用户默认权限

     

    没有指定任何规则,如果用户不存在,将使用justcreate的默认属性来创建用户,如果已存在,将不执行任何操作

    新建用户设置权限

     启用 密码123456 可操作key:cached:* 可执行命令:get

    切换用户,验证权限

    IO多线程

    指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依旧是单线程的。

    Redis6加入多线程,但跟Memcached这种从IO处理到数据访问多线程的实现模式有些差异,Redis的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程的。之所以这么设计是不想因为多线程而变得复杂,需要去控制key,lua,事务,LPUSH/LPOP等等的并发问题,整体设计如下

    另外多线程IO默认也是不开启的,需要在配置文件中配置

    io-threads-do-reads yes

    io-threads 4

    工具支持Cluster

    之前老版Redis想要搭集群需要单独安装ruby环境,Redis5将redis-trib.rb的功能集成到redis-cli。另外官方redis-benchmark工具开始支持cluster模式了,通过多线程的方式对多个分片进行压测

     Redis新功能持续关注

    Redis6新功能还有:

    1、RESP3新的 Redis 通信协议:优化服务端与客户端之间通信

    2、Client sidecaching客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。

    3、Proxy集群代理模式:Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多Key操作。

    4、Modules API

    Redis6中 模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。Redis一开始就是一个向编写各种系统开放的平台。

  • 相关阅读:
    LQ0001 方程整数解【枚举】
    第3章-线性方程组(3)
    FP64、FP32、FP16、int8
    (三)爬取一些网页图片
    Sketch是什么软件,如何收费和获得免费版
    angular:trunk包探究
    当大火的文图生成模型遇见知识图谱,AI画像趋近于真实世界
    京东平台数据分析:2023年9月京东扫地机器人行业品牌销售排行榜
    jdk 安装与配置环境(windows系统)
    Flutter 环境配置
  • 原文地址:https://blog.csdn.net/qq_53157982/article/details/125458571