Nosql概述
为什么要用Nosql
- 大数据时代下,传统的关系型数据库(SQL)已经不能支持存储数据的需求
- 演变历史
APP→DAL→MySQL
- 90年代,一个基本的网站访问量一般不会太大,单个数据库完全足够
- 那个时候更多的去使用静态网页 HTML - 服务器根本没有太大的压力
- 这种情况下,整个网站的瓶颈在于:
1.数据量如果太大,一个机器放不下了
2.数据的索引(B+ Tree),一个机器内存也放不下
3.访问量(读写混合)太大,一个服务器承受不了- 只要你开始出现以上三种情况之一,那么你就必须要晋级
Memcached(缓存) + MySQL + 垂直拆分(读写分离)
- 网站80%的情况都是在读,每次都要去查询数据库的话就十分麻烦。所以我们希望减轻数据的压力,我们可以使用缓存来保证效率
- 发展过程:优化数据结构和索引→文件缓存(IO)→Memcached(当时最热门的技术)
分库分表 + 水平拆分 + MySQL集群
- 技术和业务在发展的同时,对人的要求也越来越高
- 数据库的本质:读、写
- 早些年MySQL引擎使用ISAM,使用表锁机制(一百万条数据中查询一条数据,会将整个表锁上),十分影响效率,高并发下会出现十分严重的锁问题
- 后来MySQL引擎采用Innodb,查询时使用行锁机制(一百万调数据中查询一条数据,仅会锁定该行数据,但无索引情况下任采用表锁;间隙锁)
- 慢慢的就开始采用分库分表来解决写的压力,MySQL在那个年代推出了表分区,这个并没有多少公司使用
如今最近的年代
- 技术爆炸(2010开始):十几年时间,世界已经发生了翻天覆地的变化(定位、音乐、热榜这些数据非常多)
- MySQL等关系型数据库就不够用了,数据量大,变化快
- 如果继续使用MySQL来存储一些比较大的文件,博客,图片等,数据量很大,效率就低了。这时候如果有一种数据库来专门处理这种数据,MySQL压力就变得十分小(研究如何处理这些问题)大数据的IO压力下,表几乎没法更大
- 为什么要用Nosql?
随着web2.0互联网的诞生,传统的关系型数据库很难对付web2.0时代。尤其是超大规模的高并发的社区。暴露出来很多难以克服的问题,NoSQL在当今大数据环境下发展十分迅速,Redis是发展最快的。
用户的个人信息,社交网络,地理位置。用户自己产生的数据,用户日志等等爆发式增长。
这个时候我们就要使用Nosql数据库,Nosql可以很好的处理以上的情况。
什么是NoSQL
NoSQL = Not only SQL(不仅仅是SQL)
- NoSQL泛指非关系型数据库,数据的存储不需要固定的行列格式,不需要多余的操作就可以横向扩展,取值通过键值对来取值
- 关系型数据库:规范的表格,行,列,格式固定
NoSQL的特点
- 方便扩展:数据之间没有关系,很好扩展
- 大数据量高性能(Redis一秒写8万次,读取11万次,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
- 数据类型是多样型的(不需要事先设计数据库,随取随用)
- 传统的RDBMS和NoSQL
传统的 RDBMS
- 结构化组织
- SQL
- 数据库和关系都存在单独的表中
- 数据操作语言,数据定义语言
- 严格的一致性
- 基础的事务操作
...
NoSQL
- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储,列存储,文档存储,图形数据库
- 最终一致性
- CAP定理和BASE理论(异地多活)
了解:3V+3高
- 大数据时代的3V:主要是描述问题的
1.海量Volume
2.多样Variety
3.实时Velocity - 大数据时代的3高:主要是对程序的要求
1.高并发
2.高可扩(随时水平拆分,机器不够了,可以加机器)
3.高性能(保证用户体验和性能)
NoSQL的四大分类
KV键值对
- 新浪:Redis
- 美团:Redis + Tair
- 阿里、百度:Redis + memcache
文档型数据库(bson格式,和json一样)
- MongoDB(一般必须要掌握)
- 一个基于分布式文件存储的数据库,C++编写
- 主要用于处理大量的文档
- 是一个介于关系型数据库和非关系型数据库中中间的产品
- mongoDB是非关系型数据库中功能最丰富,最像关系型数据库的
- ConthDB
列存储数据库
- Hbase
- 分布式文件系统
图数据库
- 它不是用来存放图形的,而是用于存储图这种数据结构的
- Neo4j,InfoGrid
四者对比
分类 | Examples举例 | 典型应用场景 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo Cablnet/Tyrant,Redis,Voldemort,Oracle BDB | 内容缓存,主要用于处理大量数据的高访问负载,也用于一些日志系统等等 | Key指向Value的键值对,通常用hash table来实现 | 查找速度快 | 数据无结构化,通常只被当作字符串或者二进制数据 |
列存储数据库 | Cassandra,Hbase,Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存放在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档型数据库 | CouchDB,MongoDB | Web应用(与Key-Value类似,Value是结构化的,不同的是数据库能够了解Value的内容) | Key-Value对应的键值对,Value为结构化数据 | 数据结构要求不严格,表结构可变,不需要像关系型数据库一样需要预先定义表结构 | 查询性能不高,而且缺乏统一的查询语法 |
图数据库 | Neo4J,InfoGrid,Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法。比如最短路径寻址,N度关系查找等 | 很多时候需要对整个图做计算才能得出需要的信息,而且这种结构不太好做分布式的集群方案 |
Redis入门
概述
Redis是什么?
Redis(Re
mote Di
ctionary S
erver),即远程字典服务
是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型Key-Value数据库,并提供多种语言的API。
Redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
免费和开源,是当下最热门的NoSQL技术之一,也被人们称之为结构化数据库。
Redis能做什么?
1、内存存储、持久化,内存中是断电即失、所以说持久化很重要(rdb,aof两种持久化方式)
2、效率高,可以用于高速缓存
3、发布订阅系统
4、地图信息分析
5、计时器、计数器(浏览量)
6、…
特性
1、多样的数据类型
2、持久化
3、集群
4、事务
…
学习Redis中需要用到的东西
1、官网:https://redis.io/
2、中文网:http://www.redis.cn/
Windows安装
官方不建议在Windows环境进行Redis开发,这里就不多介绍了,主要是下载解压,双击运行即可,注意文件夹路径不得含有中文
查看Linux上面是否有安装Redis
- [root@localhost bin]# whereis redis-cli
- redis-cli: /usr/bin/redis-cli
- [root@localhost bin]# whereis redis-server
- redis-server: /usr/bin/redis-server
Linux安装
一、下载安装包
- 官网下载安装包
redis-6.0.6.tar.gz
- 使用wget下载安装包
wget http://download.redis.io/releases/redis-4.0.8.tar.gz
二、解压Redis的安装包(程序一般放到opt目录下)
cd /opt
tar xzvf redis-6.0.6.tar.gz
三、进入解压后的文件可以看到Redis的配置文件
四、基本的环境安装
# 安装gcc
yum install gcc-c++
# 注意6.0以上之间make会报错,主要是gcc版本太旧,解决方法在下方
# make安装需要的环境(执行完后当前)
# make
# 如make时出现大量error,需要升级gcc5.3及以上,这里升级到gcc9.3
yum -y install centos-release-scl
yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
scl enable devtoolset-9 bash
# 需要注意的是scl命令启用只是临时的,退出shell或重启就会恢复原系统gcc版本。如果要长期使用gcc 9.3的话
echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
# 这样退出shell重新打开就是新版的gcc了
# 再次make,多了src目录
make
# 这里最好进到src目录下操作
cd src/
# 确认下安装
# make install PREFIX=/usr/local/redis 会安装到/usr/local/redis下
make install
五、Redis的默认安装路径
/usr/local/bin/
目录下
cd /usr/local/bin/
ls
六、复制配置文件到自己创建的目录下
mkdir rconf
cp /opt/redis-6.0.6/redis.conf rconf
# 该目录下多了redis.conf文件,修改它就是修改redis配置
cd rconf/
七、Redis默认不是后台启动的,需要修改配置文件
# 将daemonize no 改成daemonize yes
vim redis.conf
八、启动redis
# 回到bin目录下
cd ..
# 启动redis
./redis-server rconf/redis.conf
或者
# 加上&号使redis以后台程序方式运行
redis-server &
又或者
redis-server
- 测试一下连接
# 用redis-cli连接一下测试
[root@VM-12-17-centos bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name sunbai
OK
127.0.0.1:6379> get name
"sunbai"
127.0.0.1:6379>
九、关闭Redis
# 利用redis-cli的shutdown命令即可
127.0.0.1:6379> shutdown
not connected> exit
或者
# pkill停止redis
pkill redis
将redis加入到开机启动
# 意思就是开机调用这段开启redis的命令
vim /etc/rc.local //在里面添加内容:/usr/local/bin/redis-server /usr/local/bin/rconf/redis.conf
让redis-cli指令可以在任意目录下直接使用
# 将redis-cli,redis-server拷贝到bin下,让redis-cli指令可以在任意目录下直接使用
cp /usr/local/redis/bin/redis-server /usr/local/bin/
cp /usr/local/redis/bin/redis-cli /usr/local/bin/
检测后台进程是否存在
ps -ef |grep redis
检测6379端口是否在监听
netstat -lntp | grep 6379
设置redis密码
- 运行命令:redis-cli
- 查看现有的redis密码(可选操作,可以没有)
运行命令:config get requirepass 如果没有设置过密码的话运行结果会如下所示
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
- 设置redis密码
运行命令:config set requirepass ****(****为你要设置的密码),设置成功的话会返回‘OK’字样 - 测试连接
重启redis服务
//(redis-cli -h 127.0.0.1 -p 6379 -a ****(****为你设置的密码))
输入 redis-cli 进入命令模式,使用 auth ‘*****’ (****为你设置的密码)登陆
让外网能够访问redis
- 配置防火墙: firewall-cmd --zone=public --add-port=6379/tcp --permanent(开放6379端口)
重启防火墙:systemctl restart firewalld(重启防火墙以使配置即时生效)
查看系统所有开放的端口:firewall-cmd --zone=public --list-ports - 此时 虽然防火墙开放了6379端口,但是外网还是无法访问的,因为redis监听的是127.0.0.1:6379,并不监听外网的请求。
- 把文件夹目录里的redis.conf配置文件里的bind 127.0.0.1前面加#注释掉
- 命令:redis-cli连接到redis后,通过 config get daemonize和config get protected-mode 是不是都为no,如果不是,就用config set 配置名 属性 改为no。
测试性能
redis-benchmark是一个压力测试工具
序号 | 选项 | 描述 | 默认值 |
---|---|---|---|
1 | -h | 指定服务器主机名 | 127.0.0.1 |
2 | -p | 指定服务器端口 | 6379 |
3 | -s | 指定服务器 | socket |
4 | -c | 指定并发连接数 | 50 |
5 | -n | 指定请求数 | 10000 |
6 | -d | 以字节的形式指定 SET/GET 值的数据大小 | 2 |
7 | -k | 1=keep alive 0=reconnect | 1 |
8 | -r | SET/GET/INCR 使用随机 key, SADD 使用随机值 | |
9 | -P | 通过管道传输 请求 | 1 |
10 | -q | 强制退出 redis。仅显示 query/sec 值 | |
11 | –csv | 以 CSV 格式输出 | |
12 | -l(L 的小写字母) | 生成循环,永久执行测试 | |
13 | -t | 仅运行以逗号分隔的测试命令列表。 | |
14 | -I(i 的大写字母) | Idle 模式。仅打开 N 个 idle 连接并等待。 |
测试:100个并发连接, 10000请求
[root@VM-12-17-centos bin]# redis-benchmark -h localhost -p 6379 -c 100 -n 10000
# 首先进行ping请求测试
====== PING_INLINE ======
# 10000个请求0.18秒
10000 requests completed in 0.18 seconds
# 100个并行的客户端
100 parallel clients
# 每次只写入3个字符串
3 bytes payload
# 保持连接的数量:1(只有一台服务器来处理这些请求)
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
# 所有请求在3毫秒内完成
0.01% <= 0.2 milliseconds
0.09% <= 0.3 milliseconds
0.31% <= 0.4 milliseconds
0.48% <= 0.5 milliseconds
0.96% <= 0.6 milliseconds
2.78% <= 0.7 milliseconds
6.05% <= 0.8 milliseconds
51.96% <= 0.9 milliseconds
88.54% <= 1.0 milliseconds
92.52% <= 1.1 milliseconds
93.91% <= 1.2 milliseconds
95.19% <= 1.3 milliseconds
96.51% <= 1.4 milliseconds
97.40% <= 1.5 milliseconds
97.96% <= 1.6 milliseconds
98.36% <= 1.7 milliseconds
98.69% <= 1.8 milliseconds
98.94% <= 1.9 milliseconds
99.16% <= 2 milliseconds
100.00% <= 2 milliseconds
# 每秒处理56179.77个请求
56179.77 requests per second
====== PING_BULK ======
10000 requests completed in 0.18 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
78.65% <= 1 milliseconds
98.21% <= 2 milliseconds
100.00% <= 2 milliseconds
56818.18 requests per second
# set命令测试
====== SET ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
78.25% <= 1 milliseconds
97.63% <= 2 milliseconds
100.00% <= 2 milliseconds
57471.27 requests per second
# get命令测试
====== GET ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
69.58% <= 1 milliseconds
96.72% <= 2 milliseconds
100.00% <= 2 milliseconds
57471.27 requests per second
====== INCR ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
75.53% <= 1 milliseconds
98.04% <= 2 milliseconds
100.00% <= 2 milliseconds
58479.53 requests per second
====== LPUSH ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
80.42% <= 1 milliseconds
98.77% <= 2 milliseconds
100.00% <= 2 milliseconds
57803.47 requests per second
====== RPUSH ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
72.09% <= 1 milliseconds
96.58% <= 2 milliseconds
100.00% <= 2 milliseconds
57471.27 requests per second
====== LPOP ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
77.78% <= 1 milliseconds
97.85% <= 2 milliseconds
99.92% <= 3 milliseconds
100.00% <= 3 milliseconds
57142.86 requests per second
====== RPOP ======
10000 requests completed in 0.18 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
73.52% <= 1 milliseconds
96.27% <= 2 milliseconds
99.86% <= 3 milliseconds
100.00% <= 3 milliseconds
56818.18 requests per second
====== SADD ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
79.72% <= 1 milliseconds
97.38% <= 2 milliseconds
99.88% <= 3 milliseconds
100.00% <= 3 milliseconds
57471.27 requests per second
====== HSET ======
10000 requests completed in 0.18 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
77.95% <= 1 milliseconds
96.28% <= 2 milliseconds
99.63% <= 3 milliseconds
100.00% <= 3 milliseconds
55865.92 requests per second
====== SPOP ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
79.25% <= 1 milliseconds
98.04% <= 2 milliseconds
100.00% <= 2 milliseconds
57471.27 requests per second
====== LPUSH (needed to benchmark LRANGE) ======
10000 requests completed in 0.17 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
76.99% <= 1 milliseconds
97.17% <= 2 milliseconds
99.78% <= 3 milliseconds
100.00% <= 3 milliseconds
57803.47 requests per second
====== LRANGE_100 (first 100 elements) ======
10000 requests completed in 0.31 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.90% <= 1 milliseconds
81.92% <= 2 milliseconds
94.65% <= 3 milliseconds
98.54% <= 4 milliseconds
100.00% <= 4 milliseconds
31948.88 requests per second
====== LRANGE_300 (first 300 elements) ======
10000 requests completed in 0.65 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.01% <= 1 milliseconds
1.05% <= 2 milliseconds
14.98% <= 3 milliseconds
84.11% <= 4 milliseconds
92.23% <= 5 milliseconds
96.38% <= 6 milliseconds
98.51% <= 7 milliseconds
99.33% <= 8 milliseconds
99.83% <= 9 milliseconds
100.00% <= 9 milliseconds
15290.52 requests per second
====== LRANGE_500 (first 450 elements) ======
10000 requests completed in 0.92 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.01% <= 1 milliseconds
0.14% <= 2 milliseconds
1.02% <= 3 milliseconds
5.91% <= 4 milliseconds
86.20% <= 5 milliseconds
94.64% <= 6 milliseconds
96.17% <= 7 milliseconds
97.53% <= 8 milliseconds
98.37% <= 9 milliseconds
99.04% <= 10 milliseconds
99.49% <= 11 milliseconds
99.83% <= 12 milliseconds
99.98% <= 13 milliseconds
100.00% <= 13 milliseconds
10869.57 requests per second
====== LRANGE_600 (first 600 elements) ======
10000 requests completed in 1.13 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
0.01% <= 1 milliseconds
0.04% <= 2 milliseconds
0.21% <= 3 milliseconds
0.69% <= 4 milliseconds
5.36% <= 5 milliseconds
74.05% <= 6 milliseconds
94.49% <= 7 milliseconds
97.01% <= 8 milliseconds
97.98% <= 9 milliseconds
98.83% <= 10 milliseconds
99.51% <= 11 milliseconds
99.84% <= 12 milliseconds
99.95% <= 13 milliseconds
100.00% <= 13 milliseconds
8810.57 requests per second
====== MSET (10 keys) ======
10000 requests completed in 0.18 seconds
100 parallel clients
3 bytes payload
keep alive: 1
host configuration "save": 900 1 300 10 60 10000
host configuration "appendonly": no
multi-thread: no
3.80% <= 1 milliseconds
92.29% <= 2 milliseconds
99.04% <= 3 milliseconds
100.00% <= 3 milliseconds
56497.18 requests per second
Redis的基础知识
Redis默认有16个数据库
- 默认使用的是第0个
- 可以使用select进行切换数据库
[root@VM-12-17-centos bin]# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
# 可以获取name "sunbai"
127.0.0.1:6379> get name
"sunbai"
# 切换到第1个数据库
127.0.0.1:6379> select 1
OK
# 切换后获取不到name了
127.0.0.1:6379[1]> get name
(nil)
# 切换回来又能获取到了
127.0.0.1:6379[1]> select 0
OK
127.0.0.1:6379> get name
"sunbai"
Redis是单线程的
- 明白Redis是很快的,官方表示,Redis是基于内存操作,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了,所以就使用了单线程了
- Redis是C语言写的,官方提供的数据为100000+的QPS,完全不比同样是使用key-value的Memcache差
- Redis为什么单线程还这么快?
- 误区1:高性能的服务器一定是多线程的
- 误区2:多线程一定比单线程效率高(实际上多线程会带来CPU的上下文切换)
- 核心:Redis是将所有的数据全部放在内存中的,所以说使用单线程去操作效率就是最高的。多线程CPU上下文会切换,这是非常耗时的操作,对于内存系统来说,如果没有上下文切换,效率就是最高的。Redis多次读写都是在一个CPU上的,在内存情况下,这个就是最佳的方案。
常用命令
-
SELECT [db] 切换到db数据库
-
DBSIZE 查看当前数据库数据条数
-
KEYS [匹配字符] 查看符合匹配字符的key(*为通配符)
-
FLUSHDB 清空当前数据库
-
FLUSHALL 清空全部的数据库
-
EXISTS [key] 判断某个键是否存在
-
MOVE [key] [db] 将当前对应的键值对移动到指定的db库中
-
EXPIRE [key] [sec] 设置指定key的键值对在sec秒后过期
-
TTL [key] 查看指定的key还有多少秒过期
- 返回-1表示永不过期
-
DEL [key] 移除指定key
-
TYPE [key] 查询指定key对应的value的值的类型
Redis五大数据类型
String
- SET [key] [value] 设置key对应的value
- GET [key] 获取主键对应的value
- APPEND [key] [string] 向key对应的字符串后面追加拼接字符串
- 如果当前key不存在,就相当于set了一个key
- STRLEN [key] 查看字符串长度
- INCR [key] 让对应的key自增1
- key对应的value必须是数字,否则会报错:(error) ERR value is not an integer or out of range
- INCRBY [key] [num] 让对应的key自增num
- key对应的value必须是数字,否则会报错:(error) ERR value is not an integer or out of range
- DECR [key] 让对应的key自减1
- key对应的value必须是数字,否则会报错:(error) ERR value is not an integer or out of range
- DECRBY [key] [num] 让对应的key自减num
- key对应的value必须是数字,否则会报错:(error) ERR value is not an integer or out of range
- GETRANGE [key] [start] [end] 获取指定key的start到end的子串
- 取值区间是个闭合区间,即取[start,end]范围,会取到start位置和end位置
- 支持负数倒数
- SETRANGE [key] [value] 从第key个位置开始,用value替换value长度的字符串
- 不支持负数倒数
- SETEX [key] [time] [value] 设置一个含过期时间(time秒)的key-value
- 无论这个key是否已存在,都设置成功
- time的单位默认是秒
- SETNX [key] [value] 设置一个key-value,如果这个key是不存在的才能设置成功
- 如果设置的key已经存在了,则设置失败
- 该命令可用于做分布式session或分布式锁
- MSET [k-v…] 批量设置key-value对,参数是多个key-value对,空格隔开,所以参数一定是偶数个
- MGET [keys…] 批量获取key值,参数是要获取的key列表,用空格隔开
- MSETNX [k-v…] 与MSET类似,但是必须是这些key是不存在的才能设置成功
- MSETNX是原子性的操作,要么一起成功,要么一起失败
- GETSET [key] [value] 设置并获取值(先get再set)
List
在redis里面可以将List玩成栈或者队列,所有的list命令都是用l开头的,List实际上是一个链表,可以从中间插入值
- LPUSH [key] [element] 向指定列表中放入一个元素
- LRANGE [key] [start] [stop] 获取指定范围的元素,start为开始下标,stop为结束下标
- 下标采用闭区间
- 支持负数逆序
- RPUSH [key] [element…] 向列表的右边添加元素(一个或多个,用空格隔开)
- LPOP [key] 将列表的左边第一个数据取出
- RPOP [key] 将列表的右边第一个数据取出
- LINDEX [key] [index] 获取左边起第index个元素
- LLEN [key] 查询key列表的长度
- LREM [key] [count] [element] 移除从左开始的count个element元素
- count>0 从前往后移除,count=0移除全部,count<0从后往前移除
- LTRIM [key] [start] [stop] 截取key中[start,stop]部分,自操作
- RPOPLPUSH [source] [target] 移除source列表右边一个元素到target的左边第一个元素
- LSET [key] [index] [element] 设置key的第index元素为element,相当于更新操作
- 如果超出了下标会报错:(error) ERR index out of range
- 如果不存在这样的key会报错:(error) ERR no such key
- LINSERT [key] [BEFORE|AFTER] [targetele] [element] 向目标key的targetele的前(before)或者后(after)插入一个element元素
Set
set是一种无序不重复集合。与List类似,所有的set命令都是以s开头的
- SADD [key] [element…] 向指定key的set集合中添加element元素
- 可以添加多个
- 如果没有该set集合,则创建该set集合
- 如果该key不是一个set集合,则报错:(error) WRONGTYPE Operation against a key holding the wrong kind of value
- SMEMBERS [key] 查看set集合中所有元素
- SCARD [key] 获取set集合中的元素个数
- SREM [key] [item] 移除指定key集合的item元素
- SRANDMEMBER [key] [count] 从key集合中随机获取count个元素
- 没有count时默认是1
- SPOP [key] 随机弹出key集合中的一个元素
- SMOVE [source] [target] [element] 把source集合中的element元素移动到target集合中
- SDIFF [source] [target…] 获取source集合与target集合的差集(source - target)
- target可以有1到多个
- SINTER [key…] 获取多个key之间的交集
- 共同好友可用这个
- SUNION [key…] 获取多个key之间的并集
Hash
类似一个Map集合,与前面类似,所有的hash命令以h开头
- HSET [key] [k-v…] 向指定key中加入k-v键值对,k-v键值对可用有多个
- 例:HSET myhash k1 v1 k2 v2
- HGET [key] [k] 获取key中k对应的值
- HMGET [key] [k…] 获取key中多个k对应的值
- HGETALL [key] 获取key中所有的值
- HLEN [key] 获取key中k-v键值对的个数
- HEXISTS [key] [k] 查询key中的k是否存在
- HKEYS [key] 获取key所有的键
- HINCRBY [key] [k] [v] 将key中的k对应的值自增v
- 当需要减少时,将v设置为负数即可
- HSETNX [key] [k] [v] 为key的k键设置一个v值,如果这个键是不存在的,则设置成功,否则设置失败
Zset
有序集合,在set的基础上增加了一个值(底层是用跳表实现的),与之前的类似,所有的Zset命令以Zset开头
- ZADD [key] [n] [value] 向key中添加value值,排序大小为n
- n必须是数值
- ZRANGE [key] [start] [end] 查询key中以start开始,end结束的所有元素,升序排序
- start和end取闭区间
- 支持负数逆向
- ZREVRANGE [key] [start] [end] 查询key中以start开始,end结束的所有元素,倒序排序
- start和end取闭区间
- 支持负数逆向
- ZRANGEBYSCORE [key] [min] [max] [WITHSCORES?] 将指定key在min到max范围内升序排序
- 如果min>max,结果只会是空的
- WITHSCORES是可选项,如果写了该选项,则会将分数显示在key后面
- +inf代表无穷大 -inf代表无穷小
- ZREVRANGEBYSCORE [key] [min] [max] [WITHSCORES?] 将指定key在min到max范围内降序排序
- ZREM [key] [item] 移除key集合中的item元素
- ZCARD [key] 获取有序集合中的元素个数
- ZCOUNT [key] [min] [max] 获取指定区间的成员数量
三种特殊数据类型
geospatial 地理位置
- 朋友定位,附近的人,打车距离计算
- Redis的Geo可以推算地理位置的信息,两地之间的距离,方圆几里的人
- 该数据类型的命令都以GEO开头
- 其底层是Zset实现的,所以Zset的命令适用于geospatial
- GEOADD [key] [longitude] [latitude] [menber] [longitude latitude menber …] 为指定的key添加地理位置
- longitude 是经度
- latitude 是纬度
- menber 是位置名
- 后面可添加多个
- 南北两极无法直接添加
- GEOPOS [key] [member] 获取指定key的member位置的经纬度信息
- GEODIST [key] [member1] [member2] [m | km | ft | mi] 获取指定key下的member1位置到member2位置的距离,最后一个参数指定单位,默认是m
- m 表示单位米
- km 表示单位为千米
- mi 表示单位为英里
- ft 表示单位为英尺
- GEORADIUS [key] [longituds] [latitude] [radius] [m | km | ft | mi] [withdist] [withcoord] [count n] [asc | dec] 搜索指定经纬度在指定半径内的地理位置(搜索附近的人)
- radius 指定半径
- withdist 显示到中间距离的位置
- withcoord 筛选出指定的结果
- count n 筛选前n条结果
- asc | dec 根据距离升序|降序排序
- GEORADIUSBYMEMBER [key] [member] [radius] [m | km | ft | mi] [withdist] [withcoord] [count n] [asc | dec] 找出指定元素周围的其他元素
- radius 指定半径
- withdist 显示到中间距离的位置
- withcoord 筛选出指定的结果
- count n 筛选前n条结果
- asc | dec 根据距离升序|降序排序
hyperloglog 基数统计的算法
- 什么是基数?
假如有两个集合A{1,3,5,7,8,7},B{1,3,5,7,8}
基数(不重复的元素) = 5,可以接受误差- 简介
Redis2.8.9版本就更新了Hyperloglog数据结构
Redis Hyperloglog基数统计的算法- 网页的UV(一个人访问一个网站多次,但是还是算作一个人)
传统的方式,set保存用户的id,然后就可以统计set中的元素数量作为判断标准
这种方式如果保存大量的用户id,就会比较麻烦,我们的目的是为了计数,而不是保存用户id- 优点:占用的内存是固定的,2^64不同的元素的基数,只需要费12KB的内存,如果要从内存角度来比较的话,Hyperloglog是首选
- 缺点:0.81%的错误率,但是统计的任务,是可以忽略不计的
- 该类型的命令都是以PF开头的
- PFADD [key] [element …] 为指定的key添加element元素,可以是多个元素
- PFCOUNT [key] 统计这个key下的基数
- PFMERGE [destkey] [sourcekey] 将sourcekey的元素合并到destkey中
bitmaps 位图
- 采用位存储,是操作二进制位来进行存储记录的,就只有0和1两个状态
- 统计用户信息(活跃|不活跃、登录|未登录、打卡|未打卡)等只有两个状态的,用位存储
- SETBIT [key] [offset] [value] 为key设置offset位置的值
- GETBIT [key] [offset] 获取key对应offset位置的值
- BITCOUNT [key] [start end] 统计key的值的和,可以指定start和end范围
Redis的事务
- Redis的单条命令是保证原子性的,要么同时成功,要么同时失败,但是Redis的事务不保证原子性
- Redis事务没有隔离级别的概念,事务中的命令,只有发起执行命令的时候才会执行
- Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行
- 一次性、顺序性、排他性执行一系列的命令
- Redis事务步骤
- 开启事务(MULTI)
- 命令入队(Redis命令序列)
- 执行事务(EXEC)
- 放弃事务:DISCARD
编译型异常(代码有问题!命令有错!),事务中所有的命令都不会被执行!
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> getset k3
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 所有的命令都不会被执行
(nil)
运行时异常(类似java中的1/0),如果事务队列中存在语法性错误,那么执行命令的时候,其他命令是可以正常执行的,错误命令抛出异常
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 # 虽然第一条命令报错了,但是事务依旧正常执行成功了
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k9 v9
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379> get k9
"v9"
Redis实现乐观锁
悲观锁
- 很悲观,认为什么时候都会出问题,无论做什么都会加锁
- 性能低下
乐观锁
- 很乐观,认为什么时候都不会出现问题,所以不会上锁。在更新数据的时候取判断一下,在此期间是否有人修改过这个数据
- 通过获取version,更新的时候比较version
- 性能相对好很多
通过watch实现乐观锁
- 直接监视money后执行,事务可以正常执行
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 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 money
"80"
127.0.0.1:6379> get out
"20"
- 在第一个客户端建设money并开启事务
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 20
QUEUED
127.0.0.1:6379> INCRBY out 20
QUEUED
- 事务执行之前在第二个客户端修改money
127.0.0.1:6379> get money
"100"
127.0.0.1:6379> decrby money 50
(integer) 50
- 回到第一个客户端执行事务,由于监视的money被修改了,事务执行失败
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get money
"50"
- 乐观锁解锁:UNWATCH
- 当事务被乐观锁锁上,可以通过使用UNWATCH解锁监视
- 重新监视当前值(重新上锁)
- 再次执行当前事务(会基于重新上锁时的值执行事务)
Jedis的使用
什么是Jedis
Jedis是Redis官方推荐的java连接开发工具。使用Java操作Redis中间件,如果你要使用Java操作Redis,那么一定要对Jedis十分的熟悉
测试
- Maven仓库:https://mvnrepository.com/artifact/redis.clients/jedis/4.3.1
- 创建一个maven项目
- 导入对应的依赖
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
</dependencies>
- 编码测试
- 连接数据库
- 操作命令
- 断开连接
public class TestPing {
public static void main(String[] args) {
// 1.new Jedis 对象
Jedis jedis = new Jedis("119.23.214.154", 6379);
// 如果有密码,需要执行auth
// jedis.auth("password");
// jedis所有命令就是Redis的所有命令
jedis.select(1);
System.out.println(jedis.ping());
jedis.set("test","test");
String test = jedis.get("test");
System.out.println(test);
// 关闭连接
jedis.close();
}
}
常用的API
Jedis常用的API与Redis五大数据类型及key操作对应,如String的命令SET对应Jedis就是jedis.set(String key, String value)
Jedis操作事务
- 正常执行事务
public class TestPing {
public static void main(String[] args) {
// 1.new Jedis 对象
Jedis jedis = new Jedis("119.23.214.154", 6379);
// 如果有密码,需要执行auth
// jedis.auth("password");
// jedis所有命令就是Redis的所有命令
jedis.select(1);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","sunbai");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
multi.set("user2", result);
multi.exec();
}catch (Exception e){
// 失败则放弃事务
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
// 关闭连接
jedis.close();
}
multi.set("test","test");
String test = jedis.get("test");
System.out.println(test);
// 关闭连接
jedis.close();
}
}
- 执行过程中出错
public class TestPing {
public static void main(String[] args) {
// 1.new Jedis 对象
Jedis jedis = new Jedis("119.23.214.154", 6379);
// 如果有密码,需要执行auth
// jedis.auth("password");
// jedis所有命令就是Redis的所有命令
jedis.select(1);
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","sunbai");
// 开启事务
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
try {
multi.set("user1", result);
multi.set("user2", result);
multi.incrBy("user1",1); // 代码抛出错误,没有执行该条命令
multi.set("user3", result);
List<Object> exec = multi.exec();
System.out.println("错误代码执行结果:"+exec);
}catch (Exception e){
// 失败则放弃事务
multi.discard();
e.printStackTrace();
}finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
System.out.println(jedis.get("user3"));
// 关闭连接
jedis.close();
}
multi.set("test","test");
String test = jedis.get("test");
System.out.println(test);
// 关闭连接
jedis.close();
}
}
SpringBoot整合Redis
- SpringBoot操作数据:spring-data jpa jdbc mongodb redis
- SpringData也是和SpringBoot齐名的项目
- 说明:在SpringBoot 2.x之后,原来使用的Jedis被替换为了lettuce
Jedis: 采用的直连,多个线程操作的话,是不安全的,如果想要避免不安全的,使用jedis pool连接池(类似BIO)
Lettuce: 采用Netty,实例可以在多个线程中进行共享,不存在线程不安全的情况。可以减少线程数据了(类似NIO)
源码分析
@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"} // 我们可以自己定义一个redisTemplate来替换这个默认的
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class) // 这里连接方法用的工厂模式做的连接池
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
// 默认的 RedisTemplate,没有过多的设置,Redis对象都是需要序列化
// 两个泛型都是Object类型,我们后面使用需要强制转换<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean // 由于String是redis中最常使用的类型,所以单独提出来了一个bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
整合测试
- 导入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.7-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 编写配置文件
# 配置redis
spring:
data:
redis:
host: 119.23.214.154
prot: 6379
- 编码测试
@SpringBootTest
class Redis02SpringbootApplicationTests {
@Resource
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// redisTemplate.opsForValue() 操作字符串 类似String
// redisTemplate.opsForList() 操作List 类似List
// redisTemplate.opsForSet() 操作set 类似Set
// redisTemplate.opsForHash() 操作Hash 类似Map
// redisTemplate.opsForZset() 操作Zset 类似有序集合
// redisTemplate.opsForHyperLogLog() 操作基数统计
// redisTemplate.opsForGeo() 操作坐标
// redisTemplate.opsForValue().setBit() 操作位图
// 除了基本的操作,我们常用的方法都可以直接通过redisTemplate操作,比如事务和基本的CRUD
// redisTemplate.getConnectionFactory().getClusterConnection() 获取连接Redis的连接对象
redisTemplate.opsForValue().set("mykey", "Sun白");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
- Redis序列化配置
SpringBoot默认集成redis默认的序列化方式是JDK序列化方式,这种序列化方式存储到Redis后不可读,我们可以改成用JSON作为序列化方式
Redis序列化配置
通过配置类配置一个redisTemplate的bean,指定序列化方式可以配置自己的RedisTemplate
@Configuration
public class RedisConfig{
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// jackson序列化方式
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
// String序列化方式
StringRedisSerializer stringSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringSerializer);
// value的序列化方式采用jackson
template.setValueSerializer(serializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringSerializer);
// hash的value采用jackson
template.setHashValueSerializer(serializer);
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
Redis.conf详解
配置文件unit单位对大小写不敏感
includes可以将其他配置文件包含进来
network 网络配置
- bind 127.0.0.1 指明绑定的IP是127.0.0.1,只有127.0.0.1可以访问这个Redis,可以将这个IP写成*或0.0.0.0或者注释掉让任意主机可以访问
- protected-mode yes 指定是否是保护的模式,yes开启,no关闭,一般是开启的。如果想要可以远程访问Redis,在没有设置密码的情况下这个模式要关闭
- port 6379 指定默认端口是6379,修改这个就可以将Redis服务开放到其他端口上
- tcp-backlog 511
- timeout 0 关闭客户端的连接后,客户端会空转多少秒,这里默认设置为0,表示不可用
- tcp-keepalive 300 tcp保持连接多少秒后关闭,默认300秒
general 通用配置
- daemonize yes 是否以守护进程的方式运行,yes开启,no关闭,以守护进程的方式运行就是后台运行,默认是no
- supervised no 进程管理的相关配置,可选参数 no | upstart | systemd | auto
- pidfile /var/run/redis_6379.pid 如果以后台的方式运行,我们就需要指定一个pid文件
- loglevel notice 设置日志级别,可选参数是 debug | verbose | notice | warning
- logfile “” 日志位置文件名,配置这个可以指定生成的日志文件位置,为空的话就是标准的控制台输出
- databases 16 可用数据库的数量配置,默认16个
- always-show-logo yes 是否总是显示日志,yes开启,no关闭,默认开启
snapshotting 快照设置
- save 900 1 快照频率的配置,这里表示在900秒内有一次key的修改,就进行快照,可以配置多个。这里配置语法是save [sec] [n],即是从一次快照之后的下一次key被修改开始计时,在sec秒内有n次被修改,则在sec秒后进行快照操作
- stop-writes-on-bgsave-error yes 这个配置决定快照出错后,是否继续快照工作,yes是,no否,默认是
- rdbcompression yes 是否压缩RDB文件,yes压缩,no不压缩,默认yes
- rdbchecksum yes 是否检查RDB文件,yes检查,no不检查,开启后会保存RDB文件的时候进行错误的校验
- dbfilename dump.rdb 保存的RDB文件的文件名
- dir ./ 设置快照文件保存的路径,这里./表示保存到redis-service的当前目录下
replication 主从复制相关的配置
- replicaof [masterip] [masterport] 配置主机的地址为masterip,主机的端口为masterport
- masterauth [master-password] 配置主机的密码为master-password
- masteruser [username] 配置主机的用户名为username
- replica-serve-stale-data yes当从库同主库失去连接或者复制正在进行,从机库有两种运行方式: 1) 如果 replica-serve-stale-data 设置为 yes(默认设置),从库会继续响应客户端的读请求。 2) 如果 replicaserve-stale-data 设置为 no,除去指定的命令之外的任何请求都会返回一个错误"SYNC with master progress"。
- replica-read-only yes 是否设置从库只读,yes是,no否
security 安全相关的配置
clients 客户端的一些限制配置
- maxclients 10000 客户端连接数量上限的限制,这里设置连接上限为10000个,一般不设置
memory management 内存管理配置
- maxmemory 1024 限制最大内存使用1024bytes,默认不配置
- maxmemory-policy noeviction 内存达到上限之后的处理策略
- volatile-lru 只对设置了过期时间的key进行LRU(默认)
- allkeys-lru 删除LRU算法的key
- volatile-lfu 只对设置了过期时间的key进行LFU
- allkeys-lfu 删除LFU算法的key
- volatile-random 随机删除即将过期key
- allkeys-random 随机删除key
- volatile-ttl 删除即将过期的
- noeviction 永不过期,返回错误
append only mode AOF持久化方式配置
- appendonly no 是否开启AOF持久化方式,yes开启,no关闭,默认关闭
- appendfilename “appendonly.aof” AOF持久化方式生成文件的文件名,这里配置为appendonly.aof
- appendfsync everysec 同步的配置 always | everysec | no
- always 每次修改都会写入
- everysec 每秒同步一次
- no 不执行同步,这个时候操作系统自己同步数据,速度最快
- auto-aof-rewrite-percentage 100 指定 Redis 重写 AOF 文件的条件,默认为 100,它会对比上次生成的 AOF 文件大小。如果当前 AOF 文件的增长量大于上次 AOF 文件的 100%,就会触发重写操作;如果将该选项设置为 0,则不会触发重写操作。
- auto-aof-rewrite-min-size 64mb 指定触发重写操作的 AOF 文件的大小,默认为 64MB。如果当前 AOF 文件的大小低于该值,此时就算当前文件的增量比例达到了 auto-aof-rewrite-percentage 选项所设置的条件,也不会触发重写操作。换句话说,只有同时满足以上这两个选项所设置的条件,才会触发重写操作。
- auto-load-truncated yes 当 AOF 文件结尾遭到损坏时,Redis 在启动时是否仍加载 AOF 文件。
Redis的持久化
RDB方式(快照方式)
- 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是Snapshot快照,它恢复时是将快照直接读到内存里
- Redis会单独创建(fork)子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件
- 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能
- 优点:如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效
- 缺点:最后一次持久化后的数据可能丢失
- Redis默认的持久化方式就是RDB方式
- RDB方式保存的文件,默认的文件名就是dump.rdb
快照触发机制
- redis.conf配置的save的规则满足的情况下
- 执行flushall命令
- 执行save命令
- 关闭Redis时
查看RDB文件存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin"
AOF方式(追加方式)
- AOF持久化模式又叫文件追加模式,就是在Redis进行写操作的时候,将写命令追加在AOF文件的最后,相当于一个历史操作记录文件一般,该操作每秒执行一次,数据的完整度会比较高
- Redis的AOF持久化操作默认是不开启的,通过将appendonly设置成yes开启,重启Redis后生效
- RDB和AOF持久化方式同时开启的话,Redis启动或恢复数据时,优先使用AOF方式
- 如果aof文件损坏,则启动Redis时会由于AOF文件损坏而启动失败,这时候可以通过
redis-check-aof
修复aof文件,通过命令redis-check-aof --fix
修复aof文件,修复会将错误的命令行去除 - 如果在开启AOF模式之后将aof文件删除,那么Redis写命令追加到aof失败,不会再创建aof文件
- AOF重写:当AOF文件达到auto-aof-rewrite-percentage及auto-aof-rewrite-min-size两个配置的要求时,会触发重写操作,重写操作实质上是Redis创建一个新的AOF文件来替代现有的AOF文件,新的AOF文件的会将可合并的命令合并,减少浪费空间的冗余命令
- 优点:数据完整性好
- 缺点:从文件的角度来说,AOF文件大小远远大于RDB文件,修复的速度也比RDB慢。AOF运行效率也要比RDB低
Redis订阅发布
订阅发布概述
- Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。微信、微博、关注系统等
- Redis客户端可以订阅任意数量的频道
- 订阅/发布消息图:
- 消息发布需要三个成员
- 消息发布者
- 频道
- 消息订阅者
- 下图展示了频道channel1,以及订阅这个频道的三个客户端——client2、client5和client1之间的关系:
- 当有新消息通过PUBLISH命令发送给channel1时,这个消息就会被发送给订阅它的三个客户端:
相关命令
这些命令被广泛用于构建即时通信应用,比如网络聊天室(chatroom)和实时广播、实时提醒等
测试
- 在客户端1订阅sunbai这个频道
127.0.0.1:6379> SUBSCRIBE sunbai
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "sunbai"
- 在客户端2推送消息"hello"到频道sunbai
127.0.0.1:6379> PUBLISH sunbai hello
(integer) 1
127.0.0.1:6379> PUBLISH sunbai test
(integer) 1
- 回到客户端1查看,发现消息多了一条"hello"
127.0.0.1:6379> SUBSCRIBE sunbai
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "sunbai"
3) (integer) 1
1) "message" # 消息
2) "sunbai" # 频道
3) "hello" # 消息的具体内容
1) "message"
2) "sunbai"
3) "test"
原理
- Redis是使用C实现的,通过分析Redis源码里面的pubsub.c文件,了解发布和订阅机制的底层实现,藉此加深对Redis的理解
- Redis通过PUBLISH、SUBSCRIBE和PSUBSCRIBE等命令实现发布和订阅功能
- 通过SUBSCRIBE命令订阅某频道后,redis-server里维护了一个字典,字典就是一个个channel,而字典的值则是一个链表,链表中保存了所有订阅这个channel的客户端。SUBSCRIBE命令的关键,就是将客户端添加到给定channel的订阅链表中
- 通过PUBLISH命令向订阅者发送消息,redis-server会使用给定的频道作为键,在它所维护的channel字典中查找记录了订阅这个频道的所有客户端的链表,遍历这个链表,将消息发布给所有订阅者
- Pub/Sub从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以针对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布后,所有订阅它的客户端都会收到相应的消息。这一功能最明显的用法就是用做实时消息系统,比如普通的即时聊天,群聊等功能
Redis集群环境搭建
环境配置
- 配置从库,不用配置主库,可以用命令
info replication
查看当前Redis主从复制信息
127.0.0.1:6379> info replication
# Replication
role:master # 角色 master 当前的Redis是主机
connected_slaves:0 # 没有从机
master_replid:9203bdbbcaded4337e017d253c2c01c9752a293c
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@VM-12-17-centos ~]# cd /usr/local/bin/rconf/
[root@VM-12-17-centos rconf]# ls
redis.conf
[root@VM-12-17-centos rconf]# cp redis.conf redis-79.conf
[root@VM-12-17-centos rconf]# cp redis.conf redis-80.conf
[root@VM-12-17-centos rconf]# cp redis.conf redis-81.conf
[root@VM-12-17-centos rconf]# ls
redis-79.conf redis-80.conf redis-81.conf redis.conf
- 分别修改三个配置文件(由于是模拟的,单机模拟集群,为了防止冲突所以需要修改)
- 端口port分别改为6379 6380 6381
- 后台运行daemonize都打开,设为yes
- pidfile修改,分别改为/var/run/redis_6379.pid /var/run/redis_6380.pid /var/run/redis_6381.pid
- logfile日志文件需要分别命名6379.log 6380.log 6381.log区分日志
- RDB方式的日志文件dbfilename需要区分,分别命名为dump6379.rdb dump6380.rdb dump6381.rdb
- Redis3.2之后的版本,如果非单机集群的话,配置文件中要将protected-mode设置为no,否则无法进行主从复制
- 这里AOF持久化方式统一关闭了,相关配置无需修改
- 启动三个redis服务
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-server rconf/redis-79.conf
[root@VM-12-17-centos bin]# redis-server rconf/redis-80.conf
[root@VM-12-17-centos bin]# redis-server rconf/redis-81.conf
[root@VM-12-17-centos bin]# ls # 启动成功,生成了三个日志文件
6379.log 6381.log busybox-x86_64 rconf redis-check-aof redis-cli redis-server
6380.log appendonly.aof dump.rdb redis-benchmark redis-check-rdb redis-sentinel
[root@VM-12-17-centos bin]# ps -ef|grep redis # 三个服务确实启动成功了
root 9095 1 0 11:36 ? 00:00:00 redis-server 127.0.0.1:6379
root 9114 1 0 11:36 ? 00:00:00 redis-server 127.0.0.1:6380
root 9131 1 0 11:36 ? 00:00:00 redis-server 127.0.0.1:6381
root 9820 363 0 11:39 pts/1 00:00:00 grep --color=auto redis
集群配置
- 分别登录三个Redis,查看集群信息,会发现三台都是主机
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_replid:258f4f7a95718022fb3384db4bce211433ec189b
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
我们一般情况下只要配置从机就好了(认老大,一主二从)
- 登录6380,配置为6379的从机
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379
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:6
master_sync_in_progress:0
slave_repl_offset:420
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4de46497d6167c93f602760a15195a2137eef1eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:420
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:420
- 登录6381,配置为6379的从机
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
127.0.0.1:6380>
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6381
127.0.0.1:6381> SLAVEOF 127.0.01 6379
OK
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.01
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_repl_offset:462
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4de46497d6167c93f602760a15195a2137eef1eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:462
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:462
- 登录6379,查看主从配置信息,多了两个从机
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=98,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=98,lag=0
master_replid:4de46497d6167c93f602760a15195a2137eef1eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:98
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:98
- 上面是命令配置的,真实的主从配置中应该在配置文件中配置,这样就可以做到每次重启Redis都可以自动加入集群中了
将redis81.conf及redis.conf的replicaof注释去掉,配置成replicaof 127.0.0.1 6379,这样就配置好集群了,然后只需要重启80和81的Redis即可 - 主机可以读可写,从机默认只能读不能写
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
127.0.0.1:6380> set k80 v80
(error) READONLY You can't write against a read only replica.
- 主从复制情况下,主机宕机了,从机都不会变成主机
# 关闭主机
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> shutdown
not connected>
# 登录从机80,80还是从机
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
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:3133
master_link_down_since_seconds:93
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4de46497d6167c93f602760a15195a2137eef1eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3133
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3133
127.0.0.1:6380>
# 登录从机81,81还是从机
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6381
127.0.0.1:6381> INFO replication
# Replication
role:slave
master_host:127.0.01
master_port:6379
master_link_status:down
master_last_io_seconds_ago:-1
master_sync_in_progress:0
slave_repl_offset:3133
master_link_down_since_seconds:117
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4de46497d6167c93f602760a15195a2137eef1eb
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:3133
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:3133
127.0.0.1:6381>
- 主机从新上线后,主机仍然是主机
# 开启79服务,查看集群信息,仍然是主机
[root@VM-12-17-centos bin]# redis-server rconf/redis-79.conf
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=42,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=42,lag=0
master_replid:8b273a433b7911fee4e6687ebdae30afbc202e10
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:42
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:42
# 设置一个k79
127.0.0.1:6379> set k79 v79
OK
# 在80查询,可以获取到
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
127.0.0.1:6380> get k79
"v79"
# 在81查询,也可以获取到
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6381
127.0.0.1:6381> get k79
"v79"
- 从机宕机后,重新上线后如果配置成了从机(用命令行的模式下重新启动后不会变成从机,但是用配置文件的方式上线后会自动变成从机),在从机宕机期间在主机保存的数据会被同步到从机上
Redis主从复制
概念
主从复制是指将一台Redis服务器的数据,复制到其他Redis服务器。前者称为主节点(master/lader),后者称之为从节点(slave/follower)。数据的复制都是单向的,只能由主节点到从节点。Master以写为主,Slave以读为主,可以做到读写分离。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但从节点只能有一个主节点。
主从复制的作用主要包括:
- 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式
- 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复,实际上是一种服务的冗余
- 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载,尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量
- 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础
一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下
- 从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大
- 从容量上,单个Redis服务器内存容量有限,就算一台Redis服务器内存容量为256G,也不能将所有内存用作Redis存储内存般来说,单台Redis最大使用内存不应该超过20G。
电商网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是"多读少写"。对于这种场景,我们可以使如下这种架构:
复制原理
- slave 启动成功连接到 master 后会发送一个sync同步命令
- Master 接到命令,启动后台的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,miaster将传送整个数据文件到slave,并完成一次完全同步。
- 全量复制:而slave服务在接收到数据库文件数据后,将其存盘并加载到内存中
- 增量复制: Master 继续将新的所有收集到的修改命令依次传给slave,完成同步
- 但是只要是重新连接master,一次完全同步(全量复制)将被自动执行
其他的主从复制方式
层层链路模式:上一个节点连接下一个节点,成链状
- 该方式下,链路的头节点挂了,后面的节点也不会变成主节点,不能写入数据
- 中间节点断了,后续节点不能同步
- 该方式下,如果前一个节点挂了,可以通过命令
SLAVEOF no one
配置,将当前节点变成主节点(手动操作) - 如果主节点挂了,后面的节点通过命令
SLAVEOF no one
配置,自己变成主节点之后,主节点重新上线,后面的节点不再是当前重新上线的主节点的从节点了
哨兵模式(自动选举主节点的模式)
概述
- 主从切换技术的方法是:当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成-段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式。
- Redis从2.8开始正式提供了Sentinel(哨兵)架构来解决这个问题。谋朝篡位的自动版,能够后台监控主机是否故障,如果故障了根据投票数自动将从库转换为主库。
- 哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
- 这里的哨兵有两个作用
- 通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器
- 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机
- 然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式
- 假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象称为主观下线
- 当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行failover[故障转移]操作
- 切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线
测试
- 我们目前的状态的一主二从,需要配置一下哨兵模式
[root@VM-12-17-centos bin]# cd rconf/
[root@VM-12-17-centos rconf]# vim sentinel.conf
[root@VM-12-17-centos rconf]# cat sentinel.conf
# sentinel 哨兵
# monitor 监控
# mainredis 监控主机别名
# 127.0.0.1 监控目标主机的ip
# 6379 监控目标Redis服务的端口
# 1 当有1个哨兵检测到主redis服务异常了就判定redis挂了
sentinel monitor mainredis 127.0.0.1 6379 1
- 启动哨兵
[root@VM-12-17-centos bin]# redis-sentinel rconf/sentinel.conf
24549:X 27 Nov 2022 15:00:28.769 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
24549:X 27 Nov 2022 15:00:28.769 # Redis version=6.0.6, bits=64, commit=00000000, modified=0, pid=24549, just started
24549:X 27 Nov 2022 15:00:28.769 # Configuration loaded
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 6.0.6 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 24549
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
24549:X 27 Nov 2022 15:00:28.770 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
24549:X 27 Nov 2022 15:00:28.776 # Sentinel ID is 86b6c43c1762cba09617c14d05633febfe1798ae
24549:X 27 Nov 2022 15:00:28.776 # +monitor master mainredis 127.0.0.1 6379 quorum 1
24549:X 27 Nov 2022 15:00:28.777 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:00:28.780 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mainredis 127.0.0.1 6379
- 关闭主机
[root@VM-12-17-centos bin]# redis-cli
127.0.0.1:6379> keys *
1) "k80"
2) "k81"
3) "k1"
4) "k79"
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> shutdown
- 查看sentinel控制台输出,主机变成了6380
...
24549:X 27 Nov 2022 15:02:58.634 # +sdown master mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.634 # +odown master mainredis 127.0.0.1 6379 #quorum 1/1
24549:X 27 Nov 2022 15:02:58.634 # +new-epoch 1
# 尝试故障转移
24549:X 27 Nov 2022 15:02:58.634 # +try-failover master mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.638 # +vote-for-leader 86b6c43c1762cba09617c14d05633febfe1798ae 1
24549:X 27 Nov 2022 15:02:58.638 # +elected-leader master mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.638 # +failover-state-select-slave master mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.704 # +selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.704 * +failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:58.756 * +failover-state-wait-promotion slave 127.0.0.1:6380 127.0.0.1 6380 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:59.266 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:59.266 # +failover-state-reconf-slaves master mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:02:59.324 * +slave-reconf-sent slave 127.0.0.1:6381 127.0.0.1 6381 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:03:00.324 * +slave-reconf-inprog slave 127.0.0.1:6381 127.0.0.1 6381 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:03:00.324 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mainredis 127.0.0.1 6379
24549:X 27 Nov 2022 15:03:00.382 # +failover-end master mainredis 127.0.0.1 6379
# 切换主节点
24549:X 27 Nov 2022 15:03:00.383 # +switch-master mainredis 127.0.0.1 6379 127.0.0.1 6380
24549:X 27 Nov 2022 15:03:00.383 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mainredis 127.0.0.1 6380
24549:X 27 Nov 2022 15:03:00.383 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mainredis 127.0.0.1 6380
24549:X 27 Nov 2022 15:03:30.404 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mainredis 127.0.0.1 6380
- 登录6380查看主从信息,6380变成了主机
[root@VM-12-17-centos ~]# cd /usr/local/bin/
[root@VM-12-17-centos bin]# redis-cli -h 127.0.0.1 -p 6380
127.0.0.1:6380> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6381,state=online,offset=23297,lag=0
master_replid:4dc7bb02e3e6ac4bfa23cf49cbfc8f72e742b836
master_replid2:8b273a433b7911fee4e6687ebdae30afbc202e10
master_repl_offset:23431
second_repl_offset:20807
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1388
repl_backlog_histlen:22044
127.0.0.1:6380> keys *
1) "k79"
2) "k2"
3) "k1"
4) "k80"
5) "k81"
127.0.0.1:6380> get k2
"v2"
- 6379重新上线,变成了从机
[root@VM-12-17-centos bin]# redis-server rconf/redis-79.conf
[root@VM-12-17-centos bin]# redis-cli
127.0.0.1:6379> info replication
# Replication
role:slave # 变成了从机
master_host:127.0.0.1
master_port:6380
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:59988
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4dc7bb02e3e6ac4bfa23cf49cbfc8f72e742b836
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:59988
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:59550
repl_backlog_histlen:439
127.0.0.1:6379> keys *
1) "k1"
2) "k79"
3) "k81"
4) "k80"
5) "k2"
127.0.0.1:6379> get k2
"v2"
小结
- 优点
1、哨兵集群,基于主从复制模式,所有的主从配置优点它全有
2、主从可以切换,故障可以转移,系统的可用性就会更好
3、哨兵模式就是主从模式的升级,从手动到自动,更加健壮 - 缺点
1、Redis 不好在线扩容,集群容量一旦到达上限,在线扩容就十分麻烦
2、实现哨兵模式的配置其实很麻烦,里面有很多选择 - 哨兵模式的全部配置
# ExampIe sentine1.conf
# 哨兵sentine1实例运行的端口 默认26379
# 如果有哨兵集群,我们还需要配置每个哨兵端口
port 26379
# 哨兵sentine1的工作目录
dir /tmp
# 哨兵sentine1监控的redis主节点的 ip port
# master-name 可以自己命名的主节点名字 只能由字母A-Z、数宁0-9 、这三个字符”.-_"组成
# quorum 配置多少个sentine1哨兵统一认为master主节点失联 那么这时客观上认为主节点失联了
# sentine1 monitor <master-name> <ip> <redis-port> <quorum>
sentine1 monitor mymaster 127.0.0.1 6379 2
# 当在Redis实例中开启了requirepass foobared 投权密码 这样所有连接Redis实例的客户端都要提供密码
# 设置哨兵sentine1 连接主从的密码 注意必须为主从设置一样的验证密码
# sentine7 auth-pass <master-name> <password>
sentineT auth-pass mymaster MySUPER--secret-0123passwOrd
# 指定多少毫秒之后 主节点没有应答哨兵sentine] 此时 哨兵主观上认为主节点下线 默认30秒
# sentinel down-after-mi11iseconds <master-name> <mi7liseconds>
sentine1 down-after-milliseconds mymaster 30000
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行同步,这个数字越小,完成faiover所需的时间就越长
# 但是如果这个数字越大,就意味着越多的slavel为replication而不可用。可以通过将这个值设为1来保证每次只有一个slave处于不能处理命令请求的状态
# sentine] para77e1-syncs <emaster-name> <enumsTaves>
sentine] para11e1-syncs mymaster 1
# 故障站移的超时时间 failover-timeout 可以用在以下这些方面:
# 1.同一个sentine1对同一个master两次failover之向的间隔时向。
# 2.当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
# 3.当想要取消一个正在进行的failover所而要的时间。
# 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时时间,slaves依然会被正确配置为指向master,但是就不按para11e1-syncs所配置的规则来了
# 默认三分钟
# sentinel failover-timeout <master-name> <milliseconds>
sentine1 failover-timeout mymaster 180000
# SCRIPTS EXECUTION
# 配置当某一事件发生时所需要执行的脚本,可以通过脚木束通知管理员”例如当系统运行不正常时发邮件通知相关人员
# 对于脚本的运行结果有以下规则:
# 若脚本执行后返回1,那么该脚本稍后将会被再次款行,道复次数目前默认为10
# 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行
# 通知型脚木:当sentine1有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚术
# 这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。
# 调用该脚本时,将传给脚本两个参数,一个是事件的类型,个是事件的描述。
# 如果sentine1.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentine1无法正常启动成功。
# 通知脚本
# sentine notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客户端重新配置主节点参数脚本
# 当一个master由于faiTover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息
# 以下参数将会在调用脚本时传给脚本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>总是"failover"
# <role>是"leader"或者"observer"中的一个。
# 参数 from-ip,from-port,to-ip,to-port是用来和旧的master和新的master(即用的slave)通信的
# 这个脚本应该是通用的,能被多次调用,不是针对性的。
# sentinel client-reconfig-script <master-name> escript-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
Redis缓存穿透和雪崩
Redis缓存的使用,极大的提升了应用程序的性能和效率,特别是数据查询方面。但同时,它也带来了一些问题。其中,最要害的问题,就是数据的一致性问题,从严格意义上讲,这个问题无解。如果对数据的一致性要求很高,那么就不能使用缓存。另外的一些典型问题就是,缓存穿透、缓存雪崩和缓存击穿。目前,业界也都有比较流行的解决方案。
缓存穿透(数据查不到)
概念
缓存穿透的概念很简单,用户想要查询一个数据,发现redis内存数据库没有,也就是缓有没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败(由于查询数据库没有结果,所以不写入缓存)。当用户很多的时候,缓存都没有命中,于是都去请求了持久层数据库。这会给持久层数据库造成很大的压力,这时候就相当于出现了缓存穿透。
解决方案
- 在请求的时候加一道过滤器(布隆过滤器)
- 如果缓存没有命中,那么在缓存中加一个空对象的缓存,同时设置过期时间,下次请求时就无需再次查询数据库了,但是这个方法会带来两个问题
- 如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
- 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
缓存击穿(空档期请求量太大)
概念
- 这里需要注意和缓存穿透的区别,缓存击穿,是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞
- 当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般是热点数据,由于缓存过期,会同时访问数据库来查询最新数据,并且回写缓存,会导使数据库瞬间压力过大
- 类似微博热搜,导致微博服务器宕机
解决方案
- 设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后产生的问题 - 加互斥锁
使用分布式锁(setnx),保证对于每个key同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只需要等待即可。这种方式将高并发的压力转移到了分布式锁,因此对分布式锁的考验很大。
缓存雪崩(缓存集中过期)
概念
- 缓存雪崩,是指在某一个时间段缓存集中过期失效。
- 产生雪崩的原因之一,比如在写本文的时候,马上就要到双十二零点,很快就会迎来一波抢购,这波商品时间比较集中的放入了级存,假设缓存一个小时。那么到了凌晨一点钟的时候,这批商品的缓存就都过期了。而对这批商品的访问查询,都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰,于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
- 其实集中过期,倒不是非常致命,比较致命的缓存雪崩,是缓存服务器某个节点宕机或断网。因为自然形成的缓存雪崩,一定是在某个时间段集中创建缓存,这个时候,数据库也是可以顶住压力的。无非就是对数据库产生周期性的压力而已。而缓存服务节点的宕机,对数据库服务器造成的压力是不可预知的,很有可能瞬间就把数据库压垮。
解决方案
- 设置key随机过期时间
通过设置key的过期时间随机在某个区间,那么即使这些key是集中创建的,那么这些key也不会集中过期 - Redis高可用
这个思想的含义是,既然redis有可能挂掉,那我多增设几台redis,这样一台挂掉之后其他的还可以继续工作,其实就是搭建的集群。(异地多活) - 限流降级
这个解决方案的思想是,在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。 - 数据预热
数据加热的含义就是在正式部署之前,我先把可能的数据先预先访问一遍,这样部分可能大量访问的数据就会加载到缓存中。在即将发生大并发访问前手动触发加载缓存不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
一些错误的解决
- 有时候会报异常
原因: Redis已经启动
解决: 关掉Redis,重启即可
- redis-cli shutdown
- redis-server
然后你就能看到Redis愉快的运行了.