Redis分析

Redis特性:

1:丰富的数据类型

2:进程内与跨进程

3:功能丰富:持久化机制,过期策略

4:支持多种变成语言

5:高可用,集群

 

 

Redis的数据类型:

String, Hash ,  Set, List,   Zset,  Hyperloglog    Geo,  Streams

 

Stirng数据类型:

每个键值对都会有一个dictEntry,里面指向了key和value的指针,next指向下一个dictEntry

key是字符串,存在在自定义的SDS中,value存储在redisObject中,五种常用数据类型都是通过redisObject来存储

字符串内部有三种编码:

1:int  存储8个字节的长整型

2:embstr 代表embstr格式的SDS,存储小于44个字节的字符串

3:raw,存储大于44个字节的字符串,最大存储512M

 

Redis使用SDS实现字符串原因:

C语言本身没有字符串类型,只能有字符数组char[]实现

1:使用字符数组必须先给目标变量分配足够的空间,否则可能会溢出

2:如果要获取字符长度,必须比那里字符数组,时间复杂度O(n)

3:C字符串长度的变更会对字符数组做内存重分配

4:通过从字符串开始到结尾碰到的第一个\0来标记字符串结束,不能保存图片,视频,压缩文件等二进制内容

 

SDS特点:

1:不用担心内存溢出问题,如果需要会对SDS扩容

2:获取字符串长度时间复杂度为O(1),有len属性

3:通过空间预分配和惰性空间释放,防止多次重分配内存

4:判断是否结束标志是len属性

 

 

embstr和raw的区别:

embstr的使用只分配了一次内存空间,raw分配了两次

embstr实现为只读

 

int数据不再是整数,或者大小超过了long范围自动转换成embstr,对于embstr的修改都先会转成raw,再进行修改,转换过程不可逆

 

使用场景:

    热点数据缓存,对象缓存,全页缓存,可以提升热点数据的访问速度

    数据共享分布式:Redis是分布式的独立服务,可以在多个应用之间共享,如分布式Session

    分布式锁:setnx方法,加上过期时间

    全局ID:Int类型,Incrby,利用其原子性

     计数器:int类型,incr方法,文章的阅读量,微博的点赞数,允许一定的延迟,先写入Redis在定时同步到数据库

     限流:Int类型,Incr方法,访问者的IP作为key,访问一次增加一次计数

     位统计:Bitcount  非常节省空间,可以用来做大数据量的统计,如在线用户统计,留存用户统计

 

 

Hash(哈希):

      

 

Hash和String的主要区别

1:Hash将所有相关的值聚集到一个key中,节省内存空间

2:只使用一个key,减少key冲突

3:当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU的消耗

Hash不适合场景:

1:Field不能单独设置过期时间

2:没有bit操作

3:需要考虑数据量分布的问题

底层结构:

 

应用场景:

           String可以做的事情, Hash都可以做

            存储对象类型的数据:比如对象或者一张表的数据

            购物车:key:用户id      field:商品id     value:商品数量  +1:hincr   -1:hdecr  删除:hdel   全选:hgetall  商品数:hlen

      

List列表:

    底层使用quicklist存储,quicklist存储了一个双向链表,每个节点都是一个ziplist

     

 

应用场景:

           用户消息时间线timeline:List是有序的,可以用来做用户时间线

           消息队列:队列-->先进先出   栈--->先进后出

 

 

 

Set集合:

   存储类型:String类型的无序集合,最大存储2^32-1个

  存储原理:Redis使用intset或者hashtable存储set,如果元素都是整数类型,使用inset存储,如果不是整数类型使用hashtable(数组+链表存储),元素超过512个也会用hashtable存储

 

应用场景:

   抽奖

   点赞,签到,打卡

   商品标签

    商品筛选

   用户关注,推荐模型

 

Zset有序集合:

     存储类型:

存储原理:

    元素数量小于128个,所有member的长度都小于64字节使用ziplist编码,在ziplist内部按照score排序递增来存储

    超过阈值欧虎使用skiplist+dict存储

 

 Skiplist跳表:

应用场景:

   排行榜

   

 

数据结构总结:

 

 

编码转换总结:

 

 

 

高级特性:

发布订阅模式

subscribe channel-1  channel-2 channel-3  订阅频道

publish channel-1 33333                              发布消息

unsubscribe channel-1                                 取消订阅

订阅频道支持通配符

 

Redis事务:

特点:

   1:按进入队列的顺序执行

    2:不会受到其他客户端的请求的影响

四个命令:multi(开启事务)   exec(执行事务)   discard(取消事务)   wathc(监视)

Redis事务不能嵌套,多个multi命令效果一样,开启事务后,客户端可以继续向服务端发送任意多条命令,这些命令不会被立即执行,而是放入一个队列中,当exec命令时候,队列中命令才被执行

 

Redis中的Watch命令可以提供CAS乐观锁行为,也就是多个线程更新变量时候,会和原值做比较,只有它没有被其他线程修改的情况下才更新成新值,用watch监视一个或者多个值,如果至少有一个key被修改了,则事务取消

 

事务遇到错误:

1:在执行exec之前发生错误,包括语法错误,编译器错误等待,这种情况下事务会被拒绝执行,队列中所有命令无法执行

2:在执行exec之后发生错误,比如String使用了Hash命令,这时候只有错误的命令没有被执行

 

 

 

Lua脚本:

对IP进行限流

lua缓存:执行script load命令时候计算脚本的SHA1摘要并记录在脚本缓存中

脚本超时:Redis的指令执行本身是单线程的,这个线程还要执行lua脚本,如果lua脚本执行超时或者陷入死循环,Redis提供了

 

                     lua-time-limit参数限制脚本最长运行时间,默认5s,当脚本超过这一个限制后,redis将开始接受其他命令但不会执                           行(确保脚本的原子性,此时脚本并没有被终止),而是返回一个BUSY错误,Redis可以提供一个script kill命令来中                         止脚本,如果当前lua脚本对redis数据进行了修改,那么通过script kill命令是不能终止脚本的,因为要保证脚本运行                       的原子性,如果脚本执行了一部分终止,那么就违背了脚本原子性要求,最终要保证脚本要么执行,要么都不执行

                     遇到这种情况,只能通过shutdown nosave和shutdown来终止

 

 

 

Redis为什么这么快?

1:纯内存结构

2:单线程

3:多路复用

 

 

多路复用:

传统的阻塞IO模型

如果当前FD(文件描述符)不可读,系统就不会对其他操作做出响应,阻塞当前线程

 

I/O多路复用:

多路:多个TCP连接(Sokcet或者Channel)

复用:复用一个或多个线程

不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符

 

内存回收:

1:key过期

2:内存使用达到上限

 

 

过期策略:

1:定时过期(主动淘汰)

每个设置过期时间的key创建一个定时器,到过期时间就立即清除,对内存友好,但是会使用大量CPU,影响响应事件和吞吐量

2:惰性过期(被动淘汰)

只有当访问一个key时候才会判断key是否过期,过期则清除,可以最大化的节省CPU资源,但是对内存不友好,极端情况下出现大量过期key没有再次被访问从而不会被清除

3:定期过期

每隔一段时间会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已经过期的key,是前两者的折中方案

 

Redis中同时使用了惰性过期和定期过期

如果key都不过期,Redis内存满了怎么办?

 

淘汰策略

当内存使用达到最大极限时候,需要使用淘汰算法来决定清理掉哪些数据,以保证新数据的存入

 

LRU:最近最少使用,判断最近被使用的时间,目前最远的数据优先被淘汰

LFU:最不常用

Random:随机删除

 

建议使用volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key

 

如果使用传统的LRU算法需要额外的数据结构存储,消耗内存

Redis LRU对传统的LRU算法进行改进,通过随机采样来调整算法的精度,根据配置的采样值(默认5个)随机从数据库中选择m个key,淘汰热度最低的key对应的缓存数据,m值越大,越能精确查找到待淘汰的缓存数据

 

热度最低的数据?

Redis中所有对象redisObject中都有一个lru字段,使用低24位来记录对象的热度,在被访问时候更新lru的值,采用全局变量的值,该全局变量的值是由Redis中的定时函数每100毫秒生成的,不采用当前时间戳是减少调用系统函数time的次数,提高效率,当lru的值和全局变量差值越大,该对象热度越低,超过24bit能表示的最大时间后它会从头开始计算,这种情况下就是两个相加来判断热度

 

LFU基于访问频率的淘汰机制:

Redis对象redisObject中的lru_bits中24位用作LFU时候,其被分为两个部分:

     1:高16位用来记录访问时间

      2:低8位用来记录访问频率

对象被读写时候,lfu的值会被更新,没有被访问时候,通过衰减因子来控制,N分钟没被访问就减少N

 

 

持久化机制:

RDB(Redis DataBase)快照和AOF(Append Only File)

 

RDB:

      Redis默认的持久化方案,当满足一定条件时候,会将当前内存中的数据写入磁盘,生成快照文件dump.rdb,重启通过加载dump.rdb文件恢复数据

RDB触发:

      1:自动触发

            a:配置规则触发

             

            b:shutdown触发,保证服务器正常关闭

            c:flushall

       2:手动触发

            如果我们需要重启服务或者迁移数据,这个时候需要手动触发RDB快照保存

             a:save

                   save在生成快照时候会阻塞当前Redis服务器,Redis不能处理其他命令,数据量大的时候会造成长时间阻塞

              b:bgsave

                   Redis在后台异步进行快照操作,快照同时能响应客户端请求

        优势:

           1:RDB是一个非常紧凑的文件,保存了redis在某个时间点上的数据集,适合备份和灾难恢复

            2:生成RDB文件时候,redis主线程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作

            3:RDB在恢复大数据集时候的速度比AOF的恢复速度快

       劣势:

            1:没办法做到秒级持久化/实时持久化,bgsave每次运行都要执行fork操作创建子进程,频繁执行成本过高

             2:在一定间隔时间做一次备份,如果redis以外down掉,会丢失最后一次快照后的所有数据

 

 

AOF:

      Redis默认不开启,AOF采用日志形式记录每个操作,并追加到文件中,Redis重启时候会根据日志文件的内容把写指令从前        到后执行一次以完成数据恢复

      由于操作系统缓存,AOF数据并没有真正写入硬盘,而是进入系统的硬盘缓存,Redis默认是everysec,表示每秒执行一次              fsync,可能会导致丢失这1s数据

      no:表示不执行fsync,由操作系统保证数据同步到磁盘,速度最开,但是不太安全

      always:表示每次都写入执行fsync,以保证数据同步到磁盘,效率很低

 

       当AOF文件大小超过了所设定的阈值时候,Redis会启动AOF文件内容的压缩,值保留可以恢复数据的最小指令集

        重写触发机制:

       

     

 

         AOF重写过程中,AOF文件被修改了

         

 

      优点:

           AOF持久化方法提供了多种同步频率,即使使用默认的也是丢失1s数据

       缺点:

           1:对于具有相同数据的Redis,AOF文件通常比RDF文件体积更大(RDB存储数据快照)

            2:高并发情况下RDB比AOF具有更好的性能

 

可以允许一小段内数据丢失,可以使用RDB,一把两者同时使用

 

 

Redis集群

需要集群的原因:

1:在某些并发量非常高的情况下,性能还是会受到影响

2:扩展,Redis数据存放在内存中,如果数据量大,很容易受到硬件限制

3:可用性,单个Redis宕机其他机器还能运行

 

 

Redis主从复制:

原理

      1:连接阶段         

              a:slave node启动时候,会在自己本地保存master node的信息,包括master node的host和ip

              b:slave node 内部有个定时任务,每隔1s检查是否有新的master node需要连接,如果有则和master node建立socket                        网络连接,连接成功,从节点建立事件处理器来处理任务,当从从节点变成主节点一个客户端后,会给主节点发送                        ping请求

       2:数据同步阶段

                c:master node第一次执行全量复制,通bgsave在本地生成一份RDB快照,在RDB生成期间,master将接受到的命令                        缓存到内存中,将RDB快照文件发送给slave node,slave node 首先清除自己的数据,然后使用RDB文件加载数                          据

       3:命令传播阶段

                  d:master node持续将写命令异步复制给slave node,如果从节点有一段时间断开了和主节点的连接,master通过                                 master_repl_offset记录了偏移量

 

  主从复制不足之处:

      1:RDB文件过大情况下,非常耗时

       2:在一主一从或者一主多从情况下,如果主服务器宕机,对外提供的服务将不可用,需要手动切换

 

 

 

Sentinel(哨兵):

Sentinel本身没有主从之分,只有Redis服务节点有主从之分

 

1:服务下线

         Sentinel默认以每秒1次的频率向Redis服务节点发送PING命令,如果在donw-after-milliseconds内都没有收到有效回复,Sentinel会将该服务器标记为下线,此时Sentinel节点会继续询问其他的Sentinel节点,确认这个节点是否下线,如果多数Sentinel节点都认为master下线,master才真正确认被下线,此时需要重新选举

  2:故障转移

            在Sentinel集群选择一个Leader,由Leader完成故障转移流程,通过Raft算法实现Sentinel选举

             Raft核心思想:先到先得,少数服从多数  http://thesecretlivesofdata.com/raft/

            a:选举slave 节点成为主节点

                  选出Sentinel Leader之后,由Sentinel Leader向某个节点发送slaveof no one命令,让他成为独立节点

                  向其他节点发送slave of x.x.x.xxx(本机服务),让它们成为这个节点的子节点,故障转移完成

            b:有多个slave node,如何选择一个

                     有四个因素影响选举的结果,断开连接时长,优先级排序,复制数量,进程id

                      如果与哨兵连接断开的比较久,超过了某个阈值,直接失去选举权,如果拥有选举权,则判断优先级(replica-                                priority),数值越小优先级越高,如果优先级相同,则判断谁从master中复制的数据最多(复制偏移量最大),选择最                        多的,如果复制数量相同则选择进程id最小的那个

 

Sentinel功能总结:

1:监控,Sentinel会不断检查主服务器和从服务器是否正常运行

2:通知,如果某一个被监控的实例出现问题,Sentinel可以通过API发出通知

3:自动故障转移,如果主服务器出现故障,Sentinel可以通过故障转移将从服务器升级为主服务器,并发出通知

4:配置管理,客户端连接到Sentinel,获取当前Redis主服务器的地址

 

 

不足:

主从切换的过程会丢失数据,因为只有一个master

只能单点写,没有解决水平扩容问题

如果数据量很大,我们需要多个master-slave的group,将数据分布到不同的group中

 

 

Redis分布式方案:

1:客户端Sharding    ShardedJedis

2:代理proxy          Twemproxy ,  Codis,    Redis Cluster

 

Redis Cluster:用于解决分布式的需求,去中心化

一致性Hash算法:所有哈希值空间组织成一个虚拟的圆环(哈希环),整个空间按顺时针方向组织,

     解决了动态增减节点时,所有数据都需要重新分布的问题,它只会影响到下一个相邻的节点,对其他节点没影响

 

 

Redis分区既没有使用哈希取模,也没有用一致性哈希,而是使用虚拟槽实现

 

       每个master节点维护一个16384位的位序列,对象分布到Redis节点上时候,对Key用CRC16算法计算再%16348,得到一个slot的值,数据落到负责这个slot的Redis节点上,key和slot的关系永远不会变,变的只是slot和Redis节点的关系

     客户端连接到的服务器上没有访问的节点,使用客户端重定向进行操作

 

 

 

 

 

 

Pipeline:

Redis使用的是客户端/服务器(C/S)模型和请求/响应协议的TCP服务器,通常情况下遵循以下请求:

    1: 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应

     2:服务端处理命令,并将结果返回给客户端

Redis客户端和Redis服务器之间使用TCP协议进行连接,一个客户端可以通过一个socket连接发起多个请求命令,每个请求命令发出后client通常会阻塞并等待redis服务器处理,redis处理完请求后会将结果通过响应报文返回给client,因此当执行多条命令时候都需要等待上一条命令执行完毕才能执行

 

Pipeline通过一个队列将所有的命令缓存起来,然后将多个命令在一次连接中发送给服务器

 

 

Lettuce:

     SpringBoot 2.X默认的客户端,直接调用Spring的RedisTemplate操作

Redisson

      在Redis的基础上实现的java内存数据网格,提供分布式和可扩展的java数据结构

       基于Netty实现,采用非阻塞IO,性能高

       支持异步请求,支持连接池,pipeline,LUA Scripting等

        不支持事务,官方建议使用LUA Scripting代替事务

         

 

数据一致性:

       一旦被缓存的数据发生变化的时候,我们既要操作数据库的数据也要操作Redis的数据,此时有两种选择

       1:先操作Redis的数据,然后操作数据库的数据

        2:先操作数据库的数据,然后操作Redis数据

 

Redis更新数据时候是删除还是更新

更新缓存之前是不是要经过其他表的查询,接口调用计算后才能得到最新的数据,而不是直接从数据库拿到的值,如果是的话建议直接删除缓存,这种方案更简单,而且避免了数据库的数据和缓存不一致情况

 

a:先更新数据库,在删除缓存

异常情况下:

   1:更新数据库失败,程序捕获异常,不会到下一步,所以数据不会出现不一致

    2:更新数据库成功,删除缓存失败,数据库是新数据,缓存是旧数据,发生了不一致情况

解决方案:

    可以提供重试机制,例如删除缓存失败,捕获异常,将需要删除的key发送到消息队列,然后不断尝试删除这个key,这种方式会对业务代码造成入侵

    异步更新缓存,例如更新数据库时候会往binlog写入日志,所以可以通过一个服务来监听binlog(canal),然后在客户端完成删除key操作,如果删除失败的话,再发送到消息队列

 

 

b:先删除缓存,再更新数据库

    异常情况:

       1:删除缓存失败,程序捕获异常,不会到下一步,所以数据不会出现不一致情况

        2:删除缓存成功,更新数据库失败,因为以数据库的数据为准,所以不存在数据不一致情况

              这种情况在并发操作下有问题

              1)线程A需要更新数据,首先删除了Redis缓存

               2)线程B查询数据,发现缓存不存在,到数据库查询旧值,写入Redis,返回

               3)线程A更新了数据库

                这个时候Redis是旧的值,数据库是新的值,发生了数据不一致情况

 

可以采用延时双删的策略,在写入数据之后,再删除一次缓存

 

缓存雪崩:

      大量Redis热点数据同时过期(失效),因为设置了相同的过期时间,刚好这个时候Redis请求的并发量很大,会导致所有请求落到数据库

      解决方案:

      1) 加互斥锁或者使用队列,针对同一个key只允许一个线程到数据库查询

       2)缓存定时预先更新,避免同时失效

       3)增加随机数,使key在不同的时间过期

       4)缓存永不过期

 

缓存穿透:

        查询Redis和数据库中不存在额数据

        解决方案:

         1):缓存空数据

          2):缓存特殊字符串

           在应用里面拿到这个特殊字符时候就知道数据库里面没有值了,没必要再去查询数据库了

           3):布隆过滤器

 

缓存击穿:

       大量用户访问热点数据,当该热点数据失效时候,请求都去查询数据库

      解决方案:

      1)热点数据不过期

       2)增加互斥锁

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

            

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值