Redis 数据类型
# 一、String(字符串)
# 1.1 介绍
string 是 redis 最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。value其实不仅是String,也可以是数字。string 类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象。string 类型的值(value)最大能存储 512MB。
1
# 1.2 常用命令
赋值
# 1) 语法 SET KEY_NAME VALUE SET 命令用于设置给定 key 的值。如果 key 已经存储值, SET 就覆写旧值,且无视类型。 # 2) 示例 127.0.0.1:6379> set user tchua # 赋值 OK 127.0.0.1:6379> get user # 取值 "tchua" 127.0.0.1:6379> set user xiaohua # 覆盖原有值 OK 127.0.0.1:6379> get user "xiaohua"
1
2
3
4
5
6
7
8
9
10
11
12删除
127.0.0.1:6379> set user huahua OK 127.0.0.1:6379> get user "huahua" 127.0.0.1:6379> del user (integer) 1 127.0.0.1:6379> get user (nil)
1
2
3
4
5
6
7
8自增/自减
# 1 语法 INCR KEY_Name Incr命令将key中储存的数字值增1。如果 key不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR操作. 1) incr key 递增数字 2) incrby key increment 增加指定整数 127.0.0.1:6379> INCRBY id 15 (integer) 15 127.0.0.1:6379> INCRBY id 20 (integer) 35 3) decrby key decrement 减少指定的整数 127.0.0.1:6379> DECRBY id 5 (integer) 30 127.0.0.1:6379> DECRBY id 10 (integer) 20
1
2
3
4
5
6
7
8
9
10
11
12
13
14批量操作
# 设置多值 127.0.0.1:6379> MSET user xiaohua age 10 OK 127.0.0.1:6379> get user "xiaohua" 127.0.0.1:6379> get age "10" # 获取多值 127.0.0.1:6379> mget user age 1) "xiaohua" 2) "10"
1
2
3
4
5
6
7
8
9
10
11
# 1.3 key命名规范
"对象类型:对象id:对象属性",对于多个单词推荐用.分割。如键user:1:friends表示id为1的用户的好友列表.
127.0.0.1:6379> set user:1:friends [xiaohua,dahua,tchua]
OK
127.0.0.1:6379> get user:1:friends
"[xiaohua,dahua,tchua]"
1
2
3
4
5
2
3
4
5
# 1.4 应用场景
缓存功能:字符串最经典的使用场景,redis最为缓存层,Mysql作为储存层,绝大部分请求数据都是
redis中获取,由于redis具有支撑高并发特性,所以缓存通常能起到加速读写和降低 后端压力的作用。
计数器:许多运用都会使用redis作为计数的基础工具,他可以实现快速计数、查询缓存的功能,
同时数据可以一步落地到其他的数据源,使用 INCRBY 完成统计“点击数”,由于单线程,可以避免并发问题。
如: 微博数, 粉丝数。
共享session:出于负载均衡的考虑,分布式服务会将用户信息的访问均衡到不同服务器上,
用户刷新一次访问可能会需要重新登录,为避免这个问题可以用redis将用户session集中管理,
在这种模式下只要保证redis的高可用和扩展性的,每次获取用户更新或查询登录信息
都直接从redis中集中获取。
限速:出于安全考虑,每次进行登录时让用户输入手机验证码,为了短信接口不被频繁访问,
会限制用户每分钟获取验证码的频率。
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 二、Hash(哈希)
# 2.1 介绍
Hash可以看成是一个string类型的field和value的映射表,也就是键值对类型。所以适合存储 值对象的信息,如姓名,年龄,生日等等,每一个hash可以存储4294967295个键值对。
1
# 2.2 常用命令
赋值
# 1) 赋值 127.0.0.1:6379> HSET users user:1 xiaohua age:1 18 bir:1 1992-05-25 (integer) 3 # 2) 获取 127.0.0.1:6379> HGET users user:1 "xiaohua" 127.0.0.1:6379> HGET users age:1 "18" # 3) 插入新值 127.0.0.1:6379> HSET users money:1 18000 (integer) 1 # 4) 获取全部值 127.0.0.1:6379> HGETALL users 1) "user:1" 2) "xiaohua" 3) "age:1" 4) "18" 5) "bir:1" 6) "1992-05-25" 7) "money:1" 8) "18000"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21删除
# 1) 删除具体某一个值 127.0.0.1:6379> HDEL users user:1 (integer) 1 # 2) 删除整个key 127.0.0.1:6379> del users
1
2
3
4
5
# 2.3 应场景
哈希结构相对于字符串序列化缓存信息更加直观,并且在更新操作上更加便捷。所以常常用于用户信息等管理,但是哈希类型和关系型数据库有所不同,哈希类型是稀疏的,而关系型数据库是完全结构化的,关系型数据库可以做复杂的关系查询,而redis去模拟关系型复杂查询开发困难,维护成本高。
1
# 三、List(列表)
# 3.1 介绍
简单的字符串列表,可以从头部或尾部向redis列表添加元素。
1
特性
1. 列表中的元素是有序的,这就意味着可以通过下标获取某个元素或者某个范围内的元素列表。 2. 列表中的元素是可以重复的。
1
2数据存储方式
1. arraylist(数组的方式):所以根据"索引"去"查询"是比较快的,但是"新增"和"删除"比较慢,因为涉及到"位移"操作。 2. LinkedList方式(双向链表的方式):对每个元素都记录了前后的"指针","插入"和"删除"的时候只是改变元素的"指针"指向即可,所以速度快。 3. 双向链表添加数据 4. 双向链表删除数据
1
2
3
4
# 3.2 常用命令
两端添加
# lpush 左边添加 (栈) 先进后出 127.0.0.1:6379> lpush user_list 1 2 3 4 (integer) 4 127.0.0.1:6379> LRANGE user_list 0 -1 1) "4" 2) "3" 3) "2" 4) "1" # rpush 右边添加 (队列) 先进先出 127.0.0.1:6379> rpush ls 1 2 3 4 (integer) 4 127.0.0.1:6379> LRANGE ls 0 -1 1) "1" 2) "2" 3) "3" 4) "4"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16删除
# 1) 左边删除 127.0.0.1:6379> lpush user_list 1 2 3 4 (integer) 4 127.0.0.1:6379> LPOP user_list "4" 127.0.0.1:6379> LPOP user_list "3" 127.0.0.1:6379> LPOP user_list "2" 127.0.0.1:6379> LPOP user_list "1" # 2) 右边删除 127.0.0.1:6379> lpush user_list 1 2 3 4 (integer) 4 127.0.0.1:6379> RPOP user_list "1" 127.0.0.1:6379> RPOP user_list "2" 127.0.0.1:6379> RPOP user_list "3" 127.0.0.1:6379> RPOP user_list "4"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.3 应用场景
1. twitter 的关注列表、粉丝列表等都可以用 Redis 的 list结构来实现
2. 抢购:使用redis的原子操作来实现这个“单线程”,首先我们把库存存在good_things:1这个列表中,如果有10件库存,就往列表中push10个数。抢购开始后,每来一个用户,就或从good_things:1中pop一个数,表示抢购成功,列表为空时,表示商品已经被抢完。pop操作是原子性的,此时如果有很多用户同时到达,也是依次执行的。
1
2
3
2
3
# 3.4 常用技巧
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpush+ltrim=Capped Collection(有限集合)
lpush+brpop=Message Queue(消息队列)
1
2
3
4
2
3
4
# 四、Set(集合)
# 4.1 介绍
集合类型也是用来保存多个字符串的元素,但和列表不同的是集合中不允许有重复的元素,并且集合中的元素是无序的,不能通过索引下标获取元素,redis除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集,并合理的使用好集合类型,能在实际开发中解决很多实际问题。
1
# 4.2 常用命令
# 1) 增加
127.0.0.1:6379> sadd myset 1 2 3
(integer) 3
# 2) 删除
127.0.0.1:6379> SREM myset 1
(integer) 1
# 3) 获得集合中所有元素
127.0.0.1:6379> SMEMBERS myset
1) "2"
2) "3"
# 4) 判断元素是否存在集合中 0:不存在 1:存在
127.0.0.1:6379> SISMEMBER myset 1
(integer) 0
127.0.0.1:6379> SISMEMBER myset 2
(integer) 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 4.3 应用场景
集合类型比较典型的使用场景,如一个用户对娱乐、体育比较感兴趣,另一个可能对新闻感兴趣,这些兴趣就是标签,有了这些数据就可以得到同一标签的人,以及用户的共同爱好的标签, 这些数据对于用户体验以及曾强用户粘度比较重要。(用户和标签的关系维护应该放在一个事物内执行,防止部分命令失败造成数据不一致)
1
# 4.4 常用技巧
sadd=tagging(标签)
spop/srandmember=random item(生成随机数,比如抽奖)
sadd+sinter=social Graph(社交需求)
1
2
3
2
3
# 五、(Sorted Set)有序集合
# 5.1 介绍
有序集合和集合有着必然的联系,他保留了集合不能有重复成员的特性,但不同得是,有序集合中的元素是可以排序的,但是它和列表的使用索引下标作为排序依据不同的是,它给每个元素设置一个分数,作为排序的依据。
1
# 5.2 常用命令
- 加入元素
127.0.0.1:6379> zadd my-zset 100 num01
(integer) 1
127.0.0.1:6379> zadd my-zset 100 num001
(integer) 1
127.0.0.1:6379> zadd my-zset 101 num02
(integer) 1
127.0.0.1:6379> zadd my-zset 102 num03
(integer) 1
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
查看元素
127.0.0.1:6379> ZRANGE my-zset 0 -1 1) "num001" 2) "num01" 3) "num02" 4) "num03" 127.0.0.1:6379> ZRANGE my-zset 0 -1 withscores 1) "num001" 2) "100" 3) "num01" 4) "100" 5) "num02" 6) "101" 7) "num03" 8) "102"
1
2
3
4
5
6
7
8
9
10
11
12
13
14删除
127.0.0.1:6379> ZREM my-zset num001 (integer) 1
1
2补充
zscore my-zset num01 -- 获得元素的分数 ZINCRBY my-zset 100 num001 -- 增加某个元素的分数 ZRANK my-zset num001 -- 获得元素的排名
1
2
3
# 5.3 应用场景
Redis有序序列的使用场景与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序。当你需要一个有序的并且不重复的集合列表,那么可以选择sorted set数据结构,比如twitter 的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
另外还可以用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。
1
2
2
# 六、总结
# 6.1 各数据类型应用场景
类型 | 简介 | 特性 | 场景 |
---|---|---|---|
String(字符串) | 二进制安全 | 可以包含任何数据,比如jpg图片或者序列化的对象,一个键最大能存储512M | |
Hash(字典) | 键值对集合,即编程语言中的Map类型 | 适合存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值(Memcached中需要取出整个字符串反序列化成对象修改完再序列化存回去) | 存储、读取、修改用户属性 |
List(列表) | 链表(双向链表) | 增删快,提供了操作某一段元素的API | 1、最新消息排行等功能(比如朋友圈的时间线) 2、消息队列 |
Set(集合) | 哈希表实现,元素不重复 | 1、添加、删除、查找的复杂度都是O(1) 2、为集合提供了求交集、并集、差集等操作 | 1、共同好友 2、利用唯一性,统计访问网站的所有独立ip 3、好友推荐时,根据tag求交集,大于某个阈值就可以推荐 |
Sorted Set(有序集合) | 将Set中的元素增加一个权重参数score,元素按score有序排列 | 数据插入集合时,已经进行天然排序 | 1、排行榜 2、带权重的消息队列 |
# 6.2 Redis 特性
1) 性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s(官方测试数据)。
2) 丰富的数据类型 – Redis支持二进制案例的 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。
3) 原子 – Redis的所有操作都是原子性的,意思就是要么成功执行要么失败完全不执行。单个操作是原子性的。多个操作也支持事务,即原子性,通过MULTI和EXEC指令包起来。
4) 丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。
1
2
3
4
2
3
4
# 6.3 公共命令
1) DEL key
2) DUMP key:序列化给定key,返回被序列化的值
3) EXISTS key:检查key是否存在
4) EXPIRE key second:为key设定过期时间
5) TTL key:返回key剩余时间
6) PERSIST key:移除key的过期时间,key将持久保存
7) KEY pattern:查询所有符号给定模式的key
8) RANDOM key:随机返回一个key
9) RANAME key newkey:修改key的名称
10) MOVE key db:移动key至指定数据库中
11) TYPE key:返回key所储存的值的类型
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 七、补充
# 7.1 内存淘汰策略
过期策略
设置一个key后,指定key的过期时间。
set name 123
EXPIRE name 20 -- 将键的生存时间设置为20s
PEXPIRE name 20 -- 将键的生存时间设置为20毫秒
EXPIREAT name timestamp --将键的生存时间设置为timestamp 所指定的时间
TTL name -- 查看key剩余的过期时间(s)
PTTL name -- 查看key剩余的过期时间(毫秒)
PERSIST name -- 移除一个键的过期时间
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
定期删除
定期删除指的是Redis默认每隔100ms就随机抽取一些设置了过期时间的key,检测这些key是否过期,如果过期了就将其删掉,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。
1惰性删除
惰性删除不再是Redis去主动删除,而是在客户端要获取某个key的时候,Redis会先去检测一下这个key是否已经过期,如果没有过期则返回给客户端,如果已经过期了,那么Redis会删除这个key,不会返回给客户端。 所以惰性删除可以解决一些过期了,但没被定期删除随机抽取到的key。但有些过期的key既没有被随机抽取,也没有被客户端访问,就会一直保留在数据库,占用内存,长期下去可能会导致内存耗尽。所以Redis提供了内存淘汰机制来解决这个问题。
1
2
内存淘汰机制
Redis在使用内存达到某个阈值(通过maxmemory配置)的时候,就会触发内存淘汰机制,选取一些key来删除。内存淘汰有许多策略,下面分别介绍这几种不同的策略。
# ## 开启参数
# maxmemory <bytes> 配置内存阈值
# maxmemory-policy noeviction -- 配置淘汰机制
1
2
3
4
2
3
4
淘汰机制
1) noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。默认策略 2) allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。 3) allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 4) volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。 5) volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。 6) volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。
1
2
3
4
5
6LRU
LRU是Least Recently Used的缩写,即最近最少使用。LRU源于操作系统的一种页面置换算法,选择最近最久未使用的页面予以淘汰。在Redis里,就是选择最近最久未使用的key进行删除。
1
# 7.2 缓存失效
缓存雪崩
# 现象 影响轻则,查询变慢,重则当请求并发更高时,出来大面积服务不可用。 # 原因 雪崩就是指缓存中大批量热点数据过期后系统涌入大量查询请求,因为大部分数据在Redis层已经失效,请求渗透到数据库层,大批量请求犹如洪水一般涌入,引起数据库压力造成查询堵塞甚至宕机 # 解决方案 将缓存失效时间分散开,比如每个key的过期时间是随机,防止同一时间大量数据过期现象发生,这样不会出现同一时间全部请求都落在数据库层,如果缓存数据库是分布式部署,将热点数据均匀分布在不同Redis和数据库中,有效分担压力,别一个人扛。
1
2
3
4
5
6缓存穿透
# 现象 就是指用户不断发起请求的数据,在缓存和DB中都没有,比如DB中的用户ID是自增的,但是用户请求传了-1,或者是一个特别大的数字,这个时候用户很有可能就是一个攻击者,这样的功击会导致DB的压力过大,严重的话就是把DB搞挂了。因为每次都绕开了缓存直接查询DB # 解决方案 1、每次系统 A 从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 2、在接口层增加校验,不合法的参数直接返回。不相信任务调用方,根据自己提供的API接口规范来,作为被调用方,要考虑可能任何的参数传值。 3、高级用户布隆过滤器(Bloom Filter),这个也能很好地防止缓存穿透。原理就是利用高效的数据结构和算法快速判断出你这个Key是否在DB中存在,不存在你return就好了,存在你就去查了DB刷新KV再return。
1
2
3
4
5
6缓存击穿
# 现象 跟缓存雪崩类似,但是又有点不一样。雪崩是因为大面积缓存失效,请求全打到DB;而缓存击穿是指一个key是热点,不停地扛住大并发请求,全都集中访问此key,而当此key失效瞬间,持续的大并发就击穿缓存,全都打在DB上。就又引发雪崩的问题。 # 解决方案 1、可以将热点数据设置为永远不过期。 2、基于redis or zookeeper实现互斥锁,等待第一个请求构建完缓存之后,再释放锁,进而其它请求才能通过该key访问数据。
1
2
3
4
5
# 参考文档:
https://juejin.im/post/5d8882c8f265da03951a325e
编辑 (opens new window)
上次更新: 2023/06/09, 09:57:05