redis的数据类型:string
文章目录
- String类型介绍
- redis采用的字符集
- json类型介绍
- String类型的命令
- set key value + [EX seconds] + [NX|XX]
- incr key
- incr对操作的key对应的value类型有限制吗?
- `incr key`操作的返回值是什么?
- incr操作的key可以不存在吗?
- 多个客户端同时针对同一个key进行incr操作时,存不存在“线程安全”问题?
- incrby key n
- incrby操作的key如果不存在,结果会怎样?
- incrby key n中n可以是负数吗?
- incrby操作的key对应的value类型可以不是整数吗?
- decr key
- decryby key n
- incrbyfloat key float
- append key + Str
- 如何拼接两个string类型的字符串?
- append key + Str 中,key可以不存在吗?
- `append key3 你好` 其中key3在语句执行时不存在,请问redis执行完这条命令之后返回的结果是什么?
- getrange key [start] [end]
- 如何从一个字符串中切分出一个子串呢?
- Redis中start和end指定的区间,是左开右闭区间吗?
- start和end可以传负数吗?
- getrange key start end中,key对应是value可以是string类型的汉字吗?
- SETRANGE key [offset] [value]
- 我想把一个字符串从下标6开始往后,替换成我给的字符串str,我该怎么做?
- offset指定的string下标从0开始还是从1开始?
- setrange操作的key对应的value和替换用的value可以是中文字符串吗?
- setrange操作的key可以不存在吗?
- strlen key
- 如何获得key对应的字符串的长度?
- strlen操作的key可以不存在吗?
- strlen操作的key对应的value可以不是string类型吗?
- 如何获得一个string类型的value的内部编码?
- string 内部哪些编码方式?分别都是在什么情况下使用?
- 请问如果String类型的value中存的是“1.5”,那它内部的存储方式是什么呢?
- 假如说现在有一个场景,有非常多的 key,类型都是 string。但是每个 value 的 string 长度都是 100 左右,请问string类型内部编码方式应该选哪种?
- Redis中string类型的应用场景
- 1.Redis最典型的应用场景:充当mysql的Cache
- 伪代码实现
- 什么是热点数据?
- 上述策略有一个明显的问题,随着时间的推移, redis 中的数据肯定是越来越多,那Redis的容量其实是很有限的呀,容不下这么多数据怎么办?
- Redis中为什么要给key加很多前缀?
- 2.计数功能
- 什么叫做,异步的方式将播放量同步到其他数据源?
- 3.共享session会话
- 为什么要共享session会话?
- 4.手机验证码
String类型介绍
redis中的string类型最大的特点就是,string中的字符串不仅仅可以用来存储文本数据、整数和普通的文本字符串,还可以用来存JSON、xml、二进制数据(图片,视频,音频…),因为对于任何类型的数据,redis都会把数据按UTF-8编码成一串二进制存储起来
为了防止音频视频 体积过大,Redis 限制string类型的value大小最大是 512M。
为啥要做这个限制呢,主要还是因为Redis 是单线程模型,不希望进行的操作太复杂,希望操作能比较快速~~
redis采用的字符集
讲 mysql 的时候,我们知道 mysql 默认的字符集,是拉丁文。插入中文,就会失败,而redis就不会,因为redis的字符集只有UTF-8一种
json类型介绍
JSON(JavaScript Object Notation)即 JavaScript 对象表示法 ,是一种轻量级的数据交换格式。它能够以人类可读的文本形式表示数据,结构清晰。比如 {“name”: “John”, “age”: 30} ,很容易理解其中表示的是一个人的姓名和年龄信息。
json字符串不仅可以表示简单的键值对,也能嵌套表示复杂的对象和数组结构 。例如 {“students”: [{“name”: “Alice”, “score”: 90}, {“name”: “Bob”, “score”: 85}]} ,能表示多个学生的成绩信息。
几乎所有现代编程语言都有处理 JSON 的库或内置支持,方便不同语言编写的系统之间进行数据交互。
String类型的命令
set key value + [EX seconds] + [NX|XX]
Redis 的 SET
命令结合 EX
、NX
、XX
等选项可以实现更灵活的键值设置功能,具体格式和含义如下:
基本语法:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
各选项含义:
EX seconds
:设置键的过期时间,单位为秒(例如EX 3600
表示 1 小时后过期)。NX
:全称 “Not Exists”,仅当键不存在时才设置值(常用于分布式锁场景,避免覆盖已有值)。XX
:全称 “Exists”,仅当键已存在时才设置值(常用于更新已有键)。
示例:
-
设置键
user:1001
的值为zhangsan
,并设置 1 小时过期:SET user:1001 zhangsan EX 3600
-
仅当
user:1001
不存在时,才设置值并过期 1 小时:SET user:1001 zhangsan EX 3600 NX
(若键已存在,命令返回
nil
,不做任何操作) -
仅当
user:1001
已存在时,才更新值并保持 1 小时过期:SET user:1001 lisi EX 3600 XX
(若键不存在,命令返回
nil
,不做任何操作)
注意:
NX
和XX
是互斥的,不能同时使用。- 若同时指定
EX
和PX
,以最后出现的选项为准。 - 当
set key value + ex + EX seconds
后面没有加NX或XX
时- 如果 key 不存在,则会创建新的键值对。
- 如果 key 存在,则是让新的 value 覆盖旧的 value。可能会改变原来的数据类型。同时原来这个 key 的 ttl(生存时间)也会失效,即新的ttl也会覆盖旧的ttl,即使新语句中没有设置ttl,旧的ttl依然会失效
set key value ex 10
相当于set key value; expire key 10
,唯一区别在于前者是原子性操作,相当于把两步合为一步SETNX + key value
相当于set key value NX
SETXX + key value
相当于set key value XX
SETEX + key value
相当于set key value EX + seconds
SETPX/PSETEX + key value
相当于set key value PX + pseconds
incr key
incr key
效果是让key对应的value=value + 1
时间复杂度:O(1)
incr对操作的key对应的value类型有限制吗?
此时key对应的value必须得是整数~(64位/8字节表示的整数. 相当于C++中的long long 或者Java中long 表示的范围是非常非常大的)
incr key
操作的返回值是什么?
就是 + 1 之后的value
incr操作的key可以不存在吗?
incr操作的key如果不存在,就会把这个key的value当做0来使用.
多个客户端同时针对同一个key进行incr操作时,存不存在“线程安全”问题?
由于redis处理命令的时候,是单线程模型. 多个客户端同时针对同一个key进行incr操作,不会引起“线程安全”问题.
incrby key n
效果是让key对应的value=value + n
时间复杂度:O(1)
incrby操作的key如果不存在,结果会怎样?
先创造一个<key ,0>的键值对,value = 0 + n = n
incrby key n中n可以是负数吗?
可以
incrby操作的key对应的value类型可以不是整数吗?
不行
decr key
效果是value=value - 1
时间复杂度:O(1)
特殊情况处理和incr 保持一致,故不再多说
decryby key n
效果是value=value - n
特殊情况处理和incr 保持一致,故不再多说
时间复杂度:O(1)
incrbyfloat key float
效果就是value=value+float
时间复杂度:O(1)
注意:
- 此时key对应的value必须得是浮点数~
- float值可以为负,可以通过加上负数的形式来实现减法~~
append key + Str
如何拼接两个string类型的字符串?
格式为 append key + Str
,含义是将key对应的String类型的value
和后面的String类型的Str
,两个字符串拼成一个字符串,str追加到value的后面
append key + Str 中,key可以不存在吗?
可以的,执行完毕之后,key对应的value就是string类型的str
append key3 你好
其中key3在语句执行时不存在,请问redis执行完这条命令之后返回的结果是什么?
返回的结果是6,append key + Str
的返回值是追加后字符串的总长度(单位是字节)。在本题中追加后的字符串就是你好
,一个汉字用UTF-8去编码,占3个字节,两个汉字就是6字节
注意:
- 当前咱们的xshell终端,默认的字符编码是utf8。在终端中输入汉字之后,也就是按照utf8编码的,而一个汉字在utf8字符集中,通常是3个字节~
- 在启动redis客户端的时候,加上一个-raw这样的选项.就可以使redis客户端能够自动的把二进制数据尝试翻译.
- 操作linux的时候,千万注意,不要乱按ctrl + s,ctrl + s在xshell中的作用是“冻结当前画面”,ctrl + q解除冻结~~
getrange key [start] [end]
如何从一个字符串中切分出一个子串呢?
使用getrange key start end
其中key对应的value就是我们要切分的目标字符串
start就是我们切分的起点下标,end就是我们切分的终点下标,执行完getrange key start end之后,返回的结果就是将原字符串从start到end这中间的一段切分下来形成的子字符串(包括start和end)
Redis中start和end指定的区间,是左开右闭区间吗?
不是,左右都是闭区间!redis 中指定的区间,都是闭区间!!!
C++ 和 Java 中,谈到一个区间,大多都是前闭后开(左闭右开)
编程这个大圈子中,区间大多是前闭后开,但是确实有特殊情况
start和end可以传负数吗?
在 Redis 的 GETRANGE 命令中,start 和 end 参数可以传入负数,表示从字符串的末尾开始计数,其中:
- -1 代表字符串的最后一个字符
- -2 代表倒数第二个字符,以此类推
getrange key start end中,key对应是value可以是string类型的汉字吗?
可以是可以,但是切起来会比较奇怪
如果字符串中保存的是汉字,此时进行子串切分,很可能切出来的就不是完整的汉字了
上述的代码,是强行切出了中间的四个字节.随便这么一切,切出的结果在utf8码表上不知道能查出啥了
上述问题,在C++中的substr中同样存在,Java中就没事,为什么呢?
- Java中字符串的基本单位,是字符(Java的字符,占2个字节的字符). Java中相当于String帮我们把汉字的编码转换都处理好了~~
- C++中字符串的基本单位是字节,C++这里头对于汉字的处理,是没那么完善的,就需要程序猿手动处理了
SETRANGE key [offset] [value]
我想把一个字符串从下标6开始往后,替换成我给的字符串str,我该怎么做?
SETRANGE key 6 str
当offset=6时,就会将key对应的字符串从下标6开始往后,替换成我给的str字符串
offset指定的string下标从0开始还是从1开始?
从0开始
setrange操作的key对应的value和替换用的value可以是中文字符串吗?
当然可以,但是如果当前value是一个中文字符串,进行setrange的时候,是可能会搞出问题的!!
我们都知道一个汉字要用三个字节来存,如果你从下标为1的位置开始,将下标123替换为一个汉字,那其实后面解释的时候,是会把012三个下标对应的三个字节解释成一个汉字,这样就肯定不是我们原来想换的汉字了,极有可能出现乱码
setrange操作的key可以不存在吗?
可以,setrange 针对不存在的 key 也是可以操作的。不过会把 offset 之前的内容填充成 0x00(注意这是一个字节,两位16进制数据就是一个字节 ),下图中\x00就是代表0x00
strlen key
如何获得key对应的字符串的长度?
strlen key
注意:strlen的返回长度单位是字节,而MySQL中varchar(N)此处N的单位是字符. mysql中的一个字符,就可以是一个完整的汉字,所以mysql中的一个字符可能是多个字节
C++中,字符串的长度本身就是用字节为单位.Java中,字符串的长度则是以字符为单位的. Java中的一个char == 2字节,Java中的char基于unicode这样的编码方式 就能够表示中文等符号~~
刚才说了半天,一个汉字通常是 3 个字节呀~~(编码方式是 utf8)Java 里头咋一个 2 字节的 char 就能表示汉字呢??
Java 中的 char 用的是 unicode编码. 一个汉字使用两个字节
Java 中的 String,则是用的 utf8. 一个汉字就是 3 个字节了.
Java 的标准库内部,在进行上述的操作过程中,程序猿一般是感知不到编码方式的变换的~~
strlen操作的key可以不存在吗?
可以,如果是这样,那么结果返回0
strlen操作的key对应的value可以不是string类型吗?
不可以,也就是说如果value不是string类型,那么strlen key执行就会报错
如何获得一个string类型的value的内部编码?
object encoding + key
string 内部哪些编码方式?分别都是在什么情况下使用?
- int 64位/8字节的整数,若字符串中存储的是一串数字,比如“1234545”,像这样的字符串我们通常就用int编码方式
- embstr 压缩字符串. 适用于表示比较短的字符串.,当字符串的长度小于39时,通常用embstr编码方式来存储
- raw 普通字符串. 适用于表示更长的字符串,只是单纯的持有字节数组,当字符串的长度大于39时,我们通常用raw编码方式来存储
请问如果String类型的value中存的是“1.5”,那它内部的存储方式是什么呢?
实验告诉我们是embstr,即redis 存储小数,本质上还是当做字符串来存储(string类型中embstr编码方式)。这就和整数相比差别很大了。整数直接使用 int 来存(准确来说是一个 long long (C++) / long (Java)),比较方便进行算术运算。小数则是使用字符串来存。意味着每次进行算术运算,都需要把字符串转成小数,进行运算,结果再转回字符串保存……
假如说现在有一个场景,有非常多的 key,类型都是 string。但是每个 value 的 string 长度都是 100 左右,请问string类型内部编码方式应该选哪种?
本来按道理来说,长度大于39,我们应该选择raw,但是raw所占的空间更大,如果我们更关注整体的内存空间,这样的字符串使用 embstr 来存储也不是不能考虑。
那人家规定embstr最多只能存39字节,再多了存不下,你说怎么办呢?
很简单,改配置就行了,先看 redis 是否提供了对应的配置项,可以修改 39 这个数字,如果没有提供配置型,就需要针对 redis 源码进行魔改~
为啥很多大厂,往往是自己造轮子,而不是直接使用业界成熟的呢?
开源的组件,往往考虑的是通用性,但是大厂往往会遇到一些极端的业务场景。 往往就需要根据当前的极端业务,针对上述的开源组件进行定制化
网上也经常有种说法,大厂造轮子,一般是为了 kpi
Redis中string类型的应用场景
1.Redis最典型的应用场景:充当mysql的Cache
Redis+MYSQL架构下,数据的访问策略是什么?应用服务器访问数据的时候,会先查询Redis,如果命中了,会怎么处理,如果没有命中,又会怎么处理?
整体的思路:应用服务器访问数据的时候, 先查询 Redis.如果 Redis 上数据存在了, 就直接从 Redis 取数据交给应用服务器.不继续访问数据库了.
如果 Redis 上数据不存在, 再读取 MySQL. 把读到的结果, 返回给应用服务器同时, 把这个数据也写入到 Redis 中.
伪代码实现
下面的伪代码模拟了图2-10的业务数据访问过程:
1)假设业务是根据用户uid获取用户信息
UserInfo getUserInfo(long uid) {...
}
2)首先从Redis获取用户信息,我们假设用户信息保存在“user:info:”对应的键中:
// 根据uid得到Redis的键
String key = "user:info:" + uid;// 尝试从Redis中获取对应的值
String value = Redis 执行命令: get key;
// 如果缓存命中(hit)
if (value != null) {// 假设我们的用户信息按照JSON格式存储UserInfo userInfo = JSON 反序列化(value);return userInfo;
}
// 如果缓存未命中(miss)
if (value == null) {// 从数据库中,根据uid获取用户信息UserInfo userInfo = MySQL 执行 SQL: select * from user_info where uid = <uid>// 如果表中没有uid对应的用户信息if (userInfo == null) {响应404return null;}// 将用户信息序列化成JSON格式String value = JSON 序列化(userInfo);// 写入缓存,为了防止数据腐烂(rot),设置过期时间为1小时(3600秒)Redis 执行命令: set key value ex 3600// 返回用户信息return userInfo;
}
什么是热点数据?
Redis 这样的缓存,经常用来存储 “热点” 数据。所谓的热点数据,也就是高频被使用的数据,是如何定义的呢?(什么样的数据叫做被高频使用的数据?)
这个定义方式,结合业务场景有很多种方式的。刚才上述描述的过程,相当于是把最近使用到的数据作为热点数据。
暗含了一层假设: 某个数据一旦被用到了,那么很可能在最近这段时间就会被反复用到,也就是时空局部性
上述策略有一个明显的问题,随着时间的推移, redis 中的数据肯定是越来越多,那Redis的容量其实是很有限的呀,容不下这么多数据怎么办?
- 在把数据写给 redis 的同时,给这个 key 设置一个过期时间。到了过期时间,这个键值对就会被redis删除了
- Redis 也在内存不足的时候,提供了淘汰策略,比如我们机组中学到的LRUCache置换算法
Redis中为什么要给key加很多前缀?
主要就是为了区分不同种类的数据
mysql中,记录是存在表中的,而redis中没有表这样的结构,那我们就通过前缀对键值对进行归类
与 MySQL 等关系型数据库不同的是,Redis 没有表、字段这种命名空间,而且也没有对键名有强制要求(除了不能使用一些特殊字符 )。但设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用 “业务名:对象名:唯一标识:属性” 作为键名。
例如,MySQL 的数据库名为 vs,用户表名为 user_info,那么对应的键可以使用 “vs:user_info:6379”、“vs:user_info:6379:name”
来表示,如果当前 Redis 只会被一个业务使用,可以省略业务名 “vs:”
。如果键名过长,则可以使用团队内部都认同的缩写替代,例如 “user:6379:friends:messages:5217”
可以被 “u:6379:fr:m:5217”
代替。毕竟键名过长,还是会导致 Redis 的性能明显下降的。
2.计数功能
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步处理或者落地到其他数据源。如下图所示,例如视频网站的视频播放次数可以使用Redis来完成:用户每播放一次视频,相应的视频播放数就会自增1。
企业为啥老乐意收集用户的数据??
说人话就是,统计你喜欢看啥,统计出来之后,就给你多推送啥
统计 => 进一步明确用户的需求 => 根据需求改进和迭代产品
Redis 并不擅长数据统计,比如,想在上述的 Redis 中,统计播放量前 100 的视频有哪些,基于 Redis 搞就很麻烦,相比之下,如果是 mysql 来存储上述数据, SELECT * FROM videos ORDER BY play_count DESC LIMIT 100;
一个 sql 就搞定了~~
什么叫做,异步的方式将播放量同步到其他数据源?
异步写入的意思是,Redis把数据更改的信息传过来之后,不用看着mysql将信息同步,再继续工作,而是我给MYSQL发个信号,通知他一声,然后我就去干下一份工去了
实际中要开发一个成熟、稳定的真实计数系统,要面临的挑战远不止如此简单:防作弊(识别出恶意刷票的情况,并屏蔽这种情况)、按照不同维度计数(不只看播放量)、避免单点问题、数据持久化到底层数据源等。
3.共享session会话
为什么要共享session会话?
就比如你去医院看病。这个病一次瞧不好。需要多次治疗,但是你每次去的时候不一定能够确保都是同一个医生给你看病。那如果你前两次去的时候是甲医生给你看,第三次去的时候是乙医生给你看。那么乙医生头一次给你看病,不了解你的情况,工作就很难开展。那这个问题应该怎么解决呢?医院的解决方式就是给你发个病历,前两次去看病的时候,甲大夫都会将这次看病的过程记录在病历中,第三次你再去看病的时候,一大夫就不用再重新问了。而是直接看病例就能快速的了解你的情况。那么医院引入病例的操作就相当于redis中引入共享session会话
引入共享机制之前,对于某个用户,每个应用服务器都会维护一份该用户与当前服务器的会话数据,不同服务器之间的会话数据彼此之间是不同步的,比如我前两次追番的记录,都在1号服务器的session会话中,我第三次访问到了2号服务器,那这个2号服务器中就没有我的追番记录。
引入共享session机制之后,所有的session信息统一由一个服务器来管理和存储,此时所有的会话数据,就可以被所有服务器共享
4.手机验证码
这个过程很简单,厂家首先将验证码发送到用户的手机中,然后用户把短信收到的验证码这一串数,再提交到系统中,系统进行验证验证码是否正确
但是这个过程中也有一些要求,比如
- 限制验证码5分钟内有效
- 限制用户在 1分钟之内,最多获取 5 次验证码
这个主要还是怕用户频繁获取验证码,对于我们的服务器压力过大。或者 (每次获取验证码必须间隔 30 秒~~ )
伪代码
限制一分钟内最多获取五次验证码
验证码5分钟内有效
将用户输入的验证码,与redis数据库中的验证码进行比对,检测输入是否正确
String 发送验证码(phoneNumber) {key = "shortMsg:limit:" + phoneNumber;// 设置过期时间为 1 分钟(60 秒)// 使用 NX,只在不存在 key 时才能设置成功bool r = Redis 执行命令: set key 1 ex 60 nxif (r == false) {// 说明之前设置过该手机的验证码了long c = Redis 执行命令: incr keyif (c > 5) {// 说明超过了一分钟 5 次的限制了// 限制发送return null;}}// 走到这说明要么之前没有设置过手机的验证码;要么次数没有超过 5 次String validationCode = 生成随机的 6 位数的验证码();validationKey = "validation:" + phoneNumber;// 验证码 5 分钟(300 秒)内有效Redis 执行命令: set validationKey validationCode ex 300;// 返回验证码,随后通过手机短信发送给用户return validationCode;
}// 验证用户输入的验证码是否正确
bool 验证验证码(phoneNumber, validationCode) {validationKey = "validation:" + phoneNumber;String value = Redis 执行命令: get validationKey;if (value == null) {// 说明没有这个手机的验证码记录,验证失败return false;}if (value == validationCode) {return true;} else {return false;}
}