本文介绍了Redis,一种开源的内存数据结构存储系统,强调其高性能、多种数据结构支持、内存存储、持久化策略、发布订阅功能及工作原理。
一、Redis的介绍
Redis(Remote Dictionary Server),即远程字典服务,是一个开源的、使用ANSI C语言编写的、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis因其高性能、高并发、丰富的数据类型和灵活的数据操作而广受欢迎。
主要特点:
- 高性能:Redis使用内存存储数据,读写速度极快,官方测试数据显示,读的速度可达110000次/s,写的速度可达81000次/s。
- 数据类型丰富:支持字符串(String)、哈希(Hash)、列表(List)、集合(Set)、有序集合(Sorted Set,简称zset)等多种数据类型,以及HyperLogLogs(基数统计)、Bitmaps(位图)和Geospatial(地理位置)等特殊数据类型。
- 原子性操作:Redis的所有操作都是原子性的,支持对多个操作的原子性执行。
- 持久化:支持RDB(Redis Database)和AOF(Append Only File)两种持久化机制,确保数据在服务器停机后不会丢失。
- 主从同步:支持主从同步,数据可以从主服务器向任意数量的从服务器同步,实现数据冗余和读取操作的可扩展性。
二、redis的常用命令
Redis通用命令
通用指令是部分数据类型的,都可以使用的指令,常见的有:
- KEYS: 查看符合模板的所有key,不建议在生产环境设备上使用
- DEL: 删除一个指定的key
- EXISTS: 判断key是否存在
- EXPIRE: 给一个key设置有效期,有效期到期时该key会被自动删除
- TTL:查看一个KEY的剩余有效期
通过help [command]可以查看一个命令的具体用法,例如:
String类型
string类型,也就是字符串类型,是Redis中最简单的存储类型。其value是字符串,不过根据字符串的格式不同,又可以分为3类:
- string:普通字符串
- int:整数类型,可以做自增、自减操作
- float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.
String的常见命令有:
- SET:添加或者修改已经存在的一个String类型的键值对
- GET:根据key获取String类型的value
- MSET:批量添加多个String类型的键值对
- MGET:根据多个key获取多个String类型的value
- INCR:让一个整型的key自增1
- INCRBY:让一个整型的key自增并指定步长,例如: incrby num 2 让num值自增2
- INCRBYFLOAT:让一个浮点类型的数字自增并指定步长
- SETNX:添加一个String类型的键值对,前提是这个key不存在,否则不执行
- SETEX:添加一个String类型的键值对,并且指定有效期
Hash类型
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。string结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便:
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD:
Hash的常见命令有:
- HSET key field value:添加或者修改hash类型key的field的值
- HGET key field:获取一个hash类型key的field的值
- HMSET:批量添加多个hash类型key的field的值
- HMGET:批量获取多个hash类型key的field的值
- HGETALL:获取一个hash类型的key中的所有的field和value
- HKEYS:获取一个hash类型的key中的所有的field
- HVALS:获取一个hash类型的key中的所有的value
- HINCRBY:让一个hash类型key的字段值自增并指定步长
- HSETNX:添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
List类型
Redis中的List类型与Java中的LinkedList类似,可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征也与LinkedList类似:
- 有序
- 元素可以重复
- 插入和删除快
- 查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
List的常见命令有:
- LPUSH key element ...:向列表左侧插入一个或多个元素
- LPOP key:移除并返回列表左侧的第一个元素,没有则返回nil
- RPUSH key element ...:向列表右侧插入一个或多个元素
- RPOP key:移除并返回列表右侧的第一个元素
- LRANGE key star end:返回一段角标范围内的所有元素
- BLPOP和BRPOP:与LPOP和RPOP类似,只不过在没有元素时等待指定时间,而不是直接返回nil
Set类型
Redis的Set结构与Java中的HashSet类似,可以看做是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:
- 无序
- 元素不可重复
- 查找快
- 支持交集、并集、差集等功能
set的常见命令有:
- SADD key member ...:向set中添加一个或多个元素
- SREM key member ...:移除set中的指定元素
- SCARD key:返回set中元素的个数
- SISMEMBER key member:判断一个元素是否存在于set中
- SMEMBERS:获取set中的所有元素
- SINTER key1 key2 ...:求key1与key2的交集
- SDIFF key1 key2 ...:求key1与key2的差集
- SUNION key1 key2 ..:求key1和key2的并集
SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加 hash表。SortedSet具备下列特性:
- 可排序
- 元素不重复
- 查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
SortedSet的常见命令有:
- ZADD key score member:添加一个或多个元素到sorted set,如果已经存在则更新其score值
- ZREM key member:删除sorted set中的一个指定元素
- ZSCORE key member:获取sorted set中的指定元素的score值
- ZRANK key member:获取sorted set中的指定元素的排名
- ZCARD key:获取sorted set中的元素个数
- ZCOUNT key min max:统计score值在给定范围内的所有元素的个数
- ZINCRBY key increment member: 让sorted set中的指定元素自增,步长为指定的increment值
- ZRANGE key min max:按照score排序后,获取指定排名范围内的元素
- ZRANGEBYSCORE key min max:按照score排序后,获取指定score范围内的元素
- ZDIFF、ZINTER、ZUNION:求差集、交集、并集
注意:所有的排名默认都是升序,如果要降序则在命令的Z后面添加REV即可
三、Redis的数据类型,分别适用什么样的场景
String(字符串)
适用场景:缓存任意类型的数据,如字符串、整数、浮点数等。由于其灵活性和普遍性,String 类型是 Redis 中最常用的数据类型。
示例:缓存用户信息、配置信息、计数器等。
Hash(哈希)
适用场景:存储对象类型的数据,每个字段都有一个对应的值。适用于需要表示一个对象或者映射关系的场景。
示例:存储用户信息(如用户名、密码、邮箱等),或者商品的属性(如名称、价格、库存等)。
List(列表)
适用场景:存储多个有序的字符串,适用于需要保存有序列表的场景,如消息队列、时间轴等。
示例:实现一个消息队列,或者保存用户的浏览历史。
Set(集合)
适用场景:存储多个无序且不重复的字符串,适用于需要快速查找某个元素是否存在于集合中的场景。
示例:用于实现用户标签、好友关系等。
Sorted Set(有序集合)
适用场景:存储多个带分数(score)的字符串,并且根据分数进行从小到大的排序。适用于需要排序的场景,如排行榜、成绩列表等。
示例:实现一个游戏排行榜,或者按照时间顺序排序的日志。
四、redis相对于mysql有什么区别
数据类型区别:
MySQL是关系型数据库,主要用于存放持久化数据,将数据存储在硬盘中,读取速度较慢。
Redis是NOSQL,即非关系型数据库,也是缓存数据库,将数据存储在缓存中,缓存的读取速度快,能够大大的提高运行效率,但是保存时间有限
本质区别:
MySQL:基于磁盘,读写速度没有Redis快,但是不受空间容量限制,性价比高;
Redis:基于内存,读写速度快,也可做持久化,但是内存空间有限,当数据量超过内存空间时,需扩充内存,而内存成本较高;
五、Redis的内存淘汰策略
当 Redis 内存达到最大限制且有新的写入操作需要更多内存时,Redis 会根据配置的内存淘汰策略来决定应该移除哪些数据以释放空间。 主要有以下几种内存淘汰策略:
1. noeviction(默认策略): 不淘汰任何数据,当内存满时,新的写入命令将会报错。
2. volatile-lru:仅针对设置了过期时间的键,淘汰最近最少使用的(Least Recently Used,LRU)键
六、Redis的过期策略
- 定时删除:设置键值对过期时间的同时,创建一个定时器,到期时立即删除。这种策略可以立即释放过期数据占用的内存,但是会占用大量的 CPU 资源去处理定时器。在 Redis 中,这种策略并不是作为主要的过期键处理策略使用的,因为在键很多的时候,会消耗大量的 CPU 资源去处理定时器,这在实际应用中是不可接受的。
- 惰性删除:只有当客户端访问一个键值对时,才会检查该键值对是否过期,如果过期则删除。这种策略 CPU 资源占用小,但是如果过期的键值对一直不被访问,那么它们占用的内存就不会被释放。
- 定期删除:每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键值对。这种策略是前两种策略的折中方案。
七、Redis的持久化
持久化存储:是将redis存储在内存中的数据存储在硬盘中,实现数据永久性保存。
在redis中,持久化存储分为两种:一种是 aof日志追加的方式,另一种是rdb数据快照的方式;
- RDB(Remote Dictionary Base):在指定的时间间隔内,将内存中的数据集快照写入磁盘。这种方式是默认的持久化方式,适用于大规模数据的备份和恢复。
- AOF(Append Only File):以日志的形式记录每个写操作,当Redis重启时,且我们需要数据恢复时,通过重新执行这些写操作来恢复数据。这种方式适用于对数据安全性要求较高的场景。
八、Redis如何跟Mysql数据保持一致
双写一致性问题
Redis作为缓存,它是如何与MySQL的数据保持同步的呢?特别是在追求双写一致性的道路上,我们该如何操作呢?
其实有两种情况需要讨论:
- 数据高度一致性:加锁
- 数据同步可以有延时:
中间件通知(MQ、Canal)
延迟双删(先删除缓存、再修改数据库,经过一段时间,再次删除缓存)
8.1 数据高度一致性
想要达到强一致性,我们可以借助Redisson提供的读写锁
- 共享锁(读锁readLock):一旦加上这个锁,其他线程就可以共享读操作,不会互相干扰,真是好帮手!
- 排他锁(独占锁writeLock):这个锁更霸道,一旦加上,其他线程就别想读写操作了,得乖乖等着。
8.2 数据同步允许延时
中间件通知
- MQ中间件:数据更新后,就通知缓存删除。
- Canal中间件:Canal伪装成MySQL的从节点,通过读取binlog数据来更新缓存,神奇又方便,而且不需要改业务代码
延迟双删
- 延时双删听起来挺高级,但实际操作起来有点复杂。
- 在更新数据库后,首次删除缓存中的数据,然后经过一段预定的延时(通常是几百毫秒到几秒),再次删除缓存中可能仍然存在的数据。
- 这个延时的目的是为了确保数据库的更新操作能够完成,避免旧的缓存数据被重新读取。
- 第一次删除是为了避免在延时期间有新的请求使用到旧的缓存数据,第二次删除则是在数据库更新完成后彻底移除旧数据,以保持数据一致性。
九、Redis过期时间常用命令
1. EXPIRE
EXPIRE key seconds 命令用于设置key的过期时间,单位为秒。如果key已经存在,则更新其过期时间;如果key不存在,则操作无效
EXPIRE mykey 60
这个命令将mykey
的过期时间设置为60秒。
2. PEXPIRE
PEXPIRE mykey milliseconds 命令与 EXPIRE 类似,但它以毫秒为单位设置key的过期时间。
PEXPIRE mykey 60000
这个命令将mykey
的过期时间设置为60000毫秒(即60秒)。
3. EXPIREAT
EXPIREAT key timestamp
命令用于设置key的过期时间,这里的timestamp
是UNIX时间戳(秒)。
EXPIREAT mykey 1609459200
这个命令将mykey
的过期时间设置为UNIX时间戳1609459200
对应的时刻。
4. PEXPIREAT
PEXPIREAT key milliseconds-timestamp
命令与EXPIREAT
类似,但它以毫秒为单位接收时间戳。
PEXPIREAT mykey 1609459200000
这个命令将mykey
的过期时间设置为毫秒时间戳1609459200000
对应的时刻。
注意事项
- 如果key已经过期,那么它将被自动删除。
- 你可以使用
TTL
或PTTL
命令来查看key的剩余生存时间(TTL,Time To Live)。 - 如果在设置过期时间时key不存在,那么
EXPIRE
、PEXPIRE
、EXPIREAT
和PEXPIREAT
命令将不会执行任何操作,并且返回0。 - 需要注意的是,Redis的过期策略是惰性删除加上定期删除。这意味着过期key并不会立即被删除,而是在尝试访问该key时(惰性删除)或Redis的定时任务中(定期删除)被删除。