zoukankan      html  css  js  c++  java
  • 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

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

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

  • 相关阅读:
    OutputCache 缓存key的创建 CreateOutputCachedItemKey
    Asp.net Web Api源码调试
    asp.net mvc源码分析DefaultModelBinder 自定义的普通数据类型的绑定和验证
    Asp.net web Api源码分析HttpParameterBinding
    Asp.net web Api源码分析HttpRequestMessage的创建
    asp.net mvc源码分析ActionResult篇 RazorView.RenderView
    Asp.Net MVC 项目预编译 View
    Asp.net Web.config文件读取路径你真的清楚吗?
    asp.net 动态创建TextBox控件 如何加载状态信息
    asp.net mvc源码分析BeginForm方法 和ClientValidationEnabled 属性
  • 原文地址:https://www.cnblogs.com/blackdd/p/12089184.html
Copyright © 2011-2022 走看看