文章目录
1、预备知识
在正式介绍5种数据结构之前,了解一下Redis的一些全局命令、数据结构和内部编码、单线程命令处理机制是十分有必要的,它们能为后面内容的学习打下一个好的基础,主要体现在两个方面:第一、Redis的命令有上百个,如果纯靠死记硬背比较困难,但是如果理解Redis的一些机制,会发现这些命令有很强的通用性。第二、Redis不是万金油,有些数据结构和命令必须在特定场景下使用,一旦使用不当可能对Redis本身或者应用本身造成致命伤害。
1.1:一些全局命令
Redis有5种数据结构,它们是键值对中的值,对于键来说有一些通用的 命令。
1.查看所有键:keys * .
2. 键总数:dbsize
dbsize命令在计算键总数时不会遍历所有键,而是直接获取Redis内置的 键总数变量,所以dbsize命令的时间复杂度是O(1)。而keys命令会遍历所 有键,所以它的时间复杂度是O(n),当Redis保存了大量键时,线上环境禁止使用。
3. 检查键是否存在:exists key
如果键存在则返回1,不存在则返回0:
4. 删除键:del key [key …]
del是一个通用命令,无论值是什么数据结构类型,del命令都可以将其 删除,例如下面将字符串类型的键java和列表类型的键mylist分别删除。
5. 键过期:expire key seconds
Redis支持对键添加过期时间,当超过过期时间后,会自动删除键,例 如为键hello设置了3秒过期时间:
ttl命令会返回键的剩余过期时间,它有3种返回值:
- 大于等于0的整数:键剩余的过期时间。
- -1键没设置过期时间。
- -2键不存在
6.键的数据结构类型:type key
例如键hello是字符串类型,返回结果为string。键mylist是列表类型,返 回结果为list:
2:数据结构和内部编码
type命令实际返回的就是当前键的数据结构类型,它们分别是: string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集 合),但这些只是Redis对外的数据结构,如图:
实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现, 这样Redis会在合适的场景选择合适的内部编码,如图2-2所示。
可以通过object encoding命查询 内部编码:
Redis这样设计的好处:
第一,可以改进内部编码,而对外的数据 结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令,例如Redis3.2提供了quicklist,结合了ziplist和linkedlist两者的优势,为列表类型提供了一种更为优秀的内部编码实现,而对外部用户来说基本感知不到。
第二,多种内部编码实现可以在不同场景下发挥各自的优 势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有 所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
2.1:字符串
字符串类型是Redis最基础的数据结构。首先键都是字符串类型,而且 其他几种数据结构都是在字符串类型基础上构建的,所以字符串类型能为其 他四种数据结构的学习奠定基础。如图2-7所示,字符串类型的值实际可以 是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字 (整数、浮点数),甚至是二进制(图片、音频、视频),但是值最大不能 超过512MB。
2.1.1:常用命令
1.设置值
set key value [ex seconds] [px milliseconds] [nx|xx]
set命令有几个选项:
- ex seconds:为键设置秒级过期时间。
- px milliseconds:为键设置毫秒级过期时间。
- nx:键必须不存在,才可以设置成功,用于添加。
- xx:与nx相反,键必须存在,才可以设置成功,用于更新。
除了set选项,Redis还提供了setex和setnx两个命令:
setex key seconds value
setnx key value
setnx和setxx在实际使用中有什么应用场景吗?以setnx命令为例子,由于 Redis的单线程命令处理机制,如果有多个客户端同时执行setnx key value, 根据setnx的特性只有一个客户端能设置成功,setnx可以作为分布式锁的一种 实现方案
2.获取值:get key 如果要获取的键不存在,则返回nil(空)
3.批量设置值
mset key value [key value ...]
4.批量获取值
mget key value [key value ...]
如果有些键不存在,那么它的值为nil(空),结果是按照传入键的顺序返回:
批量操作命令可以有效提高开发效率,假如没有mget这样的命令,要执行n次get命令需要按照图2-8的方式来执行,具体耗时如下:
n次get时间 = n次网络时间 + n次命令时间
使用mget命令后,要执行n次get命令操作只需要按照图2-9的方式来完成,具体耗时如下:
n次get时间 = 1次网络时间 + n次命令时间
对于Redis客户端来说,一次命令除了命令时间还是有网络时间,而Redis的处理能力已经足够高,所以对于开发人员来说,网络可能会成为性能的瓶颈,所以学会使用批量操作,有助于提高业务处理效率,当不能无节制,不然会造成Redis阻塞或网络阻塞。
5.计数:incr key
incr命令用于对值做自增操作,返回结果分为三种情况:
- 值不是整数,返回错误。
- 值是整数,返回自增后的结果。
- 键不存在,按照值为0自增,返回结果为1。
除了incr命令,Redis提供了decr(自减)、incrby(自增指定数字)、 decrby(自减指定数字)、incrbyfloat(自增浮点数):
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
很多存储系统和编程语言内部使用CAS机制实现计数功能,会有一定的 CPU开销,但在Redis中完全不存在这个问题,因为Redis是单线程架构,任 何命令到了Redis服务端都要顺序执行。
2.1.2:不常用命令
1.追加值:append key value
2.字符串长度:strlen key
每个中文占用3个字节
3:设置并返回原值:getset key value
4.设置指定位置的字符:setrange key offeset value
下面操作将值由jedis变为了redis:
5.获取部分字符串:getrange key start end
偏移量从0开始计算
2.2: 内部编码
字符串类型的内部编码有3种:
- nt:8个字节的长整型。
- embstr:小于等于39个字节的字符串。
- raw:大于39个字节的字符串。
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
整数类型示例如下:
短字符串示例如下:
长字符串示例如下:
2.3: 使用场景
1.缓存功能
图2-10是比较典型的缓存使用场景,其中Redis作为缓存层,MySQL作 为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
2.计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、 查询缓存的功能,同时数据可以异步落地到其他数据源。例如一些视频播放数系统就是使用Redis作为视频播放数计数的基础组件,用户每播放一次视频,相应的视频播放数就会自增1.
开发提示
实际上一个真实的计数系统要考虑的问题会很多:防作弊、按照不同维 度计数,数据持久化到底层数据源等。
3.共享Session
一个分布式Web服务将用户的Session信息(例如用户登 录信息)保存在各自服务器中,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题是用户无法容忍的。为了解决这个问题,可以使用Redis将用户的Session进行集中管理,在这种模式下只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
4.限速
很多应用出于安全的考虑,会在每次进行登录时,让用户输入手机验证 码,从而确定是否是用户本人。但是为了短信接口不被频繁访问,会限制用 户每分钟获取验证码的频率,例如一分钟不能超过5次。
3:哈希
几乎所有的编程语言都提供了哈希(hash)类型,它们的叫法可能是哈 希、字典、关联数组。在Redis中,哈希类型是指键值本身又是一个键值对 结构,形如value={{field1,value1},…{fieldN,valueN}},Redis键值对和 哈希类型二者的关系可以用下图来表示。
注:哈希类型中的映射关系叫作field-value,注意这里的value是指field对应 的值,不是键对应的值,请注意value在不同上下文的作用。
3.1:命令
1.设置值:hset key field value
此外Redis提供了hsetnx命令,它 们的关系就像set和setnx命令一样,只不过作用域由键变为field。
2.获取值:hget key field
3.删除field:hdel key field [field …]
4.计算field个数:hlen key
5.批量设置或获取field-value
hmget key field [field ...]
hmset key field value [field value ...]
6.判断field是否存在:hexists key field
7.获取所有field:hkeys key
hkeys命令应该叫hfields更为恰当,它返回指定哈希键所有的field
8.获取所有value:hvals key
9.获取所有的field-value:hgetall key
提示:
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。 如果开发人员只需要获取部分field,可以使用hmget,如果一定要获取全部 field-value,可以使用hscan命令,该命令会渐进式遍历哈希类型,hscan将在后文介绍
10.hincrby hincrbyfloat
hincrby key field
hincrbyfloat key field
3.2:内部编码
哈希类型的内部编码有两种:
1.·ziplist(压缩列表): 当哈希类型元素个数小于hash-max-ziplist-entries 配置(默认512个)、同时所有值都小于hash-max-ziplist-value配置(默认64 字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的 结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。
2.hashtable(哈希表): 当哈希类型无法满足ziplist的条件时,Redis会使 用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而 hashtable的读写时间复杂度为O(1)。
当field个数比较少且没有大的value时,内部编码为ziplist:
当有value大于64字节,内部编码会由ziplist变为hashtable:
当field个数超过512,内部编码也会由ziplist变为hashtable:
、
3.3:使用场景
图2-15为关系型数据表记录的两条用户信息,用户的属性作为表的列, 每条用户信息作为行。
如果将其用哈希类型存储,如图2-16所示。
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。可以将每个用户的id定义为键后缀,多对field- value对应每个用户的属性