Redis(一)数据类型、常用命令
文章目录
- 一、初识Redis
- 1.1 Redis特性
- 1.2 Redis的优缺点
- 1.2.1 Redis的优点
- 1.2.2 Redis的缺点
- 1.3 为什么要用缓存
- 1.4 Redis和其他容器的比较
- 1.4.1 Redis和Map
- 1.4.2 Redis和Memcached
- 1.5 Redis的简单使用
- 1.5.1 启动Redis服务端
- 1.5.2 Redis配置文件
- 1.5.3 启动Redis客户端
- 1.5.4 关闭Redis客户端
- 1.6 Redis的一些安全策略
- 二、数据类型与常用命令
- 2.1 数据类型前瞻
- 2.1.1 全局命令
- 2.1.2 内部编码
- 2.2 字符串
- 2.2.1 字符串命令
- 2.2.2 字符串内部编码
- 2.2.3 字符串使用场景
- 2.3 哈希
- 2.3.1 哈希命令
- 2.3.2 哈希内部编码
- 2.3.3 哈希使用场景
- 2.4 列表
- 2.4.1 列表命令
- 2.4.2 列表内部编码
- 2.4.3 列表使用场景
- 2.5 集合
- 2.5.1 集合命令
- 2.5.2 集合内部编码
- 2.5.3 集合内部编码
- 2.6 有序集合
- 2.6.1 有序集合命令
- 2.6.2 有序列表内部编码
- 2.6.3 有序列表使用场景
- 2.7 key管理命令
- 2.7.1 单个key管理
- 2.7.2 遍历键
- 2.7.3 数据库管理
- 2.8 容器型数据结构的通用规则
- 三、数据类型的比较及应用
- 3.1 Redis有哪些数据类型
- 3.2 Redis的应用场景(从数据类型考虑)
- 3.2.1 字符串的使用场景
- 3.2.2 哈希的使用场景
- 3.2.3 列表的使用场景
- 3.2.4 集合的使用场景
- 3.2.5 有序集合的使用场景
- 3.3 Redis数据类型及命令的相关问题
- 3.3.1 Redis中对于过期时间的应用
- 3.3.2 秒杀系统的简单设计
- 3.3.3 在Redis中存对象的几种方式
- 3.3.4 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
- 3.3.5 一个Redis实例最多能存放多少的keys?List、Set、Sorted Set最多能存放多少元素?
- 3.3.6 怎么使用Redis做异步队列?
- 3.3.7 Redis如何实现延时队列
- 3.4 键值设计
- 3.4.1 key名设计
- 3.4.2 value设计
- 3.4.3 控制key的生命周期
- 四、Redis工作原理
- 4.1 Redis线程模型
- 4.2 Redis为什么这么快
- 五、布隆过滤器
- 5.1 布隆过滤器的基本使用
- 5.2 布隆过滤器的原理
- 5.3 布隆过滤器的其它应用
本系列文章:
Redis(一)数据类型、常用命令
Redis(二)Redis客户端的使用
Redis(三)持久化、主从复制
Redis(四)Redis内存
Redis(五)哨兵、集群
Redis(六)缓存、分布式锁
Redis(七)Redis优化建议
一、初识Redis
Redis是一个使用C语言编写的,开源的高性能非关系型(NoSQL)的键值对数据库
。
Redis中存储的是键值对,值的类型有5种:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)
。
Redis将所有数据都存放在内存中,所以读写性能非常好
,Redis每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB。因此Redis被广泛应用于缓存方向。
Redis还可以将内存的数据利用快照和日志的形式进行持久化
。
Redis 也经常用来做分布式锁
。
1.1 Redis特性
- 1、速度快
官方给出的数字是读写性能可以达到10万/秒。 - 2、存储数据的方式是键值对
Redis键值对中的值,主要用来存储5种数据结构:字符串、哈希、列表、集合、有序集合。 - 3、丰富的功能
除了5种数据结构,Redis还提供了许多额外的功能:
1、提供了
键过期
功能,可以用来实现缓存
。
2、提供了发布订阅功能,可以用来实现消息系统。
3、支持Lua脚本功能,可以利用Lua创造出新的Redis命令。
4、提供了简单的事务功能,能在一定程度上保证事务特性。
5、提供了流水线功能,这样客户端能将一批命令一次性传到Redis服务端
,减少了网络的开销。
- 4、简单稳定
Redis使用单线程模型。 - 5、持久化
Redis提供了两种持久化方式:RDB和AOF
,即可以用两种策略将内存的数据保存到物理设备中。 - 6、主从复制
Redis提供了复制功能,实现了多个相同数据的Redis副本,复制功能是分布式Redis的基础。 - 7、高可用和分布式
Redis从2.8版本正式提供了高可用实现Redis Sentinel(哨兵),能够保证Redis节点的故障发现和故障自动转移
。Redis从3.0版本正式提供了分布式实现Redis Cluster(集群),是Redis真正的分布式实现
,提供了高可用、读写和容量的扩展性。
1.2 Redis的优缺点
1.2.1 Redis的优点
- 1、读写性能优异
Redis能读的速度是110000次/s,写的速度是81000次/s。 - 2、支持数据持久化
支持AOF和RDB两种持久化方式。 - 3、支持事务
Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。 - 4、数据结构丰富
除了支持string类型的value外,还支持hash、set、zset、list等数据结构。 - 5、支持主从复制
主机会自动将数据同步到从机,可以进行读写分离。
1.2.2 Redis的缺点
- 1、数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
Redis适合的场景主要局限在较小数据量的高性能操作和运算上。 - 2、Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂
为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
1.3 为什么要用缓存
Redis经常被用来做缓存,那么为什么要用缓存呢?主要从“高性能”和“高并发”这两点来看待这个问题:
- 1、高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。此时将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快
。数据库中的对应数据改变之后,同步改变缓存中相应的数据即可。
- 2、高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的
,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
1.4 Redis和其他容器的比较
1.4.1 Redis和Map
- 1、
Redis可以用几十G内存来做缓存
,Map不行,一般JVM也就分几个G数据就够大了。 - 2、Redis的缓存可以
持久化
,Map实现的是本地缓存
,最主要的特点是轻量以及快速,但是它们的生命周期随着 jvm 的销毁而结束。 - 3、Redis可以实现
分布式的缓存
,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性
。Map实现的是本地缓存,不具有一致性。 - 4、Redis可以处理
每秒百万级的并发
,是专业的缓存服务,Map只是一个普通的对象。 - 5、Redis缓存有
过期机制
,Map无此功能。
1.4.2 Redis和Memcached
Memcached的多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。所以高并发情况下,有时单线程的Redis比多线程的Memcached效率要高。
- 选择Redis的场景:
1、复杂数据结构
,Redis可以存储的数据类型多样,最典型的的使用场景是,用户订单列表,用户消息,帖子评论等, Memcached无法满足这些数据结构。
2、需要进行数据的持久化功能
.
3、高可用,Redis支持集群,可以实现主动复制,读写分离
,而对于Memcached如果想要实现高可用,需要进行二次开发。
4、存储的内容比较大(如字符串可存储的最大容量是512M)
,Memcached存储的value最大为1M。 - 选择Memcached的场景:
纯Key-Value,数据量非常大的业务,使用Memcached更合适。原因:
- Memcached的内存分配采用的是预分配内存池的管理方式,能够省去内存分配的时间,Redis是临时申请空间,可能导致碎片化。
- 数据量大时,Memcached更快。
- Memcached使用非阻塞的IO复用模型,Redis也是使用非阻塞的IO复用模型,但是Redis还提供了一些非键值对存储之外功能,如:排序,聚合等,这些会阻塞整个IO调度。从这点上由于Redis提供的功能较多,Memcached更快些。
- 线程模型,Memcached使用多线程,主线程监听,子线程接受请求,执行读写。Redis使用的单线程,虽然无锁冲突,但是难以利用多核的特性提升吞吐量。
1.5 Redis的简单使用
Redis几个比较重要的可执行文件:
redis-server:启动Redis;
redis-cli:Redis命令行客户端;
redis-benchmark:Redis基准测试工具;
redis-check-aof:Redis AOF持久化文件检测和修复工具;
redis-check-dump:Redis RDB持久化文件检测和修复工具;
redis-sentinel:启动Redis Sentinel。
1.5.1 启动Redis服务端
Redis分为client和server,启动Server端的方式为:
1、切到对应的Redis目录(示例:
D:\Redis
);
2、运行命令redis-server.exe
。
服务端启动后,界面为:
可以看到:当前的Redis版本的是3.0.5,Redis的默认端口是6379。
1.5.2 Redis配置文件
如果要使用配置文件启动的话,命令是:redis-server redis.windows.conf
。
redis.windows.conf为Redis配置文件。需注意文件路径,未明确指定时,表示在redis-server的同路径下。示例:
在生产环境上,一般都是通过修改配置文件的方式来修改Redis服务端的相关配置。
1.5.3 启动Redis客户端
启动了Redis服务端后,就可以启动客户端了,还是在安装Redis的目录,命令为:redis-cli -h {host} -p {port}
。示例:redis-cli -h 127.0.0.1 -p 6379
。
启动Redis客户端后就可以进行操作了,示例:
在启动Redis-cli时,如果没有-h参数,那么默认连接127.0.0.1;如果没有-p,那么默认6379端口,也就是说如果-h和-p都没写就是连接127.0.0.1:6379这个Redis实例。
1.5.4 关闭Redis客户端
断开Redis客户端连接的命令是:redis-cli shutdown nosave|save
,可以选择是否在关闭前持久化数据。
Redis客户端关闭后,要重新连接,先启动一下服务端即可。
1.6 Redis的一些安全策略
Redis默认情况下,会绑定在bind0.0.0.0:6379,这样就会将Redis的服务暴露到公网上,如果在没有开启认证的情况下,可以导致任意用户在访问目标服务器的情况下,未授权就可访问Redis以及读取Redis的数据,攻击者就可以在未授权访问Redis的情况下可以利用Redis的相关方法,成功在Redis服务器上写入公钥,进而可以直接使用私钥进行直接登录目标主机。
- 1、指令安全(禁止一些高危命令)
Redis有一些非常危险的指令,这些指令会对Redis的稳定以及数据安全造成非常严重的影响。比如keys指令会导致Redis卡顿,flushdb和flushall会让Redis的所有数据全部清空。如何避免人为操作失误导致这些灾难性的后果也是运维人员特别需要注意的风险点之一。
Redis在配置文件中提供了rename-command指令用于将某些危险的指令修改成特别的名称,用来避免人为误操作。比如在配置文件的security块增加下面的内容:
rename-command keys abckeysabc
如果还想执行keys方法,那就不能直接敲keys命令了,而需要键入abckeysabc。 如果想完全封杀某条指令,可以将指令rename成空串,就无法通过任何字符串指令来执行这条指令了。示例:
rename-command flushall ""
- 2、端口安全
Redis 默认会监听 *:6379,如果当前的服务器主机有外网地址,Redis的服务将会直接暴露在公网上。Redis的服务地址一旦可以被外网直接访问,内部的数据就彻底丧失了安全性。高级一点的黑客们可以通过Redis执行Lua脚本拿到服务器权限,恶意的竞争对手们甚至会直接清空你的Redis数据库。
所以,运维人员务必在Redis的配置文件中指定监听的IP地址,避免这样的惨剧发生。更进一步,还可以增加Redis的密码访问限制,客户端必须使用auth指令传入正确的密码才可以访问Redis,这样即使地址暴露出去了,普通黑客也无法对Redis进行任何指令操作。 - 3、Lua脚本安全
开发者必须禁止Lua脚本由用户输入的内容 (UGC) 生成,这可能会被黑客利用以植入恶意的攻击代码来得到Redis的主机权限。同时,我们应该让Redis以普通用户的身份启动,这样即使存在恶意代码黑客也无法拿到root权限。 - 4、SSL代理
Redis并不支持SSL链接,意味着客户端和服务器之间交互的数据不应该直接暴露在公网上传输,否则会有被窃听的风险。如果必须要用在公网上,可以考虑使用SSL代理。 - 5、以低权限运行redis服务
为redis服务创建单独的用户和根目录,并且配置禁止登录。 - 6、为redis添加密码验证
- 7、禁止外网访问Redis
修改redis.conf文件,添加或修改bind127.0.0.1,使得redis服务只在当前主机使用。 - 8、做log监控,及时发现攻击
二、数据类型与常用命令
2.1 数据类型前瞻
2.1.1 全局命令
先往Redis中存入一些数据,示例:
- 1、查看所有键
命令是keys *
:
- 2、查看键总数
命令是dbsize
:
- 3、检查键是否存在:
命令是exists key
,0和1代表是否存在指定key,示例:
- 4、删除键
命令是del key [key ...]
。del是一个通用命令,无论值是什么数据结构类型,del命令都可以将其删除。0和1代表是否成功删除指定key,示例:
del命令还可以支持删除多个键。示例:
此时Redis中已经没有键值对了,我们添加几个,以供测试:
- 5、对键添加过期时间
命令是expire key seconds
,单位为秒。当超过过期时间后,会自动删除键。0和1代表是否设置成功,示例:
- 6、查看键的剩余过期时间:
命令是ttl
,有3种返回值:
大于等于0的整数
:键剩余的过期时间,单位为秒。
-1
:键没设置过期时间。
-2
:键不存在。
示例:
对于字符串类型键,执行set命令会去掉该key的过期时间
。示例:
- 7、查看键的数据结构类型:
命令是type key
,示例:
- 8、重命名键:
命令是rename key newkey
,示例
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖。示例:
由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性。 - 9、随机返回一个键:
命令是randomkey
,示例:
2.1.2 内部编码
type
命令实际返回的就是当前键的数据结构类型,它们分别是:string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合),但这些只是Redis对外的数据结构。实际上每种数据结构都有自己底层的内部编码实现,而且是多种实现,这样Redis会在合适的场景选择合适的内部编码。
可以通过object encoding
命令查询内部编码,示例:
127.0.0.1:6379> object encoding hello
"embstr"
127.0.0.1:6379> object encoding mylist
"ziplist"
Redis数据结构和内部编码示例:
Redis这样设计有两个好处:
- 1、可以改进内部编码,而对外的数据结构和命令没有影响,这样一旦开发出更优秀的内部编码,无需改动外部数据结构和命令。
- 2、多种内部编码实现可以在不同场景下发挥各自的优势,例如ziplist比较节省内存,但是在列表元素比较多的情况下,性能会有所下降,这时候Redis会根据配置选项将列表类型的内部实现转换为linkedlist。
2.2 字符串
字符串是Redis最基础的Value类型。字符串类型的值实际可以是字符串(简单的字符串、复杂的字符串(例如JSON、XML))、数字(整数、浮点数),甚至是二进制(图片、音频、视频)。
Redis的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。
2.2.1 字符串命令
- 1、设置值
命令是set key value [ex seconds] [px milliseconds] [nx|xx]
。示例:
set命令有几个选项:
ex seconds:为键设置秒级过期时间。
px milliseconds:为键设置毫秒级过期时间。
nx:键必须不存在,才可以设置成功,用于添加键值对
。
xx:与nx相反,键必须存在,才可以设置成功,用于更新键值对
。
除了set选项,Redis还提供了setex和setnx两个命令。它们的作用和ex和nx选项是一样的。示例(hello键存在,所以用setnx更新值并未成功):
用setex命令是可以更新键成功的,更新的同时并设置了一下键过期时间。如以下例子是更新键值,并设置过期时间为60秒:
- 2、获取值
命令是get key
:
如果要获取的键不存在,则返回nil(空)。 - 3、批量设置值
命令是mset key value [key value ...]
,示例:
- 4、批量获取值
命令是mget key [key ...]
,示例:
批量操作命令可以提高开发效率。在不使用mget这样的命令时,执行n次get命令需要的时间为n次get时间 = n次网络通信时间(一来一回) + n次命令执行时间
:
使用mget命令后,要执行n次get命令需要的时间为:n次get时间 = 1次网络通信时间 + n次命令执行时间
:
Redis可以支撑每秒数万的读写操作,这里的指的是Redis服务端的处理能力。
对于客户端来说,每次批量操作所发送的命令数不是无节制的,如果数量过多可能造成Redis阻塞或者网络拥塞。 - 5、计数
命令是incr key
,incr命令用于对值做自增操作,返回结果分为三种情况:
1、值不是整数,返回错误。
2、值是整数,返回自增后的结果。
3、键不存在,按照值为0自增,返回结果为1。
incr命令的使用示例:
除了incr命令,Redis还提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数)。
- 6、追加值
命令是append key value
,示例:
- 7、获取字符串长度
命令是strlen key
,单位是字节,示例:
- 8、设置并返回原值
命令是getset key value
,示例:
2.2.2 字符串内部编码
字符串类型的内部编码有3种(Redis会根据当前值的类型和长度决定使用哪种内部编码实现):
int:8个字节的长整型。
embstr:小于等于39个字节的字符串。
raw:大于39个字节的字符串。
2.2.3 字符串使用场景
- 1、缓存
常见的是:Redis作为缓存层,MySQL作为存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到加速读写和降低后端压力的作用。
设计合理的键名,有利于防止键冲突和项目的可维护性,比较推荐的方式是使用“业务名:对象名:id:[属性]”作为键名(也可以不是分号)。 - 2、计数
许多应用都会使用Redis作为计数的基础工具,它可以实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源。 - 3、共享Session
可以使用Redis将用户的Session进行集中管理。 - 4、限速
比如可以用键的过期时间来限制用户每分钟获取验证码的频率等。
2.3 哈希
哈希指Value是个键值对,即{field1,value1}
。一个哈希最多可以存储2的32次方 -1个元素
。示例:
哈希类型中的映射关系叫作field-value
。
和操作String的命令相比,对hash的很多操作命令是在String的操作命令之前加了h
。
Redis的字典相当于Java语言里面的HashMap,它是无序字典。内部实现结构上同Java的HashMap也是一致的,同样的数组+链表二维结构。第一维hash的数组位置碰撞时,就会将碰撞的元素使用链表串接起来。
不同的是,Redis的字典的值只能是字符串,另外它们rehash的方式不一样,因为Java的HashMap在字典很大时,rehash是个耗时的操作,需要一次性全部rehash。Redis为了高性能,不能堵塞服务,所以采用了渐进式rehash策略。
渐进式rehash会在rehash的同时,保留新旧两个hash结构,查询时会同时查询两个hash结构,然后在后续的定时任务中以及hash的子指令中,循序渐进地将旧hash的内容一点点迁移到新的hash结构中。
当hash移除了最后一个元素之后,该数据结构自动被删除,内存被回收。
hash结构也可以用来存储用户信息,不同于字符串一次性需要全部序列化整个对象,hash可以对用户结构中的每个字段单独存储。这样当需要获取用户信息时可以进行部分获取。而以整个字符串的形式去保存用户信息的话就只能一次性全部读取,这样就会比较浪费网络流量。
2.3.1 哈希命令
- 1、设置值
命令是hset key field value
,示例:
- 2、获取值
命令是hget key field
,示例:
- 3、删除field
命令是hdel key field [field ...]
,返回结果为成功删除field的个数,示例:
- 4、计算field个数
命令是hlen key
,示例:
- 5、批量设置、批量获取
批量设置的命令是hmset key field value [field value ...]
;批量获取的命令是hmget key field [field ...]
**。
hmset和hmget分别是批量设置和获取field-value,示例
- 6、判断field是否存在
命令是hexists key field
,存在时结果为1,否则结果为0,示例:
- 7、获取所有field
命令为hkeys key
,作用为返回指定哈希键所有的field,示例:
- 8、获取所有value
命令是hvals key
,示例:
- 9、获取所有的field-value
命令是hgetall key
,示例:
在使用hgetall时,如果哈希元素个数比较多,会存在阻塞Redis的可能。如果只需要获取部分field,可以使用hmget,如果一定要获取全部field-value,可以使用hscan
命令。 - 10、计算value的字符串长度
命令是hstrlen key field
。
2.3.2 哈希内部编码
哈希类型的内部编码有两种:
- ziplist(压缩列表)
当哈希类型元素个数小于hash-max-ziplist-entries
配置(默认512个)、同时所有值都小于hash-max-ziplist-value
配置(默认64字节)时,Redis会使用ziplist作为哈希的内部实现,ziplist使用更加紧凑的结构实现多个元素的连续存储,所以在节省内存方面比hashtable更加优秀。 - hashtable(哈希表)
当哈希类型无法满足ziplist的条件时,Redis会使用hashtable作为哈希的内部实现,因为此时ziplist的读写效率会下降,而hashtable的读写时间复杂度为O(1)。
2.3.3 哈希使用场景
哈希类型可以用来存储实体类信息,示例:
到目前为止,已经能够用三种方法缓存用户信息。
- 1、原生字符串
每个属性一个键,示例:
set user:1:name tomset user:1:age 23set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差,所以此种方案一般不会在生产环境使用。
- 2、序列化字符串
将用户信息序列化后用一个键保存,示例:
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
- 3、哈希
每个用户属性使用一对field-value,但是只用一个键保存。示例:
hmset user:1 name tomage 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
2.4 列表
列表用来存储多个有序的字符串,一个列表最多可以存储2的32次方 -1个元素
。
在Redis中,可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。
列表:按照插入顺序的字符串链表(双向链表),主要命令是LPUSH和RPUSH,能够支持反向查找和遍历。
列表有两个特点:有序、可重复
。
Redis的列表相当于LinkedList,意味着列表的插入和删除操作非常快,时间复杂度为O(1),但是索引定位很慢,时间复杂度为O(n)。
当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。
Redis的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进Redis的列表,另一个线程从这个列表中轮询数据进行处理。
2.4.1 列表命令
列表的四种操作类型:
操作类型 | 操作 |
---|---|
添加 | rpush lpush linsert |
查找 | lrange lindex llen |
删除 | lpop rpop lrem ltrim |
修改 | lset |
阻塞操作 | blpop brpop |
- 1、从右边插入元素
命令是rpush key value [value ...]
,示例:
从左边插入元素的命令是lpush key value [value ...]
,使用方法和rpush相同,只不过从左侧插入。 - 2、向某个元素前或者后插入元素
命令是linsert key before|after pivot value
linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后(after)插入一个新的元素value,示例:
- 3、获取指定范围内的元素列表
命令是lrange key start end
,lrange操作会获取列表指定索引范围所有的元素。索引下标有两个特点:
第一,索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
第二,lrange中的end选项包含了自身。
获取列表的第2到第4个元素示例:
- 4、获取列表指定索引下标的元素
命令是lindex key index
,获取当前列表最后一个元素示例:
慢操作
:lindex相当于Java链表的get(int index)方法,它需要对链表进行遍历,性能随着参数index增大而变差。 ltrim和字面上的含义不太一样,个人觉得它叫lretain(保留) 更合适一些,因为ltrim跟的两个参数start_index和end_index定义了一个区间,在这个区间内的值,ltrim要保留,区间之外统统砍掉。我们可以通过ltrim来实现一个定长的链表,这一点非常有用。index可以为负数,index=-1表示倒数第一个元素,同样 index=-2表示倒数第二个元素。 - 5、获取列表长度
命令是llen key
。示例:
- 6、从列表左侧弹出元素、从列表右侧弹出元素
从列表左侧弹出元素的命令是lpop key
,从列表右侧弹出的命令是rpop key
。示例:
- 7、删除指定元素
命令是lrem key count value
,lrem命令会从列表中找到等于value的元素进行删除**,根据count的不同分为三种情况:
count>0,从左到右,删除最多count个元素。
count<0,从右到左,删除最多count绝对值个元素。
count=0,删除所有。
假如当前列表变为“a a a a a java b a”,下面操作将从列表左边开始删除4个为a的元素:
- 8、按照索引范围修剪列表
命令是ltrim key start end
,只保留列表第2个到第4个元素示例:
- 9、修改指定索引下标的元素
命令是lset key index newValue
,将列表中的第3个元素设置为python示例:
2.4.2 列表内部编码
列表类型的内部编码有两种:
- ziplist(压缩列表)
当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。 - linkedlist(链表)
当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成quicklist。因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针prev和next 。所以Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
2.4.3 列表使用场景
- 1、消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
- 2、文章列表
每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
2.5 集合
集合类型也可以用来保存多个字符串,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
Set:用哈希表类型的字符串序列,没有顺序,集合成员是唯一的,没有重复数据,底层主要是由一个value为null的Hashmap来实现的。
一个集合最多可以存储(2的32次方 -1)个元素
。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。
Redis的集合相当于Java语言里面的HashSet,它内部的键值对是无序的唯一的。它的内部实现相当于一个特殊的字典,字典中所有的value都是一个值NULL。当集合中最后一个元素移除之后,数据结构自动删除,内存被回收。
2.5.1 集合命令
- 1、添加元素
命令是sadd key element [element ...]
,结果为添加成功的元素个数,示例:
- 2、删除元素
命令是srem key element [element ...]
,结果为成功删除元素个数,示例:
- 3、计算元素个数
命令是scard key
。scard的时间复杂度为O(1),它不会遍历集合所有元素,而是直接用Redis内部的变量,示例:
- 4、判断元素是否在集合中
命令是sismember key element
,如果给定元素element在集合内返回1,反之返回0,示例:
- 5、随机从集合返回(最多)指定个数元素
命令是srandmember key [count]
**,[count]是可选参数,如果不写默认为1,示例:
- 6、从集合随机弹出元素
命令是spop key
。srandmember和spop都是随机从集合选出元素,两者不同的是spop命令执行后,元素会从集合中删除,而srandmember不会。 - 7、获取所有元素
命令是smembers key
,返回结果是无序的。
smembers和lrange、hgetall都属于时间复杂度较高的命令,如果元素过多存在阻塞Redis的可能性,可以使用sscan
来完成。
此时假如有两个集合:myset1里的元素是"1 2 3",myset2里的元素是"2 3 4",示例:
- 8、求多个集合的交集
命令是sinter key [key ...]
,示例:
- 9、求多个集合的并集
命令是suinon key [key ...]
,示例:
- 10、求多个集合的差集
命令是sdiff key [key ...]
,示例:
2.5.2 集合内部编码
集合类型的内部编码有两种:
- intset(整数集合)
当集合中的元素都是整数且元素个数小于set-max-intset-entries配置(默认512个)时,Redis会选用intset来作为集合的内部实现,从而减少内存的使用。 - hashtable(哈希表)
当集合类型无法满足intset的条件时,Redis会使用hashtable作为集合的内部实现。
2.5.3 集合内部编码
集合类型比较典型的使用场景是标签(tag)。例如一个用户可能对娱乐、体育比较感兴趣,另一个用户可能对历史、新闻比较感兴趣,这些兴趣点就是标签。有了这些数据就可以得到喜欢同一个标签的人,以及用户的共同喜好的标签,这些数据对于用户体验以及增强用户黏度比较重要。
2.6 有序集合
有序集合保留了集合不能有重复成员的特性,并且给每个元素设置一个分数(score)用来排序。一个有序集合最多可以存储(2的32次方 -1)个元素
。
Zset:和set类型基本一致,不过它会给每个元素关联一个double类型的分数(score),这样就可以为成员排序,并且插入是有序的。
有序集合提供了获取指定分数和元素范围查询、计算成员排名等功能。列表、集合和有序集合三者的异同点:
数据结构 | 是否允许重复元素 | 是否有序 | 有序实现方式 | 应用场景 |
---|---|---|---|---|
列表 | 是 | 是 | 索引下标 | 消息队列等 |
集合 | 否 | 否 | 否 | 标签等 |
有序集合 | 否 | 是 | 分值 | 排行榜系统等 |
2.6.1 有序集合命令
- 1、添加成员
命令是zadd key score member [score member ...]
。
向有序集合user:ranking添加用户tom和他的分数251示例:
- 2、计算成员个数
命令是zcard key
。 - 3、计算某个成员的分数
命令是zscore key member
,获取value的score示例:
- 4、计算成员的排名
命令是zrank key member
和zrevrank key member
,zrank是从分数从低到高返回排名,zrevrank反之。示例:
- 5、删除成员
命令是zrem key member [member ...]
,返回结果为成功删除的个数。 - 6、增加成员的分数
命令是zincrby key increment member
,给tom增加了9分,分数变为了260分示例:
- 7、返回指定排名范围的成员
命令是zrange key start end [withscores]
和zrevrange key start end [withscores]
,有序集合是按照分值排名的,zrange是从低到高返回,zrevrange反之。
返回排名最低的是三个成员,如果加上withscores选项,同时会返回成员的分数示例:
- 8、返回指定分数范围的成员
命令是zrangebyscore key min max [withscores] [limit offset count]
和zrevrangebyscore key max min [withscores] [limit offset count]
,zrangebyscore按照分数从低到高返回,zrevrangebyscore反之。示例:
- 9、返回指定分数范围成员个数
命令是zcount key min max
,示例:
- 10、删除指定排名内的升序元素
命令是zremrangebyrank key start end
,示例:
- 11、删除指定分数范围的成员
命令是zremrangebyscore key min max
。将250分以上的成员全部删除,返回结果为成功删除的个数示例:
2.6.2 有序列表内部编码
zset类似于Java的SortedSet和HashMap的结合体,一方面它是一个set,保证了内部value的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。它的内部实现用的是一种叫着「跳跃列表」的数据结构。
zset中最后一个value被移除后,数据结构自动删除,内存被回收。
有序集合类型的内部编码有两种:
- ziplist(压缩列表)
当有序集合的元素个数小于zset-max-ziplist-entries配置(默认128个),同时每个元素的值都小于zset-max-ziplist-value配置(默认64字节)时,Redis会用ziplist来作为有序集合的内部实现,ziplist可以有效减少内存的使用。 - skiplist(跳跃表)
当ziplist条件不满足时,有序集合会使用skiplist作为内部实现,因为此时ziplist的读写效率会下降。
2.6.3 有序列表使用场景
有序集合比较典型的使用场景就是排行榜系统。例如视频网站需要对用户上传的视频做排行榜,榜单的维度可能是多个方面的:按照时间、按照播放数量、按照获得的赞数等。
2.7 key管理命令
2.7.1 单个key管理
- 1、键重命名
命令为rename key newkey
。
如果在rename之前,键java已经存在,那么它的值也将被覆盖,示例:
127.0.0.1:6379> set a b
OK
127.0.0.1:6379> set c d
OK
127.0.0.1:6379> rename a c
OK
127.0.0.1:6379> get a
(nil)
127.0.0.1:6379> get c
"b"
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖,例如下面操作renamenx时,newkey=python已经存在,返回结果是0代表没有完成重命名,所以键java和python的值没变:
127.0.0.1:6379> set java jedis
OK
127.0.0.1:6379> set python redis-py
OK
127.0.0.1:6379> renamenx java python
(integer) 0
127.0.0.1:6379> get java
"jedis"
127.0.0.1:6379> get python
"redis-py"
在使用重命名命令时,注意事项:
- 由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性,这点不要忽视。
- 如果rename和renamenx中的key和newkey如果是相同的,在Redis3.2和之前版本返回结果略有不同。Redis3.2中会返回OK,Redis3.2之前的版本会提示错误。
- 2、随机返回一个键
命令为randomkey
。 - 3、键过期
除了expire、ttl命令以外,Redis还提供了expireat、pexpire、pexpireat、pttl、persist等一系列命令。
expire key seconds
:键在seconds秒后过期。
expireat key timestamp
:键在秒级时间戳timestamp后过期。
ttl命令和pttl都可以查询键的剩余过期时间,但是pttl精度更高可以达到毫秒级别,有3种返回值:
大于等于0的整数:键剩余的过期时间(ttl是秒,pttl是毫秒)。
-1:键没有设置过期时间。
-2:键不存在。
expireat命令可以设置键的秒级过期时间戳。
除此之外,Redis2.6版本后提供了毫秒级的过期方案:
pexpire key milliseconds
:键在milliseconds毫秒后过期。
pexpireat key milliseconds-timestamp
:键在毫秒级时间戳timestamp后过期。
但无论是使用过期时间还是时间戳,秒级还是毫秒级,在Redis内部最终使用的都是pexpireat。
在使用Redis相关过期命令时,需要注意的点:
- 如果expire key的键不存在,返回结果为0。
- 如果过期时间为负值,键会立即被删除,犹如使用del命令一样。
- persist命令可以将键的过期时间清除。
对于字符串类型键,执行set命令会去掉过期时间
。- Redis不支持二级数据结构(例如哈希、列表)内部元素的过期功能,例如不能对列表类型的一个元素做过期时间设置。
- setex命令作为set+expire的组合,不但是原子执行,同时减少了一次网络通讯的时间。
- 4、迁移键
Redis提供了move、dump+restore、migrate三组迁移键的方法。
move key db
Redis内部可以有多个数据库,彼此在数据上是相互隔离的,该命令就是把指定的键从源数据库移动到目标数据库中,不建议在生产环境使用。
dump keyrestore key ttl value
dump+restore可以实现在不同的Redis实例之间进行数据迁移的功能,整
个迁移的过程分为两步:
1)在源Redis上,dump命令会将键值序列化,格式采用的是RDB格式。
2)在目标Redis上,restore命令将上面序列化的值进行复原,其中ttl参数代表过期时间,如果ttl=0代表没有过期时间。
有关dump+restore有两点需要注意:第一,整个迁移过程并非原子性的,而是通过客户端分步完成的。第二,迁移过程是开启了两个客户端连接,所以dump的结果不是在源Redis和目标Redis之间进行传输。
migrate host port key|"" destination-db timeout [copy] [replace] [keys key [key ...]]
migrate命令也是用于在Redis实例间进行数据迁移的,实际上migrate命令就是将dump、restore、del三个命令进行组合,从而简化了操作流程。migrate命令具有原子性,而且从Redis3.0.6版本以后已经支持迁移多个键的功能,有效地提高了迁移效率,migrate在10.4节水平扩容中起到重要作用。
migrate的实现过程和dump+restore基本类似,但是有3点不太相同:第一,整个过程是原子执行的,不需要在多个Redis实例上开启客户端的,只需要在源Redis上执行migrate命令即可。第二,migrate命令的数据传输直接在源Redis和目标Redis上完成的。第三,目标Redis完成restore后会发送OK给源Redis,源Redis接收后会根据migrate对应的选项来决定是否在源Redis上删除对应的键。
move、dump+restore、migrate三个命令比较:
2.7.2 遍历键
Redis提供了两个命令遍历所有的键,分别是keys和scan。
- 1、全量遍历键
命令为keys pattern
。简单使用示例:
127.0.0.1:6379> keys *
1) "hill"
2) "jedis"
3) "redis"
如果Redis包含了大量的键,执行keys命令很可能会造成Redis阻塞,所以一般建议不要在生产环境下使用keys命令。有时候确实需要遍历键,有以下3种做法:
- 在一个不对外提供服务的Redis从节点上执行,这样不会阻塞到客户端的请求,但是会影响到主从复制。
- 如果确认键值总数确实比较少,可以执行keys命令。
- 使用scan命令渐进式的遍历所有键,可以有效防止阻塞。
- 2、渐进式遍历
scan
采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是O(1),但是要真正实现keys的功能,需要执行多次scan。
scan cursor [match pattern] [count number]
除了scan以外,Redis提供了面向哈希类型、集合类型、有序集合的扫描遍历命令,解决诸如hgetall、smembers、zrange可能产生的阻塞问题,对应的命令分别是hscan、sscan、zscan,它们的用法和scan基本类似。
2.7.3 数据库管理
- 1、切换数据库
命令为select dbIndex
。Redis默认配置中是有16个数据库,索引为0到15。一般一个Redis实例中只使用索引为0的数据库即可。建议如果要使用多个数据库功能,完全可以在一台机器上部署多个Redis实例,彼此用端口来做区分,因为现代计算机或者服务器通常是有多个CPU的。这样既保证了业务之间不会受到影响,又合理地使用了CPU资源。 - 2、清除数据库
flushdb/flushall命令用于清除数据库,两者的区别的是flushdb只清除当前数据库,flushall会清除所有数据库。
如果在0号数据库执行flushdb,1号数据库的数据依然还在。在任意数据库执行flushall会将所有数据库清除。
2.8 容器型数据结构的通用规则
list/set/hash/zset这四种数据结构是容器型数据结构,它们共享下面两条通用规则:
- 1 、create if not exists
如果容器不存在,那就创建一个,再进行操作。比如rpush操作刚开始是没有列表的,Redis就会自动创建一个,然后再rpush进去新元素。 - 2 、drop if no elements
如果容器里元素没有了,那么立即删除元素,释放内存。这意味着lpop操作到最后一个元素,列表就消失了。
三、数据类型的比较及应用
3.1 Redis有哪些数据类型
Redis主要有5种数据类型,包括String,List,Set,Zset,Hash,满足大部分的使用要求:
数据类型 | 常用命令 | 可以存储的值 | 操作 | 应用场景 |
---|---|---|---|---|
STRING | set,get,decr,incr,mget 等 | 字符串、整数或者浮点数 ;字符串形式的对象 |
对整个字符串或者字符串的其中一部分执行操作;对整数和浮点数执行自增或者自减操作
|
做简单的键值对缓存。 常规计数:微博数,粉丝数等。 |
LIST | lpush,rpush,lpop,rpop,lrange | 列表(固定顺序) |
从两端压入或者弹出元素; 对单个或者多个元素进行修剪,只保留一个范围内的元素 |
存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的数据
|
SET | sadd,spop,smembers,sunion | 无序集合 |
添加、获取、移除单个元素; 检查一个元素是否存在于集合中; 计算交集、并集、差集; 从集合里面随机获取元素 |
交集、并集、差集的操作 ,如共同关注、共同粉丝、共同喜好等
|
HASH | hget,hset,hgetall 等 | 包含键值对的无序散列表 |
添加、获取、移除单个键值对; 获取所有键值对; 检查某个键是否存在 |
结构化的数据,比如一个对象。可以用Hash数据结构来存储用户信息,商品信息等等 |
ZSET | zadd,zrange,zrem,zcard | 有序集合(顺序可以动态改变) |
添加、获取、删除元素; 根据分值范围或者成员来获取元素; 计算一个键的排名 |
去重并且可以排序,如获取排名前几名的用户 。在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等
|
3.2 Redis的应用场景(从数据类型考虑)
3.2.1 字符串的使用场景
string
——适合最简单的k-v存储
,类似于memcached的存储结构,短信验证码缓存、限流、计数器、分布式锁、分布式Session
等,就用这种类型来存储。
- 1、缓存
较典型的缓存使用场景:Redis作缓存层,MySQL作存储层,绝大部分请求的数据都是从Redis中获取。由于Redis具有支撑高并发的特性,所以缓存通常能起到提高读写性能和降低后端压力的作用。示例:
在使用Redis做缓存时,就涉及到了key的设计。简单来说,key的设计,可以参考表名:主键名:主键值:列名
。 - 2、计数
Redis作为计数的基础工具,可以实现快速计数(并将数据异步存储到数据库)的功能。例如用户每播放一次视频,相应的视频播放数就会自增1:
- 3、共享Session
可以使用 Redis 来统一存储多台应用服务器的会话信息。一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。 - 4、限速
很多网站出于安全考虑,会在登录时,让用户输入手机验证码,并限制用户每分钟获取验证码的频率,伪代码:
- 5、分布式锁
在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。可以使用Redis自带的setnx
命令实现分布式锁,还可以使用官方提供的RedLock分布式锁实现。
3.2.2 哈希的使用场景
hash
一般key为ID或者唯一标示,value对应的就是详情了。如商品详情,个人信息详情,新闻详情
等。
比如有关系型数据表记录的两条用户信息:
id | name | age | city |
---|---|---|---|
1 | zhangsan | 25 | luoyang |
2 | lisi | 26 | kaifeng |
如果用哈希类型来缓存的话:
相比于使用字符串序列化缓存用户信息,哈希类型变得更加直观,并且在更新操作上会更加便捷。比如可以将每个用户的id定义为键后缀,多对field-value对应每个用户的属性。伪代码:
3.2.3 列表的使用场景
list——因为list是有序的,比较适合存储一些有序且数据相对固定的数据。如省市区表、字典表
等。因为list是有序的,适合根据写入的时间来排序,如:最新的XXX,消息队列
等。
- 1、消息队列
Redis的lpush+brpop命令组合即可实现阻塞队列,生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素。 - 2、复杂对象列表
以文章为例,每个用户有属于自己的文章列表,现需要分页展示文章列表。此时可以考虑使用列表,因为列表不但是有序的,同时支持按照索引范围获取元素。
每篇文章使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content:
向用户文章列表添加文章,user:{id}:articles
作为用户文章列表的键:
3.2.4 集合的使用场景
set
——可以简单的理解为ID-List的模式,如微博中一个人有哪些好友,set最牛的地方在于,可以对两个set提供交集、并集、差集操作。例如:赞、踩、标签、查找两个人共同的好友
等。
3.2.5 有序集合的使用场景
Sorted Set
——是set的增强版本,增加了一个score参数,自动会根据score的值进行排序。比较适合类似于top 10等不根据插入的时间来排序的数据(排行榜)
。
3.3 Redis数据类型及命令的相关问题
3.3.1 Redis中对于过期时间的应用
Redis中可以使用expire命令设置一个键的生存时间,到时间后Redis会自动删除,其应用场景:
- 设置限制的优惠活动的信息;
- 一些及时需要更新的数据,如:积分排行榜;
- 手机验证码的时间;
- 限制网站访客访问频率。
3.3.2 秒杀系统的简单设计
- 提前预热数据,放入Redis;
- 商品列表放入List;
- 商品的详情数据 Hash保存,设置过期时间;
- 商品的库存数据Zset保存;
- 用户的地址信息Set保存;
- 订单产生扣库存通过Redis制造分布式锁,库存同步扣除;
- 订单产生后发货的数据,产生List,通过消息队列处理;
- 秒杀结束后,再把Redis数据和数据库进行同步。
3.3.3 在Redis中存对象的几种方式
- 1、原生字符串类型(每个属性一个键)
set user:1:name tomset user:1:age 23set user:1:city beijing
优点:简单直观,每个属性都支持更新操作。
缺点:占用过多的键,内存占用量较大,同时用户信息内聚性比较差
,所以此种方案一般不会在生产环境使用。
- `2、序列化字符串类型(将用户信息序列化后用一个键保存)
set user:1 serialize(userInfo)
优点:简化编程,如果合理的使用序列化可以提高内存的使用效率。
缺点:序列化和反序列化有一定的开销,同时每次更新属性都需要把全部数据取出进行反序列化,更新后再序列化到Redis中。
将对象转换为json字符串后进行存储的方式,也是类似。
- 3、哈希类型(每个用户属性使用一对field-value,但是只用一个键保存)
hmset user:1 name tomage 23 city beijing
优点:简单直观,如果使用合理可以减少内存空间的使用。
缺点:要控制哈希在ziplist和hashtable两种内部编码的转换,hashtable会消耗更多内存。
3.3.4 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys
命令可以扫出指定模式的key列表。如果这个Redis正在给线上的业务提供服务,那使用keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕服务才能恢复。
这个时候可以使用scan
指令,scan指令可以无阻塞的提取出指定模式的key列表,会有一定的重复概率,在客户端做一次去重就可以了。
用scan
所花费的时间,比用keys
所花费的时间长。
3.3.5 一个Redis实例最多能存放多少的keys?List、Set、Sorted Set最多能存放多少元素?
理论上Redis可以处理多达232 的 keys,并且在实际中进行了测试,每个实例至少存放了2亿5千万的keys。我们正在测试一些较大的值。任何list、set、和 sorted set都可以放232个元素。换句话说,Redis的存储极限是系统中的可用内存值。
3.3.6 怎么使用Redis做异步队列?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一段时间,然后再检查有没有信息。如果不想sleep的话,可以使用blpop,在没有信息的时候,会一直阻塞,直到信息的到来。Redis可以通过pub/sub主题订阅模式实现一个生产者、多个消费者,当然也存在一定的缺点,当消费者下线时,生产的消息会丢失。
3.3.7 Redis如何实现延时队列
使用SortedSet,使用时间戳做score,消息内容作为key,调用zadd来生产消息,消费者使用zrangbyscore获取n秒之前的数据做轮询处理。
3.4 键值设计
3.4.1 key名设计
- 【建议】: 可读性和可管理性
以业务名(或数据库名)为前缀(防止key冲突),用冒号分隔,比如业务名:表名:id,示例:ugc:video:1
。 - 【建议】:简洁性
保证语义的前提下,控制key的长度,当key较多时,内存占用也不容忽视,示例:user:{uid}:friends:messages:{mid}
简化为u:{uid}:fr:m:{mid}
。 - 【强制】:不要包含特殊字符
反例:包含空格、换行、单双引号以及其他转义字符。
3.4.2 value设计
- 【强制】:拒绝太大的字符串
string类型控制在10KB以内,hash、list、set、zset元素个数不要超过5000。 - 【推荐】:选择适合的数据类型。
反例:
set user:1:name tom
set user:1:age 19
set user:1:favor football
正例:
hmset user:1 name tom age 19 favor football
3.4.3 控制key的生命周期
建议使用expire设置过期时间(条件允许可以打散过期时间,防止集中过期),不过期的数据重点关注idletime(空闲时间)。
四、Redis工作原理
4.1 Redis线程模型
Redis 基于 Reactor(事件驱动模型) 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器
。
Redis的处理流程:
Redis客户端对服务端的每次调用都经历了【发送命令
,执行命令
,返回结果
】三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。
多个客户端发送的命令的执行顺序是不确定的,但是不会有两条命令被同时执行,不会产生并发问题
,这就是Redis的单线程基本模型。
文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。
4.2 Redis为什么这么快
- 1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速
像Mysql这样的成传统关系型数据库是索引文件存储来内存,数据文件存储在硬盘的,那么硬盘的性能和瓶颈将会影响到数据库。
硬盘型数据库工作模式:
内存型数据库工作模式:
本身内存和硬盘的读写方式不相同导致了它们在读写性能上的巨大差异。 - 2、数据结构简单,对数据操作也简单
- 3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗CPU
这里的单线程指的是,Redis处理网络请求的时候只有一个线程,而不是整个Redis服务是单线程的。
Redis4.0之前一直采用单线程的主要原因有以下三个:
- 使用单线程模型,Redis的开发和维护更简单,因为单线程模型方便开发和调试;
- 即使使用单线程模型也并发的处理多客户端的请求,主要使用的是多路复佣和非阻塞 IO;
- 对于Redis系统来说,主要的性能瓶颈是内存或者网络带宽而并非CPU。
- 4、使用多路I/O复用模型,非阻塞IO
这里的I/O指的是网络I/O,多路指的是多个网络连接,复用指的是复用一个线程。Redis使用多路 I/O 复用模型的大致流程如下:
- 在Redis中的I/O多路复用程序会
监听多个客户端连接的Socket
。- 每当有客户端通过Socket流向Redis发送请求进行操作时,I/O多路复用程序会
将请求放入一个队列
。- 同时I/O多路复用程序会
同步、有序、每次传送一个任务给处理器处理
。- I/O多路复用程序会在上一个请求处理完毕后再继续分派下一个任务。
用图表示多路 I/O 复用模型的流程,如下:
五、布隆过滤器
布隆过滤器 (Bloom Filter) 是专门用来解决去重问题的。它在起到去重的同时,在空间上还能节省90%以上,只是有一定的误判概率。
布隆过滤器可以理解为一个不怎么精确的set结构,当你使用它的contains方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
5.1 布隆过滤器的基本使用
布隆过滤器有二个基本指令,bf.add
添加元素,bf.exists
查询元素是否存在,它的用法和set集合的sadd和sismember差不多。bf.add只能一次添加一个元素,如果想要一次添加多个,就需要用到bf.madd指令。同样如果需要一次查询多个元素是否存在,就需要用到bf.mexists指令。示例:
127.0.0.1:6379> bf.add codehole user1
(integer) 1
127.0.0.1:6379> bf.exists codehole user1
(integer) 1
127.0.0.1:6379> bf.madd codehole user4 user5 user6
1) (integer) 1
2) (integer) 1
3) (integer) 1
- 如何测量误判率
先随机出一堆字符串,然后切分为2组,将其中一组塞入布隆过滤器,然后再判断另外一组的字符串存在与否,取误判的个数和字符串总量一半的百分比作为误判率。
Redis中的布隆过滤器可以自定义参数,需要在add之前使用bf.reserve指令显式创建。如果对应的key已经存在,bf.reserve 会报错。bf.reserve有三个参数,分别是key、error_rate和initial_size。错误率越低,需要的空间越大。initial_size参数表示预计放入的元素数量,当实际数量超出这个数值时,误判率会上升。
因此,如果要避免超出导致误判率升高,可以提前设置一个较大的数值。如果不使用bf.reserve,默认的error_rate是0.01,默认的initial_size是100。
布隆的概率是有误差的,只要不比预计误判率高太多,都是正常现象。
布隆过滤器的initial_size估计的过大,会浪费存储空间,估计的过小,就会影响准确率,用户在使用之前一定要尽可能地精确估计好元素数量,还需要加上一定的冗余空间以避免实际元素可能会意外高出估计值很多。
布隆过滤器的error_rate越小,需要的存储空间就越大,对于不需要过于精确的场合,error_rate设置稍大一点也可以。比如在新闻去重上而言,误判率高一点只会让小部分文章不能让合适的人看到,文章的整体阅读量不会因为这点误判率就带来巨大的改变。
5.2 布隆过滤器的原理
每个布隆过滤器对应到Redis的数据结构里面就是一个大型的位数组和几个不一样的无偏hash函数。所谓无偏就是能够把元素的hash值算得比较均匀。
向布隆过滤器中添加key时,会使用多个hash函数对key进行hash算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个hash函数都会算得一个不同的位置。再把位数组的这几个位置都置为1就完成了add操作。
向布隆过滤器询问key是否存在时,跟add一样,也会把hash的几个位置都算出来,看看位数组中这几个位置是否都位1,只要有一个位为0,那么说明布隆过滤器中这个key不存在。如果都是1,这并不能说明这个key就一定存在,只是极有可能存在,因为这些位被置为1可能是因为其它的key存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。
使用时不要让实际元素远大于初始化大小,当实际元素开始超出初始化大小时,应该对布隆过滤器进行重建,重新分配一个size更大的过滤器,再将所有的历史元素批量add进去 (这就要求我们在其它的存储器中记录所有的历史元素)。因为error_rate不会因为数量超出就急剧增加,这就给我们重建过滤器提供了较为宽松的时间。
5.3 布隆过滤器的其它应用
在爬虫系统中,需要对URL进行去重,已经爬过的网页就可以不用爬了。但是URL太多了,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。
邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。
Redis(一)数据类型、常用命令相关推荐
- Redis五大数据类型常用命令
Redis五大数据类型 Redis-Key 1.查看当前数据库的key keys * #返回当前数据库下的所有key 2.设置key set key value 3.获取key的value get k ...
- Redis五大数据类型常用命令与使用场景总结
常用五种数据类型 字符串string 哈希hash 列表list 集合set 有序集合zset ----------字符串string---------- 常见命令 set key value mse ...
- redis sorted_set数据类型常用命令及跳表skip_list原理
- redis set数据类型常用命令及应用场景
- redis hash数据类型常用命令
可以对field进行数值计算:hincrbyfloat sean age -1 应用场景:点赞.收藏等.
- 解析Redis操作五大数据类型常用命令
摘要:分享经常用到一些命令和使用场景总结,以及对Redis中五大数据类型如何使用cmd命令行的形式进行操作的方法. 本文分享自华为云社区<Redis操作五大数据类型常用命令解析>,作者:灰 ...
- redis 介绍和常用命令
redis 介绍和常用命令 redis简介 Redis 是一款开源的,基于 BSD 许可的,高级键值 (key-value) 缓存 (cache) 和存储 (store) 系统.由于 Redis 的键 ...
- Redis简介、常用命令及优化
文章目录 一.关系数据库与非关系型数据库概述 1.1 关系型数据库 1.2 非关系型数据库 二.关系数据库与非关系型数据库区别 2.1 数据存储方式不同 2.2 扩展方式不同 2.3 对事务性 ...
- 笔记【Redis数据结构、常用命令、key淘汰及持久化策略】
文章目录 简要介绍 服务安装 数据结构 通用命令 字符串类型命令 散列类型命令 列表类型命令 集合类型命令 有序集合类型命令 HyperLogLog命令 GeoHash命令(地图坐标) 排序命令 事务 ...
- git、linux、redis基础及常用命令
Git 分布式版本控制工具 课程内容 Git概述 Git代码托管服务 Git常用命令 在IDEA中使用Git 1. 前言 1.1 什么是Git Git是一个分布式版本控制工具,主要用于管理开发过程中的 ...
最新文章
- 找出数组中不重复的一个数
- 自定义注解完成数据库切库
- html中设置data-*属性值 并在js中进行获取属性值
- matlab拉普拉斯算子边缘提取_使用平面光学器件进行图像边缘的差分检测
- 人脸对齐(四)--CLM算法及概率图模型改进
- hibernate HQL添加语句
- win10 计算机 权限,介绍电脑windows10管理员权限开启的4种方法
- 计算机主板别称是什么城,上海别称什么城?
- python课程设计的心得体会_数据库课程设计心得体会精选篇
- 超定方程组及其求解方法
- 统计数字liuseroj.picp.io
- 大数据笔记(三):HDFS集群搭建-伪分布式模式
- 超五类双绞线,六类双绞线,七类双绞线的细微区别
- 66ccff.xyz
- linux 命令 tr 详解
- 洛谷【2142】高精度减法
- 网易互联网(网易严选)测试开发工程师
- 原来ChatGPT可以充当这么多角色
- 安卓APP证书Android签名证书.keystore文件制作生成
- 输入学生学号、成绩,并排序