Redis
1 Redis简介
1.1 问题
- 海量用户
- 高并发
- 罪魁祸首——关系型数据库
- 性能瓶颈——磁盘IO性能低下
- 扩展瓶颈——数据库关系复杂,扩展性差,不便于大规模集群
1.2 解决思路(NoSQL)
- 降低磁盘IO次数,越低越好——内存存储
- 去除数据间关系,越简单越好——不存储关系,仅存储数据
1.3 NoSQL
- NOSQL:Not-Only SQL泛指非关系型数据库,作为关系型数据库的补充。
- 作用:应对基于海量用户和海量数据前提下的数据处理问题。
- 特征:
- 可扩容,可伸缩
- 大数据量下高性能
- 灵活的数据模型
- 高可用
- 常见NoSQL数据库:
- Redis
- memcache
- HBase
- MongoDB
1.4 解决方案(电商场景)
1、商品基本信息(MySQL)
- 名称
- 价格
- 厂商
2、商品附加信息(MongoDB)
- 描述
- 详情
- 评论
3、图片信息(分布式文件系统)
4、搜索关键字(ES、Lucene、solr)
5、热点信息(Redis、memcache、tair)
- 高频
- 波段性
1.5 Redis
- Redis:REmote DIctionary Server,是用C语言开发的一个开源的高性能键值对(key-value)数据库。
- 特征:
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能
- 多数据类型支持
- 字符串类型——string
- 列表类型——list
- 散列类型——hash
- 集合类型——set
- 有序集合类型——sorted_set
- 持久化支持,可以进行数据灾难恢复
1.6 Redis的应用
- 为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类等高访问量信息等
- 任务队列,如秒杀、抢购、购票排队等
- 即时信息查询,如各类排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信息等
- 时效性信息控制,如验证码控制、投票控制等
- 分布式数据共享,如分布式集群架构中的session分离
- 消息队列
- 分布式锁
2 Redis的下载与安装
2.1 下载
1、Linux版(适用于企业级开发)
2、Windows版本(适合零基础学习)
- GitHub下载地址:https://github.com/MicrosoftArchive/redis/releases
- 百度云下载地址:https://pan.baidu.com/s/1Uqg2bfukpi7NRLvatmh4EA,提取码:6v3p
2.2 启动
- 运行服务端——双击
redis-server.exe
或使用命令行(CMD):
redis-server.exe
运行客户端——双击
redis-cli.exe
或使用命令行(CMD):redis-cli.exe
3 Redis的基本操作
3.1 添加信息
- 功能:设置key、value数据
- 命令:
set key value
- 示例:
set name xianhuii
3.2 查询信息
- 功能:根据key查询对应的value,如果不存在,返回(nil)(空)
- 命令:
get key
- 示例:
get name
3.3 清屏
- 功能:清楚屏幕中的信息
- 命令:
clear
3.4 帮助
- 功能:获取命令帮助文档,获取组中所有命令信息名称
- 命令:
help 命令名称 help @组
- 示例:
help help get help @string
3.5 退出客户端
- 功能:退出客户端
- 命令:
# 方式一: quit # 方式二: exit # 方式三(Windows): <ESC>
4 Redis的数据类型
4.1 数据存储类型介绍
1、业务数据的特殊性
1)作为缓存使用
- 原始业务功能设计
- 秒杀
- 618活动
- 双11活动
- 排队购物
- 运营平台监控到的突发高频访问数据
- 突发时政要闻,被强势关注围观
- 高频、复杂的统计数据
- 在线人数
- 投票排行榜
2)附加功能
- 系统功能优化或升级
- 单服务器升级集群
- Session管理
- Token管理
2、Redis数据类型(5种常用)
- string——String
- hash——HashMap
- list——LinkedList
- set——HashSet
- sorted_set——TreeSet
3、Redis数据存储格式
- Redis自身是一个Map,其中所有的数据都是采用key:value的形式存储
- 数据类型指的是存储的数据的类型,也就是value部分的类型,key部分永远都是字符串
4.2 string
1、string类型
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 存储数据的格式:一个存储空间保存一个数据
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用
2、string类型数据的基本操作
- 添加/修改数据:
set key value
- 获取数据:
get key
- 删除数据:
del key
- 添加/修改多个数据:
mset key1 value1 key2 value2 ……
- 获取多个数据:
mget key1 key2 ……
- 获取数据字符个数(字符串长度):
strlen key
- 最佳信息到原始信息后部(如果原始信息存在就追加,否则新建):
append key value
3、单数据操作与多数据操作的选择之惑
- 单数据操作:
set key value
- 多数据操作:
set key1 value1 key2 value2 ……
Redis指令的执行流程:
需要分析比较发送命令、执行命令所需要的时间。
如果数据量较大,发送消耗时间较长,并且会引起单线程的阻塞,则需要对数据进行切割,分成多次发送。
如果数据量较小,合并数据发送与分割发送的时间相差无几,则需要合并发送,可缩短发送时间,提高效率。
4、string类型数据的扩展操作1
1)业务场景
- 大型企业级应用中,分表是基本操作,使用多张表存储同类型数据,但是对应的主键id必须保证统一性,不能重复。
- Oracle数据库具有sequence的设定,可以解决该问题。但是MySQL数据库并不具有类似的机制,那么该如何解决?
2)解决方案
- 设置数值数据增加指定范围的值:
incr key # +1 incrby key increment # +increment(正负整数) incrbyfloat key increment # +increment(正负小数)
- 设置数值数据减少指定范围的值:
decr key # -1 decrby key increment # -increment(正负整数)
3)string作为数值操作
- string在Redis内部存储默认就是一个字符串,当遇到增减类操作
incr
、decr
时会转换成数值型进行计算。 - Redis所有的操作都是原子性的,采用单线程处理所有的业务,命令是一个一个执行的,因此无须考虑高并发带来的数据影响。
- 注意:按数值进行操作的数据,如果原始数据不能转换成数值,或者超越了Redis数值上限,将会报错。
4)Tips 1
- Redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性。
- 此方案适用于所有数据库,且支持数据库集群。
5、string类型数据的扩展操作2
1)业务场景
- “最强女主”启动海选投票,只能通过微信投票,每个微信号每4个小时只能投1票。
- 电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。
- 新闻网站会出现热点新闻,热点新闻最大的特征是时效性,如何自动控制热点新闻的时效性?
2)解决方案
- 设置数据具有指定的生命周期:
setex key seconds value psetex key milliseconds value
3)Tips 2
- Redis控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作。
6、string类型数据的注意事项
- 数据操作不成功的反馈与数据正常操作之间的差异:
- 表示运行结果是否成功:
- (integer)0 → false:失败
- (integer)1 → true:成功
- 表示运行结果值:
- (integer)3 → 3:3个
- (integer)1 → 1:1个
- 表示运行结果是否成功:
- 数据未获取到:(nil)等同于null
- 数据最大存储量:512MB
- 数值计算最大范围(Java中的Long的最大值):9223372036854775807
7、string类型应用场景
1)业务场景
- 主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数与微博数量
2)解决方案
在Redis中为大V用户设定用户信息,以用户主键和属性值作为key,后台设定定时刷新策略即可:
- user:id:3506728370:fans → 12210947
- user:id:3506728370:blogs → 1221
- user:id:3506728370:focuss → 12247
在Redis中以JSON格式存储大V用户信息,定时刷新(也可以使用hash类型):
- user:id:3506728370 → {id:3506728370,name:Xianhuii,fans:123468,blogs:7988,focus:98}
3)Tips 3
- Redis应用于各种结构型和非结构型高热度数据访问加速。
8、key的设置约定
- 数据库中的热点数据key命名惯例——表名:主键名:主键值:字段名
- order:id:99999:name
- equip:id:99999:type
- news:id:99999:title
4.3 hash
1、hash类型
- 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
- hash存储结构优化:
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
2、hash类型数据的基本操作
- 添加/修改数据:
hset key field value
- 获取数据:
hget key field hgetall key
- 删除数据:
hdel key field1 [field2]
- 添加/修改多个数据:
hmset key field1 value1 field2 value2 ……
- 获取多个数据:
hmget key field1 field2 ……
- 获取哈希表中字段的数量:
hlen key
- 获取哈希表中是否存在指定的字段:
hexists key field
3、hash类型数据扩展操作
- 获取哈希表中所有的字段名或字段值:
hkeys key hvals key
- 设置指定字段的数值数据增加指定范围的值:
hincrby key field increment hincrbyfloat key field increment
4、hash类型数据操作的注意事项
- hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的职位(nil)。
- 每个hash可以存储2^32^-1个键值对。
- hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用。
- hgetall操作可以获取全部属性,如果内部field过多,遍历整体数据效率就会很低,有可能成为数据访问的瓶颈。
5、hash类型应用场景1
1)业务场景
- 电商网站购物车设计与实现。
2)业务分析
- 仅分析购物车的Redis存储模型:添加、浏览、更改数量、删除、清空。
- 购物车与数据库间持久化同步(不讨论)。
- 购物车与订单间关系(不讨论):
- 提交购物车:读取数据生成订单。
- 商家临时价格调整:隶属于订单级别。
- 未登录用户购物车信息存储(不讨论):cookie存储。
3)解决方案
- 以客户id作为可以,每位客户创建一个hash存储结构存储对应的购物车信息。
- 将商品编号作为field,购买数量作为value进行存储。
- 添加商品:追加全新的field与value。
- 浏览:遍历hash。
- 更改数量:自增/自减,设置value值。
- 删除商品:删除field。
- 清空:删除key。
4)当前设计是否加速了购物车的呈现
- 当前仅仅是将数据存储到了Redis中,并没有起到加速的作用,商品信息还需要二次查询数据库。
- 改进:
- 每条购物车中的商品记录保存成两条field。
- field1专门用于保存购买数量:
- 命名格式:商品id:nums
- 保存数据:数值
- field2专门用于保存购物车中显示的信息,包含文件描述、图片地址、所属商家信息等:
- 命名格式:商品id:info
- 保存数据:JSON
- 如果当前set中没有该field,则添加,否则什么都不做:
hsetnx key field value
4)Tips 4
- Redis应用于购物车数据存储设计。
6、hash类型应用场景2
1)业务场景
- 双11活动日,销售手机充值卡的商家对移动、联通、电信的30元、50元、100元商品退出抢购活动,每种商品抢购上限1000张。
2)解决方案
- 以商家id作为key。
- 将参与抢购的商品id作为field。
- 将参与抢购的商品数量作为对应的value。
- 抢购时使用降值的方式控制产品数量。
- 实际业务中还有超卖等实际问题,这里不做讨论。
3)Tips 5
- Redis应用于抢购、限购、限量发放优惠券、激活码等业务的数据存储设计。
7、hash类型应用场景3
1)业务场景
- string存储对象(JSON)与hash存储对象。
4.4 list
1、list类型
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分。
- 需要的存储结构:一个存储空间保存多个数据,并且通过数据可以体现进入顺序。
- list类型:保存多个数据,底层使用双向链表存储结构实现。
2、list类型数据基本操作
- 添加/修改数据:
lpush key value1 [value2] …… rpush key value1 [value2] ……
- 获取数据:
lrange key start stop lindex key index llen key
- 获取并移除数据:
lpop key rpop key
3、list类型数据扩展操作1
- 规定时间内获取并移除数据:
blpop key1 [key2] timeout brpop key1 [key2] timeout
- 客户端1:
- 客户端2:
- 客户端1:
4、list类型数据扩展操作2
1)业务场景
- 微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息。
- 如果取消点赞,移除对应好友信息。(如果位置在中间怎么办?)
2)解决方案
- 移除指定数据:
lrem key count value
3)Tips 6
- Redis应用于具有操作先后顺序的数据控制。
5、list类型数据操作注意事项
- list中保存的数据都是string类型的,数据总容量是有限的,最多2^32^-1个元素。
- list具有索引的概念,但是操作数据时通常以队列的形式进行入队和出队操作,或者以栈的形式进行入栈和出栈操作。
- 获取全部数据操作的结束索引设置为-1。
- list可以对数据进行分页操作,通常第一页的信息来自于list,第二页及更多的信息通过数据库的形式加载。
6、list类型应用场景
1)业务场景
- Twitter、新浪微博、腾讯微博中个人用户的关注里诶包需要按照用户的关注顺序进行展示,粉丝里诶包需要将最近关注的粉丝列在前面。
- 新闻、资讯类网站如何将最新的新闻或资讯按照发生的时间顺序展示?
- 企业运营过程中,系统将产生出大量的运营数据,如何保障多态服务器操作日志的统一顺序输出?
2)解决方案
依赖list的数据具有顺序的特征对信息进行管理。
使用队列模型解决多路信息汇总合并的问题。
使用栈模型解决最新消息的问题。
a客户端:
- b客户端:
- c客户端:
3)Tips 7
- Redis应用于最新消息展示。
4.5 set
1、set类型
- 新的存储需求:存储大量的数据,在查询方面提供更高的效率。
- 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询。
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的。
2、set类型数据的基本操作
- 添加数据:
sadd key member1 [member2] ……
- 获取全部数据:
smembers key
- 删除数据:
srem key member1 [member2] ……
- 获取集合数据总量:
scard key
- 判断集合中是否包含指定数据:
sismember key member
3、set类型数据的扩展操作1
1)业务场景
- 每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现?
2)业务分析
- 系统分析出各个分类的最新或最热点信息条目并组织成set集合。
- 随机挑选其中部分信息。
- 配合用户关注信息分类中的热点信息组织成展示的全信息集合。
3)解决方案
- 随机获取集合中指定数量的数据:
srandmember key [count]
- 随机获取集合中的某些数据并将该数据移出集合:
spop key [count]
4)Tips 8
- Redis应用于随机推荐类信息检索,例如热点歌单推荐、热点新闻推荐、热点旅游路线、应用APP推荐、大V推荐等。
4、set类型数据的扩展操作2
1)业务场景
- 脉脉为了促进用户间的交流,保障业务成单率的提升,需要让每位用户拥有大量的好友,事实上职场新人不具有更多的职场好友,如何快速为用户积累更多的好友?
- 新浪微博为了增加用户热度,提高用户留存性,需要微博用户关注更多的人,以此获得更多的信息或热门话题,如何提高用户关注他们的总量?
- QQ新用户入网年龄越来越低,这些用户的朋友圈、交际圈非常小,往往集中在一所学校甚至一个班级中,如何帮助用户快速积累好友用户带来更多的活跃度?
- 微信公众号是微信信息流通的渠道之一,增加用户关注的公众号成为提高用户活跃度的一种方式,如何帮助用户积累更多关注的公众号?
- 美团外卖为了提升成单量,必须帮助用户挖掘美食需求,如何推荐给用户最适合自己的美食?
2)解决方案
- 求两个集合的交、并、差集:
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
3)Tips 9
- Redis应用于同类信息的关联搜索,二度关联搜索,深度关联搜索。
- 显示共同关注(一度)。
- 显示共同好友(一度)。
- 由用户A出发,获取到好友用户B的好友信息列表(一度)。
- 由用户A出发,获取到好友用户B的购物清单列表(二度)。
- 由用户A出发,获取到好友用户B的游戏充值列表(二度)。
5、set类型数据操作的注意事项
- set类型不允许数据重复,如果添加的数据在set中已经存在,将只保留一份。
- set虽然与hash的存储结构相同,但是无法启用hash中存储值的空间。
6、set类型应用场景——权限校验
1)业务场景
- 集团公司共具有12000名员工,内部OA系统中具有700多个角色,3000多个业务操作,23000多种数据,每个员工具有一个或多个角色,如何快速进行业务操作的权限校验?
2)解决方案
- 依赖set集合数据不重复的特征,依赖set集合hash存储结构特征完成数据过滤与快速查询。
- 根据用户id获取用户所有角色。
- 根据用户所有角色获取用户所有操作权限放入set集合。
- 根据用户所有角色获取用户所有数据权限放入set集合。
3)校验工作
- Redis提供基础数据还是提供校验结果?——基础数据!
4)Tips 10
- Redis应用于同类型不重复数据的合并操作。
7、set类型应用场景——网站访问次数
1)业务场景
- 公司对旗下新的网站做推广,统计网站的PV(访问量)、UV(独立访客)、IP(独立IP)。
- PV:网站被访问次数,可通过刷新页面提高访问量。
- UV:网站被不同用户访问的次数,可通过cookie统计访问量,相同用户切换IP地址,UV不变。
- IP:网站被不同IP地址访问的总次数,可通过IP地址统计访问量,相同IP不同用户访问,IP不变。
2)解决方案
- 利用set集合的数据去重特征,记录各种访问数据。
- 建立string类型数据,利用incr统计日访问量(PV)。
- 建立set模型,记录不同cookie数量(UV)。
- 建立set模型,记录不同IP数量(IP)。
3)Tips 11
- Redis应用于同类型数据的快速去重。
8、set类型应用场景——黑白名单
1)业务场景
- 黑名单
咨询类、信息类网站追求高访问量,但是由于其信息的价值,往往容易被不法分子利用,利用爬虫技术,快速获取信息,个别特种行业网站信息通过爬虫获取分析后,可以转换成商业机密进行出售。例如第三方火车票、机票、酒店刷票代购软件、电商刷评论、刷好评。
同时,爬虫带来的伪流量也会给经营者带来错觉,产生错误的决策。有效避免网站被爬虫反复爬取称为每个网站都要考虑的基本问题。在基于技术层面区分出爬虫用户后,需要将此类用户进行有效的屏蔽,这就是黑名单的典型应用。
- 白名单
对于安全性更高的应用访问,仅仅靠白名单是不能解决安全问题的,此时需要设定可访问的用户群体,依赖白名单做更为苛刻的访问验证。
2)解决方案
- 基于经营战略设定问题用户发现、鉴别规则。
- 周期性更新满足规则的用户黑名单,加入set集合。
- 用户行为信息达到后与黑名单进行对比,确认行为去向。
- 黑名单过滤IP地址:应用于开放游客访问权限的信息源。
- 黑名单过滤设备信息:应用于限定访问设备的信息源。
- 黑名单过滤用户:应用于基于访问权限的信息源。
4.6 sorted_set
1、sorted_set类型
- 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式。
- 需要的存储结构:新的存储模型,可以保存可排序的数据。
- sorted_set类型:在set存储结构的基础上添加可排序字段。
2、基本操作
- 添加数据:
zadd key score1 member1 [score2 member2]
- 获取全部数据:
zrange key start stop [WITHSCORES] zrevrange key start stop [WITHSCORES]
- 删除数据:
zrem key member [member ……]
- 按条件获取数据:
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] zrevrangebyscore key max min [WITHSCORES]
- 按条件删除数据:
zremrangebyrank key start stop zremrangebyscore key min max
注意:
- min与max用于限定搜索查询的条件。
- start与stop用于限定查询范围,作用作索引,表示开始和结束索引。
- offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量。
获取集合数据总量:
zcard key zcount key min max
- 集合交、并操作:
zinterstore destination numkeys key [key……] zunionstore destination numkeys key [key……]
3、扩展操作——排行榜
1)业务场景
- 票选广东十大杰出青年,各类综艺选秀海选投票。
- 各类资源网站Top10(电影、歌曲、文档、电商、游戏等)。
- 聊天室活跃度统计。
- 游戏好友亲密度。
2)业务分析
- 为所有参与排名的资源建立排序依据。
3)解决方案
- 获取数据对应的索引(排名):
zrank key member zrevrank key member
- score值的获取与修改:
zscore key member zincrby key increment mamber
4)Tips 13
- Redis应用于计数器组合排序功能对应的排名。
4、注意事项
- score保存的数据的存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992。
- score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时要慎重。
- sorted_set底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果。
5、应用场景——时效性任务管理
1)业务场景
- 基础服务+增值服务类网站会设定各位会员的试用,让用户充分体验会员优势。例如观影试用VIP、游戏VIP体验、云盘下载体验VIP、数据查看体验VIP。当VIP体验到期后,如何有效管理此类信息?即便对于正式VIP用户也存在对应的管理方式。
- 网站会定期开启投票、讨论,限时进行,逾期作废。如何有效管理此类过期信息?
2)解决方案
- 对于基于时间线限定的任务处理,将处理事件记录为score值,利用排序功能区分处理的先后顺序。
- 记录下一个要处理的时间,当到期后处理对应任务,移除Redis中的记录,并记录下一个要处理的时间。
- 当新任务加入时,判定并更新当前下一个要处理的任务事件。
- 为提升sorted_set的性能,通常将任务根据特征存储成若干个soretd_set。例如1小时内、1天内、周内、月内、季内、年度等,操作时逐级提升,即将操作的若干个任务纳入到1小时内处理的队列中。
- 获取当前系统时间:
time
3)Tips 14
- Redis应用于定时任务执行顺序管理或任务过期管理。
6、应用场景——带有权重的任务管理
1)业务场景
- 任务/消息权重设定应用
当任务或消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理?
2)解决方案
- 对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可。
- 多条件任务权重设定
如果权重条件过多,需要对排序score值进行处理,保障score值能够兼容2条件或者多条件。例如外贸订单优先于国内订单,总裁订单优先于员工订单,经理订单优先于员工订单。
因为score长度受限,需要对数据进行截断处理,尤其是时间设置为小时或分钟即可(折算后)。
先设定订单类别,后设定订单发起角色类别,整体score长度必须是统一的,不足位补0。第一排序规则首首位不能为0:外贸101,国内102,经理004,员工008。
员工下的外贸单score值为:101008(优先)。
经理下的国内单score值为:102004。
3)Tips 15
-Redis应用于即时任务/消息队列执行管理。
4.7 数据类型实践案例
1、案例1——限时按次结算
1)业务场景
- 人工只能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型。先对试用用户的使用行为进行限速,限制每个用户每分钟最多发起10次调用。
2)解决方案
- 设计计数器,记录调用次数,用于控制业务执行次数。以用户id作为key,使用次数作为value。
- 在调用前获取次数,判断是否超过限定次数:
- 不超过次数的情况下,每次调用计数+1。
- 业务调用失败,计数-1。
- 为计数器设置生命周期为指定周期,例如1秒/分钟,自动清空周期内使用次数。
3)改良方案
- 取消最大值的判定,利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值。
- 判断是否为(nil):
- 是——设置为Max-次数
- 不是——计数+1
- 业务调用失败——计数-1
- 遇到异常,即+操作超过上限,视为使用达到上限。
4)Tips 16
- Redis应用于限时按次结算的服务控制。
2、案例2——基于时间顺序的数据操作
1)业务场景
- 使用微信的过程中,当微信接收消息后,会默认将最近接收的消息置顶,当多个好友及关注的订阅号同时发送消息时,该排序会不停地进行交替。同时还可以将重要的会话设置为置顶。一旦用户离线后,再次打开微信时,消息该按照什么样的顺序显示?
2)解决方案
- 依赖list的数据具有顺序的特征对消息进行管理,将list结构作为栈使用。
- 对置顶与普通会话分别创建独立的list分别管理。
- 当某个list中接收到用户消息后,将消息发送方的id从list的一侧加入list(此处设定左侧)。
- 多个相同id发出的消息反复入栈会出现问题,在入栈之前无论是否具有当前id对应的消息,先删除对应id。
- 推送消息时先推送置顶会话list,再推送普通会话list,推送完成的list清楚所有数据。
- 消息的数量,也就是微信用户对话数量采用计数器的思想另行记录,伴随list操作同步更新。
3)Tips 17
- Redis应用基于时间顺序的数据操作,而不关注具体时间。
4.8 Tips列表
- 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应用于基于时间顺序的操作,而不关注具体时间。
5 通用命令
5.1 key通用命令
1、key特征
- key是一个字符串,通过key获取Redis中保存的数据。
2、key应该设计哪些操作?
- 对于key自身状态的相关操作,如删除、判定存在、获取类型等。
- 对于key有效性控制相关操作,如有效期设定、判断是否有效、有效状态的切换等。
- 对于key快速查询操作,如按指定策略查询key。
- ……
3、基本操作
- 删除指定key:
del key
- 判断key是否存在:
exists key
- 获取key的类型:
type key
4、扩展操作(时效性控制)
- 为指定key设置有效期:
expire key seconds pexpire key milliseconds expireat key timestamp pexpireat key milliseconds-timestamp
- 获取key的有效时间:
ttl key # -2:不存在或已失效;-1:永久存在;其他:有效时间 pttl key
- 切换key从时效性转换为永久性:
persist key
5、扩展操作(查询模式)
- 查询key:
keys pattern
查询模式规则:
- *:匹配任意数量的任意符号。
- ?:匹配一个任意符号。
- []:匹配一个指定符号。
keys * # 查询所有 keys X* # 查询所有以X开头 keys *i # 查询所有以i结尾 keys ??anhuii # 查询所有前面两个任意字符,后面以anhuii结尾 keys user:? # 查询所有以user:开头,最后一个字符任意 keys X[ia]nhuii # 查询所有以X开头,以nhuii结尾,中间包含一个字母为i或a
6、其他操作
- 为key改名:
rename key newkey # 如果存在会覆盖 renamenx key newkey # 如果不存在则改名,如果存在则不起作用
- 对所有key排序:
sort
- 查看key通用操作:
help @generic
5.2 数据库通用命令
1、数据库
1)key的重复问题
- key是由程序员定义的。
- Redis在使用过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的key。
- 数据不区分种类混杂在一起,极易出现重复或冲突。
2)解决方案
- Redis为每个服务提供有16个数据库,编号从0到15。
- 每个数据库之间的数据相互独立。
2、基本操作
- 切换数据库:
select [0~15]
- 其他操作:
quit # 退出 ping # 测试服务器是否连通 echo message
3、其他操作
- 数据移动:
move key db
- 数据清除:
dbsize # 查看数据库大小 flushdb # 清除当前数据库中的数据 flushall # 清除所有数据库的数据
6 Jedis
6.1 Jedis简介
1、编程语言与Redis
- Java语言连接Redis服务:
- Jedis:Jedis is a blazingly small and sane Redis java client.
- SpringData Redis
- Lettuce
- C、C++、C#、Erlang、Lua、Objective-C、Perl、PHP、Python、Ruby、Scala
6.2 HelloWorld
1、Jedis依赖
- Maven仓库:https://mvnrepository.com/artifact/redis.clients/jedis
- Maven依赖:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
2、使用
package com.xianhuii; import org.junit.Test; import redis.clients.jedis.Jedis; public class JedisTest { @Test public void testJedis() { // 1、连接Redis Jedis jedis = new Jedis("127.0.0.1", 6379); // 2、操作Redis jedis.set("name", "Xianhuii"); String name = jedis.get("name"); System.out.println(name); // Xianhuii // 3、关闭连接 jedis.close(); } }
6.3 Jedis读写Redis数据
1、Jedis简单读写
package com.xianhuii; import org.junit.Test; import redis.clients.jedis.Jedis; import java.util.List; import java.util.Map; public class JedisTest { @Test public void testJedis() { // 1、连接Redis Jedis jedis = new Jedis("127.0.0.1", 6379); // 2、操作Redis jedis.set("name", "Xianhuii"); String name = jedis.get("name"); System.out.println(name); // Xianhuii // 3、关闭连接 jedis.close(); } @Test public void testList() { // 1、连接Redis Jedis jedis = new Jedis("127.0.0.1", 6379); // 2、操作Redis jedis.lpush("list1", "a", "b", "c"); jedis.rpush("list1", "x"); List<String> list1 = jedis.lrange("list1", 0, -1); for (String s: list1) { System.out.println(s); } System.out.println(jedis.llen("list1")); // 3、关闭连接 jedis.close(); } @Test public void testHash() { // 1、连接Redis Jedis jedis = new Jedis("127.0.0.1", 6379); // 2、操作Redis jedis.hset("hash1", "a1", "a1"); jedis.hset("hash1", "a2", "a2"); jedis.hset("hash1", "a3", "a3"); Map<String, String> hash1 = jedis.hgetAll("hash1"); System.out.println(hash1); System.out.println(jedis.hlen("hash1")); // 3、关闭连接 jedis.close(); } }
2、案例:服务调用次数控制
1)案例
- 人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型。现对试用用户的使用行为进行限速,限制每个用户每分钟最多发起10次调用。
- 案例要求:
- 设定A、B、C三个用户。
- A用户限制10次/分钟调用,B用户限制30次/分钟调用,C用户不限制。
2)需求分析
- 设定一个服务方法,用于模拟实际业务调用的服务,内部采用打印模拟调用。
- 在业务调用前服务调用控制单元,内部使用Redis进行控制,参照之前的方案。
- 对调用超限使用异常进行控制,异常处理设定为打印提示信息。
- 主程序启动3个线程,分别表示3种不同用户的调用。
3)实现
package com.xianhuii; import redis.clients.jedis.Jedis; import redis.clients.jedis.exceptions.JedisDataException; public class Service { private String id; private int num; public Service(String id, int num) { this.id = id; this.num = num; } // 1、控制单元 public void service() { Jedis jedis = new Jedis("127.0.0.1", 6379); String value = jedis.get("compid:" + id); try { // 判断该值是否存在 if (value == null) { jedis.setex("compid:" + id, 5, Long.MAX_VALUE - num + ""); } else { Long val = jedis.incr("compid:" + id); business(id, num - (Long.MAX_VALUE - val)); } } catch (JedisDataException e) { System.out.println("使用已经到达次数上限,请升级会员级别"); return; } finally { jedis.close(); } } // 2、业务操作 public void business(String id, Long val) { System.out.println("用户:" + id + "业务操作执行第" + val + "次"); } } // 3、多线程模拟多用户 class MyThread extends Thread { private Service service; public MyThread(String id, int num) { service = new Service(id, num); } public void run() { while (true) { service.service(); try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 4、主程序 class Main { public static void main(String[] args) { MyThread myThread1 = new MyThread("初级用户", 10); MyThread myThread2 = new MyThread("高级用户", 30); myThread1.start(); myThread2.start(); } }
4)后续
- 对业务控制方案进行改造,设定不同用户等级的判定。
- 将不同用户等级对应的信息、限制次数等设定到Redis中,使用Hash保存。
6.4 Jedis简易工具类开发
1、Jedis连接池——JedisPool
JedisPool
:Jedis提供的连接池技术poolConfig
:连接池配置对象。host
:Redis服务地址。port
:Redis服务端口号。
public JedisPool(GenericObjectPoolConfig poolConfig, String host) { this(poolConfig, host, 6379, 2000, (String)null, 0, (String)null); }
2、实现
redis.properties
:
redis.host=127.0.0.1 redis.port=6379 redis.maxTotal=30 redis.maxIdle=10
JedisUtils.java
:
package com.xianhuii.util; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import java.util.ResourceBundle; public class JedisUtils { private static JedisPool jedisPool = null; private static String host; private static int port; private static int maxTotal; private static int maxIdle; // 初始化JedisPool,仅加载一次 static { ResourceBundle redis = ResourceBundle.getBundle("redis"); host = redis.getString("redis.host"); port = Integer.parseInt(redis.getString("redis.port")); maxTotal = Integer.parseInt(redis.getString("redis.maxTotal")); maxIdle = Integer.parseInt(redis.getString("redis.maxIdle")); JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); jedisPoolConfig.setMaxTotal(maxTotal); jedisPoolConfig.setMaxIdle(maxIdle); jedisPool = new JedisPool(jedisPoolConfig, host, port); } // 获取Jedis public static Jedis getJedis() { return jedisPool.getResource(); } // 测试 public static void main(String[] args) { Jedis jedis = JedisUtils.getJedis(); jedis.set("name", "Xianhuii"); String name = jedis.get("name"); System.out.println(name); jedis.close(); } }
6.5 可视化客户端——Redis Desktop Manager
7 Redis安装(Linux)
7.1 下载、解压、编译Redis
# 1、选择下载、安装目录 cd /www/server/ # 2、下载 wget http://download.redis.io/releases/redis-5.0.5.tar.gz # 3、解压 tar xzf redis-5.0.5.tar.gz # 4、编译 cd redis-5.0.5 make
7.2 启动Redis服务端
# 1、进入Redis安装目录下的src目录中 cd /www/server/redis/src/ # 2、启动Redis redis-server # 使用默认端口 redis-server --port 端口号 # 使用指定端口 redis-server 配置文件名 # 使用指定配置文件,如:../redis-6379.conf,可以启动多个Redis服务端 # 3、查看是否启动 ps -ef | grep redis- # 4、停止Redis进程 kill -s 9 <进程id>
7.3 启动内置Redis客户端
# 1、进入Redis安装目录下的src目录中 cd /www/server/redis/src/ # 2、启动Redis redis-cli # 使用默认端口 redis-cli -p 端口号 # 使用指定端口
7.4 Redis配置文件
- Redis安装目录下有一个
redis.conf
文件。
cd /www/server/redis cat redis.conf | grep -v "#" | grep -v "^$" > redis-6379.conf # 去除注释、空格,然后复制 vim redis-6379.conf
- 编辑
redis-6379.conf
:
port 6379 # 启动端口号 daemonize yes # 是否后台启动 logfile "/www/server/redis/redis-6379.log" # 日志文件 dir /www/server/redis/ # 当前服务文件保存位置,包含日志文件、持久化文件等
- 可以将配置文件放在一个专门的目录:
mkdir conf mv redis-6379.conf conf
8 持久化
8.1 持久化简介
1、什么是持久化
- 持久化:利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制。
2、为什么要进行持久化
- 防止数据的以外丢失,确保数据安全性。
3、持久化过程保存什么
- 数据(快照)(RDB):以快照的形式,将当前数据状态进行保存。存储数据结果,存储格式简单,关注点在数据。
- 过程(日志)(AOF):以日志的形式,将数据的操作过程进行保存。存储操作过程,存储格式复杂,关注点在数据的操作过程。
8.2 RDB
- 默认情况下,Redis服务端启动时会自动从rdb文件中读取备份:
[10884] 09 May 15:51:24.245 * DB loaded from disk: 0.000 seconds
1、RDB前台执行——save
命令:
save
作用:手动执行一次保存操作。
2、RDB相关配置
dbfilename dump-6379.rdb dir ./data/ rdbcompression yes rdbchecksum yes
dbfilename dump.rdb
:- 说明:设置本地数据库文件名,默认值为
dump.rdb
- 经验:通常设置为
dump-端口号.rdb
- 说明:设置本地数据库文件名,默认值为
dir ./data/
:- 说明:设置存储
.rdb
文件的路径 - 经验:通常设置成存储空间较大的目录中,目录名称为
data
- 说明:设置存储
rdbcompression yes
- 说明:设置存储至本地数据库时是否压缩数据,默认为
yes
,采用LZF压缩 - 经验:通常默认为开启状态,如果设置为
no
,可以节省CPU运行时间,但会使存储的文件变大(巨大)
- 说明:设置存储至本地数据库时是否压缩数据,默认为
rdbchecksum yes
- 说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
- 经验:通常默认为开启状态,如果设置为
no
,可以节约读写性过程为10%时间消耗,但是存在一定的数据损坏风险
3、save指令工作原理
Redis是单线程的。
save指令加入到任务队列中。
save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用。
4、RDB后台执行——bgsave
- 数据量过大,单线程执行方式会造成效率过低。需要使用后台执行RDB操作。
- 命令:
bgsave
- 作用:手动启动后台保存操作,但不是立即执行。
5、bgsave指令工作原理
- 发送指令:
bgsave
。 - 返回消息:
Background saving started
。 - 调用
fork
函数生成子进程。 - 创建rdb文件。
[10884] 09 May 15:58:02.495 * Background saving started by pid 8876 [10884] 09 May 15:58:02.648 # fork operation complete [10884] 09 May 15:58:02.650 * Background saving terminated with success
bgsave
命令是针对save
阻塞问题做的优化。- Redis内部所有涉及到RDB操作都采用
bgsave
的方式,save
命令可以放弃使用。
6、bgsave指令相关配置
stop-writes-on-bgsave-error yes
:- 说明:后台存储过程中如果出现错误现象,是否停止保存操作
- 经验:通常默认为开启状态
7、RDB自动执行——save配置
- 配置
redis-6379.conf
:
save second changes
- 作用:限定时间范围内,key的变化数量达到指定数量,则进行持久化。
- 参数:
second
:监控时间范围。changes
:监控key的变化量。
- 位置:在
conf文件
中进行配置。 - 示例:
save 900 1 save 300 10 save 60 10000
8、save配置工作原理
- 发送指令。
- 返回结果:
- 会对数据产生影响。
- 真正产生了影响。
- 不对数据进行比对。
- [变量]影响数量+1。
- 注意:
- save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的。
- save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系。
- save配置启动后执行的是
bgsave
操作。
9、RDB三种启动方式对比
方式 | save指令 | bgsave指令 |
---|---|---|
读写 | 同步 | 异步 |
阻塞客户端指令 | 是 | 否 |
额外内存消耗 | 否 | 是 |
启动新进程 | 否 | 是 |
10、RDB特殊启动形式
全量复制(详细看主从复制章节)。
服务器运行过程中重启:
debug reload
关闭服务器时指定保存数据:
shutdown save
11、RDB的优缺点
1)优点
- RDB是一个紧凑压缩的二进制文件,存储效率较高。
- RDB内部存储的是Redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景。
- RDB恢复数据的速度要比AOF快得多。
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复。
2)缺点
- RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据。
bgsave
指令每次运行要执行fork
操作创建子进程,要牺牲一些性能。- Redis的众多版本中未对RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象。
- 存储数据量较大,效率较低。基于快照的思想,每次读写都是全部数据,当数据量巨大时,效率非常低。
- 大数据量下的IO性能较低。
8.3 AOF
1、AOF概念
- AOF(Append Only File):以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。与RDB相比可以简单描述为记录数据产生的过程。
- AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。
2、AOF写数据过程
- 客户端发送指令。
- 服务端将指令放到AOF写命令刷新缓存区。
- 到一定阶段,将命令同步到AOF文件中。
3、AOF写数据的三种策略(appendfsync
)
always
(每次):每次写入操作均同步到AOF文件中,数据零误差,性能较低。everysec
(每秒):每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高。在系统突然宕机的情况下会丢失1秒内的数据。no
(系统控制):由操作系统控制每次同步到AOF文件的周期,整体过程不可控。
4、AOF功能开启(配置redis.conf)
配置
appendonly
:appendonly yes|no
- 作用:是否开启AOF持久化功能,默认为不开启状态。
配置
appendfsync
:appendfsync always|everysec|no
- 作用:AOF写数据策略。
配置
appendfilename
:appendfilename filename
- 作用:AOF持久化文件名,默认文件名为
appendonly.aof
,建议配置为appendonly-端口号.aof
。
- 作用:AOF持久化文件名,默认文件名为
配置
dir
:dir
- AOF持久化文件保存路径,与RDB持久化文件保持一致即可。
5、AOF重写
1)问题
如果连续执行如下指令该如何处理:
set name zs set name ls set name ww incr num incr num incr num
2)AOF重写
- 随着命令不断写入AOF,文件会越来越大。为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。
- AOF文件重写是将Redis进程内的数据转化为写命令同步到AOF文件的过程。
- 简单说,就是将对同一个数据的若干条命令执行结果转化成最终结果数据对应的指令进行记录。
3)AOF重写的作用
- 降低磁盘占用量,提高磁盘利用率。
- 提高持久化效率,降低持久化写时间,提高IO性能。
- 降低数据恢复用时,提高数据恢复效率。
4)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个元素。
- 如:
5)AOF重写方式
I. 手动重写(指令)
指令:
bgrewriteaof
原理:
- 发送指令。
- 返回消息:Background append only file rewriting started。
- 调用fork函数生成子进程。
- 重写aof文件。
- 返回消息。
II. 自动重写(配置)
设置自动重写触发条件:
suto-aof-rewrite-min-size size auto-aof-rewrite-percentage percentage
自动重写触发比对参数(运行指令
info persistence
获取具体信息):aof_current_size aof_base_size
自动重写触发条件:
- aof_current_size > auto-aof-rewrite-min-size
- (aof_current_size - aof_base_size) / aof_base_size >= auto-aof-rewrite-percentage
6)AOF重写工作原理
8.4 RDB与AOF区别
1)RDB VS AOF
持久化方式 | RDB | AOF |
---|---|---|
占用存储空间 | 小(数量级:压缩) | 大(指令级:重写) |
存储速度 | 慢 | 快 |
恢复速度 | 快 | 慢 |
数据安全性 | 会丢失数据 | 依据策略决定 |
资源消耗 | 高/重量级 | 低/轻量级 |
启动优先级 | 低 | 高 |
2)RDB与AOF如何选择
- 对数据非常敏感,建议使用默认的AOF持久化方案。
- AOF持久化策略使用
everysec
,每秒钟fsync
一次。该策略Redis仍可以保持很好的处理性能,当出现问题时,最多丢失0~1秒内的数据。 - 注意:由于AOF文件存储体积大,恢复速度较慢。
- AOF持久化策略使用
- 数据呈现阶段有效性,建议使用RDB持久化方案。
- 数据可以良好的坐到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案。
- 注意:利用RDB实现紧凑的数据持久化会使Redis性能降低。
- 综合对比:
- RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊。
- 如果不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF。
- 如果能够承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB。
- 灾难恢复选用RDB。
- 双保险策略,同时开启RDB和AOF。重启后,Redis优先使用AOF来恢复数据,降低丢失数据的量。
8.5 持久化的应用场景
- Tips 1:
Redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性。(不用,应该从数据库中获取最大id,从而确定主键id) - Tips 3:
Redis应用于各种结构型和非结构型高热度数据访问加速。(不用,数据本质上是从数据库中来的) - Tips 4:
Redis应用于购物车数据存储设计(不用,购物车数据本质上也会存储到数据库中)。 - Tips 5:Redis应用于抢购,限购类、限量发放优惠券、激活码等业务的数据存储设计。
- Tips 6:Redis应用于具有操作先后顺序的数据控制。
- Tips 7:Redis应用于最新消息展示。
- Tips 9:
Redis应用于同类信息的关联搜索、二度关联搜索、深度关联搜索。(不用) - Tips 12:Redis应用于基于黑白名单设定的服务控制。
- Tips 13:Redis应用于计数器组合排序功能对应的排名。
- Tips 15:
Redis应用于即时任务/消息队列执行管理。 - Tips 16:Redis应用于按次结算的服务控制。
9 事务
9.1 事务简介
- 多个Redis客户端在执行指令的过程中,多条连续执行的指令被其他Redis客户端干扰、打断、插队,就会发生问题。
- Redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次按照添加顺序依次执行,中间不会被打断或者干扰。
9.2 事务的基本操作
1、开启事务
指令:
multi
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中。
2、执行事务
指令:
exec
作用:设定事务的结束位置,同时执行事务。与
multi
成对出现,成对使用。注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行
exec
命令才开始执行。
3、取消事务
指令:
discard
作用:终止当前事务的定义,发生在
multi
之后,exec
之前。
9.3 事务的工作流程
9.4 事务的注意事项
1、定义事务的过程中,命令格式输入错误怎么办?——语法错误
- 命令书写格式有误。
- 处理结果:如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。
2、定义事务的过程中,命令执行出现错误怎么办?——运行错误
- 运行错误:命令格式正确,但是无法正确的执行。如:对list进行
incr
操作。 - 处理结果:能够正确运行的命令会被执行,运行错误的命令不会被执行。
- 注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
3、手动进行事务回滚
- 记录操作过程中被影响的数据之前的状态:
- 单数据:string
- 多数据:hash、list、set、zset
- 设置指令恢复所有的被修改的项:
- 单数据:直接set(注意周边属性,例如时效)
- 多数据:修改对应值或整体克隆复制
9.5 锁
1、什么是锁
1)业务场景
- 天猫双11热卖过程中,对已经售罄的货物追加补货,4个业务员都有权限进行补货。补货的操作可能是一系列的操作,牵扯到多个连续操作,如何保障不会重复操作?
2)业务分析
- 多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作。
- 在操作之前锁定要操作的数据,一旦发生变化,终止当前操作。
3)解决方案
在开启事务
multi
之前,对key添加监视锁,在执行exec
前如果key发生了变化,终止事务执行:watch key1 [key2……]
取消对所有key的监视:
unwatch
4)Tips 18
- Redis应用基于状态控制的批量任务执行。
2、分布式锁
1)业务场景
天猫双11热卖过程中,对已经售罄的货物追加补货,且补货完成。客户端购买热情高涨,3秒内将所有商品购买完毕。本次补货已经将库存全部清空,如何避免最后一件商品不被多人同时购买?【超卖问题】
2)业务分析
- 使用watch监控一个key有没有改变已经不能解决问题,此处要监控的是具体数据。
- 虽然Redis是单线程的,但是多个客户端对同一数据同时进行操作时,如何避免不被同时修改?
3)解决方案——分布式锁
使用
setnx
设置一个公共锁:setnx lock-key value
利用
setnx
命令的返回值特征,有值则返回设置失败,无值则返回设置成功。- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作。
- 对于返回设置失败的,不具有控制权,排队等待。
操作完毕通过
del
删除
- 注意:上述解决方案是一种设计概念,依赖规范保障,具有风险性。
4)Tips 19
- Redis应用基于分布式锁对应的场景控制。
3、死锁解决方案
1)业务场景
- 依赖分布式锁的机制,某个用户操作时对应客户端宕机,且此时已经获取到锁。如何解决?
2)业务分析
- 由于锁操作由用户控制加锁、解锁,必定会存在加锁后未解锁的风险。
- 需要解锁操作不能仅依赖用户控制,系统级别要给出对应的保底处理方案。
3)解决方案——expire
使用
expire
为锁key添加时间限定,到时不释放,放弃锁:expire lock-key second pexpire lock-key milliseconds
- 由于操作通常都是微秒或毫秒级,因此该锁定事件不宜设置过大。具体时间需要业务测试后确认。
- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
- 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时。
- 锁时间设定推荐:最大耗时210%+平均网络延迟110%。
- 如果业务最大耗时<<网络平均延时,通常为2个数量级,取其中单个耗时较长即可。
10 删除策略
10.1 过期数据
1、Redis中的数据特征
- Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过
TTL
指令获取其状态:- XX:具有时效性的数据。
- -1:永久有效的数据。
- -2:已经过期的数据|被删除的数据|未定义的数据。
- 过期的数据真的被删除了吗?——根据数据删除策略进行删除。
2、时效性数据的存储结构
10.2 数据删除策略
1、数据删除策略的目标
- 在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体Redis性能的下降,甚至引发服务器宕机或内存泄露。
2、定时删除——立即删除
- 创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。
- 优点:节约内存,到时间就删除,快速释放掉不必要的内存占用。
- 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响Redis服务器响应时间和指令吞吐量。
- 总结:用处理器性能换取存储空间(拿时间换空间)。
3、惰性删除——下次访问删除
- 数据到达过期时间,不做处理,等下次访问该数据时:
- 如果未过期,返回数据。
- 如果已过期,删除(
expireIfNeeded()
),返回(nil)。
- 优点:节约CPU性能,发现必须删除的时候才删除。
- 缺点:内存压力很大,出现长期占用内存的数据。
- 总结:用存储空间换取处理器性能(拿时间换空间)。
4、定期删除
- Redis启动服务器初始化时,读取配置
server.hz
的值,默认为10。 - 每秒执行
server.hz
次serverCron()
-->databaesCron()
-->activeExpireCycle()
。 activeExpireCycle()
对每个expires[n]
逐一进行检测,每次执行250ms/server.hz。- 对某个
expires[n]
检测时,随机挑选W个key检测:- 如果key超时,删除key。
- 如果一轮中删除的key的数量>W*25%,循环该过程。
- 如果一轮中删除的key的数量<=W*25%,检查下一个
expire[n]
,0~15循环。 - W取值=
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
属性值。
- 参数
current_db
用于记录activeExpireCycle()
进入哪个expires[n]
执行。 - 总结:周期性轮询Redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度(周期性抽查存储空间——随机抽查,重点抽查)。
- 特点:
- CPU性能占用设置有峰值,检测频度可自定义设置。
- 内存压力不是很大,长期占用内存的冷数据会被持续清楚。
5、删除策略对比
- 定时删除:
- 节约内存,无占用。
- 不分时段占用CPU资源,频度高。
- 时间换空间。
- 惰性删除(默认):
- 内存占用严重。
- 延时执行,CPU利用率高。
- 空间换时间。
- 定期删除(默认):
- 内存定期随机清理。
- 每秒花费固定的CPU资源维护内存。
- 随机抽查,重点抽查。
10.3 逐出算法
1、新数据进入检测
1)问题
- 当新数据进入Redis时,如果内存不足怎么办?
2)解决
Redis使用内存存储数据,在执行每一个命令前,会调用
freeMemoryIfNeeded()
检测内存是否充足。如果内存不满足新加入数据的最低存储要求,Redis要临时删除一些数据为当前指令清理存储空间。
清理数据的策略称为逐出算法。
注意:逐出数据的过程不是100%能够清理出足够的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息:
(error)OOM command not allowed when use memory >'maxmemory'
。
2、数据逐出相关配置
最大可使用内存:
maxmemory
占用物理内存的比例,默认值为0,表示不限制。生成环境中根据需求设定,通常设置在50%以上。
每次选取待删除数据的个数:
maxmemory-samples
选取数据时不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据。
删除策略:
maxmemory-policy volatile-lru|allkeys-lru|no-enviction|……
达到最大内存后,对被挑选出来的数据进行删除的策略。
检测易失数据(可能会过期的数据集
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配置。
- 使用
11 redis.conf
11.1服务器基础配置
1、服务器端设定
设置服务器以守护进程的方式运行:
daemonize yes|no
绑定主机地址:
bind 127.0.0.1
设置服务器端口号:
port 6379
设置数据库数量:
databases 16
2、日志配置
设置服务器以指定日志记录级别:
loglevel debug|verbose|notice|warning
日志记录文件名:
logfile 端口号.log
3、客户端配置
设置同一时间最大客户端连接数,默认无限制(0)。当客户端连接到达上限,Redis会关闭新的连接:
maxclients 0
客户端闲置等待最大时长,达到最大值后关闭连接。如需关闭该功能,设置为0:
timeout 300
4、多服务器快捷配置
导入并加载指定配置文件信息,用于快速创建Redis公共配置较多的Redis实例配置文件,便于维护:
include /path/server-端口号.conf
12 高级数据类型
12.1 Bitmaps
1、基础操作
获取指定key对应偏移量上的bit值:
getbit key offset
设置指定key对应偏移量上的bit值,value只能是0或1:
setbit key offset value
2、扩展操作
1)业务场景
- 电影网站:
- 统计每天某一部电影是否被点播。
- 统计每天有多少部电影被点播。
- 统计每周/月/年有多少部电影被点播。
- 统计年度哪部电影没有被点播。
2)业务分析
- 每一位对应电影id,该位为1表示被点播,该位为0表示没有被点播。如:01010011,可表示id为1、2、5、7的电影被点播。
3)扩展操作
对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中:
bitop op destKey key1 [key2……]
op:
- and:交。
- or:并。
- not:非。
- xor:异或。
统计指定key中1的数量:
bitcount key [start end]
12.2 HyperLogLog
1、统计独立UV
- 原始方案:set
- 存储每个用户的id(字符串)。
- 改进方案:Bitmaps
- 存储每个用户的状态(bit)。
- 全新的方案:HyperLogLog。
2、基数统计
- {1, 3, 5, 7, 5, 7, 8} --> 基数集:{1, 3, 5, 7, 8} --> 基数:5。
- 基数:数据集去重后元素的个数。
- HyperLogLog是用来做基数统计的,运用了LogLog算法。
3、基本操作
添加数据:
pfadd key element [element ……]
统计数据:
pfcount key [key ……]
合并数据:
pfmerge destkey sourcekey [sourcekey ……]
4、Tips 22
- Redis应用于独立信息统计。
5、相关说明
- 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据。
- 核心是基数估算算法,最终数值存在一定误差。
- 误差范围:基数估计的结果是一个带有0.81%标准错误的近似值。
- 耗空间极小,每个HyperLogLog的key占用了12K的内存用于标记基数。
pfadd
命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大。pfmerge
命令合并后占用的存储空间为12K,无论合并之前数据量多少。
12.3 GEO
1、案例
- 火热的生活服务类软件(地理位置服务):
- 微信/陌陌
- 美团/饿了么
- 携程/马蜂窝
- 高德/百度
- ……
2、基本操作
添加坐标点:
geoadd key longitude latitude member [longitude latitude member ……]
获取坐标点:
geopos key member [member ……]
计算坐标点距离:
geodist key member 1member2 [unit]
根据坐标求范围内的数据:
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
根据点求范围内数据:
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
获取指定点对应的坐标hash值:
geohash key member [member ……]
13 主从复制
13.1 主从复制简介
1、互联网“三高”架构
- 高并发
- 高性能
- 高可用
可用性=(全年秒数-全年宕机秒数)/全年秒数。
业界可用性目标:5个9,即99.999%(服务器年宕机时长低于315秒,约5.25分钟)。
2、Redis是否高可用
1)单机Redis的风险与问题
- 机器故障:
- 现象:硬盘故障、系统奔溃。
- 本质:数据丢失,很可能对业务造成灾难性打击。
- 结论:基本上会放弃使用Redis。
- 容量瓶颈:
- 现象:内存不足,从16G升级到64G,从64G升级到128G,无限升级内存。
- 本质:穷,硬件条件跟不上。
- 结论:放弃使用Redis。
- 结论:
- 为了避免单点Redis服务器故障,准备多台服务器,互相连通。
- 将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。
- 即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份。
3、多台服务器连接方案
- 提供数据方:
master
:- 主服务器,主节点,主库。
- 主客户端。
- 接收数据方:
slave
:- 从服务器,从节点,从库。
- 从客户端。
- 需要解决的问题:数据同步。
- 核心工作:
master
的数据复制到slave
中。
4、主从复制
- 主从复制:将
master
中的数据,即时、有效地复制到slave
中。 - 特征:一个
master
可以拥有多个slave
,一个slave
只对应一个master
。 - 职责:
master
:- 写数据
- 执行写操作时,将出现变化的数据自动同步到
slave
- 读数据(可忽略)
slave
:- 读数据
- 写数据(禁止)
5、主从复制的作用——高可用集群
- 读写分离:
master
写、slave
读,提高服务器的读写负载能力。 - 负载均衡:基于主从结构,配合读写分离,由
slave
分担master
负载,并根据需求的变化,改变slave
的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量。 - 故障恢复:当
master
出现问题时,由slave
提供服务,实现快速的故障恢复。 - 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式。
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案。
13.2 主从复制工作流程
- 主从复制过程大体可以分为3个阶段:
- 建立连接阶段(即准备阶段):
slave
连接master
- 数据同步阶段:
master
同步给slave
- 命令传播阶段
- 建立连接阶段(即准备阶段):
1、阶段一:建立连接阶段
- 建立
slave
到master
的连接,使master
能够识别slave
,并保存slave
端口号。
1)建立连接阶段工作流程
- 步骤1:设置
master
的地址和端口,保存master
信息:
slave
发送指令:slaveof ip port
。master
接收到指令,响应。
- 步骤2:建立
socket
连接:
slave
保存master
的IP与端口:masterhost
、masterport
。slave
根据保存的信息创建连接master
的socket
。
- 步骤3:发送
ping
命令(定时器任务):
slave
周期性发送命令:ping
。master
响应:pong
。
- 步骤4:身份验证:
slave
发送指令:auth password
。master
验证授权。
- 步骤5:发送
slave
端口信息:
slave
发送指令:replconf listening-port <port-number>
。master
保存slave
的端口号。
2)结果
slave
:保存master
的地址与端口。master
:保存slave
的端口。- 之间创建了连接的
socket
。
3)主从连接(slave
连接master
)
方式一:客户端发送命令:
slaveof <masterip> <masterport>
方式二:启动服务器参数:
redis-server -slaveof <masterip> <masterport>
方式三:服务器配置:
slaveof <masterip> <masterport>
slave
系统信息:master_link_down_since_seconds
masterhost
masterport
master
系统信息:slave_listening_port
(多个)
4)主从断开连接
从客户端发送命令:
slaveof no one
5)授权访问
master
配置文件设置密码:requirepass <password>
master
客户端发送命令设置密码:config set requirepass <password> config get requirepass
slave
客户端发送命令设置密码:auth <password>
slave
配置文件设置密码:masterauth <password>
启动客户端设置密码:
redis-cli -a <password>
2、阶段二:数据同步阶段工作流程
- 在
slave
初次连接master
后,复制master
中的所有数据到slave
。 - 将
slave
的数据库状态更新成master
当前的数据库状态。
1)数据同步阶段工作流程
- 步骤1:请求同步数据:
slave
发送指令:psync2
。
- 步骤2:创建RDB同步数据:
master
执行bgsave
。- 第一个
slave
连接时,master
创建命令缓冲区。 master
生成RDB文件,通过socket
发送给slave
。
- 步骤3:恢复RDB同步数据:
slave
接收RDB,清空数据,执行RDB文件恢复数据。- 以上所有步骤叫做——全量复制。
slave
发送命令告知RDB恢复已经完成。
- 步骤4:请求部分同步数据:
master
发送复制缓冲区信息。
- 步骤5:恢复部分同步数据:
slave
接收信息,执行bgrewriteaof
,恢复数据。- 以上为——部分复制。
2)结果
slave
:具有master
端全部数据,包含RDB过程接收的数据。master
:保存slave
当前数据同步的位置。- 完成了
master
和slave
之间的数据克隆。
3)数据同步阶段master
说明
如果
master
数据量巨大,数据同步阶段应该避开流量高峰期,避免造成master
阻塞,影响业务正常执行。复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使
slave
陷入死循环状态。配置
master
复制缓冲区大小:repl-backlog-size 1mb
master
单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执行bgsave
命令和创建复制缓冲区。
4)数据同步阶段slave
说明
为避免
slave
进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的写对外服务:slave-server-stale-data yes|no
数据同步阶段,
master
发送给slave
信息可以理解master
是slave
的一个客户端,主动向slave
发送命令。多个
slave
同时对master
请求数据同步,master
发送的RDB文件增多,会对带宽造成巨大冲击,如果master
带宽不足,因此数据同步需要根据业务需求,适量错峰。slave
过多时,建议调整拓扑结构,由一主多从变为树状结构,中间的节点既是master
,也是slave
。注意使用树状结构时,由于层级深度,导致深度越高的slave
与最顶层master
间数据同步延迟较大,数据一致性变差,应谨慎选择。
3、阶段三:命令传播阶段
- 当
master
数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播。 master
将接收到的数据变更命令发送给slave
,slave
接收命令后执行命令。
1)命令传播阶段的部分复制
- 命令传播阶段出现了断网现象:
- 网络闪断闪连(忽略)。
- 短时间网络中断(部分复制)。
- 长时间网络中断(全量复制)。
- 部分复制的三个核心要素:
- 服务器的运行id(run id)。
- 主服务器的复制积压缓冲区。
- 主从服务器的复制偏移量。
2)服务器运行ID(runid)
- 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id。
- 组成:运行id由40位字符组成,是一个随机的十六进制字符。如:43865df1ab720f7728970903540ffe09bb47f64f。
- 作用:运行id被用于在服务器间进行传输,识别身份。如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别。
- 实现方式:运行id在每台服务器启动时自动生成,
master
在首次连接slave
时,会将自己的运行id发送给slave
,slave
保存此id,通过info server
命令,可以查看节点的runid。
3)复制缓冲区
- 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,
master
都会将传播的命令记录下来,并存储在复制缓冲区。 - 组成:
- 偏移量
- 字节值
- 工作原理:
- 通过
offset
区分不同的slave
当前数据传播的差异。 master
记录已发送的信息对应的offset
。slave
记录已接收的信息对应的offset
。
- 通过
- 复制缓冲区默认数据存储空间大小是1M,由于存储空间大小是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列。
- 由来:每台服务器启动时,如果开启AOF或被连接成为
master
节点,即创建复制缓冲区。 - 作用:用于保存
master
收到的所有指令(仅影响数据变更的指令,例如set
、select
)。 - 数据来源:当
master
接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中。
4)主从服务器复制偏移量(offset)
- 概念:一个数字,描述复制缓冲区中的指令字节位置。
- 分类:
master
复制偏移量:记录发送给所有slave
的指令字节对应的位置(多个)。slave
复制偏移量:记录slave
接收master
发送过来的指令字节对应的位置(一个)。
- 数据来源:
master
端:发送一次记录一次。slave
端:接收一次记录一次。
- 作用:同步信息,比对
master
与slave
的差异,当slave
断线后,恢复数据使用。
4、数据同步+命令传播阶段工作流程
1)数据同步阶段
slave
发送指令:psync2 ? -1
。master
执行bgsave
生成RDB文件,记录当前的复制偏移量offset
。master
发送+FULLRESYNC runid offset
,通过socket
发送RDB文件给slave
。slave
收到+FULLRESYNC
,保存master
的runid
和offset
,清空当前全部数据,通过socket
接收RDB文件,恢复RDB数据。slave
发送命令:psync2 runid offset
。master
接收命令,判定runid
是否匹配,判定offset
是否在复制缓冲区中。master
:如果runid
或offset
有一个不满足,执行全量复制;如果runid
或offset
校验通过,offset
相同,忽略;如果runid
或offset
校验通过,offset
不相同,发送+CONTINUE offset
,通过socket
发送复制缓冲区中未同步的数据。slave
收到+CONTINUE
,保存master
的offset
。接收信息后,执行bgrewriteaof
,恢复数据。
- 全量复制:1~4步。
- 部分复制:5~8步。
2)命令传播阶段
slave
发送命令:replconf ack offset
。master
接收命令,判定offset
是否在复制缓冲区中。master
:如果不在缓冲区,执行全量复制;如果在缓冲区,且offset
相同,忽略;如果在缓冲区,且offset
不相同,发送+CONTINUE offset
,通过socket
发送复制缓冲区中未同步的数据。slave
收到+CONTINUE
,保存master
的offset
。接收信息后,执行bgrewriteaof
,恢复数据。
5、心跳机制
1)心跳机制
- 进入命令传播阶段后,
master
与slave
间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线。 master
心跳:- 指令:
PING
。 - 周期:由
repl-ping-slave-period
决定,默认10秒。 - 作用:判断
slave
是否在线。 - 查询:
INFO replication
,获取slave
最后一次连接时间间隔,lag
项维持在0或1视为正常。
- 指令:
slave
心跳任务:- 指令:
REPLCONF ACK {offset}
。 - 周期:1秒。
- 作用1:汇报
slave
自己的复制偏移量,获取最新的数据变更指令。 - 作用2:判断
master
是否在线。
- 指令:
2)心跳阶段注意事项
当
slave
多数掉线,或延迟过高时,master
为保障数据稳定性,将拒绝所有同步操作:min-slaves-to-write 2 min-slaves-max-lag 10
slave
数量少于2个,或者所有所有slave
的延迟都大于等于10秒时,强制关闭master
写功能,停止数据同步。slave
数量由slave
发送REPLCONF ACK
命令确认。slave
延迟由slave
发送REPLCONF ACK
命令确认。
13.3 主从复制常见问题
1、频繁的全量复制
1)问题1
- 伴随着系统的运行,
master
的数据量会越来越大,一旦master
重启,runid
将发生变化,会导致全部slave
的全量复制操作。 - 内部优化调整方案:
master
内部创建master_replid
变量,使用runid
相同的策略生成,长度41位,并发送给所有slave
。- 在
master
关闭时执行命令shutdowm save
,进行RDB持久化,将runid
与offset
保存到RDB文件中。repl-id
、repl-offset
。- 通过
redis-check-rdb rdb文件
命令可以查看该信息。
master
重启后加载RDB文件,恢复数据。重启后,将RDB文件中保存的repl-id
与repl-offset
加载到内存中。master_repl_id = repl
、master_repl_offset=repl-offset
。- 通过
info
命令可以查看该信息。
- 作用:本机保存上次的
runid
,重启后恢复该值,使所有slave
认为还是之前的master
。
2)问题2
问题现象:网络环境不佳,出现网络中断,
slave
不提供服务。问题原因:复制缓冲区过小,断网后
slave
的offset
越界,触发全量复制。最终结果:
slave
反复进行全量赋值。解决方案:修改赋值缓冲区大小。
repl-backlog-size
建议设置如下:
- 测算从
master
到slave
的重连平均时长second
。 - 获取
master
平均每秒产生写命令数据总量write_size_per_second
。 - 最优复制缓冲区空间=
2 × second × write_size_per_second
。
- 测算从
2、频繁的网络中断
1)问题1
问题现象:
master
的CPU占用过高或slave
频繁断开连接。问题原因:
slave
每秒发送REPLCONF ACK
命令到master
。- 当
slave
接到了慢查询时(keys *
、hgetall
等),会大量占用CPU性能。 master
每秒调用复制定时函数replicationCron()
,比对slave
发现长时间没有进行响应。
最终结果:
master
各种资源(输出缓冲区、带宽、连接等)被严重占用。解决方案:通过设置合理的超时时间,确认是否释放
slave
。repl-timeout
该参数定义了超时时间的阈值(默认60秒),超过该值,释放
slave
。
2)问题2
问题现象:
slave
与master
连接断开。问题原因:
master
发送ping
指令频度较低。master
设定超时时间较短。ping
指令在网络中存在丢包。
解决方案:提高
ping
指令发送的频度。repl-ping-slave-period
超时时间
repl-time
的时间至少是ping
指令频度的5到10倍,否则slave
很容易判定超时。
3、数据不一致
问题现象:多个
slave
获取相同数据不同步。问题原因:网络信息不同步,数据发送有延迟。
解决方案:
优化主从间的网络环境,通常放置在同一个机房部署,如使用阿里云等云服务器时要注意此现象。
监控主从节点延迟(通过
offset
)判断,如果slave
延迟过大,暂时屏蔽程序对该slave
的数据访问:slave-serve-stale-data yes|no
开启后进响应
info
、slaveof
等少数命令(慎用,除非对数据一致性要求很高)。
14 哨兵
## 14.1 哨兵简介
1、主机“宕机”
- 将宕机的
master
下线。 - 找一个
slave
作为master
。 - 通知所有的
slave
连接新的master
。 - 启动新的
master
与slave
。 - 全量复制+部分复制。
2、哨兵
- 哨兵(sentinel)是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的
master
并将所有slave
连接到新的master
。
3、哨兵的作用
1)监控
- 不断地检查
master
和slave
是否正常运行。 master
存活检测、master
与slave
运行情况检测。
2)通知(提醒)
- 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
3)自动故障转移
- 断开
master
与slave
连接,选取一个slave
作为master
,将其他slave
连接到新的master
,并告知客户端新的服务器地址。
4)注意
- 哨兵也是一台Redis服务器,只是不提供数据服务。
- 通常哨兵配置数量为单数。
14.2 启用哨兵模式
1、配置哨兵
配置一拖二的主从结构。
配置三个哨兵(配置相同,端口不同):
sentinel-26379.conf
。port 26379 dir /tmp sentinel monitor mymaster 127.0.0.1 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel parallel-syncs mymaster 1 sentinel failover-timeout mymaster 180000
启动哨兵(先启主机,再启从机,最后启哨兵):
redis-sentinel sentinel-端口号.conf
14.3 哨兵工作原理
1、主从切换
- 哨兵在进行主从切换过程中经历三个阶段:
- 监控:同步信息。
- 通知:保持联通。
- 故障转移:
- 发现问题
- 竞选负责人
- 优选新
master
- 新
master
上任,其他slave
切换master
,原master
作为slave
(故障恢复后连接)
2、阶段一:监控阶段
1)同步各个节点的状态信息
- 获取各个
sentinel
的状态(是否在线)。 - 获取
master
的状态:master
属性:runid
role:master
- 各个
slave
的详细信息。
- 获取所有
slave
的状态(根据master
中的slave
信息):slave
属性:runid
role:slave
master_host
、master_port
offset
- ……
3、阶段二:通知阶段
4、阶段三:故障转移阶段
服务器列表中挑选备选
master
,排除原则:- 在线的
- 响应慢的
- 与原
master
断开时间久的 - 优先原则:优先级、
offset
、runid
发送指令(
sentinel
):- 向新的
master
发送slaveof no one
- 向其他
slave
发送slaveof 新masterIP 端口
- 向新的
15 集群
15.1 集群简介
1、问题——业务发展过程中遇到峰值瓶颈
- Redis提供的服务OPS可以达到10万/秒,当前业务OPS已经达到20万/秒。
- 内存单机容量达到256G,当前业务需求内存容量1T。
2、集群架构
- 集群:使用网络将若干计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果。
3、集群作用
- 分散单台服务器的访问压力,实现负载均衡。
- 分散单台服务器的存储压力,实现可扩展性。
- 降低单台服务器宕机带来的业务灾难。
15.2 Redis集群结构的设计
1、数据存储的设计
- 通过算法设计,计算出key应该保存的位置。
- 将所有的存储空间分成16384份,每台主机保存一部分(每份代表的是一个存储空间,而不是key的保存空间)。
- 将key按照计算出的结果放到对应的存储空间。
- 增强可扩展性。
2、集群内部通讯的设计
- 各个数据库相互通信,保存各个库中槽的编号数据。
- 一次命中,直接返回。
- 一次未命中,告知具***置。
15.3 集群搭建
配置
redis.conf
,添加集群信息:cluster-enabled yes # 开启cluster集群 cluster-config-file nodes-端口号.conf # 配置节点配置文件,连接集群后会自动生成 cluster-node-timeout 100000 # 超时
启动服务器。
连接集群(切换到src目录):
./redis-trib.rb create --replicas <1个master连接的slave个数> <所有master的IP ……> <所有slave的IP ……>
启动客户端:
redis-cli -c [-p 端口]
主从下线、主从切换?
15.4 Cluster配置、指令
1、配置
设置加入集群,成为其中的节点:
cluster-enabled yes|no
集群配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容:
cluster-config-file <filename>
节点服务响应超时时间,用于判定该节点是否下线或切换为从节点:
cluster-node-time <milliseconds>
master
连接的slave
的最小数量:cluster-migration-barrier <count>
2、指令
查看集群节点信息:
cluster nodes
进入一个从节点Redis,切换其主节点:
cluster replicate <master-id>
发现一个新节点,新增主节点:
cluster meet ip:port
忽略一个没有slot的节点:
cluster forget <id>
手动故障转移:
cluster failover
16 企业级解决方案
16.1 缓存预热
1、问题——“宕机”
- 服务器启动后快速宕机。
2、问题排查
- 请求数量较高。
- 主从之间数据吞吐量较大,数据同步操作频度较高。
3、解决方案
- 前置准备工作:
- 日常例行统计数据访问记录,统计访问频度较高的热点数据。
- 利用LRU数据删除策略,构建数据留存队列。例如:storm与kafka配合。
- 准备工作:
- 将统计结果中的数据分类,根据级别,Redis优先加载级别较高的热点数据。
- 利用分布式多服务器同时进行数据读取,提速数据加载过程。
- 实施:
- 使用脚本程序固定触发数据预热过程。
- 如果条件允许,使用了CDN(内容分发网络),效果会更好。
4、总结
- 缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题。用户直接查询事先被预热的缓存数据。
16.2 缓存雪崩
1、现象——数据库服务器奔溃
- 系统平稳运行过程中,忽然数据库连接量激增。
- 应用服务器无法及时处理请求。
- 大量408、500错误页面出现。
- 客户反复刷新页面获取数据。
- 数据库奔溃。
- 应用服务器奔溃。
- 重启应用服务器无效。
- Redis服务器奔溃。
- Redis集群奔溃。
- 重启数据库后再次被瞬间流量放倒。
2、问题排查
- 在一个较短的时间内,缓存中较多的key集中过期。
- 此周期内请求访问过期的数据,Redis未命中,Redis向数据库获取数据。
- 数据库同时接受到大量的请求无法及时处理。
- Redis大量请求被积压,开始出现超时现象。
- 数据库流量激增,数据库奔溃。
- 重启后仍然面对缓冲中无数据可用。
- Redis服务器资源被严重占用,Redis服务器奔溃。
- Redis集群呈现崩塌,集群瓦解。
- 应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃。
- 应用服务器、Redis、数据库全部重启,效果不理想。
3、问题分析
- 短时间范围内。
- 大量key集中过期。
4、解决方案
1)理论
- 更多的页面静态化处理。
- 构建多级缓存架构:Nginx缓存+Redis缓存+ehcache缓存。
- 检测MySQL严重耗时业务进行优化,对数据库的瓶颈排查:例如超时查询、耗时较高事务等。
- 灾难预警机制。监控Redis服务器性能指标:
- CPU占用、CPU使用率
- 内存容量
- 查询平均响应时间
- 线程数
- 限流、降级:短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问。
2)操作
- LRU与LFU切换。
- 数据有效期策略调整:
- 根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟。
- 过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量。
- 超热数据使用永久key。
- 定期维护(自动+人工):对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时。
- 加锁(慎用!)。
5、总结
- 缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如果能够有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。
16.3 缓存击穿
1、现象——数据库服务器奔溃
- 系统平稳运行过程中。
- 数据库连接量瞬间激增。
- Redis服务器无大量key过期。
- Redis内存平稳,无波动。
- Redis服务器CPU正常。
- 数据库崩溃。
2、问题排查
- Redis中某个key过期,该key访问量巨大。
- 多个数据请求从服务器直接压到Redis后,均未命中。
- Redis在短时间内发起了大量对数据库中同一数据的访问。
3、问题分析
- 单个key高热数据。
- key过期。
4、解决方案
预先设定
以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物街期间,加大此类信息key的过期时长。
注意:购物街不仅仅指当天,以及后续若干天,访问峰值呈逐渐降低的趋势。
现场调整
监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key。
后台刷新数据
启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失。
二级缓存
设置不同的失效时间,保障不会被同时淘汰。
加锁
分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎用!
5、总结
- 缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中Redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。
- 应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可。
16.4 缓存穿透
1、现象——数据库服务器崩溃
- 系统平稳运行过程中。
- 应用服务器流量随时间增量较大。
- Redis服务器命中率随时间逐步降低。
- Redis内存平稳,内存无压力。
- Redis服务器CPU占用激增。
- 数据库服务器压力激增。
- 数据库崩溃。
2、问题排查
- Redis中大面积出现未命中。
- 出现非正常URL访问。
3、问题分析
- 获取的数据在数据库中也不存在,数据库查询未得到对应的数据。
- Redis获取到null数据未进行持久化,直接返回。
- 下次此类数据到达重复上述过程。
- 出现黑客攻击服务器。
4、解决方案
缓存null
对查询结果为null的数据进行缓存(长期使用,定期清理),设定短时限,例如30~60秒,最高5分钟。
白名单策略
提前预热各类数据id对应的bitmaps,id作为bitmaps的offset,相当于设置了数据白名单。当加载正常数据时,放行,加载异常数据时直接拦截(效率偏低)。
使用布隆过滤器(有关布隆过滤器的命中问题对当前状况可以忽略)。
实时监控
实时监控Redis命中率(业务正常范围时,通常会有一个波动值)与null数据的占比。
- 非活动时段波动:通常检测3~5倍,超过5倍纳入重点排查对象。
- 活动时段波动:通常检测10~50倍,超过50倍纳入重点排查对象。
根据倍数不同,启动不同的排查流程。然后使用黑名单进行防控(运营)。
key加密
问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验。
例如每天随机分配60个加密串,挑选2到3个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问。
5、总结
- 缓存穿透访问了不存在的数据,跳过了合法数据的Redis数据缓存,每次访问数据库,导致对数据库服务器造成压力。
- 通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并及时报警。
- 应对策略应该在临时预案防范多方面做文章。
- 无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除。
16.5 性能指标监控
1、监控指标
- 性能指标:Performance。
- 内存指标:Memory。
- 基本活动指标:Basic activity。
- 持久性指标:Persistence。
- 错误指标:Error。
1)性能指标:Performance
latency
:Redis响应一个请求的时间。instantaneous_ops_per_sec
:平均每秒处理请求总数。hit rate (calculated)
:缓存命中率(计算出来的)。
2)内存指标:Memory
used_memory
:已使用内存。mem_fragmentation_ratio
:内存碎片率。evicted_keys
:由于最大内存限制被移除的key的数量。blocked_clients
:由于BLPOP
、BRPOP
、or BRPOPLPUSH
而被阻塞的客户端。
3)基本活动指标:Basic activity
connected_clients
:客户端连接数。connected_slaves
:slave
数量。master_last_io_seconds_ago
:最近一次主从交互之后的秒数。keyspace
:数据库中key的总数。
4)持久性指标:Persistence
rdb_last_save_time
:最后一次持久化保存到磁盘的时间戳。rdb_changes_since_last_save
:自最后一次持久化以来数据库的更改数。
5)错误指标:Error
rejected_connections
:由于达到maxclient
限制而被拒绝的连接数。keyspace_misses
:key查找失败(没有命中)次数。master_link_down_since_seconds
:主从断开的持续时间(以秒为单位)。
2、监控方式
1)工具
- Cloud Insight Redis
- Prometheus
- Redis-stat
- Redis-faina
- RedisLive
- zabbix
2)命令
- benchmark
- redis cli
- monitor
- slowlog
3、监控命令
1)benchmark
命令:
redis-benchmark [-h ] [-p ] [-c ] [-n <requests>] [-k ]
示例1:
redis-benchmark
说明:50个连接,10000次请求对应的性能。
示例2:
redis-benchmark -c 100 -n 5000
说明:100个连接,5000次请求对应的性能。
2)monitor
命令:
monitor
打印服务器调试信息。
3)slowlog
命令:
slowlog [operator]
operator:
- get:获取慢查询日志。
- len:获取慢查询日志条目数。
- reset:重置慢查询日志。
相关配置:
showlog-log-slower-than 1000 # 设置慢查询的时间下线,单位:微秒 slowlog-max-len 100 # 设置慢查询命令对应的日志显示长度,单位:命令数