Redis 缓存数据库入门教程

Redis是C语言开发的一个高性能键值对(key -value) 内存数据库,可以用作数据库,缓存和消息中间件等。

Redis

Redis是什么

简介

Redis是C语言开发的一个高性能键值对(key -value) 内存数据库,可以用作数据库,缓存和消息中间件等。

特点

  1. 作为内存数据库,它的性能非常优秀,数据存储在内存当中,读写速度非常快,支持并发10W QPS(每秒查询次数),单进程单线程,是线程安全的,采用IO多路复用机制。

  2. 丰富的数据类型,支持字符串,散列,列表,集合,有序集合等,支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载。

  3. 主从复制,哨兵,高可用,可用作分布式锁。可以作为消息中间件使用,支持发布订阅。


Redis 数据类型

和Java的数据结构类比:

Redis Java
string String
hash HashMap
list LinkedList
set HashSet
sorted set TreeSet

1. string (字符串)

介绍:

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  


扩展操作1:Redis解决MySQL分库分表操作的唯一ID问题

场景:

大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键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)
  • Redis所有操作都是原子性的,都是单线程处理所有业务,所以无需担心并发带来的数据问题。

另一篇介绍常见的唯一ID生成方案,数据库自增/UUID/snowflake雪花算法/zookeeper/MongoDB的objectId


扩展操作2:数据时效性问题

Redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所具有时效性限定控制的操作。

场景:
  • 微信投票:每四小时只能投一票。

  • 电商热门商品推荐:热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。

  • 热点新闻,新闻网站会出现热点新闻,最大特征是时效性,所以如何控制热点新闻的时效性。

解决办法:

设置数据具有指定的生命周期:

// 设置N秒时间,N秒后失效删除掉数据
setex key seconds values

// 设置N毫秒时间,N毫秒后失效删除掉数据
psetex key milliseconds value

扩展操作3:主页高频访问信息显示控制

Redis 应用于各种结构型和非结构型高热度数据访问加速

场景:

例如微博大V主页显示的粉丝数与微博数量,每个用户访问到其主页都会有这几个数据

解决办法:

Redis 存储格式

  • 以用户主键和属性作为 key ,数量为 value ,后台设定定时刷新策略
// 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
  • 以用户主键作为 key ,JSON作为 value,定时刷新(也可以用hash类型)
// set 用户:id:id号 JSON
set user:id:11111 {ID:11111,NAME:大V,fans:20,blogs:20,focus:30}

对于第一种,可以随时利用incr key进行数据更新,
而第二种需要全取出来才可以更新(取数据方便,更新数据麻烦),不过对于这种而言,上面举例的粉丝数多几个少几个用户也不会特别去关注,只需设定刷新策略,来选择用哪种方式去实现。


2. hash(哈希):

介绍:

hash 是一个string类型的key-field-value 映射表,特别适合用来存储对象,和单个string类型不同的是,hash可以对信息的每个属性字段进行单独存储,而string类型则需要对用户对象进行序列化保存,并且以字符串存储整个序列化数据(整存整取)对象类的数据存储如果有比较频繁的更新需求操作会显得笨重,而hash可以进行整存零取,从而节约网络流量,不过内存占用也会相对比string大。

新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息

需要的存储结构:一个存储空间保存多个键值对数据

hash类型:底层使用哈希表结构实现数据存储

KEY
VALUE
FIELD1
FIELD2
FIELD3
VALUE1
VALUE2
VALUE3

特性

  • hash存储结构优化:

    • 如果field数量较少,存储结构优化为类数组结构
    • 如果field数量较多,存储结构优化为HashMap结构
  • 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
  • 如果key 对应的 field的没有值,则不添加,否则插入新的field
hsetnx key field value

扩展操作1:购物车设计

场景:

电商网站购物车设计与实现

解决办法:

1、初步设计:

  • 以客户ID作为key,每位客户创建一个hash存储结构存储对应的购物车信息。
  • 将商品编号作为field,购买数量作为value进行存储。
  • 添加商品:追加全新的field与value
  • 浏览:遍历hash
  • 更改数量;自增/增减,设置value值
  • 删除商品:删除field
  • 清空:删除key

2、改进设计:

上述的设计仅仅在缓存里设置了商品ID和数量,但是一个完整的购物车是除了商品ID和数量之外,还显示一定量的商品信息(标题、介绍、类型之类),按照初步设计来的话,要显示商品信息还得去数据库查,这就造成了数据库压力增大,并没有提高效率,所以商品信息也该缓存起来。

解决:

  • 将每条购物车中的商品记录保存成两条field

  • field1专用于保存购买数量:

    • 命名格式:商品ID:数量
    • 保存数据:商品数量
  • field2专用于保存商品信息:

    • 命名格式:商品ID:信息
    • 保存数据:商品信息JSON
用户:用户ID:11111
购物车
商品ID:数量
商品ID:信息

问题:如果多个用户添加同一件商品到购物车,那么每个购物车都会保存一份同样的商品信息,所以,需要将商品信息独立出来存储,做一个独立的Hash,减少不必要的开支。又回到了之前保存的商品ID-数量这一个设计上面。

解决:

用到了 hsetnx key field value 命令, 如果 key 对应的 field 的没有值,则不添加,否则插入新的 field

扩展操作2:用户热点信息

上述 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
用户:ID:ID号:粉丝
粉丝数
用户:ID:ID号:博客
博客数
用户:ID:ID号:关注
关注数

// 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存储:

20
30
40
11111
user:id:11111
VALUE
ID
NAME
fans
blogs
focus
大V

扩展操作3:商家抢购活动

场景:

双十一活动日,销售手机充值卡的商家对移动、联通、电信的30元、50元、100元商品退出抢购活动,每种商品抢购上线100张。

解决方案:
  • 以商家ID作为 key
  • 将参与抢购的商品ID作为 field
  • 将参与抢购的商品数量作为对应的 value
  • 抢购时使用降值的方式控制产品数量(实际业务还有超卖等问题,后面再讨论)

tips : redis 应用于抢购,限购类,限量发放优惠卷,激活码等业务的数据存储设计。

对比string 和 hash

  • string适合用于整个操作(如json方式存储的信息)查询起来比较方便
  • hash 适合用于零散的操作(hash内部又是包含多个field-value)比较适合经常进行修改操作的数据

3. list (列表):

介绍

  • 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
  • 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • list类型:保存多个数据,底层使用双向链表存储结构实现
  • Value对象内部是以LinkedList和ZipList承载。当List的元素个数和单个元素的长度较小时,redis会使用ZipList存储,减少内存的占用,其他情况使用LinkedList
    ZipList(压缩列表)使用条件
    1、列表对象保存的所有字符串元素的长度都小于64字节 2、列表对象保存的元素数量小于512个

特性:

  • list中保存的数据都是string类型的,数据总容量是有限的,最多2^32-1个元素

  • list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或者以栈的形式进行入栈出栈操作。(左右操作)

  • 获取全部数据结束索引设置为-1

  • list可以对数据进行分页操作,通常第一页的信息来自于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

扩展操作1:朋友圈点赞列表

场景:

微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息。

解决办法:

将这条朋友圈信息作为Key,点赞列表作为value,每一个新人点赞都将用rpush 加到value里面,当用户想取消点赞的时候,该怎么办呢?

因为用户位置在过一段时间后,不一定会是在最后,对于这种情况可以用到上面的移除指定数据 lrem 命令lrem list 1 xxx
根据上面的例子:redis 可应用于具有操作先后顺序的数据控制 ,例如消息队列,数据队列,都可以用list来模拟

扩展操作2:用于最新消息展示

场景:

(用在数据库里的话就是与最新添加的数据按照时间倒序显示差不多 order by time DESC)

个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近的粉丝列在前面

新闻资讯类的网站将最新新闻资讯按照时间顺序展示

集群服务器日志统一顺序输出,将所有集群服务器日志都打到Redis缓存服务器上,再有其他服务器在缓存上读取日志。

list能存储大量数据,而且它还有存储顺序,能用对应的索引访问,看上去是非常好的存储方式,但是list的底层内部存储结构是一个链表结构。 我们知道链表结构的存储效率是很低的,对于这么一个能存储大量数据的,但是读取速度慢的结构,就不合适用了,需要引入一种新的存储结构set

4. set (集合):

介绍:

新的存储需求:存储大量的数据,在查询方面提供更高的效率

需要的存储结构:能够保存大量的数据,高校的内部存储机制,便于查询

set类型:与hash存储结构完全相同,仅存储键(key,field)值,不存储值(value = nil),并且值不允许重复(field天然不允许重复,重复就覆盖了之前的)也就是只利用key-field这部分来存储数据

集合中最大的成员数为 23212^{32}-1每个集合可存储40多亿个成员)。

value 的空间无法使用,因为开发人员已经决定使用Set这一类型作为存储结构,那么它能调用的API已经规定了,不能跨结构使用

结构对比:
hash类型:

KEY
VALUE
FIELD1
FIELD2
FIELD3
VALUE1
VALUE2
VALUE3

set类型:value 为空

KEY
VALUE
VALUE1
VALUE2
VALUE3
nil1
nil2
nil3

基本操作:

添加数据

// 类型不允许重复,如果添加数据在set中存在,将保留一份,后续数据添加失败
sadd key member1 [member2]

获取全部数据

smembers key

删除数据

srem key member1 [member2]

获取集合数据总量

scard key

判断集合中是否包含指定数据

sismember key member

随机获取集合中指定数量的数据

srandmember key [count]

随机获取集合中的某个数据并将该数据移除集合

spop key [count]

扩展操作1:数据信息推荐

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  

扩展操作2:业务权限校验

场景:

OA系统员工有一个或者多个角色,每一种角色对应多种不同业务权限,需要快速的进行业务权限校验

解决方案:

依赖set集合数据不重复的特性,依赖sset集合hash存储结构特征完成数据过滤与快速查询

根据用户ID获取用户所有角色

根据用户所有角色获取用户所有操作权限放入set集合

根据用户所有角色获取用户所有数据入set集合

这里有两种方式来验证权限:

  1. 将当前用户:业务的所有权限都查询出来,然后将查询结果在代码里遍历比对当前用户是否拥有此权限
//查询 用户id:01业务的所有权限
smembers userid:01
  1. 直接通过key找到对应的权限返回,有则是1(代表true)
//判断userid:01集合中是否包含insert这个指定数据 
sismember userid:01 insert

对比上面1和2两种方式,我们使用Redis来提供基础数据还是提供校验结果?

第一种是将数据和业务逻辑分离开来,redis只提供数据,业务校验在业务层将结果遍历处理

第二种是将业务校验放在数据提供方进行校验,直接将结果返回

现在流行数据-业务分离,数据是纯粹提供基础数据,但是业务逻辑处理还是在代码里,但是第二种的处理方式是,Redis不仅仅提供了基础数据,而且还将这部分业务校验逻辑一并处理,这种方式的业务耦合度会比较高(按照分层思想,我们不应该将业务逻辑处理放到数据提供层去),比较推荐第一种做法,该分的还是得分,避免以后扩展业务逻辑的不必要的麻烦

扩展操作3:网站访问数据统计

利用 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统计

扩展操作4:网站黑白名单

利用 Redis 的Set集合的数据去重特性,记录各种访问数据

场景:

基于Redis制作网站黑名单,过滤IP地址、设备信息、用户信息等

解决方案:
建立set模型,记录不同名单信息,提供给业务逻辑层进行访问权限判定

5. sorted_set (有序集合):

介绍:

数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式

Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。

在set的存储结构基础上做改变,不同的是每个元素都会关联一个 double/整数 类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。如果为整数类型则为64位。

有序集合的成员是唯一的,但分数(score)却可以重复。

集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。 集合中最大的成员数为 2^32 - 1 (4294967295, 每个集合可存储40多亿个成员)。

set类型:value 为空

KEY
VALUE
VALUE1
VALUE2
VALUE3
nil1
nil2
nil3

sorted_set类型:value 为空,多加一个score,用来做排序用

KEY
VALUE
VALUE1
VALUE2
VALUE3
nil1
nil2
nil3
score1
score2
score3

用法

添加数据

//添加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 ...] 

扩展操作1:网站榜单排序

利用 Redis 的sorted_set集合的数据排序特性实现各类榜单,应用于计数器组合排序功能对应的排名

场景:

各类投票、网站排名、活跃度统计榜单(百度热榜),游戏亲密度(如王者荣耀的)等等。。。

解决方案:

获取数据对应的索引(排名)

 zrank key member 
 zrevrank key member 

score值获取与修改

 zscore key member 
 zincrby key increment member 

扩展操作2:网站会员试用体验

利用 Redis 应用于定时任务执行顺序管理或任务过期管理

场景:

各类VIP体验、开启投票、讨论,限时进行,逾期作废等会过期的信息

解决方案:

对于基于时间线限定的任务处理,将处理时间记录为score值,利用排序功能区分处理的先后顺序
记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间

当新任务加入时,判定并更新当前下一个要处理的任务时间

为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_set。例如1小时内,1天内,周内, 月内,季内,年度等,操作时逐级提升,将即将操作的若干个任务纳入到1小时内处理的队列中

扩展操作3:任务队列权重管理

应用于即时任务/消息队列执行管理

场景:

任务/消息权重设定应用 当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如 何实现任务权重管理。

解决方案:

对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可

应用场景总结:

  1. redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
  2. redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
  3. redis应用于各种结构型和非结构型高热度数据访问加速
  4. redis 应用于购物车数据存储设计
  5. redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
  6. redis 应用于具有操作先后顺序的数据控制
  7. redis 应用于最新消息展示
  8. redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
  9. redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
  10. redis 应用于同类型不重复数据的合并、取交集操作
  11. redis 应用于同类型数据的快速去重
  12. redis 应用于基于黑名单与白名单设定的服务控制
  13. redis 应用于计数器组合排序功能对应的排名
  14. redis 应用于定时任务执行顺序管理或任务过期管理
  15. redis 应用于及时任务/消息队列执行管理
  16. redis 应用于按次结算的服务控制
  17. redis 应用于基于时间顺序的数据操作,而不关注具体时间
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章