本系列文章:
Redis进阶之路(一)5种数据类型、Redis常用命令
Redis学习之路(二)Jedis、持久化、事务
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特性
上面是对Redis的一个通常性的介绍,接下来较为详细地总结一下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、主从复制
- 7、高可用和分布式
Redis从2.8版本正式提供了高可用实现Redis Sentinel(哨兵),能够保证Redis节点的故障发现和故障自动转移
。Redis从3.0版本正式提供了分布式实现Redis Cluster(集群),是Redis真正的分布式实现
,提供了高可用、读写和容量的扩展性。
1.2 为什么要用缓存
Redis经常被用来做缓存,那么为什么要用缓存呢?主要从“高性能”和“高并发”这两点来看待这个问题:
- 1、高性能
假如用户第一次访问数据库中的某些数据。这个过程会比较慢,因为是从硬盘上读取的。此时将该用户访问的数据存在数缓存中,这样下一次再访问这些数据的时候就可以直接从缓存中获取了。操作缓存就是直接操作内存,所以速度相当快
。数据库中的对应数据改变之后,同步改变缓存中相应的数据即可。
- 2、高并发
直接操作缓存能够承受的请求是远远大于直接访问数据库的
,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。
1.3 Redis和Map的比较
- 1、
Redis 可以用几十 G 内存来做缓存
,Map 不行,一般 JVM 也就分几个 G 数据就够大了。 - 2、Redis 的缓存可以
持久化
,Map实现的是本地缓存
,最主要的特点是轻量以及快速,但是它们的生命周期随着 jvm 的销毁而结束。 - 3、Redis 可以实现
分布式的缓存
,在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性
。Map实现的是本地缓存,不具有一致性。 - 4、Redis 可以处理
每秒百万级的并发
,是专业的缓存服务,Map 只是一个普通的对象。 - 5、Redis 缓存有
过期机制
,Map无此功能。
1.4 Redis和Memcache的比较
Memcache的多线程模型引入了缓存一致性和锁,加锁带来了性能损耗。所以高并发情况下,有时单线程的 Redis 比多线程的Memcache效率要高。
- 选择Redis的场景:
1、复杂数据结构
,Redis可以存储的数据类型多样,最典型的的使用场景是,用户订单列表,用户消息,帖子评论等, Memcache无法满足这些数据结构。
2、需要进行数据的持久化功能
.
3、高可用,redis 支持集群,可以实现主动复制,读写分离
,而对于 Memcached 如果想要实现高可用,需要进行二次开发。
4、存储的内容比较大(如字符串可存储的最大容量是512M)
,Memcache存储的 value 最大为 1M。 - 选择Memcache的场景: 纯Key-Value,数据量非常大的业务,使用Memcache更合适。原因是:
1、Memcache的内存分配采用的是预分配内存池的管理方式,能够省去内存分配的时间,Redis是临时申请空间,可能导致碎片化。
2、数据量大时,Memcache更快。
3、Memcache使用非阻塞的 IO 复用模型,Redis也是使用非阻塞的 IO 复用模型,但是Redis还提供了一些非键值对存储之外功能,如:排序,聚合等,这些会阻塞整个 IO 调度。从这点上由于 redis 提供的功能较多,Memcache更快些。
4、线程模型,Memcache使用多线程,主线程监听,子线程接受请求,执行读写。Redis使用的单线程,虽然无锁冲突,但是难以利用多核的特性提升吞吐量。
1.5 Redis有哪些优缺点
经过上面的简单的认识,可以总结下Redis的优缺点了。
- Redis的优点:
1、
读写性能优异
, Redis能读的速度是110000次/s,写的速度是81000次/s。
2、支持数据持久化
,支持AOF和RDB两种持久化方式。
3、支持事务
,Redis的所有操作都是原子性的,同时Redis还支持对几个操作合并后的原子性执行。
4、数据结构丰富
,除了支持string类型的value外还支持hash、set、zset、list等数据结构。
5、支持主从复制
,主机会自动将数据同步到从机,可以进行读写分离。
- Redis的缺点:
1、
数据库容量受到物理内存的限制,不能用作海量数据的高性能读写
,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。
2、主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题
,降低了系统的可用性。
3、Redis较难支持在线扩容,在集群容量达到上限时在线扩容会变得很复杂
。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源造成了很大的浪费。
1.6 安装Redis
尽量在Linux上安装Redis,因为真正商用的Redis软件肯定都是安装在Linux环境的。我的电脑太旧,安装了Vmware后,运行起来很卡,只能放弃这个想法。
如果想要在Linux上安装Redis,可以在电脑上装个虚拟机。装虚拟机的方法,可以参考这篇文章:《如何在Windows上通过VMware安装Ubuntu16.04系统?》。关于这种安装方式,我当时安装的版本是VMware-workstation-full-15.5.7-17171714.exe和ubuntu-20.04.2.0-desktop-amd64.iso。
我在Windows环境上安装了Redis,安装方式可参考:《REDIS系列教材 (一)- WINDOWS教程》。
关于Redis安装,就介绍这么多。接下来的内容,如无特殊说明,运行的Redis命令均是在Windows环境进行的。
1.7 Redis的简单使用
Redis分为client和server,启动Server端的方式为:
1、切到对应的Redis目录(我放置Redis的目录为:
D:\Redis
);
2、运行命令redis-server.exe
。
服务端启动后,界面为:
可以看到:当前的Redis版本的是3.0.5,Redis的默认端口是6379。
如果要使用配置文件启动的话,命令是:redis-server redis.windows.conf
。
redis.windows.conf为redis配置文件。需注意文件路径,未明确指定时,表示在redis-server的同路径下。示例:
启动了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实例。
断开Redis客户端连接的命令是:redis-cli shutdown
。
Redis客户端关闭后,要重新连接,先启动一下服务端即可。
二、5种数据类型与常用命令
2.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种返回值:
1)大于等于0的整数:键剩余的过期时间,单位为秒。
2)-1:键没设置过期时间。
-3)2:键不存在。
示例:
对于字符串类型键,执行set命令会去掉该key的过期时间
。示例:
- 7、查看键的数据结构类型的命令是
type key
,示例:
- 8、键重命名的命令是
rename key newkey
,示例:
为了防止被强行rename,Redis提供了renamenx命令,确保只有newKey不存在时候才被覆盖。示例:
由于重命名键期间会执行del命令删除旧的键,如果键对应的值比较大,会存在阻塞Redis的可能性。 - 9、随机返回一个键的命令是
randomkey
,示例:
2.2 字符串
字符串
是Redis最基础的Value类型,Value最大值不能超过512MB
。
- 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
。
- 3、批量设置值的命令是
mset key value [key value ...]
,示例:
- 4、批量获取值的命令是
mget key [key ...]
,示例:
可以看出,如果键不存在,那么它的返回值为nil(空)。
批量操作命令可以提高开发效率。在不使用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.3 哈希
哈希指Value是个键值对,即{field1,value1}
。一个哈希最多可以存储2的32次方 -1个元素
。示例:
哈希类型中的映射关系叫作field-value
。
和操作String的命令相比,对hash的很多操作命令是在String的操作命令之前加了h
。
- 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.4 列表
列表用来存储多个有序的字符串,一个列表最多可以存储2的32次方 -1个元素
。
在Redis中,可以对列表两端插入和弹出,还可以获取指定范围的元素列表、获取指定索引下标的元素等。
列表有两个特点:1、元素是有序
的;2、元素可以是重复
的。
列表的四种操作类型:
操作类型 | 操作 |
---|---|
添加 | 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
,获取当前列表最后一个元素示例:
- 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.5 集合
集合类型也可以用来保存多个字符串,集合中不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。
一个集合最多可以存储(2的32次方 -1)个元素
。Redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。
- 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.6 有序集合
有序集合保留了集合不能有重复成员的特性,并且给每个元素设置一个分数(score)用来排序。一个有序集合最多可以存储(2的32次方 -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.7 假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用keys
命令可以扫出指定模式的key列表。如果这个Redis正在给线上的业务提供服务,那使用keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕服务才能恢复。
这个时候可以使用scan
指令,scan指令可以无阻塞的提取出指定模式的key列表,会有一定的重复概率,在客户端做一次去重就可以了。
用scan
所花费的时间,比用keys
所花费的时间长。
2.8 用Redis怎么实现异步队列
一般使用列表作为队列,rpush
生产消息,lpop
消费消息。当lpop
获取不到消息时,要适当 sleep 一会再重试。
- 1、可不可以不用 sleep
list 还有个指令叫 blpop,在没有消息的时候,它会阻塞住直到消息到来。 - 2、能不能生产一次消费多次
使用 pub/sub 主题订阅者模式,可以实现1:N 的消息队列。 - 3、pub/sub 有什么缺点
在消费者下线的情况下,生产的消息会丢失。
2.9 如果有大量的 key 需要设置同一时间过期,一般需要注意什么?
如果大量的 key 过期时间设置的过于集中,到过期的那个时间点,Redis可能会出现短暂的卡顿现象。一般需要在过期时间上加一个随机值,使得过期时间分散一些
。
三、5种数据的比较及使用
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的设计,可以参考表名:主键名:主键值:列名
。
假如现在要做一个从Redis获取用户信息的功能,伪代码示例:
- 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 中对于过期时间的应用
Redis 中可以使用 expire 命令设置一个键的生存时间,到时间后Redis会自动删除,其应用场景:
- 设置限制的优惠活动的信息;
- 一些及时需要更新的数据,如:积分排行榜;
- 手机验证码的时间;
- 限制网站访客访问频率。
3.4 秒杀系统的简单设计
- 提前预热数据,放入Redis;
- 商品列表放入List;
- 商品的详情数据 Hash保存,设置过期时间;
- 商品的库存数据Zset保存;
- 用户的地址信息Set保存;
- 订单产生扣库存通过Redis制造分布式锁,库存同步扣除;
- 订单产生后发货的数据,产生List,通过消息队列处理;
- 秒杀结束后,再把Redis数据和数据库进行同步。
四、Redis工作原理
4.1 Redis线程模型
Redis 基于 Reactor(事件驱动模型) 模式开发了自己的网络事件处理器,使用 I/O 多路复用程序来同时监听多个套接字,并将到达的事件传送给文件事件分派器,分派器会根据套接字产生的事件类型调用相应的事件处理器
。
Redis的处理流程:
Redis客户端对服务端的每次调用都经历了【发送命令
,执行命令
,返回结果
】三个过程。其中执行命令阶段,由于Redis是单线程来处理命令的,所有每一条到达服务端的命令不会立刻执行,所有的命令都会进入一个队列中,然后逐个被执行。
多个客户端发送的命令的执行顺序是不确定的,但是不会有两条命令被同时执行,不会产生并发问题
,这就是Redis的单线程基本模型。
文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。
4.2 Redis为什么这么快
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速
。
像Mysql这样的成传统关系型数据库是索引文件存储来内存,数据文件存储在硬盘的,那么硬盘的性能和瓶颈将会影响到数据库。
硬盘型数据库工作模式:
内存型数据库工作模式:
本身内存和硬盘的读写方式不相同导致了它们在读写性能上的巨大差异。2、数据结构简单,对数据操作也简单
。3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU
(这里的单线程指的是,Redis处理网络请求的时候只有一个线程,而不是整个Redis服务是单线程的)。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 复用模型的流程,如下: