01 . Redis简介及部署主从复制

简介

Remote Dictionary Server, 翻译为远程字典服务, Redis是一个完全开源的基于Key-Value的NoSQL存储系统,他是一个使用ANSIC语言编写的,遵守BSD协议,支持网络、可基于内存的可持久化的日志型、Key-Value数据库,并提供多种语言的API.

它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

# Redis架构主要有两个程序:
# 	Redis客户端  redis-cli
# 	Redis服务器  redis-server
起源

Redis作者是Salvatore Sanfilippo,来自意大利的西西里岛.

2008年他在开发一个LLOOGG的网站时,需要一个高性能的队列功能,最开始使用MySQL来实现,无奈性能总是提不上去,所以决定自己实现一个专属于LLOOGG的数据库,他就是Redis的前身。后台 Sanfilippoj将 Redis1.0放到Github上大受欢迎。

BSD协议

Redis基于BSD开源协议,BSD开源协议是一个给于使用者很大自由的协议。可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。当你发布使用了BSD协议的代码,或者以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:

  • 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。
  • 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。
  • 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。

BSD代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对商业集成很友好的协议。

很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者 二次开发。

Redis原理

命令执行结构

# 1. 客户端发送命令后,Redis服务器将为这个客户端链接创造一个'输入缓存',将命令放到里面
# 2. 再由Redis服务器进行分配挨个执行,顺序是随机的,这将不会产生并发冲突问题,也就不需要事务了.
# 3. 再将结果返回到客户端的'输出缓存'中,输出缓存先存到'固定缓冲区',如果存满了,就放入'动态缓冲区',客户端再获得信息结果

# 如果数据时写入命令,例如set name:1  zhangsan 方式添加一个字符串.
# Redis将根据策略,将这对key:value来用内部编码格式存储,好处是改变内部编码不会对外有影响,正常操作即可,
# 同时不同情况下存储格式不一样,发挥优势.
为什么需要Redis?

传统数据库在存储数据时,需要先定义schema,以确定类型(字节宽度),并以行存储,所以每行所占的字节宽度是一致的(便于索引数据)。数据库内部数据分为多个datapage(一般是4kb)存储,随着数据量的增大,数据库查询速度会越来越慢,其主要瓶颈在于磁盘I/O:

# 寻址时间(ms)级别
# 带宽(G/M)

由于数据量增大查找datapage的时间也会变长,所以索引出现了。索引是一个B+T,存储在内存中,根据索引记录的信息,可以快速定位到datapage的位置。索引虽然会大大加快查询速度,但是因为增删改需要维护索引的B+T,所以会把增删改的速度拖慢,所以索引不适合频繁写的表。

此外,当数据库高并发查询的情况下,单位时间内所需的数据量也是很大的,此时可能会受到磁盘带宽的影响,影响磁盘的查询速度。

在I/O上,内存相比较于磁盘,拥有较好的性能;

# 寻址时间(ns级别,磁盘是其10w倍)
# 带宽(双通道DDR400的宽带为6.4GBps)

所以,出现了一批基于内存的关系型数据库,比如SAP HAHA数据库,其物理机器内存2T,包含软件以及服务,购买需要1亿元,由于内存关系型数据库的昂贵价格,所以大部分公司采用了折中的方案,使用磁盘关系型数据库+内存缓存,比如 Oracle+Memcached,Mysql+Redis

为什么使用Redis?

个人觉得项目中使用redis,主要从两个角度去考虑性能和并发,当然,redis还具备可以做分布式锁等其他功能,但是如果只是为了分布式锁这些其他功能,完全还有其他中间件(如zookeeper等)代替,并不是非要使用redis,因此,这个问题主要从性能和并发两个角度去答.

性能

如下图所示,我摩恩碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存,这样,后面的请求就去缓存中读取,使得请求能够迅速响应.

image.png

迅速响应的标准,根据交互效果的不同,这个响应时间没有固定标准。不过曾经有人这么告诉我:"在理想状态下,我们的页面跳转需要在瞬间解决,对于页内操作则需要在刹那间解决。另外,超过一弹指的耗时操作要有进度提示,并且可以随时中止或取消,这样才能给用户最好的体验。"

那么瞬间、刹那、一弹指具体是多少时间呢?

一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。

经过周密的计算,一瞬间为0.36秒,一刹那有0.018秒,一弹指changda7.2秒

并发

如下图所示,在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库.

image.png

单线程的Redis为什么这么快?
# 1. 基于内存的访问,非阻塞I/O,Redis使用事件驱动模型epoll多路复用实现,连接,读写,关闭都转换为事件不在网络I/O上浪费过多的时间.
# 2. 单线程避免高并发的时候,多线程有锁的问题和线程切换的CPU开销问题.《虽然是单线程,但可以开多实例弥补》
# 3. 使用C语言编写,更好的发挥服务器性能,并且代码简介,性能高.
Redis的特点
  • 高性能: Redis将所有数据集存储在内存中,可以在入门级Linux机器中每秒写(SET)11万次,读(GET)8.1万次。Redis支持Pipelining命令,可一次发送多条命令来提高吞吐率,减少通信延迟。
  • 持久化:当所有数据都存在于内存中时,可以根据自上次保存以来经过的时间和/或更新次数,使用灵活的策略将更改异步保存在磁盘上。Redis支持仅附加文件(AOF)持久化模式。
  • 数据结构: Redis支持各种类型的数据结构,例如字符串,散列,集合,列表,带有范围查询的有序集,位图,超级日志和带有半径查询的地理空间索引。
  • 原子操作:处理不同数据类型的Redis操作是原子操作,因此可以安全地设置或增加键,添加和删除组中的元素,增加计数器等。
  • 支持的语言: Redis支持许多语言,如ActionScript,C,C ++,C#,Clojure,Common Lisp,D,Dart,Erlang,Go,Haskell,Haxe,Io,Java,JavaScript(Node.js),Julia,Lua ,Objective-C,Perl,PHP,Python,R,Ruby,Rust,Scala,Smalltalk和Tcl。
  • 主/从复制: Redis遵循非常简单快速的主/从复制。配置文件中只需要一行来设置它,而Slave在Amazon EC2实例上完成10 MM key集的初始同步需要21秒。
  • 分片: Redis支持分片。与其他键值存储一样,跨多个Redis实例分发数据集非常容易。
  • 可移植: Redis是用ANSI C编写的,适用于大多数POSIX系统,如Linux,BSD,Mac OS X,Solaris等。
Redis与其他key-value存储有什么不同
  • Redis有着更为复杂的数据结构并且提供对他们的原子性操作,这是一个不同于其他数据库的进化路径。Redis的数据类型都是基于基本数据结构的同时对程序员透明,无需进行额外的抽象。
  • Redis运行在内存中但是可以持久化到磁盘,所以在对不同数据集进行高速读写时需要权衡内存,因为数据量不能大于硬件内存。在内存数据库方面的另一个优点是,相比在磁盘上相同的复杂的数据结构,在内存中操作起来非常简单,这样Redis可以做很多内部复杂性很强的事情。同时,因RDB和AOF两种磁盘持久化方式是不适合随机访问,所以他们可以是紧凑的以追加的方式生成。

Redis应用场景

缓存

合理的使用 缓存 能够明显加快访问的速度,同时降低数据源的压力。这也是 Redis 最常用的功能。Redis 提供了 键值过期时间EXPIRE key seconds)设置,并且也提供了灵活控制 最大内存内存溢出 后的 淘汰策略

排行榜

每个网站都有自己的排行榜,例如按照 热度排名 的排行榜,发布时间 的排行榜,答题排行榜 等等。Redis 提供了 列表list)和 有序集合zset)数据结构,合理的使用这些数据结构,可以很方便的构建各种排行榜系统。

计数器

计数器 在网站应用中非常重要。例如:点赞数1浏览数1。还有常用的 限流操作,限制每个用户每秒 访问系统的次数 等等。Redis 支持 计数功能INCR key),而且计数的 性能 也非常好,计数的同时也可以设置 超时时间,这样就可以 实现限流

社交网络

赞/踩,粉丝,共同好友/喜好,推送,下拉刷新等是社交网站必备的功能。由于社交网站 访问量通常比较大,而且 传统的数据库 不太适合保存这类数据,Redis 提供的 数据结构 可以相对比较容易实现这些功能。

消息队列

Redis 提供的 发布订阅PUB/SUB)和 阻塞队列 的功能,虽然和专业的消息队列比,还 不够强大,但对于一般的消息队列功能基本满足。

Redis五种数据类型应用场景

此时只做介绍,数据类型具体介绍请看后面

  • 对于string 数据类型,常规的set/get操作,因为string 类型是二进制安全的,可以用来存放图片,视频等内容,另外由于Redis的高性能读写功能,而string类型的value也可以是数字,一般做一些复杂的计数功能的缓存,还可以用作计数器(INCR,DECR),比如分布式环境中统计系统的在线人数,秒杀等。
  • 对于 hash 数据类型,value 存放的是键值对结构化后的对象,比较方便操作其中某个字段,比如可以做单点登录存放用户信息,以cookiele作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果.
  • 对于 list 数据类型,可以实现简单的消息队列,另外可以利用lrange命令,做基于redis的分页功能,性能极佳,用户体验好.
  • 对于 set 数据类型,由于底层是字典实现的,查找元素特别快,另外set 数据类型不允许重复,利用这两个特性我们可以进行全局去重,比如在用户注册模块,判断用户名是否注册;另外就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
  • 对于 zset 数据类型,有序的集合,可以做范围查找,排行榜应用,取 TOP N 操作等,还可以做延时任务.

Redis事务

事务表示

一组动作,要么全部执行,要么全部不执行,例如在社交网站上用户A关注了
用户B, 那么需要在用户A的关注表中加入用户B, 并且在用户B的粉丝表中
添加用户A, 这两个行为要么全部执行, 要么全部不执行, 否则会出现数据
不一致的情况。

Redis 提供了简单的事务功能, 将一组需要一起执行的命令放到 multi
exec 两个命令之间。 multi 命令代表事务开始, exec 命令代表事务结束, 它们
之间的命令是原子顺序执行的, 例如下面操作实现了上述用户关注问题。

之间执行命令将不执行,在缓冲中,等exec后裁真正开始执行

如果其中有语法错误,命令打错了,那整个事务将结束.
如果把值写错了,多个字母,但语法正确,那事务是正确的,要手动恢复,不支持回滚.
在事务开始前,用watch key可以检要操作的key,如果key在事务开始后有变化,例如multi开始修改时,这个key被其他客户端修改,事务将不进行操作.

一个Redis从开始到执行会经历以下阶段

# 1. 事务开始
# 2. 命令入队
# 3. 执行事务
Redis事务相关命令
命令 描述
DISCARD 取消事物,放弃执行事物内的所有命令
EXEC 执行所有事物块内的命令 EXEC
MULTI 标志一个事务块的开始 MULTI
UNWATCH 取消WATCH命令多所有key的监视

| WAHCH key [key …] | 监视一个(或多个)key,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

Redis事务与Mysql事务区别
# 1. Redis不支持回滚,即一条命令当做事务执行时,当有一个中间的命令发生错误,mysql将会把之前的操作取消并结束事务.

# 2. 但是Redis不会,Redis还会继续把剩下的命令执行下去,忽略发生错误的命令.

Redis的过期策略以及内存淘汰机制

比如Redis只能存5G数据,可是你写了10G,那会删5G的数据。怎么删的,这个问题思考过么?还有,你的数据已经设置了过期时间,但是时间到了,内存占用率还是比较高,有思考过原因么?

Redis采用的是定期删除 + 惰性删除策略

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除,虽然内存及时释放,但是十分消耗CPU资源,大并发情况下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用这一策略.

定期删除+惰性删除是如何工作的?

定期删除,redis默认每个100ms检查,是否有过期的key,有过期key则删除。需要说明的是,redis不是每个100ms将所有的key检查一次,而是随机抽取进行检查(如果每隔100ms,全部key进行检查,redis岂不是卡死)。因此,如果只采用定期删除策略,会导致很多key到时间没有删除。

于是,惰性删除派上用场,也就是说你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除.

采用定期删除+惰性删除就没其他问题了吗?
并不是,如果定期删除删除没删除key。然后你也没即时去请求key,也就是说惰性删除也没生效。这样,redis的内存会越来越高。那么就应该采用内存淘汰机制.
在redis.conf中有一行配置

# maxmemory-policy volatile-lru

#该配置就是配内存淘汰策略的
# 1. noeviction: 当内存不足以容纳写入数据时,新写入会报错,应该没人用.
# 2. allkeys-lru: 当内存不足以容纳新写入数据时,在键空间中,移除最少使用的那个key.推荐使用
# 3. allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,应该也没人用,不删最少使用,随机删?
# 4. volatile-lru: 当内存不足以容纳写入数据时,在设置了过期时间的键空间中,移除最少使用的key,
# 一般是吧redis既当缓存又当持久化存储才用,不推荐.
# 5. volatile-random: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key,依然不推荐.
# 6. volatile-ttl: 当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期的key优先移除,不推荐.

# 如果没有设置expire的key,不满足先决条件(prerequisites);
# 那么volatile-lru,volatile-random和volatile-ttl策略行为和noeviction(不删)基本一致

Redis单机部署

此篇文章只做单机服务器搭建,高可用哨兵和集群请看下一篇

环境
[Redis-Server]
	主机名 = redis-master-1
	系统 = CentOS7.6.1810
	地址 = 121.36.43.223
	软件 = redis-4.0.14

# 版本
# Redis的奇数版本为非稳定版本,例如2.7.3.1,如果为偶数则为稳定版本,例如3.2,3.4;
节点名 IP 软件版本 硬件 网络 说明
redis-master 192.168.171.136 list 里面都有 2C4G Nat,内网 测试环境
下载解压Redis源码包
yum -y install gcc
wget http://download.redis.io/releases/redis-4.0.14.tar.gz
tar xvf redis-4.0.14.tar.gz -C /opt/
cd /opt/redis-4.0.14
编译安装
# Redis的编译,只将命令文件编译,将会在当前目录生成bin目录
make && make install  PREFIX=/usr/local/redis
cd ..
mv redis-4.0.14/* /usr/local/redis/

# 创建环境变量
echo 'PATH=$PATH:/usr/local/redis/src/' >> /etc/profile
source /etc/profile

# 此时在任何目录位置都可以是用redis-server等相关命令
[root@redis1 ~]# redis-
redis-benchmark  redis-check-rdb  redis-sentinel   redis-trib.rb    
redis-check-aof  redis-cli        redis-server 
Redis可执行文件
可执行文件 作用
redis-server 启动redis服务
redis-cli redis 命令行客户端
redis-benchmark Redis基准测试工具
redis-check-aof redis AOF持久化文件检测和修复工具
redis-check-dump redis RDB持久化文件检测和修复工具
redis-sentinel 启动redis sentinel
修改配置文件
# redis进程是否以守护进程的方式运行,yes为是,no为否(不以守护进程的方式运行会占用一个终端)
daemonize no


# 指定redis进程的PID文件存放位置
pidfile /var/run/redis.pid


# redis进程的端口号
port 6379


# 绑定的主机地址
bind 127.0.0.1


# 客户端闲置多长时间后关闭连接,默认此参数为0即关闭此功能
timeout 300


# redis日志级别,可用的级别有debug.verbose.notice.warning
loglevel verbose


# log文件输出位置,如果进程以守护进程的方式运行,此处又将输出文件设置为stdout的话,就会将日志信息输出到/dev/null里面去了
logfile stdout


# 设置数据库的数量,默认为0可以使用select <dbid>命令在连接上指定数据库id
databases 16


# 指定在多少时间内刷新次数达到多少的时候会将数据同步到数据文件
save <seconds> <changes>


# 指定存储至本地数据库时是否压缩文件,默认为yes即启用存储
rdbcompression yes


# 指定本地数据库文件名
dbfilename dump.db


# 指定本地数据问就按存放位置
dir ./


# 指定当本机为slave服务时,设置master服务的IP地址及端口,在redis启动的时候他会自动跟master进行数据同步
slaveof <masterip> <masterport>


# 当master设置了密码保护时,slave服务连接master的密码
masterauth <master-password>


# 设置redis连接密码,如果配置了连接密码,客户端在连接redis是需要通过AUTH<password>命令提供密码,默认关闭
requirepass footbared


# 设置同一时间最大客户连接数,默认无限制。redis可以同时连接的客户端数为redis程序可以打开的最大文件描述符,如果设置 maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回 max number of clients reached 错误信息
maxclients 128


# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key。当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory<bytes>


# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no。
appendonly no


# 指定跟新日志文件名默认为appendonly.aof
appendfilename appendonly.aof


# 指定更新日志的条件,有三个可选参数 - no:表示等操作系统进行数据缓存同步到磁盘(快),always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全), everysec:表示每秒同步一次(折衷,默认值);
appendfsync everysec

我们需要修改的配置

# 设置后台启动
# 由于Redis默认是前台启动,不建议使用.可以修改为后台
daemonize yes


# 禁止protected-mode yes/no(保护模式,是否只允许本地访问)
protected-mode


# 设置远程访问
# Redis默认只允许本机访问,把bind修改为bind 0.0.0.0 此设置会变成允许所有远程访问,如果指定限制访问,可设置对应IP。
# bind指定是redis所在服务器网卡的IP,不指定本机网卡IP,可能导致你的Redis实例无法启动
# 如果想限制IP访问,内网的话通过网络接口(网卡限定),让客户端访问固定网卡链接redis
# 如果是公网,通过iptables指定某个IP允许访问
bind 0.0.0.0

# 配置Redis日志记录
# 找到logfile,默认为logfile "",改为自定义日志格式
logfile  /var/log/redis_6379.log

# 把requirepass修改为123456,修改之后重启下服务
requirepass "123456"
# 不重启Redis设置密码
# 在配置文件中配置requirepass的密码(当Redis重启时密码依然生效)
127.0.0.1:6379> config set requirepass test123
# 查询密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "test123"

# 密码验证
127.0.0.1:6379> auth test123
OK
127.0.0.1:6379> set name flying
OK
127.0.0.1:6379> get name
"flying"

# 远程主机连接
# redis-cli  -h  redis_ip -p redis_port -a password
启动测试
# 放到后台输出,redis自带日志了,可以输出到黑洞
nohup redis-server /usr/local/redis/redis.conf &> /usr/local/redis/redis.log &

# 关闭命令
redis-cli -h 127.0.0.1 -p 6379 -a 123456 shutdown
# 注意:不建议使用 kill -9,这种方式不但不会做持久化操作,还会造成缓冲区等资源不能优雅关闭。极端情况下造成 AOF 和 复制丢失数据 的情况。
# shutdown 还有一个参数,代表是否在关闭 redis 前,生成 持久化文件,命令为 redis-cli shutdown nosave|save。


# 设置开机自启动
echo "redis-server /usr/local/redis.conf" >> /etc/rc.local
注册Redis为系统服务

在/etc/init.d目录添加Redis服务的启动,暂停和重启脚本

vim /etc/init.d/redis
#!/usr/bin/env bash
# chkconfig: 2345 10 90  
# description: Start and Stop redis 
PORT=6379
EXEC=/usr/local/redis/src/redis-server
CLIEXEC=/usr/local/redis/src/redis-cli

PIDFILE=/var/run/redis_${PORT}.pid
CONF="/etc/redis/${PORT}.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF &>/dev/null &
        fi
        ;;
    stop)
	PID=$(cat $PIDFILE)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                echo "Stopping ..."
                $CLIEXEC -p $PORT shutdown
                while [ -d /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart)
        "$0" stop
        sleep 3
        "$0" start
        ;;
    *)
        echo "Please use start or stop as first argument"
        ;;
esac



chmod +x /etc/init.d/redis  
mkdir /etc/redis  
cp /usr/local/redis/redis.conf /etc/redis/6379.conf  
chkconfig --add redis  
chkconfig redis on  
  
service redis start  
service redis restart

Redis常用命令

Redis-value操作

此处Redis五种数据类型具体操作先不演示,只做一个简单的key操作,具体操作等Python操作Redis再写详细,前面主要以运维为主

# 添加一个key[name]为youmen
127.0.0.1:6379> set name youmen
OK

# 获取key
127.0.0.1:6379> get name
"youmen"

# 查看当前数据库里面的key
127.0.0.1:6379> keys *
1) "name"

# 判断key是否存在
127.0.0.1:6379> exists name
(integer) 1

# 删除key值,也就删除了这条数据
127.0.0.1:6379> del name
(integer) 1
# 查找不到对应的key返回值就会使(integer)0
127.0.0.1:6379> exists name
(integer) 0



# 设置key的过期时间,过期后key自动删除
127.0.0.1:6379> set name youmen
OK
127.0.0.1:6379> set name youmen ex 2
127.0.0.1:6379> expire name 10
(integer) 1
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> exists name
(integer) 0
持久化命令
# save:将数据同步保存到磁盘
# bgsave:将数据异步保存到磁盘
# lastsave:返回上次成功将数据保存到磁盘的Unix时戳
# shundown:将数据同步保存到磁盘,然后关闭服务
远程服务控制
# info:提供服务器的信息和统计
# info clients:  查看客户端信息.
# monitor:实时转储收到的请求
# slaveof:改变复制策略设置
# config:在运行时配置Redis服务器
# client list: 查看链接的客户端有哪些
# chient kill 127.0.0.1:50390:  杀掉客户端链接
# config get dir: 查看存储文件目录
# config get *:  查看所有配置
# config set requirepass 123 : 修改配置,即时生效
# config get bind :  查看配置文件中的监听地址
Redis info参数介绍
# 集群每个节点获取的信息不一样
127.0.0.1:6379> info
# Server
redis_version:4.0.14
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:5ad4d17d599d7e92
redis_mode:standalone			# 运行模式,单机或集群
os:Linux 3.10.0-1062.1.1.el7.x86_64 x86_64	# 服务器的宿主操作系统
arch_bits:64					# 架构
multiplexing_api:epoll			# redis所使用的事件循环机制
atomicvar_api:atomic-builtin	# 原子处理api
gcc_version:4.8.5				# 编译Redis时所使用的GCC版本
process_id:19955				# 服务器进程的PID
run_id:3cd8b85e4c852fc93adbbb51eaee051a0a6a788d	# 标识redis server的随机值
tcp_port:6379
uptime_in_seconds:4272			# redis server启动的时间(单位s)
uptime_in_days:0				# redis server启动的时间(单位d)
hz:10								
# hz:10  edis内部调度(进行关闭timeout的客户端,删除过期key等)频率,程序规定serverCron每秒运行十次.
lru_clock:1935465						
# 自增的时钟,用于LRU管理,该时钟ms(hz=10,因此每1000ms/10=100ms执行一次定时任务)更新一次
executable:/usr/local/redis/src/redis-server	# 执行文件
config_file:/etc/redis/6379.conf  # 配置文件路径
# Clients
connected_clients:1				# 已连接客户端的数量(不包括通过从属服务器连接的客户端)
client_longest_output_list:0			# 当前连接的客户端中,最长的输出列表
client_biggest_input_buf:0			# 当前连接的客户端中,最大输入缓存
blocked_clients:0				# 正在等待阻塞命令(BLPOP、BRPOP、Brpoplpush)的客户端的数量

# Memory
used_memory:849400				# 由Redis分配器分配的内存总量,以字节(byte)为单位
used_memory_human:829.49K			# 以人类可读的格式返回Redis分配的内存总量
used_memory_rss:8278016				
# 从操作系统的角度,返回Redis已分配的内存总量(俗称常驻集大小),这个值和top,ps等命令输出一致
used_memory_rss_human:7.89M			
# 以人类可读的格式,从操作系统角度,返回Redis已分配的内存总量(俗称常驻集大小),这个值和top,ps等命令输出一致

used_memory_peak:849472				# redis的内存消耗峰值(以字节为单位)
used_memory_peak_human:829.56K			# 以人类可读的格式返回redis的内存消耗峰值
used_memory_peak_perc:99.99%			# (used_memory/ used_memory_peak) *100%

used_memory_overhead:836622
# Redis为了维护数据集的内部机制所需的内存开销,
# 包括所有客户端输出缓冲区、查询缓冲区、AOF重写缓冲区和主从复制的backlog

used_memory_startup:786608			# Redis服务器启动时消耗的内存
used_memory_dataset:12778			# used_memory—used_memory_overhead
used_memory_dataset_perc:20.35%			
# 100%*(used_memory_dataset/(used_memory—used_memory_startup))

total_system_memory:1926860800			# 整个系统内存
total_system_memory_human:1.79G			# 以人类可读格式,显示整个系统内存
used_memory_lua:37888				# lua脚本存储占用的内存
used_memory_lua_human:37.00K			# 以人类可读的格式,显示lua脚本占用的内存
maxmemory:0					# Redis实例的最大内存配置
maxmemory_human:0B				# 以人类可读格式,显示Redis实例的最大内存配置
maxmemory_policy:noeviction			# 当达到maxmemory时的淘汰策略
mem_fragmentation_ratio:9.74			# used_memory_rss/used_memory
mem_allocator:jemalloc-4.0.3			# 内存分配器
active_defrag_running:0				# 表示没有活动的defrag任务正在运行(defrag: 表示内存碎片整理)
lazyfree_pending_objects:0			# 0表示不存在延迟释放(也有资料翻译末惰性删除)的挂起对象
# Persistence
loading:0					# 服务器是否正在载入持久化文件
rdb_changes_since_last_save:0			
# 离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化

rdb_bgsave_in_progress:0			# 服务器是否正在创建rdb文件
rdb_last_save_time:1578992739			
# 离最近一次成功创建rdb文件的时间戳,当前时间戳-rdb_last_save_time=多少秒未成功生成rdb文件
rdb_last_bgsave_status:ok			# 最近一次rdb持久化是否成功
rdb_last_bgsave_time_sec:0			# 最近一次成功生成rdb文件耗时总数
rdb_current_bgsave_time_sec:-1		
# 如果服务器正在创建rdb文件,那么这个域记录的就是当前创建操作已经耗费的秒数
rdb_last_cow_size:6537216			 
# RDB过程中父进程与子进程相比执行了多少修改(包括读缓冲区,写缓冲区,数据修改等)
aof_enabled:0					# 是否开启了aof
aof_rewrite_in_progress:0			# 标识aof的rewrite操作是否正在进行中
aof_rewrite_scheduled:0			
# rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,
# 那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite
aof_last_rewrite_time_sec:-1			# 最近一次aof rewrite耗费的时长
aof_current_rewrite_time_sec:-1			# 如果rewrite操作正在进行,则记录所使用的时间,单位秒
aof_last_bgrewrite_status:ok			# 上次bgrewriteaof操作的状态
aof_last_write_status:ok			# 上次aof写入状态
aof_last_cow_size:0				
# AOF过程中父进程与子进程相比执行了多少次修改(包括读缓冲区,写缓冲区,数据修改等)

# Stats
total_connections_received:4
# 新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,
# 说明短连接严重或连接池使用有问题,需调研代码的连接设置
total_commands_processed:87			# Redis处理的命令数
instantaneous_ops_per_sec:0			# redis当前的qps,redis内部较实时的每秒执行的命令数
total_net_input_bytes:2757			# redis网络入口流量字节数
total_net_output_bytes:53214			# redis网络出口流量字节数
instantaneous_input_kbps:0.00			# redis网络入口kps
instantaneous_output_kbps:0.00			# redis网络入口kps
rejected_connections:0				
# 拒绝的连接的个数,redis连接个数达到maxclients限制,拒绝新连接个数
sync_full:0					# 主从完全同步成功次数
sync_partial_ok:0				# 主从部分同步成功次数
sync_partial_err:0				# 主从部分失败次数
expired_keys:1					# 运行以来过期的key的数量
expired_stale_perc:0.00				# 过期的比率
expired_time_cap_reached_count:0		# 过期计数
evicted_keys:0					# 运行以来剔除(超过maxmemory后)的key的数量
keyspace_hits:26				# 命中次数
keyspace_misses:10				# 未命中次数
pubsub_channels:0				# 当前使用中的频道数量
pubsub_patterns:0				# 当前使用的模式的数量
latest_fork_usec:578				# 最近一次fork操作阻塞redis进程的耗时数,单位微妙
migrate_cached_sockets:0			# 是否已经缓存到了改地址的链接
slave_expires_tracked_keys:0			# 从实例到期key的数量
active_defrag_hits:0				# 主动碎片整理命中次数
active_defrag_misses:0				# 主动碎片整理未命中次数
active_defrag_key_hits:0			# 主动碎片整理key命中次数
active_defrag_key_misses:0			# 主动碎片整理key未命中次数.
# Replication
role:master					# 实例的角色,是master or slave
connected_slaves:0				# 连接的slave实例个数
master_replid:54da017499c5257de9b00d168dd49c04b8bbe7ef	# 主实例启动随机字符串
master_replid2:0000000000000000000000000000000000000000	# 主实例启动随机字符串
master_repl_offset:0			
# 主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟,与master_replid可被用来标识主实例复制流的位置.
second_repl_offset:-1 				# 主从同步偏移量2,此值如果和上面的offset相同说明主从一致没延迟
repl_backlog_active:0				# 复制积压缓冲区是否开启
repl_backlog_size:1048576			# 复制积压缓冲大小
repl_backlog_first_byte_offset:0		# 复制缓冲区偏移量的大小
repl_backlog_histlen:0
# 此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小

# CPU
used_cpu_sys:1.90				# 将所有redis主进程在核心态所占用的CPU时求和累积起来
used_cpu_user:1.14				# 将所有redis主进程在用户态所占用CPU时求和累计起来
used_cpu_sys_children:0.01		# 将后台进程在核心态所占用的CPU时求和累计起来
used_cpu_user_children:0.00		# 将后台进程在用户态所占用CPU时求和累计起来

# Cluster
cluster_enabled:0

# Keyspace
db0:keys=7,expires=0,avg_ttl=0

Redis主从复制

Redis主从复制原理
# 主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

# 默认情况下,每台Redis服务器都是主节点;且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

1 . redis的复制功能是支持多个数据库之间的数据同步。一类是主数据库(master)一类是从数据库(slave),主数据库可以进行读写操作,当发生写操作的时候自动将数据同步到从数据库,而从数据库一般是只读的,并接收主数据库同步过来的数据,一个主数据库可以有多个从数据库,而一个从数据库只能有一个主数据库。

2 . 通过redis的复制功能可以很好的实现数据库的读写分离,提高服务器的负载能力。主数据库主要进行写操作,而从数据库负责读操作。

Redis主从复制过程

img

过程

# 1:当一个从数据库启动时,会向主数据库发送sync命令,

# 2:主数据库接收到sync命令后会开始在后台保存快照(执行rdb操作),并将保存期间接收到的命令缓存起来

# 3:当快照完成后,redis会将快照文件和所有缓存的命令发送给从数据库。

# 4:从数据库收到后,会载入快照文件并执行收到的缓存的命令。

 

# 注意:redis2.8之前的版本:当主从数据库同步的时候从数据库因为网络原因断开重连后会重新执行上述操作,不支持断点续传。

# redis2.8之后支持断点续传。

详细过程

   在客户端输入建立主从关系的命令之后,建立过程分为3阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;

阶 段 一 、连接建立阶段
   [该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。]
   --> 步骤1:保存主节点信息
         从节点服务器内部维护masterhost和masterport字段,用于存储主节点的ip和port信息。
         slaveof是异步命令,从节点完成主节点ip和port的保存后,立即回复客户端ok信息,实际复制才刚开始;
   --> 步骤2:建立socket连接   
         从节点每秒1次调用复制定时函数replicationCron(),如果发现指定主节点可以连接,便会根据主节点的ip和port,创建socket连接。
         如果连接成功,则:
            从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。
            主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端。[后面的数据同步操作会以从节点向主节点发送命令请求的形式来进行]
   --> 步骤3:发送ping命令检查链接    
         从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是检查socket连接是否可用,以及主节点当前是否能够处理请求。
         从节点发送ping命令后,可能出现3种情况:
             (1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。
             (2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。
             (3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。[socket暂时不可用]
   --> 步骤4:身份验证 
       [如果从节点设置masterauth选项,则从节点需要向主节点提交身份验证;]
         · 从节点向主节点发送auth命令,auth命令的参数即为配置文件中的masterauth的值。
         · 如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。
   --> 步骤5:从向主发送自身端口信息
       该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用


阶 段 二 、数据同步阶段[核心阶段]
 【该阶段可以理解为从节点数据的初始化。】
  过程:从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。
      根据主从节点当前状态的不同,可以分为全量复制和部分复制。
            [全量复制和部分复制原理见对应笔记]
  注意:数据同步阶段之前,从是主的客户端,主不是从的客户端;而到了这一阶段及以后,主从互为客户端。
        原因在于:在此之前,主节点只需要响应从节点的请求即可,不需要主动发请求,而在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。


阶 段 三 、命令传播阶段
【在这个阶段主节点将自己缓冲区内的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。】
   此阶段是最终阶段,主从节点开始维持心跳机制:PING和REPLCONF ACK
   --> 此阶段问题:一致性延迟;
       命令传播是异步的过程,即主节点发送写命令后并不会等待从节点的回复;
       数据不一致的程度,与主从节点之间的网络状况、主节点写命令的执行频率、以及主节点中的repl-disable-tcp-nodelay配置等有关。
        repl-disable-tcp-nodelay no:    
          默认no,即不开启延迟发送。设置为yes时,TCP会对包进行合并从而减少带宽,但是命令发送的频率会降低,从节点数据延迟增加,一致性变差;具体发送频率与Linux内核的配置有关,默认配置为40ms。
          只有当应用对Redis数据不一致的容忍度较高,且主从节点之间网络状况不好时,才会设置为yes;多数情况使用默认值no。
Redis主从复制作用及缺点
# 1)数据冗余:主从复制实现了数据的热备份;
# 2)故障恢复:主节点出现问题,从节点快速提供服务;实际上是一种服务的冗余。
# 3)读负载均衡:主从复制配合读写分离,可以大大提高Redis服务器的并发量。
#      [默认开启读写分离,主读从写;要使得从可写,修改配置文件,关键字"slave-read-only yes",默认yes表示从节点只读,修改为no从节点可写]
# 4)高可用基石:主从复制是哨兵和集群能够实施的基础;

Redis主从复制缺点

# 1. 故障恢复无法自动化
# 2. 写操作无法负载均衡
# 3. 存储能力受到单机的限制
Redis主从复制部署
节点名 IP 软件版本 硬件 网络 说明
redis-master 192.168.171.136 list 里面 2C4G Nat,内网 测试环境
redis-slave 192.168.171.137 list里面 2C4G Nat,内网 测试环境

请现在两台主机安装好Redis再做下面操作,下面操作是有两台初始化好的Redis为前提的

修改Redis-Master的redis.conf配置文件

[root@redis-slave redis]# mkdir  /usr/local/redis/backup

[root@redis-master redis]# cat redis.conf 
daemonize yes
pidfile /var/run/redis-16379.pid
logfile /var/log/redis-16379.log
port 16379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-16379.db
dir /usr/local/redis/backup
masterauth 123456
requirepass 123456

# 重启服务使服务生效
[root@redis-master redis]# redis-server ./redis.conf 

修改Redis-Slave的redis.conf配置文件

[root@redis-slave redis]# mkdir  /usr/local/redis/backup

[root@redis-slave redis]# cat redis.conf 
daemonize yes
pidfile /var/run/redis-26379.pid
logfile /var/log/redis-26379.log
port 26379
bind 0.0.0.0
timeout 300
databases 16
dbfilename dump-26379.db
dir /usr/local/redis/backup
masterauth 123456
requirepass 123456
slaveof 192.168.171.136 16379

# 重启服务使服务生效
[root@redis-master redis]# redis-server ./redis.conf 
验证主从复制是否生效
# 主节点
[root@redis-master redis]# redis-cli -h 127.0.0.1 -p 16379 -a 123456
127.0.0.1:16379> set zhou youmen
OK

# 从节点
[root@redis-slave redis]# redis-cli -h 127.0.0.1 -p 26379 -a 123456
Warning: Using a password with '-a' option on the command line interface may not be safe.
127.0.0.1:26379> get zhou
"youmen"

# 至此主从复制OK了

Redis主从复制数据同步详解

# 在Redis2.8以前,从节点向主节点发送sync命令请求同步数据,此时的同步方式只有全量复制;
# 在Redis2.8及以后,从节点可以发送psync命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。[从节点本身可能有部分数据,如从节点宕机再连的从节点]
  
# 网络中断后,从节点会一直尝试重连   
概述
# 全量复制: 用于初次复制或其他无法进行复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作.

# 部分复制: 用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制更加高效.

# [若网络中断时间过长,或主节点未能完整保存中断期间自身所执行的写命令,则无法进行部分复制,只能全量复制]
全量复制
# 命令:psync

# 条件:从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;[根据自身所记录的相关数据标记位判断]

# 开始执行:
#     1、执行bgsave,fork出子进程,用于在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令;
#       [此过程十分消耗CPU,内存(页表复制),硬盘IO;bgsave的性能可以通过更复杂的方式进行调优]

#     2、主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点接收到RDB文件,存放在本机磁盘中;
#       [此过程对主从节点的带宽都会带来很大的消耗]
     
    
#     3、 从节点先清除自己内存中的旧数据,然后载入接收的RDB文件到内存中,将数据库状态更新至主节点执行bgsave时的数据库状态;
#       [从节点清空老数据、载入新RDB文件的过程是阻塞的,无法响应客户端的命令;]
     
#     4、主节点将复制缓冲区中所有写命令发送从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态;
#       [如果从节点开启了AOF,则会在数据恢复完成后,触发bgrewriteaof的执行,保证从节点AOF文件更新至主节点的最新状态;这会带来很大的CPU、磁盘IO消耗]
部分复制
# 部分复制的实现,依赖于三个重要的概念:

#    1、复制偏移量
#       主节点和从节点分别维护一个复制偏移量(offset),代表主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。    
#       offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。

#    2、复制积压缓冲区
#       复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB。
#      创建:当主节点成功拥有第一个从节点时,创建复制积压缓冲区;
#         作用:备份主节点最近发送给从节点的数据。
#            【无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。】
#            复制积压缓冲区作为写命令的备份,还存储了每个命令的复制偏移量(offset),时间较早的写命令会被挤出缓冲区,故当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。

#    3、服务器运行ID(runid)
#       每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样);
#       作用:runid用来唯一识别一个Redis节点。[info server命令可以查看]
#          主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:
#        --> 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
#        --> 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。    

# 总结:从节点保留的主节点runid与当前主节点的runid相同,并且主从offset值差之间的所有命令都在主节点的复制积压缓冲区中,则进行部分复制,任何一个条件不达标则进行全量复制;    

Redis主从之间心跳机制

在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING和REPLCONF ACK; 心跳机制主要用于主从复制的超时判断、数据安全

主->从:PING
#     每隔指定的时间,主节点会向从节点发送PING命令,这个PING命令的作用,主要是为了让主节点进行超时判断。
#     PING发送的频率由repl-ping-slave-period参数控制,单位是秒,默认值是10s。
#     关于该PING命令究竟是由主节点发给从节点,还是相反,有一些争议;因为在Redis的官方文档中,对该参数的注释中说明是从节点向主节点发送PING命令;但是根据其源码的实现逻辑,是主节点发给从节点的。
从->主:REPLCONF ACK
#      在命令传播阶段,从节点会向主节点发送REPLCONF ACK命令,即向主节点报告自己的复制偏移量是多少,频率是每秒1次;
#      命令格式为:REPLCONF ACK {offset};其中offset指从节点保存的复制偏移量



# REPLCONF ACK命令的作用包括:
#  (1)实时监测主从节点网络状态:该命令会被主节点用于复制超时的判断。
#       此外,在主节点中使用info Replication,可以看到其从节点的状态中的lag值,代表的是主节点上次收到该REPLCONF ACK命令的时间间隔,在正常情况下,该值应该是0或1 ;
#       [此消息正常情况下应该一秒收一个];

#  (2)检测命令丢失:从节点发送了自身的offset,主节点会与自己的offset对比,如果从节点数据缺失(如网络丢包),主节点会推送缺失的数据(这里会利用复制积压缓冲区内的数据备份)。  
#      offset和复制积压缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;

#  (3)用来判断延迟时间:Redis主节点中使用min-slaves-to-write和min-slaves-max-lag参数,来保证主节点在不安全的情况下不会执行写命令;
#        所谓不安全,是指从节点数量太少,或延迟过高。
#        例如min-slaves-to-write和min-slaves-max-lag分别是3和10,含义是如果从节点数量小于3个,或所有从节点的延迟值都大于10s,则主节点拒绝执行写命令。
#        而这里从节点延迟值的获取,就是通过主节点接收到REPLCONF ACK命令之后的时间来判断的,即前面所说的info Replication中的lag值。    

Redis主从中应用问题

读写分离应用问题
# 由主节点提供写服务,由一个或多个从节点提供读服务(多个从节点既可以提高数据冗余程度,也可以最大化读负载能力);
       
问题:
 (1)延迟与不一致问题
 --命令传播阶段:[由于主从复制的命令传播是异步的,延迟与数据的不一致不可避免。]
     优化:
         · 优化主从节点之间的网络环境(如在同机房部署);
         · 监控主从节点延迟(通过offset)判断,如果从节点延迟过大,通知应用不再通过该从节点读取数据;        
         · 使用集群同时扩展写负载和读负载等。

--命令传播阶段以外的其他情况
    [如主从连接在数据同步阶段,或从节点失去同主节点的连接的情况下]
      优化:
        设置从节点的slave-serve-stale-data参数
            [此参数控制在高延迟情况下从节点的表现]
            默认值为“yes”,即从节点仍能够响应客户端的命令; 如果为no,则从节点只能响应info、slaveof等少数命令;
            该参数的设置与应用对数据一致性的要求有关;如果对数据一致性要求很高,则应设置为no。



(2)数据过期问题        
 在单机版Redis中,存在两种删除策略:
     惰性删除:服务器不会主动删除数据,只有当客户端查询某个数据时,服务器判断该数据是否过期,如果过期则删除。
     定期删除:服务器执行定时任务删除过期数据,但是考虑到内存和CPU的折中(删除会释放内存,但是频繁的删除操作对CPU不友好),该删除的频率和执行时间都受到了限制。    
     Redis 3.2以上,从节点在读取数据时,增加了对数据是否过期的判断,如果该数据已过期,则不返回给客户端;[尽量使用新版客户端]
                
(3)故障切换问题            
     使用哨兵或集群,要不就是自己写脚本做计划任务健康检查
复制超时问题
主从节点复制超时是导致复制中断的最重要的原因之一;

-->超时检测:
 主从都会判断连接是否超时:
    1)如果主节点判断连接超时,其会释放相应从节点的连接,从而释放各种资源,否则无效的从节点仍会占用主节点的各种资源(输出缓冲区、带宽、连接等);此外连接超时的判断可以让主节点更准确的知道当前有效从节点的个数,有助于保证数据安全(配合min-slaves-to-write等参数)。
    2)如果从节点判断连接超时,则可以及时重新建立连接,避免与主节点数据长期的不一致。


-->判断机制:
  主从复制超时判断的核心,在于repl-timeout参数,该参数规定了超时时间的阈值(默认60s),对于主节点和从节点同时有效;
    
    主从超时条件如下:
      主:每秒1次调用复制定时函数replicationCron(),在其中判断当前时间距离上次收到各个从节点REPLCONF ACK的时间,是否超过了repl-timeout值,如果超过了则释放相应从节点的连接。
      从:同样是在复制定时函数中判断,基本逻辑是:    
          -1 如果当前处于连接建立阶段,主从连接建立中,从节点等待时间超过repl-timeout,则释放与主节点的连接;
          -2 如果当前处于数据同步阶段,且收到主节点的RDB文件的时间超时,则停止数据同步,释放连接;
          -3 如果当前处于命令传播阶段,且距离上次收到主节点的PING命令或数据的时间已超过repl-timeout值,则释放与主节点的连接。

-->需要注意的坑:
   (1)数据同步阶段:                      
        如果RDB文件过大,主节点在fork子进程+保存RDB文件时耗时过多,可能会导致从节点长时间收不到数据而触发超时;此时从节点会重连主节点,然后再次全量复制,再次超时,再次重连……这是个悲伤的循环;
        为了避免这种情况的发生,要注意Redis单机数据量不要过大,另一方面就是适当增大repl-timeout值,具体的大小可以根据bgsave耗时来调整。
   (2)命令传播阶段:
          在该阶段主节点会向从节点发送PING命令,频率由repl-ping-slave-period控制;该参数应明显小于repl-timeout值(后者至少是前者的几倍)。
          如果两个参数相等或接近,网络抖动导致个别PING命令丢失,此时恰巧主节点也没有向从节点发送数据,则从节点很容易判断超时。
   (3)慢查询导致的阻塞:
         如果主节点或从节点执行了一些慢查询(如keys *或者对大数据的hgetall等),导致服务器阻塞;阻塞期间无法响应复制连接中对方节点的请求,可能导致复制超时。
复制中断问题
除了主从节点超时,还有其他情况可能导致复制中断,其中最主要的是复制缓冲区溢出问题。

--> 在全量复制阶段,主节点会将执行的写命令放到复制缓冲区中,缓冲区存放的数据包括从bgsave开始生成RDB文件,到从节点数据重载完成;
    当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接,于是开始循环重连,缓冲区溢出,断开,又重连。。。;
   ``复制缓冲区的大小由
     client-output-buffer-limit slave {hard limit} {soft limit} {soft seconds}配置,默认值为client-output-buffer-limit slave 256MB 64MB 60,
     其含义是:如果buffer大于256MB,或者连续60s大于64MB,则主节点会断开与该从节点的连接。该参数可以通过config set命令动态配置(即不重启Redis也可以生效)。
    
    [注意分别复制缓冲区和复制积压缓冲区:
    复制缓冲区是客户端输出缓冲区的一种,主节点会为每一个从节点分别分配复制缓冲区;
    复制积压缓冲区一个主节点只有一个,无论它有多少个从节点。两者都用来存储写命令,]
各场景复制的选择及优化选择
(1)第一次建立复制
     此时全量复制不可避免,但仍有几点需要注意:
     1、如果主节点的数据量较大,应该尽量避开流量的高峰期,避免造成进程阻塞;    
     2、如果有多个从节点需要建立对主节点的复制,可以考虑将几个从节点错开,避免主节点带宽占用过大。    
     3、如果从节点过多,也可以调整主从复制的拓扑结构,由一主多从结构变为树状结构(中间的节点既是其主节点的从节点,也是其从节点的主节点);
      [但使用树状结构应该谨慎:虽然主节点的直接从节点减少,降低了主节点的负担,但是多层从节点的延迟增大,数据一致性变差;且结构复杂,维护相当困难。]

(2)主节点重启    
    主节点重启可以分为两种情况来讨论,一种是故障导致宕机,另一种则是有计划的重启。
    1、主节点宕机
      !主节点宕机重启后,runid会发生变化,因此不能进行部分复制,只能全量复制。
      【实际上在主节点宕机的情况下,应进行故障转移处理,将其中的一个从节点升级为主节点,其他从节点从新的主节点进行复制;且故障转移应尽量的自动化,尽量使用哨兵或自写程序完成】
    2、安全重启:debug reload    
      ```在一些场景下,可能希望对主节点进行重启,例如主节点内存碎片率过高,或者希望调整一些只能在启动时调整的参数。如果使用普通的手段重启主节点,会使得runid发生变化,可能导致不必要的全量复制。
      ```Redis提供了debug reload的重启方式:重启后,主节点的runid和offset都不受影响,避免了全量复制。
      [但debug会清空当前内存中的数据,重新从RDB文件中加载,这个过程会导致主节点的阻塞,因此也需要谨慎。]

(3)从节点重启        
    从节点宕机重启后,其保存的主节点的runid会丢失,因此即使再次执行slaveof,也无法进行部分复制。只能全量走起;

(4)网络中断    
     如果主从节点之间出现网络问题,造成短时间内网络中断,可以分为多种情况讨论。
       1、网络问题时间极为短暂,只造成了短暂的丢包,主从节点都没有判定超时(未触发repl-timeout);此时只需要通过REPLCONF ACK来补充丢失的数据即可。[自动完成]
       2、网络问题时间很长,主从节点判断超时(触发了repl-timeout),且丢失的数据过多,超过复制积压缓冲区大小;
          此时主从节点无法进行部分复制,只能进行全量复制。    
          为了尽可能避免这种情况的发生,应该根据实际情况适当调整复制积压缓冲区的大小;此外及时发现并修复网络中断,也可以减少全量复制。
       3、介于前述两种情况之间,主从节点判断超时,且丢失的数据仍然都在复制积压缓冲区中;此时主从节点可以进行部分复制。    
复制相关配置
配置分为主的配置、从的配置以及与主从都有的配置,下面分别说明。
(1)主从都有的配置
     1、slaveof <masterip> <masterport>:
         Redis启动时起作用;作用是建立复制关系,开启了该配置的Redis服务器在启动后成为从节点。该注释默认注释掉,即Redis服务器默认都是主节点。
     2、repl-timeout 60:
        与各个阶段主从节点连接超时判断有关,ping命令发出后收不到回应,超过此时间超时。 

(2)主节点相关配置
     1、repl-diskless-sync no:            
        作用于全量复制阶段,控制主节点是否使用diskless复制(无盘复制)。
        所谓diskless复制,是指在全量复制时,主节点不再先把数据写入RDB文件,而是直接写入slave的socket中,整个过程中不涉及硬盘;diskless复制在磁盘IO很慢而网速很快时更有优势。默认关闭;
     2、repl-diskless-sync-delay 5:
        该配置作用于全量复制阶段,当主节点使用diskless复制时,该配置决定主节点向从节点发送之前停顿的时间,单位是秒;只有当diskless复制打开时有效,默认5s。
        设置停顿时间原因:
         1) 向slave的socket的传输一旦开始,新连接的slave只能等待当前数据传输结束,才能开始新的数据传输
         2) 多个从节点有较大的概率在短时间内建立主从复制。
     3、client-output-buffer-limit slave 256MB 64MB 60:
        与全量复制阶段主节点的缓冲区大小有关        
        默认为client-output-buffer-limit slave 256MB 64MB 60,
        [在全量复制过程中,若积压缓冲区使用大小超过256M,或直接在60s之内使用超过64M,将会断开与该从服务器的链接]
     4、repl-disable-tcp-nodelay no:
        与命令传播阶段的延迟有关;是否将多个命令传播打包至一个tcp包中进行传输;
        [适合在网络带宽足够但是,对于缓存的主从一致性要求不高的场合]
     5、masterauth <master-password>:
        与连接建立阶段的身份验证有关[一般不设置]
     6、repl-ping-slave-period 10:
        与命令传播阶段主从节点的超时判断有关,主节点多长时间ping一下从节点,默认10秒;
     7、repl-backlog-size 1mb:
        复制积压缓冲区的大小;[可以调大,但是会影响一致性速度]
     8、repl-backlog-ttl 3600:
        当主节点没有从节点时,复制积压缓冲区保留的时间,这样当断开的从节点重新连进来时,可以进行全量复制;默认3600s。如果设置为0,则永远不会释放复制积压缓冲区。
     9、min-slaves-to-write 3与min-slaves-max-lag 10:
        规定了主节点的最小从节点数目,及对应的最大延迟;
        当主节点的从节点少于这个数时,或每个从节点与主节点之间的延迟都超过所设定的10秒时,将会限制命令广播中命令的类型,主节点只能向从节点发送最基础的命令;

(3)从节点相关配置        
     1、slave-serve-stale-data yes:
        与从节点数据陈旧时是否响应客户端命令有关,用于设置当主节点与从节点之间的网络延迟较大时,从节点是否可以接收主节点的广播命令;
     2、slave-read-only yes:
        从节点是否只读;默认是只读的。由于从节点开启写操作容易导致主从节点的数据不一致,因此该配置尽量不要修改。
单机内存大小限制
在Redis的使用中,限制单机内存大小的因素非常之多,下面总结一下在主从复制中,单机内存过大可能造成的影响:
    (1)切主:当主节点宕机时,一种常见的容灾策略是将其中一个从节点提升为主节点,并将其他从节点挂载到新的主节点上,此时这些从节点只能进行全量复制;如果Redis单机内存达到10GB,一个从节点的同步时间在几分钟的级别;如果从节点较多,恢复的速度会更慢。如果系统的读负载很高,而这段时间从节点无法提供服务,会对系统造成很大的压力。

    (2)从库扩容:如果访问量突然增大,此时希望增加从节点分担读负载,如果数据量过大,从节点同步太慢,难以及时应对访问量的暴增。

    (3)缓冲区溢出:如果单机内存中的数据过于庞大,则可能导致在全量复制阶段,主节点的复制缓冲区溢出,从而导致复制中断;从而导致从节点再次连接,发现数据又不一样,又全量复制,由发生缓冲区溢出的恶性循环;

    (4)超时:如果数据量过大,全量复制阶段主节点fork+保存RDB文件耗时过大,从节点长时间接收不到数据触发超时;于是又发生类似的恶性循环;

总结:主节点单机内存不能太大,redis占用主机内存的比例也不能过大:最好只使用50%-65%的内存,留下30%-45%的内存用于执行bgsave命令和创建复制缓冲区等。    
posted @ 2020-05-07 13:54  常见-youmen  阅读(306)  评论(0编辑  收藏  举报