文章导航-readme

前言

    上一篇文章Redis之对象篇——Redis对象系统简介简单介绍了Redis的对象系统。Redis使用对象来表示数据库中的键和值每个对象都由一个redisObject结构表示,该结构中和保存数据有关的三个属性分别是type属性、 encoding属性和ptr属性。

typedef struct redisObiect{
//类型
unsigned type:4;
//编码
unsigned encoding:4;
//指向底层数据结构的指针
void *ptr;
}

    字符串对象是 Redis 中最基本的数据类型,也是我们工作中最常用的数据类型。redis中的键都是字符串对象,而且其他几种数据结构都是在字符串对象基础上构建的。字符串对象的值实际可以是字符串、数字、甚至是二进制,最大不能超过512MB 。

一、内部实现

    Redis字符串对象底层的数据结构实现主要是int和简单动态字符串SDS(这个字符串,和我们认识的C字符串不太一样,了解具体请看图解Redis之数据结构篇——简单动态字符串SDS),其通过不同的编码方式映射到不同的数据结构。

字符串对象的内部编码有3种 :intrawembstr。Redis会根据当前值的类型和长度来决定使用哪种编码来实现。

  1. 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串对象结构的ptr属性里面(将void*转换成1ong),并将字符串对象的编码设置为int

  2. 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为raw

  3. 如果字符串对象保存的是一个字符串值,并且这个字符申值的长度小于等于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并将对象的编码设置为embstr

    embstr编码是专门用于保存短字符串的一种优化编码方式,我们可以看到embstrraw编码都会使用SDS来保存值,但不同之处在于embstr会通过一次内存分配函数来分配一块连续的内存空间来保存redisObjectSDS。而raw编码会通过调用两次内存分配函数来分别分配两块空间来保存redisObjectSDS。Redis这样做会有很多好处。

  • embstr编码将创建字符串对象所需的内存分配次数从raw编码的两次降低为一次
  • 释放 embstr编码的字符串对象同样只需要调用一次内存释放函数
  • 因为embstr编码的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用CPU缓存提升性能。

    Redis中根据数据类型和长度来使用不同的编码和数据结构存储存在于Redis中的每一种对象类型上。其这种小细节上的优化令我叹服不止,后续我们会看到Redis中到处都是这种内存与性能上的小细节优化!

二、常用命令

    字符串类型的命令比较多 ,我们先来了解几个日常开发中常用的。

1 设置值

redis> set testKey testValue
OK
set key value [ex seconds] [px milliseconds] [nx|xx]
  • ex seconds:为键设置秒级过期时间。

    如命令:set username xiaoming ex 100相当于执行下面两条命令

    SET username xiaoming
    EXPIRE username 100

    set key value [ex seconds]操作是原子性的,相比连续执行上面两个命令,它更快。

  • px milliseconds:为键设置毫秒级过期时间。

  • nx:键必须不存在,才可以设置成功,用于添加。

    //mykey 不存在
    redis> set mykey "Hello" nx
    (integer) 1
    //mykey 已经存在
    redis> set mykey "World" nx
    (integer) 0
    redis> GET mykey
    "Hello"
    redis>

    由于set key value nx同样是原子性的操作,因此可以作为分布式锁的一种实现方案。

  • xx:与nx相反,键必须存在,才可以设置成功,用于更新

以上几个命令的替代命令是SETNX, SETEX,PSETEX,但是由于SET命令加上选项已经可以完全取代SETNX, SETEX,PSETEX的功能,所以在将来的版本中,redis可能会不推荐使用并且最终抛弃这几个命令。

2 获取值

get key

返回keyvalue。如果key不存在,返回特殊值nil。如果keyvalue不是string,就返回错误,因为GET只处理string类型的values

redis> GET nokey
(nil)
redis> SET mykey "Hello World"
OK
redis> GET mykey
"Hello World"

3 批量设置值

    由于Redis目前的应用非常广泛,目前大多数公司对Redis的调用基本都会有一层自己的封装,看起来就像是在调用本地缓存一样,对于批量性的操作,一些对于Redis不太了解的可能就像使用本地缓存一样进行循环set。这样对性能是有很大的损耗的。实际上Redis提供了批量操作的命令。

MSET key value [key value ...]

对应给定的keys到他们相应的values上。MSET会用新的value替换已经存在的value,就像普通的SET命令一样。如果不想覆盖已经存在的values,可以使用MSETNX key value [key value ...]

注意:MSET是原子的,所以所有给定的keys是一次性set的。客户端不可能看到这种一部分keys被更新而另外的没有改变的情况。

redis> MSET key1 "Hello" key2 "World"
OK
redis> GET key1
"Hello"
redis> GET key2
"World"

4 批量获取值

MGET key [key ...]

结果是按照传入键的顺序返回所有指定的key的value。对于每个不对应string或者不存在的key,都返回特殊值nil。

redis> SET key1 "Hello"
OK
redis> SET key2 "World"
OK
redis> MGET key1 key2 nokey
1) "Hello"
2) "World"
3) (nil)

    Redis可以支撑每秒数万的读写操作,但是这指的是Redis服务端的处理能力,对于客户端来说,一次命令除了命令时间还是有网络时间,如n次get操作

使用get命令

n次get时间 = n次网络时间 + n次命令时间

mget操作

n次get时间 = 1次网络时间 + n次命令时间

而在实际开发中因为Redis的处理能力已经足够高,性能瓶颈的因素往往是网络。

学会使用批量操作,有助于提高效率,但是要掌握一个平衡的度,每次批量操作所发送的命令数并不是无节制的由于Redis是单线程架构,如果数量过多可能造成Redis阻塞或者网络拥塞。

5 计数

incr key

对存储在指定key的数值执行原子的加1操作。

如果指定的key不存在,那么在执行incr操作之前,会先将它的值设定为0

如果指定的key中存储的值不是字符串类型(fix:)或者存储的字符串类型不能表示为一个整数,

那么执行这个命令时服务器会返回一个错误(eq:(error) ERR value is not an integer or out of range)。

这个操作仅限于64位的有符号整型数据。

注意: 由于redis并没有一个明确的类型来表示整型数据,所以这个操作是一个字符串操作。

执行这个操作的时候,key对应存储的字符串被解析为10进制的64位有符号整型数据

事实上,Redis 内部采用整数形式(Integer representation)来存储对应的整数值,所以对该类字符串值实际上是用整数保存,也就不存在存储整数的字符串表示(String representation)所带来的额外消耗。

redis> SET mykey "1"
OK
redis> INCR mykey
(integer) 2
redis> GET mykey
"3"
redis>

除了incr命令, Redis提供了decr(自减)incrby(自增指定数字)decrby(自减指定数字)incrbyfloat(自增浮点数)

decr key
incrby key increment
decrby key decrement
incrbyfloat key increment

6 其它

    对于常用的redis字符串命令和一些其它的命令我们列一个表格以便来更直观的看到。

命令 描述 时间复杂度
set key value [ex seconds] [px milliseconds] [nx|xx] 设置值 O(1)
get key 获取值 O(1)
del key [key ...] 删除key O(N)(N是键的个数)
mset key [key value ...] 批量设置值 O(N)(N是键的个数)
mget key [key ...] 批量获取值 O(N)(N是键的个数)
incr key 将 key 中储存的数字值增一 O(1)
decr key 将 key 中储存的数字值减一 O(1)
incrby key increment 将 key 所储存的值加上给定的增量值(increment) O(1)
decrby key increment key 所储存的值减去给定的减量值(decrement) O(1)
incrbyfloat key increment 将 key 所储存的值加上给定的浮点增量值(increment) O(1)
append key value 如果 key 已经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的末尾 O(1)
strlen key 返回 key 所储存的字符串值的长度。 O(1)
setrange key offset value 用 value 参数覆写给定 key 所储存的字符串值,从偏移量 offset 开始 O(1)
getrange key start end 返回 key 中字符串值的子字符 O(N)(N是字符串的长度)

三、常用场景

    reids字符串的使用场景应该是最为广泛的,甚至有些对redis其它几种对象不太熟悉的人,基本所有场景都会使用字符串(序列化一下直接扔进去)。在众多的使用场景中总结一下大概分以下几种。

1. 作为缓存层

    如上图,Redis经常作为缓存层,来缓存一些热点数据。来加速读写性能从而降低后端的压力。一般在读取数据的时候会先从Redis中读取,如果Redis中没有,再从数据库中读取。在Redis作为缓存层使用的时候,必须注意一些问题,如:缓存穿透、雪崩以及缓存更新问题......

2. 计数器\限速器\分布式系统ID

    计数器\限速器\分布式ID等主要是利用Redis字符串自增自减的特性。

  • 计数器:经常可以被用来做计数器,如微博的评论数、点赞数、分享数,抖音作品的收藏数,京东商品的销售量、评价数等。
  • 限速器:如验证码接口访问频率限制,用户登陆时需要让用户输入手机验证码,从而确定是否是用户本人,但是为了短信接口不被频繁访问,会限制用户每分钟获取验证码的频率,例如一分钟不能超过5次。
  • 分布式ID:由于Redis自增自减的操作是原子性的因此也经常在分布式系统中用来生成唯一的订单号、序列号等。

3. 分布式系统共享session

    通常在单体系统中,Web服务将会用户的Session信息(例如用户登录信息)保存在自己的服务器中。但是在分布式系统中,这样做会有问题。因为分布式系统通常有很多个服务,每个服务又会同时部署在多台机器上,通过负载均衡机制将将用户的访问均衡到不同服务器上。这个时候用户的请求可能分发到不同的服务器上,从而导致用户登录保存Session是在一台服务器上,而读取Session是在另一台服务器上因此会读不到Session。

    这种问题通常的做法是把Session存到一个公共的地方,让每个Web服务,都去这个公共的地方存取Session。而Redis就可以是这个公共的地方。(数据库、memecache等都可以各有优缺点)。

4. 二进制存储

    由于Redis字符串可以存储二进制数据的特性,因此也可以用来存储一些二进制数据。如图片、 音频、 视频等。

参考

《Redis设计与实现》

《Redis开发与运维》

《Redis官方文档》

-----END-----

Redis对象——字符串的更多相关文章

  1. redis数据类型-字符串类型

    Redis数据类型 字符串类型 字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据.你可以用其存储用户的邮箱.JSON化的对象甚至是一张图片.一个字符串类型键允许存储的 ...

  2. 【redis源码阅读】redis对象

    结构定义 在redis中,对象的数据结构定义如下: ​typedef struct redisObject { ​unsigned type:4; ​unsgined encoding:4; ​uns ...

  3. 第二百九十五节,python操作redis缓存-字符串类型

    python操作redis缓存-字符串类型 首先要安装redis-py模块 python连接redis方式,有两种连接方式,一种是直接连接,一张是通过连接池连接 注意:以后我们都用的连接池方式连接,直 ...

  4. Redis对象类型

    Redis对象类型 Redis基于基础的数据结构创建的对象: 字符串对象. 列表对象. 哈希对象. 集合对象 有序集合对象. 对象回收:Redis对象系统实现了基于引用计数技术的内存回收机制,当程序不 ...

  5. Redis 数据结构-字符串源码分析

    相关文章 Redis 初探-安装与使用 Redis常用指令 本文将从以下几个部分进行介绍 1.前言 2.常用命令 3.字符串结构 4.字符串实现 5.命令是如果操作字符串的 前言 平时在使用 Redi ...

  6. Redis底层探秘(五):Redis对象

    前面几篇文章,我们一起学习了redis用到的所有主要数据结构,比如简单动态字符串(sds).双端链表.字典.压缩列表.整数集合等等. redis并没有直接使用这些数据结构来实现键值对数据库,而是基于这 ...

  7. Redis对象的设计与实现

    一.Redis对象结构Redis中的每个对象都由一个redisObject结构表示: typedef struct redisObject { unsigned type;//类型 unsigned ...

  8. redis之字符串命令源代码解析(二)

    形象化设计模式实战             HELLO!架构                     redis命令源代码解析 在redis之字符串命令源代码解析(一)中讲了get的简单实现,并没有对 ...

  9. 面试官:你了解过Redis对象底层实现吗

    上一章我们讲了Redis的底层数据结构,不了解的人可能会有疑问:这个和平时用的五大对象有啥关系呢?这一章我们就主要解释他们所建立的联系. 看这个文件之前,如果对ziplist.skiplist.int ...

随机推荐

  1. SpringBoot发送邮箱验证码

    一.开启QQ邮箱服务 (1)登陆QQ找到设置,点击账户 (2)往下拉,开启POP3/SMTP服务和IMAP/SMTP服务 当开启IMAP/SMTP会有一串密文密码,保存起来后面要用到 二.spring ...

  2. 修改,编译,GDB调试openjdk8源码(docker环境下)

    在上一章<在docker上编译openjdk8>里,我们在docker容器内成功编译了openjdk8的源码,有没有读者朋友产生过这个念头:"能不能修改openjdk源码,构建一 ...

  3. spring组件注册

    基于注解和类的组件注册 @Conditional 作用:按照一定的条件进行判断,如果满足条件的话就给spring容器中注册bean ​ 该注解既可以标注到方法上面,也可以标注到类上面(只有满足条件时, ...

  4. Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?

    结果: 12,-11 原因: Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11.四舍五入的原理是在参数上加0.5然后进行下取整.

  5. Kali Linux 安装open-vm-tools

    Kali Linux是基于Debian的Linux发行版,集成了精心挑选的渗透测试和安全审计的工具,供渗透测试和安全设计人员使用.(以及一些各种颜色的hacker  ^-^) 首先需要安装好虚拟机(V ...

  6. SSM整合activiti框架

    一:WorkFlow简介 1:什么是工作流工作流(Workflow),指“业务过程的部分或整体在计算机应用环境下的自动化”.是对工作流程及其各操作步骤之间业务规则的抽象.概括描述.在计算机中,工作流属 ...

  7. Jackson 序列化和反序列化

    博客地址:https://www.moonxy.com 一.前言 Jackson 功能很强大,既能满足简单的序列化和反序列化操作,也能实现复杂的.个性化的序列化和反序列化操作.到目前为止,Jackso ...

  8. 关于SP优化

    SET STATISTICS PROFILE ON;SET STATISTICS TIME ON;SET STATISTICS IO ON;--打开三个开关SET STATISTICS PROFILE ...

  9. Beescms_v4.0 sql注入漏洞分析

    Beescms_v4.0 sql注入漏洞分析 一.漏洞描述 Beescms v4.0由于后台登录验证码设计缺陷以及代码防护缺陷导致存在bypass全局防护的SQL注入. 二.漏洞环境搭建 1.官方下载 ...

  10. Java匹马行天下之Java帝国的崛起(大结局)

    Java匹马行天下之Java帝国的崛起大结局 前言: [博客*缘] 网络真情伴, 博客友谊连. 笑中藏泪暖中寒. 回想那些悲喜, 苦涩也缠绵. 往事难回首, 新篇染旧言. 世间多少梦能全. 感谢相牵, ...