(1) redis-cli
127.0.0.1:6379> info memory
(2) redis-cli info memory
内存碎片率=Redis向操作系统申请的内存 / Redis中的数据占用的内存
mem_fragmentation_ratio = used_memory_rss / used_memory
mem_fragmentation_ratio:内存碎片率。
redis-cli info memory |grep ratio
config set activedefrag yes #自动碎片清理
memory purge #手动碎片清理
redis实例的内存使用率超过可用最大内存,操作系统将开始进行内存与swap空间交换。
避免内存交换发生的方法:
内存清理策略,保证合理分配redis有限的内存资源。
当内存使用达到设置的最大阈值时,需选择一种key的回收策略,默认情况下回收策略是禁止删除(noenviction)。配置文件中修改 maxmemory-policy 属性值:
im /etc/redis/6379.conf
---598行----
maxmemory-policy noenviction #修改max-memory-policy属性值
##回收策略有以下几种:##
●volatile-lru
#使用LRU算法从已设置过期时间的数据集合中淘汰数据
(移除最近最少使用的key,针对设置了TTL的key)
●volatile-ttl
#从已设置过期时间的数据集合中挑选即将过期的数据淘汰
(移除最近过期的key)
●volatile-random
#从已设置过期时间的数据集合中随机挑选数据淘汰
(在设置了TTL的key里随机移除)
●allkeys-lru
#使用LRU算法 从所有数据集合中淘汰数据
(移除最少使用的key,针对所有的key)
●allkeys-random
#从数据集合中任意选择数据淘汰(随机移除key)
●noenviction
#禁止淘汰数据(不删除直到写满时报错)
2.1设置Redis客户端连接的超时时间
vim /etc/redis/6379.conf
-----114行------
114 timeout 0
#单位为秒(s),取值范围为0~100000。默认值为0,表示无限制,即Redis不会主动断开连接,即使这个客户端已经空闲了很长时间。
#例如可设置为600,则客户端空闲10分钟后,Redis会主动断开连接。
#注意:在实际运行中,为了提高性能,Redis不一定会精确地按照timeout的值规定的时间来断开符合条件的空闲连接,例如设置timeout为10s,但空闲连接可能在12s后,服务器中新增很多连接时才会被断开。
2.2 设置redis自动碎片清理
config set activedefrag yes #自动碎片清理
memory purge #手动碎片清理
2.3设置redis最大内存阈值
内存阈值如果不设置,则没有限制,直到把服务器的内存干满、之后会使用交换分区。
设置内存阈值后,不会使用swap交换分区。且如果设置了key回收策略,当内存使用达到设置的最大阈值时,系统会进行key回收。
vim /etc/redis/6379.conf
-----567行------
567 # maxmemory
568 maxmemory 1gb #例如设置最大内存阈值为1gb
定义:缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。
一个简单的雪崩过程:
Redis 集群产生了大面积故障;
缓存失败,此时仍有大量请求去访问 Redis 缓存服务器;
在大量 Redis 请求失败后,这些请求将会去访问数据库;
由于应用的设计依赖于数据库和 Redis 服务,很快就会造成服务器集群的雪崩,最终导致整个系统的瘫痪。
产生的原因:
1)缓存中有大量数据同时过期,导致大量请求无法得到处理。
2)Redis 缓存实例发生故障宕机了
解决方案:
【事前】高可用缓存:高可用缓存是防止出现整个缓存故障。即使个别节点,机器甚至机房都关闭,系统仍然可以提供服务,Redis 哨兵(Sentinel) 和 Redis 集群(Cluster) 都可以做到高可用;
【事中】缓存降级(临时支持):当访问次数急剧增加导致服务出现问题时,我们如何确保服务仍然可用。在国内使用比较多的是 Hystrix,它通过熔断、降级、限流三个手段来降低雪崩发生后的损失。只要确保数据库不死,系统总可以响应请求,每年的春节 12306 我们不都是这么过来的吗?只要还可以响应起码还有抢到票的机会;
【事后】Redis备份和快速预热:Redis数据备份和恢复、快速缓存预热。
缓存击穿是指当前热点数据存储到期时,多个线程同时并发访问热点数据。因为缓存刚过期,所有并发请求都会到数据库中查询数据。
解决方法:
将热点数据设置为永不过期;
加互斥锁:互斥锁可以控制查询数据库的线程访问,但这种方案会导致系统的吞吐量下降,需要根据实际情况使用。
缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起id为-1的数据或者特别大的不存在的数据。有可能是黑客利用漏洞攻击从而去压垮应用的数据库。
id<=0
的字段直接拦截;在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999%等等)。
高可用的计算公式是1-(宕机时间)/(宕机时间+运行时间)有点类似与网络传输的参数误码率,我们用9的个数表示可用性:
2个9:99%,一年内宕机时长:1%×365天=3.6524天=87.6h
4个9:99.99%,一年内宕机时长:0.01%×365天=52.56min
5个9:99.999%,一年内宕机时长:0.001%*365天=5.265min
11个9:几乎一年宕机时间只有几秒钟
但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等
在Redis中,实现高可用的技术主要包括持久化、主从复制、哨兵和cluster集群,下面分别说明它们的作用,以及解决了什么样的问题。
持久化: 持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
主从复制: 主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份(和同步),以及对于读操作的负载均衡和简单的故障恢复。
哨兵: 在主从复制的基础上,哨兵实现了自动化的故障恢复。(主挂了,找一个从成为新的主,哨兵节点进行监控)
Cluster集群: 通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。(6台起步,成双成对,3主3从)
主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(Master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。
默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。
1)若启动一个slave机器进程,则它会向Master机器发送一个sync command命令,请求同步连接。
(2)无论是第一次连接还是重新、【】连接,Master机器都会启动一个后台进程,将数据快照保存到数据文件中(执行rdb操作),同时Master还会记录修改数据的所有命令并缓存在数据文件中.
(3)后台进程完成缓存操作之后,Master机器就会向slave机器发送数据文件,slave端机器将数据文件保存到硬盘上,然后将其加载到内存中,接着Master机器就会将修改数据的所有操作一并发送给slave端机器。若slave出现故障导致宕机,则恢复正常后会自动重新连接。
(4)Master机器收到slave端机器的连接后,将其完整的数据文件发送给slave端机器,如果Mater同时收到多个slave发来的同步请求,则Master会在后台启动一个进程以保存数据文件,然后将其发送给所有的slave端机器,确保所有的slave端机器都正常。
主从 | redis的版本号 | IP地址 |
---|---|---|
master | redis-5.0.7 | 192.168.195.100 |
slave1 | redis-5.0.7 | 192.168.195.200 |
slave2 | redis-5.0.7 | 192.168.195.101 |
实验前准备好三台源码编译安装好的redis虚拟机
步骤一:修改master节点的配置文件
vim /etc/redis/6379.conf
bind 0.0.0.0 #70行,修改监听地址为0.0.0.0(生产环境中,尤其是多网卡最好填写物理网卡的IP)
daemonize yes #137行,开启守护进程,后台启动
logfile /var/log/redis_6379.log #172行,指定日志文件存放目录
dir /var/lib/redis/6379 #264行,指定工作目录
appendonly yes #700行,开启AOF持久化功能
/etc/init.d/redis_6379 restart #重启redis服务
#修改slave1的配置文件
vim /etc/redis/6379.conf
bind 0.0.0.0 #70行,修改监听地址为0.0.0.0(生产环境中需要填写物理网卡的IP)
daemonize yes #137行,开启守护进程,后台启动
logfile /var/log/redis_6379.log #172行,指定日志文件目录
dir /var/lib/redis/6379 #264行,指定工作目录
replicaof 192.168.73.105 6379 #288行,指定要同步的Master节点的IP和端口
appendonly yes #700行,修改为yes,开启AOF持久化功能
#将配置文件传给slave2
scp /etc/redis/6379.conf 192.168.73.107:/etc/redis/
/etc/init.d/redis_6379 restart #重启redis
netstat -natp | grep redis #查看主从服务器是否已建立连接
127.0.0.1:6379> keys * 127.0.0.1:6379> set name zhangsan 127.0.0.1:6379> get name
从节点查询 没有问题
主从切换技术的方法是:当服务器宕机后,需要手动一台从机切换为主机,这需要人工干预,不仅费时费力而且还会造成一段时间内服务不可用。为了解决主从复制的缺点,就有了哨兵机制。
哨兵的核心功能:在主从复制的基础上,哨兵引入了主节点的自动故障转移。
哨兵模式的组成:
哨兵节点: 哨兵系统由一个或多个哨兵节点组成,哨兵节点是特殊的redis节点,不存储数据。
数据节点: 主节点和从节点都是数据节点。
此外:哨兵节点也可以是单独独立在其他的主机上,并不需要一定安装redis主从复制的节点服务器上 、
1、由哨兵节点定期监控发现主节点是否出现了故障
每个哨兵节点每隔1秒会问主节点、从节点及其它哨兵节点发送一次ping命令做一次心检测。如果主节点在一定时间范围内不回复或者是回复一个错误消息,那么这个哨兵就会认为这个主节点主观下线了(单方面的)。当超过半数哨兵节点认为该主节点主观下线了,这样就客观下线了。
2、当主节点出现故障,此时哨兵节点会通过Raft算法(选举算法)实现选举机制共同选举出一个哨兵节点为leader,来负责处理主节点的故障转移和通知。所以整个运行哨兵的集群的数量不得少于3个节点。
3、由leader哨兵节点执行故障转移,过程如下:
需要特别注意的是,客观下线是主节点才有的概念;如果从节点和哨兵节点发生故障,被哨兵主观下线后,不会再有后续的客观下线和故障转移操作
1.过滤掉不健康的(己下线的),没有回复哨兵ping响应的从节点。
2.选择配置文件中从节点优先级配置最高的。(replica-priority,默认值为100)
3.选择复制偏移量最大,也就是复制最完整的从节点。
哨兵的启动依赖于主从模式,所以须把主从模式安装好的情况下再去做哨兵模式。
主从 | redis的版本号 | IP地址 | 哨兵点 |
---|---|---|---|
master | redis-5.0.7 | 192.168.195.100 | Sentinel 1 |
slave1 | redis-5.0.7 | 192.168.195.200 | Sentinel 2 |
slave2 | redis-5.0.7 | 192.168.195.101 | Sentinel 3 |
在redis主从复制的基础上进行哨兵模式的部署
哨兵的的配置文件是redis软件中自带的配置
vim /opt/redis-5.0.7/sentinel.conf
......
protected-mode no #17行,取消注释,关闭保护模式
port 26379 #21行,Redis哨兵默认的监听端口
daemonize yes #26行,指定sentinel为后台启动
logfile "/var/log/sentinel.log" #36行,指定日志文件存放路径
dir "/var/lib/redis/6379" #65行,指定数据库存放路径
sentinel monitor mymaster 192.168.195.100 6379 2 #84行,修改
#指定该哨兵节点监控192.168.195.100:6379这个主节点,该主节点的名称是mymaster。
#最后的2的含义与主节点的故障判定有关:至少需要2个哨兵节点同意,才能判定主节点故障并进行故障转移
sentinel down-after-milliseconds mymaster 3000 #113行,判定服务器down掉的时间周期,默认30000毫秒(30秒)
sentinel failover-timeout mymaster 180000 #146行,同一个sentinel对同一个master两次failover之间的间隔时间(180秒)
#传给两外2个哨兵节点
scp /opt/redis-5.0.7/sentinel.conf 192.168.195.200:/opt/redis-5.0.7/
scp /opt/redis-5.0.7/sentinel.conf 192.168.195.100:/opt/redis-5.0.7/
#启动三台哨兵
cd /opt/redis-5.0.7/
redis-sentinel sentinel.conf &
#在哨兵节点查看监控状态
[root@localhost ~]# redis-cli -p 26379 info Sentinel
#在Master 上查看redis-server进程号:
[root@localhost ~]# ps -ef | grep redis
#杀死 Master 节点上redis-server的进程号
[root@localhost ~]# kill -9 pid号 #Master节点上redis-server的进程号
[root@localhost ~]# netstat -natp | grep redis
实验结果
[root@localhost redis-5.0.7]# tail -f /var/log/sentinel.log
#新master进行键值对的创建
[root@localhost redis-5.0.7]# redis-cli
127.0.0.1:6379> set newname lisi
OK
127.0.0.1:6379> get newname
"lisi"
127.0.0.1:6379>
集群,即Redis Cluster,是Redis3.0开始引入的分布式存储方案。
集群由多个节点(Node)组成,Redis的数据分布在这些节点中。集群中的节点分为主节点和从节点:只有主节点负责读写请求和集群信息的维护;从节点只进行主节点数据和状态信息的复制。
(1)数据分区: 数据分区(或称数据分片)是集群最核心的功能。
(2)高可用: 集群支持主从复制和主节点的自动故障转移(与哨兵类似);当任一节点发生故障时,集群仍然可以对外提供服务。
通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。
Redis集群引入了哈希槽的概念。
Redis集群有16384个哈希槽(编号0-16383)。
集群的每个节点负责一部分哈希槽。
每个Key通过CRC16校验后对16384取余来决定放置哪个哈希槽,通过这个值,去找到对应的插槽所对应的节点,然后直接自动跳转到这个对应的节点上进行存取操作。
以3个节点组成的集群为例:
真实生产环境中,redis的cluster集群至少需要六台服务器才能实现 ,如果因为电脑性能问题
可以尝试redis多实例部署
实验组件的部署
安装包:redis-5.0.7.tar.gz
192.168.195.100 我们就用一台机器来搭建
cd /etc/redis/
mkdir -p redis-cluster/redis600{1..6} #创建redis集群的工作目录、和每个节点的工作目录
ls -R redis-cluster/
cd /opt/redis-5.0.7/src/
ls
cd /opt/redis-5.0.7/
for i in {1..6}; do cp /opt/redis-5.0.7/redis.conf /etc/redis/redis-cluster/redis600$i; cp /opt/redis-5.0.7/src/redis-server /opt/redis-5.0.7/src/redis-cli /etc/redis/redis-cluster/redis600$i; done
#使用for循环复制可以节省很多时间
cd /etc/redis/redis-cluster/
ls -R
cd redis6001
vim redis.conf
#bind 127.0.0.1 ##第69行;注释掉监听地址,表示监听任意地址(也可设置为0.0.0.0)
protected-mode no #修改第88行,关闭保护模式,设置为no
port 6001 #修改第92行,修改监听端口为6001
daemonize yes #修改第136行,开启后台运行
appendonly yes #修改第699行,开启AOF持久化
#集群模式配置
cluster-enabled yes #第832行取消注释,开启集群模式
cluster-config-file nodes-6001.conf #第840行,每个集群配置文件,取消注释、修改对应的端口(每个节点都要设置对应的)
cluster-node-timeout 15000 #第846行,集群节点之间通信的超时时间;取消注释
cd ../redis6001
ls
for i in {1..6}; do cd /etc/redis/redis-cluster/redis600$i; ./redis-server redis.conf; done
ps -elf |grep redis
redis-cli --cluster create 127.0.0.1:6001 127.0.0.1:6002 127.0.0.1:6003 127.0.0.1:6004 127.0.0.1:6005 127.0.0.1:6006 --cluster-replicas 1 #把节点加入到集群当中
--cluster-replicas 指定每个主节点有多少个从节点做主从复制
redis-cli -p 6001 #登录redis数据库
cluster slots #查看主从对应关系
redis-cli -p 6001 -c
-c 实现节点间的跳转,如果不加会报错
set name hj
登录到6004节点
redis-cli -p 6004 -c
keys *
get name
数据量过多如何处理?
当数据量过多的情况下,一种简单的方式是升级Redis实例的资源配置,包括增加内存容量、磁盘容量、更好配置的CPU等,但这种情况下Redis使用RDB进行持久化的时候响应会变慢,Redis通过fork子进程来完成数据持久化,但fork在执行时会阻塞主线程,数据量越大,fork的阻塞时间就越长,从而导致Redis响应变慢。
Redis的切片集群可以解决这个问题,也就是启动多个Redis实例来组成一个集群,再按照一定的规则把数据划分为多份,每一份用一个实例来保存,这样客户端只需要访问对应的实例就可以获取数据。在这种情况下fork子进程一般不会给主线程带来较长时间的阻塞,如下图:
切片集群架构图
将20GB的数据分为4分,每份包含5GB数据,客户端只需要找到对应的实例就可以获取数据,从而减少主线程阻塞的时间。
当数据量过多的时候,可以通过升级Redis实例的资源配置或者通过切片集群的方式。前者实现起来简单粗暴,但这数据量增加的时候,需要的内存也在不断增加,主线程fork子进程就有可能会阻塞,而且该方案受到硬件和成本的限制。相比之下第二种方案是一种扩展性更好的方案,如果想保存更多的数据,仅需要增加Redis实例的个数,不用担心单个实例的硬件和成本限制。在面向百万、千万级别的用户规模时,横向扩展的 Redis 切片集群会是一个非常好的选择。
选择切片集群也是需要解决一些问题的:
Redis采用了Redis Cluster的方案来实现切片集群,具体的Redis Cluster采用了哈希槽(Hash Slot)来处理数据和实例之间的映射关系。在Redis Cluster中,一个切片集群共有16384个哈希槽(为什么Hash Slot的个数是16384),这些哈希槽类似于数据的分区,每个键值对都会根据自己的key被影射到一个哈希槽中,映射步骤如下:
这时候可以得到一个key对应的哈希槽了,哈希槽又是如何找到对应的实例的呢?
在部署Redis Cluster的时候,可以通过cluster create命令创建集群,此时Redis会自动把这些槽分布在集群实例上,例如一共有N个实例,那么每个实例包含的槽个数就为16384/N。当然可能存在Redis实例中内存大小配置不一的问题,内存大的实例具有更大的容量。这种情况下可以通过cluster addslots命令手动分配哈希槽。
redis-cli -h 33.33.33.3 –p 6379 cluster addslots 0,1redis-cli -h 33.33.33.4 –p 6379 cluster addslots 2,3redis-cli -h 33.33.33.5 –p 6379 cluster addslots 4
复制代码
复制代码
要注意的是,如果采用cluster addslots的方式手动分配哈希槽,需要将16384个槽全部分配完,否则Redis集群无法正常工作。现在通过哈希槽,切片集群就实现了数据到哈希槽、哈希槽到实例的对应关系,那么客户端如何确定需要访问的实例是哪一个呢?
(二)客户端定位集群中的数据
客户端请求的key可以通过CRC16算法计算得到,但客户端还需要知道哈希槽分布在哪个实例上。在最开始客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端,实例之间会把自己的哈希槽信息发给和它相连的实例,完成哈希槽的扩散。这样客户端访问任何一个实例的时候,都能获取所有的哈希槽信息。当客户端收到哈希槽的信息后会把哈希槽对应的信息缓存在本地,当客户端发送请求的时候,会先找到key对应的哈希槽,然后就可以给对应的实例发送请求了。
但是,哈希槽和实例的对应关系不是一成不变的,可能会存在新增或者删除的情况,这时候就需要重新分配哈希槽;也可能为了负载均衡,Redis需要把所有的实例重新分布。
虽然实例之间可以互相传递消息以获取最新的哈希槽分配信息,但是客户端无法感知这个变化,就会导致客户端访问的实例可能不是自己所需要的了。
Redis Cluster提供了重定向的机制,当客户端给实例发送数据读写操作的时候,如果这个实例上没有找到对应的数据,此时这个实例就会给客户端返回MOVED命令的相应结果,这个结果中包含了新实例的访问地址,此时客户端需要再给新实例发送操作命令以进行读写操作,MOVED命令如下:
GET hello:key(error) MOVED 33.33.33.33:6379
复制代码
复制代码
返回的信息代表客户端请求的key所在的哈希槽为3333,实际是在33.33.33.33这个实例上,此时客户端只需要向33.33.33.33这个实例发送请求就可以了。
此时也存在一个小问题,哈希槽中对应的数据过多,导致还没有迁移到其他实例,此时客户端就发起了请求,在这种情况下,客户端就对实例发起了请求,如果数据还在对应的实例中,会给客户端返回数据;如果请求的数据已经被转移到其他实例上,客户端就会收到实例返回的ASK命令,该命令表示:哈希槽中数据还在前一种、ASK命令把客户端需要访问的新实例返回了。此时客户端需要给新实例发送ASKING命令以进行请求操作。
值得注意的是ASK信息和MOVED信息不一样,ASK信息并不会更新客户端本地的缓存的哈希槽分配信息,也就是说如果客户端再次访问该哈希槽还是会请求之前的实例,直到数据迁移完成.