Redis的惊鸿一瞥

目录

1. 我是谁,我从哪里来,将到哪里去

1.1 我是谁

1.1.1 特点:

1.1.2 优势:

1.2 我从哪里来

1.3 将到哪里去

2. 下载安装

3. “集美”们

3.1 集美之String(字符串)

3.2 集美之List (列表)

3.3 集美之Hash (字典)

3.4 集美之Set (集合)

3.5 集美之Zset (sorted  set  有序集合)

4. 什么是快乐星球

4.1 快乐星球之应用

4.2 快乐星球之分布式锁

4.2.1 常见的分布式锁

4.2.2 Redis分布式锁实现

4.3 快乐星球之缓存一致性

4.3.1 谈谈一致性

4.3.2 三个经典的缓存模式

4.3.3 操作缓存的时候,到底是删除缓存呢,还是更新缓存?

4.3.4 双写的情况下,先操作数据库还是先操作缓存?

5. 爷青回

5.1 爷青回之跳表

5.1.1 构建一个跳表

5.1.2 删除节点

5.1.3 查

5.1.4 跳表 与平衡树、哈希表的比较

5.1.5 Redis中作者选择SkipList的原因

5.2 爷青回之淘汰策略

5.2.1 过期删除策略

5.2.2 内存淘汰策略

5.2.3 Redis的内存淘汰机制

5.2.4 LFU 模式

5.3 爷青回之持久化

5.3.1 快照(RDB:Redis DataBase)

5.3.2 AOF (Apend Only File)日志

6. 年轻人不讲武德

6.1 年轻人不讲武德之雪崩

6.1.1 现象与原因

6.1.2 解决方案

6.2 年轻人不讲武德之击穿

6.2.1 现象与原因

6.2.2 解决方案

6.3 年轻人不讲武德之穿透

6.3.1 现象与原因

6.3.2 解决方案

7. yyds

7.1 yyds之多线程

8. 最后的晚餐


1. 我是谁,我从哪里来,将到哪里去

        万事万物追根揭底都是一个哲学问题,本篇文章就从redis的前世今生讲起,希望给大家学习redis带来一些帮助。

1.1 我是谁

        Redis 是互联网技术领域使用最为广泛的存储中间件,它是“Remote Dictionary Service ” (远程字典服务〉的首字母缩写。

1.1.1 特点:

        >> Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

        >> Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

        >> Redis支持数据的备份,即master-slave模式的数据备份。

1.1.2 优势:

        >> 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

        >> 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。

        >> 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。      

        >> 单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。 丰富的特性 – Redis还支持                                 publish/subscribe, 通知, key 过期等等特性。

1.2 我从哪里来

        Redis 由意大利人 Salvatore Sanfilippo (网名 Antirez )开发,图 1-1 所示的是他 的个人照片。

        我们都知道 Redis 的默认端口是 6379 ,这个端口号也不是随机选的,而是由于 机键盘字母“MERZ ”的位置决定的,如图1-2 所示。

        

        “MERZ ”在 Antirez 的朋友圈语言中是“愚蠢”的代名词,它由于意大利广告 女郎“Alessia Merz "在电视节目上说了一堆愚蠢的话

        而被人熟知.

1.3 将到哪里去

        中高级后端开发者绕不开的必备技能

                提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,提高效率,将数据缓存

                在内存中。

2. 下载安装

        为你,千千万万遍

        请移步百度Google一下,或Google百度一下。

3. “集美”们

        人间烟火,山河远阔;无一是你,无一不是你。

        (1) 5 种基础数据结构

                <1>. string( 字符串):Redis最基本的数据类型,一个键对应一个值,一个键值最大存储512MB。

                <2>. list (列表):hash是一个键值对的集合,是一个String类型的field和value的映射表,适合用于存储对象。

                <3>. hash (字典):是redis的简单的字符串列表,按插入顺序排序。

                <4>.  set (集合):是String字符串类型的无序集合,也不可重复 。

                <5>. zset (sorted  set  有序集合):是String类型的有序集合,也不可重复。                                                      

                                                                   有序集合中的每个元素都需要指定一个分数,根据分数对元素进行升序排序。

        (2) 其他的数据结构

                <1>. 位图:最小单位是比特(bit ),每个 bit 的取值只能是 0或1,其实就是普通的字符串,也就是 byte 数组。

                           可以用于统计用户一共签到了多少天。

                <2>. Hyperloglog:用来解决这种统计问题的,提供不精确的去重计数方案,虽然不精确,但是也不是非常离谱,

                                      标准误差是 81%,主要用于不精确的UV统计。

                <3>. .......

        (3) 基本语法

        

        (4)通用实例

        

        (5)通用基本命令

                请参考:https://www.runoob.com/redis/redis-keys.html

3.1 集美之String(字符串)

        >> Redis 最简单的数据结构,如图 1-4 所示,它的内部表示就是一个字符数组。

        >> 字符串结构使用非常广泛,一个常见的用途就是缓存用户信息。

                                        

        >> Redis 的字符串是动态字符串,是可以修改的字符串,内部结构的实现类似于Java的 ArrayList ,

             采用预分配冗余空间的方式来减少内存的频繁分配,如图 1-4 所示。

        注:常用命令请参考:https://www.runoob.com/redis/redis-strings.html

3.2 集美之List (列表)

        >> 相当于 Java 语言里面的 LinkedList 注意它是链表而不是数组。

        >> 如图 1-5 所示 表中的每个元素都使用双向指针顺序,串起来可以同时支持前向后向遍历。

        

        >> 本结构常用来做异步队列使用 将需要延后处理的任务结构体序列化成字符串,塞进 Redis 的列表,

             另个线程从这个列表中轮询数据进行处理。

        注:常用命令请参考:https://www.runoob.com/redis/redis-lists.html

3.3 集美之Hash (字典)

        >> Redis 的字典相 当于 Java 语言里面的HashMap ,如 1-7 示,它是无序字典内部存储了很多键值对。

        >> 实现结构上与Java的HashMap 是一样的,都是“数组+链表”二维结构。如图 1-8 所示,第一维hash 的数组位置碰撞时,

             就会将碰撞的元素使用链表串接起来。

        注:常用命令请参考:https://www.runoob.com/redis/redis-hashes.html

3.4 集美之Set (集合)

        >> 集合相当于 Java 语言里面的 HashSet ,它内部的键值对是无序的、唯一的。它的内部实现相当于一个特殊的字典,

             字典中所有的 value 都是一个值 NULL。

        >> set 结构可以用来存储在某活动中中奖的用户 ID ,因为有去重功能,可以保证同一个用户不会中奖两次。

        注:常用命令请参考:https://www.runoob.com/redis/redis-sets.html

3.5 集美之Zset (sorted  set  有序集合)

        >> zset 可能是 Redis 提供的最有特色的数据结构。如图 1-10 所示,它类似于Java的SortedSet和HashMap的结合体,

             一方面它是个 set ,保证了内部 value 的唯性,另方面它可以给每个 value 赋予一个 score , 代表这个 value 的排序权重。

        >> 它的内部实现用的是一种叫作“跳跃列表”的数据结构。

        >> zset 可以用来存储粉丝列表, value 值是粉丝的用户 ID, score 是关注时间。我们可以对粉丝列表按关注时间进行排序。

        >> zset 还可以用来存储学生的成绩, value 值是学生的 ID, score 是他的考试成绩。我们对成绩按分数进行排序就可以得到

             他的名次。

        注:常用命令请参考:https://www.runoob.com/redis/redis-sorted-sets.html

4. 什么是快乐星球

        什么是快乐星球,如果你想知道什么是快乐星球的话,那么我们一起来研究研究。

4.1 快乐星球之应用

        (1) 持久化数据:利用zset类型存储排行榜、利用hash记录点赞数、评论数等

        (2) 高速缓存:会话缓存、热点数据缓存

        (3) 分布式锁

        (4) 消息队列

        (5) 自增序列:活动排行榜、计数、分布式id

        (6) 发布,订阅消息:消息通知

4.2 快乐星球之分布式锁

4.2.1 常见的分布式锁

        (1) MySql : 建一张分布式锁的表实现。

        (2) Zk :请移步去学习Zk相关知识。

        (3) Redis :请继续阅读本篇博客。

        (4) 自研分布式锁:如谷歌的Chubby。

4.2.2 Redis分布式锁实现

        (1) 简单实现:

                        <1> 对于某个资源加锁:setNx resourceName value   

                        <2> 加入过期时间:set resourceName value ex 5 nx

        (2) Redission: Redission是Redis的客户端,相比于Jedis功能简单。

        (3) RedLock:利用多个Redis集群,用多数的集群加锁成功,减少Redis某个集群出故障, 造成分布式锁出现问题的概率。

4.3 快乐星球之缓存一致性

4.3.1 谈谈一致性

        (1) 强一致性:要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大

        (2) 弱一致性:系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地

                                保证到某个时间级别(比如秒级别)后,数据能够达到一致状态

        (3) 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。

4.3.2 三个经典的缓存模式

        (1) Cache-Aside Pattern:旁路缓存模式。

        (2) Read-Through/Write-through:Read-Through实际只是在Cache-Aside之上进行了一层封装,它会让程序代码变得更简洁,

             同时也减少数据源上的负载。

        (3) Write-behind(异步缓存写入):通过批量异步的方式来更新数据库。

4.3.3 操作缓存的时候,到底是删除缓存呢,还是更新缓存?

        >> 日常开发中,我们一般使用的就是Cache-Aside模式。    

             Cache-Aside在写入请求的时候,为什么是删除缓存而不是更新缓存呢?

        

                                                                                                1.线程A先发起一个写操作,第一步先更新数据库。

                                                                                                2.线程B再发起一个写操作,第二步更新了数据库

                                                                                                3.由于网络等原因,线程B先更新了缓存

                                                                                                4.线程A更新缓存。

                这时候,缓存保存的是A的数据(老数据),数据库保存的是B的数据(新数据),数据不一致了,脏数据出现啦。

        如果是删除缓存取代更新缓存则不会出现这个脏数据问题。

4.3.4 双写的情况下,先操作数据库还是先操作缓存?

        >> Cache-Aside缓存模式中,有些小伙伴还是会有疑问,在写请求过来的时候,为什么是先操作数据库呢?    

             为什么不先操作缓存呢?

        1.线程A发起一个写操作,第一步del cache

        2.此时线程B发起一个读操作,cache miss

        3.线程B继续读DB,读出来一个老数据

        4.然后线程B把老数据设置入cache

        5.线程A写入DB最新的数据

                酱紫就有问题啦,缓存和数据库的数据不一致了。缓存保存的是老数据,数据库保存的是新数据。

        因此,Cache-Aside缓存模式,选择了先操作数据库而不是先操作缓存。

5. 爷青回

        世间所有的相遇都是久别重逢

5.1 爷青回之跳表

        跳表是一种数据结构。它允许快速查询一个有序连续元素的数据链表。

        跳跃列表的平均查找和插入时间复杂度都是O(log n),优于普通队列的O(n)。

        跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。        

        首先其位于 L0 层的概率肯定是 100% ,而兼职到 Ll 层只有 50% 的概率,到L2 层只有 25% 概率, 到 L3 层只有 12.5% 的概率,以此类推, 直随机到最顶层 L31(最多32层)绝大多数元素都过不了几层, 只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,元素能进入到顶层的可能 性就会越大。

5.1.1 构建一个跳表

5.1.2 删除节点

5.1.3 查

                                        >> 查找 5 的过程:

                                             1.head->8, 8>5,从head开始,去下一层查找。

                                             2.head->4->8, 8>5,从 4 元素开始查找。去下一层查找

                                             3.head->4->8, 8>5,从 4 元素开始查找。去下一层查找.

                                             4.head->4->6, 6>5,从 4 元素开始查找。去下一层查找.

                                             5.head->4->5, 5==5,返回节点5.

5.1.4 跳表 与平衡树、哈希表的比较

                                  注:p:指针数目

5.1.5 Redis中作者选择SkipList的原因

5.2 爷青回之淘汰策略

5.2.1 过期删除策略

        删除达到过期时间的key。

        1.定时删除    

                >> 对于每一个设置了过期时间的key都会创建一个定时器,一旦到达过期时间就立即删除。      

                >> 该策略可以立即清除过期的数据,对内存较友好,但是缺点是占用了大量的CPU资源去 处理过期的数据,

                     会影响Redis的吞吐量和响应时间。

        2.惰性删除    

                 >> 当访问一个key时,才判断该key是否过期,过期则删除。该策略能最大限度地节省CPU资源,

                      但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key没有被再次访问,

                      因此不会被清除,导致占用了大量的内存。

        3.定期删除      

                >> 每隔一段时间,扫描Redis中过期key字典,并清除部分过期的key。该策略是前两者的一个折中方案,

                     还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。

5.2.2 内存淘汰策略

        Redis的内存淘汰策略,是指内存达到maxmemory极限时,使用某种算法来决定清理掉哪些数据,以保证新数据的存入。

5.2.3 Redis的内存淘汰机制

        1. noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。

        2. allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。

        3. allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个 key。

        4. volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key。

        5. volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。

        6. volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。

5.2.4 LFU 模式

        Redis 4.0 里引人了一个新的淘汰策略-----LFU 模式

注:    

>> LRU的全称是Least Recently Used    

>> LFU 的全称是 Least Frequently Used    

>> 在配置文件中,通过maxmemory-policy可以配置要使用哪一个淘汰机制。

5.3 爷青回之持久化

5.3.1 快照(RDB:Redis DataBase)

        快照原理:操作系统的多进程 COW (Copy On Write )机制

        注:ROW(Redirect on first write) ,实例:mysql的binlog

5.3.2 AOF (Apend Only File)日志

        AOF原理:存储的是 Redis 服务器的顺序指令序列, AOF 日志只记录对内存进行修改的指令记录。

        >> AOF 日志是以文件的形式存在的,当程序对 AOF 日志文件进行写操作时,实际 上是将内容写到了内核为文件描述符分配的

              一个内存缓存中,然后内核会异步将脏 数据刷回到磁盘的。

        >> 在生产环境的服务器中, Redis 通常是每隔 ls 左右执行一次 fsync 操作,这 ls 的周期是可以配置的。

              这是在数据安全性和性能之间做的一个折中,在保持高 性能的同时,尽可能使数据少丢失。

        >> 其他类似技术:log4j

6. 年轻人不讲武德

        所有命运的馈赠,都早已暗地里标好了价格。

6.1 年轻人不讲武德之雪崩

6.1.1 现象与原因

        1.查询变慢,大面积服务不可用。

        2.同一时间缓存大面积失效,所有的请求直接打到数据库上,DB扛不住挂了,如果是重要的库,

           例如用户库,那牵联就一大片了,瞬间倒一片。

6.1.2 解决方案

        1. 批量往redis存数据的时候,把每个key的失效时间加上个随机数,这样就能保证数据不会在同一个时间大面积失效。

6.2 年轻人不讲武德之击穿

6.2.1 现象与原因

        1.与雪崩类似,但是又不一样。雪崩是因为大面积缓存失效,请求全打到DB;

           而击穿是指一个key是热点,并发请求全都集中访问此key,而当此key过期瞬间,持续的并发就击穿缓存,全都打在DB上。

           进而又引发雪崩的问题。

6.2.2 解决方案

        1.设置热点key不过期。或者加上互斥锁。

6.3 年轻人不讲武德之穿透

6.3.1 现象与原因

        1.指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,

           这个时候用户很有可能就是一个攻击者,这样的功击会导致DB的压力过大,严重的话就是把DB搞挂了。

           因为每次都绕开了缓存直接查询DB

6.3.2 解决方案

        1.在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,

           作为被调用方,要考虑可能任何的参数传值。

        2.在缓存查不到,DB中也没有的情况,可以将对应的key的value写为null,或者其他特殊值写入缓存,

           同时将过期失效时间设置短一点,以免影响正常情况。这样是可以防止反复用同一个ID来暴力攻击。

        3.正常用户是不会这样暴力功击,只有是恶意者才会这样做,可以在网关NG作一个配置项,为每一个IP设置访问阀值。

        4.高级用户布隆过滤器(Bloom Filter),这个也能很好地防止穿透。原理就是利用高效的数据结构和算法快速判断出

           你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。

7. yyds

        你有没有很想和谁重新认识一次

7.1 yyds之多线程

        >> Redis 4.0版本引入了Lazy Free,将慢操作异步化,这也是在事件处理上向多线程迈进了一步。

        >> Redis 6.0版本实现了多线程I/O:

                 正如官方以前的回复,Redis的性能瓶颈并不在CPU上,而是在内存和网络上。

                 因此6.0发布的多线程并未将事件处理改成多线程,而是在I/O上,此外,如果把事件处理改成多线程,不但会导致锁竞争,

              而且会有频繁的上下文切换,即使用分段锁来减少竞争,对Redis内核也会有较大改动,性能也不一定有明显提升。

       >> 局限性:Redis6.0版本的多线程并非彻底的多线程,I/O线程只能同时执行读或者同时执行写操作,

                           期间事件处理线程一直处于等待状态,并非流水线模型,有很多轮训等待开销。

       >> 小结:Redis 4.0引入Lazy Free线程,解决了诸如大键删除导致服务器阻塞问题,在6.0版本引入了I/O Thread线程,

                       正式实现了多线程,但相较于Tair,并不太优雅,而且性能提升上并不多,压测看,

                       多线程版本性能是单线程版本的2倍,Tair多线程版本则是单线程版本的3倍。

8. 最后的晚餐

        本文主要介绍的Redis相关重点内容,有详有略,如某部分想深入学习可以去Redis官网或通过其他途径学习,因水平有限,文中所述难免有不当之处,欢迎批评指正,如想交流学习,也欢迎读者在评论处留下自己的见解和想法。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值