笔记内容包括两个视频的笔记:
Redis—尚硅谷java研究院
(推荐)Redis入门到精通【黑马程序员】https://www.bilibili.com/video/BV1CJ411m7Gc
REmote Dictionary Server:是一种用C语言开发的开源的高性能键值对数据库。
1.1 技术的分类
Java、Servlet、Jsp、Tomcat、RDBMS、JDBC、Linux、Svn 等
Spring、 SpringMVC、SpringBoot、Hibernate、MyBatis等
NoSQL、Java多线程、Nginx、MQ、ElasticSearch、Hadoop等
1.2 WEB1.0 及WEB2.0
1.3 解决服务器CPU内存压力
思考: Session共享问题如何解决?
此种方案需要将Session数据以Cookie的形式存在客户端,不安全,网络负担效率低
此种方案会导致大量的IO操作,效率低.
此种方案会导致每个服务器之间必须将Session广播到集群内的每个节点,Session数据会冗余,节点越多浪费越大,存在广播风暴问题.
目前来看,此种方案是最好的。将Session数据存在内存中,每台服务器都从内存中读取数据,速度快,结构还相对简单.
将活跃的数据缓存到Redis中,客户端的请求先打到缓存中来获取对应的数据,如果能获取到,直接返回,不需要从MySQL中读取。如果缓存中没有,再从MySQL数据库中读取数据,将读取的数据返回并存一份到Redis中,方便下次读取.
扩展: 对于持久化的数据库来说,单个库单个表存在性能瓶颈,因此会通过水平切分、垂直切分、读取分离等技术提升性能,此种解决方案会破坏一定的业务逻辑,但是可以换取更高的性能.
对数据高并发的读写
海量数据的读写
对数据高可扩展性的
目前 NoSQL 不能完全替代关系型数据库.使用关系型数据库结合 NoSQl 数据库进行完成项目
2.3.1 当数据比较复杂时不适用于 NoSQL 数据库
2.3.2 关系型数据库依然做为数据存储的主要软件.
2.3.3 NoSQL 数据库当作缓存工具来使用.
2.3.3.1 把某些使用频率较高的内容不仅仅存储到关系型数据库中还存储到 NoSQL 数据中
2.3.3.2 考虑到: NoSQL 和关系型数据库数据同步的问题
Redis 持久化策略
3.1 rdb持久化策略
3.1.1 默认的持久化策略.
3.1.2 每隔一定时间后把内存中数据持久化到 dump.rdb 文件中
3.1.3 缺点:
3.1.3.1 数据过于集中.(都存储到了一个文件中)
3.1.3.2 可能导致最后的数据没有持久化到 dump.rdb 中(因为是每隔一段时间)
3.1.3.2.1 解决办法:使用命令:SAVE 或BGSAVE 手动持久
化.
3.2 aof持久化策略
3.2.1 监听 Redis 的日志文件,监听如果发现执行了修改,删除,
新增命令.立即根据这条命令把数据持久化.
3.2.2 缺点:
3.2.2.1 效率降低
1.6 常用的缓存数据库
思考: 如下两条SQL的快慢
select * from users where id =3(快)
select avg(age) from users(慢)需要先查行年龄,再平均
列式数据库对查平均快
1.7 数据库排名
http://db-engines.com/en/ranking
Redis是一个开源的key-value存储系统。
和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis支持各种不同方式的排序。
与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
2.3 Redis官网
Redis官方网站 http://Redis.io
Redis中文官方网站 http://www.Redis.net.cn
手册网址: http://doc.redisfans.com/
2.4 关于Redis版本
3.2.5 for Linux
不用考虑在Windows环境下对Redis的支持
Redis官方没有提供对Windows环境的支持,是微软的开源小组开发了对Redis对Windows的支持.
下载获得redis-3.2.5.tar.gz后将它放入我们的Linux目录/opt
解压命令:tar -zxvf redis-3.2.5.tar.gz
解压完成后进入目录:cd redis-3.2.5
在redis-3.2.5目录下执行make命令
编译:make
安装:make install
创建软链接
ln -s 原始目录名 快速访问目录名
创建配置文件管理目录
mkdir conf
mkdir config
创建数据文件管理目录
mkdir data
运行Make命令时出现错误,提示 gcc:命令未找到 ,原因是因为当前Linux环境中并没有安装gcc 与 g++ 的环境
yum install gcc
yum install gcc-c++
参考Linux课程中<<03_在VM上安装CentOS7>>中的第40步骤
Hint: It's a good idea to run 'make test' ;)
INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install
make[1]: Leaving directory '/home/ftp/redis/redis-3.0.4/src'
放到这个目录下之后,可以在任何目录下访问。
Redis-benchmark:性能测试工具,可以在自己本子运行,看看自己本子性能如何(服务启动起来后执行)
Redis-check-aof:修复有问题的AOF文件,rdb和aof后面讲
Redis-check-dump:修复有问题的dump.rdb文件
Redis-sentinel:Redis集群使用
Redis-server:前台启动Redis
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 3.0.4 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in standalone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 26926
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
这是前台启动,我们要把他设置为后台启动。
redis-server /myredis/redis.conf
ouc-13 26926 22709 0 15:30 pts/4 00:00:00 redis-server *:6379
ouc-13 29372 28429 0 15:49 pts/3 00:00:00 grep --color=auto redis
端口号是6379
conf文件有如下内容即可:
port 6379
daemonize yes
logfile "6379.log"
dir /redis-4/data #log和持久化rdb文件文件的目录
redis-cli
命令访问启动好的Redisredis-cli -h 127.0.0.1 -p 端口号
redis-cli shutdown
l 指定端口关闭 redis-cli -p 端口号 shutdown
如何启动多个redis:
默认配置启动
redis-server
redis-server –-port 6379
redis-server –-port 6380 ……
指定配置文件启动
redis-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 ……
默认连接
redis-cli
连接指定服务器
redis-cli -h 127.0.0.1
redis-cli –port 6379
redis-cli -h 127.0.0.1 –port 6379
daemonize yes
以守护进程方式启动,使用本启动方式, redis将以服务的形式存在,日志将不再打印到命令窗口中
port 6379
设定当前服务启动端口号
dir “/自定义目录/redis/data“
设定当前服务文件保存位置,包含日志文件、持久化文件(后面详细讲解)等
logfile "6***.log“
设定日志文件名,便于查阅
Redis默认创建16个库,每个库对应一个下标,从0开始。通过客户端连接后默认进入到0 号库,推荐只使用0号库.
使用命令 select 库的下标
来切换数据库,例如 select 8
统一的密码管理:所有库都是同样密码,要么都OK,要么一个都连不上
多路复用是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。
Memcached 是 多线程 + 锁 ;Redis 是 单线程 + 多路IO复用.
1.阻塞IO, 给女神发一条短信, 说我来找你了, 然后就默默的一直等着女神下楼, 这个期间除了等待你不会做其他事情, 属于备胎做法.
2 非阻塞IO, 给女神发短信, 如果不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会做其他事情, 属于专一做法.
3 IO多路复用, 是找一个宿管大妈来帮你监视下楼的女生, 这个期间你可以些其他的事情. 例如可以顺便看看其他妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们的区别是什么?
3.1 select大妈 每一个女生下楼, select大妈都不知道这个是不是你的女神, 她需要一个一个询问, 并且select大妈能力还有限, 最多一次帮你监视1024个妹子
3.2 poll大妈不限制盯着女生的数量, 只要是经过宿舍楼门口的女生, 都会帮你去问是不是你女神
3.3 epoll大妈不限制盯着女生的数量, 并且也不需要一个一个去问. 那么如何做呢? epoll大妈会为每个进宿舍楼的女生脸上贴上一个大字条,上面写上女生自己的名字, 只要女生下楼了, epoll大妈就知道这个是不是你女神了, 然后大妈再通知你.
上面这些同步IO有一个共同点就是, 当女神走出宿舍门口的时候, 你已经站在宿舍门口等着女神的, 此时你属于阻塞状态
接下来是异步IO的情况
你告诉女神我来了, 然后你就去王者荣耀了, 一直到女神下楼了, 发现找不见你了, 女神再给你打电话通知你, 说我下楼了, 你在哪呢? 这时候你才来到宿舍门口. 此时属于逆袭做法
string String
hash HashMap
list LinkedList
set HashSet
sorted set TressSet
http://www.redis.cn/commands.html
命令 | 说明 |
---|---|
keys * | 查看当前库的所有键 |
exists | 判断某个键是否存在 |
type | 查看键的类型 |
del | 删除某个键 |
expire | 为键值设置过期时间,单位秒 |
ttl | 查看还有多久过期,返回-1表示永不过期,-2表示已过期 |
dbsize | 查看当前数据库中key的数量 |
flushdb | 清空当前库 |
Flushall | 通杀全部库 |
# 设置key有效期
expire key seconds
pexpire key milliseconds
expireat key timestamp
pexpireat key milliseconds-timestamp
# 查询key有效时间 time to live
ttl key # 如果key不存在或者已过期,返回 -2。如果key存在并且没有设置过期时间(永久有效),返回 -1 。整数代表有效秒数
pttl key # 毫秒数
# 切换key从时效性转换为永久性
persist key
查询匹配的key值
keys pattern正则
其他操作
rename key newkey
renamenx key newkey
排序key
sort
sort list1
String是Redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value
String类型是二进制安全的。意味着Redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
String类型是Redis最基本的数据类型,一个Redis中字符串value最多可以是512M
常用操作
m:multiple
为什么有m的操作:发送命令还需要时长
命令 | 说明 |
---|---|
get | 查询对应键值 |
set | 添加键值对 |
append | 将给定的追加到原值的末尾 ,返回的长度 |
strlen | 获取值的长度,不包括\0 |
setnx | 只有在key 不存在时设置key的值 |
incr |
将key中存储的数字值增1 只能对数字值操作,如果为空,新增值为1 |
decr |
将key中存储的数字值减1 只能对数字之操作,如果为空,新增值为-1 |
incrby/decrby 、incrbyfloat key 值 |
将key中存储的数字值增减,自定义步长。值可正可负,但小数得用float。操作具有原子性,命令都是一个一个执行的,所以安全。超过上限或原来不是数会报错。 |
mset |
同时设置一个或多个key-value对 |
mget | 同时获取一个或多个value |
msetnx | 同时设置一个或多个key-value对,当且仅当所有给定的key都不存在 |
getrange <起始位置> <结束位置> | 获得值的范围,双闭区间,类似java中的substring |
setrange <起始位置> | 用覆盖所存储的字符串值,从<起始位置>开始 |
setex |
设置键值的同时,设置过去时间,单位秒。ex是expire。对投票系统等很有效。但重新设置set的值后,时间就失效了 |
psetex key milliseconds value | |
getset | 以新换旧,设置了新值的同时获取旧值 |
数据操作不成功的反馈与数据正常操作之间的差异
string 类型数据操作的注意事项
① 表示运行结果是否成功
(integer) 0 → false 失败
(integer) 1 → true 成功
② 表示运行结果值
(integer) 3 → 3 3个
(integer) 1 → 1 1个
数据未获取到
( nil)等同于null
数据最大存储量
512MB
数值计算最大范围( java中的long的最大值)
9223372036854775807
key的命名规范:
表名:主键名:主键值:字段名
如stu:id:1:class
场景:学生有姓名,学号,班级等信息,想让学生为key,剩下的为值。那么此时值想让称为一个hashmap,这样更方便更改。
新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
需要的存储结构:一个存储空间保存多个键值对数据
hash类型:底层使用哈希表结构实现数据存储
hash存储结构优化
如果field数量较少,存储结构优化为类数组结构
如果field数量较多,存储结构使用HashMap结构
hset key field value #改
hget key field #获取单个字段
hgetall key #获取所有字段
hdel key field1 [field2]
hmset key field1 value1 field2 value2...#设置多个
hmget key field1 field2...#获取多个
hlen key #字段数量
hexists key field #查是否存在指定字段
hkeys key
hvals key
hincrby key field increment
hincrbyfloat key field increment
例子:
127.0.0.1:6379> hset user id 01
(integer) 0
127.0.0.1:6379> hset user name lisi
(integer) 0
127.0.0.1:6379> hset user class 1
(integer) 1
# 即hset可以多句分开设置,不会覆盖
127.0.0.1:6379> hgetall user
1) "id"
2) "01"
3) "name"
4) "lisi"
5) "class"
6) "1"
127.0.0.1:6379> hget user name
"lisi"
127.0.0.1:6379> hdel user class
(integer) 1
127.0.0.1:6379> hgetall user
1) "id"
2) "01"
3) "name"
4) "lisi"
127.0.0.1:6379> hmget user id name
1) "01"
2) "lisi"
127.0.0.1:6379> hexists user id
(integer) 1
127.0.0.1:6379> hexists user age
(integer) 0
127.0.0.1:6379> hkeys user
1) "id"
2) "name"
hash 类型数据操作的注意事项
hash类型应用场景:
淘宝购物车涉及与实现
业务分析:
解决方案
场景2:双11活动日,销售手机充值卡的商家对移动、联通、电信的30元、 50元、 100元商品推出抢购活动,每种商品抢购上限1000张
解决方案
业务场景
string存储对象( json)与hash存储对象
单键多值
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。
它的底层实际是个**双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差**
不可以操作中间的
命令 | 说明 |
---|---|
lpush/rpush | 从左边/右边插入一个或多个值。 |
lpop/rpop | 从左边/右边吐出一个值。 值在键在,值光键亡。 |
rpoplpush | 从列表右边吐出一个值,插到列表左边 |
lrange | 按照索引下标获得元素(从左到右),可以用-1 |
lindex | 按照索引下标获得元素(从左到右) |
llen | 获得列表长度 |
linsert before | 在的后面插入 插入值 |
lrem | 从左边删除n个value(从左到右) |
# 规定时间内获取并移除数据,现在没有没关系,可以等一会
blpop key1 [key2] timeout
brpop key1 [key2] timeout
brpoplpush source destination timeout
业务场景:
微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息
如果取消点赞,移除对应好友信息
# 移除指定数据,中间拿数据,但其实是从左面拿指定value,拿完count个value后结束
lrem key count value
list操作注意事项:
场景2:
twitter、新浪微博、腾讯微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最
近关注的粉丝列在前面
twitter、新浪微博、腾讯微博中个人用户的关注列表需要按照用户的关注顺序进行展示,粉丝列表需要将最
近关注的粉丝列在前面
新闻、资讯类网站如何将最新的新闻或资讯按照发生的时间顺序展示?
企业运营过程中,系统将产生出大量的运营数据,如何保障多台服务器操作日志的统一顺序输出?
解决方案
tips:redis 应用于最新消息展示
Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的
Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。
常用操作
list内部是链表结构,读取比较慢,所以想起set,高效查询。
把前面的hash表的field保留,value去除,即得到了set。
新的存储需求:存储大量的数据,在查询方面提供更高的效率
需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
set类型:与hash存储结构完全相同,仅存储键,不存储值( nil),并且值是不允许重复的
命令 | 说明 |
---|---|
sadd … | 将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。 |
smembers | 取出该集合的所有值。 |
sismember | 判断集合是否为含有该值,有返回1,没有返回0 |
scard | 返回该集合的元素个数。 |
srem … | 删除集合中的某个元素。 |
spop | 随机从该集合中吐出一个值。 |
srandmember | 随机从该集合中取出n个值。 不会从集合中删除 |
sinter | 返回两个集合的交集元素。 |
sunion | 返回两个集合的并集元素。 |
sdiff | 返回两个集合的差集元素。 |
缺点: 用户ID数据冗余
hset | 给集合中的 键赋值 |
---|---|
hget | 从集合 取出 value |
hmset … | 批量设置hash的值 |
hexists key | 查看哈希表 key 中,给定域 field 是否存在。 |
hkeys | 列出该hash集合的所有field |
hvals | 列出该hash集合的所有value |
hincrby | 为哈希表 key 中的域 field 的值加上增量 increment |
hsetnx | 将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 |
扩展:
srandmember key [count] #随机获取集合中指定数量的数据
spop key [count] #随机获取集合中的某个数据并将该数据移除集合
# 求两个集合的交、并、差集
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
# 求两个集合的交、并、差集并存储到指定集合中
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
# 将指定数据从原始集合中移动到目标集合中
smove source destination member
场景:
每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现?
业务分析
redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐,大V推荐等
场景2:
脉脉为了促进用户间的交流,保障业务成单率的提升,需要让每位用户拥有大量的好友,事实上职场新人不具有更多的职场好友,如何快速为用户积累更多的好友?
新浪微博为了增加用户热度,提高用户留存性,需要微博用户在关注更多的人,以此获得更多的信息或热门话题,如何提高用户关注他人的总量?
QQ新用户入网年龄越来越低,这些用户的朋友圈交际圈非常小,往往集中在一所学校甚至一个班级中,如何帮助用户快速积累好友用户带来更多的活跃度?
微信公众号是微信信息流通的渠道之一,增加用户关注的公众号成为提高用户活跃度的一种方式,如何帮助用户积累更多关注的公众号?
美团外卖为了提升成单量,必须帮助用户挖掘美食需求,如何推荐给用户最适合自己的美食?
Tips 9:
redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
显示共同关注(一度)
显示共同好友(一度)
由用户A出发,获取到好友用户B的好友信息列表(一度)
由用户A出发,获取到好友用户B的购物清单列表(二度)
由用户A出发,获取到好友用户B的游戏充值列表(二度)
set 类型数据操作的注意事项
场景3:
集团公司共具有12000名员工,内部OA系统中具有700多个角色, 3000多个业务操作, 23000多种数据,每位员工具有一个或多个角色,如何快速进行业务操作的权限校验?
场景4:
公司对旗下新的网站做推广,统计网站的PV(访问量) ,UV(独立访客) ,IP(独立IP)。
PV:网站被访问次数,可通过刷新页面提高访问量
UV:网站被不同用户访问的次数,可通过cookie统计访问量,相同用户切换IP地址, UV不变
IP:网站被不同IP地址访问的总次数,可通过IP地址统计访问量,相同IP不同用户访问, IP不变
解决方案
利用set集合的数据去重特征,记录各种访问数据
建立string类型数据,利用incr统计日访问量( PV)
建立set模型,记录不同cookie数量( UV)
建立set模型,记录不同IP数量( IP)
tips:redis 应用于同类型数据的快速去重
场景5:
黑名单
资讯类信息类网站追求高访问量,但是由于其信息的价值,往往容易被不法分子利用,通过爬虫技术,快速获取信息,个别特种行业网站信息通过爬虫获取分析后,可以转换成商业机密进行出售。例如第三方火车票、机票、酒店刷票代购软件,电商刷评论、刷好评。
同时爬虫带来的伪流量也会给经营者带来错觉,产生错误的决策,有效避免网站被爬虫反复爬取成为每个网站都要考虑的基本问题。在基于技术层面区分出爬虫用户后,需要将此类用户进行有效的屏蔽,这就是黑名单的典型应用。
ps:不是说爬虫一定做摧毁性的工作,有些小型网站需要爬虫为其带来一些流量。
白名单
对于安全性更高的应用访问,仅仅靠黑名单是不能解决安全问题的,此时需要设定可访问的用户群体,
依赖白名单做更为苛刻的访问验证。
解决方案
基于经营战略设定问题用户发现、鉴别规则
周期性更新满足规则的用户黑名单,加入set集合
用户行为信息达到后与黑名单进行比对,确认行为去向
黑名单过滤IP地址:应用于开放游客访问权限的信息源
黑名单过滤设备信息:应用于限定访问设备的信息源
黑名单过滤用户:应用于基于访问权限的信息源
tips:redis 应用于基于黑名单与白名单设定的服务控制
新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
需要的存储结构:新的存储模型,可以保存可排序的数据
sorted_set类型:在set的存储结构基础上添加可排序字段
score不是数据。
Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score) ,这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。
因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。
常用操作
命令 | 说明 |
---|---|
zadd … | 将一个或多个 member 元素及其 score 值加入到有序集 key 当中。注意分在前,值在后。 |
zrange [WITHSCORES] | 返回有序集 key 中,下标在 之间的元素 带WITHSCORES,可以让分数一起和值返回到结果集。 |
zrangebyscore key min max [withscores] [limit offset count] | 返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。 |
zrevrangebyscore key max min [withscores] [limit offset count] | 同上,改为从大到小排列。 |
zincrby | 为元素的score加上增量 |
zrem | 删除该集合下,指定值的元素 |
zremrangebyrank/zremrangebyscore key start stop【或min max】 | 条件删除 |
zcount | 统计该集合,分数区间内的元素个数 |
zrank | 返回该值在集合中的排名,从0开始。 |
获取集合数据总量
zcard key
zcount key min max
集合交、并操作
zinterstore destination numkeys key [key ...]
zunionstore destination numkeys key [key ...]
127.0.0.1:6379> zadd test 94 zs
(integer) 1
127.0.0.1:6379> zadd test 100 ls
(integer) 1
127.0.0.1:6379> zadd test 60 ww
(integer) 1
127.0.0.1:6379> zadd test 47 zl
(integer) 1
127.0.0.1:6379> zrange test 0 -1
1) "zl"
2) "ww"
3) "zs"
4) "ls"
127.0.0.1:6379> zrange test 0 -1 withscores
1) "zl"
2) "47"
3) "ww"
4) "60"
5) "zs"
6) "94"
7) "ls"
8) "100"
场景1:
票选广东十大杰出青年,各类综艺选秀海选投票
各类资源网站TOP10(电影,歌曲,文档,电商,游戏等)
聊天室活跃度统计
游戏好友亲密度
业务分析
为所有参与排名的资源建立排序依据
获取数据对应的索引(排名)
zrank key member
zrevrank key member
score值获取与修改
zscore key member
zincrby key increment member
redis 应用于计数器组合排序功能对应的排名
注意事项:
score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据, score值将被反复覆盖,保留最后一次修改的结果
场景2:
基础服务+增值服务类网站会设定各位会员的试用,让用户充分体验会员优势。例如观影试用VIP、游戏VIP体验、云盘下载体验VIP、数据查看体验VIP。当VIP体验到期后,如果有效管理此类信息。即便对于正式VIP用户也存在对应的管理方式。
网站会定期开启投票、讨论,限时进行,逾期作废。如何有效管理此类过期信息。
解决方案:
对于基于时间线限定的任务处理,将处理时间记录为score值,利用排序功能区分处理的先后顺序
记录下一个要处理的时间,当到期后处理对应任务,移除redis中的记录,并记录下一个要处理的时间
当新任务加入时,判定并更新当前下一个要处理的任务时间
为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_set。例如1小时内, 1天内,周内,月内,季内,年度等,操作时逐级提升,将即将操作的若干个任务纳入到1小时内处理的队列中
time # 获取当前系统时间
tips: redis 应用于定时任务执行顺序管理或任务过期管理
场景3:
任务/消息权重设定应用
当任务或者消息待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理。
解决方案:
对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可多条件任务权重设定
如果权重条件过多时,需要对排序score值进行处理,保障score值能够兼容2条件或者多条件,例如外贸订单优先于国内订单,总裁订单优先于员工订单,经理订单优先于员工订单
因score长度受限,需要对数据进行截断处理,尤其是时间设置为小时或分钟级即可(折算后)
先设定订单类别,后设定订单发起角色类别,整体score长度必须是统一的,不足位补0。第一排序规则首位不得是0
例如外贸101,国内102,经理004,员工008。
员工下的外贸单score值为101008(优先)
经理下的国内单score值为102004
key的重复问题:
key是由程序员定义的
解决方案:
redis为每个服务提供有16个数据库,编号从0到15
select index #切换数据库
quit
ping #返回PONG代表连通
echo
move key db #移动到另外一个库
dbsize
flushdb # 删除掉当前库数据
flushall # 删除掉所有库
计量单位说明,大小写不敏感
include
类似jsp中的include,多实例的情况可以把公用的配置文件提取出来
一个空闲的客户端维持多少秒会关闭,0为永不关闭。
对访问客户端的一种心跳检测,每个n秒检测一次,官方推荐设置为60秒
是否为后台进程
存放pid文件的位置,每个实例会产生一个不同的pid文件
四个级别根据使用阶段来选择,生产环境选择notice 或者warning
日志文件名称
是否将Redis日志输送到linux系统日志服务中
日志的标志
输出日志的设备
设定库的数量 默认16
在命令行中设置密码
config get requirepass
config set requirepass "123456"
config get requirepass
auth 123456
get kl
最大客户端连接数
设置Redis可以使用的内存量。一旦到达内存使用上限,Redis将会试图移除内部数据,移除规则可以通过maxmemory-policy来指定。如果Redis无法根据移除规则来移除内存中的数据,或者设置了“不允许移除”,
那么Redis则会针对那些需要申请内存的指令返回错误信息,比如SET、LPUSH等。
设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小。
一般设置3到7的数字,数值越小样本越不准确,但是性能消耗也越小。
可视化客户端:Redis Desktop Manager
Jedis jedis = new Jedis("localhost",6379);
jedis.set("name","lisi");//方法名和原来的命令一致
jedis.get("name");
jedis.close();
//http://xetorthio.github.io/jedis/
Commons-pool-1.6.jar
Jedis-2.1.0.jar
maven为jedis,可选junit
package com.itheima;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.List;
import java.util.Map;
public class JedisTest {
@Test
public void testJedis(){
//1.连接redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//2.操作redis
// jedis.set("name","itheima");
String name = jedis.get("name");
System.out.println(name);
//3.关闭连接
jedis.close();
}
@Test
public void testList(){
//1.连接redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//2.操作redis
jedis.lpush("list1","a","b","c");
jedis.rpush("list1","x");
List<String> list1 = jedis.lrange("list1", 0, -1);
for(String s : list1){
System.out.println(s);
}
System.out.println(jedis.llen("list1"));
System.out.println();
//3.关闭连接
jedis.close();
}
@Test
public void testHash(){
//1.连接redis
Jedis jedis = new Jedis("127.0.0.1", 6379);
//2.操作redis
jedis.hset("hash1","a1","b1");
jedis.hset("hash1","a2","a2");
jedis.hset("hash1","a3","b3");
Map<String, String> hash1 = jedis.hgetAll("hash1");
System.out.println(hash1);
System.out.println(jedis.hlen("hash1"));
System.out.println();
//3.关闭连接
jedis.close();
}
}
① 设定一个服务方法,用于模拟实际业务调用的服务,内部采用打印模拟调用
② 在业务调用前服务调用控制单元,内部使用redis进行控制,参照之前的方案
③ 对调用超限使用异常进行控制,异常处理设定为打印提示信息
④ 主程序启动3个线程,分别表示3种不同用户的调用
package com.itheima;
import com.itheima.util.JedisUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;
public class Service {
private String id;
private int num;
public Service(String id,int num){
this.id = id;
this.num = num;
}
//控制单元
public void service(){
// Jedis jedis = new Jedis("127.0.0.1",6379);
Jedis jedis = JedisUtils.getJedis();
String value = jedis.get("compid:"+id);
//判断该值是否存在
try{
if(value == null){
//不存在,创建该值
jedis.setex("compid:"+id,5,Long.MAX_VALUE-num+"");
}else{
//存在,自增,调用业务
Long val = jedis.incr("compid:"+id);
business(id,num-(Long.MAX_VALUE-val));
}
}catch (JedisDataException e){
System.out.println("使用已经到达次数上限,请升级会员级别");
return;
}finally{
jedis.close();
}
}
//业务操作
public void business(String id,Long val){
System.out.println("用户:"+id+" 业务操作执行第"+val+"次");
}
}
class MyThread extends Thread{
Service sc ;
public MyThread(String id,int num){
sc = new Service(id,num);
}
public void run(){
while(true){
sc.service();
try {
Thread.sleep(300L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Main{
public static void main(String[] args) {
MyThread mt1 = new MyThread("初级用户",10);
MyThread mt2 = new MyThread("高级用户",30);
mt1.start();
mt2.start();
}
}
package com.itheima.util;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.ResourceBundle;
public class JedisUtils {
private static JedisPool jp = null;
private static String host = null;
private static int port;
private static int maxTotal;
private static int maxIdle;
static {//定义放到外面
ResourceBundle rb = ResourceBundle.getBundle("redis");//redis.properties
host = rb.getString("redis.host");
port = Integer.parseInt(rb.getString("redis.port"));
maxTotal = Integer.parseInt(rb.getString("redis.maxTotal"));
maxIdle = Integer.parseInt(rb.getString("redis.maxIdle"));
JedisPoolConfig jpc = new JedisPoolConfig();
jpc.setMaxTotal(maxTotal);
jpc.setMaxIdle(maxIdle);
jp = new JedisPool(jpc,host,port);
}
public static Jedis getJedis(){
return jp.getResource();
}
public static void main(String[] args){
JedisUtils.getJedis();
}
}
// 原方法//这种每次都要拿一个连接池,没有效率,所以放到static代码块中
public static Jedis getJedis() {
JedisPoolConfig jpc=new JedisPoolConfig();
jpc.setMaxTotal(30);
jpc.setMaxIdle(10);
String host="127.0.0.1";
int port=6379;
JedisPool jp=new JedisPool(jpc,host,port);
return jp.getResource();
}
redis.properties
redis.host=127.0.0.1
redis.port=6379
redis.maxTotal=30
redis.maxIdle=10
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
Redis事务的主要作用就是串联多个命令防止别的命令插队
基本操作:
multi # 开启事务
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
exec # 执行事务
作用:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行
discard # 取消事务
作用:终止当前事务的定义,发生在multi之后, exec之前
事务的注意事项
手动进行事务回滚
记录操作过程中被影响的数据之前的状态
单数据: string
多数据: hash、 list、 set、 zset
设置指令恢复所有的被修改的项
单数据:直接set(注意周边属性,例如时效)
多数据:修改对应值或整体克隆复制
锁:
基于特定条件的事务执行
业务场景
天猫双11热卖过程中,对已经售罄的货物追加补货, 4个业务员都有权限进行补货。补货的操作可能是一系列的操作,牵扯到多个连续操作,如何保障不会重复操作?
业务分析:
多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作
在操作之前锁定要操作的数据,一旦发生变化,终止当前操作
watch的变量发生变化了,那么事务将不会执行。例如多终端修改。
解决方案
# 对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行 # 得写在multi外
watch key1 [key2……]
# 取消对【所有】 key 的监视
unwatch
Tips 18:redis 应用基于状态控制的批量任务执行
业务场景
天猫双11热卖过程中,对已经售罄的货物追加补货,且补货完成。客户购买热情高涨, 3秒内将所有商品购买完毕。本次补货已经将库存全部清空,如何避免最后一件商品不被多人同时购买? 【超卖问题】
业务分析
使用watch监控一个key有没有改变已经不能解决问题,此处要监控的是具体数据
虽然redis是单线程的,但是多个客户端对同一数据同时进行操作时,如何避免不被同时修改?
watch的值是不停在改变的,1件的时候一个人买到其他人就消掉?他是监控一个值能不能变的,而不是监控其他人能不能改这个值的,
解决方案:
setnx lock-keyname value # 设置一个公共锁
#value值不重要,比如我们要锁name这个变量,那么就是setnx lock-name true
利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
对于返回设置失败的,不具有控制权,排队或等待
操作完毕通过del操作释放锁
注意:上述解决方案是一种设计概念,依赖规范保障,具有风险性
Tips 19:
redis 应用基于分布式锁对应的场景控制
set num 10
setnx lock-num 1
incrby num -1
del lock-num
如果别的终端在这个过程中页想加锁,别的终端是加不上的
。到时他得重新输入命令他再锁
业务场景:死锁
依赖分布式锁的机制,某个用户操作时对应客户端宕机,且此时已经获取到锁。如何解决?
业务分析
由于锁操作由用户控制加锁解锁,必定会存在加锁后未解锁的风险
需要解锁操作不能仅依赖用户控制,系统级别要给出对应的保底处理方案
setnx lock-name 1
此时停电了,没有释放锁。
所以得给锁加失效时间
解决方案:
setnx lock-name 1
expire lock-name 20 #20s
由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,至到输入Exec后,Redis会将之前的命令队列中的命令依次执行。
组队的过程中可以通过discard来放弃组队。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁
乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH 命令之后, EXEC 命令或 DISCARD 命令先被执行了的话,那么就不需要再执行 UNWATCH 了。
l 单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
l 没有隔离级别的概念
队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题
l 不保证原子性
Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚
l CentOS6 默认安装 ,CentOS7需要手动安装
l 联网: yum install httpd-tools
无网络: 进入cd /run/media/root/CentOS 7 x86_64/Packages
顺序安装
apr-1.4.8-3.el7.x86_64.rpm
apr-util-1.5.2-6.el7.x86_64.rpm
httpd-tools-2.4.6-67.el7.centos.x86_64.rpm
l ab –n 请求数 -c 并发数 -p 指定请求数据文件
-T “application/x-www-form-urlencoded” 测试的请求
超卖问题
请求超时问题
节省每次连接redis服务带来的消耗,把连接好的实例反复利用
连接池参数:
MaxTotal:控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;如果赋值为-1,则表示不限制;如果pool已经分配了MaxTotal个jedis实例,则此时pool的状态为exhausted。
maxIdle:控制一个pool最多有多少个状态为idle(空闲)的jedis实例;
MaxWaitMillis:表示当borrow一个jedis实例时,最大的等待毫秒数,如果超过等待时间,则直接抛JedisConnectionException;
testOnBorrow:获得一个jedis实例的时候是否检查连接可用性(ping());如果为true,则得到的jedis实例均是可用的;
l LUA脚本
Lua 是一个小巧的脚本语言,Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,Lua并没有提供强大的库,一个完整的Lua解释器不过200k,所以Lua不适合作为开发独立应用程序的语言,而是作为嵌入式脚本语言。
很多应用程序、游戏使用LUA作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。这其中包括魔兽争霸地图、魔兽世界、博德之门、愤怒的小鸟等众多游戏插件或外挂
l LUA脚本在Redis中的优势
将复杂的或者多步的redis操作,写为一个脚本,一次提交给redis执行,减少反复连接redis的次数。提升性能。
LUA脚本是类似redis事务,有一定的原子性,不会被其他命令插队,可以完成一些redis事务性的操作
但是注意redis的lua脚本功能,只有在2.6以上的版本才可以使用。
l 利用lua脚本淘汰用户,解决超卖问题。
Redis提供了2个不同形式的持久化方式 RDB 和 AOF
save命令:
save #手动执行一次保存操作
save指令操作配置:需要在conf文件中配置
要点:
RDB工作原理:
如图,4个客户端分别输入指令,redis是单线程的,所以需要排序。
有个问题是如果save是第三个,save执行时间很长,所以后面的get需要等待很长时间。就会阻塞。所以线上不建议使用save指令,会很拖慢性能。
注意: save指令的执行会阻塞当前Redis服务器, 直到当前RDB过程完成为止, 有可能会造成长时间阻塞, 线上环境不建议使用。
bgsave指令:手动启动后台保存操作,但不是立即执行
bgsave # 后台save
bgsave原理:
发生bgsave后,返回一个消息,但并没有真正执行,redis会抽空去调用fork函数生成子进程,这个子进程不参与redis序列的命令操作,单独用一个子进程操作save,让子进程去创建rgb文件。做完之后返回消息告诉控制台。可以在日志文件中查看有backgroud saving terminated with succeess。
注意: bgsave命令是针对save阻塞问题做的优化。 Redis内部所有涉及到RDB操作都采用bgsave的方式, save命令可以放弃使用
bgsave相关配置:
dbfilename dump.rdb
dir
rdbcompression yes
rdbchecksum yes
stop-writes-on-bgsave-error yes
说明:后台存储过程中如果出现错误现象,是否停止保存操作
经验:通常默认为开启状态
但是save和bgsave都需要手动保存,难免疏忽,使用需要自动执行。
# 在配置文件中设置
save second changes
作用:满足限定时间范围内key的变化数量达到指定数量即进行持久化。即时间片内变化大才自动保存,是快照的思想。
参数
second:监控时间范围
changes:监控key的变化量
位置:在conf文件中进行配置
示例:
save 900 1 # 900s内变化一个就保存
save 300 10
save 60 10000
save配置相关配置:
dbfilename dump.rdb
dir
rdbcompression yes
rdbchecksum yes
配置文件中save原理
发送3条指令,每条都会返回个结果,通过结果判断这条指令算不算影响数量。不进行数据比对指的是两个set就都算
注意:
save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系
save配置启动后执行的是bgsave操作
RDB三种保存方式对比:
方式 | save指令 | bgsave指令 | save配置 |
---|---|---|---|
读写 | 同步 | 异步 | |
阻塞客户端指令 | 是 | 否 | |
额外内存消耗 | 否 | 是 | |
启动新进程 | 否 | 是 |
RDB特殊启动形式:
默认情况下执行shutdown命令时, 自动执行
bgsave(如果没有开启AOF持久化功能)
RDB优点:
RDB是一个紧凑压缩的二进制文件, 存储效率较高
RDB内部存储的是redis在某个时间点的数据快照, 非常适合用于数据备份,全量复制等场景
RDB恢复数据的速度要比AOF快很多
应用:服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复。
RGB缺点:
RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据
bgsave指令每次运行要执行fork操作创建子进程, 要牺牲掉一些性能
Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各版本服务之间数据格式无法兼容现象
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
备份是如何执行的
Redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后一次持久化后的数据可能丢失。
在Linux程序中,fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,Linux中引入了“写时复制技术”,一般情况父进程和子进程会共用同一段物理内存,只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。
在redis.conf中配置文件名称,默认为dump.rdb
默认为Redis启动时命令行所在的目录下,也可以修改
save: 只管保存,其它不管,全部阻塞
bgsave:按照保存策略自动保存
l stop-writes-on-bgsave-error yes
当Redis无法写入磁盘的话,直接关掉Redis的写操作
l rdbcompression yes
进行rdb保存时,将文件压缩
l rdbchecksum yes
在存储快照后,还可以让Redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
l 备份:先通过config get dir 查询rdb文件的目录 , 将*.rdb的文件拷贝到别的地方
l 恢复: 关闭Redis,把备份的文件拷贝到工作目录下,启动redis,备份数据会直接加载。
l 优点: 节省磁盘空间,恢复速度快.
l 缺点: 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就
会丢失最后一次快照后的所有修改
RDB弊端:
存储数据量较大,效率较低
基于快照思想,每次读写都是全部数据,当数据量巨大时,效率非常低
大数据量下的IO性能较低
基于fork创建子进程,内存产生额外消耗
宕机带来的数据丢失风险
解决思路:
不写全数据,仅记录部分数据
降低区分数据是否改变的难度,改记录数据为记录操作过程
对所有操作均进行记录,排除丢失数据的风险
什么是AOF:
AOF写数据过程:
AOF写数据三种策略(appendfsync)
AOF功能开启:
配置
appendonly yes|no
作用:是否开启AOF持久化功能,默认:为不开启状态
配置:appendfsync always|everysec|no
- 作用:AOF写数据策略
配置
appendfilename filename
作用:AOF持久化文件名,默认文件名未appendonly.aof,建议配置为appendonly-端口号.aof
配置
dir
作用
AOF持久化文件保存路径,与RDB持久化文件保持一致即可
AOF写数据遇到的问题:
AOF重写
随着命令不断写入AOF,文件会越来越大,为了解决这个问题, Redis引入了AOF重写机制压缩文件体积。 AOF文件重
写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。 简单说就是将对同一个数据的若干个条命令执行结
果转化成最终结果数据对应的指令进行记录。
AOF重写作用
AOF重写规则
AOF重写方式:
手动重写
命令行输入bgrewriteaof
自动重写
配置文件里写
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
RDB启动方式 —— bgsave指令工作原理
AOF手动重写 —— bgrewriteaof指令工作原理
AOF自动重写方式:
自动重写触发条件设置
auto-aof-rewrite-min-size 文件大小size
auto-aof-rewrite-percentage 比例percent
自动重写触发比对参数( 运行指令info Persistence获取具体信息 )
aof_current_size
aof_base_size
自动重写触发条件
AOF工作流程:
Always时,Set正常执行,另开一个子进程进行重写。
Sec时会先放到缓存区。
AOF重写流程:
AOF缓冲区同步文件策略, 由参数appendfsync控制
系统调用write和fsync说明:
write操作会触发延迟写( delayed write) 机制, Linux在内核提供页缓冲区用来提高硬盘IO性能。 write操作在写入系统缓冲区后直接返回。 同步硬盘操作依赖于系统调度机制, 列如:缓冲区页空间写满或达到特定时间周期。 同步文件之前, 如果此时系统故障宕机, 缓冲区内数据将丢失。
fsync针对单个文件操作( 比如AOF文件) , 做强制硬盘同步, fsync将阻塞知道写入硬盘完成后返回, 保证了数据持久化。
除了write、 fsync、 Linx还提供了sync、 fdatasync操作, 具体API说明参见:
AOF文件的保存路径,同RDB的路径一致
AOF和RDB同时开启,redis听谁的?
AOF文件故障备份
AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载
如遇到AOF文件损坏,可通过
redis-check-aof --fix appendonly.aof 进行恢复
l 始终同步,每次Redis的写入都会立刻记入日志
l 每秒同步,每秒记入日志一次,如果宕机,本秒的数据可能丢失。
l 把不主动进行同步,把同步时机交给操作系统。
l AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制,当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩,只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof。
l Redis如何实现重写
AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),遍历新进程的内存中数据,每条记录有一条的Set语句。重写aof文件的操作,并没有读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点类似。
l 何时重写
重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。
系统载入时或者上次重写完毕时,Redis会记录此时AOF大小,设为base_size,如果Redis的AOF当前大小>= base_size +base_size*100% (默认)且当前大小>=64mb(默认)的情况下,Redis会对AOF进行重写。
l 优点:
备份机制更稳健,丢失数据概率更低。
可读的日志文本,通过操作AOF稳健,可以处理误操作。
l 缺点:
比起RDB占用更多的磁盘空间
恢复备份速度要慢
每次读写都同步的话,有一定的性能压力。
持久化方式 | RDB | AOF |
---|---|---|
占用存储空间 | 小(数据级:压缩) | 大(指令级:重写) |
存储速度 | 慢 | 快 |
恢复速度 | 快 | 慢 |
数据安全性 | 会丢失数据 | 依据策略决定 |
资源消耗 | 高/重量级 | 低/轻量级 |
启动优先级 | 低 | 高 |
对数据非常敏感, 建议使用默认的AOF持久化方案
AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能, 当出
现问题时,最多丢失0-1秒内的数据。
注意:由于AOF文件存储体积较大,且恢复速度较慢
数据呈现阶段有效性,建议使用RDB持久化方案
数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段
点数据恢复通常采用RDB方案
注意:利用RDB实现紧凑的数据持久化会使Redis降的很低,慎重总结:
综合比对
RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
如不能承受数分钟以内的数据丢失,对业务数据非常敏感, 选用AOF
如能承受数分钟以内的数据丢失, 且追求大数据集的恢复速度, 选用RDB
灾难恢复选用RDB
双保险策略, 同时开启 RDB 和 AOF, 重启后, Redis优先使用 AOF 来恢复数据,降低丢失数据的量
l 官方推荐两个都启用。
l 如果对数据不敏感,可以选单独用RDB
l 不建议单独用 AOF,因为可能会出现Bug。
l 如果只是做纯内存缓存,可以都不用
Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态
过期的数据真的被删除了吗?扔垃圾我们往往都是过会再扔。等CPU空闲时候再处理扔。
这四种操作会给key设置一个过期时间,这个过期时间存放在expires区域里。右面的1359…是一个系统时间点
数据删除策略的目标:在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或内存泄露
创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作。此时存储空间的东西也删除了expires空间的内容也删除了。
优点:节约内存,到时就删除,快速释放掉不必要的内存占用
缺点: CPU压力很大,无论CPU此时负载量多高,均占用CPU去进行删除,会影响redis服务器响应时间和指令吞吐量
总结:用处理器性能换取存储空间(拿时间换空间)
数据到达过期时间,不做处理(此时还在expires区里存在)。等下次访问该数据时
优点:节约CPU性能,发现必须删除的时候才删除
缺点:内存压力很大,出现长期占用内存的数据
总结:用存储空间换取处理器性能(拿时间换空间)
内部:有个expireIfNeeded()函数,调用获取函数时(获取值),均会执行这个操作。
两种方案都走极端,有没有折中方案?
每个库都有一个expire区,expire[0]…expire[15]
Redis启动服务器初始化时,读取配置server.hz的值,默认为10。(通过info server
查询),该值代表每秒对16个库整体进行的查询次数。
每秒钟执行server.hz(10)次==serverCron()==对服务器进行定时轮询。其中会调用:==databaseCron()==继续对每个库进行轮询,该函数会调用:==activeExporeCycle()==对变量进行检查
activeExpireCycle()对每个expires[0]里的变量逐一进行检测,每次执行250ms/server.hz
对某个expires[0]检测时,随机挑选W个key检测
那么下次轮询从几号库开始查询呢?:参数current_db用于记录activeExpireCycle() 进入哪个expires[] 执行。如果activeExpireCycle()执行时间到期,下次从current_db继续向下执行。取值0-15?
定期删除总结:
周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
特点1: CPU性能占用设置有峰值,检测频度可自定义设置
特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
总结:周期性抽查存储空间(随机抽查,重点抽查)
\1. 定时删除
节约内存,无占用 不分时段占用CPU资源,频度高 拿时间换空间
\2. 惰性删除
内存占用严重 延时执行, CPU利用率高 拿空间换时间
\3. 定期删除
内存定期随机清理 每秒花费固定的CPU资源维护内存 随机抽查, 重点抽查
redis里用后两种
新数据进入检测:当新数据进入redis时,如果内存不足怎么办?expire控制的是过期数据,如果都不会过期怎么办?
Redis使用内存存储数据,在执行每一个命令前,会调用==freeMemoryIfNeeded()==检测内存是否充足。如果内存不满足新加入数据的最低存储要求, redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法(临时淘汰)。
注意:逐出算法不一定肯定成功:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息:
(error) OOM command not allowed when used memory >‘maxmemory’
最大可使用内存:maxmemory
占用物理内存的比例,默认值为0,表示不限制,全用掉内存。生产环境中根据需求设定,通常设置在50%以上。
每次选取待删除数据的个数:maxmemory-samples
选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
删除策略,删除哪个:maxmemory-policy
达到最大内存后的,对被挑选出来的数据进行删除的策略
检测易失数据(可能会过期的数据集server.db[i].expires )
① volatile-lru:挑选最近最少使用的数据淘汰。Least Recently Used
② volatile-lfu:挑选最近使用次数最少的数据淘汰。Least Frequently Used
③ volatile-ttl:挑选将要过期的数据淘汰
④ volatile-random:任意选择数据淘汰
检测当前库全库数据(所有数据集server.db[i].dict )
⑤ allkeys-lru:挑选最近最少使用的数据淘汰
⑥ allkeys-lfu:挑选最近使用次数最少的数据淘汰
⑦ allkeys-random:任意选择数据淘汰
放弃数据驱逐
⑧ no-enviction(驱逐):禁止驱逐数据( redis4.0中默认策略),会引发错误OOM( Out Of Memory)
如何配置:在配置文件中:
maxmemory-policy volatile-lru
数据逐出策略配置依据:
使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置
# -----服务器端设置-----
daemonize yes|no # 设置服务器以守护进程的方式运行
bind 127.0.0.1 # 绑定主机地址,如果不绑别人也能访问
port 6379 # 设置服务器端口号
databases 16 # 设置数据库数量
# -----日志配置-----
loglevel debug|verbose|notice|warning # 设置服务器以指定日志记录级别
logfile 端口号.log # 日志记录文件名
# -----客户端配置-----
maxclients 0 # 设置同一时间最大客户端连接数,默认无限制。当客户端连接到达上限, Redis会关闭新的连接
# 注意:日志级别开发期设置为verbose即可,生产环境中配置为notice,简化日志输出量,降低写日志IO的频度
timeout 300 # 客户端闲置等待最大时长,达到最大值后关闭连接。如需关闭该功能,设置为 0
多服务器快捷配置:
导入并加载指定配置文件信息,用于快速创建redis公共配置较多的redis实例配置文件,便于维护
include /path/server-端口号.conf
相当于继承
电影网站是有资源常年没有人浏览,可以删除。年度浏览最低起,月浏览量最低。我们用计算机上最小单位bit存储状态。用string
Bitmaps类型的基础操作:
setbit 20200606 0 1
,就代表这个字符串的第0位为1Bitmaps类型的扩展操作
业务场景
电影网站
统计每天某一部电影是否被点播
统计每天有多少部电影被点播
统计每周/月/年有多少部电影被点播
统计年度哪部电影没有被点播
第二列为每日的浏览状态,那么进行或操作的第3列就是每月浏览状态
Bitmaps类型的扩展操作
对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中
bitop or destKey key1 [key2…]。如bitop or 202006 20200606 20200607
and:交
or:并
not:非
xor:异或
统计指定key中1的数量
bitcount key [start end]
Tips 21:
redis 应用于信息状态统计
用处:统计独立UV
原始方案: set
存储每个用户的id(字符串)
改进方案: Bitmaps
存储每个用户状态( bit)
全新的方案: Hyperloglog
基数:基数是数据集去重后元素个数,即set
HyperLogLog 是用来做基数统计的,运用了LogLog的算法
HyperLogLog类型的基本操作
添加数据
pfadd key element [element …]
统计数据
pfcount key [key …]
合并数据
pfmerge destkey sourcekey [sourcekey…]
Tips 22:
redis 应用于独立信息统计
相关说明
用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
核心是基数估算算法,最终数值存在一定误差
误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少
GEO类型的基本操作
添加坐标点
geoadd key longitude latitude member [longitude latitude member …]
获取坐标点
geopos key member [member …]
计算坐标点距离
geodist key member1 member2 [unit]
GEO类型的基本操作
添加坐标点
georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
获取坐标点
georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]
计算经纬度
geohash key member [member …]
Tips 23:
redis 应用于地理位置计算
高并发、高性能、高可用
单机redis的风险与问题
问题1.机器故障
现象: 硬盘故障、系统崩溃
本质: 数据丢失,很可能对业务造成灾难性打击
结论: 基本上会放弃使用redis.
问题2.容量瓶颈
现象:内存不足,从16G升级到64G,从64G升级到128G,无限升级内存
本质:穷,硬件条件跟不上
结论:放弃使用redis
结论:
为了避免单点Redis服务器故障,准备多台服务器,互相连通。 将数据复制多个副本保存在不同的服务器上, 连接在一起, 并保证数据是同步的。 即使有其中一台服务器宕机,其他服务器依然可以继续提供服务,实现Redis的高可用, 同时实现数据冗余备份
多台服务器连接方案
收集数据(写)
提供数据(读) 提供数据(读) 提供数据(读)
同步数据
master
slave
提供数据方: master
主服务器,主节点,主库
主客户端
接收数据方: slave
从服务器,从节点,从库
从客户端
需要解决的问题:数据同步
核心工作:master的数据复制到slave中
主从复制:即将master中的数据即时、有效的复制到slave中
特征:一个master可以拥有多个slave,一个slave只对应一个master。Master以写为主,Slave以读为主。
master和slave是个相对的概念,下面的slave还可以当做下一级的master
职责:
主从复制的作用:
读写分离: master写、 slave读,提高服务器的读写负载能力
负载均衡: 基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
高可用基石: 基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
主从复制过程大体可以分为3个阶段
建立连接阶段(即准备阶段)
数据同步阶段
命令传播阶段
建立slave到master的连接,使master能够识别slave, 并保存slave端口号
建立连接阶段工作流程
步骤1:设置master的地址和端口,连接成功就保存master信息
步骤2:建立socket连接,以后通过这个通道传数据
步骤3:发送ping命令(定时器任务),周期性地向master发生指令,验证master还在不在。master返回pong
步骤4:身份验证
步骤5:发送slave端口信息,这样master保存了slave,回头找slave就简单了
至此,主从连接成功!
状态:
slave:保存master的地址与端口
master:保存slave的端口
总体:之间创建了连接的socket
方式一:客户端发送命令 slaveof
方式二:启动服务器参数 :启动redis时在命令行后面加上参数:redis-server conf文件 -slaveof
。(此时在前台启动server能看到slave正在连接的一些信息)
方式三:服务器配置(主流方式),在配置文件里添加:slaveof
slave系统信息
master_link_down_since_seconds
masterhost
masterport
master系统信息
slave_listening_port(多个)
客户端发送命令:slaveof no one
说明:
slave断开连接后,不会删除已有数据,只是不再接受master发送的数据
master客户端发送命令设置密码requirepass
master配置文件设置密码
config set requirepass
config get requirepass
slave客户端发送命令设置密码 auth
slave配置文件设置密码 masterauth
slave启动服务器设置密 redis-server –a
思想:在slave初次连接master后,复制master中的所有数据到slave,将slave的数据库状态更新成master当前的数据库状态 。接下来就是只复制更改的部分了。
数据同步阶段工作流程:
步骤1: 请求同步数据
步骤2: 创建RDB同步数据
步骤3: 恢复RDB同步数据
步骤4: 请求部分同步数据
步骤5: 恢复部分同步数据
达到状态:
slave:具有master端全部数据,包含RDB过程接收的数据
master:保存slave当前数据同步的位置
注意:上面的缓冲区只在部分复制时才起作用,他里面是AOF指令,而不是数据快照。
\1. 如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
\2. 复制缓冲区大小设定不合理,会导致数据溢出,后进的指令会冲掉先进的指令。如进行全量复制周期太长,进行部分复制时又出现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。在master端更改:repl-backlog-size 1mb
\3. master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执
行bgsave命令和创建复制缓冲区
\1. 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务。
slave-serve-stale-data yes|no
\2. 数据同步阶段, master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令
\3. 多个slave同时对master请求数据同步, master发送的RDB文件增多, 会对带宽造成巨大冲击, 如果master带宽不足, 因此数据同步需要根据业务需求, 适量错峰
\4. slave过多时, 建议调整拓扑结构,由一主多从结构变为树状结构, 中间的节点既是master,也是slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大, 数据一致性变差, 应谨慎选择
当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的状态,同步的动作称为命令传播
master将接收到的数据变更命令发送给slave, slave接收命令后执行命令
命令传播阶段出现了断网现象
网络闪断闪连 忽略
短时间网络中断 部分复制
长时间网络中断 全量复制
部分复制的三个核心要素
服务器的运行 id( run id)
主服务器的复制积压缓冲区
主从服务器的复制偏移量
概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id。每个计算机也有自己的运行ID,slave也有。
组成:运行id由40位字符组成,是一个随机的十六进制字符
例如: fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
作用:运行id被用于在服务器间进行传输,识别身份
如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别。master发送ID,slave比对ID,
实现方式: 运行id在每台服务器启动时自动生成的, master在首次连接slave时,会将自己的运行ID发
送给slave, slave保存此ID,通过info Server命令,可以查看节点的runid
概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出( FIFO)的队列, 用于存储服务器执行过的命令, 每次传播命令(命令传播程序负责), master都会将传播的命令记录下来, 并存储在复制缓冲区 。
如果一台slave断网,那么此时命令收不到,但别的slave收到了,所以个slave创建了一个复制缓冲区:FIFO队列。
复制缓冲区默认数据存储空间大小是1M,由于存储空间大小是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会被放入队列
由来:每台服务器启动时,如果开启有AOF或被连接成为master节点, 即创建复制缓冲区
作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set, select)
数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中
组成
偏移量
字节值
工作原理:
master先把他放到复制缓冲区,不是放整个命令,而是放入AOF日志文件里的$3 \r \n,set等。此时将缓冲区分格,每个格放一个字符。上面的类似索引的"偏移量"识别当前slave执行到哪里了。所以不同slave的偏移量是不一样的。
概念:一个数字,描述复制缓冲区中的指令字节位置
分类:
master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
数据来源:
master端:发送一次记录一次
slave端:接收一次记录一次
作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用
master发过来的为红的offset,slave记下来的为蓝的offset。
如果ID不匹配。那么就触发全部复制。或者slave传过来的offset没了(在master里找不到这个偏移量了,被队列挤出去了),那么就触发全量复制。
心跳机制:进入命令传播阶段候, master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
master心跳:
指令: PING
周期:由repl-ping-slave-period决定,默认10秒
作用:判断slave是否在线
查询: INFO replication 获取slave最后一次连接时间间隔, lag项维持在0或1视为正常
slave心跳任务
指令: REPLCONF ACK {offset}
周期: 1秒
作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
作用2:判断master是否在线
心跳阶段注意事项
当slave多数掉线,或延迟过高时, master为保障数据稳定性,将拒绝所有信息同步操作。都掉了就没有同步的意义了。或者同步完成时间太久了
min-slaves-to-write 2 # 少于2时就不再写了
min-slaves-max-lag 8 # 链接延迟,在info里可以看到
slave数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
slave数量由slave发送REPLCONF ACK命令做确认
slave延迟由slave发送REPLCONF ACK命令做确认
主从复制总结:
这个地方还没看视频
伴随着系统的运行, master的数据量会越来越大,一旦master重启, runid将发生变化,会导致全部slave的
全量复制操作
内部优化调整方案:
\1. master内部创建master_replid变量,使用runid相同的策略生成,长度41位,并发送给所有slave
\2. 在master关闭时执行命令 shutdown save,进行RDB持久化,将runid与offset保存到RDB文件中
repl-id repl-offset
通过redis-check-rdb命令可以查看该信息
\3. master重启后加载RDB文件, 恢复数据
重启后,将RDB文件中保存的repl-id与repl-offset加载到内存中
master_repl_id = repl master_repl_offset = repl-offset
通过info命令可以查看该信息
作用:
本机保存上次runid,重启后恢复该值,使所有slave认为还是之前的master
问题现象
网络环境不佳,出现网络中断, slave不提供服务
问题原因
复制缓冲区过小,断网后slave的offset越界,触发全量复制
最终结果
slave反复进行全量复制
解决方案
修改复制缓冲区大小
repl-backlog-size
建议设置如下:
\1. 测算从master到slave的重连平均时长second
\2. 获取master平均每秒产生写命令数据总量write_size_per_second
\3. 最优复制缓冲区空间 = 2 * second * write_size_per_second
问题现象
master的CPU占用过高 或 slave频繁断开连接
问题原因
slave每1秒发送REPLCONF ACK命令到master
当slave接到了慢查询时( keys * , hgetall等),会大量占用CPU性能
master每1秒调用复制定时函数replicationCron(),比对slave发现长时间没有进行响应
最终结果
master各种资源(输出缓冲区、带宽、连接等) 被严重占用
解决方案
通过设置合理的超时时间,确认是否释放slave:repl-timeout
该参数定义了超时时间的阈值(默认60秒),超过该值,释放slave
频繁的网络中断( 2)
问题现象
slave与master连接断开
问题原因
master发送ping指令频度较低
master设定超时时间较短
ping指令在网络中存在丢包
解决方案
提高ping指令发送的频度:repl-ping-slave-period
超时时间repl-time的时间至少是ping指令频度的5到10倍,否则slave很容易判定超时
问题现象
多个slave获取相同数据不同步
问题原因
网络信息不同步,数据发送有延迟
解决方案
优化主从间的网络环境,通常放置在同一个机房部署,如使用阿里云等云服务器时要注意此现象
监控主从节点延迟(通过offset)判断,如果slave延迟过大,暂时屏蔽程序对该slave的数据访问:slave-serve-stale-data yes|no
开启后仅响应info、 slaveof等少数命令(慎用,除非对数据一致性要求很高)
l 切入点问题?slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制
l 从机是否可以写?set可否?
l 主机shutdown后情况如何?从机是上位还是原地待命
l 主机又回来了后,主机新增记录,从机还能否顺利复制
l 其中一台从机down后情况如何?依照原有它能跟上大部队吗?
l 每次从机联通后,都会给主机发送sync指令
l 主机立刻进行存盘操作,发送RDB文件,给从机
l 从机收到RDB文件后,进行全盘加载
l 之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
中途变更转向:会清除之前的数据,重新建立拷贝最新的
风险是一旦某个slave宕机,后面的slave都没法备份
当一个master宕机后,后面的slave可以立刻升为master,其后面的slave不用做任何修改。
用 slaveof no one 将从机变为主机。
反客为主的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库.
配置哨兵
l 调整为一主二从模式
l 自定义的/myredis目录下新建sentinel.conf文件
l 在配置文件中填写内容
sentinel monitor mymaster 127.0.0.1 6379 1
其中mymaster为监控对象起的服务器名称, 1 为 至少有多少个哨兵同意迁移的
数量。
l 启动哨兵
执行redis-sentinel /myredis/sentinel.conf
redis提供的服务OPS可以达到10万/秒,当前业务OPS已经达到10万/秒
内存单机容量达到256G,当前业务需求内存容量1T
使用集群的方式可以快速解决上述问题
集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
集群作用
分散单台服务器的访问压力,实现负载均衡
分散单台服务器的存储压力,实现可扩展性
降低单台服务器宕机带来的业务灾难
数据存储设计
这个key存到哪个redis存储空间:
通过CRC16算法设计,计算出key应该保存的位置
将所有的存储空间计划切割成16384份,每台主机保存一部分
每份代表的是一个存储空间,不是一个key的保存空间
将key按照计算出的结果,然后(%16384),比如得到37,放到对应的37存储空间
那如何加入一台新的计算机,怎么重新划分?每个原来的redis拿出一部分存到新的redis上。即改变槽的位置。
怎么知道槽新地址?
集群内部通讯设计:
原生安装(单条命令)
配置服务器( 3主3从)
建立通信( Meet)
分槽( Slot)
搭建主从( master-slave)
工具安装(批处理)
添加节点:cluster-enabled yes|no
cluster配置文件名,该文件属于自动生成,仅用于快速查找文件并查询文件内容cluster-config-file
节点服务响应超时时间,用于判定该节点是否下线或切换为从节点 cluster-node-timeout
master连接的slave最小数量
cluster-migration-barrier
查看集群节点信息 cluster nodes
进入一个从节点 redis,切换其主节点 cluster replicate
发现一个新节点,新增主节点cluster meet ip:port
忽略一个没有solt的节点cluster forget
手动故障转移cluster failover
redis-trib命令
添加节点redis-trib.rb add-node
删除节点redis-trib.rb del-node
重新分片redis-trib.rb reshard
容量不够,redis如何进行扩容?
并发写操作, redis如何分摊?
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求
执行yum install ruby
执行yum install rubygems
l cd /run/media/root/CentOS 7 x86_64/Packages 获取如下rpm包
l 拷贝到/opt/rpmruby/目录下,并cd到此目录
l 执行:rpm -Uvh *.rpm --nodeps –force 按照依赖安装各个rpm包
l 按照依赖安装各个rpm包
l 执行在opt目录下执行 gem install --local redis-3.2.0.gem
拷贝多个redis.conf文件
开启daemonize yes
Pid文件名字
指定端口
Log文件名字
Dump.rdb名字
Appendonly 关掉或者换名字
cluster-enabled yes 打开集群模式
cluster-config-file nodes-端口号.conf 设定节点配置文件名
cluster-node-timeout 15000 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换
将6个实例全部启动,nodes-端口号.conf文件都生成正常
合体
l 进入到 cd /opt/redis-3.2.5/src
l 执行
./redis-trib.rb create --replicas 1
192.168.31.211:6379 192.168.31.211:6380 192.168.31.211:6381
192.168.31.211:6389 192.168.31.211:6390 192.168.31.211:6391
l 注意: IP地址修改为当前服务器的地址,端口号为每个Redis实例对应的端口号.
redis-cli -c -p 端口号
通过cluster nodes 命令查看集群信息
redis cluster 如何分配这六个节点
一个集群至少要有三个主节点。
选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
l 一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
l 集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5500 号插槽。
节点 B 负责处理 5501 号至 11000 号插槽。
节点 C 负责处理 11001 号至 16383 号插槽
l 在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口.
l redis-cli客户端提供了 –c 参数实现自动重定向。
如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
l 不在一个slot下的键值,是不能使用mget,mset等多键操作。
l 可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去
l CLUSTER KEYSLOT 计算键 key 应该被放置在哪个槽上。
l CLUSTER COUNTKEYSINSLOT 返回槽 slot 目前包含的键值对数量
l CLUSTER GETKEYSINSLOT 返回 count 个 slot 槽中的键
l 如果主节点下线?从节点能否自动升为主节点?
l 主节点恢复后,主从关系会如何?
l 如果所有某一段插槽的主从节点都当掉,redis服务是否还能继续?
redis.conf中的参数 cluster-require-full-coverage
public class JedisClusterTest {
public static void main(String[] args) {
Set set =new HashSet();
set.add(new HostAndPort("192.168.31.211",6379));
JedisCluster jedisCluster=new JedisCluster(set);
jedisCluster.set("k1", "v1");
System.out.println(jedisCluster.get("k1"));
}
}
l 优点
实现扩容
分摊压力
无中心配置相对简单
l 缺点
多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持。
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步
master主机宕机,从slave里选一个作为master
谁来确认master宕机了?
找一个主?怎么找法?
修改配置后,原始的主恢复了怎么办?
哨兵(sentinel) :是一个分布式系统,用于对主从结构中的每台服务器进行监控,当master出现故障时通过投票机制从slave中选择新的master并将所有slave连接到新的master。
哨兵的作用
监控:
不断的检查master和slave是否正常运行。
master存活检测、 master与slave运行情况检测
通知(提醒)
当被监控的服务器出现问题时, 向其他(哨兵间,客户端) 发送通知。
自动故障转移
断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址
注意:
哨兵也是一台redis服务器,只是不提供数据服务。通常哨兵配置数量为单数
配置一拖二的主从结构
配置三个哨兵(配置相同,端口不同)
参看sentinel.conf
复制conf 到别的地方,做为新的哨兵
启动哨兵
redis-sentinel sentinel-端口号.conf
先启动主机,再启动从机,再启动哨兵
配置项 | 范例 | 说明 |
---|---|---|
sentinel auth-pass <服务器名称> | sentinel auth-pass mymaster itcast | 连接服务器口令 |
sentinel down-after-milliseconds <自定义服 务名称><主机地址><端口><主从服务器总量> | 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 <服务名称><脚本路径> | 服务器无法正常联通时,设定的执行脚本,通常调试使用。 |
哨兵在进行主从切换过程中经历三个阶段
监控
通知
故障转移
用于同步各个节点的状态信息
获取各个sentinel的状态(是否在线)
获取master的状态
master属性
runid
role: master
各个slave的详细信息
获取所有slave的状态(根据master中的slave信息)
slave属性
runid
role: slave
master_host、 master_port
offset
…
服务器列表中挑选备选master
在线的
响应慢的
与原master断开时间久的
优先原则
优先级
offset
runid
发送指令( sentinel )
向新的master发送slaveof no one
向其他slave发送slaveof 新masterIP端口
主从切换总结
服务器列表中挑选备选master
在线的
响应慢的
与原master断开时间久的
优先原则
优先级
offset
runid
监控
同步信息
通知
保持联通
故障转移
发现问题
竞选负责人
优选新master
新master上任,其他slave切换master,原master作为slave故障回复后连接
宕机:服务器启动后迅速宕机
问题排查
\1. 请求数量较高
\2. 主从之间数据吞吐量较大,数据同步操作频度较高
解决方案
前置准备工作:
\1. 日常例行统计数据访问记录,启动之前统计访问频度较高的热点数据
\2. 利用LRU数据删除策略,构建数据留存队列
例如: storm与kafka配合
准备工作:
\1. 将统计结果中的数据分类,根据级别, redis优先加载级别较高的热点数据
\2. 利用分布式多服务器同时进行数据读取, 提速数据加载过程
\3. 热点数据主从同时预热
实施:
\1. 使用脚本程序固定触发数据预热过程
\2. 如果条件允许, 使用了CDN(内容分发网络),效果会更好
总结
缓存预热就是系统启动前, 提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓
存的问题!用户直接查询事先被预热的缓存数据
数据库服务器崩溃( 1)
\1. 系统平稳运行过程中,忽然数据库连接量激增
\2. 应用服务器无法及时处理请求
\3. 大量408, 500错误页面出现
\4. 客户反复刷新页面获取数据
\5. 数据库崩溃
\6. 应用服务器崩溃
\7. 重启应用服务器无效
\8. Redis服务器崩溃
\9. Redis集群崩溃
\10. 重启数据库后再次被瞬间流量放倒
问题排查
\1. 在一个较短的时间内,缓存中较多的key集中过期
\2. 此周期内请求访问过期的数据, redis未命中, redis向数据库获取数据
\3. 数据库同时接收到大量的请求无法及时处理
\4. Redis大量请求被积压,开始出现超时现象
\5. 数据库流量激增,数据库崩溃
\6. 重启后仍然面对缓存中无数据可用
\7. Redis服务器资源被严重占用, Redis服务器崩溃
\8. Redis集群呈现崩塌,集群瓦解
\9. 应用服务器无法及时得到数据响应请求,来自客户端的请求数量越来越多,应用服务器崩溃
\10. 应用服务器, redis,数据库全部重启,效果不理想
问题分析
短时间范围内
大量key集中过期
解决方案(道)
\1. 更多的页面静态化处理
\2. 构建多级缓存架构
Nginx缓存+redis缓存+ehcache缓存
\3. 检测Mysql严重耗时业务进行优化
对数据库的瓶颈排查:例如超时查询、耗时较高事务等
\4. 灾难预警机制
监控redis服务器性能指标
CPU占用、 CPU使用率
内存容量
查询平均响应时间
线程数
\5. 限流、降级
短时间范围内牺牲一些客户体验,限制一部分请求访问,降低应用服务器压力,待业务低速运转后再逐步放开访问
解决方案(术)
\1. LRU与LFU切换
\2. 数据有效期策略调整
根据业务数据有效期进行分类错峰, A类90分钟, B类80分钟, C类70分钟
过期时间使用固定时间+随机值的形式,稀释集中到期的key的数量
\3. 超热数据使用永久key
\4. 定期维护(自动+人工)
对即将过期数据做访问量分析,确认是否延时,配合访问量统计,做热点数据的延时
\5. 加锁
慎用!
总结
缓存雪崩就是瞬间过期数据量太大,导致对数据库服务器造成压力。如能够有效避免过期时间集中,可以有效解决雪崩现象的出现(约40%),配合其他策略一起使用,并监控服务器的运行数据,根据运行记录做快速调整。
数据库服务器崩溃( 2)
\1. 系统平稳运行过程中
\2. 数据库连接量瞬间激增
\3. Redis服务器无大量key过期
\4. Redis内存平稳,无波动
\5. Redis服务器CPU正常
\6. 数据库崩溃
问题排查
\1. Redis中某个key过期,该key访问量巨大
\2. 多个数据请求从服务器直接压到Redis后,均未命中
\3. Redis在短时间内发起了大量对数据库中同一数据的访问
问题分析
单个key高热数据
key过期
解决方案(术)
\1. 预先设定
以电商为例,每个商家根据店铺等级, 指定若干款主打商品,在购物节期间, 加大此类信息key的过期时长
注意:购物节不仅仅指当天,以及后续若干天,访问峰值呈现逐渐降低的趋势
\2. 现场调整
监控访问量,对自然流量激增的数据延长过期时间或设置为永久性key
\3. 后台刷新数据
启动定时任务,高峰期来临之前, 刷新数据有效期, 确保不丢失
\4. 二级缓存
设置不同的失效时间,保障不会被同时淘汰就行
\5. 加锁
分布式锁,防止被击穿,但是要注意也是性能瓶颈,慎重!
总结
缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服
务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度
较高,配合雪崩处理策略即可
数据库服务器崩溃( 3)
\1. 系统平稳运行过程中
\2. 应用服务器流量随时间增量较大
\3. Redis服务器命中率随时间逐步降低
\4. Redis内存平稳,内存无压力
\5. Redis服务器CPU占用激增
\6. 数据库服务器压力激增
\7. 数据库崩溃
问题排查
\1. Redis中大面积出现未命中
\2. 出现非正常URL访问
问题分析
获取的数据在数据库中也不存在,数据库查询未得到对应数据
Redis获取到null数据未进行持久化,直接返回
下次此类数据到达重复上述过程
出现黑客攻击服务器
解决方案(术)
\1. 缓存null
对查询结果为null的数据进行缓存(长期使用,定期清理), 设定短时限,例如30-60秒, 最高5分钟
\2. 白名单策略
提前预热各种分类数据id对应的bitmaps, id作为bitmaps的offset,相当于设置了数据白名单。 当加载正常数据时,放
行,加载异常数据时直接拦截(效率偏低)
使用布隆过滤器(有关布隆过滤器的命中问题对当前状况可以忽略)
\3. 实施监控
实时监控redis命中率( 业务正常范围时,通常会有一个波动值)与null数据的占比
非活动时段波动:通常检测3-5倍,超过5倍纳入重点排查对象
活动时段波动:通常检测10-50倍, 超过50倍纳入重点排查对象
根据倍数不同,启动不同的排查流程。然后使用黑名单进行防控(运营)
\4. key加密
问题出现后,临时启动防灾业务key,对key进行业务层传输加密服务,设定校验程序,过来的key校验
例如每天随机分配60个加密串,挑选2到3个,混淆到页面数据id中,发现访问key不满足规则,驳回数据访问
总结
缓存击穿访问了不存在的数据,跳过了合法数据的redis数据缓存阶段,每次访问数据库,导致对数据库服务器造成压力。通常此类
数据的出现量是一个较低的值,当出现此类情况以毒攻毒,并及时报警。应对策略应该在临时预案防范方面多做文章。
无论是黑名单还是白名单,都是对整体系统的压力,警报解除后尽快移除。
监控指标
性能指标: Performance
内存指标: Memory
基本活动指标: Basic activity
持久性指标: Persistence
错误指标: Error
监控指标
性能指标: Performance
Name | 描述 |
---|---|
latency | Redis响应一个请求的时间 |
instantaneous_ops_per_sec | 平均每秒处理请求次数 |
hit rate | 缓存命中率(计算出来的) |
监控指标
内存指标: Memory
name | 描述 |
---|---|
used_memory | 已使用内存 |
mem_fragmentation_ratio | 内存碎片率 |
evited_keys | 由于最大内存限制被移除的key值 |
blocked_clients | 由于BLPOP,BRPOP,BRPOPLPUSH而被阻塞的客户端 |
监控指标
基本活动指标: Basic activity
Name | 描述 |
---|---|
connected_clients | 客户端连接数 |
connected_slaves | slave数量 |
master_last_io_seconds_ago | 最近一次主从交互之后的秒数 |
keyspace | 数据库中的key值总数 |
监控指标
持久性指标: Persistence
name | 描述 |
---|---|
rdb_last_save_time | 最后一次持久化保存到磁盘的时间戳 |
rdb_changes_since_last_save | 自最后一次持久化以来数据库的更改次数 |
监控指标
错误指标: Error
name | |
---|---|
rejected_connections | 由于达到maxclient限制而被拒绝的连接数 |
keyspace_misses | key值查找失败(没有命中)次数 |
master_link_down_since_seconds | 主从断开的持续时间(秒) |
监控方式
工具
Cloud Insight Redis
Prometheus
Redis-stat
Redis-faina
RedisLive
zabbix
命令
benchmark
redis cli
monitor
showlog
benchmark
命令
redis-benchmark [-h ] [-p ] [-c ] [-n
范例1
redis-benchmark
说明: 50个连接, 10000次请求对应的性能
范例2
redis-benchmark -c 100 -n 5000
说明: 100个连接, 5000次请求对应的性能
monitor
命令
monitor
打印服务器调试信息
showlong
命令
showlong [operator]
get :获取慢查询日志
len :获取慢查询日志条目数
reset :重置慢查询日志
相关配置
slowlog-log-slower-than 1000 #设置慢查询的时间下线,单位:微妙
slowlog-max-len 100 #设置慢查询命令对应的日志显示长度,单位:命令数