一、概述

定义: Redis(Remote Dictionary Server ),即远程字典服务,它是一个开源的,使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存可持久化的日志型、Key-Value数据库,并提供多种语言的API。Redis 默认端口为 6379,是一个NoSQL数据库。

redis 获取hmap redis 获取serversql数据_数据

    Redis是一个key-value存储系统,和Memcached类似,只是它支持存储的value类型相对更多,包括 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), zset有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。这些数据类型都支持push/pop(汇编中的堆栈操作指令,操作数与栈的压入和弹出)、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。Redis也被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。

    Redis还支持各种不同方式的排序。与memcached一样,为了提高数据效率,将数据缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

    Redis 是一个高性能的key-value数据库,其补偿了memcached对key/value类数据存储的短板,对关系数据库起到很好的补充作用,且其有适应Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等多种平台环境的客户端,便于用户操作。
集群:Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步。

附: 参考书籍《redis开发与运维》,地址:https://pan.baidu.com/s/12rlHhOKP7_72GE8a74lN1g;eep2

二、架构及工作原理图解

2.1 Redis主从复制原理:

redis 获取hmap redis 获取serversql数据_redis运维_02


redis 获取hmap redis 获取serversql数据_数据_03


redis 获取hmap redis 获取serversql数据_redis 获取hmap_04


redis 获取hmap redis 获取serversql数据_redis运维_05


1、当启动一个Slave进程后,它会向Master发送一个SYNC Command,请求同步连接。当master接到命令后,会执行BGSAVE命令生成RDB文件(即快照文件),如果主节点在生成RDB的过程当中,客户端发来了写入指令,这个时候主节点会把指令全部写入缓冲区,等RDB生成完了,会把RDB文件发送给从节点,最后再把缓冲区的指令发送给从节点。无论是第一次连接还是重新连接,Master都会启动一个后台进程,将数据快照保存到数据文件中,同时Master会记录所有修改数据的命令并追加缓存在数据文件中;

2、后台进程完成缓存操作后,Master就发送数据文件(dump.rdb)给Slave,Slave端将数据文件保存到硬盘上,然后将其在加载到内存中,接着Master将缓存下来的所有修改数据的操作命令,发送给Slave端。

3、若Slave出现故障导致宕机,恢复正常后会自动重新连接,Master收到Slave的连接后,将其完整的数据文件发送给Slave,如果Mater同时收到多个Slave发来的同步请求,Master只会后台启动一个进程保存数据文件,然后将**其发送给所有的Slave,**确保Slave正常。

4、实际工作中,我们要存储海量的数据,那么BGSAVE指令生成的RDB文件会非常巨大,这个文件传送给从节点也会非常慢,如果缓冲区命令很多的话,从节点同步数据时也会执行很久,所以,考虑解决单点问题和海量存储问题,就要做Redis集群。可以考虑:把一个主从结构变成多个,把存储的key分摊到各个主从结构中来分担压力。

redis 获取hmap redis 获取serversql数据_Redis_06

Redis复制工作原理:

  1. 如果设置了一个Slave,无论是第一次连接还是重连到Master,它都会发出一个SYNC命令
    \
  2. 当Master收到SYNC命令之后,会做两件事:

    a) Master执行BGSAVE,即在后台保存数据到磁盘(rdb快照文件);

    b) Master同时将新收到的写入和修改数据集的命令存入缓冲区(非查询类);
    \
  3. 当Master在后台把数据保存到快照文件完成之后,Master会把这个快照文件传送给Slave,而Slave则把内存清空后,加载该文件到内存中;
    \
  4. 而Master也会把此前收集到缓冲区中的命令,通过Reids命令协议形式转发给Slave,Slave执行这些命令,实现和Master的同步;
    \
  5. Master/Slave此后会不断通过异步方式进行命令的同步,达到最终数据的同步一致;
    \
  6. 需要注意的是Master和Slave之间一旦发生重连都会引发全量同步操作。但在2.8之后版本,也可能是部分同步操作。

:部分复制工作从 2.8版本开始,当Master和Slave之间的连接断开之后,他们之间可以采用持续复制处理方式代替采用全量同步。 Master端为复制流维护一个内存缓冲区(in-memory backlog),记录最近发送的复制流命令;同时,Master和Slave之间都维护一个复制偏移量(replication offset)和当前Master服务器ID(Master run id)。当网络断开,Slave尝试重连时: a. 如果MasterID相同(即仍是断网前的Master服务器),并且从断开时到当前时刻的历史命令依然在Master的内存缓冲区中存在,则Master会将缺失的这段时间的所有命令发送给Slave执行,然后复制工作就可以继续执行了; 否则,依然需要全量复制操作;

2.2 Redis主从复制架构:

redis 获取hmap redis 获取serversql数据_redis运维_07


如上图所示,Redis 在 Master-Slave(主从)模式下,可像图中树状结构那样,级联多个salve,Redis Server 可以设置为另一个 Redis Server 的主机(从机),从机定期从主机拿数据,而master下的一个从机同样可以设置为另一个 Redis Server 的主机,即三层关系模型,从而形成 Redis Server 集群,同一个Master可以拥有多个Slaves,Master下的Slave还可以继续接受同一架构中其它slave的链接与同步请求,实现数据的级联复制,即Master->Slave->Slave模式;无论是主机还是从机都是 Redis Server。主机Master可负责读写服务,从机**Slave只负责读,**其中多个slave专门提供只读查询与数据的冗余,Master端专门提供写操作;

Redis 复制在 master 这一侧是非阻塞的,也就是说在和 slave 同步数据的时候,master 仍然可以执行客户端的操作命令而无需等待相关命令结束退出,不受正在复制/同步命令的影响,Master会继续处理一个或多个slave的读写请求。Slave端同步数据也可以修改为非阻塞的方式,当slave在执行新的同步时,它仍可以用旧的数据信息来提供查询;否则,当slave与master失去联系时,slave会返回一个错误给客户端;

另外,可通过配置禁用Master数据持久化机制,将其数据持久化操作交给Slaves完成,避免在Master中需开销独立的进程来完成此操作,降低master的负载,实现其的轻量级。

2.3 Redis 分区

分区是分割数据到多个Redis实例的处理过程,因此每个实例只保存key的一个子集。

分区类型

Redis 有两种类型分区。 假设有4个Redis实例 R0,R1,R2,R3,和类似user:1,user:2这样的表示用户的多个key,对既定的key有多种不同方式来选择这个key存放在哪个实例中。也就是说,有不同的系统来映射某个key到某个Redis服务。

范围分区

最简单的分区方式是按范围分区,就是映射一定范围的对象到特定的Redis实例。

比如,ID从0到10000的用户会保存到实例R0,ID从10001到 20000的用户会保存到R1,以此类推。

这种方式是可行的,并且在实际中使用,不足就是要有一个区间范围到实例的映射表。这个表要被管理,同时还需要各 种对象的映射表,通常对Redis来说并非是好的方法。

哈希分区:

另外一种分区方法是hash分区。这对任何key都适用,也无需是object_name:这种形式,像下面描述的一样简单:

用一个hash函数将key转换为一个数字,比如使用crc32 hash函数。对key foobar执行crc32(foobar)会输出类似93024922的整数。
对这个整数取模,将其转化为0-3之间的数字,就可以将这个整数映射到4个Redis实例中的一个了。93024922 % 4 = 2,就是说key foobar应该被存到R2实例中。注意:取模操作是取除的余数,通常在多种编程语言中用%操作符实现。

2.4 Redis 持久化

Redis 作为内存数据库,数据运行期间都是放在内存里的,但是内存中数据容易在进程重启后丢失,因此需要将内存中的数据,包括数据库状态保存到磁盘,即内存落盘来实现持久化,Redis是通过将操作写入日志文件来实现这一功能的,从而来保证数据不会因为宕机而丢失。Redis 为我们提供了2种持久化方案:一种是基于快照的RDB(Redis DataBase),另外一种是基于 AOF 日志。实际Redis服务提供四种持久化存储方案:RDB、AOF、虚拟内存(VM)和 DISKSTORE。虚拟内存(VM)方式,从Redis Version 2.4开始就被官方明确表示不再建议使用,Version 3.2版本中更找不到关于虚拟内存(VM)的任何配置范例;DISKSTORE方式,是从Redis Version 2.8版本开始提出的一个存储设想,到目前为止Redis官方也没有在任何stable版本中明确建议使用这用方式。在Version 3.2版本中同样找不到对于这种存储方式的明确支持。

首先我们来回顾下数据从 Redis 中到磁盘的这一过程:

  • 客户端向数据库发起 write 指令(数据在客户端的内存中);
  • 数据库收到 write 指令和对应的写数据(数据在服务端内存中);
  • 数据库调用将数据写入磁盘的系统调用函数(数据在系统内核缓冲区);
  • 操作系统将写入缓冲区中的数据写到磁盘控制器中(数据在磁盘缓冲区中);
  • 磁盘控制器将磁盘缓冲区中的数据写入磁盘的物理介质中(数据真正写入磁盘中)。

1)RDB (Redis Database快照/内存快照) 通过快照的形式将数据保存到磁盘中。所谓快照,可以理解为在某一时间点将数据集拍照并保存下来。Redis 通过这种方式可以在指定的时间间隔或者执行特定命令(save/bgsave)时将当前系统中的数据保存备份,以二进制的形式写入磁盘中,默认文件名为dump.rdb。RDB 的触发有3种机制,执行save命令;执行bgsave命令;在redis.config中配置自动化,rdb文件保存的目录是有dir配置项决定;文件名则是由dbfilename决定;触发机制则有save决定;rdbcompression配置项就决定是否压缩rdb文件,默认为yes。Redis作为单线程程序,要同时负责多个客户端套接字的并发读写操作和内存结构的逻辑读写。前2种方式多用于调试时客户端执行,而在生产环境中我们更多地需要是自动化的触发机制,即在redus.config中对持久化进行配置:

save 900 1 //是指在 900 秒内,如果有一个或一个以上的修改操作,那么就自动进行一次自动化备份;格式:save 秒钟 写操作次数;默认是1分钟内改了1万次,或5分钟内改了10次,或15分钟内改了1次。

save 300 10 //意味着在 300 秒内如果有十次或以上的修改操作,那么就进行数据备份

save “” //禁止掉数据持久化

stop-writes-on-bgsave-error yes //如果持久化出错,主进程是否停止写入,yes停止写

rdbcompression yes //开启压缩,即对存储到磁盘中的快照执行压缩后再存储,edis会采用LZF算法进行压缩,当然这会消耗一定的CPU

rdbchecksum yes //开启完整性检查,存储快照后,让redis使用CRC64算法来进行数据校验,可能会增加大约10%的性能消耗

dbfilename dump.rdb //制定rdb文件名称

dir /home/redis/data/ //文件保存路径
\

RDB生成过程:(*.rdb文件)

RDB中的核心思路是Copy-on-Write(COW),来保证在进行快照操作的这段时间,需要压缩写入磁盘上的数据在内存中不会发生变化。在正常的快照操作中,一方面Redis主进程会fork一个新的快照进程专门来做这个事情,这样保证了Redis服务不会停止对客户端包括写请求在内的任何响应。另一方面这段时间发生的数据变化会以副本的方式存放在另一个新的内存区域,待快照操作结束后才会同步到原来的内存区域。在没有将数据全部写入到磁盘前,这次快照操作都不算成功。如果出现了服务崩溃的情况,将以上一次完整的RDB快照文件作为恢复内存数据的参考。Redis会自动读取rdb日志恢复数据;在快照操作过程中不能影响上一次的备份数据。Redis服务会在磁盘上创建一个临时文件进行数据操作,待操作成功后才会用这个临时文件替换掉上一次的备份。鉴于RDB文件生成机制的资源消耗,4.0版本中引入的RDB和AOF的混合方式。

redis 获取hmap redis 获取serversql数据_Redis_08


父进程进程会fork()产生一个和自己完全相同的子进程,这里借用了Linux内核的“写时复制技术COW”,父子进程会共享(此共享并不是我们通常所说的共享映射和私有映射,而是通过将页映射到每个进程页表形成共享)所有的私有可写的物理页,并将父子进程对应的页表项修改为只读,当有一方试图写共享的物理页,由于页表项属性是只读的会发生COW缺页异常,缺页异常处理程序会为写操作的一方分配新的物理页,并将原来共享的物理页内容拷贝到新页,然后建立新页的页表映射关系,这样写操作的进程就可以继续执行,不会影响另一方,父子进程对共享的私有页面访问就互相不干扰了,当共享的页面最终只有一个拥有者(即使其他映射页面到自己页表的进程都发生写时复制分配了新的物理页),这个时候如果拥有者进程想要写这个页就会重新使用这个页而不用分配新页。这样,通过 fork 创建的子进程能够获得和父进程完全相同的内存空间,而父进程对内存的修改对于子进程是不可见的,两者不会相互影响;通过 fork 创建子进程时不会立刻触发大量内存的拷贝,内存在被修改时会以页为单位进行拷贝,这也就避免了大量拷贝内存而带来的性能问题;

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。

bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。

redis 获取hmap redis 获取serversql数据_Redis_09


注意:

RDB文件体积小,生成及加载快,但是rdb持久化方式不能做到实时持久化,异常情况下容易导致数据丢失。另外,不同版本的rdb文件可能存在不兼容的情况。RDB文件非常适合做容灾备份,比如每天凌晨生成RDB文件。另外,如果redis里存放的数据不是太重要,比如使用redis做缓存,丢失部分数据没有影响的话,使用RDB通常是更佳的方式。redis数据存放的分区快要写满时,在不停止redis下将数据写到另一个分区中。我们可以使用config set dir ‘新分区目录’ 修改rdb文件存放的目录。然后执行bgsave生成新的RDB文件到新的目录中。

RDB生成过程函数调用:

redis 获取hmap redis 获取serversql数据_数据_10

lastsave   //获取最后一次成功执行快照的时间
flushall    //会产生dump.rdb文件,但里面是空的

RDB优点:

  • RDB文件是某个时间节点的快照,默认使用LZF算法进行压缩,压缩后的文件体积远远小于内存大小,适用于备份、全量复制等场景;
  • Redis加载RDB文件恢复数据要远远快于AOF方式;

RDB缺点:

  • RDB方式实时性不够,无法做到秒级的持久化;
  • 每次调用bgsave都需要fork子进程,fork子进程属于重量级操作,频繁执行成本较高;
  • RDB文件是二进制的,没有可读性,AOF文件在了解其结构的情况下可以手动修改或者补全;
    版本兼容RDB文件问题;
  • 针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决

2)AOF:(Append Only-file)

RDB 持久化是全量备份,比较耗时,所以Redis就提供了一种更为高效地AOF持久化方案,它的工作原理可简单理解如下:Redis的AOF日志采用写后日志,即先写内存,后写日志,因为Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。Redis先执行命令,把数据写入内存,然后才记录日志。AOF日志记录Redis的每个写命令,步骤分为:命令追加(append)、文件写入(write)和文件同步(sync),这些命令是以文本形式保存。AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录。当服务器异常从新启动时,Redis就会利用 AOF 日志中记录的这些操作从新构建原始数据集。

redis 获取hmap redis 获取serversql数据_redist配置详解_11


Redis会在收到客户端修改指令后,进行参数修改、逻辑处理,如果没有问题,就立即将该指令文本存储到 AOF 日志中,也就是说,先执行指令才将日志存盘。这点不同于 leveldb、hbase等存储引擎,它们都是先存储日志再做逻辑处理。服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器的 aof_buf 缓冲区。关于何时将 aof_buf 缓冲区的内容写入AOF文件中,Redis提供了3种写回策略:它是通过appendfsync参数来配置,简要描述如下:

  • always:每次发生数据修改就会立即记录到磁盘文件中,这种方案的完整性好但是IO开销很大,性能较差;
  • everysec:在每一秒中进行同步,速度有所提升。但是如果在一秒内宕机的话可能失去这一秒内的数据;
  • no:默认配置,即不使用 AOF 持久化方案。可以在redis.config中进行配置,appendonly no改换为yes;

AOF 重写机制:

AOF会记录每个写命令到AOF文件,随着Redis的运行,AOF文件会变得越来越大。如果不加以控制,会对Redis服务器,甚至对操作系统造成影响,另外如果实例宕机重启,那么重放整个AOF将会变得十分耗时,而在日志记录中,又有很多无意义的记录,比如我现在将一个数据 incr 一千次,那么就不需要去记录这1000次修改,只需要记录最后的值即可。数据恢复也越慢。为了解决AOF文件体积膨胀的问题,Redis提供AOF文件重写机制来对AOF文件进行“瘦身”。

Redis 提供了bgrewriteaof指令用于对AOF日志进行重写,该指令运行时会开辟一个子进程,即主线程fork出后台的bgrewriteaof子进程,fork会把主线程的内存拷贝一份给bgrewriteaof子进程,这里面就包含了数据库的最新数据。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。所以aof在重写过程,在fork进程时是会阻塞主线程的。

同样的也可以在redis.config中对重写机制的触发进行配置:通过将no-appendfsync-on-rewrite设置为yes,开启重写机制;auto-aof-rewrite-percentage 100意为比上次从写后文件大小增长了100%再次触发重写;auto-aof-rewrite-min-size 64mb意为当文件至少要达到64mb才会触发自动重写;重写也是会耗费资源的,所以当磁盘空间足够的时候,这里可以将 64mb 调整更大写,降低重写的频率,达到优化效果。将AOF配置为appendfsync everysec之后,Redis在处理一条命令后,并不直接立即调用write将数据写入 AOF 文件,而是先将数据写入AOF buffer(server.aof_buf)。调用write和命令处理是分开的,Redis只在每次进入epoll_wait之前做 write 操作。Redis另外的两种策略,一个是永不调用 fsync,让操作系统来决定合适同步磁盘,这样做很不安全;另一个是来一个指令就调用 fsync 一次,这种导致结果非常慢。这两种策略在生产环境中基本都不会使用。

AOF持久化开启:默认情况下,Redis是没有开启AOF的;可以通过配置redis.conf文件来开启AOF持久化,关于AOF的配置如下:

# appendonly参数开启AOF持久化
appendonly yes

# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"

# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./

# 同步策略,即主要用于设置“真正执行”操作命令向AOF文件中同步的策略
# appendfsync always
appendfsync everysec
# appendfsync no

# aof重写期间是否同步;当配置为always和everysec的设置会使真正的I/O操作高频度的出现,甚至会出现长时间的卡顿情况
no-appendfsync-on-rewrite no

# 重写触发配置;当前AOF文件的大小超过了上次重写后AOF文件的百分之多少后,就再次开始重写AOF文件。例如该参数值的默认设置值为100,意思就是如果AOF文件的大小超过上次AOF文件重写后的1倍,就启动重写操作;生产环境下,不可能随时随地使用“BGREWRITEAOF”命令去重写AOF文件。所以更多时候我们需要依靠Redis中对AOF文件的自动重写策略。auto-aof-rewrite-min-size表示启动AOF文件重写操作的AOF文件最小大小。如果AOF文件大小低于这个值,则不会触发重写操作;这2个参数只是用来控制Redis中自动对AOF文件进行重写的情况,但收到调用“BGREWRITEAOF”命令,则不受这两个限制条件左右
auto-aof-rewrite-percentage 100  //auto-aof-rewrite-percentage这个值的计算方式是,当前aof文件大小和上一次重写后aof文件大小的差值,再除以上一次重写后aof文件大小。也就是当前aof文件比上一次重写后aof文件的增量大小,和上一次重写后aof文件大小的比值
auto-aof-rewrite-min-size 64mb  //运行AOF重写时文件的最小大小,默认为64MB

# 加载aof出错如何处理
aof-load-truncated yes

# 文件重写策略
aof-rewrite-incremental-fsync yes
以下是Redis中关于AOF的主要配置信息:

3) RDB和AOF混合方式(4.0版本)

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的方法。简单来说,就是内存快照以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。

这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。这个方式既能享受到 RDB 文件快速恢复的好处,又能享受到 AOF 只记录操作命令的简单优势, 实际环境中用的很多。

恢复过程: 这时,我们只需重启redis,Redis重启时会判断是否开启aof,如果开启了aof,那么就优先加载aof文件;如果aof存在,那么就去加载aof文件,加载成功的话redis重启成功,如果aof文件加载失败,那么会打印日志表示启动失败,此时可以去修复aof文件后重新启动;若aof文件不存在,那么redis就会转而去加载rdb文件,如果rdb文件不存在,redis直接启动成功;

如果rdb文件存在就会去加载rdb文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么redis重启成功,且使用rdb文件恢复数据;至于为什么会优先加载AOF呢?因为AOF保存的数据更完整,理论上AOF基本上最多损失1s的数据。

总结:RDB的快照、AOF的重写都需要fork,这是一个重量级操作,会对Redis造成阻塞。为了不影响Redis主进程响应,我们需要尽可能降低阻塞。比如:

  • 降低fork的频率,如:手动来触发RDB生成快照、与AOF重写;建议定期检查Redis的情况,然后可以手动触发备份、重写数据;
  • 控制Redis最大使用内存,防止fork耗时过长;
  • 使用更高配置的硬件;
  • 合理配置Linux的内存分配策略,避免因为物理内存不足导致fork失败。
  • 如果Redis中的数据不是特别敏感或者可以通过其它方式重写生成数据,可以考虑关闭持久化,如果丢失数据可以通过其它途径补回;
  • 单机上部署多个实例的情况,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行;
  • 配置主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令;
  • 最后就是考虑RDB持久化与AOF持久化可以同时使用。

2.5 Key和哈希槽(slot)

1)Redis中key的理解

Redis 服务器都有多个 Redis 数据库,每个Redis 数据库都有自己独立的键值空间。Redis的key都是以字符串的形式存储的,它使用一个SDS ( simple dynamic string (简单的动态字符串)变量来存储字符串。每个 Redis 数据库使用 dict 保存数据库中所有的键值对,用它作为数据库的底层实现,同样也是哈希键的底层实现,而键值空间的键也就是数据库的键,每个键都是一个字符串对象,而值对象有5种,可能为:字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的一种对象,因Redis支持5种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。但Redis并没有直接使用数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含了上述五种类型对象;Redis使用对象来表示数据库中的键和值,每次当我们在Redis的数据库中新创建一个键值对时,我们至少要创建两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。Redis中的每个对象都由一个RedisObject结构表示。

redis 获取hmap redis 获取serversql数据_redist配置详解_12

除了键空间,Redis 也使用 Dict 结构来保存键的过期时间,其键是键空间中的键值,而值是过期时间;通过过期字典,Redis 可以直接判断一个键是否过期,首先查看该键是否存在于过期字典,如果存在,则比较该键的过期时间和当前服务器时间戳,如果大于,则该键过期,否则未过期。如下图所示:

redis 获取hmap redis 获取serversql数据_Redis_13


redis 获取hmap redis 获取serversql数据_Redis_14


Key-value在redis存储的时候,通常会有层级或者说是目录,这时候我们在set的时候,需要将key值使用":"的 符号来区分层级关系;每个key都对应位Redis中的一个对象,对象的类型也是五种,分别代表字符串、列表、集合、有序集合和哈希对象。redis的对象系统中使用引用计数来实现内存回收。当引用计数为0时,释放对象回收内存。另外,对象的ptr指针指向对象的底层数据结构,对应encoding属性

redis 获取hmap redis 获取serversql数据_Redis_15


redis 获取hmap redis 获取serversql数据_redis运维_16


redis 获取hmap redis 获取serversql数据_redis 获取hmap_17

/* Objects encoding. Some kind of objects like Strings and Hashes can be
 * internally represented in multiple ways. The 'encoding' field of the object
 * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

Redis的key是以hash表的形式存储的,上层存储表现是字典;Redis 使用「dict」结构来保存所有的键值对(key-value)数据,它是一个全局哈希表,所以对 key 的查询能以 O(1) 时间得到( O(1) 表耗时/耗空间与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。 哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标(不考虑冲突的话));

redis 获取hmap redis 获取serversql数据_redis 获取hmap_18


redis 获取hmap redis 获取serversql数据_Redis_19

总之,每个 key 计算之后都会对应一个编号在 0-16383 之间的哈希槽,redis使用一个「哈希表」来保存所有键值对,哈希表的最大好处就是让我们可以用 O(1) 的时间复杂度来快速查找到键值对。哈希表实际就是一个数组,哈希表可以类比 Java 中的 HashMap来理解,数组的每个元素叫做哈希桶。至于哈希桶,它存放的是指向键值对数据的指针(dictEntry*),这样通过指针就能找到键值对数据,然后因为键值对的值可以保存字符串对象和集合数据类型的对象,所以键值对的数据结构中并不是直接保存值本身,而是保存了 "void * key "和 "void * value "指针,分别指向了实际的键对象和值对象,这样一来,即使值是集合数据,也可以通过 void * value 指针找到。void * key 和 void * value 指针指向的就是 Redis 对象,Redis 中的每个对象都由 redisObject 结构表示。更多参看数据结构回顾,如下图所示:

redis 获取hmap redis 获取serversql数据_redis 获取hmap_20


redis 获取hmap redis 获取serversql数据_Redis_21

其中,ht_table为长度为 2 的 数组,正常情况使用 ht_table[0] 存储数据,当执行 rehash 的时候,使用 ht_table[1] 配合完成 。如下图所示的ht[2]:

redis 获取hmap redis 获取serversql数据_redis 获取hmap_22


redis 获取hmap redis 获取serversql数据_redis 获取hmap_23

  • redisDb 结构:即 Redis 数据库的结构,结构体里存放了指向了 dict 结构的指针;
  • dict 结构:结构体里存放了 2 个哈希表,正常情况下都是用「哈希表1」(ht1),「哈希表2」只有在 rehash 的时候才用;
  • dictht 结构:哈希表的结构,结构里存放了哈希表数组,数组中的每个(条)元素都是指向一个哈希表节点结构(dictEntry)的指针;
  • dictEntry 结构:哈希表节点的结构,结构里存放了 **void * key 和 void * value 指针, key 指向的是 String 对象,而 value 则可以指向 String 对象,也可以指向集合类型的对象,比如 List 对象、Hash 对象、Set 对象和 Zset 对象。

key 的哈希值最终会映射到 ht_table 的一个位置,如果发生哈希冲突,则拉出一个哈希链表。ht_table 数组每个位置就是哈希桶,保存了Redis 对象的所有键值对。哈希桶的每个元素的结构由 dictEntry (字典元素)定义,包括 指向 key 的指针、 指向实际 value 的指针和哈希冲突拉出的链表;这里的key 都是 string 类型,value 是个 union(联合体),当它的值是 uint64_t、int64_t 或 double 类型时,就不再需要额外的存储,这有利于减少内存碎片。当然,val 也可以是 void 指针,指向值的指针,以便能存储任何类型的数据。next 指向另一个 dictEntry 结构, 多个 dictEntry 可以通过 next 指针串连成链表;当多个不同的键拥有相同的哈希值时,哈希表用一个链表将这些键连接起来,处理 哈希冲突。而哈希桶并没有保存值本身,而是存储了指向具体值的指针,从而实现了哈希桶能存不同数据类型的需求。哈希桶中,键值对的都是由一个叫做 redisObject 的对象定义,Redis 中每个对象都是用 redisObject 表示。其中的encoding属性(编码方式):表示 ptr 指向的数据类型的具体数据结构,即这个对象使用了什么数据结构作为底层实现保存数据。同一个对象使用不同编码实现内存占用存在明显差异,内部编码对内存优化非常重要。

redis 获取hmap redis 获取serversql数据_Redis_24


redis 获取hmap redis 获取serversql数据_数据_25


当需要查询一个key时,首先利用预设的哈希计算初key的数组位置,然后找到数组的位置,再去链表中遍历找到相关的节点进行返回。

当我们执行 set key value 的命令,*key指针指向 SDS 字符串保存 key,而 value 的值保存在 *ptr 指针指向的数据结构,消耗的内存:key + value。我们设置key的时候,应尽量考虑降低 Redis 内存使用,最简单的方式就是缩减键(key)与值(value)的长度。另外,key也推荐采用命名空间的方式来命名,key 的命名推荐使用业务模块名:表名:数据唯一id这种格式,方便快速定位key;再比如我们执行:HSET person name “xiaolincoding” age 18后,就创建了一个哈希表键,而键的值是一个包含两个键值对的哈希表对象;执行:RPUSH stu “xiaolin” “xiaomei”,创建一个个列表键,键的值是一个包含两个元素的列表对象;

三、集群相关

Redis集群方案目前主流的有三种,分别是TwemproxyCodisRedis Cluster

redis 获取hmap redis 获取serversql数据_Redis_26


其中前两者属于“代理模式”,分别由推特和豌豆荚开发后开源,而Codis还解决了Twemproxy扩缩容的问题,且兼容Twemproxy,运行稳定;Redis Cluster是由官方出品的,用去中心化的方式实现,不属于代理模式。

3.1 Codis原理

redis 获取hmap redis 获取serversql数据_redist配置详解_27


如上图所示:当只有一个master,多个slave时,要保证master高可用,避免单点,就需要将salve提升为master,实现主从切换,而图中右侧的“Sentinel”,中文名又称哨兵,在Redis里其主要负责监控主从节点,如果主节点挂了,就会把从提升为主节点。而Redis Sentinel做集群就可避免本身的单点故障,造成主从切换失败。

Sentinel哨兵的运行机制示意图图下,了解哨兵时怎样故障切换的:

redis 获取hmap redis 获取serversql数据_Redis_28


图中是三个哨兵和一主两从的节点,哨兵之间会互相监测运行状态,并且会交换一下节点监测的状态,同时哨兵也会监测主从节点的状态。

当哨兵检测到某一个节点没有正常回复,并且距离上次正常回复的时间超过了某个阈值,那么就认为该节点为主观下线。这时,其他哨兵也会来监测该节点是不是真的主观下线,如果有足够多数量的哨兵都认为它确实主观下线了,那么它就会被标记为客观下线,这个时候哨兵会找下线节点的从节点,然后与其他哨兵协商出一个从节点做主节点,并将剩余的从节点指向新的主节点。如下如所示:

redis 获取hmap redis 获取serversql数据_redis运维_29

关于主从节点的切换有两个环节,第一个是哨兵要选举出领头人来负责下线机器的故障转移,第二是从Slave中选出主节点,领头人的选举规则是谁发现客观下线谁就可以马上要求其他哨兵认自己做老大,其他哨兵会无条件接受第一个发过来的人,并告知老大,如果超过一半人都同意了,那他老大的位置就坐实了。

redis 获取hmap redis 获取serversql数据_redis 获取hmap_30


关于从节点选举,一共有四个因素影响选举的结果,分别是断开连接时长、优先级排序、复制数量、进程id,如果连接断开的比较久,超过了某个阈值,就直接失去了选举权,如果拥有选举权,那就看谁的优先级高,这个在配置文件里可以设置,数值越小优先级越高,如果优先级相同,就看谁从master中复制的数据最多,选最多的那个,如果复制数量也相同,就选择进程id最小的那个。【海量数据下的codis】:存储海量数据的需求,同步会非常缓慢,所以我们应该把一个主从结构变成多个,把存储的key分摊到各个主从结构中来分担压力。代理通过一种算法把要操作的key经过计算后分配到各个组中,这个过程叫做分片

redis 获取hmap redis 获取serversql数据_Redis_31


redis 获取hmap redis 获取serversql数据_Redis_32


在Codis里面,它把所有的key分为1024个槽,每一个槽位都对应了一个分组,具体槽位的分配,可以进行自定义,现在如果有一个key进来,首先要根据CRC32算法,针对key算出32位的哈希值,然后除以1024取余,然后就能算出这个KEY属于哪个槽,然后根据槽与分组的映射关系,就能去对应的分组当中处理数据了。CRC全称是循环冗余校验,主要在数据存储和通信领域保证数据正确性的校验手段。

注: 槽位的映射关系是保存在proxy里面;不同proxy需要同步映射关系,从而保持数据一致性。

redis 获取hmap redis 获取serversql数据_redis运维_33

codis有一个进程叫codis-ha,codis-ha实时监测proxy的运行状态,如果有异常就会干掉,它包含了哨兵的功能,从而替换了上述的sentinel;codis-ha利用了k8s的pod的特性,保证启动的proxy和设置的数量一样,该进程监测Proxy的异常,并且干掉异常的proxy,然后自动拉起来一个新的proxy替代,永远保证足够数量的proxy。

redis 获取hmap redis 获取serversql数据_Redis_34


但是codis-ha在Codis整个架构中是没有办法直接操作代理和服务,因为所有的代理和服务的操作都要经过dashboard处理。所以部署的时候会利用k8s的亲和性将codis-ha与dashboard部署在同一个节点上。

另外,codis自己开发了集群管理界面,集群管理可以通过界面化的方式更方便的管理集群,这个模块叫codis-fe

3.2 Redis Cluster原理

Redis集群与codis不同,Codis它是通过代理分片的,但是Redis Cluster是去中心化没有代理,所以只能通过客户端分片,它分片的槽数跟Codis不太一样,Codis是1024个,而Redis cluster有16384个,槽跟节点映射关系保存在每个节点上,每个节点每秒钟会ping十次其他几个最久没通信的节点,其他节点也是一样的原理互相PING ,PING的时候一个是判断其他节点有没有问题,另一个是顺便交换一下当前集群的节点信息、包括槽与节点映射的关系等。客户端操作key的时候先通过分片算法算出所属的槽,然后随机找一个服务端请求。

redis 获取hmap redis 获取serversql数据_redis运维_35


从上图可看到的信息:

1、 对象保存到Redis之前先经过CRC16哈希到一个指定的Node上,例如Object4最终Hash到了Node1上。

2、 每个Node被平均分配了一个Slot段,对应着0-16384,Slot不能重复也不能缺失,否则会导致对象重复存储或无法存储。

3、 Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移。例如Node1如果掉线了,0-5640这些Slot将会平均分摊到Node2和Node3上,由于Node2和Node3本身维护的Slot还会在自己身上不会被重新分配,所以迁移过程中不会影响到5641-16384Slot段的使用。

简单总结下哈希Slot的优缺点:

  • 缺点:每个Node承担着互相监听、高并发数据写入、高并发数据读出,工作任务繁重
  • 优点:将Redis的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。
  • 总结:Redis主从和哈希的设计优缺点正好是相互弥补的。

redis 获取hmap redis 获取serversql数据_redis运维_36

redis 获取hmap redis 获取serversql数据_redist配置详解_37


想扩展并发读就添加Slaver,想扩展并发写就添加Master,想扩容也就是添加Master。

redis 获取hmap redis 获取serversql数据_redis 获取hmap_38


但是可能这个槽并不归随机找的这个节点管,节点如果发现不归自己管,就会返回一个MOVED ERROR通知,引导客户端去正确的节点访问,这个时候客户端就会去正确的节点操作数据。

redis 获取hmap redis 获取serversql数据_redis运维_39


1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

2))节点的fail是通过集群中超过半数的节点检测失效时才生效.

3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可;

4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value;

5) redis-cluster选举:集群中所有master参与选举,如果半数以上master节点与master节点通信超时(cluster-node-timeout),即超过半数以上的master发现其他master挂掉后,认为当前master节点挂掉;。会将其他对应的Slave节点升级成Master,超过半数mster挂掉,那redis cluster就挂掉了。

6)整个集群不可用(cluster_state:fail):

6.1> 如果集群任意master挂掉,且当前master没有slave.集群进入fail状态,也可以理解成进群的slot映射[0-16383]不完成时进入fail状态;

6.2> 如果进群超过半数以上master挂掉,无论是否有slave集群进入fail状态。

redis 获取hmap redis 获取serversql数据_redist配置详解_40

3.3 Redis 集群架构演变

1)2.8版本以前 ,Redis官方并没有高可用框架, 主从模式的弊端非常明显,从节点仅能作为数据备份,无法做到高可用,当主节点宕机以后,需要手动切换或者依赖第三方的框架,比如codis 等;

2)2.8版中官方给出了 Sentinel模式,解决了主节点宕机故障切换问题,哨兵sentinel可监控master节点的状态,如果master节点异常,则会做主从切换,将某一台slave自动提升为master并接管集群的功能,但哨兵模式下,是中心化的,主节点的压力大时,节点无法扩容。哨兵模式基于主从模式(实际为主备),实现读写分离,解决了主从模式的自动切换问题,系统可用性也更高。

redis 获取hmap redis 获取serversql数据_Redis_41

  • 主从:两个角色:主服务器,和从服务器两种,客户端均可以访问,一个读写,一个只读。
  • 主备:只有主服务器可以访问,其他备用的服务器只是在主服务器挂掉的情况下顶替使用;当使用主备模式,主服务器其实就是一个单点,还是会有单点的故障问题,只不过是挂掉的时候会及时切换备用服务器。

redis 获取hmap redis 获取serversql数据_Redis_42


另外该模式还存在以下问题:

  • 在主从切换的瞬间会导致业务瞬断的情况,哨兵模式只有一个主节点对外提供服务,没法支持很高的并发
  • 单个主节点内存不宜设置得过大,否则会导致持久化文件过大,影响数据恢复或主从同步的效率
  • 每个节点存储的数据是一样的,每个节点的数据都是全量复制,哨兵模式下的存储受限于内存最小的节点,这样就会导致浪费内存,且不好在线扩容

Redis 3.0 版官方提供了Cluster模式,支持数据分片存储(即每台Redis节点上存储不同的内容,很容易在线扩容),去中心化,多个master,所有节点都可提供读写,这样节点就可方便扩容,单个节点写的压力就小了很多,官方文档称可以线性扩展到上万个节点(官方推荐不超过1000个节点)。该集群模式性能和高可用性均优于之前版本的哨兵模式,且集群配置非常简单。集群节点之间使用 Gossip 协议(流言协议,它是一个最终一致性协议)通信,交换的信息内容包括节点出现故障、新节点加入、主从节点变更信息、slot信息等等。常用的Gossip消息分为4种,分别是:ping、pong、meet、fail。Redis Cluster 采用虚拟哈希槽分区,所有的键根据哈希函数映射到 0 ~ 16383 整数槽内,每一个节点负责维护一部分槽以及槽所映射的键值数据。

redis 获取hmap redis 获取serversql数据_redist配置详解_43


更多参看Redis Cluster

四、Redis 安装

软件下载地址:http://redis.io/download,

4.1 普通安装:

解压编译:

$ wget http://download.redis.io/releases/redis-5.0.5.tar.gz
$ tar xzf redis-5.0.5.tar.gz
$ cd redis-5.0.5
$ make  ##官网至说了make,但也可以make && make install
$ src/redis-server   ##按照后的二进制文件存放在src目录下,在该目录下启动redis
$ src/redis-cli   ##这是Redis自带客户端软件,便于用户使用该命令与redis交互。
redis> set foo bar  ##写入数据,用SET命令可将一个value,这里时bar,常用引号引起来,存储到Key名为 foo里;
OK
redis> get foo     ##获取数据
"bar"
redis> INCR connections  ##incr命令在现有指定key的值后默认增加整数1;
redis> EXPIRE resource:lock 120  ##该命令可指定上一条命令的key:value在redis中保存的时间,通过TTL实现;
redis> TTL key   ##可获取key保存在redis的剩余时间(s),当TTL返回值为-2时,表这时指定的key:value已在redis中被删        除,不指定expire的情况下,默认key的TTL值为-1,表key永不失效,在redis中存储永不过期;而且当key被重新赋值时,TTL的值也将被重置。

附录:Redis的tutorial教程练习界面:http://try.redis.io/,可以带初学者熟悉相关命令;

redis 获取hmap redis 获取serversql数据_redis 获取hmap_44

4.2 集群安装

cd /usr/local
mkdir redis_cluster  //创建集群目录
mkdir 7000 7001 7002  //分别代表三个节点    其对应端口 7000 7001 7002
cp /usr/local/redis-你的版本/redis.conf  ./redis_cluster/7000/   
 //拷贝到7001目录
 cp /usr/local/redis-你的版本/redis.conf  ./redis_cluster/7001/   
 //拷贝到7002目录
 cp /usr/local/redis-你的版本/redis.conf  ./redis_cluster/7002/

分别编辑三个节点下的配置文件:

daemonize yes //redis后台运行
pidfile /var/run/redis_7000.pid //pidfile文件对应7000,7002,7003
port 7000 //端口7000,7002,7003
cluster-enabled yes //开启集群 把注释#去掉
cluster-config-file nodes_7000.conf //集群的配置 配置文件首次启动自动生成 7000,7001,7002
cluster-node-timeout 5000 //请求超时 设置5秒够了
appendonly yes //aof日志开启 有需要就开启,它会每次写操作都记录一条日志

启动服务器上的三个节点实例:(一个进程就是一个实例,可以看做一个虚拟主机)

cd /usr/local
redis-server  redis_cluster/7000/redis.conf
redis-server  redis_cluster/7001/redis.conf
redis-server  redis_cluster/7002/redis.conf

:redis-server会以非daemon的方式来运行,且默认服务端口为6379,会占用当前的窗口,一旦ctrl+c,redis也会结束,因此当需要已daemon方式启动时,需配置文件中开机,命令行用nohup redis命令 & 这种形式启动redis;

服务启动验证:

ps -ef | grep redis #查看是否启动成功;

netstat -tnlp | grep redis #可以看到redis监听端口`

把上述创建好的节点都串连起来搭建集群:

默认目录:redis-trib.rb(/usr/local/redis-你的版本/src/redis-trib.rb),这是redis官方提供的一个工具,它是用ruby写的一个程序,故还需要安装ruby,构建环境:

yum -y install ruby ruby-devel rubygems rpm-build
#再用 gem 这个命令来安装 redis接口 gem是ruby的一个工具包.
gem install redis
/usr/local/redis-3.2.1/src/redis-trib.rb create --replicas 1 192.168.1.237:7000 192.168.1.237:7001 192.168.1.237:7003 192.168.1.238:7003 192.168.1.238:7004 192.168.1.238:7005

执行完命令即可完成redis集群搭建。

说明:
–replicas 1 表示 自动为每一个master节点分配一个slave节点 上面有6个节点,程序会按照一定规则生成 3个master(主)3个slave(从);
另外注意: 防火墙一定要开放监听的端口,否则会创建失败。

运行中,提示Can I set the above configuration? (type ‘yes’ to accept): yes //输入yes

接下来 提示 Waiting for the cluster to join… 安装的时候在这里就一直等等等,没反应,傻傻等半天,看这句提示上面一句,Sending Cluster Meet Message to join the Cluster.

这是因上述redis配置只在其中一台Server上完成,里还需要到Server2和3上做同样的操作。

在192.168.1.238, redis-cli -c -p 700* 分别进入redis各节点的客户端命令窗口, 依次输入 cluster meet 192.168.1.238 7000……
回到Server1,已经创建完毕了。
查看一下 /usr/local/redis/src/redis-trib.rb check 192.168.1.237:7000
到这里集群已经初步搭建好了。

六、Redis配置说明:

在Redis中,数据库是由一个整数索引标识,而不是由一个数据库名称。默认情况下,一个客户端连接到数据库0。redis配置文件中下面的参数来控制数据库总数:

redis 获取hmap redis 获取serversql数据_redis运维_45


上图中,databases 16 表示数据库从0可以连接到15,共16个redist数据库。更多配置选项说明如下:

#redis.conf
# Redis configuration file example.
# ./redis-server /path/to/redis.conf
 
################################## INCLUDES ###################################
#这在你有标准配置模板但是每个redis服务器又需要个性设置的时候很有用。
# include /path/to/local.conf
# include /path/to/other.conf
 
################################ GENERAL #####################################
 
#是否在后台执行,yes:后台运行;no:不是后台运行(老版本默认)
daemonize yes

#3.2里的参数,是否开启保护模式,默认开启。要是配置里没有指定bind和密码。开启该参数后,redis只会本地进行访问,拒绝外部访问。要是开启了密码   和bind,可以开启。否   则最好关闭,设置为no。
protected-mode yes

#redis的进程文件
pidfile /var/run/redis/redis-server.pid
 
#redis监听的端口号。
port 6379
 
#此参数确定了TCP连接中已完成队列(完成三次握手之后)的长度, 当然此值必须不大于Linux系统定义的/proc/sys/net/core/somaxconn值,默认是511,而Linux的默认参数值是128。当系统并发量大并且客户端速度缓慢的时候,可以将这二个参数一起参考设定。该内核参数默认值一般是128,对于负载很大的服务程序来说大大的不够。一般会将它修改为2048或者更大。在/etc/sysctl.conf中添加:net.core.somaxconn = 2048,然后在终端中执行sysctl -p。
tcp-backlog 511
 
#指定 redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求
bind 0.0.0.0
 
#配置unix socket来让redis支持监听本地连接。
# unixsocket /var/run/redis/redis.sock
#配置unix socket使用文件的权限
# unixsocketperm 700
 
# 此参数为设置客户端空闲超过timeout,服务端会断开连接,为0则服务端不会主动断开连接,不能小于0。
timeout 0
 
#tcp keepalive参数。如果设置不为0,就使用配置tcp的SO_KEEPALIVE值,使用keepalive有两个好处:检测挂掉的对端。降低中间设备出问题而导致网络看似连接却已经与对端端口的问题。在Linux内核中,设置了keepalive,redis会定时给对端发送ack。检测到对端关闭需要两倍的设置值。
tcp-keepalive 0
 
#指定了服务端日志的级别。级别包括:debug(很多信息,方便开发、测试),verbose(许多有用的信息,但是没有debug级别信息多),notice(适当的日志级别,适合生产环境),warn(只有非常重要的信息)
loglevel notice
 
#指定了记录日志的文件。空字符串的话,日志会打印到标准输出设备。后台运行的redis标准输出是/dev/null。
logfile /var/log/redis/redis-server.log
 
#是否打开记录syslog功能
# syslog-enabled no
 
#syslog的标识符。
# syslog-ident redis
 
#日志的来源、设备
# syslog-facility local0
 
#数据库的数量,默认使用的数据库是DB 0。可以通过”SELECT “命令选择一个db
databases 16
 
################################ SNAPSHOTTING ################################
# 快照配置
# 注释掉“save”这一行配置项就可以让保存数据库功能失效
# 设置sedis进行数据库镜像的频率。
# 900秒(15分钟)内至少1个key值改变(则进行数据库保存--持久化) 
# 300秒(5分钟)内至少10个key值改变(则进行数据库保存--持久化) 
# 60秒(1分钟)内至少10000个key值改变(则进行数据库保存--持久化)
save 900 1
save 300 10
save 60 10000
 
#当RDB持久化出现错误后,是否依然进行继续进行工作,yes:不能进行工作,no:可以继续进行工作,可以通过info中的rdb_last_bgsave_status了解RDB持久化是否有错误
stop-writes-on-bgsave-error yes
 
#使用压缩rdb文件,rdb文件压缩使用LZF压缩算法,yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间
rdbcompression yes
 
#是否校验rdb文件。从rdb格式的第五个版本开始,在rdb文件的末尾会带上CRC64的校验和。这跟有利于文件的容错性,但是在保存rdb文件的时候,会有大概10%的性能损耗,所以如果你追求高性能,可以关闭该配置。
rdbchecksum yes
 
#rdb文件的名称
dbfilename dump.rdb
 
#数据目录,数据库的写入会在这个目录。rdb、aof文件也会写在这个目录
dir /var/lib/redis
 
################################# REPLICATION #################################
#复制选项,slave复制对应的master。
# slaveof <masterip> <masterport>
 
#如果master设置了requirepass,那么slave要连上master,需要有master的密码才行。masterauth就是用来配置master的密码,这样可以在连上master后进行认证。
# masterauth <master-password>
 
#当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续响应客户端的请求。2) 如果slave-serve-stale-data设置为no,除去INFO和SLAVOF命令之外的任何请求都会返回一个错误”SYNC with master in progress”。
slave-serve-stale-data yes
 
#作为从服务器,默认情况下是只读的(yes),可以修改成NO,用于写(不建议)。
slave-read-only yes
 
#是否使用socket方式复制数据。目前redis复制提供两种方式,disk和socket。如果新的slave连上来或者重连的slave无法部分同步,就会执行全量同步,master会生成rdb文件。有2种方式:disk方式是master创建一个新的进程把rdb文件保存到磁盘,再把磁盘上的rdb文件传递给slave。socket是master创建一个新的进程,直接把rdb文件以socket的方式发给slave。disk方式的时候,当一个rdb保存的过程中,多个slave都能共享这个rdb文件。socket的方式就的一个个slave顺序复制。在磁盘速度缓慢,网速快的情况下推荐用socket方式。
repl-diskless-sync no
 
#diskless复制的延迟时间,防止设置为0。一旦复制开始,节点不会再接收新slave的复制请求直到下一个rdb传输。所以最好等待一段时间,等更多的slave连上来。
repl-diskless-sync-delay 5
 
#slave根据指定的时间间隔向服务器发送ping请求。时间间隔可以通过 repl_ping_slave_period 来设置,默认10秒。
repl-ping-slave-period 10
 
#复制连接超时时间。master和slave都有超时时间的设置。master检测到slave上次发送的时间超过repl-timeout,即认为slave离线,清除该slave信息。slave检测到上次和master交互的时间超过repl-timeout,则认为master离线。需要注意的是repl-timeout需要设置一个比repl-ping-slave-period更大的值,不然会经常检测到超时。
# repl-timeout 60
 
#是否禁止复制tcp链接的tcp nodelay参数,可传递yes或者no。默认是no,即使用tcp nodelay。如果master设置了yes来禁止tcp nodelay设置,在把数据复制给slave的时候,会减少包的数量和更小的网络带宽。但是这也可能带来数据的延迟。默认我们推荐更小的延迟,但是在数据量传输很大的场景下,建议选择yes。
repl-disable-tcp-nodelay no
 
#复制缓冲区大小,这是一个环形复制缓冲区,用来保存最新复制的命令。这样在slave离线的时候,不需要完全复制master的数据,如果可以执行部分同步,只需要把缓冲区的部分数据复制给slave,就能恢复正常复制状态。缓冲区的大小越大,slave离线的时间可以更长,复制缓冲区只有在有slave连接的时候才分配内存。没有slave的一段时间,内存会被释放出来,默认1m。
# repl-backlog-size 5mb
 
#master没有slave一段时间会释放复制缓冲区的内存,repl-backlog-ttl用来设置该时间长度。单位为秒。
# repl-backlog-ttl 3600
 
#当master不可用,Sentinel会根据slave的优先级选举一个master。最低的优先级的slave,当选master。而配置成0,永远不会被选举。
slave-priority 100
 
#redis提供了可以让master停止写入的方式,如果配置了min-slaves-to-write,健康的slave的个数小于N,mater就禁止写入。master最少得有多少个健康的slave存活才能执行写命令。这个配置虽然不能保证N个slave都一定能接收到master的写操作,但是能避免没有足够健康的slave的时候,master不能写入来避免数据丢失。设置为0是关闭该功能。
# min-slaves-to-write 3
 
#延迟小于min-slaves-max-lag秒的slave才认为是健康的slave。
# min-slaves-max-lag 10
 
# 设置1或另一个设置为0禁用这个特性。
# Setting one or the other to 0 disables the feature.
# By default min-slaves-to-write is set to 0 (feature disabled) and
# min-slaves-max-lag is set to 10.
 
################################## SECURITY ###################################
#requirepass配置可以让用户使用AUTH命令来认证密码,才能使用其他命令。这让redis可以使用在不受信任的网络中。为了保持向后的兼容性,可以注释该命令,因为大部分用户也不需要认证。使用requirepass的时候需要注意,因为redis太快了,每秒可以认证15w次密码,简单的密码很容易被攻破,所以最好使用一个更复杂的密码。
# requirepass foobared
 
#把危险的命令给修改成其他名称。比如CONFIG命令可以重命名为一个很难被猜到的命令,这样用户不能使用,而内部工具还能接着使用。
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
 
#设置成一个空的值,可以禁止一个命令
# rename-command CONFIG ""
################################### LIMITS ####################################
 
# 设置能连上redis的最大客户端连接数量。默认是10000个客户端连接。由于redis不区分连接是客户端连接还是内部打开文件或者和slave连接等,所以maxclients最小建议设置到32。如果超过了maxclients,redis会给新的连接发送’max number of clients reached’,并关闭连接。
# maxclients 10000
 
#redis配置的最大内存容量。当内存满了,需要配合maxmemory-policy策略进行处理。注意slave的输出缓冲区是不计算在maxmemory内的。所以为了防止主机内存使用完,建议设置的maxmemory需要更小一些。
# maxmemory <bytes>
 
#内存容量超过maxmemory后的处理策略。
#volatile-lru:利用LRU算法移除设置过过期时间的key。
#volatile-random:随机移除设置过过期时间的key。
#volatile-ttl:移除即将过期的key,根据最近过期时间来删除(辅以TTL)
#allkeys-lru:利用LRU算法移除任何key。
#allkeys-random:随机移除任何key。
#noeviction:不移除任何key,只是返回一个写错误。
#上面的这些驱逐策略,如果redis没有合适的key驱逐,对于写命令,还是会返回错误。redis将不再接收写请求,只接收get请求。写命令包括:set setnx setex append incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby getset mset msetnx exec sort。
# maxmemory-policy noeviction
 
#lru检测的样本数。使用lru或者ttl淘汰算法,从需要淘汰的列表中随机选择sample个key,选出闲置时间最长的key移除。
# maxmemory-samples 5
 
############################## APPEND ONLY MODE ###############################
#默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了。但是redis如果中途宕机,会导致可能有几分钟的数据丢失,根据save来策略进行持久化,Append Only File是另一种持久化方式,可以提供更好的持久化特性。Redis会把每次写入的数据在接收后都写入 appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。
appendonly no
 
#aof文件名
appendfilename "appendonly.aof"
 
#aof持久化策略的配置
#no表示不执行fsync,由操作系统保证数据同步到磁盘,速度最快。
#always表示每次写入都执行fsync,以保证数据同步到磁盘。
#everysec表示每秒执行一次fsync,可能会导致丢失这1s数据。
appendfsync everysec
 
# 在aof重写或者写入rdb文件的时候,会执行大量IO,此时对于everysec和always的aof模式来说,执行fsync会造成阻塞过长时间,no-appendfsync-on-rewrite字段设置为默认设置为no。如果对延迟要求很高的应用,这个字段可以设置为yes,否则还是设置为no,这样对持久化特性来说这是更安全的选择。设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes。Linux的默认fsync策略是30秒。可能丢失30秒数据。
no-appendfsync-on-rewrite no
 
#aof自动重写配置。当目前aof文件大小超过上一次重写的aof文件大小的百分之多少进行重写,即当aof文件增长到一定大小的时候Redis能够调用bgrewriteaof对日志文件进行重写。当前AOF文件大小是上次日志重写得到AOF文件大小的二倍(设置为100)时,自动启动新的日志重写过程。
auto-aof-rewrite-percentage 100
#设置允许重写的最小aof文件大小,避免了达到约定百分比但尺寸仍然很小的情况还要重写
auto-aof-rewrite-min-size 64mb
 
#aof文件可能在尾部是不完整的,当redis启动的时候,aof文件的数据被载入内存。重启可能发生在redis所在的主机操作系统宕机后,尤其在ext4文件系统没有加上data=ordered选项(redis宕机或者异常终止不会造成尾部不完整现象。)出现这种现象,可以选择让redis退出,或者导入尽可能多的数据。如果选择的是yes,当截断的aof文件被导入的时候,会自动发布一个log给客户端然后load。如果是no,用户必须手动redis-check-aof修复AOF文件才可以。
aof-load-truncated yes
 
################################ LUA SCRIPTING ###############################
# 如果达到最大时间限制(毫秒),redis会记个log,然后返回error。当一个脚本超过了最大时限。只有SCRIPT KILL和SHUTDOWN NOSAVE可以用。第一个可以杀没有调write命令的东西。要是已经调用了write,只能用第二个命令杀。
lua-time-limit 5000
 
################################ REDIS CLUSTER ###############################
#集群开关,默认是不开启集群模式。
# cluster-enabled yes
 
#集群配置文件的名称,每个节点都有一个集群相关的配置文件,持久化保存集群的信息。这个文件并不需要手动配置,这个配置文件有Redis生成并更新,每个Redis集群节点需要一个单独的配置文件,请确保与实例运行的系统中配置文件名称不冲突
# cluster-config-file nodes-6379.conf
 
#节点互连超时的阀值。集群节点超时毫秒数
# cluster-node-timeout 15000
 
#在进行故障转移的时候,全部slave都会请求申请为master,但是有些slave可能与master断开连接一段时间了,导致数据过于陈旧,这样的slave不应该被提升为master。该参数就是用来判>断slave节点与master断线的时间是否过长。判断方法是:
#比较slave断开连接的时间和(node-timeout * slave-validity-factor) + repl-ping-slave-period
#如果节点超时时间为三十秒, 并且slave-validity-factor为10,假设默认的repl-ping-slave-period是10秒,即如果超过310秒slave将不会尝试进行故障转移 
#可能出现由于某主节点失联却没有从节点能顶上的情况,从而导致集群不能正常工作,在这种情况下,只有等到原来的主节点重新回归到集群,集群才恢复运作
#如果设置成0,则无论从节点与主节点失联多久,从节点都会尝试升级成主节
# cluster-slave-validity-factor 10

#master的slave数量大于该值,slave才能迁移到其他孤立master上,如这个参数若被设为2,那么只有当一个主节点拥有2 个可工作的从节点时,它的一个从节点会尝试迁移。
#主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。
# cluster-migration-barrier 1

#默认情况下,集群全部的slot有节点分配,集群状态才为ok,才能提供服务。设置为no,可以在slot没有全部分配的时候提供服务。不建议打开该配置,这样会造成分区的时候,小分区的mster一直在接受写请求,而造成很长时间数据不一致。
#在部分key所在的节点不可用时,如果此参数设置为”yes”(默认值), 则整个集群停止接受操作;如果此参数设置为”no”,则集群依然为可达节点上的key提供读操作
# cluster-require-full-coverage yes
################################## SLOW LOG ###################################
###slog log是用来记录redis运行中执行比较慢的命令耗时。当命令的执行超过了指定时间,就记录在slow log中,slog log保存在内存中,所以没有IO操作。
#执行时间比slowlog-log-slower-than大的请求记录到slowlog里面,单位是微秒,所以1000000就是1秒。注意,负数时间会禁用慢查询日志,而0则会强制记录所有命令。
slowlog-log-slower-than 10000
 
#慢查询日志长度。当一个新的命令被写进日志的时候,最老的那个记录会被删掉。这个长度没有限制。只要有足够的内存就行。你可以通过 SLOWLOG RESET 来释放内存。
slowlog-max-len 128
 
################################ LATENCY MONITOR ##############################
#延迟监控功能是用来监控redis中执行比较缓慢的一些操作,用LATENCY打印redis实例在跑命令时的耗时图表。只记录大于等于下边设置的值的操作。0的话,就是关闭监视。默认延迟监控功能是关闭的,如果你需要打开,也可以通过CONFIG SET命令动态设置。
latency-monitor-threshold 0
 
############################# EVENT NOTIFICATION ##############################
#键空间通知使得客户端可以通过订阅频道或模式,来接收那些以某种方式改动了 Redis 数据集的事件。因为开启键空间通知功能需要消耗一些 CPU ,所以在默认配置下,该功能处于关闭状态。
#notify-keyspace-events 的参数可以是以下字符的任意组合,它指定了服务器该发送哪些类型的通知:
##K 键空间通知,所有通知以 __keyspace@__ 为前缀
##E 键事件通知,所有通知以 __keyevent@__ 为前缀
##g DEL 、 EXPIRE 、 RENAME 等类型无关的通用命令的通知
##$ 字符串命令的通知
##l 列表命令的通知
##s 集合命令的通知
##h 哈希命令的通知
##z 有序集合命令的通知
##x 过期事件:每当有过期键被删除时发送
##e 驱逐(evict)事件:每当有键因为 maxmemory 政策而被删除时发送
##A 参数 g$lshzxe 的别名
#输入的参数中至少要有一个 K 或者 E,否则的话,不管其余的参数是什么,都不会有任何 通知被分发。详细使用可以参考http://redis.io/topics/notifications
 
notify-keyspace-events ""
 
############################### ADVANCED CONFIG ###############################
#数据量小于等于hash-max-ziplist-entries的用ziplist,大于hash-max-ziplist-entries用hash
hash-max-ziplist-entries 512
#value大小小于等于hash-max-ziplist-value的用ziplist,大于hash-max-ziplist-value用hash。
hash-max-ziplist-value 64
 
#数据量小于等于list-max-ziplist-entries用ziplist,大于list-max-ziplist-entries用list。
list-max-ziplist-entries 512
#value大小小于等于list-max-ziplist-value的用ziplist,大于list-max-ziplist-value用list。
list-max-ziplist-value 64
 
#数据量小于等于set-max-intset-entries用iniset,大于set-max-intset-entries用set。
set-max-intset-entries 512
 
#数据量小于等于zset-max-ziplist-entries用ziplist,大于zset-max-ziplist-entries用zset。
zset-max-ziplist-entries 128
#value大小小于等于zset-max-ziplist-value用ziplist,大于zset-max-ziplist-value用zset。
zset-max-ziplist-value 64
 
#value大小小于等于hll-sparse-max-bytes使用稀疏数据结构(sparse),大于hll-sparse-max-bytes使用稠密的数据结构(dense)。一个比16000大的value是几乎没用的,建议的value大概为3000。如果对CPU要求不高,对空间要求较高的,建议设置到10000左右。
hll-sparse-max-bytes 3000
 
#Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存。
activerehashing yes
 
##对客户端输出缓冲进行限制可以强迫那些不从服务器读取数据的客户端断开连接,用来强制关闭传输缓慢的客户端。
#对于normal client,第一个0表示取消hard limit,第二个0和第三个0表示取消soft limit,normal client默认取消限制,因为如果没有寻问,他们是不会接收数据的。
client-output-buffer-limit normal 0 0 0
#对于slave client和MONITER client,如果client-output-buffer一旦超过256mb,又或者超过64mb持续60秒,那么服务器就会立即断开客户端连接。
client-output-buffer-limit slave 256mb 64mb 60
#对于pubsub client,如果client-output-buffer一旦超过32mb,又或者超过8mb持续60秒,那么服务器就会立即断开客户端连接。
client-output-buffer-limit pubsub 32mb 8mb 60
 
#redis执行任务的频率为1s除以hz。
hz 10
 
#在aof重写的时候,如果打开了aof-rewrite-incremental-fsync开关,系统会每32MB执行一次fsync。这对于把文件写入磁盘是有帮助的,可以避免过大的延迟峰值。
aof-rewrite-incremental-fsync yes

七、Redis运维操作命令

1)Redis客户端工具:redis-cli

启动 redis 客户端,打开终端并输入命令 redis-cli。该命令会连接本地的 redis 服务,

$redis-cli
redis 127.0.0.1:6379>   ##默认以6379端口连接,除非用-p参数指定端口
redis 127.0.0.1:6379> PING    ##用于检测 redis 服务是否启动,恢复pong即成功
PONG

2)远程/以其他端口执行上述命令:

语法:redis-cli -h host -p port -a password
参考实例:

$redis-cli -h 127.0.0.1 -p 8230 -a "mima"   ##如果日式a参数异常,检查密码,且密码与a有空格
redis 127.0.0.1:8230>
redis 127.0.0.1:8230> PING
PONG

3)验证密码是否正确:

redis 127.0.0.1:6379> AUTH "mima"
OK
redis 127.0.0.1:6379> config get protect*
1) "protected-mode"
2) "yes"
redis 127.0.0.1:6379> config set protected-mode no
OK
redis 127.0.0.1:6379>  config rewrite  #上述设置永久生效
OK

4)其他连接:

redis 127.0.0.1:6379> select 0 ##切换到指定的数据库,默认使用 0 号数据库
OK
127.0.0.1:6379>select 15  ##切换到数据库15
OK
127.0.0.1:6379[15]>   ##注意 Redis 现在的命令提示符多了个 [15]

5)其他操作:

time  返回时间戳+微秒
dbsize 返回key的数量
bgrewriteaof 重写aof
bgsave 后台开启子进程dump数据
save 阻塞进程dump数据
lastsave 
 
slaveof host port 做host port的从服务器(数据清空,复制新主内容)
slaveof no one 变成主服务器(原数据不丢失,一般用于主服失败后)
 
flushdb  清空当前数据库的所有数据
flushall 清空所有数据库的所有数据(误用了怎么办?)
 
shutdown [save/nosave] 关闭服务器,保存数据,修改AOF(如果设置)
 
slowlog get 获取慢查询日志
slowlog len 获取慢查询日志条数
slowlog reset 清空慢查询

info []
 
config get 选项(支持*通配)
config set 选项 值
config rewrite 把值写到配置文件
config restart 更新info命令的信息
 
debug object key #调试选项,看一个key的情况
debug segfault #模拟段错误,让服务器崩溃
object key (refcount|encoding|idletime)
monitor #打开控制台,观察命令(调试用)
client list #列出所有连接
client kill #杀死某个连接  CLIENT KILL 127.0.0.1:43501
client getname #获取连接的名称 默认nil
client setname "名称" #设置连接名称,便于调试

未完待续……

6)key管理

127.0.0.1:7000> keys *   //查看redis中所有键,通配符有 ?*[] 和转义 \。
127.0.0.1:7000> keys str* //查找以st开头的所有键
127.0.0.1:7000> EXISTS RSMS:286985_561676919:63389155  //查询键是否存在,返回整数1即存在;返回(integer) 0,不存在,如下所示
(integer) 1
127.0.0.1:7000> TYPE RSMS:286985_561676919:63389155  //查看键的类型
zset
127.0.0.1:7000> rename string str                 // 修改键的名称为str,成功返回OK
127.0.0.1:7000> del str		  //删除键
127.0.0.1:7000> expire str 300        // 设置键str过期时间为300s,返回(integer) 1
127.0.0.1:7000> persist str             // 去除上面设置的str键过期时间,返回(integer) 1
127.0.0.1:7000> PTTL str               //以毫秒为单位返回 key 的剩余的过期时间;换成ttl查看以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)
(integer) 11919352
127.0.0.1:7000> object encoding str //查看键str的PTR(PoinTeR指针)编码,redis对象的ptr指针指向对象的底层数据结构,对应encoding属性
"embstr"

7)集群管理

redis 127.0.0.1:6379> cluster nodes  //查看集群状态
redis 127.0.0.1:6379> config get cluster-config-file  //查看对应集群配置文件路径
redis 127.0.0.1:6379> config get dir	//查看默认配置文件位置
//Redis集群添加节点,首先将添加的节点启动
cd /usr/local/redis-4.0.14/src/
./redis-trib.rb add-node 127.0.0.1:7003 127.0.0.1:7000 //删除del-node
//现场
./src/redis-trib.rb add-node 2409:8080::5010:1003:0:14:7003  2409:8080:5800:e000:5010:1003:0:14:7000
>>> Adding node 2409:8080::5010:1003:0:14:7003 to cluster 2409:8080::5010:1003:0:14:7000
>>> Performing Cluster Check (using node 2409:8080::5010:1003:0:14:7000)
M: ecd3122fd0438fd1d9cf1c412c222e1c00561d8d 2409:8080::5010:1003:0:14:7000
   slots:4096-8191 (4096 slots) master
   1 additional replica(s)
M: da3e62d1df4433e66feb0e4485b8c5e2d3e7f784 2409:8080::5010:1003:0:13:7000
   slots:0-4095 (4096 slots) master
   2 additional replica(s)
S: b2856e6da9e8497e4873bb93c207508d7fd7eb60 2409:8080::5010:1003:0:14:7004
   slots: (0 slots) slave
   replicates e051c23bc12274593044de7dd160e683fa97e8bd
M: e051c23bc12274593044de7dd160e683fa97e8bd 2409:8080::5010:1003:0:13:7001
   slots:8192-12287 (4096 slots) master
   1 additional replica(s)
S: a75ccf148b8962ddc08c2acc2bedb35930ebdde1 2409:8080::5010:1003:0:13:7004
   slots: (0 slots) slave
   replicates da3e62d1df4433e66feb0e4485b8c5e2d3e7f784
M: d8878ad49c6d347f0037d5665b3fde93e94bbc4f 2409:8080::5010:1003:0:14:7001
   slots:12288-16383 (4096 slots) master
   1 additional replica(s)
S: 1164ae6de8865c3f3e2285fb62554842e63a0f49 2409:8080::5010:1003:0:13:7002
   slots: (0 slots) slave
   replicates d8878ad49c6d347f0037d5665b3fde93e94bbc4f
S: f3108eb4f1b2700cb185be78d3375fa3b5adc89f 2409:8080::5010:1003:0:13:7003
   slots: (0 slots) slave
   replicates ecd3122fd0438fd1d9cf1c412c222e1c00561d8d
S: 3981f3b14e7392993d948a3717d932c3aac4311f 2409:8080::5010:1003:0:14:7002
   slots: (0 slots) slave
   replicates da3e62d1df4433e66feb0e4485b8c5e2d3e7f784
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 2409:8080::5010:1003:0:14:7003 to make it join the cluster.
[OK] New node added correctly.
You have new mail in /var/spool/mail/root

redis-cli -c -p 7000 cluster nodes  //验证,增加了新的节点之后,这个新的节点可以成为主节点或者是从节点
/redis-trib.rb reshard 127.0.0.1:7000  //配置添加的节点未主节点,即将当前集群中的某些哈希槽移动到新节点里面, 这个新节点就成为真正的主节点了,比如移动1000个,并指定把这些哈希槽转移到哪个节点上,输入该节点ID;输入all 表示从所有的主节点中随机转移,凑够1000个哈希槽,然后再输入yes,redis集群就开始分配哈希槽了。

redis-cli -c -p 7006 cluster replicate master_ID //把7006节点添加为master的从节点
redis-cli -p 7000 cluster nodes | grep slave | grep master_ID

参考文献:https://c.lanmit.com/shujuku/NoSQL/7764.html,下载最新稳定版目前为:Stable (5.0);https://redis.io/commands

八、其他相关问题

8.1 Redis缓存穿透,击穿和雪崩

1)Redis缓存雪崩:是指redis在某个时间大量key失效,比如,大量的热点数据过期时间相同,导致数据在同一时刻集体失效。这是就会去后台的数据库去请求数据,造成数据库访问压力突然急剧增大,压力骤增,引起雪崩,redis雪崩危害巨大,甚至有可能造成服务器宕机,给公司造成巨大的经济损失。

解决方案:

1.将热点数据的过期时间打散。给热点数据设置过期时间时加个随机值;即设置超时时间的时候要设置随机值,不要设置固定值;

2.加互斥锁。当热点key过期后,大量的请求涌入时,只有第一个请求能获取锁并阻塞,此时该请求查询数据库,并将查询结果写入redis后释放锁。后续的请求直接走缓存。

3.设置缓存不过期或者后台有线程一直给热点数据续期。

2)缓存穿透:是指访问一个Redis缓存和数据库中都不存在的数据,请求从持久层查不到数据则就无法写入缓存,而用户不断发起请求,由于缓存是不命中时被动写的,导致这个不存在的数据每次请求都要到存储层去查询,失去了Redis缓存的意义。此时,缓存起不到保护后端持久层的意义,就像被穿透了一样。导致数据库存在被打挂的风险。

在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大直至崩溃。

解决方案:

1、设置并发锁,防止大量请求压力到达数据库,如果获取到锁了,去数据库查询,如果没有,说明有其他线程在查询数据库,那么只需要重试一下就好了

2、增加对接口请求参数的校验。对请求的接口进行鉴权,数据合法性的校验等;比如查询的userId不能是负值或者包含非法字符等。

3、当数据库返回空值时,将空值缓存到redis,并设置合理的过期时间。

4、配置布隆过滤器。使用布隆过滤器存储所有可能访问的 key,不存在的 key 直接被过滤,存在的 key 则再进一步查询缓存和数据库。

布隆过滤器是防止缓存穿透的方案之一。布隆过滤器主要是解决大规模数据下不需要精确过滤的业务场景,如检查垃圾邮件地址,爬虫URL地址去重, 解决缓存穿透问题等。布隆过滤器用于在一个存在一定数量的集合中过滤一个对应的元素,判断该元素是否一定不在集合中或者可能在集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。布隆过滤器是基于bitmap和若干个hash算法实现的。如下图所示:

redis 获取hmap redis 获取serversql数据_redis 获取hmap_46

1.元素tie经过hash1,hash2,hash3运算出对应的三个值落到了数组下标为4,6,8的位置上,并将其位置的默认值0,修改成1。

2.元素niu同理落到了数组下标为1,3,4的位置上,并将其位置的默认值0,修改成1。此时bitmap中已经存储了tie,niu数据元素。当请求想通过布隆过滤器判断tie元素在程序中是否存在时,通过hash运算结果到数组对应下标位置上发现值已经都被置为1,此时返回true。

什么不使用HashMap呢?如果用HashSet或Hashmap存储的话,每一个用户ID都要存成int,占4个字节即32bit。而一个用户在bitmap中只需要1个bit,内存节省了32倍。

并且大数据量会产生大量的hash冲突,结果就是产生hash冲突的数据,仍然会进行遍历挨个比对(即使转成红黑树),这样对内存空间和查询效率的提升,仍然是有限的。如果数据量不大时,尽管使用。而且hashmap方便进行CRUD。更多参看布隆过滤器

3)缓存击穿:是指请求缓存中没有但数据库中有的数据(一般是某个热点 key缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,就同时去数据库去取数据,引起数据库压力瞬间增大,造成瞬时数据库请求量大、压力骤增,导致数据库被打挂。

解决方案:

1、设置热点数据永不过期,或者后台有线程一直给热点数据续期。
2、加互斥锁:当热点key过期后,大量的请求涌入时,只有第一个请求能获取锁并阻塞,此时该请求查询数据库,并将查询结果写入redis后释放锁。后续的请求直接走缓存。

8.2 相关知识关联

redis 获取hmap redis 获取serversql数据_数据_47


Java 常见的序列化工具空间压缩比:

redis 获取hmap redis 获取serversql数据_Redis_48

8.3 Redis CAP理论

CAP 即三个单词首字母的缩写:Consistency、Availability、Partition tolerance;CAP的3个特性,只能满足其中2个,这涉及到取舍的问题,采用一定策略来满足。我们一般认为redis为CP型的。

redis 获取hmap redis 获取serversql数据_redis运维_49

  • Consistency(一致性):强调的是数据正确且最新
  • Availability(可用性):强调的是系统返回不出错(高可用),但是返回的结果不一定是最新的
  • Partition tolerance(分区容忍性):指分布式系统中的节点被划分为多个区域,每个区域内部可以通信,但是区域之间无法通信,在分布式系统中,分区容忍性必不可少,因为需要总是假设网络是不可靠的

Redistribution不同模式下的CAP选择:

  • 单机模式:单机部署redis,保证了数据的一致性而牺牲了可用性,只是保证了用户可以看到相同的数据和当网络通信出问题是能够保证隔离的子系统能够继续运行,单机模式下master与slave之间不存在通信问题,但当master节点挂掉以后子节点便不能保证能够正常的提供服务;这种默斯和只实现了数据的一致性Consistency(一致性);
  • 哨兵模式sentinel:实现了故障自动转移,保障了高可用性,但该模式的确定也很大,它实现了数据的一致性和高可用性 Consistency(一致性)、Availability(可用性)
  • 集群模式:该模式下采用数据分片的方式存储,即采用虚拟槽分区,所有的键值根据哈希算法映射到数据槽内,每个节点负责维护一部分的槽及槽锁映射的键值数据,这使得redis由一个单纯的nosql内存数据库变为一个分布式的nosql数据库使redis具有了分区容忍性,并且实现了负载均衡,当某个节点挂了以后数据在其他节点上具有备份并且这个节点马上就可以投入使用,实现了高可用性,但也同时因为这一点redis失去了数据的强一致性;cluster模式实现了分区容忍性和高可用性 Availability(可用性)、Partition Tolerance(分区容错性)

CAP取舍策略:

redis 获取hmap redis 获取serversql数据_redis 获取hmap_50

参考文献:redis内存优化Redis 的 9 种数据结构图解;