0%

Redis

Redis使用.

安装

  • 下载压缩包
  • 传到Linux服务器解压
  • 到文件目录下用make命令编译
  • make PREFIX=/usr/local/redis install安装到指定目录
  • 到上一步的目录下./redis-server启动服务端
  • ./redis-cli进入客户端,如果是非本机连接:redis-cli -h IP地址 -p 端口(默认IP为本机,端口6379)

配置

将解压文件夹里的redis.conf移动到安装目录,然后编辑该文件.

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
#表示现在只能本机访问
bind 127.0.0.1

#端口号
port 6379

#让redis以守护进程方式运行
daemonize yes

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

#指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
#save <seconds> <changes>
#Redis默认配置文件中提供了三个条件:
save 900 1
save 300 10
save 60 10000
#分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

#指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF(压缩算法)压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

#指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb

#指定本地数据库存放目录
dir ./

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

编辑文件内有一段include,用来引入其它配置文件,有共用的配置文件时需要用到.

Redis内存维护策略

为数据设置超时时间

ttl key1查询key1的过期时间.没有设置过期时间的就是永不过期.-1为永不过期,-2表示已过期

expire key time设置过期时间,以秒为单位.字符串要采用另一种方式setex(String key,int seconds,String value)

LRU算法

内存页面管理的算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。

LRU->Least Recently Used

LFU->Least Frequently Used

  1. volatile-lru:设定超时时间的数据中,删除最不常使用的数据.
  2. allkeys-lru:查询所有的key中最近最不常使用的数据进行删除,这是应用最广泛的策略.
  3. volatile-random:在已经设定了超时的数据中随机删除.
  4. allkeys-random:查询所有的key,之后随机删除.
  5. volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上将要过期的数据进行删除操作.
  6. noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回.
  7. volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  8. allkeys-lfu:从所有键中驱逐使用频率最少的键

Redis数据类型

  • string
  • set
  • list
  • hash
  • zset

key *查询当前库的所有键

exists <key>查询某个键是否存在

type <key>查看键类型

del <key>删除某个键

dbsize查看当前库的key的数量

Flushdb清空当前库

Flushall清空全部

string

string是最基本的类型,它是二进制安全的,可以包含任意数据,例如图片或者序列化的对象.value最大值为512M.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

set k1 v1
ok

get k1
"v1"

append k1 234//在k1的value后追加234
(integer)5

strlen k1
(integer)5

setnx k1 abc//SET if Not eXists

set k2 12
incr k2//为k2自增1,注意要类型匹配能够加的才加

decr k2//减一

incrby/decrby k2 step//自定义加减的步长
1
2
3
4
5
mset k1 v1 k2 v2//一次设置多个值

mget k1 k2//一次获取多个值

msetnx k1 v1 k2 v2//当k1,k2不存在时设置值
1
2
getrange k1 0 2//获取指定key的指定范围内的子字符串,闭集
//与之类似的setrange是设置范围内的值
1
2
3
setex <key> <过期时间> <value>

getset <key> <value>//以新换旧,同时获得旧值

严格来说,Redis中只有string类型,下面几种集合类型内部都是放的string.

List

List是一个字符串列表,按照插入顺序排序.底层是一个双向链表,两端操作性能高.

lpush/rpush :从左边或右边插入一个或多个值

lpop/rpop :弹出一个值(list内不再保留)

rpoplpush :从key1右边弹出一个值压入key2左边

lrange :按照所有下标获得元素,从左到右,0~-1可以表示全部

lindex :按照索引获得元素

llen :获得列表长度

linsert before :在value的前面插入新值,after是在后面

lrem :从左边删除n个value,n为正数表示从左往右删,负数表示从右往左删,0表示删除所有

Set

Set是无序的,不重复的集合,底层是一个value为null的hash表.

sadd :添加多个值

smembers :获取所有值

sismember :判断是否包含v

scard :返回个数

srem :删除元素

spop :随机弹出一个,删除

srandmember :随机取出n个值,不会删除

sinter :返回两个集合的交集

sunion :并集

sdiff :差集

hash

键值对,类似于Map<String,String>.

hset :key是hash的名字,filed是键值对的键

1
hset userInfo user:1010:uid 1010

hmset :设置多个键值对

1
hmset userInfo user:1010:username admin user:1010:passwd 123

hget key filed:取出键值对的值

hexists key :判断是否存在

hkeys :列出所有filed

hvals :列出所有value

hgetall :列出所有键值对

hincrby :为field所指的value加上increment值

hsetnx :不存在键值对时添加

zset

zset有序,但是不是按照元素本身排序,而是每个元素关联了一个评分,按照这个评分排序(从低到高).集合元素唯一,但评分不唯一.

zadd :添加多个,如果添加value相同但分数不同,会把原来的分数覆盖

zrange :返回下标范围内元素,withscores可以带分数返回

zrangebyscore min max:返回分数范围内的value,同样也可以用withscores返回分数

zrevrangebyscore key max min:反过来

zincrby :为元素的分数加上增量

zrem :删除指定元素

zcount :统计分数区间内的个数

zrank :返回该元素在集合中的排名,从0开始

用Java操作redis

需要用Jedis这个包,然后调用其方法即可.Jedis的方法与上面的方法完全一样.

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
String result = jedis.ping();
System.out.println(result);
int i = 0;
while (i < 1000) {
String value = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
jedis.lpush("name", value);
i++;
}
jedis.close();
}

Redis事务

Redis事务的主要作用就是串联多个命令防止别的命令插队.

Multi开始事务,将后续命令加入队列,Exec执行事务(队列里的命令),discard取消事务.

执行事务出现错误有两种:第一,类似于编译时异常,比如几条命令中有一条写错了set写成srt,事务就会直接取消;第二,类似于运行时异常,事务开始执行后某一条命令出现错误,事务并不会取消,而是正确执行的就执行,错误的就取消.

可以通过watch监视一个或多个key,如果发生改变,那么与之相关的事务全部取消.与之相对的命令是unwatch.

三个特性:

  • 单独的隔离操作,事务中的命令都是串行的,事务在执行过程中不会被其它客户端的命令打断.
  • 没有隔离级别,因为事务队列里的命令在未提交之前都没有实际执行,也就不存在Mysql里的脏读幻读等问题.
  • 不保证原子性,同一个事务中有错误的话,并不影响事务中其它命令执行.

现在设想一个场景:商品页面售货,有大量请求进来购买,服务器用redis记录商品数量,当数量小于等于0时停止售卖.

为了测试Redis在高并发情况下的事务,现在安装一个高并发模拟工具

1
sudo apt-get install apache2-utils

这里面有一个ab工具用来模拟并发,执行命令:

1
ab -n 1000 -c 200 -p /opt/postfile -T "application/x-www-form-urlencoded" 127.0.0.1:8080/Redis/doseckill

-n表示请求个数,-c并发数(瞬间发送的请求个数),-p表示要发送的数据文件,-T表示请求头信息,后面是请求地址.

在没有启用事务且并发较高的情况下,有可能出现商品数量已经小于0,但仍在售卖的现象.例如有100个请求获取到商品数量为5,然后它们都去给数量减一,结果就造成数量为-95,出现超卖的问题.

这里其实应该使用watch监视库存.

1
2
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.watch(kc);//监视redis中key为kc的值
1
2
3
4
Transaction transaction = jedis.multi();
transaction.decr(kcKey);
transaction.sadd(userKey,uid);
List<Object> list = transaction.exec();

接着判断这个集合,如果为空表示事务执行失败.

但是这有个问题,使用watch后,一次并发的200次请求只有一个能成功让数量减一,而其它199个都因为事务的原因不能减.这不符合逻辑,因为此时商品数量还有.如果向参数设置那样一共就1000个请求,五次并发就没了,商品只卖了5个.

如何改进?这里使用lua脚本.该脚本有一定原子性,不会被其它命令插队.只需要让每个请求执行这个脚本即可.

持久化

RDB

RDB其实就是把数据以快照的形式保存在磁盘上。

RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

既然RDB机制是通过把某个时刻的所有数据生成一个快照来保存,那么就应该有一种触发机制,是实现这个过程。对于RDB来说,提供了三种机制:save、bgsave、自动化。

1、save 触发方式

该命令会阻塞当前 Redis 服务器,执行 save 命令期间,Redis 不能处理其他命令,直到 RDB 过程完成为止。具体流程如下:

执行完成时候如果存在老的 RDB 文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。

2、bgsave 触发方式

执行该命令时,Redis 会在后台异步进行快照操作,快照同时还可以响应客户端请求。具体流程如下:

具体操作是 Redis 进程执行 fork 操作创建子进程,RDB 持久化过程由子进程负责,完成后自动结束。阻塞只发生在 fork 阶段,一般时间很短。基本上 Redis 内部所有的 RDB 操作都是采用 bgsave 命令。

3、自动触发

自动触发是由我们的配置文件来完成的。在 redis.conf 配置文件中,里面有如下配置,我们可以去设置:

①save:这里是用来配置触发 Redis 的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如 “save m n”。表示 m 秒内数据集存在 n 次修改时,自动触发 bgsave。

默认如下配置:

#表示 900 秒内如果至少有 1 个 key 的值变化,则保存

save 900 1

#表示 300 秒内如果至少有 10 个 key 的值变化,则保存

save 300 10

#表示 60 秒内如果至少有 10000 个 key 的值变化,则保存 save 60 10000

不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。

②stop-writes-on-bgsave-error :默认值为 yes。当启用了 RDB 且最后一次后台保存数据失败,Redis 是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果 Redis 重启了,那么又可以重新开始接收数据了

③rdbcompression ;默认值是 yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。

④rdbchecksum :默认值是 yes。在存储快照后,我们还可以让 redis 使用 CRC64 算法来进行数据校验,但是这样做会增加大约 10% 的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

⑤dbfilename :设置快照的文件名,默认是 dump.rdb

⑥dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。

我们可以修改这些配置来实现我们想要的效果。因为第三种方式是配置的,所以我们对前两种进行一个对比:

4、RDB 的优势和劣势

①、优势

(1)RDB 文件紧凑,全量备份,非常适合用于进行备份和灾难恢复。

(2)生成 RDB 文件的时候,redis 主进程会 fork() 一个子进程来处理所有保存工作,主进程不需要进行任何磁盘 IO 操作。

(3)RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。

②、劣势

RDB 快照是一次全量备份,存储的是内存数据的二进制序列化形式,存储上非常紧凑。当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据,父进程修改内存子进程不会反应出来,所以在快照持久化期间修改的数据不会被保存,可能丢失数据。

三、AOF 机制

全量备份总是耗时的,有时候我们提供一种更加高效的方式 AOF,工作机制很简单,redis 会将每一个收到的写命令都通过 write 函数追加到文件中。通俗的理解就是日志记录。

配置:

表示是否开启AOF持久化:

  appendonly yes(默认no,关闭)

AOF持久化配置文件的名称:

  appendfilename “appendonly.aof”

AOF持久化策略(默认每秒):

  appendfsync always (同步持久化,每次发生数据变更会被立即记录到磁盘,性能差但数据完整性比较好)

  appendfsync everysec (异步操作,每秒记录,如果一秒钟内宕机,有数据丢失)

  appendfsync no (将缓存回写的策略交给系统,linux 默认是30秒将缓冲区的数据回写硬盘的)

AOF的Rewrite(重写) :

  定义:AOF采用文件追加的方式持久化数据,所以文件会越来越大,为了避免这种情况发生,增加了重写机制.

  当AOF文件的大小超过了配置所设置的阙值时,Redis就会启动AOF文件压缩,只保留可以恢复数据的最小指令集,可以使用命令bgrewriteaof

触发机制:Redis会记录上次重写时的AOF文件大小,默认配置时当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

     auto-aof-rewrite-percentage 100 (一倍)

     auto-aof-rewrite-min-size 64mb

1、持久化原理

他的原理看下面这张图:

每当有一个写命令过来时,就直接保存在我们的 AOF 文件中。

2、文件重写原理

AOF 的方式也同时带来了另一个问题。持久化文件会变的越来越大。为了压缩 aof 的持久化文件。redis 提供了 bgrewriteaof 命令。将内存中的数据以命令的方式保存到临时文件中,同时会 fork 出一条新进程来将文件重写。

重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 aof 文件,这点和快照有点类似。

3、AOF 也有三种触发机制

(1)每修改同步 always:同步持久化 每次发生数据变更会被立即记录到磁盘 性能较差但数据完整性比较好

(2)每秒同步 everysec:异步操作,每秒记录 如果一秒内宕机,有数据丢失

(3)不同 no:从不同步

4、优点

(1)AOF 可以更好的保护数据不丢失,一般 AOF 会每隔 1 秒,通过一个后台线程执行一次 fsync 操作,最多丢失 1 秒钟的数据。(2)AOF 日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。

(3)AOF 日志文件即使过大的时候,出现后台重写操作,也不会影响客户端的读写。

(4)AOF 日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。比如某人不小心用 flushall 命令清空了所有数据,只要这个时候后台 rewrite 还没有发生,那么就可以立即拷贝 AOF 文件,将最后一条 flushall 命令给删了,然后再将该 AOF 文件放回去,就可以通过恢复机制,自动恢复所有数据

5、缺点

(1)对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大

(2)AOF 开启后,支持的写 QPS 会比 RDB 支持的写 QPS 低,因为 AOF 一般会配置成每秒 fsync 一次日志文件,当然,每秒一次 fsync,性能也还是很高的

(3)以前 AOF 发生过 bug,就是通过 AOF 记录的日志,进行数据恢复的时候,没有恢复一模一样的数据出来。

四、RDB 和 AOF 到底该如何选择

选择的话,两者加一起才更好。因为两个持久化机制你明白了,剩下的就是看自己的需求了,需求不同选择的也不一定,但是通常都是结合使用。有一张图可供总结:

对比了这几个特性,剩下的就是看自己了。

主从复制

什么是主从复制

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

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

主从复制的作用

  1. 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  2. 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  3. 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写 Redis 数据时应用连接主节点,读 Redis 数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高 Redis 服务器的并发量。
  4. 读写分离:可以用于实现读写分离,主库写、从库读,读写分离不仅可以提高服务器的负载能力,同时可根据需求的变化,改变从库的数量;
  5. 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是 Redis 高可用的基础。

主从复制启用

从节点开启主从复制,有 3 种方式:

  1. 配置文件: 在从服务器的配置文件中加入:slaveof <masterip> <masterport>
  2. 启动命令: redis-server 启动命令后加入 –slaveof <masterip> <masterport>
  3. 客户端命令: Redis 服务器启动后,直接通过客户端执行命令:slaveof <masterip>
    <masterport>,则该 Redis 实例成为从节点。

通过 info replication 命令可以看到复制的一些信息

主从复制原理

主从复制过程大体可以分为 3 个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段。

在从节点执行 slaveof 命令后,复制过程便开始运作,下面图示大概可以看到,
从图中可以看出复制过程大致分为 6 个过程
20200717160722

主从配置之后的日志记录也可以看出这个流程

1)保存主节点(master)信息。
执行 slaveof 后 Redis 会打印如下日志:

20200717160849

2)从节点(slave)内部通过每秒运行的定时任务维护复制相关逻辑,当定时任务发现存在新的主节点后,会尝试与该节点建立网络连接
20200717160910
从节点与主节点建立网络连接

从节点会建立一个 socket 套接字,从节点建立了一个端口为 51234 的套接字,专门用于接受主节点发送的复制命令。从节点连接成功后打印如下日志:

20200717161129

如果从节点无法建立连接,定时任务会无限重试直到连接成功或者执行 slaveof no one 取消复制

关于连接失败,可以在从节点执行 info replication 查看 master_link_down_since_seconds 指标,它会记录与主节点连接失败的系统时间。从节点连接主节点失败时也会每秒打印如下日志,方便发现问题:

1
# Error condition on socket for SYNC: {socket_error_reason}

3)发送 ping 命令。
连接建立成功后从节点发送 ping 请求进行首次通信,ping 请求主要目的如下:
· 检测主从之间网络套接字是否可用。
· 检测主节点当前是否可接受处理命令。
如果发送 ping 命令后,从节点没有收到主节点的 pong 回复或者超时,比如网络超时或者主节点正在阻塞无法响应命令,从节点会断开复制连接,下次定时任务会发起重连。
20200717161041
20200717161113

从节点发送的 ping 命令成功返回,Redis 打印如下日志,并继续后续复制流程:
20200717161129 (1)

4)权限验证。如果主节点设置了 requirepass 参数,则需要密码验证,从节点必须配置 masterauth 参数保证与主节点相同的密码才能通过验证;如果验证失败复制将终止,从节点重新发起复制流程。

5)同步数据集。主从复制连接正常通信后,对于首次建立复制的场景,主节点会把持有的数据全部发送给从节点,这部分操作是耗时最长的步骤。

6)命令持续复制。当主节点把当前的数据同步给从节点后,便完成了复制的建立流程。接下来主节点会持续地把写命令发送给从节点,保证主从数据一致性。

主从复制的细节

参考这个连接:https://www.cnblogs.com/daofaziran/p/10978628.html

全量同步
Redis 全量复制一般发生在 Slave 初始化阶段,这时 Slave 需要将 Master 上的所有数据都复制一份。具体步骤如下:

  • 从服务器连接主服务器,发送 SYNC 命令;
  • 主服务器接收到 SYNC 命名后,开始执行 BGSAVE 命令生成 RDB 文件并使用缓冲区记录此后执行的所有写命令;
  • 主服务器 BGSAVE 执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;
  • 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;
  • 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;
  • 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

20200717153700

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。

增量同步

Redis 增量复制是指 Slave 初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

Redis 主从同步策略

主从刚刚连接的时候,进行全量同步;全同步结束后,进行增量同步。当然,如果有需要,slave 在任何时候都可以发起全量同步。redis 策略是,无论如何,首先会尝试进行增量同步,如不成功,要求从机进行全量同步。

注意点
如果多个 Slave 断线了,需要重启的时候,因为只要 Slave 启动,就会发送 sync 请求和主机全量同步,当多个同时出现的时候,可能会导致 Master IO 剧增宕机。

额外的注意点:

  • 一个主机的从服务器后面又跟了一个从服务器,这样一个链条,当主服务器宕机后,可以使用slaveof no one使中间那台从服务器变为主服务器继续工作.

  • 哨兵模式是上面那种情况的自动版,它会监听主机的工作情况.具体如下文所述:

一、哨兵模式概述

哨兵模式是一种特殊的模式,首先 Redis 提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待 Redis 服务器响应,从而监控运行的多个 Redis 实例。

20200717160225

这里的哨兵有两个作用

通过发送命令,让 Redis 服务器返回监控其运行状态,包括主服务器和从服务器

当哨兵监测到 master 宕机,会自动将 slave 切换成 master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对 Redis 服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

用文字描述一下故障切换(failover)的过程。假设主服务器宕机,哨兵 1 先检测到这个结果,系统并不会马上进行 failover 过程,仅仅是哨兵 1 主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行 failover 操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

二、Redis 配置哨兵模式

配置 3 个哨兵和 1 主 2 从的 Redis 服务器来演示这个过程。

服务类型 是否是主服务器 IP 地址 端口
Redis 192.168.11.128 6379
Redis 192.168.11.129 6379
Redis 192.168.11.130 6379
Sentinel - 192.168.11.128 26379
Sentinel - 192.168.11.129 26379
Sentinel - 192.168.11.130 26379

20200717161324

多哨兵监控 Redis

首先配置 Redis 的主从服务器,修改 redis.conf 文件如下

1
2
3
4
5
6
7
8
# 使得Redis服务器可以跨网络访问
bind 0.0.0.0
# 设置密码
requirepass "123456"
# 指定主服务器,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
slaveof 192.168.11.128 6379
# 主服务器密码,注意:有关slaveof的配置只是配置从服务器,主服务器不需要配置
masterauth 123456

上述内容主要是配置 Redis 服务器,从服务器比主服务器多一个 slaveof 的配置和密码。

配置 3 个哨兵,每个哨兵的配置都是一样的。在 Redis 安装目录下有一个 sentinel.conf 文件,copy 一份进行修改

1
2
3
4
5
6
7
# 禁止保护模式
protected-mode no
# 配置监听的主服务器,这里sentinel monitor代表监控,mymaster代表服务器的名称,可以自定义,192.168.11.128代表监控的主服务器,6379代表端口,2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作。
sentinel monitor mymaster 192.168.11.128 6379 2
# sentinel author-pass定义服务的密码,mymaster是服务名称,123456是Redis服务器密码
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster 123456

上述关闭了保护模式,便于测试。

有了上述的修改,我们可以进入 Redis 的安装目录的 src 目录,通过下面的命令启动服务器和哨兵

1
2
3
4
# 启动Redis服务器进程
./redis-server ../redis.conf
# 启动哨兵进程
./redis-sentinel ../sentinel.conf

注意启动的顺序。首先是主机(192.168.11.128)的 Redis 服务进程,然后启动从机的服务进程,最后启动 3 个哨兵的服务进程。

以上关于主从复制和哨兵模式都是redis关于高可用,高并发的策略,但是都不是最终策略.Redis的终极策略是搭建Redis集群.

Redis集群

一、redis-cluster 设计
Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态, 每个节点都和其他所有节点连接。

20200717163629

其结构特点:
1、所有的 redis 节点彼此互联 (PING-PONG 机制), 内部使用二进制协议优化传输速度和带宽。
2、节点的 fail 是通过集群中超过半数的节点检测失效时才生效。
3、客户端与 redis 节点直连, 不需要中间 proxy 层. 客户端不需要连接集群所有节点, 连接集群中任何一个可用节点即可。
4、redis-cluster 把所有的物理节点映射到 [0-16383]slot 上(不一定是平均分配),cluster 负责维护 node<->slot<->value。
5、Redis 集群预分好 16384 个桶,当需要在 Redis 集群中放置一个 key-value 时,根据 CRC16(key) mod 16384 的值,决定将一个 key 放到哪个桶中。

a.redis cluster 节点分配
现在我们是三个主节点分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot) 的方式来分配 16384 个 slot 的话,它们三个节点分别承担的 slot 区间是:

  • 节点 A 覆盖 0-5460;

  • 节点 B 覆盖 5461-10922;

  • 节点 C 覆盖 10923-16383.

    获取数据:
    如果存入一个值,按照 redis cluster 哈希槽的算法: CRC16(‘key’)384 = 6782。 那么就会把这个 key 的存储分配到 B 上了。同样,当我连接 (A,B,C) 任何一个节点想获取’key’这个 key 时,也会这样的算法,然后内部跳转到 B 节点上获取数据

    新增一个主节点:
    新增一个节点 D,redis cluster 的这种做法是从各个节点的前面各拿取一部分 slot 到 D 上,我会在接下来的实践中实验。大致就会变成这样:

  • 节点 A 覆盖 1365-5460

  • 节点 B 覆盖 6827-10922

  • 节点 C 覆盖 12288-16383

  • 节点 D 覆盖 0-1364,5461-6826,10923-12287

同样删除一个节点也是类似,移动完成后就可以删除这个节点了。

b.Redis Cluster 主从模式
redis cluster 为了保证数据的高可用性,加入了主从模式,一个主节点对应一个或多个从节点,主节点提供数据存取,从节点则是从主节点拉取数据备份,当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉

上面那个例子里, 集群有 ABC 三个主节点, 如果这 3 个节点都没有加入从节点,如果 B 挂掉了,我们就无法访问整个集群了。A 和 C 的 slot 也无法访问。

所以我们在集群建立的时候,一定要为每个主节点都添加了从节点, 比如像这样, 集群包含主节点 A、B、C, 以及从节点 A1、B1、C1, 那么即使 B 挂掉系统也可以继续正确工作。

B1 节点替代了 B 节点,所以 Redis 集群将会选择 B1 节点作为新的主节点,集群将会继续正确地提供服务。 当 B 重新开启后,它就会变成 B1 的从节点。

不过需要注意,如果节点 B 和 B1 同时挂了,Redis 集群就无法继续正确地提供服务了。

二、redis 集群的搭建
集群中至少应该有奇数个节点,所以至少有三个节点,每个节点至少有一个备份节点,所以下面使用 6 节点(主节点、备份节点由 redis-cluster 集群确定)
下载 redis
1、安装 redis 节点指定端口
解压 redis 压缩包,编译安装

1
2
3
4
[root@localhost redis-3.2.0]# tar xzf redis-3.2.0.tar.gz
[root@localhost redis-3.2.0]# cd redis-3.2.0
[root@localhost redis-3.2.0]# make
[root@localhost redis01]# make install PREFIX=/usr/andy/redis-cluster

在 redis-cluster 下 修改 bin 文件夹为 redis01, 复制 redis.conf 配置文件
创建目录 redis-cluster 并在此目录下再创建 7000 7001 7002 7003 7004 7005 共 6 个目录,在 7000 中创建配置文件 redis.conf,内容如下:

1
2
3
4
5
6
daemonize yes #后台启动
port 7001 #修改端口号,从7001到7006
cluster-enabled yes #开启cluster,去掉注释
cluster-config-file nodes.conf #自动生成
cluster-node-timeout 15000 #节点通信时间
appendonly yes #持久化方式

同时把 redis.conf 复制到其它目录中

2、安装 redis-trib 所需的 ruby 脚本
注意:centos7 默认的 ruby 版本太低 (2.0),要卸载重装 (最低 2.2)

1
2
3
yum remove ruby
yum install ruby
yum install rubygems

复制 redis 解压文件 src 下的 redis-trib.rb 文件到 redis-cluster 目录并安装 gem

1
gem install redis-3.x.x.gem

若不想安装 src 目录下的 gem,也可以直接gem install redis

注意,gem install 可能会报错
Unable to require openssl,install OpenSSL and rebuild ruby (preferred) or use ….
解决步骤:

  1. yum install openssl-devel -y
  2. 在 ruby 安装包 / root/ruby-x.x.x/ext/openssl,执行 ruby ./extconf.rb
  3. 执行 make, 若出现 make: *** No rule to make target /include/ruby.h', needed byossl.o’. Stop.; 在 Makefile 顶部中的增加top_srcdir = ../..
  4. 执行 make install

3、启动所有的 redis 节点
可以写一个命令脚本 start-all.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
cd 7000
redis-server redis.conf
cd ..
cd 7001
redis-server redis.conf
cd ..
cd 7002
redis-server redis.conf
cd ..
cd 7003
redis-server redis.conf
cd ..
cd 7004
redis-server redis.conf
cd ..
cd 7005
redis-server redis.conf
cd ..

设置权限启动

1
2
[root@localhost redis-cluster]# chmod 777 start-all.sh 
[root@localhost redis-cluster]# ./start-all.sh

查看 redis 进程启动状态

1
2
3
4
5
6
7
8
[root@localhost redis-4.0.2]# ps -ef|grep cluster
root 54956 1 0 19:17 ? 00:00:00 redis-server *:7000 [cluster]
root 54961 1 0 19:17 ? 00:00:00 redis-server *:7001 [cluster]
root 54966 1 0 19:17 ? 00:00:00 redis-server *:7002 [cluster]
root 54971 1 0 19:17 ? 00:00:00 redis-server *:7003 [cluster]
root 54976 1 0 19:17 ? 00:00:00 redis-server *:7004 [cluster]
root 54981 1 0 19:17 ? 00:00:00 redis-server *:7005 [cluster]
root 55071 24089 0 19:24 pts/0 00:00:00 grep --color=auto cluster

可以看到 redis 的 6 个节点已经启动成功
注意:这里并没有创建集群

4、使用 redis-trib.rb 创建集群
注意:redis-trib.rb 在 redis/src 目录下。

1
./redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7000

使用 create 命令 –replicas 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
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
[root@localhost redis]# ./src/redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7000
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
127.0.0.1:7001
127.0.0.1:7002
127.0.0.1:7003
Adding replica 127.0.0.1:7004 to 127.0.0.1:7001
Adding replica 127.0.0.1:7005 to 127.0.0.1:7002
Adding replica 127.0.0.1:7000 to 127.0.0.1:7003
M: f4ee0a501f9aaf11351787a46ffb4659d45b7bd7 127.0.0.1:7001
slots:0-5460 (5461 slots) master
M: 671a0524a616da8b2f50f3d11a74aaf563578e41 127.0.0.1:7002
slots:5461-10922 (5462 slots) master
M: 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1 127.0.0.1:7003
slots:10923-16383 (5461 slots) master
S: 34e322ca50a2842e9f3664442cb11c897defba06 127.0.0.1:7004
replicates f4ee0a501f9aaf11351787a46ffb4659d45b7bd7
S: 62a00566233fbff4467c4031345b1db13cf12b46 127.0.0.1:7005
replicates 671a0524a616da8b2f50f3d11a74aaf563578e41
S: 2cb649ad3584370c960e2036fb01db834a546114 127.0.0.1:7000
replicates 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join...
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: f4ee0a501f9aaf11351787a46ffb4659d45b7bd7 127.0.0.1:7001
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: 671a0524a616da8b2f50f3d11a74aaf563578e41 127.0.0.1:7002
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: 2cb649ad3584370c960e2036fb01db834a546114 127.0.0.1:7000
slots: (0 slots) slave
replicates 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1
S: 34e322ca50a2842e9f3664442cb11c897defba06 127.0.0.1:7004
slots: (0 slots) slave
replicates f4ee0a501f9aaf11351787a46ffb4659d45b7bd7
M: 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1 127.0.0.1:7003
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 62a00566233fbff4467c4031345b1db13cf12b46 127.0.0.1:7005
slots: (0 slots) slave
replicates 671a0524a616da8b2f50f3d11a74aaf563578e41
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

上面显示创建成功,有 3 个主节点,3 个从节点,每个节点都是成功连接状态。

三、redis 集群的测试
测试存取值,客户端连接集群 redis-cli 需要带上 -c ,redis-cli -c -p 端口号

1
2
3
4
5
6
7
[root@localhost redis]# ./redis-cli -c -p 7001
127.0.0.1:7001> set name andy
-> Redirected to slot [5798] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get name
"andy"
127.0.0.1:7002>

根据 redis-cluster 的 key 值分配,name 应该分配到节点 7002[5461-10922] 上,上面显示 redis cluster 自动从 7001 跳转到了 7002 节点。

测试一下 7000 从节点获取 name 值

1
2
3
4
5
[root@localhost redis]# ./redis-cli -c -p 7000
127.0.0.1:7000> get name
-> Redirected to slot [5798] located at 127.0.0.1:7002
"andy"
127.0.0.1:7002>

注意不要使用mset,mget这种一次管理多个值的方式,因为这几个值可能不在一起.在分开的服务器无法正确重定向.可以使用大括号将键值对加入一个组,放在一起.

四、集群节点选举
现在模拟将 7002 节点挂掉,按照 redis-cluster 原理会选举会将 7002 的从节点 7005 选举为主节点。

1
2
3
4
5
6
7
8
9
[root@localhost redis-cluster]# ps -ef | grep redis
root 7966 1 0 12:50 ? 00:00:29 ./redis-server 127.0.0.1:7000 [cluster]
root 7950 1 0 12:50 ? 00:00:28 ./redis-server 127.0.0.1:7001 [cluster]
root 7952 1 0 12:50 ? 00:00:29 ./redis-server 127.0.0.1:7002 [cluster]
root 7956 1 0 12:50 ? 00:00:29 ./redis-server 127.0.0.1:7003 [cluster]
root 7960 1 0 12:50 ? 00:00:29 ./redis-server 127.0.0.1:7004 [cluster]
root 7964 1 0 12:50 ? 00:00:29 ./redis-server 127.0.0.1:7005 [cluster]
root 11346 10581 0 14:57 pts/2 00:00:00 grep --color=auto redis
[root@localhost redis-cluster]# kill 7952

在查看集群中的 7002 节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost src]# ./redis-trib.rb check 127.0.0.1:7002
>>> Performing Cluster Check (using node 127.0.0.1:7002)
S: 671a0524a616da8b2f50f3d11a74aaf563578e41 127.0.0.1:7002
slots: (0 slots) slave
replicates 62a00566233fbff4467c4031345b1db13cf12b46
M: 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1 127.0.0.1:7003
slots:10923-16383 (5461 slots) master
1 additional replica(s)
M: 62a00566233fbff4467c4031345b1db13cf12b46 127.0.0.1:7005
slots:5461-10922 (5462 slots) master
1 additional replica(s)
M: f4ee0a501f9aaf11351787a46ffb4659d45b7bd7 127.0.0.1:7001
slots:0-5460 (5461 slots) master
1 additional replica(s)
S: 34e322ca50a2842e9f3664442cb11c897defba06 127.0.0.1:7004
slots: (0 slots) slave
replicates f4ee0a501f9aaf11351787a46ffb4659d45b7bd7
S: 2cb649ad3584370c960e2036fb01db834a546114 127.0.0.1:7000
slots: (0 slots) slave
replicates 18948dab5b07e3726afd1b6a42d5bf6e2f411ba1
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

可以看到集群连接不了 7002 节点,而 7005 有原来的 S 转换为 M 节点,代替了原来的 7002 节点。我们可以获取 name 值:

1
2
3
4
5
6
[root@localhost redis]# ./redis-cli -c -p 7001
127.0.0.1:7001> get name
-> Redirected to slot [5798] located at 127.0.0.1:7005
"andy"
127.0.0.1:7005>
127.0.0.1:7005>

从 7001 节点连入,自动跳转到 7005 节点,并且获取 name 值。

现在我们将 7002 节点恢复,看是否会自动加入集群中以及充当的 M 还是 S 节点。

1
2
3
[root@localhost redis-cluster]# cd 7002
[root@localhost 7002]# ./redis-server redis.conf
[root@localhost 7002]#

再 check 一下 7002 节点,可以看到 7002 节点变成了 7005 的从节点。