1 Redis 入门

1.1 技术发展

1.1.1 web 1.0 时代

  • web 1.0 的时代,数据访问量非常有限,用一夫当关的高性能的单点服务器就可以解决大部分的问题。

web 1.0 时代.png

1.1.2 web 2.0 时代

  • 随着 web 2.0 时代的到来,用户访问量大幅度提升,同时产生了大量的用户数据,加上后来的智能移动设备的普及(移动互联网),所有的互联网平台都面临了巨大的性能挑战。

web 2.0 时代.png

1.1.3 解决 CPU 和内存压力

解决 CPU 和内存压力.png

  • 传统的 JavaWeb 是通过 session 来辨别用户的,那么 session 怎么保持一致?session 怎么存储?
  • 方案 ① :
    • 不使用 session ,使用 cookie 。
    • 后果:cookie 是客户端技术,安全性较低,而且 cookie 中保存的数据有限制。
  • 方案 ②:
    • 将 session 通过序列化等技术,保存在文件服务器或数据库里面。
    • 后果:大量的 IO ,性能低。
  • 方案 ③:
    • session 复制。
    • 后果:服务器越多,内存 及 带宽浪费越大。
  • 方案 ④:
    • 存储在缓存数据库中。
    • 优点:在内存中,速度快,数据结构简单。

1.1.4 解决 IO 压力

解决 IO 压力.png

  • 打破了传统关系型数据库以业务逻辑为依据的存储模式,改为针对不同的数据结构类型,选择以性能为最优先的存储方式。

    1.2 Redis 的简介

1.2.1 关系型数据库的问题

  • 性能瓶颈:磁盘 IO 性能低下(关系型数据库存取数据的时候要走磁盘 IO 。磁盘性能本身是比较低)。
  • 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群(关系型数据库经常会遇见多表关联,一旦多表关联过大,性能会急剧下降)。

1.2.2 解决思路

  • 降低磁盘 IO 次数,越低越好 --> 内存存储。
  • 去除数据间关系,越简单越好 --> 不存储关系,仅存储数据。

1.2.3 NoSQL

  • NoSQL:即 Not Only SQL(泛指非关系型数据库),作为关系型数据库的补充。
  • 作用:应对基于 海量用户海量数据 前提下的数据处理问题。
  • 特征:
    • 可扩容、可伸缩。
    • 大数据量下高性能。
    • 灵活的数据模型。
    • 高可用。
  • 常见的 NoSQL 数据库:
    • Redis。
    • memcache
    • HBASE。
    • MongoDB。
    • ……

1.2.4 解决方案(电商场景)

  • ① 商品基本信息(MySQL):
    • 名称。
    • 价格。
    • 厂商。
  • ② 商品附加信息(MongoDB):
    • 描述。
    • 详情。
    • 评论。
  • ③ 图片信息(分布式文件系统)。
  • ④ 搜索关键字(ES、Lucene、Solr)。
  • 热点信息(Redis、tair)
    • 高频
    • 波段性

电商场景解决方案.png

1.2.5 Redis

  • 概念:Redis(Redis Directory Server) 是用 C 语言开发的一个开源的高性能的键值对(key-value)数据库。
  • 特征:
    • ① 数据间没有必然的关联关系。
    • ② 高性能(官方提供测试数据,50 个并发执行 100000 个请求,读的速度是 110000 次/s,写的速度是 81000 次/s)。
    • ③ 多数据类型支持:
      • 字符串类型:string。
      • 列表类型:list。
      • 散列类型:hash。
      • 集合类型:set。
      • 有序集合类型:sorted_set。
    • ④ 持久化支持:可以进行数据灾难数据恢复。
  • 应用:
    • 为热点数据加速查询(主要场景),比如:热点商品、热点新闻、热点资讯、推广类等高访问量信息等。
    • 任务队列,比如:秒杀、抢购、购票排队等。
    • 即时信息查询,比如:各类排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信息等。
    • 时效性信息控制,比如:验证码控制、投票控制等。
    • 分布式数据共享,比如:分布式集群架构中的 session 控制。
    • 消息队列。
    • 分布式锁。

1.3 Redis 的下载和安装

1.3.1 Redis 的下载

  • Windows 版本(适合零基础学习):
  • Linux 版本(适用于企业级开发):
    • Redis 高级开始使用。
    • 以 Redis 5.0.10 版本作为主版本。
    • 下载地址

1.3.2 Redis 的安装

Redis的安装1.png

Redis的安装2.png

Redis的安装3.png

Redis的安装4.png

Redis的安装5.png

Redis的安装6.png

Redis的安装7.png

1.3.3 Redis 的客户端工具(RedisDesktopManager)

RDM的安装1.png

RDM的安装2.png

RDM的安装3.png

RDM的安装4.png

RDM的安装5.png

1.4 Redis 的基本操作

1.4.1 信息添加

  • 功能:设置 key,value 数据。
  • 命令:
  1. set key value
  • 示例:
  1. set name 许大仙

Redis的基本操作之信息添加.png

1.4.2 信息查询

  • 功能:根据 key 查询对应的 value ,如果不存在,返回空(nil)。
  • 命令:
  1. get key
  • 示例:
  1. get name

Redis的基本操作之信息查询.png

1.4.3 清除屏幕信息

  • 功能:清除屏幕中的信息
  • 命令:
  1. clear

1.4.4 帮助

  • 功能:获取命令帮助文档,获取组中所有命令信息名称。
  • 命令:
  1. help 命令名称
  1. help @组名
  • 示例:
  1. help get

Redis的基本操作之信息帮助1.png

  • 示例:
  1. help @string

Redis的基本操作之信息帮助2.png

2 数据类型

2.1 数据存储类型介绍

2.1.1 业务数据的特殊性

  • 作为缓存使用:
    • ① 原始业务功能设计:
      • 秒杀。
      • 618 活动。
      • 双 11 活动。
      • 排队购票。
    • ② 运营平台监控到的突发高频访问数据:突发时政要闻,被强势关注围观。
    • ③ 高频、复杂的统计数据:
      • 在线人数。
      • 投票排行榜。
  • 附加功能(系统功能优化或升级):
    • ① 单服务器升级集群。
    • ② session 的管理。
    • ③ Token 的管理。

2.1.2 Redis 的数据类型(5 种常用)

  • string(类似于 Java 中的 String )。
  • hash(类似于 Java 中的 HashMap )。
  • list(类似于 Java 中的 LinkedList )。
  • set(类似于 Java 中的 HashSet )。
  • sorted_set(类似于 Java 中的 TreeSet )。

2.2 Redis 数据存储格式

  • Redis 自身是一个 Map ,其中所有的数据都是采用 key:value 的形式存储。
  • 数据类型 指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远是字符窜。

Redis的数据存储格式.png

2.3 string

2.3.1 简介

  • 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型。
  • 存储数据的格式:一个存储空间保存一个数据。
  • 存储内容:通常使用字符串,如果字符串以整数的形式展示,那么可以作为数字操作使用,但是依然是字符串

string 简介.png

2.3.2 string 类型数据的基本操作

  • 添加 / 修改数据:
  1. set key value
  • 获取数据:
  1. get key
  • 删除数据:
  1. del key
  • 示例:
  1. set name hello
  2. get name
  3. set age 100
  4. get age
  5. del age
  6. get age
  7. del age

string 类型数据的基本操作示例1.gif

  • 添加 / 修改多个数据:
  1. mset key1 value1 key2 value2 ...
  • 获取多个数据:
  1. mget key1 key2 ...
  • 获取数据字符个数(字符串长度):
  1. strlen key
  • 追加信息到原始信息后边(如果原始信息存在就追加,否则新建):
  1. append key value
  • 示例:
  1. mset a 1 b 2 c 3
  2. mget a b c
  3. set age 100
  4. get age
  5. strlen age
  6. append age 200
  7. get age

string 类型数据的基本操作示例2.gif

2.3.3 单数据操作 VS 多数据操作

  • 单数据操作:
  1. set key value
  • 多数据操作:
  1. mset key1 value1 key2 value2 ...

单数据操作 VS 多数据操作.gif

注意:同等情况下,多数据操作要比单数据操作执行效率高,但是,如果多数据操作一次发送的数据太多,那么也要进行切分(比如:一次发送 1 亿个数据,实在是太大了,那么就切成每次发送 100 万个)。

2.3.4 string 类型数据的扩展操作

  • 业务场景:大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键 id 必须保证唯一性,不能重复。Oracle 数据库具有 sequence 设定,可以解决此问题,但是 MySQL 数据库并不具有类似的机制,那么该如何解决呢?

string 类型数据的扩展操作业务场景.png

  • 解决方案:
  • ① 设置数值增加指定范围的值:
  1. # 给指定 key 对应的 value 加 1
  2. incr key
  1. # 给指定 key 对应的 value 增加指定的值
  2. incry key increment
  1. incrbyfloat key increment
  • ② 设置数值减少指定范围的值:
  1. # 给指定 key 对应的 value 减 1
  2. decr key
  1. # 给指定 key 对应的 value 减少指定的值
  2. decrby key decrement
  • 示例:
  1. set num 1
  2. incr num
  3. get num
  4. decr num
  5. get num

string 类型数据的扩展操作示例.gif

  • string作为数值操作:
  • ① string 在 Redis 内存存储默认就是一个字符串,当遇到增减类操作 incr 、decr 时会转换为数值型进行计算。
  • ② Redis 所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。

注意:按数值进行操作的数据,如果原始数据不能转换为数值或超越了 Redis 数值上限范围,将报错。

  • 总结:
  • Redis 用于控制数据库表的主键 id ,为数据库主键提供生成策略,保证数据库表的主键唯一。
  • 此方案适用于所有的关系型数据库,且支持数据库集群。

2.3.5 string 类型数据的扩展操作

  • 业务场景:
    • 最强女生 启动海选投票,只能通过微信投票,每个微信号每 4 小时只能投 1 票。
    • ② 电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门维持 3 天,3 天后自动取消热门。
    • ③ 新闻网站会出现热点新闻,热点新闻最大的特征就是时效性,如何自动控制热点新闻的时效性?
  • 解决方案:
  • 设置数据具有制定的生命周期:
  1. setex key seconds value
  1. psetex key milliseconds value
  • 示例:
  1. setex tel 10 1
  2. get tel

string 类型数据的扩展操作示例2.gif

注意:如果先设置了 setex xxx 3 1,又设置了 set xxx 2,那么 setex xxx 3 1 指令将会失效。

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

2.3.6 string 类型数据操作的注意事项

  • 数据操作不成功的反馈和数据正常操作之间的差异:
    • ① 表示运行结果是否成功:
      • (integer) 0 --> false,失败。
      • (integer) 1 --> true,成功。
    • ② 表示运行结果值:
      • (integer) 3 --> 3,3 个。
      • (integer) 1 --> 1,1 个。
  • 数据未获取到:(nil) 等同于 null 。
  • 数据最大存储量:512 MB。
  • 数值计算最大范围(Java 中的 long 的最大值):9223372036854775807 。

2.3.7 string 类型应用场景

  • 业务场景:主页高频访问信息显示,例如:新浪微博大 V 主页,显示粉丝数和微博数量。

string 类型应用场景.png

  • 解决方案:在 Redis 中为大 V 用户设定用户信息,以用户主键和属性值作为 key ,后台设定定时刷新策略即可。
  1. user:id:100100100:fans ---> 13097000
  2. user:id:100100100:focuses ---> 82
  3. user:id:100100100:blogs ---> 11250
  • 示例:
  1. set user:id:100100100:fans 13097000
  1. set user:id:100100100:focuses 82
  1. set user:id:100100100:blogs 11250

string 类型应用场景示例.png

  • 解决方案:在 Redis 中以 JSON 格式存储大 V 用户信息,定时刷新(也可以使用 hash 类型)。
  1. user:id:100100100 --> {id:100100100,fans:13097000,focuses:82,blogs:11250}
  • 示例:
  1. set user:id:100100100 {id:100100100,fans:13097000,focuses:82,blogs:11250}

string 类型应用场景示例2.png

  • 总结:Redis 应用于各种结构型和非结构型高热度数据进行访问加速。

2.3.8 key 的设置约定

  • 数据库中的热点数据 key 命名习惯:

数据库中的热点数据 key 命名习惯.png

2.4 hash

2.4.1 存储的困惑

  • 对应类数据的存储,如果使用 string 存储,在具有频繁更新需求操作时,会显得笨重。

hash 存储的困惑1.png

hash 存储的困惑2.png

  • 使用 hash 结构来存储数据。

hash 类型.jpg

2.4.2 简介

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用就是存储对象信息。
  • 需要的存储结构:一个存储空间保存多个键值对数据。
  • hash 类型:底层使用哈希表结构实现数据存储。

hash 类型.png

  • hash 存储结构优化:
    • 如果 field 数量较少,存储结构优化为类数组结构。
    • 如果 field 数量较多,存储结构使用 HashMap 结构。

2.4.3 hash 类型数据的基本操作

  • 添加 / 修改数据:
  1. hset key field value
  • 获取数据:
  1. hget key field
  1. hgetall key
  • 删除数据:
  1. hdel key field1 [field2]
  • 示例:
  1. hset user name tom
  2. hset user age 18
  3. hset user weight 80
  4. hget user name
  5. hgetall user
  6. hdel user name age
  7. hgetall user

hash 类型示例1.gif

  • 添加 / 修改多个数据:
  1. hmset key field1 value1 field2 value2 ...
  • 获取多个数据:
  1. hmget key field1 field2 ...
  • 获取哈希表中字段的数量:
  1. hlen key
  • 获取哈希表中是否存在指定的字段:
  1. hexists key field
  • 示例:
  1. hmset user name tom age 18
  2. hmget user name age
  3. hlen user
  4. hexists user name

hash 类型示例2.gif

2.4.4 hash 类型数据的扩展操作

  • 获取哈希表中所有的字段名或字段值:
  1. hkeys key
  1. hvals key
  • 设置指定字段的数值增加指定范围的值:
  1. hincrby key field increment
  1. hincrbyfloat key field increment
  • 示例:
  1. hmset user name tom age 18
  2. hkeys user
  3. hvals user
  4. hincrby user age 19
  5. hgetall user

hash 类型示例3.gif

2.4.5 hash 类型数据操作的注意事项

  • hash 类型下的 value 只能存储字符串,不能存储其它数据类型,不存在嵌套现象。如果数据没有获取到,对应的值为 (nil) 。
  • 每个 hash 可以存储 2^32 -1 个键值对。
  • hash 类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但是 hash 设计初衷并不是为了存储大量对象而设计的,不要滥用,更不要将 hash 当做对象列表来使用。
  • hgetall 操作可以获取全部 field 和 value,如果 hash 内部 field 过多,遍历整体数据效率就会很低,有可能成为数据访问瓶颈。

2.4.6 hash 类型应用场景

  • 业务场景:电商网站购物车设计和实现。

hash 类型应用场景.png

  • 业务分析:
    • 仅分析购物车的 redis 存储模型:
      • 添加。
      • 浏览。
      • 更改数量。
      • 删除。
      • 清空。
    • 购物车和数据库持久化同步(不讨论)。
    • 购物车和订单的关系(不讨论)
      • 提交购物车:读取数据生成订单。
      • 商家临时价格调整:隶属于订单级别。
    • 未登录用户购物车信息存储(不讨论):
      • cookie 存储。
  • 解决方案:

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

  1. # 001 用户买了 100 个 g:01 和 200 个 g:02 商品
  2. hmset user:id:001 g:01 100 g:02 200
  1. # 002 用户买了 200 个 g:01 和 300 个g:02 商品以及 400 个 g:03 商品
  2. hmset user:id:002 g:01 200 g:02 300 g:03 400
  1. # 查询 001 用户购买的商品
  2. hgetall user:id:001
  1. # 001 用户 不要 g:01 商品了
  2. hdel user:id:001 g:01
  1. # 查询 001 用户购买的商品
  2. hgetall user:id:001
  1. # 001 用户又买了 10 个 g:02 商品
  2. hincrby user:id:001 g:02 10
  1. # 清空购物车
  2. del user:id:001

hash 类型示例4.gif

  • 当前设计仅仅是将数据存储到了 Redis 中,并没有起到加速的作用,商品信息还需要二次查询数据库,那么该怎么办?
  • ① 每条购物车中的商品记录保存两条 field 。
  • ② field1 专门用于保存购买数量:
    • 命名格式:商品id:nums
    • 保存数据:数值。
  • ② field2 专门用于保存购物车中显示的信息,包含文字描述、图片地址、所属商家信息等。

    • 命名格式:商品id:info
    • 保存数据:JSON。
  • 示例:

  1. hmset user:id:001 g01:num2 100 g01:info {...}
  1. hmset user:id:002 g01:num2 100 g01:info {...}
  • 如果用户 001 和 用户 002 都购买了 g01 商品,那么 g02 的 info 会很冗余,怎么办?
  • 解决方案:将 field2 抽取成一个独立的 hash 。

  • 示例:

  1. # 公共信息
  2. hset g:01 info {...}
  1. hset user:id:001 g01:nums 100
  1. # 公共信息
  2. hset g:01 info {...}
  1. hset user:id:002 g01:nums 200
  • 如果用户 001 和 用户 002 都购买了 g01 商品,那么都需要向 Redis 中增加公共信息 g:01 的 hash ,这样显得效率很低,怎么办?
  • 解决方案:先判断下 Redis 中是否存在这样的 hash ,如果没有就存储到 Redis 中,如果就直接跳过。
  1. # 先判断 Redis 中是否存在 g:01 的 hash ,如果不存在,就增加
  2. hsetnx g:01 info {..}
  1. hset user:id:001 g01:nums 100
  1. # 先判断 Redis 中是否存在 g:01 的 hash ,如果不存在,就增加;如果存在,直接跳过
  2. hsetnx g:01 info {..}
  1. hset user:id:002 g01:nums 200
  • 总结:Redis 应用于购物车数据存储设计。

2.4.7 hash 类型应用场景

  • 业务场景:双 11 活动日,销售手机充值卡的商家对移动、联通、电信的 30 元、50 元、100 元商品推出抢购活动,每种商品抢购上限 1000 张。

hash 类型应用场景2.png

  • 解决方案:
    • 以商家 id 作为 key。
    • 将参与抢购的商品 id 作为 field。
    • 将参与抢购的商品数量作为对应的 value。
    • 抢购的时候使用将值的方式控制产品数量。

实际业务中还有超卖等实际问题,这里不做讨论。

  • 示例:
  1. hmset p01 c30 1000 c50 1000 c100 1000
  1. hincrby p01 c30 -1
  1. hincrby p01 c100 -20
  1. hgetall p01
  • 总结:Redis 应用于抢购、限购类、限量发放优惠券、激活码等业务的数据存储设计。

2.5 list

2.5.1 简介

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

list 简介.png

list 类型存储空间.png

2.5.2 list 类型数据的基本操作

  • 添加数据:
  1. lpush key value1 [value2] ...
  1. rpush key value1 [value2] ...
  • 获取数据:
  1. lrange key start stop
  1. lindex key index
  1. llen key
  • 获取并移除数据:
  1. lpop key
  1. rpop key
  • 示例:
  1. lpush list1 huawei
  1. lpush list1 apple
  1. lpush list1 microsoft
  1. lrange list1 0 2
  1. lrange list1 0 -1

list 类型示例1.gif

2.5.3 list 类型数据的扩展操作

  • 规定时间内获取并移除操作:
  1. blpop key1 [key2] ... timeout
  1. brpop key1 [key2] ... timeout
  • 示例:
  1. # 等待 30 s ,如果有数据直接取出
  2. blpop list1 30
  1. lpush list1 1 2

list 类型示例2.gif

2.5.4 list 类型数据的扩展操作

  • 业务场景:
    • 微信朋友圈点赞,要求按点赞顺序显示点赞好友信息。
    • 如果取消点赞,移除对应好友信息。

list 类型数据扩展操作2.png

  • 解决方案(移除指定数据):
  1. lrem key count value
  • 示例:
  1. rpush 001 a b c d e
  1. lrange 001 0 -1
  1. lrem 001 1 d
  1. lrange 001 0 -1

list 类型示例3.gif

  • 总结:Redis 应用于具有操作先后顺序的数据控制。

2.5.5 list 类型数据操作的注意事项

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

2.5.6 list 类型应用场景

  • 业务场景:
    • ① twitter、新浪微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面。

list 类型应用场景.png

  • ② 新闻、资讯类网站如何将最近的新闻或资源按照发生的时间顺序展示?
  • ③ 企业运营过程中,系统将产生大量的运营数据,如何保证多台服务器操作日志统一顺序输出?

list 类型应用场景2.png

  • 解决方案:

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

  1. # 一号机器
  2. rpush logs a1..
  3. rpush logs a2..
  1. # 二号机器
  2. rpush logs b1..
  3. rpush logs b2..
  1. # 三号机器
  2. rpush logs c1..
  3. rpush logs c2..
  1. lrange logs 0 -1

list 类型示例4.gif

  • 总结:Redis 可以应用于最新消息的展示。

2.6 set

2.6.1 简介

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

set 类型简介.png

2.6.2 set 类型数据的基本操作

  • 添加数据:
  1. sadd key member1 [member2] ...
  • 获取全部数据:
  1. smemebers key
  • 删除数据:
  1. srem key member1 [member2] ...
  • 示例:
  1. sadd users zhangsan
  1. sadd users lisi wangwu zhaoliu tianqi
  1. smembers users
  1. srem users zhangsan
  1. smembers users

set 类型示例1.gif

  • 获取集合数据总量:
  1. scard key
  • 判断集合中是否包含指定数据:
  1. sismember key member
  • 示例:
  1. scard users
  1. sismember users lisi

set 类型示例2.gif

2.6.3 set 类型数据的扩展操作

  • 业务场景:每位用户首次使用今日头条时会设置 3 项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别产生兴趣,增加客户的留存度,如何实现?
  • 业务分析:
    • 系统分析出各个分类的最新或最热点信息条目,并组织成 set 集合。
    • 随机挑选其中部分信息。
    • 配合用户关注信息分类中的热点信息,组织成展示的全信息集合。
  • 解决方案:

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

      1. srandmember key [count]
    • 随机获取集合中的某个数据并将该数据移出集合:

      1. spop key [count]
  • 示例:
  1. sadd news n1 n2 n3 n4
  1. smembers news
  1. srandmember news 1
  1. srandmember news 1
  1. smembers news
  1. spop news
  1. smembers news

set 类型示例3.gif

  • 总结:Redis 应用于随机推荐类信息检索,例如:热点歌单推荐、热点新闻推荐、热卖旅游线路、应用 APP 推荐,大 V 推荐等等。

2.6.4 set 类型数据的扩展操作

  • 业务场景:
    • 脉脉为了促进用户间的交流,保障业务成单率的提升,需要让每位用户拥有大量的好友,事实上职场新人不具有更多的职场好友,如何快速的为用户积累更多的好友?
    • 新浪微博为了增加用户热度,提高用户留存性,需要微博用户在关注更多的人,以此获取更多的信息或热门话题,如何提高用户关注他人的总量?
    • QQ 新用户入网年龄越来越低,这些用户的朋友圈交际圈非常小,往往集中在一所学校甚至一个班级中,如何帮助用户快速积累好友,给用户带来更多的活跃度?
    • 微信公众号是微信信息流通的渠道之一,增加用户关注的公众号称为提高用户活跃度的一种方式,如何帮助用户积累更多关注的公众号?
    • 美团外卖为了提升成单量,必须帮助用户挖掘美食需求,如何推荐给用户最适合自己的美食?
  • 解决方案:

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

      1. sinter key1 [key2] ...
      1. sunion key1 [key2] ...
      1. sdiff key1 [key2] ...
    • 求两个集合的交、并、差集并存储到指定集合中:

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

      1. smove source destination member
  • 示例:
  1. sadd u1 a1 s1 b1
  1. sadd u2 s1 w1
  1. sinter u1 u2
  1. sunion u1 u2
  1. sdiff u1 u2
  1. smove u1 u3 s1
  1. smembers u3

set 类型示例4.gif

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

2.6.5 set 类型操作的注意事项

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

2.6.6 set 类型应用场景

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

set 类型应用场景.png

  • 解决方案:

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

  1. sadd rid:001 getall getById
  1. sadd rid:002 getCount getall insert
  1. sunionstore uid:007 rid:001 rid:002
  1. # 查看 uid:007 中的所有权限,通过编程方式,判断需要操作的权限是否在此集合中,建议
  2. smembers uid:007
  1. # 直接判断需要操作的权限是否在 Redis 中
  2. sismember uid:007 insert

set 类型示例5.gif

  • 总结:Redis 引用于同类型不重复数据的合并操作。

2.6.7 set 类型应用场景

  • 业务场景:
    • 公司对旗下新的网站做推广,统计网站的 PV(访问量),UV(独立访客)、IP(独立 IP)。
    • PV:网站被访问次数,可以通过刷新页面提高访问量。
    • UV:网站被不同用户访问的次数,可以通过 cookie 统计访问量,相同用户切换 IP 地址,UV 不变。
    • IP:网站被不同 IP 地址访问的次数,可以通过 IP 地址统计访问量,相同 IP 不同用户访问,IP 不变。
  • 解决方案:

    • 利用 set 集合的数据去重特征,记录各种访问数据。
    • 建立 string 类型数据,利用 Incr 统计日访问量(PV)。
    • 建立 set 模型,记录不同 cookie 数量(UV)。
    • 建立 set 模型,记录不同 IP 数量(IP)。
  • 示例:

  1. sadd ips 1.2.34
  1. sadd ips 2.3.4.5
  1. sadd ips 1.2.3.4
  1. scard ips

set 类型示例6.gif

  • 总结:Redis 应用于同类型数据的快速去重。

2.6.8 set 类型应用场景

  • 业务场景:
    • 黑名单:
      • 资讯类信息类网站追求高访问量,但是由于其信息的价值,往往容易被不法分子利用,通过爬虫技术,快速获取信息,个别特种行业网站信息通过爬虫获取分析后,可以转换为商业机密进行出售。例如:第三方火车票、机票、酒店刷票代购软件,电商刷评论、刷好评。
      • 同时,爬虫带来的伪流量也会给经营者带来错觉,产生错误的决策,有效避免网站被爬虫反复爬取成为每个网站都要考虑的基本问题。在基于技术层面区分爬虫用户后,需要将此类用户进行游戏哦啊的屏蔽,这就是黑名单的典型应用。
    • 白名单:对于安全性更高的应用访问,仅仅靠黑名单是不能解决安全问题的,此时需要设定可以访问的用户群体,依赖白名单做更为苛刻的访问验证。
  • 解决方案:
    • 基于经营战略设定问题用户发现、鉴别规则。
    • 周期性更新满足规则的用户黑名单,加入 set 集合。
    • 用户行为信息达到后和黑名单进行比对,确认行为去向。
    • 黑名单过滤 IP 地址:应用于开放游客访问权限的信息源。
    • 黑名单过滤设备信息:应用于限定访问设备的信息源。
    • 黑名单过滤用户:应用于基于访问权限的信息源。
  • 总结:Redis 应用于基于黑白名单设定的服务控制。

2.7 sorted_set

2.7.1 简介

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

sorted_set 类型简介.png

2.7.2 sorted_set 类型数据的基本操作

  • 添加数据:
  1. zadd key socre1 member1 [score2 member2] ...
  • 获取全部数据:
  1. zrange key start stop [withscores]
  1. zrevrange key start stop [withscores]
  • 删除数据:
  1. zrem key member [member2] ...
  • 示例:
  1. zadd scores 94 zhangsan
  1. zadd socres 100 lisi
  1. zadd socres 59 wangwu
  1. zrange scores 0 -1
  1. zrange scores 0 -1 withscores
  1. zrevrange scores 0 -1 withscores
  1. zrem scores lisi
  1. zrevrange scores 0 -1 withscores

sorted_set 类型示例1.gif

  • 按条件获取数据:
  1. zrangebyscore key min max [withscores] [limit offset count]
  1. zrevrangebysocre key max min [withscores] [limit offset count]
  • 条件删除:
  1. zremrangebyrank key start stop
  1. zremrangebysocre key min max

注意:

  • min 和 max 用于限定搜索查询的条件。
  • start 和 stop 用于限定查询范围,作用于索引,表示开始索引和结束索引。
  • offset 和 count 用于限定查询范围,作用于查询结果,表示开始位置和结束位置。
  • 示例:
  1. zadd scores 45 zhangsan 67 lisi 71 wangwu 99 zhaoliu 100 tianqi
  1. zrangebysocre scores 50 99 withscores
  1. zremrangebyscore scores 50 70
  1. zrange scores 0 -1 withscores

sorted_set 类型示例2.gif

  • 获取集合数据总量:
  1. zcard key
  1. zcount key min max
  • 集合交、并操作:
  1. zinterstore destination numkeys key1 [key2]...
  1. zunionstore destination numkeys key1 [key2]...
  • 示例:
  1. zadd scores 45 zhangsan 67 lisi 71 wangwu 99 zhaoliu 100 tianqi
  1. zcard scores
  1. zcount scores 90 150

sorted_set 类型示例3.gif

2.7.3 sorted_set 类型数据的扩展操作

  • 业务场景:
    • 票选江苏十大杰出青年,各类综艺选秀海选投票。
    • 各类资源网站 TOP 10(电影、歌曲、文档、电商、游戏等)。
    • 聊天室活跃度统计。
    • 游戏好友亲密度。

sorted_set 类型数据的扩展操作1.png

  • 业务分析:
    • 为所有参与排序的资源建立排序依据。
  • 解决方案:

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

      1. zrank key member
      1. zrevrank key member
    • score 值的获取和修改:

      1. zscore key member
      1. zincrby key increment member
  • 示例:
  1. zadd movies 143 aa 97 bb 20 cc
  1. zrevrange movies 0 -1 withscores
  1. zrank movies aa
  1. zscore movies aa
  1. zincrby movies 1 aa
  1. zscore movies aa

sorted_set 类型示例4.gif

  • 总结:Redis 应用于计数器组合排序功能对应的排名。

2.7.4 sorted_set 类型数据操作的注意事项

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

2.7.5 sorted_set 类型应用场景

  • 业务场景:
    • 基础服务 + 增值服务类网站会设定各位会员的试用,让用户充分体验会员优势。例如:观影试用 VIP ,游戏 VIP 体验,云盘下载体验 VIP ,数据查看体验 VIP 。当 VIP 体验到期后,如何有效管理此类信息?即使对于正式的 VIP 用户,也应该存在对应的管理方式。
    • 网站会定期开启投票、讨论、限时进行,逾期作废,如何有效管理此类过期信息?
  • 解决方案:

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

  1. zadd ts 15009802315 uid:001
  1. zadd ts 15009802316 uid:001
  1. zadd ts 15009802317 uid:001
  1. zrange ts 0 -1 withscores
  1. # 当任务到期后,通知下一个用户,时间要过期,然后将当前的当期的任务删除
  • 总结:Redis 应用于定时任务执行顺序管理或任务过期管理。

2.7.6 sorted_set 类型应用场景

  • 业务场景:当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理?
  • 解决方案:对于带有权重的任务,优先处理权重高的任务,采用 score 记录权重即可。

  • 示例:

  1. zadd task 4 order:id:005
  1. zadd task 1 order:id:425
  1. zadd task 9 order:id:345
  1. zrevrange task 0 -1 withscores
  1. # 获取第一个,得到 rder:id:345
  2. zrevrange task 0 0
  1. # 移除 第一个任务
  2. zrem task order:id:345

2.8 数据类型实践案例

2.8.1 按次结算的服务控制

  • 业务场景:人工智能领域的语义识别和自动对话是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型,现在对试用用户的使用行为进行限速,限制为每个用户每分钟最多发起 10 次调用。
  • 解决方案:
    • ① 设计计数器,记录调用次数,用于控制业务执行次数。以用户 id 作为 key ,使用次数作为 value 。
    • ② 在调用前获取次数,判断是否超过限定次数。
      • 不超过次数的情况下,每次调用计数 +1 。
      • 业务调用失败,计数 -1 。
    • ③ 为计数器设置生命周期为指定周期,例如:1 秒/分钟,自动清空周期内使用次数。

按次结算的服务控制的解决方案1.png

  • 示例:
  1. # 判断计数器是否存在
  2. get uid:00415
  1. # 第一次,肯定不存在
  2. setex uid:00415 60 1
  1. # 判断计数器是否存在,当然存在,返回 1
  2. get uid:00415
  1. incr uid:00415
  1. # 判断计数器是否存在,当然存在,返回 2
  2. get uid:00415
  1. incr uid:00415
  1. ...
  2. # 判断计数器是否存在,当然存在,返回 10 ,提示用户超过次数
  3. get uid:00415
  • 问题:

按次结算的服务控制的解决方案2.png

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

按次结算的服务控制的解决方案3.png

  • 总结:Redis 应用于限时按次结算的服务控制。

3 通用命令

3.1 key 有关通用命令

3.1.1 key 特征

  • key 是一个字符串,通过 key获取 Redis 中保存的数据。
  • 对于 key 自身状态的相关操作,例如:删除、判断存在、获取类型等。
  • 对于 key 有效性控制相关操作,例如:有效期设定、判断是否有效、有效状态的切换等。
  • 对于 key 快速查询操作,例如:按照指定策略查询 key。
  • ……

3.1.2 key 基本操作

  • 删除指定的 key:
  1. del key
  • 获取 key 是否存在:
  1. exists key
  • 获取 key 的类型:
  1. type key
  • 示例:
  1. set string1 string1
  2. hset hash1 hash1 hash1
  3. lpush list1 list1
  4. sadd set1 set1
  5. zadd zset1 1 zset1
  1. type zset1
  1. type string1
  1. type hash1

3.1.3 key 时效性控制操作

  • 为指定 key 设置有效性:
  1. expire key seconds
  1. pexpire key milliseconds
  1. expireat key timestamp
  1. pexpireat key milliseconds-timestamp
  • 获取 key 的有效时间:
  1. ttl key
  1. pttl key
  • 切换 key 从时效性转为永久性:
  1. persist key

3.1.4 key 查询操作

  • 查询 key:
  1. key pattern
  • pattern 规则:
    • *:匹配任意数量的任意符号。
    • ?:匹配一个任意符号。
    • []:匹配一个指定符号。
  • 示例:查询所有
  1. keys *
  • 示例:查询以 it 开头
  1. keys it*
  • 示例:查询以 it 结尾
  1. keys *it
  • 示例:查询所有前面两个字符,后面以 it 结尾
  1. keys ??it
  • 示例:查询以 user: 开头,最后一个字符任意
  1. keys user:?
  • 示例:查询所有以 u 开头,以 er:1 结尾,中间包含一个字母 s 或 t
  1. keys u[st]er:1

3.1.5 key 其他操作

  • 为 key 改名:
  1. rename key newkey
  1. renamenx key newkey
  • 对所有 key 排序(list、hash、sorted_sort):
  1. sort
  • 其他 key 通用操作:
  1. help @generic

3.2 数据库通用命令

3.2.1 简介

  • key 的重复问题:
    • ① key是由程序员定义的。
    • ② redis 在使用的过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的 key 。
    • ③ 数据不区分种类、类别混杂在一起,极易出现重复或冲突。
  • 解决方案:
    • redis 为每个服务提供了 16 个数据库,编号从 0 ~ 15 。
    • 每个数据库之间的数据相互独立。

3.2.2 数据库的基本操作

  • 切换数据库:
  1. select index
  • 其他操作:
  1. quit
  1. ping
  1. echo message

3.2.3 数据库的其他操作

  • 数据移动
  1. move key db
  • 数据清除:
  1. flushdb
  1. flushall
  • 查看当前库的 key 的数量:
  1. dbsize

4 Jedis

4.1 Jedis 简介

Jedis 简介.png

  • Java 语言连接 Redis 服务:
    • Jedis。
    • Spring Data Redis。
    • Lettuce。
  • 其它编程语言也可以连接 Redis 服务,如:C、C++、Lua 等。

4.2 Jedis 入门

  • 准备工作:

    • IDEA 2021+。
    • JDK 11+。
    • Maven 3.8。
  • pom.xml

  1. <dependency>
  2. <groupId>redis.clients</groupId>
  3. <artifactId>jedis</artifactId>
  4. <version>3.7.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>junit</groupId>
  8. <artifactId>junit</artifactId>
  9. <version>4.13.2</version>
  10. <scope>test</scope>
  11. </dependency>

注意:

  • JedisPool:操作单机版的 Redis。
  • JedisSentinelPool:操作哨兵模式的 Redis。
  • JedisCluster:操作 Redis 集群。
  • 测试类:
  1. package com.github.fairy.era;
  2. import org.junit.Test;
  3. import redis.clients.jedis.Jedis;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-12-05 22:20
  8. */
  9. public class JedisTest {
  10. @Test
  11. public void test() {
  12. // 连接 Redis
  13. Jedis jedis = new Jedis("127.0.0.1", 6379);
  14. // 操作 Redis
  15. if (!jedis.exists("name")) {
  16. jedis.set("name", "许大仙");
  17. }else{
  18. String name = jedis.get("name");
  19. System.out.println("name = " + name);
  20. }
  21. // 关闭 连接
  22. jedis.close();
  23. }
  24. }

4.3 Jedis 读写 Redis 数据

  • 示例:
  1. package com.github.fairy.era;
  2. import org.junit.After;
  3. import org.junit.Before;
  4. import org.junit.Test;
  5. import redis.clients.jedis.Jedis;
  6. import redis.clients.jedis.JedisPool;
  7. import java.util.*;
  8. /**
  9. * @author 许大仙
  10. * @version 1.0
  11. * @since 2021-12-05 22:20
  12. */
  13. public class JedisTest {
  14. private Jedis jedis;
  15. @Before
  16. public void before() {
  17. JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);
  18. jedis = jedisPool.getResource();
  19. }
  20. @After
  21. public void after() {
  22. if (null != jedis) {
  23. jedis.close();
  24. }
  25. }
  26. @Test
  27. public void testList() {
  28. if (!jedis.exists("list")) {
  29. jedis.rpush("list", "a", "b", "c", "d");
  30. } else {
  31. List<String> list = jedis.lrange("list", 0, -1);
  32. Optional.ofNullable(list).orElse(new ArrayList<>()).forEach(System.out::println);
  33. }
  34. }
  35. @Test
  36. public void testHash() {
  37. if (!jedis.exists("hash")) {
  38. Map<String, String> map = new HashMap<>();
  39. map.put("a", "aa");
  40. map.put("b", "bb");
  41. map.put("c", "cc");
  42. jedis.hset("hash", map);
  43. } else {
  44. Map<String, String> hash = jedis.hgetAll("hash");
  45. Optional.ofNullable(hash).orElse(new HashMap<>()).forEach((k, v) -> {
  46. System.out.println("k = " + k);
  47. System.out.println("v = " + v);
  48. });
  49. }
  50. }
  51. }

4.4 业务请求次数控制案例

4.4.1 业务需求

  • 人工智能领域的语义识别和自动对话是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型,现在对试用用户的使用行为进行限速,限制为每个用户每分钟最多发起 10 次调用。
  • 案例要求:
    • ① 设定 A 、B 、C 三个用户。
    • ② A 用户限制 10 次/分调用,B 用户限制 30 次/分调用,C 用户不限制。

4.4.2 需求分析

  • ① 设定一个服务方法,用于模拟实际业务调用的服务,内部采用打印模拟调用。
  • ② 在业务调用前服务调用控制单元,内部使用 Redis 来进行控制,参照之前的方案。
  • ③ 对调用超限使用异常进行控制,异常处理设定为打印提示信息。
  • ④ 主程序启动 3 个线程,分别表示 3 种不同的用户的调用。

4.4.3 实现

  • ① 设定业务方法:
  1. package com.github.fairy.era.service;
  2. /**
  3. * @author 许大仙
  4. * @version 1.0
  5. * @since 2021-12-06 08:52
  6. */
  7. public interface BusinessService {
  8. /**
  9. * 业务操作
  10. *
  11. * @param id
  12. * @param name
  13. * @param incr
  14. */
  15. void business(String id, String name, Long incr);
  16. }
  1. package com.github.fairy.era.service.impl;
  2. import com.github.fairy.era.service.BusinessService;
  3. import com.github.fairy.era.utils.RedisUtils;
  4. import redis.clients.jedis.Jedis;
  5. import redis.clients.jedis.exceptions.JedisDataException;
  6. /**
  7. * @author 许大仙
  8. * @version 1.0
  9. * @since 2021-12-06 08:53
  10. */
  11. public class BusinessServiceImpl implements BusinessService {
  12. @Override
  13. public void business(String id, String name, Long incr) {
  14. System.out.println("用户id:" + id + "用户名:" + name + "业务操作执行了。。。,第" + incr + "次");
  15. }
  16. }
  • ② 设定控制单元方法:
  1. package com.github.fairy.era.service;
  2. /**
  3. * @author 许大仙
  4. * @version 1.0
  5. * @since 2021-12-06 08:52
  6. */
  7. public interface BusinessService {
  8. /**
  9. * 控制单元
  10. *
  11. * @param id
  12. */
  13. void speedLimit(String id, String name, Long limitSeconds, Long count);
  14. /**
  15. * 业务操作
  16. *
  17. * @param id
  18. * @param name
  19. * @param incr
  20. */
  21. void business(String id, String name, Long incr);
  22. }
  1. package com.github.fairy.era.service.impl;
  2. import com.github.fairy.era.service.BusinessService;
  3. import com.github.fairy.era.utils.RedisUtils;
  4. import redis.clients.jedis.Jedis;
  5. import redis.clients.jedis.exceptions.JedisDataException;
  6. /**
  7. * @author 许大仙
  8. * @version 1.0
  9. * @since 2021-12-06 08:53
  10. */
  11. public class BusinessServiceImpl implements BusinessService {
  12. @Override
  13. public void speedLimit(String id, String name, Long limitSeconds, Long count) {
  14. Jedis jedis = RedisUtils.jedis();
  15. String key = "compId:" + id;
  16. try {
  17. // 判断该值是否存在
  18. if (!jedis.exists(key)) {
  19. // 不存在
  20. jedis.setex(key, limitSeconds, String.valueOf(Long.MAX_VALUE - count));
  21. } else {
  22. // 存在,自增并调用业务
  23. Long incr = jedis.incr(key);
  24. this.business(id, name, count - (Long.MAX_VALUE - incr));
  25. }
  26. } catch (JedisDataException e) {
  27. System.out.println("用户" + name + "使用已经到达次数上限,请升级会员级别");
  28. return;
  29. } finally {
  30. RedisUtils.close();
  31. }
  32. }
  33. @Override
  34. public void business(String id, String name, Long incr) {
  35. System.out.println("用户id:" + id + "用户名:" + name + "业务操作执行了。。。,第" + incr + "次");
  36. }
  37. }
  • ③ 设置多线程,模拟用户调用
  1. package com.github.fairy.era.thread;
  2. import com.github.fairy.era.service.BusinessService;
  3. import com.github.fairy.era.service.impl.BusinessServiceImpl;
  4. /**
  5. * @author 许大仙
  6. * @version 1.0
  7. * @since 2021-12-06 08:55
  8. */
  9. public class SpeedLimitThread extends Thread {
  10. private final BusinessService businessService = new BusinessServiceImpl();
  11. /**
  12. * 限制时间
  13. */
  14. private final Long limitSeconds;
  15. /**
  16. * 限制次数
  17. */
  18. private final Long count;
  19. public SpeedLimitThread(Long limitSeconds, Long count) {
  20. this.limitSeconds = limitSeconds;
  21. this.count = count;
  22. }
  23. @Override
  24. public void run() {
  25. while (true) {
  26. businessService.speedLimit(String.valueOf(Thread.currentThread().getId()), currentThread().getName(), limitSeconds, count);
  27. try {
  28. Thread.sleep(1000);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  • ④ 工具类:
  1. package com.github.fairy.era.utils;
  2. import redis.clients.jedis.Jedis;
  3. import redis.clients.jedis.JedisPool;
  4. import redis.clients.jedis.JedisPoolConfig;
  5. /**
  6. * @author 许大仙
  7. * @version 1.0
  8. * @since 2021-12-06 09:02
  9. */
  10. public class RedisUtils {
  11. private static final JedisPool jedisPool;
  12. private static Jedis jedis;
  13. static {
  14. JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
  15. jedisPoolConfig.setMaxTotal(200);
  16. jedisPoolConfig.setMaxIdle(50);
  17. jedisPoolConfig.setMinIdle(30);
  18. jedisPoolConfig.setMaxWaitMillis(1000 * 200000);
  19. jedisPoolConfig.setTestOnBorrow(true);
  20. jedisPoolConfig.setTestOnCreate(true);
  21. jedisPoolConfig.setTestOnReturn(true);
  22. jedisPool = new JedisPool(jedisPoolConfig, "127.0.0.1", 6379, 100000);
  23. }
  24. /**
  25. * 获取 Jedis
  26. *
  27. * @return
  28. */
  29. public static Jedis jedis() {
  30. jedis = jedisPool.getResource();
  31. return jedis;
  32. }
  33. public static void close() {
  34. if (null != jedis) {
  35. jedis.close();
  36. }
  37. }
  38. }
  • ⑤ 测试:
  1. package com.github.fairy.era;
  2. import com.github.fairy.era.thread.SpeedLimitThread;
  3. /**
  4. * @author 许大仙
  5. * @version 1.0
  6. * @since 2021-12-06 09:19
  7. */
  8. public class Test {
  9. public static void main(String[] args) {
  10. SpeedLimitThread t1 = new SpeedLimitThread(60L, 10L);
  11. t1.setName("A");
  12. t1.start();
  13. SpeedLimitThread t2 = new SpeedLimitThread(30L, 10L);
  14. t2.setName("B");
  15. t2.start();
  16. }
  17. }

4.5 模拟验证码发送

  • 需求:

    • ① 输入手机号,点击发送后随机生成 6 位验证码,2 分钟有效。
    • ② 输入验证码,点击验证,返回成功或失败。
    • ③ 每个手机号每天只能输入 3 次。
  • 示例:

  1. package com.github.fairy.era.redis;
  2. import org.junit.jupiter.api.BeforeEach;
  3. import org.junit.jupiter.api.Test;
  4. import redis.clients.jedis.Jedis;
  5. import java.security.SecureRandom;
  6. /**
  7. * @author 许大仙
  8. * @version 1.0
  9. * @since 2021-12-13 09:06
  10. */
  11. public class RedisTest {
  12. private Jedis jedis;
  13. @BeforeEach
  14. public void before() {
  15. jedis = new Jedis("127.0.0.1", 6379);
  16. }
  17. /**
  18. * ① 生成验证码
  19. *
  20. * @return
  21. */
  22. public String getCode() {
  23. StringBuilder sb = new StringBuilder();
  24. SecureRandom secureRandom = new SecureRandom();
  25. for (int i = 0; i < 6; i++) {
  26. int randomNum = secureRandom.nextInt(10);
  27. sb.append(randomNum);
  28. }
  29. return sb.toString();
  30. }
  31. /**
  32. * ② 发送验证码:每个手机号每天只能发送 3 次,并将验证码存储到 Redis 中
  33. *
  34. * @param phone 手机号
  35. * @param phoneExpire 手机号过期时间
  36. * @param codeExpire 验证码过期时间
  37. */
  38. public void verifyPhone(String phone, Long phoneExpire, Long codeExpire) {
  39. // 手机发送次数的 key
  40. String phoneCountKey = "verifyCode:" + phone + ":count";
  41. // 每个手机号每天只能发送 3 次
  42. String phoneCount = jedis.get(phoneCountKey);
  43. if (null == phoneCount) {// 第一次发送
  44. jedis.setex(phoneCountKey, phoneExpire, "1");
  45. } else if (Integer.parseInt(phoneCount) <= 2) {
  46. jedis.incr(phoneCountKey);
  47. } else {
  48. throw new RuntimeException(phone + ",您今天发送验证码次数已经超过了 3 次");
  49. }
  50. // 验证码的 key
  51. String codeKey = "verifyCode:" + phone + ":code";
  52. // 保存验证码
  53. String vCode = jedis.get(codeKey);
  54. if (null == vCode) {
  55. jedis.setex(codeKey, codeExpire, getCode());
  56. }
  57. }
  58. /**
  59. * ③ 校验验证码
  60. *
  61. * @param phone 手机号
  62. * @param inputCode 用户输入的验证码
  63. */
  64. public void verifyCode(String phone, String inputCode) {
  65. // 验证码的 key
  66. String codeKey = "verifyCode:" + phone + ":code";
  67. // 保存验证码
  68. String vCode = jedis.get(codeKey);
  69. if (inputCode.equalsIgnoreCase(vCode)) {
  70. System.out.println("验证码校验成功");
  71. } else {
  72. throw new RuntimeException(phone + ",您输入的验证码错误,请重新检查后再输入");
  73. }
  74. }
  75. @Test
  76. public void test() {
  77. verifyPhone("12345678901", 24 * 60 * 3600L, 2 * 60L);
  78. verifyCode("12345678901", "799109");
  79. }
  80. @Test
  81. public void close() {
  82. if (jedis != null) {
  83. jedis.close();
  84. }
  85. }
  86. }

5 命令总结

5.1 通用命令

  • 查看当前库的所有 key :
  1. keys *
  • 判断某个 key 是否存在:
  1. exists key
  • 查看 key 对应的 value 的类型:
  1. type key
  • 删除 key :
  1. del key
  • 为给定的 key 设置过期时间:
  1. expire key seconds
  1. pexpire key milliseconds
  1. expireat key timestamp
  1. pexpireat key milliseconds-timestamp
  • 获取 key 的有效时间(以秒为单位查看 key 还能存在多长时间):
    • 正数:剩余的存活时间(单位:秒)。
    • -1:永不过期。
    • -2:已过期。
  1. ttl key
  1. pttl key
  • 切换 key 从时效性转为永久性:
  1. persist key
  • 切换数据库:
  1. select index
  • 查看当前数据库的 key 的数量:
  1. dbsize
  • 清空当前数据库:
  1. flushdb
  • 清空所有数据库:
  1. flushall
  • 将 key 从当前数据库移动到其他数据库:
  1. move key db
  • 退出命令行客户端(redis-cli):
  1. exit
  1. quit
  • 测试客户端和服务端是否联通:
  1. ping
  • 为 key 改名:
  1. rename key newkey
  1. renamenx key newkey

5.2 string 命令

  • 添加 / 修改数据:
  1. set key value
  • 获取数据:
  1. get key
  • 追加信息到原始信息后边(如果原始信息存在就追加,否则新建):
  1. append key value
  • 获取数据字符个数(字符串长度):
  1. strlen key
  • 当 key 不存的时候,添加数据:
  1. setnx key value
  • 添加 / 修改多个数据:
  1. mset key1 value2 key2 value2 ...
  • 获取多个数据:
  1. mget key1
  • 自增 1 :
    • 将 key 中储存的数字值增 1 。
    • 只能对数字值操作,如果为空,新增值为 1 。
  1. incr key
  • 自减 1:
    • 将 key 中储存的数字值减 1 。
    • 只能对数字值操作,如果为空,新增值为 -1 。
  1. decr key
  • 给指定 key 对应的 value 增加指定的值:
  1. # 原值 + increment
  2. incrby key increment
  • 给指定 key 对应的 value 减少指定的值:
  1. # 原值 - decrement
  2. decrby key decrement
  • 添加数据的同时,设置过期时间:
  1. setex key seconds value
  1. psetex key milliseconds value

5.2 list 命令

  • 添加数据(从左边放入元素):
  1. lpush key value1 [value2] ...
  • 添加数据(从右边放入元素):
  1. rpush key value1 [value2] ...
  • 从左边弹出一个元素:
  1. lpop key
  • 从右边弹出一个元素:
  1. rpop key
  • 返回 list 集合的长度:
  1. llen key
  • 根据 list 集合的索引打印元素数据(正着数:0,1,2,3,…,倒着数:-1,-2,-3,…):
  1. lrange key start end
  • 根据索引从 list 集合中获取元素:
  1. lindex key index
  • 根据 count 指定的数据从 key 对应的 list 中删除 value(具体执行从左向右,遇到一个删除一个,直到删除完毕) :
  1. lrem key count value
  • 在 value 前面或后面插入新值(如果 value 有重复,从左到右,以第一个遇到的 value 为基准):
  1. linsert key before|after value new-value

5.3 set 命令

  • 添加数据:
  1. sadd key member1 [member2] ...
  • 返回指定的 set 集合中所有的元素:
  1. smembers key
  • 删除数据:
  1. srem key member1 [member2] ...
  • 返回 set 集合中元素的数量:
  1. scard key
  • 判断指定的元素是否在 set 集合中,如果返回 1 ,表示存在;如果返回 0 ,表示不存在:
  1. sismember key memeber
  • 随机从 set 集合中获取指定数量 n 个元素(不会删除数据)。
  1. srandmember key [count]
  • 随机获取 set 集合中的某个元素,并将该元素移除 set 集合:
  1. spop key [count]
  • 获取多个 set 集合的交集:
  1. sinter key1 [key2] ...
  • 获取多个 set 集合的并集:
  1. sunion key1 [key2] ...
  • 获取多个 set 集合的差集:
  1. sdiff key1 [key2] ...
  • 将多个 set 集合的交集存储到指定的集合中:
  1. sinterstore destination key1 [key2]
  • 将多个 set 集合的并集存储到指定的集合中:
  1. sunionstore destination key1 [key2]
  • 将多个 set 集合的差集存储到指定的集合中:
  1. sdiffstore destination key1 [key2]
  • 将指定数据从原始集合移动到目标集合中:
  1. smove source destination member

5.4 hash 命令

  • 添加 / 修改数据:
  1. hset key field value
  • 根据指定的 field 获取对应的 value :
  1. hget key field
  • 获取指定 hash 集合中的 filed 和 value :
  1. hgetall key
  • 删除数据:
  1. hdel key field1 [field2]
  • 批量添加 / 修改数据:
  1. hmset key field1 value1 field2 value2 ...
  • 批量获取数据:
  1. hmget key field1 field2 ...
  • 获取 hash 集合中 filed 的数量:
  1. hlen key
  • 判断 hash 集合中是否存在指定的 field :
  1. hexists key field
  • 获取 hash 集合中所有的 filed :
  1. hkeys key
  • 获取 hash 集合总所有的 value :
  1. hvals key
  • 给 hash 集合中指定的 field 增加或减少指定的 increment:
    • increment 为正数,表示增加。
    • increment 为负数,表示减少。
  1. hincrby key field increment

5.5 zset(sorted_set) 命令

  • 添加数据:
  1. zadd key socre1 member1 [score2 member2] ...
  • 获取全部数据:
  1. zrange key start stop [withscores]
  1. zrevrange key start stop [withscores]
  • 删除数据:
  1. zrem key member [member2] ...
  • 根据条件获取数据:
  1. zrangebyscore key min max [withscores] [limit offset count]
  1. zrevrangebysocre key max min [withscores] [limit offset count]
  • 返回集合中元素的个数:
  1. zcard key
  1. zcount key min max
  • 获取集合中元素对应的索引:
  1. zrank key member
  1. zrevrank key member
  • 交集:
  1. zinterstore destination numkeys key1 [key2]...
  • 并集:
  1. zunionstore destination numkeys key1 [key2]...
  • score 值的获取:
  1. zscore key member
  • score 的修改:
  1. zincrby key increment member