专栏首页后端redis 系列:总结篇
原创

redis 系列:总结篇

Redis 总体介绍

Redis 是 key-value 型的 memory 缓存中间件,相信大部分程序员都在项目中使用过它。我们也可以利用 memory 来实现缓存,只是使用 redis 的话,可以将缓存功能统一到一个组件里,方便后续重用拓展。

在底层上, redis 使用了 IO 多路复用技术,像 select、epoll 等。能较好的保障吞吐量。而且 redis 采用了单线程处理请求,避免了线程切换和锁竞争锁带来的额外消耗。

加上 redis 本身也对一些数据结构进行了优化设计,所以 redis 的性能非常好,官方给出的测试报告是单机可以支持约 10w/s 的 QPS。

Redis 通信协议

redis 是基于 tcp 长连接的 C/S 架构,采用的是文本序列化协议,并且和 http 一样,也是一个请求一个响应,客户端接到响应后再继续请求。

当然,也可以将多次请求发送过去,然后一次响应回所有执行结果,这就是所谓的管道 pipeline 技术。

redis 的文本序列化协议比较简单,通过一些规范格式去解析文本,大概如下:

  • \r\n 表示解析结束
  • 简单字符串,以“+”开头
  • 错误 Errors,以“-”开头
  • 整数类型,以“:”开头
  • 大字符串类型,以“\$”开头
  • 数组类型,以“*”开头

例如,客户端向服务器发送命令:

SET key value

将被解析为:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

上面的命令可以看成:

*<参数数量> CR LF

$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

而服务器的回复则有很多类型,一般由响应数据的第一个字节决定:

状态回复(status reply)的第一个字节是 "+"

错误回复(error reply)的第一个字节是 "-"

整数回复(integer reply)的第一个字节是 ":"

批量回复(bulk reply)的第一个字节是 "$"

多条批量回复(multi bulk reply)的第一个字节是 "*"

例如,响应回来的状态回复如下:

+OK

redis 的数据结构

为了让开发者能更好的使用缓存,redis 支持了 5 种数据类型。底层是由 6 种数据结构组成的。

5 种数据类型

字符串:字符串类型是 redis 里最基础的数据类型,像 set name "hello" 操作后,在 get name 时返回的就是字符串,而且还支持了对位的操作。一般一个键能存储 512MB 的值。

hash:哈希类型主要是用来存储对象的,一般我们如果有一整个对象要存储,里面包含了多个字段,则可以使用 hash 来存储,因为 redis 提供了对这些字段的提取和设置,减少了开发者对它的二次处理,比如序列化反序列化操作。

list:一个简单的字符串列表,它允许我们从两端进行 push,pop 操作,还支持一定范围的列表元素。可以看成是双向列表。

set:集合是一个不重复值的组合,为我们提供了交集、并集、差集等操作,像找出共同好友这种需求就可以使用集合操作了。

sorted set:有序集合,在上面集合的基础上提供了排序功能,通过一个 score 属性来进行排序。

6 种底层数据结构

上面的数据类型实际上在 redis 底层是有对应的数据结构来实现的,都是 redis 经过精心设计的,能很好的提高处理效率。

简单动态字符串:redis 是使用 C 语言写的,而 C 语言里的字符串类型比较原始,比如使用 \0 作为字符结束符。所以 redis 实现了属于自己的字符串类型,比如字符串长度,预先分配内存,动态拓展等特点,也保证了处理安全性。

链表:一个双端链表,有 prev,next 指针去获取前后节点,带有 len 属性,能保存多种类型的值。

字典:通过哈希算法来实现 key-value 的映射操作,采用链地址法解决了 hash 冲突,一般时间复杂度能达到 O(1)。

跳跃表:一个多层有序链表,每一层都是对下面一层的有序提取,能降低搜索次数,有点像有序二叉树的搜索一样。

跳跃表

整数集合:一个有序的整数集合,不会有重复元素。

压缩列表(ziplist):经过特殊编码的一块连续内存,能有效的节省内存。

快速列表:将 ziplist 组织为了一个双向链表,由于 ziplist 的内部连续性,能降低链表的内存碎片问题,提高内存利用率。

redis 的淘汰策略

redis 的淘汰策略主要是 LRU 淘汰、TTL 淘汰和随机淘汰这三种机制。

  • LRU 淘汰:最近最少使用的淘汰掉
  • TTL 淘汰:越早过期的越先淘汰掉。
  • 随机淘汰:采用随机算法淘汰掉。

由于 redis 可以对键设置过期时间,也可以不设置,所以淘汰策略还得再细分:

  • volatile-lru:针对设置了过期时间的 key 执行 LRU 淘汰策略,没有设置过期时间的不会被淘汰。
  • volatile-ttl:只针对设置了过期时间的 key 执行 TTL 淘汰。
  • volatile-random:只针对设置了过期时间的 key 执行随机淘汰。
  • allkeys-lru:针对所有键进行 LRU 淘汰策略
  • allkeys-random:针对所有键进行随机淘汰策略
  • no-enviction:不执行淘汰策略,如果有写入操作,则报错;读请求可以继续进行。

在 Redis 的配置文件 redis.conf 里我们可以进行淘汰策略的设置:

# 数据达到多大后执行淘汰策略
maxmemory 300mb

# 淘汰策略的设置
maxmemory-policy volatile-lru

Redis 使用场景

Redis 的使用场景有很多,最常用的莫过于数据缓存了。但由于它提供了多种数据类型,因此我们还可以进行其他场景的开发,比如:

  • 排行榜:前面提到过有序集合(sorted set),由于每次写入都会进行排序,而且不含重复值,所以我们可以将用户的唯一标识,比如 userId 作为 key,分数作为 score,然后就可以进行 ZADD 操作,以得到排行榜。
  • 签到:签到往往只有 2 种状态,已签到和未签到。这就跟 0 和 1 一样,所以 redis 的 setbitgetbit 这种对位的操作就适合签到场景。
  • 计数:redis 是单线程操作,这种计数功能,比如点赞数、粉丝数的操作可以交给 redis 以避免并发竞争问题。当然,也得考虑持久化问题。

关于分布式锁

有的时候我们可能会使用 redis 作为分布式锁的辅助使用,通过对 redis 操作响应以判断当前是否可以获取到锁。

不过这样的解决方案会有单节点的瓶颈,如果 redis 宕机了,就会导致锁的不可用。

有的朋友可能会说 redis 也有它的高可用方案。但实际上 redis 的高可用方案还是不适合分布式锁的应用,会有多节点同时获取到锁的风险。

如果真的需要比较严谨的分布式锁,还是得使用 zookeeperetcd等分布式协调方案,能保证强一致性。

Redis 使用注意点

缓存雪崩和穿透

Redis 通过缓存冗余的数据,为我们的程序提供了高性能的保障。但需要注意的是一旦缓存失效,那么就会有大量的请求过来,压垮系统,这就是缓存雪崩。

除了缓存雪崩,还有缓存穿透的可能。比如每次访问不一样的数据,则请求还是会落到后方。

为了防止缓存雪崩,我们可以对请求做控制,比如加入到消息队列,慢慢消化它;又或者直接开启限流功能,将流量控制在合理的范围内。

而针对缓存穿透,我们可以建立黑白名单,将一些恶意请求拎出来,然后直接拒绝掉。如果是正常的请求,那可以将筛选出来的结果也暂时缓存起来,即使得到的值是 NULL 值。

数据并发问题

由于 Redis 是以组件形式存在,所以实际上我们的程序通信可以认为是分布式的了,也就是会有缓存和后端数据一致性的问题。

常见的做法是在有新数据到来时,将缓存 key 删除掉,等待下次的查询重新填补上缓存。

之所以在更新数据时不让 Redis 也做更新动作,是为了防止多个更新动作一起发生,可能由于网络原因,导致后更新的比前面更新的先一步达到 Redis, 这样就会跟原来的流程不一样了。所以只采取了删除动作,不做其他。

不过,就算是删除 key 这种方案也有一定概率跟上面的情况一样,真的要严谨的话,一般会设置定时过期时间,让数据最多在这段时间不一致。

总结

Redis 的使用很简单,但实际上涉及的知识挺多的,特别需要注意它的并发数据一致问题。只有了解 Redis 越多,我们才能更好的掌握它,更多细节大伙可以自行深入了解,希望本文能帮到大家,谢谢!


感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。

可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!

阅新技术,阅读更多的新知识。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

关注作者,阅读全部精彩内容

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Python系列总结篇~

    关于学习Python,相较于C语言更多的是简洁,易用,上手也更简单。Python是小编现学的第二门编程语言,作为自学的我来说,学完Python后越感觉到语言只是...

    小Bob来啦
  • Android总结篇系列之Permission

    前言:权限是一种安全机制。Android权限主要用于限制应用程序内部某些具有限制性特性的功能使用以及应用程序之间的组件访问。对于用户来说,这无疑是一种对自身安全...

    AlicFeng
  • Redis系列 | Redis5 配置及优化总结

    Tinywan
  • [原创-总结]WCF技术剖析系列总结篇

    近半年以来,一直忙于我的第一本WCF专著《WCF技术剖析》的写作,一直无暇管理自己的Blog。到目前为止《WCF技术剖析(卷1)》的写作暂告一段落,初步预计于下...

    蒋金楠
  • Redis系列总结--这几点你会了吗?

    前面几篇已经对Redis中几个关键知识点做了介绍,本篇主要对Redis系列做一下总结以及对Redis中常见面试题简单进行介绍一下。首先我们对前面几篇谈到的Red...

    创译科技
  • 【SSH进阶之路】Hibernate系列——总结篇(九)

    这篇博文是Hibernate系列的最后一篇,既然是最后一篇,我们就应该进行一下从头到尾,整体上的总结,将这个系列的内容融会贯通。

    程序猿小亮
  • 数据结构与算法系列之总篇

    不知何时起,江湖上出现了一个门派,名曰“计算机技术”。其以功法多样、内功高深以及有教无类而闻名江湖。各路侠客,闻名而至。然,多数人只热衷于功法,而畏怯其内功难度...

    尜尜人物
  • golang-nsq系列(四)--源码解析总结篇

    随着互联网技术在各行各业的应用高速普及与发展,各层应用之间调用关系越来越复杂,架构、开发、运维成本越来越高,高内聚、低耦合、可扩展、高可用已成为了行业需求。

    astraw99
  • Redis系列——1.科普篇

    Redis是REmote Dlctionary Server(远端字典服务器的缩写),是目前最火热的非关系型数据库,最新的稳定版本是redis 5.0。

    陈琛
  • 栈与队列:总结篇!

    相信不仅仅是C++中有这些问题,那么大家使用其他编程语言,也可以考虑一下这四个问题,栈和队列是如何实现的。

    代码随想录
  • 三大系列总结

    1.offset系列 经常用于获得元素位置 offsetLeft offsetTop

    清出于兰
  • hive sql系列(总结)

    hive sql系列主打sql,通过案例,从实现到分析,帮助大家找到写sql的快乐

    大数据最后一公里
  • Redis总结

    爱撒谎的男孩
  • Redis系列——4.数据结构

    hello,小宝贝们,又见面啦,赶紧夸我,毕竟更文这么勤快。好了,寒暄结束,开始进入正文。

    陈琛
  • Redis系列——10.字典结构

    大年初五送财神,emmm,希望今年暴富,每年都是这么单纯简单的小愿望,没有一次让我实现的。

    陈琛
  • Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇

    Spring cloud系列教程第十篇- Spring cloud整合Eureka总结篇

    凯哥Java
  • 爬虫系列的总结

    时光荏苒,四个月时间如流沙般从手心中流逝。这四个月自己算是收获颇多。因为在张哥的影响下,自己渐渐喜欢上写作。自己将所学的爬虫知识、学习心得以及如何学习分享出来。...

    猴哥yuri
  • JVM规范系列:总结

    我们花了几天的时间来阅读《Java虚拟机规范》,了解要实现一个虚拟机应该包括什么内容。通过这么一次阅读,我们大致了解了虚拟机规范的内容。

    陈树义
  • vue系列之面试总结

    答:Vue 实例从创建到销毁的过程,就是生命周期。也就是从开始创建、初始化数据、编译模板、挂载 Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 Vue ...

    桃翁

扫码关注云+社区

领取腾讯云代金券