[TOC]
1、Redis初级(Windows)
1、Redis入门
1、Redis 简介
1、Nosql的出现
NoSql出现的解决的问题:
- 海量用户
- 高并发
罪魁祸首——关系型数据库:
- 性能瓶颈:磁盘IO性能低下
- 扩展瓶颈:数据关系复杂,扩展性差,不便于大规模集群
解决思路:
- 降低磁盘IO次数,越低越好—— 内存存储
- 去除数据间关系,越简单越好——不存储关系,仅存储数据
以上解决思路的实际实现:NoSql
2、Nosql 简介
NoSQL:即 Not-Only SQL( 泛指非关系型的数据库),作为关系型数据库的补充。
作用:应对基于海量用户和海量数据前提下的数据处理问题。
特征:
- 可扩容,可伸缩
- 大数据量下高性能
- 灵活的数据模型
- 高可用
常见 Nosql 数据库:
- ==Redis==
- memcache
- HBase
- MongoDB
3、具体解决方案 ——(电商场景)
- 商品基本信息 ——MySQL
- 名称
- 价格
- 厂商
- 商品附加信息 —— MongoDB
- 描述
- 详情
- 评论
- 图片信息 —— 分布式文件系统
- 搜索关键字 —— ES、Lucene、solr
- 热点信息 —— Redis、memcache、tair
- 高频
- 波段性
4、Redis
概念:Redis (REmote DIctionary Server) 是用 ==C 语言==开发的一个==开源==的高性能==键值对==(key-value)数据库。
在线测试:http://try.redis.io/
使用文档:http://doc.redisfans.com/
特征:
- 数据间没有必然的关联关系
- 内部采用单线程机制进行工作
- 高性能。官方提供测试数据,50个并发执行100000 个请求,读的速度是110000 次/s,写的速度是81000次/s。
- 多数据类型支持
- 字符串类型——string
- 列表类型——list
- 散列类型——hash
- 集合类型——set
- 有序集合类型——sorted_set
- 这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。
- 在此基础上,Redis支持各种不同方式的排序。
- 与memcached一样,为了保证效率,数据都是缓存在内存中。
- 区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
- 持久化支持。可以进行数据灾难恢复
- 并且在此基础上实现了**master-slave(主从)**同步
Redis是单线程+多路IO复用技术
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)
==串行 vs 多线程+锁(memcached) vs 单线程+多路IO复用(Redis)==
(与Memcache三点不同: 支持多数据类型,支持持久化,单线程+多路IO复用)
5、Redis 的应用
- 配合关系型数据库做高速缓存
- 为热点数据加速查询(主要场景),如热点商品、热点新闻、热点资讯、推广类等高访问量信息等
- 任务队列,如秒杀、抢购、购票排队等
- 即时信息查询,如各位排行榜、各类网站访问统计、公交到站信息、在线人数信息(聊天室、网站)、设备信号等
- 时效性信息控制,如验证码控制、投票控制等
- 布式数据共享,如分布式集群架构中的 session 分离
- 多样的数据结构存储持久化数据
- 消息队列
- 分布式锁
2、Redis 的下载与安装
1、Redis 的下载
Linux 版:(适用于企业级开发)
- Redis 高级开始使用
- 以4.0 版本作为主版本
Windows 版本(适合零基础学习)
- Redis 入门使用
- 以 3.2 版本作为主版本
- 下载地址
2、安装 Redis
核心文件:
- redis-server.exe:服务器启动命令
- redis-cli.exe:命令行客户端
- redis.windows.conf:redis核心配置文件
- Linux环境下是redis.conf
- redis-benchmark.exe :性能测试工具,可以在自己本子运行,看看自己本子性能如何
- redis-check-aof.exe:AOF文件修复工具,修复有问题的AOF文件
- redis-check-dump.exe:RDB文件检查工具(快照持久化文件),修复有问题的dump.rdb文件
- 在Linux环境下还有一个redis-sentinel:Redis集群使用
3、启动 Redis
服务器启动:
1、前台启动(不推荐)
- 端口:6379
- PID:随机生成
客户端连接:
前台启动,命令行窗口不能关闭,否则服务器停止。
2、后台启动(推荐)
修改redis.windows.conf文件将里面的daemonize no 改成 yes,让服务在后台启动。
可以使用客户端Ping一下看看能不能连接成功
3、Redis 的基本操作
1、命令行模式工具使用思考
- 功能性命令
- 清除屏幕信息
- 帮助信息查阅
- 退出指令
2、信息添加
功能:设置 key,value 数据
命令
1
set key value
范例
1
set name zhangsan
3、信息查询
功能:根据 key 查询对应的 value,如果不存在,返回空(nil)
命令
1
get key
范例
1
get name
4、清除屏幕信息
功能:清除屏幕中的信息
命令
1
clear
5、退出客户端命令行模式
功能:退出客户端
命令
1
2
3quit
exit
<ESC>
6、帮助
功能:获取命令帮助文档,获取组中所有命令信息名称
命令
1
2help 命令名称
help @组名
2、Redis 数据类型
1、数据存储类型介绍
1、业务数据的特殊性
1、作为缓存使用
- 原始业务功能设计
- 秒杀
- 618活动
- 双11活动
- 排队购票
- 运营平台监控到的突发高频访问数据
- 突发时政要闻,被强势关注围观
- 高频、复杂的统计数据
- 在线人数
- 投票排行榜
2、附加功能
系统功能优化或升级
- 单服务器升级集群
- Session 管理
- Token 管理
2、Redis 数据类型(5种常用)
redis | java |
---|---|
string | String |
hash | HashMap |
list | LinkedList |
set | HashSet |
sorted_set | TreeSet |
2、String
1、redis 数据存储格式
redis 自身是一个 Map,其中所有的数据都是采用 key : value
的形式存储
数据类型指的是存储的数据的类型,也就是 value 部分的类型,==key 部分永远都是字符串==
2、string 类型
- 存储的数据:单个数据,最简单的数据存储类型,也是最常用的数据存储类型
- 存储数据的格式:一个存储空间保存一个数据
- 存储内容:通常使用字符串,如果字符串以整数的形式展示,可以作为数字操作使用
- String类型是二进制安全的。
- 意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象。
- String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
- String类型是二进制安全的。
3、String类型的数据结构
String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用==预分配冗余空间==的方式来减少内存的频繁分配.
如图中所示:
- 内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。
- 当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。
- 需要注意的是字符串最大长度为512M。
4、string 类型数据的基本操作
添加/修改数据
1
set key value
只有在 key 不存在时 设置 key 的值
1
setnx key value
用 value 覆写 key 所储存的字符串值,从<起始位置>开始(索引从0开始)。
1
setrange <key><起始位置><value>
获取数据
1
get key
获得值的范围,类似java中的substring,前包,后包
1
getrange <key><起始位置><结束位置>
删除数据
1
del key
添加/修改多个数据(m:Multiple[ˈmʌltɪpl])
1
mset key1 value1 key2 value2 …
获取多个数据
1
mget key1 key2 …
获取数据字符个数(字符串长度)
1
strlen key
追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
1
append key value
设置数值数据增加指定范围的值(操作具有原子性)
1
2
3
4
5
6
7
8
9# 将指定的key的值加1
incr key
# 将指定的key的值加 increment (increment为整型)
# 当然increment也可以为负数,若increment为负数,则功能相当于decrby
incrby key increment
# 将指定的key的值加 increment,increment为浮点数
incrbyfloat key increment设置数值数据减少指定范围的值
1
2
3
4
5
6# 将指定的key的值减1
decr key
# 将指定的key的值减 increment (increment为整型)
# 当然increment也可以为负数,若increment为负数,则功能相当于incrby
decrby key increment设置数据具有指定的生命周期
1
2
3setex key seconds value
psetex key milliseconds value
单数据操作与多数据操作的选择之惑:
1 | set key value |
- 假设需要花费时间的操作有:
- set 过程
- 存储过程
- 返回结果的过程(result)
- 并且set与result的时间一样
- 发送100条数据
- 单指令发送:200 * set/result过程 + 100 * 存储过程
- 多指令发送:2 * set/result过程 + 100 * 存储过程
- 这样看来似乎多数据操作会比单数据操作好
- 其实不然,看似多数据操作会比单数据操作好要快,但是多数据操作数据的回馈并没有比单数据操作好
- 这里的数据的回馈指的是进行展示的数据
- 当数据量达到一亿,一次性发送一亿的数据,客户端这边需要等待数据存储的过程将会更长,而使用100万次发送100万次数据的复合操作来说,用户的体验会更好
- 结论:具体情况具体分析。
5、string 作为数值操作
- string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算。
- redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响。
- 注意:按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。
9223372036854775807
(java中long型数据最大值,Long.MAX_VALUE)
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,后台设定定时刷新策略即可
1
2
3
4
5user:id:3506728370:fans → 12210947
user:id:3506728370:blogs → 6164
user:id:3506728370:focuss → 83在redis中以json格式存储大V用户信息,定时刷新(也可以使用hash类型)
1
user:id:3506728370 → {"id":3506728370,"name":"春晚","fans":12210862,"blogs":6164, "focus":83}
8、key 的设置约定
数据库中的热点数据key命名惯例
9、string 类型应用场景
Tips 1 :
redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
此方案适用于所有数据库,且支持数据库集群
大型企业级应用中,分表操作是基本操作,使用多张表存储同类型数据,但是对应的主键 id 必须保证统一性,不能重复。Oracle 数据库具有 sequence 设定,可以解决该问题,redis 可以解决 MySQL数据库该问题
Tips 2 :
- redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
- “最强女生”启动海选投票,只能通过微信投票,每个微信号每 4 小时只能投1票。
- 电商商家开启热门商品推荐,热门商品不能一直处于热门期,每种商品热门期维持3天,3天后自动取消热门。
- 新闻网站会出现热点新闻,热点新闻最大的特征是时效性,如何自动控制热点新闻的时效性。
- redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
Tips 3 :
- redis应用于各种结构型和非结构型高热度数据访问加速
- 主页高频访问信息显示控制,例如新浪微博大V主页显示粉丝数与微博数量
- redis应用于各种结构型和非结构型高热度数据访问加速
3、hash
1、hash 类型
存储的困惑
对象类数据的存储如果具有较频繁的更新需求操作会显得笨重
- 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
- 需要的存储结构:一个存储空间保存多个键值对数据
- hash类型:底层使用哈希表结构实现数据存储
hash存储结构优化:
- 如果field数量较少,存储结构优化为类数组结构
- 如果field数量较多,存储结构使用HashMap结构
2、Hash 的数据结构
Hash类型对应的数据结构是两种:
- ziplist(压缩列表)
- hashtable(哈希表)。
当field-value长度较短且个数较少时,使用ziplist,否则使用hashtable。
3、hash 类型数据的基本操作
添加/修改数据
1
2# 设置存储的hashMap 的key 和 value
hset key field value获取数据
1
2
3
4
5# 获取存储的 hashMap的key——field
hget key field
# 获取存储的hashMap的所有key
hgetall key删除数据
1
hdel key field1 [field2]
添加/修改多个数据
1
hmset key field1 value1 field2 value2 …
获取多个数据
1
hmget key field1 field2 …
获取哈希表中字段的数量
1
hlen key
获取哈希表中是否存在指定的字段
1
hexists key field
获取哈希表中所有的字段名或字段值
1
2
3hkeys key
hvals key设置指定字段的数值数据增加指定范围的值
1
2
3
4
5# 整型
hincrby key field increment
# 浮点
hincrbyfloat key field increment如果key存在就不改变,如果key不存在就设置filed 与 value
1
hsetnx key field value
4、hash 类型数据操作的注意事项
- hash类型下的value只能存储==字符串==**,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到,对应的值为(nil)**
- 每个 hash 可以存储 2^32 - 1 个键值对
- hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
- hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈
5、string存储对象(json)与hash存储对象
- string存储对象(json)
- 讲究整体性——一次性数据以整体操作:要么一次性更新,要么一次性获取
- 讲究的是以 ==读== 为主
- hash存储对象
- 由于使用hash存储的话可以使用field将属性隔离开,所以hash讲究的是==更新==操作
- hash讲究的是==群组==概念,把一系列的数据包装成一个群组,对外产生唯一一个接口——key
- 如果业务环境以更新操作或修改数量比较多的操作,推荐使用hash的方法存储对象
- 总结:具体情况具体分析
6、hash 类型应用场景
- Tips 4
- redis 应用于购物车数据存储设计
- 电商网站购物车设计与实现
- redis 应用于购物车数据存储设计
- Tips 5
- redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
- 双11活动日,销售手机充值卡的商家对移动、联通、电信的30元、50元、100元商品推出抢购活动,每种商品抢购上限1000张
- redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
4、list
1、list 类型
- 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
- 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
- list类型:保存多个数据,底层使用双向链表存储结构实现
2、list 的数据结构
List的数据结构为快速链表quickList。
首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表。
- 它将所有的元素紧挨着一起存储,分配的是一块连续的内存。
当数据量比较多的时候才会改成quicklist。
因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。
Redis将链表和ziplist结合起来组成了quicklist。也就是将多个ziplist使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。
3、list 类型数据基本操作
添加/修改数据
1
2
3
4
5# 从队列左边添加数据
lpush key value1 [value2] ……
# 从队列右边添加数据
rpush key value1 [value2] ……获取数据
1
2
3
4
5
6
7
8
9# 从队列的左边获取数据,从start到stop(队列的最右边第一个数据的下标为-1)
# 所以取出所有数据的命令为:lrange key 0 -1
lrange key start stop
# 从队列的左边获取第index个数据
lindex key index
# 队列的key个数
llen key获取并移除数据(值在键在,值光键亡)
1
2lpop key
rpop key规定时间内获取并移除数据
1
2
3
4
5
6
7
8# 在规定时间内从左/右边获取并移除数据,若以达规定时间key1没有数据,返回(nil)
blpop key1 [key2] timeout
brpop key1 [key2] timeout
# 从列表中取出最后一个元素,并插入到另外一个列表的头部;
# 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。
# source 为 想要取出数据的列表,destination 为 目标列表,timeout 为超时时间
brpoplpush source destination timeout从key1列表右边吐出一个值,插到key2列表左边。
1
rpoplpush <key1><key2>
在value的后面插入newvalue插入值
1
linsert <key> before <value><newvalue>
将列表key下标为index的值替换成value
1
lset <key><index><value>
移除指定数据
1
lrem key count value
4、list 类型数据操作注意事项
- list中保存的数据都是string类型的,数据总容量是有限的,最多2^32 - 1 个元素 (
4294967295
)。 - list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
- 获取全部数据操作结束索引设置为-1
- list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载
5、list 类型应用场景
**Tips 6 **
- redis 应用于具有操作先后顺序的数据控制
- 微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息;如果取消点赞,移除对应好友信息
- redis 应用于具有操作先后顺序的数据控制
**Tips 7 **
redis 应用于最新消息展示
twitter、新浪微博、腾讯微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列在前面
新闻、资讯类网站将最新的新闻或资讯按照发生的时间顺序展示
企业运营过程中,系统将产生出大量的运营数据,保障多台服务器操作日志的统一顺序输出
5、set
1、set 类型
- 新的存储需求:存储大量的数据,在查询方面提供更高的效率
- 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
- set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的(自动排重)
- Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的==复杂度都是O(1)。==
2、Set 的数据结构
Set数据结构是dict字典,字典是用哈希表实现的。
- Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象。
- Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。
3、set 类型数据的基本操作
添加数据
1
sadd key member1 [member2]
获取全部数据
1
smembers key
删除数据
1
srem key member1 [member2]
获取集合数据总量
1
scard key
判断集合中是否包含指定数据
1
sismember key member
随机获取集合中指定数量的数据
1
srandmember key [count]
随机获取集合中的某个数据并将该数据移出集合
1
spop key [count]
求两个集合的交、并、差集
1
2
3
4
5
6
7
8# 交集
sinter key1 [key2]
# 并集
sunion key1 [key2]
# 差集(注意差集的key1与key2互换的话可能导致结果不同)
sdiff key1 [key2]求两个集合的交、并、差集并存储到指定集合中
1
2
3
4
5
6
7
8# 交集
sinterstore destination key1 [key2]
# 并集
sunionstore destination key1 [key2]
# 差集(注意差集的key1与key2互换的话可能导致结果不同)
sdiffstore destination key1 [key2]将指定数据从原始集合中==移动==到目标集合中
1
smove source destination member
4、set 类型数据操作的注意事项
- set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份
- set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间
5、set 类型应用场景
- **Tips 8 **
- redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
- 每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别逐渐产生兴趣,增加客户留存度
- redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
- **Tips 9 **
- redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
- 显示共同关注(一度)
- 显示共同好友(一度)
- 由用户A出发,获取到好友用户B的好友信息列表(一度)
- 由用户A出发,获取到好友用户B的购物清单列表(二度)
- 由用户A出发,获取到好友用户B的游戏充值列表(二度)
- 脉脉为了促进用户间的交流,保障业务成单率的提升,需要让每位用户拥有大量的好友,事实上职场新人不具有更多的职场好友,如何快速为用户积累更多的好友?
- 新浪微博为了增加用户热度,提高用户留存性,需要微博用户在关注更多的人,以此获得更多的信息或热门话题,如何提高用户关注他人的总量?
- QQ新用户入网年龄越来越低,这些用户的朋友圈交际圈非常小,往往集中在一所学校甚至一个班级中,如何帮助用户快速积累好友用户带来更多的活跃度?
- 微信公众号是微信信息流通的渠道之一,增加用户关注的公众号成为提高用户活跃度的一种方式,如何帮助用户积累更多关注的公众号?
- 美团外卖为了提升成单量,必须帮助用户挖掘美食需求,如何推荐给用户最适合自己的美食?
- redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
- **Tips 10 **
- redis应用于同类型不重复数据的合并操作
- 集团公司共具有12000名员工,内部OA系统中具有700多个角色,3000多个业务操作,23000多种数据,每位员工具有一个或多个角色,如何快速进行业务操作的权限校验?
- redis应用于同类型不重复数据的合并操作
- **Tips 11 **
- redis 应用于同类型数据的快速去重
- 公司对旗下新的网站做推广,统计网站的PV(访问量),UV(独立访客),IP(独立IP)。
- PV:网站被访问次数,可通过刷新页面提高访问量
- UV:网站被不同用户访问的次数,可通过cookie统计访问量,相同用户切换IP地址,UV不变
- IP:网站被不同IP地址访问的总次数,可通过IP地址统计访问量,相同IP不同用户访问,IP不变
- 公司对旗下新的网站做推广,统计网站的PV(访问量),UV(独立访客),IP(独立IP)。
- redis 应用于同类型数据的快速去重
- **Tips 12 **
- redis 应用于基于黑名单与白名单设定的服务控制
- 黑名单
- 资讯类信息类网站追求高访问量,但是由于其信息的价值,往往容易被不法分子利用,通过爬虫技术,快速获取信息,个别特种行业网站信息通过爬虫获取分析后,可以转换成商业机密进行出售。例如第三方火车票、机票、酒店刷票代购软件,电商刷评论、刷好评。
- 同时爬虫带来的伪流量也会给经营者带来错觉,产生错误的决策,有效避免网站被爬虫反复爬取成为每个网站都要考虑的基本问题。在基于技术层面区分出爬虫用户后,需要将此类用户进行有效的屏蔽,这就是黑名单的典型应用。
- ps:不是说爬虫一定做摧毁性的工作,有些小型网站需要爬虫为其带来一些流量。
- 白名单
- 对于安全性更高的应用访问,仅仅靠黑名单是不能解决安全问题的,此时需要设定可访问的用户群体,依赖白名单做更为苛刻的访问验证。
- 黑名单
- redis 应用于基于黑名单与白名单设定的服务控制
6、sorted_set
1、sorted_set 类型
- 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
- 需要的存储结构:新的存储模型,可以保存可排序的数据
- sorted_set类型:在set的存储结构基础上添加可排序字段
- 集合的成员是唯一的,但是评分可以是重复了 。
2、Sorted_set 的数据结构
Sorted_set(zset)是Redis提供的一个非常特别的数据结构:
- 一方面它等价于Java的数据结构Map<String, Double>,可以给每一个元素value赋予一个权重score;
- 另一方面它又类似于TreeSet,内部的元素会按照权重score进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。
zset底层使用了两个数据结构:
- hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值。
- 跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表。
跳跃表(跳表)
1、简介
有序集合在生活中比较常见,例如根据成绩对学生排名,根据得分对玩家排名等。
对于有序集合的底层实现,可以用:
- 数组
- 数组不便元素的插入、删除
- 平衡树
- 平衡树或红黑树虽然效率高但结构复杂
- 链表
- 链表查询需要遍历所有效率低。
Redis采用的是跳跃表。跳跃表效率堪比红黑树,实现远比红黑树简单。
2、实例
对比有序链表和跳跃表,从链表中查询出51
(1) 有序链表
要查找值为51的元素,需要从第一个元素开始依次查找、比较才能找到。共需要6次比较。
(2) 跳跃表
- 从第2层开始,1节点比51节点小,向后比较。
- 21节点比51节点小,继续向后比较,后面就是NULL了,所以从21节点向下到第1层
- 在第1层,41节点比51节点小,继续向后,61节点比51节点大,所以从41向下
- 在第0层,51节点为要查找的节点,节点被找到,共查找4次。
从此可以看出跳跃表比有序链表效率要高
3、sorted_set 类型数据的基本操作
添加数据
1
zadd key score1 member1 [score2 member2]
获取全部数据
1
2
3
4
5
6# 从start到stop顺序获取key当中的数据
# WITHSCORES 获取key的member的同时获取member的scores
zrange key start stop [WITHSCORES]
# 从start到stop逆序获取key当中的数据
zrevrange key start stop [WITHSCORES]删除数据
1
zrem key member [member ...]
按条件获取数据
1
2
3
4
5# min与max用于限定搜索查询的条件
# LIMIT 与mysql的LIMIT用法一样,用来限制数据的数量
zrangebyscore key min max [WITHSCORES] [LIMIT]
zrevrangebyscore key max min [WITHSCORES]条件删除数据
1
2
3zremrangebyrank key start stop
zremrangebyscore key min max获取集合数据总量
1
2
3zcard key
zcount key min max注意:
- min与max用于限定搜索查询的条件
- start与stop用于限定查询范围,作用于索引,表示开始和结束索引
- offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量
集合交、并操作
1
2
3
4
5
6
7# 求最大最小值
# zinterstore sss 3 s1 s2 s3 agggregate max/min
zinterstore destination numkeys key [key ...]
# 求公共部分的和
# zinterstore ss 3 s1 s2 s3
zunionstore destination numkeys key [key ...]获取数据对应的索引(排名)
1
2
3zrank key member
zrevrank key memberscore值获取与修改
1
2
3zscore key member
zincrby key increment member获取当前系统时间
1
2
3# score 秒
# member 毫秒
time
4、sorted_set 类型数据操作的注意事项
- score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
- score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
- sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反复覆盖,保留最后一次修改的结果
5、sorted_set 类型应用场景
- **Tips 13 **
- redis 应用于计数器组合排序功能对应的排名
- 票选广东十大杰出青年,各类综艺选秀海选投票
- 各类资源网站TOP10(电影,歌曲,文档,电商,游戏等)
- 聊天室活跃度统计
- 游戏好友亲密度
- redis 应用于计数器组合排序功能对应的排名
- **Tips 14 **
- redis 应用于定时任务执行顺序管理或任务过期管理
- 基础服务+增值服务类网站会设定各位会员的试用,让用户充分体验会员优势。例如观影试用VIP、游戏VIP体验、云盘下载体验VIP、数据查看体验VIP。当VIP体验到期后,如果有效管理此类信息。即便对于正式VIP用户也存在对应的管理方式。
- 网站会定期开启投票、讨论,限时进行,逾期作废。如何有效管理此类过期信息。
- redis 应用于定时任务执行顺序管理或任务过期管理
- **Tips 15 **
- redis 应用于即时任务/消息队列执行管理
- 任务/消息权重设定应用:
- 当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理。
- 任务/消息权重设定应用:
- redis 应用于即时任务/消息队列执行管理
7、数据类型实践案例
1、业务场景1
人工智能领域的语义识别与自动对话将是未来服务业机器人应答呼叫体系中的重要技术,百度自研用户评价语义识别服务,免费开放给企业试用,同时训练百度自己的模型。现对试用用户的使用行为进行限速,限制每个用户每分钟最多发起10次调用
2、解决方案
- 设计计数器,记录调用次数,用于控制业务执行次数。以用户id作为key,使用次数作为value
- 在调用前获取次数,判断是否超过限定次数
- 不超过次数的情况下,每次调用计数+1
- 业务调用失败,计数-1
- 为计数器设置生命周期为指定周期,例如1秒/分钟,自动清空周期内使用次数
3、解决方案改良
- 取消最大值的判定,利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值
- 判断是否为nil,
- 如果是,设置为Max-次数
- 如果不是,计数+1
- 业务调用失败,计数-1
- 遇到异常即+操作超过上限,视为使用达到上限
**Tips 16 **
- redis 应用于限时按次结算的服务控制
4、业务场景2
使用微信的过程中,当微信接收消息后,会默认将最近接收的消息置顶,当多个好友及关注的订阅号同时发送消息时,该排序会不停的进行交替。同时还可以将重要的会话设置为置顶。一旦用户离线后,再次打开微信时,消息该按照什么样的顺序显示?
5、业务分析
6、解决方案
- 依赖list的数据具有顺序的特征对消息进行管理,将list结构作为栈使用
- 对置顶与普通会话分别创建独立的list分别管理
- 当某个list中接收到用户消息后,将消息发送方的id从list的一侧加入list(此处设定左侧)
- 多个相同id发出的消息反复入栈会出现问题,在入栈之前无论是否具有当前id对应的消息,先删除对应id
- 推送消息时先推送置顶会话list,再推送普通会话list,推送完成的list清除所有数据
- 消息的数量,也就是微信用户对话数量采用计数器的思想另行记录,伴随list操作同步更新
**Tips 17 **
redis 应用于基于时间顺序的数据操作,而不关注具体时间
8、解决方案列表
- 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 应用于基于时间顺序的数据操作,而不关注具体时间
3、Redis 通用指令
1、key通用指令
1、key 特征
- key是一个字符串,通过key获取redis中保存的数据
- key应该设计哪些操作?
- 对于key自身状态的相关操作,例如:删除,判定存在,获取类型等
- 对于key有效性控制相关操作,例如:有效期设定,判定是否有效,有效状态的切换等
- 对于key快速查询操作,例如:按指定策略查询key
- ……
2、key 基本操作
删除指定key
1
del key
根据value选择非阻塞删除
1
unlink key
仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作。
- 惰性删除lazyfree的机制,它可以将删除键或数据库的操作放在后台线程里执行,删除对象时只是进行逻辑删除,从而尽可能地避免服务器阻塞。
获取key是否存在
1
exists key
获取key的类型
1
type key
3、key 扩展操作
1、key 扩展操作——时效性控制
为指定key设置有效期
1
2
3
4
5
6
7# 设置的是时间
expire key seconds
pexpire key milliseconds
# 设置的是时间戳
expireat key timestamp
pexpireat key milliseconds-timestamp获取key的有效时间
1
2
3
4
5
6# time to live
# 不存在返回-2
# 存在返回-1(永久)
# 存在并且设置了有效期(返回有效时间)
ttl key
pttl key切换key从时效性转换为永久性
1
persist key
2、key 扩展操作——查询模式
查询key
1
keys pattern
查询模式规则:
*
:匹配任意数量的任意符号?
:配合一个任意符号[]
:匹配一个指定符号
4、key 其他操作
为key改名
1
2
3
4
5# 如果修改的名称在redis当中存在,则会进行覆盖(将里面的内容进行覆盖)
# 解决方法:renamenx(如果存在则改名失败)
rename key newkey
renamenx key newkey对所有key排序
1
2# 只是排序,不动元数据存储的顺序
sort其他key通用操作
1
help @generic
2、数据库通用指令
1、数据库
key 的重复问题:
- key是由程序员定义的
- redis在使用过程中,伴随着操作数据量的增加,会出现大量的数据以及对应的key
- 数据不区分种类、类别混杂在一起,极易出现重复或冲突
解决方案:
- redis为每个服务提供有16个数据库,编号从0到15
- 默认使用的是第0号数据库
- 每个数据库之间的数据相互独立
- 这些数据库共用一块空间
2、db 基本操作
切换数据库
1
select index
其他操作
1
2
3
4
5
6
7
8
9
10
11quit
# PONG
# 进行数据回显,测试海外是否连通
ping
# 给redis控制台输出日志
# eg:
# 127.0.0.1:6379> echo abc
# 127.0.0.1:6379> "abc"
echo message数据移动
1
move key db
- move相当于剪切操作
- 如果原数据库没有数据,move失败
- 如果目标数据库已存在数据,move失败
- 注意:
- 进行move操作的是原数据库
- 数据移动的数据库是目标数据库
数据清除
1
2
3
4
5
6
7
8
9# 检查当前数据库有多少个key
dbsize
# 清除当前数据库的所有数据
flushdb
# 清除redis当中所有数据(最强大的一个命令,慎用)
# 执行flushall命令,也会产生dump.rdb文件,但里面是空的,无意义
flushall
3、常用服务器命令
检验连接状态
1
2#如果连接成功返回PONG,连接失败返回错误信息
PING验证密码是否正确
1
auth password
查看服务器信息
1
INFO [section]
查看配置信息
1
config get patten
修改当前配置信息
Config Set 命令可以动态地调整 Redis 服务器的配置(configuration)而无须重启,但此时配置文件中仍是修改前的配置,可搭配config rewrite命令一起使用:
1
CONFIG SET parameter value
重写配置文件
Config rewrite 命令对启动 Redis 服务器时所指定的 redis.conf 配置文件进行改写。与config set不同,set之后会将配置信息修改而无需重启服务,但此时redis.conf配置文件里记录的参数仍是set之前的值,如果将redis服务重启后会读取conf文件中的配置,这时候读到的还是set之前的配置,因此我们可以在set配置之后使用rewrite命令将当前的配置回写至配置文件内,这样就能不停机修改配置信息了,因此config set和config rewrite是配合使用的:
1
CONFIG REWRITE
重置统计信息
- 使用Config Resetstat 命令重置 INFO 命令中的某些统计数据,包括:
- Keyspace hits (键空间命中次数)
- Keyspace misses (键空间不命中次数)
- Number of commands processed (执行命令的次数)
- Number of connections received (连接服务器的次数)
- Number of expired keys (过期key的数量)
- Number of rejected connections (被拒绝的连接数量)
- Latest fork(2) time(最后执行 fork(2) 的时间)
- The aof_delayed_fsync counter(aof_delayed_fsync 计数器的值)
1
CONFIG RESETSTAT
- 使用Config Resetstat 命令重置 INFO 命令中的某些统计数据,包括:
获取当前时间
Time 命令用于返回当前服务器时间,返回一个包含两个字符串的列表: 第一个字符串是当前时间(以 UNIX 时间戳格式表示),而第二个字符串是当前这一秒钟已经逝去的微秒数。
1
time
DeBug
debug object key获取 key 的调试信息,当key不存在时返回错误信息。
debug segfault 命令执行一个非法的内存访问从而让 Redis 崩溃,仅在开发时用于 BUG 调试,执行后需要重启服务。
1
2debug object key
debug segfault查看当前Redis中所有可用命令
使用Command 命令用于返回所有的Redis命令的详细信息,以数组形式展示:
1
command
使用command count命令查看当前Redis中命令的数量:
1
command count
使用command info命令查看当前Redis中指定的命令的详细信息:
1
COMMAND INFO command-name [command-name ...]
彩蛋
Redis5之后新增的彩蛋,使用LOLWUT命令即可返回一副随机图像以及当前redis的版本信息。事实上LOLWUT没有任何作用,但它想告诉我们的是:”编程不仅仅是把一些代码放在一起创建有用的东西,也可以是无用但有趣的。“
1
LOLWUT
4、Jedis
1、Jedis简介
编程语言与redis:
- Java语言连接redis服务
- Jedis
- SpringData Redis
- Lettuce
- C 、C++ 、C# 、Erlang、Lua 、Objective-C 、Perl 、PHP 、Python 、Ruby 、Scala
- 可视化连接redis客户端
- Redis Desktop Manager
- Redis Client
- Redis Studio
2、HelloWorld(Jedis版)
1、准备工作
jar包导入
基于maven
1
2
3
4
5<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
2、客户端连接redis
连接redis
1
Jedis jedis = new Jedis("localhost", 6379);
操作redis(jedis的API与redis的命令是一样的)
1
2jedis.set("name", "itheima");
jedis.get("name");关闭redis连接
1
jedis.close();
3、Jedis简易工具类开发
1、基于连接池获取连接
- JedisPool:Jedis提供的连接池技术
- poolConfig:连接池配置对象
- host:redis服务地址
- port:redis服务端口号
1 | /** |
2、封装连接参数
jedis.properties:
1 | localhost = |
3、加载配置信息
静态代码块初始化资源:
1 | static{ |
4、加载配置信息
对外访问接口,提供jedis连接对象,连接从连接池获取:
1 | public static Jedis getJedis(){ |
4、可视化客户端
Redis Desktop Manager:
2、Redis高级(Linux)
1、基于Linux环境安装Redis
1、Redis在Linux环境下的安装
下载安装包
1
wget http://download.redis.io/releases/redis-?.?.?.tar.gz
解压
1
tar –xvf 文件名.tar.gz
编译
1
make
安装
1
make install [destdir=/目录]
2、Redis基础环境设置
创建软链接
1
ln -s 原始目录名 快速访问目录名
创建配置文件管理目录
1
2
3mkdir conf
# 或者
mkdir config创建数据文件管理目录
1
mkdir data
3、Redis服务启动
默认配置启动
1
2
3redis-server
redis-server –-port 6379
redis-server –-port 6380 ……指定配置文件启动
1
2
3
4
5redis-server redis.conf
redis-server redis-6379.conf
redis-server redis-6380.conf ……
redis-server conf/redis-6379.conf
redis-server config/redis-6380.conf ……开机自启动
注册服务:
1
vim /lib/systemd/system/redis.service
配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14[Unit]
Description=Redis
After=network.target
[Service]
Type=forking
PIDFile=/var/run/redis_6379.pid
ExecStart=/opt/app/redis6/bin/redis-server /opt/app/redis6/bin/redis.conf
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
PrivateTmp=true
[Install]
WantedBy=multi-user.target使用systemctl命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17# 重载服务
systemctl daemon-reload
# 开机自启
systemctl enable redis
# 启动
systemctl start redis
# 重启
systemctl restart redis
# 停止
systemctl stop redis
# 查看状态
systemctl status redis
# 关闭开机启动
systemctl disable redis
4、Redis客户端连接
默认连接
1
redis-cli
其中加上–raw可以防止中文乱码
1
redis-cli --raw
连接指定服务器
1
2
3redis-cli -h 127.0.0.1
redis-cli –port 6379
redis-cli -h 127.0.0.1 –port 6379
5、Redis服务端配置
基本配置
以守护进程方式启动,使用本启动方式,redis将以服务的形式存在,日志将不再打印到命令窗口中
1
daemonize yes
取消绑定ip,监听所有IP
1
2# 把这一行注释,监听所有IP
#bind 127.0.0.1开启保护模式
1
2# protected-mode yes 如果改为no,则是关闭保护模式,这种模式下不能配置系统服务,建议还是开启
protected-mode yes设定当前服务启动端口号
1
port 6***
设定当前服务文件保存位置,包含日志文件、持久化文件(后面详细讲解)等
1
dir "/自定义目录/redis/data"
设定日志文件名,便于查阅
1
logfile "6***.log"
2、Redis 持久化
1、持久化简介
1、什么是持久化
利用永久性存储介质将数据进行保存,在特定的时间将保存的数据进行恢复的工作机制称为持久化。
2、为什么要进行持久化
防止数据的意外丢失,确保数据安全性
3、持久化过程保存什么
将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程
2、RDB
1、RDB普通启动方式
1、RDB启动方式 —— save指令
1、RDB启动方式
谁,什么时间,干什么事情
命令执行:
- 谁:redis操作者(用户)
- 什么时间:即时(随时进行)
- 干什么事情:保存数据
2、RDB启动方式 —— save指令
命令
1
save
作用:手动执行一次保存操作
3、RDB启动方式 —— save指令相关配置
dbfilename dump.rdb
- 说明:**设置存储.rdb文件的路径** - 经验:通常设置成存储空间较大的目录中,==目录名称data==1
2
3
4
5
6
- 说明:**设置本地数据库文件名,默认值为 dump.rdb**
- 经验:通常设置为==dump-端口号.rdb==,方便查看
- ```sh
dirrdbcompression yes
- 说明:**设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行**(让redis使用CRC64算法来进行数据校验) - 经验:**通常默认为开启状态**,如果设置为no,可以节约读写性过程约10%时间消耗,但是==存储一定的数据损坏风险==1
2
3
4
5
6
- 说明:**设置存储至本地数据库时是否压缩数据,默认为 yes**,采用 `LZF 压缩`
- 经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
- ```sh
rdbchecksum yes
4、RDB启动方式 —— save指令工作原理
注意:save指令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用。
2、RDB启动方式 —— bgsave指令
1、RDB启动方式
数据量过大,单线程执行方式造成效率过低如何处理?
后台执行:
- 谁:redis操作者(用户)发起指令;redis服务器控制指令执行
- 什么时间:即时(发起);合理的时间(执行)
- 干什么事情:保存数据
2、RDB启动方式 —— bgsave指令
命令
1
bgsave
作用:手动启动后台保存操作,但不是立即执行
3、RDB启动方式 —— bgsave指令工作原理
- Redis会单独创建(fork)一个子进程来进行持久化;
- Redis会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
- 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
关于fork:
- Fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等) 数值都和原进程一致,但是是一个全新的进程,并作为原进程的子进程
- 在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”
- 一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
注意:
- bgsave命令是针对save阻塞问题做的优化。
- Redis内部所有涉及到RDB操作都采用bgsave的方式
- save命令可以放弃使用。
4、RDB启动方式 —— bgsave指令相关配置
dbfilename dump.rdb
dir
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes
底层使用了`bgsave`指令1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- 说明:**后台存储过程中如果出现错误现象,是否停止保存操作**
- 经验:通常**默认为开启状态**
##### 3、RDB启动方式 ——save配置
###### 1、RDB启动方式
反复执行保存指令,忘记了怎么办?不知道数据产生了多少变化,何时保存?
自动执行:
- 谁:redis服务器发起指令(基于条件)
- 什么时间:满足条件
- 干什么事情:保存数据
###### 2、RDB启动方式 ——save配置
- 配置
```sh
save second changes作用:满足限定时间范围内key的变化数量达到指定数量即进行持久化
参数
second
:监控时间范围changes
:监控key的变化量
位置:在conf文件中进行配置
范例:
1
2
3save 900 1
save 300 10
save 60 10000注意:
- 一般second与changes两个值的设置差别会比较大,要不就前小后大,要不就前大后小。具体看相关的业务。
- 两个值差别不大的话,设置没什么意义。
3、RDB启动方式 ——save配置原理
注意:
- save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
- save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系
- save配置启动后执行的是bgsave操作
4、save配置相关配置
- dbfilename dump.rdb
- dir
- rdbcompression yes
- rdbchecksum yes
2、RDB的备份
- 先通过config get dir 查询rdb文件的目录
- 将*.rdb的文件拷贝到别的地方
- rdb的恢复
- 关闭Redis
- 先把备份的文件拷贝到工作目录下 cp dump2.rdb dump.rdb
- 启动Redis, 备份数据会直接加载
- rdb的恢复
3、RDB的停止
动态停止RDB:
1 | #save后给空值,表示禁用保存策略 |
4、RDB三种启动方式对比
方式 | save指令 | bgsave指令 |
---|---|---|
读写 | 同步 | 异步 |
阻塞客户端指令 | 是 | 否 |
额外内存消耗 | 否 | 是 |
启动新进程 | 否 | 是 |
注:由于替换save配置启动RDB在底层也是调用了bgsave指令,所以这里不做展示。
5、RDB特殊启动形式
全量复制
- 在主从复制中详细讲解
服务器运行过程中重启
1
debug reload
关闭服务器时指定保存数据
1
shutdown save
默认情况下执行shutdown命令时,自动执行bgsave(如果没有开启AOF持久化功能)
6、RDB优缺点
1、RDB优点
- RDB是一个==紧凑压缩的二进制文件,存储效率较高==
- RDB内部存储的是redis在==某个时间点==的数据快照,非常适合用于数据备份,全量复制等场景
- RDB==恢复数据==的速度要比AOF==快==很多
- 应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,==用于灾难恢复==。
2、RDB缺点
- RDB方式无论是执行指令还是利用配置,==无法做到实时持久化,具有较大的可能性丢失数据==
- bgsave指令==每次运行要执行fork操作创建子进程,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑,要牺牲掉一些性能==
- 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
- Redis的==众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象==
- 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改。
关于第四缺点的相关说明:
- redis2.0的RDB文件不能恢复成redis4.0的数据
一个解决方法:(不得已的方法)
- 先将redis2.0的RDB文件恢复成redis2.0的数据;
- 在将数据存储到数据库当中;
- 最后将数据库作为数据源将数据恢复成redis4.0的数据,并生成redis4.0的RDB文件
3、AOF
1、RDB存储的弊端
- 存储数据量较大,效率较低
- 基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
- 大数据量下的IO性能较低
- 基于fork创建子进程,内存产生额外消耗
- 宕机带来的数据丢失风险
解决思路:
- 不写全数据,仅记录部分数据
- 降低区分数据是否改变的难度,改记录数据为记录操作过程
- 对所有操作均进行记录,排除丢失数据的风险
2、AOF概念
- AOF(append only file)持久化:
- 以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中命令达到恢复数据的目的。
- 与RDB相比可以简单描述为改记录数据为记录数据产生的过程
- AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式
3、AOF写数据过程
- 客户端的请求写命令会被append追加到AOF缓冲区内;
- AOF缓冲区根据AOF持久化策略[always,everysec,no]将操作sync同步到磁盘的AOF文件中;
- AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量;
- Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的;
4、AOF写数据三种策略(appendfsync)
- always(每次):
- 每次写入操作均同步到AOF文件中,==数据零误差,性能较低==,不建议使用。
- everysec(每秒):
- 每秒将缓冲区中的指令同步到AOF文件中,==数据准确性较高,性能较高==,建议使用,也是默认配置
- 在系统突然==宕机的情况下丢失1秒内的数据==
- no(系统控制):
- 由操作系统控制每次同步到AOF文件的周期,==整体过程不可控==
5、AOF功能开启
配置
1
appendonly yes|no
作用:是否开启AOF持久化功能,默认为不开启状态
配置
1
appendfsync always|everysec|no
作用:AOF写数据策略
6、AOF相关配置
配置
1
appendfilename filename
作用:
- AOF持久化文件名,默认文件名为
appendonly.aof
- **建议配置为
appendonly-端口号.aof
**,方便查看
- AOF持久化文件名,默认文件名为
配置
1
dir
作用:AOF持久化文件保存路径,与RDB持久化文件保持一致即可
7、AOF写数据遇到的问题
如果连续执行如下指令该如何处理?
8、AOF重写
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。
AOF文件重写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。
简单说就是将对同一个数据的若干个条命令执行结果转化成最终结果数据对应的指令进行记录。
9、AOF重写作用
- 降低磁盘占用量,提高磁盘利用率
- 提高持久化效率,降低持久化写时间,提高IO性能
- 降低数据恢复用时,提高数据恢复效率
10、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个元素
11、AOF重写方式
手动重写
1
bgrewriteaof
自动重写
1
2auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
12、AOF手动重写 —— bgrewriteaof指令工作原理
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指上就是把rdb 的快照,以二级制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。
1 | no-appendfsync-on-rewrite=yes |
- 如果 no-appendfsync-on-rewrite=yes,不写入aof文件只写入缓存,用户请求不会阻塞,但是在这段时间如果宕机会丢失这段时间的缓存数据。(降低数据安全性,提高性能)
- 如果 no-appendfsync-on-rewrite=no,还是会把数据往磁盘里刷,但是遇到重写操作,可能会发生阻塞。(数据安全,但是性能降低)
13、AOF自动重写方式
自动重写触发条件设置
1
2
3
4
5# 设置重写的基准值,最小文件64MB。达到这个值开始重写。
auto-aof-rewrite-min-size size
# 设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
auto-aof-rewrite-percentage percent自动重写触发比对参数( 运行指令info Persistence获取具体信息)
1
2aof_current_size
aof_base_size自动重写触发条件
列出当前redis的所有的运行属性值
1
info
AOF什么时候会自动重写?
- Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
- 重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
例如:文件达到70MB开始重写,降到50MB,下次什么时候开始重写?100MB
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小 >= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
14、AOF工作流程
15、AOF重写流程
- bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行。
- 主进程fork出子进程执行重写操作,保证主进程不会阻塞。
- 子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失。
- 子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息。
- 主进程把aof_rewrite_buf中的数据写入到新的AOF文件。
- 使用新的AOF文件覆盖旧的AOF文件,完成AOF重写。
16、系统调用write和fsync说明
AOF缓冲区同步文件策略,由参数appendfsync控制
系统调用write和fsync说明:
- write操作会触发延迟写(delayed write)机制,Linux在内核提供页缓冲区用来提高硬盘IO性能。
- write操作在写入系统缓冲区后直接返回。
- 同步硬盘操作依赖于系统调度机制,列如:缓冲区页空间写满或达到特定时间周期。
- 同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
- fsync针对单个文件操作(比如AOF文件),做强制硬盘同步,fsync将阻塞知道写入硬盘完成后返回,保证了数据持久化。
除了write、fsync、Linx还提供了sync、fdatasync操作,具体参见API说明。
17、AOF的优缺点
1、优点
- 备份机制更稳健,丢失数据概率更低。
- 可读的日志文本,通过操作AOF稳健,可以处理误操作。
2、缺点
- 比起RDB占用更多的磁盘空间。
- 恢复备份速度要慢。
- 每次读写都同步的话,有一定的性能压力。
- 存在个别Bug,造成恢复不能。
4、RDB与AOF区别
1、RDB VS AOF
持久化方式 | RDB | AOF |
---|---|---|
占用存储空间 | 小(数据级:压缩) | 大(指令级:重写) |
存储速度 | 慢 | 快 |
恢复速度 | 快 | 慢 |
数据安全性 | 会丢失数据 | 依据策略决定 |
资源消耗 | 高/重量级 | 低/轻量级 |
启动优先级 | 低 | 高 |
2、RDB与AOF的选择之惑
- 对数据非常敏感,建议使用默认的AOF持久化方案
- AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出现问题时,最多丢失0-1秒内的数据。
- 注意:由于AOF文件存储体积较大,且恢复速度较慢
- 数据呈现阶段有效性,建议使用RDB持久化方案
- 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段点数据恢复通常采用RDB方案
- 注意:利用RDB实现紧凑的数据持久化会使Redis降的很低,慎重总结:
- 综合比对
- RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
- 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
- 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
- 灾难恢复选用RDB
- 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量
官方推荐两个都启用。
- 如果对数据不敏感,可以选单独用RDB。
- 不建议单独用 AOF,因为可能会出现Bug。
- 如果只是做纯内存缓存,可以都不用。
- RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾。
- Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大
- 只做缓存:如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式。
- 同时开启两种持久化方式
- 在这种情况下,当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整。
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?
- 建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份), 快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
- 性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果使用AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。
- 代价:
- 一是带来了持续的IO
- 二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。
- 只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。
- 默认超过原大小100%大小时重写可以改到适当的数值。
5、AOF+RDB混合[推荐]
1、介绍
看了上面的RDB和AOF的介绍后,我们可以发现:
- 使用RDB持久化会有数据丢失的风险,但是恢复速度快,
- 而使用AOF持久化可以保证数据完整性,但恢复数据的时候会很慢。
于是从Redis4之后新增了混合AOF和RDB的模式:
- 先使用RDB进行快照存储,然后使用AOF持久化记录所有的写操作,
- 当重写策略满足或手动触发重写的时候,将最新的数据存储为新的RDB记录。
- 这样的话,重启服务的时候会从RDB何AOF两部分恢复数据,即保证了数据完整性,又提高了恢复的性能。
开启混合模式后:
- 每当bgrewriteaof命令之后会在AOF文件中以RDB格式写入当前最新的数据,之后的新的写操作继续以AOF的追加形式追加写命令。
- 当redis重启的时候,加载 aof 文件进行恢复数据:先加载 rdb 的部分再加载剩余的 aof部分。
2、配置
修改下面的参数即可开启AOF,RDB混合持久化:
1 | aof-use-rdb-preamble yes |
3、使用
开启混合持久化模式后,重写之后的aof文件里和rdb一样存储二进制的 快照数据,继续往redis中进行写操作,后续操作在aof中仍然是以命令的方式追加。
因此重写后aof文件由两部分组成:
- 一部分是类似rdb的二进制快照
- 另一部分是追加的命令文本:
6、持久化应用场景
Tips 1:redis用于控制数据库表主键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 应用于按次结算的服务控制
3、Redis 事务
1、事务简介
1、什么是事务
Redis执行指令过程中,多条连续执行的指令被干扰,打断,插队
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性按照添加顺序依次执行,中间不会被打断或者干扰。
Redis事务的主要作用就是串联多个命令防止别的命令插队。
一个队列中,一次性、顺序性、排他性的执行一系列命令
2、事务基本操作
1、事务的边界
redis的事务发生在 multi
与 exec
之间,能保证一系列预定义命令一次性按照添加顺序依次执行,中间不会被打断或者干扰。在执行事务当中出现错误,可以使用discard取消事务。
2、事务的基本操作
开启事务
1
multi
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
执行事务
1
exec
作用:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
事务定义过程中发现出了问题,怎么办?
取消事务
1
discard
作用:终止当前事务的定义,发生在multi之后,exec之前
3、事务的工作流程
4、事务的注意事项
1、定义事务的过程中,命令格式输入错误怎么办?
- 语法错误
- 指命令书写格式有误
- 处理结果
- 如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。
2、定义事务的过程中,命令执行出现错误怎么办?
- 运行错误
- 指命令格式正确,但是无法正确的执行。例如对list进行incr操作
- 处理结果
- 能够正确运行的命令会执行,运行错误的命令不会被执行
注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。
3、手动进行事务回滚
- 记录操作过程中被影响的数据之前的状态
- 单数据:string
- 多数据:hash、list、set、zset
- 设置指令恢复所有的被修改的项
- 单数据:直接set(注意周边属性,例如时效)
- 多数据:修改对应值或整体克隆复制
由于redis的事务没有自动进行回滚的功能,需要程序员进行手动的回滚,需要程序员自己记录事务执行前变量的值,非常的不方便。因此,redis的事务控制很少使用。
5、Redis 事务三特性
- 单独的隔离操作
- 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 没有隔离级别的概念
- 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
- 不保证原子性
- 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
3、锁
1、基于特定条件的事务执行——锁
Redis是的锁基于乐观锁的,乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制
实现事务的
对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行
1
watch key1 [key2……]
注意:不能在事务当中进行watch操作,即在mutil当中使用,会报错。
取消对所有 key 的监视
1
unwatch
使用 setnx 设置一个公共锁
1
setnx lock-key value
利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
- 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
- 对于返回设置失败的,不具有控制权,排队或等待 操作完毕通过del操作释放锁
注意:上述解决方案是一种设计概念,依赖规范保障,具有风险性
使用 expire 为锁key添加时间限定,到时不释放,放弃锁
1
2expire lock-key second
pexpire lock-key milliseconds由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
- 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
- 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
- 锁时间设定推荐:最大耗时 * 120% + 平均网络延迟 * 110%
- 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可
2、锁的应用场景
- **Tips 18 **
- redis 应用基于状态控制的批量任务执行
- 天猫双11热卖过程中,对已经售罄的货物追加补货,4个业务员都有权限进行补货。补货的操作可能是一系列的操作,牵扯到多个连续操作,如何保障不会重复操作?
- redis 应用基于状态控制的批量任务执行
- **Tips 19 **
- redis 应用基于分布式锁对应的场景控制
- 天猫双11热卖过程中,对已经售罄的货物追加补货,且补货完成。客户购买热情高涨,3秒内将所有商品购买完毕。本次补货已经将库存全部清空,如何避免最后一件商品不被多人同时购买?【超卖问题】
- redis 应用基于分布式锁对应的场景控制
4、redssion
众所周知,Redis 其实并没有对 Java 提供原生支持。作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。而 Redisson 就是用于在 Java 程序中操作 Redis 的库,它使得我们可以在程序中轻松地使用 Redis。Redisson 在 java.util
中常用接口的基础上,为我们提供了一系列具有分布式特性的工具类。
1、如何安装 Redisson
安装 Redisson 最便捷的方法是使用 Maven
1 | <dependency> |
你可以通过搜索 Maven 中央仓库 mvnrepository 来找到 Redisson 的各种版本。
2、如何编译运行 Redisson
安装 Redisson 后,只需使用 Java 编译器即可编译和运行 Redisson 代码:
1 | javac RedissonExamples.java |
3、对Redisson API的相关使用
对Redisson API的相关使用,可以参考以下博客:
- 厉害了,原来 Redisson 这么好用!
- Redisson基本用法
- Java中间件 - Redisson(上) - 简介
- Java中间件 - Redisson(中) - SpringBoot整合Redisson
4、Redis 删除策略
1、过期数据
1、Redis中的数据特征
Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态:
- XX :具有时效性的数据
- -1 :永久有效的数据
- -2 :已经过期的数据 或 被删除的数据 或 未定义的数据
过期的数据真的删除了吗?
并不是,过期数据的删除其实主要是由redis的删除策略进行控制。但一般来说,过期的数据并不是马上删除的,还是存放在redis的内存当中,只是根据redis的删除策略对过期的数据在不同情况下进行真正删除。
2、数据删除策略
redis有三种数据删除策略,分别是:
- 定时删除
- 惰性删除
- 定期删除
2、数据删除策略
官网:https://redis.io/commands/expire#expire-accuracy
1、时效性数据的存储结构
2、数据删除策略的目标
在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或内存泄露。
3、数据删除策略——定时删除
- 创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
- 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
- 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
- 总结:用处理器性能换取存储空间(拿时间换空间)
4、数据删除策略——惰性删除
数据到达过期时间,不做处理。等下次访问该数据时
- 如果未过期,返回数据
- 发现已过期,删除,返回不存在
- 优点:节约CPU性能,发现必须删除的时候才删除
- 缺点:内存压力很大,出现长期占用内存的数据
- 总结:用存储空间换取处理器性能(拿空间换时间)
5、数据删除策略——定期删除
两种方案都走极端,有没有折中方案?
- Redis启动服务器初始化时,读取配置
server.hz
的值,默认为10- 每秒钟执行server.hz次serverCron() –》 activeExpireCycle() – 》activeExpireCycle()
- *activeExpireCycle()**对每个expires[]逐一进行检测,每次执行250ms/server.hz
- 对某个expires[*]检测时,随机挑选W个key检测
- 如果key超时,删除key
- 如果一轮中删除的key的数量 > W * 25%,循环该过程
- 如果一轮中删除的key的数量 ≤ W * 25%,检查下一个expires[*],0-15循环
- W取值 =
ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP
属性值
- 参数current_db用于记录activeExpireCycle() 进入哪个expires[*] 执行
- 如果**activeExpireCycle()**执行时间到期,下次从current_db继续向下执行databasesCron
- 周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
- 特点1:CPU性能占用设置有峰值,检测频度可自定义设置
- 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
- 总结:周期性抽查存储空间(随机抽查,重点抽查)
6、删除策略比对
定时删除 | 节约内存,无占用 | 不分时段占用CPU资源,频度高 | 拿时间换空间 |
---|---|---|---|
惰性删除 | 内存占用严重 | 延时执行,CPU利用率高 | 拿空间换时间 |
定期删除 | 内存定期随机清理 | 每秒花费固定的CPU资源维护内存 | 随机抽查,重点抽查 |
redis会使用的两个删除策略:
- 惰性删除
- 定期删除
3、逐出算法
1、新数据进入检测
当新数据进入redis时,如果内存不足怎么办?
Redis使用内存存储数据,在执行每一个命令前,会调用
freeMemoryIfNeeded()
检测内存是否充足。如果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。
1
(error) OOM command not allowed when used memory >'maxmemory'
2、影响数据逐出的相关配置
最大可使用内存
1
maxmemory
占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。
查看当前最大可使用内存
1
2
3127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "0"默认值为0
设置当前最大可使用内存
1
2
3
4
5127.0.0.1:6379> config set maxmemory 1GB
OK
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "1073741824"也可以通过配置文件对最大可使用内存进行配置
1
2
3
4
5
6
7
8
9# 配置文件
maxmemory <bytes>
# 下面的写法均合法:
maxmemory 1024000
maxmemory 1GB
maxmemory 1G
maxmemory 1024KB
maxmemory 1024K
maxmemory 1024MBmaxmemory参数默认值为0。因32位系统支持的最大内存为4GB,所以在32位系统上Redis的默认最大内存限制为3GB;在64位系统上默认Redis最大内存即为物理机的可用内存;
每次选取待删除数据的个数
1
maxmemory-samples
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
删除策略
1
2
3
4
5
6
7
8
9
10
11
12# 配置文件
maxmemory-policy noeviction
#命令行
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "noeviction"
127.0.0.1:6379> config set maxmemory-policy allkeys-random
OK
127.0.0.1:6379> config get maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-random"达到最大内存后的,对被挑选出来的数据进行删除的策略
redis有8种删除策略:
检测易失数据(可能会过期的数据集server.db[i].expires)
volatile-lru
:挑选最近最少使用的数据淘汰(早期redis一般的默认策略)volatile-lfu
:挑选最近使用次数最少的数据淘汰volatile-ttl
:挑选将要过期的数据淘汰volatile-random
:任意选择数据淘汰
检测全库数据(所有数据集server.db[i].dict )
- allkeys-lru:挑选最近最少使用的数据淘汰
- allkeys-lfu:挑选最近使用次数最少的数据淘汰
- allkeys-random:任意选择数据淘汰
放弃数据驱逐
no-enviction(驱逐)
:禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)。当内存达到设置的最大值时,所有申请内存的操作都会报错(如set,lpush等),只读操作如get命令可以正常执行
在配置启动的文件中配置:
1 | maxmemory-policy volatile-lru |
3、LRU算法
1、介绍
LRU(Least Recently Used)表示最近最少使用,该算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
2、底层实现
LRU算法的常见实现方式为链表:新数据放在链表头部 ,链表中的数据被访问就移动到链头,链表满的时候从链表尾部移出数据。
而在Redis中使用的是近似LRU算法,为什么说是近似呢?Redis中是随机采样5个(可以修改参数maxmemory-samples
配置)key,然后从中选择访问时间最早的key进行淘汰,因此当采样key的数量与Redis库中key的数量越接近,淘汰的规则就越接近LRU算法。但官方推荐5个就足够了,最多不超过10个,越大就越消耗CPU的资源。
但在LRU算法下,如果一个热点数据最近很少访问,而非热点数据近期访问了,就会误把热点数据淘汰而留下了非热点数据,因此在Redis4.x中新增了LFU算法。
在
LRU算法
下,Redis会为每个key新增一个3字节的内存空间用于存储key的访问时间;
4、LFU算法
1、介绍
LFU(Least Frequently Used)表示最不经常使用,它是根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。
LFU算法反映了一个key的热度情况,不会因LRU算法的偶尔一次被访问被误认为是热点数据。
2、底层实现
LFU算法的常见实现方式为链表:新数据放在链表尾部 ,链表中的数据按照被访问次数降序排列,访问次数相同的按最近访问时间降序排列,链表满的时候从链表尾部移出数据。
5、数据逐出策略配置依据
使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置
5、Redis 核心配置
服务器基础配置
1、服务器端设定
设置服务器以守护进程的方式运行
1
daemonize yes|no
绑定主机地址
1
bind 127.0.0.1
没有配置bind的话,默认使用的是127.0.0.1,localhost也是可以。
但是一旦配置了bind,就必须使用配置的IP进行访问,localhost也不行了
设置服务器端口号
1
port 6379
设置数据库数量
1
databases 16
2、日志配置
设置服务器以指定日志记录级别
1
loglevel debug|verbose|notice|warning
日志记录文件名
1
logfile 端口号.log
注意:日志级别==开发期设置为verbose==即可,==生产环境中配置为notice==,简化日志输出量,降低写日志IO的频度
3、客户端配置
设置同一时间最大客户端连接数,默认无限制。当客户端连接到达上限,Redis会关闭新的连接
1
maxclients 0
客户端闲置等待最大时长,达到最大值后关闭连接。如需关闭该功能,设置为 0,单位是:秒/s
1
timeout 300
4、多服务器快捷配置
导入并加载指定配置文件信息,用于快速创建redis公共配置较多的redis实例配置文件,便于维护
1
include /path/server-端口号.conf
即:如果配置文件过多,可以将一些公共部分抽取出来作为一个公共的配置文件,在其他的配置文件当中,使用以上配置将公共配置文件进行导入
6、高级数据类型
1、Bitmaps
1、存储需求
计算机所能操作的最小单位是:Byte字节,1Byte = 8bit
而使用Bitmaps能让我们去操作bit,用于状态的判断(即:非真既假的情况)
合理地使用操作位能够有效地提高内存使用率和开发效率。
Redis提供了Bitmaps这个“数据类型”可以实现对位的操作:
- Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作。
- Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。
2、Bitmaps类型的基础操作
获取指定key对应偏移量上的bit值
1
2# a:0110 0011 --》 getbit a 6 <-> 1
getbit key offset设置指定key对应偏移量上的bit值,value只能是1或0
1
2# a:0110 0011 --》 getbit a 6 0 <-> a:0100 0011
setbit key offset value对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中
1
2
3# a:01010011 b:11011001
# bitop or c a b <-> c:11011011
bitop op destKey key1 [key2...]and
:交or
:并not
:非xor
:异或
统计指定key中1的数量
1
2# bitcount c <-> 6
bitcount key [start end]
3、Bitmaps与set对比
假设网站有1亿用户, 每天独立访问的用户有5千万, 如果每天用集合类型和Bitmaps分别存储活跃用户可以得到表
set和Bitmaps存储一天活跃用户对比:
数据类型 | 每个用户id占用空间 | 需要存储的用户量 | 全部内存量 |
---|---|---|---|
集合类型 | 64位 | 50000000 | 64位*50000000 = 400MB |
Bitmap | 1位 | 100000000 | 1位*100000000 = 12.5MB |
很明显, 这种情况下使用Bitmaps能节省很多的内存空间, 尤其是随着时间推移节省的内存还是非常可观的
set和Bitmaps存储独立用户空间对比:
数据类型 | 一天 | 一个月 | 一年 |
---|---|---|---|
集合类型 | 400MB | 12GB | 144GB |
Bitmaps | 12.5MB | 375MB | 4.5GB |
但Bitmaps并不是万金油, 假如该网站每天的独立访问用户很少, 例如只有10万(大量的僵尸用户) , 那么两者的对比如下表所示, 很显然, 这时候使用Bitmaps就不太合适了, 因为基本上大部分位都是0。
set和Bitmaps存储一天活跃用户对比(独立用户比较少)
数据类型 | 每个userid占用空间 | 需要存储的用户量 | 全部内存量 |
---|---|---|---|
集合类型 | 64位 | 100000 | 64位*100000 = 800KB |
Bitmaps | 1位 | 100000000 | 1位*100000000 = 12.5MB |
4、Bitmaps的应用场景
- **Tips 21 **
- redis 应用于信息状态统计
- 电影网站
- 统计每天某一部电影是否被点播
- 统计每天有多少部电影被点播
- 统计每周/月/年有多少部电影被点播
- 统计年度哪部电影没有被点播
- 电影网站
- redis 应用于信息状态统计
2、HyperLogLog
1、基数
- 基数是数据集去重后元素个数
- HyperLogLog 是用来做基数统计的,运用了LogLog的算法
- HyperLogLog 的优点是:在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。
- 在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
- 但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
- 示例:
- {1, 3, 5, 7, 5, 7, 8}
- 基数集: {1, 3, 5 ,7, 8}
- 基数:5
- {1, 1, 1, 1, 1, 7, 1}
- 基数集: {1,7}
- 基数:2
- {1, 3, 5, 7, 5, 7, 8}
2、LogLog算法(跳过)
3、HyperLogLog类型的基本操作
添加数据
1
pfadd key element [element ...]
统计数据
1
pfcount key [key ...]
合并数据
1
pfmerge destkey sourcekey [sourcekey...]
4、HyperLogLog的相关说明
- 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
- 核心是基数估算算法,最终数值存在一定误差
- 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
- 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
- pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
- Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少
5、HyperLogLog的应用场景
- **Tips 22 **
- redis 应用于独立信息统计
- 统计独立UV
- 原始方案:set
- 存储每个用户的id(字符串)
- 改进方案:Bitmaps
- 存储每个用户状态(bit)
- 全新的方案:Hyperloglog
- 原始方案:set
- 统计独立UV
- redis 应用于独立信息统计
3、GEO
1、GEO简介
- Redis 3.2 中增加了对GEO类型的支持。
- GEO,Geographic,地理信息的缩写。
- 该类型,就是元素的2维坐标,在地图上就是经纬度。
- redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作
GEO用于计算两地经纬度的距离
2、GEO类型的基本操作
添加坐标点
1
2# geoadd + 容器key + 经度 + 维度 + 名称
geoadd key longitude latitude member [longitude latitude member ...]获取坐标点
1
geopos key member [member ...]
它会做一些经纬度的度分秒的转换
计算坐标点距离,单位:米/m
1
2# unit为单位,默认为米/m,可以设置成千米/km
geodist key member1 member2 [unit]注意:geo计算的是水平位置的距离
根据坐标求范围内的数据(不定点,如移动当中的位置)
1
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
- [withcoord]:结果跟随坐标
- [withdist]:结果跟随距离
- [withhash]:结果跟坐标的hash值
- [count count]:结果取一定的范围,从count到count
另外,还能再加上两个参数:
- asc/desc:按照距离进行升序/降序
根据点求范围内的数据(定点)
1
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
一些操作与上面一样
获取指定点对应的坐标hash值
1
geohash key member [member ...]
3、GEO的应用场景
- **Tips 23 **
- redis 应用于地理位置计算
- 火热的生活服务类软件(当中显示的距离)
- 微信 / 陌陌
- 美团 / 饿了么
- 携程 / 马蜂窝
- 高德 / 百度
- 火热的生活服务类软件(当中显示的距离)
- redis 应用于地理位置计算
7、主从复制
1、主从复制简介
1、互联网“三高”架构
- 高并发
- 高性能
- 高可用
对于高可用:
假设在一年当中,服务器的宕机有:
- 在一月,服务器宕机4小时27分15秒
- 在四月,服务器宕机11分36秒
- 在十月,服务器宕机2分16秒
那么在这一年当中服务器的可用性为:
2、“Redis”是否高可用
单机redis的风险与问题:
- 问题1:机器故障
- 现象:硬盘故障、系统崩溃
- 本质:数据丢失,很可能对业务造成灾难性打击
- 结论:基本上会放弃使用redis.
- 问题2:容量瓶颈
- 现象:内存不足,从16G升级到64G,从64G升级到128G,无限升级内存
- 本质:穷,硬件条件跟不上
- 结论:放弃使用redis
- 结论:
- 为了避免单点Redis服务器故障,准备多台服务器,互相连通。
- 将数据复制多个副本保存在不同的服务器上,连接在一起,并保证数据是同步的。
- 即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用,同时实现数据冗余备份。
3、多台服务器连接方案
- 提供数据方:master
- 主服务器,主节点,主库
- 主客户端
- 接收数据方:slave
- 从服务器,从节点,从库
- 从客户端
- 需要解决的问题: 数据同步
- 核心工作: master的数据复制到slave中
4、主从复制
主从复制即将master中的数据即时、有效的复制到slave中
特征:一个master可以拥有多个slave,一个slave只对应一个master
职责:(读写分离)
- master:
- 写数据
- 执行写操作时,将出现变化的数据自动同步到slave
- 读数据(可忽略)
- slave:
- 读数据
- 写数据(禁止)
5、高可用集群
1、在一个slave结点宕机之后,并不影响redis可用性
2、在一个master结点宕机之后,可以有一个slave升级为master继续使用,并不影响redis可用性
3、在一个master结点压力过大,可以将一部分工作交给一个slave结点去做,让这个slave作为master去管理它的从结点(master与slave只是相对来说的)
4、如果一个master来接收外界数据不太安全的话,也可以将多个master做成集群
6、主从复制的作用
- 读写分离:master写、slave读,提高服务器的读写负载能力
- 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
- 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
- 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
- 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
2、主从复制工作流程
1、总述
- 主从复制过程大体可以分为3个阶段
- 建立连接阶段(即准备阶段)
- 数据同步阶段
- 命令传播阶段
2、阶段一:建立连接阶段
- 建立slave到master的连接,使master能够识别slave,并保存slave端口号
1、建立连接阶段工作流程
- 步骤1:设置master的地址和端口,保存master信息
- 步骤2:建立socket连接
- 步骤3:发送ping命令(定时器任务)
- 步骤4:身份验证
- 步骤5:发送slave端口信息
- 至此,主从连接成功!
状态:
- slave:
- 保存master的地址与端口
- master:
- 保存slave的端口
- 总体:
- 之间创建了连接的socket
2、主从连接(slave连接master)
方式一:客户端发送命令
1
slaveof <masterip> <masterport>
方式二:启动服务器参数
1
redis-server -slaveof <masterip> <masterport>
方式三:服务器配置(常用)
1
slaveof <masterip> <masterport>
slave系统信息:
- master_link_down_since_seconds:主从断开的持续时间(以秒为单位) .
- masterhost
- masterport
master系统信息:
- slave_listening_port(多个)
3、主从断开连接
客户端发送命令
1
slaveof no one
说明: slave断开连接后,不会删除已有数据,只是不再接受master发送的数据
4、授权访问
master客户端发送命令设置密码
1
requirepass <password>
master配置文件设置密码
1
2
3config set requirepass <password>
config get requirepassslave客户端发送命令设置密码
1
auth <password>
slave配置文件设置密码
1
masterauth <password>
slave启动服务器设置密码
1
redis-server –a <password>
由于redis在主从进行数据交流的是在内网上进行的,所以一般不设置密码也没有关系。
2、阶段二:数据同步阶段工作流程
- 在slave初次连接master后,复制master中的所有数据到slave
- 将slave的数据库状态更新成master当前的数据库状态
1、数据同步阶段工作流程
- 步骤1:请求同步数据
- 步骤2:创建
RDB同步
数据(全量复制) - 步骤3:恢复RDB同步数据
- 步骤4:请求
部分同步
数据(部分复制)(AOF同步) - 步骤5:恢复部分同步数据
- 至此,数据同步工作完成!
状态:
- slave: 具有master端全部数据,包含RDB过程接收的数据
- master: 保存slave当前数据同步的位置
- 总体: 完成了数据克隆
2、数据同步阶段master说明
如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
- 可以选择在半夜的3、4点钟进行数据同步
- 注意:这是全量复制的时候,也就是你新增从属服务器要同步的时候,一般之后的实时同步都是部分复制 量很少的,速度很快。
复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。
1
repl-backlog-size 1mb
通过设置复制缓冲区大小就能解决这个问题
master单机内存占用主机内存的比例不应过大,建议使用
50%-70%
的内存,留下30%-50%的内存用于执行bgsave命令和创建复制缓冲区
3、数据同步阶段slave说明
为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
1
2
3
4
5# 开启只读服务应该是这个指令:
slave-read-only yes
# 当主服务器挂掉时是否提供过期数据
slave-serve-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位字符组成,是一个随机的十六进制字符
- 例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
- 作用:运行id被用于在服务器间进行传输,识别身份
- 如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别
- 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发送给slave,slave保存此ID,通过
info Server
命令,可以查看节点的runid
3、复制缓冲区
概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
由来:每台服务器启动时,如果开启有AOF或被连接成为master节点,即创建复制缓冲区
作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select)
数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中
组成:
- 偏移量
- 字节值
工作原理
- 通过offset区分不同的slave当前数据传播的差异
- master记录已发送的信息对应的offset
- slave记录已接收的信息对应的offset
4、主从服务器复制偏移量(offset)
- 概念:一个数字,描述复制缓冲区中的指令字节位置
- 分类:
- master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
- slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
- 之后通过master与slave之间的offset对比,就知道当前的slave有多少数据没有复制过去,相等表示当前slave已经有master的全部数据
- 数据来源: master端:发送一次记录一次 slave端:接收一次记录一次
- 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用
4、数据同步+命令传播阶段工作流程
5、心跳机制
- 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
- master心跳:
- 指令:
PING
- 周期:由
repl-ping-slave-period
决定,默认10秒(由于一个master会有多个slave,所以周期相对于slave来说会比较长) - 作用:判断slave是否在线
- 查询:
INFO replication
获取slave最后一次连接时间间隔,lag项维持在0或1视为正常- 关于lag:如果在网络上的话,较为稳定,出现0的次数会比较少
- 指令:
- slave心跳任务
- 指令:
REPLCONF ACK {offset}
- 周期:1秒(由于一个slave会对应一个master,所以周期会比较短)
- 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
- 作用2:判断master是否在线
- 指令:
6、心跳阶段注意事项
当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作
1
2
3min-slaves-to-write 2
min-slaves-max-lag 10slave数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
slave数量由slave发送REPLCONF ACK命令做确认
slave延迟由slave发送REPLCONF ACK命令做确认
7、主从复制工作流程(完整)
3、主从复制常见问题
1、频繁的全量复制(1)
伴随着系统的运行,master的数据量会越来越大,一旦master重启,runid将发生变化,会导致全部slave的全量复制操作
内部优化调整方案:
- master内部创建master_replid变量,使用runid相同的策略生成,长度
41
位,并发送给所有slave - 在master关闭时执行命令
shutdown save
,进行RDB持久化,将runid与offset保存到RDB文件中- repl-id repl-offset
- 通过
redis-check-rdb
命令可以查看该信息
- master重启后加载RDB文件,恢复数据
- 重启后,将RDB文件中保存的repl-id与repl-offset加载到内存中
- master_repl_id = repl
- master_repl_offset = repl-offset
- 通过info命令可以查看该信息
- 重启后,将RDB文件中保存的repl-id与repl-offset加载到内存中
- 作用:本机保存上次runid,重启后恢复该值,使所有slave认为还是之前的master
2、频繁的全量复制(2)
问题现象:网络环境不佳,出现网络中断,slave不提供服务
问题原因:复制缓冲区过小,断网后slave的offset越界,触发全量复制
最终结果:slave反复进行全量复制,对外不提供服务
解决方案:修改复制缓冲区大小
1
repl-backlog-size
建议设置如下:
- 测算从master到slave的重连平均时长second
- 获取master平均每秒产生写命令数据总量write_size_per_second
最优复制缓冲区空间 = 2 * second * write_size_per_second
3、频繁的网络中断(1)
问题现象:master的CPU占用过高 或 slave频繁断开连接
问题原因:
- slave每1秒发送REPLCONF ACK命令到master
- 当slave接到了慢查询时(keys * ,hgetall等),会大量占用CPU性能
- master每1秒调用复制定时函数replicationCron(),比对slave发现长时间没有进行响应
最终结果:master各种资源(输出缓冲区、带宽、连接等)被严重占用
解决方案:通过设置合理的超时时间,确认是否释放slave
1
repl-timeout
该参数定义了超时时间的阈值(默认60秒),超过该值,释放slave
4、频繁的网络中断(2)
问题现象:slave与master连接断开
问题原因:
- master发送ping指令频度较低
- master设定超时时间较短
- ping指令在网络中存在丢包
解决方案:提高ping指令发送的频度
1
repl-ping-slave-period
超时时间repl-time的时间至少是ping指令频度的5到10倍,否则slave很容易判定超时
5、数据不一致
问题现象:多个slave获取相同数据不同步
问题原因:网络信息不同步,数据发送有延迟
解决方案
优化主从间的网络环境,通常放置在同一个机房部署
- 如使用阿里云等云服务器时要注意此现象,因为对于云服务器来说,在同一城市服务器不一定同一个机房
监控主从节点延迟(通过offset)判断,如果slave延迟过大,暂时屏蔽程序对该slave的数据访问
1
slave-serve-stale-data yes|no
开启后仅响应info、slaveof等少数命令(慎用,除非对数据一致性要求很高)
注意:
- 开启后并不是说关掉这台服务器,而是关掉对这台服务器数据的访问,一般在==调试==当中使用
- 另外,数据不同步在分布式的数据层级上面是属于非常正常的一件事,主要看你的业务需求对该数据的一致性是否有严格的要求。
- 如果对某些数据的一致性特别严格的话,建议把这一部分数据单独存放,找一台机器又读又写,数据量不是特别大。
- 对那些数据特别不是特别高的分开放。
- 这样可以在一定程度上解决问题
8、哨兵模式
1、哨兵简介
1、主机“宕机”
当主机宕机了怎么办?
- 关闭master和所有slave
- 找一个slave作为master
- 修改其他slave的配置,连接新的主
- 启动新的master与slave
- 全量复制 * N + 部分复制 * N
相关问题:
- 关闭期间的数据服务谁来承接?
- 找一个主?怎么找法?
- 修改配置后,原始的主恢复了怎么办?
问题解决:哨兵机制
2、哨兵
哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行==监控==,当出现故障时通过投票机制==选择==新的master并将所有slave连接到新的master。
哨兵(sentinel) 也是一个redis服务器集群,只是配置文件的与平常的redis服务器有一点不同
3、哨兵的作用
- 监控
- 不断的检查master和slave是否正常运行。
- master存活检测、master与slave运行情况检测
- 通知(提醒)
- 当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
- 自动故障转移
- 断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
- 注意:
- 哨兵也是一台redis服务器,只是不提供数据服务
- 通常哨兵配置数量为==单数==
- 防止哨兵在竞选中打平的这种尴尬局面
2、启用哨兵模式
1、配置哨兵
配置一拖二的主从结构——1个master对应2个slave
配置三个哨兵(配置相同,端口不同) 参看sentinel.conf
启动哨兵
1
redis-sentinel sentinel-端口号.conf
启动哨兵的时候,哨兵相应的配置文件也会改变。
添加进去哨兵的相关内容:其他哨兵的主机名、IP、端口、runid等等
2、配置哨兵
查看redis原配置的一个好命令:如果你不想看配置文件当中的注释,使用如下命令:
1 | cat sentinel.conf | grep -v "#" | grep -v "^$" |
配置项 | 范例 | 说明 |
---|---|---|
sentinel auth-pass <自定义服务器名称> <password> |
sentinel auth-pass mymaster itcast |
连接服务器口令 |
sentinel monitor <自定义服务名称><主机地址><端口><主从服务器总量> |
sentinel monitor mymaster 192.168.194.131 6381 1 | 设置哨兵监听的主服务器信息,最后的参数决定了最终参与选举的服务器数量(-1) |
sentinel down-after-milliseconds<自定义服务名称><毫秒数(整数)> |
sentinel down-after-milliseconds mymaster 3000 | 指定哨兵在监控Redis服务时,判定服务器挂掉的时间周期,默认30秒(30000),也是主从切换的启动条件之一 |
sentinel parallel-syncs<服务名称><服务器数(整数)> |
sentinel parallel-syncs mymaster 1 | 指定每次同时进行主从的slave数量,数值越大,要求网络资源越高,要求越小,同步时间越长 |
sentinel failover-timeout<服务名称><毫秒数(整数)> |
sentinel failover-timeout mymaster 9000 | 指定出现故障后,故障切换的最大超时时间,超过该值,认定切换失败,默认3分钟。即在进行同步的时候,如果同步时间过慢也算失败 |
sentinel notification-script<服务名称><脚本路径> |
服务器无法正常联通时,设定的执行脚本,通常调试使用。 |
注意:
- 关于
<自定义服务名称>
,上面设定的是mymaster,设定之后在配置文件当中的各项配置中就不要修改 - 关于sentinel monitor 的最后一个参数
<主从服务器总量>
,上面设定这个值为x(这里的x = 1)- x 的意义:如果有x个哨兵认为当前master宕机了,那么就认定该master已经宕机了——这是判断master是否宕机的一个标准
- 这个值通常设定为
哨兵的数量 的一半+1
——这里也是为什么设定哨兵的数量最好是单数(防止出现打平的局面)
3、哨兵工作原理
1、主从切换
- 哨兵在进行主从切换过程中经历三个阶段:
- 监控
- 通知
- 故障转移
2、阶段一:监控阶段
- 用于同步各个节点的状态信息
- 获取各个sentinel的状态(是否在线)
- 获取master的状态
- master属性
- runid
- role:master
- 各个slave的详细信息
- master属性
- 获取所有slave的状态(根据master中的slave信息)
- slave属性
- runid
- role:slave
- master_host、master_port
- offset
- ……
- slave属性
3、阶段二:通知阶段
4、阶段三:故障转移阶段
1、sentinel1发现master宕机
- 先将master的状态修改为flags:SRI_S_DOWN——主观下线
- 将这个信息在sentinel集群当中传播
- sentinel1报出sdown,并通知其他哨兵,发送指令
sentinel is-master-down-by-address-port
给其余哨兵节点; - 哨兵的选举机制是以各哨兵节点接收到发送sentinel is-master-down-by-address-port指令的哨兵id 投票,票数最高的哨兵id会成为本次故障转移工作的哨兵Leader;
- sentinel1报出sdown,并通知其他哨兵,发送指令
- 其他的sentinel前往围观,查看master是不是真的宕机
- 当有一半以上的sentinel认定master已经宕机,则将master的状态修改为flags:SRI_O_DOWN——客观下线
2、选举一个sentinel去解决当前master宕机问题
每竞选轮回一次,竞选次数加1
3、服务器列表中挑选备选master
- 在线的
- 响应快的
- 与原master断开时间短的
- 优先原则
- 优先级,优先级越高胜出
- offset,offset越大胜出
- runid,runid越小胜出
- 发送指令( sentinel )
- 向新的master发送
slaveof no one
- 向其他slave发送
slaveof 新masterIP端口
- 向新的master发送
4、故障转移阶段总结
- 监控
- 同步信息
- 通知
- 保持联通
- 故障转移
- 发现问题
- 竞选负责人
- 优选新master
- 新master上任,其他slave切换master,原master作为slave故障回复后连接
4、日志查看
哨兵1(sentinel1)日志:
master6379下线之后:
master6379重新上线:
9、集群
1、集群简介
1、现状问题
业务发展过程中遇到的峰值瓶颈:
- redis提供的服务OPS可以达到10万/秒,当前业务OPS已经达到10万/秒
- 内存单机容量达到256G,当前业务需求内存容量1T
使用集群的方式可以快速解决上述问题
2、集群架构
集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其====。
3、集群作用
- 分散单台服务器的访问压力,实现负载均衡
- 分散单台服务器的存储压力,实现可扩展性
- 降低单台服务器宕机带来的业务灾难
4、Redis 集群的限制
- db库:单机的Redis默认有16个db数据库,但在集群模式下只有一个db0;
- 复制结构:上面的复制结构有树状结构,但在集群模式下只允许单层复制结构;
- 事务/lua脚本:仅允许操作的key在同一个节点上才可以在集群下使用事务或lua脚本;(使用Hash Tag可以解决)
- 多键的Redis事务是不被支持的。
- lua脚本不被支持
- key的批量操作:如mget、mset操作,只有当操作的key都在同一个节点上才可以执行;(使用Hash Tag可以解决)
- 多键操作是不被支持的
- keys/flushall:只会在该节点之上进行操作,不会对集群的其他节点进行操作;
- 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。
Hash Tag
上面介绍集群限制的时候,由于key被分布在不同的节点之上,因此无法跨节点做事务或lua脚本操作,但我们可以使用hash tag方式解决。
hash tag:当key包含{}的时候,不会对整个key做hash,只会对{}包含的部分做hash然后分配槽slot;因此我们可以让不同的key在同一个槽内,这样就可以解决key的批量操作和事务及lua脚本的限制了;
但由于hash tag会将不同的key分配在相同的slot中,如果使用不当,会造成数据分布不均的情况,需要注意。
2、Redis集群结构设计
1、数据存储设计
通过算法设计,计算出key应该保存的位置
将所有的存储空间计划切割成
16384
份,每台主机保存一部分- ==每份代表的是一个存储空间==,不是一个key的保存空间
将key按照计算出的结果放到对应的存储空间
增强可扩展性
原本redis的数据存储:
经过Redis集群结构的数据存储:
2、集群内部通讯设计(迭代查询)
- 各个数据库相互通信,保存各个库中槽的编号数据
- 一次命中,直接返回
- 一次未命中,告知具体位置
3、原理
1、数据分区规则
衡量数据分区方法的标准有两个重要因素:
- 是否均匀分区;
- 增减节点对数据分布的影响;
由于哈希算法具有随机性,可以保证数据均匀分布,因此Redis集群采用哈希分区的方式对数据进行分区,哈希分区就是对数据的特征值进行哈希,然后根据哈希值决定数据放在哪里。
2、常见的哈希分区
1、哈希取余:
计算key的hash值,对节点数量做取余计算,根据结果将数据映射到对应节点;但当节点增减时,系统中所有数据都需要重新计算映射关系,引发大量数据迁移;
2、一致性哈希
将hash值区间抽象为一个环形,节点均匀分布在该环形之上,然后根据数据的key计算hash值,在该hash值所在的圆环上的位置延顺时针行走找到的第一个节点的位置,该数据就放在该节点之上。相比于哈希取余,一致性哈希分区将增减节点的影响限制为相邻节点。
例:在AB节点中新增一个节点E时,因为B上的数据的key的hash值在A和B所在的hash区间之内,因此只有C上的一部分数据会迁移到B节点之上;同理如果从BCD中移除C节点,由于C上的数据的key的hash值在B和C所在的hash区间之内,因此C上的数据顺时针找到的第一个节点就是D节点,因此C的数据会全部迁移到D节点之上。 但当节点数量较少的时候,增删节点对单个节点的影响较大,会造成数据分布不均,如移除C节点时,C的数据会全部迁移到D节点上,此时D节点拥有的数据由原来的1/4变成现在的1/2,相比于节点A和B来说负载更高。
3、带虚拟节点的一致性哈希 (Redis集群)
Redis采用的方案,在一致性哈希基础之上,引入虚拟节点的概念,虚拟节点被称为槽(slot)。Redis集群中,槽的数量为16384。
槽介于数据和节点之间,将节点划分为一定数量的槽,每个槽包含哈希值一定范围内的数据。由原来的hash–>node 变为 hash–>slot–>node。
当增删节点时,该节点所有拥有的槽会被重新分配给其他节点,可以避免在一致性哈希分区中由于某个节点的增删造成数据的严重分布不均。
3、通信机制
在上面的哨兵方案中,节点被分为数据节点和哨兵节点,哨兵节点也是redis服务,但只作为选举监控使用,只有数据节点会存储数据。而在Redis集群中,所有节点都是数据节点,也都参与集群的状态维护。
在Redis集群中,数据节点提供两个TCP端口,在配置防火墙时需要同时开启下面两类端口:
- 普通端口:即客户端访问端口,如默认的6379;
- 集群端口:普通端口号加10000,如6379的集群端口为16379,用于集群节点之间的通讯;
集群的节点之间通讯采用Gossip协议
,节点根据固定频率(每秒10次)定时任务进行判断,当集群状态发生变化,如增删节点、槽状态变更时,会通过节点间通讯同步集群状态,使集群收敛。
集群间发送的Gossip消息有下面五种消息类型:
MEET
:在节点握手阶段,对新加入的节点发送meet消息,请求新节点加入当前集群,新节点收到消息会回复PONG消息;PING
:节点之间互相发送ping消息,收到消息的会回复pong消息。ping消息内容包含本节点和其他节点的状态信息,以此达到状态同步;PONG
:pong消息包含自身的状态数据,在接收到ping或meet消息时会回复pong消息,也会主动向集群广播pong消息;FAIL
:当一个主节点判断另一个主节点进入fail状态时,会向集群广播这个消息,接收到的节点会保存该消息并对该fail节点做状态判断;PUBLISH
:当节点收到publish命令时,会先执行命令,然后向集群广播publish消息,接收到消息的节点也会执行publish命令;
4、访问集群
上面介绍了槽的概念,在每个节点存储着不同范围的槽,数据也分布在不同的节点之上,我们在访问集群的时候,如何知道数据在哪个节点或者在哪个槽之上呢? 下面介绍两种访问连接:
1、Dummy客户端
使用redis-cli客户端连接集群被称为dummy客户端,只会在执行命令之后通过MOVED错误重定向找到对应的节点,如图,我们可以使用redis-cli -c命令进入集群命令行,当查看或设置key的时候会根据上面提到的CRC16算法计算key的hash值找到对应的槽slot,然后重定向到对应的节点之后才能操作,我们也使用cluster keyslot命令查看key所在的槽solt:
1 | # 使用-c进入集群命令行模式 |
2、Smart客户端
相比于dummy客户端,smart客户端在初始化连接集群时就缓存了槽slot和节点node的对应关系, 也就是在连接任意节点后执行cluster slots,我们使用的JedisCluster就是smart客户端:
1 | cluster slots |
集群代理:Redis6版本中新增的特性,客户端不需要知道集群中的具体节点个数和主从身份,可以直接通过代理访问集群。与Redis在不同的分支,将在后面的文章中具体介绍。
3、cluster集群结构搭建
1、搭建方式
- 原生安装(单条命令)
- 配置服务器(3主3从)
- 建立通信(Meet)
- 分槽(Slot)
- 搭建主从(master-slave)
- 工具安装(批处理)
2、Cluster配置
配置一个配置文件,借助这个配置文件去配置其他类型配置文件的命令:
1 | sed "s/6379/6380/g" redis-6379.conf > redis-7380.conf |
将redis-6379.conf配置文件当中的6379修改为6380之后生成一个redis-6380.conf的配置文件
添加节点
1
cluster-enabled yes|no
cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容
1
cluster-config-file <filename>
这里建议修改cluster的配置文件的名字,因为如果在同一个目录下有多个cluster结点的话,可能会因为相关的配置文件的同名而导致一定的问题。
建议改名:nodes-端口.conf节点服务响应超时时间,用于判定该节点是否下线或切换为从节点
1
cluster-node-timeout <milliseconds>
与后面当master宕机之后,slave日志的展示有关
对于线上,30s或60s都行,看具体的业务
master连接的slave最小数量
1
cluster-migration-barrier <count>
3、启动redis服务
1、启动master结点
1 | redis-server /redis-4.0.0/conf/redis-6379.conf |
按照上面的方法依次启动另外的五个结点(三主三从)
2、查看当前redis服务
1 | ps -ef | grep redis |
3、将当前的六个结点相连
把启动的一个个redis结点进行连接
相关命令:下载的redis包下的src目录下的redis-trib.rb
要想启动redis-trib.rb,需要两个工具:
- ruby
- rubygem
需要将它们先进行下载。
注意:
- redis的版本不同,对应下载的ruby也会有所不同
- 如果ruby和gem的版本不够,它会提醒你升级到对应的版本
在Redis 6当中,redis-cli –cluster代替了之前的redis-trib.rb,我们无需安装ruby环境即可直接使用它附带的所有功能:创建集群、增删节点、槽迁移、完整性检查、数据重平衡等等。
redis-trib.rb命令的执行:
1 | # 如果直接执行redis-trib.rb它是识别不出来的,而且只有在当前目录下有效,需要将它用./redis-trib.rb方式执行 |
当中的1表示master与slave之间的数量
- eg:
- 1:1个master有1个slave
- 2:1个master有2个slave
对应的,后面的结点IP和端口需要与前面的数字相对应
- eg:
- 前面1,后面6:3对——1个muster1个slave
- 前面2,后面6:2对——1个muster2个slave
在选择yes之前,也就是生成相关配置文件之前:
选择yes之后生成相关配置文件:
4、此时redis服务端的日志
master服务端:
slave服务端:
4、使用cluster设置与获取数据
存取数据:
若是按照之前的方法启动:
redis-cli
1
2
3
4
5
6
7
8
9
- 则你在进行set/get等操作的时候会报错,redis会告诉你当前数据应该设置在哪一个槽当中,很麻烦
![image-20210907142834922](redis高级/image-20210907142834922.png)
- 所以应当商量说过的另一个启动方式:
```sh
redis-cli -c
再进行set/get操作,发现成功,redis会返回该值已经重定向到对应的槽当中,并且返回OK
5、在Cluster集群下出现相关问题的解决方法
在Cluster集群下测试出现的相关问题:
- 当slave结点宕机会出现什么问题?
- 当master结点宕机会出现什么问题?
1、当slave结点宕机会出现什么问题?
宕机的slave结点对应的master:
宕机前:
宕机后:
对应的从结点重新上线:
其他master结点:
宕机前:
宕机后:(这里包括其他的从结点也一样)
重新上线:
宕机的从结点:
宕机前:
宕机后:
重新上线
由上面可以得到,在Cluster集群当中,当一个slave结点宕机并不会产生多大的影响,只是将相应宕机的从结点进行标记而已,整一个redis集群依旧是可用的。当宕机的slave结点重新上线之后在将它加入对应的主节点就行。
2、当master结点宕机会出现什么问题?
宕机的master结点:
宕机前:
宕机后:
重新上线
宕机的master结点对应的slave结点:
宕机前:
宕机后:
此时通过
cluster nodes
命令去查看当前cluster集群的状态:把宕机的master结点标记为fail,因为宕机的结点可能重新上线,所以这里只是做了标记
重新上线:
使用
cluster nodes
查看当前cluster集群的状态:
其他结点只是更新一下当前结点的状态而已
6、Cluster节点操作命令
查看集群节点信息
1
cluster nodes
进入一个从节点 redis,切换其主节点
1
cluster replicate <master-id>
发现一个新节点,新增主节点
1
cluster meet ip:port
忽略一个没有solt的节点
1
cluster forget <id>
手动故障转移
1
cluster failover
7、redis-trib命令
添加节点
1
redis-trib.rb add-node
删除节点
1
redis-trib.rb del-node
重新分片
1
redis-trib.rb reshard
4、集群参数优化
cluster_node_timeout
- 默认值为15s。
- 影响ping消息接收节点的选择,值越大对延迟容忍度越高,选择的接收节点就越少,可以降低带宽,但会影响收敛速度。应该根据带宽情况和实际要求具体调整。
- 影响故障转移的判定,值越大越不容易误判,但完成转移所消耗的时间就越长。应根据网络情况和实际要求具体调整。
cluster-require-full-coverage
- 为了保证集群的完整性,只有当16384个槽slot全部分配完毕,集群才可以上线,但同时,若主节点发生故障且故障转移还未完成时,原主节点的槽不在任何节点中,集群会处于下线状态,影响客户端的使用。
- 该参数可以改变此设定:
- no:表示当槽没有完全分配时,集群仍然可以上线;
- yes:默认配置,只有槽完全分配,集群才可以上线;
10、企业级解决方案
1、缓存预热
1、“宕机”
服务器启动后迅速宕机
2、问题排查
- 请求数量较高
- 主从之间数据吞吐量较大,数据同步操作频度较高
3、解决方案
前置准备工作:
- 日常例行统计数据访问记录,统计访问频度较高的热点数据
- 利用LRU数据删除策略,构建数据留存队列
- 例如:storm与kafka配合
准备工作:
- 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
- 利用分布式多服务器同时进行数据读取,提速数据加载过程
- 热点数据主从同时预热
实施:
- 使用脚本程序固定触发数据预热过程
- 如果条件允许,使用了CDN(内容分发网络),效果会更好
4、结论
缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
2、缓存雪崩
1、数据库服务器崩溃(1)
- 系统平稳运行过程中,忽然数据库连接量激增
- 应用服务器无法及时处理请求
- 大量408,500错误页面出现
- 客户反复刷新页面获取数据
- 数据库崩溃
- 应用服务器崩溃
- 重启应用服务器无效
- Redis服务器崩溃
- Redis集群崩溃
- 重启数据库后再次被瞬间流量放倒
2、问题排查
- 在一个==较短==的时间内,缓存中==较多==的key==集中过期==
- 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
- 数据库同时接收到大量的请求无法及时处理
- Redis大量请求被积压,开始出现超时现象
- 数据库流量激增,数据库崩溃
- 重启后仍然面对缓存中无数据可用
- Redis服务器资源被严重占用,Redis服务器崩溃
- Redis集群呈现崩塌,集群瓦解
- 应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
- 应用服务器,redis,数据库全部重启,效果不理想
3、问题分析
- 短时间范围内
- 大量key集中过期
4、解决方案(道)
- 更多的页面静态化处理
- 构建多级缓存架构
- Nginx缓存+redis缓存+ehcache缓存
- 检测Mysql严重耗时业务进行优化
- 对数据库的瓶颈排查:例如超时查询、耗时较高事务等
- 灾难预警机制
- 监控redis服务器性能指标
- CPU占用、CPU使用率
- 内存容量
- 查询平均响应时间
- 线程数
- 监控redis服务器性能指标
- 限流、降级
- 短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问
5、解决方案(术)
LRU与LFU切换
数据有效期策略调整
根据业务数据有效期进行分类错峰,A类90分钟,B类80分钟,C类70分钟
过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
超热数据使用永久key
定期维护(自动+人工)
- 对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
加锁
- 慎用!
6、总结
缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。
原本情况:
服务雪崩的情况:
3、缓存击穿
1、数据库服务器崩溃(2)
- 系统平稳运行过程中
- 数据库连接量瞬间激增
- Redis服务器无大量key过期
- Redis内存平稳,无波动
- Redis服务器CPU正常
- 数据库崩溃
2、问题排查
- Redis中某个key过期,该key访问量巨大
- 多个数据请求从服务器直接压到Redis后,均未命中
- Redis在短时间内发起了大量对数据库中同一数据的访问
3、问题分析
- 单个key高热数据
- key过期
4、解决方案(术)
预先设定
- 以电商为例,每个商家根据店铺等级,指定若干款主打商品,在购物节期间,加大此类信息key的过期时长
- 注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低的趋势
现场调整
- 监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
后台刷新数据
- 启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失
二级缓存
- 设置不同的失效时间,保障不会被同时淘汰就行
加锁(但是要注意也是性能瓶颈,慎重!)
分布式锁,防止被击穿,
利用互斥锁保证同一时刻只有一个客户端可以查询底层数据库的这个数据,一旦查到数据就缓存至Redis内,避免其他大量请求同时穿过Redis访问底层数据库;
在使用互斥锁的时候需要避免出现死锁或者锁过期的情况:
- 使用lua脚本或事务将获取锁和设置过期时间作为一个原子性操作(如:set kk vv nx px 30000),以避免出现某个客户端获取锁之后宕机导致的锁不被释放造成死锁现象;
- 另起一个线程监控获取锁的线程的查询状态,快到锁过期时间时还没查询结束则延长锁的过期时间,避免多次查询多次锁过期造成计算资源的浪费;
5、总结
缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度较高,配合雪崩处理策略即可。
4、缓存穿透
1、数据库服务器崩溃(3)
- 系统平稳运行过程中
- 应用服务器流量随时间增量较大
- Redis服务器命中率随时间逐步降低
- Redis内存平稳,内存无压力
- Redis服务器CPU占用激增
- 数据库服务器压力激增
- 数据库崩溃
2、问题排查
- Redis中大面积出现未命中
- 出现非正常URL访问
3、问题分析
- 获取的数据在数据库中也不存在,数据库查询未得到对应数据
- Redis获取到null数据未进行持久化,直接返回
- 下次此类数据到达重复上述过程
- ==出现黑客攻击服务器==
4、解决方案(术)
缓存null
- 对查询结果为null的数据进行缓存(长期使用,定期清理),设定短时限,例如30-60秒,最高5分钟
白名单策略
提前预热各种分类数据id对应的bitmaps,id作为bitmaps的offset,相当于设置了数据白名单。当加载正常数据时,放行,加载异常数据时直接拦截(效率偏低)
使用布隆过滤器(有关布隆过滤器的命中问题对当前状况可以忽略)
- (布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。
- 布隆过滤器可以用于检索一个元素是否在一个集合中。
- 它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。
- 将所有可能存在的数据哈希到一个足够大的
bitmaps
中,一个一定不存在的数据会被这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力
布隆过滤器有误判率,虽然不能完全避免数据穿透的现象,但已经可以将99.99%的穿透查询给屏蔽在Redis层了,极大的降低了底层数据库的压力,减少了资源浪费。
实施监控
- 实时监控redis命中率(业务正常范围时,通常会有一个波动值)与null数据的占比
- 非活动时段波动:通常检测3-5倍,超过5倍纳入重点排查对象
- 活动时段波动:通常检测10-50倍,超过50倍纳入重点排查对象 根据倍数不同,启动不同的排查流程。然后使用黑名单进行防控(运营)
- 实时监控redis命中率(业务正常范围时,通常会有一个波动值)与null数据的占比
key加密
- 问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验
- 例如每天随机分配60个加密串,挑选2到3个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问
5、总结
缓存穿透访问了不存在的数据,跳过了合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类数据的出现量是一个较低的值,当出现此类情况以毒攻毒,==并及时报警==。应对策略应该在临时预案防范方面多做文章。
无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除。
5、缓存更新
缓存服务(Redis)和数据服务(底层数据库)是相互独立且异构的系统,在更新缓存或更新数据的时候无法做到原子性的同时更新两边的数据,因此在并发读写或第二步操作异常时会遇到各种数据不一致的问题。如何解决并发场景下更新操作的双写一致是缓存系统的一个重要知识点。
第二步操作异常:缓存和数据的操作顺序中,第二个动作报错。如数据库被更新, 此时失效缓存的时候出错,缓存内数据仍是旧版本;
缓存更新的设计模式有四种:
Cache aside:
查询:先查缓存,缓存没有就查数据库,然后加载至缓存内;
更新:先更新数据库,然后让缓存失效;或者先失效缓存然后更新数据库;
为了避免在并发场景下,多个请求同时更新同一个缓存导致脏数据,因此不能直接更新缓存而是另缓存失效。(看Redis 的缓存一致性)
推荐使用先失效缓存,后更新数据库,配合延迟失效来更新缓存的模式;
Read through:在查询操作中更新缓存,即当缓存失效时,Cache Aside 模式是由调用方负责把数据加载入缓存,而 Read Through 则用缓存服务自己来加载;
Write through:在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后由缓存自己更新数据库;
Write behind caching:俗称write back,在更新数据的时候,只更新缓存,不更新数据库,缓存会异步地定时批量更新数据库;
四种缓存更新模式的优缺点:
- Cache Aside:实现起来较简单,但需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository);
- Read/Write Through:只需要维护一个数据存储(缓存),但是实现起来要复杂一些;
- Write Behind Caching:与Read/Write Through 类似,区别是Write Behind Caching的数据持久化操作是异步的,但是Read/Write Through 更新模式的数据持久化操作是同步的。
- 优点是直接操作内存速度快,多次操作可以合并持久化到数据库。
- 缺点是数据可能会丢失,例如系统断电等。
缓存本身就是通过牺牲强一致性来提高性能,因此使用缓存提升性能,就会有数据更新的延迟性。这就需要我们在评估需求和设计阶段根据实际场景去做权衡了。
6、缓存降级
缓存降级是指当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,即使是有损部分其他服务,仍然需要保证主服务可用。可以将其他次要服务的数据进行缓存降级,从而提升主服务的稳定性。
降级的目的是保证核心服务可用,即使是有损的。如去年双十一的时候淘宝购物车无法修改地址只能使用默认地址,这个服务就是被降级了,这里阿里保证了订单可以正常提交和付款,但修改地址的服务可以在服务器压力降低,并发量相对减少的时候再恢复。
降级可以根据实时的监控数据进行自动降级也可以配置开关人工降级。是否需要降级,哪些服务需要降级,在什么情况下再降级,取决于大家对于系统功能的取舍。
7、性能指标监控
1、监控指标
- 性能指标:
Performance
- 内存指标:
Memory
- 基本活动指标:
Basic activity
- 持久性指标:
Persistence
- 错误指标:
Error
1、性能指标:Performance
Name | Description |
---|---|
latency | Redis响应一个请求的时间 |
instantaneous_ops_per_sec | 平均每秒处理请求总数 |
hit rate(calculated) | 缓存命中率(计算出来的) |
2、内存指标:Memory
Name | Description |
---|---|
used_menory | 已使用内存 |
mem_fragmentation_ratio | 内存碎片率 |
evicted_keys | 由于最大内存限制被移除的key的数量 |
blocked_clients | 由于BLPOP,BRPOP,or BRPOPlPUSH而备阻塞的客户端 |
3、基本活动指标:Basic activity
Name | Description |
---|---|
connected_clients | 客户端连接数 |
connected_slaves | Slave数量 |
master_last_io_seconds_ago | 最近一次主从交互之后的秒数 |
keyspace | 数据库中的key值总数 |
4、持久性指标:Persistence
Name | Description |
---|---|
rdb_last_save_time | 最后一次持久化保存到磁盘的时间戳 |
rdb_changes_since_last_save | 自最后一次持久化以来数据库的更改数 |
5、错误指标:Error
Name | Description |
---|---|
rejected_connections | 由于达到maxclient限制而被拒绝的连接数 |
keyspace_misses | Key值查找失败(没有命中)次数 |
master_link_down_since_seconds | 主从断开的持续时间 |
2、redis相关的工具与监控命令
- 工具
- Cloud Insight Redis
- Prometheus
- Redis-stat
- Redis-faina
- RedisLive
- zabbix
- 命令
- benchmark
- redis cli
- monitor
- showlog
1、命令——benchmark
注意:
benchmark是一个指令,而不是一个redis命令
所以不是在redis的客户端上启动的,而是像启动redis的服务端或客户端那样直接执行的
命令
1
redis-benchmark [-h ] [-p ] [-c ] [-n <requests]> [-k ]
范例1
1
redis-benchmark
范例2
1
redis-benchmark -c 100 -n 5000
说明:100个连接,5000次请求对应的性能
2、命令——monitor
注意:
monitor它是一个redis命令,而不是一个指令
所以需要在redis的客户端上启动的
命令
1
monitor
打印服务器调试信息
3、命令——showlong
命令
1
showlong [operator]
operator:
- get :获取慢查询日志
- len :获取慢查询日志条目数
- reset :重置慢查询日志
相关配置
1
2slowlog-log-slower-than 1000 #设置慢查询的时间下线,单位:微妙
slowlog-max-len 100 #设置慢查询命令对应的日志显示长度,单位:命令数
3、Redis 6
1、NoSQL数据库简介
1、技术发展
技术的分类
- 解决功能性的问题:Java、Jsp、RDBMS、Tomcat、HTML、Linux、JDBC、SVN
- 解决扩展性的问题:Struts、Spring、SpringMVC、Hibernate、Mybatis
- 解决性能的问题:NoSQL、Java线程、Hadoop、Nginx、MQ、ElasticSearch
1、Web 1.0时代
Web1.0的时代,数据访问量很有限,用一夫当关的高性能的单点服务器可以解决大部分问题。
2、Web 2.0时代
随着Web2.0的时代的到来,用户访问量大幅度提升,同时产生了大量的用户数据。加上后来的智能移动设备的普及,所有的互联网平台都面临了巨大的性能挑战。
3、解决CPU及内存压力
4、解决IO压力
2、NoSQL数据库
1、NoSQL数据库概述
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
NoSQL 不依赖业务逻辑方式存储,而以简单的key-value模式存储。因此大大的增加了数据库的扩展能力。
- 不遵循SQL标准。
- 不支持ACID。(事务的四大特性)
- 远超于SQL的性能。
2、NoSQL适用场景
- 对数据高并发的读写
- 海量数据的读写
- 对数据高可扩展性的
3、NoSQL不适用场景
- 需要事务支持
- 基于sql的结构化查询存储,处理复杂的关系,需要即席查询。
- 即席查询是用户根据自己的需求,灵活的选择查询条件,系统能够根据用户的选择生成相应的统计报表
- (用不着sql的和用了sql也不行的情况,请考虑用NoSql)
4、常用的NoSQL数据库
1、Memcache
- 很早出现的NoSql数据库
- 数据都在内存中,一般不持久化
- 支持简单的key-value模式,支持类型单一
- 一般是作为缓存数据库辅助持久化的数据库
2、Redis
几乎覆盖了Memcached的绝大部分功能
数据都在内存中,支持持久化,主要用作备份恢复
除了支持简单的key-value模式,还支持多种数据结构的存储,比如 list、set、hash、zset等。
一般是作为缓存数据库辅助持久化的数据库
3、MongoDb
- 高性能、开源、模式自由(schema free)的文档型数据库
- 数据都在内存中, 如果内存不足,把不常用的数据保存到硬盘
- 虽然是key-value模式,但是对value(尤其是json)提供了丰富的查询功能
- 支持二进制数据及大型对象
- 可以根据数据的特点替代RDBMS ,成为独立的数据库。或者配合RDBMS,存储特定的数据。
3、行式存储数据库(大数据时代)
1、行式数据库
2、列式数据库
1、Hbase
HBase是Hadoop项目中的数据库。它用于需要对大量的数据进行==随机==、==实时==的读写操作的场景中。
HBase的目标就是处理数据量非常庞大的表,可以用普通的计算机处理超过10亿行数据,还可处理有数百万列元素的数据表。
2、Cassandra[kəˈsændrə]
Apache Cassandra是一款免费的开源NoSQL数据库,其设计目的在于管理由大量商用服务器构建起来的庞大集群上的**海量数据集(数据量通常达到PB级别)**。在众多显著特性当中,Cassandra最为卓越的长处是对写入及读取操作进行规模调整,而且其不强调主集群的设计思路能够以相对直观的方式简化各集群的创建与扩展流程。
计算机存储单位 计算机存储单位一般用B,KB,MB,GB,TB,EB,ZB,YB,BB来表示,它们之间的关系是:
位 bit (比特)(Binary Digits):存放一位二进制数,即 0 或 1,最小的存储单位。
字节 byte:8个二进制位为一个字节(B),最常用的单位。
1KB (Kilobyte 千字节)=1024B,
1MB (Megabyte 兆字节 简称“兆”)=1024KB,
1GB (Gigabyte 吉字节 又称“千兆”)=1024MB,
1TB (Trillionbyte 万亿字节 太字节)=1024GB,其中1024=2^10 ( 2 的10次方),
1PB(Petabyte 千万亿字节 拍字节)=1024TB,
1EB(Exabyte 百亿亿字节 艾字节)=1024PB,
1ZB (Zettabyte 十万亿亿字节 泽字节)= 1024 EB,
1YB (Jottabyte 一亿亿亿字节 尧字节)= 1024 ZB,
1BB (Brontobyte 一千亿亿亿字节)= 1024 YB.
注:“兆”为百万级数量单位。
4、图关系型数据库
主要应用:社会关系,公共交通网络,地图及网络拓谱(n*(n-1)/2)
5、DB-Engines 数据库排名
http://db-engines.com/en/ranking
2、Redis配置文件介绍
自定义目录:/myredis/redis.conf
1、###Units单位###
配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
大小写不敏感
2、###INCLUDES包含###
类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
3、###网络相关配置
1、bind
默认情况bind=127.0.0.1只能接受本机的访问请求
不写的情况下,无限制接受任何ip地址的访问
生产环境肯定要写你应用服务器的地址;服务器是需要远程访问的,所以需要将其注释掉
如果开启了protected-mode,那么在没有设定bind ip且没有设密码的情况下,Redis只允许接受本机的响应
如果配置了bind * -::*
:表示无限制接受任何ip地址的访问
保存配置,停止服务,重启启动查看进程,不再是本机访问了。
2、protected-mode
将本机访问保护模式设置no
默认为yes,表示只能进行本机访问
3、port
端口号,默认 6379
4、tcp-backlog
设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。默认是511
注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn
的值(128),所以需要确认增大/proc/sys/net/core/somaxconn
和/proc/sys/net/ipv4/tcp_max_syn_backlog
(128)两个值来达到想要的效果
5、timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
默认为0,单位:秒/s
6、tcp-keepalive
对访问客户端的一种心跳检测,每个n秒检测一次。默认是300s
单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
4、###GENERAL通用###
1、daemonize
是否为后台进程,设置为yes
守护进程,后台启动
2、pidfile
存放pid文件的位置,每个实例会产生一个不同的pid文件
3、loglevel
指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
四个级别根据使用阶段来选择,生产环境选择notice 或者warning
4、logfile
日志文件名称,默认为空
5、databases 16
设定库的数量 默认16,默认数据库为0,可以使用SELECT <dbid>
命令在连接上指定数据库id
5、###SECURITY安全###
设置密码
访问密码的查看、设置和取消。默认是没有密码的
在命令中设置密码,只是临时的。重启redis服务器,密码就还原了。
永久设置,需要再配置文件中进行设置。
6、####LIMITS限制###
1、maxclients
- 设置redis同时可以与多少个客户端进行连接。
- 默认情况下为10000个客户端。
- 如果达到了此限制,redis则会拒绝新的连接请求,并且向这些连接请求方发出“max number of clients reached”以作回应。
2、maxmemory
- 建议必须设置,否则,将内存占满,造成服务器宕机
- 设置redis可以使用的内存量。一旦到达内存使用上限,redis将会试图移除内部数据,移除规则可以通过
maxmemory-policy
来指定。(逐出算法) - 如果redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,那么redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
- 但是对于无内存申请的指令,仍然会正常响应,比如GET等。如果你的redis是主redis(说明你的redis有从redis),那么在设置内存使用上限时,需要在系统中留出一些内存空间给同步队列缓存,只有在你设置的是“不移除”的情况下,才不用考虑这个因素。
3、maxmemory-policy
- volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
- allkeys-lfu:在所有集合key中,使用LFU算法移除key(最近使用次数最少)
- volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
- allkeys-random:在所有集合key中,移除随机的key
- volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
- noeviction:不进行移除。针对写操作,只是返回错误信息
4、maxmemory-samples
- 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
- 一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。
3、Redis的发布和订阅
1、什么是发布和订阅
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
Redis 客户端可以订阅任意数量的频道。
2、Redis的发布和订阅
Redis中的订阅、发布实现了发布/订阅消息范式,发布者不是计划发送消息给特定的订阅者,而是发布消息到不同的频道,发布者不需要知道是哪些订阅者订阅了消息。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道是什么样的发布者发布的消息。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。
在Redis的发布订阅模式中,有三个部分:
Publisher(发布者)
:发送消息到频道中,每次只能往一个频道发送一条消息;Subscriber(订阅者)
:订阅频道,订阅者可以同时订阅多个频道;Channel(频道)
:将发布者发布的消息转发给当前订阅此频道的订阅者
客户端可以订阅频道如下图
当给这个频道发布消息后,消息就会发送给订阅的客户端
3、发布订阅命令行实现
1 | # 发布消息到指定的频道 |
4、使用
使用搭建的集群来测试Redis的订阅发布模式,A节点作为发布者,A,B,C节点作为订阅者消费A节点发布的消息:
- 订阅者6381:与发布者在同一节点,订阅www,csdn,wyk三个频道;
- 订阅者6382:订阅符合csdn和wyk模式的所有频道;
- 订阅者6383:订阅csdn频道;
- 发布者6381:分别往csdn1,csdn2,csdn,wyk四个频道发送消息,验证三个订阅者接收消息的情况以及发布者发布消息后的返回值;
断开后的订阅者重新订阅后会丢失断开期间发布者发布的消息:
在集群模式中,发布者发布消息后的返回值取决于订阅者与发布者在不在同一个节点上:
- 发布者发布消息后返回值为与发布者相同节点当前订阅了该频道的客户端数量。
5、对比
在上面的示例中,大家也可以看到,Redis中的发布订阅非常像消息队列,但还是有不同,我们就来对比一下Redis的List实现消息队列以及传统消息队列Kafka看看有哪些不同:
1、对比List
与Redis中的List对比,基于List实现的消息队列需要结合lpush + brpop
来实现。
- 胜(多消费组):
- 当多个客户端同时消费同一个List消息队列时,消费者A使用brpop消费的数据就从list中弹出了,消费者B就再也读不到该数据;
- 而在发布订阅中,多个订阅者可以订阅相同的频道,频道内的数据会分发到各个订阅者,不会出现某一个订阅者消费了之后,另一个订阅者读不到该数据的情况。
- 负(断点消费):
- 但对于List的消息队列来说,当消费者断开后重连,仍然可以从List中断点消费还没消费的数据;
- 而发布订阅中,如果订阅者断开重连,会丢失断开期间发布者发布的数据,无法恢复。
2、对比Kafka
Redis的发布订阅以及List并不是要和专业的消息队列对标,而是可以实现类似的功能,真正在消息队列领域做的好的有很多,RabbitMQ、ActiveMQ、RocketMQ、Kafka、Pulsar等等,发布订阅相比于它们有什么异同呢?
- 不同点:
- 持久化:Kafka会将数据持久化到磁盘内,而Redis的发布订阅做不到;
- 断点消费:上面也提到,当订阅者断开重连会丢失断开期间发布者发布的消息,而kafka中会记录每个消费者消费的topic的offset,因此kafka可以从断开的offset继续消费;
- 偏移量:基于上一条,同样的kafka的消费者可以指定从某个offset开始重新消费,而Redis发布订阅根本不会记录订阅者消费的偏移量;
- 消费方式: 在Redis发布订阅中,数据消费情况是由发布者控制的,当发布者发布到频道中后,只有当前连接了频道的订阅者才能消费到数据,断开重连的会失去那部分数据。而kafka中消费进度是由消费者控制的,消费者从topic中拉取数据并记录消费的offset。
- 相同点:
- 消息模型:在JMS消息模型中有点对点和订阅发布两种,Kafka和Redis发布订阅都是采用发布订阅的模型。
- 消费者组:Kafka里在不同的消费者组中的消费者消费相同的topic时会各自维护一个offset,因此不会出现A消费之后的数据,B就消费不到的情况。Redis中订阅者订阅相同的频道也不会出现类似的情况。
4、解决库存遗留问题——LUA脚本
1、LUA脚本
Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。
这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂。
2、LUA脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作。
但是注意redis的lua脚本功能,只有在Redis 2.6以上的版本才可以使用。
利用lua脚本淘汰用户,解决超卖问题。
redis 2.6版本以后,通过lua脚本解决争抢问题,实际上是redis 利用其单线程的特性,用任务队列的方式解决多任务并发问题。
3、在Redis中使用LUA脚本示例
1 | local userid=KEYS[1]; |
5、Redis 6新功能
1、ACL
1、简介
Redis ACL是 Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。
在Redis 5版本之前,Redis 安全规则只有密码控制,还有通过rename 来调整高危命令比如 flushdb , KEYS* , shutdown 等。Redis 6 则提供ACL的功能对用户进行更细粒度的权限控制 :
- 接入权限:用户名和密码
- 可以执行的命令
- 可以操作的 KEY
参考官网
2、命令
展现用户权限列表
1
acl list
数据说明:
查看添加权限指令类别
1
2# 加参数类型名可以查看类型下具体命令
acl cat <参数类型名>查看添加权限指令类别
加参数类型名可以查看类型下具体命令
查看当前用户
1
acl whoami
创建和编辑用户ACL
1
aclsetuser
3、ACL规则
1、有效ACL规则的列表
某些规则只是用于激活或删除标志,或对用户ACL执行给定更改的单个单词。其他规则是字符前缀,它们与命令或类别名称、键模式等连接在一起。
类型 | 参数 | 说明 |
---|---|---|
启动用户 | on | 激活某用户账号 |
禁用用户 | off | 禁用某用户账号。注意:已验证的连接仍然可以工作。如果默认用户被标记为off,则新连接将在未进行身份验证的情况下启动,并要求用户使用AUTH选项发送AUTH或HELLO,以便以某种方式进行身份验证。 |
权限的添加删除 | + |
将指令添加到用户可以调用的指令列表中 |
- |
从用户可执行指令列表移除指令 | |
+@ |
添加该类别中用户要调用的所有指令,有效类别为@admin、@set、@sortedset…等,通过调用ACL CAT命令查看完整列表。特殊类别@all表示所有命令,包括当前存在于服务器中的命令,以及将来将通过模块加载的命令。 | |
-@ |
从用户可调用指令中移除类别 | |
allcommands | +@all的别名 | |
nocommand | -@all的别名 | |
可操作键的添加或删除 | ~ |
添加可作为用户可操作的键的模式。例如~*允许所有的键 |
2、查看ACL的有哪些命令
1 | help @server |
3、通过命令创建新用户默认权限
1 | acl setuser user1 |
在上面的示例中,我根本没有指定任何规则。如果用户不存在,这将使用just created的默认属性来创建用户。如果用户已经存在,则上面的命令将不执行任何操作。
4、设置有用户名、密码、ACL权限、并启用的用户
1 | acl setuser user2 on >password ~cached:* +get |
5、切换用户,验证权限
2、IO 多线程
1、简介
Redis6终于支撑多线程了,告别单线程了吗?
IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。
**Redis6执行命令依然是==单线程==**。
2、原理架构
Redis 6 加入多线程,但跟 Memcached 这种从 IO处理到数据访问多线程的实现模式有些差异。
Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。
之所以这么设计是不想因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。
整体的设计大体如下:
另外,多线程IO默认也是不开启的,需要再配置文件中配置
1 | io-threads-do-reads yes |
3、工具支持 Cluster
之前老版Redis想要搭集群需要单独安装ruby环境,Redis 5 将 redis-trib.rb 的功能集成到 redis-cli 。
另外官方 redis-benchmark 工具开始支持 cluster 模式了,通过多线程的方式对多个分片进行压测。
4、Redis新功能持续关注
Redis6新功能还有:
- RESP3新的 Redis 通信协议:优化服务端与客户端之间通信
- Client side caching客户端缓存:基于 RESP3 协议实现的客户端缓存功能。为了进一步提升缓存的性能,将客户端经常访问的数据cache到客户端。减少TCP网络交互。
- Proxy集群代理模式:Proxy 功能,让 Cluster 拥有像单实例一样的接入方式,降低大家使用cluster的门槛。不过需要注意的是代理不改变 Cluster 的功能限制,不支持的命令还是不会支持,比如跨 slot 的多Key操作。
- Modules API
- Redis 6中模块API开发进展非常大,因为Redis Labs为了开发复杂的功能,从一开始就用上Redis模块。
- Redis可以变成一个框架,利用Modules来构建不同系统,而不需要从头开始写然后还要BSD许可。
- Redis一开始就是一个向编写各种系统开放的平台。
6、Redis 的缓存一致性
1、Redis 数据一致性简介
在做系统优化时,想到了将数据进行分级存储的思路。因为在系统中会存在一些数据,有些数据的实时性要求不高,比如一些配置信息。
基本上配置了很久才会变一次。而有一些数据实时性要求非常高,比如订单和流水的数据。所以这里根据数据要求实时性不同将数据分为三级:
- 第1级:订单数据和支付流水数据;这两块数据对实时性和精确性要求很高,所以不添加任何缓存,读写操作将直接操作数据库。
- 第2级:用户相关数据;这些数据和用户相关,具有读多写少的特征,所以我们使用redis进行缓存。
- 第3级:支付配置信息;这些数据和用户无关,具有数据量小,频繁读,几乎不修改的特征,所以我们使用本地内存进行缓存。
但是只要使用到缓存,无论是本地内存做缓存还是使用 redis 做缓存,那么就会存在数据同步的问题,因为配置信息缓存在内存中,而内存时无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题。
接下来就讨论一下关于保证缓存和数据库双写时的数据一致性。
2、解决方案
那么我们这里列出来所有策略,并且讨论他们优劣性。
- 先更新数据库,后更新缓存
- 先更新数据库,后删除缓存
- 先删除缓存,后更新数据库
- 先更新缓存,后更新数据库
- 数据异步同步(最佳实现)
3、详解
1、先更新数据库,后更新缓存(不推荐)
这种场景一般是没有人使用的,主要原因是在==更新缓存==那一步,为什么呢?
- 其一:因为有的业务需求缓存中存在的值并不是直接从数据库中查出来的,有的是需要经过一系列计算来的缓存值,那么这时候后你要更新缓存的话其实代价是很高的。如果此时有大量的对数据库进行写数据的请求,但是读请求并不多,那么此时如果每次写请求都更新一下缓存,那么性能损耗是非常大的。
- 例子:比如在数据库中有一个值为 1 的值,此时我们有 10 个请求对其每次加一的操作,但是这期间并没有读操作进来,如果用了先更新数据库的办法,那么此时就会有十个请求对缓存进行更新,会有大量的冷数据产生,如果我们不更新缓存而是删除缓存,那么在有读请求来的时候那么就会只更新缓存一次。
- 其二:存在缓存数据和数据库数据不一致情况
- 例子:当有两个线程A、B,同时对一条数据进行操作,一开始数据库和redis的数据都为tony,当线程A去修改数据库,将tong改为allen,然后线程A在修改缓存中的数据,可能因为网络原因出现延迟,这个时候线程B,将数据库中的数据修改成了Mike、然后将redis中的tony,也改成了Mike,然后线程A恢复正常,将redis中的缓存改成了allen,此时就出现了缓存数据和数据库数据不一致情况。
- 这种情况是很致命的,因为在这个值被重新修改或过期之前,A和B读取的都是错误数据。
2、先更新缓存,后更新数据库(不推荐)
这一种情况和第一种情况是一样的,主要原因是在==更新缓存==那一步:
- 其一:缓存的数据可能需要经过计算,如果每次写请求都更新一下缓存,那么性能损耗是非常大的。
- 其二:存在缓存数据和数据库数据不一致情况
- 当有两个线程A、B,同时对一条数据进行操作,线程A先将redis中 的数据修改为了allen,然后CPU切换到了线程B,将redis中的数据修改为了mike,然后将数据库中的信息也修改了mike,然后线程A获得CPU执行,将数据库中的信息改为了allen,此时出现缓存和数据库数据不一致情况。
3、先删除缓存,后更新数据库(存在问题)(推荐)
该方案也会出问题,具体出现的原因如下:
此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
- 请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作
- 此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中
- 但是此时请求 A 并没有更新成功,或者事务还未提交,那么这时候就会产生数据库和 Redis 数据不一致的问题
如何解决呢?
- 其实最简单的解决办法就是==延时双删==的策略。
但是上述的保证事务提交完以后再进行删除缓存还有一个问题:
- 就是如果你使用的是 Mysql 的读写分离的架构的话,那么其实主从同步之间也会有时间差。
此时来了两个请求,请求 A(更新操作) 和请求 B(查询操作)
- 请求 A 更新操作,删除了 Redis
- 请求主库进行更新操作,主库与从库进行同步数据的操作
- 请 B 查询操作,发现 Redis 中没有数据
- 去从库中拿去数据
- 此时同步数据还未完成,拿到的数据是旧数据
此时的解决办法:如果是对 Redis 进行填充数据的查询数据库操作,那么就强制将其指向主库进行查询。
4、先更新数据库,后删除缓存(推荐)
问题:这一种情况也会出现问题:
- 可能会短暂出现数据不一致情况,但最终都会一致。
- 当有两个线程A、B,线程A先去将数据库的值修改为allen,然后需要去删除redis中的缓存,当线程B去读取缓存时,线程A已经完成delete操作时,缓存不命中,需要去查询数据库,然后在更新缓存,数据一致性;如果线程A没有完成delete操作,线程B直接命中,返回的数据与数据库中的数据不一致,可能会短暂出现数据不一致情况,但最终都会一致。
- 当有两个线程A、B,线程A去修改数据库中的值改为allen,然后出现网络波动,线程B将数库中的值修改为了Mike,然后两个线程都会删除缓存,保证数据一致性
- 当数据过期或者初始化时,会出现数据不一致情况,也就是线程B从数据库中,查询到数据为tony,然后线程A将tony修改为了allen,然后去删除redis中的数据,然后线程B将读到的tony,更新到了数据库中,出现了数据不一致问题
- 解决方案:对于不过期的数据我们要在上线的时候做好数据的预热,保证缓存命中。对于存在过期的数据,因为有过期时间,只会在特定的时间段内数据不一致,下次数据过期后,可以恢复,对于实时性要求不高时,可以接受。
- 更新数据库成功了,但是在删除缓存的阶段出错了没有删除成功,那么此时再读取缓存的时候每次都是错误的数据了。
此时解决方案就是利用消息队列进行删除的补偿。具体的业务逻辑用语言描述如下:
- 请求 A 先对数据库进行更新操作
- 在对 Redis 进行删除操作的时候发现报错,删除失败
- 此时将Redis 的 key 作为消息体发送到消息队列中
- 系统接收到消息队列发送的消息后再次对 Redis 进行删除操作
但是这个方案会有一个缺点就是会对业务代码造成大量的侵入,深深的耦合在一起,所以这时会有一个优化的方案,我们知道对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
这也是第5种解决方法:数据异步同步
5、数据异步同步(最佳实现)
对 Mysql 数据库更新操作后再 binlog 日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql 数据库的 binlog 日志对缓存进行操作。
Canal:基于数据库增量日志解析,提供增量数据订阅和消费https://github.com/alibaba/canal
mysql会将操作记录在Binary log日志中,通过canal去监听数据库日志二进制文件,解析log日志,同步到redis中进行增删改操作。
canal的工作原理:canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送dump 协议;MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal );canal 解析 binary log 对象(原始为 byte 流)。
4、总结
每种方案各有利弊,比如在第二种先删除缓存,后更新数据库这个方案我们最后讨论了要更新 Redis 的时候强制走主库查询就能解决问题,那么这样的操作会对业务代码进行大量的侵入,但是不需要增加的系统,不需要增加整体的服务的复杂度。
最后一种方案我们最后讨论了利用订阅 binlog 日志进行搭建独立系统操作 Redis,这样的缺点其实就是增加了系统复杂度。其实每一次的选择都需要我们对于我们的业务进行评估来选择,没有一种技术是对于所有业务都通用的。没有最好的,只有最适合我们的。
7、LUA脚本
1、介绍
Redis2.6之后新增的功能,我们可以在redis中通过lua脚本操作redis。与事务不同的是事务是将多个命令添加到一个执行的集合,执行的时候仍然是多个命令,会受到其他客户端的影响,而脚本会将多个命令和操作当成一个命令在redis中执行,也就是说该脚本在执行的过程中,不会被任何其他脚本或命令打断干扰。正是因此这种原子性,lua脚本才可以代替multi和exec的事务功能。同时也是因此,在lua脚本中不宜进行过大的开销操作,避免影响后续的其他请求的正常执行。
2、使用lua脚本的好处
- lua脚本是作为一个整体执行的,所以中间不会被其他命令插入
- 可以把多条命令一次性打包,所以可以有效减少网络开销;
- lua脚本可以常驻在redis内存中,所以在使用的时候,可以直接拿来复用。也减少了代码量
3、应用
redis脚本使用eval命令执行lua脚本,其中numkeys表示lua script里有多少个key参数,redis脚本根据该数字从后面的key和arg中取前n个作为key参数,之后的都作为arg参数:
1 | eval script numkeys key [key ...] arg [arg ...] |
1、例1:记录IP登录次数
1 | # 利用hash记录所有登录的IP次数 |
2、例2:当10秒内请求3次后拒绝访问
1 | # 1.给访问ip的key递增 |
通过上面的例子也可以看出,我们可以在redis里使用eval命令调用lua脚本,且该脚本在redis里作为单条命令去执行不会受到其余命令的影响,非常适用于高并发场景下的事务处理。同样我们可以在lua脚本里实现任何想要实现的功能,迭代,循环,判断,赋值 都是可以的。
4、lua脚本缓存
redis脚本也支持将脚本进行持久化,这样的话,下次再使用就不用输入那么长的lua脚本了。事实上使用eval执行的时候也会缓存,eval与load不同的是eval会将lua脚本执行并缓存,而load只会将脚本缓存。
相同点是它们都使用sha算法进行缓存,因此只要lua脚本内容相同,eval与load缓存的sha码就是一样的。而缓存后的脚本,我们可以使用evalsha
命令直接调用,极大的简化了我们的代码量,不用重复的将lua脚本写出来。
1 | #eval 执行脚本并缓存 |
8、Redis6新特性之ACL安全策略(用户权限管理)
自从Redis6.0以来,大家呼吁了很久的权限管理功能**(ACL[access control list 访问控制列表])终于发布了,通过此功能,我们可以设置不同的用户并对他们授权命令或数据权限。这样我们可以避免有些用户的误操作导致数据丢失或避免数据泄露**的安全风险。
1、介绍
在Redis6之前的版本,我们只能使用requirepass参数给default用户配置登录密码,同一个redis集群的所有开发都共享default用户,难免会出现误操作把别人的key删掉或者数据泄露的情况,那之前我们也可以使用rename command的方式给一些危险函数重命名或禁用,但是这样也防止不了自己的key被其他人访问。
因此Redis6版本推出了ACL(Access Control List)访问控制权限的功能,基于此功能,我们可以设置多个用户,并且给每个用户单独设置命令权限和数据权限。 为了保证向下兼容,Redis6保留了default用户和使用requirepass的方式给default用户设置密码,默认情况下default用户拥有Redis最大权限,我们使用redis-cli连接时如果没有指定用户名,用户也是默认default。
我们可以在配置文件中或者命令行中设置ACL,如果使用配置config文件的话需要重启服务,使用配置aclfile文件或者命令行授权的话无需重启Redis服务但需要及时将权限持久化到磁盘,否则下次重启的时候无法恢复该权限。
官网:https://redis.io/topics/acl
2、配置文件模式
配置ACL的方式有两种,一种是在config文件中直接配置,另一种是在外部aclfile中配置。配置的命令是一样的,但是两种方式只能选择其中一种,我们之前使用requirepass给default用户设置密码,默认就是使用config的方式,执行config rewrite重写配置后会自动在config文件最下面新增一行记录配置default的密码和权限。
1、conf文件模式
使用redis.conf文件配置default和其他用户的ACL权限:
1 | # 1.在config文件中配置default用户的密码 |
因此我们可以直接在config配置文件中使用上面default用户ACL这行DSL命令设置用户权限,或者我们也可以配置外部aclfile配置权限。
配置aclfile需要先将config中配置的DSL注释或删除,因为Redis不允许两种ACL管理方式同时使用,否则在启动redis的时候会报下面的错误:
1 | # Configuring Redis with users defined in redis.conf and at the same setting an ACL file path is invalid. This setup is very likely to lead to configuration errors and security holes, please define either an ACL file or declare users directly in your redis.conf, but not both. |
2、外部ACLFILE模式
使用外部aclfile文件配置Default和其他用户的ACL权限:
1 | # 1.注释redis.conf中所有已授权的ACL命令,如: |
开启aclfile之后不再推荐在redis.conf文件中通过requirepass配置default的密码,因为它不再生效,同时开启aclfile之后也不能使用redis-cli -a xxx登陆,必须使用redis-cli –user xxx –pass yyy来登陆:
3、对比conf和aclfile模式
在redis.conf和aclfile模式中配置DSL 官方更推荐使用aclfile,因为如果在redis.conf中配置了权限之后需要重启redis服务才能将配置的权限加载至redis服务中来,但如果使用aclfile模式,可以调用acl load命令将aclfile中配置的ACL权限热加载进环境中,类似于Mysql中的flush privileges。
redis.conf | users.acl | |
---|---|---|
配置方式 | DSL | DSL |
加载ACL配置 | 重启Redis服务 | ACL LOAD命令 |
持久化ACL配置 | CONFIG REWRITE命令 | ACL SAVE命令 |
4、命令行模式
1、介绍
上面可以看到,我们在配置文件中配置的ACL权限,需要执行ACL LOAD或者重启Redis服务才能生效,事实上我们可以直接在命令行下配置ACL,在命令行模式下配置的权限无需重启服务即可生效。我们也可以在命令行模式下配置ACL并将其持久化到aclfile或者config文件中(这取决于配置文件中选择的是config模式还是外部aclfile模式),一旦将权限持久化到aclfile或cofig文件中,下次重启就会自动加载该权限,如果忘记持久化,一旦服务宕机或重启,该权限就会丢失。
1 | # 如果使用config模式,将ACL权限持久化到redis.conf文件中使用下面的命令: |
2、ACL规则
ACL是使用DSL(Domain specific language)定义的,该DSL描述了用户能够执行的操作。该规则始终从上到下,从左到右应用,因为规则的顺序对于理解用户的实际权限很重要。ACL规则可以在redis.conf文件以及users.acl文件中配置DSL,也可以在命令行中通过ACL命令配置。
某些规则只是用于激活或删除标志,或对用户ACL执行给定更改的单个单词。其他规则是字符前缀,它们与命令或类别名称、键模式等连接在一起。
类型 | 参数 | 说明 |
---|---|---|
启动用户 | on | 激活某用户账号 |
禁用用户 | off | 禁用某用户账号。注意:已验证的连接仍然可以工作。如果默认用户被标记为off,则新连接将在未进行身份验证的情况下启动,并要求用户使用AUTH选项发送AUTH或HELLO,以便以某种方式进行身份验证。 |
权限的添加删除 | + |
将指令添加到用户可以调用的指令列表中 |
- |
从用户可执行指令列表移除指令 | |
+ |
允许使用已禁用命令的特定子命令 | |
+@ |
添加该类别中用户要调用的所有指令,有效类别为@admin、@set、@sortedset…等,通过调用ACL CAT命令查看完整列表。特殊类别@all表示所有命令,包括当前存在于服务器中的命令,以及将来将通过模块加载的命令。 | |
-@ |
从用户可调用指令中移除类别 | |
allcommands | +@all的别名 | |
nocommand | -@all的别名 | |
可操作键的添加或删除 | ~ |
添加可作为用户可操作的键的模式。例如~*允许所有的键 |
禁止访问某些Key | * resetkeys | 使用当前模式覆盖所有允许的模式。如: ~foo:* ~bar:* resetkeys ~objects:* ,客户端只能访问匹配 object:* 模式的 KEY。 |
为用户配置有效密码:
><password>
:将此密码添加到用户的有效密码列表中。例如,>mypass
将“mypass”添加到有效密码列表中。该命令会清除用户的nopass标记。每个用户可以有任意数量的有效密码。<<password>
:从有效密码列表中删除此密码。若该用户的有效密码列表中没有此密码则会返回错误信息。#<hash>
:将此SHA-256哈希值添加到用户的有效密码列表中。该哈希值将与为ACL用户输入的密码的哈希值进行比较。允许用户将哈希存储在users.acl文件中,而不是存储明文密码。仅接受SHA-256哈希值,因为密码哈希必须为64个字符且小写的十六进制字符。!<hash>
:从有效密码列表中删除该哈希值。当不知道哈希值对应的明文是什么时很有用。nopass
:移除该用户已设置的所有密码,并将该用户标记为nopass无密码状态:任何密码都可以登录。resetpass命令可以清除nopass这种状态。resetpass
:清空该用户的所有密码列表。而且移除nopass状态。resetpass之后用户没有关联的密码同时也无法使用无密码登录,因此resetpass之后必须添加密码或改为nopass状态才能正常登录。reset
:重置用户状态为初始状态。执行以下操作resetpass,resetkeys,off,-@all。
5、ACL HELP
使用下面的命令查看help文档:
1 | acl help |
6、ACL LIST
我们可以使用ACL LIST命令来查看当前活动的ACL,默认情况下,有一个“default”用户:
1 | 127.0.0.1:6379> acl list |
其中user为关键词,default为用户名,后面的内容为ACL规则描述,on表示活跃的,nopass表示无密码, ~* 表示所有key,+@all表示所有命令。所以上面的命令表示活跃用户default无密码且可以访问所有命令以及所有数据。
7、ACL USERS
返回所有用户名:
1 | 127.0.0.1:6379> acl users |
8、ACL WHOAMI
返回当前用户名:
1 | 127.0.0.1:6379> acl whoami |
9、ACL CAT
查看命令类别,用于授权:
1 | ACL CAT:显示所有的命令类别 。 |
10、ACL SETUSER
使用下面的命令创建或修改用户属性,username区分大小写:
1 | # username区分大小写 |
默认规则下新增的用户处于非活跃状态,且没有密码,同时也没有任何命令和key的权限:
例:使用下面的命令新增用户/修改用户的权限:
1 | #on为活跃状态,密码为wyk123456,允许对所有csdn开头的key使用get和set命令 |
11、ACL GETUSER
使用下面的命令查看用户的ACL权限:
1 | #查看用户的ACL权限 |
12、ACL DELUSER
删除指定的用户:
1 | #删除指定的用户 |
13、ACL SAVE
前面提到过,我们可以使用acl save命令将当前服务器中的ACL权限持久化到aclfile中,如果没持久化就关闭redis服务,那些ACL权限就会丢失,因此我们每次授权之后一定要记得ACL SAVE将ACL权限持久化到aclfile中:
1 | # 将acl权限持久化到磁盘的aclfile中 |
14、ACL LOAD
我们也可以直接在aclfile中修改或新增ACL权限,修改之后不会立刻生效,我们可以在redis命令行中执行acl load将该aclfile中的权限加载至redis服务中:
1 | # 将aclfile中的权限加载至redis服务中 |
15、ACL GENPASS
随机返回sha256密码,我们可以直接使用该密文配置ACL密码:
1 | # 随机返回一个256bit的32字节的伪随机字符串,并将其转换为64字节的字母+数字组合字符串 |
16、ACL LOG
查看ACL安全日志:
1 | acl log |
17、AUTH
使用auth命令切换用户:
1 | AUTH <username> <password> |
18、总结
由于Redis是高性能的数据库,正常情况下每秒可以接收百万级别的请求,因此我们的用户密码一定要是非常复杂的组合,否则很容易就会被暴力跑字典给破解了,不管怎么说,这次Redis6版本带来的新特性ACL权限控制也是解决了我们很大的痛点,终于可以权限隔离了!
9、Redis6新特性之RESP3与客户端缓存(Client side caching)
Redis6引入新的RESP3协议,并以此为基础加入了客户端缓存的新特性,在此特性下,大大提高了应用程序的响应速度,并降低了数据库的压力。
1、什么是客户端缓存
1、介绍
客户端缓存是一种用于创建高性能服务的技术,在此技术下,应用程序端将数据库中的数据缓存在应用端的内存中,当应用程序访问数据时直接从本机内存中读取,而无需连接数据库端,减少了网络IO,提升了应用程序的响应速度,同时也减少了数据库端的压力。
官网:https://redis.io/topics/client-side-caching
Why RESP3:http://antirez.com/news/125
没有客户端缓存:
应用端先查询Redis端,如果没有Redis缓存则到源数据库端查询,如果有则直接从Redis端查询数据,更新数据时直接更新MySQL端并同步至Redis内;
有客户端缓存:
应用端先查询本地缓存如Guava、Caffeine,若没有本地缓存则访问Redis缓存,如果Redis缓存中也没有则查询源数据库;
2、客户端缓存的优点
- 降低了客户端的数据延迟,提升客户端的响应速度;
- 数据库端接收的查询减少,降低了数据库端的压力,因此在相同的数据集下可以使用更少的节点提供服务;
疑问:
为了实现客户端缓存,我们面临这样的问题,当进程中缓存了数据,而数据库端数据发生变更,该如何通知到进程,避免客户端显示失效的数据呢?(缓存一致性)
在Redis中可以使用发布订阅机制,向客户端发布数据失效的通知,但该模式下即使某些客户端中没有包含过期数据也会向所有客户端发送无效的消息,非常影响数据库的性能。
在之前的版本中,客户端缓存采用缓存槽(caching slot)**的方式记录每个客户端内的key是否发生变化以及时同步,最新版中已弃用该方式,而是采用记录key的名称或前缀**。
2、什么是RESP3
RESP 全称 REdisSerializationProtocol
,是 Redis 服务端与客户端之间通信的协议。在Reds6之前的版本,使用的是RESP2协议,数据都是以字符串数组
的形式返回给客户端,不管是 list 还是 sorted set。因此客户端需要自行去根据类型进行解析,这样会增加了客户端实现的复杂性。
为了照顾老用户,Redis6在兼容 RESP2 的基础上,开始支持 RESP3,但未来会全面切换到RESP3之上。今天的客户端缓存在基于RESP3才能有更好的实现,可以在同一个连接中运行数据的查询和接收失效消息。而目前在RESP2上实现的客户端缓存,需要两个客户端连接以转发重定向的形式实现。
1 | # 使用RESP2协议 |
3、客户端缓存的实现方式
Redis客户端缓存被称为Tracking,在RESP3协议下,有两种模式:
- 默认模式:服务器记录客户端访问了哪些key,当其中的key发生变更时给客户端发送失效信息,消耗服务器端内存;
- 广播模式:客户端订阅访问过的key的前缀,当符合模式的key发生变更就会被通知(即使变更的key没有被客户端缓存),服务器端不记录客户端访问的key,因此不会消耗服务器端的内存;
4、默认模式
1、原理
服务器端会记录访问key的客户端列表并维护一个表,这个表被称为==失效表==(Invalidation Table),如果插入一个新的key,服务器端会给客户端发送失效信息并从客户端踢除该key,避免提供过时数据。
在失效表中不会记录key和客户端内对应指针的映射关系,只会记录key的指针和各客户端ID(每个Redis客户端都有一个唯一ID)的映射关系,当发送完失效信息后,客户端剔除key,服务端从失效表中删除key的指针和客户端ID的映射关系。
在失效表中key的命名空间只有一个,即是说,在db0~db15中相同的key名,在失效表中会记录在同一个命名空间内,即使客户端缓存的是db0内的key,如果db1内的同名key被更新,也会通知客户端剔除db0内的同名key。
客户端缓存的操作就是对key的内存地址进行操作:
- 当开启客户端缓存的客户端从Redis获取数据时,Redis服务端会调用
enableTracking
方法在上面的失效表中记录key和客户端ID的映射关系; - 若key被修改,则Redis服务端会调用
trackingInvalidateKey
函数根据该key被缓存的客户端列表ID调用sendTrackingMessage
函数向它们发送失效消息。(发送失效消息前会检查客户端的Client_Tracking
和NOLOOP
状态) - 服务端发送完失效消息后会从失效表中将该key与客户端ID的映射关系删除;
- 由于客户端可能会在开启之后关闭了缓存功能,在失效表中删除key和该客户端ID之间的映射关系比较消耗性能,因此服务端采用懒删除的方式,只是将该客户端的Client_Tracking相关标志位删除;
2、应用
上面提到我们可以使用HELLO命令切换RESP3协议,在此协议下我们使用tracking命令开启track追踪,此时服务端会记录客户端在连接的生命周期内的只读的key,当客户端开启track追踪后,key的数据会被缓存在客户端内存中:
1 | # 开启RESP3协议 |
为了演示失效消息的通知,这里使用telnet测试客户端缓存,然后在另一个redis-cli对key做操作:
1 | # 使用telnet连接客户端 |
当开启了tracking后,客户端缓存的key如果在别处被修改为与原值一样,也会收到失效消息;
当客户端缓存失效后,该key再被修改时,客户端不会再收到消息,也就是再查询该key之后 才会在客户端缓存key的值;
当客户端缓存的key因过期策略或内存淘汰策略被驱逐时,服务端也会发送失效消息给开启了tracking的客户端:
当开启了tracking的客户端获取的key不存在时,如果在另一个客户端新增/修改了该key,那个tracking的客户端也会收到失效消息,可见如果key不存在也会在客户端缓存中缓存空值,这种结果因人而异,个人认为这样不太好:
- 一是客户端会徒增大量的无用缓存(空值)
- 二是服务端的失效表会维护更多的key->clientID的映射关系。
5、广播模式
1、原理
另一个客户端缓存的实现方式是广播模式(broadcasting)**,广播不会消耗服务端的内存,而是向各客户端发送更多的失效消息。广播模式与默认模式类似,不同的是广播模式下维护的是前缀表,在前缀表中存储客户端订阅的key前缀与客户端ID之间的映射关系。**
在这种模式下,有以下的主要行为:
- 客户端使用
BCAST
选项开启客户端缓存的广播模式,并使用PREFIX
指定一个或多个前缀。如果不指定前缀则默认客户端接收所有的key的失效消息,如果指定则只会接收匹配该前缀的key的失效消息; - 在广播模式下,服务端维护的不是失效表,而是前缀表(Prefix Table),每个前缀映射一些客户端ID;
- 每次修改跟任意前缀匹配的键时,所有订阅该前缀的客户端都将收到失效消息;
- 服务端的CPU消耗与订阅的key前缀数量成正比,订阅的key前缀数量越多服务器端压力越大;
- 服务器可以为订阅特定前缀的客户端创建单个回复,并向所有的客户端发送相同的回复来进行优化,有助于降低CPU使用率。
2、应用
同样,在广播模式下也需要开启RESP3协议,这里我们仍然使用刚才的telnet会话进行演示。
使用下面的命令开启广播模式的客户端缓存,上面提到广播模式下服务端维护一个前缀表,记录key的前缀和客户端id的映射关系,因此我们也可以在客户端指定需要接收失效消息的key前缀:
1 | # telnet访问redis客户端(略) |
广播模式下,只要符合客户端设置的key前缀的key发生新增、修改、删除、过期、淘汰等动作,即使该key没有被该客户端缓存,也会收到key的失效消息;
6、重定向模式
为了兼容RESP2协议,在Redis6中客户端缓存可以以重定向(Redirect)的方式实现,不再使用 RESP3 原生支持的PUSH消息,而是将消息通过 Pub/Sub 通知给另外一个客户端连接:
1 | # 查看客户端id |
7、OPTIN 和 OPTOUT
在默认模式或重定向模式下,我们可以有选择的对需要的key进行缓存,而由于广播模式是匹配key前缀,因此不能使用此命令。
1 | # RESP3 默认模式 |
- OPTIN:只有执行client caching yes之后的第一个key才会被缓存;
- OPTOUT:与OPTIN相反,执行client caching no之后的第一个只读key不会被缓存;
注意:在redis6.0.3版本中outin和optout选项时灵时不灵,可能还有BUG;
8、NOLOOP选项
我们的客户端修改自己已缓存的key的时候也会收到这个key的过期信息,事实上这个客户端是不需要收到该消息的,这造成了浪费,因此我们可以使用NOLOOP选项将该客户端设置为:本客户端修改的key不会收到相关的失效信息。
1 | # 开启客户端缓存的NOLOOP选项 |
开启noloop选项的客户端,如果在该客户端上修改它已经缓存的key,自己不会收到该key的失效消息:
没开启noloop选项的客户端,如果在该客户端上修改它已经缓存的key,自己也会收到该key的失效消息:
9、失效表key上限
可以使用 tracking_table_max_keys 参数修改服务端失效表内记录的缓存的key的数量,当失效表内记录的缓存key达到配置的数量时会随机从失效表内移除缓存:
1 | # 查询最大缓存的数量 |
10、Redis6新特性之集群代理(Cluster Proxy)
在之前的文章中介绍了Redis6的集群搭建和原理,我们可以使用dummy和smart客户端连接集群,本篇介绍Redis6新增的一个功能:集群代理。客户端不需要知道集群中的具体节点个数和主从身份,可以直接通过代理访问集群,对于客户端来说通过集群代理访问的集群就和单机的Redis一样,因此也能解决很多集群的使用限制。
1、介绍
在Redis6的release note中可以看到新功能中的ACL,RESP3,客户端缓存我们在前面的文章中已经介绍过,本篇就看一下集群代理。集群代理与Redis在Github上是不同的项目,地址如下:
Github:https://github.com/RedisLabs/redis-cluster-proxy
集群代理(Redis Cluster Proxy): 将集群抽象为单实例,客户端不需要知道集群中的具体节点个数和主从身份,通过代理访问集群,就像访问单机Redis一样。同时集群代理也能解决在集群模式下multiple操作的限制及跨slot操作限制(如mget,mset…)。
Redis集群代理的特点:
- 自动化路由:每个查询被自动路由到集群的正确节点;
- 多线程:多路复用通信模型,每个线程都有自己的集群连接;
- 顺序性:在多路复用上下文中,保证查询的执行和应答顺序;
- 无感知更新集群信息:当请求/重定向错误时会自动更新集群信息,客户端提交的查询会在集群信息更新完成后重新执行,对于客户端来说这一切是无感的,客户端不会收到请求/重定向的错误信息,而是直接收到查询的结果;
- 跨槽/节点查询:支持跨slot或node的mutiple操作key,如mget,mset,del等。但由于mset,del会破坏原子性,因此该配置默认关闭;
- ACL:支持连接开启了ACL的Redis集群;
- DBSIZE:对于没有指定节点的命令,将会合并所有的信息的总和并返回;
2、安装
1、下载解压
从github上下载解压源码(2020-06-30:目前最新版是unstable版本)
1 | # git命令 |
2、安装gcc4.9+版本
在之前安装Redis6的文章中有介绍,此处略过安装gcc9.1:
1 | # 开启gcc9.1 |
3、编译
执行下面的命令编译源码,出现下图表示安装成功:
1 | # 进入目录并编译 |
如果遇到错误 unknown type name ‘_Atomic’ ,请检查gcc版本重新安装;
4、安装
编译成功后使用下面的命令安装Redis集群代理服务,出现下图表示安装成功:
1 | # 安装Redis集群代理,可指定安装目录 |
5、使用
1、配置启动
从源码中将配置文件copy到安装目录:
1 | cp /home/wyk/redis-cluster-proxy-unstable/proxy.conf /opt/app/redis-cluster-proxy/ |
修改配置文件:vim /opt/app/redis-cluster-proxy/proxy.conf
1 | #配置Redis集群,这里我使用前几篇文章中配置的Redis6集群,三主三从 |
创建日志文件并使用下面的命令指定配置文件启动集群代理:
1 | # 创建日志文件 |
连接集群代理客户端:
Redis集群代理服务监听7777端口,我们可以使用Redis命令行指定7777端口启动集群代理客户端:
1 | # 连接Redis集群代理客户端 |
2、跨节点slot操作
上面提到在集群代理中,会将集群抽象成一个Redis实例,对用户来说跨slot/node操作是无感的,而在默认集群中会重定向到对应slot所在的节点进行操作。
默认集群模式:
在之前的Redis集群文章中演示了在dummy客户端中操作集群内的key时会重定向到该key存储的slot所在的节点:
1 | # 使用-c进入集群命令行模式 |
集群代理模式:
在集群代理模式下,可以跨slot甚至跨节点操作key,而在集群模式下链接客户端是做不到的。下图演示了如果在集群代理中使用mset和mget跨slot跨node设置或查询key,对于用户来说仿佛是在使用一个单实例的Redis:
6、故障转移
手动的使集群中一个主节点宕机,测试集群代理能否感知到集群的故障转移:
1 | # 在6381主节点执行命令,手动的让其宕机 |
情况一、主节点6381宕机,6391节点升级为主节点,集群恢复正常,但6381节点还没启动,此时集群代理无法使用,需要启动6381节点之后集群代理才能恢复使用:
情况二、手动将6381主节点宕机,当从节点6391升级为主节点后,重启6381节点作为6391的从节点,此时集群的主从机器全部正常启动,查询集群代理,不会收到影响:
3、结尾
目前在Github上最新的版本仍是unstable版,毕竟是新功能,还是有很多BUG的,像集群的故障转移在集群代理中就没有做的很好,其次就是如果集群代理服务本身没有解决单点故障(可以尝试配合HAProxy等代理服务做负载均衡)。
官方最后声明中也提到【当前处于α版本,不推荐在生产环境使用]】:
This project is currently alpha code that is indented to be evaluated by the community in order to get suggestions and contributions. We discourage its usage in any production environment.
但不可否认的是集群代理给redis集群提供了轻量的代理层,也解决了很多在集群模式中的使用限制,未来的潜力还很大,让我们拭目以待吧!
11、Redis6新特性之IO多线程
终于,Redis的多线程版本横空出世,大大提高了并发,本篇就带大家来看看什么是IO多线程,和我们理解的多线程有什么区别,与Memcached的多线程又有什么区别。
1、介绍
作为Redis6版本中的其中一大新特性,IO多线程大大提升了Redis的并发性能。该功能也是在社区内被反复提起,而之前Antirez在自己的博客中也曾经做过简单的介绍:http://antirez.com/news/126
2、为什么Redis6.0之前是单线程模型
首先我们要明确一个共识,我们通常所说的Redis单线程是指获取 (socket 读)、解析、执行、内容返回 (socket 写) 等都由一个顺序串行的主线程处理,这个主线程就是我们平时说的”单线程”**,而其他的清理脏数据、无用连接的释放、LRU淘汰策略等等也是有其他线程在处理的,因此其实在Redis6之前的Redis本质上也是多线程的。**
为什么这些操作要放在同一个主线程中,官方给出的解释:传送门
- 通常瓶颈不在 CPU,而是在内存和网络IO;
- 多线程会带来线程不安全的情况;
- 多线程可能存在线程切换、甚至加锁解锁、死锁造成的性能损耗;
- 单线程降低了Redis内部实现复杂度;
- hash的惰性rehash,lpush等线程不安全的命令可以无锁执行;
3、什么是IO多线程
既然上面说单线程那么好,为什么Redis6.0又要引入多线程呢?
Redis 抽象了一套 AE 事件模型,将 IO 事件和时间事件融入一起,同时借助多路复用机制(linux上用epoll) 的回调特性,使得 IO 读写都是非阻塞的,实现高性能的网络处理能力。加上 Redis 基于内存的数据处理,这就是 “单线程,但却高性能” 的核心原因。
但 IO 数据的读写依然是阻塞的,这也是 Redis 目前的主要性能瓶颈之一,特别是在数据吞吐量特别大的时候,具体情况如下:
上图的下半部分,当 socket 中有数据时,Redis 会通过系统调用将数据从内核态拷贝到用户态,供 Redis 解析用。这个拷贝过程是阻塞的,术语称作 “同步 IO”,数据量越大拷贝的延迟越高,时间消耗也越大,糟糕的是这些操作都是单线程处理的。(写 reponse 时也是一样)
这是 Redis 目前的瓶颈之一,Redis6.0 引入的 “多线程” 机制就是对于该瓶颈的优化。核心思路是,将主线程的 IO 读写任务拆分出来给一组独立的线程执行,使得多个 socket 的读写可以并行化。
与 Memcached 从 IO 处理到数据访问多线程的实现模式有些差异。Redis 的IO多线程只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想 Redis 因为多线程而变得复杂,需要去控制 key、lua、事务,LPUSH/LPOP 等等的并发问题。
4、开启IO多线程
默认情况下,Redis多线程是禁用的,我们可以在配置文件选择开启:vim redis.conf
1 | # 开启IO多线程 |
官方建议:至少4核的机器才开启IO多线程,并且除非真的遇到了性能瓶颈,否则不建议开启此配置 ,且配置的线程数少于机器总线程数,如果有4核建议开启2,3个线程,如果有8核建议开6线程。 线程并不是越多越好,多于8个线程意义不大。
5、性能对比
因资源有限,我手边的机器渣渣配置如下,开启3个线程对比单线程:
配置:
1 | [root@BD-T-uatredis9 ~]# free -h |
测试命令:
使用redis-benchmark进行压测,这里模拟在4核4线程的机器上分别测试3线程和单线程在100W请求,数据大小在128b,512b,1024b,200个客户端,执行SET和GET的QPS性能对比
1 | #三线程 |
结果:
可能是我机器太渣了,3线程比单线程的QPS提升有120%~140%,网友测试的在4线程下QPS提升了100%。
网友的测试结果:
1 | Redis Server: 阿里云 Ubuntu 18.04,8 CPU 2.5 GHZ, 8G 内存,主机型号 ecs.ic5.2xlarge |
注意,数据仅供验证参考,不能作为线上指标:
- 本测试只是使用早期的
unstble
分支的性能,不排除稳定版的性能会更好。- 本测试并没有针对严谨的延时控制和不同并发的场景进行压测。
6、源码解析
刚才提到IO多线程只是在网络数据的读写上是多线程了,具体流程如下:
流程:
- 主线程获取 socket 放入等待列表
- 将 socket 分配给各个 IO 线程(并不会等列表满)
- 主线程阻塞等待 IO 线程读取 socket 完毕
- 主线程以单线程执行命令 (如果命令没有接收完毕,会等 IO 下次继续)
- 主线程阻塞等待 IO 线程将数据回写 socket 完毕(一次没写完,会等下次再写)
- 解除绑定,清空等待队列
- IO 线程要么同时在读 socket,要么同时在写,不会同时读或写;
- IO 线程只负责读写 socket 解析命令,不负责执行命令,由主线程串行执行命令;
- IO 线程数可配置,默认为 1;
- 上面的过程是完全无锁的,因为在 IO 线程处理的时主线程会等待全部的 IO 线程完成,所以不会出现 data race 的场景。
源码:
redis-server 逻辑首先执行 initThreadedIO()函数对 线程进行初始化,当然,也包括 根据配置 server.io_threads_num 控制线程个数,其中主线程的处理逻辑为 IOThreadMain() 函数
1 | /* networking.c: line 2666 */ |
handleClientsWithPendingReadsUsingThreads()
待处理任务分配
1 | /* networking.c: line 2871 */ |
readQueryFromClient()
函数
1 | /* networking.c: line 1791 */ |
函数 postponeClientRead()
将任务放入处理队列,而根据上面 IOThreadMain()
和 handleClientsWithPendingReadsUsingThreads()
的任务处理逻辑进行处理
1 | /* networking.c: line 2852 */ |
7、对比Memcached
前些年memcached 是各大互联网公司常用的缓存方案,因此redis 和 memcached 的区别基本成了面试官缓存方面必问的面试题,最近几年memcached用的少了,基本都是 redis。不过随着Redis6.0加入了多线程特性,类似的问题可能还会出现,接下来我们只针对多线程模型来简单比较一下它们。
首先看一下Memcached的线程模型:
如上图所示:Memcached 服务器采用 master-woker 模式进行工作,服务端采用 socket 与客户端通讯。主线程、工作线程 采用 pipe管道进行通讯。主线程采用 libevent 监听 listen、accept 的读事件,事件响应后将连接信息的数据结构封装起来,根据算法选择合适的工作线程,将连接任务携带连接信息分发出去,相应的线程利用连接描述符建立与客户端的socket连接 并进行后续的存取数据操作。
Redis6.0与Memcached多线程模型对比:
- 相同点:都采用了 master线程-worker 线程的模型
- 不同点:Memcached 执行主逻辑也是在 worker 线程里,模型更加简单,实现了真正的线程隔离,符合我们对线程隔离的常规理解。而 Redis 把处理逻辑交还给 master 线程,虽然一定程度上增加了模型复杂度,但也解决了线程并发安全等问题。
8、结尾
大家都会拿Redis和memcached对比,但Redis不是memcached,它只是做到like memcached的多线程,而不是跟memcached一样的完全隔离的多线程模型。Redis中因为有lua脚本,事务,Lpush等等复杂性,需要考虑的问题很多,不管怎么样,最新版的Redis6带给我们的IO多线程着实是个惊喜,互联网大厂们应该很快就会纷纷上线此功能了!