来自  资质荣誉 2019-10-10 08:26 的文章
当前位置: 澳门太阳娱乐手机登录 > 资质荣誉 > 正文

Redis入门到实战

Redis,REmote DIctionary Server,是一个由Salvatore Sanfilippo写的Key-Value存储系统。

Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

一、Redis 简介

Redis 是完全开源免费的,是一个高性能的key-value数据库。

Redis 与其他 key - value 缓存产品有以下三个特点:

Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。

Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。

Redis支持数据的备份,即master-slave模式的数据备份。

Redis支持事务和集群。

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s。

 

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

二、Redis配置文件

   如果使用自己写的配置文件启动,使用命令:redis-server <配置文件路径>/<配置文件文件名>

    redis.conf 配置项说明如下:

1. Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程

    daemonize yes

2. 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定

    pidfile /var/run/redis.pid

3. 指定Redis监听端口,默认端口为6379,作者在自己的一篇博文中解释了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利歌女Alessia Merz的名字

    port 6379

  1. 绑定的主机地址

    bind 127.0.0.1   这个Ip要设置成你服务器的Ip

5.当 客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能

    timeout 300

6. 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose

    loglevel verbose

7. 日志记录方式,默认为标准输出,如果配置Redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null

    logfile stdout

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

    databases 16

9. 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合

    save <seconds> <changes>

    Redis默认配置文件中提供了三个条件:

    save 900 1

    save 300 10

    save 60 10000

    分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒内有10000个更改。

 

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

    rdbcompression yes

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

    dbfilename dump.rdb

  1. 指定本地数据库存放目录

    dir ./

13. 设置当本机为slav服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步

    slaveof <masterip> <masterport>

  1. 当master服务设置了密码保护时,slav服务连接master的密码

    masterauth <master-password>

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

    requirepass foobared

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

    maxclients 128

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

    maxmemory <bytes>

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

    appendonly no

  1. 指定更新日志文件名,默认为appendonly.aof

     appendfilename appendonly.aof

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

    appendfsync everysec

 

21. 指定是否启用虚拟内存机制,默认值为no,简单的介绍一下,VM机制将数据分页存放,由Redis将访问量较少的页即冷数据swap到磁盘上,访问多的页面由磁盘自动换出到内存中(在后面的文章我会仔细分析Redis的VM机制)

     vm-enabled no

  1. 虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个Redis实例共享

     vm-swap-file /tmp/redis.swap

23. 将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(Redis的索引数据 就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为0

     vm-max-memory 0

  1. Redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是要根据存储的 数据大小来设定的,作者建议如果存储很多小对象,page大小最好设置为32或者64bytes;如果存储很大大对象,则可以使用更大的page,如果不 确定,就使用默认值

     vm-page-size 32

25. 设置swap文件中的page数量,由于页表(一种表示页面空闲或使用的bitmap)是在放在内存中的,,在磁盘上每8个pages将消耗1byte的内存。

     vm-pages 134217728

26. 设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成比较长时间的延迟。默认值为4

     vm-max-threads 4

  1. 设置在向客户端应答时,是否把较小的包合并为一个包发送,默认为开启

    glueoutputbuf yes

28. 指定在超过一定的数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法

    hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

29. 指定是否激活重置哈希,默认为开启(后面在介绍Redis的哈希算法时具体介绍)

    activerehashing yes

30. 指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件

    include /path/to/local.conf

 

Windows

下载地址:

图片 1

安装完成后在安装目录下执行:

redis-server.exe redis.windows.conf

图片 2

三、Redis数据类型

    Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。

String:

    Get、set。注意:一个键最大能存储512MB。

Hash:

    Hmset、hmget、hkeys、hvals

List:

    Lpush、lrange

Set

    sadd、smembers

zset

    ZADD 集合名  序号  集合元素

 

Linux

下载地址:

图片 3

下载,解压缩并编译Redis最新稳定版本:

wget http://download.redis.io/releases/redis-5.0.3.tar.gztar xzf redis-5.0.3.tar.gzcd redis-5.0.3make

启动Redis服务:

cd src./redis-server ../redis.conf

图片 4

Redis 的配置文件,Windows是安装目录的redis.windows.conf文件,Linux是安装目录下的redis.conf文件。

在连接上Redis服务后,可以通过config命令查看或者编辑配置项。

四、Redis 发布订阅

    Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

    subscribe <channel>  客户端订阅消息,<channel>为相应的频道名,如:subscribe testChannel

    publish <channel> <message>  发布消息,同时订阅该频道的客户端能收到该消息,如:publish testChannel testValue_1

 

查看

redis 127.0.0.1:6379> config get ${name}

例:

127.0.0.1:6379> config get port1) "port"2) "6379"

五、Redis 事务

    Redis 事务可以一次执行多个命令, 并且带有以下两个重要的保证:

事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

一个事务从开始到执行会经历以下三个阶段:

开始事务。

命令入队。

执行事务。

 

MULTI  开始一个事物

Set testKey value_1

Get testKey

Hmset  map key1 value1 key2 value2

Hmget map key1

Exec 执行事物

 

编辑

redis 127.0.0.1:6379> config set ${name} ${value}

例:

127.0.0.1:6379> config set loglevel "notice"OK

注:部分配置不能通过config命令动态编辑,需要手动编辑配置文件并重启服务才能生效。

六、数据持久化

redis可以对数据进行持久化保存,有两种方式:RDB 和 AOF

RDB方式:

快照是默认的持久化方式。这种方式是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb,可以通过配置文件的dbfilename参数进行修改,路径可以通过dir参数进行修改。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置:

save 900 1  #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000

client 也可以使用save或者bgsave命令通知redis做一次快照持久化,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。

 

AOF方式:

redis会将每一个收到的写命令都通过write函数追加到文件中,默认是appendonly.aof,可以在配置文件中修改appendfilename参数来改变文件名。当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。该方法的不足在于,随着数据量越来越大,文件也会越来越大,重启redis时,加载数据的时间也会越长。

appendonly yes           #启用aof持久化方式
# appendfsync always   #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec     #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no    #完全依赖os,性能最好,持久化没保证

 

部分参数说明

是否以守护线程运行,默认为no,使用yes启用守护线程;

Redis监听端口,默认为6379;

注:作者曾解释过6379的来历。6379在手机按键对应的英文是MERZ,意大利歌女Alessia Merz的名字。参考链接:

指定客户端连接地址,默认为127.0.0.1,也就是只能本地连接,屏蔽该参数启用远程连接;

客户端空闲多长时间关闭该连接,指定为0关闭该功能;

save <seconds> <changes>

指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合使用;

Redis默认提供了三个条件:

save 900 1save 300 10save 60 10000

说明Redis在下列三种情况将会同步数据到文件中:

  1. 在900秒后至少1个key发生改变;
  2. 在300秒后至少10个key发生改变;
  3. 在60秒后至少10000个key发生改变;

本地数据库文件名,默认是dump.rdb;

本地数据库文件存放路径,默认是./;

slaveof <masterip> <masterport>

当在主从复制中,自己作为slave,设置master的ip和端口,在该slave启动时,会自动从master进行数据同步;

当master设置了密码后,slave连接master的密码;

设置Redis连接密码,默认关闭;

开启Redis数据持久化到日志中,默认为no未开启;

由于默认的数据持久化方案,存储到dump.rdb文件中,在断电或服务突然挂掉的情况下会丢失数据,开启日志持久化可以弥补该不足;

日志文件名,默认为appendonly.aof;

日志更新频率,有3个可选值;

  1. no,让操作系统自己决定,速度最快;
  2. always,每次操作都会写更新日志,速度较慢但最安全;
  3. everysec,每秒更新一次日志,折中方案;

Redis支持五种数据类型:string,hash,list,set及zset(sorted set:有序集合)。

七、Redis主从

    redis主从复制过程: 当配置好slave后,slave与master建立连接,然后发送sync命令。无论是第一次连接还是重新连接,master都会启动一个后台进程,将 数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存。后台进程完成写文件后,master就发送文件给slave,slave将 文件保存到硬盘上,再加载到内存中,接着master就会把缓存的命令转发给slave,后续master将收到的写命令发送给slave。如果master同时收到多个slave发来的同步连接命令,master只会启动一个进程来写数据库镜像,然后发送给所有的slave。master同步数据时是非阻塞式的,可以接收用户的读写请求。然而在slave端是阻塞模式的,slave在同步master数据时,并不能够响应客户端的查询。

可以在master禁用数据持久化,只需要注释掉master 配置文件中的所有save配置,然后只在slave上配置数据持久化

使用slaveof <ip> <port>命令来添加从服务器,ip和端口为主服务器的ip和端口。如:有两个redis实例,分别为 127.0.0.1:6379和127.0.0.1:6380,要把6380做为6379的从节点,需要在6380中,执行slaveof 127.0.0.1 6379命令。然后使用info命令查看状态。 注意:如果使用客户端执行slaveof命令,重启实例后,主从关系将不存在,如果需要长期的主从关系,可以将命令配置在启动文件中。

 

string

最基本类型,二进制安全,也可以包含jpg或序列化后的对象,最大支持512M;

例:

127.0.0.1:6379> SET name "caojiantao"OK127.0.0.1:6379> GET name"caojiantao"

八、Redis-sentinel高可用

    Redis-Sentinel是Redis官方推荐的高可用性(HA)解决方案,当用Redis做Master-slave的高可用方案时,假如master宕机了,Redis本身(包括它的很多客户端)都没有实现自动进行主备切换,而Redis-sentinel本身也是一个独立运行的进程,它能监控多个master-slave集群,发现master宕机后能进行自动切换。

它的主要功能有以下几点

不时地监控redis是否按照预期良好地运行;

如果发现某个redis节点运行出现状况,能够通知另外一个进程(例如它的客户端);

能够进行自动切换。当一个master节点不可用时,能够选举出master的多个slave(如果有超过一个slave的话)中的一个来作为新的master,其它的slave节点会将它所追随的master的地址改为被提升为master的slave的新地址。

需要注意的是,配置文件在sentinel运行期间是会被动态修改的,例如当发生主备切换时候,配置文件中的master会被修改为另外一个slave。这样,之后sentinel如果重启时,就可以根据这个配置来恢复其之前所监控的redis集群的状态。

sentinel monitor <name> 127.0.0.1 6379 2      #<name> 可以任意取,但要保证一组配置的name相同

sentinel down-after-milliseconds <name> 60000

#sentinel can-failover <name> yes

sentinel failover-timeout <name> 180000

sentinel parallel-syncs <name> 1

当sentinel集群式,解决这个问题的方法就变得很简单,只需要多个sentinel互相沟通来确认某个master是否真的死了,这个2代表,当集群中有2个sentinel认为master死了时,才能真正认为该master已经不可用了,如果只有一个sentinel,可以写成1。

 

down-after-milliseconds
sentinel会向master发送心跳PING来确认master是否存活,如果master在“一定时间范围”内不回应 或者是回复了一个错误消息,那么这个sentinel会主观地(单方面地)认为这个master已经不可用了(subjectively down, 也简称为SDOWN)。而这个down-after-milliseconds就是用来指定这个“一定时间范围”的,单位是毫秒。

 

can-failover

no 表示当前sentinel是一个观察者,只参与投票不参与实施failover

全局中至少有一个是yes

 

parallel-syncs

当新master产生时,同时进行“slaveof”到新master并进行“SYNC”的slave个数

默认为1,建议保持默认值  

在salve执行salveof与同步时,将会终止客户端请求

failover-timeout

failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作

当前sentinel将会认为此次failoer失败

 

Sentinel的“仲裁会”

当一个master被sentinel集群监控时,需要为它指定一个参数,这个参数指定了当需要判决master为不可用,并且进行failover时,所需要的sentinel数量,本文中我们暂时称这个参数为票数

不过,当failover主备切换真正被触发后,failover并不会马上进行,还需要sentinel中的大多数sentinel授权后才可以进行failover。
当ODOWN时,failover被触发。failover一旦被触发,尝试去进行failover的sentinel会去获得“大多数”sentinel的授权(如果票数比大多数还要大的时候,则询问更多的sentinel)
这个区别看起来很微妙,但是很容易理解和使用。例如,集群中有5个sentinel,票数被设置为2,当2个sentinel认为一个master已经不可用了以后,将会触发failover,但是,进行failover的那个sentinel必须先获得至少3个sentinel的授权才可以实行failover。
如果票数被设置为5,要达到ODOWN状态,必须所有5个sentinel都主观认为master为不可用,要进行failover,那么得获得所有5个sentinel的授权。

 

配置版本号

为什么要先获得大多数sentinel的认可时才能真正去执行failover呢?

当一个sentinel被授权后,它将会获得宕掉的master的一份最新配置版本号,当failover执行结束以后,这个版本号将会被用于最新的配置。因为大多数sentinel都已经知道该版本号已经被要执行failover的sentinel拿走了,所以其他的sentinel都不能再去使用这个版本号。这意味着,每次failover都会附带有一个独一无二的版本号。我们将会看到这样做的重要性。

而且,sentinel集群都遵守一个规则:如果sentinel A推荐sentinel B去执行failover,B会等待一段时间后,自行再次去对同一个master执行failover,这个等待的时间是通过failover-timeout配置项去配置的。从这个规则可以看出,sentinel集群中的sentinel不会再同一时刻并发去failover同一个master,第一个进行failover的sentinel如果失败了,另外一个将会在一定时间内进行重新进行failover,以此类推。

redis sentinel保证了活跃性:如果大多数sentinel能够互相通信,最终将会有一个被授权去进行failover.
redis sentinel也保证了安全性:每个试图去failover同一个master的sentinel都会得到一个独一无二的版本号。

 

配置传播

一旦一个sentinel成功地对一个master进行了failover,它将会把关于master的最新配置通过广播形式通知其它sentinel,其它的sentinel则更新对应master的配置。

一个faiover要想被成功实行,sentinel必须能够向选为master的slave发送SLAVE OF NO ONE命令,然后能够通过INFO命令看到新master的配置信息。

当将一个slave选举为master并发送SLAVE OF NO ONE`后,即使其它的slave还没针对新master重新配置自己,failover也被认为是成功了的,然后所有sentinels将会发布新的配置信息。

新配在集群中相互传播的方式,就是为什么我们需要当一个sentinel进行failover时必须被授权一个版本号的原因。

每个sentinel使用##发布/订阅##的方式持续地传播master的配置版本信息,配置传播的##发布/订阅##管道是:__sentinel__:hello

因为每一个配置都有一个版本号,所以以版本号最大的那个为标准。

举个栗子:假设有一个名为mymaster的地址为192.168.1.50:6379。一开始,集群中所有的sentinel都知道这个地址,于是为mymaster的配置打上版本号1。一段时候后mymaster死了,有一个sentinel被授权用版本号2对其进行failover。如果failover成功了,假设地址改为了192.168.1.50:9000,此时配置的版本号为2,进行failover的sentinel会将新配置广播给其他的sentinel,由于其他sentinel维护的版本号为1,发现新配置的版本号为2时,版本号变大了,说明配置更新了,于是就会采用最新的版本号为2的配置。

这意味着sentinel集群保证了第二种活跃性:一个能够互相通信的sentinel集群最终会采用版本号最高且相同的配置。

 

SDOWN和ODOWN的更多细节

sentinel对于不可用有两种不同的看法,一个叫主观不可用(SDOWN),另外一个叫客观不可用(ODOWN)。SDOWN是sentinel自己主观上检测到的关于master的状态,ODOWN需要一定数量的sentinel达成一致意见才能认为一个master客观上已经宕掉,各个sentinel之间通过命令SENTINEL is_master_down_by_addr来获得其它sentinel对master的检测结果。

从sentinel的角度来看,如果发送了PING心跳后,在一定时间内没有收到合法的回复,就达到了SDOWN的条件。这个时间在配置中通过is-master-down-after-milliseconds参数配置。

当sentinel发送PING后,以下回复之一都被认为是合法的:

PING replied with +PONG.
PING replied with -LOADING error.
PING replied with -MASTERDOWN error.

其它任何回复(或者根本没有回复)都是不合法的。

从SDOWN切换到ODOWN不需要任何一致性算法,只需要一个gossip协议:如果一个sentinel收到了足够多的sentinel发来消息告诉它某个master已经down掉了,SDOWN状态就会变成ODOWN状态。如果之后master可用了,这个状态就会相应地被清理掉。

正如之前已经解释过了,真正进行failover需要一个授权的过程,但是所有的failover都开始于一个ODOWN状态。

ODOWN状态只适用于master,对于不是master的redis节点sentinel之间不需要任何协商,slaves和sentinel不会有ODOWN状态。

 

网络隔离时的一致性

redis sentinel集群的配置的一致性模型为最终一致性,集群中每个sentinel最终都会采用最高版本的配置。然而,在实际的应用环境中,有三个不同的角色会与sentinel打交道:

  • Redis实例.

  • Sentinel实例.

  • 客户端.

为了考察整个系统的行为我们必须同时考虑到这三个角色。

下面有个简单的例子,有三个主机,每个主机分别运行一个redis和一个sentinel:

             +-------------+
             | Sentinel 1  | <--- Client A
             | Redis 1 (M) |
             +-------------+
                     |
                     |
 +-------------+     |                     +------------+
 | Sentinel 2  |-----+-- / partition / ----| Sentinel 3 | <--- Client B
 | Redis 2 (S) |                           | Redis 3 (M)|
 +-------------+                           +------------+

在这个系统中,初始状态下redis3是master, redis1和redis2是slave。之后redis3所在的主机网络不可用了,sentinel1和sentinel2启动了failover并把redis1选举为master。

Sentinel集群的特性保证了sentinel1和sentinel2得到了关于master的最新配置。但是sentinel3依然持着的是就的配置,因为它与外界隔离了。

当网络恢复以后,我们知道sentinel3将会更新它的配置。但是,如果客户端所连接的master被网络隔离,会发生什么呢?

客户端将依然可以向redis3写数据,但是当网络恢复后,redis3就会变成redis的一个slave,那么,在网络隔离期间,客户端向redis3写的数据将会丢失。

也许你不会希望这个场景发生:

  • 如果你把redis当做缓存来使用,那么你也许能容忍这部分数据的丢失。

  • 但如果你把redis当做一个存储系统来使用,你也许就无法容忍这部分数据的丢失了。

因为redis采用的是异步复制,在这样的场景下,没有办法避免数据的丢失。然而,你可以通过以下配置来配置redis3和redis1,使得数据不会丢失。

min-slaves-to-write 1
min-slaves-max-lag 10

通过上面的配置,当一个redis是master时,如果它不能向至少一个slave写数据(上面的min-slaves-to-write指定了slave的数量),它将会拒绝接受客户端的写请求。由于复制是异步的,master无法向slave写数据意味着slave要么断开连接了,要么不在指定时间内向master发送同步数据的请求了(上面的min-slaves-max-lag指定了这个时间)。

 

Slave选举与优先级

当一个sentinel准备好了要进行failover,并且收到了其他sentinel的授权,那么就需要选举出一个合适的slave来做为新的master。

slave的选举主要会评估slave的以下几个方面:

  • 与master断开连接的次数

  • Slave的优先级

  • 数据复制的下标(用来评估slave当前拥有多少master的数据)

  • 进程ID

如果一个slave与master失去联系超过10次,并且每次都超过了配置的最大失联时间(down-after-milliseconds option),并且,如果sentinel在进行failover时发现slave失联,那么这个slave就会被sentinel认为不适合用来做新master的。

更严格的定义是,如果一个slave持续断开连接的时间超过

(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state

就会被认为失去选举资格。符合上述条件的slave才会被列入master候选人列表,并根据以下顺序来进行排序:

  1. sentinel首先会根据slaves的优先级来进行排序,优先级越小排名越靠前(?)。

  2. 如果优先级相同,则查看复制的下标,哪个从master接收的复制数据多,哪个就靠前。

  3. 如果优先级和下标都相同,就选择进程ID较小的那个。

一个redis无论是master还是slave,都必须在配置中指定一个slave优先级。要注意到master也是有可能通过failover变成slave的。

如果一个redis的slave优先级配置为0,那么它将永远不会被选为master。但是它依然会从master哪里复制数据。

 

 

hash

Key-Value键值对集合,适合用来存储简单对象;

例:

127.0.0.1:6379> hmset user name caojiantao age 18OK127.0.0.1:6379> hget user age"18"

九、Redis集群

    Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。集群的每个节点负责一部分hash槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。使用哈希槽的好处就在于可以方便的添加或移除节点。当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。

    16384个哈希槽初始时均匀分配的,一般至少会有3个主节点,和3个从节点,比如:

    第一组主从节点:(主)127.0.0.1:1000, (从)127.0.0.1:1010

    第二组主从节点:(主)127.0.0.1:2000, (从)127.0.0.1:2010

    第三组主从节点:(主)127.0.0.1:3000, (从)127.0.0.1:3010

    哈希槽只会分配到主节点,127.0.0.1:1000(0 ~ 5461),127.0.0.1:2000(5462 ~ 10923),127.0.0.1:3000(10924 ~ 16383)。

    放入数据时,会根据key算出相应的哈希值,在找哈希值对应的服务器,将数据加到该服务器中。如:存入键值 testKey:testValue倒redis集群,假设根据testKey算出的哈希值为5000,该记录会加入到端口为1000的实例中,如果加入testKey2:testValue2,计算出testKey2的哈希值为11000,该记录会加入到端口为3000的实例中。

   如果主节点宕机,从节点会接替主节点分配的哈希槽,并将原来的从节点切换为主节点,前提是打开了redis-sentinel。如果宕机的主节点重新启动,会被当作从节点。如:127.0.0.1:1000节点宕机,该节点的从节点127.0.0.1:1010会被提升为主节点。此后127.0.0.1:1000重新启动,会做为127.0.0.1:1010的从节点。

  如果有新节点添加,可以手动为新节点分配XX个哈希槽,XX个哈希槽从原有的节点上来平均拆分,例如:新加了端口为4000的集群节点,该节点分配900个哈希槽,会在1000,2000,3000端口的实例上各取300个哈希槽分给4000端口实例。

 删除节点,需要先将要删除节点的哈希槽,分配给其他节点,在进行删除。

redis的集群管理,通过一个ruby脚本来进行的,该脚本在redis安装文件夹下面,名称为redis-trib.rb,需要安装ruby环境,ruby版本必须是2.0以上的

 

安装ruby

下载ruby的压缩包,使用tar命令解压,然后进入解压后的文件,分别执行下面的命令,不出错就说明安装完毕。参考:

./configure

make

make install

 

安装rails

安装rails需要将国外镜像切换到国内的镜像,参考:

安装完之后,查看ruby和rails的版本

ruby -v

rails -v

还要执行命令

gem install redis

应该是安装ruby 和 redis的关联吧。

安装之后 就可以用来搭建集群了。

搭建集群

首先在本机启动9个redis实例,3个主节点,每个主节点有2个从节点,规划如下:

主节点1:127.0.0.1:7001,对应从节点 127.0.0.1:7002,127.0.0.1:7003

主节点2:127.0.0.1:8001,对应从节点 127.0.0.1:8002,127.0.0.1:8003

主节点3:127.0.0.1:9001,对应从节点 127.0.0.1:9002,127.0.0.1:9003

每个端口的配置文件中都开启集群:

cluster-enabled yes

cluster-config-file nodes.conf #nodes.conf文件名改成对应的端口,如7001_nodes.conf,7002_nodes.conf

cluster-node-timeout 5000

注意:这里不需要配置slaveof,**这里不需要配置slaveof,这里不需要配置slaveof 重要的事说三遍。配置了slaveof,在配置集群时会报错。**

随后启动9个redis实例。然后用redis-trib.rb脚本去配置集群信息。

./redis-trib.rb create --replicas 2 172.18.8.35:7001 172.18.8.35:8001 172.18.8.35:9001 172.18.8.35:7002 172.18.8.35:7003  172.18.8.35:8002 172.18.8.35:8003  172.18.8.35:9002 172.18.8.35:9003

--replicas 后面有个2,这个2表示一个主节点有两个从节点,后面的<ip>:<port>列表,先从主节点开始,主节点完了,会根据--replicas X的这个X值,配置从节点。

list

简单的字符串列表,双向链表的数据结构;

例:

127.0.0.1:6379> lpush months 1 1127.0.0.1:6379> lpush months 2 2127.0.0.1:6379> rpush months 3 3127.0.0.1:6379> lrange months 0 101) "2"2) "1"3) "3"127.0.0.1:6379> lpop months"2"127.0.0.1:6379> rpop months"3"

set

string类型的无序集合,hash结构,操作复杂度为O;

例:

127.0.0.1:6379> sadd team zhangsan lisi 2127.0.0.1:6379> smembers team1) "zhangsan"2) "lisi"127.0.0.1:6379> sadd team lisi 0

zset

同set,不过每个子元素会关联一个double类型的分数score,zset根据score排序;

例:

127.0.0.1:6379> zadd days 1 one 1127.0.0.1:6379> zadd days 0 zero 1127.0.0.1:6379> zadd days 2 two 1127.0.0.1:6379> zrangebyscore day 0 10(empty list or set)127.0.0.1:6379> zrangebyscore days 0 101) "zero"2) "one"3) "two"

事务

multi...exec

一次执行多条命令,有以下特点:

  1. 发送exec指令前,所有的操作都会放入队列缓存;
  2. 执行事务时,任何命令执行失败,其他命令正常被执行,已操作的命令不会回滚;
  3. 执行过程中,其他客户端的命令不会插入到该事务中;
127.0.0.1:6379> multiOK127.0.0.1:6379> set a 1QUEUED127.0.0.1:6379> set b 2QUEUED127.0.0.1:6379> get aQUEUED127.0.0.1:6379> del aQUEUED127.0.0.1:6379> exec1) OK2) OK3) "1"4)  1

发布订阅

Redis支持一个发布订阅的消息通信模式,发送者发送消息,订阅者接受消息,可订阅任意数量的频道;

图片 5

三个客户端都订阅了channel这个频道;

图片 6

一旦有消息发布pub到channel中,之前订阅该channel的三个客户端都会收到这个message;

例:

客户端订阅talk频道;

127.0.0.1:6379> subscribe talkReading messages... (press Ctrl-C to quit)1) "subscribe"2) "talk"3)  1

另开客户端发布消息值talk频道;

127.0.0.1:6379> publish talk "hello world" 1

此时客户端收到消息;

1) "message"2) "talk"3) "hello world"

脚本

Redis使用Lua解释器执行,执行命令为eval;

eval script numkeys key [key ...] arg [arg ...]
  • script,lua脚本内容
  • numkeys,key的个数
  • key,Redis中key属性
  • arg,自定义参数

注:key和arg在lua脚本占位符分别为KEYS[]和ARGV[],必须大写,数组下标从1开始。

例:获取脚本参数

127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 "key1" "key2" "argv1"1) "key1"2) "key2"3) "argv1"

通常会将脚本存储到一个lua文件中,假如test.lua内容如下:

return {KEYS[1],KEYS[2],ARGV[1]}

执行这个lua脚本命令;

redis-cli.exe --eval test.lua "key1" "key2" , "argv1"1) "key1"2) "key2"3) "argv1"

注意参数格式与之前有点出入,执行lua脚本文件不需要numkeys,key和arg参数用逗号相隔;

客户端

阻塞I/O模型,调用方法都是同步的,不支持异步调用,并且Jedis客户端非线程安全,需要结合连接池使用;

maven依赖:

<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version></dependency>

demo示例:

String host = "127.0.0.1";int port = 6379;// 连接本地的 Redis 服务Jedis jedis = new Jedis(host, port);// 查看服务是否运行System.out.println("服务正在运行: " + jedis.ping;// 基本操作String key = "welcome";jedis.set(key, "hello world");System.out.println(jedis.get;// 连接池配置GenericObjectPoolConfig config = new GenericObjectPoolConfig();config.setMaxTotal;// 连接池操作JedisPool pool = new JedisPool(config, host, port);Jedis a = pool.getResource();// a.close();System.out.println;Jedis b = pool.getResource();System.out.println;

基于Netty框架,异步调用,线程安全;

maven依赖:

<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.0.3.RELEASE</version></dependency>

demo示例:

// 1. 构造uriRedisURI uri = RedisURI.builder() .withHost("127.0.0.1") .withPort .build();// 2. 创建clientRedisClient client = RedisClient.create;// 3. 连接redisStatefulRedisConnection<String, String> connect = client.connect();// 4. 获取操作命令RedisCommands<String, String> commands = connect.sync();String key = "welcome";System.out.println(commands.get;connect.close();

springboot集成

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starters</artifactId> <version>2.0.5.RELEASE</version></dependency>

注:springboot 2.x 之后使用了Lettuce替换掉了底层Jedis的依赖。

在application.yml添加下面属性

spring: redis: host: 127.0.0.1 port: 6379 password: 123456 # 连接池配置 lettuce: pool: max-idle: 8

springboot默认注入了RedisTemplate和StringRedisTemplate两个实例用来操作Redis,前者key和value都是采用JDK序列化,后者只能操作String数据类型;

可直接注入使用;

@Autowired@Qualifier("redisTemplate")private RedisTemplate redisTemplate;@Autowired@Qualifier("stringRedisTemplate")private StringRedisTemplate stringRedisTemplate;public void test() { String key = "welcome"; Object o = redisTemplate.opsForValue().get; // 此处为null,由于key序列化方式为JDK System.out.println; String s = stringRedisTemplate.opsForValue().get; System.out.println;}

注:Redis默认注入原理可参考RedisAutoConfiguration类。

默认注入的两种RedisTemplate显然不适用所有的业务场景,自定义Template一般只需下列两个步骤;

  1. 自定义RedisSerializer;
  2. 注入自定义Template;

参考第三方序列化框架protostuff,序列化后体积较小,速度快;

import io.protostuff.*;import io.protostuff.runtime.RuntimeSchema;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.data.redis.serializer.SerializationException;/** * @author caojiantao */public class ProtoStuffSerializer<T> implements RedisSerializer<T> { private Class<T> clazz; public ProtoStuffSerializer(Class<T> clazz) { this.clazz = clazz; } @Override public byte[] serialize throws SerializationException { if (t == null) { return new byte[0]; } Schema<T> schema = RuntimeSchema.getSchema; return ProtostuffIOUtil.toByteArray(t, schema, LinkedBuffer.allocate; } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null) { return null; } Schema<T> schema = RuntimeSchema.getSchema; T t = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes, t, schema); return t; }}

然后手动注入到spring容器中;

import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;@Configurationpublic class RedisConfig { @Bean("customTemplate") public RedisTemplate<String, Student> customTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Student> template = new RedisTemplate<>(); // 注入redis连接工厂实例 template.setConnectionFactory; ProtoStuffSerializer<Student> serializer = new ProtoStuffSerializer<>(Student.class); // 设置key、value序列化方式 template.setKeySerializer(RedisSerializer.string; template.setValueSerializer(serializer); template.afterPropertiesSet(); return template; }}

将Redis操作与Spring Cache相结合,能够更加简便开发,主要问题是需要构建RedisCacheManager这个实例,这里序列化的方式仍可采用上述protostuff方案;

参考配置代码;

import lombok.Setter;import lombok.extern.slf4j.Slf4j;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.context.annotation.Bean;import org.springframework.data.redis.cache.RedisCacheConfiguration;import org.springframework.data.redis.cache.RedisCacheManager;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.RedisSerializationContext;import org.springframework.data.redis.serializer.RedisSerializer;import org.springframework.stereotype.Component;import org.springframework.util.CollectionUtils;import java.util.HashMap;import java.util.List;import java.util.Map;@Setter@Slf4j@Component@ConfigurationPropertiespublic class RedisConfig { private List<SerializerConfig> serializerConfig; @Bean @SuppressWarnings("unchecked") RedisCacheManager cacheManager(RedisConnectionFactory factory) { Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(); if (!CollectionUtils.isEmpty(serializerConfig)) { RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig(); ProtoStuffSerializer serializer; for (SerializerConfig config : serializerConfig) { try { Class<?> clazz = Class.forName(config.getClassType; serializer = new ProtoStuffSerializer; configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); configurationMap.put(config.getCacheName(), configuration); } catch (ClassNotFoundException e) { log.error("添加redis缓存序列化映射关系失败:{}", e); } } } return RedisCacheManager.builder .withInitialCacheConfigurations(configurationMap) .build(); }}

这里的SerializerConfig便是spring cache中cacheName与序列化对象类型的映射;

import lombok.Data;@Datapublic class SerializerConfig { private String cacheName; private String classType;}

例如需要缓存Student对象;

import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;import java.io.Serializable;@Data@NoArgsConstructor@AllArgsConstructorpublic class Student implements Serializable { private String no; private String name; private Integer age;}

在application.yml中添加如下配置;

redis: serializerConfig: - cacheName: students classType: com.iflytek.demo.Student

那么springboot初始化便会创建好students这个redis类型的缓存;

编写测试controller测试;

import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.Cacheable;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class TestController { @RequestMapping("/student") @Cacheable(cacheNames = "students") public Student student(String no) { System.out.println("请求查询数据:" + no); return new Student(no, "hahahah", 18); } @RequestMapping("/delete") @CacheEvict(cacheNames = "students") public void flushAll(String no) { }}

第一次请求查询Student时;

图片 7

重复该请求发现控制台并不会打印日志,查看Redis该记录已缓存;

请求no对应的delete请求,查看Redis该记录已清除,控制台打印日志;

更改请求no参数,控制台继续打印日志,查看Redis该记录已缓存;

至此Redis结合Spring Cache完成。

主从同步

将所有数据存储到单个Redis主要存在两个问题;

  1. 数据备份;
  2. 数据量过大降低性能;

主从模式很好的解决了以上问题。一个Redis实例作为主机,其他的作为从机,主机主要用于数据的写入,从机则主要提供数据的读取。从机在启动时会同步全量主机数据,主机也会在写入数据的时候同步到所有的从机。

图片 8

有两种方式可以设置主从关系;

  1. 在启动配置文件指定slaveof参数;
  2. 启动Redis实例后执行slaveof ip port命令;

简单测试,复制redis.conf文件,主要配置如下:

master:

port 6379logfile "6379.log"dbfilename "dump-6379.rdb"

slave_1:

port 6380logfile "6380.log"dbfilename "dump-6380.rdb"slaveof 127.0.0.1 6379

slave_2:

port 6381logfile "6381.log"dbfilename "dump-6381.rdb"slaveof 127.0.0.1 6379

slave_3:

port 6382logfile "6382.log"dbfilename "dump-6382.rdb"slaveof 127.0.0.1 6379

依次启动上述四个Redis实例;

./redis-server 6379.conf./redis-server 6380.conf./redis-server 6381.conf./redis-server 6382.conf

连接6379主机master,查看replication信息;

127.0.0.1:6379> info replication# Replicationrole:masterconnected_slaves:3slave0:ip=127.0.0.1,port=6380,state=online,offset=322,lag=0slave1:ip=127.0.0.1,port=6381,state=online,offset=322,lag=1slave2:ip=127.0.0.1,port=6382,state=online,offset=322,lag=0master_replid:417b1e3811a2d9b3465876d65c67a36949de8f9fmaster_replid2:0000000000000000000000000000000000000000master_repl_offset:322second_repl_offset:-1repl_backlog_active:1repl_backlog_size:1048576repl_backlog_first_byte_offset:1repl_backlog_histlen:322

说明了当前Redis实例为主机,有三个从机;

在当前主机写入数据;

127.0.0.1:6379> set msg "hello world"OK

在其他任意从机执行获取操作;

127.0.0.1:6382> get msg"hello world"

已经成功设置主从同步。

哨兵模式

主从模式存在一定的弊端,master一旦发生宕机,主从同步过程将会中断。

Sentinel作为一个单独的服务,用来监控master主机,间接监控所有slave从机,如下图所示;

图片 9

sentinel主要有以下三个特点;

  1. 监控Redis实例是否正常运行;
  2. 节点发生故障,能够通知另外;

当master发生故障,sentinel会采用在当前sentinel集群中投票方式,从当前所有slave中,推举一个作为新的master,从而保证了Redis的高可用性。

集群模式

在哨兵模式下,每个Redis实例都是存储的全量数据。为了最大化利用内存空间,采用集群模式,即分布式存储,每台Redis存储不同的内容。

数据存储在16384个slot中,所有的数据都是根据一定算法映射到某个slot中;

图片 10

集群模式至少三个Redis节点,否则会提示:

./redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001*** ERROR: Invalid configuration for cluster creation.*** Redis Cluster requires at least 3 master nodes.*** This is not possible with 2 nodes and 0 replicas per node.*** At least 3 nodes are required.

在src目录创建confs文件夹,复制redis.conf文件6分,三主三从;

主要配置如下;

port 7000cluster-enabled yescluster-node-timeout 15000cluster-config-file "nodes-7000.conf"pidfile /var/run/redis-7000.pidlogfile "cluster-7000.log"dbfilename dump-cluster-7000.rdbappendfilename "appendonly-cluster-7000.aof"

顺序启动相关Redis示例,最后创建集群;

./redis-server confs/7000.conf./redis-server confs/7001.conf./redis-server confs/7002.conf./redis-server confs/7003.conf./redis-server confs/7004.conf./redis-server confs/7005.conf./redis-cli --cluster create 127.0.0.1:7000 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 --cluster-replicas 1

集群部署成功后,连接7000这个节点,注意连接命令:

./redis-cli -c -p 7000127.0.0.1:7000> get name-> Redirected to slot [5798] located at 127.0.0.1:7001127.0.0.1:7001>

分布式锁

场景:定时任务集群部署,Job需要加锁单次执行;

方案:基于Redis实现分布式锁,以Job唯一标识为key,设置expiration,在Job执行前获取锁判定;

优点:实现较为简单,过期策略防止死锁,效率较高;

基于springboot 2.x项目,参考代码如下;

加锁:

/** * 尝试加锁 * * @param lockKey 加锁的KEY * @param requestId 加锁客户端唯一ID标识 * @param expireTime 过期时间 * @param timeUnit 时间单位 * @return 是否加锁成功 */public Boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit timeUnit) { RedisConnection connection = connectionFactory.getConnection(); Boolean result = connection.set(lockKey.getBytes(StandardCharsets.UTF_8), requestId.getBytes(StandardCharsets.UTF_8), Expiration.from(expireTime, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT); connection.close(); return result;}

requestId通常用作标识加锁请求的唯一性,只有对应的加锁请求,才能成功解锁。

解锁:

/** * 释放锁 * * @param lockKey 加锁的KEY * @param requestId 加锁客户端唯一ID标识 * @return 是否释放成功 */public boolean releaseLock(String lockKey, String requestId) { // Lua代码,一次性执行保证原子性,避免并发问题 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisConnection connection = connectionFactory.getConnection(); byte[][] keysAndArgs = new byte[2][]; keysAndArgs[0] = lockKey.getBytes(StandardCharsets.UTF_8); keysAndArgs[1] = requestId.getBytes(StandardCharsets.UTF_8); Long result = connection.scriptingCommands().eval(script.getBytes(StandardCharsets.UTF_8), ReturnType.INTEGER, 1, keysAndArgs); connection.close(); return result != null && result > 0;}

注意解锁姿势,保证原子性使用Lua脚本避免并发问题。

本文由澳门太阳娱乐手机登录发布于 资质荣誉,转载请注明出处:Redis入门到实战

关键词: