Redis再再 深入学习

3 篇文章 0 订阅
2 篇文章 0 订阅

Redis深入学习

一、数据结构类型
  • string ,set get ex
  • list :lpush lpop 用于消息队列
  • set :sadd、smenbers 一键多值,用于标签
  • zset : zadd user:rank score menber
    • zadd user:rank 1 james 21 kate 32 jack 46 frank 258 tom;` james获得1票,kate21票…
    • zrange user:rank james ---- 找到james的票数 --dict
    • zrange user:rank 0 10 — 找到前10的人的名字
    • zrank user:rank james 返回它的排名
    • zscore user:rank james 返回它的分数值
    • zrange user:rank 0 10 with scores —就是把前10的人票数和名字都列出来
  • hash:hset user:001 name huanghuo age 1 / hgetall user:001 / hget user:001 name
二、过期策略

最近最少使用原则LRU.、 深入

随机时间 – 深入

设置的过期时间

永不过期

三、持久化
  • RDB

    他会fork一个进程,在指定时间内把内存的数据备份到dump.gdb文件中,再次启动的时候就会把读取日志到内存中。缺点就是无法实时化,进程占用内存比较大。主要用于大规模的数据恢复

  • AOF,二者可以同时存在,但是是先去读取AOF文件的

    以日志的形式记录每个写操作。

    缺点就是文件会很大。优点就是数据完整性高,当发生宕机的时候,可以用它来恢复数据就和mysql的redolog一个作用

四、事务

multi 开启事务、exec 执行事务、discard 回滚事务

redis是部分支持事务的,最后执行的时候,成功的就成功,失败的就失败

redis没有隔离性因为它是每个命令进入队列,最后一起执行的

五、主从复制(高可用)

slave of 本质是从库心跳线程不断去主库同步数据,通过RDB文件

  • 分为两种:全量复制(只在第一次的时候),增量复制

  • 先说两个参数:runid是每台机的id,用来标志他是否是第一次请求同步数据。offset:是主库和从库都有的参数,比如主库发送了N个字节的数据过来,那么主库和从库的offset就是N,在增量同步的时候会使用到。

  • 过程:

    • 这一切都是刚刚连上主库,发送slaveof的初始化操作

      **一种总结:**从库发送slaveof给主库,判断自己是否是有runid,如果没有那么就是第一次同步,会发送PSYNC ? -1指令,主库会把快照发给从库,从库进行复制,并记录offset和runid。如果不是第一次复制(主库比较从库发过来的runid和自己的runid是否匹配),那么从库需要重新发送一个指令PSYNC RUNID OFFSET给主库,主库根据offset,发送快照给从库。

      **补充总结:**配置好slave后,slave与master建立连接,然后发送sync命令. 无论是第一次连接还是重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存. 后台进程完成写文件后,master就发送文件给slave,slave将文件保存到硬盘上,再加载到内存中, 接着master就会把缓存的命令转发给slave,后续master将收到的写命令发送给slave. 如果master同时收到多个slave发来的同步连接命令,master只会启动一个进程来写数据库镜像, 然后发送给所有的slave

      **再次总结:**可以总结为下面三个过程:

      • 同步快照阶段:Master创建并发送快照给Slave,Slave再入快照并解析。Master同时将此阶段产生的新的命令写入到积压缓冲区中。----区分第几次
      • 同步写缓冲阶段:Master向Slave同步存储在缓冲区的写操作命令。—主库在想从库发送快照的时候,发生的写操作,就进入缓冲区。
      • 同步增量阶段:Master向SLave同步写操作命令。
    • 正常每一次:

      主库有了写操作,那么就会主动推送给所有从库。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

六、哨兵模式(高可用,和主从复制是一伙的)

哨兵有多个,一般就是几个redis几个哨兵,他监控所有的redis服务器(在sentinel.conf中配置),每次ping一下各个服务器,如果超过半数哨兵都判断不ok,那么就投票出来换一个redis’做主库。

七、redis的集群

根据分区规则,比如哈希算法,我们把数据放到不同的redis服务器中。

具体实现cluster-enabled : true \ cluster-config-file

多个master,然后每个master又有自己的slave。

在这里插入图片描述

八、Zset实现原理

ZSET(SORTED SET)在redis中的编码可以是ziplist或者是skiplist

  • 跳表的原理skiplist

    参考视频:https://www.bilibili.com/video/BV1LJ411t7Aihttps://www.bilibili.com/video/BV18J411s7eN 两个非常好的skiplist原理讲解,这方面视频很少。

    skiplist的java实现:https://blog.csdn.net/bohu83/article/details/83654524

    
      public Integer insert(int key, int value) {
        	SkipListEntry p, q;
            int i = 0;
     
            // 查找适合插入的位子
            p = findEntry(key);
     
            // 如果跳跃表中存在含有key值的节点,则进行value的修改操作即可完成
            if(p.key ==key) {
                Integer oldValue = p.value;
                p.value = value;
                return oldValue;
            }
     
         // 如果跳跃表中不存在含有key值的节点,则进行新增操作
            q = new SkipListEntry(key, value); 
          
         q.left = p;
         q.right = p.right;
         p.right.left = q;
         p.right = q;
     
           //本层操作完毕,看更高层操作
         //抛硬币随机决定是否上层插入
         while ( r.nextDouble() < 0.5 /* Coin toss */ )
         {
        	 if ( i >= h )   // We reached the top level !!!
             {
                //Create a new empty TOP layer
        		 addEmptyLevel();
             }
        	 /* ------------------------------------
             Find first element with an UP-link
             ------------------------------------ */
    	      while ( p.up == null )
    	      {
    	         p = p.left;
    	      }
          /* --------------------------------
    	   Make p point to this UP element
    	   -------------------------------- */
              p = p.up;
     
    	/* ---------------------------------------------------
              Add one more (k,*) to the column
    	   Schema for making the linkage:
                   p <--> e(k,*) <--> p.right
                             ^
    		          |
    		          v
    		          q
    	   ---------------------------------------------------- */
      	SkipListEntry e;
      	 // 这里需要注意的是除底层节点之外的节点对象是不需要value值的
      	e = new SkipListEntry(key, null); 
      	/* ---------------------------------------
    	   Initialize links of e
    	   --------------------------------------- */
    	e.left = p;
    	e.right = p.right;
    	e.down = q;
    	/* ---------------------------------------
    	   Change the neighboring links..  
    	   --------------------------------------- */
    	p.right.left = e;
    	p.right = e;
    	q.up = e;
        
    	//把q执行新插入的节点:
    	 q = e; 
    	 // level增加
    	 i = i + 1;  
    	
        	 
         }
        n = n+1; //更新链表长度        
            return null;
    

    redis的skiplist参考文章:https://blog.csdn.net/weixin_38008100/article/details/94629753

    跳表跳表的查找插入都是logN复杂度。

  • 压缩表ziplist

    参考文章:https://blog.csdn.net/yellowriver007/article/details/79021049?utm_medium=distribute.pc_relevant.none-task-blog-baidujs-2

    压缩表是用字节组成双向链表,在内存中一起占用一块区域.下图是一个真实的结构
    在这里插入图片描述
    解释

    • 这个ziplist一共包含33个字节。字节编号从byte[0]到byte[32]。图中每个字节的值使用16进制表示。
    • 头4个字节(0x21000000)是按小端(little endian)模式存储的字段。什么是小端呢?就是指数据的低字节保存在内存的低地址中(参见维基百科词条Endianness)。因此,这里的值应该解析成0x00000021,用十进制表示正好就是33。
    • 接下来4个字节(byte[4…7])是,用小端存储模式来解释,它的值是0x0000001D(值为29),表示最后一个数据项在byte[29]的位置(那个数据项为0x05FE14)。
    • 再接下来2个字节(byte[8…9]),值为0x0004,表示这个ziplist里一共存有4项数据。
    • 接下来6个字节(byte[10…15])是第1个数据项。其中,prevrawlen=0,因为它前面没有数据项;len=4,相当于前面定义的9种情况中的第1种,表示后面4个字节按字符串存储数据,数据的值为”name”。
    • 接下来8个字节(byte[16…23])是第2个数据项,与前面数据项存储格式类似,存储1个字符串”tielei”。
    • 接下来5个字节(byte[24…28])是第3个数据项,与前面数据项存储格式类似,存储1个字符串”age”。
    • 接下来3个字节(byte[29…31])是最后一个数据项,它的格式与前面的数据项存储格式不太一样。其中,第1个字节prevrawlen=5,表示前一个数据项占用5个字节;第2个字节=FE,相当于前面定义的9种情况中的第8种,所以后面还有1个字节用来表示真正的数据,并且以整数表示。它的值是20(0x14)。
    • 最后1个字节(byte[32])表示,是固定的值255(0xFF)。

    hash结构如果数据少,那么也是采用的ziplist结构.下图的示意图其实是来自以下命令:当数据大的时候就会变成dict结构

    hset user:001 name tielei
    hset user:001 age 20
    

    一些问题:

    • 怎么实现双向? 我们使用遍历的方法找到指定位置,然后因为每个压缩表节点存放了前一个节点的长度,那么我们就能够往前找元素.
    • 怎么实现从头或者从尾巴插入数据?ziplist存放了zltail,表示最后一个数据在什么位置上,那么我们就能在尾巴上插入数据.
    • 为么数据元素变多或者变大,就不用ziplist了?因为他还是使用遍历的手段,但数据量大的时候,效率是不高的.另外他插入元素的时候,过程是进行内存拷贝的,但数据量大的时候,效率不高
    • 优点是什么?占用内存小,是一整块内存,不会有内存碎片问题.
  • redis的zset(sort set)

    • zset的编码可以是ziplist或者是skiplist。同时满足以下条件时使用ziplist编码 :下面的具体限制数字都是可以在配置文件中更改的

      • 元素数量小于128个
      • 所有menber长度都小于64字节
    • redis的skiplistNode是存放key(score)和value(menber)的,但是实现上不是放在一起的,是同时包含一个字段dict和skiplist的,skiplist就是score的多层链表,dict就存放menber值,和skiplist的score有对应关系,这样查找score是O(logN),找对应的menber就只是O(1)。因此也可以有重复的score,就不会想hash表一样。

    • skipList的随机数是<0.25; 最高层限制是32层

    • 有了上面的预备知识我们可以知道下面命令的过程:

      • zscore 是查询menber的score是由dict来提供。但是如果是ziplist怎么办呢?他的key value是连在一起的,因此得到key的位置以后,下一个就是value
      • zrange 是查询就是差skiplist
      • zrank 是查询menber的排名,那么就是先去dict,然后再去skiplist
  • skiplist和平衡树、哈希表对比

    • 哈希表不是有序排列的,只能找到key对应的值,无法做范围查询,而平衡树skiplist能够实现范围查询。
    • 在做范围查找的时候,平衡树操作相对复杂,先查找出小值,之后中序遍历查找不超过范围最大值的节点。如果不是个完全平衡二叉树,这里的中序遍历并不简单。而跳表找到最小值以后,直接进行若干个遍历就ok找到范围最大值。
    • 平衡树插入删除操作都可能引发子树的调整,逻辑调整,左旋右旋之类的。而跳表只要改指针
    • 查找单个key,hash的时间复杂度是O(1),是最快的。
    • 算法实现上,跳表简单得多。

范围查询,而平衡树skiplist能够实现范围查询。

  • 在做范围查找的时候,平衡树操作相对复杂,先查找出小值,之后中序遍历查找不超过范围最大值的节点。如果不是个完全平衡二叉树,这里的中序遍历并不简单。而跳表找到最小值以后,直接进行若干个遍历就ok找到范围最大值。

  • 平衡树插入删除操作都可能引发子树的调整,逻辑调整,左旋右旋之类的。而跳表只要改指针

  • 查找单个key,hash的时间复杂度是O(1),是最快的。

  • 算法实现上,跳表简单得多。

  • 缓存失效

    缓存穿透:key不在缓存中存在,也不存在于数据库,但是每次都会会去数据库找–这个有点扯,办法是给他缓存一个空值。

    缓存击穿:缓存存的值过期了,这个时候有大量的并发过来,就会压垮DB。 用setnx锁的方式,来锁住访问请求数量。

    缓存雪崩:假如缓存层宕机了,全部的key都没了,这个时候db的压力也就大了。解决办法:一个是集群,一个是在启动redis的时候限流,一个是数据预热,把可能的缓存先加载一次。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值