返回顶部
首页 > 资讯 > 数据库 >Redis的键String全面详解
  • 883
分享到

Redis的键String全面详解

Redis键StringRedisString 2023-06-07 10:06:05 883人浏览 薄情痞子
摘要

目录String开篇1 字符串键1.1 C语言的字符串实现1.2 Redis的利器,SDS1.3 String In ActionString在分布式锁中的应用总结String开篇 在介绍之前,笔者想介绍一下Redis的

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的资料请关注我们其它相关文章!

您可能感兴趣的文档:

--结束END--

本文标题: Redis的键String全面详解

本文链接: https://lsjlt.com/news/249333.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Redis的键String全面详解
    目录String开篇1 字符串键1.1 C语言的字符串实现1.2 Redis的利器,SDS1.3 String In ActionString在分布式锁中的应用总结String开篇 在介绍之前,笔者想介绍一下Redis的...
    99+
    2023-06-07
    Redis键String Redis String
  • Redis02 使用Redis数据库(String类型)全面解析
    一 String类型 首先使用启动服务器进程 : redis-server.exe 1. Set   设置Key对应的值为String 类型的value。   例子:向 Redis数据库中插入一条数...
    99+
    2022-06-04
    类型 数据库 Redis
  • Redis安全策略详解
    目录缓存穿透缓存击穿缓存雪崩布隆过滤器基于布隆过滤器解决缓存穿透问题缓存穿透 高并发情况下查询一个不存在的key 产生的背景(原因): 缓存穿透是指使用不存在的key进行大量的高并发查询,导致缓存无法命中,每次请求都要都...
    99+
    2022-07-27
    Redis安全 Redis安全策略
  • Redis数据类型string和Hash详解
    目录String类型命令操作设置指定key的值获取指定key的值返回key中字符串值的子串获取多个给定key的值返回key所对应的字符串的长度设置一个或多个键值对将key中所存储的数...
    99+
    2024-04-02
  • 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....
    99+
    2023-08-16
    servlet java mvc
  • Redis数据库安全详解
    目录前言开放最小化很重要认证不可少防止管理员误操作也很重要担心网络被监听 开启TLS之旅吧总结前言 本篇文章基于的Redis 的环境为: redis_version:7.0.5 文档内容均为学习Redis 官方文档心得....
    99+
    2024-04-02
  • Java全面解析string类型的xml字符串
    目录解析string类型的xml字符串所需要的包自行导入解析String类型t复杂xml,多级节点,最好的例子字符串xml如下解析代码解析string类型的xml字符串 我先拼接一个...
    99+
    2024-04-02
  • Android中Java instanceof关键字全面解析
    instanceof关键字用于判断一个引用类型变量所指向的对象是否是一个类(或接口、抽象类、父类)的实例。 instanceof是Java的一个二元操作符,和==,>,&...
    99+
    2022-06-06
    JAVA instanceof Android
  • 全面了解C语言 static 关键字
    目录一,前言二、认识多文件1、多文件的创建2、为什么要有多文件3、为什么要有头文件4、多文件在代码中的具体体现三、最名不符实的关键字 - static1、static 修饰局部变量2...
    99+
    2024-04-02
  • MyBatis 动态SQL全面详解
    目录前言动态sql1.先看一下模块目录结构 2.物理建模和逻辑建模 3. 引入依赖 4.全局配置文件5.sql共性抽取文件 6.mapper接口 if静态sql:动态sql:wher...
    99+
    2024-04-02
  • JWT Json Web Token全面详解
    目录概述应用场景JWT认证过程JWT的数据结构headerpayloadsignatureJWT的用法JWT的优缺点概述 最近学习了一下JWT,全名为Json Web Token,是...
    99+
    2022-11-13
    JWT Json Web Token Json Web Token
  • React props全面详细解析
    目录一、Props 是什么二、props children模式1. props 插槽组件2. render props模式3. render props模式三、进阶实践一、Props...
    99+
    2022-11-13
    React props React props的使用
  • MySQL:子查询(全面详解)
    MySQL:子查询 前言一、需求分析与问题解决1、实际问题2、子查询的基本使用3、子查询的分类 二、单行子查询1、单行比较操作符2、代码示例3、HAVING 中的子查询4、CASE中的子查询5、子查询中的空值问题6、非法...
    99+
    2023-08-16
    mysql 数据库
  • 【Linux】VIM命令(全面详解)
    VI和VIM命令详解 一.VI和VIM是什么?二.VI和VIM使用和区别?1.使用2.区别 三.VIM的三种格式1.普通模式2.编辑模式(插入模式)3.指令模式(命令模式) 四.VI/VIM键盘图 一.VI和VIM是什么...
    99+
    2023-08-23
    vim linux 编辑器 vi 服务器
  • 详解Redis 键和字符串常用命令
    目录Redis 相关知识Redis中的数据类型redis 键(key)Redis字符串(String)常用命令String的数据结构Redis 相关知识 Redis的默认端口号为63...
    99+
    2024-04-02
  • Redis实现延迟队列的全流程详解
    目录1、前言1.1、什么是延迟队列1.2、应用场景1.3、为什么要使用延迟队列2、Redis sorted set3、Redis 过期键监听回调4、Quartz定时任务5、Delay...
    99+
    2023-03-14
    Redis延迟队列实现 Redis延迟队列原理
  • Spring-全面详解(学习总结)
    目录一、Spring介绍简介特点、主要特点为什么要学?二、IOC(依赖注入)Spring 容器IOC 和 bean介绍控制反转:容器概述:bean介绍AOP总结一、Spring介绍 ...
    99+
    2024-04-02
  • 详细全面解析Java泛型
    1.概述 作为一个面向对象的编程语言,Java可以通过实现一些类,作为我们各种需求的一个模板,方便我们的使用。但有时候,这个类的范围可能比我们想要的范围要大,我们只想限定于满足类的某...
    99+
    2024-04-02
  • MySQL:单行函数(全面详解)
    MySQL:单行函数 前言一、函数的理解1、什么是函数2、不同DBMS函数的差异3、MySQL的内置函数及分类 二、数值函数1、基本函数2、角度与弧度互换函数3、三角函数4、指数与对数5、...
    99+
    2023-10-09
    mysql 数据库 sql
  • webpack自定义loader全面详解
    目录什么是loaderloader类型如何指定loader类型如何禁用一些loader?开发自定义两个loader,并分别实现url-loader和file-loaderfile-l...
    99+
    2023-01-04
    webpack自定义loader webpack loader
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作