Redis+Caffeine介绍
2022-04-09 17:11:01 0 举报
AI智能生成
缓存Redis+Caffeine介绍,你真的学会了吗?
作者其他创作
大纲/内容
架构演变:文件缓存->Memcache->Mysql主从读写分离->分表分库
Volume:海量数据
Variety:数据类型、终端设备的多样
Velocity:实时
3V
高可扩:不断扩展现有功能与新功能
高性能
高并发
3高
新要求
开发的项目多了,越来越能体会,传统数据库访问速度是限制系统性能的最大瓶颈。
而 Redis 基于内存的特性,可以极大地提高读写效率,使用得当,往往使系统性能有质的提升。
在项目开发中遇到访问频率非常高的热点数据时,可以优先考虑使用 Redis 进行存储操作。
Redis诞生背景
redis是一款高性能的NOSQL系列的非关系型数据库
Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库
Redis 非常适合作为热点数据的缓存
基于内存的键值型数据库
Redis是什么?
全称为:Remote Dictionary Server
Re di s(Remote Dictionary Server)
Redis->Remote Dictionary Server
Remote Dictionary Server(远程字典服务器)
Redis(Remote Dictionary Server )
即远程字典服务
(远程字典服务器)
远程字典服务
远程数据服务
Redis(远程字典服务)
基于C语言编写的
使用ANSI C语言编写
该软件使用C语言编写
C 语言编写的
纯C编码
Redis是C语言写的
可以存储键和五种不同类型的值之间的映射
Redis是的kv键值对数据库
Redis是一个key-value存储系统
Key-Value数据库
内存key-value缓存数据库
Redis是基于key-value操作的
key-value存储系统
可基于内存亦可持久化的日志型
内存中的数据结构存储系统
不同级别的 磁盘持久化(persistence)
Redis是一款内存高速缓存数据库。
存在内存中
Redis跟memcache不同的是,储存在Redis中的数据是持久化的,断电或重启后,数据也不会丢失。
可持久化
支持数据持久化
基于内存,可持久化
1秒钟可以处理几万个请求
以设置和获取一个256字节字符串为例,它的读取速度可高达110000次/s,写速度高达81000次/s。
官方提供的数据QPS为100000+,完全不比同样是使用key-value的Memecache差
Redis以内存作为数据存储介质,所以读写数据的效率极高,远远超过数据库。
高速缓存数据库
Redis支持String、Hash、List、Set、sorted set等数据结构
字符串、列表、集合、有序集合、散列表。
- 字符串(string)- 散列(hash)- 列表(list)- 集合(set)- 有序集合(sorted set、zset)
还支持BitMaps、HyperLogLog、GEO等数据结构
值支持五种基本数据类型,三种特殊数据类型
支持多种类型的数据结构
Redis通过提供多种键值数据类型来适应不同场景下的存储需求
多种键值数据类型
数据结构是专门进行设计的,对数据操作也简单
丰富的数据类型
支持多种数据结构
数据结构丰富
支持丰富的数据类型
其实是因为作者把某个女星的姓名用九宫格打出来是6379
默认端口是6379
支持数据备份、集群、哨兵
Redis支持主从模式,可以配置集群,这样更利于支撑起大型的项目,
这也是Redis的一大亮点
多种集群方案
什么是Redis
多线程,非阻塞IO
键的类型只能为字符串
支持的数据类型只有String
依靠客户端分片写入数据实现集群
支持value: 1MB
在memcached,通常需要把数据拿到客户端进行类似的修改再set回去,增加了网络io的次数和数据体积。
Memcache
单线程,多路IO复用
持久性
支持value: 512MB
redis支持服务器端的操作
相比于memcached而言,有更多的数据结构和支持更丰富的数据操作。
redis比memcache拥有的数据结构更多
数据结构更多
memcache原生不支持集群,redis支持cluster集群模式的。
memcached没有原生的集群模式,需要依靠客户端来实现往集群分片中写入数据,但是redis目前是原生支持cluster模式。
集群模式
redis是单线程(6.0多线程),memcache是多线程
由于redis只使用单核,memcached可以使用多核,所以平均每个核上redis存储小数据比memcached高,
redis在存储小数据时性能更高
但是100k以上的数据,memcached性能高于redis
在100k以上的数据中,memcache要高于redis
性能对比
单纯的k-v是memcached高
但是采取hash结构的话,内存利用率是redis高
内存使用高效率
Redis
Redis和memcache有什么区别
map 或者 guava 实现的是本地缓存
轻量以及快速
生命周期随着 jvm 的销毁而结束
在多实例的情况下,每个实例都需要各自保存一份缓存,缓存不具有一致性。
本地缓存的特点
在多实例的情况下,各实例共用一份缓存数据,缓存具有一致性。
分布式缓存的特点
需要保持 redis 或 memcached服务的高可用,整个程序架构上较为复杂。
缺点
第一次访问数据库慢
缓存后,访问直接操作内存,快
缓存数据库数据到redis,减少访问数据库
redis的高并发的实现:【本质就是读写分离】
首先先理解一个常识,单机的redis能够承载的qps是上万到几万不等,如果qps在10万+,那单机的redis肯定是会崩的。
所以处理处理高并发说白了就是分流,redis采取主从的模式,对于落到redis上的操作我们区分为读和写。
正常情况下,读操作的数量都是大于写的,所以我们把所有的读操作落在slave上,所有的写操作落在master上,然后master把数据同步到所有的slave上。
高并发
为什么要使用Redis , 不使用map/guava做缓存
随着业务的运转,redis内存应该淘汰掉冷数据:http://redis.cn/topics/lru-cache.html
+ 缓存数据不重要,如点赞数,评论数;+ 不是全量数据+ 缓存应该随着访问变化--热数据
作为缓存的几点要求
Redis作为数据库/缓存的区别
1、Redis分为单机版和集群版
2、不依赖外部库
3、redis依赖于单线程模型
简单
支持网络
Redis Serialization Protocal
RESP
通讯协议
1、Java
2、Php
3、c
支持多种编辑语言,提供多种语言的API
Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
自己构建了VM 机制
由意大利人Salvatore Sanfilippo(网名:antirez)开发的
开源的
每秒可读11万次
每秒可写8万1千次
性能方面
通过Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)
高可用、分布式
1、发布订阅
支持事务,操作都是原子的
支持事务
事务(transactions)
2、事务
LUA脚本(Lua scripting)
LUA脚本
LRU驱动事件(LRU eviction)
LRU驱动事件
3、Lua脚本与驱动事件
节省网络开销
注意每次pipeline携带的数据量
注意m操作与pipeline的区别:原子 vs 非原子
pipeline每次只能作用在一个redis节点上
4、pipeline
功能丰富
读写性能优异
复制(replication)
主从复制
支持主从复制
Redis优点
基于内存,受物理内存限制
不具备自动容错和恢复,需手动
宕机,主机的部分数据未同步从机,导致数据不一致
难支持在线扩容,复杂
Redis缺点
Redis一共有16个数据库
如图
默认使用16个数据库,默认使用第0个
127.0.0.1:6379> select 3OK127.0.0.1:6379[3]> DBSIZE(integer)0
可以使用select进行切换数据库
Redis的16个数据库是相互独立的,可以使用select index进行切换
清除当前数据库的数据
清除当前数据库
flushdb
清空所有数据库的记录
清空所有数据库
flushall
清空Redis数据、清除数据库
Redis的数据库
Redis可以设置过期时间
redis-benchmark是一个压力测试工具
简单测试
进入usr/local/bin
测试性能
Redis中文
Redis官网(英文)
Redis英文
Redis官网
Redis简介、入门、基础知识、概述
数据类型、基础类型、数据结构
数据结构
基础数据结构
数据结构介绍
五种基础数据类型
Redis的数据类型
数据类型介绍
Redis基本数据类型
redis数据类型
基础数据类型
redis都有哪些数据类型?分别在哪些场景比较合适?
多样的数据类型
Redis其他操作
数据操作、基本操作
数据操作
图解5种数据类型
string
String(字符串)
字符串(string)
String(就是类似java中的字符串操作)
存储字符串,整数,浮点数
存储简单字符串,json对象
string:字符串
字符串类型 String
字符串
string操作
基本字符串
String
raw
int
empstr
embstr
内部编码
k-V存储
可做简单的k-v缓存
字符类型
set user:id:name xiaoming
set user:id:age 20
setxx
设置值
存储: set key value
set [key] [value]
只能修改,必须存在key,才能存在
set k v
存放一个k-v对
如set lock1 1 EX 10 NX
set k v xx: key存在 才设置(update)
set key value [expiration EX seconds|PX milliseconds][NX|XX]
实战使用 set key:{id}:{filed}
set
存储对象
getset: 返回旧值
get k
获得k对应v
get key
get [key]
取值
获取: get key
get
mset k1 v1 k2 v2 k3 v3
mset k1 v1 k2 v2 ...
mset 批量设置
存放多个k-v
批量添加记录
mset key value [key value ...]
mget k1 k2 k3
mget 批量获得
mget k1 k2 k3 ...
获得多个v
mget key [key ...]
mget / mset
getset key value
getset 先get再set
如果不存在值则返回null,再设置新值;若存在,返回原来的值,并设置新指
获取值之后 修改该K的V
getset k v
get、set、mset、mget
# 设置值set name xxx# 获取值get name xxx
setnx k v:
setnx k v
setnx key value
setnx (set if not exist) 不存在再设置
setnx [key] [value]
只能新建,如果存在key,则set失败
不存在才设置(add)
当库中有该K时不存。当库中没有改K时存放非常重要(做分布式锁)
如果已经有了这个key,该操作无效
若存在key,则将xxx设置进去
如果key不存在就执行set创建(if not exists)
如果不存在这个key,就存储
若不存在,则不会设置
setnx key xxx
setnx
带过期时间的设置
setex (set with expire) 设置过期时间
setex key 30 xxx(30->指时间)
setex(set with expire)
setex [key] [seconds] [value]
ex:设置过期时间
限时存储: setex key 时间(s) value (类似限时激活码)
等价于 set+expire
setex k 60 v: 生存时间
setex key seconds value
msetex 批量设置过期时间
不存在设置
msetnx k1 v1 k4 v4
msetnx 是一个原子性的操作,要么一起成功,要么一起失败
可以批量添加,并且是原子操作
msetnx 批量判断是否存在
incr / decr / incrby / decrby
该k1对应的v的值++(v必须是Integer类型)
insc k1
设置每次走的步长
inscby k1 步长
DECRBY
descby k1 步长
该k1对应的v的值--(v必须是Integer类型)
desc k1
incr、incrby、decr、decrby、incrbyfloat
+1操作
自增
incr view
incr key
INCR 自增
decr view
自减
DECR 自减
-1操作
指定-num操作
decr key
incrby view [num]
incrby key increment
incrbyfloat key 3.5
固定长度自增
增加key对应的值3.5
指定+num操作
INCRBY 设置步长,指定增量
incrby key num
decrby view [num]
固定长度自减
decrby key num
数值、浏览量类型的字符串
strlen [key]
查看长度
返回字符串的长度
取其字符集长度
strlen
追加值
APPEND 拼接
将value追加到旧到value
append [key] [value]
append
strlen、append
getrange
getrange [key] [start] [end]
getrange [key] 0 -1就是完整字符串
GETRANGE key 0 2
GETRANGE 范围,截取字符串
截取字符串范围(闭区间)
获取字符串制定下标的所有值
getrange key start end
替换(相当于replace)
setrange
STRLEN 字符串长度
SETRANGE 替换指定位置的字符串
正反向索引:value第一个index=0,倒数第一个为-1,以此类推
设置执行下标所有对应的值
setrange [key] [start] [new]
setrange key index value
capacity
操作命令、常用命令、相关命令
redisObject
SDS
简单动态字符串(Simple Dynamic String)
扩容: 1M后,每次扩容1M
content:以字节\\0结尾
len
C字符串并不记录自身长度,想获取长度只能遍历
sds直接获取len即可
获取长度
c字符串每次长度变化都会对数组进行内存重新分配,比较耗时
对sds内容进行修改或者需要扩展时,sds有空间预分配和惰性空间释放
内存分配
C字符串不记录自身长度,不会自动进行边界检查,所以会增加溢出的风险
sds先检查空间是否满足修改所需的要求,如果不满足就先扩容再进行修改
缓冲区安全
C字符串是以空字符串(\\0)结尾,所以字符串中不能包含空字符串,只能保存文本数据
既能保存文本数据,也能保存二进制数据(通过长度判断结束,不受影响)
二进制安全
redis底层是C,为什么不用c字符串,而用SDS
底层数据结构、存储原理
缓存 热点数据缓存
1、缓存
基于incr命令实现
计数器
2、计数器
基于setnx命令实现
分布式锁
3、分布式锁
String类型使用场景
value除了是字符串还可以是数字
统计多单位的数量
session共享
分布式session
分布式数据共享
基于incrby命令实现
分布式ID生成(自增)
全局ID
限流
位统计
应用场景
本质上讲在redis中,int类型用type key来看,也是string的;
但是object encoding key来看int类型key才会是int型;int型的key可执行incr等操作。
二进制安全 redis取值是字节流
String/Byte
字符串类型 String
哈希表(hash)
hash:键值对
map格式
Hash
Hash(哈希)
Map集合,key-value键值对
hash类型
hash操作
存储 对象
哈希类型 Hash
扩容时机:元素的个数等于第一维数组的长度; bgsave时不扩容,除非达到dict_force_resize_ratio
扩容:扩容 2 倍
缩容:元素个数低于数组长度10%时
渐进式rehash
dict - dictht[2] - disctEntry
dict
哈希表
hashtable
ziplist
压缩列表
ziplist: 元素个数较小时,用ziplist节约空间
hash-max-ziplist-entries
hash-max-ziplist-value
配置
底层数据结构、内部编码、存储原理
hset myhash key1 value1
hset 单个设置值
hget
hget myhash key
hget / hset name k v
存
存值
hset key field value
hset [map] [key] [value]
存储: hset key field value
hset k field value
存多个
批量存值
hmset [map] [k1] [v1] [k2] [v2] ...
hmset 多个设置值 hmset myhash k1 v1 k2 v2
hmset k field value field value
hmset key field value [field value ...]
hset、hmset
取
hget [map] [key]
hget key field
hget k filed
hget key field: 获取指定的field对应的值
取多个
批量取值
hmget [map] [k1] [k2] ...
hmget k field filed
hmget key field [field ...]
hmget myhash key1 key2 获取多个值
hget、hmget
hget、hset、hmget、hmset
hsetnx myhash key value
hsexnx [map] [key] [value]
hsetnx 如果不存在则设置,如果不存在者不设置
设置不存在的k-v键值对(如果没有就设置成功,如果已经存在就设置失败)
hsetnx name k v
hgetall name
hgetall myhash
hgetall key
hgetall [map]
获取所有值
直接获取key下所有的hash
获取所有的field和value
取得、获得所有的k-v
hgetall k
hdecr
hdecrby
hdecr 自减
hkeys [map]
hkeys myhash
hkeys name: 返回所有key
hkeys 获取hash所有key
只获得所有的key
只取key
hkeys k
hvals [map]
只取value
hvals name: 返回所有value
hvals myhash
hvals 获取hash所有值
只获得所有的value
hvals k
hdel
hdel key field
hdel myhash k1
删除
hdel 删除hash指定的key,对应的value也被删除
hdel k filed
hexists [map] [key]
hexsits myhash key
hexists 判断hash中指定key是否存在
判断hash中key是否有field
判断key是否存在
hexistsk filed
hincr
hincrby
hincr 指定增量
hincrby key field increment
对其value值做加减法操作+ 使用场景:评论数、点赞数增加
hincrbyfloat
hincrby / hincrbyfloat
hlen [map]
hlen myhash
hlen 获取hash的size
获得k-v键值对数量
获取hash key field的数量
hlen key
Hash操作、操作命令、常用方法、相关命令、常用命令
value只能是字符串(字符串、整数、浮点数)
和string的差不多,只不过命令开头用h开头
相比于json串,可单独修改对象的字段
hash变更的数据 user name age ,尤其是用户信息子类的,经常变动的信息!
hash 更适合对象的存储,String更加适合字符串存储
因为对象的属性和值,可以认为是一个map集合里面的数据!
map 适合存储对象?
主要用来存放一些对象,后续操作的时候,可以仅仅修改对象里的某个值
value为hashmap构造
存储类型
String的应用场景、Hash都可以做
缓存视频的基本信息(数据在mysql中)仿代码
hset user:1:info pageview count
记录网站每个用户个人主页的访问量
可以快速定位,存储的信息需要被频繁的修改可用hash存储,比如实现购物车
购物车
哈希类型 Hash
List、list
list数据类型
列表(list)
List(列表)
在redis里面, 可以把list完成 栈、队列、阻塞队列
所有list命令都是用L开头的
Redis不区分大小写命令
列表类型 List
可重复集合
list:可重复集合
允许存在重复元素
可重复
支持重复元素
有序的字符串
存储有序列表
有序列表
有序可重复
list 实际上是一个链表,
before node after,left,right都可以插入
如果key不存在,创建新的链表
如果key存在,新增内容
如果移除所有的值,形成空链表,也代表不存在
在两边插入或者改动值,效率最高!中间元素,相对来说效率较低写
链表结构
双向链表
是个双向链表
是个双端队列
list是一个双向链表、双端队列
列表对象保存的元素数量小于512个
列表对象保存的所有字符串元素长度都小于64个字节
当列表对象痛死满足两个条件时,列表对象使用ziplist进行存储
他将所有的元素紧挨着存储,分配的是一块连续的内存
压缩列表(ziplist)
元素少时用 ziplist
linkedlist
linkedlist格式
元素多时用 linkedlist
早期 :ziplist+linkedlist
一个通用的双端链表实现
3.2之前 ADList
quicklist
由于普通链表指针比较浪费空间且会加重内存碎片化,所以优化为quicklist
将多个ziplist使用双向指针串起来(链表+ziplist)
既满足了快速的插入删除性能,又不会出现太大的空间冗余
特点
快速列表(quicklist)
3.2之后 quicklist
后期:quicklist
存储类型、存储原理、内部编码、底层实现、本质、底层数据结构
判断list是否存在
exists [list]
lset [list] [index] [value]
lset key index v
语法:lset key index value
关于lset的用法
Lset 将列表中指定下标的值替换成另一个值,更新操作
设置链表中指定位置为新值
替换节点值(把index下标的值用value替换,不存在就报错)
lset key 3 xxxx:接上条lpush数据结果为 f e d xxxx b a
lset
llen
llen k
llen [list]
Llen
获取list长度
查看该队列的长度
获得list的长度
lpush k v
lpush [list] [value]
lpush list-name c b a
lpush key value:
LPUSH list one
lpush key value [value ...]
将元素加入列表左表
LPUSH 插入到第一个元素
LPUSH 将一个值或者多个值,插入到列表的头部(左-left)
lpush:l的意思是left,从左往右push;+ lpush key a b c d e f在redis中存储的顺序为f e d c b a
从左边放
左存(头插)
lpush
rpush k v
rpush [list] [value]
rpush key value [value ...]
rpush key value
RPUSH list one
将一个值或者多个值,插入到列表的尾部(右-right)
从右边放
右存(尾插)
将元素加入列表右边
rpush:与lpush正好相反
rpush
3. 删除:: :
列表类型 List
rpop k
rpop key
rpop [list]
RPOP list
与lpop正好相反
右取
从右边取第一个
右边的值移除,移除list的最后一个元素
删除列表最右边的元素,并将元素返回
rpop
lpop k
lpop [list]
lpop key
LPOP list
左取
从左边取第一个
LPOP 移除第一个元素
左边的值移除,移除list的第一个元素
关于lpop的用法
语法:lpop key | 表示出栈/出队列中的第一个元素,并从原来的list集合中删除该元素
lpop:接上条lpush的数据;弹出数据为f
删除列表最左边的元素,并将元素返回
lpop
lpush、rpush、lpop、rpop
存元素、添加元素
移除元素
rpoplpush list list2
rpoplpush list list2
rpoplpush source destination
移除列表最后一个元素,并且移动到新列表
rpopLpush
brpop k timeout
brpop key timeout
从右边取,没取到的话阻塞timeout时间
List
brpop 带阻塞
blpop
blpop k timeout
blpop key timeout
从左边取,没取到的话阻塞timeout时间
等待元素出现(阻塞),第一个线程弹出
blpop 带阻塞
blpop、brpop
阻塞pop
语法: ltrim key start stop
ltrim [list] [start] [end]
ltrim key start end: 修剪
ltrim key start stop
Ltrim list 1 2
关于ltrim的用法
Ltrim 通过下标截取指定的长度
trim操作(把截取作为新的list替代旧的)
通过下标截取指定的长度,list改变,只剩下截取的元素
保留指定范围内的元素
ltrim books 1 0 (这其实是清空了整个列表,因为区间范围长度为负)
保留区别的值
ltrim
linsert [list] [before | after] [value0] [value1]
linsert key before|after v new-v
linsert key before|after value newValue
Linsert list before/after value value1
插值(在value0 前 | 后 插入value1的值)
往list中value的位置before或after插入value1
在list指定的值前或者后插入newValue
Linsert 将某个具体的value插入到别表中某个元素的前面或者后面
linsert
lindex [list] [index]
lindex key index
lindex queue 0
语法:lindex key index
Lindex list 1
关于lindex的用法
返回链表中指定下标的值
根据index操作
Lindex 通过下标获得 list 中的某一个值
lindex
语法:lrang start stop
lrange key start stop
lrange k 0 -1
lrange key start end: 范围
lrange [list] [start] [end]
lrange queue 0 -1
lrange key start end
关于lrang的用法、range操作
0 -1 获取全部lrange myList 0 -1
获取:范围获取
-1代表倒数第一个
-2 代表倒数第二个
查看队列
page size
(page-1)* size
page*size-1
实现分页
LRANGE list 0 1 通过区间获取具体的值
LRANGE list 0 -1 查看列表中的值
LRANGE 获取范围内的值
情况一:0通常表示第一个元素 1则是表示第二个,依次类推
情况二:-1表示最后一个元素 -2则表示最后一个元素之前的元素,依次类推
out of rang indexs不是一个错误:
如果start > end: 返回一个empty of list
如果end > 实际的最后一个值,则默认返回最大的值。(end为实际的end)
lrange key 0 -1:接lpush数据存在正负索引,第一个index为0,最后一个为-1获取结果为f e d c b a
lrange
Lrem 移除固定的值
语法:lrem key count value
Lrem list 1(表示个数) one(表示对应的值)
lrem k count value
count = 0: 删除所有v
count 0: 从左到右删除count个
count 0: 从右到左删除count个
lrem key count v
lrem [list] [num] [value]移除 某list num数量个 值为value的节点元素
移除list集合中指定个数的value,精确匹配
移除指定的元素
删除count个值元素值为value的数据,返回被删除的元素数量。
*count>0 从头往后删除
count<0从尾往前删除
关于lrem的用法
lrem
List操作、操作命令、常用方法、相关命令、常用命令
+ 栈 同向命令
类似 栈
分支主题
图示
Lpush Lpop 先进先出
Lpush+Lpop=stack
lpush + lpop -- stack
示例
用做堆栈
+ 队列 反向命令
RPUSH
Lpush+rpop=queue
lpush + rpop -- queue
用做队列
类似 消息队列
可做消息队列
简单的消息队列
Lpush Rpop 左近右出
lpush + brpop -- MQ
Lpush + Brpop=message queue
消息队列
Lpush+Ltrim=capped collection固定集合
lpush + ltrim -- capped collection
固定集合
+ 数组
+ 阻塞,单播队列 FIFO
微博,某个大v粉丝,可以以list的格式放在redis里去缓存。
微博大V的那种粉丝列表
可以通过lrange,从某个元素开始读取多少个元素,分页查询。
实现高性能的分页
分页查询,基于微博的下拉分页
用户消息时间线
相关tips、应用场景,可实现的功能
到货通知
邮件发送
秒杀
保存待抢购的商品列表
实现栈或队列
分页的坑
Set
集合类型 Set
Set(集合)
set的命令以s开头,set中的值不能重复,类似java里的方法;
无序唯一
无序不重复
无序不重复集合
Set与list区分,去重且无序
无序集合
无序集合 最大40亿左右
无序集合
去重
不允许重复元素
set:不可重复集合
如果需要对一些数据进行快速去重,系统部署在多台服务器上,可以用redis set
自动去重
集合 Set
intset
整数数组 set-max-intset-entries 512
当元素都是整数并且个数较小时,使用 intset 来存储
IntSet
底层数据结构、存储原理、存储类型、内部编码
sadd key member [member ...]
sadd key value
sadd k v
sadd key e
sadd [set] [member]
sadd myset value
添加记录
存储
在k的set 集合里面添加一个v,该v 不能重复
sadd set集合中添加元素
sadd
scard k
scard myset
scard [set]
scard 获取set集合中的内容元素个数
scard: 集合大小
查看指定set的元素个数
K的set集合的长度
scard key
spop key
spop [set]
spop k
spop 随机移除set集合中的元素
随机移除一个元素
随机弹出一个
取出一个
spop
smembers key
smembers key
smembers k
smembers [set]
SMEMBERS set
smembers myset
smembers: 所有元素,慎用
查看指定set的所有值
K的set集合的所有数据
SMEMBERS 查看指定set中所有的值
获取:获取set集合中所有元素
smembers
sismember k
SISMEMEBER myset value
sismember: 判断存在
判断key是否在集合中
SISMEMBER 判断某个值是否存在set集合中
sismember key member
srandmember: 随机取元素
srandmember 随机抽选出set集合中元素
srandmember myset 随机抽选出set中一个元素
随机获取一个元素
srandmember [set]
随机获取num个元素
从集合中随机挑num个元素
srandmember myset 2 随机抽选出指定个数的元素
srandmember [set] [num]
srandmember
SDIFF key1 key2
sdiff k1 k2 (k1-k2)
找set1和set2中的差集
sdiff [set1] [set2]
减集
差集 SDIFF
sinter k1 k2
sinter key [key ...]
SINTER key1 key2
找set1和set2中的交集(共同关注可以这样实现)
sinter [set1] [set2]
交集
取交集
两个set的交集
sinter
sinterstore:取交集并且插入其交集
sinterstore
交集 SINTER
sunion k1 k2
sunion key [key ...]
SUNION key1 key2
找set1和set2中的并集
sunion [set1] [set2]
并集
取并集
sunion
sunionstore destination key [key ...]
sunionstore
并集 SUNION
集合sdiff / sinter / sunion
srem key e
srem myset value
srem [set] [member]
删除:srem key value:
移除指定set的指定值
srem 移除set集合中指定的值
删除set集合中的某个元素
srem
smove [oldset] [newset] [member]
从oldset移动指定的元素到newset
smove myset myset2 value 将指定value从myset移动到myset2中
将一个指定的值,移动到另外一个set集合
smove
localhost:0>sadd user:1:follow it news his sports\"4\"localhost:0>smembers user:1:follow1) \"it\"2) \"sports\"3) \"his\"4) \"news\"localhost:0>spop user:1:follow\"his\"localhost:0>smembers user:1:follow1) \"it\"2) \"sports\"3) \"news\"localhost:0>scard user:1:follow\"3\"localhost:0>sis\
集合内实战
Set(无序不重复集合)
Set操作、操作命令、常用方法、相关命令、常用命令
spop / srandmember -- random item
抽奖
点赞、签到、打卡
sadd -- tagging
商品标签
sdiff(差集)、sinter(交集)、sunion(并集)
左交并差集、交集,并集,差集的操作
商品筛选
两个大v的粉丝放在两个set,两个set做交集共同好友
用户关注模型
用在共同关注,网站uv
sadd + sinter -- social graph
集合类型 Set
ZSet
zset
sorted set
Sorted Set/Zset
SortedSet
有序set
有序集合
有序集合(zset)
排序的set
物理内存左小右大
hash: value - score
权重/聚合指令
命令获取数据时,不随命令发生变化
交集、并集
集合操作
不允许重复元素,且元素有顺序
去重,但是可以排序
score
每个元素都有score
在set的基础上增加了一个值,
zset k1 score v1,score排序;不会重复
每个元素都会关联一个double类型的分数
Redis正是通过分数来为集合中的成员进行从小到大的排序。
带score的有序集合
只需要调整前后节点指针
随机层数
还会比较value
不止比较score
zset特点
1、都必须无重复元素
2、集合是无序的,有序集合是有序的
3、集合里面只有元素,有序集合里面有元素+游标
zset和set区别
1、list可以有重复,zset无重复元素
2、list有序,zset有序
zset和list的区别
成绩
积分
最大特点是分数可以自定义排序规则
写进去时,给一个分数,自动根据分数排序,可以玩很多花样
写数据带分数,实现排行榜
用score排序,可做排行榜
排行榜
元素个数较小时,用ziplist节约空间
跳表
skiplist
skip list(跳跃表)
二分查找
插入跳表时,随机造层
本质上讲,就是牺牲存储空间,换取查询速度
skiplist+dict
skip
存储类型、存储原理、内部编码、底层数据结构
zadd
zadd k 分数 成员
zadd myzset 10
zadd myset k1 v1
zadd key score e
存储:zadd key score value
zadd [set] [score][member]
添加
添加 可批量添加
zadd k1 8 apple 2 banana 3 orange8、2、3代表分值score
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
zadd [set] [score1] [member1] [score2] [member2] ...
批量添加
zscore key e
zscore myzset java 获取元素 score
zscore k1 apple8获取对应apple的分值
zscore
zrank key member
zrank k1 apple2获取对应apple的排序
zrank
zrem key e
zrem key value
zrem myset key
zrem [set] [member]
zrem myzset php cpp
zrem key leements
zrem key member [member ...]
zremrangebyrank key start end
zremrangebyscore key min-score max-score
删除元素、移除元素
zrem
zincrby key score e
zincrby myzset 5 python 分值递增
zincrby
zcard [set]
zcard key
zcard myzset
获得指定set的数量
zcard 获取有序集合的个数
返回元素的总个数
统计元素个数
zcard
zrange
zrank key value
获取第几
排行 (从低到高)
zrange k start end
zrange key start end [withscores]
zrange key start stop [WITHSCORES]
zrange myzset 0 -1 withscores 获取全部元素
zrange k1 0 -1banana orange apple(已按照score排序存储)
获取:zrange key start end [withscores]
带score查询
zrangeByScore
zrangebyscore [set] [min] [max]
zrangeByScore k1 3 8 (获取分值从3到8)orange apple
zrangebyscore [set] [min] [max] withscores
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
zrangebyscore k 分数的最小值 分数的最大值
zrangbyscore key min-score max-score
zrangebyscore myzset 20 30
指定分数区间排行(从低到高)
根据分值区间获取元素
按照score升序获得min到max区间的所有member
zrangebyscore
zreveage k start end
zrevrange key start stop [WITHSCORES]
zrevrange key 0 3
排行 (从高到低)
获取前几
zrevrange
zrevrangebyscore [set] [min] [max]
按照score降序获得min到max区间的所有member
Redis Zrevrangebyscore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。具有相同分数值的成员按字典序的逆序(reverse lexicographical order )排列。除了成员按分数值递减的次序排列这一点外, ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
zrevrangebyscore
zremrangebyscore key min max
Redis Zremrangebyscore 命令用于移除有序集中,指定分数(score)区间内的所有成员。
zremrangebyscore
zcount myset 0 3 获取指定区间的数量
zcount key min-score max-score
zcount myzset 20 60 根据分值统计个数
zcount
zinterstore / zunionstore
常用命令、操作命令、zset操作、相关命令
排序是怎么实现的?增删改查的速度?
按照默认属性score来排序,若score一致,则按照默认字典序排序
ZrangeByScore myset -inf +inf 显示全部用户,从小到大
-inf 负无穷 +inf 正无穷
zrangebyscore myset min max withscores
ZrangeByScore myset -1 0 withscores
可以排序
升序
zrevrange myset max min
zrevrange
降序
zset排序
排序如何实现 ?
有序集合类型 Sorted Set/Zset
指令大全
geospatial
Geospatial
GEO
Geo
地图信息分析
和地理位置相关
地理空间
地理信息定位
-180到180
从-180度到180度。
有效经度
-85到85
从-85.05112878度到85.05112878度。
有效纬度
地理位置
通过范围查询得到相近编码值,在实际地理位置上也是相邻的
范围查询
推算出地理位置的信息
用于地理经纬度计算
两地之间的举例
计算距离
共享位置
使用场景
type: zset
基于Sort Set
底层实现是zset
GEO底层是zset命令
使用zset的相关命令
可以使用zset的命令操作geo
底层结构
添加地理位置
geoadd:添加地理位置的坐标。
geoadd [key] [经度] [纬度] [名称] [经度] [纬度] [名称]..
GEOADD Sicily 13.361389 38.115556 \"Palermo\" 15.087269 37.502669 \"Catania\"
geoadd city 116.39 39.91 beijing 121.48 31.40 shanghai 113.27 23.15 guangzhou 113.88 22.55 shenzhen
GEOADD china:city px py name
添加:geoadd key longitude latitude member
添加到sorted set元素的数目,但不包括已更新score的元素。
geoadd官方文档
GEOADD 添加地理位置
查看指定key的经纬度
获取:geopos key member
geopos:获取地理位置的坐标。
geopos [key] [名称] [名称] ...
geopos city beijing
GEOPOS Sicily Palermo Catania NonExisting
GEOPOS 命令返回一个数组, 数组中的每个项都由两个元素组成: 第一个元素为给定位置元素的经度, 而第二个元素则为给定位置元素的纬度。当给定的位置元素不存在时, 对应的数组项为空值。
GEOPOS 获取指定城市的经纬度
两地之间的距离
geodist:计算两个位置之间的距离。
距离:geodist key member1 member2 [unit]
geodist [key] [地点1] [地点2] [ m | km | ft | mi ]
geodist city beijing shanghai km
GEODIST Sicily Palermo Catania 默认单位m
GEODIST Sicily Palermo Catania km
m 表示单位为米。km 表示单位为千米。mi 表示单位为英里。ft 表示单位为英尺。
指定单位的参数 unit 必须是以下单位的其中一个:
计算出的距离会以双精度浮点数的形式被返回。 如果给定的位置元素不存在, 那么命令返回空值。
GEODIST 两个城市之间的距离
GeoHash
geohash
二分区间,区间编码
根据经纬度获取一个哈希值
geohash [key] [地点] [地点] [地点] ...
距离越近,获得的哈希值越接近
GEOHASH Sicily Palermo Catania
geohash:返回一个或多个位置对象的 geohash 值。
一个数组, 数组的每个项都是一个 geohash 。 命令返回的 geohash 的位置与用户给定的位置元素的位置一一对应。
该命令将返回11个字符的Geohash字符串,所以没有精度Geohash,损失相比,使用内部52位表示。返回的geohashes具有以下特性:他们可以缩短从右边的字符。它将失去精度,但仍将指向同一地区。它可以在 geohash.org 网站使用,网址 http://geohash.org/<geohash-string>。查询例子:http://geohash.org/sqdtr74hyu0.与类似的前缀字符串是附近,但相反的是不正确的,这是可能的,用不同的前缀字符串附近。返回值一个数组, 数组的每个项都是一个 geohash 。 命令返回的 geohash 的位置与用户给定的位置元素的位置一一对应。
GEOHASH 返回一个或多个位置元素的 Geohash 表示
GEORADIUS Sicily 15 37 100 km
georadius:根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
范围:georadius/georadiusbymember
范围可以使用以下其中一个单位:m 表示单位为米。km 表示单位为千米。mi 表示单位为英里。ft 表示单位为英尺。
withdist 显示到中间距离的位置
withcoord 显示他人的定位信息
georadius [key] [经度] [纬度] [半径] [单位] ...
georadius city 108 28 2000 km withcoord withdist count 2
GEORADIUS china:city 100 30 500 km withdis withcoord count 1 以100,30这个经纬度为中心,寻找方圆500km内的城市
可以搜索半径之内的所有城市
以一点为中心(给出经纬度),找出指定距离为半径的圆内地点(附近的人)
GEORADIUS 以给定的经纬度为中心, 找出某一半径内的元素
GEORADIUSBYMEMBER Sicily Agrigento 100 km
georadiusbymember:根据储存在位置集合里面的某个地点获取指定范围内的地理位置集合。
找出位于指定元素周围的其他元素
子主题
以一点为中心(使用zset中的member),找出指定距离为半径的圆内地点(附近的人)
georadiusbymember city beijing 3000 km withcoord withdist count 2
GEORADIUSBYMEMBER 找出位于指定范围内的元素,中心点是由给定的位置元素决定
zrange [key] 0 -1
通过zset命令实现
查看指定key已有地点
删除:zrem key member
zrem [key] [名称]
zrem city chengdu guangzhou
移除地理位置
六个命令、常用命令
Geo 地理位置
Bitmaps
Bitmap
bitmaps
BitMaps
bitmap
位图
bit位,位存储
按位操作
用String类型实现的一种统计二值状态的数据类型
存01二值
操作二进制位进行记录
可以记录唯二状态的记录
只有0和1两个状态
setbit
redis中每个字节占8位
setbit sign 0 0
setbit k offset v
setbit [map] [ 0 | 1 ]
添加操作
setbit key 1 1:代表着第一个字节的第1位赋值1,并转为ascli码值@
使用bitmaps记录一周七天,打卡上班的例子周1:1 周二:0 周三:0......
getbit
getbit sign 3
getbit [map] [index]
查询操作
统计操作
bitcount k [start end] 统计
bitcount [map]
bitcount sign 统计数量
bitcount
bitop op destKey key1 key2 位运算
bitpos k targetBit [start] [end] 查找
bitfield操作多个位
常用命令
可用于统计打卡
BitMaps位图
Hyperloglog
HyperLogLog
Hyperloglogs
hyperloglogs
hyperloglog
基数
Hyperloglog基数计算
用于统计基数的数据集合类型
用于基数统计的算法
基数计算
极小空间完成独立数量统计
用于统计一个集合中不重复的元素个数(基数计数),
可以解决很多精确度要求不高的统计问题
type: string
描述
占用空间小
超小内存唯一值计数
如果要从内存角度比较的化,Hyperloglog首选!
占用的内存是固定的,2^64不同的元素的技术,只需要费12kb内存!
内存占用小
有误差
统计结果有误差
标准误差为0.81%
有极小的误差
有局限性,就是只能统计基数数量,而没办法去知道具体的内容是什么
无法查看指定key的已存入元素
通过hash函数映射,并不存储元素本身
特点、特性
有错误率 0.81%
不能返回单条元素
用于求不重复的元素个数
统计页面的UV
适用于uv浏览量统计
统计在线用户数
统计注册IP数
使用场景、应用场景
命令(pf为创建该数据结构的人的名字缩写)
命令将所有元素参数添加到 HyperLogLog 数据结构中
添加:pfadd key e1 e2...
pfadd [key] [member] [member] ...
pfadd key element [element ...]
PFADD key element [element ...] 返回值:整型,如果至少有个元素被添加返回 1, 否则返回 0。
redis 127.0.0.1:6379> PFADD mykey a b c d e f g h i j(integer) 1redis 127.0.0.1:6379> PFCOUNT mykey(integer) 10
Pfadd
统计数量
计数:pfcount key
pfcount [key]
pfcount key [key ...]
命令返回给定 HyperLogLog 的基数估算值。
PFCOUNT key [key ...] 返回值:整数,返回给定 HyperLogLog 的基数值,如果多个 HyperLogLog 则返回基数估值之和。
redis 127.0.0.1:6379> PFADD hll foo bar zap(integer) 1redis 127.0.0.1:6379> PFADD hll zap zap zap(integer) 0redis 127.0.0.1:6379> PFADD hll foo bar(integer) 0redis 127.0.0.1:6379> PFCOUNT hll(integer) 3redis 127.0.0.1:6379> PFADD some-other-hll 1 2 3(integer) 1redis 127.0.0.1:6379> PFCOUNT hll some-other-hll(integer) 6redis>
Pfcount
pfmerge [newkey] [key1] [key2] ...
PFMERGE destkey sourcekey [sourcekey ...]
pfmerge destkey sourcekey [sourcekey ...]
合并:pfmerge destKey sourceKey1 sourceKey2
合并多个集合形成一个新的集合
redis 127.0.0.1:6379> PFADD hll1 foo bar zap a(integer) 1redis 127.0.0.1:6379> PFADD hll2 a b c foo(integer) 1redis 127.0.0.1:6379> PFMERGE hll3 hll1 hll2OKredis 127.0.0.1:6379> PFCOUNT hll3(integer) 6redis>
命令将多个 HyperLogLog 合并为一个 HyperLogLog ,合并后的 HyperLogLog 的基数估算值是通过对所有 给定 HyperLogLog 进行并集计算得出的
Pfmerge(并集)
Streams
三种特殊数据类型、其他数据类型、扩展类型
select index
切换库
select <dbid>:切换数据库
# 删除数据库flushdb
清空当前库
flushdb:清空当前数据库
dbsize
查看当前库key的数量
dbsize:查看数据库数据个数
清空所有库0-15(不安全)
# 删除所有数据库flushall
flushall:清空所有数据库
move key index:将键值对从当前db移动到目标db
lastsave:获取最后一次持久化操作的时间
数据库操作、db的相关命令
Keys
keys *
获取当前库的所有key
# 查看所有key-valuekey *
keys * : 查询所有的键
type key
key的类型
获取键对应的value的类型
# 查看当前key的类型type name
type key:查看key对应值的类型
del k
删除key
del key [key ...]
del
删除: del key
del key:删除指定的key value
del key:删除key
keys pattern:查看符合指定表达式的所有key,pattern可以为*,?等
exists k
判断是否存在key
exists key [key ...]
# 判断name是否存在exists name xxx
exists key:查看key是否存在
randomkey:随意选取key
exprie
设置key的有效时间
expire key seconds
expire k seconds
# 设置name的过期时间expire name 10
expire key seconds:为键值设置过期时间
ttl k
ttl key
查看key还有多久过期
查看key剩余的过期时间
# 查看剩余的过期时间ttl name
-1表示key存在,没有过期时间
-2表示key不存在
-1->永不过期,-2->已过期
TTL key:查看key还有多久过期
rename key newkey:重命名key,强制执行
renamex key newkey:重命名key,非强制执行,如果newkey存在则语句不生效
去掉key的过期时间
persist k
# 将xxx从当前数据库移除move name xxx
key常用命令操作、key的相关命令
http://www.redis.cn/commands.html
redis命令
命令操作、Redis命令中心
值类型,五大基本类型
type
type 数据类型
编码方式,底层数据结构
encoding
encoding 编码方式
*ptr
数据指针
ptr 数据指针
vm 虚拟内存
记录上次访问时间,用于淘汰过期键值对
lru
引用计数
refcount
其他
Redis基本对象结构 RedisObject
定义新类型和底层数据结构、在RedisObject中增加新类型的定义、开发新类型创建和释放函数、开发新类型的命令操作
自定义数据类型
内部存储结构
Redis 对于某种常用数据结构,底层的存储方式至少有两种,以便于根据存储数据的实际情况使用合适的存储方式。
struct sdshdr{ int len; // 已用字节数量 int free; // 空闲字节数量 char []buff; // 用于保存字符串}
简单动态字符串(SDS)
若字符串长度小于1M,则多分配len长度空间,否则多分配1M空间
空间预分配
惰性空间释放
键值的底层都是SDS
AOF缓存区
记录本身长度 C需要遍历
空间预支配
修改字符减少内存重新分配
C只能保存文本数据 无法保存图片等二进制数据
sds是使用长度去判断
杜绝缓冲区溢出
兼容部分C字符串函数
SDS - 简单动态字符串
保存多个客户端的状态信息
列表订阅发布 慢查询 监视器
链表
ADList - 双向链表
数据库 哈希键
Hash表节点
hash冲突用单向链表解决
会逐渐rehash 新的键值对全部放到新的hash表
渐进式 rehash
一个平时用 一个rehash时候用
每个字典带 两个hash表
字典
dict - 字典
整数集合
intset - 整数集合
便于查找首尾元素
连续内存,表头有zlbytes(列表长度),zltail(表尾偏移量),zlen(列表中entry个数)
zset-max-ziplist-entries 128 最大元素个数zset-max-ziplist-value 64 元素最大长度
list-max-ziplist-size -2(8kb)最大元素数量
hash-max-ziplist-entries 512hash-max-ziplist-value 64
配置项
为了节约内存,当数据量小,元素短时, list、hash、zset 采用压缩列表实现
如果前一个数据项占用内存小于254字节,则用1字节表示
如果前一个数据项占用内存大于等于254字节,则用5字节表示
previous_entry_length(上一个节点的长度)
encoding(1字节)、content
entry结构
ziplist - 压缩表
quicklist - 快速列表(ADList + ziplist)
包含一个后退指针和多个前进指针
跳表节点
跳表以有序的方式在层次化的链表中保存数据,效率和平衡树媲美
在链表的基础上增加多级索引
O(logN), 32 层索引,0.25 概率选中
双向链表 ,平均查找效率为O(logN),表头、表尾、表长、最高层数的复杂度均为O(1);
skiplist - 跳跃表
内部底层数据结构与应用
Pub/Sub
不同数据类型有相同的元数据需要记录,使用Redis Object结构统一记录元数据同时指向实际数据
存储整形值
元数据,int
int编码
字符串小于等于44字节
元数据,ptr,SDS 连续存储 避免内存碎片
embstr编码
字符串大于44字节
元数据,ptr,SDS独立空间
row编码
Redis Object
*key,*value,*next
哈希桶
哈希表其实就是一个数组,数组的每一个元素被称为哈希桶,哈希桶保存kv实体的指针
链式哈希,同一个哈希桶中的多个元素用一个链表来保存
解决哈希冲突
分配一个更大空间的哈希表2,重新映射,释放哈希表1
每处理一个请求,按序从哈希表1 rehash一个桶到哈希表2
没有新请求时定时执行rehash
由于涉及大量数据拷贝,采用 渐进式rehash
装载因子=(load factor 哈希表大小/元素数量)>= 1 允许rehash,大于等于5时立即rehash
rehash
哈希冲突链过长
全局哈希表
键值用什么结构组织?
Redis的基本操作、数据类型、基础类型、数据结构
缓存是性能提升的大杀器!
内存的读写速度是硬盘的几十倍到上百倍。
缓存实际上就是利用内存的高速读写特性,提高热点数据的操作速度。
缓存
缓存
效率高,可以用于高速缓存
可以用作数据库、缓存
缓存系统
缓存(数据查询、短连接、新闻内容、商品内容等等)
Spring Boot 支持多种缓存实现方式,可以根据项目需求灵活选择
Spring Boot 中使用缓存非常简单,并且支持多种缓存实现
缓存数据量较小的项目,可以使用 Spring Boot 默认缓存。
缓存数据量较大的项目,可以考虑使用 Ehcache 缓存框架。
如果是大型系统,对缓存的依赖性比较高,还是建议采用独立的缓存组件 Redis ,通过主备、集群等形式提高缓存服务的性能和稳定性。
Spring Boot 支持多种缓存实现方式
Spring Boot 默认缓存是基于 ConcurrenMapCacheManager 缓存管理器实现的
从这个类名就能发现,它本质上应该是一个 Map 集合容器。
ConcurrenMapCacheManager 结构比较简单,一般用于比较轻量级的缓存使用场景。
也就是缓存的数据量比较小,缓存操作不是特别频繁的场景。
基于 ConcurrenMapCacheManager 缓存管理器
Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-cache ,生成项目后导入 Eclipse 开发环境。
使用 Spring Initializr 创建项目
引入 Web 项目依赖和缓存依赖。
<!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 缓存依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
引入项目依赖
在启动类上添加注解 @EnableCaching 开启缓存功能。
添加注解 @EnableCaching 开启缓存功能
正常服务层方法会调用数据访问层方法访问数据库,此处我们只需要演示缓存的作用,所以打印日志代替数据库访问方法。
//商品服务类 @Service@CacheConfig(cacheNames = \"GoodsCache\")public class GoodsService { private Logger logger = LoggerFactory.getLogger(this.getClass()); //按id获取商品信息 @Cacheable public GoodsDo getById(Long id) { logger.info(\"getById({})\
演示缓存的作用
对于使用缓存的 GoodsService 服务类,具体解释下:
用于指定本类中方法使用的缓存名称
该类使用的缓存名称为 GoodsCache
与其他缓存区域是隔离的。
@CacheConfig 注解
开启方法缓存,缓存的键是方法的参数,缓存的值是方法的返回值。
如果多次调用该方法时参数 id 值相同,则第一次会执行方法体,并将返回值放入缓存;
后续方法不会再执行方法体,直接将缓存的值返回。
@Cacheable注解
可以更新缓存,key = \"#id\" 表示采用参数中的 id 属性作为键。
当缓存中该键的值不存在时,则将返回值放入缓存;当缓存中该键的值已存在时,会更新缓存的内容。
@CachePut注解
可以移除缓存,当调用该方法时,会移除 goods 中 id 属性对应的缓存内容。
@CacheEvict 注解
定义服务层方法
为了充分理解缓存的含义,我们通过测试类发起测试。
@SpringBootTestclass SpringBootCacheApplicationTests { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private CacheManager cacheManager; @Autowired private GoodsService goodsService; // 显示当前使用的缓存管理器类型 @Test void showCacheManager() { // 输出:org.springframework.cache.concurrent.ConcurrentMapCacheManager logger.info(cacheManager.getClass().toString()); } // 缓存测试 @Test void cacheTest() { // 第一次执行,没有缓存,执行方法体 goodsService.getById(1L); // 再次执行,直接取出缓存,不执行方法体 goodsService.getById(1L); // 移除缓存 goodsService.remove(1L); // 再次执行,已经没有对应缓存,所以执行方法体 GoodsDo oldGoods = goodsService.getById(1L); // 打印缓存内容 logger.info(\"old goods id:{} name:{}\
测试类代码
使用 Spring Boot 默认缓存时控制台输出内容
查看下控制台输出如下,验证了设计的缓存机制。
测试与验证
Spring Boot 默认缓存实现过程
Spring Boot 默认缓存
Spring Boot 默认的缓存实现比较简单,功能也十分有限。
如果是企业级的中大型应用,需要寻求更加稳定、可靠的缓存框架。
Ehcache 是 Java 编程领域非常著名的缓存框架
具备两级缓存数据——内存和磁盘,因此不必担心内存容量问题。
另外 Ehcache 缓存的数据会在 JVM 重启时自动加载,不必担心断电丢失缓存的问题。
总之 Ehcache 的功能完整性和运行稳定性远远强于 Spring Boot 默认的缓存实现方式,而且 Spring Boot 使用 Ehcache 非常便捷
缓存框架
我们在 spring-boot-cache 项目的基础上添加 Ehcache 依赖。
<!-- Ehcache 依赖 --> <dependency> <groupId>org.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> <!-- cache-api 依赖 --> <dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency>
添加 Ehcache 依赖
spring.cache.jcache.config=classpath:ehcache.xmlspring.cache.type=jcache
在 properties文件中指定配置文件的位置
Ehcache 的配置比较复杂
然后在 resource 文件夹中添加 ehcache.xml 配置文件,内容如下:
<?xml version=\"1.0\" encoding=\"UTF-8\"?><config xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns='http://www.ehcache.org/v3' xsi:schemaLocation=\"http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core.xsd\"> <!-- 持久化路径 --> <persistence directory=\"C://ehcache\" /> <!--缓存模板 --> <cache-template name=\"CacheTemplate\"> <expiry> <!--存活时间 --> <tti>60</tti> </expiry> <resources> <!--堆空间 --> <heap unit=\"entries\">2000</heap> <!-- 堆外空间 --> <offheap unit=\"MB\">500</offheap> </resources> </cache-template> <!--缓存对象 --> <cache alias=\"GoodsCache\" uses-template=\"CacheTemplate\"> </cache></config>
添加 ehcache.xml 配置文件
由于之前已经在启动类添加 @EnableCaching ,我们再次运行测试类,输出结果如下。
使用 Ehcache 时控制台输出内容
注意控制台出现了 EhcacheManager 的字样,说明我们此时使用的缓存是 Ehcache 。
接下来就来实现下。
使用 Ehcache 缓存
Ehcache 依然是 Java 进程内的缓存框架
受限于 JVM 整体的内存分配策略。
如果是大型系统,缓存的数据量特别大,且性能要求很高,可以考虑直接使用 Redis 作为缓存。
Redis 可以采用单机、主备、集群等模式,视乎具体项目需求决定即可。
华为云提供的缓存服务
目前各大云计算厂商均提供商用版的 Redis 缓存服务,性能卓越且接入简单快速。
本节简单地演示 Spring Boot 中使用 Redis 单机缓存的方法,真实生产环境中建议至少使用主备类型的 Redis 实例。
为什么还需要Redis缓存?
<!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 缓存依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Redis 相关依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>
因为需要使用 Redis 缓存,所以将引入的依赖项修改如下:
修改 application.properties 配置文件,将 Redis 配置及缓存配置设置如下:
# 过期时间spring.cache.redis.time-to-live=6000s# Redis库的编号spring.redis.database=0# Redis实例地址spring.redis.host=127.0.0.1# Redis实例端口号,默认6379spring.redis.port=6379# Redis登录密码spring.redis.password=Easy@0122# Redis连接池最大连接数spring.redis.jedis.pool.max-active=10# Redis连接池最大空闲连接数spring.redis.jedis.pool.max-idle=10# Redis连接池最小空闲连接数spring.redis.jedis.pool.min-idle=0
修改缓存配置
修改缓存依赖
由于之前已经通过注解 @EnableCaching 开启了缓存功能,此时我们直接运行测试类进行测试,输出结果如下:
使用 Redis 缓存时控制台输出内容
从上图输出结果可以看出,已经成功使用了 Redis 缓存管理器。
另外直接使用 Redis 客户端查看生成的缓存信息,如下图已经有名为 GoodsCache::1 的缓存键存在了。
Redis 客户端查看缓存信息
测试验证
使用 Redis 缓存
(1)缓存、分布式缓存
Redis是一个基于Key-Value对的NoSQL数据库,开发维护很活跃。
虽然它是一个Key-Value数据库存储系统,但它本身支持MQ功能,所以完全可以当做一个轻量级的队列服务来使用。
各执行100万次,每10万次记录一次执行时间。
测试数据分为128Bytes、512Bytes、1K和10K四个不同大小的数据。
当数据比较小时,Redis的性能要高于RabbitMQ
如果数据大小超过了10K,Redis则慢的无法忍受
入队
无论数据大小,Redis都表现出非常好的性能
而RabbitMQ的出队性能则远低于Redis。
出队
实验表明
Redis和RabbitMQ的入队和出队操作对比
publish channel-name msg
subscribe/unsubscribe channel-name
PubSub
订阅者存放在一个链表中,发布者发布消息后,根据频道的key推送给该频道的订阅者
发布者
订阅者
频道(channel)
角色
模型
发布订阅和消息订阅对比
发布订阅系统
发布订阅PubSub
(2)消息中间件、消息队列、消息中间件MQ
大型网站访问次数的查询、更新非常频繁,
如果通过关系数据库读写,无疑会耗费大量的性能,
而使用 Redis 可以大幅提高速度并降低对关系数据库的消耗。
原理说明
Spring Boot 版本选择 2.2.5 ,Group 为 com.imooc , Artifact 为 spring-boot-redis
生成项目后导入 Eclipse 开发环境。
引入 Web 项目依赖与 Redis 依赖。
<!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Redis 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
修改 application.properties 配置文件内容如下。
# Redis库的编号spring.redis.database=0# Redis实例地址spring.redis.host=127.0.0.1# Redis实例端口号,默认6379spring.redis.port=6379# Redis登录密码spring.redis.password=Easy@0122# Redis连接池最大连接数spring.redis.jedis.pool.max-active=10# Redis连接池最大空闲连接数spring.redis.jedis.pool.max-idle=10# Redis连接池最小空闲连接数spring.redis.jedis.pool.min-idle=0
配置 Redis 数据库连接
开发网站访问统计服务类,在第 1 次获取访问次数时初始化次数为 0 ,后续每次访问次数加 1 。
//网站访问统计服务类 @Servicepublic class VisitService { // 设定访问次数Redis键名 private final static String KEY = \"visit_count\
开发网站访问统计服务类
我们通过测试类发起并发访问测试,代码如下:
/** * 访问统计服务测试 */@SpringBootTestclass VisitServiceTest { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private VisitService visitService; @Test void test() { logger.info(\"访问次数:{}\
此时我们通过 Redis 客户端发现 visit_count 的值如下:
并发访问测试结果
Redis 中的操作都是原子性的,要么执行,要么不执行,在高并发场景下依然可以准确的进行计数,关键是速度还非常之快!
并发访问测试
(3)网站的访问次数、网站访问统计
如果是大型网站,时刻有很多用户在访问网页,对热门商品排行榜的访问频率是非常恐怖的。
可以通过定时器,定时从关系数据库中,取出热门商品数据放入 Redis 缓存,
用户访问网页时,直接从Redis 缓存中获取热门商品数据。
这将大大提高响应速度,并降低对关系数据库的性能损耗。
简单的定义一个商品类,便于展现商品排行榜数据。
//商品类 public class GoodsDo { //商品id private Long id; // 商品名称 private String name; // 商品价格 private String price; //商品图片 private String pic; // 省略get set方法}
定义商品类
开发商品排行榜服务类,负责从数据库查询最新排行榜信息,并更新到 Redis ,以及从 Redis 中取出排行榜信息。
//商品排行榜服务类 @Servicepublic class GoodsRankService { // 设定商品排行榜Redis键名 private final static String KEY = \"goods_rank\
开发商品排行榜服务类
为启动类添加 @EnableScheduling 注解,以便开启定时任务,然后编写 RankListUpdateTask 类定时刷新排行榜。
通过定时器更新排行榜
需要一个控制器方法,用于演示获取商品列表的结果。
@RestControllerpublic class GoodsRankController { @Autowired private GoodsRankService goodsRankService; @GetMapping(\"getRankList\") public List getRankList() throws Exception { return goodsRankService.getRandkList(); }}
开发控制器方法
[{\"id\
运行启动类,然后访问 http://127.0.0.1:8080/getRankList ,结果如下:
稍等会再次访问,结果如下:
说明设计的缓存机制生效了。
测试
(4)热门商品排行榜、应用排行榜
使用setnx加锁,锁的value值为一个随机生成的UUID,在释放锁时进行判断。
并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁。
当且仅当key不存在时,set一个key为val的字符串,返回1
若key存在,则什么都不做,返回0
setnx的使用(SETNX key val)
获取锁时,
调用setnx,如果返回0,则该锁正在被别人使用,返回1则成功获取锁。
还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
获取锁时
通过UUID判断是不是该锁,
若是该锁,则执行delete进行锁释放。
释放锁时
先用setnx(set if not exists)抢锁,抢到之后再用expire命令给锁加一个过期时间,防止忘记释放锁
如果setnx之后expire之前进程意外结束或者需要重启,需要把setnx和expire合成一条命令来执行(保证原子性): set key value ex 5 nx
setnx + expire
set xx ex 5 nx
命令
Redlock算法
集群问题
常用
小扩展redlock(不算太重要)
(5)分布式锁
分布式集群架构中的session分离
分布式会话
(6)分布式会话
计时器、计数器(浏览量) 设计
计时器、计数器
定时push
然后leftpop
重试
空连接异常
空轮训
问题
令牌
Redis-Cell
原子性有问题
make_space 灌水之前调用漏水 腾出空间 取决于流水速率
漏桶 funnel
redis cell
cl.throttle key capacity count period 1
漏斗限流: redis-cell模块
key: clientId-actionId
value: ms
score: ms
简单限流: zset实现滑动时间窗口
setnx ex
窗口滑动
zset会越来越大
(7)计数器、限流
需要实时读写数据库时,例如评论数、点赞数
(8)实时系统
手机验证码
手机验证码
短信校验是常用到的一项保证安全的,短信的验证码都是随机产生的,而且信息都是有时间限制的,一旦过了那个时间的有效期,验证信息就会自动删除。
图例
代码
(9)短信验证
布隆过滤器
可以用于检索一个元素是否在一个集合中
bf.add
bf.madd
bf.add / bf.madd
bf.exists
bf.mexists
bf.exists / bf.mexists
命令操作
m 个二进制向量
n 个预备数据
k 个哈希函数
参数
n个预备数据,分别进行k个哈希,得出offset,将相应位置的二进制向量置为1
构建
进行k个哈希,得出offset,如果全为1,则判断存在
判断
原理
有一定的误差
当布隆过滤器说某个值存在时,这个值可能不存在;当它说某个值不存在时,那就肯定不存在
特性
与 k (哈希函数)个数成反比
与 n (预备数据)个数成正比
与 m (二进制向量)长度成反比
误差率
爬虫过滤已抓到的url就不再抓
邮件系统的垃圾邮件过滤功能
新闻客户端推荐系统去重
(10)布隆过滤器(Bloom Filter)
聊天室的在线好友列表
社交网络
keys
scan
搜索key
keys算法是遍历算法,redis是单线程,顺序执行所有指令,其他指令必须等到keys指令执行完了才能继续执行,会导致redis服务卡顿
keys pattern
不会阻塞线程
提供limit参数,可以控制每次返回结果的最大条数
返回的结果可能会有重复,需要客户端去重
遍历的过程中如果有数据修改,改动后的数据能不能遍历到是不确定的
单次返回的结果是空的并不意味着遍历结束,而是要看返回的游标值是否为0
scan cursor [MATCH pattern] [COUNT count]
检索某个key是否存在
秒杀
抢购
12306
任务队列
list结构作为队列,rpush生产消息,lpop消费消息(先进先出:队列),当lpop没有消息时,要适当sleep一会再试,
缺点:空轮询浪费资源
异步队列
lpush / rpush
rpop / lpop - brpop / blpop
订单超过30分钟未支付,自动关闭
音频仿真体验者角色15天有效期,到期前1天发邮件提醒
延时队列
Stream
数据过期处理(可以精确到毫秒)
使用乐观锁解决
使用lua脚本解决
秒杀富余
商品超卖、秒杀富余
添加缓存的逻辑的原则:缓存逻辑不能影响正常的业务逻辑执行
众多语言都支持Redis,因为Redis交换数据快,所以在服务器中常用来存储一些需要频繁调取的数据,这样可以大大节省系统直接读取磁盘来获得数据的I/O开销,更重要的是可以极大提升速度。
拿大型网站来举个例子,比如a网站首页一天有100万人访问,其中有一个板块为推荐新闻。要是直接从数据库查询,那么一天就要多消耗100万次数据库请求。上面已经说过,Redis支持丰富的数据类型,所以这完全可以用Redis来完成,将这种热点数据存到Redis(内存)中,要用的时候,直接从内存取,极大的提高了速度和节约了服务器的开销。
Redis应用场景、使用场景、案例、能做些什么
可以用作数据库、缓存和消息中间件
Redis的单线程为什么这么快?
为什么Redis性能高?
为什么redis单线程效率那么高
redis是单线程,为什么这么快
redis为什么单线程还这么快?
Redis为什么这么快
具体的问题如下
Redis是单线程的
Redis是单进程单线程模型
Redis其实是单线程的
解释:并不是多线程的性能就一定高
1、高性能的服务器一定是多线程
解释:cpu上下文切换也需要消耗性能,这种切换一旦过多,就会造成资源浪费
2、多线程(CPU上下文切换)一定比单线程效率高
关于单线程的误区
Redis是非关系型数据库,数据与数据之间没有联系
可以通过多开Redis实例完善
无法发挥多核cpu的性能
单线程的缺点
1、一次只运行一条命了
2、拒绝长命令或者慢命令
fysnc
close
3、redis并不是所有操作都是单线程
单线程的注意事项
比如cpu要到磁盘读取数据,cpu就需要等待磁盘将数据放入内存 ,这段时间很长,多线程的话CPU就不需要等待,直接去处理其他的请求,等到磁盘的数据到内存中,再来处理该数据
平衡计算机硬件之间的差异
为什么需要多线程
避免线程切换和竞态消耗
避免了多线程的频繁上下文切换
避免了线程的切换
避免了多线程的频繁上下文切换问题
对于内存来讲,如果没有上下文切换效率是最高的
多线程(CPU上下文切换:耗时操作)
线程间切换,一个线程让出处理器使用权,就是”切出“;另外一个线程获取处理器使用权,就是切入。
在切入切出过程中,操作系统会保存和恢复相关的进度信息。
这个进度信息就是常说的“上下文”,
寄存器的存储内容
程序计数器存储的指令内容
上下文中一般包含了
什么是上下文切换问题?
避免了不必要的上下文切换
避免了不必要的竞争条件
避免锁
采用单线程的好处
Redis其实是单线程的(核心)
与传统关系型数据库相比,它最大的优势就是读写速度快。
使用 Windows 版本的 Redis 进行过真实测试,每秒读写次数均可以超过1 万次。
据了解, Redis 每秒的读写操作次数其实是可以达到 10 万多次的。
Redis到底有多快?
读写速度快
多次读写都在一个CPU上操作的,在内存情况下,单线程就是最佳方案
这里的单线程只是在网络IO和键值对读写上是单线程,其他处理还是会使用多线程,例如AOF
Redis并不是所有操作都是单线程
Redis的读写操作是单线程的
纯内存操作
redis的大量操作是在操作内存
内存数据库,速度快,支持数据持久化
纯内存
纯内存 k/v
Redis的数据保存在内存中
将所有数据放入内存中的,所以使用单线程操作效率就是最高的
完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。
文件事件分派器和三个处理器都是基于纯内存快速处理请求,速度非常快。微秒级。
redis是基于内存操作的,CPU不是redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽
它基于内存操作,它的性能瓶颈其实是内存大小,频率,以及网络带宽
完全基于内存,纯内存操作
想使用一个单线程去支持高并发,那么线程就不能阻塞
io多路复用技术
让一个线程去管理多个连接
Redis里面是单线程,想支持并发连接怎么办
核心的nio的io多路复用
使用多路I/O复用模型
多路复用技术
io多路复用
非阻塞IO
IO多路复用,非阻塞I/O
IO多路复用程序,只负责轮训所有连接的socket,监听说有socket产生的请求,然后压到队列里面去。
非阻塞是指IO多路复用程序只把请求压到队列,不进行处理。
实现同时对多个文件描述符(fd)进行监控,一旦某个描述符就绪,就通知程序进行操作
服务端采用单线程,当 accept 一个请求后,在 recv 或 send 调用阻塞时,将无法 accept 其他请求(必须等上一个请求处理 recv 或 send 完 )
核心是非阻塞IO多路复用机制
指令队列
响应队列
多路复用中的两种队列
int epoll_create(int size); // 内核中间加一个 ep 对象,把所有需要监听的 socket 都放到 ep 对象中
epoll事件轮询API
使用多路I/O复用模型,非阻塞I/O
Redis的单线程为什么速度这么快?
把内存的数据写到磁盘中,防止宕机内存的数据丢失
什么是持久化
Redis持久化
redis持久化
Redis如何避免数据丢失?
redis的所有数据都保存在内存中,对数据的更新将异步地保存到磁盘中
内存存储,持久化,内存中是断电即失,所以持久化很重要 AOF,RDB)
内存存储
磁盘存储
log文件
Redis的存储分为三部分
什么是Redis持久化
redis是一个内存数据库
Redis是内存数据库,断电就会丢失所有数据,所以必须有持久化操作
当redis服务器重启,获取电脑重启,数据会丢失,
可以将redis内存中的数据持久化保存到硬盘的文件中。
Redis所有的数据保存在内存中,对数据的更新将异步地保存到磁盘中
Redis提供类RDB和AOF进行持久化操作
重启后,Redis可以从磁盘重新将数据加载到内存中,这些可以通过配置文件对其进行配置
故障恢复,比如部署了redis作为缓存,保存了一些重要的数据。
持久化主要做灾难恢复数据恢复,也可以归类到一个高可用的环节里面去
通过持久化将数据写到磁盘上,然后把磁盘上的数据备份到云存储上去。保证数据不会丢失
持久化+备份
为什么要进行持久化操作?持久化的意义
使用一致性哈希实现动态扩容缩容
必须使用固定的keys-to-nodes映射关系,节点一旦确定不能变化
如果还需要动态化,会需要将之前的数据进行再平衡操作,增加复杂性
Redis持久化数据和缓存怎么扩容
一般预留 30%
config set maxmemory 6GB
config rewrite
设置内存上限
动态调整内存上限
无法保证立即回收已经删除的 key 的内存
内存回收
setex key seconds value
设置过期时间
定时扫描
定时过期(主动淘汰)
内存友好,占用cpu资源去处理
创建一个定时器
消耗内存
每个过期时间都需要一个定时器,过期清理
给每一个设置了过期时间的key分配一个线程,该线程睡眠了过期时间后,起来删除该key
定时删除的原理:
缺点:当过期的key特别多时,创建的线程也会非常多,特别消耗cpu资源
每秒运行 10 次,采样删除
循环执行:快模式??
如果超过25%的key过期
否则退出
慢模式:随机检查 20 个key
1.测试随机的20个keys进行相关过期检测。2.删除所有已经过期的keys。3.如果有多于25%的keys过期,重复步骤1。算法默认不会超过25ms的扫描时间
Redis每秒10次做的事情
如果大量key同一时间失效,会造成循环多次,直至过期key变得稀疏(1/4以下),才会停止。频繁回收内存页,会产生一定CPU损耗,客户端读写请求卡顿。
问题:
大量key过期时间不能设置为同一时间点,尽量设置为随机数+时间点!+ 注:从库是不会进行过期扫描的,由主库生成del语句,进行淘汰key操作。
解决方案:
定时删除、定时过期
每隔一定时间去扫描库中expires字典一定数量的key,清除
定期删除原理:专门有一个线程监视所有设置了过期时间的key的时间,如果过期,将该key删除
redis默认每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,过期则删除。定期删除是随机抽取一部分并不是全部,所以可能会导致很多过期key到了时间没有被删除掉。
检查 删除 但是是随机的
缺点:实时性差一点
定期删除、定期过期
可能存在大量key
访问该key,才会判断是否过期,节省cpu,耗内存
极端下会出现大量过期的key,而不会被清除,占用内存
当你去get一个key的时候,redis会检查一下这个key是否过期了,如果过期了,那么就删除,这就是惰性删除。
惰性删除原理:当用户访问该key 时,会判断该key 是否过期了如果过期了就删除该key,给用户返回null,如果没有过期就返回value
缺点:如果用户一直不访问该key,它就一直不会删除,会一直占用内存
访问key
expired dict
del key
惰性过期(被动淘汰)
在客户端访问key时再进行检查如果过期了就立即删除unlink指令针对于del的key是一个非常大的对象,延迟删除,将其交给后台异步线程操作。
访问key时才检查是否过期,过期则删除更新
del - unlink
flushdb - flushdb async
惰性删除、惰性策略、懒惰删除、惰性过期
三种过期键的删除策略
主动删除
被动删除
(Redis默认)
可以互相解决缺点
惰性过期和定期过期
Redis使用的过期键删除策略- 惰性删除 + 定期删除
定期删除+惰性删除结合
Redis中同时使用了两种过期策略
一个key 设置过期时间后(expire k seconds),能自动的删除一个操作将在未来的某个确定时间发生。
出现的问题
订单的过期取消 (下了一个订单,超过30min没有支付,则自动取消)
key的过期删除(给key设置了一个过期时间,到达这个过期时间后,key能自动的删除)
实践:过期时间随机化
详解博客文档:https://blog.csdn.net/alex_xfboy/article/details/88959647
官方文档:http://redis.cn/commands/expire.html
学习链接
过期键的删除策略
maxmemory-policy
缓存是基于内存的,内存是有限的,如果存储数据超过内存大小,数据会被干掉。
如下因为删除是定期删除+惰性删除
设置了key过期时间,但是没到时间就失效了。或者数据过期时间到了,差不到了,但是还占用着内存。
定期删除+惰性删除
1.假设你设置了一个一批key只能存活一个小时,那么接下来的一个小时后,redis是怎么对这批key进行删除的?
定期删除:redis每隔100毫秒就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期了就删除。注意是随机检查一些key,不是所有的key都遍历一遍检查,否则cpu受不了。
惰性删除:后面获取某个key的时候,redis会检查一下key是否过期,过期了就删除,不会给你返回任何东西。并不是key到期了就被删除,而是查询key的时候,懒惰的删除。
走内存淘汰机制
2.因为过期key但是没有走惰性删除或者其他的key过多,导致内存块耗尽了,咋整?
内存淘汰:如果redis占用过多的时候,此时会进行内存淘汰,有如下一些策略1.noevication:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用,太恶心了。2.allkeys-lru:当内存满了,在键空间中,移除最近最少使用的key(最少使用的key)3.allkeys-random:内存满了,随机找一些key删除。4.volatile-lru:内存满了,只对设置了过期时间的key,使用最少的删除。5.volatile-random:内存满了,随机删除设置了过期时间的key。6.volatile-ttl:当内存满了,对有更早过期时间的key优先删除。
手写个LRU
redis的过期策略是什么?手写个LRU算法?
数据淘汰策略
Redis设置maxmemory-policy的方式 ,超过最大的容量后会怎么做
描述:Redis是基于内存结构进行数据缓存的,当内存资源消耗完毕,想要有新的数据缓存进来,要从现有的Redis内存结构中释放一些数据
内存淘汰指的是,当配置了内存限制时,redis中存储数据量达到限制值,就要进行数据淘汰。redis使用的是近似LRU算法LRU:使用lru算法;random:随机+ 如果你只是拿 Redis 做缓存,那最好使用 allkeys-xxx,客户端写缓存时不必携带过期时间。+ 如果你还想同时具备持久化功能,那就使用 volatile-xxx 策略,好处就是,没有设置过期时间的 key不会被LRU算法淘汰
ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰
描述:Redis是基于内存结构进行数据缓存的,当内存资源消耗完毕,想要有新的数据缓存进来,要从现有的Redis内存结构中释放一些数据
Redis淘汰策略
最近没有使用算法
不需要额外的存储空间,使用一个双向连表就能实现
如果一个数据在最近一段时间没有被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最久没有访问的数据最先被置换(淘汰)
LRU(least recently use)算法
最近使用频率最少的key
需要一个能记录key 使用次数的空间,使用ZSet 这样的结构就能实现
如果一个数据在最近一段时间很少被访问到,那么可以认为在将来它被访问的可能性也很小。因此,当空间满时,最小频率访问的数据最先被淘汰
LFU(least recently use)算法
最少使用
LRU
LRU淘汰原理
LFU淘汰原理
内存淘汰策略介绍
(驱逐)
默认策略,拒绝写入操作
noeviction(Redis默认)
这是默认的淘汰策略!!!
noeviction(默认)
【这种一般没有人用】
noeviction 报错
禁止驱逐数据
不做删除操作,新写入操作报错
当超过最大容量,不会删除任何key,返回一个错误
当内存不足以容纳新数据,新写入数据报错
# noeviction -> 不淘汰任何数据,当内存不够时直接抛出异常.
不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。
这样可以保证不会丢失数据,但是会让线上的业务不能持续进行。
no-enviction(Redis默认,直接抛出异常)
【最常用】
allkeys-lru
allkeys-LRU
# allkeys-lru ->
在所有key中LRU
LRU算法删除所有key
通过lru算法,删除最近没有使用的key
从所有数据中淘汰最久未使⽤的数据.
LRU算法
移除最近最少使用的key
从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
当内存不足以容纳新的数据,移除最近最少使用的key
区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不只是过期的 key 集合。
这意味着没有设置过期时间的 key 也会被淘汰。
数据呈现幂律分布
部分数据访问频率高,部分数据访问频率低
使用场景、使用策略规则
所有key(不和volatile一样)
使用LRU算法
条件
allkeys-lru(最常用)
内存不足。随机溢出某个key
随机删除所有key
在所有key的集合中随机删除一个key
# allkeys-random -> 从所有数据中随机淘汰⼀批数据.
从数据集(server.db[i].dict)中任意选择数据淘汰
跟上面一样,不过淘汰的策略是随机的 key
allkeys-random
allkeys-random 随机移除某个key
数据呈现平等分布
所有的数据访问频率都相同
allkeys-random使用场景、使用策略规则
allkeys-random(随机溢出某个key)
全局的键空间选择性移除
volatile-LRU
volatile-lru 移除最近最少使用的key
LRU算法删除 有expire的key
LRU: Least Recently Used
volatile-lru LRU算法
Least Recently used 在设置过期时间的key中,删除最近最少使用
当内存不足,在设置了过期时间的key中移除,最近最少使用的key
# volatile-lru -> 从设置了过期时间的数据中淘汰最久未使⽤的数据.
从设置了过期时间的key集合中删除最近没有使用的key
从设置了过期时间的数据中淘汰最久未使⽤的数据.
从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过期时间的 key 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。两个条件:+ 设置了过期时间+ 使用了LRU算法淘汰最少使用
volatile-lru
内存不足,在设置了过期时间的key中随机移除一个key
volatile-random 随机移除某个key
随机删除过期key
在设置了过期时间的key的集合中随机删除一个key
从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
# volatile-random -> 从设置了过期时间的数据中随机淘汰⼀批数据.
volatile-random
跟上面一样,不过淘汰的 key 是过期 key 集合中随机的 key+ 设置了过期时间+ 随机淘汰
volatile-TTL
volatile-ttl
删除最近将要过期key
Time To Live 在设置过期时间的key中,删除剩余存活时间最短的key
volatile-ttl 有更早过期时间的key优先移除
在设置了过期时间的key集合中删除即将过期的key
内存不足,在设置过期时间的key中,有更早过期时间的key被移除
# volatile-ttl -> 淘汰过期时间最短的数据.
从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。根据剩余过期时间来淘汰
设置过期时间的键空间选择性移除
LFU: Least Frequently Used
volatile-lfu
# volatile-lfu -> 从设置了过期时间的数据中淘汰使⽤最少的数据.
从设置了过期时间的key集合中删除最近使用频率最少的key
从设置了过期时间的数据中淘汰使⽤最少的数据.
allkeys-lfu
# allkeys-lfu -> 从所有数据中淘汰使⽤最少的数据.
在所有key的集合中 删除最近使用频率最少的key
在所有key中TTL
allkeys-TTL
其他内存淘汰策略
八种内存淘汰策略、数据/内存淘汰机制、内存溢出控制策略
用VM实现
热数据经常访问,放在内存中,冷数据写入磁盘
冷热数据分离
内存回收策略、内存放不下怎么办
基于linkedhashmap实现最简单的lru【核心是他的removeEldestEntry】
拒绝Java原生
序列化与压缩
读写快速
内存的好处
持久化
内存断电即失
redis自身内存
key: 不要过长
value: ziplist / intset 等优化方式
优化
内存碎片
对象内存
normal 0 0 0
默认无限制,注意防止大命令或 monitor:可能导致内存占用超大!!
找到monitor客户端:client list | grep -v \"omem=0\"
普通客户端
slave 256mb 64mb 60
可能阻塞:主从延迟高时,从节点过多时
slave 客户端
pubsub 32mb 8mb 60
可能阻塞:生产大于消费时
pubsub 客户端
输出缓冲区
最大 1GB
输入缓冲区
客户端缓冲区
repl_back_buffer
默认1M,建议调大 例如100M
防止网络抖动时出现slave全量复制
复制缓冲区
无限制
AOF 缓冲区
缓冲内存
lua内存
used_memory
从操作系统角度看redis进程占用的总物理内存
used_memory_rss
内存碎片 used_memory_rss / used_memory 1
内存碎片必然存在
安全重启
mem_fragmentation_ratio
mem_allocator
内存查看:info memory
bgsave
bgrewriteaof
场景
去掉 THP 特性
观察写入量
overcommit_memory = 1
子进程内存消耗
内存管理
1、在指定时间间隔内将内存中的数据集快照写入磁盘,恢复时将快照文件直接读到内存中;
默认方式,不需要进行配置,默认就使用这种机制 * 在一定的间隔时间中,检测key的变化情况,然后持久化数据
2、默认开启
3、Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
4、最后一次持久化的数据可能丢失
简介
save second count:second秒内如果至少有count个key值变化,则进行保存
save \"\":禁用RDB模式
保存策略
save:保存策略
dbfilename:RDB快照文件名
dir:指定RDB快照保存的目录
stop-writes-on-bgsave-error:备份出错时,是否继续接受写操作,默认会停止
rdbcompression:是否压缩存储快照,若压缩则会采用LZF算法进行压缩
rdbchecksum:是否进行数据校验,若校验则会采用CRC64算法来校验,会增加10%的性能消耗
属性配置
5分钟一次
冷备
恢复的时候比较快
快照文件生成时间久,消耗cpu
(1)redis主进程调用fork()方法创建一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
(2)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
优点
缺点:快照持久化期间修改的数据不会被保存,可能丢失数据。
优缺点
1. 编辑redis.windwos.conf文件 # after 900 sec (15 min) if at least 1 key changed save 900 1 # after 300 sec (5 min) if at least 10 keys changed save 300 10 # after 60 sec if at least 10000 keys changed save 60 10000
2. 重新启动redis服务器,并指定配置文件名称 D:\\JavaWeb2018\\day23_redis\\资料\edis\\windows-64\edis-2.8.9>redis-server.exe redis.windows.conf
步骤
Redis DataBase
redis database
RDB(Redis DataBase)
RDB的全称
RDB是什么?
内存快照,将内存中所有数据记录到磁盘
按照一定的时间将内存的数据以快照的形式,保存到硬盘中
在指定时间间隔内,将内存中的数据集快照写入磁盘
在指定的时间间隔对你的数据进行快照存储
能够在指定的时间间隔能对你的数据进行快照存储
快照(snapshotting)
行话说的snapshot快照,恢复时是将快照文件直接读到内存里
RDB的特点
默认开启
RDB(默认)
RDB(Redis DataBase)文件
RDB(Redis Database)(默认)
rdb(redis database)默认开启
RDB默认开启
保存策略
Save the DB on disk:保存数据库到磁盘
生成dump.rdb 通过配置文件save参数来定义快照周期
#配置快照(rdb)促发规则
save <seconds> <changes>
save <秒> <更新>
save second count
second秒内如果至少有count个key值变化,则进行保存
如果指定的秒数和数据库写操作次数都满足了,就将数据库保存。
格式
900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化)
900秒内至少有1个key被改变则做一次快照
表示900s内有1条写入,就产生快照
save 900 1
300秒内至少有300个key被改变则做一次快照
300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化)
表示300s内有10条写入,就产生快照
save 300 10
60秒内至少有10000个key被改变则做一次快照
60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 60 10000
下面是保存操作的实例
save m n 时间策略
save \"\"
禁用RDB模式
关闭该规则使用
注释:注释掉“save”这一行配置项就可以让保存数据库功能失效。
你也可以通过增加一个只有一个空字符串的配置项(如下面的实例)来去掉前面的“save”配置。
save:保存策略、时间策略
dbfilename dump.rdb
dbfilename dump-${port}.rdb
dbfilename:RDB快照文件名
#rdb持久化存储数据库文件名,默认为dump.rdb
导出数据库的文件名称
图解
dir \"/etc/redis\"
dir /bigdiskpath
dir ./
工作目录
dir:指定RDB快照保存的目录
#数据文件存放目录,rdb快照文件和aof文件都会存放至该目录,请确保有写权限
导出的数据库会被写入这个目录,文件名就是上面'dbfilename'配置项指定的文件名。
只增的文件也会在这个目录创建
注意你一定要在这个配置一个工作目录,而不是文件名称。
stop-write-on-bgsave-error yes
yes代表当使用bgsave命令持久化出错时候停止写RDB快照文件
no表明忽略错误继续写文件
备份出错时,是否继续接受写操作,默认会停止
#在默认情况下,如果RDB快照持久化操作被激活(至少一个条件被激活)并且持久化操作失败,Redis则会停止接受更新操作。 #这样会让用户了解到数据没有被正确的存储到磁盘上。否则没人会注意到这个问题,可能会造成灾难。 # #如果后台存储(持久化)操作进程再次工作,Redis会自动允许更新操作。 # #然而,如果已经恰当的配置了对Redis服务器的监视和备份,你也许想关掉这项功能。 #如此一来即使后台保存操作出错,redis也仍然可以继续像平常一样工作。
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbcompression:是否压缩存储快照,若压缩则会采用LZF算法进行压缩
#是否开启RDB文件压缩,该功能可以节约磁盘空间、
#是否在导出.rdb数据库文件的时候采用LZF压缩字符串和对象? #默认情况下总是设置成‘yes’, 他看起来是一把双刃剑。 #如果你想在存储的子进程中节省一些CPU就设置成'no', #但是这样如果你的kye/value是可压缩的,你的到处数据接就会很大。
rdbcompression yes
rdbchecksum yes
rdbchecksum:是否进行数据校验
若校验,则会采用CRC64算法来校验,会增加10%的性能消耗
#在写入文件和读取文件时是否开启rdb文件检查,检查是否有无损坏,如果在启动是检查发现损坏,则停止启动。
#从版本RDB版本5开始,一个CRC64的校验就被放在了文件末尾。 #这会让格式更加耐攻击,但是当存储或者加载rbd文件的时候会有一个10%左右的性能下降, #所以,为了达到性能的最大化,你可以关掉这个配置项。
没有校验的RDB文件会有一个0校验位,来告诉加载代码跳过校验检查
rdbchecksum yes
RDB/快照的最佳配置、属性配置、相关配置
图解RDB的原理
时点性
当条件满足,redis需要执行RDB的时候,服务器会执行以下操作
创建(fork)子进程生成快照
操作redis时,创建fork子进程进行持久化
Redis 会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中
rdb保存的文件是dump.rdb
将内存内容以快照的形式写入临时RDB文件中
Redis会单独创建(fork)一个与当前进程一模一样的子进程来进行持久化,将数据写入到一个临时文件中
子进程将数据集写入到一个临时 RDB 文件中。
每隔几分钟,几小时,几天,生成redis内存中的数据的一份完整的快照
fork一个子进程,从父进程中同步复制数据,保存快照
使用fork指令,创建子进程,并且和父进程一样指向对应的数据(物理地址)
redis调用系统函数fork() ,创建一个子进程。
fork是系统调用,RDB时,会调用一次fork
Redis fork子进程bgsave,共享主线程内存数据
内存越大,耗时越长
info: latest_fork_usec
步骤1:创建(fork)子进程来进行持久化,将数据写入到一个临时文件中
当父进程发生修改时,copyonwrite(写时复制)复制一份数据并发生修改;
copy on write机制
COW( Copy On Write)机制
Redis使用操作系统的COW( Copy On Write)机制来实现快照持久化
借助操作系统提供的写时复制(COW),在执行快照的同时,正常处理写操作
在执行fork时,操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略
即fork函数发生的一刻,父子进程共享同一内存数据
当父进程要更改其中某片数据时(如执行一个写命令 ),操作系统会将该片数据复制一份,以保证子进程的数据不受影响
新的RDB文件存储的是执行fork那一刻的内存数据。
步骤2:当父进程发生修改时,Copy On Write复制一份数据并发生修改
待持久化结束后,替换上次持久化好的文件
快照写入完成后,替换原来的快照rdb文件
待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。
持久化结束后,将快照保存成dump.rdb文件,释放内存
当子进程完成对临时RDB文件的写入时,redis 用新的临时RDB 文件替换原来的RDB 文件,并删除旧 RDB 文件。
Redis在进行快照的过程中,不会修改RDB文件,只有快照结束后,才会将旧的文件替换成新的
任何时候RDB文件都是完整的。可以通过定时备份RDB文件来实 现Redis数据库备份。
步骤3:临时文件替换上次持久化好的dump.rdb文件
整个过程中,主进程是不进行任何 IO 操作的,这就确保了极高的性能
同时,子进程是不会感知到变化,这样就保证了当RDB的瞬间,子进程中的所有数据是当前数据,不会受到父进程的影响。
相当于两个redis进程,这期间主线程不参与持久化,保证redis的高性能
步骤4:子进程退出,整个过程中,主进程是不进行任何 IO 操作的
恢复时,将快照文件直接读到内存中
图解如何恢复rdb文件?
把dump.rbd放在启动目录就可以了,redis启动时会自动检查
1、只需要将rdb文件放到redis启动目录就可以,redis启动的时候会自动检查dump.rdb恢复数据
config get dir 就能直到对应的redis启动目录
2、查看需要存在的位置
如何恢复dump.rdb文件 ?
步骤5:恢复时将快照文件直接读到内存中,恢复dump.rdb文件
多长时间内至多操作多少次就会保存,可能会损失最后一次的数据
RDB的缺点就是最后一次持久化的数据可能丢失
如果需要大规模数据的恢复,且对数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。
如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那 RDB 方式要比 AOF 方式更加的高效。
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。
最后一次持久化的数据可能丢失
步骤与注意事项
RDB的原理、具体原理
从节点发送命令主节点做bgsave,同时开启buffer
数据初始化
备份就会自动生成一个dump.rdb文件
rdb的dump.rdb文件触发规则
全量复制
debug reload
触发机制不容忽略方法
save
save规则
同步
由主进程进行快照操作
阻塞客户端命令
会阻塞住其他请求
不消耗额外内存
save的规则满足的情况下,会自动触发rdb规则
save命令会阻塞主线程,一般不用
SAVE是同步保存操作,将当前 Redis 实例的所有数据快照(snapshot)以 RDB 文件的形式保存到硬盘中。
需要主动调用save命令
适用于在主动关机情况,操作redis去RDB
SAVE
BGSAVE
异步
不阻塞客户端命令
从节点SYNC时 (=BGSAVE)
bgsava会fork子进程异步持久化
fork子进程,消耗内存
通过fork子进程进行快照操作。
BGSAVE是异步操作,会fork出一个子进程,子进程负责调用 rdbSave ,并在保存完成之后向主进程发送信号,通知保存已完成;
Redis 服务器在BGSAVE 执行期间仍然可以继续处理客户端的请求
redis配置文件conf配置:设置多久,或者达到多少量去进行RDB
主动调用
调用场景
BGSAVE
手动触发
执行SAVE/BGSAVE命令,执行过程中,Redis会阻塞
执行save/bgsave命令,执行过程中redis会阻塞
除了自动快照,还可以手动发送SAVE或BGSAVE命令,让Redis执行快照,
1、save是同步IO,bgsave是异步IO
2、save是阻塞主线程,bgsave阻塞redis子线程
3、save和bgsave的复杂度都是O(n)
4、save不会消耗额外的内存,bgsave不会阻塞客户端命令
5、save会阻塞客户端命令,bgsave需要创建子线程,需要消耗内存
save和bgsave对比
执行save或bgsave命令
配置文件
不建议打开
配置文件中有快照配置
例如:save 900 1 (15分钟内有一次修改)
配置文件: save seconds changes
退出redis,也会产生rdb文件
客户端执行SHUTDOWN、shutdown命令时
shutdown
执行flushall命令,也会触发rdb规则
执行flushdb,也会产生dump.rdb,但为空文件
自动触发、自动保存
RDB的dump.rdb文件触发规则、触发机制、触发条件
底层都是调用bgsave
对数据的完整性要求不高
因为rdb这边有一个配置save 900 10类型的配置,有一个最低要求,如果再这期间宕机,就不会生成文件,导致文件丢失
适用于对数据的完整性要求不高的场景
适合大规模的数据恢复
恢复大的数据集时,RDB方式会更快一些
恢复时比较快,适合大规模的数据恢复
相对于数据集大时,比AOF的启动效率更高
RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
父进程无须执行任何磁盘的IO操作,因此恢复数据比AOF快
生成RDB文件时,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
相对于AOF持久化机制来说,基于RDB数据文件来重启和恢复redis进程,更加快速。
RDB 在恢复大数据集时的速度,比 AOF 的恢复速度要快
同样的数据文件比AOF文件小,数据恢复时比AOF要快
RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
(可以配置rdbcompression参数以禁用压缩节省CPU占用)
RDB文件是经过压缩的二进制格式,所以占用的空间会小于内存中的数据大小,更加利于传输。
适合大规模数据恢复,恢复数据比AOF快
性能最大化,子线程处理IO持久化
子进程负责所有的RDB操作,父进程不影响IO,最大化redis的性能。
redis主进程调用fork()方法,创建一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
RDB对Redis的性能影响非常小,是因为在同步数据的时候fork了一个子进程去进行持久化
RDB对redis提供对外读写服务,影响非常小,可以让redis保持高性能,
RDB:redis写数据,一般只需要写入内存,只有特定时间,才会写入磁盘AOF:每次写数据都要写入AOF,虽然有os cache,但是还是有影响
因为redis只需要fork一个子进程,让子进程执行磁盘IO操作来进行RDB持久化即可。
最大化Redis的性能,子线程处理IO持久化
每个数据文件都代表了某一时刻中redis的数据,这种多数据文件的方式
可以通过shell脚本将备份文件发送到一些远程的安全存储上去,以预定好的备份策略来定期备份redis中的数据。
非常适合做冷备
容灾性好,一个文件可以保存到安全的磁盘
只有一个文件dump.rdb,方便持久化
因为可以设置每个时间段去rdb,所以可以恢复不同时间段的所有全量数据。
RDB的优点、优势
fork子进程占用一定空间
需要一定的时间间隔操作
需要一定的时间间隔进程操作
fork大量数据时,有可能会影响性能
如果生成的快照文件比较大会影响redis的性能
每次保存 RDB 的时候,Redis 都要 fork() 出一个子进程,并由子进程来进行实际的持久化工作。
在数据集比较庞大时, fork() 可能会非常耗时,造成服务器在某某毫秒内停止处理客户端;
如果数据集非常巨大,并且 CPU 时间非常紧张的话,那么这种停止时间甚至可能会长达整整一秒。
虽然 AOF 重写也需要进行 fork() ,但无论 AOF 重写的执行间隔有多长,数据的耐久性都不会有任何损失。
在数据集比较庞大时, fork() 可能会非常耗时
RDB在生成数据文件时,如果文件很大,客户端会暂停几毫秒甚至几秒
创建fork子进程时,也会占用一定的内容空间(只要是进程都会占用空间)
RDB每次在fork子进程来执行RDB快照数据文件生成时,如果数据文件特别大,可能会导致对客户端提供的服务暂停数毫秒甚至数秒。
一定不要让RDB间隔时间太长,否则每次生成RDB文件太大,对redis本身性能可能会有影响
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。
根据数据量大小与结构和服务器性能不同,这个时间也不同。
通常将一个记录一千万个字符串类型键、大小为1GB的快照文件载入到内 存中需要花费20~30秒钟。
耗时 O(n)、耗性能、耗内存
不可控、会丢失数据
会丢失fork后一段时间内的文件
如遇突然宕机,丢失的数据比较多
如果期间redis宕机,最后一次数据无法保存
数据安全低,持久化的间隔期间宕机,导致数据丢失
快照持久化期间修改的数据不会被保存,可能丢失数据。
如果redis意外宕机了,这个最后一次修改的数据就没有了
虽然Redis 允许设置不同的保存点(save point)来控制保存 RDB 文件的频率
但是, 因为RDB 文件需要保存整个数据集的状态, 所以它并不是一个轻松的操作。
因此,可能会至少 5 分钟才保存一次 RDB 文件。
在这种情况下, 一旦发生故障停机, 就可能会丢失好几分钟的数据。
如果你需要尽量避免在服务器故障时丢失数据,那么RDB 不适合你。
快照文件默认五分钟才生成一次(根据时间策略),这意味着这五分钟的数据都有可能丢失
如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好。
一般来说RDB快照文件,都是每隔5分钟,或者更长时间生成一次,这个时候一旦redis宕机,可能丢失这段时间的数据。
数据丢失(最大的缺点、不适合做第一优先恢复方案)
RDB文件时特定的格式,阅读性差,格式固定,可能存在不兼容的情况
RDB的缺点、劣势
RDB快照(默认开启)
AOF 是以日志的形式来记录每个写操作,将每一次对数据进行修改,都把新建、修改数据的命令保存到指定文件中。
Redis 重新启动时读取这个文件,重新执行新建、修改数据的命令恢复数据。
日志记录的方式,可以记录每一条命令的操作。可以每一次命令操作后,持久化数据
默认关闭
AOF 文件的保存路径,同 RDB 的路径一致。
AOF 在保存命令的时候,只会保存对数据有修改的命令
当 RDB 和 AOF 存的不一致的情况下,按照 AOF 来恢复
如果AOF文件中出现了残余命令,会无法重启服务,需要使用redis-check-aof工具来修复:redis-check-aof -fix 文件
简介
appendfsync always:每次执行写操作都保存
appendfsync everysec(默认):每秒保存一次,可能会丢失1秒的数据
appendfsync no:从不保存
appendOnly
数据齐全
回复慢文件大
备份机制稳健,丢失数据概率低
可读的日志文本,可以处理误操作
比RDB占用更多的磁盘空间
恢复备份较慢
优缺点
编辑redis.windwos.conf文件
appendonly no(关闭aof) --> appendonly yes (开启aof) # appendfsync always : 每一次操作都进行持久化 appendfsync everysec : 每隔一秒进行一次持久化 # appendfsync no : 不进行持久化
appendonly:是否开启AOF
appendfilename:AOF备份文件的名称
appendfsync:保存策略
no-appendfsync-on-rewrite:重写时,是否执行保存策略
auto-aof-rewrite-percentage:指定重写与否的aof文件大小比例
auto-aof-rewrite-min-size:设置允许重写的最小aof文件大小
aof-load-truncated:是否截断
AOF
AOF 机制
AOF(日志)
AOF(Append Only File)日志
AOF(Append Only File)
append only module
APPEND ONLY MODE
AOF(Append-only file)
Append-only file,AOF
AOF是什么?
只追加操作的文件
以日志的形式来记录每个写操作
以追加操作日志的方式,将数据持久化到文件中
将在redis操作的所有命令全部记录下来,以日志的形式,恢复时,将这些文件全部执行一遍
AOF特点
快照功能并不是非常耐久(durable): 如果 Redis 因为某些原因而造成故障停机, 那么服务器将丢失最近写入、且仍未保存到快照中的那些数据。
从 1.1 版本开始, Redis 增加了一种完全耐久的持久化方式: AOF 持久化
诞生背景
默认是不开启AOF模式的,需要手动进行配置修改
默认关闭
需手动开启:appendonly yes
redis.conf中把appendonly改成yes
appendonly yes
打开AOF配置,在配置文件中打开AOF方式
开启方法
config set appendonly yes
开启后redis会保留一块内存供缓存使用,默认是1M
aof和rdb同时开启时,只保留save 900 1 减少fork子进程的次数(优化点)
线上开启方式
图解AOF工作原理
以日志的形式记录每个写操作
将所有的写命令追加到AOF缓冲区中
将redis执行过的所有指令记录下来(读操作不记录)
AOF 在保存命令时,只会保存对数据有修改的命令
只许追加文件,但不可以改写文件
记录每一次的写操作
写命令刷新到缓冲区
每当 Redis 执行一个改变数据集的命令时(比如 SET), 这个命令就会被追加到 AOF 文件的末尾。
将每一次对数据进行修改,都把新建、修改数据的命令保存到指定文件中。
步骤1:将所有的写命令,追加到AOF缓冲区中
由主线程完成
AOF日志是在主线程中执行,日志文件写回磁盘存在风险
AOF 文件的保存路径,同 RDB 的路径一致。
根据对应的写入策略向硬盘进行同步操作
每来一条数据都会写入到AOF,但是实际上是写到操作系统os cache中,
所以redis每隔1s会调用一次操作系统fsync操作,强制将os cache中的数据刷入到磁盘文件中。
执行命令后记录,记录redis收到的每一条命令
对比上次fsync时间,2s则阻塞
info: aof_delayed_fsync (累计值)
AOF追加阻塞
fsync(int id)函数
是什么?
Linux的glibc提供
谁提供
将指定文件的内容强制从内核缓存刷到磁盘
作用
fsync介绍
步骤2:根据写入策略,将每条命令 fsync 到硬盘AOF文件
redis启动之初会读取该文件重新构建数据
恢复数据,重启redis就可以生效了,不需要固定的位置保存
当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令,来达到重建数据集的目的。
当Redis重启后,就根据日志文件的内容将指令从头开始执行一遍,以完成数据恢复的工作
重启Redis,会重新将持久化的日志文件恢复数据
数据恢复时,重新运行这些命令,重构数据
Redis 重新启动时,读取这个文件,重新执行新建、修改数据的命令恢复数据。
AOF存放的是指令日志,恢复需要读取和执行存放的指令
当 RDB 和 AOF 存的不一致的情况下,按照 AOF 来恢复
将AOF日志中所有命令执行一遍
步骤3:读取和执行存放的指令日志,进行AOF的故障恢复
AOF具体步骤
appendfsync:保存与同步策略
bgrewriteaof:AOF重写机制
AOF的触发机制
rewrite
重写操作
AOF重写机制
重写机制(bgrewriteaof)
AOF重写机制是什么?
AOF文件只有一个,会越来越大
redis中的数据是有一定限量的,但是AOF是存放每条写命令的,所以会不断的膨胀
当AOF大到一定程度会进行rewrite操作。
随着AOF文件越来越大,需要定期对AOF文件进行重写,达到压缩的目的
基于当时redis内存中的数据,来重新构造一个更小的AOF,然后删除原来膨胀很大的AOF
1、减少磁盘占用量
2、加速恢复速度
为什么需要重写?重写作用
进程内已超时的数据不再写入文件,而且多条写命令可以合并为一条
新的AOF文件只保留最终数据的写入命令(去掉了修改命令)
重写后的文件为什么会变小 ?
设置允许重写的最小aof文件大小
当前AOF文件启动新的日志重写过程的最小值
避免刚刚启动Reids时,由于文件尺寸较小导致频繁的重写
auto-aof-rewrite-min-size
aof文件大于64M时重写
生产环境下要配置的比较大
由于重写会fork子进程,为了减少重写次数,建议配置5GB以上(优化点)
auto-aof-rewrite-min-size 64mb
当前AOF文件的大小是上次rewrite后的两倍且大于64M
auto-rewrite-min-size 64M
指定重写与否的aof文件大小比例
当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。
auto-aof-rerwrite-percentage
增长的百分比
auto-aof-rewrite-percentage 100
比如aof文件累积到64M开始第一次重写,重写后的大小为40M,
如果auto-aof-rewrite-percentage参数值是100,则当aof文件超过80M时才发生第二次重写
指超过优化后,第二次优化文件大小大于第一次优化文件后大小一倍时开始重写
auto-aof-rewirte-percentage 100
推荐配置
动态应用配置
也可使用命令bgwriteaof
Redis fork子进程bgrewriteaof,根据Redis现状创建一个新的AOF文件,每个键值对用一条命令记录它的写入,以缩小日志文件
fork子进程来进行
fork一个子进程,保存写操作
本质上,AOF也同样使用了fork操作
BGREWRITEAOF命令
bgrewriteaof 命令
执行BGREWRITEAOF命令时
Redis服务器会维护一个AOF重写缓存区
该缓冲区会在子进程创建新AOF期间记录所有写命令,子进程完成创建AOF后,服务器将缓冲区的所有内容追加到新AOF末尾
执行效果
使用命令bgwriteaof
修改配置文件
(如incr 1万次,最后执行结果为incrby 10000)
4.0版本以前
发生重写时,会将老的数据RDB到AOF文件中,将增量的数据以指令的方式append到AOF中
4.0版本之后
就可以配置自动触发AOF 重写
redis2.4之后
根据Redis版本不同有些区别
AOF的重写机制
AOF保存的文件是appendonly.aof
Redis执行的每次命令记录到单独的日志appendonly.aof文件
如果AOF文件中出现了残余命令,会无法重启服务
如果aof文件出现错误,这时候redis时启动不起来的,就需要修复aof文件,
redis提供一个工具:redis-check-aof --fix appendonly.aof
使用redis-check-aof工具来修复:redis-check-aof -fix 文件
AOF文件的修复方式
AOF的原理
是否开启AOF
是否开启AOF,默认关闭(no)
appendonly
AOF备份文件的名称
指定 AOF 文件名
appendfilename appendonly.aof
appendfilename
模式
触发条件
保存操作记录的模式
设置同步策略
三种不同的刷写模式
写入策略
写操作AOF会触发IO,降低性能
调整AOF刷新策略(模式可配置)(将数据flush到磁盘中)
alway
appendfsync always
每次执行写操作都保存
把每个写命令都立即同步到aof文件中,
always: 每次有数据修改时添加记录 性能差
同步写回
每执行一条指令,将其刷新到磁盘中
每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,
但速度也是最慢的,一般不推荐使用。
很慢,但很安全
追求安全
always:每次发生数据变更时立即同步到磁盘,效率低,安全
每次执行都记录
Always(安全,低效)
everysec
every second
everysec(每秒)
appendfsync everysec
(推荐)
(默认)
AOF根据模式同步数据,一般是everysec
默认开启的策略
受推荐的方式
【默认也推荐使用】
每秒保存/记录/刷新一次
每秒开启一次
每秒同步一次
每秒写回
每秒钟强制写入磁盘一次
每隔一秒左右执行一次fsync操作
每隔一秒执行一次fsync操作
同步方式:1s
每秒把缓冲区的数据写入磁盘
最多丢失1s的数据
最多损失一秒的数据
可能会丢掉1s的数据
可能会丢失1秒的数据
可能会丢失一秒的数据
效率高
每次操作,不同步
丢失数据介于always与no中间
可以兼顾速度和安全
在性能和持久化方面做了很好的折中
其他特点
Everysec(推荐、默认配置、兼顾速度和安全)
no
appendfsync no
操作系统控制写回
由OS决定何何时同步,
linux通常是30s
一般为30秒左右一次
从不保存
不处理,非常快,但也最不安全
不主动去刷新,只有缓存行满了,内核自己刷到磁盘中
完全依赖OS的写入
性能最好但是持久化最没有保证,不被推荐。
追求效率
等到缓冲区满了才写入磁盘,次数少,效率高,不安全
No(不推荐,高效,不安全)
当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时,由于文件尺寸较小导致频繁的重写。
auto-aof-rewrite-percentage
重写时,是否执行保存策略
#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。
no-appendfsync-on-rewrite no
no-appendfsync-on-rewrite
是否截断
aof-load-truncated
其他的AOF配置可以保持默认的,默认aof命令是无限追加的
AOF的配置文件、属性配置、配置项、相关配置
相比于RDB,丢失的数据少
appendfsync always 每次执行都记录
数据安全
每一次修改都同步,文件的完整性更好
备份机制稳健,丢失数据概率低
每隔一秒左右(模式可配置)执行一次fsync操作,最多损失一秒的数据
丢失数据介于always与no中间,在性能和持久化方面做了很好的折中
可以更好保护数据不丢失,每隔1s就执行一次fsync操作,保证os cache数据写入磁盘中,redis挂了,最多丢失1s数据
丢失数据少
rewrite操作对性能影响很小
从不同步,效率最高的!
记录日志是追加(append_only)的方式,减少了磁盘寻址的开销
文件尾部破损,也很容易恢复
写入性能高,而且文件不容易破损
宕机,可通过redis-check-aof工具解决一致性问题
AOF开启后,支持的写QPS会比RDB支持的写QPS更低,因为AOF一般会配置成每秒fsync一次日志文件。
当然,每秒一次fsync,性能还是很高的。
如果你要保证一条数据都不丢失,也是可以的,设置成每写一条数据,进行一次fsync,会导致redis的QPS性能大降。
写入性能高,减少了磁盘寻址的开销
AOF日志文件的命令通过非常可读的方式进行记录
这个特性非常适合做灾难性的误删除的紧急恢复
某人不小心用了flush all清空了所有数据,
aof没被rewrite之前,可以删除一些命令
只要这个时候后台rewrite还没有发生,那么立刻拷贝AOF文件,
把最后一条flush all命令删了,再放回去,重启Redis自动恢复所有数据。
比如
可读的日志文本,可以处理误操作
建议与RDB同时开启
AOF的优势、优点
恢复慢
恢复速度慢
修复的速度比rdb慢
做数据恢复时,会比较慢
由于需要一条一条执行操作日志中的指令
AOF运行效率也比RDB慢,因此redis默认的配置是RDB持久化
恢复备份较慢,运行效率慢
比RDB占用更多的磁盘空间
占用更多的磁盘空间
体量无限变大
相对于数据文件来说,AOF远远大于RDB
对于同一份文件来说,AOF日志文件通常比RDB数据快照文件更大
数据快照文件更大
做冷备,定期的备份,不太方便
可能要自己手写复杂的脚本去做,做冷备不合适
不合适做冷备(比较大的缺点)
恢复不稳定,容易出bug
AOF的劣势、缺点
AOF写日志
两种Redis的持久化机制
AOF的体积更大
RDB的体积比AOF小
RDB性能比AOF好
RDB恢复数据的速度比AOF快
AOF比RDB更安全
RDB数据安全差,很容易丢失数据,AOF根据策略决定
AOF文件比RDB更新频率高,优先使用AOF还原数据
RDB和AOF的区别,两种方案对比
最好两种策略配合使用
可以单独使用RDB,不建议单独使用AOF
若只是将Redis作为纯内存缓存,可以都不使用
选择总结
可以单独使用RDB
可以接受分钟内的数据丢失,选择RDB
因为那样会导致你丢失很多数据
可以单独使用RDB,但不要仅仅使用RDB
不建议单独使用AOF
也不要仅仅使用AOF
如果数据很重要,以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
通过AOF做冷备,没有RDB做冷备恢复的速度快。
因为RDB可以定时对数据库备份,并且恢复数据集的速度快,也可以避免AOF的bug
RDB每次简单粗暴生成快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
因为它会有两个问题
不推荐只使用AOF
这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。
注意:一般在工作中,生产环境使用redis时,会将redis的rdb文件进行备份,以防止数据丢失
如何选择RDB和AOF,各自的使用场景
RDB集中管理
RDB在主机中关掉,在从机中开启
AOF开启缓存和存储
AOF重写集中管理
AOF的策略是每秒保存
RDB和AOF的最佳策略
redis4.0开始支持该模式
redis在重启时通常是加载AOF文件,但加载速度慢因为RDB数据不完整,所以加载AOF
为了解决的问题
aof-use-rdb-preamble true开启后,AOF在重写时会直接读取RDB中的内容
开启方式
通过bgrwriteaof完成,不同的是当开启混合持久化后
1 子进程会把内存中的数据以RDB的方式写入aof中,2 把重写缓冲区中的增量命令以AOF方式写入到文件3 将含有RDB个数和AOF格数的AOF数据覆盖旧的AOF文件新的AOF文件中,一部分数据来自RDB文件,一部分来自Redis运行过程时的增量数据
运行过程
数据恢复
优点:既能快速备份又能避免大量数据丢失
缺点:RDB是压缩格式,AOF在读取它时可读性教差
混合模式
会优先使用AOF
RDB启动优先级比AOF低
如果2个都配置,优先加载AOF
当AOF和RDB同时开启,优先读取AOF文件(数据安全性)
同时使用RDB和AOF,重启优先加载AOF来恢复原始数据(数据全)
RDB数据会写入AOF文件头部
redis先加载AOF文件来恢复原始数据,因为AOF数据比rdb更完整,但是aof存在潜在的bug
如把错误的操作记录写入了AOF,会导出数据恢复失败所以可以把RDB作为后备数据为了考虑性能,可以只在Slave上开启RDB,并且15min备份一次
如果为了避免AOF rewite的IO以及阻塞,可以在Redis集群中不开启AOF,靠集群的备份机制来保证可用性,
在启动时,选取较新的RDB文件如果集群全部崩溃,会丢失15min前的数据
优先读取与加载AOF文件
先判断是否开启了AOF,如果存在AOF文件,则直接加载AOF文件
如果找不到AOF文件,则直接启动,不会加载RDB文件
如果没有开启AOF,则会加载RDB文件
生产环境建议AOF和RDB同时使用,RDB做灾难备份
Redis启动后,持久化文件的加载流程
同时开启,性能开销大,内存消耗更快,会同时生产rdb文件和aof文件
混合使用的缺点
用AOF来保证数据不丢失,作为数据恢复的第一选择
用RDB来做不同程度的冷备,在AOF文件都丢失或损坏不可用时,还可以使用RDB进行快速的数据恢复。
综合AOF和RDB,最好两种策略配合使用
持久化操作主要在从节点进行
若只是将Redis作为纯内存缓存,可以都不使用
RDB和AOF混合使用,同时开启两种持久化机制
如何选择合适的持久化方式?到底该如何选择
http://redis.cn/topics/persistence.html
单机持久化
学习地址
两种持久化操作/机制/方式
1、同步操作
2、与内存量息息相关:内存越大,耗时越长
3、info:latest_fork_usec :监控
1、优先使用物理机或者高效支持fork操作的虚拟化技术
2、控制redis实例最大的可用内存
3、合理分配liunx内存分配策略
4、降低fork策略,比如放宽aof的触发机制,不必要的全量复制
改善fork
1、fork操作
开销,RDB和AOF文件生成
优化:不做CPU绑定,不要CPU密集型部署
1、CPU
开销:fork内存开销,copy-on-write
优化:不允许每次都写
2、内存
开销:AOF和RDB文件写入,可以结合iostat,iotop分析
1、不要和高硬盘服务器部署在一起,存储服务,消息队列
2、no-appendfsync-on-rewrite=yes
3、根据写入量决定磁盘类型,ssd
4、单机多实例持久化文件目录可以考虑分盘
3、硬盘
2、进程外的开销
1、主线程开启AOF缓存并且存放到存放到AOF缓存区
2、AOF缓存区同步线程
3、AOF同时对比fsync时间
4、如果大于2秒则阻塞,如果小于则通过
流程
3、AOF追加阻塞
4、单机多实例部署
Redis持久化运维常见问题
Redis的持久化模式
为什么要用事务
一组命令的集合
Redis事务的本质是
Redis中的事务指单独的隔离操作
事务支持一次命令执行多个命令,一个事务中所有的命令都会被序列化
没有回滚机制,事务中错误的命令无法执行,正确的命令会全部执行
结合pipeline
Redis的事务概念、简介
一次性
在事务执行过程中,会按照顺序执行
事务中所有命令都会按顺序执行,不会被其他命令打断
顺序性
主要作:串联多个命令防止其他命令打断
排他性
隔离性
没有隔离级别的概念
批量操作在事务提交前放入魂村队列,并不会被实际运行
Redis中的单条命令是原子性执行的,但事务不保证原子性,且没有回滚
事务中的任意命令执行失败,其余的命令仍会被执行
不能保证原子性
Redis单条命令是保证原子性,但是事务不保证原子性
一条命令执行错误,其他命令正常执行
一条命令在编译时出错,所有命令都不执行
事务不保证原子性
muli开启事务,将命令放入队列,exec执行所有命令,discard放弃事务
命令原子性
setnx expeir
怎么保证原子性
⭐Redis的事务不保证原子性
每次使用事务都需要手动开启,声明周期到执行或者放弃结束
Redis的事务特点
multi
声明事务
开启事务、开始事务
标记事务的开始
标识一个事务的开始
标记一个事务块的开始
命令入队(一个或多个命令)
输入命令
命令入队
执行事务
执行某事务块内的所有命令
所有命令在事务中,并没有直接执行!只有发起执行命令时,才会执行!exec
redis单线程操作,exec的指令谁先来,就先执行哪个线程的操作。并不会因为mutli先来就先执行。
执行事务中所有在排队等待的指令并将连接状态恢复到正常
当使用WATCH 时,只有当被监视的键没有被修改,且允许检查设定机制时,EXEC会被执行
声明并执行事务
示例1
exec
watch命令
watch(监视)
watch:观察并撤销事务
实现乐观锁
CAS乐观锁
悲观锁(Redis不支持)
乐观锁
获取最新的值version,监视,select version 获取版本
watch xx
watch
watch key…
观察key,若该key的value值被修改,当exec时 ,不执行。
监视一个或多个key,如果在事务执行之前这些被监视的key被其他命令修改,这该事务则被取消
监视key,如果事务在被执行前,被监视的key被改动,则事务执行失败
标记所有指定的 key 被监视起来,在事务中有条件的执行(乐观锁)
watch key
redis监视成功
监控 Watch
利用两个客户端演示watch命令
示例3
watch k1 k2
discard(取消事务)
放弃事务、取消事务、放弃执行事务
事务队列中命令都不会被执行
刷新一个事务中所有在排队等待的指令,并且将连接状态恢复到正常。
如果已使用 WATCH,DISCARD 将释放所有被 WATCH 的 key。
取消事务
实例2
discard
如果事务执行失败,就先解锁
unwatch xx
取消watch对所有key的监控
取消watch命令对所有key的监视
unwatch
Redis的事务命令、事务用法、常用命令、相关命令
三个阶段、三个步骤
如果在事务队列中出现编译异常(语法错误),则执行exec命令,所有的命令都不会执行
事务整体都不会有效
命令有错(相当于编译异常)
如果在事务队列中出现运行时异常,则执行exec命令,错误命令抛出异常,其他命令正常执行
有错误的命令不生效但是不影响事务中别的命令
事务队列存在运行错误(运行时异常)
在执行exec之前发生错误
在执行exec之后发生错误
事务可能遇到的问题
Redis事务的相关问题、异常
悲观锁(Redis不支持):假设当前操作很大几率会被打断
乐观锁:假设当前操作不会被打断,做操作前不会锁定资源,万一被打断,则操作被放弃
通过watch实现乐观锁,多读少写,unwatch命令可以取消加锁
事务之前执行了watch(加锁),在exec/discard命令执行后锁会自动释放
可以使用watch做乐观锁操作(失败了就unwatch,然后重复操作,达到自旋效果)
多客户端同时并发写一个key,可能本来应该先到的数据后到了,导致数据版本错了。
或者多客户端同时获取一个key,修改值之后再写回去,只要顺序错了,数据就错了
Redis自己就有天然解决这个问题的CAS类的乐观锁方案
zookeeper,redis
1.使用分布式锁,确保同一时间内,只有一个系统的实例在操作某个key,别人都不允许写
2.每次要写之前,要先判断一下当前这个value的时间戳是否比缓存里的value时间戳要大,如果更旧,那么就不能用旧数据覆盖新数据
解决:使用分布式锁
redis的缓存并发竞争问题是什么?如何解决这个问题?了解Redis事务的CAS方案么?
Redis中的锁策略
Redis的事务 与 锁策略
消息订阅
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接受消息
简介:消息订阅是进程间的一种消息通信方式,即发送者发布消息(pub),订阅者(sub)接收消息,Redis支持消息订阅机制。
Redis客户端可以订阅任意数量的频道’
订阅频道
按规则订阅频道
发布订阅模式
什么是发布订阅?
订阅/发布消息图
场景:1、实时消息系统!2、实时聊天!(可以将频道当聊天室,将消息回显示给所有人即可!)3、订阅、关注系统都是可以的!
可以做好友动态、聊天室等等
实现原理
publish 频道 消息:向指定频道发布消息
subscribe 频道:订阅频道
publish/subscribe
+ 先进行订阅subscribe ooxx+ 再进行发布publish ooxx helloqiujing!+ 所有订阅ooxx的客户端都会收到相应的消息helloqiujing!
# 订阅一个或多个符合给定模式的频道psubsribe pattern [pattern......] # 查看订阅与发布系统状态pubsub subcommand [argument [argument ...]]# 将信息发送到指定的频道publish channel message# 退订所有给定模式的频道punsubscribe [pattern......] # 订阅给定的一个或者多个频道的信息subscribe channel [channel ...]# 指推定给定的频道unsubscribe [channel [channel...]]
消息的发布和订阅
先订阅频道
然后新开窗口往频道里发布内容
此时发现之前订阅了xiaoye这个频道的窗口自动监听了频道消息
订阅/发送
订阅/发送实例
Redis发布订阅、消息订阅
1.单点故障
2.数据量大,容量有限
3.压力--soket连接或计算的压力
单机、单节点、单实例会存在哪些问题?
AKF可解决以上问题
性能
扩展
可用性
为什么要集群 ?
通过加机器来解决容量和可用性的问题。
重点就在于加机器,先来看下面这张图:
一句话就能说明
X轴:水平复制,增加机器,单体系统多运行几个实例,做集群加负载均衡的模式;全量数据的克隆。
X轴
Y轴:按照功能、业务拆分,如商品管理、订单管理、用户管理,将其分散在不同的实例上。
Y轴
Z轴:Y轴也承担不了大量的数据,进行数据分区。
比如一个机器承方了所有用户信息,只有4G的内存,
但是现在用户信息就有8G内存,就要Z轴扩展
比如按地区北京、上海或者按用户id尾号等再进行拆分。
大量的数据
Z轴
AKF原则
同步阻塞:当主库操作完成后,阻塞等待备库操作完成后才会返回,但是会破坏服务的可用性
强一致性会破坏服务的可用性
强一致性
+ 异步方式:当主库完成指令后,异步的交给备库执行相应的操作,这种不会破坏服务的可用性,但是会丢失数据。最终一致性!!
(redis并没有使用这种方式)
+ 可增加中间件的方式,如使用kafka,来存储异步的消息,备库reis来进行消费,保证了数据的完整性,最终会达到数据一致性。
非强一致性
数据一致性
AFK导致的数据一致性问题
Redis主从复制、主从模式
哨兵模式 Sentinel
过半机制
故障转移
AKF所带来的问题
https://blog.csdn.net/asd13518662/article/details/116980800
AKF介绍
主从
Redis主从复制
备机不会参与业务,当主库宕机之后,才会使用备机
区别于主备概念
将一台redis服务器的数据,复制到其他的redis服务器
客户端可以访问主机,也可以访问从库
为了读写分离,提高Redis性能;保证数据安全
Master以写为主
主(master)redis以写操作为主
而写只需要分给主机,就能缓减服务器压力
一主多从,主机宕机后,需要手动更改ip确定新的主机
主机负责写
一个主机(master)
主节点(master/leader)
读的操作都分给从机上
多个从机(salve)
一个从机只能有一个master
一般从库上不会发生写操作,只会有读操作
Slave以读为主
从(slave)redis以读为主
从机从主机中同步复制数据,只读。
从机负责读
从节点(slave/follower)
主从复制,读写分离(master/slave机制)
主机数据更新后根据配置和策略,自动同步到备机的master/slave机制
主机中所有的数据和命令,都会自动被从机保存
主从之间自动同步数据
数据流通是单向的,只能是master到salve
数据的复制都是单向的,只能由主节点到从节点
数据的复制都是单向的
当主机断了,从机仍然运行连接主机,但是没有写操作,这个时候,如果主机回来了,从机依旧可以直接获取到主机写的信息
如果使用命令行,来配置的主从,这个时候如果重启,就会变成主机!主机变为从机,立马就会从主机中获取值
主从复制的细节
如果从库数量过多,会导致主库忙于fork子进程,进行数据全量同步,
使用主从级联模式分担主库全量复制时的压力
主从级联模式
简介、概念、主从概念
info replication
记住命令info replication
查看本机的复制信息
(常用的Redis命令)
查看当前主从状态信息
查看当前redis的主从策略
可以直到redis对应的一些信息,比如是主库,还是从库
info replication
后台进程
以配置文件启动redis,查看后台进程
info replication 查看当前库的信息
在一台机器上启动多个redis实例。
需要将默认的redis.conf文件复制一份
然后修改一下对应的设置,保证多个redis实例不出现共享数据就可以了
复制配置文件,redis.conf
默认情况下,每台redis服务器都是主节点
需要配置从机就可以了,主机不用配置
集群命令,通过命令实现
只配置从库,不用配置主库(配置从机,配从不配主,找老大)
在从机中配置,上面认老大的slaveof是命令式的,一次性的
⭐Redis默认自己就是主库,所以只需要配置从库
在从机上输入命令SLAVEOF 【主机的ip】 【主机的端口号】
bind 127.0.0.1port 6380dbfilename dump80.rdbslaveof 127.0.0.1 6379
slaveof
salveof ip port
salveof ip port
主库的ip
host
就是主库的端口
从机需要跟随的老大
port
SlaveOF host port
salveof命令
表示不是某个主机的从节点
执行该命令去除主从配置
salveof no one
slave从机配置
临时建立:执行slaveof ip:port命令
bind 127.0.0.1port 6379dbfilename dump.rdb
master主机配置
通过配置redis.conf里面配置REPLICATION
/bind搜索
注释bind绑定ip
端口 port
/port搜索
端口
/daemonize搜索
守护线程开启
pid 名字
/pidfile搜索
pid修改
log日志文件名字
/logfile搜索
日志文件修改
/dbfilename搜索
dbfilename修改
dump.rdb文件名字
appendonlyfile.aof文件名字
修改配置文件、一般修改配置redis.conf
通过配置实现
最好使用配置文件配置
最好使用配置文件配置实现
永久建立:配置master配置文件
1、使用命令进行主从配置,不需要重启redis服务器,但是不方便管理
2、使用配置进行主从配置,需要重启redis服务器,但是方便管理
两种配置的区别
slave-read-only yes
设置从节点只当作读的操作
salve-read-only yes
读写分离,主机读写,从机从主机中同步复制数据,只读。
主要是为了高并发,缺点是难扩容,整个集群的容量受限于某台机器内存容量
主从复制的配置
两种主从建立方式
环境搭建与主从复制的配置
单台Redis的最大使用内存不要超过20G
建议至少一主二从的配置,一主一从哨兵机制会出错
电商网站上的商品,一般都是一次上传,无数次浏览
适用场景、使用场景
实现数据热备份,是数据持久化的另一种操作
主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
数据热备份、数据冗余
故障恢复,当某一服务挂了后,可以从节点进行数据恢复
当主节点出现问题时,可以 由从节点提供服务,实现快速的故障恢复;实际上时一种服务的冗余
充当数据副本使用,避免数据丢失
故障恢复
负载均衡,主机写,从机读,分担负载
在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务,分担服务器负载
写redis数据时,应用连接主节点;
扩展读的性能
读redis数据时,应用连接从节点
在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
负载均衡、读写分离
高可用基石,主从复制是哨兵机制和集群的基础
除了上诉的作用外,主从复制还是哨兵和集群能够实施的基础,因此主从复制时redis高可用的基础
高可用基石
master读写操作,slave只有读操作
承担读压力
Redis使用默认的`异步复制`,其特点是低延迟和高性能,是绝大多数 Redis 用例的自然复制模式。
主从同步期间,master仍然提供查询、修改等操作服务,slave仍然提供查询操作服务
非阻塞
主从复制的优点、主要作用
缺点是难扩容,整个集群的容量受限于某台机器内存容量
1、将主机的读操作分摊给从节点
复制数据延迟
1、复制数据延迟(阻塞可能会造成)
读到过期数据
2、读到过期数据
从节点故障
3、从节点故障
读写分离可能遇到问题
1、主从分离
可能丢失数据
maxmemory不一致可能会丢失数据
maxmemory配置不一致
数据结构优化参数不一致
内存不一致
可能会造成主节点和从节点内存不一致
2、主从配置不一致
通过数据分片设置不要设置过大,进行低峰复制
不可避免
第一次全量复制
优化:小主节点(小分片),低峰
1、第一次全量复制不可避免
节点runId不匹配导致复制
主节点重启(runId会发生变化)
主节点重启后runId变化
优化:故障转移(哨兵、集群)
2、节点运行ID不一致
复制积压缓冲区不足
网络中断后无法进行部分复制
网络中断,部分复制无法满足
优化:rel_backlog_size(默认1m)
增加复制缓冲区的配置,进行网络增强
3、复制积压缓存区不足
3、规避全量复制
更换复制徒
一个主节点下面有三个从节点,更改为一个主节点只有一个从节点1,剩下的从节点都挂载在从节点1中。
主节点重启,从节点复制
1、单主节点复制风暴
主节点分布在多个机器
2、单机器复制风暴
主节点重启,多个从节点复制
优化:更换复制拓扑
4、规避复制风暴
运维问题
从机是从头开始复制,还是从切入点开始复制
从头复制
1、从机完全复制主机的信息
2、从机无法进行写操作
主机shutdown后,从机是上位还是原地待命
原定待命,不会变成主机
3、主机shutdown以后,从机原地待命
不能 从机只能读get
从机是否可以写 set?
常见问题
由于所有的写操作都是现在Master上操作,然后同步更新到Slave上所以从Master同步到Slave上机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重
所有的slave节点的数据复制和同步都由主节点来处理,造成master压力大
可以
主机重新启动后,从机是否能够顺利复制
从机shutdown后,情况如何
主从复制的一些问题
主从复制的缺点、主从复制的不足、问题
Master和Slave都会维护一个offset和run id ,Slave每秒都会上报自己的offset给master
Master记录在backlog(针对增量复制)中,这样才能知道双方数据是否一致
Slave发送run id 和offset到Master,Master根据情况返回信息(增量/全量)
前提
full resynchronization
开始全量复制时,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。
RDB文件生成完毕之后,master会将这个RDB发送给slave
slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。
然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。
全量复制、复制过程的核心原理(简述)
主从库建立连接,协商同步过程,为全量复制做准备
第一阶段
主库将所有数据(RDB)同步给从库,从库收到数据后,在本地完成数据加载
第二阶段
主库把第二阶段执行过程中新收到的写命令(repl buffer),再发送给从库
第三阶段
主从同步
slave向master发送psync指令;
Slave启动时会向Master发送sync指令、psync指令(请求同步)
每次从机联通后,都会给主机发送 sync 指令
当从库和主库建立MS(Master-Slave)关系后,会向主库发送sync命令、psync指令(请求同步)
从机一旦连接,就会向主机发送一个sync命令、psync命令(请求同步)
psync支持部分重同步
在主从复制过程中slave断线,再次连接不需要全量重新复制同步。
psync与sync(旧指令)区别
1.【s - m】psync runId offset
主机就会发送全部数据给从机,完成同步
2.【m - s】+FULLRESYNC {runId} {offset}
3.【s】save masterInfo
Master收到后,通过bgsave保存快照,同时将后续的命令存到缓存中
master受到psync/sync指令后,开始执行BGSAVE操作,生成RDB文件并使用缓冲区记录此后执行的所有写命令;
主库Master接收到psync/sync命令后会开始再后台保存快照,并将期间接收到的命令保存
4.【m】bgsave / write repl_back_buffer
BGSAVE完成后,向slave发送RDB文件,并继续记录写命令;
Master将RDB发给Slave
redis2.8版本后,支持向slave直接发送RDB文件,不需要将其加载到内存中发送。
当快照完成后,主库会将快照和所有的缓存的写命令发送给从库
主机立刻进行存盘操作,发送 RDB 文件给从机
5.【m - s】send RDB
master每执行一次写命令,都会向slave发送相同的命令,使其执行相同操作
之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
最后master会将内存中的写命令,同步给Slave,Slave收到后再执行一遍
当主库每当接收到写命令时会将命令发送至从库,保持数据一致性
6.【m - s】send buffer
slave加载文件,放弃旧数据
slave服务在接受到数据库文件数据后,将其存盘并加载到内存中
Slave收到文件后先写入到本地磁盘,然后在从本地磁盘加载到内存中
从库接收到后,会载入快照并执行存储的缓存命令
从机收到 RDB 文件后,进行全盘加载。
7.【s】flush old data
slave加载完毕后,执行来自于master缓冲区记录写命令
8.【s】load RDB
核心原理
Slave从机第一次启动时
Master重启时
触发时机
bgsave时间
1、【m】bgsave时间开销
RDB的文件网络传输时间
2、【m】RDB网络传输开销
从节点清空数据时间
3、【s】清空数据时间开销
从节点加载RDB时间
4、【s】加载RDB时间开销
可能存在AOF重写的时间
5、【s】AOF重写时间(可能的)
全量复制开销过程、开销大
全量复制、复制过程的核心原理
Master根据Slave发送的同步请求中的offset
slave发送psync和加载RDB文件时,会存在offset偏移量
2.【m - s】CONTINUE
在backlog中查询部分丢失的数据,发送给Slave
master只会发送增量数据。
3.【m - s】send partial data
增量复制、部分复制的核心原理
Slave不会处理过期key,只会等待Master的过期通知
过期key的处理
主从复制原理、主从复制数据同步的原理、主从复制流程
要用redis运用与工程中,只使用一台redis是万万不能的(因为会出现宕机问题,导致数据丢失,因此一般会准备3太,一主二从)
从结构上,单个redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
从容量上,单个redis服务器内存容量有限,就算一台redis服务器内存容量为256G,也不能将所有内存用作与redis存储内存。
一般来说,单个redis最大使用内存不应该超过20G
注意事项
哨兵模式sentinel
哨兵集群
Redis-Sentinel
Redis-Sentinel是Redis高可用的实现方案
运行图例 -图解哨兵模式
图解哨兵模式
当主机断了,从机可以通过命令手动变为主节点,slaveof no one,变为主节点;
但当这是主机又回来了,那就只能重新配置,重新连接
如果用主从架构部署,其实就是加上哨兵就可以了,就可以实现任何一个实例宕机,自动进行主备切换。
以前:主从复制模式
哨兵模式是一种特殊的模式
首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,他会独立运行。
其原理就是哨兵通过发送命令,等待redis服务器相应,从而监控运行的多个redis实例
(自动选举老大,只要发现老大没了,就会选举新的老大)
现在:特殊的主从复制模式
哨兵模式是什么 ?
具备主从所有优点
主从可以自动切换,系统更健壮,可用性更高
基于主从复制模式,所有的主从复制的优点,它全都有
主从自动切换,故障可以转移,系统可用性更好
哨兵模式就主从模式的升级,手动到自动
哨兵模式的优点
不好在线扩容
集群容量一旦达到上限,在线扩容就特别麻烦
实现哨兵模式,配置很麻烦
哨兵模式的配置十分麻烦
哨兵模式的缺点、不足
提醒(Notification)
消息通知: redis 实例故障,发消息给管理员
如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
消息通知、客户端通知
配置中心: 转移发生,通知client新的master地址
如果故障转移发生了,通知client客户端新的master地址。
配置中心
监控(Monitoring)
通过发送命令,让redis服务器返回监控其运行状态,包括主服务器和从服务器
哨兵(sentinel) 会不断地检查你的Master和Slave是否运作正常sentinel monitor mymaster 127.0.0.1 6379 2监控主master127.0.01端口号为6379的进程,判断为失效至少需要2个Sentinel 同意
集群监控:负责监控master 和slave 进程
监控主节点从节点是否正常运行
负责监控redis master和slave进程是否正常工作。
集群监控、故障发现
当确认主节点宕机后,在从节点中选一个座位主节点,将其他从节点连接到新的主节点上通知客户端最新的地址
检测master状态,若异常则选取一个从机升为主机,原主机降为从机
如果master节点挂了,会自动转移到slave节点上
自动故障迁移(Automatic failover)
哨兵是一个分布式系统,监控主从架构中的节点通过 span style=\
当哨兵检测到master宕机,会自动将slave切换程master,然后通过发布订阅模式,通知其他的从服务器,修改配置文件,让他们切换主机
故障转移: master 宕机,自动转移slave
主从库自动切换,解决主从复制模式下故障转移问题
投票算法
保证了集群的高可用,但仍有容量上限问题
多个哨兵监控主库,超过指定数量个哨兵发现主库不回应,就投票选举新的主库
1、客户端高可用观察
2、服务端日志分析:数据节点和sentinel节点
1、从slave节点选出一个”合适的“ 节点作为新的master节点
2、对上面的slave节点执行slaveof on one命令让其成为master节点
3、向剩下的slave节点发送命令,让他们成为新master节点的slave节点,复制规则和parallel-syncs参数有关
4、更新对原来master节点配置,并保持对其关注,当宕机的机器恢复后就命令他复制新的master节点
故障转移流程
1、选择slave-priority(slave节点优先级)最高的slave节点,如果存在则返回,如果不存在则继续
2、选择复制偏移量最大的slave节点(复制的最完整),如果存在则返回,不存在则继续
3、选择runId最小的slave节点
如何选择合适的slave节点
自动故障转移
哨兵模式的四大主要功能
哨兵+redis主从的部署架构,是不会保证数据零丢失的,只能保证redis集群的高可用性。
哨兵+redis主从部署,不保证数据零丢失,只保证高可用
min-slaves-to-write 1
min-slaves-max-lag 10
消息丢失
哨兵+主从复制的部署架构,是不会保证数据零丢失的
sentinel 集群可看成是一个 ZooKeeper 集群
每10秒,sentinel对m/s执行info
发现slave节点
发现、确认主从关系
1、每10秒,每个sentinel都会对master和slave执行info
每2秒,sentinel通过master的channel交换信息
master频道:__sentinel__:hello
通过_sentinel_hello;进行频道的交互
交换对节点的看法、以及自身信息
交互对节点的看法和自身的信息
2、每2秒,每个sentinel通过master节点的channel交换信息(pub/sub)
每1秒,sentinel对其他sentinel和redis执行ping
心跳检测,失败判定依据
心跳检查,失败判断依据
3、每1秒每一个sentinel会对其他sentinel和redis进行ping
主节点
从节点
其他Sentinel节点
Redis-Sentienl通过三个定时任务,实现了Sentienl节点对三类节点的监控
说明
哨兵模式的三个定时任务
Redis-Sentinel的sentinel节点(哨兵节点),最好是>=3个,并且数量最好是基数个
至少需要3个实例,保证健壮性
如果哨兵集群仅仅部署了两个哨兵实例,quorum=1-->几个哨兵同意认为master宕机可以进行切换。同时S1,S2会选举出一个哨兵来执行故障转移的操作。同时,需要majority(几个哨兵同意进行故障转移),也就是大多数哨兵都是运行的。
2个哨兵的majority就是2,两个哨兵都运行着,就可以允许故障转移
(原则:3的majority就是2,5的majority就是3,4的majority就是2)
一台机器挂掉,只剩一个哨兵,所以两个哨兵节点的故障转移没法执行了
如果哨兵只部署了两个,且都在redis节点上
设置:quorum=2,majority
如果MASTER1所在机器宕机了,那么三哨兵还剩两个,s2和s3可以一致认为master宕机,然后选举出来一个进行故障转移同时3个哨兵majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移。
经典的3节点哨兵集群
哨兵至少需要三个实例,来保证自己的健壮性。
尽可能的在不同服务器上配置sentinel节点(哨兵节点)
如有超半数哨兵故障,会导致无法执行主从切换,但能进行主库主观下线
Redis-Sentienl数据节点(哨兵节点)和普通的数据节点没有区别
1.故障转移时,判断一个master节点是否宕机,需要大部分哨兵都同意才行,涉及到分布式选举的问题。
2.即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑爹了。
哨兵也是分布式的,原因如下
一个哨兵进程对redis服务器进行监控,可能会出现问题
可使用多个哨兵进行监控,各个哨兵间会互相进行监控,这样就形成多哨兵模式
多哨兵模式
当主机宕机后,从库必须投票选举,过半的数量认为主机宕机,才生效。
1.从成本角度看:3台和4台的可允许失效错误的机器数都是1,还不如用3台;
2.从概率看:4台节点数比3台节点数,更容易发生1台机器生效错误的问题。
节点数量推荐奇数
每次一个哨兵要做主备切换
quorum最好配置所有哨兵的一半+1
配置quorum 主库客观下线最小同意人数
首先,需要quorum数量的哨兵认为odown,然后选举出来一个哨兵做切换。
然后,这个哨兵还得的到mojority个哨兵的授权,才能正式执行切换。
比如5个哨兵,mojority就是3
quorum设置为2,那么3个哨兵授权就能切换。
如果quorum<mojority
那么就以quorum个数量的哨兵授权才能切换。
但是如果quorum>mojority
quorum和majority之间的关系
多哨兵模式、哨兵节点至少需要三个实例
通过Redis 发布/订阅机制,发现其他哨兵并组成集群
哨兵之间的互相发现,是通过redis的pub/sub系统实现的,
每个哨兵都会往_sentinel_:hello 这个channel里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他哨兵的存在。
每隔2秒钟,每个哨兵都会往自己监控的master+slaves对应的_sentinel_:hello channel发送消息,内容为自己的host,ip,runid还有master的监控配置
每隔哨兵也会监听自己监控的每个master+slaves对应的_sentinel_:hello channel ,然后去感知到同样在监听这个master+slaves的哨兵的存在,还会跟其他哨兵交换对master的监控配置,互相进行监控配置的同步。
哨兵和slave集群的自动发现机制
哨兵进程检测redis服务进程,会间隔进行心跳检测确定redis服务进程是否存活
一旦主机挂机了,就会进行故障转移---failover,重新选取主机
就算挂掉的主机回来了,也只能作为从机继续服务了
简化版本的工作机制
每个 Sentinel 以每秒一次的频率向它所知的主服务器、从服务器以及其他 Sentinel 实例发送一个PING命令
每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他 Sentinel 实例发送一个 PING 命令
①Sentinel发送ping命令
如果一个实例,距离最后一次有效回复 PING 命令的时间,超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
实例(instance)
②Sentinel标记异常实例为主观下线
如果一个主服务器被标记为主观下线, 那么正在监视这个主服务器的所有 Sentinel 要以每秒一次的频率确认主服务器的确进入了主观下线状态。
如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有 Sentinel ,要以每秒一次的频率,确认Master 的确进入了主观下线状态
③Sentinel确认Master 的确进入了主观下线状态
如果一个主服务器被标记为主观下线, 并且有足够数量的 Sentinel在指定的时间范围内同意这一判断, 那么这个主服务器被标记为客观下线。
当有足够数量的 Sentinel在指定的时间范围内,确认 Master 的确进入了主观下线状态, 则 Master 会被标记为客观下线
大于等于配置文件指定的值
(至少要达到配置文件指定的数量)
Sentinel 实例/节点需要
④多个Sentinel节点确认Master进入主观下线状态,标记Master为客观下线
⑤在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有 Master,Slave 发送 INFO 命令
⑥当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 ;
⑦若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除;若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除
挑选出新的master后,sentinel向原master的从服务发送slaveof新master的命令,
当下线的原master上线时,sentinel会发送slaveof命令让其成为新master的slaver
总结:
详细版本的工作机制
哨兵模式在节点失败时,分为两种下线方式、sdown和odown转换机制
解释什么是主观下线, 什么是客观下线
图解什么是主观下线, 什么是客观下线
每个sentinel对redis节点失败的偏见
Subjectively Down
简称 SDOWN
sdown主观宕机:就一个哨兵如果自己觉得master宕机了。
sdown达成条件很简单,如果一个哨兵ping一个master,超过is-master-down-after-milliseconds指定的毫秒数后,就主观认为master宕机。
指的是当前 Sentinel 实例对某个 redis 服务器做出的下线判断。
主观下线
所有sentinel对redis节点失败达成共识(超过quorum的统一)
Objectively Down
简称 ODOWN
odown客观宕机:如果quorum数量的哨兵都觉得一个master宕机了。
指的是多个 Sentinel 实例在对 Master Server 做出SDOWN 判断,
并且通过 SENTINEL is-master-down-by-addr 命令互相交流之后,得出的 Master Server 下线判断,然后开启 failover.
如果一个哨兵在指定时间内,收到了quorum个指定数量的其他哨兵也认为master宕机了,
那么就认为是odown,客观认为master宕机。
sdown转换成odown条件很简单
客观下线
一、【故障发现】多个sentinel发现并确认master有问题,发现Master宕机
原因:只有一个sentinel节点完成故障转移
集群通过投票(获取半数以上选票)选举Leader执行主从切换
实现:通过sentinel is-master-down-by-addr命令竞争领导者
1、每个做主观下线的sentinel节点向其他sentinel节点发送命令,要求将它设置为领导者
2、收到命令的sentinel节点如果没有同意通过其他sentinel节点发送的命令,那么将同意该申请,否则拒绝
3、如果sentinel节点发现自己的票数已经超过sentinel集合半数且超过quorum,那么自动成为领导者
4、如果此过程有多个sentinel节点成为领导者,那么等待一段时间重新选举
选举
二、【sentinel领导选举】选举出一个sentinel作为领导、sentinel领导选举
对这个slave执行slaveof no one
哨兵从服务器列表中挑选Master 、slave-master选举算法、选新 master 的原则、新master选取策略
去掉已下线和历史网络连接状态不好的从库
(1)过滤掉不在线和响应慢的服务器
根据 master断开连接的时长,先排除不合适的从库
如果一个slave跟master断开连接的时长减掉master宕机的时长已经超过了down-after-milliseconds的10倍,那么slave就认为不适合选举为master
(2)过滤掉与原Master断开时间最久的
筛选与过滤
对slave进行排序,选新 master 的原则,需要考虑以下四个信息:
选slave-priority最高的
最后比较优先级priority
按照slave优先级进行排序,
依据优先级,配置文件中配置slave-priority属性
配置文件里设置的slave-priority越小,优先级越高
(slave-priority配置项)
优先级最高的从库
(1)slave优先级最高的
偏移量最大
复制offset大小
选复制偏移量最大的
和旧主库同步程度最接近的从库(slave_repl_offset和master_repl_offset)
slave里复制主节点的offset越靠后,说明复制的数据越多,优先级就越高
如果两个服务器优先级一致,那么回去查看从服务器中数据的offset,
offset说明数据最新,选出offset大的服务器为Master
获取原master数据最多的从机
(2)复制offset最大的
如果上面两个都一样,那么就选一个run id比较小的slave
run id、选runId最小的
run id号最小的从库
选择runid最小的
(每个redis实例启动后都会随机生成一个40位的runid)
(3)run id比较小的slave
排序
哨兵向选举出的新Master发送指令,断开与旧Master的连接
把新Master的ip地址同步到其他Slave节点
如果哨兵选举了一个新的master,哨兵会去让剩下的slave修改他们的配置,连接到新的master上面去
新Master诞生、Slave配置的自动纠正
哨兵会对一套master+slaves进行监控,有相应监控的配置
执行切换的那个哨兵,会从要切换到的新master(slave->master)那里的到一个configuration epoch,
这就是一个version号,每次切换的version号都必须是唯一的。
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout的时间,
然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号
configuration epoch
哨兵完成主从切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,通过之前说的pub/sub消息机制channel。
这里上面的version 就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成了一次新的主从切换之后,新的master配置是跟着新的version号的
其他的哨兵如果发现版本号大于自己目前存储的版本号,就会去更新自己的master配置
configuration传播
四、【通知】通知客户端主从变化
sentinel会保持对其关注
以前的Master如果重连了,那么以前的Master会变成新Master的Slave
五、【老master】等待老的master复活成为新master的slave
【sentinel集合】 预先知道sentinel节点集合、masterName
【获取sentinel】遍历sentinel节点,获取一个可用节点
【获取master节点】get-master-addr-by-name masterName
【role replication】获取master节点角色信息
客服端从哨兵订阅消息,进行主库切换
JedisSentinelPool - MasterListener -- sub \"+switch-master\"
sentinel是配置中心,而非代理!
客户端初始化时连接的是sentinel集合,而不是具体的redis节点,但是sentienl只是配置中心而不是代理
【变动通知】当节点有变动,sentinel会通知给客户端 (发布订阅)
客户端流程
哨兵模式的底层工作原理
编写sentinel.conf
配置哨兵:sentinel monitor mymaster 127.0.0.1 6379 1
sentinel monitor [名称] 127.0.0.1(主机地址) 6379(主机端口) 1(开启主机选举)
1、配置哨兵配置文件:vim sentinel.conf
sentinel monitor myredis 127.0.0.1 6379 1
# sentinel monitor 被监控的名字 host port 1
后面的数字1,代表主机挂了,slave投票看让谁接替称为主机,票数最对的,就会称为主机
2、最核心的配置,和最基础的
启动哨兵:redis-sentinel sentinel.conf
redis-sentinel sentinel.conf
3、启动启动哨兵进程
哨兵机制会在下次心跳检测到主机挂掉,故障转移,选取新主机
4、如果主机主机挂掉后,哨兵自动从从机中选择一个当主机
之前的主机就算复活了,也只能当从机了,
一旦失去不再来,除非当前主机挂掉,重新选取才有机会
5、但当原主机回来了,只能当从机
第一部分
第二部分
第三部分
哨兵模式的全部配置
1、配置主从节点
2、配置开启sentinel监控主节点
3、配置多台机器
4、详细配置节点
哨兵模式的环境搭建 与配置项
哨兵模式的安装、配置以及使用注意
对于哨兵+redis主从这种复杂的部署架构
尽量在测试环境和生产环境,都进行充足的测试和演练
建议充足测试
哨兵模式的充分测试与上线
sentinel failover 进行替换主节点上线
sentinel failover进行替换
上线主节点
sentinel failover <masterName>主节点下线
sentinel failover
下线主节点
slaveof命令,sentinel节点可以感知
上线从节点
临时下线或者永久下线,需要考虑读写分离和原因
Redis-Sentienl读写分离可以依赖于Sentienl节点消息通知,获取Redis节点的数据状态变化
考虑是否做清理、考虑读写分离
下线从节点
参考其他sentinel节点启动
上线sentinel
sentinel节点
节点的上线和下线、上下线节点
机器下线(机器过保)
机器性能不足(比如CPU、内存等)
节点自身故障(网络不稳定等)
节点运维问题
1、当作副本,并且是高可用的基础
2、扩展redis 读操作的能力
从节点的作用
client关注slave节点资源池
+switch-master: 从节点晋升
+convert-to-slave: 切换为从节点
+sdown: 主观下线
关注三个消息
高可用的读写分离
哨兵模式的常见运维
哨兵模式、哨兵机制 Sentinel
Redis集群、集群、集群模式
Cluster、cluster、cluster模式、redis集群(Redis cluster)
redis cluster、redis cluster架构、 redis cluster集群模式、Redis-Cluster集群
Redis Cluster集群模式是什么?
Sharding
分区
Redis Cluster是一种服务端Sharding技术
对Redis水平扩容
支持 redis集群(Redis cluster)
redis 3.0之后开始支持的
支持N个master node
可以在多台机器上,部署多个redis实例master
可以在多台机器上,部署多个master(实例)
每个实例master存储一部分的数据
每个master存储一部分的数据
每个master node都可以挂载多个slave节点
每个master node可以挂载多个slave node(从实例)
每个redis实例可以挂Redis从实例
多主多从的架构
每个master来说,写久写到master
读就从master对应的slave去读
读写分离的架构
提供内置的高可用支持
部分master不可用时,还是可以继续工作的。
每个master都有slave节点
如果master挂掉,redis cluster这套机制,就会自动将某个slave切换成master
如果redis主实例master挂了,会自动切换到redis从实例顶上来。
高可用的架构
多master+读写分离+高可用
所有的节点都要有一个主节点
中心挂了,服务就挂了
中心处理数据的能力有限,不能把节点性能发挥到最大
就是一个路由作用
中心化
让每个主机都拥有转发能力
去中心化
去中心化配置
快照同步
增量同步
无盘复制
wait 指令
主从复制 (异步):SYNC snapshot + backlog队列
复制
集群也使用了主从复制
只需要基于redis cluster去搭建redis集群即可
不需要手工去搭建replication复制+主从架构+读写分离+哨兵集群+高可用
集群架构
分摊压力
实现扩容
Redis Cluster集群模式的简介、优点与 特性
多键操作不被支持
mget/mset 必须在同一个slot
key批量操作支持有限
操作的key必须在同一个slot
多键redis事务不被支持,lua脚本不被支持
key事务和lua支持有限
bigkey无法分区
key是数据分区最小粒度
无法树形复制
复制只支持一层
Redis Cluster集群模式的缺点
配置文件:cluster-enabled yes
启动: redis-server *.conf
gossip通讯:cluster meet ip port
分配槽(仅对master):cluster addslots {0...5461}
配置从节点:cluster replicate node-id
原生
安装ruby环境
安装ruby
安装redis gem
安装rubygem redis
安装redis-trib.rb
脚本
cluster nodes
cluster info
cluster slot
redis-trib.rb info ip:port
验证
Redis Cluster集群模式的创建
slot(插槽)
引入哈希槽概念
采用slot的概念
通过哈希的方式
redis集群分区使用了哈希槽的概念
每个slot可以存储一批键值对
一个hash slot中会有很多key和value
Rax
槽位信息存储于每个节点中
哈希槽(Hash Slot)什么?
默认16384
16384个
数据库中每个键都属于16384个插槽中的一个
一个Redis集群包含16384个插槽(hash slot)
有2^14(16384)个哈希槽
Redis 集群中内置了 16384(2^14) 个哈希槽
一个Redis集群包含16384个插槽
分片
slots的定位:crc16(key) % 16384
key通过CRC16算法校验,并对16384取模,将数据分片,最后会将数据放到对应的哈希槽
每个key通过CRC16校验,并对16384取模,来决定落在哪个哈希槽上
集群使用公式CRC16(KEY)%16384来计算key属于哪个槽
每个key通过CRC16校验后,再对16384取模,来决定落在哪个哈希槽上
每个key通过CRC16校验后,再对16384取模来决定放置哪个槽
每个key通过CRC16校验后,并对16384取模来决定放置哪个槽
当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 CRC16算法算出一个结果,然后把结果对 16384 求余数
当往Redis Cluster中加入一个Key时,会根据CRC16(key) mod 16384计算这个key应该分布到哪个hash slot中
将kv映射到哈希槽中
结果对应0~16383之间的哈希槽
每个节点均分存储一定的哈希槽区间的数据
再将16384个slot槽,均分到master实例中
由于哈希槽个数一般远远大于实际生产的机器数量,所以这套预分区规则,可以满足所有的生产需求。
自动将数据进行分片,每个master上放一部分数据。
不同的key分散到不同的主节点上,每个主节点管辖一定的槽位
保证了高扩展,适用于数据量大,需要持续扩容
集群的每个节点负责一部分hash槽
每个 key 都会对应一个编号在 0-16383 之间的哈希槽,Redis会根据节点数量大致均等的将哈希槽映射到不同的节点。
cluster keyslot k
计算槽位
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态
如果需要添加节点,只需要各将旧节点的部分槽位移动到新节点上即可;
如果需要删除及诶单,只需要将被删除的节点槽位转移到其余节点即可;
很容易添加或删除节点
Redis Cluster集群模式分区规则优点
只有一个表
所有的key都放在这个表里
单节点Redis
自动生成16384个分区表
插入数据时,会根据如上简单算法来决定key应该存在哪个分区
每个分区里有很多key
Redis Cluster集群模式
Redis Cluster集群模式类比于表分区
当前集群有3个节点
节点 A 包含 0 到 5500号哈希槽.节点 B 包含5501 到 11000 号哈希槽.节点 C 包含11001 到 16384号哈希槽.
案例1
假如redis集群里面能存放90个key,那么redis集群把90key平分到3个主机
redis对每个主机里面30个存储位置都编号,当应用连接到主机1上面时,发送一个写的命令
如果槽号在1-30,可以直接操作主机1
如果槽号在31-60,那么redis会转发到主机2
如果槽号在61-90,那么redis会转发到主机3
主机使用CRC16,算出槽号
如果再发一个命令set age 22,那么主机2使用CRC16,再算槽号,再转发
案例2
Redis Cluster集群模式分区规则 举例说明
Redis Cluster集群模式的工作原理
用不同的算法,就决定了在多个master节点时,数据如何分布到这些节点上去,解决这个问题
来了一个key之后,计算hash值,然后对节点数量取模,取模结果一定小于节点数量,然后放到取模对应的节点上
高并发场景下时不可接受的。并发,一台机器挂掉了,原来是对3取模存的数据,现在对2取模取数据,会导致所有请求全部无法拿到有效的缓存。最大问题,一个master宕机那么大量的数据就需要重新计算写入缓存。
但是如果某台master宕机了,就得把其他两台master的数据全拿出来重新取模计算,所有数据放到两台机器上。
hash算法
master分布在一个圆环上然后有一个key过来以后,同样也是计算hash值,然后会用hash值在圆环对应的各个点上key落在圆环上以后,就会顺时针旋转去寻找距离自己最近的一个节点。
一致性hash算法保证,任何一个master宕机,只有之前在那个master上的数据会受到影响,因为朝着顺时针走,全部在之前的master上找不到了,master宕机了。会顺时针找到下一个master,也找不到。1/n的流量,会瞬间涌入数据库中,重新查询一次。
可能集中在某个hash区间内的值特别多,那么会导致大量的数据都涌入同一个master内,造成master的热点问题,性能出现瓶颈
缓存热点问题
给每个master节点都做了均匀分布的虚拟节点然后这样的话,在每个区间内,大量的数据,都会均匀的分布到不同的节点内,而不是按照顺时针的顺序去走,全部涌入同一个master内。
虚拟节点
redis cluster有固定的16384个hash slot,对每个key计算CRC16值,然后对16384取模,可以获取对应的hash slotredis cluster中每个master都会持有一部分的hash slot,比如有3个master,那么可能每个master持有5000多个hash slothash slot让node的增加和移除很简单,增加一个master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去,移动hash slot的成本是非常低的。客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现。
任何一台机器宕机,不影响其他节点,因为key找的是hash slot,不是机器。
redis发现某台机器宕机,会把这台机器的slot快速的分配到其他的master上
redis cluster的hash slot算法
一致性hash算法
略
Redis Cluster集群模式的hash slot算法
数据存储的核心算法
故障的信息
master->slave之间的关系
hash slot->node之间的映射表关系
集群有很多元数据
基于zookeeper集中式元数据的维护和存储
典型方案
分布式的大数据实时计算引擎,集中式的元数据存储架构
底层基于zookeeper(分布式协调中间件)的集群所有元数据维护。
storm
典型代表
集中式元数据维护和存储
小道留言
每个 master自己都会维护一份完整的元数据
一个master有变更,就会把元数据发送给其他的master,其他master收到了就会更新一下自己的元数据。
Redis采用的是gossip协议
每个节点都会频繁的给其他节点发送ping,
其中包含自己的状态,还有自己维护的集群元数据,互相通过ping交换元数据。
ping很频繁,而且要携带一些元数据,所以可能会加重网络负担
每个节每秒会执行10次ping,每次会选择5个最久没有通信的其他节点
当然如果发现某个节点通信延迟达到了cluster_node_timeout/2,那么立即发送ping,避免数据交换延时过长,落后的时间太长了
比如两个节点之间都10分钟没有交换数据了,那么整个集群处于严重的元数据不一致的情况,就会有问题。
所以cluster_node_timeout可以调节,如果调节比较大,那么会降低发送消息的频率。
每次ping,一个是带上自己节点的信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换。
至少包含3个其他节点的信息,最多包含总节点-2个其他节点的信息。
ping消息深入
ping
返回ping和meet
包含自己的状态和其他信息
也可用于信息广播和更新
pong
每个节点通过meet命令交换槽位信息
某个节点发送meet给新加入的节点,让新节点加入到集群中,然后新节点就会开始于其他节点进行通信
其实,内部就是发送了一个gossip meet信息,通知那个节点去加入我们的集群
meet
某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
fail
goosip协议包含多种消息
元数据的更新和读取,时效性比较好
一旦元数据出现了变更,立即就更新到集中式的存储中,其他节点读取的时候要立即就可以感知到。
好处
所有元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。
不好
集中式
元数据更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定延时,降低了压力
元数据更新有延时,可能会导致集群的一些操作会有一些滞后。
goosip协议
对比一下集中式与goosip协议
每个节点都有一个专门用于节点间通信的端口,就是自己的端口号+10000
每个节点间个一段时间都会往另外几个节点发送ping消息,同时其他节点收到ping之后返回pong
在Redis Cluster集群模式架构下,每个redis要开放两个端口
创建集群时,集群节点直接要相互通信,使用 redis-server的端口+10000 = 新的集群的监听端口
10000端口号
一个6379
6379
(6379+10000)
另外一个就要加10000的端口号,比如16379
16379端口号是用来进行节点间通信的
用来节点间通信的 gossip协议
也就是cluster bus的东西,集群总线。
cluster bus的通信,用来进行故障检测,配置更新,故障转移授权
cluster bus用了另外一种二进制协议,主要用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间
16379
举例
图解Redis Cluster集群模式的端口号
端口说明
hash slot信息
故障信息
节点的增加删除
三类交换的信息
节点间内部通信机制
https://www.cnblogs.com/runnerjack/p/10269277.html
集群方式总结
http://www.luyixian.cn/news_show_319542.aspx
经典问题详解
https://www.jb51.net/article/96230.htm
windows下多实例
dump
restore
remove
扩展性:迁移slot (同步)
每份数据分片会存储在多个互为主从的多节点上
数据写入先写主节点,再同步到到从节点
同一个分片多节点间的数据不保持一致性
读取数据时,当客户端操作的key没有分配在该节点时,redis会返回转向指令,指向正确的节点
扩容时需要把旧节点的数据迁移一部分到新的节点
请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上
方案说明
1、不使用重定向,则操作数据的key不属于当前客户端插槽德华,redis会报错提示前往对应的redis实例客户端操作数据;
2、使用重定向:redis-cli -c -p 6379其中 -c 使命令自动重定向
客户端重定向
1、不在一个slot下的键值,不能进行mget/mset等多键操作
2、通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到同一个slot中去
举例:mset {group}k1 v1 {group}k2 v2
多键操作
cluster keyslot key:计算key应该被放在哪个槽上
cluster countkeysinslot slot:返回槽slot目前包含的键值对数量->cluster countkeysinslot 12539
cluster getkeysinslot slot count:返回count个slot槽中的键
读取数据
主节点下线,从节点自动升为主节点
主节点恢复后,变为从节点
redis.conf中属性cluster-require-full-coverage控制当某一段插槽的主从节点都宕机以后,redis服务是否还继续。
集群写入数据
redis cluster的高可用原理,几乎跟哨兵是类似的
如果一个节点认为另外一个节点宕机了,那么就是pfail,主观宕机如果多个节点都认为另外一个节点宕机了,就是fail,客观宕机,跟哨兵几乎一样 sdown,odown
根据cluster-node-timeout内某个节点一直没有返回pong,那么就会被认为pfail
如果一个节点认为某个节点pfail了,那么就会在gossip消息中,ping给其他节点,如果超过半数节点都认为pfail了,那么就会变成fail
1.判断节点宕机
对宕机的master node,从其所有slave node中,选择一个切换成master node
2.从节点过滤
每个从节点,都根据自己对master复制数据的offset,来设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,优先进行选举
所有的master node开始slave选举投票,给要进行选举的slave进行投票,如果大部分master node(n/2+1)都投票给了某个从节点,那么选举通过,那个从节点可以成为master
从节点执行主备切换,从节点切换为主节点
3.从节点选举
高可用性与主备切换
准备新节点
redis-trib.rb add-node
加入集群
1_对目标节点:cluster setslot {slot} importing {sourceNodeId}
2_对源节点:cluster setslot {slot} migrating {targetNodeId}
3_对源节点循环执行:cluster getkeysinslot {slot} {count},每次获取count个键
4_对源节点循环执行:migrate {targetIp} {targetPort} key 0 {timeout}
5_对所有主节点:cluster setslot {slot} node {targetNodeId}
手工
pipeline migrate
redis-trib.rb reshard
迁移槽和数据
扩容
通过ping/pong发现故障
当半数以上主节点都标记其为pfail
故障发现
资格检查
准备选举时间
选举投票
替换主节点
迁移槽
cluster forget {downNodeId}
redis-trib.rb del-node
忘记节点
关闭节点
收缩
0.先尝试访问源节点
1.源节点返回ASK转向
2.向新节点发送asking命令
3.向新节点发送命令
迁移slot过程中如何同时提供服务?-- ask
伸缩
一致性: 保证朝着epoch值更大的信息收敛
如果当前实例没有这个键值对映射的哈希槽,那么实例返回MOVED包含新实例的访问地址
数据迁移过程中发送命令会收到ASK报错并包含正在迁移的新实例地址,
此时客户端需要先向新实例发送ASKING命令(请求执行命令),然后再发送GET命令
重定向机制
1.当某一个从节点发现自己的主节点已下线(状态为fail)时,从节点会广播一条消息,要求所有收到这条消息,并且具有投票权的主节点向这个从节点投票;
2.如果一个主节点具有投票权,并且这个主节点尚未给其他从节点投票,那么这个主节点会投票给该从节点;
3.如果集群中有N个主节点具有投票权,那么当一个从节点收集到N/2+1(超过半数)的投票时,这个从节点就成为新的主节点;
4.如果一个周期以内,没有收集到足够的支持票数,则集群进入下个配置周期,并在此发起选举,直到选举出新的主节点;
5.新的主节点出现后,撤销其哈希槽指派,并将其全部指派给自己;
6.对集群进行广播,告知其他节点已经成为新的主节点;
ps:从节点发现主节点下线时,不是立刻发起选举,而是会有一个配置时间+随机延迟,以减少多个从节点在同一时间发起选举的可能性;因为多个节点同时发起投票,会引发大家都没有获得半数以上的投票
选举主节点
用个案例解答吧比较好理解:假设从node A迁移槽 1 位到node B迁移中会将 node A的这槽 1 状态标记为Migrating(迁移)node B的状态为importing (输入口)这时候槽的映射关系不做修改,迁移过程中如果出现查询槽 1 的数据,客户端会先去node A上去查询数据,如果对应的数据还在本机上,那么直接返回,如果数据不在node A上 则会返回 ASK 转向。如果客户端接收到 ASK 转向, 那么将命令请求的发送对象调整为转向所指定的节点node B。先发送一个 ASKING 命令,然后再发送真正的命令请求。请求完成后不会更新客户端所记录的槽至节点的映射:槽 1 应该仍然映射到node A , 而不是node B 。一旦node A 针对槽 1 的迁移工作完成, node A 在再次收到针对槽 1 的命令请求时, 就会向客户端返回 MOVED 转向, 将关于槽 1 的命令请求长期地转向到node B 。
扩展-保证在slot(槽位)迁移过程中的正常写入与修改
1.向任意节点发送命令
2.节点计算槽和对应节点
3.如果指向自身,则执行命令并返回结果
4.如果不指向自身,则回复-moved (moved slot ip:port)
5.客户端重定向发送命令
moved
redis-cli -c 会自动跳转到新节点
都是客户端重定向
moved: 表示slot确实不在当前节点(或已确定迁移)
ask: 表示slot在迁移中
moved vs. ask
tips
客户端路由
问题:mget/mset必须在同一个槽
串行 mget
客户端先做聚合,crc32 - node,然后串行pipeline
串行IO
客户端先做聚合,然后并行pipeline
并行IO
hash_tag
实现
批量操作
客户端
数据量大时fork子进程缓慢
要求16384个槽全部可用
节点故障或故障转移时会不可用:(error) CLUSTERDOWN
cluster-require-full-coverage=yes
大多数业务无法容忍
集群完整性
消息发送频率
消息数据量
节点部署的机器规模
来源
避免“大”集群
cluster-node-timeout: 带宽和故障转移速度的均衡
尽量均匀分配到多机器上
publish在集群中每个节点广播,加重带宽
解决:单独用一套sentinel
集群状态下的pub/sub
带宽消耗
redis-trib.rb info 查看节点、槽、键值分布
redis-trib.rb rebalance 重新均衡(慎用)
节点和槽分配不均
CRC16一般比较均匀
可能存在hash_tag
cluster countkeyinslot {slot} 查看槽对应键值数
不同槽对应键值数量差异较大
从节点执行 redis-cli --bigkeys
优化数据结构
包含bigkey
内存配置不一致
数据倾斜
原因:热点key、bigkey
避免bigkey
热键不要用hash_tag
一致性不高时,可用本地缓存,MQ
实现简单
内存泄露隐患,只能统计单个客户端
客户端统计
增加代理端开发部署成本
代理统计
monitor本身问题,只能短时间使用
只能统计单个redis节点
服务端统计(monitor)
无侵入
增加了机器部署成本
机器段统计(抓取tcp)
热点key解决思路
请求倾斜
倾斜
从节点不接受任何读写请求
会重定向到负责槽的主节点(moved)
readonly命令
只读连接
共性问题:复制延迟、过期数据、从节点故障
cluster slaves {nodeId} 获取从节点列表
读写分离客户端会非常复杂
读写分离
只能 单机 to 集群
不支持在线迁移
不支持断点续传
单线程迁移,影响速度
redis-trib.rb import
唯品会 redis-migrate-tool
豌豆荚 redis-port
在线迁移
数据迁移
运维
Codis 是一个分布式 Redis 解决方案
用zookeeper存储槽位关系
不支持事务
rename 操作也很危险
为了支持扩容,单个 key 对应的 value 不宜过大
网络开销更大
需要运维zk
代价
Codis
代理proxy
Redis集群方案、分布式解决方案
先写缓存,在写数据库,缓存成功,数据库失败(脏读)
缓存与数据库一致性
高可用性:如果你的系统可以保证在全年,99.99%的时间内,都是处于可用的状态的,那么就可以称之为高可用性。
如果一个slave挂掉了,是不会影响可用性的,还有其他slave提供相同数据下的相同的对外查询服务
但是如果master挂掉了,写缓存的时候全部失效了,slave也没有主节点给他们复制数据了,系统相当于不可用了缓存不可用,高并发高性能的缓存不可用了,大量的流量,超过mysql承载范围内的大并发会涌入mysql,mysql宕机
redis宕机出现的问题
redis怎么做到高可用?->redis高可用架构,叫做故障转移,failover,也可以叫做主备切换
在master故障时,自动检测,并将某个slave自动切换为master node的过程叫主备切换。这个过程,实现了redis的主从架构下的高可用性。一旦master故障,在很短时间内,就会切换到另一个master上去。
Redis架构如何才能做到99.99%高可用
写 master
读 slave
架构做成主从架构一主多从,主负责写,并将数据同步复制到其他的slave节点,从节点负责读。所有读请求全部走丛节点
如何通过读写分离承载QPS超过10万+
一般来说很多项目就足够了
单主用来写入数据,单机几万qps
多从用来查询数据,多个实例可以提供每秒10万qps
主从架构,一主多从
redis高并发的同时,还需要容纳大量的数据:一主多从,每个实例都容纳了完整的数据,比如redis主就10G内存量,其实你就只能容纳10G的数据量。
但如果你的缓存要容纳的数据量很大,达到几十G甚至几百G几T,那你就需要redis集群,而且用redis集群之后,可以提供可能每秒几十万的读写并发。
Redis高并发
Redis集群方案、分布式解决方案、高可用
Redis官网下载页面: http://redis.io/download
Windows下Redis项目: https://github.com/MSOpenTech/redis
从github上查找redis下载
在github上搜索Redis,
1、下载安装包
下载zip压缩包,解压即可
1.redis-benchmark.exe #基准测试
2.redis-check-aof.exe # aof
3.redis-check-dump.exe # dump
4.redis-cli.exe \t# 客户端
5.redis-server.exe # 服务器
6.redis.windows.conf # 配置文件
解压后的文件说明
大概就是这样
找到解压后的文件,图解解压后的文件说明
2、下载获得压缩包,解压到自己电脑目录下就可以
3、开启redis,运行服务server即可
这个终端窗口不要关闭,不然服务就会被终止
双击Redis.server.exe打开Redis
4、使用redis客户端Client,连接redis(服务server不能关)
测试是否连接成功
测试存取值
双击Redis-cl.exei测试
5、测试连接ping,返回‘PONG’就是成功
6、设置值set name value
redis-server.exe redis.windows.conf
手动运行
redis-server --service-install redis.windows.conf --loglevel verbose命令注册服务
安装redis服务
redis-server --service-start开启服务
启动redis服务
redis-server.exe --service-uninstall
卸载redis服务
服务运行
redis-cli.exe 默认访问6379端口
redis-cli.exe -p 6380 访问指定的端口6380
客户端运行
单实例运行
windows下安装(不推荐)
redis
官方推荐使用Linux部署
linux安装Redis(单机)
redis推荐在linux上搭建使用,不建议在windows上搭建
下载安装包(Redis.io)
1、下载安装包,直接从官网下载,首页‘Download it’,获得安装包
2、使用XShell连接服务器,使用Xftp将安装包上传
解压压缩包
发送到服务器的opt(软件一般都安装在这里)目录下
3、解压redis的安装包,放入/opt目录下
4、进入到redis文件中,可以看到redis.conf配置文件
编译需要环境支持,如果没有c++环境的先配置环境
安装gcc环境(C语言编译环境)
yum install gcc-c++
进本环境安装
gcc -v
查看
安装gcc-c++
将所有需要配置文件进行配置
输入make
确认是否所有都安装好
输入make install
进入Redis文件夹进行软件编译
5、使用前的操作:安装gcc环境 与 软件编译
/usr/local/bin
打开阿里云把文件copy到software里面
cd /usr/localmkdir softwaretar -zxvf xxx.tar.gz
Other
6、redis默认安装路径
然后我们会在/usr/local/bin下发现我们已经安装好的Redis
7、可以在bin路径下找到server、client,操作如同window一样
就像这样
然后我们在/usr/local/bin下建立一个专门存放Redis配置文件的文件夹,因为Redis之后组集群会有很多配置文件
8、可将bin目录下文件redis.conf拷贝到当前目录下
进入redis.conf中,将daemonize no 改成yes
大概在11%的位置
修改Redis的配置文件,改成后台运行
9、redis默认不是后台启动的,修改配置文件
通过指定的配置文件启动服务
redis-server kconfig/redis.conf
10、启动redis 服务
redis-cli -p 6379
11、启动客户端
# 连接是否成ping# 设置值set name# 获取值get name# 查看所有值keys *
同Windows下安装一样
12、测试
命令:ps -ef|grep redis
13、查看redis进程是否存在
命令:shutdown
关闭并退出
14、如何关闭redis服务呢?
注意事项:外部访问记得打开防火墙
Linux下安装(官方推荐)
开启并连接
测试Redis是否安装Ok
不同操作环境下安装Redis、Redis安装的基本步骤
redis cluster,部署了10台机器,5台机器部署了redis的主实例,另外5台机器部署了redis从实例,每个主实例挂了一个从实例,5个节点提供读写服务,每个节点的读写高峰可能可以达到每秒5万,5台机器最多时25万读写请求/s。
机器配置:32G内存+8核CPU。但是分配给redis进程的是10G内存,一般线上生产环境,redis的内存尽量不要超过10G,超过10g可能会有问题。 5台机器对外提供读写,一共50G内存。
因为每个主实例都挂了一个从实例,所以时高可用的,任何一个主实例宕机,自动故障迁移,redis从实例会自动变成主实例继续提供读写服务。
保险数据,每条数据10Kb,100条是1mb,10万条数据是1g。常驻内存的是200万条保单数据,占用内存是20g,仅仅不到总内存的50%
往内存里写了什么数据?每条数据的大小是多少?
目前高峰期每秒就是3500左右的请求量
Redis线上生产环境时如何部署的?
官网
安装Redis
安装gcc环境(C语言编译环境)
安装
单位说明:1k(1000bytes)!=1kb(1024bytes)
include:引入公共配置文件
bind:限定访问的主机地址
protected-mode:是否开启安全防护模式
port:指定redis实例端口号,默认6379
tcp-backlog:指定高并发时访问排队的长度,超过后就阻塞。
timeout:超时时间
tcp-keepalive:对客户端的心跳检测间隔时间
network
daemonize:是否开启守护线程模式运行
pidfile:指定进程id文件保存的路径
debug
verbose
notice
warning
loglevel:指定日志级别
logfile:指定日志文件路径
syslog-enabled:是否记录到系统日志
syslog-ident:设置系统日志的id
syslog-facility:指定系统日志设置,必须为user或local0-local7之间的值
databases:设置数据库数量,默认为16(0-15)
general
requirepass:设定访问密码
maxclients:最大连接数
maxmemory:最大占用多少内存
noeviction
maxmemory-policy:缓存清理策略
配置
启动
Redis安装、配置、启动
Redis的安装与部署
进入服务器找到redis安装的目录,找到redis.conf 文件进行配置
进入方式
1k(1000bytes)!=1kb(1024bytes)
单位
配置文件unit单位 对大小写不敏感
单位说明
bind 127.0.0.1
限定访问的主机地址
绑定的ip
bind
是否开启安全防护模式
保护模式
protected-mode yes
protected-mode
端口设置
指定redis实例端口号
默认6379
port 6379
指定高并发时访问排队的长度,超过后就阻塞
tcp-backlog
超时时间
timeout
对客户端的心跳检测间隔时间
tcp-keepalive
网络Network
是否开启守护线程模式运行
daeminize yes
以守护进程的方式运行,默认是no,需要自行开启为yes
daemonize
指定进程id文件保存的路径
pidfile /var/run/redis_6379.pid
如果以后台的方式运行,就需要指定一个 pid
pidfile
loglevel
指定日志级别
日志 loglevel notice 四个层级
logfile \"\"
指定日志文件路径
日志的文件位置名
logfile
设置数据库数量
默认为16(0-15)
databases 16
数据库的数量,默认是 16 个数据库
databases
是否总是显示log
always-show-log yes
默认为yes
always-show-log
是否记录到系统日志
syslog-enabled
设置系统日志的id
syslog-ident
指定系统日志设置,必须为user或local0-local7之间的值
syslog-facility
通用General
设置密码,这是通过在配置文件中设置的
命令设置密码 config get requirepassconfig set requirepass \"123456\"(password)auth加权限
安全Security
设置能连接上的redis的最大客户端数量
maxclients 10000
redis 配置最大的内存容量
maxmemory <bytes>
#内存到达上限后的处理策略
处理策略
maxmemory-policy noeviction
限制Clients,内存设置
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件.rdb .aof
redis是内存数据库,如果没有持久化,那么断电数据既失
# 如果 900s 内,如果至少有一个1 key进行了修改,我们及进行持久化操作save 900 1# 如果 300s 内,如果至少有一个10 key进行了修改,就会进行持久化save 300 10# 如果 60s 内,如果至少有一个10000 key进行了修改,就会进行持久化save 60 10000
stop-writes-on-bgsave-error yes # 持久化如果出错,是否还需要继续工作
rdbcompression yes #是否压缩redb文件,需要消耗一些cpu资源
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
dir ./ # rdb文件保存的目录
RDB配置
appendonly on # 默认是不开启aof模式的,默认是使用rdb方式持久化的,大部分所有的情况下,rdb够用了
appendfilename “appendonly.aof” 持久化文件的名字
基本配置
AOF配置
持久化相关配置
Redis.conf配置详解
1、常用作嵌入式脚本语言
2、将复杂的redis操作,写为一个脚本,交于redis执行,减少连接redis的次数
3、具有一定的原子性,不被其他命令插队,可以完成事务操作
4、redis2.6以上的版本才可以使用
redis> eval lua-script key-num [key1 key2 key3 ....] [value1 value2 value3 ....]
在Redis中调用lua脚本
redis> eval \
在lua脚本中调用redis命令
案例:对IP进行限流
缓存lua脚本
lua脚本超时
Lua脚本
Java整合Redis(Lua脚本)
Spring Boot 可以非常方便地集成 Redis
Redis Client
通过Java连接Redis
使用java操作Redis中间件!
是redis官方推荐的java连接开发工具!
原生redis命令,springboot3 已弃用
如果要使用java操作redis,那么一定要对Jedis十分熟悉
Jedis: 一款java操作redis数据库的工具
什么是Jedis、简介
jedis依赖包
导入jedis依赖包
pre style=\
commons-pool2-2.3.jar
jedis-2.7.0.jar
0. 导入jar包
新建项目,引入依赖
导入fastjson依赖包,阿里巴巴的
开启后台运行
开启保护模式
设置密码
配置一下远程Redis
打开redis-cli连接本地服务,需要输入auth 密码才能继续后续操作,不然没有权限
然后输入shutdown关闭服务
redis-server redisconfigs/redis.config打开服务
重启Redis服务
jedis就可以使用所有指令方法
连接数据库
基础
操作命令
断开连接
编写测试代码
编码测试
Jedis jedis = new Jedis(\"localhost\
1. 获取连接
jedis.set(\"username\
2. 操作
jedis.close();
3. 关闭连接
使用步骤
项目使用
Jedis
JedisPoolConfig
JedisPool
核心接口
常规配置
连接池配置
jedis.set();
添加数据
jedis.get()
获取数据
jedis.append();
修改数据
覆盖原有数据
jedis.del();
删除数据
jedis.msetnx();
key-value 数组存储
jedis.mget()
数组提取
jedis.hmset();
jedis.hmget();
jedis.hlen()
jedis.hexists()
jedis.hkeys()
jedis.hvals()
map数据
核心方法
JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(50); config.setMaxIdle(10); .......
1.创建配置文件对象
1. 创建JedisPool连接池对象并加载配置文件
Jedis jedis = jedisPool.getResource();
2. 调用方法 getResource()方法获取Jedis连接
3. 操作
4. 关闭 归还到连接池中
连接池:JedisPool
private static JedisPool jedisPool; static{ //读取配置文件 InputStream is = JedisPoolUtils.class.getClassLoader().getResourceAsStream(\"jedis.properties\"); //创建Properties对象 Properties pro = new Properties(); //关联文件 try { pro.load(is); } catch (IOException e) { e.printStackTrace(); } //获取数据,设置到JedisPoolConfig中 JedisPoolConfig config = new JedisPoolConfig(); config.setMaxTotal(Integer.parseInt(pro.getProperty(\"maxTotal\"))); config.setMaxIdle(Integer.parseInt(pro.getProperty(\"maxIdle\
连接池工具类:JedisPoolUtils
单机开发
单机开发
集群开发
集群开发
开发,jedis,redis和java 客户端, redis cluster,jedis cluster api
redis-cli -c 自动重定向
客户端可能会挑选任意一个redis实例去发送命令,每个redis实例接收到命令,都会根据key计算对应的hash slot如果在本地就在本地处理,否则返回moved给客户端,客户端进行重定向cluster keyslot mykey,可以查看一个key对应的hash slot是什么用redis-cli的时候,可以加-c参数,支持自动的请求重定向,redis-cli接收到moved之后,会自动重定向到对应的节点执行命令。
请求重定向
就是根据key计算CRC16的值,然后跟苦16384取模,拿到对应的hash slot用hash tag可以手动指定key对应的slot,同一个hash tag下的key,都会在一个hash slot中,比如set mykey1:{100}和setmykey2:{100}
计算hash slot
节点间通过gossip协议进行数据交换,就知道每一个hash slot在哪个节点上。
hash slot 查找
基于重定向的客户端
基于重定向的客户端,很消耗IO,因为大部分情况下,可能都会出现一次重定向,才能找到正确的节点所以大部分客户端,比如jedis,都是smart的font color=\"#c41230\
在JedisCluster初始化的时候,就会随机选择一个node,初始化hashslot->node映射表,同时为每个节点创建一个jedisPool连接池每次基于JedisCluster执行操作,首先会在本地计算key的hashslot,然后在本地缓存的映射表找到对应的节点。如果node正好还持有hash slot就ok;如果说进行了reshard这样的操作,hashslot不在那个节点上了,就会返回moved然后JedisCluster发现对应节点返回moved,那么利用该节点的元数据,更新本地的hash slot ->node的映射表缓存重复上面步骤,知道找到对应节点,如果重试超过5次,那么就报错,JedisClusterMaxRedirectionException。
JedisCluster工作原理
如果hash slot正在迁移,那么会返回ask给jedis
jedis接到ask重定向之后,会重定向到目标节点去执行,但是因为ask发生在hash slot迁移过程中,所以不会更新本地缓存的映射表
hashslot迁移和ask重定向
smart jedis
面向集群的jedis内部实现原理
springboot2.x就把底层的redis操作从jedis改成了lettuce,因为jedis是线程不安全的
通过 redisTemplate.getConnectionFactory().getConnection(); 获得connection对象操作
lettuce
本质
实现分布式锁
命令更抽象,让程序员专注于业务逻辑
支持的数据类型更多
Redisson
springboot 2.x之后默认使用lettuce,若使用jedis配置,因为源码中许多类没有注入,类不存在,因此别使用jedis,有可能导致配置不成功
jedis 不安全,需要使用 pool 池化,像BIO
lettuce 安全,像NIO,现在比较常用
注意
redisTemlpate.opsForXXX() opsFor->operation for 操作
除了基本的操作,常用的方法也可以直接使用 redisTemplate 操作,比如事务
connection.flushDB()
connection.flushAll()
获取连接 redisTemplate.getConnectionFactory().getConnection()
使用
序列化配置,JDK序列化
开发中需要序列化,否则存入后取值会报错
实现Serialized接口
对应的实现的序列化
序列化方式
序列化
自己开发方便,一般直接使用String
定义后,可以使用@Qualifer(\"reidsTemplate\")
使用最好自己封装好工具包util
SpringBoot整合Redis(Jedis、Lettuce)
方向1:解决Session共享问题
NoSql
方向2:redis作为数据库使用
Redis与Spring的集成
Java整合Redis客户端
Redis是单线程还是多线程
基于Reactor模式开发了网络事件处理器
redis是基于reactor模式开发了网络事件处理器,也叫文件事件处理器。
这个文件事件处理器是单线程的,所以redis也被叫做单线程的模型。
是通过采取IO多路复用机制同时监听几个socket,根据socket的时间来选择对应的事件处理器。
socket有accept,read,write,close等事件
通过上面的形式实现了高性能的通信模型【NIO】
文件事件处理器是单线程的,所以redis才叫单线程的模型
文件事件处理器
1.客户端连接到server sorket请求建立连接
2.serversorket接收到请求,产生一个AE_READABLE事件
3.redis内部有一个IO多路复用程序,监听所有的socket产生的事件,将事件压倒队列里
连接应答处理器
命令请求处理器
命令回复处理器
三个事件处理器
4.文件事件分派器会从队列中拿到对应的socket和事件,然后根据socket产生的事件选择事件处理器比如文件分派处理器对AE_READABLE事件给到连接应答处理器处理这个事件
5.连接应答处理器会跟客户端建立对应连接,建立相应的socket01连接客户端,然后把socket01的AE_READABLE事件和命令请求处理器关联。
6.客户端发送set请求,socket01接收到会产生AE_READABLE事件,然后被IO多路复用程序监听到产生的事件,将事件压倒队列中
7.之前连接应答处理器已经将socket01上的AE_READABLE事件跟命令请求处理器关联了,所以这个时候文件事件分派器会直接找命令请求处理器处理socket01上面新的AE_READABLE事件。
8.命令请求处理器会把socket01里的读出来请求的相关数据,进行执行和处理。(从socket01中读取出来key和value,然后在自己内存中完成key和value的设置。)命令请求处理器将socket01的AE_WRITABLE事件跟命令回复处理器关联起来。
9.当客户端那边准备好读取响应数据时,就会在socket01上产生一个AE_WRITABLE事件,然后socket再被IO多路复用器放到队列。由于AE_WRITABLE已经与命令回复处理器关联起来了,队列中的AE_WRITABLE就会找命令回复处理器。命令回复处理器对socket01输出本次操作的一个结果:OK
10.客户端就会收到set请求的返回结果:OK
11.socket01的AE_WRITABLE事件跟命令回复处理器解除关联。
Redis线程模型
缓存更新除了缓存服务器自带的缓存失效策略之外(Redis默认的有6中策略可供选择),还可以根据具体的业务需求进行自定义的缓存淘汰
(1)定时去清理过期的缓存;(2)当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存。
常见的策略有两种:
缓存更新
设置合理的过期时间(永不过期)
缓存中某一热点数据失效,同一时间大量请求直接到了数据库
缓存穿透
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
记忆:关键字“透”,是指透过了缓存、数据库
缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空(相当于进行了两次无用的查询)。这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题。有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。通过这个直接设置的默认值存放到缓存,这样第二次到缓冲中获取就有值了,而不会继续访问数据库。
是指在某一个时间段,缓存集体过期失效或者redis宕机
缓存雪崩(缓存时间过期失效)
缓存雪崩(某一时刻大批量k/v失效)
指的是缓存大面积在同一时间失效,导致数据库短时间内无法处理大量请求,导致数据库崩溃
缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库
大面积的缓存失效,大量请求直接请求数据库,打崩了数据库
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。原有的过期时间加一个随机值,这样重复率就少了
假设每天高峰期每秒请求5000次,缓存可以抗每秒4000次,剩下1000个请求落到数据库上。
缓存redis挂了,5000个请求直接请求到数据库,此时数据库直接崩溃。系统全死了
集中过期倒不是最致命的,最终名的雪崩,是缓存服务器某个节点宕机或断网,自然形成的雪崩;因为是缓存服务器节点宕机,对数据库造成压力是不可以预知的,谁也不知道下一秒是不是就会导致数据库压垮
使用场景:双十一期间,为了保证高效,可能为关闭当天退款的功能,等第二天或者等其他时候可以退款
淘宝双十一抢购,假设商品集体缓存过期,导致缓存查不到,压力全部压到数据库上
出现场景
热点数据不过期
定时刷新
将过期时间离散化-随机过期时间
增加互斥锁更新
服务本地做二级缓存
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生
加锁排队
给每个缓存数据增加缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新缓存
批量往数据库存数据的时候,把每个key的失效时间都加上一个随机值,这个可以保证数据不会在同一时间大面积失效
热点数据永不过期,有更新时更新一下缓存
既然redis可能挂掉,那么就多增设几台redis,这样一台挂了,其他还可以工作,其实就搭建的集群
redis高可用
在缓存失效后,通过加锁或者队列来控制读数据数据库写缓存的线程数量,比如对某个key只允许一个线程查询数据和写数据,其他线程等待
先停掉一些服务,保证其他服务可用
限流降级
数据加热的含义:在正式部署之前,先把可能的数据选预先访问一边,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀
数据预热
解决方案、解决办法
缓存必须高可用,主从+哨兵/redis cluster,避免全盘崩溃
1.用户发送一个请求
2.收到请求,先查本地ehcache缓存,如果没有再查redis
3.如果ehcache和redis都没有,就查数据库
4.将数据库中的结果,写入ehcache和redis中
最好不要把缓存只放在redis里面,可以在系统内部用ehcache在维护一个缓存
事前
可以设置每秒2000个请求,一秒过来5000个请求,此时只会有2000个请求会通过限流组件,进入数据库剩下3000个走降级,返回一些友好提示。
好处:1.数据库绝对不会死,限流确保了每秒只会过去2000个请求。2.数据库不死,对用户来说2/5是可以被处理的3.只要有2/5请求可以被处理,就意味着你的系统没死,多点几次可能还能用,用户感觉只是有点故障。
本地ehcache缓存+hystrix限流&降级,避免mysql被打死
事中
redis一定要做持久化,尽快恢复缓存集群,一旦重启,自动从磁盘上加载数据,恢复内存中的数据
事后
解决
解决方案
描述:缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是, 缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
设置热点数据永远不过期。
什么是缓存雪崩
缓存雪崩解决方案
缓存雪崩
缓存雪崩是指缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。
和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。
缓存雪崩我们可以简单的理解为:由于原有缓存失效,新缓存未到期间所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。
1. 一般并发量不是特别多的时候,使用最多的解决方案是加锁排队。2. 给每一个缓存数据增加相应的缓存标记,记录缓存的是否失效,如果缓存标记失效,则更新数据缓存。3. 为key设置不同的缓存失效时间。
一般有三种处理办法:
缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
加随机值
如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
集群部署
由于缓存存放者大量的请求,当缓存服务异常或者脱机,流量直接压向后端服务器,造成级连故障
1、Redis集群
2、Redis-Sentinel
1、保证缓存的高可用
2、依赖隔离组件进行限流
3、提前演练(压力测试等)
解决方法
缓存雪崩优化
事前保证集群高可用
本地ehcache缓存+hystrix限流降级,通过加锁或队列空值写操作的线程数量
将不同数据的缓存时间设置的分散些
事后持久化机制,尽快恢复数据
同一时间大面积缓存失效
缓存设置默认值
短暂缓存空值
无效key业务上校验过滤
缓存穿透(访问的大批量key都不在缓存中)
过滤
缓存穿透(库都没数据)
指缓存和数据库中都没有数据,导致所有的请求都落到数据库上,崩溃
用户不断地发起缓存和数据库都不存在的数据,导致数据库压力过大,严重可击垮数据库
用户想要查一个数据,发现redis内存数据库没有(也就是缓存没有命中),于是向持久层数据库查询(类似mysql)。发现持久层数据也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中(举例场景:秒杀活动),于是都去请求持久层数据库,最终持久层数据库带来压力,就相当于缓存穿透
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。解决方案:布隆过滤器或者对空进行缓存,过期时间设置5分钟
把请求存起来,存空
恶意攻击
例子
请求redis不存在的缓存数据,导致请求全部打到mysql数据库,形成缓存穿透
现象
秒杀活动
接口层增加校验,如用户鉴权校验,例如id<0直接拦截
从缓存获取不到数据,数据库也找不到,可以设置key -null的缓存,并设置一个比较短的过期时间
增加参数校验
布隆过滤器(Bloom Filter)
布隆过滤器是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免对底层存储系统的查询压力
当存储层不命中,及时返回空对象也将其缓存起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
1、如果控制能够被缓存,这就意味着缓存需要更多的空间存储来存储更多的键
2、及时对空值设置了过期时间,还是会存在缓存层和存储层的数据,会出现一段时间窗口不一致,这对保持一致性的业务有影响
可能存在两个问题
缓存空对象
解决方案、解决办法
一秒5000个请求,结果其中4000个请求是黑客发出的恶意攻击
5000个请求只有1000个请求在redis中能查到,4000个请求都得去数据库里查,但是查不到,下次又去数据库里查
情景:
每次系统A从数据库只要没查到,就写一个空值到缓存里 set -999 UNKNOWN
描述:缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。
解决:接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
记忆:关键字“透”,是指透过了缓存、数据库
详见上文中的\"缓存穿透\"布隆过滤器问题:1.只能增加,不能减少;比如:redis中原来存在某一个key,但是后来去除了,布隆过滤器中不好减少
什么是缓存穿透
哈希碰撞
布隆过滤器原理
Guava实现
布隆过滤器在项目中使用
大量请求访问缓存,缓存找不到,
缓存穿透的现象
1、代码问题
2、恶意访问或者爬虫等
原因
1、业务的响应时间
2、业务本身问题
1、总调用数
2、缓冲层命中数
3、存储层命中数
3、相关指标
如何发现
1、产生更多的键
2、缓存层和存储层数据短期不一致
可能的问题
1、缓存空对象(同时设置过期时间)
2、步隆过滤器拦截
缓存的穿透优化
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
热点数据不失效
设置热点数据永不过期
互斥锁
加互斥锁
缓存击穿
描述:缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
设置热点数据永不过期
缓存击穿(读缓存又读库)
缓存击穿指并发查同一条数据
缓存击穿 (单个热点数据过期 大量请求到db)
指缓存中没有,数据库中有,并发用户大,同时读取缓存没读取到数据,又同时读取数据库,引起数据库压力大
一个key非常热点,高并发集中对这个点进行访问,当这个key失效的一瞬间,大量的请求直接请求数据库,击垮数据库,就像在完好无损的桶上凿开了一个洞
是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞。当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般就是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大!
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。1)采取限流组件,然后异步的吧一些即将过期的热点数据再重新构建
微博热搜
微博热搜宕机
某个key非常热点,承担了超高并发,当这个key失效,持续的超高并发请求就会达到mysql数据库上
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
存在问题:需要更多空间存储
分布式锁:使用分布式锁,保证对于每个key同时只有一个线程去查询后端服务器,其他线程没有获得分布式锁的权限,因此需要等待即可。这种方式将高并发的压力转移到分布式锁,因此对分布式 锁的考验很大
增加互斥锁
业务逻辑检查更新
一个存在的key,在缓存过期之后,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。注意点:+ 1.一个key+ 2.大量并发+ 3.击穿到DB
大量请求在缓存中失效,全部打进mysql数据库
多设置几台redis服务器
限流降级,保证服务可用
设置缓存时间随机,失效均匀
击穿 -> 量大!key存在,但缓存过期!(热点)
穿透 -> key不存在
缓存击穿和缓存穿透区别
仅仅只针对某一个key(可以是热点key重构)
访问数据库结果为不存在时,将返回空值,保存到缓存中,设置过期时间。缺点:空对象浪费空间;缓存层和存储层存在一致性问题
预先将所有可能存在的数据存到BitMap,用布隆过滤器进行校验,一定不存在的会被拦截
加过滤器
访问数据库中不存在的数据,会不断访问数据库
缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!
缓存预热
系统上线后,将相关的缓存数据直接加载到缓存系统中
直接写个缓存刷新页面,上线时手工操作下
数据量不大,可以在项目启动的时候自动加载
定时刷新缓存
缓存穿透/Bloom布隆过滤器
缓存异常
描述:由于redis master节点和redis salve节点和sentinel处于不同的网络分区,使得sentinel没有能够心跳感知到master,所以通过选举的方式提升了一个salve为master,这样就存在了两个master,就像大脑分裂了一样,这样会导致客户端还在old master那里写入数据,新节点无法同步数据,当网络恢复后,sentinel会将old master降为salve,这时再从新master同步数据,这会导致大量数据丢失。
解决:min-replicas-to-write 3 min-replicas-max-lag 10 第一个参数表示最少的salve节点为3个,第二个参数表示数据复制和同步的延迟不能超过10秒 配置了这两个参数:如果发生脑裂:原master会在客户端写入操作的时候拒绝请求。这样可以避免大量数据丢失。
异步复制,脑裂数据丢失
数据写入master,master还没异步发送给slave就挂掉了,sentinel选举新的master后导致这些还没来得及异步发送的数据就丢失了
解决:两个参数 min-slaves-to-write 1min-slaves-max-lag 10至少有一个slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候master就不会再接收任何请求client去做一些其他处理,比如1.在client做降级,写到本地磁盘里面,在client对外接收请求,再做降级,做限流,减慢请求涌入速度。2.client可能会采取将这个数据临时灌入一个kafka消息队列,每隔10分钟去队列里面取一次,尝试重新发挥master。
异步复制数据丢失
master节点与其他节点丢失连接,其他节点选举新的master。但是原来的master可能和客户端正常连接,客户端在脑裂时还在往原来的master里写数据。手动解决脑裂问题,把原来的master关闭设置为slave节点,导致脑裂时客户端往原来master里发的一部分数据丢失。
解决:通上两个参数控制1个slave已经10s没有跟master做数据同步,就不接受client消息。master连接不到其他slave,控制不让client再往它里写数据。
脑裂导致数据丢失
脑裂问题
延时双删
双写一致性
缓存使用场景
一致性问题定义
方案选择
先删缓存,在更新数据库,可能会脏读
先更新,成功之后删除缓存
更新缓存,然后通过异步调度来批量更新数据库,性能好但是无法保证强一致性
数据一致性怎么保证
一致性hash出现的由来:由于数据的不断增加,单台redis内存已经不能满足实际需求,我们需要将数据分区。+ 简单的hash取模分区:通过散列函数将key变成固定长度数字,对其取模,取模结果对应第几台redis实例,但是这种方式不利于redis的扩展及删除,有局限性。+ 一致性hash算法:
一致性hash
对于实时性不是特别高的场合,比如说T+1的报表,可以采用定时任务查询数据库数据,同步到redis中。在一般情况下,我们推荐直接将删除redis的方案。1.先更新数据库,再删除redis缓存异常情况:更新数据库成功,删除redis失败,造成数据不一致!解决方案:1.1提供重试机制:删除redis失败,捕获异常,将其发送到消息队列中,创建消费者再次尝试删除key,但是这样会对业务代码进行入侵1.2异步更新缓存更新数据时,会往binlog中写日志,可以通过一个服务监听binlog的变化,然后在redis中实现删除key操作,或者说直接发送到MQ中进行消费删除key操作。总之,对于后删除失败的情况,我们的做法就是不断删除,知道成功!!2.先删除缓存,在更新数据库表面上没有问题,实际还可能出现问题:高并发情况下:线程A先删除redis,这时线程B过来发现redis没有,去数据库中查询旧值并更新到redis中,线程A再去更新数据库,这样还是会发生数据不一致情况!!解决方案:2.1延迟双删:1>删除redis缓存2>更新数据库3>休眠500ms(根据实际情况而定)4>再次删除缓存
Redis和DB数据一致性
缓存预热,提前将可能会访问的数据加载到缓存中
缓存降级,保留核心业务的功能
缓存刚开始为空,大量请求涌入数据库
预留缓存,写操作时会先删除缓存中的数据,再写入数据库,缓存再从数据库中拿数据
读写串行化
缓存和数据库双写出现一致性问题解决方法
一致性问题:A删除了缓存还没写入数据库,B读缓存没有该数据,就从数据库中读取了,之后A才完成写入数据库,B读到的就是脏数据
根本原因:逻辑处理消耗1s
单库
一致性问题:A写入主库,从库还没来得及更新,B从从库中取出了脏数据之后从库才更新新的数据
根本原因:主从同步延时1s
并发读写
先进先出队列、固定长度、内存
slowlog-max-len
建议1000
slowlog-log-slower-than
slowlog get [n]
slowlog len
slowlog reset
1、客户端发送命令到服务端
2、排灯等待执行
属于慢查询
客户端超时不一定是慢查询,慢查询一定是客户端超时的一个原因
3、执行命令
3、返回结果到客户端
生命周期
1、慢查询是一个先进先出队列
2、是固定长度的
3、保存在内存中的
config set slowlog-max-len 1000
动态配置
1、慢查询阙值(微秒)
2、等于0,记录所有命令
3、<0 不记录所有命令
config set slowlog-log-slower-than 10000
两个配置
获取慢查询队列
1、slowlog get[n].
获取慢查询队列长度
2、slowlog len
清空慢查询队列
3、slowlog reset
三个命令
1、slowlog-max-len不要设置过大,默认10ms,通常设置1ms
2、slowlog-log-slower-than不要设置过小,通常设置为1000
3、理解命令的生命周期
4、定期持久化慢查询
运维经验
慢查询
1、命令本身优化
2、减少网络通信次数
长链接
连接池
NIO(非阻塞IO技术)
3、降低接入成本
优化方法
无底洞优化
前台请求,先从缓存中查询数据,缓存中没有则从数据库查询,查询数据后更新缓存,然后返回,下次查询直接从缓存中取值;
如果缓存和数据库中都没有直接返回空值。
缓存使用过程
当访问量剧增、服务出现问题(如响应时间慢或不响应)或非核心服务影响到核心流程的性能时,仍然需要保证服务还是可用的,即使是有损服务。系统可以根据一些关键数据进行自动降级,也可以配置开关实现人工降级。降级的最终目的是保证核心服务可用,即使是有损的。而且有些服务是无法降级的(如加入购物车、结算)。
缓存降级
缓存时间不失效
多级缓存
二级缓存
备份热key 走不同的机器
热点key
bigkey命令 找到干掉
Redis 4.0引入了memory usage命令和lazyfree机制
大Key
并发竞争
跳跃表
漏桶
使用Redis需要解决的问题
spiped: SSL代理
server: requirepass / masterauth
client: auth命令 、 -a参数
不支持config set动态配置
rename-command flushall \"\"
bind 内网IP
保护
RTT:往返延时(Round-Trip Time)+ 管道的作用就是减少通信的次数,将很多指令一次发送过去执行,不必每次一条指令建立一次通信。+ 返回:管道会回复一个队列答复,将所有的执行的结果以一个队列返回
管道
可读性、可管理型
通过 `object encoding k` 验证
string长度会影响encoding
简洁性
排除特殊字符
key设计
string 10K
最佳实践
网络阻塞
redis阻塞
集群节点数据不均衡
频繁序列化
bigkey的危害
read time out
could not get a resource from the pool
JedisConnectionException
应用异常
redis-cli --bigkeys
scan + debug object k
主动报警:网络流量监控,客户端监控
内核热点key问题优化
bigkey的发现
阻塞(注意隐性删除,如过期、rename)
big hash渐进删除:hscan + hdel
bigkey删除
拒绝bigkey
多个string vs. 一个hash
节省内存、但编程复杂
分段hash
hyperLogLog
计算网站独立用户数
选择合适的数据结构
object idle time: 查找垃圾kv
过期时间不宜集中,避免缓存穿透和雪崩
过期设计
value设计
kv设计
O(N)命令要关注N数量
手段:rename机制
禁用危险命令
客户端支持差
实际还是单线程
不推荐select多数据库
一次事务key必须在同一个slot
不支持回滚
不推荐事务功能
monitor命令不要长时间使用
命令使用技巧
业务希望的 redis 并发量
客户端执行命令时间
redis 资源:应用个数 * maxTotal redis最大连接数
如何预估
maxTotal
maxIdle
minIdle
连接数
blockWhenExhausted
maxWaitMillis
等待
testOnBorrow
testOnReturn
有效性检测
jmxEnabled
监控
testWhileIdle
timeBetweenEvictionRunsMillis
numTestsPerEvictionRun
空闲资源监测
开发规范
主从+哨兵+cluster
ecache+Hystrix+降级+熔断+隔离
备用方案
集合[Collections
缓存[caching]
原生类型支持[primitives support]
并发库[concurrency lib]
通用注解[common annotations]
字符串处理[String processing]
IO
Java项目广泛依赖,例如以下内容等
不可变集合:用不变的集合进行防御性编程和性能提升
新集合类型: multisets multimaps tables 等
强大的集合工具类:提供java.util.Collections中没有的工具类
扩展工具类:让视线和扩展集合类变得更容易,比如创建Collection的装饰器,或视线迭代器
Guava是对JDK集合的扩展
ImmutableList
只读设置
Collections2
函数式编程:过滤器
Collections2.transform
函数式编程: 转换
组合式函数编程
加入约束: 非空、长度验证
集合操作: 交集、差集、并集
Multiset: 无序可重复
Multimap key : key 可以重复
BiMap: 双向Map 健值不能重复
双健的Map -->Table-->rowKey+columnKey + value
Guava对集合的支持
封装了一个过期时间
定时删除
定期删除
guava本地缓存就是采用惰性删除的方式
惰性删除
过期策略
关键源码图
http://ifeve.com/google-guava
git@github.com:wstever/springboot-action.git
教程和源码
Guava Cache介绍
Guava
spring5已经放弃guava,拥抱caffeine。
guava和caffeine性能测试spring5已经放弃guava,拥抱caffeine。
那么为什么这么好的东西需要被淘汰呢,如果对于本地Cache有过深入研究的人应该知道LRU算法基本可以满足大部分的场景,但是很多人为了精益求精,基于LRU的算法,又在此基础上提出了一系列更好的,更有效果的淘汰策略。比如有ARC,LIRS和W-TinyLFU等都提供了接近最理想的命中率,他们这些算法进一步提高了本地缓存的效率。Cache的目的就是缓存,如果在功能一样的情况下,最重要的突破就是性能了。从功能上来看GuavaCache已经比较完善了,基本满足了绝大部分本地缓存的需求。那么Spring5使用Caffeine来代替GuavaCache就是因为性能的问题了。首先我们来看看官方给出来的性能测试对比的报告。
Caffeine
日常工作中,时常要面对抽奖活动,奖励发放,商品秒杀等大流量高并发的场景。高并发场景面对的第一个问题是DB的IO瓶颈。 这时比较通用的方式是加缓存对DB进行加速与保护,用redis对内存的性能来解决IO的瓶颈。但是引入Redis就一劳永逸了嘛?不是的,相对应的高并发场景又会引发Redis热KEY和大KEY的问题,造成带宽拉胯。
这时需要再做一层本地缓存。那么问题又来了,本地缓存没有过期时间等限制,现在可以用GUAVA CACHE来解决
背景
https://blog.csdn.net/qq_29817481/article/details/94021421
缓存工具类
SpringBoot为我们提供了自动配置多个CacheManager的实现
读写速度快,减轻数据库压力,提高性能
为什么能做缓存
放在请求访问和数据库之间,先操作缓存中的数据,若缓存中不存在,则从数据库读取返回并更新至缓存中
怎么做缓存
前台请求,先从缓存中查询数据,缓存中没有则从数据库查询,
查询数据后更新缓存,然后返回,下次查询直接从缓存中取值;
通过缓存加速读写速度
CPU的L1、L2、L3CaChe
CaChe加速硬盘读写
EhCaChe缓存数据库结果
浏览器缓存
利用redis优化IO响应时间
计数器先累计,然后批量写入DB
大量写入合并为批量写
1、加速读写
后端服务器通过前端缓存降低负载:业务端使用redis降低后端mysql负载
对高消耗的SQL:join结果集/分组统计结果
2、降低后端负载均衡
收益
缓存层和数据层有时间窗口不一致和更新策略有关
1、数据不一致
多了一层缓存逻辑
2、代码维护成本
比如集群等
3、运维成本
成本
缓存的收益和成本
maxmomory-policy
1、LRU/LFU/FIFO算法剔除
expire
2、超时提出
开发控制生命周期
3、主动更新
1、LRU/LFU/FIFO算法剔除一致性最差,同时维护成本也是最低的
2、超时剔除一致性稍微比第一种策略好一点,维护成本也低
3、主动更新一致性高,维护成本也高
三种策略的比较
最大内存和淘汰策略
1、低一致性
超时剔除和自动更新结合,最大内存和淘汰策略兜底
2、高一致性
建议
缓存的更新策略
select* from user where id=id
1、从MySQl获取用户信息
set user:{id} ‘select* from user where id=id’
2、设置用户信息缓存
缓存全部
缓存部分
3、缓存粒度
全部属性最好
1、通用性
部分属性最好
2、占用空间
3、代码维护
三个角度
缓存的粒度控制
http://10.219.14.24:6789/api-docs/redis/#/
公共组件
三级缓存
Redis是什么?Redis的基本了解?
Set
Redis有哪些数据类型
Redis的应用场景有哪些?
Redis的数据类型的各自应用场景?
分布式寻址都有哪些算法?
哈希槽 Redis集群时,怎么样set一个key
Redis Cluster集群模式,redis的key是如何寻址的?
了解一致性hash算法么?
Redis的缓存雪崩?
在中午高峰期,有100万用户同时访问系统A,每秒4000个请求去查询数据库如果数据库每秒4000个请求,可能会宕机
每秒4000个,3000个走缓存,1000个请求走数据库。
缓存是走内存的,内存天然就可以支撑别说是4000/s,4万/s也没问题但是数据库一般建议不要超过2000/s
数据库支撑不了高并发,为什么缓存可以支撑高并发呢?
缓存如何是实现高并发的?
缓存是如何实现高性能的?
只要用缓存,就会涉及缓存与数据库双写,只要是双写,就一定会有数据一致性的问题。
读的时候,先读缓存,缓存没有的话,按噩梦就读数据库,然后取出数据放入到缓存
更新的时候,先删除缓存,然后再更新数据库。
比如商品详情页的统计,可能只是修改了某个表的字段,但是统计可能还需要其他表传一些数据,然后再进行复杂的运算,导致更新缓存的代价很高。或者一个表被频繁的修改,但是读数据没那么频繁。1分钟内修改了100次,那么缓存就要更新100次;但是缓存在一分钟内只被读取了一次,就有了大量的冷数据。其实就是一个lazy计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让他需要被使用的时候再重新计算。
为什么是删除缓存,而不是更新缓存?
cache aside pattern 最经典的缓存+数据库读写模式
情况:先去数据库更新数据,然后删除缓存,但是删除缓存报错了没删掉,导致请求直接去缓存里拿到旧的数据,与新数据不匹配
解决:先删除缓存,再写入数据库,这样就算数据库更新失败报错,没有缓存也是去数据库拿旧数据,不会出现数据不一致情况
最初级缓存不一致情况:
情况:1.在更新一个库存的时候,同时在读取这个库存的缓存,并发的发生了。2.开始执行操作,先删除缓存中的数据,但是还没来的及将库存在数据库中修改。3.读请求去缓存里读,读不到,然后去数据库里读到旧数据,然后写到redis里。4.这个时候才完成更新数据库系统的操作,导致数据库里的数据是新的,缓存里的数据是旧的。--只有在高并发场景下,才会出现这种问题。
1.先创建几个内存队列,根据商品的id,进行hash取值,然后根据队列数量取模,放到相应的队列里2.每个队列通过异步一个线程进行消费,顺序消费队列里的请求。这样的话,就能保证先完成数据库和缓存的修改,才能够进行读数据
如果同一个数据有很多读请求过来,全都进行数据库的更新是没有意义的,
可以做过滤,如果发现队列中已经有一个更新缓存的请求了,就不用再放个更新请求操作进去了,直接等待前面的更细 操作请求完成即可
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回;如果请求等待时间超过一定时长,那么这一次直接从数据库中读取当前的旧值。
优化点
如果数据频繁更新,导致大量更新的操作在同一个队列里面,然后读请求等待很多的写请求完成后再执行,会发生大量超时,最后导致大量的请求直接走数据库。
一定要根据实际业务情况,去进行一些压力测试,和模拟线上环境,去看看这种情况下读多久能响应
如果不够的话就加机器
(1)读请求长时阻塞
还是要做好压力测试,一个大的风险就是突然间大量的读请求会在几十毫秒的延迟hang在服务上,看服务器能不能扛得住。需要多少机器才能扛住极限情况的峰值
(2)读请求并发量高
通过某个请求的参数hash进行路由到同一个机器上
(3)多台机器,要确保对同一个商品的读写请求,全部路由到同一台机器上,否则不在同一台机器上队列也就没用了
万一某个商品的请求读写特别高,全部打到相同的机器的相同队列里去了,可能造成某台机器压力过大
(4)热点商品的路由问题,导致请求的倾斜
高并发场景下注意一些问题
数据库与缓存的更新与读取进行异步串行化
如果没有要求最好不要做这个串行化,因为一旦进行串行化,会导致系统吞吐量大幅度降低可能就得用比正常情况下多几倍的机器去支撑
比较复杂的数据不一致情况
如何保证缓存与数据库双写一致性问题
缓存与数据库双写不一致
缓存并发竞争
用了缓存有啥后果?
Redis面试常见问题
Redis+Caffeine介绍
0 条评论
回复 删除
下一页