一周一个中间件-redis

前言

redis作为内存数据库,具有高速读取效率。因为单线程原因。避免多线程之间的切换时间。读取速率快。

基础知识

  • redis数据结构

    • String 字符串
    • List 数组
    • hash 是一种键值对,存储的时候必须成对的出现。redis的key对应的value,这个value是map<string,Object>结构
    • Set set数组不重复
    • ZSet sort set 数组 有序数组
    • HyperLogLog 布隆过滤器
    • GEOHash 将二维数据映射到一维数据,可以做“附近的人”业务逻辑
  • 主观下线

所谓主观下线(Subjectively Down, 简称 SDOWN)指的是单个Sentinel实例对服务器做出的下线判断,即单个sentinel认为某个服务下线(有可能是接收不到订阅,之间的网络不通等等原因)。
主观下线就是说如果服务器在down-after-milliseconds给定的毫秒数之内, 没有返回 Sentinel 发送的 PING 命令的回复, 或者返回一个错误, 那么 Sentinel 将这个服务器标记为主观下线(SDOWN )。
sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例时候在线(对该sentinel来说是“主观在线”)。
sentinel配置文件中的down-after-milliseconds设置了判断主观下线的时间长度,如果实例在down-after-milliseconds毫秒内,返回的都是无效回复,那么sentinel回认为该实例已(主观)下线,修改其flags状态为SRI_S_DOWN。如果多个sentinel监视一个服务,有可能存在多个sentinel的down-after-milliseconds配置不同,这个在实际生产中要注意。

  • 客观下线

客观下线(Objectively Down, 简称 ODOWN)指的是多个 Sentinel 实例在对同一个服务器做出 SDOWN 判断, 并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后, 得出的服务器下线判断,然后开启failover。
客观下线就是说只有在足够数量的 Sentinel 都将一个服务器标记为主观下线之后, 服务器才会被标记为客观下线(ODOWN)。
只有当master被认定为客观下线时,才会发生故障迁移。
当sentinel监视的某个服务主观下线后,sentinel会询问其它监视该服务的sentinel,看它们是否也认为该服务主观下线,接收到足够数量(这个值可以配置)的sentinel判断为主观下线,既任务该服务客观下线,并对其做故障转移操作。
sentinel通过发送 SENTINEL is-master-down-by-addr ip port current_epoch runid,(ip:主观下线的服务id,port:主观下线的服务端口,current_epoch:sentinel的纪元,runid:*表示检测服务下线状态,如果是sentinel 运行id,表示用来选举领头sentinel)来询问其它sentinel是否同意服务下线。
一个sentinel接收另一个sentinel发来的is-master-down-by-addr后,提取参数,根据ip和端口,检测该服务时候在该sentinel主观下线,并且回复is-master-down-by-addr,回复包含三个参数:down_state(1表示已下线,0表示未下线),leader_runid(领头sentinal id),leader_epoch(领头sentinel纪元)。
sentinel接收到回复后,根据配置设置的下线最小数量,达到这个值,既认为该服务客观下线。

  • 节点数据同步

rdb(redis database) 在指定时间间隔内,将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时是将快照文件直接读到内存中,来达到恢复数据的。
aop (append only file)以日志的形式记录Redis每一个写操作,将Redis执行过的所有写指令记录下来(读操作不记录),只许追加文件不可以改写文件,redis启动之后会读取appendonly.aof文件来实现重新恢复数据,完成恢复数据的工作。默认不开启,需要将redis.conf中的appendonly no改为yes启动Redis。

参考文献

windows下载redis https://github.com/microsoftarchive/redis/releases

linux下载redishttps://redis.io/download

Redis哨兵模式
Redis哨兵模式(sentinel)学习总结及部署记录(主从复制、读写分离、主从切换

集群架构

主从模式

一个master可以有多个slaves,但是主挂掉之后,slave不能变成master

sentinel模式(哨兵模式)

sentinel模式是集成在redis中。sentinel系统可以监控一个或者多个redis master服务。以及这些master服务的所有从服务。当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。
一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。

* 当sentinel发现master节点挂了以后,sentinel就会从slave中重新选举一个master。
* sentinel模式是建立在主从模式的基础上,如果只有一个Redis节点,sentinel就没有任何意义
* 当master节点挂了以后,sentinel会在slave中选择一个做为master,并修改它们的配置文件,其他slave的配置文件也会被修改,比如slaveof属性会指向新的master
* 当master节点重新启动后,它将不再是master而是做为slave接收新的master节点的同步数据
* sentinel因为也是一个进程有挂掉的可能,所以sentinel也会启动多个形成一个sentinel集群
* 当主从模式配置密码时,sentinel也会同步将配置信息修改到配置文件中,不许要担心。
* 一个sentinel或sentinel集群可以管理多个主从Redis。
* sentinel最好不要和Redis部署在同一台机器,不然Redis的服务器挂了以后,sentinel也挂了
* sentinel监控的Redis集群都会定义一个master名字,这个名字代表Redis集群的master Redis
* 根据slaveof x.x.x.x 6379 从库设置主库链接 可以做到主从复制
* 通过sentinel,设置的sentinel monitor mymaster 47.104.87.10 6379 2 向主节点获取所有主从节点信息,选中主节点。
* Master-Slave切换后,master_redis.conf、slave_redis.conf和sentinel.conf的内容都会发生改变,即master_redis.conf中会多一行slaveof的配置,sentinel.conf的监控目标会随之调换。

操作
当使用sentinel模式的时候,客户端就不要直接连接Redis,而是连接sentinel的ip和port,由sentinel来提供具体的可提供服务的Redis实现,这样当master节点挂掉以后,sentinel就会感知并将新的master节点提供给使用者。

sentinel工作模式

  • 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个PING命令。
  • 如果一个实例(instance)距离最后一次有效回复PING命令的时间超过 own-after-milliseconds 选项所指定的值,则这个实例会被Sentinel标记为主观下线。
  • 如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
  • 当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线。
    在一般情况下,每个Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
  • 当Master被Sentinel标记为客观下线时,Sentinel 向下线的 Master 的所有Slave发送 INFO命令的频率会从10秒一次改为每秒一次。
  • 若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除。 若 Master重新向Sentinel 的PING命令返回有效回复,Master的主观下线状态就会被移除。

sentinel内部3个定时任务

  • 每10秒每个sentinel会对master和slave执行info命令,这个任务达到两个目的 a)发现slave节点 b)确认主从关系
  • 每2秒每个sentinel通过master节点的channel交换信息(pub/sub)。master节点上有一个发布订阅的频道(sentinel:hello)。sentinel节点通过sentinel:hello频道进行信息交换(对节点的”看法”和自身的信息),达成共识。
  • 每1秒每个sentinel对其他sentinel和redis节点执行ping操作(相互监控),这个其实是一个心跳检测,是失败判定的依据。

Redis Cluser模式

  • 中心架构
  • 数据按照slot存储分布在多个节点,节点间数据共享,可动态调整数据分布
  • 可扩展性,可线性扩展到1000个节点,节点可动态添加或删除
  • 高可用性,部分节点不可用时,集群仍可用。通过增加Slave做standby数据副本,能够实现故障自动 failover,节点之间通过gossip协议交换状态信息,用投票机制完成Slave到Master的角色提升.
  • 槽位

redis cluster一共有16384个槽。编号为0,1,2,3…16383。这个槽是虚拟的槽位。并不是真实存在的。每个Master节点都会负责一部分槽。当redis的某些key值被映射到某些master负责的槽。至于那个Master节点负责那个槽。1)可以由用户指定,2)可以在初始化的时候自动生成(redis-trib.rb脚本)。Master才有拥有槽的所有权,slave只负责槽的使用,没有所有权。
Master节点维护着16384/8字节的位序列。Master节点用bit来标识对于某个槽自己是否拥有。集群同时还维护着槽到集群节点的映射,是由长度为16384类型为节点的数组实现的,槽编号为数组的下标,数组内容为集群节点,这样就可以很快地通过槽编号找到负责这个槽的节点。位序列这个结构很精巧,即不浪费存储空间,操作起来又很便捷。

  • 键空间分布算法

通过哈希算法再加上取模运算可以将一个值固定地映射到某个区间,在这里,这个区间叫做slots,区间由连续的slot组成。在Redis Cluster中,我们拥有16384个slot,这个数是固定的,我们存储在Redis Cluster中的所有的键都会被映射到这些slot中.
HASH_SLOT = CRC16(key) mod 16384

  • 键哈希标签原理

这是是用户将一批键存放在同一个槽中的实现方法。用户需要按照指定规则生成key。
规则: abc{userId}def和ghi{userId}jkl
redis在计算槽编号的时候只会获取{}之间的字符串进行槽编号计算,这样由于上面两个不同的键,{}里面的字符串是相同的,因此他们可以被计算出相同的槽,

  • 重新分片

当集群Master节点出现问题。出现槽和节点映射出现问题。但是槽和键的映射关系不变。
MIGRATING状态
预备迁移槽的时候槽的状态首先会变为MIGRATING状态.当客户端请求的某个Key所属的槽处于MIGRATING状态的时候,影响有下面几条:

如果Key存在则成功处理

如果Key不存在,则返回客户端ASK,仅当这次请求会转向另一个节点,并不会刷新客户端中node的映射关系,也就是说下次该客户端请求该Key的时候,还会选择MasterA节点

如果Key包含多个命令,如果都存在则成功处理,如果都不存在,则返回客户端ASK,如果一部分存在,则返回客户端TRYAGAIN,通知客户端稍后重试,这样当所有的Key都迁移完毕的时候客户端重试请求的时候回得到ASK,然后经过一次重定向就可以获取这批键

IMPORTING状态预备将槽从MasterA节点迁移到MasterB节点的时候,槽的状态会首先变为IMPORTING。IMPORTING状态的槽对客户端的行为有下面一些影响

正常命令会被MOVED重定向,如果是ASKING命令则命令会被执行,从而Key没有在老的节点已经被迁移到新的
节点的情况可以被顺利处理;

如果Key不存在则新建;

没有ASKING的请求和正常请求一样被MOVED,这保证客户端node映射关系出错的情况下不会发生写错;

总结redis数据同步:

  1. 当一个从数据库启动时,会向主数据库发送sync命令,
  2. 主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来
  3. 当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。
  4. 从数据库收到后,会载入快照文件并执行收到的缓存的命令。

安装redis

  • sentinel模式安装
  • redis cluster模式安装

redis缓存失效机制

redis对缓存失效二种方法
客户端去查询时进行的消极处理
主线程定时主动处理

redis的失效缓存机制需要从EXPIRE命令来说,EXPIRE允许用户为某个key指定超时时间,当超过这个时间之后key对应的值会被清除。redis数据库中有二个位置,dict位置存储正常数据,expires使用存储关联过期时间key的。
redis查询机制从expires中查找key的过期时间,如果不存在说明对应key没有设置过期时间,直接返回如果是slave机器,则直接返回,因为Redis为了保证数据一致性且实现简单,将缓存失效的主动权交给Master机器,
slave机器没有权限将key失效。如果当前是Master机器,且key过期,则master会做两件重要的事情:1)将删除命令写入AOF文件。2)通知Slave当前key失效,可以删除了。3)master从本地的字典中将key对于的值删除。

redis服务端定时去检查失效的缓存。使用的淘汰策略
redis定时时间:配置项 hz默认为10,就是CPU空闲时每秒执行10次,但是每次执行的时间都不超过cpu的25%时间,hz=1,一次清理最大250ms,hz=10,每次清理最大25ms。另外一个配置maxmemory最大值,当内存超过maxmemory限定时,就会触发主动清理策略。每隔一段时间随机抽取一部分key来检查清除,大于某个阈值继续检查清除,小于某个阈值就结束。
结论:因为算法采用随机key判断,故不能清理完所有过期key
定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除.
内存淘汰策略

  1. noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
  2. allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
  3. volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
  4. allkeys-random: 回收随机的键使得新添加的数据有空间存放。
  5. volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
  6. volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

redis分布式锁

redis内部命令INCR
将要加锁的key执行INCR操作进行加1.如果返回的不是大于1,就代表已经加锁了,如果等于1,代表加锁成功。

  • SETNX内部命令

如果key不存在,就将key设置value.如果成功,就加锁成功,如果key已经成功,代表已经被加锁,SETNX不做任何操作。

  • SET内部命令

带有过期时间限制的锁。
Redisson可以通过看门狗模式来进行续期。
Redis分布式锁的缺点
在redis cluster和主从架构,客户端1对原先的redis master加锁,Master宕机后,新的Slave变成Master,客户端2再对新的Master加锁。导致多个客户端对一个分布式锁完成加锁。

redis客户端

  • jedis

轻量,简洁。支持连接池。支持pipelining,事务,LUA Scripting。Redis Sentinel,Redis Cluster。不支持读写分离。需要自己实现。

@Test
public void test4combPipelineTrans() {
    jedis = new Jedis("localhost");
    long start = System.currentTimeMillis();
    // 开启Redis管道,开启事务
    Pipeline pipeline = jedis.pipelined();
    pipeline.multi();
    for (int i = 0; i < 100000; i++) {
        pipeline.set("" + i, "" + i);
    }
     // 执行Redis管道事务
    pipeline.exec();
    List<Object> results = pipeline.syncAndReturnAll();
    long end = System.currentTimeMillis();
    // 关闭Redis事务
    jedis.disconnect();
}
  • Redisson

Redisson内部提供一个监控锁的看门狗,每十秒检查一下,它的作用是在Redisson实例被关闭前,不断延长锁的有效期,默认情况下,看门狗的检查锁的超时时间是30秒,也可以通过Config.lockWatchdog.timeout来自定义设置。默认时间加锁时间是30秒,当任务执行到30-10=20秒,就会进行一次续期。把锁重置到30秒。服务器宕机后,定时任务跑步了,30秒后自动解开了

Redisson还可以通过加锁的方式提供leasetime的参数来指定加锁时间,超过时间锁自动解开了。

每次加锁加锁次数加1,每次减锁对锁进行减1,当发现加锁次数位0时,客户端释放锁成功。这就是分布式锁开源Redisson框架的实现原理。
基于netty实现,采用非阻塞IO,支持异步请求,支持连接池。支持pipelining, LUA Scripting,Redis Sentinel,Redis Cluster.Redisson建议LUA Scripting代替事务。支持读写分离,支持读写负载均衡。在主从复制,和Redis Cluster架构都可以使用。

posted @ 2020-12-01 06:31  jack_zdl  阅读(163)  评论(0编辑  收藏  举报