以下学习内容笔记全部来源于B站教学视频:【狂神说Java】Redis最新超详细版教程通俗易懂,非常感谢来自狂神老师的教学系列,附B站学习视频链接:https://www.bilibili.com/video/av840034966
1. Redis(缓存)
1.1 Nosql
1.1.1 概述
单机mysql时代:
90年代,一个基本的网站访问量不会太大,单个数据库足够!
更多的使用静态网页html
网站瓶颈:
- 数据量太大,一个机器放不下
- 数据的索引,一个机器的内存也放不下
- 访问量太大,读写混合,性能问题
缓存时代:
memcached(缓存)+mysql+读写分离(垂直拆分)
网站百分之80情况都是在读,每次都要去数据库查询太麻烦,减轻数据库的压力,可以使用缓存来保证效率(Cache)-------解决读的问题
发展过程:优化Mysql底层结构和索引–>文件缓存(IO操作)—>Memcached 当时最热门的技术!
分库分表+水平拆分+mysql集群:
技术和业务发展同时,对技术要求也越来越高
本质: 数据库(读、写)
早些年 MyISAM:表锁,影响效率,高并发下出现严重的锁问题
早些年Innodb:行锁
慢慢地使用分库分表来解决写的压力!并没有多少公司使用
Mysql集群,很好满足了个别需求
最近的年代:
2010-2020 技术发展迅速(定位、音乐、热榜)
Mysql等关系型数据库就不够用了,数据量很多,变化很快
目前一个基本的互联网项目:
为什么要用Nosql
- 用户的个人信息、地理位置、社交网络、用户自己产生的数据、用户日志等等爆发式增长!
这时候可以使用Nosql数据库处理以上的情况!
1.1.2 什么是NoSQL
NoSQL
NoSQL=not only sql
SQL:表格,行,列
泛指非关系型数据库,随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代!尤其是超大规模的高并发的社区!
NoSQL在当今大数据时代发展迅速,REDIS发展是最快的,、
NoSQL特点
-
方便扩展(数据之间没有关系,很好扩展,没有耦合性)
-
大数据量下的高性能(redis一秒可以写8W次,可以读取11w次,NoSQL的缓存级别,是一种细粒度的缓存,性能会比较高!)
-
多类型(不需要事先设计数据库!随取随用!如果是数据量十分大的表,很多人无法设计)
-
传统RDBM和NoSQL:
结构化组织
SQL
数据和关系都存在单独的表中
数据操作语言,数据定义语言,
严格的一致性
基础的事务
…
不仅仅是数据
没有固定的查询语言
键值对存储,列存储,文档存储,图形数据库(社交关系)
最终一致性
CAP定理、 BASE (异地多活)初级架构师!
基本的高性能、高可用、高可扩展
…
扩展:大数据的3v和3高
描述问题:海量、多样、实时
程序要求:高并发、高可用、高性能
1.1.3 NoSQL的分类
KV键值对
- 新浪:redis
- 美团:redis+Tair
- 阿里、百度:redis+memcache
文档型数据库(bson格式json)
-
MongoDB(一般必须掌握)
基于分布式文件存储的数据库,C++编写,处理大量的文档!
是一个介于关系型数据库和非关系型数据库中间的产品,是非关系型数据库中功能最丰富的,最像关系型数据库的!
-
ConthDB
列存储数据库
- HBase
- 分布式文件系统
图形关系数据库
-
不是放图片的,是放关系的:朋友圈社交网络、广告推荐!
-
Ne04j, infoGrid
NoSQL的对比
1.1.4 Redis
redis–Remote Dictionary Server
Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。免费且开源,当前最热门的NoSql之一
能干嘛?
-
内存存储、持久化,内存中是断电即失去,所以说持久化很重要(rdb/aof)
-
效率高、可以用于高速缓存
-
发布订阅系统
-
地图信息分析
-
计时器、计数器(浏览量)
-
…
特性
- 多样的数据类型
- 持久化
- 集群
- 事务
- …
学习中需要用到的东西
- 官网
- 中文网
- 下载地址:通过官网(官方不建议用windows开发使用)
1.2 Redis的安装
1.2.1 windows安装
-
下载安装包 https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100
-
解压到特定文件夹
- 开启Redis:双击运行运行服务(秒级启动)
默认端口:6379
- 使用Redis客户端连接
测试连接:
- ping测试 返回PONG
- 设置一个key值 然后get获取
windows下使用确实简单,但是推荐使用linux去开发使用
1.2.2 Linux安装
1.2.2.1 下载安装包
redis-6.0.6.tar.gz
1.2.2.2 解压安装包
并进入
tar -zxvf redis-6.0.6.tar.gz
配置文件:redis.conf
1.2.2.3 基础环境安装
-
yum install gcc-c++
-
gcc -v
-
make
(需要一点时间)
报错,原因分析:gcc版本过低,升级一下
# 升级gcc版本
$ yum -y install centos-release-scl
$ yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
$ scl enable devtoolset-9 bash
# 升级后查看下版本
$ gcc -v
重新执行make
-
make install
(也可以不用执行,确认一下安装)
所有东西已安装好了
1.2.2.4 确认安装路径
Redis默认安装路径:/usr/local/bin
1.2.2.5 复制配置文件
复制Redis配置文件到当前目录下
# 创建配置文件文件夹并复制配置文件
$ mkdir hconfig
$ cp /opt/redis-6.0.6/redis.conf hconfig
之后就使用这个配置文件进行启动
1.2.2.6 配置后台启动
Redis默认不是后台启动的,需要修改配置文件
# 修改daemonize为yes
$ vim redis.conf
1.2.2.7 启动Redis服务
启动Redis服务
通过指定的配置文件启动服务
redis-server hconfig/redis.conf
1.2.2.8 测试连接
测试连接
使用Redis客户端进行连接
# 连接
$ redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name yunmx # 设置一个key
OK
127.0.0.1:6379> get name # 读取一个key
"yunmx"
127.0.0.1:6379> keys * # 查询所有的key
1) "name"
127.0.0.1:6379>
1.2.2.9 查看进程
查看Redis的进程是否开启
ps -ef |grep redis
1.2.2.10 关闭Redis服务
关闭redis服务
shutdown
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> shutdown
not connected> exit
1.3 Redis性能测试工具
1.3.1 redis-benchmark
官方自带的性能测试工具—>
redis-benchmark
命令参数:
简单测试:
# 测试100个并发连接,每个并发20个请求
$ redis-benchmark -h localhost -p 6379 -c 100 -n 20
1.3.2 如何分析
查看分析:
====== PING_INLINE ======
100000 requests completed in 1.46 seconds # 100000次ping请求在1.46s内完成
100 parallel clients # 100的并发量
3 bytes payload # 3字节数据写入
keep alive: 1 # 保证一台服务器测试(单机性能)
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.00% <= 0.5 milliseconds # 0.5毫秒内完成百分之0的请求
1.37% <= 0.6 milliseconds
11.42% <= 0.7 milliseconds
23.22% <= 0.8 milliseconds
35.29% <= 0.9 milliseconds
47.60% <= 1.0 milliseconds
60.01% <= 1.1 milliseconds
72.40% <= 1.2 milliseconds
84.82% <= 1.3 milliseconds
94.78% <= 1.4 milliseconds
97.65% <= 1.5 milliseconds
98.50% <= 1.6 milliseconds
98.95% <= 1.7 milliseconds
99.28% <= 1.8 milliseconds
99.51% <= 1.9 milliseconds
99.67% <= 2 milliseconds
99.99% <= 3 milliseconds
100.00% <= 3 milliseconds # 3毫秒内完成100%的请求(100000)
68587.11 requests per second # 平均每秒完成的请求数
1.4 Redis基础知识
1.4.1 默认数据库
Redis默认数据库数量有16个,redis.conf配置文件可查看,默认使用的数据库为0
1.4.2 切换数据库
# 使用select 命令进行切换
[root@yunmx bin]# redis-cli -p 6379
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> select 2
OK
127.0.0.1:6379[2]> select 16
(error) ERR DB index is out of range
127.0.0.1:6379[2]> select 15
OK
127.0.0.1:6379[15]>
1.4.3 查看数据库大小
# DBSIZE 查看数据库大小
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> DBSIZE
(integer) 1
127.0.0.1:6379> SET AGE NAN
OK
127.0.0.1:6379> DBSIZE
(integer) 2
127.0.0.1:6379>
1.4.4 查看所有的key
# 使用keys *
127.0.0.1:6379> keys *
1) "AGE"
2) "name"
127.0.0.1:6379>
1.4.5 清空数据库
# 清空当前数据库flushdb
127.0.0.1:6379> keys *
1) "AGE"
2) "name"
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
# 清空所有数据库flush
127.0.0.1:6379> select 0
OK
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> set key1 hejie1
OK
127.0.0.1:6379> set key2 hejie2
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key2"
3) "name"
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> set name yunmx
OK
127.0.0.1:6379[1]> set key1 yunmx1
OK
127.0.0.1:6379[1]> set key2 yunmx2
OK
127.0.0.1:6379[1]> keys *
1) "key1"
2) "key2"
3) "name"
127.0.0.1:6379[1]> flushall
OK
127.0.0.1:6379[1]> keys *
(empty array)
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
1.4.6 判断key是否存在
# 使用exists 判断
127.0.0.1:6379> set name hejie
OK
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists age
(integer) 1 # 存在
127.0.0.1:6379> exists name
(integer) 1
127.0.0.1:6379> exists key1
(integer) 0 # 不存在
127.0.0.1:6379>
1.4.7 移动一个key
# move移动一个key
127.0.0.1:6379> set key1 hejie
OK
127.0.0.1:6379> set key2 hejie1
OK
127.0.0.1:6379> set key3 hejie2
OK
127.0.0.1:6379> move key1 1 # 将key1移动给数据库1
(integer) 1
127.0.0.1:6379> select 1
OK
127.0.0.1:6379[1]> keys *
1) "key1"
127.0.0.1:6379[1]>
1.4.8 设置key的生效时间
# EXPIRE 设置一个key的过期时间,单位时间:s
# TTL 查看一个key的过期时间
127.0.0.1:6379> keys *
1) "key3"
2) "key2"
127.0.0.1:6379> get key3
"hejie2"
127.0.0.1:6379> EXPIRE key3 10 # 设置key3 10秒后过期
(integer) 1
127.0.0.1:6379> ttl key3
(integer) -2 # 表示已经过期
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> keys *
1) "key2"
127.0.0.1:6379>
1.4.9 Redis单线程
单线程为什么这么快?
C语言写的,官方表示每秒的QPS是100000+,基于内存操作,Redis的瓶颈和服务器的内存和网络带宽有关,既然可以用单线程实现,就用单线程喽。
误区:
高性能服务器一定是多线程的?
多线程一定比单线程效率高?
核心:Redis是将所有数据放在内存中的,所以说使用单线程去操作效率就是最高的,因为多线程会产生CPU上下文切换,消耗时间的操作,对于内存系统来说,没有上下文切换,效率就是最高的,多次读写都是在一个CPU上的
1.5 Redis数据类型
五大数据类型
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
使用场景:value除了是字符串还可以是我们的数字
- 计数器
- 统计多单位的数量
- 粉丝数
- 对象缓存存储
1.5.1 String(字符串)
### 常见用法
127.0.0.1:6379> set kye1 yunmx # 设置一个key
OK
127.0.0.1:6379> get key1 # 获取一个key的值
(nil)
127.0.0.1:6379> keys * # 查看当前库的所有key,输出key名
1) "kye1"
127.0.0.1:6379> EXISTS kye1 # 判断key是否存在
(integer) 1
127.0.0.1:6379> EXISTS key1
(integer) 0
127.0.0.1:6379> APPEND kye1 "helloworld" # 向key追加内容,如果key不存在,就设置一个key
(integer) 15
127.0.0.1:6379> get kye1
"yunmxhelloworld"
127.0.0.1:6379> STRLEN kye1 # 获取一个key的长度
(integer) 15
127.0.0.1:6379>
### 自增长用法
127.0.0.1:6379> set count 0
OK
127.0.0.1:6379> get count
"0"
127.0.0.1:6379> incr count # 自增1
(integer) 1
127.0.0.1:6379> get count
"1"
127.0.0.1:6379> incr count
(integer) 2
127.0.0.1:6379> get count
"2"
127.0.0.1:6379> decr count # 自减1
(integer) 1
127.0.0.1:6379> get count
"1"
127.0.0.1:6379> decr count
(integer) 0
127.0.0.1:6379> get count
"0"
127.0.0.1:6379> incrby count 5 # 自增;步长5
(integer) 5
127.0.0.1:6379> get count
"5"
127.0.0.1:6379> decrby count 5 # 自减;步长5
(integer) 0
127.0.0.1:6379> get count
"0"
### 获取字符串范围
127.0.0.1:6379> set key1 yunmxhejie # 设置一个key
OK
127.0.0.1:6379> getrange key1 0 4 # 获取从标0到4范围的字符串
"yunmx"
127.0.0.1:6379> getrange key1 0 -1 # 获取全部的字符串
"yunmxhejie"
127.0.0.1:6379>
### 替换
127.0.0.1:6379> set key1 yunmx123456 # 设置一个key
OK
127.0.0.1:6379> SETRANGE key1 0 hejie # 从下标为0的位置开始替换
(integer) 11
127.0.0.1:6379> get key1
"hejie123456"
127.0.0.1:6379>
### 设置过期时间
127.0.0.1:6379> setex key1 10 yunmx # 设置一个key,过期时间为10秒
OK
127.0.0.1:6379> ttl key1 # 查看key的过期时间
(integer) 7
127.0.0.1:6379> ttl key1 # 返回-2表示已经过期
(integer) -2
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379>
### 不存在设置(分布式锁中会经常用到)
127.0.0.1:6379> setnx key2 redis # setnx:不存在key值则创建
(integer) 1
127.0.0.1:6379> setnx key2 mysql # setnx:存在key值则创建失败
(integer) 0
127.0.0.1:6379> keys *
1) "key2"
127.0.0.1:6379>
### 批量设置和获取
127.0.0.1:6379> mset key1 1 key2 2 key3 3 key4 4 # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "key1"
2) "key4"
3) "key3"
4) "key2"
127.0.0.1:6379> mget key1 key2 key3 key4 # 同时获取多个值
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> msetnx key5 5 key4 4 # 原子性操作:要么一起成功,要么一起失败
(integer) 0
127.0.0.1:6379> get key5
(nil)
127.0.0.1:6379>
### 对象
127.0.0.1:6379> set user:1 {name:yunmx,age:25} # 普通设置对象:key加json字符串
OK
127.0.0.1:6379> get user:1
"{name:yunmx,age:25}"
127.0.0.1:6379> mset user:1:name yunmx user:1:age 25 # 巧妙设计对象;如此设计,是完全可以的
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "yunmx"
2) "25"
### 组合命令
127.0.0.1:6379> getset db mysql # 如果不存在,则设置
(nil)
127.0.0.1:6379> get db
"mysql"
127.0.0.1:6379> getset db MongoDB # 如果存在,返回当前值,再设置新的值
"mysql"
127.0.0.1:6379> get db
"MongoDB"
127.0.0.1:6379>
1.5.2 List(列表)
基本的数据类型
基本的数据类型,列表
在redis里面,list可以完成栈、队列、阻塞队列!
用法:
- 他实际是一个链表,before node after ,left right都可以插入值
- 如果key不存在,创建新的链表
- 如果key存在,新增内容
- 如果移除了所有值,空链表,代表不存在
- 两边插入或者改动值,效率最高;修改中间元素,相对来说,效率偏低一点
消息排队、消息队列(lpush Rpop) 栈(lpush lpop)
### 基础命令
127.0.0.1:6379> LPUSH key1 1 # 将一个值或者多个值添加到头部
(integer) 1
127.0.0.1:6379> LPUSH key1 2
(integer) 2
127.0.0.1:6379> LPUSH key1 3
(integer) 3
127.0.0.1:6379> LRANGE key1 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> LRANGE key1 0 1
1) "3"
2) "2"
127.0.0.1:6379> rpush key1 5 # 将一个值添加到尾部
(integer) 4
127.0.0.1:6379> LRANGE key1 0 -1
1) "3"
2) "2"
3) "1"
4) "5"
### 移除元素
127.0.0.1:6379> LRANGE key1 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> LPOP key1 # 移除列表的第一个元素
"5"
127.0.0.1:6379> rPOP key1 # 移除列表的最后一个元素
"1"
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "3"
3) "2"
### 获取元素、列表长度
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> LINDEX key1 0 # 获取列表中的第一个元素
"4"
127.0.0.1:6379> LINDEX key1 1
"3"
127.0.0.1:6379> LINDEX key1 2
"2"
127.0.0.1:6379> LINDEX key1 3
(nil)
127.0.0.1:6379> LLEN key1 # 获取列表长度
(integer) 3
### 移除指定的值
127.0.0.1:6379> LRANGE key1 0 -1
1) "2"
2) "4"
3) "4"
4) "3"
5) "2"
127.0.0.1:6379> LREM key1 1 2 # 移除一个2
(integer) 1
127.0.0.1:6379> LRANGE key1 0 -1
1) "4"
2) "4"
3) "3"
4) "2"
127.0.0.1:6379> LREM key1 2 4 # 移除2个4
(integer) 2
127.0.0.1:6379> LRANGE key1 0 -1
1) "3"
2) "2"
### 截取
127.0.0.1:6379> LRANGE key1 0 -1
1) "15"
2) "14"
3) "13"
4) "12"
5) "11"
6) "10"
127.0.0.1:6379> LTRIM key1 0 3 # 通过下标截取指定的长度,list已经被改变了
OK
127.0.0.1:6379> LRANGE key1 0 -1
1) "15"
2) "14"
3) "13"
4) "12"
### 组合命令:rpoppush
127.0.0.1:6379> rpush list1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> LRANGE list1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> rpoplpush list1 list2 # 移除列表中的最后一个元素,并加入到新的列表中
"5"
127.0.0.1:6379> LRANGE list1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> LRANGE list2 0 -1
1) "5"
127.0.0.1:6379>
### 元素更新
127.0.0.1:6379> EXISTS list1 # 判断列表是否存在
(integer) 0
127.0.0.1:6379> lset list1 0 yunmx # 不存在报错
(error) ERR no such key
127.0.0.1:6379> lpush lista1 1 2 3 4
(integer) 4
127.0.0.1:6379> LRANGE list1 0 0
(empty array)
127.0.0.1:6379> LRANGE list1 0 -1
(empty array)
127.0.0.1:6379> LRANGE lista1 0 0
1) "4"
127.0.0.1:6379> lset lista1 0 yunmx # 存在修改当前下标的值
OK
127.0.0.1:6379> LRANGE lista1 0 0
1) "yunmx"
### 元素插入
127.0.0.1:6379> LRANGE lista1 0 -1
1) "yunmx"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> LINSERT lista1 before yunmx hejie # 插入当前元素的前面
(integer) 5
127.0.0.1:6379> LINSERT lista1 after yunmx hejie1 # 插入当前元素的后面
(integer) 6
127.0.0.1:6379> LRANGE lista1 0 -1
1) "hejie"
2) "yunmx"
3) "hejie1"
4) "3"
5) "2"
6) "1"
1.5.3 Set(集合)
set中的值不能重复d ,是无序的
### 基础用法
127.0.0.1:6379> sadd set1 yunmx # 往集合里面存值
(integer) 1
127.0.0.1:6379> sadd set1 hejie1
(integer) 1
127.0.0.1:6379> sadd set1 hejie01
(integer) 1
127.0.0.1:6379> sadd set1 hejie02
(integer) 1
127.0.0.1:6379> sadd set1 hejie02
(integer) 0
127.0.0.1:6379> SMEMBERS set1 # 查询集合里面所有的元素
1) "yunmx"
2) "hejie02"
3) "hejie01"
4) "hejie1"
127.0.0.1:6379> SISMEMBER set1 hejie # 判断元素是否存在
(integer) 0
127.0.0.1:6379> SISMEMBER set1 hejie5
(integer) 0
127.0.0.1:6379> SISMEMBER set1 hejie1 # 存在返回1
(integer) 1
### 获取集合中的元素的个数
127.0.0.1:6379> SCARD set1
(integer) 4
127.0.0.1:6379> sadd set1 hejie09
(integer) 1
127.0.0.1:6379> SCARD set1
(integer) 5
### 移除set集合中的指定元素
127.0.0.1:6379> SREM set1 yunmx # 移除set集合中的:yunmx
(integer) 1
127.0.0.1:6379> SCARD set1
(integer) 4
127.0.0.1:6379> SMEMBERS set1
1) "hejie01"
2) "hejie09"
3) "hejie1"
4) "hejie02"
### 抽随机用法(无序不重复)
127.0.0.1:6379> SRANDMEMBER set1 # 随机抽选出一个元素
"hejie02"
127.0.0.1:6379> SRANDMEMBER set1
"hejie09"
127.0.0.1:6379> SRANDMEMBER set1
"hejie02"
127.0.0.1:6379> SRANDMEMBER set1
"hejie09"
127.0.0.1:6379> SRANDMEMBER set1
"hejie01"
127.0.0.1:6379> SRANDMEMBER set1 2 # 随机抽选出指定的数量元素
1) "hejie1"
2) "hejie01"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "hejie09"
2) "hejie02"
127.0.0.1:6379> SRANDMEMBER set1 2
1) "hejie1"
2) "hejie01"
### 随机移除元素
127.0.0.1:6379> SMEMBERS set1
1) "hejie01"
2) "hejie09"
3) "hejie1"
4) "hejie02"
127.0.0.1:6379> spop set1 # 随机移除一个元素
"hejie02"
127.0.0.1:6379> spop set1
"hejie1"
127.0.0.1:6379> SMEMBERS set1
1) "hejie01"
2) "hejie09"
### 指定的值移动另一个集合中
127.0.0.1:6379> SMEMBERS set1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SMEMBERS set2
1) "er"
2) "san"
3) "yi"
127.0.0.1:6379> smove set1 set2 1 # 将set1中的元素移动到set2,需要移动的值为set1中的1
(integer) 1
127.0.0.1:6379> SMEMBERS set1
1) "2"
2) "3"
127.0.0.1:6379> SMEMBERS set2
1) "1"
2) "er"
3) "san"
4) "yi"
### 共同关注:B站,微博(并集)
# 数字集合:差集、交集、并集
127.0.0.1:6379> sadd set1 1 2 3 4 5 6
(integer) 6
127.0.0.1:6379> sadd set2 4 5 6 7 8 9
(integer) 6
127.0.0.1:6379>
127.0.0.1:6379> SDIFF set1 set2 # 差集:set1中的元素不存在于set2
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> SINTER set1 set2 # 交集
1) "4"
2) "5"
3) "6"
127.0.0.1:6379> SDIFF set2 set1
1) "7"
2) "8"
3) "9"
127.0.0.1:6379> SUNION set1 set2 # 并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"
1.5.4 Hash(哈希)
Map集合,key-Map集合,本质和string没有太大区别,还是一个简单的key-vlaue
应用
- 用户信息之类的
- 经常变动的信息
- 更适合存储对象,string更适合字符串存储
### 基础用法
127.0.0.1:6379> hset hash1 name yunmx # 设置一个key-vlaue
(integer) 1
127.0.0.1:6379> hget hash1 name # 获取一个字段值
"yunmx"
127.0.0.1:6379> hmset hash1 name hejie age 28 # 设置多个
OK
127.0.0.1:6379> hgetall hash1 # 获取所有元素
1) "name"
2) "hejie"
3) "age"
4) "28"
127.0.0.1:6379> hget hash1 age # 获取一个字段值
"28"
### 删除一个值
127.0.0.1:6379> HDEL hash1 name # 删除指定的key字段
(integer) 1
127.0.0.1:6379> hgetall hash1
1) "age"
2) "28"
### 获取hash所有的长度(键值对)
127.0.0.1:6379> HLEN hash1 # 获取hash中的键值对数量
(integer) 1
127.0.0.1:6379> hset hash1 age 18
(integer) 0
127.0.0.1:6379> hset hash1 age1 19
(integer) 1
127.0.0.1:6379> hset hash1 name hejie
(integer) 1
127.0.0.1:6379> HLEN hash1
(integer) 3
### 判断hash中字段是否存在
127.0.0.1:6379> HGETALL hash1
1) "age"
2) "18"
3) "age1"
4) "19"
5) "name"
6) "hejie"
127.0.0.1:6379> HEXISTS hash1 age # 判断哈希中是否存在age字段
(integer) 1
127.0.0.1:6379> HEXISTS hash1 yunmx
(integer) 0
### 只获取所有的字段或值
127.0.0.1:6379> hkeys hash1
1) "age"
2) "age1"
3) "name"
127.0.0.1:6379> HVALS hash1
1) "18"
2) "19"
3) "hejie"
### 指定自增
127.0.0.1:6379> HINCRBY hash1 age 1 # 自增1
(integer) 19
127.0.0.1:6379> HINCRBY hash1 age -1
(integer) 18
127.0.0.1:6379> hsetnx hash1 age 1 # 如果存在,不设置
(integer) 0
127.0.0.1:6379> hsetnx hash1 age2 1 # 不存在,则添加新的键值对
(integer) 1
1.5.5 Zset(有序集合)
在set的基础上增加了一个值,多了一个计数位
场景思路:
- set能做的东西它都能做,存储班级成绩表、工资表排序
- 普通消息1,重要消息2,带权重进行判断
- 排行榜应用实现:TOP N
### 基础用法
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two
(integer) 1
127.0.0.1:6379> zadd myset 3 three
(integer) 1
127.0.0.1:6379> zadd myset 4 four 5 five # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1 # 查看所有的值
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
### 排序如何实现
# 用法:从最小到最大
# ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> zadd salary 12000 yunmx # 添加4个用户
(integer) 1
127.0.0.1:6379> zadd salary 11000 hejie
(integer) 1
127.0.0.1:6379> zadd salary 10000 hejie01
(integer) 1
127.0.0.1:6379> zadd salary 9000 yunmx01
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # -inf +inf 负无穷到正无穷 从小到大
1) "yunmx01"
2) "hejie01"
3) "hejie"
4) "yunmx"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # withscores表示带一些其他参数说明
1) "yunmx01"
2) "9000"
3) "hejie01"
4) "10000"
5) "hejie"
6) "11000"
7) "yunmx"
8) "12000"
### 移除元素
127.0.0.1:6379> ZRANGE salary 0 -1 # 查看所有的元素
1) "yunmx01"
2) "hejie01"
3) "hejie"
4) "yunmx"
127.0.0.1:6379> zrem salary yunmx01 # 移除特定的元素
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"
### 获取有序集合中的个数
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"
127.0.0.1:6379> ZCARD salary
(integer) 3
### 按区间计算
127.0.0.1:6379> ZRANGE salary 0 -1
1) "hejie01"
2) "hejie"
3) "yunmx"
127.0.0.1:6379> zcount salary 1000 9000 # 查看1000到9000之间的数量
(integer) 0
127.0.0.1:6379> zcount salary 1000 10000 # 查看1000到1-000之间的数量值
(integer) 1
1.5.6 geospatial(地理空间)
城市经纬度查询:http://www.jsons.cn/lngcode/
- 朋友圈的定位
- 附近的人
- 打车距离计算
- …
GEO实现的底层原理其实就是Zset!可以使用Zset来操作GEO!
只有6个命令
- GEOADD
- GEODIST
- GEOHASH
- GEOPOS
- GEORADIUS
- GEORADIUSBYMEMBER
### 添加位置
# geoadd key longitude(经度) latitude(维度) member [longitude latitude member ...] 可添加多个
# 参数:key 值(经度 维度 名称)
127.0.0.1:6379> geoadd china:city 104.065735 30.659462 chengdu
(integer) 1
127.0.0.1:6379> geoadd china:city 106.504962 29.533155 chongqing
(integer) 1
### 获取当前定位经纬度
127.0.0.1:6379> GEOPOS china:city chengdu # 获取成都的经纬度
1) 1) "104.06573742628097534"
2) "30.65946118872339099"
127.0.0.1:6379> GEOPOS china:city chongqing # 获取重庆的经纬度
1) 1) "106.50495976209640503"
2) "29.53315530684997015"
### 计算两个地方的距离
#单位:m/km/mi/ft
127.0.0.1:6379> geodist china:city chengdu chongqing m # 计算出成都到重庆的直线距离 单位m
"266056.2971"
127.0.0.1:6379> geodist china:city chengdu chongqing km # 计算出成都到重庆的直线距离 单位km
"266.0563"
### 以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。
# GEORADIUS:
127.0.0.1:6379> GEORADIUS china:city 103 30 1 km # 以103 30这个点为中心,以1km为半径
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 2 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 20 km
(empty array)
127.0.0.1:6379> GEORADIUS china:city 103 30 200 km
1) "chengdu-qingyangqu"
2) "chengdu"
3) "chengdu-jinjiangqu"
127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km
1) "chengdu-qingyangqu"
2) "chongqing"
3) "chengdu"
4) "chengdu-jinjiangqu"
127.0.0.1:6379> GEORADIUS china:city 103 30 20000 km withcoord # 附带显示经纬度
1) 1) "chengdu-qingyangqu"
2) 1) "104.06151026487350464"
2) "30.67387107851423167"
2) 1) "chongqing"
2) 1) "106.50495976209640503"
2) "29.53315530684997015"
3) 1) "chengdu"
2) 1) "104.06573742628097534"
2) "30.65946118872339099"
4) 1) "chengdu-jinjiangqu"
2) 1) "104.06573742628097534"
2) "30.65946118872339099"
### 找出位于指定范围内的元素,中心点是由给定的位置元素决定
# GEORADIUSBYMEMBER
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km # chengdu城市方圆10km的坐标
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 100 km
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 10 km
1) "chengdu"
2) "chengdu-jinjiangqu"
3) "chengdu-qingyangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km
1) "chengdu-qingyangqu"
2) "chongqing"
3) "chengdu"
4) "chengdu-jinjiangqu"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city chengdu 1000 km withcoord # 附带坐标信息
1) 1) "chengdu-qingyangqu"
2) 1) "104.06151026487350464"
2) "30.67387107851423167"
2) 1) "chongqing"
2) 1) "106.50495976209640503"
2) "29.53315530684997015"
3) 1) "chengdu"
2) 1) "104.06573742628097534"
2) "30.65946118872339099"
4) 1) "chengdu-jinjiangqu"
2) 1) "104.06573742628097534"
2) "30.65946118872339099"
### Redis GEOHASH 命令 - 返回一个或多个位置元素的 Geohash 表示 不常用
1.5.7 HyperLogLog(基数统计)
基数:不重复的元素个数,可以接受误差
A(1 2 3 45 5 78)
B(3 4 6 7 8 9 0 9 78 )
基数统计的算法
-
网页的UV(一个人访问一个网站,但是还是算做一个)
传统的方式:set保存用户的ID,然后就可以统计set中的元素数量作为标准判断,如果保存大量的用户ID,就会比较麻烦,我们的目的是为了计数而不是保存ID
-
占用内存是固定的,2^64不同的元素技术,只需要废12KB内存
0.81%的错误率!统计UV时候,可以忽略不计
127.0.0.1:6379> PFADD key1 a 1 2 3 4 b c 6 7 9 # 设置一个HyperLOGLOG
(integer) 1
127.0.0.1:6379> PFCOUNT key1 # 查看里面不重复的数量个数
(integer) 10
127.0.0.1:6379> PFADD key2 a a 1 2 3 4 b c 6 7 9 10 10 101 10 10 8 6
(integer) 1
127.0.0.1:6379> PFCOUNT key2
(integer) 13
127.0.0.1:6379> PFMERGE key3 key1 key2 # 合并两个key的元素数量
OK
127.0.0.1:6379> PFCOUNT key3 # 查看不重复的元素数量
(integer) 13
1.5.8 Bitmap(位图)
位存储0和1
2个状态的都可以使用此数据类型
- 活跃、不活跃
- 感染人数、未感染人数
127.0.0.1:6379> SETBIT key 0 1 # 使用Bitmaps记录一周的打卡
(integer) 0
127.0.0.1:6379> SETBIT key 1 0
(integer) 0
127.0.0.1:6379> SETBIT key 2 0
(integer) 0
127.0.0.1:6379> SETBIT key 3 1
(integer) 0
## 查看某一天是否有打卡
127.0.0.1:6379> getbit key 3
(integer) 1
127.0.0.1:6379> getbit key 2
(integer) 0
### 统计操作
# 统计打开天数
127.0.0.1:6379> BITCOUNT key
(integer) 2
1.6 Redis事务
事务可以一次执行多个命令, 并且带有以下两个重要的保证:
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行
1.6.1 基本的事务操作
Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,事务执行中,按照顺序执行
Redis单条命令保证原子性,但是事务不保证原子性!
- 一次性
- 顺序性
- 排他性
- 没有隔离级别的概念,没有幻读、脏读…
---入队---
command1
command2
command2
---执行---
三个阶段:
- 开启事务(MULTI)
- 命令入队(commands)
- 执行事务(EXEC)
### 正常执行
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set key1 1 # 命令入队
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> get key1
QUEUED
127.0.0.1:6379> get key2
QUEUED
127.0.0.1:6379> exec # 事务执行,按照顺序执行
1) OK
2) OK
3) OK
4) "1"
5) "2"
### 放弃事务
127.0.0.1:6379> MULTI # 开启事务
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> DISCARD # 取消事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI
127.0.0.1:6379> get key1 # 放弃事务取不到相关的值
(nil)
### 事务错误一:命令有错,事务中所有的命令都不会被执行!
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> getset key3 # 错误的命令,直接报错
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors. # 执行事务报错
127.0.0.1:6379> get key1 # 结果是所有的命令都不会执行
(nil)
### 事务错误二:运行异常,如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set key1 "yunmx"
QUEUED
127.0.0.1:6379> incr key1
QUEUED
127.0.0.1:6379> set key2 2
QUEUED
127.0.0.1:6379> set key3 3
QUEUED
127.0.0.1:6379> get key3
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) "3"
127.0.0.1:6379> get key2
"2"
1.6.2 乐观锁
监控:watch
悲观锁:很悲观;认为任何时候都会出现问题,无论做什么都加锁,影响性能
乐观锁:很乐观,任务任何时候都不会出现问题,所以不会上锁,更新数据的时候去判断一下,在此期间,是否有人修改过这个数据,mysql用的version字段!
### Redis的测试监视
127.0.0.1:6379> set mymoney 1000
OK
127.0.0.1:6379> set myout 0
OK
127.0.0.1:6379> WATCH mymoney # 监视
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRby mymoney 20
QUEUED
127.0.0.1:6379> INCRBY myout 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,数据期间没有发生变动,正常执行了
1) (integer) 980
2) (integer) 20
127.0.0.1:6379>
### 使用watch当乐观锁,测试多线程修改值后事务提交失败
# 第一个进程开启事务
127.0.0.1:6379> watch mymoney
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY mymoney 10
QUEUED
127.0.0.1:6379> INCRBY myout 10
QUEUED
127.0.0.1:6379> exec # 未提交前执行第二个事务进行数据的修改
(nil) # 事务提交后返回修改失败,
127.0.0.1:6379>
# 第二个进程修改我们的值
127.0.0.1:6379> get mymoney
"980"
127.0.0.1:6379> set mymoney 20
OK
127.0.0.1:6379> get mymoney
"20"
127.0.0.1:6379> set mymoney 1000
OK
127.0.0.1:6379> get mymoney
"1000"
### 可以使用unwatch解锁,再次使用watch获取最新的值,再去执行事务
1.7 Redis配置文件详解
1.7.1 redis.conf
启动时候就是通过配置文件来启动的
配置文件对大小写不敏感
包含其他配置文件
网络
bind 127.0.0.1 # 绑定的ip
protected-mode yes # 保护模式
port 6379 # 端口设置
GENERAL通用配置
daemonize yes # 已守护进程方式运行,默认是no,我们需要自己开启
pidfile /var/run/redis_6379.pid # 如果以后台的方式运行,我们就需要指定一个pid文件
# 日志(4个级别)
# Specify the server verbosity level.
# This can be one of:
# 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 "" #日志的文件位置名
databases 16 # 默认的数据库数量,16个
always-show-logo yes # 是否总是显示logo 可以不显示,默认显示
快照SNAPSHOTTING
# 规则
save 900 1 # 如果900秒内,至少有一个key进行修改,我们就进行持久化操作
save 300 10
save 60 10000
# 我们可以设置自己的一个规则
stop-writes-on-bgsave-error yes # 持久化出现错误的时候,是否继续进行工作,默认是yes
rdbcompression yes # 是否压缩rdb文件,压缩就会消耗CPU资源
rdbchecksum yes # 是否校验rdb文件,出错会进行修复
dir ./ # rdb文件保存的目录
快照
持久化,在规定的时间内,执行了多少次操作,则会持久化到文件,.rdb .aof,如果没有持久化,那么数据就会断电及失
主从复制 REPLICATION
安全SECURITY
# requirepass foobared 默认没有密码
# 可以通过修改配置文件来设置密码
# 比如:
requirepass 123456
# 验证
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
# 也可以通过命令进行密码设置
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
127.0.0.1:6379> config set requirepass "110"
OK
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 110
OK
127.0.0.1:6379> ping
PONG
127.0.0.1:6379>
客户端CLIENTS
# 客户端的一些限制
maxclients 10000 # 设置能连接上redis的客户端数量限制
maxmemory <bytes> # redis配置的最大内存容量
maxmemory-policy noeviction # 内存到达上限后的处理策略
# 移除一些过期的key
# 报错
# ...
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODE模式 aof配置
appendonly no # 默认不开启aof模式,默认使用的是rdb方式持久化的
appendfilename "appendonly.aof" # 持久化的文件的名字
# appendfsync always # 每次修改值都会写入,速度比较慢
appendfsync everysec # 每秒执行一次sync,可能会丢失1s的数据!
# appendfsync no # 不同步,这个时候操作系统自己同步数据,速度最快!
1.8 Redis持久化
Redis是内存数据库,如果不将内存中的数据库状态保存到硬盘中,那么一旦服务器进程退出,服务器中的数据状态也会消失,所以Redis提供了持久化功能
1.8.1 RDB(Redis DataBase)
RDB
在指定的时间间隔内,将内存中的数据集体快照写入磁盘中,也就是进行Snapshot快照,它恢复时是将快照文件直接读到内存里。
操作
单独创建(fork)一个子进程来进行持久化,会将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程不进行任何IO操作。这就确保了极高的性能,如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高校。RDB的缺点是嘴鸥一次持久化后的数据可能丢失。我们默认的就是RDB,一般情况下不需要修改此配置!
rdb保存的文件是dump.rdb
# 触发机制
1.save的规则满足的情况下
2.执行了FLUSHALL命令
3.退出redis也会触发
如何恢复rdb文件
- 只需要将rdb文件放在redis启动目录下即可,启动的时候会自动检查dump.rdb恢复其中的数据
# 查看存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
优缺点
优点
- 适合大规模的数据恢复
- 如果你对数据的完整性要求不高
缺点
- 需要一定的时间间隔进行操作!如果redis意外宕机,最后一次修改的数据就没有了
- fork进程的时候,会占用一定的内存空间
1.8.2 AOF(Append Only File)
将我们的所有命令都记录下来,history,恢复的时候就把这个文件全部执行一遍
AOF
以日志的形式来记录每个操作,将redis执行过的所有指令记录下来(读操作不会记录),只许追加文件但不可改写文件,redis启动之后会读取文件重新构建数据,换言之,redis重启的话根据日志文件的内容将写指令从前到 后执行一次以完成数据的恢复工作
aof保存的文件是appendonly.aof文件
# 破坏aof文件后,尝试连接
[root@yunmx bin]# redis-cli
Could not connect to Redis at 127.0.0.1:6379: Connection refused
# 使用检测文件自动修复这个AOF文件:redis-check-aof
[root@yunmx bin]# redis-check-aof --fix appendonly.aof
0x 62: Expected \r\n, got: 6173
AOF analyzed: size=122, ok_up_to=23, diff=99
This will shrink the AOF from 122 bytes, with 99 bytes, to 23 bytes
Continue? [y/N]: y
Successfully truncated AOF
优缺点
优点
- 每次修改都同步,文件完整性更好,
- 每秒同步一次,这样会丢失一秒的数据
- 从不同步,效率最高的!
缺点
- 相对于数据文件来说,aof远远大于rdb,修复的速度比rdb慢
- aof运行效率要比rdb慢,会进行IO操作
扩展:
-
RBD
RDB持久化方式能够在指定的时间间隔能对你的数据进行快照存储。 -
AOF
AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以Redis协议追加保存每次写的操作到文件末尾。Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。 -
只做缓存
如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.。 -
同时开启两种持久化方式
- 在这种情况下,当Redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整
- RDB的数据不实时,同时使用两者时服务器重启也只会找AOF文件。那要不要只使用AOF呢?作者建议不要,因为RDB更适合用于备份数据库(AOF在不断变化不好备份),快速重启,而且不会有AOF可能潜在的bug,留着作为一个万一的手段。
-
性能建议
- 因为RDB文件只用作后备用途,建议只在Slave上持久化RDB文件,而且只要15分钟备份一次就够了,只保留save 900 1这条规则。
- 如果Enalbe AOF,好处是在最恶劣情况下也只会丢失不超过两秒数据,启动脚本较简单只load自己的AOF文件就可以了。代价一是带来了持续的IO,二是AOF rewrite的最后将rewrite过程中产生的新数据写到新文件造成的阻塞几乎是不可避免的。只要硬盘许可,应该尽量减少AOF rewrite的频率,AOF重写的基础大小默认值64M太小了,可以设到5G以上。默认超过原大小100%大小时重写可以改到适当的数值。
- 如果不Enable AOF ,仅靠Master-Slave Replication 实现高可用性也可以。能省掉一大笔IO也减少了rewrite时带来的系统波动。代价是如果Master/Slave同时倒掉,会丢失十几分钟的数据,启动脚本也要比较两个Master/Slave中的RDB文件,载入较新的那个。新浪微博就选用了这种架构。
1.9 Redis发布订阅
1.9.1 Redis发布订阅
订阅、发布消息图:
Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息
Redis客户端可以订阅任意数量的频道
第一个是:消息发送者 第二个是:频道 第三个是:消息订阅者
下图展示了频道 channel1 , 以及订阅这个频道的三个客户端 —— client2 、 client5 和 client1 之间的关系:
当有新消息通过 PUBLISH 命令发送给频道 channel1 时, 这个消息就会被发送给订阅它的三个客户端:
Redis 发布订阅命令
下表列出了 redis 发布订阅常用命令:
- PSUBSCRIBE pattern [pattern …]
订阅一个或多个符合给定模式的频道。- PUBSUB subcommand [argument [argument …]]
查看订阅与发布系统状态。- PUBLISH channel message
将信息发送到指定的频道。- PUNSUBSCRIBE [pattern [pattern …]]
退订所有给定模式的频道。- SUBSCRIBE channel [channel …]
订阅给定的一个或多个频道的信息。- UNSUBSCRIBE [channel [channel …]]
指退订给定的频道。
# 测试
# 订阅一个频道
127.0.0.1:6379> SUBSCRIBE yunmx # 订阅一个频道到yunmx
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yunmx"
3) (integer) 1
# 新窗口发送一个消息:发布者
[root@yunmx ~]# redis-cli
127.0.0.1:6379> PUBLISH yunmx test # 发布者发布消息到指定名称的频道
(integer) 1
# 接收到的消息
127.0.0.1:6379> SUBSCRIBE yunmx
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "yunmx"
3) (integer) 1
1) "message" # 接收的消息
2) "yunmx" # 哪个频道
3) "test" # 消息内容
Redis发布订阅的原理
使用C实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现逻辑,加深对redis的理解。(俺也不懂C,俺也不是开发,咋整)
通过PUBLISH/SUBSCRIBE/PSUBSCRIBE等命令实现发布订阅功能
- SUBSCRIBE:通过此命令订阅频道以后,redis-server里维护一个字典,字典的键就是一个个channel(频道),而字典的值则是一个链表,链表中保存了所有订阅这个频道的客户端。-----就是将客户端添加到给定channel的订阅链表中
- PUBLISH:向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查询了记录了订阅这个频道所有的客户端连接,遍历整个链表,将消息发送给所有订阅者
- PUB、SUB:就是发布、订阅,在redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值进行了消息发布以后,所有订阅它的客户端都会收到相应的消息,这一功能明显的用法就是用作实时消息系统,比如普通的即时聊天、群聊等功能
1.9.2 使用场景
- 实时消息系统
- 实时聊天------频道当作聊天室,将信息回显给所有人即可
- 订阅,公众号订阅等
- 稍微复杂的场景我们会使用消息中间件(MQ)
2.0 Redis主从复制
2.0.1 概念
主从复制
是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。master以写为主,slave以读为主。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
主从复制的作用
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。
一般来说,要将redis运用于生产项目中,只使用一台redis是万万不能的
- 从结构上,单个redis服务器会发生单点故障,且单机处理所有的请求会导致负载过大
- 从容量上,单机内存容量有限,一般来说,单台redis最大使用内存不应该超过20G
2.0.2 配置主从
主从复制,读写分离!80%的情况都是在进行读操作!减缓服务器的压力,架构中经常使用!一主二 从!
测试场景
环境配置
只配置从库,不配置主库!
# 启动一个redis,查看信息
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 从机数量
master_replid:fa0e795a3e369ed7e46a40ee8818a51255ab6df3
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
复制一个配置文件,修改一下端口号、日志保存的文件名、rdb文件名、pid文件 ,在启动2个redis
[root@yunmx bin]# redis-server redis-conf/redis.conf1 # 6380
6038:C 12 Dec 2021 13:06:38.166 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
6038:C 12 Dec 2021 13:06:38.166 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6038, just started
6038:C 12 Dec 2021 13:06:38.166 # Configuration loaded
[root@yunmx bin]# redis-server redis-conf/redis.conf2 # 6381
6045:C 12 Dec 2021 13:06:39.640 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
6045:C 12 Dec 2021 13:06:39.641 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=6045, just started
6045:C 12 Dec 2021 13:06:39.641 # Configuration loaded
未配置主从之前,三个节点都是主节点
认老大
一主(79)二从(80,81)
# 6380配置
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 # 认本机的6379端口服务的redis做老大
OK
127.0.0.1:6380> info replication
# Replication
role:slave # 变成了从节点
master_host:127.0.0.1 # 主节点的信息
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:0
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:0
# 查看主机的信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=168,lag=0 # 从机的信息
slave1:ip=127.0.0.1,port=6381,state=online,offset=168,lag=0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:168
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:168
真实的主从配置应该在配置文件中配置,这样的话是永久的,上述使用的是命令,只是暂时的
replicaof <masterip> <masterport> # 主机的IP地址和端口号
masterauth <master-password> # 如果设定有密码,配置密码即可
2.0.3 特性验证
主机可以设置值,从机不能写,主机中所有信息和数据,从机都会保存
# 主机设置一个key
127.0.0.1:6379> set key1 yunmx
OK
127.0.0.1:6379>
# 从机中也会有,从机无法设置key
127.0.0.1:6380> keys *
1) "key1"
127.0.0.1:6380> get key1
"yunmx"
127.0.0.1:6380> set key2 yunmx2
(error) READONLY You can't write against a read only replica.
127.0.0.1:6380>
老大宕机后,从机还是从机,只是会显示主机状态不正常;主机恢复,从机依旧可以直接获取到主机写入的信息,保证了一定的高可用性
如果使用的是我们命令行配置的主从,如果从机宕机后,从机就会脱离主从了,需要再次命令行配置从机,变成从机以后,就能获取到key的值了
# 停掉6379主机服务,查看从机集群状态
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:1065
master_link_down_since_seconds:10
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:55c3146a93d180ad5aa89e4d64ff894a451e77e5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1065
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:1065
# 恢复6379主机服务,验证是否还会同步数据
[root@yunmx ~]# redis-cli -p 6379
127.0.0.1:6379> set key2 yunmx2 # 恢复主节点,设置一个key
OK
127.0.0.1:6379>
# 从节点读取主机恢复后设置的key
127.0.0.1:6380> get key2 # 能够正常读取
"yunmx2"
# 停止从机服务,主机设定一个key,在恢复从机,进行验证
127.0.0.1:6379> set key3 yunmx3
OK
127.0.0.1:6380> get key3 # 从机无法获得key3的数据
(nil)
127.0.0.1:6381> get key3
"yunmx3"
2.0.4 复制原理
Slave启动成功连接到master后会发送一个sync同步命令
Master接到命令后,确定后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步
全量复制:slave服务将接收到数据库文件数据后,将其存盘并加载到内存中
增量复制:Mster继续将新的所有收集到的修改命令依次传给slave,完成同步
但是只要重新连接master,一次完全同步将被自动执行!我们的数据一定可以在从机中看到!
2.0.5 层层链路
上一个M链接下一个S!
可以完成主从复制!
如果没有79,这个时候能不能选择一个老大出来呢,这个时候需要手动去配置!
谋朝篡位:slaveof no one
使自己变成主机!如果老大回来了,也是需要手动配置
2.1 Redis哨兵模式
自动版选老大的模式
2.1.1 概述
-
主从切换技术的方式是:当主服务器宕机后,需要手动把一台服务器切换为主服务,这就需要人工干预,费时费力,还会造成一段时间内服务不能使用。这不是一种推荐的方式,更多的是我们考虑哨兵模式,Redis从2.8开始正式提供Sentinel(哨兵)架构来解决这个问题。
-
能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库
-
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,是一个独立的进程,作为进程,它会独立运行。原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
2.1.2 基本架构
哨兵模式的作用:
- 通过发送命令,让Redis服务器返回监控运行状态,包括主服务和从服务
- 当哨兵检测到master宕机后,会自动将slave切换成master,然后通过发布订阅模式通过其他的从服务器,修改配置文件,让他们切换主机
一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控,各个哨兵之间还会进行监控,这样就形成了多哨兵模式
使用哨兵模式,至少都会启动6个进程
假设主服务宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务不可用,这个现场叫主观下线。当后面的哨兵也检测到主服务不可用,并且数量达到一定值后,那么哨兵就会进行一次投票,投票的结果由一个哨兵发起,进行failover故障转移操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务实现切换主机,这个过程被称为客观下线
2.1.3 场景测试
我们目前测试的架构是一主二从
配置哨兵模式的配置文件
# 新建配置文件并编辑以下内容
sentinel monitor myredis 127.0.0.1 6379 1# 语法:sentinel monitor 被监控的名称 host port 1(1代表主机宕机后,从机投票让谁来接替成为主机,)
启动哨兵
[root@yunmx bin]# redis-sentinel redis-conf/sentinel.conf # 启动一个哨兵
12680:X 12 Dec 2021 15:13:42.570 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
12680:X 12 Dec 2021 15:13:42.570 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=12680, just started
12680:X 12 Dec 2021 15:13:42.570 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 12680
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
12680:X 12 Dec 2021 15:13:42.571 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
12680:X 12 Dec 2021 15:13:42.575 # Sentinel ID is ea7ccf0119a4cf2873cf3bb108da5c7af86d36bd
12680:X 12 Dec 2021 15:13:42.575 # +monitor master myredis 127.0.0.1 6379 quorum 1
12680:X 12 Dec 2021 15:13:42.575 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:13:42.580 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
手动宕机测试
# 关掉主机
127.0.0.1:6379> SHUTDOWN
not connected>
# 哨兵监控的一些信息
12680:X 12 Dec 2021 15:16:49.174 # +failover-state-select-slave master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.241 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.241 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:49.324 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.181 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.181 # +failover-state-reconf-slaves master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:50.237 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.182 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.234 # +failover-end master myredis 127.0.0.1 6379
12680:X 12 Dec 2021 15:16:51.234 # +switch-master myredis 127.0.0.1 6379 127.0.0.1 6381 # 哨兵显示主机自动切换到了6381
12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ myredis 127.0.0.1 6381
12680:X 12 Dec 2021 15:16:51.234 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
12680:X 12 Dec 2021 15:17:21.244 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
# 检查6381主机的信息
127.0.0.1:6381> info replication
# Replication
role:master # 变成了master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=21944,lag=0
master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215
master_replid2:367d9493cb151b433bd535ad9e49603d1fa35013
master_repl_offset:21944
second_repl_offset:11812
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:21944
如果master宕机以后,这个时候就会从从机中随机选择一个服务器(有一个自己的投票算法)作为主机
主机恢复
# 重新开启之前宕机的主机。观察哨兵的反应
12680:X 12 Dec 2021 15:23:41.461 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ myredis 127.0.0.1 6381
# 查看先前主机的信息
127.0.0.1:6379> info replication
# Replication
role:slave # 变成从机
master_host:127.0.0.1
master_port:6381
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:41645
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:a4719b795f6c088ed1a11408a2bc52cc48ece215
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:41645
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:39205
repl_backlog_histlen:2441
# 只能归并到新的主机下,当作从机,这就是哨兵模式的规则!
优缺点
-
哨兵集群,基于主从复制模式,所有的主从配置优点它多有
-
主从可以切换,故障可以转移,系统的可用性会更好
-
就是主从模式的升级,手动到自动,更加健壮!
-
不好在线扩容,集群容量一旦达到上线,在线扩容就十分麻烦!
-
实现哨兵模式的配置其实很麻烦的,里面有很多选择!
哨兵模式的全部配置
<后续进行详细学习>
# Example sentinel.conf
# port <sentinel-port>
# The port that this sentinel instance will run on
# sentinel实例运行的端口
port 26379 # 哨兵进程运行的端口号
# sentinel announce-ip <ip>
# sentinel announce-port <port>
#
# The above two configuration directives are useful in environments where,
# because of NAT, Sentinel is reachable from outside via a non-local address.
#
# When announce-ip is provided, the Sentinel will claim the specified IP address
# in HELLO messages used to gossip its presence, instead of auto-detecting the
# local address as it usually does.
#
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
# will announce the specified TCP port.
#
# The two options don't need to be used together, if only announce-ip is
# provided, the Sentinel will announce the specified IP and the server port
# as specified by the "port" option. If only announce-port is provided, the
# Sentinel will announce the auto-detected local IP and the specified port.
#
# Example:
#
# sentinel announce-ip 1.2.3.4
# dir <working-directory>
# Every long running process should have a well-defined working directory.
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
# for the process to don't interferer with administrative tasks such as
# unmounting filesystems.
dir /tmp
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# master-name : master Redis Server名称
# ip : master Redis Server的IP地址
# redis-port : master Redis Server的端口号
# quorum : 主实例判断为失效至少需要 quorum 个 Sentinel 进程的同意,只要同意 Sentinel 的数量不达标,自动failover就不会执行
#
# Tells Sentinel to monitor this master, and to consider it in O_DOWN
# (Objectively Down) state only if at least <quorum> sentinels agree.
#
# Note that whatever is the ODOWN quorum, a Sentinel will require to
# be elected by the majority of the known Sentinels in order to
# start a failover, so no failover can be performed in minority.
#
# Slaves are auto-discovered, so you don't need to specify slaves in
# any way. Sentinel itself will rewrite this configuration file adding
# the slaves using additional configuration options.
# Also note that the configuration file is rewritten when a
# slave is promoted to master.
#
# Note: master name should not include special characters or spaces.
# The valid charset is A-z 0-9 and the three characters ".-_".
#
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel auth-pass <master-name> <password>
#
# Set the password to use to authenticate with the master and slaves.
# Useful if there is a password set in the Redis instances to monitor.
#
# Note that the master password is also used for slaves, so it is not
# possible to set a different password in masters and slaves instances
# if you want to be able to monitor these instances with Sentinel.
#
# However you can have Redis instances without the authentication enabled
# mixed with Redis instances requiring the authentication (as long as the
# password set is the same for all the instances requiring the password) as
# the AUTH command will have no effect in Redis instances with authentication
# switched off.
#
# Example:
#
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# sentinel down-after-milliseconds <master-name> <milliseconds>
#
# Number of milliseconds the master (or any attached slave or sentinel) should
# be unreachable (as in, not acceptable reply to PING, continuously, for the
# specified period) in order to consider it in S_DOWN state (Subjectively
# Down).
# 选项指定了 Sentinel 认为Redis实例已经失效所需的毫秒数。当实例超过该时间没有返回PING,或者直接返回错误, 那么 Sentinel 将这个实例标记为主观下线(subjectively down,简称 SDOWN )
#
# Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000
# sentinel parallel-syncs <master-name> <numslaves>
#
# How many slaves we can reconfigure to point to the new slave simultaneously
# during the failover. Use a low number if you use the slaves to serve query
# to avoid that all the slaves will be unreachable at about the same
# time while performing the synchronization with the master.
# 选项指定了在执行故障转移时, 最多可以有多少个从Redis实例在同步新的主实例, 在从Redis实例较多的情况下这个数字越小,同步的时间越长,完成故障转移所需的时间就越长。
sentinel parallel-syncs mymaster 1
# sentinel failover-timeout <master-name> <milliseconds>
#
# Specifies the failover timeout in milliseconds. It is used in many ways:
#
# - The time needed to re-start a failover after a previous failover was
# already tried against the same master by a given Sentinel, is two
# times the failover timeout.
#
# - The time needed for a slave replicating to a wrong master according
# to a Sentinel current configuration, to be forced to replicate
# with the right master, is exactly the failover timeout (counting since
# the moment a Sentinel detected the misconfiguration).
#
# - The time needed to cancel a failover that is already in progress but
# did not produced any configuration change (SLAVEOF NO ONE yet not
# acknowledged by the promoted slave).
#
# - The maximum time a failover in progress waits for all the slaves to be
# reconfigured as slaves of the new master. However even after this time
# the slaves will be reconfigured by the Sentinels anyway, but not with
# the exact parallel-syncs progression as specified.
# 如果在该时间(ms)内未能完成failover操作,则认为该failover失败
#
# Default is 3 minutes.
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#
# sentinel notification-script and sentinel reconfig-script are used in order
# to configure scripts that are called to notify the system administrator
# or to reconfigure clients after a failover. The scripts are executed
# with the following rules for error handling:
#
# If script exits with "1" the execution is retried later (up to a maximum
# number of times currently set to 10).
#
# If script exits with "2" (or an higher value) the script execution is
# not retried.
#
# If script terminates because it receives a signal the behavior is the same
# as exit code 1.
#
# A script has a maximum running time of 60 seconds. After this limit is
# reached the script is terminated with a SIGKILL and the execution retried.
# NOTIFICATION SCRIPT
#
# sentinel notification-script <master-name> <script-path>
#
# Call the specified notification script for any sentinel event that is
# generated in the WARNING level (for instance -sdown, -odown, and so forth).
# This script should notify the system administrator via email, SMS, or any
# other messaging system, that there is something wrong with the monitored
# Redis systems.
#
# The script is called with just two arguments: the first is the event type
# and the second the event description.
#
# The script must exist and be executable in order for sentinel to start if
# this option is provided.
# 指定sentinel检测到该监控的redis实例指向的实例异常时,调用的报警脚本。该配置项可选,但是很常用。
#
# Example:
#
# sentinel notification-script mymaster /var/redis/notify.sh
# CLIENTS RECONFIGURATION SCRIPT
#
# sentinel client-reconfig-script <master-name> <script-path>
#
# When the master changed because of a failover a script can be called in
# order to perform application-specific tasks to notify the clients that the
# configuration has changed and the master is at a different address.
#
# The following arguments are passed to the script:
#
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
#
# <state> is currently always "failover"
# <role> is either "leader" or "observer"
#
# The arguments from-ip, from-port, to-ip, to-port are used to communicate
# the old address of the master and the new address of the elected slave
# (now a master).
#
# This script should be resistant to multiple invocations.
#
# Example:
#
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
2.2 Redis缓存穿透和雪崩
服务的高可用问题!
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面,但同时也会带来一些问题,其中,最要害的就是数据的一致性问题,从严格意义上来讲,这个问题无解,如果对数据一致性要求较高的,那么就不能使用缓存。还有一些典型的问题就是:缓存穿透、缓存雪崩、缓存击穿。目前,业界也都有比较流行的解决方案。
2.2.1 缓存穿透
查不到导致的
概念
用户想要查询一个数据,发现redis内存数据库没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
在这里插入图片描述
解决方案
- 布隆过滤器
是一种数据结构,对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力
- 缓存空对象
当存储层不命中时候,即使返回的空对象也将其存储起来,同时会设置一个过期时间,之后在访问这个数据将会从缓存中获取,保护了后端数据源
缓存空对象的2个问题:
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能存在很多很多空值的键
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一定时间窗口的不一致,这对于需要保持一致性的业务会有影响
2.2.2 缓存击穿
量太大,缓存过期!
概念
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
缓存击穿是指一个key非常热点,在不停的扛着大并发, 大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
当某个key在过期的瞬间,会大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导致数据库瞬间压力过大
解决方案
- 设置热点数据不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
- 加互斥锁
分布式锁:保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的 权限,因此只需要等待即可,这种方式将高并发的压力转移到了分布式锁,因为对分布式锁的考验很大
2.2.3 缓存雪崩
概念
是指某一个时间段,缓存集中过期失效,redis宕机
解决方案
- redis高可用
redis集群,保证服务的高可用(异地多活)
- 限流降级
在缓存失效后,通过加锁、队列来控制读数据库写缓存的线程数量,比如对某个key只允许一个线程查看数据和写缓存,其他线程等待
- 数据预热
在正式部署之前,先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀