官方文档
参考教程
参考博客1
参考博客2
注意:
在 Redis 中,命令大小写不敏感。
下载最新版本:https://redis.io/download/,我下载的是7.0.7
我把安装包放在了/usr/local下
tar -zxvf redis-7.0.7.tar.gz
# 进入 Redis 的 src 目录
cd redis-7.0.7/src
# 进行安装
make && make install PREFIX=/usr/local/redis7
# 在/usr/local/bin下创建一个文件夹config
mkdir config
# 从解压文件夹中复制redis.conf过来
cp /usr/local/redis-7.0.7/redis.conf config
# 打开配置文件,将daemonize配置的 no 修改为 yes,输入:wq回车保存
vim config/redis.conf
进行如上配置之后,启动 Redis 服务的时候将在后台运行。
# server
cp /usr/local/redis7/bin/redis-server /usr/local/bin/
# 客户端
cp /usr/local/redis7/bin/redis-cli /usr/local/bin/
# 哨兵
cp /usr/local/redis7/bin/redis-sentinel /usr/local/bin/
添加完成后在任何目录下输入 redis-server 可启动服务器,输入 redis-cli 可启动客户端。
# 指定以/usr/local/bin/config/redis.conf配置文件启动服务器
redis-server /usr/local/bin/config/redis.conf
# 然后另外开启一个终端启动服务端
redis-cli
# 用此命令启动客户端可以避免中文乱码问题
redis-cli --raw
# 查看Redis
ps -ef | grep redis
# 通过端口号检查 Redis 服务器状态:
netstat -nlt| grep 6379
# 在客户端中依次执行以下命令退出
shutdown
exit
参考博客:https://blog.csdn.net/weixin_52826368/article/details/126256943
keys *:查看当前数据库所有的key
exists key:判断一个 key 是否存在,存在返回 1,否则返回 0。
del key:删除某个 key,或是一系列 key,比如:del key1 key2 key3 key4。成功返回 1,失败返回 0(key 值不存在)。
type key:返回某个 key 元素的数据类型(none:不存在,string:字符,list:列表,set:元组,zset:有序集合,hash:哈希),key 不存在返回空。
keys key—pattern:返回匹配的 key 列表,比如:keys foo* 表示查找 foo 开头的 keys。
randomkey:随机获得一个已经存在的 key,如果当前数据库为空,则返回空字符串。
clear:清除界面。
rename oldname newname:更改 key 的名字,新键如果存在将被覆盖。
renamenx oldname newname:更改 key 的名字,新键如果存在则更新失败。
dbsize:返回当前数据库的 key 的总数。
flushall:清空全部数据
flushdb:清空当前库的数据
# 可以在创建key之后设置过期时间
expire keyName 5
# 也可以在创建key的同时设置过期时间
set keyName hello ex 5
127.0.0.1:6379> set str hello ex 100
OK
127.0.0.1:6379> ttl str
(integer) 78
flushdb:清空当前数据库中的所有键。 flushall:清空所有数据库中的所有键。
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> dbsize
(integer) 0
127.0.0.1:6379> set obj hello11
OK
127.0.0.1:6379> dbsize
(integer) 1
127.0.0.1:6379> flushall
OK
config get:用来读取运行 Redis 服务器的配置参数。
config set:用于更改运行 Redis 服务器的配置参数。
auth:认证密码。
刚开始时 Reids 并未设置密码,密码查询结果为空。
然后设置密码为 test111,再次查询报错。
经过 auth 命令认证后,可正常查询。
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass test111
OK
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth test111
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "test111"
config get *max-*-entries*
127.0.0.1:6379> config get *max-*-entries*
1) "hash-max-ziplist-entries"
2) "512"
3) "set-max-intset-entries"
4) "512"
5) "zset-max-ziplist-entries"
6) "128"
config resetstat:重置数据统计报告,通常返回值为“OK”。
info 命令可以查询 Redis 几乎所有的信息,其命令选项有如下:
server: Redis server 的常规信息
clients: Client 的连接选项
memory: 存储占用相关信息
persistence: RDB and AOF 相关信息
stats: 常规统计
replication: Master/Slave 请求信息
cpu: CPU 占用信息统计
cluster: Redis 集群信息
keyspace: 数据库信息统计
all: 返回所有信息
default: 返回常规设置信息
本章第3节已经介绍了一种设置密码的方式,现在介绍另一种通过修改配置文件来设置密码的方式。
# 方式一:在redis-cli窗口中使用config set requirepass 密码内容
# 方式二:修改配置文件
# step1
grep -n requirepass /usr/local/redis-5.0.5/redis.conf
# step2
vim /usr/local/redis-5.0.5/redis.conf
# step3
把requirepass配置后面的密码内容修改为test111
# step4
在redis-server窗口按ctrl+C退出服务器,然后重新启动
# 方式一:在redis-cli窗口中使用auth 密码内容
# 方式二:在启动客户端时登录
# 启动客户端时登录
redis-cli -a test111
传统的 key-value 是指支持使用一个 key 字符串来索引 value 字符串的存储,而 Redis 中,value 不仅仅支持字符串,还支持更多的复杂结构,包括列表、集合、哈希表等。
字符串类型的值最多能存储 512M 字节的内容。
# 将变量stringName的值设置为stringValue
set stringName stringValue
# 后面带一个nx参数,表示set 命令只有在没有相同 key 的情况下成功
set stringName stringValue nx
# 后面带一个xx参数,表示set 命令在有相同 key 值的情况下成功;如果后面不带xx或nx参数,默认将为这种效果
set stringName stringValue xx
示例:
root@instance-06frzma5:~# redis-cli
127.0.0.1:6379> set stringTest value0
OK
127.0.0.1:6379> get stringTest
"value0"
127.0.0.1:6379> set stringTest value1 nx
(nil)
127.0.0.1:6379> get stringTest
"value0"
127.0.0.1:6379> set stringTest value1 xx
OK
127.0.0.1:6379> get stringTest
"value1"
127.0.0.1:6379>
# stringName +1
incr stringName
# stringName +100
incr stringName 100
# stringName -1
decr stringName
# stringName -100
decrby stringName 100
示例:
127.0.0.1:6379> set index 0
OK
127.0.0.1:6379> incr index
(integer) 1
127.0.0.1:6379> incrby index 100
(integer) 101
127.0.0.1:6379> decr index
(integer) 100
127.0.0.1:6379> decrby index 20
(integer) 80
127.0.0.1:6379>
运用 mset 和 mget 命令可以一次性完成多个 key-value 的对应关系;使用 mget 命令,Redis 返回一个 value 数组。
127.0.0.1:6379> mset a 1 b hello c 2.0
OK
127.0.0.1:6379> mget a b c
1) "1"
2) "hello"
3) "2.0"
Redis Lists是简单的字符串列表。
# 向listName列表中从左边插入一个或多个元素
lpush listName 1 2 three four 5
# 查看列表中指定范围的元素,第一个数字0表示开始位置,第二个数字4表示结束位置;两个参数都可以为负数,表示倒数第几个元素
lrange listName 0 4
# 向listName列表中从右边插入一个或多个元素
rpush listName1 1 2 3 4 5
示例:
127.0.0.1:6379> lpush listName 1 2 three four 5
(integer) 5
127.0.0.1:6379> lrange listName 0 4
1) "5"
2) "four"
3) "three"
4) "2"
5) "1"
127.0.0.1:6379> rpush listName1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lrange listName1 0 4
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> lrange listName1 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
del表示清空列表,lpop表示从左边弹出一个元素,rpop表示从右边弹出一个元素。
127.0.0.1:6379> rpush list 1 2 3 4 5
(integer) 5
127.0.0.1:6379> lpop list
"1"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "3"
3) "4"
4) "5"
127.0.0.1:6379> rpop list
"5"
127.0.0.1:6379> lrange list 0 -1
1) "2"
2) "3"
3) "4"
127.0.0.1:6379> del list
(integer) 1
127.0.0.1:6379> lrange list 0 -1
(empty list or set)
一个列表最多可以包含 4294967295(2 的 32 次方减一)个元素,这意味着它可以容纳海量的信息,最终瓶颈一般都取决于服务器内存大小。
Redis 提供了阻塞式访问 brpop 和 blpop 命令。用户可以在获取数据不存在时阻塞请求队列,如果在时限内获得数据则立即返回,如果超时还没有数据则返回 nil。
# 从左边弹出一个元素,若获取不到则等待times秒
blpop listName times
# 从右边弹出一个元素,若获取不到则等待times秒
brpop listName times
# 查看listName列表的长度(元素个数)
llen listName
哈希主要用来表示对象,它们有能力存储很多对象。
# 创建一个对象objName,att1、att2、att3表示它的三个属性,val1、val2、val3表示对应的属性值
hmset objName att1 val1 att2 val2 att3 val3
# 获取指定属性的值域
hget objName att1
# 获取对象的所有属性和对应的值域
hgetall objName
# 获取多个属性的值域
hmget objName att1 att2
示例:
127.0.0.1:6379> hmset student name tracy gender male
OK
127.0.0.1:6379> hgetall student
1) "name"
2) "tracy"
3) "gender"
4) "male"
127.0.0.1:6379> hget student name
"tracy"
127.0.0.1:6379> hget student gender
"male"
127.0.0.1:6379> hmget student name gender
1) "tracy"
2) "male"
127.0.0.1:6379> hmset student name tracy gender male age 18
OK
127.0.0.1:6379> hgetall student
1) "name"
2) "tracy"
3) "gender"
4) "male"
5) "age"
6) "18"
127.0.0.1:6379> hincrby student age 1
(integer) 19
127.0.0.1:6379> hincrby student age -1
(integer) 18
Redis Set是一个无序的字符串集合,其中不会出现重复的元素。
# 添加元素
sadd setName item1 item2 item3
# 查看所有元素
smembers setName
示例:
127.0.0.1:6379> sadd setName 1 2 null
(integer) 3
127.0.0.1:6379> smembers setName
1) "null"
2) "1"
3) "2"
127.0.0.1:6379> sismember setName 3
(integer) 0
127.0.0.1:6379> sismember setName 1
(integer) 1
匹配成功返回 1,匹配失败返回 0。
有序集合的每一个成员都关联了一个权值,这个权值被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是权值可以是重复的。
# 往无序列表setName中添加一个元素item,score是一个参数(类型必须为小数或整数),用来作为排序依据。
zadd setName score item
示例:
127.0.0.1:6379> zadd setName 1 "wang"
(integer) 1
127.0.0.1:6379> zadd setName 2 "tuantuan"
(integer) 1
127.0.0.1:6379> zadd setName 3 "tuotuo"
(integer) 1
127.0.0.1:6379> zadd setName 0.2 "miao"
(integer) 1
一个是正序排序,一个是逆序排序。后面可以加一个withscores参数,会显示分数。
127.0.0.1:6379> zrange setName 0 -1
1) "miao"
2) "wang"
3) "tuantuan"
4) "tuotuo"
127.0.0.1:6379> zrevrange setName 0 -1
1) "tuotuo"
2) "tuantuan"
3) "wang"
4) "miao"
127.0.0.1:6379> zrevrange setName 0 -1 withscores
1) "tuotuo"
2) "3"
3) "tuantuan"
4) "2"
5) "wang"
6) "1"
7) "miao"
8) "0.20000000000000001"
127.0.0.1:6379> zrange setName 0 -1 withscores
1) "miao"
2) "0.20000000000000001"
3) "wang"
4) "1"
5) "tuantuan"
6) "2"
7) "tuotuo"
8) "3"
redis单条命令能保证原子性,但redis的事务是不保证原子性的。 某条命令错误,不影响事务中其他命令的执行。可通过上锁来保证事务的原子性。
redis中的事务: 一组命令的集合,顺序执行,具有一次性、顺序性、排他性。
multi表示开启事务,exec表示提交并执行事务。
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set name tracy
QUEUED
127.0.0.1:6379(TX)> set gender female
QUEUED
127.0.0.1:6379(TX)> set age 26
QUEUED
127.0.0.1:6379(TX)> get name
QUEUED
127.0.0.1:6379(TX)> get gender
QUEUED
127.0.0.1:6379(TX)> get age
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) "tracy"
5) "female"
6) "26"
悲观锁:认为什么时候都会出现问题,都加锁。
乐观锁:反之,认为什么时候都不会出现问题,都不需要加锁。更新数据的时候再去判断数据在此期间是否被修改过。
乐观锁实现:在redis中可通过watch关键字来给对象上乐观锁,底层是通过获取version,然后比较version判断是否被修改过。
127.0.0.1:6379> set money 1000
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 给money上乐观锁,事务执行完之后会自行解锁
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> decrby money 723
QUEUED
127.0.0.1:6379(TX)> incrby out 723
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> get out
QUEUED
127.0.0.1:6379(TX)> exec # 如果在事务执行期间,money被其他线程修改过,事务将会执行失败
1) (integer) 277
2) (integer) 723
3) "277"
4) "723"
127.0.0.1:6379> watch money # 上锁
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> incrby money 100
QUEUED
127.0.0.1:6379(TX)> decrby out 100
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> exec # 在事务执行期间有其他线程修改了money,导致事务执行失败
(nil)
127.0.0.1:6379> unwatch # 解锁
OK
127.0.0.1:6379> watch money # 重新上锁,获取最新值
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> incrby money 100
QUEUED
127.0.0.1:6379(TX)> decrby out 100
QUEUED
127.0.0.1:6379(TX)> get money
QUEUED
127.0.0.1:6379(TX)> exec # 执行成功
1) "1123"
2) (integer) 1223
3) (integer) -100
4) "1223"
先创建一个spring boot项目。
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.21</version>
</dependency>
package tracy;
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class Ping {
public static void main(String[] args) {
/**
* 1 new一个Jedis对象,第一个参数为主机,第二个是端口号
*/
Jedis jedis=new Jedis("localhost",6379);
/**
* 2 redis所有的命令对应jedis的一个个方法,自己查看下API就知道怎么用了
*/
System.out.println(jedis.ping());//连接成功会输出pong
//这里实现一个事务
JSONObject jsonObject=new JSONObject();
jsonObject.put("name","tracy");
jsonObject.put("gender","female");
jsonObject.put("major","software enginerring");
jedis.flushDB();
String str=jsonObject.toJSONString();
jedis.watch(str);//上一个乐观锁
Transaction multi=jedis.multi();//开启事务
try{
multi.set("user",str);
multi.exec();//执行事务
}catch(Exception e){
multi.discard();//如果失败,放弃事务
e.printStackTrace();
}finally {
System.out.println(jedis.get("user"));
//关闭jedis连接
jedis.close();
}
}
}
执行结果:
PONG
{"gender":"female","major":"software enginerring","name":"tracy"}
redis.conf详解
# redis服务器的启动命令
#./redis-server /path/to/redis.conf
# 存储单元
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
# redis不区分大小写
# units are case insensitive so 1GB 1Gb 1gB are all the same.
# 可以把别的配置文件也写到这个配置文件中来,可以把多个redis.conf组合成一个
# include /path/to/local.conf
# include /path/to/other.conf
# include /path/to/fragments/*.conf
# 绑定ip地址
bind 127.0.0.1 -::1
# 保护模式,保证安全性
protected-mode yes
# 端口号
port 6379
# 是否以守护进程的方式开启,相当于后台运行
daemonize yes
# 如果以后台方式运行,需要指定一个pid文件
pidfile /var/run/redis_6379.pid
# 日志级别
# 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
# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
# 日志的文件位置名,为空则表示输出
logfile ""
# 数据库的数量,默认16
databases 16
# 是否总是显示logo
always-show-logo no
redis是内存数据库,断点就会清零,因此有必要持久化。
# Unless specified otherwise, by default Redis will save the DB:
# * After 3600 seconds (an hour) if at least 1 change was performed
# * After 300 seconds (5 minutes) if at least 100 changes were performed
# * After 60 seconds if at least 10000 changes were performed
#
# You can set these explicitly by uncommenting the following line.
# 持久化,规定时间内执行多少次,会持久化到.rdb.aof文件
# save 3600 1 300 100 60 10000
# 持久化出错了,继续工作吗
stop-writes-on-bgsave-error yes
# 是否压缩rdb文件,会消耗cpu资源
rdbcompression yes
# 保存时,是否校验rdb文件,可以进行一些校准工作
rdbchecksum yes
# rdb文件保存的目录,是在服务器中的路径
dir ./
# 设置rdb文件名
dbfilename dump.rdb
这部分留到后面主从复制章节。
# 为redis的连接创建密码,默认是没有密码的
# requirepass foobared
# 最大客户端数
# maxclients 10000
# 最大内存容量
# maxmemory <bytes>
# 内存达到上限之后的处理策略
# maxmemory-policy noeviction
# volatile-lru -> 只对设置了过期时间的key采用LRU删除策略
# allkeys-lru -> 对所有的key采用LRU删除策略
# volatile-lfu ->只对设置了过期时间的key采用LFU删除策略
# allkeys-lfu -> 对所有的key采用LFU删除策略
# volatile-random -> 随机删除即将过期的key
# allkeys-random -> 随机删除key
# volatile-ttl -> 删除最接近过期时间ttl的key
# noeviction -> 永不过期,返回错误
# 默认不开启aof模式,大部分情况下,rdb模式的持久化够用了
appendonly no
# 持久化文件名
appendfilename "appendonly.aof"
# 每秒执行一次同步,可能丢失一秒的数据
appendfsync everysec
# 每次修改都同步,比较慢
# appendfsync always
# 不执行同步,速度快,但不可靠
# appendfsync no
# 默认aof文件是无限追加的
no-appendfsync-on-rewrite no
# 如果aof文件大于64M,就会fork一个新的进程来将我们的文件进行重写
auto-aof-rewrite-min-size 64mb
Redis是内存数据库,不把内存中的数据库保存到磁盘的话,断电后数据将无法被保存。
在 指定的时间间隔内 将内存中的数据集快照写入磁盘中,恢复时是将快照读取到内存中。
在RDB模式下,Redis会单独创建一个子进程进行持久化,现将数据写入到一个临时文件中,待持久化过程结束,再用这个临时文件替换上次持久化好的文件。整个过程性能很高,但是对数据恢复的完整性不是很敏感,最后一次持久化后的数据如果宕机了可能会导致丢失。
在主从复制中,rdb用来作备用,放在从机上面。
一般情况下,使用默认配置就够了。
优点:
适合大规模的数据恢复
适用于对数据完整性不高的情况,宕机可能会导致数据丢失
缺点:
需要一定的时间间隔,如果意外宕机了,最后一次修改的数据可能会丢失。
父进程创造子进程需要消耗一定的时间。
见上一章的持久化部分。
触发规则(生成dump.rdb文件):
1 满足redis.conf中save规则:
save 3600 1 300 100 60 10000
2 在redis-cli中执行flushall命令
3 退出redis
把rdb文件放到redis启动目录下,redis会检查并恢复其中的数据。
127.0.0.1:6379> config get dir
1) "dir"
2) "/root" # 这就是rdb文件存放的目录
将我们的 所有的写命令 都记录下来,追加到日志中,恢复数据时把所有命令都执行一遍。优点是数据更可靠;缺点是持久化效率、运行效率比rdb低,占用内存也更多。
默认是不开启的。当RDB和AOF都打开时,会优先用AOF文件来恢复数据。
配置见上一章的aof部分。
- 如果仅使用Redis作为缓存,即仅在服务器运行时存在,其实不用做任何持久化操作。
- 对于RDB,建议只在主从复制的从机上使用来作为备用,15分钟备份一次就够了。
- 对于AOF,好处是即使是最坏情况下丢失数据也不超过2s,坏处是AOF持久化会导致持续的IO,并且当AOF文件达到一定程度时会引起该文件的rewrite也会导致进程的阻塞。因此AOF文件上限应该设置大一些,比如5G以上。
- 如果想要保证数据的高可用性,不一定要使用AOF,主从复制一般也够用了,通过使用主机和从机上最新的RDB文件来恢复数据,不会带来持续的IO和rewrite,代价是当主机和从机都挂了(比如断电),会导致15分钟的数据丢失。
发布订阅是一种消息通信模式:发送者发送消息,订阅者接收消息。
# 将消息发布到指定频道
publish channel message
# 订阅一个或多个频道
subscribe channel [channel...]
# 订阅所有符合给定模式的频道
psubscribe pattern [pattern...]
# 退订一个频道
unsubscribe channel
# 退订所有符合给定模式的频道
punsubscribe pattern [pattern...]
消息订阅者(redis-cli)订阅tracy频道:
127.0.0.1:6379> subscribe tracy
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "tracy"
3) (integer) 1
1) "message"
2) "tracy"
消息发布者(redis-cli)往tracy线程中发布一条消息:
127.0.0.1:6379> publish tracy "welcome to tracy's channel!"
(integer) 1
127.0.0.1:6379>
消息订阅者(redis-cli)受到消息发布者发布的消息:
127.0.0.1:6379> subscribe tracy
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "tracy"
3) (integer) 1
1) "message"
2) "tracy"
3) "welcome to tracy's channel!"
建议主机以写为主,从机以读为主,因为大部分情况下都是进行读操作,因此主从复制能实现负载均衡。
redis集群最低配置是一主二从。
- 默认情况下,每台Redis服务器都是主节点;因此主从复制需要手动配置。
- 一个主节点可以有多个从节点,但一个从节点只能有一个主节点。
- 主机可以写,从机只能读。
- 主机中所有的信息都会被从机保存。
- 主机断开了,从机还是能继续工作,但是现在整个集群不能进行写操作了;等主机回来了,读写恢复正常。
作用:
- 数据冗余:热备份
- 故障恢复
- 负载均衡
- 高可用基础
127.0.0.1:6379> info replication
# Replication
role:master # 角色
connected_slaves:0 # 没有从机
master_failover_state:no-failover
master_replid:76c21a5ae3a2405d082e2ac88370ba4157aa9239
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配置文件来模拟一台主机(6379),两台从机(6380、6381)。
主redis6379.conf
# 端口号
port 6379
# 以后台方式运行,需要指定一个pid文件
pidfile /var/run/redis_6379.pid
# 日志的文件位置名
logfile "./log/6379.log"
# 设置rdb文件名
dbfilename dump6379.rdb
从:redis6380.conf
# 端口号
port 6380
# 以后台方式运行,需要指定一个pid文件
pidfile /var/run/redis_6380.pid
# 日志的文件位置名
logfile "./log/6380.log"
# 设置rdb文件名
dbfilename dump6380.rdb
从:redis6381.conf
# 端口号
port 6381
# 以后台方式运行,需要指定一个pid文件
pidfile /var/run/redis_6381.pid
# 日志的文件位置名
logfile "./log/6381.log"
# 设置rdb文件名
dbfilename dump6381.rdb
== 这种方式设置从机,一旦从机重启了,就会变回主机,不再属于原来的集群,需要重新设置。==
只需要配置从机,主机不需要配置,因为每台Redis服务器默认是主节点。
在三个终端下分别执行以下三条命令,启动三个Redis服务,并分别打开redis-cli
root@instance-06frzma5:~# redis-server /usr/local/bin/config/redis6379.conf
root@instance-06frzma5:~# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
root@instance-06frzma5:~# redis-server /usr/local/bin/config/redis6380.conf
root@instance-06frzma5:~# redis-cli -p 6380
127.0.0.1:6380> ping
PONG
root@instance-06frzma5:~# redis-server /usr/local/bin/config/redis6381.conf
root@instance-06frzma5:~# redis-cli -p 6381
127.0.0.1:6381> ping
PONG
在两个从机的redis-cli中执行以下命令:
slaveof 127.0.0.1 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=14,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=14,lag=0
master_failover_state:no-failover
master_replid:5a64423055330c1080030c187d4d066c11017494
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
在从机中查看:
127.0.0.1:6381> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_read_repl_offset:280
slave_repl_offset:280
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:5a64423055330c1080030c187d4d066c11017494
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:280
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:15
repl_backlog_histlen:266
在redis-cli中执行slaveof no one命令将会导致当前服务器变回主机
修改redis6380.conf和redis6381.conf
# 配置所属的主机ip和端口
replicaof 127.0.0.1 6379
从机(Slave)连接至主机(Master):
- 无论是首次连接,还是断开后连接,只要是连接上了,Slave会发送一个sync同步命令。
- Master接收到命令,会将整个数据文件传送给Slave,完成一次完全同步。(全量复制)
- 后续当Master中发生更新操作,将会将所有修改命令依次传给Slave,完成同步。(增量复制)
主从切换技术的方法是∶当主服务器宕机后,需要手动把一台从服务器切换为主服务器,这就需要人工干预,费事费力,还会造成一段时间内服务不可用。这不是一种推荐的方式,更多时候,我们优先考虑哨兵模式IRedis从2.8开始正式提供了Sentinel (哨兵)架构来解决这个问题。
哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个 独立 的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。
优点:
主从可切换,故障可以转移,提升系统可用性
是主从模式的升级,从手动到自动
缺点:
redis不好在线扩容
实现哨兵模式的配置比较麻烦
- 主机挂掉了。
- 主观下线:一个哨兵检测到这个结果,认为主机不可用。
- 客观下线:后面多个哨兵监测到主机不可用,然后其中一个哨兵发起投票,即进行故障转移操作(failover),由所有从机进行投票,从剩下的从机中选出一个新的主机。然后这些哨兵通过发布订阅模式把自己监控的该从机切换成主机。
考虑到我的云服务器资源有限,这里我只配置一个哨兵。
创建一个sentinel.conf配置文件,做如下配置:
# redis-master表示监控的redis服务器的命名,随便写
# 最后的1表示:如果有1个哨兵发现主机挂掉了,会发起投票选一个新的主机
sentinel monitor redis-master 127.0.0.1 6379 1
1 启动一主(6379)二从(6380和6381)
并且让两个从机从属于主机。
2 启动哨兵
redis-sentinel /usr/local/bin/config/sentinel.conf
3 让master挂了
127.0.0.1:6379> shutdown
not connected> exit
4 观察哨兵监控信息
195744:X 06 Jan 2023 14:40:14.293 # Sentinel ID is cd97118419830e7d65a3993b5dd3e735f3d1df34
195744:X 06 Jan 2023 14:40:14.293 # +monitor master redis-master 127.0.0.1 6379 quorum 1
195744:X 06 Jan 2023 14:43:45.484 # +sdown master redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.484 # +odown master redis-master 127.0.0.1 6379 #quorum 1/1
195744:X 06 Jan 2023 14:43:45.484 # +new-epoch 2
195744:X 06 Jan 2023 14:43:45.484 # +try-failover master redis-master 127.0.0.1 6379 # 这会主机已经挂掉了
195744:X 06 Jan 2023 14:43:45.488 * Sentinel new configuration saved on disk
195744:X 06 Jan 2023 14:43:45.488 # +vote-for-leader cd97118419830e7d65a3993b5dd3e735f3d1df34 2 # 开启投票
195744:X 06 Jan 2023 14:43:45.488 # +elected-leader master redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.488 # +failover-state-select-slave master redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.550 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.550 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.605 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.685 * Sentinel new configuration saved on disk
195744:X 06 Jan 2023 14:43:45.685 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.685 # +failover-state-reconf-slaves master redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:45.748 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:46.744 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:46.744 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:46.800 # +failover-end master redis-master 127.0.0.1 6379
195744:X 06 Jan 2023 14:43:46.800 # +switch-master redis-master 127.0.0.1 6379 127.0.0.1 6381 # 主机换成了6381
195744:X 06 Jan 2023 14:43:46.800 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ redis-master 127.0.0.1 6381
195744:X 06 Jan 2023 14:43:46.800 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ redis-master 127.0.0.1 6381
195744:X 06 Jan 2023 14:43:46.803 * Sentinel new configuration saved on disk
195744:X 06 Jan 2023 14:44:16.810 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ redis-master 127.0.0.1 6381
关闭哨兵进程
ps aux|grep redis-sentinel
kill pid
上面只是做了一个最基础的配置,实际上哨兵模式的配置文件是很多的。
# 绑定IP地址
# bind 127.0.0.1
# 端口
port 26379
# 保护模式(是否禁止外部链接,除绑定的ip地址外)
# protected-mode no
# 是否作为守护程序运行
daemonize no
# 启用守护进程运行后,Redis将在/var/run/redis-sentinel.pid中写入一个pid文件
pidfile /var/run/redis-sentinel.pid
# 指定日志文件名。 如果值为空,将强制Sentinel日志标准输出。守护进程下,如果使用标准输出进行日志记录,则日志将发送到/dev/null
logfile ""
# 以下两个配置指令在环境中非常有用,因为NAT可以通过非本地地址从外部访问Sentinel。
# 当提供announce-ip时,Sentinel将在通信中声明指定的IP地址,而不是像通常那样自动检测本地地址。
# 类似地,当提供announce-port 有效且非零时,Sentinel将宣布指定的TCP端口。
# 这两个选项不需要一起使用,如果只提供announce-ip,Sentinel将宣告指定的IP和“port”选项指定的服务器端口。
# 如果仅提供announce-port,Sentinel将通告自动检测到的本地IP和指定端口。
# sentinel announce-ip <ip>
# sentinel announce-port <port>
# 工作目录
# dir <working-directory>
dir /usr/local/redis7/sentinel
# 告诉Sentinel监听指定主节点,并且只有在至少<quorum>哨兵达成一致的情况下才会判断它 O_DOWN 状态。
# 从机是自动发现的
# 注意:主节点(master)名称不能包含特殊字符或空格。有效字符可以是 A-z 0-9 和这三个字符 ".-_".
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 如果redis配置了密码,那这里必须配置认证,否则不能自动切换
# sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 主节点或副本在指定时间内没有回复PING,便认为该节点为主观下线 S_DOWN 状态。默认是30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 在故障转移期间,多少个副本节点进行数据同步
# sentinel parallel-syncs <master-name> <numreplicas>
sentinel parallel-syncs mymaster 1
# 指定故障转移超时(以毫秒为单位)。
# # 它以多种方式使用:
# - 在先前的故障转移之后重新启动故障转移所需的时间已由给定的Sentinel针对同一主服务器尝试,是故障转移超时的两倍。
# - 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。
# - 取消已在进行但未生成任何配置更改的故障转移所需的时间
# - 当进行failover时,配置所有slaves指向新的master所需的最大时间。
# 即使过了这个超时,slaves依然会被正确配置为指向master。
# sentinel failover-timeout <master-name> <milliseconds>
# 默认3分钟
sentinel failover-timeout mymaster 180000
# 通知脚本
# sentinel notification-script和sentinel reconfig-script用于配置调用的脚本,以通知系统管理员或在故障转移后重新配置客户端。
# # 脚本使用以下规则执行以进行错误处理:
# - 如果脚本以“1”退出,则稍后重试执行(最多重试次数为当前设置的10次)。
# - 如果脚本以“2”(或更高的值)退出,则不会重试执行。
# - 如果脚本因为收到信号而终止,则行为与退出代码1相同。
# - 脚本的最长运行时间为60秒。 达到此限制后,脚本将以SIGKILL终止,并重试执行。
# sentinel notification-script <master-name> <script-path>
#
# 为警告级别生成的任何Sentinel事件调用指定的通知脚本(例如-sdown,-odown等)。
# 此脚本应通过电子邮件,SMS或任何其他消息传递系统通知系统管理员 监控的Redis系统出了问题。
#
# 使用两个参数调用脚本:第一个是事件类型,第二个是事件描述。
# 该脚本必须存在且可执行,以便在提供此选项时启动sentinel。
#
# 举例:
#
# sentinel notification-script mymaster /var/redis/notify.sh
# 客户重新配置脚本
# sentinel client-reconfig-script <master-name> <script-path>
# 当主服务器因故障转移而变更时,可以调用脚本执行特定于应用程序的任务,以通知客户端,配置已更改且主服务器地址已经变更。
# 以下参数将传递给脚本:
# <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 用于传递主服务器的旧地址和所选副本的新地址。
# 举例:
# sentinel client-reconfig-script mymaster /var/redis/reconfig.sh
# 安全
# 避免脚本重置,默认值yes
# 默认情况下,SENTINEL SET将无法在运行时更改notification-script和client-reconfig-script。
# 这避免了一个简单的安全问题,客户端可以将脚本设置为任何内容并触发故障转移以便执行程序。
sentinel deny-scripts-reconfig yes
# REDIS命令重命名
# 例如,如果主“mymaster”和相关副本的“CONFIG”全部重命名为“GUESSME”,我可以使用:
# SENTINEL rename-command mymaster CONFIG GUESSME
# 设置此类配置后,每次Sentinel使用CONFIG时,它将使用GUESSME。 请注意,实际上不需要尊重命令案例,因此在上面的示例中写“config guessme”是相同的。
# SENTINEL SET也可用于在运行时执行此配置。
# 为了将命令设置回其原始名称(撤消重命名),可以将命令重命名为它自身:
# SENTINEL rename-command mymaster CONFIG CONFIG
这一章都是一些可用性问题。
用户查询一条数据,缓存没有命中,持久层数据库中也没有。这种现象多了,缓存总是无法命中,于是频繁请求持久层数据库,给持久层数据库带来很大负担。
解决:
1 过滤器
2 空缓存
布隆过滤器
对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合则丢弃该请求,从而避免对更底层存储系统的查询压力。
缓存空对象
对于请求未命中的对象,在缓存中设置一些空对象,查询的时候就会返回缓存中的空对象,从而避免去查询持久层数据库。
缺点:空对象占内存;空对象的存在会导致缓存和持久层数据不一致。
缓存中的热点数据扛着整个系统的大并发量,在该热点数据失效的瞬间,大量请求击穿缓存,去访问持久层。
设置热点数据永不过期:
缺点:一直不过期会浪费空间
分布式锁:
给数据上锁,保证数据只能同时被一个请求查询,高并发的压力被转移到了分布式锁。
缺点:分布式锁会面临很大考验。
在一个时间段,缓存集中过期失效。可能原因有数据缓存到期、缓存服务器宕机和断网等原因。这时所有请求直接进入持久层,导致数据库也顶不住压力挂了。