Redis(五)主从复制、集群操作、应用问题解决、Redis6新功能

主从复制

主从复制是指主机数据更新后根据配置和策略,自动同步到从机的master/slaver机制,Master(主机)以写为主,而Slave(从机)以读为主。

主从复制能做什么?

  1. 读写分离,性能扩展。
  2. 容灾快速备份。

在这里插入图片描述

用到的相关命令

配置当前服务器成为其他实例的从服务器。

slaveof <ip> <port> 

打印主从信息

info replication

需要注意的问题

  1. 在主机上进行写操作,在从机上读取数据。如果在从机写数据会报错。
  2. 主机宕机重启即可。
  3. 由于是在一台操作系统上配置主从,通过命令行的设置只在此次服务中生效,因此从机宕机后,会成为主机。可以将配置增加到文件中,就可以永久生效了,这里还是在命令行里接着设置。

一主二仆

slave宕机后,master执行写操作,slave重启后仍能同步。

master宕机后,slave原地待命。

薪火相传

上一个slave可以是下一个slave的master,slave同样可以接收其他slaves的连接和同步请求,该slave作为链条中下一个的master,可以有效减轻master的写压力,去中心化从而降低风险。

中途变更转向会清除之前的数据,重新建立拷贝最新的。风险是一旦某个slave宕机,后面的slave都没法备份了。

master宕机后,slave还是slave,无法写入数据。

在这里插入图片描述

反客为主

当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。

使用slave no one命令将slave变为master。

复制原理

  1. Slave启动成功连接到Master后发送一个sync命令。
  2. Master接到命令后启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,Master将传送整个数据文件到slave,以完成一次完全同步。
  3. 全量复制:而Slave服务在接收到数据库文件数据后,将其存盘并加载到内存中。
  4. 增量复制:Master继续将新的所有收集到的修改命令依次传给Slave,完成同步。
  5. 只要重新连接Master,一次完成同步(全量复制)将被自动执行。

在这里插入图片描述

哨兵模式

哨兵模式是反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将slave转换为master。

在这里插入图片描述
使用哨兵模式要有sentinel.conf文件,内容如下

sentinel monitor mymaster 127.0.0.1 6379 1

其中mymaster为监控对象起的服务器名称,1为至少有多少个哨兵同意迁移的数量。

启动哨兵

redis做压测可以使用自带的redis-benchmark工具。

执行redis-sentinel sentinel.conf即可启动哨兵。

在这里插入图片描述
当master宕机后,slave选举产生新的master,选举依据优先级别slave-priority,值越小,优先级越高,默认值为100。

当原先的master重启恢复后,它又会怎么样呢?

故障恢复

当master宕机后,从其所有slave中挑选一个slave作为新的master,选择条件依次为:

  1. 选择优先级靠前的。
  2. 选择偏移量最大的。
  3. 选择runid最小的slave。

注:

优先级在redis.conf中默认100,值越小优先级越高;

偏移量是指获得原主机数据最全的;

每个redis实例启动后都会随机生成一个40位的runid。

挑选出新的master之后的sentinel向原master的slave发送slaveof master 的命令,复制新的master。

当原master重启上线时,sentinel会给其发送slaveof 命令,让原master成为新主机的slave

复制延时

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

Java实现

private static JedisSentinelPool jedisSentinelPool=null;

public static Jedis getJedisFromSentinel(){
	if(jedisSentinelPool==null){
        Set<String> sentinelSet=new HashSet<>();
        sentinelSet.add("192.168.11.103:26379");

        JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10); //最大可用连接数
		jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
		jedisPoolConfig.setMinIdle(5); //最小闲置连接数
		jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
		jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
		jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong

		jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
		return jedisSentinelPool.getResource();
    }else{
		return jedisSentinelPool.getResource();
    }
}

Redis集群

问题引入

容量不够,Redis如何进行扩容;并发写操作,Redis又如何分摊数据。

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

之前Redis通过代理主机来解决,但Redis 3.0中提供了更好的解决方案,即无中心化集群配置。

集群及其配置

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

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

集群配置

需要配置六个服务作为集群,端口号分别为:

6379、6380、6381、6389、6390、6391

修改并创建好这六个端口对应的redis.conf:

include redis.conf
port 6379 #对应端口号,下述同
pidfile /var/run/redis_6379.pid
dbfilename dump6379.rdb
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000

启动该六个服务:
在这里插入图片描述
将六个结点合为一个集群,首先要确保所有实例启动正常:

[root@localhost ~]# cd redis-6.2.6/
[root@localhost redis-6.2.6]# cd src/
[root@localhost src]# redis-cli --cluster create --cluster-replicas 1 192.168.36.128:6379 192.168.36.128:6380 192.168.36.128:6381 192.168.36.128:6389 192.168.36.128:6390 192.168.36.128:6391

注:–replicas 1表示为集群中的每一个主节点创建一个从节点。

在这里插入图片描述
在这里插入图片描述
由于是无中心化集群配置,因此从集群的任一主机登录均可。

在登录时,应采用集群策略连接,命令选项应加上-c:

redis-cli -c -p 6379

通过cluster nodes命令即可查看集群信息:

127.0.0.1:6379> cluster nodes
bf6cecf59af3c66dd9eb57f3d9533d1ad8afc87b 192.168.36.128:6391@16391 slave 197eb5b52ebceab096907c310c872813abf8fc88 0 1648191838810 2 connected
b21db9680a72a4656503fca78ed1c3a70f274153 192.168.36.128:6381@16381 master - 0 1648191840855 3 connected 10923-16383
9ea6265df5f0b969b031ab2e1ee82c890693f2e2 192.168.36.128:6379@16379 myself,master - 0 1648191840000 1 connected 0-5460
e6e95710e2ccfe6f398cac2afc7ee8558fc94668 192.168.36.128:6389@16389 slave b21db9680a72a4656503fca78ed1c3a70f274153 0 1648191841000 3 connected
197eb5b52ebceab096907c310c872813abf8fc88 192.168.36.128:6380@16380 master - 0 1648191841877 2 connected 5461-10922
73778dceaaeba827cd6d21919c133cfc681687ec 192.168.36.128:6390@16390 slave 9ea6265df5f0b969b031ab2e1ee82c890693f2e2 0 1648191842901 1 connected

集群如何分配主从节点

一个集群至少要有三个主节点,分配原则尽量保证每个主数据库运行在不同的IP地址,且每个从库和主库不在一个IP地址上。

slots(插槽)

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

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

集群中的每个节点负责处理一部分插槽,在上边查看cluster nodes集群信息时可以看到6379负责 0-5460号插槽,6380负责5461-10922号插槽,6381负责10923-16383号插槽。

在集群中录入值

在redis-cli每次录入、查询键值操作时,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应该前往的redis实例地址和端口。

redis-cli客户端提供了-c参数实现自动重定向,若不使用-c参数,存储数据时,会出现MOVED重定向操作。

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

192.168.36.128:6381> mset k1 v1 k2 v2 k3 v3
(error) CROSSSLOT Keys in request don't hash to the same slot

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

192.168.36.128:6381> mset name{user} lucy age{user} 20
-> Redirected to slot [5474] located at 192.168.36.128:6380
OK

查询集群中的值

CLUSTER GETKEYSINSLOT <slot> <conut>返回count个slot槽中的键。

192.168.36.128:6380> cluster keyslot user
(integer) 5474
192.168.36.128:6380> cluster countkeysinslot 5474
(integer) 2
192.168.36.128:6379> cluster getkeysinslot 5474 10
(empty array)
192.168.36.128:6379> cluster getkeysinslot 325 10
1) "k6"

当查询不是当前集群所负责的插槽,查询为空。

故障恢复

如果主节点宕机下线,15秒超时后,从节点将自动升为主节点;且主节点恢复后,主节点会变为从机。

如果某一段插槽的主从都宕机,而cluster-require-full-coverage设为yes,那么整个集群都挂掉。

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

集群的Jedis开发

package com.atguigu.seckill;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;

/**
 * @version 1.0
 * @Description
 * @Author 月上叁竿
 * @Date 2022-03-25 15:49
 **/
public class TestJedisCluster {
    public static void main(String[] args) {
        HostAndPort hp = new HostAndPort("192.168.36.128",6379);
        JedisCluster jedisCluster = new JedisCluster(hp);

        jedisCluster.set("k4", "v4");
        String k4 = jedisCluster.get("k4");
        System.out.println(k4);
    }
}

集群的优劣

优点

  1. 实现扩容
  2. 分摊并发压力
  3. 无中心配置相对简单

缺点

  1. 多键操作不被支持。
  2. 多键的Redis事务不被支持,且LUA脚本不被支持。
  3. 由于集群方案出现较晚,很多公司采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

Redis应用问题解决

缓存穿透

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

解决方案

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

解决方案如下:

  1. 对空值缓存:如果一个查询返回的数据为空(不管数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长时间不超过5分钟。
  2. 设置可访问的名单(白名单):使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移值,每次访问和bitmaps里的id进行比较,如果访问id不在bitmaps里,就进行拦截,不允许访问。
  3. 采用布隆过滤器:(布隆过滤器(Bloom Filter))是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法, 缺点是有一定的误识别率和删除困难。
  4. 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问数据,和运维人员进行配合,设置黑名单限制服务等。

缓存击穿

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

解决方案

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

解决方案如下:

  1. 预先设置热门数据:在Redis高峰访问之前,把一些热门数据提前存入到Redis里,加大这些热门数据key的时长。
  2. 实时调整:现场监控哪些数据热门,实时调整key的过期时长。
  3. 使用锁:
    1. 在缓存失效的时候(判断拿出来的值为空),而不是立即去加载后端DB。
    2. 先使用缓存工具的某些带成功操作返回值的操作(比如setnx),去set一个mutex key。
    3. 当操作返回成功时,再进行加载后端DB的操作,并回设缓存,最后删除mutex key。
    4. 当操作返回失败,证明有线程在加载后端DB,当前线程睡眠一段时间再重试整个get缓存的方法。

缓存雪崩

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

缓存雪崩与缓存击穿的区别在于雪崩针对很多key缓存,而击穿只针对某一个key

正常访问情况:

在这里插入图片描述
缓存失效瞬间:

在这里插入图片描述
缓存失效时的雪崩效应对底层系统的冲击非常可怕!

解决方案如下:

  1. 构建多级缓存架构:Nginx缓存 + Redis缓存 + 其他缓存(Ehcache等)。
  2. 使用锁或队列:用加锁或者队列的方式保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用于高并发情况。
  3. 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
  4. 将缓存失效时间分散开:比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

分布式锁

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

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

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper

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

  1. 性能:Redis最高。
  2. 可靠性:Zookeeper最高。

后续更新吧…感觉还是没有通透 通透再来。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值