Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis键String

Redis的键String全面详解

作者:言西早

这篇文章主要为大家介绍了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
}

而free有什么用呢?

1.3 String In Action

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

String在点赞系统中的应用

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

数据模型:

以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数据不是更好吗?

Why Not Set or Zset?

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

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

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

String在分布式锁中的应用

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

这个命令是:

加锁

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

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

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

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

总结

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

参考资料

《Redis设计与实现》

《Redis开发与运维》

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

您可能感兴趣的文章:
阅读全文