Redis基础

概念

简介

Redis:Remote Dictionary Server,使用C语言开发的一个开源的高性能键值对(key-value)数据库

特征

  1. 数据间没有必然的关联关系
  2. 内部采用单线程机制进行工作
  3. 高性能。官方提供测试数据,50个并发执行100000个请求,读的速度是110000次/秒,写的速度是81000次/秒
  4. 多数据类型支持
    1. 字符串类型 string
    2. 列表类型 list
    3. 散列类型 hash
    4. 集合类型 set
    5. 有序集合类型 sorted_set
  5. 持久化支持,可以进行数据灾难恢复

应用

  • 为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类高访问量信息等
  • 任务队列,如秒杀、抢购、购票排队等
  • 即时信息查询,如排行榜、各类网站访问统计、公交到站信息、在线人数信息、设备信号等
  • 分布式数据共享,如分布式集群架构中的session分离
  • 消息队列
  • 分布式锁

数据类型

string

  • 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型

  • 存储数据的格式:一个存储空间保存一个数据

  • 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用

    基本操作

  • 添加/修改数据:set key value

  • 获取数据:get key

  • 删除数据:del key

  • 添加/修改多个数据:mset key1 value1 key2 value2 ...

  • 获取多个数据:mge key1 key2 ...

  • 获取数据字符串个数(字符串长度):strlen key

  • 追加信息到原始信息候补(如果原始信息存在就追加,否则新建):append key value

setmset

需要考虑以下几个点:

  • set是一条指令条操作一条数据,mset是一条指令操作多条记录。指令的传输和响应都需要耗时
  • redis是单线程执行指令,在执行指令的时候需要耗时,如果mset包含的数据太多,会阻塞redis
  • 所以:一般少量数据两者影响不大,大量数据分割成合适的大小,再多次使用mset,这样能减少指令传输耗时和减少redis阻塞时间

扩展操作

业务场景A

大型企业级应用中,分表操作时基本操作,使用多张表存储同类型数据,但是对应的主键id必须保证统一性,不能重复。Oracle数据库具有sequence设定,可以解决该问题,但是MySql数据库并不具有类似的机制,怎么解决?

解决方案

  • 设置数值数据增加指定范围的值

    1
    2
    3
    incr key  
    incrby key increment
    incrbyfloat ket increment
  • 设置数值数据减少指定范围的值

    1
    2
    decr key  
    decr key increment

    string作为数值操作

  • string在redis内部存储默认就是一个字符串,当遇到增减类操作incr、decr时会转成数值型进行计算

  • redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响

  • 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis数值上线范围,将报错


业务场景B

活动海选投票,只能通过微信投票,每个微信号每4小时只能投1票。

电商商家开启热门商品推荐,热门商品不能一直处于热门期,每个商品热门期持续3天,3天后自动取消热门

新闻网站会出现热点新闻,热点新闻最大的特征就是时效性,如何自动控制热点新闻的时效性

解决方案

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

    1
    2
    setex key seconds value  
    psetex key milliseconds value

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


业务场景C

主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数和微博数量

解决方案

  • 在redis中为大V用户设定用户信息,以用户主键和属性值为key,后台设定定时刷新策略即可

    1
    2
    3
    eg ->  user:id:544935942:fans  -> 120658  
    eg -> user:id:544935942:blogs -> 6164
    eg -> user:id:544935942:focuss -> 100
  • 在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型)

    1
    eg -> user:id:544935942 -> {id:544935942,name:张三,fans:120658,blogs:6164,focuss:100}  
  • redis应用于各种结构性和非结构性高热度数据访问加速*

key的设置约定

数据库中的热点数据key命名惯例,以:拼接

表名 主键名 主键值 字段名
eg1: order id 20230415 name
eg2: equip id 82295157 type
eg3: news id 544935942 title

注意事项

  • 数据操作不成功的反馈与数据正常操作之间的差异
    • 表示运行结果是否成功
      • (integer)0 -> false -> 失败
      • (integer)1 -> true -> 成功
    • 表示运行结果值
      • (integer)3 -> 表示3个
      • (integer)1 -> 表示1个
  • 数据未获取到
    • (nill) -> null
  • 数据最大存储量
    • 512MB
  • 数值计算有最大范围

hash

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
  • 需要的存储结构:一个存储空间保存多个键值对数据
  • hash存储结构优化
    • 如果field数量较少,存储结构优化为类数组结构
    • 如果field数量较多,存储结构使用HashMap结构

基本操作

  • 添加/修改单条数据:hset key field value
  • 获取指定field数据:hget key field
  • 获取全部field数据:hgetall key
  • 删除数据:hdel key field1 field2 ...
  • 添加/修改多条数据:hmset key field1 value1 field2 value2 ...
  • 获取多条数据:hmget key field1 field2 ...
  • 获取hash表中字段的数量:hlen key
  • 获取hash表中是否存在指定的字段:hexists key field

扩展操作

  • 获取hash表中所有的字段或字段值

    1
    2
    hkeys key  
    hvals key
  • 设置指定字段的数值数据增加指定范围的值

    1
    2
    hincrby key field increment  
    hincrbyfloat key field increment

    注意事项

  • hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象,如果数据未获取到,对应值为nil

  • 每个hash可以存储2的32次方-1个键值对

  • hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性,但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用

应用场景

业务场景A

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

解决方案:

  • 仅分析购物车的redis存储模型:添加、浏览、更改数量、删除、清空

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    用户Id -> Key  
    购物车商品Id/SkuId -> field
    购物车商品数量 -> value

    添加商品 -> hset
    修改数量 -> hset/hincrby
    删除商品 -> hdel
    商品条目数 -> hlen
    商品总数 -> sum(hvals)
    浏览商品 -> hgetall
    • 按照上述的操作方式,可以发现redis中只存储了购物车的商品id和商品数量,并未存储商品信息,这样在查询整个购物车的时候,还需要二次查询商品信息,所以还需要做改进,可以将商品提取到单独一个hash中

      1
      2
      3
      4
      5
      key -> shoppingcart:product.info  
      field -> 商品Id:info
      value -> 商品信息

      当用户添加商品到购物车的时候,通过 hsetnx key field value将商品存入shoppingcart:product.info,这样做可以避免重复添加商品到hash

业务场景B

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

解决方案:

  • 以商家id作为key
  • 将参与抢购的商品id作为field
  • 将参与抢购的商品数量作为对应的value
  • 抢购时使用降值得方式控制产品数量

string存对象(json)和hash寸对象

  • string存对象讲究整体性,一次性存一次性取,讲究读为主
  • hash存对象,用field把属性隔离开,主要讲究得是更新操作比较有灵活性,讲究写为主
  • 说通俗一点,当一个对象不需要频繁修改,只是存储之后查出来展示,那么用string来存是可以的,如果一个对象经常需要对各个属性进行修改,那么就使用hashhash可以修改指定field的值

list

  • 数据存储需求:存储多个数据,并对数据进行存储空间的顺粗进行区分
  • 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • list类型:保存多个数据,底层使用双向链表存储结构实现

顺序表(查询快,增删慢),链表(查询慢,增删快),双向链表
顺序表-链表-双向链表.png

基础操作

  • 添加/修改数据

    1
    2
    lpush key value1 value2 ...  
    rpush key value1 value2 ...
  • 获取数据

    1
    2
    3
    lrange key start stop  (lrange key 0 -1 表示查询整个列表)  
    lindex key index
    llen key
  • 获取并移除数据

    1
    2
    lpop key  
    rpop key

扩展操作

  • 规定时间内获取并移除数据

    1
    2
    blpop key1 key2 ... timeout  
    brpop key1 key2 ... timeout

    就是当list没数据的时候,执行上面的命令会阻塞,等有其他客户端往上面监听的list添加数据,随后被取出,timeout为超时时间,单位为秒

应用场景

业务场景A

微信朋友圈点赞,要求按点赞顺序显示点赞好友信息,如果取消点赞,则移除对应好友信息

解决方案:

1
2
3
4
朋友圈Id -> key -> 举例 -> a01  
用户点赞 -> rpush -> 举例 -> rpush a01 zhangsan lisi wangwu
展示 -> lrange key start stop -> 举例 -> lrang a01 0 -1
取消点赞 -> lrem key count value -> 举例 -> lrem a01 1 lisi

注意事项

  • list中保存的数据都是string类型,数据总容量也是有限的,最多2的32次方-1个元素
  • list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
  • 获取全部数据操作结束索引设置为-1
  • list可以对数据进行分页操作,通常第一页的信息来自list,第2页及更多的信息通过数据库的形式加载

比较典型的业务场景

  • twitter、微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面
  • 新闻、资讯类网站将最新的新闻或咨询按照发生的时间顺序展示
  • 企业运营过程中,系统将产出大量的运营数据,保障堕胎服务器操作日志的统一顺序输出

上面的解决方案

  • 依赖list的数据具有顺序的特征对信息进行管理
  • 使用栈模型解决最新消息的问题
  • 使用队列模式解决多路信息汇总合并的问题

set

  • 存储需求:存储大量的数据,在查询方便提供更高的效率
  • 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
  • set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值时不允许重复的

基本操作

  • 添加数据:sadd key member1 member2 ...
  • 获取全部数据:smembers key
  • 删除数据:srem key member1 member2 ...
  • 获取集合数据总量:scard key
  • 判断集合中是否包含指定数据:sismember key member
  • 随机获取集合中指定数量的数据:srandmember key [count]
  • 随机获取集合中某个数据并将该数据移出集合:spop key ...

扩展操作

  • 求两个集合的交、并、差集

    1
    2
    3
    sinter key1 key2  
    sunion key1 key2
    sdiff key1 key2
  • 求两个集合的交、并、差集并存储到指定集合中

    1
    2
    3
    sinterstore destination key1 key2  
    sunionstore destination key1 key2
    sdiffstore destination key1 key2
  • 将指定数据从原始集合中移动到目标集合中

    1
    smove source destination member  

    交并差.png

应用场景

业务场景A

每位用户首次使用今日头条会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别主键产生兴趣,增加客户留存都,该如何实现?

业务分析

  • 系统分析出各个分类的最新或最热点信息条目并组织成set集合
  • 随机挑选其中部分信息
  • 配合用户关注信息分类中的热点信息组成展示的全信息集合

redis应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热点旅游线路,应用APP推荐,大V推荐等

业务场景B

脉脉之类的APP中共同好友的功能,这里通过两个set进行交并差集的运算可以满足需求

  • redis应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
  • 显示共同关注(交集)(一度)
  • 显示共同好友(交集)(一度)
  • 由用户A出发,获取到好友用户B的好友信息列表(一度)
  • 由用户A出发,获取到好友用户B的购物清单列表(二度)
  • 由用户A出发,获取到好友用户B的游戏充值列表(二度)

业务场景C

集团公司共具有12000名员工,内部OA系统中具有700多个角色,3000多个业务操作,23000多中数据,每个员工具有一个或多个角色,如何快速进行业务操作的权限校验?

解决思路:

  • 依赖set集合数据不重复的特征,依赖set集合hash存储结构特征完成数据过滤与快速查询
  • 根据用户id获取用户所有角色
  • 根据用户所有角色获取用户所有操作权限放入set集合
  • 根据用户所有角色获取用户所有数据权限放入set集合

业务场景D

统计网站的PV、UV、IP

解决思路:

  • 利用set集合的数据去重特转增,记录各种访问数据
  • 建立string类型数据,利用incr统计日访问量
  • 建立set模型,记录不同cookie数量(UV)
  • 建立set模型,记录不同IP数量(IP)

业务场景E

黑白名单

解决思路:

  • 基于经营战略设定问题用户发现、鉴别规则
  • 周期性更新满足规则的用户黑名单,加入set集合
  • 用户行为信息达到后于黑名单进行比对,确认行为去向
  • 黑名单过滤IP地址:应用于开放游客访问权限的信息源
  • 黑名单过滤设备信息:应用于限定访问设备的信息源
  • 黑名单过滤用户:应用于基于访问权限的信息源

注意事项

  • set类型不允许数据重复,如果添加的数据在set中已经存在,将只保留一份
  • set虽然于hash的存储结构相同,但是无法启用hash中存储值的空间

sorted_set

  • 存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
  • 需要的存储结构:新的存储模型,可以保存可排序的数据
  • sorted_set类型:在set的存储结构上添加可排序字段

基础操作

  • 添加数据

    1
    zadd key score1 member \[score2 member\] ...  
  • 获取全部数据

    1
    2
    zrang key start stop \[WITHSCORES\]  
    zrevrange key start stop \[WITHSCORES\]
  • 删除数据

    1
    zrem key member \[member ...\]  
  • 按条件获取数据

    1
    2
    zrangebyscore key min max \[WITHSCORES\] \[LIMIT\]  
    zrevrangebyscore key max min \[WITHSCORES\]
  • 按条件删除数据

    1
    2
    zremrangebyrank key start stop  
    zremrangebyscore key min max
  • 获取集合数据总量

    1
    2
    zcard key  
    zcard key min max
  • 集合交、并操作

    1
    2
    zinterstore destination numkeys key \[key ...\]  
    zunionstore destination numkeys key \[key ...\]
  • 获取数据对应的索引(排名)

    1
    2
    zrank key memeber  
    zrevrank key member
  • score值获取与修改

    1
    2
    zscore key member  
    zincrby key increment member

注意:

  • min与max用于限定搜索查询的条件
  • start与stop用于限定查询访问,作用于索引,表示开始和结束索引
  • offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量

应用场景

业务场景A

海选投票、各种TopN排行榜、聊天室活跃度统计、游戏好友亲密度

解决方案:通过获取索引

业务场景B

体验VIP功能,当体验VIP到期后,如何有效管理此类信息,即便对于正式VIP用户也应该存在对应的管理方式。网站定期开启投票、讨论、限期进行,逾期作废,如何有效管理此类过期信息

解决方案:

  • 对基于时间线限定的任务处理,将处理时间记录为score值,利用顺序功能分区处理的先后顺序
  • 记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间
  • 当新任务加入时,判断并更新当前下一个要处理的任务时间
  • 为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_set,例如1小时内、1天内、周内、月内、季内、年内等,操作时逐级提升,将即将操作的若干任务纳入到1小时内处理的队伍中

业务场景C

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

解决方案:

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

注意事项

  • score保存的数据存储空间是64位
  • score保存的数据也可以是个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时要慎重
  • sorted_set底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果

实践案例

业务场景A

人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型。现对试用用户的使用行为进行限速,限制每个用户每分钟最多发起10次调用

解决方案:

  • 设计计数器,记录调用次数,用于控制业务执行次数,以用户id作为key,使用次数为value
  • 在调用前获取次数,判断是否超过限定次数
    • 不超过次数的情况下,每次调用技术+1
    • 超过次数则业务调用失败,计数-1
  • 为计时器设置生命周期为指定周期,例如1秒/分钟,自动清空周期内使用次数

业务场景A.png

解决方案改良:

  • 取消最大值的判断,利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值
  • 判断值是否为nil
    • 如果是,设置为数值的Max-次数
    • 如果不是,计数+1
    • 业务调用失败,计数-1
  • 遇到异常即+操作超过上限,视为使用达到上限

业务场景A改良.png

业务场景B

使用微信的过程中,当微信接收消息后,会默认将最近接受的消息置顶,当多个好友及关注的订阅号同时发送消息时,该顺序会不停的进行交替。同时还可以将重要的会话设置为置顶。一旦用户里离线后,再次打开微信时,消息该按照什么样的顺序显示?

解决方案:

  • 依赖list的数据具有顺序的特征对消息进行管理,将list结构作为栈使用
  • 对指定与普通会话分别创建独立的list分别管理
  • 当某个list中接收到用户消息后,将消息发送方的idlist的一侧加入list(此处设定左侧)
  • 多个消息id发出的消息反复入栈会出现问题,在入栈前无论是否具有当前id对应的消息,先删除对应id
  • 推送消息时先推送置顶会话list,再推送普通会话list,推送完成的list清除所有数据
  • 消息的数量,也就是微信用户对话数量采用计数器的思路另行记录,伴随list操作同步更新

key通用操作

key基本操作

  • 删除指定key:del key
  • 获取key是否存在:exists key
  • 获取key的类型:type key

key的时效性控制

  • 为指定key设置有效期

    1
    2
    3
    4
    expire key seconds  
    pexpire key milliseconds
    expireat key timestamp
    pexpireat key milliseconds-timestamp
  • 获取key的有效实践

    1
    2
    ttl key  
    pttl key
  • 切换key从时效性转换为永久性

    1
    persist key  

    key的查询模式

  • 查询key:keys parttern,查询模式规则:

    • *:匹配任意数量的任意符号
    • ?:匹配一个任意字符
    • []:匹配一个指定符号

    举例:

    1
    2
    3
    4
    5
    6
    keys \* -> 查询所有  
    keys it\* -> 查询所有以it开头
    keys \*save -> 查询所有以save结尾
    keys ??user -> 查询所有前面两个任意字符,后面以user结尾
    keys user:" -> 查询所有以user:开头,后面一个任意字符
    keys u\[st\]er:1 ->查询所有以u开头,以er:1结尾,中间包含字符s或t

key的其他操作

  • 为key改名

    1
    2
    rename key newkey  
    renamenx key newkey
  • 对所有key排序

    1
    sort  
  • 其他key通用操作

    1
    help @generic  

数据库通用操作

key的重复问题

  • key是由程序员定义的
  • redis在使用过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的key
  • 数据不区分种类、类别混在在一起,极易出现重复或冲突

解决方案:

  • redis为每个服务提供有16个数据库,编号从0到15
  • 每个数据库之间的数据相互独立

基本操作

  • 切换数据库

    1
    select index  
  • 其他操作

    1
    2
    3
    quit  
    ping
    echo message

其他操作

  • 数据移动

    1
    move key db  
  • 数据清除

    1
    2
    3
    dbsize --库里有多少个key  
    flushdb --清空当前数据库
    flushall --清空有数据库