- 1、Redis前传
- 2、Redis基础
- 3、Redis入门
- 向list里添加一个值 Lpush(队列添加,队列查看)、Rpush(栈添加,栈查看)
- 从list里移除值 Lpop(向左移除),Rpop(向右移除)
- 按照索引序号取值 Lindex(自左开始取值)
- 1取列表长度
- 移除list里指定的值
- 截取显示
- 移除列表的最后一个元素,并添加一个新的元素
- 更新修改list中指定下表的值,若不存在则报错
- 插入元素
- 存值
- 取值
- 同时设置多个值
- 同时获取多个值
- 全部获取,以键值对的方式打印所有结果
- 删除指定的元素
- 只获取Key
- 只获取Value
- 指定元素自增长
- 判断hash内是否存在指定的元素
- 添加地理位置
- 查看库内两地距离
- 查看半径范围内的元素
- 键值对查询半径范围内的元素
- 准确的经纬度查询
- 限定范围限定数量查询
- 以特定的城市位置为中心点查询周围的城市
- 4、Redis事务操作
- 放弃事务,在还没有执行事务前取消队列里的值,一键取消,再无事务
- 线程一
- 线程二
- 返回线程一
- 重启事务,关闭事务再重新开启获取最新的数据
- 5、Redis核心操作
- 日志的级别
- debug (a lot of information, useful for development/testing)
- verbose (many rarely useful info, but not a mess like the debug level)
- notice (moderately verbose, what you want in production probably) #默认生成环境
- warning (only very important / critical messages are logged)
- 6、Redis订阅发布
- 1、sub发布
- 2、pub订阅
- 3、发布列表刷新
- 8、哨兵模式
- Example sentinel.conf
- 哨兵sentinel实例运行的端口 默认26379
- 哨兵sentinel的工作目录
- 哨兵sentinel监控的redis主节点的 ip port
- master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符”.-_”组成。
- quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
- sentinel monitor
- 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
- 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
- sentinel auth-pass
- 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
- sentinel down-after-milliseconds
- 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
- sentinel parallel-syncs
- 故障转移的超时时间 failover-timeout 可以用在以下这些方面:
- 1. 同一个sentinel对同一个master两次failover之间的间隔时间。
- 2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
- 3.当想要取消一个正在进行的failover所需要的时间。
- 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
- 默认三分钟
- sentinel failover-timeout
- SCRIPTS EXECUTION
- 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
- 对于脚本的运行结果有以下规则:
- 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
- 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
- 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
- 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
- 通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
- 这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
- 一个是事件的类型,
- 一个是事件的描述。
- 如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
- 通知脚本
- sentinel notification-script
- 客户端重新配置主节点参数脚本
- 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
- 以下参数将会在调用脚本时传给脚本:
- 目前
总是“failover”, 是“leader”或者“observer”中的一个。 - 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
- 这个脚本应该是通用的,能被多次调用,不是针对性的。
- sentinel client-reconfig-script
Redis
1、Redis前传
1.1、SQL分类
- SQL(Structured Query Language):结构化查询语言
- 是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,
- 用于存取数据以及查询、更新和管理
- 以及KV形式储存的非关系型数据库。
- 四大NoSQL分类
- K-V键值对:Redis +其他(Tair、memecache)
- 文档型数据库:MongoDB,基于分布式储存的数据库,用于处理大朗文档(介于关系型数据库与非关系行数据库之间,是最像关系型数据库的非关系型数据库)
- 列存储数据库:HBase,分布式文件系统
- 图关系型数据库:Neo4j、infoGrid,主要存储关系型拓扑图,存储关联关系的但类型或多类型数据库,不存储图形
1.2、NoSQL介绍
- 计算机N+1时代,数据成为主流,关系型数据库已经承受不住高量级数据的并发查询及调度。
- 解决SQL服务器困难的演进
- Memcached(缓存)+MySQL+垂直拆分
最早:MyISAM:表锁,一行和列的形式关联处理,难以做到高并发的效果,容易出现死锁问题
- 分库分表 + 水平拆分 + MySQL集群
本质:数据库的读和写
演进一:InnoDB:行锁,及分库分表拆分业务数据,分别使用单独库进行读和写,但是效率并不高
- 分库分表+水平拆分+垂直拆分+MySQL高集群
多种不同数据的读和写
企业级MySQL集群:拆分各类不同的数据(图片,文件,文本,交易……)分别储存,分别读写,体量高级群,但是维护不便
![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210322151450045.png#crop=0&crop=0&crop=1&crop=1&id=CpBl2&originHeight=480&originWidth=1040&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 定制型高集群SQL同步/异步处理
使用关系型及非关系型数据库对应处理不同类型的数据。
用户名/密码 ——> MySQL
图片 ——> mangoDB
评论、博客 ——> Redis
.......
NoSQL
- NoSQL = Not Only SQL(不仅仅是SQL)
- NoSQL的特点
- 数据之间没有关联关系(数据以Key-Value的形式储存),高扩展
- 读写速度快,数据量极大(写8w/s,读11w/s)是一种细粒度的缓存,高性能
- 项目集成不需要事先设计数据库,随取随用,高可用
- 无固定的查询语言,查询宽松,只要求结果一直性
3V+3高
- 大数据时代的3V
- 海量Volume
- 多样Variety
- 实时Velocity
- 大数据时代的3高
- 高并发
- 高可用
- 高性能
1.3、Redis概述
Redis概述
- Redis(Remote Dicitionay Server):远程字典服务
- Redis 是一个开源的使用 ANSI C 语言编写、遵守 BSD 协议、支持网络、可基于内存、分布式、可选持久性的键值对(Key-Value)存储数据库,并提供多种语言的 API。
Redis用途
- 是目前市场最大,免费开源且最热门的NoSQL技术之一
- 被称为结构化数据库
- 内存持久化能力强,(内存断电即失,所以持久化很重要)
- 数据持久化化的表现:
- RDB:Redis数据库备份文件(Redis Database Backup)持久化方式, 提供周期性基于时间点的数据集快照备份, 比如每小时生成一个快照备份.
- AOF模式, 仅追加到文件(AppendOnlyFile)持久化方式, 在每次数据库服务收到写操作时记录日志文件, 当服务重启时, 自动回放该日志来重建原始数据集。日志中使用Redis自己的协议, 并按照统一的格式, 采用只追加的方法记录。当日志文件太大时, Redis可以在后台重写该日志, 生成一个最小化版本的日志文件。
- 效率高,可高速缓存
- 发布订阅量及地图信息分析
Redis特性
- 多样性数据库
- 持久性缓存
- 高性能集群
- 多事务处理
2、Redis基础
2.1.1、Windows安装
1. 下载安装包[Redis](https://github.com/MicrosoftArchive/redis/releases)
![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210322174928026.png#crop=0&crop=0&crop=1&crop=1&id=TClfi&originHeight=729&originWidth=1311&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 配置环境变量
- 进去redis启动服务
D:\redis>redis-server.exe redis.windows.conf
- 启动redis
重新开一个新cmd窗口
D:\redis>redis-cli
连接成功
将Redis 加入 Windows 服务,执行
redis-server --service-install redis.windows.conf
Redis successfully installed as a service. 服务启动成功
设置密码
-a 123456 //密码
Redis 详细参数配置说明:
- ./redis-server /path/to/redis.conf 按照指定的配置文件启动
- include /path/to/other.conf 包含其它的redis配置文件
- daemonize yes 启用后台守护进程运行模式
- pidfile /var/run/redis.pid redis启动后的进程ID保存文件
- port 6379 指定使用的端口号
- bind IP 监听指定的网络接口
- unixsocket /tmp/redis.sock 指定监听的socket,适用于unix环境
- timeout N 客户端空闲N秒后断开连接,参数0表示不启用
- loglevel notice 指定服务器信息显示的等级,4个参数分别为debug\verbose\notice\warning
- logfile “” 指定日志文件,默认是使用系统的标准输出
- syslog-enabled no 是否启用将记录记载到系统日志功能,默认为不启用
- syslog-ident redis 若启用日志记录,则需要设置日志记录的身份
- syslog-facility local0 若启用日志记录,则需要设置日志facility,可取值范围为local0~local7,表示不同的日志级别
- databases 16 设置数据库的数量,默认启动时使用DB0,使用“select ”可以更换数据库
- tcp-backlog 511 此参数确定TCP连接中已完成队列(3次握手之后)的长度,应小于Linux系统
- 的/proc/sys/net/core/somaxconn的值,此选项默认值为511,而Linux的somaxconn默认值为128,当并发量比较大且客户端反应缓慢的时候,可以同时提高这两个参数。
- tcp-keepalive 0 指定ACKs的时间周期,单位为秒,值非0的情况表示将周期性的检测客户端是否可用,默认值为60秒。
2.1.2、Linux安装
- 下载官方安装镜像文件Redis
- 上传文件至Linux系统
在linux系统中,应用建议安装到opt目录下,也可以在opt下一级安装
- 安装完成
- 进入Redis程序控制
- yum install gcc-c++,安装编译环境
- 查看gcc版本
make,配置多有文件
再一次make
- make install查看安装情况
- cd /usr查看路径
- 创建Redisconfig配置文件,之后所有redis操作就使用这个文件了
vim redis.conf设置默认后台启动
启动INSERT模式修改配置
12.启动Redis服务
![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210328144930629.png#crop=0&crop=0&crop=1&crop=1&id=NEPnd&originHeight=541&originWidth=1624&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
进入Redis所在目录,打开redis-cli
- 查看进程
- 退出redis服务
- shutdown / quit
- shutdown / quit
- 命令总结
yum install gcc-c++ #安装编译环境
gcc -v #查看gcc版本
make #安装环境
make install #安装查看环境
vim redis.donf #设置默认后台启动
cd /usr local bin #进入bin目录
redis-server redisconfig/redis.conf #启动redis服务
redis-cli -p 6379 #启动redis控制面板
ps -ef|grep redis #查看进程
shutdown/quit #关闭/退出
ctrl+c #退出显示层
配置模式中:i->insert #进入手写配置模式
esc-> :wq #退出并保存
3、Redis入门
3.1、redis-benchmark
redis-benchmark——性能压力测试工具
使用方法:
//测试并发数
redis-benchmark -h 127.0.0.1 -p 6086 -c 50 -n 10000 -t get
redis-benchmark -h 127.0.0.1 -p 6086 -c 50 -n 10000 -t set
//带参数完整命令
redis-benchmark -h 127.0.0.1 -p 6379 -c 50 -n 10000 -q
//使用 pipelining
默认情况下,每个客户端都是在一个请求完成之后才发送下一个请求
(benchmark 会模拟 50 个客户端除非使用 -c 指定特别的数量), 这意味着服务器几乎是按顺序读取每个客户端的命令。Also RTT is payed as well.
$ redis-benchmark -n 1000000 -t set,get -P 16 -q
SET: 403063.28 requests per second
GET: 508388.41 requests per second
3.2、Redis基础知识
1. Redis单线程
- 线程性能依赖的是CPU,不是内存
- Redis性能依赖的是内存,CPU不是Redis性能瓶颈
- Redis的瓶颈是根据机器的内存和网络宽带,单线程可以避免上下文切换,避免消耗资源
- 关于线程的G点
- 高性能服务器不意味着多线程
- 多线程一定比单线程效率高 (在同等情况下,多线程确实比较高效,但是多线程程序存在多上下文的切换,消耗资源,这对于内存系统来说,效率就降低了)
- 对于内存系统来说,多次读写都是一在一个CPU上,不产生资源消耗才是最佳解决方案
- 程序处理走向
- CPU > 内存 > 硬盘
- Redis可支持的数据类型
- Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
- 它支持多种类型的数据结构
- 字符串(strings)
- 散列(Hashes)
- 集合(sets)
- 有序集合(sorted sets)
- bitmaps
- hyperloglogs
- LUA脚本(Lua scripting)
- LRU驱动事件(LRU eviction)
- 事务(transactions)
- 磁盘持久化(persistence)
- Redis哨兵(Sentinel)
- 分区(Cluster)
3.3、Redis数据库命令
Redis-Key/String-value
#开启服务器
redis -server root/redis.conf
#开启客户端
redis -cli -p 6379
#验证是否成功
ping #如果命令返回PONG说明连接成功
#清屏
clear
#清除当前数据库
flushdb
#清除所有数据库
flushALL
########################################################################################################
#设置值
set key value [EX seconds] [PX milliseconds] [NX|XX]
=>set k1 v1
#取值
get k1
#多值设置
mset key value [key value ...]
=>mset k1 v1 k2 v2 k3 v3
#多值取值
mget k1 k2 k3
#存在时间
expire key seconds
=> expire k1 10
#查看时间
ttl key
=>ttl k1
#查询字符类型
type key
=>type k1
#查询字符串范围长度
getrange key start end
=>getrange name 0 5
#查询字符全字长
=>getrange name 0 -1
#######################################################################################################
#设置过期时间 setx (set with expire)
setex key seconds value
=>setex key 10 "hello"
ttl key #查看
#不存在设置(常用关于分布式锁) setnx(set if not exist)
set key value [EX seconds] [PX milliseconds] [NX|XX] #如果不存在就创建字段
=>setnx key2 age
(integer) 1 #存在就显示1
=> setnx key age
(integer) 0 #存在就显示0
#原子可操作性,要么一起成功,要么一起失败
msetnx key value [key value ...]
=>msetnx k1 v1 k4 v4
(integer) 0 #失败显示0
msetnx k4 v4
(integer) 1 #成功显示1
#设置对象(1)
set user:1 {name:zhangsan,age:3}
#取对象
get user:1
=>"{name:zhangsan,age:3}"
#设置对象(2)
mset user:1:name lisi user:1:age 18
#去对象
mget user:1:name user:1:age
=>1) "lisi"
2) "18"
#修改值 如果存在,获取原来的值并设置新的值 如果不存在直接返回nil
getset key value
127.0.0.1:6379> getset key zhangsan
(nil) #不存在
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> getset name lisi
=>"zhangsan" #存在
127.0.0.1:6379> get name
"lisi" #修改
3.4、String字符串类型
String-value
#判断某个key是否存在
exists key [key ...]
=>127.0.0.1:6379> exists name
(integer) 1
#追加字段,如果当前key不存在就相当于set key
append key value
=>127.0.0.1:6379> append key "hello"
(integer) 13 #存在,显示字符串长度
127.0.0.1:6379> get key
"zhangsanhello" #追加拼接
#查看字符串长度
strlen key
=>(integer) 13
#自增长 步长i++
127.0.0.1:6379> set views 0 #初始化浏览量为0
=>set article:21:views 0 #设置地21篇作品初始阅读量为0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #增长+1 浏览量变为+1
(integer) 1
127.0.0.1:6379> get views
"1"
127.0.0.1:6379> decr views #自减 浏览量变为-1
(integer) 0
127.0.0.1:6379> get views
"0"
#设置自动增长的量
incrby key increment
=>127.0.0.1:6379> incrby views 10 #自增长量为10
(integer) 10
127.0.0.1:6379> incrby views 10 #自增长量为10
(integer) 20
127.0.0.1:6379> decrby vivews 5#自减长量为5
(integer) 15
#####################################################################################################
#获取有效范围内的值,截取字符串
getrange key start end
127.0.0.1:6379> getrange key1 0 3 #获取0-3范围内的值
"kuan"
127.0.0.1:6379> getrange key1 0 -1 #获取全部值
"kuangshen"
#修改或替换区间字符串的值
setrange key offset value
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xxx #修改第一位所在的值,修改为xx
(integer) 7
127.0.0.1:6379> get key2 #查询
"axxxefg"
- String类似使用场景,value除外的字符串都可以是数字
- 计数器
- 统计多单位数量
- 粉丝数
- 对象缓存存储
3.5、List列表类型
- List的几种数据类型,list有序存储
- 在Redis中,list可以以栈,队列,阻塞队列等类型存在,且所有list命令都是以L开头
```bash
向list里添加一个值 Lpush(队列添加,队列查看)、Rpush(栈添加,栈查看)
lpush key value [value …] => 127.0.0.1:6379> lpush list one #L方法向list里添加一个one (integer) 1 127.0.0.1:6379> lpush list two #L方法向list里添加一个two (integer) 2 127.0.0.1:6379> lpush list three #L方法向list里添加一个three (integer) 3 127.0.0.1:6379> lrange list 0 -1 #遍历list里所有的值 1) “three” 2) “two” 3) “one” 127.0.0.1:6379> lrange list 1 2 #遍历list里指定的内容 1) “two” 2) “one”
rpush key value [value …] => 127.0.0.1:6379> rpush list one #R方法向list里添加一个one (integer) 1 127.0.0.1:6379> rpush list two #R方法向list里添加一个two (integer) 2 127.0.0.1:6379> rpush list three #R方法向list里添加一个three (integer) 3 127.0.0.1:6379> lrange list 0 -1 #遍历list里所有的值 1) “one” 2) “two” 3) “three” 127.0.0.1:6379> lrange list 1 2 #遍历list里指定的内容 1) “two” 2) “three”
#
从list里移除值 Lpop(向左移除),Rpop(向右移除)
lpop key => 127.0.0.1:6379> lpop list #移除list最左测的元素(最前面一个) “one” rpop key => 127.0.0.1:6379> rpop list #移除list最右测的元素(最后面一个) “three”
#
按照索引序号取值 Lindex(自左开始取值)
127.0.0.1:6379> lindex list 1 #左边第一个
"two"
127.0.0.1:6379> lindex list 2 #左边第二个
"three"
1取列表长度
Llen 127.0.0.1:6379> llen list #精确移除 (integer) 3
移除list里指定的值
Lrem 127.0.0.1:6379> lrem list 1 one (integer) 1 127.0.0.1:6379> lrem list 2 one #若存在多个one时,移除多个相同的值 (integer) 1
截取显示
trim 127.0.0.1:6379> ltrim list 0 1 OK 127.0.0.1:6379> lrange list 0 -1 1) “one” 2) “two”
移除列表的最后一个元素,并添加一个新的元素
RpopLpush 127.0.0.1:6379> rpoplpush list otherlist #将list中第一个元素一到otherlist中 “two” 127.0.0.1:6379> lrange list 0 -1 #移除后的list 1) “one” 127.0.0.1:6379> lrange otherlist 0 -1 #移入后的otherlist 1) “two”
更新修改list中指定下表的值,若不存在则报错
lset key index end 127.0.0.1:6379> lrange list 0 0 1) “one” 127.0.0.1:6379> lset list 0 item #更新操作 OK 127.0.0.1:6379> lrange list 0 0#遍历操作 1) “item”
插入元素
linsert key BEFORE|AFTER pivot value #在具体元素前后添加/插入元素 127.0.0.1:6379> linsert list before item hello (integer) 2 127.0.0.1:6379> lrange list 0 -1 1) “hello” 2) “item”
- list实际上可以看做链表,before、after、node、left、right都可以往里面插入
- 如果key不存在,就新建链表
- 如果key不存在,往里面新增内容
- 如果移除所有值,链表为空,代表不存在
- 包括消息队列(Lpush Rpop),栈(Lpush Lpop)
<a name="0bb32108"></a>
### 3.6、set集合类型
> Set无线存储
- 命令用法
```bash
#向set添加元素
sadd key member [member ...]
=>
127.0.0.1:6379> sadd name hello1,heelo2
(integer) 1
#查看元素
smembers name
=>
1) "hello1,heelo2"
2) "hello3"
#移动元素(从A移动到B)
smove source destination member
=>
smove name name2 hello3
#查看共同关注
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 #查看差集
1) "b"
2) "a"
127.0.0.1:6379> sinter key1 key2 #查看交集
1) "c"
127.0.0.1:6379> sunion key1 key2 #查看并集
1) "a"
2) "c"
3) "b"
4) "d"
5) "e"
3.7、Hash哈希集合
Hash类型,Map集合,Key-Value集合
取值
hget key field => hget key1 hash1 “hello”
同时设置多个值
hmset key field value [field value …] => hmset key fiel1 hello1 fiel2 hello2 OK
同时获取多个值
hmset key field value [field value …] => hmget key fiel1 fiel2 1) “hello1” 2) “hello2”
全部获取,以键值对的方式打印所有结果
hgetall key => hgetall key 1) “fiel1” 2) “hello1” 3) “fiel2” 4) “hello2”
删除指定的元素
hdel key field [field …] => hdel key fiel1 (integer) 1
#
只获取Key
hkey key => hkeys key1 1) “f1” 2) “f2” 3) “f3”
只获取Value
hvals key => hvals key1 1) “h1” 2) “h2” 3) “h3”
指定元素自增长
hincrby key field increment => 127.0.0.1:6379> hset key f 5 (integer) 1 127.0.0.1:6379> hincrby key f 1 #自增+1 (integer) 6 (integer) 6 127.0.0.1:6379> hincrby key f -2 #自增-2 (integer) 4
判断hash内是否存在指定的元素
hsetnx key field value => 127.0.0.1:6379> hsetnx key1 f4 hello #不存在就创建元素,存在就不能设置 (integer) 1
- Hash类型的使用
- 多用于数据动态更新,主要是经常变得的数据信息
- 多用于用户变更,键值对数据动态更新的存储
<a name="e7e743a8"></a>
### 3.8、Zset有序集合
> 与set使用无异,只是增加了有序排列的功能
- 命令行
```bash
#添加元素
zadd key [NX|XX] [CH] [INCR] score member [score member ...]
=>
127.0.0.1:6379> zadd k 2000 xiaohong
(integer) 1
127.0.0.1:6379> zadd k 1999 xiaohuang
(integer) 1
127.0.0.1:6379> zadd k 2001 xiaozhang
(integer) 1
#查看元素
zrange key start stop [WITHSCORES]
=>
127.0.0.1:6379> zrange k 0 -1 #按照顺序打印
1) "xiaohuang"
2) "xiaohong"
3) "xiaozhang"
#查看最小到最大元素
zrangebyscore key min max [WITHSCORES] [LIMIT offset count]
=>
127.0.0.1:6379> zrangebyscore k 1999 2001
1) "xiaohuang"
2) "xiaohong"
3) "xiaozhang"
127.0.0.1:6379> zrangebyscore k 1999 2000
1) "xiaohuang"
2) "xiaohong"
127.0.0.1:6379> zrangebyscore k 1999 1999
1) "xiaohuang"
127.0.0.1:6379> zrangebyscore k -inf +inf #查询负无穷到正无穷范围内的元素
1) "xiaohuang"
2) "xiaohong"
3) "xiaozhang"
#以键值对形式查看元素
127.0.0.1:6379> zrangebyscore k -inf +inf withscores
1) "xiaohuang"
2) "1999"
3) "xiaohong"
4) "2000"
5) "xiaozhang"
6) "2001"
#移除Zset中的元素
zrem key member [member ...]
=>
127.0.0.1:6379> zrem k xiaohong
(integer) 1
127.0.0.1:6379> zrange k 0 -1
1) "xiaohuang"
2) "xiaozhang"
#获取有序集合中的个数
zcard key
=>
127.0.0.1:6379> zcard k
(integer) 2
#有序数列降序
zrevrange key start stop [WITHSCORES]
=>
127.0.0.1:6379> zrevrange k 0 -1
1) "xiaolin"
2) "xiaolou"
3) "xiaozhang"
4) "xiaohuang"
#查询区间内的元素
zcount key min max
=>
127.0.0.1:6379> zcount k 1 2000
(integer) 1
3.9、Geospatial位置解析
Geospatial地理位置分析
- 定位、附近人、滴滴打车、距离推算等等
```bash
添加地理位置
geoadd key longitude latitude member [longitude latitude member …] => 127.0.0.1:6379> geoadd China:city 116.397128 39.916527 zunyi (integer) 1 127.0.0.1:6379> geoget (error) ERR unknown command ‘geoget’ 127.0.0.1:6379> geoadd China:city 106.937265 27.706626 guiyang (integer) 1 127.0.0.1:6379> geoadd China:city 106.504962 29.533155 chongqing (integer) 1
查看库内两地距离
geodist key member1 member2 [unit] => 127.0.0.1:6379> geodist China:city zunyi guiyang km #遵义到贵阳的经纬度距离 “1612.8457”
查看半径范围内的元素
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [AS => 127.0.0.1:6379> georadius China:city 106 28 3000 km #精度=105,维度=28,方位3Km范围内的城市 1) “chongqing” 2) “guiyang” 3) “zunyi”
键值对查询半径范围内的元素
127.0.0.1:6379> georadius China:city 106 28 3000 km withdist #以106.28为中心点范围里的城市 1) 1) “chongqing” 2) “177.4913” 2) 1) “guiyang” 2) “97.7765” 3) 1) “zunyi” 2) “1633.4591”
准确的经纬度查询
georadius China:city 106 28 30000 km withcoord #方圆3000km里所在的城市 1) 1) “chongqing” 2) 1) “106.50495976209641” 2) “29.53315530684997” 2) 1) “guiyang” 2) 1) “106.93726748228073” 2) “27.706625100545686” 3) 1) “zunyi” 2) 1) “116.39712899923325” 2) “39.916526473629808”
限定范围限定数量查询
127.0.0.1:6379> georadius China:city 106 28 3000 km withcoord count 1 查询范围最短的一个地方 1) 1) “guiyang” 2) 1) “106.93726748228073” 2) “27.706625100545686”
以特定的城市位置为中心点查询周围的城市
georadiusbymember China:city guiyang 2000 km #以贵阳为中心,查询周围得城市,多用于导航 1) “chongqing” 2) “guiyang” 3) “zunyi”
- 有效的经度从-180度到180度。
- 有效的纬度从-85.05112878度到85.05112878度。
- GEO底层实现原理实质是Zset。
<a name="06bd1ebb"></a>
### 3.10、Hyperloglog基数统计
> 统计基数:不重复的元素的数量
- 用在数据统计比较多
```bash
#添加数据
pfadd key element [element ...]
127.0.0.1:6379> pfadd k a b c d e f
(integer) 1
127.0.0.1:6379> pfadd k2 e f g h l j k
(integer) 1
#查看数据个数
pfcount key [key ...]
127.0.0.1:6379> pfcount k
(integer) 6
#合并数据
pfmerge destkey sourcekey [sourcekey ...]
127.0.0.1:6379> pfmerge k3 k k2 #合并k和k2成为k3
OK
127.0.0.1:6379> pfcount k3
(integer) 11
3.11、Bitmaps位图场景详解
分析状态的用途最多
- 统计分析状态
- 使用Bitmaps位图,使用二进制操作,只用0和1表示
4、Redis事务操作
4.1、基本事务操作
Redis单条命令是保证原子性的,但事务不保存原子性
- Redis事务本质:一组命令集合,一个事务中的所有命令都会被序列化,在事务执行过程中,按照顺序执行。
- 一次性,顺序性,排他性
- Redis没有隔离级别的概念,所有命令在事务中没有直接被执行,只有发起执行是才会执行
- Redis事务的三个阶段:
- 开启事务 Mutil
- 命令入队 ……..
- 执行事务 exec ```bash 127.0.0.1:6379> multi #开启事务 OK 127.0.0.1:6379> set k1 v1 #设置值,进入队列 QUEUED 127.0.0.1:6379> set k2 v2 QUEUED 127.0.0.1:6379> set k3 v3 QUEUED 127.0.0.1:6379> exec #执行事务 1) OK 2) OK 3) OK
放弃事务,在还没有执行事务前取消队列里的值,一键取消,再无事务
127.0.0.1:6379> set k1 v1 QUEUED 127.0.0.1:6379> set k2 v QUEUED 127.0.0.1:6379> set k4 v4 QUEUED 127.0.0.1:6379> discard #放弃事务 OK 127.0.0.1:6379> get k4 (nil)
- 编译时异常
- 代码有错,过不了编译,能后添加队列,但是不能执行事务
- 运行时异常
- 事务存在语法错误,事务执行正常,但是抛出错误命令,单条事务不存在原子性
```bash
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> incr k1 #产生语法错误
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
2) (error) ERR value is not an integer or out of range #抛出异常
3) OK
4) OK
5) "v3"
4.2、实现锁的监控
监控:以锁的形式存在,分为乐观锁和悲观锁
- 乐观锁:
- 在开启事务后,不论什么时候都不会出问题,不论做什么都不会加锁,更新数据时判断数据完整性以达到安全的目的
- 步骤1:获取version
- 更新数据时比较version的变化
- 在开启事务后,不论什么时候都不会出问题,不论做什么都不会加锁,更新数据时判断数据完整性以达到安全的目的
- 悲观锁:
- 只要开启事务,不论什么时候会出问题,不论做什么都会加锁
- Redis监控
127.0.0.1:6379> set m 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch m #定义监控对象
OK
127.0.0.1:6379> multi #启动事务
OK
127.0.0.1:6379> decrby m 20 #监控事件
QUEUED
127.0.0.1:6379> incrby out 20 #监控事件
QUEUED
127.0.0.1:6379> exec #执行事务
1) (integer) 80 #监控情况
2) (integer) 20
- 多线程修改或更新值时,使用watch可以当做redis的乐观锁
- 在多个线程同时进行时,在执行事务前,另一个线程对数据修改,线程将进入锁状态,导致事务执行失败,只有更新的时候,才会更新锁。
- 开启乐观锁
```bash
线程一
127.0.0.1:6379> set m 100 OK 127.0.0.1:6379> set out 0 OK 127.0.0.1:6379> watch m OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby m 20 QUEUED 127.0.0.1:6379> incrby out 20 QUEUED 127.0.0.1:6379> exec 1) (integer) 80 2) (integer) 20
线程二
127.0.0.1:6379> get m “80” 127.0.0.1:6379> set m 1000 OK
返回线程一
127.0.0.1:6379> watch m OK 127.0.0.1:6379> multi OK 127.0.0.1:6379> decrby m 10 QUEUED 127.0.0.1:6379> incrby out 10 QUEUED 127.0.0.1:6379> exec #在线程二插队的情况下,主线程事务执行失败 (nil)
重启事务,关闭事务再重新开启获取最新的数据
unwatch watch m
- 利用redis的watch功能,监控这个redisKey的状态值
- 获取redisKey的值
- 创建redis事务
- 给这个key的值+1
- 然后去执行这个事务,如果key的值被修改过则回滚,key不+1
<a name="732996bb"></a>
### 4.3、集成Jedis及事务管理
> Jedis是官方推荐的java连接开发工具,使用java操作Redis的中间件
- Java集成Jedis
```xml
<!--导入依赖-->
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
连接数据库
public static void main(String[] args) {
//1.创建Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379); //使用IP地址+端口号连接
//指令启动命令,测试连接
System.out.println(jedis.ping());
}
操作命令 ```bash package com.iflytek;
import redis.clients.jedis.Jedis;
import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
/**
- @author 黄果
@date 2021/3/26 20:55 **/ public class TestPing { public static void main(String[] args) {
//1.创建Jedis对象
Jedis jedis = new Jedis("127.0.0.1",6379); //使用IP地址+端口号连接
//指令启动命令,测试连接
System.out.println("清空数据:"+jedis.flushDB());
System.out.println("判断某个键是否存在:"+jedis.exists("username"));
System.out.println("新增<'username','zhangsan'>的键值对:"+jedis.set("username","zhangsan"));
System.out.println("新增<'passwork','passwork'>的键值对:"+jedis.set("passwork","passwork"));
System.out.println("系统中所有的键如下:");
Set<String> keys = jedis.keys("*");
System.out.println(keys);
System.out.println("删除键passwork:"+jedis.del("passwork"));
System.out.println("判断键passwork是否存在:"+jedis.exists("passwork"));
System.out.println("查看键username所存储值的类型:"+jedis.type("username"));
System.out.println("随机返回Key空间的一个:"+jedis.randomKey());
System.out.println("重命名Key:"+jedis.rename("username","name"));
System.out.println("取出改后的name:"+jedis.get("name"));
System.out.println("按索引查询:"+jedis.select(0));
System.out.println("删除当前选择数据库中的所有Key:"+jedis.flushDB());
System.out.println("返回当初数据库中key的数目:"+jedis.dbSize());
System.out.println("删除所有数据库中的所有key:"+jedis.flushAll());
} } ```
- 断开连接
- quit/ctrl+c
- jedis.close();
Jedis处理事务
package com.iflytek;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
/**
* @author 黄果
* @date 2021/3/26 21:47
**/
public class JedisAffair {
public static void main(String[] args) {
//创建连接对象
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
//指令操作命令执行
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","zhangsan");
//开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
//添加乐观锁,处理事务
jedis.watch(result);
//处理锁异常
try {
multi.set("user1",result);
multi.set("user2",result);
//模拟语法错误,数据异常
int i = 1/0;
multi.exec(); //执行成功,执行事务
} catch (Exception e) {
multi.discard(); //提交失败,放弃事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); //关闭事务
}
}
}
4.4、SpringBoot集成Redis
SpringBoot整合Redis
- Jedis:采用直连,多线程操作,易出现线程安全的情况
- Lettuce:springboot2.2.6以后的版本官方推荐使用,采用netty,共享多个线程,避免了多线程不安全的情况,减少线程数据流
- 在SpringBoot的自动装配中,RedisAutoConfiguration ——> RedisProperties中集成了Redis的所有基本配置,因此在集成Redis项目时可以不用定义Redis变量
```java @ConfigurationProperties( prefix = “spring.redis” )
//自动装配的字段变量 public class RedisProperties { private int database = 0; private String url; private String host = “localhost”; private String username; private String password; private int port = 6379; private boolean ssl; private Duration timeout; private Duration connectTimeout; private String clientName; private RedisProperties.ClientType clientType; private RedisProperties.Sentinel sentinel; private RedisProperties.Cluster cluster; private final RedisProperties.Jedis jedis = new RedisProperties.Jedis(); private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();
- redisTempate类,**如果自定义Tempate,那么就将全局接管Redis的所有模板,集成的redisTemplate就会失效**<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210327140833449.png#crop=0&crop=0&crop=1&crop=1&id=UOu4X&originHeight=369&originWidth=1151&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
> 详情解析
```java
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
//SpringBoot集成的Redis模板
//一旦自定义Template,该类就将彻底失效
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////
//Redis中常用的类型-String
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
- 连接查询
package com.iflytek.redis02springboot;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
class Redis02SpringbootApplicationTests {
//添加自动注入,导入环境
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
void contextLoads() {
/**
* opsForxxxxx
* 使用命令格式
*/
//redisTemplate指令操作不同类型的数据
//opsForValue 操作字符串,类似String
//opsForList 操作List 类似List
redisTemplate.opsForValue().set("key1","nihao");
System.out.println(redisTemplate.opsForValue().get("key1"));
//获取连接
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
}
}
- 四种常见的数据类型序列化
- 默认序列化方式是JDK序列化,但自定义时一般用json传递对象
- RedisConfig的自定义 ```java package com.iflytek.redis02springboot.Config;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.lang.model.UnknownEntityException;
/**
- @author 黄果
- @date 2021/3/27 22:14
- 现世安稳,岁月静好,佛祖保佑,永无BUG **/
//自定义RedisTemplate public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory)
throws UnknownEntityException {
//为了开放方便,直接使用<String,Object>
RedisTemplate<String,Object> template = new RedisTemplate<String,Object>();
template.setConnectionFactory(factory);
//Json序列化设置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
//String序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//配置具体序列化方式
//key采用string的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash采用string的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//key采用string的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//key采用string的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
- 无序列化添加
```java
//定义未序列化的实体类
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private String name;
private int age;
}
//测试类
@Test
public void test1() throws JsonProcessingException {
User user = new User("hg", 18);
//添加序列化
String jsonUser = new ObjectMapper().writeValueAsString(user);
//没序列化时,一般使用json传递对象
redisTemplate.opsForValue().set("user",jsonUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
//定义序列化的实体类
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable{
private String name;
private int age;
}
//测试类
@Test
public void test1() throws JsonProcessingException {
User user = new User("hg", 18);
//添加序列化
String jsonUser = new ObjectMapper().writeValueAsString(user);
//在默认的JDK序列化中,定义序列化传值
redisTemplate.opsForValue().set("user",user");
System.out.println(redisTemplate.opsForValue().get("user"));
}
- 对于SpringBoot集成Redis的操作,自定义工具类,便于企业级开发的模型
5、Redis核心操作
5.1、Redis.conf配置文件
Redis.conf配置文件
- 进入配置模式
cd /usr local bin #进入/usr/local/bin目录
cd redisconfig #进入redisconfig目录
vim redis.conf #进入redis配置文件
- 在系统中,redis对大小写并不敏感
- 可以配置多个配置文件
- 多加载文件
- 绑定本地IP,开启保护机制以及端口号等,在集群开发的时候,IP和端口号是需要修改的
- 通用配置后台默认开启
``` daemonize yes #开启守护进程启用后台默认启动 pidfile /var/run/redis_6379.pid #如果以后台的方式运行,就需要制定一个pid文件
日志的级别
debug (a lot of information, useful for development/testing)
verbose (many rarely useful info, but not a mess like the debug level)
notice (moderately verbose, what you want in production probably) #默认生成环境
warning (only very important / critical messages are logged)
loglevel notice
logfile “” #日志存放位置
database 16 #数据库数量默认16个 always-show-logo no #logo显示设置
- 快照
- 持久化生成,在规定时间内,执行多少次操作,才会持久化到文件.red/.aof
- redis存储于内存,如果没有持久化,就会断电即失
save 3600 1 #900s内修改了1个Key,则进行持久化操作 save 300 100 #300s内修改了10个key,则进行持久化操作 save 60 10000 #60s内修改了10000个key,则进行持久化操作
stop-writes-on-bgsave-error yes #持久化储存,是否继续工作 rdbcompression yes #是否压缩rdb文件,这个过程消耗CPU资源 rdbchecksum yes #持久化出错,保存rdb文件校验错误 dir ./ #rdb文件存储目录
requirepass 123456 #设置连接密码,默认密码为空,设置在reqirepass foobared下面
config get requirepass #查看密码 config set requirepass 123456 #设置密码
<br /> ![](https://gitee.com/hg14150/blogiamges/raw/master/img/image-20210328191843199.png#crop=0&crop=0&crop=1&crop=1&id=UY3c7&originHeight=193&originWidth=759&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- redis限制
```bash
maxclients 10000 #设置能连接上redis的最大客户端数量
maxmemory <bytes> #客户端配置最大内存容量
maxmemory-policy noeviction #内存达到上限之后的处理策略
#6中处理策略
1、volatile-lru #只对设置了过期时间的key进行LRU
2、allkeys-lur #删除lru算法的key
3、volatile-random #随机删除即将过期的key
4、noevicion #永不过期,返回错误
5、allkeys-random #随机删除
6、volatile-ttl #删除即将过期的key
5.2、持久化RDB操作
Redis是内存数据库,如果内存数据库状态不能保存到磁盘,一旦退出服务器,数据就会消失,还会发生断电即失的结果,因此需要RDB来提供持久化功能,压缩成二进制等时机开子进程重写文件
- RDB(Redis DataBase)
- 解读RDB
- 在指定的时间间隔内将内存中的数据集以快照的方式写入磁盘(snapshot快照),它恢复时将快照文件直接读到内存中。redis单独创建(fork)一个子进程进行持久化,会将数据写到一个临时文件中,待持久化过程都结束后,再用这个临时文件替换上一次持久化好的文件,这个过程不需要进行任何I/O操作,保障了极高的性能,如果需要进行大规模数据恢复时,且对于数据的完整性敏感性不高。那么RDB方式要比AOF高效得多。
- RDB保存的文件是dump.rdb,都是在配置文件的快照中实现的配置,一点触发就会生成dump.rdb文件
- 触发机制
- save的规则满足情况下,系统自动触发rdb规则
- 执行flushall命令时,触发rdb规则
- 退出redis时,触发rdb规则
- 只要触发rdb规则就会生成一个dump.rdb文件
- 如何恢复rdb文件
- 只需要将rdb文件放在redis启动目录下,redis就会自动检测dimp.rdb并恢复其中的数据
config get dir #查询当前目录
/usr/local/bin/dump.rdb #将dump.rdb文件放在查询到的目录下即可
- 只需要将rdb文件放在redis启动目录下,redis就会自动检测dimp.rdb并恢复其中的数据
- 优点
- 大规模集群服务器宕机时,恢复dump.rdb文件,即恢复数据
- 对数据完整性要求不高
- 缺点
- 需要一定的时间间隔,在这段时间间隔中容易数据失真
- 如果redis意外宕机,最后一次更新的数据可能会丢失
- fork进程时,会占用一定的内存空间
5.3、持久化AOF操作
AOF是Redis持久化的重要手段之一,只追加文件,也就是每次处理完请求命令后都会将此命令追加到aof文件的末尾。
- AOF(append only file)
- 解读AOF
- 每次都在aof文件后面追加命令。与主进程收到请求、处理请求是串行化的,而非异步并行的。所以aof的频率高的话绝逼会对Redis带来性能影响,因为每次都是刷盘操作。跟mysql一样了。Redis每次都是先将命令放到缓冲区,然后根据具体策略(每秒/每条指令/缓冲区满)进行刷盘操作。如果配置的always,那么就是典型阻塞,如果是sec,每秒的话,那么会开一个同步线程去每秒进行刷盘操作,对主线程影响稍小。
- 向外拓展
- Redis每次在写入AOF缓冲区之前,都会调用flushAppendOnlyFile(),判断是否需要将AOF缓冲区的内容写入和同步到AOF文件中。这个决策是由配置文件的三个策略来控制的
- always
- everysec
- no
- Redis每次在写入AOF缓冲区之前,都会调用flushAppendOnlyFile(),判断是否需要将AOF缓冲区的内容写入和同步到AOF文件中。这个决策是由配置文件的三个策略来控制的
- 追加重写(rewrite)
- Redis4.0开始的rewrite支持混合模式(rdb和aof并用),直接将rdb持久化的方式来操作将二进制内容覆盖到aof文件中(rdb是二进制,所以很小),然后再有写入的话还是继续append追加到文件原始命令,等下次文件过大的时候再次rewrite(还是按照rdb持久化的方式将内容覆盖到aof中)。但是这种模式也是配置的,默认是开,也可以关闭。
- 重写原理
- aof_rewrite_buf:rewrite(重写)缓冲区、aof_buf:写命令存放的缓冲区
- 开始bgrewriteaof的时候,判断当前有没有bgsave/bgrewriteaof在执行,若有,则不执行,这个再rdb篇幅也有提到
- 主进程fork()出子进程,在执行fork()这个方法的时候是阻塞的,子进程创建完毕后就不阻塞了
- 主进程fork完子进程后,主进程能继续接收客户端的请求,所有写命令依然是写入AOF文件缓冲区并根据配置文件的策略同步到磁盘的。
- 因为fork的子进程仅仅共享主进程fork()时的内存,后期主进程在更改内存数据,子进程是不可见的。因此Redis采取重写缓冲区(aof_rewite_buf)保存fork之后的客户端请求。防止新AOF文件生成期间丢失主进程执行的新命令所生成的数据。所以此时客户端的写请求不仅仅写入原来的aof_buf缓冲区,还写入了重写缓冲区。这就是我为什么用深蓝色的框给他两框到一起的原因。
- 子进程通过内存快照的形式,开始生成新的aof文件。
- 新aof文件生成完后,子进程向主进程发信号。
- 主进程收到信号后,会把重写缓冲区(aof_rewite_buf)中的数据写入到新的AOF文件(主要是避免这部分数据丢失)
- 使用新的AOF文件覆盖旧的AOF文件,且标记AOF重写完成。
- 触发原理
- 手动触发:执行bgrewriteaof命令
- 自动触发1:auto-aof-rewrite-min-size
- AOF文件最小重写大小,只有当AOF文件大小大于该值时候才可能重写,4.0默认配置64mb。
- 自动触发2:auto-aof-rewrite-percentage
- 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比,如100代表当前AOF文件是上次重写的两倍时候才重写。
- 触发条件
- 没有bgsave命令RDB持久化/AOF持久化在执行
- 没有bgrewriteaof在进行;
- 当前AOF文件大小要大于server.aof_rewrite_min_size的值;
- 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者大于指定的增长百分比(auto-aof-rewrite-percentage参数)
- AOF保存的文件是APPendonly.aof在redis配置文件中appendonly是默认关闭的,需要手动开启,重启即生效
- 数据恢复
aof(append only file)配置
append only no #默认不开启aof模式 默认是启动rdb方式实现持久化
appendfilename "appendonly.aof" #aof持久化名称
appendfsync always #每次修改都会执行sync,消耗性能
appendfsync everysec #每秒执行一次sync,在执行瞬间可能丢失1秒的数据
appendfsync no #不执行sync,操作系统自动同步数据,速度最快
- 优点
- 每次修改都同步数据,文件完整性跟高
- 每秒同步一次,可能会丢失一秒的数据
- 从不同步数据,效率最高
- 缺点
- 相对于数据文件来说,aof远远大于rdb,修复速度同样也慢
- aof运行效率也慢,所以默认不选aof
6、Redis订阅发布
Redis发布订阅(pub/sub)是一种信息通信模式
Redis发布:pub发送消息,sub订阅消息
- 第一个:消息发送者。第二个:频道。第三个:消息订阅者。
- 解析发布(pub)
- 一个发布相当于发出了一个共享
- 可以由多个客户端订阅,每个客户端之间相互独立
- 类似于一对多的模式
- 实例分析
```bash
1、sub发布
127.0.0.1:6379> subscribe hello #发布一个名字叫hello的频道 Reading messages… (press Ctrl-C to quit) #消息等待发布 1) “subscribe” 2) “hello” 3) (integer) 1
2、pub订阅
27.0.0.1:6379> publish hello “hello,world” #发布者发布消息的频道 (integer) 1 127.0.0.1:6379> publish hello “hello,redis” (integer) 1
3、发布列表刷新
1) “message” #消息 2) “hello” #发送频道的信息 3) “hello,world” #信息的具体内容 1) “message” 2) “hello” 3) “hello,redis”
- 命令解析<br /> ![](https://gitee.com/hg14150/blogiamges/raw/master/img/20201206013502721.png#crop=0&crop=0&crop=1&crop=1&id=B8ffe&originHeight=670&originWidth=1160&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 原理
- Redis是使用c实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现,籍此加深对Redis的理解。
- Redis通过publish,subscribe和psubscribe等命令实现发布和订阅功能。
- 通过subscribe命令订阅某频道后,Redis-server里维护了一个字典,**字典的键就是一个频道**,而字典的值则是一个链表,链表中保存了所有订阅这个频道的客户端。
- subscribe命令的关键,就是将客户端添加到给定频道的订阅链表中。
- 通过publish命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的频道字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者。
- pub/sub从字面上理解就是发布(publish)与订阅(subscribe),在Redis中,可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用作实时消息系统,比如:普通的即时聊天,群聊等功能。
- 应用场景
- 实时消息系统
- 实时聊天(频道当做聊天室,将信息回显给所有人即可)
- 订阅,关注系统都是可以的
- 稍微复杂的场景我们就会使用消息中间件MQ
<a name="d3505a29"></a>
## 7、Redis主从复制
> 简单的说,就是一个数据,多态设备同步,当主设备宕机后,备用设备无空隙衔接
<a name="1d93c2f0"></a>
### 7.1、主从分离模式
- 单主机,多从机集群,好处是可用性高,效率高,坏处是一旦主机宕机,整个服务就将失去写入能力
- 面临问题
- 机器故障。项目部署到一台 Redis 服务器,当发生机器故障时,需要迁移到另外一台服务器并且要保证数据是同步的。而数据是最重要的,这十分重要,如果不在乎,基本上也就不会使用 Redis 了。
- 容量瓶颈。当实际需求需要扩容 Redis 内存时,从 16G 的内存升到 64G,单台Redis最大内存不能超过20G,不然一定会宕机,此时就需要集群部署
- 深层理解
- **默认情况下,每台Redis服务器都是一个主节点,且每一个主节点可以有多个节点,但是一个从节点只能有一个主节点**。持久化保证了即使 redis 服务重启不会丢失数据,因为 redis 服务重启后会将硬盘上持久化的数据恢复到内存中,但是当 redis 服务器的硬盘损坏了可能会导致数据丢失,可以通过 redis 的主从复制机制避免这种单点故障<br />![](https://gitee.com/hg14150/blogiamges/raw/master/img/u=2710149246,980531347&fm=15&gp=0.jpg#crop=0&crop=0&crop=1&crop=1&id=KrdZ8&originHeight=300&originWidth=552&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 主从复制主要作用:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即**写Redis数据时应用连接主节点,读Redis数据时应用连接从节点**),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
- 配置环境
- 主从复制环境配置只需要配置从库,不配置主库。
```bash
#进入Redis目录下启动Redis
[root@hg14150 ~]# cd /usr/local/bin
[root@hg14150 bin]# cd redisconfig
[root@hg14150 redisconfig]# ls
redis.conf
[root@hg14150 redisconfig]# cd ..
[root@hg14150 bin]# redis-server redisconfig/redis.conf
[root@hg14150 bin]# redis-cli -p 6379
#查看当前库信息
127.0.0.1:6379> info replication
# Replication
role:master #角色
connected_slaves:0 #连接的从机数
master_failover_state:no-failover
master_replid:2eb1f48ceaff2169f9ec9be4e40cf4f23fdbc7f3
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
- 配置信息
- 复制多台Redis服务器
#从机名称不能相同,不然无法区分
[root@hg14150 redisconfig]# cp redis.conf redis79.conf
[root@hg14150 redisconfig]# cp redis.conf redis80.conf
[root@hg14150 redisconfig]# cp redis.conf redis81.conf
- 复制多台Redis服务器
- 配置从机端口号,pid名字,log文件名,dump.reb名称
- 配置如下
port 63__ #与从机端口号一直
pidfile /var/run/redis_63__.pid #与从机端口号一直
logfile "___.log" #与从机端口号一直
dump__.rdb #与从机端口号一直
- 开启服务
- 多机多集群,开四台Redis服务器,每台服务器启动一个Redis从机服务
- 配置从机
- 默认情况下每台服务器都是主节点
- 主机和从机是自定义的
- 定义主机
slaveof host port #设置地址为___,端口号为___的服务器为主机
slaveof 127.0.0.1 6379 ```
- 多机多集群,开四台Redis服务器,每台服务器启动一个Redis从机服务
- 查询从机信息
多设备多配置
每台从机环境下都要配置主机信息,以达到主从关系 ```
- 命令启用主从复制
- 配置文件: 在从服务器的配置文件中加入:slaveof
- 启动命令: redis-server启动命令后加入 —slaveof
- 客户端命令: Redis服务器启动后,直接通过客户端执行命令:slaveof sterport>,则该Redis实例成为从节点。
- 通过 info replication 命令可以看到复制的一些信息
- 细节概要
- 在主从复制中,主机只负责写,从机只负责读
- 主机中的所有信息都会被从机自动保存
- 默认情况下,主机宕机后,从机依旧只读取连接的主机,即使宕机后也只认他
- 当从机宕机后,再次启动将会恢复主机的身份,此时主机进行写操作时,宕机的从机再次连接时读取不了主机信息的,必须要在启动后重新配置主机,变成从机后才能读取
- 命令启用主从复制
- 主从复制的原理
- 从机连接主机,发送SYNC命令;
- 主机接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令,完成一次完全同步;
- 主机BGSAVE执行完后,向所有从机发送快照文件,并在发送期间继续记录被执行的写命令;
- 全量复制:从机收到快照文件后丢弃所有旧数据,载入收到的快照,存入内存中;
- 增量复制:主机快照发送完毕后开始向从机发送缓冲区中的写命令,继续完成同步;
- 从机完成对快照的载入,开始接收命令请求,并执行来自主机缓冲区的写命令;
- 只要重新定义主机,从机就会完成一次全量复制的过程;
7.2、主从混合模式
![](https://gitee.com/hg14150/blogiamges/raw/master/img/u=2009366105,2694605544&fm=26&gp=0.jpg#crop=0&crop=0&crop=1&crop=1&id=UMfbx&originHeight=300&originWidth=865&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 区间递进方式集群,但依然无法保证读写功能完整
- 谋权篡位法:
- 当主机宕机后,将会失去写入功能,那么在没有哨兵模式的情况下,只能手动设置一个从机,使其变成主从恢复写入功能,此时宕机的主机重启后无法进行主机操作了,手动设置的从机相当于谋权篡位
- slaveof no one
8、哨兵模式
主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,优先考虑哨兵模式。
- 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
- 哨兵模式的两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。
然而哨兵也可能发生意外,因此在集群中会配置多哨兵监控,哨兵相互监控
![](https://gitee.com/hg14150/blogiamges/raw/master/img/11320039-3f40b17c0412116c.png#crop=0&crop=0&crop=1&crop=1&id=efz3q&originHeight=384&originWidth=747&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
- 用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。
- 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover操作。
- 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。
配置哨兵模式
配置
# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,127.0.0.1代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作,即投票操作。
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456 #在开发中使用
启用哨兵模式
# 启动Redis服务器进程
redis-server redisconfig/redis.conf
# 启动哨兵进程
redis-sentinel redisconfig/sentinel.conf
注意启动的顺序。首先是主机的Redis服务进程,然后启动从机的服务进程,最后启动3个哨兵的服务进程。
Java中使用Jedis操作哨兵模式
/**
* 测试Redis哨兵模式
* @author 三岁
*/
public class TestSentinels {
@SuppressWarnings("resource")
@Test
public void testSentinel() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(10);
jedisPoolConfig.setMaxIdle(5);
jedisPoolConfig.setMinIdle(5);
// 哨兵信息
Set<String> sentinels = new HashSet<>(Arrays.asList(
"127.0.0.1:6380",
"127.0.0.1:6381",
"127.0.0.1:6382"));
// 创建连接池
JedisSentinelPool pool = new JedisSentinelPool("mymaster", sentinels,jedisPoolConfig,"123456");
// 获取客户端
Jedis jedis = pool.getResource();
// 执行两个命令
jedis.set("mykey", "myvalue");
String value = jedis.get("mykey");
System.out.println(value);
}
}
使用Spring配置RedisTemplate使用哨兵模式
<bean id = "poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<!-- 最大空闲数 -->
<property name="maxIdle" value="50"></property>
<!-- 最大连接数 -->
<property name="maxTotal" value="100"></property>
<!-- 最大等待时间 -->
<property name="maxWaitMillis" value="20000"></property>
</bean>
<bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="poolConfig" ref="poolConfig"></constructor-arg>
<constructor-arg name="sentinelConfig" ref="sentinelConfig"></constructor-arg>
<property name="password" value="123456"></property>
</bean>
<!-- JDK序列化器 -->
<bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer"></bean>
<!-- String序列化器 -->
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory"></property>
<property name="keySerializer" ref="stringRedisSerializer"></property>
<property name="defaultSerializer" ref="stringRedisSerializer"></property>
<property name="valueSerializer" ref="jdkSerializationRedisSerializer"></property>
</bean>
<!-- 哨兵配置 -->
<bean id="sentinelConfig" class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<!-- 服务名称 -->
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="mymaster"></property>
</bean>
</property>
<!-- 哨兵服务IP和端口 -->
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.128"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.129"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="192.168.11.130"></constructor-arg>
<constructor-arg name="port" value="26379"></constructor-arg>
</bean>
</set>
</property>
</bean>
- 多哨兵演示过程
- 优点
- 哨兵集群,基于主从复制模式,主机所有配置他都有
- 主机可以切换,故障可以切换,系统性能更好
- 哨兵模式实质就是首配设置的升级版
- 缺点
- Redis在线扩容会影响性能,集群一旦达到上限,扩容十分麻烦
- 实现哨兵模式实质上比较麻烦
- 哨兵模式的配置
```bash
Example sentinel.conf
哨兵sentinel实例运行的端口 默认26379
port 26379
哨兵sentinel的工作目录
dir /tmp
哨兵sentinel监控的redis主节点的 ip port
master-name 可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符”.-_”组成。
quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了
sentinel monitor
sentinel monitor mymaster 127.0.0.1 6379 1
当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码
设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码
sentinel auth-pass
sentinel auth-pass mymaster MySUPER—secret-0123passw0rd
指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒
sentinel down-after-milliseconds
sentinel down-after-milliseconds mymaster 30000
这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,
这个数字越小,完成failover所需的时间就越长, 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。
sentinel parallel-syncs
sentinel parallel-syncs mymaster 1
故障转移的超时时间 failover-timeout 可以用在以下这些方面:
1. 同一个sentinel对同一个master两次failover之间的间隔时间。
2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
3.当想要取消一个正在进行的failover所需要的时间。
4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了
默认三分钟
sentinel failover-timeout
sentinel failover-timeout mymaster 180000
SCRIPTS EXECUTION
配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。
对于脚本的运行结果有以下规则:
若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10
若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。
通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,
这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,
一个是事件的类型,
一个是事件的描述。
如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。
通知脚本
sentinel notification-script
sentinel notification-script mymaster /var/redis/notify.sh
客户端重新配置主节点参数脚本
当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。
以下参数将会在调用脚本时传给脚本:
目前总是“failover”,
是“leader”或者“observer”中的一个。
参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的
这个脚本应该是通用的,能被多次调用,不是针对性的。
sentinel client-reconfig-script
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh ```
9、缓存穿透与雪崩(重点)
缓存穿透(查不到内容,重复查询导致的结果)
- 缓存处理流程、
- 前台请求,后台先从缓存中取数据,取到直接返回结果,取不到时从数据库中取,数据库取到更新缓存,并返回结果,数据库也没取到,那直接返回空结果。
- 用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。网络安全中也有人恶意使用这种手段进行攻击被称为洪水攻击。
- 解决方案1
- 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截;
- 从缓存取不到的数据,在数据库中也没有取到,这时也可以将key-value对写为key-null,缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击
- 解决方案2
- 布隆过滤器
- 对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
- 对所有可能查询的参数以Hash的形式存储,以便快速确定是否存在这个值,在控制层先进行拦截校验,校验不通过直接打回,减轻了存储系统的压力。
- 布隆过滤器
- 有八位二进制排列组合的数据向量,当数据一hash值进行缓存,在数据向量中就能查到对应的值
- 布隆过滤器很难进行删除操作,在数据向量中只能查到hash值对应的固定值,但对应的缓存数据不止一个,所以删除操作是比较难的,这也导致了查询时候可能会出现误判行为
- 全部以二进制查询,就就让保密性极好
- 缓存空对象
- 一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
- 一次请求若在缓存和数据库中都没找到,就在缓存中方一个空对象用于处理后续这个请求。
- 这样做有一个缺陷:存储空对象也需要空间,大量的空对象会耗费一定的空间,存储效率并不高。解决这个缺陷的方式就是设置较短过期时间
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
- 布隆过滤器
缓存击穿(量太大,缓存过期)
- 相较于缓存穿透,缓存击穿的目的性更强,是指一个key非常热点,在不停的扛着大并发,在缓存过期的一刻,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞。这就是缓存被击穿,只是针对其中某个key的缓存不可用而导致击穿,但是其他的key依然可以使用缓存响应。
- 比如热搜排行上,一个热点新闻被同时大量访问就可能导致缓存击穿。
- 解决方案
- 设置热点数据永不过期
- 这样就不会出现热点数据过期的情况,但是当Redis内存空间满的时候也会清理部分数据,而且此种方案会占用空间,一旦热点数据多了起来,就会占用部分空间。
- 加互斥锁(分布式锁)
- 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key。保证同时刻只有一个线程访问。这样对锁的要求就十分高。
- 设置热点数据永不过期
缓存雪崩(大量数据缓存同时过期)
- 大量的key设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩。和缓存击穿不同的是。
- 解决方案
- redis高可用
- 这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群
- 限流降级
- 这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 数据预热**
- 数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
- redis高可用
10、面试重灾区
- 如何保障redis与MySQL的数据一致性
- 先更新Mysql,再更新Redis,如果更新Redis失败,可能仍然不⼀致
- 先删除Redis缓存数据,再更新Mysql,再次查询的时候在将数据添加到缓存中,这种⽅案能解决1 ⽅案的问题,但是在⾼并发下性能较低,⽽且仍然会出现数据不⼀致的问题,⽐如线程1删除了 Redis缓存数据,正在更新Mysql,此时另外⼀个查询再查询,那么就会把Mysql中⽼数据⼜查到 Redis中
- 延时双删,步骤是:先删除Redis缓存数据,再更新Mysql,延迟⼏百毫秒再删除Redis缓存数据, 这样就算在更新Mysql时,有其他线程读了Mysql,把⽼数据读到了Redis中,那么也会被删除掉, 从⽽把数据保持⼀致