• 什么是缓存?
  • 本地缓存
    • Caffeine 库
  • 多级缓存
  • Redis 分布式缓存
    • 数据类型
    • 常用操作
    • Java 操作 Redis
      • Spring Boot Redis Template
      • Redisson
    • 主从模型搭建
    • 哨兵集群搭建
    • 日志持久化
  • 缓存(Redis)应用场景
    • 数据共享
    • 单点登录
    • 计数器
    • 限流
    • 点赞
    • 实时排行榜
    • 分布式锁
  • 缓存常见问题
    • 缓存雪崩
    • 缓存击穿
    • 缓存穿透
    • 缓存更新一致性

安装教程

1、准备

升级gcc

1、因为redis-5以上要使用gcc5.3以上版本进行编译,但是centos7默认安装的gcc版本是4.8.5,所以需要升级gcc版本:

image-20230603113339603

如果gcc版本过低,编译的时候会出错:

image-20230603113412451

1
2
3
4
5
#安装gcc
yum -y install gcc tcl

# 查看gcc版本是否在5.3以上,centos7.6默认安装4.8.5
gcc -v
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 升级到gcc 9.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 -e "\nsource /opt/rh/devtoolset-9/enable" >>/etc/profile


# 查看gcc版本
# [root@node01 ~]# gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/opt/rh/devtoolset-9/root/usr/libexec/gcc/x86_64-redhat-linux/9/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,fortran,lto --prefix=/opt/rh/devtoolset-9/root/usr --mandir=/opt/rh/devtoolset-9/root/usr/share/man --infodir=/opt/rh/devtoolset-9/root/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-gcc-major-version-only --with-linker-hash-style=gnu --with-default-libstdcxx-abi=gcc4-compatible --enable-plugin --enable-initfini-array --with-isl=/builddir/build/BUILD/gcc-9.3.1-20200408/obj-x86_64-redhat-linux/isl-install --disable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=x86-64 --build=x86_64-redhat-linux
Thread model: posix
gcc version 9.3.1 20200408 (Red Hat 9.3.1-2) (GCC)

2、安装Redis

下载安装

1
2
3
4
5
6
7
8
9
10
11
# 下载redis安装包
wget http://download.redis.io/releases/redis-6.2.5.tar.gz

# 解压安装包
[root@node01 modules]# tar -zxvf redis-6.2.5.tar.gz

# 进入安装包
[root@node01 modules]# cd redis-6.2.5/

# 编译和安装
make && make test && make install

如果出错,执行

1
2
3
4
5
# 编译出错时,清出编译生成的文件
make distclean

# 卸载
make uninstall

3、配置

1、将服务端脚本和客户端脚本移动到新建的文件夹下面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 在安装目录下创建一个 bin 目录
[root@node01 redis-6.2.5]# mkdir bin
[root@node01 redis-6.2.5]# cd src/
# 将 redis-server 和 redis-cli 移动到 bin 目录中
[root@node01 src]# cp redis-server ../bin/
[root@node01 src]# cp redis-cli ../bin/

[root@node01 src]# cd ..
[root@node01 redis-6.2.5]# ls
00-RELEASENOTES BUGS CONTRIBUTING deps Makefile README.md runtest runtest-moduleapi sentinel.conf tests utils
bin CONDUCT COPYING INSTALL MANIFESTO redis.conf runtest-cluster runtest-sentinel src TLS.md
# 将 redis 的配置文件 redis.conf 移动到 bin 目录下
[root@node01 redis-6.2.5]# cp redis.conf bin/
[root@node01 redis-6.2.5]# cd bin/
[root@node01 bin]# ls
redis-cli redis.conf redis-server

# 修改配置文件,修改的内容在下面
[root@node01 bin]# vim redis.conf
[root@node01 bin]# ./redis-server redis.conf
[root@node01 bin]# ./redis-cli
127.0.0.1:6379> set test hello
OK
127.0.0.1:6379> get test
"hello"

2、配置redis.conf文件

1
2
3
4
5
6
7
8
9
10
11
# 设置可以访问redis服务的IP
bind 0.0.0.0

# 设置redis的访问端口
port 6379

# 设置访问redis的密码
requirepass 123456

# 设置 redis-server 以守护线程方式启动
daemonize yes

3、启动redis,登录数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动 redis 服务, 后面加 & 参数后台表示启动
$REDIS_HOME/bin/redis-server redis.conf &

# 查看 redis 进程是否启动
[root@localhost bin]# ps -ef | grep redis
root 85823 62546 0 23:54 pts/1 00:00:00 ./redis-server 0.0.0.0:6379
root 85839 83082 0 23:55 pts/2 00:00:00 grep --color=auto redis


# 启动 redis 的客户端
$REDIS_HOME/bin/redis-cli

# 登录 redis
auth [yourpassword]

4、开启自启动

1
2
# 创建开机自启动脚本的文件
vim /etc/init.d/redis

编写开机自启动的脚本, 注意替换自己的redis的启动命令的位置以及配置文件的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#!/bin/sh
# Configurations injected by install_server below....

EXEC=/home/redis-6.2.6/bin/redis-server
CLIEXEC=/home/redis-6.2.6/bin/redis-cli
PIDFILE=/var/run/redis_6379.pid
CONF="/home/redis-6.2.6/bin/redis.conf"
REDISPORT="6379"
###############
# SysV Init Information
# chkconfig: - 58 74
# description: redis_6379 is the redis daemon.
### BEGIN INIT INFO
# Provides: redis_6379
# Required-Start: $network $local_fs $remote_fs
# Required-Stop: $network $local_fs $remote_fs
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Should-Start: $syslog $named
# Should-Stop: $syslog $named
# Short-Description: start and stop redis_6379
# Description: Redis daemon
### END INIT INFO


case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
echo "Starting Redis server..."
# 启动 Redis
$EXEC $CONF
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo "Stopping ..."
# $CLIEXEC -p $REDISPORT shutdown
# 带有密码的redis关闭方式: redis-cli -a 123456 shutdown
$CLIEXEC -a 123456 shutdown
while [ -x /proc/${PID} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
echo "Redis stopped"
fi
;;
status)
if [ ! -f $PIDFILE ]
then
echo 'Redis is not running'
else
PID=$(cat $PIDFILE)
if [ ! -x /proc/${PID} ]
then
echo 'Redis is not running'
else
echo "Redis is running ($PID)"
fi
fi
;;
restart)
$0 stop
$0 start
;;
*)
echo "Please use start, stop, restart or status as first argument"
;;
esac

设置开机自启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 为redis开机启动脚本赋予执行权限
chmod +x /etc/init.d/redis

# 开启redis服务
systemctl start redis

# 查看redis的状态
systemctl status redis

# 停止redis服务
systemctl stop redis

# 查看redis服务的状态
systemctl status redis

# 设置redis开机自启动
systemctl enable redis

# 设置禁止redis开机自启动
systemctl disable redis

需要关闭防火墙

1
2
3
4
5
6
7
8
9
1.首先切换到 root:su -root

2.进入 /bin 目录:cd /bin

3.执行命令:systemctl stop firewalld.service(关闭防火墙)

systemctl disable firewalld.service(关闭防火墙自动启动)

4.查看防火墙状态:systemctl status firewalld.service(查看防火墙服务状态)

上述练习可用docker代替👍👍👍

1
auth 密码

什么是缓存?

缓存就是数据交换的缓冲区(称作Cache),是存贮数据(使用频繁的数据)的临时地方。当用户查询数据,首先在缓存中寻找,如果找到了则直接执行。如果找不到,则去数据库中查找。

常规操作

这些数据类型都是支持push/pop、add/remove 及交集并集和差集,而且这些操作都是原子性的

其他

Redis支持各种不同方式的排序,Redis会周期性的吧更新的数据写入磁盘或者把修改操作追加到记录文件中,并且还可以实现(master-slave)主从同步。

单线程+多路IO复用技术

  1. redis是单线程+多路IO复用技术
  2. 多路复用是指一个线程来检查多个文件描述符(socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则直到超时。

数据类型操作

key操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
keys * 查看当前库有那些Keys
existe key是否存在
del key 删除key
type key key是什么类型
unlink key 删除key(非阻塞删除)
expire key 设置key过期时间
ttl key 查看多久过期(-1表示永不过期,-2表示已过期)
select 切换数据库
dbsize 查看当前数据库key的数量
flushdb 清空当前数据库
flushall 清空所有

set 设置 key
get 获取key
append 最佳值
strlen 获取值长度
setnx 只有key不存在时,设置key的值
incr 将key中存储的数字增加1(仅对数字操作)
decr 将key的值减1
incrby / decrby key 步长 固定增加减少多少(步长)

mset <key1><value1><key2><value2> 可设置多个值
mget ... 获取多个值
msetnx 同时设置多个,仅对不存在的key(有一个key存在,则会失败)
setex <key> <过期时间> <value>设置过期时间同时设置值
getset <key> <value> 以新换旧,设置新值的同时获取旧值

数据类型

sring 字符串

string的数据结构为简单的动态字符串,内部结构实现上类似Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,最大长度512M

list 列表
  1. 简单的字符串列表,按照插叙顺序排序,你可以添加一个元素在列表的头(左边)或者尾部(右边)
  2. 他的底层实际是一个双向链表,对两边操作性能很高。
1
2
3
4
5
6
7
8
lpush/rpush <key><value1><value2>... 左/右添加
lpop/rpop 弹出左/右值
rpoplpush <key1><key2> 弹出列表右边一个值,插入到key2列表左边
lrange <key><start><stop> 按照索引下标获取元素(左到右)
llen <key>获取长度
linsert <key> before <value> <newvalue><value>后插入<newvalue>
lrem <key><n><value> 从左边删除n个value(从左到右)
lset <key><index><value>将列表key下标为index的值替换成value
set 集合

与list类似,但是可以自动去重

1
2
3
4
5
6
7
8
9
10
11
sadd <key><value1><value2> 添加多个值
smembers <key> 取出该集合的所有值
sismenmber <key><value> 判断集合<key>是否含有该<value>值 有1,没有0
scard <key> 返回该集合的元素个数
srem <key><value1><value2>... 删除集合中的某个元素
spop <key> 随机吐出集合中的某个值
srandmember <key><n> 随机从该集合中取出n个值,不会删除
smove <source><destination> value 把集合中的值一个值移动到另一个集合
sinter <key1><key2> 返回两个集合的交集元素
sunion <key1><key2> 返回并集元素
sdiff <key1><key2> 返回差集元素
hash 哈希

hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

类似Java里面的Map<String, Object>

1
2
3
4
5
6
7
8
hset <key><field><value>给key集合中的field键赋值
hget <key><field> 获取field的值
hmset <key1><field1><value><field2><value2>... 设置多个值
hexists <key1><field> 查看field是否存在
hkeys <key> 列出所有field
hvals <key> 列出所有value
hincrby <key><field><increment> 对field 增加 1
hsetnx <key><field><value> field不存在,才会添加
zset 有序集合

zset与普通集合set 非常相似,是一个没有相同元素的字符串集合。

不同之处是有序集合的每个成员都关联了一个评分(score),这个评分被用来排序

底层 使用了两个数据结构

(1) hash, hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素找到相应的score值

(2)跳跃表,跳跃表的目的是在于给元素value排序,根据score的范围获取元素列表

下图,普通链表查找顺序需要挨个比较查找,但是下方如果要找出51,跳跃表则会和第2层1进行比较,51比1大,第2层下一个与51进行比较最后到NULL会下降的下一层进行再同样的方式进行比较…最终4次找出51

image-20230603213758789

1
2
3
4
5
6
7
zadd <key><score1><value1><score2><value2>...  将一个或多个member元素及其score值加入到有序集key当中。
zrange <key><start><stop>[WITHSCORES] 返回有序集key中,下标在<start><stop>之间的元素
zrangebyscore <key> <min> <max> [withscores] [limit offset count] 返回有序集 key 中,所有score值介于min和max 之间(包括等于min或max )的成员
zincrby <key><increment><value> 为元素的score加上增量
zrem <key><value> 删除该集合下,指定值的元素v
zcount <key><min><max> 统计该集合,分数区间内的元素个数。
zrank <key><value> 返回该值在集合中的排名,从0开始。
Bitmaps
  1. Bitmaps本身不是一种数据类型,实际上它就是字符串,但是他可可以对字符串进行位操作
  2. Bitmaps单独提供一套命令,所以redis中使用Bitmaps和使用字符串的方法不同,可以把Bitmaps想象成一个以位为单位的数组,数组中每个单元只能存储0和1,数组下标再Bitmaps中叫做偏移量
  3. 可用于做多用户活跃度
1
2
3
4
5
6
7
setbit <key> <offset> <value>
getbit <key> <offset>
bitcount <key> [start end]
bitop operation destkey key [key ...]
对多个字符串进行位操作,并将结果保存到destkey中
operation 可以是 AND、OR、XOR 或者 NOT
BITOP AND destkey srckey1 srckey2 srckey3 ... srckeyN,对多个key求逻辑与,并将结果保存到destkey中
HyperLogLog
  1. 用于基数统计,UV(UniqueVisitor,独立访客), 独立IP、搜索记录等需要去重和计数的问题。
  2. 会自动去重
1
2
3
pfadd <key><element>[element...] 添加指定元素到hyperloglog中 (成功1否则0)
pfcount <key> 数量统计
pfmerge <destkey><sourcekey> [sourcekey...] 将一个或者多个HLL合并后的结果存储在另一个HLL中, 比如每月活跃度用户可以使用每天的活跃度来计算
Geospatial

地理信息的缩写,可用于经纬度设置、查询、范围查询、距离查询、经纬度Hash等常见操作,一般通过Java程序一次性导入

1
2
3
4
heoadd <key><longitude><latitude><member>[longitude latitude member...]
例如:
geoadd china:city 121.47.31.23 shanghai
geoadd china:city 106.50.29.53 chognqing 114.05 22.52 shenzhen 116.38 39.90 beijing

配置文件介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Units 配置大小单位,开头定义了一些基本的度量单位,只支持bytes,不支持bit
bind 默认情况bind=127.0.0.1只能接受本机的访问请求 注释掉
protected-mode 将本机访问保护模式设置no
port 端口号
tcp-backlog 设置tcp的backlog,backlog其实是一个连接队列,backlog队列总和=未完成三次握手队列 + 已经完成三次握手队列。
timeout 一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭
tcp-keepalive 对访问客户端的一种心跳检测,每个n秒检测一次。
daemonize 是否为后台进程,设置为yes docker设置为no
pidfile 存放pid文件的位置,每个实例会产生一个不同的pid文件
loglevel 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为notice
logfile 日志文件名称
databases 设定库的数量 默认16,默认数据库为0,可以使用SELECT <dbid>命令在连接上指定数据库id
requirepass 密码
maxclients 设置redis同时可以与多少个客户端进行连接。
maxmemory 内存大小
maxmemory-policy 内存策略
volatile-lru:使用LRU算法移除key,只对设置了过期时间的键;(最近最少使用)
allkeys-lru:在所有集合key中,使用LRU算法移除key
volatile-random:在过期集合中移除随机的key,只对设置了过期时间的键
allkeys-random:在所有集合key中,移除随机的key
volatile-ttl:移除那些TTL值最小的key,即那些最近要过期的key
noeviction:不进行移除。针对写操作,只是返回错误信息
maxmemory-samples 设置样本数量,LRU算法和最小TTL算法都并非是精确的算法,而是估算值,所以你可以设置样本的大小,redis默认会检查这么多个key并选择其中LRU的那个。
一般设置3到7的数字,数值越小样本越不准确,但性能消耗越小。

发布与订阅

  1. Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息。
  2. Redis 客户端可以订阅任意数量的频道。

image-20230603215831421

1
2
3
4
5
打开一个客户端订阅channel1
SUBSCRIBE channel1
打开另一个客户端,给channel1发布消息hello
publish channel1 hello
打开第一个客户端可以看到发送的消息

image-20230603220207952

image-20230603220214534

Jedis

Java操作redis工具

1
2
3
4
5
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.4.1</version>
</dependency>
简单使用
1
2
3
4
5
6
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
System.out.println(jedis.lrange("list", 0, 4));
[123, 100, 200, 300, 500]
System.out.println(jedis.keys("*"));
[uu, uu2, k1, k2, andkey, list]
1
jedis.flushDB(); // 清空所有key

image-20230606220300199

验证码功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public static void verifyCode(String phone, String code) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
String verifyCode = jedis.get(phone);
if (verifyCode == null) {
System.out.println("未发送验证码!");
return;
}
if (!verifyCode.equals(code)) {
System.out.println("失败");
return;
}
System.out.println("成功");
}

// 发送验证码
public static void sendVerifyCode(String phone) {
int verifyCodeTimeout = 120;
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("123456");
if (jedis.get(phone) != null) {
System.out.println("验证码存在!");
jedis.close();
return;
}
jedis.setex(phone, verifyCodeTimeout, getVerifyCode());
}

// 生成随机验证码
public static String getVerifyCode() {
return String.valueOf(new Random().nextInt(10000));
}

SpringBoot整合redis

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
8
// 基本配置
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=123456
// 配置连接池
spring.redis.lettuce.pool.max-idle=16
spring.redis.lettuce.pool.max-active=32
spring.redis.lettuce.pool.min-idle=8

事务和锁

  1. 串联多个命令,防止命令插队
  2. Multi、Exec、discard
  3. 当Multi命令开始,输入的命令都会依次进入到命令队列中,但不会执行,知道输入Exec后,Redis会将之前的命令队列中的命令依次执行(执行命令,类似commit)
  4. 组队的过程中可以通过discard来放弃组队(放弃命令,类似rollback)
  5. 如果组队中有错误,整条组队都不会执行,如果执行中有错误,就仅错误的命令不执行。🎈🎈🎈

image-20230607105134192

事务冲突
  1. 悲观锁,每次拿数据的时候都认为i别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block知道它拿到锁。传统的关系型系统数据库里边就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等。
  2. 乐观锁,通过版本号来限制操作,例如AB两个人对某个数据进行修改,先修改的会更新version这个字段,后修改的会发现数据库中的version和自己的version不一样,就不能进行操作,直到自己更新后才能操作。

在执行multi之前,可通过watch key[key…]来监视一个或者多个key,如果在事务执行之前执行,并且这个key被修改了,那么事务就会被打断(乐观锁)

image-20230607111754781

image-20230607111853044

Redis事务三特性🎈🎈🎈
  1. 单独的隔离操作:事务中的所有命令都会序列化,按顺序执行。事务在执行的过程中,不会被其他客户端发送来的命令打断
  2. 没有隔离级别的概念:队列中的命令没有提交之前不会实际被执行,因为事务提交之前任何指令都不会被实际执行
  3. 不保证原子性:事务中如果有一条命令执行失败,其后的命令任然会被执行,没有回滚
案例
1
2
3
4
5
6
7
8
9
10
@GetMapping("/t")
public String test() {
ValueOperations valueOperations = redisTemplate.opsForValue();
Integer product = Integer.valueOf(String.valueOf(valueOperations.get("product")));
if (product > 0) {
System.out.println("产品: " + valueOperations.increment("product", -1));
return "抢购成功";
}
return "抢完了";
}

500个线程,抢购商品,商品卖成负数了

image-20230607213612719

通过lua脚本保证原子性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String lua = "local current = redis.call('get', 'product')\n" +
"if tonumber(current) > 0 then\n" +
" redis.call('decr', 'product')\n" +
" return 1\n" +
"end\n" +
"return 0\n";

@Autowired
public TestController(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}

@GetMapping("/t")
public String test() {
RedisScript longDefaultRedisScript = new DefaultRedisScript(lua, Long.class);
Integer execute = Integer.valueOf(String.valueOf(redisTemplate.execute(longDefaultRedisScript, null, "1")));
if (execute == 1) {
return "请购成功";
}
return "抢购完了";
}

多线程出现问题

超卖
  1. 通过事务乐观锁解决(会出现少卖)
  2. 通过Lua脚本
连接超时

请求连接数大于redis设置的连接数,可通过设置redis的超时时间和连接个数

持久化

RDB
  1. 指定时间间隔类,进行数据集快照,但是会造成数据丢失(服务器宕机)
  2. redis会单独创建(fork)一个子进程来进行持久化,会先将数据写入到一个临时文件中,持久结束后,再用这个临时文件替换上一次持久化好的文件,整个过程中,主进程是不进行任何io操作的,这就确保了极高的性能,如果需要大规模的数据恢复,且对数据的完整性不是非常敏感,那rdb方式要比aop方式更加高效。
AOF
  1. 对写操作进行持久化,不会出现数据丢失

  2. 通过日志的形式,记录每一个写操作


  3. 如果AOF和RDB同时开启,redis会默认执行AOF的

  4. 配置修改(其中配置文件被删除,会自动生成一个.bak的文件防止删除,修改名字后又可以用了)

  5. appendonly no 修改为 yes

  6. 文件异常恢复,如果AOF文件损坏了,可以用过/usr/local/bin/redis-check-aof–fix 指令修复appendonly.aof文件

  7. 同步频率设置 appendfsync:aways 当redis写入立即记入日志,会损耗性能,但是完整性好,everysec 每秒记入一次,no 不主动同步,交给操作系统

  8. Rewrite压缩(压缩.aof文件大小):当appendonly.aof文件大于64M时才会重写压缩

主从复制

  1. 读写分离(主机写,从机读),性能扩展,容灾快速回复(某一台Redis宕机后,其他能快速恢复提供服务)
  2. 一主一从或者一主多从
  3. 原理:
搭建
  1. 创建一个主机且一个或者多个从机

  2. 准备redis.conf配置文件,关闭appendonly

  3. 在每个redis配置文件中填入以下

    image-20230609151820424

  4. 再依次启动redis服务器,在从机中通过slaveof [host] [port],可通过info replicatioin查看当前redis的主从状态

一主两从
  1. 从服务器如果挂掉了,再此重启后会变成主服务器(需要重新设置slaveof)
  2. 主服务器挂掉后,从服务器依然知道主服务器,不会篡位。主服务器重启后,依然能用
  3. 原理:
    1. 当从连接上主服务器后,从服务器向主服务器发送行数据同步信息
    2. 主服务器接收到从服务器发送过来的同步信息后进行持久化,将持久化文件发送给从服务器使用
  4. 数据类型:全量复制和增量复制
薪火相传

原版

image-20230609215134339

更新(此方式,可降低主服务器的压力)

image-20230609215240573

反客为主
  1. 当主服务器挂掉后,从服务器上位
  2. 指令slaveof no one 将从机变为主机(缺点,需要手动设置, 可通过哨兵模式处理)

哨兵模式

主服务器发生故障,自动将从机切换成主机,异常服务器重启后会自动变成从机;并且会有复制延时的问题:由于所有写操作都是写再master上的,然后再同步到slave上,所有有一定延迟,系统繁忙时,延迟问题会加剧

image-20230609220135353

步骤:

  1. 自定义/myredis目录下新建sentinel.conf文件名字(不能写错)
  2. 配置哨兵: sentinel monitor mymaster [host] [ip] 1 (mymaster为监控对象的服务器名称,1为至少有多少个哨兵同意迁移的数量)
  3. 启动哨兵:redis-sentinel 配置文件路径
  4. 其中redis配置文件中replica-priority可设置成为主服务器优先级(选择顺序: 优先级->偏移量(同步数据量)->runid最小的)

集群

解决:读写压力,主从复制,薪火相传模式,主机宕机导致IP地址放生变化从而需要去更改配置

无中心化集群

服务间分别创建主从复制,这样能保证如果一个服务宕机了,其他服务还能使用

应用问题解决

缓存穿透

现象: web应用服务器压力突然变大,访问redis缓存不存在的数据,然后直接去访问数据库(导致突然大量请求直接访问数据库,导致数据库压力迅速增加,最终导致数据库崩溃)

解决方案:

  1. 对空值缓存: 如果一个查询返回的数据为空(不管数据存不存在),我们任然把这个空结果进行缓存
  2. 设置可访问的名单(白名单): 设置访问白名单,可以使用bitmaps定义一个可以访问的名单,
  3. 采用布隆过滤器: 布隆提出的一个很长的很二进制向量(位图)和一系列映射函数(哈希函数)

image-20230609225045889

缓存击穿

数据库访问压力瞬间增加, 但是redis里面没有出现大量key过期,并且redis运行正常

原因: redis某个key过期了,大量访问使用这个key

解决方案:

  1. 预先设置热门数据: 在redis高峰访问时间之前,把一些人们数据存储到redis中,并且加长这写热门key的时常
  2. 实时调整: 现场监控看那个那些数据热门,实时调整key的过期时长
  3. 使用锁:
    1. 缓存失效的时候(判断是否为空), 不是立即去load db
    2. 先使用缓存工具的某些带成功的返回值的操作(比如redis的setnx)

image-20230609230937909

缓存雪崩
  1. 大量缓存过期失效,全部访问到数据库了
  2. 解决:
    1. 构建多级缓存架构:nginx缓存 + redis缓存 + 其他缓存(ehcache等)
    2. 使用锁或队列:用锁或者队列的方式来保证不会有大量的线程对数据库操作读写,从而避免失效时的大量并发请求在数据库中(不适合高并发情况)
  3. 缓存失效时间不一致

image-20230610125517494

分布式锁

分布式锁实现方案:

  1. 基于数据库实现分布式锁
  2. 基于缓存 redis等
  3. 基于Zookeeper
redis锁
  1. 通过setnx设置锁,del 来释放锁(setnx设置key不存在的值)会死锁,可设置过期时间(set users 10 nx ex 12,上锁同时设置过期时间)
  2. 通过UUID防止误删除,通过设置key的value为uuid,防止线程之间互删
  3. 通过lua保证原子性: 查询到删除

新特性

ACL(访问控制列表)

对用户权限进行控制

IO多线程
  1. 客户端交互部分的网络IO交互处理模块时多线程,而执行命令依然时单线程
  2. 主要用于处理网络数据读写
  3. 需要开启配置 io-threads-do-reads yes io-threads 4
工具支持Cluster

之前老版Redis想要搭集群需要单独安装ruby环境,Redis5将redis-trib.rb的功能集成到了redis-cli。另外redis-benchmark工具开始支持cluster模式了,通过多线程的方式对多个分片进行压测