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

Redis主从复制

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

能做什么?

读写分离,性能扩展

容灾快速恢复

怎么用

创建/myredis文件夹

mkdir /myredis
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操作

先设置下值

测试代码如下

    @GetMapping("/testLock")
    public void testLock() {
        //获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "110", 3, TimeUnit.SECONDS);
        //获取锁成功,查询num的值
        if (lock) {
            Object value = redisTemplate.opsForValue().get("num");
            //判断num为空return
            if (StringUtils.isEmpty(value)) {
                return;
            }
            //有值就转成int
            int num = Integer.parseInt(value + "");
            //把redis的num加1
            redisTemplate.opsForValue().set("num", ++num);
            //释放锁,del
            redisTemplate.delete("lock");
        } else {
            //获取锁失败,每隔0.1s在获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

使用ab进行测试

命令如下

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

 查看结果


UUID防止误删

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

使用uuid表示不同的操作

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

    @ResponseBody
    @GetMapping("/testLock")
    public void testLock() {
        String uuid = UUID.randomUUID().toString();
        //获取锁
        Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
        //获取锁成功,查询num的值
        if (lock) {
            Object value = redisTemplate.opsForValue().get("num");
            //判断num为空return
            if (StringUtils.isEmpty(value)) {
                return;
            }
            //有值就转成int
            int num = Integer.parseInt(value + "");
            //把redis的num加1
            redisTemplate.opsForValue().set("num", ++num);
            //释放锁,del
            String lockUuid = (String) redisTemplate.opsForValue().get(lock);
            if (uuid.equals(lockUuid)) {
                redisTemplate.delete("lock");
            }
        } else {
            //获取锁失败,每隔0.1s在获取
            try {
                Thread.sleep(100);
                testLock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

LUA保证删除原子性

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

            /*使用lua脚本来锁*/
            // 定义lua 脚本
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            // 使用redis执行lua执行
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
            redisScript.setScriptText(script);
            // 设置一下返回值类型 为Long
            // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
            // 那么返回字符串与0 会有发生错误。
            redisScript.setResultType(Long.class);
            // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
            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一开始就是一个向编写各种系统开放的平台。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值