Redis_2.0
Redis为什么这么快?
- 数据结构简单
Redis数据结构是自己设计的,不同的数据结构具有不同的编码,而且根据存储的键值对的个数和大小来决定内部编码。好处就是改进内部编码,对外的数据结构没有影响,多编码的实现可以在不同的场景下发挥各自的优势,性能也有所提升。 - 单线程的处理保证了原子性,完全基于内存,采用多路IO模型,非阻塞IO,Redis在底层构建了自己的VM模型。
多路:多个网络连接
复用:复用同一个线程
因此Redis的瓶颈就是内存和带宽
文档
频道的订阅
频道的订阅类似于微信公众的订阅,一旦频道发布消息,关注的用户会接收到这些信息
设置连接名
连接1 设置名字coffeemao
,查看连接名列表,查看连接名字,处于哪一个数据库中,连接分配的主机等信息。
连接名字设置成功会返回ok
127.0.0.1:6379> client setname coffeemao
OK
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> client list
id=6 addr=127.0.0.1:5247 fd=10 name=coffeemao age=1146 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=18446744073709537584 events=r cmd=client
127.0.0.1:6379> client getname
"coffeemao"
127.0.0.1:6379>
下面是各字段的含义::
id: 唯一的64位的客户端ID(Redis 2.8.12加入)。
addr: 客户端的地址和端口
fd: 套接字所使用的文件描述符
age: 以秒计算的已连接时长
idle: 以秒计算的空闲时长
flags: 客户端 flag
db: 该客户端正在使用的数据库 ID
sub: 已订阅频道的数量
psub: 已订阅模式的数量
multi: 在事务中被执行的命令数量
qbuf: 查询缓冲区的长度(字节为单位, 0 表示没有分配查询缓冲区)
qbuf-free: 查询缓冲区剩余空间的长度(字节为单位, 0 表示没有剩余空间)
obl: 输出缓冲区的长度(字节为单位, 0 表示没有分配输出缓冲区)
oll: 输出列表包含的对象数量(当输出缓冲区没有剩余空间时,命令回复会以字符串对象的形式被入队到这个队列里)
omem: 输出缓冲区和输出列表占用的内存总量
events: 文件描述符事件
cmd: 最近一次执行的命令
127.0.0.1:6379> client setname hello
OK
127.0.0.1:6379> client list
id=6 addr=127.0.0.1:5247 fd=10 name=coffeemao age=1191 idle=45 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=18446744073709537584 events=r cmd=client
id=8 addr=127.0.0.1:6474 fd=12 name=hello age=22 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=18446744073709537584 events=r cmd=client
127.0.0.1:6379> client getname
"hello"
127.0.0.1:6379>
新创建的连接默认是没有名字的。
127.0.0.1:6379> client getname
(nil)
127.0.0.1:6379> client list
id=9 addr=127.0.0.1:11739 fd=9 name= age=18 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=18446744073709537584 events=r cmd=client
127.0.0.1:6379>
提示:在 Redis 应用程序发生连接泄漏时,为连接设置名字是一种很好的 debug 手段
redis.windows.conf
window
版本的Redis3.2
的配置文件,
################################## INCLUDES ###################################
# include .\path\to\local.conf
# include c:\path\to\other.conf
################################## NETWORK #####################################
# bind 192.168.1.100 10.0.0.1
# bind 127.0.0.1 ::1
bind 127.0.0.1
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 0
################################# GENERAL #####################################
loglevel notice
logfile ""
databases 16
################################ SNAPSHOTTING ################################
# Save the DB on disk:
save 900 1
save 300 10
save 60 10000
rdbcompression yes
rdbchecksum yes
# The filename where to dump the DB
dbfilename dump.rdb
# The working directory.
dir ./
################################# REPLICATION #################################
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
# another Redis server. A few things to understand ASAP about Redis replication.
slave-serve-stale-data yes
slave-read-only yes
repl-diskless-sync no
repl-diskless-sync-delay 5
repl-disable-tcp-nodelay no
slave-priority 100
################################## SECURITY ###################################
# Require clients to issue AUTH <PASSWORD> before processing any other
# commands. This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
################################### LIMITS ####################################
# maxclients 10000
# maxmemory <bytes>
# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs a bit more CPU. 3 is very fast but not very accurate.
#
# maxmemory-samples 5
############################## APPEND ONLY MODE ###############################
appendonly no
# The name of the append only file (default: "appendonly.aof")
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
################################ LUA SCRIPTING ###############################
lua-time-limit 5000
################################ REDIS CLUSTER ###############################
# cluster-enabled yes
# cluster-require-full-coverage yes
################################## SLOW LOG ###################################
slowlog-log-slower-than 10000
slowlog-max-len 128
################################ LATENCY MONITOR ##############################
latency-monitor-threshold 0
############################# EVENT NOTIFICATION ##############################
notify-keyspace-events ""
############################### ADVANCED CONFIG ###############################
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-size -2
list-compress-depth 0
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
hll-sparse-max-bytes 3000
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
################################## INCLUDES ###################################
# include /path/to/local.conf
# include /path/to/other.conf
redis的主从复制
主从复制,是指将一台 Redis 服务器的数据,复制到其他的 Redis 服务器
master被称为主节点,slave被称为从节点,数据的复制是单向的,只能由主节点到从节点。
Master 以写为主,Slave 以读为主。
一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
默认情况下,每台 Redis 服务器都是主节点。
作用
数据冗余,主从复制实现数据的热备份,是持久化之外的一种数据冗余实现方式
数据恢复,主节点崩掉,从节点立刻顶上去,防止服务崩溃
负载均衡,读写分离,主机写,从机读,分担主节点的负载,提高redis服务器的并发量
查看主从复制信息info replication
监听
悲观锁
Pessimistic Lock
每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁。这样别人想拿到这个数据就会 堵塞 直到它拿到锁。
传统的关系型数据库使用的这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在操作之前先上锁。
乐观锁
Optimistic Lock 每次去拿数据的时候都认为别人不会修改,所以不会上锁。
但是在更新的时候会判断一下再此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。
乐观锁策略:提交版本必须大于记录当前版本才能执行更新。
单位都是 bytes
redis集群的搭建
https://redis.io/docs/manual/scaling/
由于电脑带不起redis的集群,所以采取docker搭建redis的集群
首先创建网卡
docker network create redis --subnet 172.38.0.0/16
redis-node.sh这个文件位置任意
根目录下创建集群节点的数据
# 创建文件
vim redis-node.sh
# 内容
#!/bin/sh
for port in $(seq 1 6);
do
mkdir -p /mydata/redis/node-${port}/conf
mkdir -p /mydata/redis/node-${port}/data
touch /mydata/redis/node-${port}/conf/redis.conf
cat <<EOF>> /mydata/redis/node-${port}/conf/redis.conf
port 6379
bind 0.0.0.0
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 172.38.0.1${port}
cluster-announce-port 6379
cluster-announce-bus-port 16379
appendonly yes
EOF
done
# 添加执行权限
chmod 777 redis-node.sh
#执行脚本文件
./redis-node.sh
编写脚本redis-run.sh批量开启redis的节点
vim redis-run.sh
#!/bin/bash
for port in $(seq 1 6);
do
docker run -p 637${port}:6379 -p 1667${port}:16379 --name redis-${port} -v /mydata/redis/node-${port}/data:/data -v /mydata/redis/node-${port}/conf/redis.conf:/etc/redis/redis.conf -d --net redis --ip 172.38.0.1${port} redis redis-server /etc/redis/redis.conf
done
# 添加执行权限
chmod 777 redis-run.sh
#执行脚本文件
./redis-run.sh
集群搭建完毕,查看和进入redis的集群中进行测试,发现集群具有高可用的性能
# 查看开启的容器服务
docker ps
# 进入redis-1节点容器
docker exec -it redis-1 /bin/sh
# 查看一下文件
ls
# 集群分配主节点和从节点
redis-cli --cluster create 172.38.0.11:6379 172.38.0.12:6379 172.38.0.13:6379 172.38.0.14:6379 172.38.0.15:6379 172.38.0.16:6379 --cluster-replicas 1
# 查看搭建好的集群信息
cluster info
# 查看集群节点的信息
cluster nodes
# 存放值,可见将a存放在了 3节点中
127.0.0.1:6379> set a b
-> Redirected to slot [15495] located at 172.38.0.13:6379
OK
# 在开启一个会话,将redis-3停了
docker stop redis-3
# redis-3已经崩了,取不到值,且连接不到,重新连接一下集群,再次获取 a,发现a存储在redis-4中,此时主节点是1,2,3,4,不过172.38.0.13:6379@16379 master,fail
172.38.0.13:6379> get a
Could not connect to Redis at 172.38.0.13:6379: No route to host
(16.91s)
not connected> redis-cli -c
(error) ERR unknown command `redis-cli`, with args beginning with: `-c`,
172.38.0.13:6379> get a
-> Redirected to slot [15495] located at 172.38.0.14:6379
"b"
# 高可用已经满足,查看节点信息,在开启redis-3服务,发现redis-4变为主节点了,它的从节点变为redis-3
172.38.0.14:6379> cluster nodes
6833635ea5931eac8038167eb4c91ed834fe8051 172.38.0.16:6379@16379 slave f26a16c880e89ccc95b39bccb802746a1f0e4810 0 1665018276000 2 connected
c7701e07f2da209e66fc331edd3e19500041d32f 172.38.0.15:6379@16379 slave 11aa05b5435a39b2160200c1fb8674fd96d9b3bf 0 1665018277000 1 connected
f26a16c880e89ccc95b39bccb802746a1f0e4810 172.38.0.12:6379@16379 master - 0 1665018277764 2 connected 5461-10922
cbec022e01523937ab3f5fb21b4e90cb70e36844 172.38.0.14:6379@16379 myself,master - 0 1665018276000 7 connected 10923-16383
11aa05b5435a39b2160200c1fb8674fd96d9b3bf 172.38.0.11:6379@16379 master - 0 1665018277000 1 connected 0-5460
e81450c6d2cb620d377dc51bbcc12ce2c765f014 172.38.0.13:6379@16379 slave cbec022e01523937ab3f5fb21b4e90cb70e36844 0 1665018277250 7 connected
# 退出集群
exit
shutdown
持久化
Redis 是内存型数据库,如果不将内存中的数据同步保存到磁盘上的话,一旦服务器进程退出,那么服务器中的数据就会丢失。
RDB
指定时间间隔将内存中的数据存入磁盘中,可以理解成为快照,恢复的操作就是将快照文件直接读取到内存中。
Redis会单独的创建 fork 一个子进程来进行持久化。会现将数据写入到一个临时文件中,待持久化过程结束了,再使用这个临时文件代替上次持久化好的文件。
整个过程中,主进程不会进行IO,保证了极大的性能。如果需要进行大规模的数据恢复,且对于数据恢复的完整性不是很敏感【具有一定的容错性】,RDB比AOF更加的高效。
RDB的缺点就是最后一次的持久化后的数据可能丢失。
复制
fork的作用就是复制一个与当前进程一样的进程,新的进程的所有数据(变量,环境变量,程序计数器)的数值都和原进程一致。这是一个新的进程作为原进程的子进程。
RDB保存的是dump.rdb的二进制文件
save
save 3600 1
save 300 100
save 60 10000
默认
1 分钟内改了 1 万次
5 分钟内改了 10 次
15 分钟内改了 1 次
如果想禁用 RDB 持久化的策略,只要不设置任何 save 指令,或者给 save 传入一个空字符串参数也可以。
手动使用 save 命令,立马生效 。
stop-writes-on-bgsave-error
如果配置为 no,表示你不在乎数据不一致或者有其他的手段发现和控制,默认为 yes。
rbdcompression
对于存储到磁盘中的快照,可以设置是否进行压缩存储。
如果是的话,redis 会采用 LZF 算法进行压缩,如果你不想消耗 CPU 来进行压缩的话,可以设置为关闭此功能。
rdbchecksum
在存储快照后,还可以让 redis 使用 CRC64 算法来进行数据校验。
但是这样做会增加大约 10% 的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
默认为 yes
AOF
以日志的形式记录每一个写的操作。
只允许尾追加文件,但是不可以改写文件
AOF保存的是appendonly.aof文件。
Sentinel(哨兵)
哨兵是一个独立的进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。
哨兵的作用
通过发送命令,让Redis的服务器返回监控其运行的状态,包括主机和从机
当哨兵检测到其他的master宕机,会自动的将slave切换到master,通过发布订阅模式通知其他的从机,修改配置文件,让他们切换主机。
多哨兵的模式,假设主机宕机,哨兵1先检测到这个信息,系统并不会马上进行故障的转移,仅仅是哨兵1 主观的认为主机不可用,称之为主观下线
后面的哨兵检测到这个主机不可用,当到达一定的数量的时候,哨兵之间会进行一次投票,投票的结果有一个哨兵发起为即可,进行故障转移的操作。
切换成功之后,通过发布订阅的模式,让各个哨兵自己的监控实现切换主机,客观下线
使用
配置文件的建立
redis目录下新建myconfig的目录并且创建sentinel.conf
文件
配置内容如下,末尾的 2 是选票到达 2 选举成功
sentinel monitor myredis 127.0.0.1 6379 2
优点
- 哨兵集群的模式是基于主从模式的,主从之间可以实现动态的一个切换,故障可以进行转移,系统可用性更好。
- 哨兵模式是主从模式的升级,系统更加的健壮,可用性更加的高。
缺点就是难扩容,实现复杂。
缓存的问题
Redis缓存的使用,极大的提高了应用程序运行的性能和效率,特别是数据查询的方面。
但是存在问题即是数据一致性的问题,对于数据一致性要求高的就不能使用缓存。另外缓存存在着一系列的问题比如韩缓存穿透,缓存雪崩,缓存击穿
缓存穿透
日常正确的使用缓存的逻辑:
查询一个数据,先到缓存中查询。
如果缓存中存在,则返回。
如果缓存中不存在,则到数据库查询。
如果数据库中存在,则返回数据,且存到缓存。
如果数据库中不存在,则返回空值。
但是缓存穿透就是数据库和缓存中都没有
这样缓存就不能拦截,数据库中查不到,就不能存到缓存中。这样每次这样的查询都会直接查询到数据库,就是穿透,这样数据库的承受压力很大。
解决方案就是:布隆过滤器,缓存空对象
布隆过滤器是一种数据结构,对所有可能查询到的参数以hash形式存储,在控制层先进行校验,不符合则丢弃,从而避免了对底层存储系统的查询压力。
缓存空对象
就是当存储层无法命中后,返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,避免了从后端数据的获取
这种方法存在的问题
如果空值能够被缓存起来,这就意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键。
即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响。
缓存击穿
是指一个key非常的热点,在不断的抗住大的并发,大并发集中对一个点进行访问,在这个key失效的瞬间,持续的大并发机会穿破缓存,直接请求数据库,就相当在屏障上开了一个口子。
当某个key在过期的瞬间,有大量的请求并发访问,这类数据一般都是热点数据。由于缓存过期,会同时访问数据库查询最新的数据,并且写入到缓存中,瞬间使数据库的压力过大。
解决方案是设置热点数据永不过时,加互斥锁
从缓存来看,没有设置过期时间,所以不会出现热点key过期后产生的问题
分布式锁:使用分布式锁,保证对于每个 key 同时只有一个线程去查询后端服务,其他线程没有获得分布式锁的权限,因此只能等待。这种方式将高并发的压力转移到了分布式锁
缓存雪崩
在某一时间段内,缓存集中过期失效,大量的查询请求全部对数据库进行操作,产生周期性的压力波峰。缓存服务节点的宕机,对数据库服务器的压力不可预知,有可能瞬间将数据库压垮
解决方案:搭建集群,限流降级,数据预热,
搭建集群,实现redis的高可用,搭建集群
限流降级,缓存失效之后,通过加锁或者队列来控制数据库写入缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待
数据预热,在所有数据正式部署之前,先把可能的数据预先访问一边,这样部分可能大量的数据就会加载到缓存中。在即将大量访问前手动加载缓存不同的key,设置不同的过期时间,让缓存失效时间点尽量均匀。