redis学习汇总

一、redis是用C语言开发的一个开源高性能键值对内存数据库,可用作数据库、缓存、消息代理:

性能优秀,读写速度非常快;线程安全;丰富的数据类型;支持数据持久化;

  高性能:执行耗时久且结果不经常变动的sql,将结果放进缓存,查询时间短。

      经典场景是:电商平台,某个产品一天之内信息都不变,但每次查询耗时2S(放进缓存可能就是每次2ms),一天内100万次浏览;

  高并发:redis缓存作为缓冲,支持高性能的主从复制的集群策略。MySQL并不好支持高并发,单机最多支撑到2000qps,而缓存单机支撑的并发量一秒几万十几万,是MySQL的几十倍并发量。;

  可用作分布式锁,功能可用其他中间件替代(如zookeeper)

  redis内部使用一个redisObject对象表示所有key和value。redisObject包含type(表示一个value对象具体是何种数据类型)、encoding(表示不同数据类型在redis内部的存储方式)

1、redis五种主要的数据类型:String、Hash、list、set、sortedSet有序集合

(1)String是redis最基本类型,二进制安全故redis的string类型可包含任何数据,例如jpg图或者序列化的对象,简单的set/get

(2)Hash是一个键值(key-value)集合,redis的hash是一个string的key和value的映射表,适合存储对象,常用命令hget、hset、hgetall;

(3)list列表是简单的字符串列表,按照插入顺序排序(有序且可重复)。

1 可做粉丝列表:key=某大V,value=[张三、李四、王五]

2可做文章的评论列表

3通过lrange命令,从某个元素开始读取多少个元素,可基于list实现分页查询。基于redis实现简单的高性能分页,做类似微博下拉不断分页的东西,性能高

4 做简单的消息对列,从list头放进去,从list尾巴拿出来

常用命令:lpush、lpop、rpush、rpop、lrange(获取部分列表)。redis的list的实现是双向链表

(4)set是string的无序集合,通过hashtable实现。set中元素无序且不重复

做交集、并集、差集操作,比如交集可以是两个人的共同微博粉丝

常用命令sadd、spop、smembers、sunion

(5)sortedSet与set的区别就是有序,常用命令zadd、zrange、zrem、zcard。

sortedSet关联了一个double类型权重的参数score,redis就是通过score来为集合中的成员进行从小到大排序

sortedSet内部使用hashmap和跳跃表skipList来保证数据的存储和有序,hashmap放的是成员到score的映射,跳跃表放的是所有成员,排序依据是hashmap里存的score,跳跃表查询效率高,实现简单:

做排行榜:zadd board 命令,是给score的

zadd board 85  张三

zadd board 90  王五

zadd board 80  李四

zrevrange board 0 99  获取排名前100的用户

zrank board 李四  可看到用户在排行榜里的排名

2、redis的架构模式

(1)单机版:多个客户端对应一个redis服务器,特点是简单,但是内存容量有限,处理能力有限,无法高可用,不支持高并发;

(2)主从复制:多个从redis服务器从一个主redis服务器拷贝数据,多个客户端从从redis服务器get数据(也可从主redis服务器get),从服务器仅提供读操作,主服务器提供写操作。

  优点:用户从主redis服务器复制多个从redis服务器,两者数据相同,主服务器更新数据到从服务器,降低了主服务器的读压力,再转交从库

  缺点:未解决主服务器的写压力

注:关于主从架构的,文章后面有详细的说明,请直接移步文章末尾

(3)哨兵:监听主节点的存活状态,如果主节点挂掉,从节点能继续提供缓存功能(主从节点结合哨兵模式能解决单点故障问题,提高redis可用性)

(4)集群(proxy型)

3、redis怎么用

结合springboot使用,一种是通过RedisTemplate使用,另一种是使用spring cache集成Redis(注解的方式)

redis注解:

@cacheable根据方法的请求参数对其结果进行缓存  @cacheable(value = "",key = "")

@cacheable(value=“user”,key="#id")

public User getUser(int  id){

  return xxxmap.get(id);

}

@cachePut,与@cacheable作用一样,不同的是它每次都会出发真实方法的调用@cachePut(value=“”,key="")

@cachePut(value=“user”,key="#user.id")

public User saveUser(User user){

  return user;

}

@cacheEvict  根据条件对缓存进行清空@cacheEvict(value=“”,key="")

@cacheEvict(value=“user”,key="#id")

public void deleteUser(int  id){

  removeUser( id );

}

二、使用redis有什么缺点?

1、缓存与DB双写一致性问题:分布式环境下非常容易出现缓存与数据库间数据一致性问题

我们只能采取合适的策略去降低缓存与数据库间数据不一致的概率,而无法保证两者间的强一致性

  (1)有强一致性要求的数据,不要用缓存

  (2)采取正确更新策略,更新数据库后及时更新缓存,或者先更新DB再删缓存,删缓存失败可提供补偿,用消息队列

2、缓存雪崩:即缓存在同一时间大面积失效,这时来了一拨请求,全怼到DB上导致DB连接异常

  解决办法就是,方法1:在往redis存数据的时候,设置缓存的失效时间加一个随机值,避免集体失效

  方法2 :若redis是集群部署,将热点数据均匀分布在不同redis库中,也能避免全部失效,或者设置热点数据永不过期,有更新操作就更新缓存就好了

  方法3: 建立备份缓存,缓存A和缓存B,A设置超时时间B不设置,先从A读缓存,A没有读B并且更新A,B

  雪崩实际出现的场景:电商,定时午夜12点刷新缓存(即设置的缓存失效时间是午夜12点),但是12点有个秒杀活动,活动开始时大量用户涌入但缓存失效了,所有请求全怼到数据库上了,DB挂了

3、缓存穿透:即请求数据库和缓存中不存在的数据,导致所有请求到DB上,从而DB连接异常

  解决办法1:接口层增加校验,比如用户鉴权,参数做校验,不合法的直接return,比如id<=0的直接拦截

  解决办法2:redis里的布隆过滤器,利用高校的数据结构和算法快速断定你这个key是否在数据库中,不存在则return,存在就去查DB刷新KV再return

4、缓存击穿:与雪崩有点像,某个key是热点,大量请求同时访问它,在这个key失效的瞬间,持续的大并发直接落到数据库上,在key这个点上击穿了缓存

  解决办法:设置热点数据永不过期,或者加上互斥锁

使用互斥锁(mutex key)

业界比较常用的做法,是使用mutex。简单地来说,就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db,而是先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX或者Memcache的ADD)去set一个mutex key,当操作返回成功时,再进行load db的操作并回设缓存;否则,就重试整个get缓存的方法

SETNX,是「SET if Not eXists」的缩写,也就是只有不存在的时候才设置,可以利用它来实现锁的效果。

public String get(key) {
      String value = redis.get(key);
      if (value == null) { //代表缓存值过期
          //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
      if (redis.setnx(key_mutex, 1, 3 * 60) == 1) {  //代表设置成功
               value = db.get(key);
                      redis.set(key, value, expire_secs);
                      redis.del(key_mutex);
              } else {  //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
                      sleep(50);
                      get(key);  //重试
              }
          } else {
              return value;      
          }
 }

三、单线程redis为什么这么快?

redis是单线程工作模型。快的原因是1 纯内存操作  2、单线程操作,避免经常的上下文切换  3、采用非阻塞I/O多路复用机制

1、纯内存操作:redis将所有数据放在内存中,是不需要从磁盘读取数据,0次IO。

2、单线程操作:(1)简化了算法实现  (2)避免了线程切换和加锁释放锁带来的消耗。(3)缺点是阻塞,若一个命令执行时间过长,会造成其他命令的阻塞。

3、非阻塞I/O多路复用机制:I/O多路复用实际上是指多个连接的管理可以在同一进程,多路是指多个网络连接,复用的是同一个线程。在网络服务中,I/O多路复用起的作用是一次性把多个连接的事件通知业务代码处理,处理方式由业务代码决定。

pS:I/O多路复用机制涉及到了redis的线程模型,这里扩展一下:

redis的线程模型

(1)文件事件处理器file event handler(单线程模式运行,redis基于reactor模式开发的网络事件处理器):

  采用IO多路复用机制同时监听多个socket,并将socket放入一个队列中,每次从队列中取一个socket给事件分派器来处理并行访问-->串行访问

  文件事件处理器包括:多个socket、IO多路复用程序、文件事件分派器、事件处理器

(2)文件事件:

1 当socket变得可读时,比如客户端对redis执行write操作或close操作,或者有新的可以应答的socket出现(客户端对redis执行connect操作),socket会产生一个AE_READABLE事件;

2 当socket变得可写时(客户端对redis执行read操作),socket会产生一个AE_WRITABLE事件;

3 IO多路复用程序同时监听AE_WRITABLE和AE_READABLE两种事件,若一个socket同时产生了这两种事件,文件事件分派器优先处理AE_READABLE事件

(3)文件事件处理器:若是客户端要连接redis,那么会为socket关联连接应答处理器

          若是客户端要写数据到redis,那么会为socket关联命令请求处理器

          若是客户端要从redis读数据,那么会为socket关联命令回复处理器

四、redis的过期策略及内存淘汰机制(过期策略和内存淘汰机制是要结合使用的,才能保证过期的key全部删除干净

  设置了过期时间的数据,并不一定到时间了就立马会被删除,只是数据过期了,要根据redis的淘汰策略来看

1、redis采用定期删除+惰性删除策略:

定期删除是随机检查设了过期时间的key,来删除

惰性删除是,当你查询某个key时,redis检查一下,过期了就删除

2、redis的淘汰策略有7种(redis4加了一种访问频率最少,最不经常使用的KV淘汰),推荐allkeys-LRU最近最少使用淘汰策略

已设置过期时间中有三种策略:1最近最少使用less recently use的数据淘汰、2剩余时间最短time to live的数据淘汰、3随机选择数据淘汰

4从所有KV 中对最近最少使用的数据淘汰、

5从所有KV中随机淘汰、

6不淘汰策略,若超过最大内存额返回错误信息、

7通过统计访问频率,将访问频率最少的也就是最不经常使用的KV淘汰

另:LRU算法的实现:继承LinkedHashMap,构造方法据给定初始化大小调用super方法,并重写删除最老entry数据方法

public class LRUCache<k,v> extends LinkedHashMap<k,v>{

  private final int CACHE_SIZE;

  public LRUCache( int cacheSize){

    super( (int)Math.ceil( cacheSize / 0.75) + 1 , 0.75f, true);

    //设置一个hashmap的初始大小,同时最后一个true指的是让LinkedHashMap按照访问顺序来进行排序,最近访问的放在头,最早访问的放在尾

    CACHE_SIZE = cacheSize;

  }

  @override

  protected boolean removeEldestEntry(){

    return size() > CACHE_SIZE;

    //当map中的数据量大于指定的缓存个数时,自动删除最老的数据

  }

}

阿里云redis开发规范解读

-------开始-----

1、key命名设计:可读性、可管理性、简洁性

  规范建议使用冒号进行分割拼接,因为很多redis客户端是根据冒号分类的:   apps:app:1、apps:app:2、apps:app:3

2、value设计:拒绝bigkey

  规范建议String类型的value控制在10kb范围内,因为redis随着value不断增长,在超过10kb后,性能明显下降

3、控制key的生命周期:设定过期时间

  尽可能对每个Key都设置过期时间

4、时间复杂度为O(n)的命令需要注意N的数量

  以list为例,元素数量越多,命令的性能越差,而redis 是单线程的,若出现一个慢命令,会导致之后的命令耗时增长。这也是jdk1.8对hashmap进行链条冲突优化:当entry数量不少于64时,如果冲突链表长度达到8,会将其转化为红黑树,因为链表长度越长,性能越差。

5、禁用命令:KEYS、FLUSHDB、FLUSHALL等

  这些命令在搭建redis环境的时候就应该禁用掉(在config配置文件中通过rename-command禁用)。

  FLUSHDB、FLUSHALL这两个命令是会删库数据的

6、推荐使用批量操作提升操作效率

  批量命令包括原生命令(mget、mset、hmget、hmset、LPUSH key value)、非原生命令(Pipeline)

7、monitor命令控制使用时间

  monitor命令一般是用来观察redis服务端都在执行哪些命令并实时输出。规范建议控制monitor命令的使用时间,是因为随着monitor命令的执行时间越长,越多数据积压在输出缓冲区,从而导致输出缓冲区占用内存越来越大

-----结束-----

一、redis与memcached的区别(为什么选择redis而不用memcached)/mem ca qu/

1、存储方式:memcache把数据全部存在内存中,断电就会挂掉,数据不能超过内存大小,而redis部分数据会存在磁盘上,能保证数据持久性

2、支持的数据类型:memcache只支持简单的key-value,redis支持五种数据类型

3、value大小:redis可以达到1GB,而memcache只有1MB

4、redis内存使用率比memcached高,由于其组合式的压缩,采用hash结构来做k-v存储;

5、性能上,redis只使用单核,每一个核上redis在存储小数据时比memcached性能更高,而memcached可使用多核,在大数据量时比redis性能高;

6、集群模式:memcached原生不支持集群模式,redis目前是支持原生cluster集群模式的;

二、redis的主从架构

1、redis不能支撑高并发的瓶颈是单机,qps大概上万到几万不等,所以要用集群的缓存架构

2、redis要支撑超过10万+的高并发要怎么做?

  解决方案就是读写分离,一般来说,对缓存一般都是用来支撑读高并发的,写的请求是比较少的。

    此处读写分离,就是用主从架构,一主多从,主负责写并同步数据到从节点(是异步同步数据的),从节点提供读操作

3、redis   replication 以及master持久化对于主从架构的安全意义

redis  replication  --> 主从架构 --> 读写分离  -->  水平扩容支撑读高并发

(1)图解redis replication 基本原理

(2)redis replication核心机制

  1 redis 采用异步方式复制数据到slave节点,redis 2.8开始,slave节点会周期性地确认自己每次复制的数据量

  2 一个master节点可配置多个slave节点

  3 slave node 也可以连接其他slave node

  4 slave node做复制时,是不会阻塞master node正常工作的

  5 slave node复制时,也不会阻塞对自己的查询操作,它会用旧的数据集来提供服务。复制完成时,需要删除旧数据集,加载新数据集,这时就会暂停对外服务。

  6 slave node 主要用来进行横向扩容,做读写分离,扩容的slave node可提高读的吞吐量 

(3)master持久化对于主从架构的安全保障的意义

  主从架构,必须建议开启master node的持久化

  不建议用slave node作为master node的数据热备,因为如果关掉master的持久化,可能在master宕机重启时,数据是空的,然后一经复制,slave node的数据也丢了。

  即使采用了高可用机制,slave node可自动接管master node,但是也可能哨兵sential还没检测到master失败了,master node就自动重启了,还是可能导致上面的所有slave node数据清空故障。

4、redis 主从架构

***redis高并发:主从架构,一主多从,主写从读,单机几万QpS,数据量很大就要用redis集群;

***redis高可用:主从架构部署,加上哨兵,就可实现任一实例宕机,自动进行主备切换。

(1)主从架构的核心原理

  当启动一个slave node时,它会发送一个psync命令给master node。

  如果这是slave node重新连接master node,那么master node仅仅会复制给slave node部分缺少的数据。

  否则,会触发一次full resynchronization(全量复制),开始时master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕后,master会将这个RDB文件发给slave,slave会先将RDB文件写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。(整个过程涉及RDB文件和写命令两部分数据)

  slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份RDB数据服务所有slave node。

(2)主从复制的断点续传

  redis2.8开始,就支持主从复制的断点续传,如果主从复制过程中,网络连接断了,那么可以接着上次复制的地方,继续复制,而不是从头重新复制。

  master node会在内存中维护一个backlog,master和slave都会保存一个replica offset还有一个master id

  offset就是保存在backlog中的,如果master和slave网络连接断了,slave会让master从上次的replica offset开始继续复制,但是如果在backlog中没有找到对应的offset,就会执行一次resynchronization

(3)无磁盘化复制:master在内存中直接创建RDB,发送给slave,不会在自己本地落地磁盘了。

  repl-diskless-sync  no  ,默认是no的,默认不进行无磁盘化复制,修改为yes则会

  repl-diskless-sync-delay 等待一定时长再开始复制,因为要等更多的slave重新连接过来

(4)过期key处理

  slave不会过期key,只会等待master过期key

  如果master过期了一个key,通过LRU淘汰了一个key,会模拟一条del命令发送给slave。

主从复制:从节点仅提供读操作,主节点提供写操作,对于读多写少的状况,给主节点配置多个从节点可提高响应效率

主从复制的完整流程

  (1)、slave node执行slaveof[ masterIP ][ masterPort ]保存主节点信息:

  slave node启动,仅保存master node的信息,包括master node的host和IP,但是复制还没开始。master的host和IP是redis.conf里面的slaveof配置的

  (2)、slave node从节点的定时任务发现主节点信息,建立和主节点的socket连接

  slave node内部有个定时任务,每秒检查是否有新的master node要连接和复制,如果有,就建立socket连接

  (3)、从节点发送Ping信号,主节点返回Pong,两边能相互通信

  口令认证,如果master设置了requirePass,那么slave node必须发送masterauth的口令过去认证

  (4)、连接建立后,主节点第一次执行全量复制,将所有数据发送给从节点(数据同步)

  (5)、主节点把当前的数据同步给从节点后,便完成了复制的建立过程。接下来主节点会持续的把写命令异步发送给从节点,保证主从数据一致性;

  对第(4)点的补充说明:

  ----开始----

  数据同步相关的核心机制:第一次slave连接master时,执行全量复制的细节机制

  • (1)master和slave都会维护一个offset:master会在自身不断累加offset,slave也会在自身不断累加offset。slave每秒都会上报自己的offset给master,同时master会保存每个slave的offset(不是特定用在全量复制的)
  • (2)backlog:master node有一个backlog,默认1MB大小。master给slave复制数据时,会将数据同步一份在backlog中。backlog主要是用来做全量复制中断后的增量复制的(断点续传)
  • (3)master run id:info server命令,可以看到master run id   
  • 如果根据host+IP定位master node是不可靠的。因为如果master node重启或数据出现了变化,那么slave应该根据不同的run id区分,run id不同就要做全量复制,如果需要不更改run id 重启redis,可用redis-cli debug reload
  • (4)psync:从节点使用psync从master node进行复制,psync runid offset
  • master会根据自身的情况返回响应信息,可能是FULLRESYNC runid offset促发全量复制,可能是CONTINUE触发增量复制。

  全量复制

  • (1)master执行bgsave,会在本地生成一份RDB快照文件
  • (2)master node将RDB快照文件发送给slave node,若RDB复制时间超过60s(repl-timeout),那么slave就认为复制失败,可适当调大这个参数
  • 对于千兆网卡的机器,一般每秒传输100MB,6G文件很可能超过60S
  • (3)master在生成RDB时,会将所有新的写命令缓存在内存中,在slave保存了RDB后,再将新的命令复制给slave
  • client-output-buffer-limit slave 256MB 64MB 60,如果在复制期间,内存缓冲区(就是前面缓存新的写命令的地方)持续消耗超过64MB或者一次性超过256MB,那么停止复制,复制失败
  • (4)slave node 接收到RDB后,清空自己的旧数据,然后从磁盘中重新加载RDB到自己的内存中,同时基于旧的数据版本对外提供服务
  • (5)如果slave开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF文件

  如果复制的数据量在4-6G之间,很可能全量复制时间消耗到1分半到2分钟

  增量复制

  • (1)如果全量复制过程中,master-slave网络连接中断,那么slave重连master时,会触发增量复制
  • (2)master直接从自身的backlog中获取部分丢失的数据,发送给slave,默认backlog就是1MB
  • (3)master就是根据slave发送的psync中的offset来从backlog中获取数据的(可参考“数据同步相关的核心机制”的第(4)点)

  heartbeat

  主从节点互相都会发送heartbeat信息

  master默认每隔10秒发送一次heartbeat,slave每隔1秒发送一个heartbeat

  异步复制: master每次接收到写命令后,先在内部写入数据,然后异步发送给slave

  ----结束----

主从复制的问题

  (1)、一旦主节点宕机,从节点晋升为主节点,同时需要修改应用方的主节点地址,还要命令所有从节点去复制新的主节点,整个过程需要人工干预;

  (2)、主节点的写能力、存储能力受到单机的限制

问题1 的解决方案是核心在于提高redis主从架构下的高可用性(主节点挂掉了,如何保证系统仍能接续使用?(slave节点挂掉是不会影响可用性的)):

使用   哨兵sentinal node:redis高可用架构,叫做故障转移,也可叫做主备切换。在master node故障时,自动检测,并将某个slave node自动切换为master node的过程叫做主备切换,实现redis主从架构下的高可用性。

(5)哨兵介绍

  • 1 功能包括 主节点存活检测、主从运行情况检测、自动故障转移、主从切换(也叫主备切换)

  集群监控:不断检查主、从服务器是否正常运行(监控master和slave进程是否正常工作)

  消息通知:被监控的某个redis实例有故障,哨兵会通过API脚本向管理员或者其他应用程序发出通知

  自动故障转移:当主节点不能正常工作时,哨兵会开始一次自动的故障转移操作,将与失效主节点是主从关系的其中一个从节点升级为新的主节点,并将其他从节点指向新的主节点,避免了人工干预

   配置提供者:哨兵模式下,客户端应用在初始化时连接的是哨兵节点集合,从中获取主节点信息。故障转移发生后,通知client客户端新的master地址。

  • 2 哨兵本身也是分布式的,作为一个哨兵集群去运行,协同工作:

  故障转移时,判断一个master node是否宕机了,需要大部分哨兵都同意才行,涉及到了分布式选举的问题;

  即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的。

目前采用的sentinal 2 版本,主要是让故障转移的机制和算法变得更加健壮简单。

(6)哨兵核心

  • 1 哨兵集群至少需要3个实例,来保证自己的健壮性;
  • 2 哨兵+redis主从的部署架构,只能保证redis集群的高可用性,不能保证数据0丢失
  • 3 哨兵+redis主从的架构,尽量在测试环境和生产环境都进行充分的演练;

(7)为什么redis哨兵集群只有2个节点无法正常工作?

  前提:哨兵集群必须部署2个以上节点

  若仅部署了2个哨兵实例,quorum = 1

  configuration  : quorum = 1

  master宕机,s1和s2中只要有一个哨兵认为master宕机就可以进行切换(参数quorum决定在认为master宕机时需要几个哨兵认可,若quorum=2时,即要有2个哨兵认为master宕机才能进行切换),同时s1、s2中会选举出一个哨兵来执行故障转移

  同时这个时候,需要majority大多数哨兵都是运行的,2个哨兵的majority就是2(3的majority=2,4的majority=2,5的majority=3),2个哨兵都运行着,就可以允许执行故障转移。但是如果整个m1和s1所在的机器宕机了,那么哨兵只有一个了,此时就没有majority来允许执行故障转移了,虽然还有一台机器还有一个R1,但是故障转移不会执行。

(8)经典的3节点哨兵集群

  configuration: quorum = 2,majority=2

  如果M1所在机器宕机了,那么还剩S2、S3两个哨兵,S2和S3一致认为master宕机,然后选举出一个来执行故障转移

  同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移。

(9)两个丢失数据的场景:

  •  场景1哨兵、主从架构下的异步复制数据丢失:master node接收写的请求并返回成功,此时数据还在master内存中。由于master复制数据到slave是异步的,可能还没复制数据,master就挂了,此时哨兵检测到master挂了,选举新的master供读写操作,那么旧的master还未复制的那部分数据就丢了
  • 场景2脑裂问题:master主节点,出现了异常性的有相同数据,相同工作的两个节点(集群脑裂导致的数据丢失)
  • 图例:
  • 脑裂问题图描述:主节点与从节点哨兵集群所在的网络失去联系。哨兵以为原主节点出故障了。选举出新的master,而实际上原主节点依旧正常工作着,此时整个环境中存在两个master,客户端client可能与原master处于同一个网络,连接正常,继续写数据到原master。当管理员发现问题,原master再次恢复时会被作为一个slave挂到新的master上,自己的数据清空,重新从新的master复制数据,就出问题了(后client写入原master的那部分数据丢了)
  • 解决方案:两个配置min-slaves-to-write 2,    min-slaves-max-lag  10,  可减少异步复制数据和脑裂导致的数据丢失(这两个配置的意思是,要求至少有2个slave,数据复制和同步的延时不能超过10秒)
  • 详解:
  • 对场景1min-slaves-max-lag可确保一旦slave复制数据和ack延时太长,就认为可能master宕机后损失的数据太多了,那么旧拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低到可控范围内。
  • 客户端client写数据到master的速度可能大于master复制数据到slave的速度,master可能已经缓存了2000条数据了,而slave才复制了1200条,此时master宕机的话就会丢失800条,而设置了min-slaves-to-write和min-slaves-max-lag就是限制了如果延时超过10S,master就先不接受写请求了,客户端自己在内存中暂时缓存,尝试写其他master或者停顿一段时间。一般这种情况会在客户端做降级,写到磁盘里,在client对外接收请求,再做降级,做限流,减慢请求涌入的速度,或者client会采取将数据临时灌入一个kafka中,每隔10分钟去队列里取一次,尝试重新发回master。
  • 对场景2,如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可确保说,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求。这样脑裂后的旧master就不会接受client的新数据,避免了数据丢失。上面的配置确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己ack消息,就拒绝新的写请求,因此在脑裂场景下如此设置,最多丢失10秒的数据

(10)redis哨兵的7个核心底层原理(包含slave选举算法)

  • --1--  sdown和odown转换机制:sdown和odown是两种失败状态
  • sdown是主观宕机,就是一个哨兵如果自己觉得一个master宕机了,那么就是主观宕机;
  • odown是客观宕机,如果quorum数量的哨兵都觉得一个master宕机了,那么就是客观宕机;
  • sdown达成的条件:如果一个哨兵去ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机了;
  • sdown到odown转换的条件:如果一个哨兵在指定时间内,收到了quorum指定数量的其他哨兵也认为那么master是sdown了,那么就认为是odown了,客观认为master宕机。
  • --2--  哨兵集群的自动发现机制: 哨兵之间的相互发现是通过redis的pub/sub系统实现的。
  • 每个哨兵都会往_sentinal_ :hello 这个channel里发送一个消息,其他哨兵去这个channel订阅消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他哨兵的存在;
  • 每隔两秒钟,每个哨兵都会往自己监控的某个master+slave对应的_sentinal_ :hello这个channel里发送一个消息,内容是自己的host、ip和runid,还有对这个master的监控配置;
  • 每个哨兵也会去监听自己监控的每个master+slave对应的_sentinal_ :hello这个channel,然后去感知到同样在监听这个master+slave的其他哨兵的存在;
  • 每个哨兵还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步。
  • --3--  slave配置的自动纠正
  • 哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据;
  • 如果slave连接到了一个错误的master上,比如故障转移后,那么哨兵会确保他们连接到正确的master上(哨兵会确保其他slave修改配置)。
  • --4-- slave-->master选举算法
  • 如果一个master被认为odown了,而且majority哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先是要选举一个slave出来;
  • 会考虑slave的一些信息:
  •       (1)跟master断开连接的时长:(先筛选掉不适合做master的)如果一个slave跟master断开连接已经超过了down-after-milliseconds的10倍,外加master宕机的时长,那么slave就被认为不适合选举为master(slave跟master的数据相差可能很多了):
  •                        (down-after-milliseconds * 10)+ milliseconds_since_master_is_in_SDOWN_state  ;
  •   接下来会对slave进行排序
  •       (2)slave优先级:按照slave优先级进行排序,slave-priority越低,优先级越高(slave-priority默认100);
  •       (3)复制offset:如果slave priority相同,那么看replica offset,哪个slave复制了超多的数据,offset越靠后,优先级就越高;
  •       (4)run id:如果前两个条件都相同,那么选一个run id较小的那个slave。
  • --5-- quorum 和 majority
  • 每次一个哨兵要做主备切换,首先要quorum数量的哨兵都认为sdown(即odown客观认为master宕机了),然后选举出一个哨兵来做切换,这个哨兵还得得到majority哨兵的授权才能正式执行切换
  • 如果quorum<majority,比如5个哨兵,majority就是3,quorum设为2,那么就要3个哨兵授权就可以执行切换;
  • 如果quorum>=majority,那么必须quorum数量的哨兵都授权,比如5个哨兵,quorum设为5,那么必须5个哨兵都同意授权才能执行切换。
  • --6--  configuration epoch
  • 执行切换的那个哨兵,会从要切换到的新master那里得到一个configuration epoch,这就是一个version号,每次切换的version都必须是唯一的
  • 如果第一个选举出的哨兵切换失败了,那么其他哨兵会等待一个failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号。
  • --7-- configuration传播
  • 哨兵完成切换后,会在自己本地更新生成最新的master配置,然后同步给其他哨兵,就是之前的pub/sub消息机制;
  • 这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换后,新的master配置是跟着新的version的,其他哨兵都是根据版本号大小来更新自己的master配置的

三、持久化

 如果只把redis当作缓存服务器,那么完全不用考虑持久化,但是大多数服务器架构中,redis不单单只做缓存服务器,还可以作为数据库保存业务数据,就需要持久化。

Redis的两种数据持久化方式:RDB、AOF(append-only file追加)

1、RDB快照存储数据持久化方式。将redis某一时刻的内存数据保存到硬盘的文件中,默认保存的文件名为dump.rdb,在redis服务器启动时,会重新加载dump.rdb文件中的数据到内存中恢复数据。

三种生成RDB文件的方式:

(1)save命令(占用主进程,阻塞其他客户端请求)、

(2)bgsave命令(新增子进程来同步数据,当forks子进程数据量大时也会阻塞)、

(3)服务器配置自动触发(redis.conf中配置  save  300  10 多少秒内至少达到多少次写操作就备份数据)

优点:(1)与AOF相比,通过rdb文件恢复数据比较快;(2)rdb文件非常紧凑,适合数据备份;(3)使用子进程进行数据备份,对redis服务器性能影响小;

缺点:(1)服务器宕机的话,而没有触发设置同步数据的条件,可能会丢失某段时间内的数据;

(2)save方式会造成服务器阻塞;

(3)bgsave方式在forks子进程时数据量大,也会发生阻塞;

(4)兼容性差:RDB文件需要满足特定格式,老版本redis不兼容新版本的RDB文件。

2、AOF:append-only file,记录客户端对服务器的每次写操作命令,并将这些写操作以Redis协议追加保存到以后缀aof文件末尾。在redis服务器重启时,会加载并运行aof文件的命令,恢复数据。

开启AOF持久化方式,redis默认不开启,可通过redis.conf文件配置:appendonly yes

写入.aof文件的三种策略:appendfsync  always(每个写操作都保存到.aof文件) / everysec(默认,每秒写入一次.aof文件,最多会丢失1秒的数据) / no(不推荐)

优点:AOF只是追加日志文件,对服务器性能影响小,备份数据速度比RDB快,消耗内存少;兼容性好

缺点:生成的日志文件太大;恢复数据的速度比RDB慢;对性能影响大

3、当RDB和AOF两种方式都开启时Redis会优先使用AOF日志文件来恢复数据,因为AOF保存的文件比RDB更完整

四、redis的并发竞争问题如何解决

单进程单线程模式,采用队列模式将并发访问变为串行访问

五、redis常用管理命令

# dbsize 返回当前数据库 key 的数量

# info  返回当前redis服务器状态和一些统计信息

# monitor 实时监听并返回redis服务器接收到的所有请求信息

# shutdown 把数据同步保存到磁盘上,并关闭redis服务

# flushdb  慎用,删除当前数据库中所有key,此方法不会失败

# flushall  慎用,删除全部数据库中所有key,此方法不会失败

另:查询key是否存在:exists key +key名字

删除key:  del  key key2 ...

六、redis客户端与服务端之间的通讯协议:RESP。实现简单、快速解析、可读性好;

七:redis设置过期时间的四种方式

1、expire key seconds  :设置key在n秒后过期

2、pexpire key milliseconds  :设置key在n毫秒后过期

3、expireat key timestamp  :设置key在某个时间戳(精确到秒)之后过期

4、pexpireat key millisecondsTimestamp:  设置key在某个时间戳(精确到毫秒)之后过期

八、redis三种删除策略

定时删除:设置键的过期时间并设置一个定时任务,当键达到过期时间,立即执行对键的删除操作。缺点是过期键多的话对应的删除任务也多,占CPU

惰性删除:从键空间获取键时,检查键是否过期,过期就删除否则返回该键。  缺点是过期键可能不会被删除(没查询这个键),占内存  

定期删除:每隔一点时间,程序对数据库检查,删除过期键

原文地址:https://www.cnblogs.com/blackdd/p/12089184.html