Redis入门基础操作常用解决方案
Redis
NoSQL 入门和概述
问题现象
- 海量用户的访问
- 高并发,短时间被大量用户访问
根本原因(关系型数据库)
- 数据存储到磁盘上的,IO 性能底下
- 关系型数据库关系复杂,网状结构,牵一发而动全身,扩展性差,不便于大规模集群
解决思路
- 降低磁盘 IO 次数,越低越好,使用内存来存储
- 去除数据简关系,关系越简单越好,不存储关系,只存储数据
什么是 NoSQL
- 即 Not-Only SQL(泛指非关系的数据库),意为,不能仅仅只用 SQL,因该使用其他的方法,这种其他的东西,就泛指为 NoSQL ,而不是指具体的 SQL 语言,作为关系型数据库的补充(实际上,数据还是存储在硬盘上的,不能将数据全部存储在内存中去)
作用
- 应对基于海量用户海量数据前提下的数据处理问题
特征
- 可扩容,可伸缩
- 大数据量下的高性能
- 灵活的数据模型
- 高可用
常见的 NoSQL 数据库
- Redis
- memcache
- Hbase
- MongoDB
常见的解决方案

Redis 简介
-
Redis 简介
- Redis (Remote Dictionary Server,远程字典服务) 是用 C 语言开发的一个开源高性能键值对(Key:Value)的数据库
- 特点
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作,也就是单任务的,处理完一个任务再处理下一个任务而不是同时处理多个任务
- 高性能,50 个并发执行 100000 个请求,IO 速度分别为 110000次/s 和 81000次/s
- 多数据类型支持
- 持久化支持,可以进行数据的灾难恢复,例如突然断电
- 应用
- 为热点数据加速查询,例如热点新闻,热点商品等在某一时段可能成为热点事物并被同时大量访问的东西
- 任务队列,如秒杀,抢购,查询等
- 即时信息的查看,例如聊天记录,排行榜等
- 时效性信息的控制,例如短信验证码的有效期
- 消息队列
- 分布式锁
Redis 下载安装 & 启动
- Linux 版本(适用于企业级开发)
- Redis 高级开始使用
- Windows 版本(适用于初学者)
- Redis 入门使用
- 解压 redis 压缩包
- 进入解压目录,双击运行 redis-server.exe
-

-
其中,port 代表 redis 启动的端口号,pid 代表一个 redis 服务对象的 id ,用于区分多个不同的 redis 服务
-
- 再次双击运行 redis-cli.exe 进入 redis 客户端
Redis 基础操作
基本操作命令
-
设置 key value 数据
-
命令: set key value 示例: set name zhangsan
-
-
数据查询
-
命令: get key 示例: get name -
若对应的数据不存在,返回空( nil )
-
-
清屏
-
命令: clear
-
-
帮助命令
-
命令: help 指令 示例: help set
-
-
退出命令
-
exit quit
-
数据存储类型
作为缓存使用
- 原始业务功能设计
- 秒杀
- 618 活动
- 双 11 活动
- 12306 网上购票
- 突发的高频访问数据
- 突发的时政要闻等,微博热搜
- 高频,复杂的统计数据
- 在线直播数据,在线人数
- 投票数据
附加功能
- 系统功能优化升级
- 单服务器升级集群
- Session 管理
- Token 管理
Redis 数据类型
- 基于上述缓存和附加功能,总结出 Redis 的物种常用数据类型
- string 字符串类型,类比于 Java 中的 String
- list 列表类型,类比于 Java 中的 LinkedList
- hash 散列类型,类比于 Java 中的 HashMap
- set 集合类型,类比于 Java 中的 HashSet
- sorted_set 有序集合类型,类比于 Java 中的 TreeSet
Redis 数据存储格式
- redis 自身是一个 Map,其中所有的数据都是采用 key:value 的形式存储的
- redis 的数据类型指的是 value 的数据类型,key 的数据类型永远都是 value
string 类型
简介
- 存储的是单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 一个数据存储空间保存一个数据
- 通常使用字符串,如果字符串以整数形式表示,则可以作为数字操作使用,但其类型任然是字符串
基本操作
-
添加/修改数据
-
命令: set key value 示例: set name zhangsan //添加数据set name lisi //对同一个 key 再次赋值,就为修改数据
-
-
获取数据
-
命令: get key 示例: get name
-
-
删除数据
-
命令: del key 示例: del name
-
-
补充
- redis 中,操作的成功与失败,分别用 (integer)0,(integer)1 表示
-
添加修改多个数据(多个数据中原始存在的的则会覆盖)
-
命令: mset key1 value1 key2 value2 ... 示例: mset name zhangsan age 18 ...
-
-
获取多个数据(不存在的数据会给出 nil 值)
-
命令: mget key1 key2 ... 示例: mget name age ...
-
-
追加信息到原始信息后部(若原始信息存在则追加,否则新建数据)
-
命令: append key value 示例: append age + 例如:原先 age 的数据为 18,append 后为: 18+ -
返回值为追加后,字符串的长度
-
-
获取字符串长度
-
命令: strlen key 示例: strlen name
-
单数据操作 & 多数据操作如何选择
-
单数据操作
- get
- set
-
多数据操作
- mget
- mset
-
如何选择
- 根据具体情况而定,面对一次性非常大数据量的指令,例如单次进行一亿次的 set 操作,不管是单数据操作还是多数据操作,都会消耗大量时间,此时,可以考虑对数据进行一个切分操作,将一亿条数据切分成一百万一份,然后使用多数据操作进行多次执行
string 扩展操作 1
-
业务场景
- 对于大量数据,分表是基本操作,使用多张表存储同类型的数据,但是对应的主键 id 必须保证统一性,不能重复,Oracle 数据库具有 sequence 设定,MySQL 并不具备类似的机制,该如何解决?
-
解决方案
- 将主键 id 交由 redis 来控制
- redis 用于控制数据库表主键,为数据库表主键提供生成策略,保证数据库表主键的唯一性
- 此方案适用于所有数据库,且支持数据库集群
-
具体操作
-
设置数值数据增加指定范围的值
-
命令: incr key // 对字符串数值形式的数据进行 + 1 的操作 示例: incr num // num 对应的 value 值 + 1命令: incrby key 增加值 // 对字符串数值形式的数据进行加上指定数值的操作 示例: incrby num 10 // num 对应的 value 值 + 10命令: incrbyfloat key 增加值 // 对字符串数值新式的数据进行加上指定浮点数的操作 示例: incrbyfloat num 1.5 // num 对应的 value 值 + 1.5
-
-
设置数值数据减少指定范围的值
-
命令: decr key // 对字符串数值形式的数据进行 - 1 的操作 命令: decrby key 减少值 // 对字符串数值形式的数据进行减去指定数值的操作
-
-
补充
- incrby ,incrbyfloat,dscrby 都能够使用负数来实现增减数值的效果
- 当数值为浮点数时,就不能直接使用 incrby 进行整数或浮点数的增减,需要使用 incrbyfloat
- string 在 redis 内部默认就是一个字符串,当遇到增减类操作时,就会转换为数值进行计算
- redis 的所有操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行,无需考虑并发带来的数据影响
- 注意
- 按数值进行操作的数据,如果原始数据不能转换为数值,或超过 redis 数值范围上限( 9223372036854775807,Java long 数据类型的最大值: Long.MAX_VALUE),将报错
-
string 扩展操作 2
-
业务场景
- 海选投票中,每个账号每四个小时只能投 1 票,商品的促销期,只能维持三天,如何字段控制这种时效性?
-
解决方案
- 设置一个数据具有指定的生命周期
-
具体操作
-
命令: setex key 秒数 value // 设置 key 对应的 value 数据生命周期为多少秒 示例: setex num 5 value // 设置 num 对应的 value 数据生命周期为 5 秒命令: psetex key 毫秒数 value // 设置 key 对应的 value 数据生命周期为多少毫秒 示例: psetex num 5 value // 设置 num 对应的 value 数据生命周期为 5 毫秒
-
-
补充
- 当设置了数据的时效性,且在时效范围内时,对数据进行了 set 操作,则之前设置的时效性作废
string 数据类型操作注意事项
- 数据操作不成功与数据正常操作之间的差异
- 表示运行结果是否成功
- (integer) 0 失败
- (integer) 1 成功
- 表示运行结果值
- (integer) 3 3个
- (integer) 1 1个
- 数据未获取到
- (nil) 等同于 null
- 数据存储最大量
- 512 MB
- 数值计算最大范围
- -9223372036854775807 ~ 9223372036854775807
- 表示运行结果是否成功
string 类型应用场景
-
业务场景
- 微博上大 V 的关注数,粉丝数等,这就是一个热点信息,每一个查看该主页的人,都会看到该信息
-
解决方案
- 以用户主键和属性作为 key ,后台设定定时刷新策略即可
- 例如
- user-id-13132323423:fans —> 1212133 代表的是 user 表中 id 为 13132323423 用户的 fans 数量
- user-id-122132323 —> {id:122132323,name:张三,fans:323233} 代表的是 user 表中 id 为 122132323 用户的 fans 信息,以 json 格式进行存储
-
数据库中热点数据 key 的命名规范
-
表名 主键名 主键值 字段名
user : id : 1001 : name表名 主键名 主键值 字段名
order : id : 1143 : order_name
-
hash 类型
hash 简介
-

- 通过 field 和 value 存储多条数据作为一个存储空间,将这整个存储空间作为 key 的 value,这个 value 的数据类型就叫 hash
- 一个存储空间保存多个键值对数据
-
hash 存储结构优化
- 当 hash 中存储的 field 较少时,存储结构优化为类数组的结构
- 如果 hash 中存储的 field 较多时,存储结构使用 HashMap 结构
hash 基本操作
-
添加/修改数据
-
命令: hset key field value 示例: hset user name zhangsan
-
-
获取数据
-
命令: hget key field 示例: hget user name命令: hgetall key // 获取 key 对应的所有 field 数据 示例: hgetall user
-
-
删除数据(也删除多个 field 的数据)
-
命令: hdel key field1 [field2] 示例: hdel user name age
-
-
添加/修改多个数据
-
命令: hmset key field1 value1 field2 value2 ...
-
-
获取多个数据
-
命令: hmget key field1 field2 ...
-
-
获取 hash 表中 key 对应字段( field )的数量
-
命令: hlen key
-
-
获取 hash 表中是否存在指定字段
-
命令: hexists key field
-
hash 类型数据扩展操
-
获取 hash 表中 key 对应的所有字段名或字段值
-
命令: hkeys key // 获取指定 key 对应的所有 field 的值 命令: hvals key // 获取指定 key 对应的所有 filed 的 value 值
-
-
设置指定字段的数值数据增加指定范围的值
-
命令: hincrby key field 增加值 // 执行指定 key 的数值类型的 field 属性加上指定值的操作 命令: hincrbyfloat key field 增加值 // 执行指定 key 的数值类型的 field 属性加上指定浮点值的操作 -
补充
- 当操作的数值类型 filed 为浮点数据时,便不能使用 hincrby 进行操作了
- 注意
- hash 数据类型中的 value 只能存储字符串,不允许存储其他数据类型,不允许存在嵌套现象,如果数据未获取到,对应的值为 (nil)
- 每个 hash 存储的键值对为 2^32 - 1 个
- hash 的类型十分贴近对象的数据存储形式,并且可以灵活的删除对象属性,但是,hash 设计的初衷并不是为了存储大量对象而设计的,切记不可滥用,更不可将 hash 作为对象列表使用
- hgetall 操作可以获取全部的 field,如果内部 field 过多,遍历整体数据的效率过低,可能成为数据访问的瓶颈,推荐需要那个 field 就获取那个 field
-
hash 类型应用场景 1
-
业务场景
- 电商网站购物车设计与实现
-
数据模型
-
业务分析
- 使用用户的 id 作为 key
- 使用 商品 id 作为 field (购物车中的多个不同商品,对应的就是 hash 中的多个 field)
- 每个商品的数量使用 field 的 value 来表示,可以对商品数量进行修改
- 全选商品时,使用 hgetall 来获取所有的商品 id
- 计算购物车中总量时,使用 hlen
- 计算商品总数时,使用 hvals 将返回的值进行累加
- 删除商品时,使用 hdel
- 某一个商品增加数量,使用 hincrby
-
缺点
- 当前的设计只是将数据存储到了 redis 中,并没有起到加速的作用,具体到用户查看购物车时,还需要根据 redis 中存储的信息到数据库中进行二次查询
-
解决方案
-
每条购物车中的商品记录保存两条 field
- field1 专用于保存购买数量
- 命名格式: 商品id:nums
- 保存数据: 商品数量
- field2 专用于保存购物车中显示的信息,例如文字描述,图片路径,商家信息等
- 命名格式: 商品id:info
- 保存数据: json 格式的商品信息
- field1 专用于保存购买数量
-
优化
-
此时,保存的商品信息,对于同一个商品,实际上是一样的,差距只是在于购买的商品数量不同,因此,可以将商品信息抽取出来,将商品的 id 作为 field,商品信息的 json 字符串作为 value,成为一个键值对,也就是一个商品 hash,这样,就可以将多个不同的商品 hash 独立出来,作为一个公共的 hsah 区域(也可以根据商品种类,分为多个公共 hash 区域),每个用户的购物车中,保存的就只是商品购买数量,至于商品的信息,全部都去公共的商品 hash 中通过商品 id 进行查询
-
不可能一开始就将所有的商品全部加入到商品公共 hash 中去,因为每时每刻都可能会出现新的商品上架,可以采取用户购买了一种商品,就将该商品加入到商品公共 hash 中去,同时为了避免重复的商品信息,使用
命令: hsetnx key field value // 判断当前 key 对应的 field 是否有值,若有,则不进行添加操作来添加商品信息,该命令会先判断当前 key 对应的 field 是否有值,若有,则不进行添加操作,若该商品的信息发生变化,则依然通过 hset 来修改其信息
-
-
hash 类型应用场景 2
- 业务场景
- 双 11 活动,某种商品限时限量购买,需要进行抢购
- 解决方案
-

-
商家 id 作为 key,商品 id 作为 field,参与抢购的数量作为 value
-
在抢购时,每当用户抢购一个商品,商品的数量就进行降值操作,使用 hincrby 通过负数值来实现
-
- 补充
- 原则上,redis 只做数据存储,不涉及业务逻辑,涉及到业务逻辑的地方,应该视实际情况慎重考虑
- string 存储数据 与 hash 存储数据
- 通过 string 存储的数据,更加考虑数据的整体性,注重对数据的读操作
- hash 存储的数据,具有一种群组特性,将一系列数据包装起来,对外使用一个单独的 key 进行读取,注重的是对数据的更新
- 实际使用过程中,根据二者的特性,灵活选取
list 类型
简介
- 数据存储需求: 存储多个数据,并对数据进入存储空间的顺序进行区分
- 需要的存储结构: 一个存储空间保存多个数据,且通过数据可以体现进入的顺序
- list 类型: 保存多个数据,底层使用双向链表的存储结构进行实现
list 类型基本操作
-
添加/修改数据
-
命令: lpush key value1 [value2] ... // 从左边开始添加多个数据到 list 中 命令: rpush key value1 [value2] ... // 从右边开始添加多个数据到 list 中
-
-
获取数据(获取数据统一从左边进行获取)
-
// 获取列表中指定索引范围的数据(索引从 0 开始,若添加数据时,使用的是 lpush 则查出的数据顺序与添加时的顺 序相反,反之亦然(类似于 Stack 结构);倒数第一个索引为 -1,可以直接通过 lrange 0 -1 查询出所有数据) 命令: lrange key start stop// 获取列表中指定所有的数据(索引从 0 开始) 命令: lindex key index// 获取指定列表的长度 命令: llen key
-
-
获取并移除数据(可以从右边也可以从左边获取)
-
// 从左边出元素 命令: lpop key// 从右边出元素 命令: rpop key
-
-
补充
- 关于 list 中添加元素,出元素等操作,可以结合 Stack 的数据结构进行理解
list 扩展操作 1
-
规定时间内,从多个 list 中获取并移除数据
-
命令: blpop key1 [key2] ... timeout // 从左边开始移除 命令: brpop key1 [key2] ... timeout // 从右边开始移除 -
解析
- 此操作是阻塞形式的,当设置了阻塞时间,当前执行此命令的线程就会进入等待状态,若 list 中本来就有数据,则会立即取出来,否则,在设置的等待时间内,若对 list 进行了 lpush 或 rpush 操作,使得 list 中有数据,则之前等待执行 blpop 或 brpop 的线程会立即获取到该数据
-
list 扩展操作 2
-
业务场景
- 微信朋友圈的点赞,要求按照点赞的顺序显示点赞的好友信息
-
解决方案
-

-
每一条朋友圈都是唯一的,可以作为一个 key,而朋友圈的点赞列表就作为一个 list
-
当有人点赞时,执行 lpush 或 rpush 操作将点赞人的信息放入 list 中并显示出来
-
当有人取消点赞时,使用
// 移除指定的数据,意为: 删除指定 key 对应的 list 中的 count 个 value 数据,也就是说,list 中有 3 个 a ,可以使用 lrem list 1 a 只删除一个 a ,也可以使用 lrem list 3 a 将 3 个 a 都删除 命令: lrem key count value
-
-
补充
- redis 适合用于具有先后顺序的数据控制
list 数据操作注意事项
- list 中保存的数据都是 string 类型的,数据的总容量是有限的,最多 2^32 - 1 个元素(4294967295)
- list 具有索引的概念,可以按照索引操作,但是实际操作时,通常以队列或栈的形式进行数据的出入操作
- 获取全部数据操作结束索引为 -1,以此类推
- list 可以对数据进行分页操作,通常第一页的信息来源于 list (速度快),第二页及之后的信息来源于数据库
list 类型应用场景 1
- 业务场景
- 新浪,腾讯,微博中,个人用户的关注列表,粉丝列表等,粉丝列表将最近的关注进行展示等
- 多台日志服务器的日志搜集
- 解决方案
- 依赖 list 的数据具有顺序的特性进行管理
- 使用队列的模型解决多路信息汇总的问题
- 使用栈模型解决最新消息的问题
- 补充
- redis 应用于最新消息的展示
set 类型
简介
- 存储大量的数据,在查询方面提供高效率
- 需要的存储结构
- 保存大量的数据,搞笑的内部存储机制,便于查询
- set 类型,与 hash 存储结构完全相同,仅仅存储键,不存储值(nil),键不允许重复
set 类型数据的基本操作
-
添加数据
-
命令: sadd key value1 value2
-
-
获取全部数据
-
命令: smembers key
-
-
删除数据
-
命令: srem key value1 value2
-
-
获取集合数据总量
-
命令: scard key
-
-
判断集合中是否包含指定的数据
-
sismember key value
-
set 数据类型扩展操作 1
-
业务场景
- 每位用户在首次使用今日头条时,会设置 3 项爱好内容,但是后期为了增加用户的活跃度,兴趣点,必须让用户对其他类别的信息产生兴趣,如何实现?
-
业务分析
- 系统分析出各个分类的最新或最热点信息条目组织成 set 集合
- 随机挑选出其中的部分信息
- 配合用户关注信息分类中的热点信息组织成展示的信息集合
-
解决方案
-
随机获取集合中指定数量的数据
-
命令: srandmember key [count]
-
-
随机获取集合中的某个数据并将数据移出集合
-
命令: spop key
-
-
补充
- redis 能够应用于随机推荐类信息的检索,例如歌单推荐,热点新闻推荐等等
-
set 数据类型扩展操作 2
-
业务场景
- 社交软件中,扩大用户的交际圈
- 用户微信公众号的关注度
- 美团给用户推荐美食
-
解决方案
-
求两个集合的交,并,差集
-
命令: sinter key1 [key2] // 交集 命令: sunion key1 [key2] // 并集// 差集(redis 中的差集是有方向性的,以在左边的 key 对应的集合为准,以右边的集合比较,相当于从左边的集合中元素去除与右边相比较,相同的元素,剩下的就叫差集) 命令: sdiff key1 [key2]
-
-
将两个集合的交,并,差集存储到指定集合中
-
命令: sinterstore 指定集合 key1 [key2] 命令: sunionstore 指定集合 key1 [key2] 命令: sdiffstore 指定集合 key1 [key2]
-
-
将指定数据从原始数据移动到指定目标集合中
-
命令: smove source 指定集合 指定元素
-
-
补充
- redis 可以用于同类信息的关联检索,二度关联检索,深度关联检索
- 显示共同关注
- 显示共同好友
- 由用户 A 出发,获取到好友用户 B 的好友信息列表
- 由用户 A 出发,获取到好友用户 B 的购物清单
-
set 操作注意事项
- set 操作数据不允许重复,如果添加的数据在 set 中已经存在,将只保留一份
- set 虽然和 hash 的结构很相似,但是无法启用 hash 中存储值的那部分空间
set 类型应用场景 1
- 业务场景
- 某公司有 12000 名员工,每位员工具有一个或多个角色,如何快速进行权限校验
- 解决方案
- 依赖 set 集合数据不重复的特征,依赖 set 集合 hash 存储结构特征,完成数据过滤与快速查询
- 根据不同角色的不同权限构造 set 集合
- 根据用户的角色,将对应权限的 set 集合,合并到某个用户对应的权限 set 集合中去
- 当用户执行权限时,根据用户的权限 set 集合,查询是否具有该权限
- 依赖 set 集合数据不重复的特征,依赖 set 集合 hash 存储结构特征,完成数据过滤与快速查询
- 补充
- redis 可以应用于同类数据的不重复数据的合并操作
set 应用场景 2
- 业务场景
- 统计网站的访问量(PV),独立访问(UV),独立IP(IP)
- PV: 网站被访问次数,可通过刷新页面提供访问量统计访问量
- UV: 网站被不同用户访问的次数,可提供 cookie 统计访问量,相同用户切换 ip 地址,UV 不变
- IP: 网站被不同 IP 地址访问的总次数,可通过 IP 地址,相同 IP 不同用户访问 IP 不变
- 统计网站的访问量(PV),独立访问(UV),独立IP(IP)
- 解决方案
- 使用 string 类型,通过 incr 统计日访问量
- 使用 set 模型,记录不同的 cookie ,通过 scard 统计 cookie 数量
- 使用 set 模型,记录不同的 ip 的数量,通过 scard 统计 ip 数量
set 类型应用场景 3
- 业务场景
- 某些网站的信息具有一定价值,为了不被不法分子窃取,例如爬虫等,需要设置黑名单和白名单来验证访问者的身份
- 解决方案
- 将用户的黑名单放入 set 集合中,当用户到访问达时,将其与黑名单 set 中的数据进行对比
- 还可以如果黑名单 set 过滤 ip ,设备信息等
sorted_set 类型
简介
- 新需求
- 数据的排序有利于数据的展示,需要提供一种根据自身特征进行排序的方式
- 新的存储模型,可以保存可排序的数据
- sorted_set 类型
- 在 set 的存储结构上添加可排序的字段 score
- 图示
sorted_set 基本操作
-
添加数据
-
命令: zadd key score1 member1 [score2 member2]
-
-
获取全部数据
-
// 升序获取 start 到 stop 索引之间的值(闭区间),加上 WITHSCORES 参数后,会显示 score 字段 命令: zrange key start stop [WITHSCORES] // 降序获取 start 到 stop 索引之间的值,加上 WITHSCORES 参数后,会显示 score 字段 命令: zrevrange key start stop [WITHSCORES]
-
-
删除数据
-
命令: zrem key member [member ...]
-
-
按条件获取数据
-
// 查询指定范围 min ~ max 内的数据(闭区间),升序,limit 限定查询结果,用法与 MySQL 相同 命令: zrangebyscore key min max [WITHSCORES] [LIMIT] // 查询指定范围 min ~ max 内的数据(闭区间),降序,limit 命令: zrevrangebyscore key max min [WITHSCORES]
-
-
按条件删除数据
-
// 删除 start 到 stop 索引之间的值(闭区间) 命令: zremrangebyrank key start stop 命令: zremrangebyscore key min max
-
-
注意
- min与max用于限定搜索查询的条件
- start与stop用于限定查询范围,作用于索引,表示开始和结束索引
- offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量
-
获取集合数据总量
-
// 获取数据总数 命令: zcard key// 获取指定范围 min ~ max(闭区间) 的数据总数 命令: zcount key min max
-
-
集合交并操作
-
// 多个集合的 交 操作,执行 交 操作时,还会对集合中的 score 进行求和操作(还有其他操作,如最大值等) 命令: zinterstore 保存结果的集合 参与操作的集合个数 key1 key2 ...// 多个集合的 并 操作,执行 并 操作时,还会对集合中的 score 进行求和操作(还有其他操作,如最大值等) 命令: zunionstore 保存结果的集合 参与操作的集合个数 key1 key2 ...
-
sorted_set 类型扩展操作
-
业务场景
- 票选
- 资源网站 TOP10
- 活跃度
- 好友亲密度排行
-
解决方案
-
// 获取数据对应的索引,升序 命令: zrank key member// 获取数对应的索引,降序 命令: zrevrank key member -
// 获取 score 值,升序 命令: zscore key member// score 值增加指定数值 命令: zincrby key increment member -
使用 zrank 或 zrevrank 获取排名数
-
使用 zscore 获取排名依据的值
-
使用 zincrby 修改排名依据,改动排名
-
sorted_set 注意事项
- score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
- score保存的数据也可以是一个双精度的 double 值,基于双精度浮点数的特征,可能会丢失精度,使用时需要慎重
- sorted_set 底层存储还是基于set 结构的,因此数据不能重复,如果重复添加相同的数据,score 值将被反复覆盖,保留最后一次修改的结果
sorted_set 类型应用场景 1
-
业务场景
- 对于一些应用提供的一些增值服务,会给出一些试用功能,限定试用时间
- 限时讨论,逾期作废等
- 如何有效管理此类过期信息
-
解决方案
-
对于基于时间线限定的任务处理,将处理时间记录为score值,利用排序功能区分处理的先后顺序
-
记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间
-
当新任务加入时,判定并更新当前下一个要处理的任务时间
-
为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_set。例如1小时内,1天内,周内, 月内,季内,年度等,操作时逐级提升,将即将操作的若干个任务纳入到1小时内处理的队列中
-
// 获取当前系统时间 // "1410856598" 秒(UNIX 时间戳) // "928370" 微秒(当前这一秒已经过去的微妙数,一微秒等于百万分之一秒) 命令: time
-
-
补充
- redis 应用于定时任务执行顺序的管理或任务过期管理
sorted_set 类型应用场景 2
- 业务场景
- 任务/消息权重设定应用,当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理?
- 解决方案
- 对于带有权重的任务,使用 score 设置权重即可
- 如果权重条件过多时,需要对排序score值进行处理,保障score值能够兼容2条件或者多条件,例如外贸 订单优先于国内订单,总裁订单优先于员工订单,经理订单优先于员工订单因 score 长度受限,需要对数据进行截断处理,尤其是时间设置为小时或分钟级即可(折算后)
- 先设定订单类别,后设定订单发起角色类别,整体 score 长度必须是统一的,不足位补 0。第一排序规则首 位不得是 0
- 例如外贸 101,国内 102,经理 004,员工 008。
- 员工下的外贸单 score 值为 101008(优先)
- 经理下的国内单 score 值为 102004
数据类型实践案例 1
-
业务场景
- 人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价 语义识别服务,免费开放给企业试用,同时训练百度自己的模型。现对试用用户的使用行为进行限速,限制 每个用户每分钟最多发起10次调用
-
解决方案
- 设计计数器,记录调用次数,用于控制业务执行次数。以用户id作为key,使用次数作为value
- 在调用前获取次数,判断是否超过限定次数 不超过次数的情况下,每次调用计数+1,超过则业务调用失败
- 为计数器设置生命周期为指定周期,例如1分钟,自动清空周期内使用次数
- 图示
-
解决方案改良
-
取消最大值的判定,利用incr操作超过最大值( 9007199254740992 )抛出异常的形式替代每次判断是否大于最大值
-
判断是否为nil, 如果是,设置为Max-次数 如果不是,计数+1
-
遇到异常即: 操作超过上限,视为使用达到上限,则业务调用失败
-
生命周期到了后,清空使用次数
-
图示
-
补充
- redis 应用于按照次数来结算的服务控制
-
数据类型实践案例 2
- 业务场景
- 使用微信的过程中,当微信接收消息后,会默认将最近接收的消息置顶,当多个好友及关注的订阅号同时发 送消息时,该排序会不停的进行交替。同时还可以将重要的会话设置为置顶。一旦用户离线后,再次打开微 信时,消息该按照什么样的顺序显示?
- 解决方案
-
业务分析
-
依赖 list 的数据具有顺序的特征对消息进行管理,将 list 结构作为栈使用
-
对置顶与普通会话分别创建独立的 list 分别管理
-
当某个 list 中接收到用户消息后,将消息发送方的 id 从 list 的一侧加入 list(此处设定左侧)
-
多个相同 id 发出的消息反复入栈会出现问题,在入栈之前无论是否具有当前 id 对应的消息,先删除对应 id
-
推送消息时先推送置顶会话 list,再推送普通会话 list,推送完成的 list 清除所有数据
-
消息的数量,也就是微信用户对话数量采用计数器的思想另行记录,伴随 list 操作同步更新
-
Redis 解决方案总结
- Tips 1:redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
- Tips 2:redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
- Tips 3:redis应用于各种结构型和非结构型高热度数据访问加速
- Tips 4:redis 应用于购物车数据存储设计
- Tips 5:redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
- Tips 6:redis 应用于具有操作先后顺序的数据控制
- Tips 7:redis 应用于最新消息展示
- Tips 8:redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
- Tips 9:redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
- Tips 10:redis 应用于同类型不重复数据的合并、取交集操作
- Tips 11:redis 应用于同类型数据的快速去重
- Tips 12:redis 应用于基于黑名单与白名单设定的服务控制
- Tips 13:redis 应用于计数器组合排序功能对应的排名
- Tips 14:redis 应用于定时任务执行顺序管理或任务过期管理
- Tips 15:redis 应用于及时任务/消息队列执行管理
- Tips 16:redis 应用于按次结算的服务控制
- Tips 17:redis 应用于基于时间顺序的数据操作,而不关注具体时间
通用指令
Key 通用指令
- key 是一个字符串,通过 key 获取 redis 中保存的数据
- 对于 key 自身的操作,例如: 删除,判定存在,获取类型等
- 对于 key 有效性控制相关,例如有效期设定,判断是否有效等
- 对于 key 的快速查询
key 基本操作
-
删除指定 key
-
命令: del key
-
-
获取 key 是否存在
-
命令: exists key
-
-
获取 key 的类型
-
命令: type key
-
key 时效性操作
-
为 key 设置有效期
-
命令: expire key seconds // 设置秒单位的有效期 命令: pexpire key milliseconds // 设置毫秒单位的有效期 命令: expireat key timestamp // 设置有效期为时间戳 命令: pexpireat key milliseconds-timestamp // 设置有效期为毫秒时间戳
-
-
获取 key 的有效时间
-
命令: ttl key // 返回指定 key 剩余的有效期,若 key 不存在返回值为 -2,若 key 存在且失效,返回值为 -1 命令: pttl key // 返回的时间以毫秒为单位
-
-
切换 key 时效为永久
-
命令: persist key
-
key 查询操作
-
查询 key
-
命令: keys pattern // 通过匹配模式查询 key -
匹配模式
-
key 其他操作
-
为 key 改名
-
命令: rename key newkey // 若修改的 key 名称已经存在,会覆盖已经存在的 key 命令: renamenx key newkey // 如果修改的 key 名称不存在,则改名
-
-
对所有 key 排序
-
命令: sort [desc]// 对 list ,set,sorted-set 等集合类型排序(升序),通过 desc 控制排序规则
-
-
其他 key 通用操作
-
命令: help @generic
-
db 通用指令
问题引入
- key 的重复问题
- key是由程序员定义的
- redis在使用过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的 key
- 数据不区分种类、类别混杂在一起,极易出现重复或冲突
- 解决方案
- redis为每个服务提供有16个数据库,编号从 0 到 15
- 每个数据库之间的数据相互独立
db 基本操作
-
切换数据库
-
命令: select index
-
-
其他操作
-
命令: quit // 退出 命令: ping // 测试服务器是否联通,返回 PONG 代表服务器通,也可以通过参数自定义返回信息 命令: echo message // 控制台输出
-
-
数据移动
-
命令: move key db // 将指定 key 的数据移动到指定索引位置的数据库,原数据库的数据将被删除,移动到的数据库中,也不能存在相同的 key
-
-
数据清除
-
命令: dbsize // 查看数据库中有多少个 key 命令: flushdb // 刷新当前数据库的数据 命令: flushall // 刷新所有数据
-
Jedis
Jedis 简介
- Java 语言连接 redis 的服务之一
Jedis 初体验
-
package com.dhj;import org.junit.Test; import redis.clients.jedis.Jedis;public class JedisTest {@Testpublic void testJedis() {/*** 1.连接 redis,* 参数1: 需要连接 redis 服务的 ip* 参数2: 需要连接 redis 服务的 端口号*/Jedis jedis = new Jedis("127.0.0.1", 6379);/*** 2.操作 redis* jedis 中对 redis 的操作,与在 redis 中完全相同*/jedis.set("key1", "value1");System.out.println(jedis.get("key1"));// 3.关闭连接jedis.close();} }
Jedis 应用需求
- 业务场景
- 人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评 价语义识别服务,免费开放给企业试用,同时训练百度自己的模型。现对试用用户的使用行为进行限速, 限制每个用户每分钟最多发起10次调用
- 需求分析
- 设定一个服务方法,用于模拟实际业务调用的服务,内部采用打印模拟调用
- 在业务调用服务前调用控制单元,内部使用 redis 进行控制,参照之前的方案
- 对调用超限使用异常进行控制,异常处理设定为打印提示信息
- 主程序启动 3 个线程,分别表示 3 种不同用户的调用
Jedis 简单工具类制作
-
package com.dhj;import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig;import java.util.ResourceBundle;public class RedisUtil {static JedisPool jedisPool;static String host;static int port;static int maxTotal;static int maxIdle;// 静态代码块初始化 JedisPoolstatic {// 加载配置文件,使用 ResourceBundle 时,无需写上文件格式后缀ResourceBundle rb = ResourceBundle.getBundle("redis");host = rb.getString("redis.host");port = Integer.parseInt(rb.getString("redis.port"));maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 最大连接数jedisPoolConfig.setMaxTotal(maxTotal);// 活动连接数jedisPoolConfig.setMaxIdle(maxIdle);jedisPool = new JedisPool(jedisPoolConfig, host, port);}// 返回 Jedsi 对象public static Jedis getJedis() {return jedisPool.getResource();}// 关闭 Jedis 资源public static void closeJedis(Jedis jedis) {jedis.close();}// 测试一下public static void main(String[] args) {Jedis jedis = getJedis();jedis.set("key2", "zhangsan");System.out.println(jedis.get("key2"));} }
Redis 可视化工具
- 搜索引擎搜素下载 redis-desktop-manager 即可
基于 Linux 的 Redis
安装 redis
-
安装 redis
wget http://download.redis.io/releases/redis-?.?.?.tar.gz -
安装 gcc
yum install gcc-c++ -
若出现 ‘jemalloc/jemalloc.h:没有那个文件或目录’ 等字样,执行如下命令
make distcleanmake distclean -
否则直接执行
make -
出现如下字样 ‘Hint: It’s a good idea to run ‘make test’ ;’ 代表编译成功
-
再次执行
make install -
进入 redis 安装目录下的 src 目录
-
执行
- ./redis.server
- ./redis-cli
-
测试一下
- set name zhangsan
- get name
指定端口启动 redis
-
redis-server --port 端口号 -
客户端连接指定 redis 端口
redis-cli -p 端口号
指定配置文件的方式启动
-
在 redis 目录下查看 redis 配置文件并写入自定义的配置文件
cat redis.conf | grep -v '#' | grep -v '^$' > redis-6379.conf -
修改配置文件
port 6379 // 指定启动的端口号 daemonize yes // 是否以守护进程的方式启动,关闭后,会在控制台输出启动信息,开启则以后台方式启动 logfile "6379.log" // 日志文件名 dir /opt/module/redis-4.0.0/data // 日志文件的存放路径 -
以配置文件的方式启动
redis-server 配置文件路径 -
统一管理配置文件
- 在 redis 目录下创建 conf 目录,将配置文件移动到该目录下,方便统一管理
-
多个配置文件启动 redis 服务
- 通过多个配置文件,定义不同的启动信息,就可以快速启动多个 redis 服务
Redis 持久化
持久化简介
- 什么是持久化
- 利用永久性存储介质将数据进行保存,在特定时间将保存的数据进行恢复的工作机制称为持久化
- 为什么要持久化
- 防止数据的意外丢失,确保数据的安全性
- 持久化过程保存什么
- 将当前数据状态进行保存了,快照形式,存储数据结果,存储格式简单,关注点在数据
- 将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程
- 在 redis 中,两种方式都进行了使用,快照形似的称为 RDB,日志形式的称为 AOF
RDB
RDB 启动方式 1 之 save 指令
-
指令
save -
作用
- 手动执行一次保存操作
- 会在之前的 conf 配置文配置的目录下,生成 .rdb 配置文件
save 指令相关配置
配置解析
- dbfilename dump.rdb 说明:设置本地数据库文件名,默认值为 dump.rdb 经验:通常设置为 dump-端口号.rdb
- dir 说明:设置存储.rdb文件的路径 经验:通常设置成存储空间较大的目录中,目录名称data
- rdbcompression yes 说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩 经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
- rdbchecksum yes 说明:设置是否进行 RDB 文件格式校验,该校验过程在写文件和读文件过程均进行 经验:通常默认为开启状态,如果设置为 no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
配置修改
- 修改 conf 目录下的 redis 配置文件
bgsave 指令使用
-
由于 redis 是单线程的,save 指令的执行会阻塞当前 redis 服务器,知道当前 RDB 过程完成为止,可能造成长时间阻塞,线上环境不建议使用
-
save 指令后台执行
bgsave // 手动启用后台保存操作,但是不立即执行
RDB 启动方式 2 自动执行
save second changes
-
作用
- 在 second 的时间范围内,如果 key 的变化数量达到指定数量即进行持久化
-
参数
- second 监控时间范围
- changes 监控 key 的变化量
-
配置
-
在 conf 文件中进行配置
-
示例
save 900 1 save 300 10 save 60 10000
-
save 其他指令
-
关闭服务器时保存数据
shutdown save -
服务器运行过程中重启
debug reload
save 指令对比
RDB 优缺点
- 优点
- RDB 是一个紧凑压缩的二进制文件,存储效率较高
- RDB 内部存储的是 redis 在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景、
- RDB 恢复数据的速度要比 AOF 快很多
- 应用:服务器中每 x 小时执行 bgsave 备份,并将 RDB 文件拷贝到远程机器中,用于灾难恢复。
- 缺点
- RDB 方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
- bgsave 指令每次运行要执行 fork 操作创建子进程,要牺牲掉一些性能
- Redis 的众多版本中未进行 RDB 文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象
AOF
RDB 存储的弊端
- 存储的数据量大,效率低
- 大数据量下,IO 性能低
- 基于 fork 创建子进程,产生内存和 CPU 的额外消耗
- 因为并不是实时的持久化存储,可能带来宕机造成的数据丢失
解决思路
- 不存储全数据,记忆部分数据
- 记录的数据改为记录操作过程
- 对所有操作都进行记录,排除丢失数据的风险
AOF 简介
- ADF ( append only file ) 持久化,以独立的日志方式记录每次写命令,重启时,再执行 AOF 文件中的命令达到恢复数据的目的,与 RDB 相比,将记录数据改为记录数据产生的过程
- AOF 的主要作用是解决了数据持久化的实时性,目前已经是 redis 持久化的主流方式
AOF 写数据的三种策略
- always 每次
- 每次操作均写入同步到 AOF 文件中,数据零误差,性能较低
- everysec 每秒(建议使用,也是默认配置)
- 每秒将缓冲区中的指令同步到 AOF 文件中,数据准确率较高,性能也相对较高
- no 系统控制
- 由操作系统控制每次同步到AOF文件的周期,整体过程不可控
AOF 功能开启
-
配置 (在 conf 目录下修改配置文件)
appendonly yes|no // 开启 AOF,默认为不开启 appendfsync always|everysec|no // 配置 AOF 写数据的策略 -
AOF 相关配置
appendfilename filename // 修改 AOF 持久化文件的文件名 dir // 修改持久化文件的保存路径
AOF 重写
- 随着命令不断写入 AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF 文件重写是将 Redis进程内的数据转化为写命令同步到新 AOF 文件的过程。简单说就是将对同一个数据的若干个条命令执行结果转化成最终结果数据对应的指令进行记录
- 作用
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
AOF 重写规则
- 进程内已超时的数据不再写入文件
- 忽略无效指令,重写时使用进程内数据直接生成,这样新的 AOF 文件只保留最终数据的写入命令,如: del key1、 hdel key2、srem key3、set key4 111、set key4 222 等
- 对同一数据的多条写命令合并为一条命令 如lpush list1 a、lpush list1 b、 lpush list1 c 可以转化为: lpush list1 a b c。 为防止数据量过大造成客户端缓冲区溢出,对 list、set、hash、zset 等类型,每条指令最多写入64个元素
AOF 重写方式
-
手动重写( 后台运行 )
bgrewriteaof -
自动重写
auto-aof-rewrite-min-size size auto-aof-rewrite-percentage percentage -
自动重写触发条件设置
auto-aof-rewrite-min-size size // 自动 AOF 重写的最小尺寸,达到该值时,则自动重写 auto-aof-rewrite-percentage percent // 自动 AOF 重写的百分比,达到该百分比时,自动重写,该比值为当前的尺寸减去配置的尺寸比上配置尺寸,若最终结果 >= auto-aof-rewrite-percentage percent,则自动重写 -
上述信息以及其他运行信息,可以通过运行如下指令获得
info Persistence
AOF 重写流程


RDB & AOF 区别

RDB & AOF 如何选择
- 对数据非常敏感,建议使用默认的 AOF 持久化方案
- AOF持久化策略使用 everysecond,每秒钟 fsync一次。该策略 redis 仍可以保持很好的处理性能,当出 现问题时,最多丢失0-1秒内的数据。
- 注意:由于AOF文件存储体积较大,恢复速度较慢
- 数据呈现阶段有效性,建议使用 RDB 持久化方案
- 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段性数据恢复通常采用 RDB 方案
- 注意:利用 RDB 实现紧凑的数据持久化会使 Redis 降的很低,慎重使用
- 综合比对
- RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
- 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用 AOF
- 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用 RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启 RDB 和 AOF,重启后,Redis 优先使用 AOF 来恢复数据,降低丢失数据的量
持久化的应用场景
- Tips 1 redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
- Tips 2 redis 应用于具有操作先后顺序的数据控制
- Tips 3 redis 应用于最新消息展示
- Tips 4 redis 应用于基于黑名单与白名单设定的服务控制
- Tips 5 redis 应用于计数器组合排序功能对应的排名
- Tips 6 redis 应用于按次结算的服务控制
Redis 事务
什么是事务
- Redis 执行指令的过程中,多条连续执行的指令被干扰,打断,插队
- 事务就是一个命令执行的队列,将一系列预定义的命令包装成一个整体,当执行时,一次性按照添加的顺序一次执行,中间不会被打断或者干扰
- 一个队列,一次性,顺序性,排他性的执行一系列命令
事务的基本操作
-
开启事务
multi // 设定事务的开始位置,此指令执行后,后续的所有指令都将加入到事务中且不会立即执行 -
执行事务
exec // 设定事务结束的位置,同时执行事务,与 mulit 成对出现且成对使用 -
取消事务
discard // 终止当前事务,发生在 mulit 之后,exec 之前
事务的工作流程

事务的注意事项
- 如果事务队列中的指令存在语法错误,整个事务会被销毁,所有的命令都不会执行,包括语法正确的命令
- 如果事务操作中的命令语法无误,但是无法正确的执行,例如对 list 进行 incr 操作,那么最后只会执行语法事务队列中语法正确的命令,错误的命令将不会执行
锁
- 基于特定的事务执行
业务场景
- 天猫双 11 热卖过程中,对已经售罄的货物追加补货,4 个业务员都有权限进行补货。补货的操作可能是一系列的操作,牵扯到多个连续操作,如何保障不会重复操作?
业务分析
- 多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作
- 在操作之前锁定要操作的数据,一旦发生变化,终止当前操作
解决方案
-
对 key 添加监视锁,在执行 exec 前如果 key 发生了变化,终止事务执行
watch key1 [key2 ……] -
取消对所有 key 的监视
unwatch
注意
- 不能在事务里面进行 watch 操作
分布式锁
业务场景
- 天猫双 11 热卖过程中,对已经售罄的货物追加补货,且补货完成。客户购买热情高涨,3 秒内将所有商品购买完毕。本次补货已经将库存全部清空,如何避免最后一件商品不被多人同时购买?该问题被称为超卖问题
业务分析
- 使用 watch 监控一个key有没有改变已经不能解决问题,此处要监控的是具体数据
- 虽然 redis 是单线程的,但是多个客户端对同一数据同时进行操作时,如何避免不被同时修改?
解决方案
-
设置一个公共锁
命令: setnx lock-key value 示例: setnx lock-name "zhangsan" -
利用 setnx 命令的返回值特征,有值则返回设置失败,无值则返回设置成功
- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
- 对于返回设置失败的,不具有控制权,排队或等待
- 当某一个 redis 服务对一个 key 使用了 setnx 指令,则在该 redis 服务没有执行 del 命令删除该 key 时,其他的服务无法对该数据进行操作,相当于加上了一个锁
-
补充
/* 将 key 设置值为 value,如果 key 不存在,这种情况下等同 SET 命令。 当 key 存在时,什么也不做。 返回值 1: 如果 key 被设置了 0: 如果 key 没有被设置 */ setnx key value -
上述解决方案是一种设计上的概念,不是 redis 自带的机制,因此,需要保证执行相关操作时,具有一定的规范,例如约定好使用同一个公共锁,否则,该操作就具有风险性
死锁
业务场景
- 依赖分布式锁的机制,某个用户操作时,对应的客户机宕机了,此时以及获取到了锁,但是却无法释放锁,如何解决
业务分析
- 由于锁操作由用户控制加锁解锁,必定会存在加锁后未解锁的风险
- 需要解锁操作不能仅依赖用户控制,系统级别要给出对应的保底处理方案
解决方案
-
使用 expire 为锁 key 添加时间限定,到时不释放,放弃锁
expire lock-key second // 设置锁的生命周期时间为秒级 pexpire lock-key milliseconds // 设置锁的生命周期时间为毫秒级 -
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
-
例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
-
测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
-
锁时间设定推荐:最大耗时120%+平均网络延迟110%
-
如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可
数据删除 & 数据逐出策略
Redis 中数据的特征
- redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过 TTL 指令获取其状态
- xxx 具有时效性的数据
- -1 :永久有效的数据
- -2 :已经过期的数据 或 被删除的数据 或 未定义的数据
- 过期数据
- 指的是那些有时效性的,到达了有效期,而没有被马上删除的数据
过期数据底层存储结构

数据删除策略
数据删除策略的目标
- 在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体 redis 性能的下降,甚至引发服务器宕机或内存泄露
定时删除
- 创建一个定时器,当 key 设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
- 优点: 节约内存,到时就删除,快速释放掉不必要的内存占用
- 缺点: CPU 压力很大,无论 CPU 此时负载量多高,均占用 CPU,会影响 redis 服务器响应时间和指令吞吐量
- 总结: 用处理器性能换取存储空间(拿时间换空间)
惰性删除
- 数据到达时,不做处理,等待下次访问该数据时,若已经过期了,则删除该数据,若没有过期,则返回该数据
- 优点: 节约CPU性能,发现必须删除的时候才删除
- 缺点: 内存压力很大,出现长期占用内存的数据
- 总结:用存储空间换取处理器性能 expireIfNeeded() (拿时间换空间)
定期删除

逐出策略 ( 算法 )
问题
- 当新数据进入时,内存不足怎么办
新数据进入检测
-
redis 使用内存存储数据,在执行每一个命令前,会调用 freeMemoryIfNeeded() 检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis 要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法
-
注意: 逐出数据的过程不是 100% 能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
(error) OOM command not allowed when used memory >'maxmemory'
影响数据逐出的相关配置
-
最大可用内存
maxmemory // 占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。 -
每次选取待删除数据的个数
maxmemory-samples // 选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据 -
删除策略
maxmemory-policy // 达到最大内存后的,对被挑选出来的数据进行删除的策略 -
检测易失数据(可能会过期的数据集server.db[i].expires)
volatile-lru // 挑选最近最少使用的数据淘汰 volatile-lfu // 挑选最近使用次数最少的数据淘汰 volatile-ttl // 挑选将要过期的数据淘汰 volatile-random // 任意选择数据淘汰 -
检测全库数据 ( 所有数据集 server.db[i].dict )
allkeys-lru // 挑选最近最少使用的数据淘汰 allkeys-lfu // 挑选最近使用次数最少的数据淘汰 allkeys-random // 任意选择数据淘汰 -
放弃数据驱逐
no-enviction (驱逐):禁止驱逐数据 (redis4.0中默认策略),会引发错误 OOM(Out Of Memory)
数据逐出配置策略
-
使用 info 命令输出监控信息,查询缓存 hit (命中) 和 miss (丢失) 的次数,根据业务需求调优Redis配置
-
在 redis 配置文件中,配置逐出策略
maxmemory-policy volatile-lru
Redis 配置
-
设置服务器以守护进程的方式运行
daemonize yes|no -
绑定服务器(只能通过该 ip 地址访问)
bind 127.0.0.1 -
设置服务器端口号
port 6379 -
设置数据库数量
databases 16 -
设置服务器以指定日志记录级别
loglevel debug|verbose|notice|warning -
日志记录文件名
logfile 端口号.log- 注意: 日志级别开发期设置为verbose即可,生产环境中配置为notice,简化日志输出量,降低写日志IO的频度
-
设置同一时间最大客户连接数,默认无限制,当达到最大连接后,会关闭新连接
maxclients 0 -
客户端闲置等待最大时长,达到最大之后关闭连接,如需关闭该功能,设置为 0,单位: 秒
timeout 300 -
多服务器快捷配置,导入并加载指定配置文件信息,用于快速创建 redis 公共配置较多的 redis 实例配置文件,便于维护
include /path/server-端口号.conf
高级数据类型
bitmaps
bitmaps 基础操作
获取指定 key 对应的偏移量上的 bit 值
getbit key offset
设置指定 key 对应偏移量上的 bit 值,value 只能是 1 或 0
setbit key offset value
bitmaps 扩展操作
-
对指定 key 按位进行交、并、非、异或操作,并将结果保存到 destKey 中
bitop op destKey key1 [key2...]- and 交
- or 并
- not 非
- xor 异或
- 统计指定 key 中 1 的数量 bitop op destKey key1 [key2…] bitcount key [start end]
-
统计指定 key 中 1 的数量
bitcount key [start end]
HyperLogLog
基数
- 基数是数据集去重后元素个数,例如 [1,2,3,3,4],去重后 [1,2,3,4] 基数为 4
- HyperLogLog 是用来做基数统计的,运用了 LogLog 的算法
loglog算法

添加数据
pfadd key element [element ...]
统计数据
pfcount key [key ...]
合并数据
pfmerge destkey sourcekey [sourcekey...]
相关说明
- 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
- 核心是基数估算算法,最终数值存在一定误差
- 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
- 耗空间极小,每个 hyperloglog key 占用了 12K 的内存用于标记基数
- pfadd 命令不是一次性分配 12K 内存使用,会随着基数的增加内存逐渐增大
- Pfmerge 命令合并后占用的存储空间为 12K,无论合并之前数据量多少
GEO
简介
- 通过给定的多个地理位置坐标点,对多个地理位置坐标点做一些操作,如计算坐标点之间的距离
基本操作
添加坐标点
// 将坐标添加到指定的 key 中,给出坐标的横坐标 longitude 纵坐标 latitude 以及坐标的名称 member
geoadd key longitude latitude member [longitude latitude member ...]
获取坐标点
geopos key member [member ...]
计算坐标点距离( 水平位置的距离,默认单位为 m ,通过 unit 来定义单位,例如 km )
geodist key member1 member2 [unit]
根据坐标,求范围内的数据
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
根据点求范围内的数据
// 求出指定 key 中,指定点 member 指定范围 redius 单位为 m|km 内的点
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
获取指定点对应的坐标 hash 值
geohash key member [member ...
Redis 集群
主从复制
背景介绍
- 互联网三高架构
- 高并发
- 高性能
- 高可用 ( 全年的时间 - 服务器全年没有提供服务的时间 \ 全年时间 * 100% >= 99.999%,称之为高可用,即服务器全年宕机时间低于 315 秒,约 5.25 分钟)
- 单机 redis 的风险与问题
- 现象:机器故障,系统崩溃等
- 本质: 数据丢失,可能对业务造成灾难性影响
- 容量瓶颈
- 现象: 内存不足
- 本质: 没钱,硬件条件跟不上
- 结论: 放弃使用 redis
- 解决方案
- 准备多台服务器,互相连通,将数据复制到多个副本,保存在不同的服务器,并且保证数据是同步的,其中一台 redis 服务器宕机,其他 redis 服务器任然可以继续使用,实现 redis 的高可用,同时实现数据的冗余备份
多台服务器连接方案
-
提供数据方
- master,主服务器,主节点,连接主服务器的客户端叫做主客户端
-
数据提供方
- slave,从服务器,从节点,连接从服务器的客户端叫做从客户端
-
图示
-
需要解决的问题
- 数据同步
-
核心工作
- master 的数据复制到 slave 中
主从复制
- 主从复制即将 master 中的数据,及时的,有效的复制到 slvae 中
- 职责
- master
- 写数据
- 执行写操作,将出现变化的数据自动同步到 slave
- 读数据(可忽略)
- slave
- 读数据
- 写数据(禁止)
- master
- 作用
- 读写分离,master 写,slave 读,提高服务器的读写负载能力
- 负载均衡: 基于主从结构,配合读写分离,由 slave 分担 master 负载,并根据需求的变化,改变 slave 的数量,通过多个从节点分担数据读取负载,大大提高 redis 服务器并发量与数据吞吐量
- 故障恢复: 当 master 出现问题时,由 slave 提供服务,实现快速的故障恢复
- 数据冗余: 实现数据热备份,是持久化之外的一种数据冗余方式
- 高可用基石: 基于主从复制,构建哨兵模式与集群,实现 redis 的高可用方案
主从复制三个阶段
主从复制工作流程
-
总述
- 建立连接,slave 连接上 master 节点
- 数据同步,master 对 slave 进行数同步
- 命令传播,master 对 slave 进行数据的反复同步
-
图示

-
建立连接
- 建立 master 到 slave 的连接,使得 master 能够识别到 slave 并保存 slave 的端口号
- 图解

搭建主从
-
编辑配置 .conf 文件
// 主节点 bind 192.168.175.132 // 绑定本机 ip 地址 appedonly no // 关闭 aof 持久化 pidfile /var/run/redis_6379.pid // pid 文件 dbfilename dump-6379.rdb // rdb 备份文件名 port 6379 // 启动端口号// 从节点 1 bind 192.168.175.133 // 绑定本机 ip 地址 appedonly no // 关闭 aof 持久化 pidfile /var/run/redis_6380.pid // pid 文件 dbfilename dump-6380.rdb // rdb 备份文件名 port 6380 // 启动端口号// 从节点 2 bind 192.168.175.134 // 绑定本机 ip 地址 appedonly no // 关闭 aof 持久化 pidfile /var/run/redis_6381.pid // pid 文件 dbfilename dump-6381.rdb // rdb 备份文件名 port 6381 // 启动端口号... 若为多台主从结构,以此类推 -
通过配置文件,启动三台 redis 服务器
redis-server ./myconf/myredis.conf // 通过指定配置文件的路径启动 redis-server redis-cli -h 192.168.175.132 -p 6379 // 通过指定的 ip 和端口连接 redis 服务 -
连接 redis 服务器客户端,通过 info replication 查看主从复制的相关信息
#Replication role:master // 当前服务器的角色,为 master connected_slaves:0 // 从机的连接数 master_failover_state:no-failover master_replid:6e48debd07a2655e5e4478ba7c5930cf1f02c531 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:0 second_repl_offset:-1 repl_backlog_active:0 repl_backlog_size:1048576 repl_backlog_first_byte_offset:0 repl_backlog_histlen:0 -
配置主从1(一主多仆)
// 这里以 6379 为主机,6380 和 6381 作为从机 SLAVEOF 192.168.175..132 6379 // 配置 ip 为 192.168.175.132 端口号 6379 作为当前 redis 服务器的主机,可配置多台 // 也可以通过配置文件来永久配置从机(通过命令配置的从机,在重启后会失效) slaveof <master ip> <master port> -
配置完成,到 master 查看
#Replication role:master // 当前机器为 master 机,任何没有从关系的机器都为 master 机,以此类推,树形结构 connected_slaves:2 // 有两台从机 slave0:ip=192.168.175.133,port=6380,state=online,offset=84,lag=0 slave1:ip=192.168.175.134,port=6381,state=online,offset=84,lag=1 master_failover_state:no-failover master_replid:72d1dd670d53ffd686e0ded5aecddabe21423dc5 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:84 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:84 -
测试
1. 使用 ping 命令测试,返沪结果 pong 2. master 机保存数据,其余 slave 机查看是否有数据,有,则主从同步关联成功 -
注意
- 作为从节点的 redis 服务器不能做写操作
- 当其中一台从节点挂掉,再次连接 master 时,master 会将数据从头开始复制到从节点上
- 当 master 节点挂掉,从节点依然是从节点,不会替代 master 节点,当 master 节点恢复时,依然能够作为 master 节点正常工作
-
主从复制原理
- 当从服务器连接上主服务器后,从服务器向主服务器发送 sync 进行数据同步的命令
- 主服务器接收到从服务器的同步消息,主服务器进行持久化,然后把持久化的 rdb 文件发送给从服务器,从服务器读取 rdb 文件中的数据
-
配置主从2(薪火相传)
- 一台 slave 节点连接了 master 节点后,也可以作为另一台 slave 节点的主服务器,之间相关联,这样就能够缓解只有一台 master 节点时,master 节点的压力,达到去中心化的效果
- 操作方法
- 选定一个 Slave 作为 master,将其他的 slave 连接到该 Slave,以此类推
-
配置主从3(反客为主)
- 当一个 master 节点挂掉时,其他的 slave 节点可以迅速的补充上来,作为 master,降低了使用一个 master 的风险
- 操作方法
- 在 slave 节点上,执行 slaveof no one ,表示,在 master 节点挂掉时,将该 slave 作为 master
哨兵模式
-
什么是哨兵模式
- 自动监控 master 是否正常,如果出现故障,根据投票数,自动选举 slave 机转换为 master 机,选举的原则为节点机 conf 配置文件中 slave-priority 的值
-
搭建哨兵模式
-
自定义创建 sentinel.conf 文件
-
配置哨兵,填写如下内容
// sentinel monitor 监控 mymaster 主机,主机 ip 为 127.0.0.1 端口为 6379 至少有 1 个哨兵同意 master 迁移 sentinel monitor mymaster 192.168.175.132 6379 1 // 若为了方便查看日志,也可以设置为前台启动 daemonize no -
将配置文件分发到需要充当哨兵的节点处,master 节点也可以作为哨兵
-
先启动主机进程,然后启动从机的进程(为了方便,在配置文件中直接配置为从机启动),最后通过如下命令依次启动哨兵进程
redis-sentinel ../sentinel.conf -
杀掉 master 节点的进程,在 redis-cli 中使用如下命令,查看剩余的 slave 节点中是否有变为 master 的节点,若有,则哨兵配置成功
info replication -
补充
- 当 master 节点挂掉,被转换为 master 机的节点中的 conf 配置文件相关的部分也会被自动更改,其余节点的 conf 文件相关的部分也会随之更改
- 当被挂掉的 master 主机被重启后,会变为 slave 从机
-
Redis 应用问题
缓存穿透
- 什么是缓存穿透
- 请求过来的 key 在 Redis 中是不存在的,如果有大量这样的请求,因为这块缓存不存在,这些请求就像穿透缓存一样,直接打在了数据库上,导致数据库压力过大,叫做缓存穿透
- 解决方案
- 对空值缓存: 如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果 null 进行缓存,设置空结果 的过期时间会很短,最长不超过五分钟
- 设置可访问的名单(白名单): 使用 bitmaps 类型定义一个可以访问的名单,名单 id 作为 bitmaps 的偏移量,每次访问和 bitmap 里面的 id 进行比较,如果访 问 id 不在 bitmaps 里面,进行拦截,不允许访问
- 采用布隆过滤器: 布隆过滤器(Bloom Filter)是 1970 年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数), 布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难) 将所有可能存在的数据哈希到一个足够大的 bitmaps 中,一个一定不存在的数据会被 这个 bitmaps 拦截掉,从而避免了对底层存储系统的查询压力
- 进行实时监控:当发现 Redis 的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
缓存击穿
- 什么是缓存击穿
- 在 redis 缓存中,某个热点的 key 失效,大量的并发请求在访问该 key 时,无法在 redis 缓存中进行处理,转向了后端数据库,导致数据库压力增大,就好像这些请求击穿了这块的缓存,叫做缓存击穿
- 怎么解决
- 预先设置热门数据:在 redis 高峰访问之前,把一些热门数据提前存入到 redis 里面,加大这些热门数据 key 的时长
- 实时调整:现场监控哪些数据热门,实时调整 key 的过期时长
- 使用锁
- 就是在缓存失效的时候(判断拿出来的值为空),不是立即去 load db
- 先使用缓存工具的某些带成功操作返回值的操作(比如 Redis 的 SETNX)去 set 一个 mutex key
- 当操作返回成功时,再进行 load db 的操作,并回设缓存,最后删除 mutex key
- 当操作返回失败,证明有线程在 load db,当前线程睡眠一段时间再重试整个 get 缓存的方法。

缓存雪崩
- 什么是缓存雪崩
- 某一时刻,出现大规模的缓存失效的情况,大量的请求访问到了数据库上,在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果运维马上又重启数据库,马上又会有新的流量把数据库打死。这就是缓存雪崩
- 怎么解决
- 构建多级缓存架构:nginx 缓存 + redis 缓存 +其他缓存(ehcache 等)
- 使用锁或队列: 用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存 储系统上。不适用高并发情况
- 设置过期标志更新缓存: 记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际 key 的缓存。
- 将缓存失效时间分散开: 比如我们可以在原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重复率就会降低, 就很难引发集体失效的事件
Redis 新功能
ACL
ACL 简介
- Redis ACL 是 Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。 在 Redis 5 版本之前,Redis 安全规则只有密码控制 还有通过 rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。 Redis 6 则提供 ACL 的功能对用户进行更细粒度的权限控制:
- 接入权限:用户名和密码
- 可以执行的命令
- 可以操作的 KEY
- 参考官网:https://redis.io/topics/acl
命令
展现用户权限列表
acl list
查看可以添加权限的指令类别
acl cat
加参数类型名可以查看类别下的具体指令
acl cat string
查看当前用户
acl whoami
ACL 规则

创建新用户默认权限
acl setuser 用户名
/* 在上面的示例中,我根本没有指定任何规则。
如果用户不存在,这将使用 just created 的默
认属性来创建用户。如果用户已经存在,则
上面的命令将不执行任何操作*/
设置有用户名、密码、ACL 权限、并启用的用户
/*该用户名为 user2 , on 代表启用状态,> 后面跟用户的密码,~cached: 代表用户只能操作以 cached:* 开头的 key ,+get 代表只能对其进行 get 操作*/
acl setuser user2 on >password ~cached:* +get
切换用户
auth 用户名 密码
IO 多线程
简介
- Redis6 的 IO 多线程其实指客户端交互部分的网络 IO 交互处理模块多线程,而非执行命令多线程。Redis6 执行命令依然是单线程
- Redis6 加入多线程,但跟 Memcached 这种从 IO 处理到数据访问多线程的实现模式有些差异。Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等 的并发问题。整体的设计大体如下
- 多线程 IO 默认也是不开启的,需要再配置文件中配置
配置 IO 多线程
io-threads-do-reads yes // 启用 IO 多线程
io-threads 4 // 设置线程数量
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!









