我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Redis的键String全面详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Redis的键String全面详解

String开篇

在介绍之前,笔者想介绍一下Redis的设计精髓,也就是其单线程设计,对于一个宣称能抗住十万qps的数据库,其单线程的设计让人不可思议,但redis开创的单线程设计哲学是至今其仍是KV型数据库一方霸主的重要原因,而其骄傲的资本是其立足于内存,而又不止步于内存(持久化、压缩、淘汰算法)的诸多设计。

之所以想介绍它的单线程设计,正是因为Redis的很多数据结构,都是为了阻击单线程架构的宿敌——阻塞而产生的,这也会是系列文章的一个主要脉络

Redis是KV型的数据库,其数据结构代表着对应的键,Redis的键有五大类型,分别是String、List、Hash、Set、Zset,另外还有三种不常用的类型 HyperlogLog、BitMap、Geo,本文着重介绍前五种

1 字符串键

1.1 C语言的字符串实现

字符串键的使用和实现都比较简单

Redis的底层实现是C语言,但是C语言的字符串具有种种弊端,对于Redis来说,最不可接受的有如下几点:

  • 二进制不安全

C语言中用于识别字符串的函数会自动对”\0”(也就是空字符字符作出截断,这对于二进制文本来说是极不安全的,如果有如“redis\0is\0good”的文本,那么C语言就只能识别到”redis”,这从根本上断绝了用它来传输二进制文本的可能

  • 字符串长度获取复杂度太高

C语言中并未维护获取字符串长度的变量,每次获取它的长度,都必须从头到尾遍历字符串一次,才能够获取它的长度,当这个字符串长度太长时,Redis的单线程会不可避免地陷入阻塞,这是Redis地设计者不希望看到的

  •  缓冲区溢出问题

C语言中的字符串拼接默认地假定在该字符串的后面有足够的内存以放下后来的字符串,一旦这个假定成立,就会发生缓冲区溢出,C语言处理此类问题的方式也是相当粗暴,一旦溢出发生,后面”无辜”的数据内存就会被覆盖掉,这对于数据库来说是无法容忍的。

1.2 Redis的利器,SDS

SDS是Redis自己的字符串实现,其对于以上三个问题都给出了很好的解决

我们可以通过如下代码发现,SDS实现的字符串具有更好的封装性,显得更面向对象了

struct sdshdr{
        int len;//记录字符串长度
        int free;//记录可用空间的长度
        char buf[];//保存每一个字符
//APIs
}
  • 通过len,我们得以实现常数复杂度获取长度
  • char数组保存空间,以实现二进制安全

而free有什么用呢?

  • 和C语言中字符串的缓冲区大小完全听天由命不同,每一个SDS被创建、修改时,都会有一个等同于自身大小的缓冲区(这个空间最大为1M)
  • 有了这个缓冲区,Redis得以避免频繁的空间重分配,因为迁移字符串是一个极其消耗性能的操作,它必须找到一块新空间,并把原来的字符串一个个搬运过去,这也会增加Redis主线程阻塞的几率
  • 这种做法体现了一种在空间和时间上的权衡,拿空间换时间,其带来的好处就是Redis空间重分配的时间大大减少

1.3 String In Action

接下来笔者想就String在技术层面和业务层面的作用来讲讲String的妙用

String在点赞系统中的应用

点赞系统社区功能里最常见核心的功能之一,其承载的巨大数据量又是一般的业务所不具有的,而String在此系统的设计中扮演了较为重要的作用,这里借用Bilibili的点赞架构来讲述String在计数功能中发挥何种作用(为了方便理解有做改动,思想不变)

  • 核心需求:点赞数目、最近点赞列表
  • 技术选型:Redis+mysql,更新方式采用CacheAside

数据模型:

  • Redis中:

以likeCount:userId存储某个稿件点赞的数目

key-value = likeCount:{userId}- {likes},{disLikes}
//用业务ID和该业务下的实体ID作为缓存的Key,并将点赞数与点踩数拼接起来存储以及更新

以Zset存储最近的点赞,但是此集合不能无限膨胀,需要剪裁,当需要更多信息时,返回DB以查询更多

key-value = user:likes:{entityId} - member(messageID)-score(likeTimestamp)

* 用userId作为key,value则是一个ZSet,member为被点赞的实体ID,score为点赞的时间。

当改业务下某用户有新的点赞操作的时候,被点赞的实体则会通过 zadd的方式把最新的点赞\

记录加入到该ZSet里面来

* 为了维持用户点赞列表的长度(不至于无限扩张),需要在每一次加入新的点赞记录的时候,

按照固定长度裁剪用户的点赞记录缓存。该设计也就代表用户的点赞记录在缓存中是有限度

长度的,超过该长度的数据请求需要回源DB查询MySQL中:

有人会问了,Redis的效率极高,还支持持久化,为何我不采用set或者Zset以存在Redis里?这对于热点的like数据不是更好吗?

  • 点赞记录表 - likes : 每一次的点赞记录(用户userId、被点赞的实体ID(entityId)、点赞来源、时间)等信息,并且在userId、entityId两个维度上建立了满足业务求的联合索引。
  • 点赞数表 - counts :以实体ID(entityID)为主键,聚合了该实体的点赞数、点踩数等信息。并且按照entityID维度建立满足业务查询的索引。

Why Not Set or Zset?

  • 首先说说Set实现like有什么问题,首当其冲的是用无序的set存几乎没有任何拓展性,比较经典的实现是,在以like:{entityId}的set键里存储userId,在调用它isMember以查看是否点赞过,以及其大小时,可以获得风驰电掣的速度,但在面对诸如点赞列表、分析用户在时间尺度上的点赞行为等业务需求上,set显得无能为力

而对于Zset,似乎是实现此结构的天然首选

Member-存entityId
Score-存时间戳
点赞列表-求zset的最后n项即可

其排序和查找特性似乎是为了上述的需求量身定制,然而Zset的问题比set还要糟糕,set仅仅是业务拓展能力不足,Zset作为点赞的容器极有可能引起redis主线程的阻塞:

  • Zset对于isMember的查询是O(N)的,也就是说无异于去遍历整个链表,这对于业务量很可能在十几万甚至上百万的点赞上来说,是不可接受的开销
  • 还是数据量的问题,对于每个实体来说都要存上十万个实体id与它们的时间戳,这样的实体可能还有上万个,这对Redis是无法承受之重,毕竟Redis也不是专门服务你点赞业务的(这个问题在Set中同样存在)

String在分布式锁中的应用

SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:

这个命令是:

加锁

SET {加锁的键} {客户端标识} NX PX {持有锁的最大时间}’

  • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功
  • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败
  • 客户端标识用于表示此锁的拥有权
  • PX 10000表示此锁在10000秒内失效,防止发生异常产生无法释放锁的情况

释放锁的过程就是删除此键,让我们回想一下CAS思路下的替换操作

这显然是两步操作,需要用Lua脚本来进行原子化,具体的逻辑如下

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
  • 首先是确认此锁属于自己(客户端id、是否是原值,等等形式)
  • 若是属于自己,再对其进行改动,如释放、释放后通知在等待锁的线程(此例子中是删除此键)
  • 其他的应用
  • 共享Session、jsON对象、单点过滤,此处不再一一赘述。

总结

作为KV型数据库中最简单的数据结构,SDS的功用仅仅是为Redis的强大开了个头,但即便是如此简单的结构,我们仍能在其中看见许多其为了避免Redis陷入阻塞噩梦的巧思,在接下来的介绍中我们能看见更多的Redis的精妙设计与实现

参考资料

《Redis设计与实现》

《Redis开发与运维》

以上就是Redis的键String全面详解的详细内容,更多关于Redis键String的资料请关注我们其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Redis的键String全面详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Redis的键String全面详解

目录String开篇1 字符串键1.1 C语言的字符串实现1.2 Redis的利器,SDS1.3 String In ActionString在分布式锁中的应用总结String开篇在介绍之前,笔者想介绍一下Redis的设计精髓,也就是其单
2023-06-07

Redis02 使用Redis数据库(String类型)全面解析

一 String类型首先使用启动服务器进程 :redis-server.exe 1. Set设置Key对应的值为String 类型的value。例子:向 Redis数据库中插入一条数据类型为String 的记录。在客户端输入命令:C:sof
2022-06-04

Redis安全策略详解

目录缓存穿透缓存击穿缓存雪崩布隆过滤器基于布隆过滤器解决缓存穿透问题缓存穿透高并发情况下查询一个不存在的key产生的背景(原因):缓存穿透是指使用不存在的key进行大量的高并发查询,导致缓存无法命中,每次请求都要都要穿透到后端数据库查
2022-07-27

SpringMVC详解(超全面)

目录 一、SpringMvc入门1、回顾MVC模式1.1 概念1.2 优缺点1.2.1 优点1.2.2 缺点 2、SpringMVC概念1、概念2、优点 3、第一个Spring MVC程序3.1 使用步骤3.1.
2023-08-16

React props全面详细解析

props 是 React 组件通信最重要的手段,它在 React 的世界中充当的角色是十分重要的。学好 props 可以使组件间通信更加灵活,同时文中会介绍一些 props 的操作技巧,和学会如何编写嵌套组件
2022-11-13

Android中Java instanceof关键字全面解析

instanceof关键字用于判断一个引用类型变量所指向的对象是否是一个类(或接口、抽象类、父类)的实例。 instanceof是Java的一个二元操作符,和==,>,<是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的
2022-06-06

MySQL:子查询(全面详解)

MySQL:子查询 前言一、需求分析与问题解决1、实际问题2、子查询的基本使用3、子查询的分类 二、单行子查询1、单行比较操作符2、代码示例3、HAVING 中的子查询4、CASE中的子查询5、子查询中的空值问题6、非法使用子查
2023-08-16

【Linux】VIM命令(全面详解)

VI和VIM命令详解 一.VI和VIM是什么?二.VI和VIM使用和区别?1.使用2.区别 三.VIM的三种格式1.普通模式2.编辑模式(插入模式)3.指令模式(命令模式) 四.VI/VIM键盘图 一.VI和VIM是什么
2023-08-23

JWT Json Web Token全面详解

这篇文章主要为大家介绍了JWT Json Web Token全面详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
2022-11-13

Redis实现延迟队列的全流程详解

Redisson是Redis服务器上的分布式可伸缩Java数据结构,这篇文中主要为大家介绍了Redisson实现的优雅的延迟队列的方法,需要的可以参考一下
2023-03-14

Redis中的连接命令与键命令操作详解

目录一、连接命令pingecho mselect iauth pwordquit二、键命令set key valueget keydel key1 [key2 … keyn]exists keytype keyexpire k
Redis中的连接命令与键命令操作详解
2024-09-24

Spring全面详解(学习总结)

Spring FrameWork一、 前言二、IOC(控制反转)2.1 对于IOC的理解2.2如何使用IOC2.3配置文件的解读2.4IOC容器创建bean的两种方式2.5从IOC容器中取bean2.6bean的属性如
2023-08-16

编程热搜

目录