Redis

作者: zhemehao819 | 来源:发表于2022-02-20 21:25 被阅读0次

    一、Redis基础

    1. 概述

    NoSQL(NoSQL = Not Only SQL),意思是不仅仅是SQL,泛指非关系型的数据库。

    1.1 CAP理论

    • C:Consistency(强一致性)
    • A:Availability(可用性)
    • P:Partion tolerance(分区容错性)

    CAP理论图的核心是:一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求,最多只能同时较好的满足两个。因此,根据CAP原理将NoSQL数据库分成了满足CA原则、满足CP原则和满足AP原则三大类:

    CA: 传统Oracle数据库
    AP: 大多数网站架构的选择
    CP: Redis、Mongodb

    1.3 Redis特点

    • Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用
    • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储
    • Redis支持数据的备份,即master-slave模式的数据备份。

    2. Redis五大数据类型

    操作指令参考: http://redisdoc.com/
    所有的key都为String类型,讨论数据类型是说的value的类型

    2.1 字符串(String)

    //=====================基本操作=======================//
    //设置String
    set key value
    mset key1 value1 key2 value2...
    //设置生命周期
    setex key seconds value 
    
    //得到String
    get key 
    mget key1 key2...
    
    //删除String
    del key
    
    //向字符串的后面追加字符,如果有就补在后面,如果没有就新建
    append key value
    
    //=================String作为数值的操作===================//
    //增长指令,只有当value为数字时才能增长
    incr key  
    incrby key increment  
    incrbyfloat key increment 
    
    //减少指令,有当value为数字时才能减少
    decr key  
    decrby key increment
    
    • string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
    • redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。

    指定生命周期

    //设置数据的生命周期,单位 秒
    setex key seconds value
    //设置数据的生命周期,单位 毫秒
    psetex key milliseconds value
    

    2.2 列表(List)

    • 元素有序且可重
    • List中保存的数据都是string类型的
    //=====================基本操作=======================//
    //添加修改数据,lpush为从左边添加,rpush为从右边添加
    lpush key value1 value2 value3...
    rpush key value1 value2 value3...
    
    //查看数据, 从左边开始向右查看. 如果不知道list有多少个元素,end的值可以为-1,代表倒数第一个元素
    //lpush先进的元素放在最后,rpush先进的元素放在最前面
    lrange key start end
    //得到长度
    llen key
    //取出对应索引的元素
    lindex key index
    
    //获取并移除元素(从list左边或者右边移除)
    lpop key
    rpop key
    
    
    //规定时间内获取并移除数据,b=block,给定一个时间,如果在指定时间内放入了元素,就移除
    blpop key1 key2... timeout
    brpop key1 key2... timeout
    
    //移除指定元素 count:移除的个数 value:移除的值。 移除多个相同元素时,从左边开始移除
    lrem key count value
    

    2.3 集合(Set)

    • 不重复且无序
    • string类型的无序集合。它是通过HashTable实现的
    //添加元素
    sadd key member1 member2...
    
    //查看元素
    smembers key
    
    //移除元素
    srem key member
    
    //查看元素个数
    scard key
    
    //查看某个元素是否存在
    sismember key member
    
    //从set中任意选出count个元素
    srandmember key count
    
    //从set中任意选出count个元素并移除
    spop key count
    
    //求两个集合的交集、并集、差集
    sinter key1 key2...
    sunion key1 key2...
    sdiff key1 key2...
    
    //求两个set的交集、并集、差集,并放入另一个set中
    sinterstore destination key1 key2...
    sunionstore destination key1 key2...
    sdiffstore destination key1 key2...
    
    //求指定元素从原集合放入目标集合中
    smove source destination key
    

    2.4 哈希(Hash)

    • 类似java里面的Map<String,Object>
    • hash类型下的value只能存储字符串,不允许存储其他数据类型
    //插入(如果已存在同名的field,会被覆盖)
    hset key field value
    hmset key field1 value1 field2 value2...
    //插入(如果已存在同名的field,不会被覆盖)
    hsetnx key field value
    
    //取出
    hget key field
    hgetall key
    
    //删除
    hdel key field1 field2...
    
    //获取field数量
    hlen key
    
    //查看是否存在
    hexists key field
    
    //获取哈希表中所有的字段名或字段值 
    hkeys key
    hvals key
    
    //设置指定字段的数值数据增加指定范围的值 
    hincrby key field increment 
    hdecrby key field increment
    

    2.5 有序集合Zset(sorted set)

    • 有序不重复
    • zset和set一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数
    • 可进行排序

    //插入元素, 需要指定score(用于排序)
    zadd key score1 member1 score2 member2
    
    //查看元素(score升序), 当末尾添加withscore时,会将元素的score一起打印出来
    zrange key start end (withscore)
    //查看元素(score降序), 当末尾添加withscore时,会将元素的score一起打印出来
    zrevrange key start end (withscore)
    
    //移除元素
    zrem key member1 member2...
    
    //按条件获取数据, 其中offset为索引开始位置,count为获取的数目
    zrangebyscore key min max [withscore] [limit offset count]
    zrevrangebyscore key max min [withscore] [limit offset count]
    
    //按条件移除元素
    zremrangebyrank key start end
    zremrangebysocre key min max
    //按照从大到小的顺序移除count个值
    zpopmax key [count]
    //按照从小到大的顺序移除count个值
    zpopmin key [count]
    
    //获得元素个数
    zcard key
    
    //获得元素在范围内的个数
    zcount min max
    
    //求交集、并集并放入destination中, 其中numkey1为要去交集或并集集合的数目
    zinterstore destination numkeys key1 key2...
    zunionstore destination numkeys key1 key2...
    
    //查看某个元素的索引(排名)
    zrank key member
    zrevrank key member
    
    //查看某个元素索引的值
    zscore key member
    //增加某个元素索引的值
    zincrby key increment member
    

    3. Redis持久化

    3.1 简介

    什么是持久化

    利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。

    为什么要持久化

    防止数据的意外丢失,确保数据安全性

    持久化过程保存什么

    • 将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
    • 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程

    3.2 RDB

    RDB的触发有两种方式:

    • 手动(使用命令save)
    • 自动(save m n)

    自动: 默认的RDB快照触发机制(可以在redis.conf中的SNAPSHOTTING中查看)

    1分钟内改动了1万次
    5分钟内改动了10万次
    15分钟内改动了1次
    
    默认的名字为:dump.rdb
    

    手动: 命令save或者是bgsave
    如果出现某个数据非常重要,无法等到自动备份,这时候我们可以通过这
    个命令来进行手动备份

    save:save时只管保存,其它全部阻塞
    bgsave:Redis会在后台异步进行快照操作

    如何恢复RDB

    将备份文件 (dump.rdb) 移动到启动Redis服务的目录即可

    优缺点

    优势

    • 适合大规模的数据恢复
    • 对数据完整性和一致性要求不高

    劣势

    • 在一定间隔时间做一次备份,所以如果Redis服务器意外down掉的话,就会丢失最后一次快照后的所有修改
    • Fork的时候,内存中的数据被克隆了一份,会占用更多的内存空间

    停止RDB

    使用下面的命令:
    CONFIG SET save ""

    小结

    • RDB是一个非常紧凑的文件。
    • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他I0操作,所以RDB持久化方式可以最大化redis的性能。
    • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一一些。
      数据丢失风险大。
    • RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候fork的过程是非常耗时间的,可能会导致Redis在一些毫秒级不能回应客户端请求

    3.3 AOF

    AOF概念

    • AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令,以达到恢复数据的目的。与RDB相比可以简单描述为改记录数据为记录数据产生的过程
    • AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式

    AOF写数据三种策略

    • always: 每次写入操作均同步到AOF文件中,数据零误差,性能较低,不建议使用
    • everysec: 每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高 ,默认配置,在系统突然宕机的情况下丢失1秒内的数据
    • no: 由操作系统控制每次同步到AOF文件的周期,整体过程不可控

    AOF功能开启:

    appendonly yes|no
    appendfsync always|everysec|no
    

    AOF自动重写

    • 自动重写触发条件设置
    //触发重写的最小大小
    auto-aof-rewrite-min-size size 
    //触发重写须达到的最小百分比
    auto-aof-rewrite-percentage percent
    
    • 自动重写触发比对参数( 运行指令info Persistence获取具体信息 )
    //当前.aof的文件大小
    aof_current_size 
    //基础文件大小
    aof_base_size
    

    4. Redis事务

    基于特定条件的事务执行

    • 对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行

      watch key1, key2....
      
    • 取消对所有key的监视

      unwatch
      

    分布式锁

    • 使用 setnx 设置一个公共锁

      //上锁
      setnx lock-key value
      //释放锁
      del lock-key
      
      • 利用setnx命令的返回值特征,有值(被上锁了)则返回设置失败,无值(没被上锁)则返回设置成功
      • 操作完毕通过del操作释放锁

    注意:上述解决方案是一种设计概念,依赖规范保障,具有风险性

    分布式锁加强

    • 使用 expire 为锁key添加时间限定,到时不释放,放弃锁

      expire lock-key seconds
      pexpire lock-key milliseconds
      
    • 由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
      -- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
      -- 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
      -- 锁时间设定推荐:最大耗时120%+平均网络延迟110%
      -- 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可

    二、Redis应用

    1. Redis分布式锁

    商品超卖问题:

    在多线程情况下会出现并发问题导致商品超卖问题。(多个线程同时获取到stock数量)

    1)解决多线程并发问题

    加锁解决多线程并发问题:

    上述代码,在分布式环境情况下,每个环境的jvm不同(不同进程),synchronized锁不是同一把锁,此时还是会有并发问题。

    2)解决多台服务部署应用导致并非问题

    使用redis分布式锁解决,使用setnx + 超时时间:

    其中:

    • 使用try finally是为了在异常情况下依然可以释放锁;
    • 设置锁的过期时间,防止程序运行期间jvm挂了导致finally块中代码也无法执行去释放锁;
    • 第29行替换掉27、28行是为了原子操作。

    上述代码还是有问题:
    假设有3个客户端来请求这段代码,其中客户端A需要15秒钟才执行完,在执行第10秒的时候因为到了过期时间自动释放锁,此时客户端B拿到锁进来了,假设执行了5秒中B还正在执行中,此时客户端A执行到第43行去删除锁,此时删除的客户端拿的锁。那么,客户端C自然就可以再次拿锁进入。。。依次循环等于锁一直处于失效状态。

    3)解决锁的误释放问题

    问题的根本原因是自己设置的锁被别人释放掉了,可以设置一个客户端id来表示自己的锁。

    4)释放锁的原子操作问题

    当某一客户端a执行到46行时,卡了一下导致到了过期时间,客户端释放了锁,此时客户端B设置了锁进入程序,此时客户端A恢复,此时客户端执行47行删除的锁其实是客户端B的锁。

    使用看门狗+锁续命来解决:弄一个监控线程轮训主线程是否还在执行,如果还在执行就给锁续命增加过期时间。

    使用redisson框架来解决:

    Redisson分布式锁实现原理:

    Redlock实现原理(解决主从切换时锁失效的情况):

    其实,分布式锁是和高并发是违背的,上述代码等同于串行执行。----分段加锁处理。

    2. 缓存数据库双写不一致问题

    情况1:

    情况2:

    解决方案:使用分布式锁,使得每个线程操作串行执行。

    3. redis为什么这么快

    1)内存数据库
    2)单线程
    3)底层的数据结构
    4)全局维护了一个hash表。


    三、Redis五大数据类型应用场景

    1. String应用场景

    2. Hash应用场景

    3. List应用场景

    比如:微信公共号的消息流。

    4. Set应用场景

    集合操作可实现共同关注模型、推荐模型(可能认识的人)。

    5. Zset应用场景

    四、Redis 底层的数据结构

    Redis 底层的数据结构一共有 6 种,和数据类型对应关系如下图:

    1.SDS

    字符串在 Redis 中是很常用的,键值对中的键是字符串,值有时也是字符串。

    Redis 使用 C 语言实现的,但是它没有直接使用 C 语言的 char* 字符数组来实现字符串,而是自己封装了一个名为简单动态字符串(simple dynamic string,SDS) 的数据结构来表示字符串。

    1.1 C 语言字符串的缺陷

    C 语言的字符串其实就是一个字符数组,即数组中每个元素是字符串中的一个字符。

    比如,下图就是字符串“xiaolin”的 char* 字符数组的结构:

    在 C 语言里,对字符串操作时,char * 指针只是指向字符数组的起始位置,而字符数组的结尾位置就用“\0”表示,意思是指字符串的结束

    因此,C 语言标准库中字符串的操作函数,就通过判断字符是不是“\0”,如果不是说明字符串还没结束,可以继续操作,如果是则说明字符串结束了,停止操作。

    举个例子,C 语言获取字符串长度的函数 strlen,就是通过字符数组中的每一个字符,并进行计数,等遇到字符为“\0”后,就会停止遍历,然后返回已经统计到的字符个数,即为字符串长度。下图显示了 strlen 函数的执行流程:

    很明显,C 语言获取字符串长度操作的时间复杂度是 O(N)(*****这是一个可以改进的地方*)

    C 语言的字符串用 “\0” 字符作为结尾标记有个缺陷。假设有个字符串中有个 “\0” 字符,这时在操作这个字符串时就会提早结束,比如 “xiao\0lin” 字符串,计算字符串长度的时候则会是 4。

    还有,除了字符串中不能 “\0” 字符外,用 char* 字符串中的字符必须符合某种编码(比如ASCII)。

    这些限制使得 C 语言的字符串只能保存文本数据,不能保存像图片、音频、视频文化这样的二进制数据(*****这也是一个可以改进的地方*****)

    C 语言标准库中字符串的操作函数是很不安全的,对程序员很不友好,稍微一不注意,就会导致缓冲区溢出。

    举个例子,strcat 函数是可以将两个字符串拼接在一起的。

    c //将 src 字符串拼接到 dest 字符串后面 char *strcat(char dest, const char src);

    C 语言的字符串是不会记录自身的缓冲区大小的,所以 strcat 函数假定程序员在执行这个函数时,已经为 dest 分配了足够多的内存,可以容纳 src 字符串中的所有内容,而一旦这个假定不成立,就会发生缓冲区溢出将可能会造成程序运行终止,(这是一个可以改进的地方)。

    而且,strcat 函数和 strlen 函数类似,时间复杂度也很高,也都需要先通过遍历字符串才能得到目标字符串的末尾。然后对于 strcat 函数来说,还要再遍历源字符串才能完成追加,对字符串的操作效率不高。

    通过以上的分析,可以得知 C 语言的字符串 不足之处以及可以改进的地方:

    • 获取字符串长度的时间复杂度为 O(N);
    • 字符串的结尾是以 “\0” 字符标识,而且字符必须符合某种编码(比如ASCII),只能保存文本数据,不能保存二进制数据;
    • 字符串操作函数不高效且不安全,比如可能会发生缓冲区溢出,从而造成程序运行终止;

    Redis 实现的 SDS 的结构就把上面这些问题解决了,接下来我们一起看看 Redis 是如何解决的。

    1.2 SDS 结构设计

    SDS 的数据结构:

    结构中的每个成员变量分别介绍下:

    • len,SDS 所保存的字符串长度。这样获取字符串长度的时候,只需要返回这个变量值就行,时间复杂度只需要 O(1)。
    • alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算 出剩余的空间大小,然后用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区益处的问题。
    • flags,SDS 类型,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
    • buf[],字节数组,用来保存实际数据。不需要用 “\0” 字符来标识字符串结尾了,而是直接将其作为二进制数据处理,可以用来保存图片等二进制数据。它即可以保存文本数据,也可以保存二进制数据,所以叫字节数组会更好点。

    总的来说,Redis 的 SDS 结构在原本字符数组之上,增加了三个元数据:len、alloc、flags,用来解决 C 语言字符串的缺陷。

    1.3 节省内存空间

    SDS 结构中有个 flags 成员变量,表示的是 SDS 类型。

    Redos 一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。

    这 5 种类型的主要区别就在于,它们数据结构中的 len 和 alloc 成员变量的数据类型不同之所以 SDS 设计不同类型的结构体,是为了能灵活保存不同大小的字符串,从而有效节省内存空间

    2. 压缩列表

    压缩列表是 Redis 数据类型为 list 和 hash 的底层实现之一。

    • 当一个列表键(list)只包含少量的列表项,并且每个列表项都是小整数值,或者长度比较短的字符串,那么 Redis 就会使用压缩列表作为列表键(list)的底层实现。
    • 当一个哈希键(hash)只包含少量键值对,并且每个键值对的键和值都是小整数值,或者长度比较短的字符串,那么 Redis 就会使用压缩列表作为哈希键(hash)的底层实现。

    压缩列表结构设计

    压缩列表是 Redis 为了节约内存而开发的,它是由连续内存块组成的顺序型数据结构,有点类似于数组。不作过多讲解。

    在有序集合zset的实现里面,跳跃表和压缩列表都有用到。
    根据上面都描述,压缩列表的使用场景一般是短字符串,且元素个数不能太多。所以在zset里面有几个配置:
    zset-max-ziplist-entries: 默认值128, 当元素个数小于这个值都时候使用压缩列表。否则使用跳跃表。
    zset-max-ziplist-value:默认值64,当每个元素的字符串长度小于这个值的时候使用压缩列表,否则使用跳跃表。
    满足这两个条件中的任意一个条件,就会转换到跳跃表。而转换到跳跃表之后,即便把元素删除,也不好回退到压缩列表。

    3. 跳跃表

    什么是跳跃表 ?

    对于一个单链表来说, 即便链表中存储的数据是有序的, 我们想要随机查找一个数据,那么也只能从头到尾遍历链表节点, 这样查找的效率就会很低,是件复杂度为 O(n)

    如果我们想要提高其查找效率, 可以考虑在链表上建索引的方式。每2个节点提取一个节点到上一级,我们把抽出来的那一级叫作索引。

    image

    可以看出, 加了一层索引以后, 查找一个节点需要遍历的节点数少了, 也就是说查找效率提升了,同理可以再加一层索引。

    从图中我们可以看出,查找效率又有提升。在例子中我们的数据很少,当有大量的数据时,我们可以增加多级索引,其查找效率可以得到明显提升。

    像这种链表加多级索引的结构,就是跳跃表!

    Redis使用跳跃表作为有序集合键的底层实现之一,如果一个有序集合包含的元素数量比较多,又或者有序集合中元素的成员是比较长的字符串时, Redis就会使用跳跃表来作为有序集合健的底层实现。

    这里我们需要思考一个问题——为什么元素数量比较多或者成员是比较长的字符串的时候Redis要使用跳跃表来实现?
    从上面我们可以知道,跳跃表在链表的基础上增加了多级索引以提升查找的效率,但其是一个空间换时间的方案,必然会带来一个问题——索引是占内存的。原始链表中存储的有可能是很大的对象,而索引结点只需要存储关键值值和几个指针,并不需要存储对象,因此当节点本身比较大或者元素数量比较多的时候,其优势必然会被放大,而缺点则可以忽略

    Redis中跳跃表的实现

    Redis的跳跃表由zskiplistNode和skiplist两个结构定义,其中 zskiplistNode结构用于表示跳跃表节点,而 zskiplist结构则用于保存跳跃表节点的相关信息,比如节点的数量,以及指向表头节点和表尾节点的指针等

    • header:指向跳跃表的表头节点,通过这个指针程序定位表头节点的时间复杂度就为O(1)

    • tail:指向跳跃表的表尾节点,通过这个指针程序定位表尾节点的时间复杂度就为O(1)

    • level:记录目前跳跃表内,层数最大的那个节点的层数(表头节点的层数不计算在内),通过这个属性可以再O(1)的时间复杂度内获取层高最好的节点的层数。

    • length:记录跳跃表的长度,也即是,跳跃表目前包含节点的数量(表头节点不计算在内),通过这个属性,程序可以再O(1)的时间复杂度内返回跳跃表的长度。

    结构右方的是四个 zskiplistNode结构,该结构包含以下属性:

    • 层(level):
      节点中用1、2、L3等字样标记节点的各个层,L1代表第一层,L代表第二层,以此类推。
      每个层都带有两个属性:前进指针和跨度。前进指针用于访问位于表尾方向的其他节点,而跨度则记录了前进指针所指向节点和当前节点的距离(跨度越大、距离越远)。在上图中,连线上带有数字的箭头就代表前进指针,而那个数字就是跨度。当程序从表头向表尾进行遍历时,访问会沿着层的前进指针进行。
      每次创建一个新跳跃表节点的时候,程序都根据幂次定律(powerlaw,越大的数出现的概率越小)随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”。

    • 后退(backward)指针:
      节点中用BW字样标记节点的后退指针,它指向位于当前节点的前一个节点。后退指针在程序从表尾向表头遍历时使用。与前进指针所不同的是每个节点只有一个后退指针,因此每次只能后退一个节点。

    • 分值(score):
      各个节点中的1.0、2.0和3.0是节点所保存的分值。在跳跃表中,节点按各自所保存的分值从小到大排列。

    • 成员对象(oj):
      各个节点中的o1、o2和o3是节点所保存的成员对象。在同一个跳跃表中,各个节点保存的成员对象必须是唯一的,但是多个节点保存的分值却可以是相同的:分值相同的节点将按照成员对象在字典序中的大小来进行排序,成员对象较小的节点会排在前面(靠近表头的方向),而成员对象较大的节点则会排在后面(靠近表尾的方向)
      在比较的时候 如果分值相同则会比较保存的对象。

    五、Redis常见问题

    1. 缓存穿透、缓存击穿、缓存雪崩 如何解决?

    缓存穿透

    当缓存与DB中都不存在该数据时,如果此时发起恶意大量请求,会导致这个不存在的数据每次请求都会查询DB,导致DB压力过大,直接挂掉。

    解决方案:
    (1)对空值缓存,并设置较短的过期时间。
    (2)设置访问白名单,使用布隆过滤器,分别通过多个哈希函数生成多个哈希值,然后将这些哈希值存到一个足够大的bitmap中,此时一个一定不存在的数据就会被这个bitmap拦截,从而减少了数据库的查询压力。
    (3)业务过滤。

    缓存击穿

    某个热点key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    解决方案:
    (1)设置热点数据永远不过期。
    (2)使用分布式锁。

    缓存雪崩

    缓存中大批量的数据集中过期,从而导致查询数据量巨大,引起数据库压力过大甚至down机。

    解决方案:
    (1)将缓存失效时间分散开
    (2)如果缓存数据库是分布式部署,将热点数据均匀分布在不同缓存中。
    (3)设置热点数据永远不过期。

    2. Redis为什么这么快?

    (1)内存存储,没有磁盘IO上的开销
    (2)单线程实现,避免了多个线程之间线程切换和锁资源争用的开销
    (3)优化的数据结构
    (4)非阻塞IO,Redis使用多路复用IO技术,在poll,epool,kqueue选择最优IO实现

    相关文章

      网友评论

          本文标题:Redis

          本文链接:https://www.haomeiwen.com/subject/fzhvlrtx.html