Redis是C语言开发的一个高性能键值对(key -value) 内存数据库,可以用作数据库,缓存和消息中间件等。
Redis是C语言开发的一个高性能键值对(key -value) 内存数据库,可以用作数据库,缓存和消息中间件等。
作为内存数据库,它的性能非常优秀,数据存储在内存当中,读写速度非常快,支持并发10W QPS(每秒查询次数),单进程单线程,是线程安全的,采用IO多路复用机制。
丰富的数据类型,支持字符串,散列,列表,集合,有序集合等,支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载。
主从复制,哨兵,高可用,可用作分布式锁。可以作为消息中间件使用,支持发布订阅。
和Java的数据结构类比:
Redis | Java |
---|---|
string | String |
hash | HashMap |
list | LinkedList |
set | HashSet |
sorted set | TreeSet |
string是最基本的数据类型,跟 mencached 一样的类型,也是二进制安全的,可以包含任何数据
如:jpg图片或者序列化的对象。最大能存储512MB。
特性
最大能存储容量:
512MB
数值计算最大范围:
Java中long的最大值 2^32-1
数据未获取到:
为nil相当于null
表示运行结果是否成功:
integer 0 -> false 失败
integer 1 -> true 成功
表示运行结果值:
integer 3 -> 3条
integer 1 -> 1条
关系型数据库数据在Redis中 key 热点数据命名惯例:
大部分数据都是从数据库来的:所以
一般的命名规范都会是:key[表名:主键名:主键值:字段名] value[xxxx]
表名 :主键名 :主键值 :字段名
order :id : 11111 : name
equip :id : 11111 : type
news :id : 11111 : title
用法:
单个操作set get 多个操作加前缀mset mget (Multiple多个)
根据发送时长和执行时长来判断用单指令还是多指令
// 添加/修改
set key value
// 获取
get key
// 删除 1成功,0失败
del key
// 添加/修改多个
mset key1 value1 key2 value2 ....
// 获取多个
mget key1 key2 ....
// 获取value的数据字符长度
strlen key
// 追加信息到值后面(存在就追加,否则新增)
append key value
大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键ID必须保证统一性,不能重复。
oracle数据库具有sequence设定,可以解决,但是MySQL没有类似机制。
利用Redis 生成ID,主要利用Redis是单线程,所以也可以用来生成唯一ID,当使用的是Redis集群的时候,比如集群中有5台Redis,初始化每台Redis的值为1,2,3,4,5,设置步长为5,并且确定一个不随机的负载均衡策略,能够保证有序唯一。
优点:不依赖数据库,灵活,且性能相对于数据库有一定提高,使用Redis集群策略还能排除单点故障问题,ID天然有序。
缺点:需要引入Redis这个新组件,开发配置工作量大
//设置数值数据增加指定范围的值,如果incr传的负数,会变成减,如果decr传整数,变成加
//每次+1,key+=1
incr key
//每次+指定的值,key+=increment
incrby ket increment
//每次增加指定小数的值
incrbufloat ket increment
//设置数值数据减少指定范围的值
//每次减一,key-=1
decr key
//每次减指定的值,key-=increment
decrby key increment
注意事项:
string在Redis内部为字符串存储,当它进行操作的时候会转类型为数字,如果含有其他非数字字符,则会报错.
超出数值上限也会报错,比如用Java语言的long类型
基本类型:long 二进制位数:64
包装类:java.lang.Long
最小值:Long.MIN_VALUE=-9223372036854775808 (-2的63次方)
最大值:Long.MAX_VALUE=9223372036854775807 (2的63次方-1)
另一篇介绍常见的唯一ID生成方案,数据库自增/UUID/snowflake雪花算法/zookeeper/MongoDB的objectId
Redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所具有时效性限定控制的操作。
微信投票:每四小时只能投一票。
电商热门商品推荐:热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。
热点新闻,新闻网站会出现热点新闻,最大特征是时效性,所以如何控制热点新闻的时效性。
设置数据具有指定的生命周期:
// 设置N秒时间,N秒后失效删除掉数据
setex key seconds values
// 设置N毫秒时间,N毫秒后失效删除掉数据
psetex key milliseconds value
Redis 应用于各种结构型和非结构型高热度数据访问加速
例如微博大V主页显示的粉丝数与微博数量,每个用户访问到其主页都会有这几个数据
Redis 存储格式
// set 用户:id:id号:粉丝 粉丝数
set user:id:11111:fans 20
// set 用户:id:id号:博客 博客数
set user:id:11111:blogs 20
// set 用户:id:id号:关注 关注数
set user:id:11111:focus 20
// set 用户:id:id号 JSON
set user:id:11111 {ID:11111,NAME:大V,fans:20,blogs:20,focus:30}
对于第一种,可以随时利用incr key进行数据更新,
而第二种需要全取出来才可以更新(取数据方便,更新数据麻烦),不过对于这种而言,上面举例的粉丝数多几个少几个用户也不会特别去关注,只需设定刷新策略,来选择用哪种方式去实现。
hash 是一个string类型的key-field-value 映射表,特别适合用来存储对象,和单个string类型不同的是,hash可以对信息的每个属性字段进行单独存储,而string类型则需要对用户对象进行序列化保存,并且以字符串存储整个序列化数据(整存整取)对象类的数据存储如果有比较频繁的更新需求操作会显得笨重,而hash可以进行整存零取,从而节约网络流量,不过内存占用也会相对比string大。
新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
需要的存储结构:一个存储空间保存多个键值对数据
hash类型:底层使用哈希表结构实现数据存储
特性
hash存储结构优化:
hash 类型下的value 只能存储字符串,不允许存储其他数据类型,不存在嵌套现象,如果数据未获取到,则返回nil(NULL)
每个hash可以存储2^32-1个键值对
hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但是hash设计初衷不是为了存储大量对象而设计的,所以不能滥用,更不可以将hash作为对象列表使用。
hgettall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈。
命名惯例
用法
// 单个
hset key field value
// 多个
hmset key field1 value1 field2 value2..
// 单个
hget key field
// 多个
hmget key field1 field2..
hgettall key
hdel key field1
hlen key
hexists key field 1存在 0不存在
// 获取所有的field名
hkeys key
// 获去所有的field-value值
hvals key
// 增加指定的数值
hincrby key field increment
// 增加指定小数的值
ingcrbyfolat field increment
hsetnx key field value
电商网站购物车设计与实现
1、初步设计:
2、改进设计:
上述的设计仅仅在缓存里设置了商品ID和数量,但是一个完整的购物车是除了商品ID和数量之外,还显示一定量的商品信息(标题、介绍、类型之类),按照初步设计来的话,要显示商品信息还得去数据库查,这就造成了数据库压力增大,并没有提高效率,所以商品信息也该缓存起来。
解决:
将每条购物车中的商品记录保存成两条field
field1专用于保存购买数量:
field2专用于保存商品信息:
问题:如果多个用户添加同一件商品到购物车,那么每个购物车都会保存一份同样的商品信息,所以,需要将商品信息独立出来存储,做一个独立的Hash,减少不必要的开支。又回到了之前保存的商品ID-数量这一个设计上面。
解决:
用到了 hsetnx key field value 命令, 如果 key 对应的 field 的没有值,则不添加,否则插入新的 field
上述 string 操作所存储的用户热点信息为例
将
// set 用户:id:id号:粉丝 粉丝数
set user:id:11111:fans 20
// set 用户:id:id号:博客 博客数
set user:id:11111:blogs 20
// set 用户:id:id号:关注 关注数
set user:id:11111:focus 20
和
// set 用户:id:id号 JSON
set user:id:11111 {ID:11111,NAME:大V,fans:20,blogs:20,focus:30}
graph LR
user:id:11111-->JSON
改成利用hash存储:
双十一活动日,销售手机充值卡的商家对移动、联通、电信的30元、50元、100元商品退出抢购活动,每种商品抢购上线100张。
tips : redis 应用于抢购,限购类,限量发放优惠卷,激活码等业务的数据存储设计。
对比string 和 hash
1、列表对象保存的所有字符串元素的长度都小于64字节 2、列表对象保存的元素数量小于512个
特性:
list中保存的数据都是string类型的,数据总容量是有限的,最多2^32-1个元素
list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或者以栈的形式进行入栈出栈操作。(左右操作)
获取全部数据结束索引设置为-1
list可以对数据进行分页操作,通常第一页的信息来自于list,第二页以及更多的信息再通过数据库查询加载,这样可以避免数据库压力过大,提高查询速度,因为一般首先看到的是第一页,后面的页数信息也不一定去看。
技术方案都不会只用一种去解决问题的,都是混合式方案,通过某种技术去解决其中一个点,可能获得的提升也是很大的。
用list比较合适去解决:
举例:使用list结构实现栈和队列。
栈后进先出用lpush+blpop ,
队列先进先出用 lpush+brpop,可应对多客户端消费一个队列。
使用:
r: right右 --> rpush右边插入
l:left左 --> lpush左边插入
lpush key value1 [value2].....lpush key 0 -1 0代表首个元素,-1表示倒数第一个,所以用0 -1可以查全部的元素
lpush key value1 [value2].....
lrange key start stop // 第几位开始-第几位结束
lindex key index // list第几个
llen key // list长度
lpop key
rpop key
blpop key1 [key2] timeout //block 阻塞版本的移除,根据timeout等n秒,再获取数据有数据则取,没有则nil
// 在某个key列表移除count个 value元素
lrem key count value
微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息。
将这条朋友圈信息作为Key,点赞列表作为value,每一个新人点赞都将用rpush 加到value里面,当用户想取消点赞的时候,该怎么办呢?
因为用户位置在过一段时间后,不一定会是在最后,对于这种情况可以用到上面的移除指定数据 lrem 命令lrem list 1 xxx
根据上面的例子:redis 可应用于具有操作先后顺序的数据控制 ,例如消息队列,数据队列,都可以用list来模拟
(用在数据库里的话就是与最新添加的数据按照时间倒序显示差不多 order by time DESC)
个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近的粉丝列在前面
新闻资讯类的网站将最新新闻资讯按照时间顺序展示
集群服务器日志统一顺序输出,将所有集群服务器日志都打到Redis缓存服务器上,再有其他服务器在缓存上读取日志。
list能存储大量数据,而且它还有存储顺序,能用对应的索引访问,看上去是非常好的存储方式,但是list的底层内部存储结构是一个链表结构。 我们知道链表结构的存储效率是很低的,对于这么一个能存储大量数据的,但是读取速度慢的结构,就不合适用了,需要引入一种新的存储结构set
新的存储需求:存储大量的数据,在查询方面提供更高的效率
需要的存储结构:能够保存大量的数据,高校的内部存储机制,便于查询
set类型:与hash存储结构完全相同,仅存储键(key,field)值,不存储值(value = nil),并且值不允许重复(field天然不允许重复,重复就覆盖了之前的)也就是只利用key-field这部分来存储数据
集合中最大的成员数为 2 32 − 1 2^{32}-1 232−1每个集合可存储40多亿个成员)。
value 的空间无法使用,因为开发人员已经决定使用Set这一类型作为存储结构,那么它能调用的API已经规定了,不能跨结构使用
结构对比:
hash类型:
set类型:value 为空
基本操作:
添加数据
// 类型不允许重复,如果添加数据在set中存在,将保留一份,后续数据添加失败
sadd key member1 [member2]
获取全部数据
smembers key
删除数据
srem key member1 [member2]
获取集合数据总量
scard key
判断集合中是否包含指定数据
sismember key member
随机获取集合中指定数量的数据
srandmember key [count]
随机获取集合中的某个数据并将该数据移除集合
spop key [count]
Redis 用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,博主大V推荐
共同好友,可能认识的人,微博XX也关注了它
通过下列指令来获取A ∪ B, A ∩ B
,A - B
用于同类信息的关联搜索:显示共同关注/显示共同好友
求两个集合的交、并、差集
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
求两个集合的交、并、差集并存储到指定集合中
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
将指定数据从原始集合中移动到目标集合中
smove source destination member
OA系统员工有一个或者多个角色,每一种角色对应多种不同业务权限,需要快速的进行业务权限校验
依赖set集合数据不重复的特性,依赖sset集合hash存储结构特征完成数据过滤与快速查询
根据用户ID获取用户所有角色
根据用户所有角色获取用户所有操作权限放入set集合
根据用户所有角色获取用户所有数据入set集合
这里有两种方式来验证权限:
//查询 用户id:01业务的所有权限
smembers userid:01
//判断userid:01集合中是否包含insert这个指定数据
sismember userid:01 insert
对比上面1和2两种方式,我们使用Redis来提供基础数据还是提供校验结果?
第一种是将数据和业务逻辑分离开来,redis只提供数据,业务校验在业务层将结果遍历处理
第二种是将业务校验放在数据提供方进行校验,直接将结果返回
现在流行数据-业务分离,数据是纯粹提供基础数据,但是业务逻辑处理还是在代码里,但是第二种的处理方式是,Redis不仅仅提供了基础数据,而且还将这部分业务校验逻辑一并处理,这种方式的业务耦合度会比较高(按照分层思想,我们不应该将业务逻辑处理放到数据提供层去
),比较推荐第一种做法,该分的还是得分,避免以后扩展业务逻辑的不必要的麻烦
利用 Redis 的Set集合的数据去重特性,记录各种访问数据
网站数据统计,公司网站推广,需要统计网站的PV(访问量),UV(独立访问量),IP(独立IP)。
PV:网站被访问次数,可通过刷新页面提高访问量
这种刷新就加一的数据,可以直接用string类型存储,利用它的incr命令来统计日访问量(PV)
UV:网站被不同用户访问的次数,可通过cookie统计访问量,相同用户切换IP地址,UV不变
建立set模型,记录不同cookie数量(UV)利用scard统计
IP:网站被不同IP地址访问的总次数,可通过IP地址统计访问量,相同IP不同用户访问,IP不变
建立set模型,记录不同IP数量(IP)利用scard统计
利用 Redis 的Set集合的数据去重特性,记录各种访问数据
基于Redis制作网站黑名单,过滤IP地址、设备信息、用户信息等
建立set模型,记录不同名单信息,提供给业务逻辑层进行访问权限判定
数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。
在set的存储结构基础上做改变,不同的是每个元素都会关联一个 double/整数 类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。如果为整数类型则为64位。
有序集合的成员是唯一的,但分数(score)却可以重复。
集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。
set类型:value 为空
sorted_set类型:value 为空,多加一个score,用来做排序用
用法
添加数据
//添加KEY 排序为score1 值为member1
zadd key score1 member1 [score2 member2]
获取全部数据
zrange key start stop
// 获取key里面的数据 0 -1 【WITHSCORES为可选,加上可以显示出每个值对应排序的分数】
zrange key start stop [WITHSCORES]
//翻转获取,倒叙-由大到小,默认由小到大排序
zrevrange key start stop [WITHSCORES]
删除数据
zrem key member [member...]
按条件获取数据
// min与max用于限定搜索查询的条件
// start与stop用于限定查询范围,作用于索引,表示开始和结束索引
// offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量
zrangebyscore key min max [WITHSCORES] [LIMIT]
zrevrangebyscore key max min [WITHSCORES]
条件删除数据
zremrangebyrank key start stop
zremrangebyscore key min max
获取集合数据总量
zcard key
zcount key min max
集合交、并操作
zinterstore destination numkeys key [key ...]
zunionstore destination numkeys key [key ...]
利用 Redis 的sorted_set集合的数据排序特性实现各类榜单,应用于计数器组合排序功能对应的排名
各类投票、网站排名、活跃度统计榜单(百度热榜),游戏亲密度(如王者荣耀的)等等。。。
获取数据对应的索引(排名)
zrank key member
zrevrank key member
score值获取与修改
zscore key member
zincrby key increment member
利用 Redis 应用于定时任务执行顺序管理或任务过期管理
各类VIP体验、开启投票、讨论,限时进行,逾期作废等会过期的信息
对于基于时间线限定的任务处理,将处理时间记录为score值,利用排序功能区分处理的先后顺序
记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间
当新任务加入时,判定并更新当前下一个要处理的任务时间
为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_set。例如1小时内,1天内,周内, 月内,季内,年度等,操作时逐级提升,将即将操作的若干个任务纳入到1小时内处理的队列中
应用于即时任务/消息队列执行管理
任务/消息权重设定应用 当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如 何实现任务权重管理。
对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可