前言

我们都知道 Redis 提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)

今天我们就来详细的聊聊 Redis 这五大常见的数据类型之一 String

结构类型 结构存储的值 结构读写能力
String 可以是字符串,整数以及浮点数; 对整个字符串或字符串的一部分进行操作;
对整数或者浮点数进行自增或者自减操作;

应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。

概述简介

String 是最基本的 key-value 结构,key 是唯一标识,value 是具体的值,value其实不仅是字符串, 也可以是数字(整数或浮点数),value 最多可以容纳的数据长度是 512M

内部实现

String 类型的底层的数据结构实现主要是 int 和 SDS(简单动态字符串)。

SDS 和我们认识的 C 字符串不太一样,之所以没有使用 C 语言的字符串表示,因为 SDS 相比于 C 的原生字符串:

  • SDS 不仅可以保存文本数据,还可以保存二进制数据。因为 SDS 使用 len 属性的值而不是空字符来判断字符串是否结束,并且 SDS 的所有 API 都会以处理二进制的方式来处理 SDS 存放在 buf[] 数组里的数据。所以 SDS 不光能存放文本数据,而且能保存图片、音频、视频、压缩文件这样的二进制数据。
  • SDS 获取字符串长度的时间复杂度是 O(1) 。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用 len 属性记录了字符串长度,所以复杂度为 O(1)
  • Redis 的 SDS API 是安全的,拼接字符串不会造成缓冲区溢出。因为 SDS 在拼接字符串之前会检查 SDS 空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。

字符串对象的内部编码(encoding)有 3 种 :intrawembstr

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

如果字符串对象保存的是一个字符串,并且这个字符串的长度小于等于 32 字节(redis 2.+ 版本),那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串,并将对象的编码设置为 embstr, embstr 编码是专门用于保存短字符串的一种优化编码方式:

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

注意,embstr 编码和 raw 编码的边界在 redis 不同版本中是不一样的:

  • redis 2.+ 是 32 字节;
  • redis 3.0-4.0 是 39 字节;
  • redis 5.0 是 44 字节;

可以看到 embstrraw 编码都会使用 SDS 来保存值,但不同之处在于 embstr 会通过一次内存分配函数来分配一块连续的内存空间来保存 redisObjectSDS,而 raw 编码会通过调用两次内存分配函数来分别分配两块空间来保存 redisObjectSDS。Redis这样做会有很多好处:

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

但是 embstr 也有缺点的:

  • 如果字符串的长度增加需要重新分配内存时,整个 redisObject 和 sds 都需要重新分配空间,所以 embstr 编码的字符串对象实际上是只读的,redis 没有为 embstr 编码的字符串对象编写任何相应的修改程序。当我们对 embstr 编码的字符串对象执行任何修改命令(例如 append)时,程序会先将对象的编码从 embstr 转换成 raw,然后再执行修改命令。

常用指令

基本操作:

# 设置 key-value 类型的值
127.0.0.1:6379> SET name sid10t
OK# 根据 key 获得对应的 value
127.0.0.1:6379> GET name
"sid10t"# 判断某个 key 是否存在
127.0.0.1:6379> EXISTS name
(integer) 1# 返回 key 所储存的字符串值的长度
127.0.0.1:6379> STRLEN name
(integer) 6# 删除某个 key 对应的值
127.0.0.1:6379> DEL name
(integer) 1

批量操作:

# 批量设置 key-value 类型的值
# mset key value [key value ...]
127.0.0.1:6379> MSET name sid10t age 18
OK# 批量获取多个 key 对应的 value
# mget key [key ...]
127.0.0.1:6379> MGET name age
1) "sid10t"
2) "18"

计数器(字符串的内容为整数的时候可以使用):

# 设置 key-value 类型的值
127.0.0.1:6379> SET cnt 0
OK# 将 key 中储存的数字值 +1
127.0.0.1:6379> INCR cnt
(integer) 1# 将 key 中存储的数字值 +10
# incrby key increment
127.0.0.1:6379> INCRBY cnt 10
(integer) 11# 将 key 中储存的数字值 -1
127.0.0.1:6379> DECR cnt
(integer) 10# 将 key 中存储的数字值 -6
# decrby key decrement
127.0.0.1:6379> DECRBY cnt 6
(integer) 4

过期(默认为永不过期):

# 设置 key 在 30 秒后过期(该方法是针对已经存在的key设置过期时间)
# expire key seconds [NX|XX|GT|LT]
127.0.0.1:6379> EXPIRE name 30
(integer) 1# 查看数据还有多久过期
127.0.0.1:6379> TTL name
(integer) 20# 设置 key-value 类型的值,并设置该 key 的过期时间为 20 秒
# set key value [NX|XX] [GET] [EX seconds|PX milliseconds|EXAT uni
127.0.0.1:6379> SET name sid10t EX 20
OK
# setex key seconds value
127.0.0.1:6379> SETEX name 20 sid10t
OK

不存在就插入:

# 不存在就插入(not exists)
# setnx key value
127.0.0.1:6379> SETNX name sid10t
(integer) 1

应用场景

缓存对象

使用 String 来缓存对象有两种方式:

  • 直接缓存整个对象的 JSON: SET user:1 '{"name":"sid10t", "age":18}'
  • 采用将 key 进行分离为 user:ID:property,采用 MSET 存储,用 MGET 获取各属性值: MSET user:1:name sid10t user:1:age 18 user:2:name sidiot user:2:age 20

常规计数

因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。

比如计算文章的阅读量:

# 初始化文章的阅读量
127.0.0.1:6379[3]> SET atc:rcnt:10086 0
OK#阅读量 +1
127.0.0.1:6379[3]> INCR atc:rcnt:10086
(integer) 1#阅读量 +1
127.0.0.1:6379[3]> INCR atc:rcnt:10086
(integer) 2# 获取对应文章的阅读量
127.0.0.1:6379[3]> GET atc:rcnt:10086
"2"

分布式锁

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

  • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
  • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。

一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:

127.0.0.1:6379[3]> SET lock uuid NX PX 10000
OK
  • lock 就是 key 键;
  • uuid 是客户端生成的唯一的标识;
  • NX 代表只在 lock 不存在时,才对 lock 进行设置操作;
  • PX 10000 表示设置 lock 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。

而解锁的过程就是将 lock 键删除,但不能乱删,要保证执行该操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 uuid 是否为加锁客户端,是的话,才将 lock 键删除。

可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。

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

这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。

共享 Session 信息

通常我们在开发后台管理系统时,会使用 Session 来保存用户的会话(登录)状态,这些 Session 信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。

例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。

分布式系统单独存储 Session 流程图:

因此,我们需要借助 Redis 对这些 Session 信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个 Redis 获取相关的 Session 信息,这样就解决了分布式系统下 Session 存储的问题。

分布式系统使用同一个 Redis 存储 Session 流程图:

五大常见的数据类型之 String相关推荐

  1. Python常见的数据类型

    常见的几个数据类型 1. 数据类型并不神秘 人类世界对万事万物都有种类划分,例如: 哺乳动物 人.猫.马.鸭嘴兽....等等 蔬菜 西红柿.波菜.茄子....等等 水果 ​ 西瓜.桃子.苹果....等 ...

  2. Python常见的数据类型【数字、布尔、字符串、列表和元组、字典】

    变量用来存储数据,那么大家有没有想过,我们应该让变量占用多大空间,保存什么样的数据呢?在讲解变量的类型之前,我们先来看一个生活中的例子,例如:我们要运送一台电脑,大卡车和小轿车都可以完成,但是,如果使 ...

  3. redis基本数据类型之String

    redis基本数据类型之String redis一共分为5中基本数据类型:String,Hash,List,Set,ZSet String String类型是包含很多种类型的特殊类型,并且是二进制安全 ...

  4. 八、一篇文章快速搞懂MySQL 常见的数据类型(整型、小数、字符型、日期型详解)

    常见的数据类型 1.数值型: 整型 小数: 定点数 浮点数 2.字符型: 较短的文本:char.varchar 较长的文本:text.blob(较长的二进制数据) 3.日期型: 一.整型 1)分类: ...

  5. c语言分治法求众数重数_五大常见算法策略之——递归与分治策略

    递归与分治策略 递归与分治策略是五大常见算法策略之一,分治策略的思想就是 分而治之 ,即先将一个规模较大的大问题分解成若干个规模较小的小问题,再对这些小问题进行解决,得到的解,在将其组合起来得到最终的 ...

  6. Hive中Map数据类型转String类型,其中具体内容不变

    --上传测试数据 drop table test_map_1; create table test_map_1 as select 1 as uid, map("key1", &q ...

  7. 分治法一个整数数列求最大值最小值_五大常见算法策略之丨递归与分治策略

    递归与分治策略 递归与分治策略是五大常见算法策略之一,分治策略的思想就是分而治之,即先将一个规模较大的大问题分解成若干个规模较小的小问题,再对这些小问题进行解决,得到的解,在将其组合起来得到最终的解. ...

  8. R语言常见的数据类型及转换

    1.常见的数据类型 2.特殊的数据类型 NULL:空数据 NA:表示无数据 NaN:表示非数字 inf:数字除以0得到的值 以上分别用is.null().is.na().is.nan().is.inf ...

  9. 数据表中常见的数据类型

    数据表中常见的数据类型有:整数类型.浮点数类型.日期与时间类型.字符串类型.二进制类型.布尔类型. 整数类型: 1int型:表示整型数值,是由四个字节组成的整数,输出范围(-2147~2147),数据 ...

最新文章

  1. 2021年大数据Spark(二十二):内核原理
  2. android 时间显示格式,Android setting中修改时间显示格式后,桌面的数字时钟widget小部件显示不更新...
  3. 刷题练习记录(3)——无重复字符的最长子串(JAVA 和 Python)——set()函数/集合...
  4. Java数据结构和算法(六)——前缀、中缀、后缀表达式
  5. 无锡易保Java面试笔试_易保面试题 - willim - BlogJava
  6. 规范的.net 事件原理
  7. 解题:POI 2004 String
  8. eclipse查看jar包中class的中文注释乱码问题的解决
  9. [Yii Framework] 数据库查询
  10. 11. sql DDL
  11. 中国最神秘的一所大学,它只存在过8年,却成了永远的第一
  12. CentOS 7.0系统安装配置LAMP服务器(Apache+PHP+MariaDB)
  13. doxygen+graphviz+doxygen-wizard yum install on linux
  14. Go 开发关键技术指南 | 带着服务器编程金刚经走进 2020 年(内含超全知识大图)...
  15. uc手机浏览器 手机模拟_在PC上测试移动端网站和模拟手机浏览器的5大方法
  16. android 9.0 开机动画,Android bootanim开机动画启动流程
  17. BottledWater-PG安装部署
  18. 记住键盘快捷键大全 提高电脑操作速度
  19. 究竟什么时候需要用RTOS?
  20. matlab控制理论学习

热门文章

  1. 名帖53 隋代 小楷《董美人墓志》
  2. 谷粒商城项目之高级篇笔记(一)
  3. CADEditorX新控件_可进君羊交流与学习
  4. 凹凸世界服务器维护到几点,凹凸世界6月10日版本更新停服维护公告_凹凸世界6月10日版本更新了什么_玩游戏网...
  5. 3D game第二次作业
  6. Delete语句优化
  7. python根据字节长度截取字符串_python 字节流 按长度截取
  8. 【蓝桥杯嵌入式】应赛技巧①多屏切换
  9. (附源码)SSM疫苗管理系统JAVA计算机毕业设计项目
  10. 【.Net实用方法总结】 整理并总结System.IO中StreamReader类及其方法介绍