Lxxxxxxy_
Lxxxxxxy_
Published on 2019-11-15 / 74 Visits
0

Redis

Redis

2019年10月26日15:50:32


Redis的含义

是一个key-value式的存储系统,使用ANSI C编写,可基于内存,可持久化的日志型,key-value的数据库。通常被称为数据结构服务器。

Redis的使用场景

数据库,消息总线,cache。

Redis的特性

存储结构:以字典结构存储数据,允许其他程序用TCP协议来连接读取字典中的内容,还可以对集合类型的数据进行交集并集等操作。

内存与持久化:数据主要存储在内存中,因此它的性能相比其他基于硬盘的数据库拥有更大的优势。Redis还提供了持久化功能,可以将内存中的数据存入硬盘中,同时不影响它继续提供服务。

功能丰富:可以将它作为缓存和消息队列来使用。

设置键生存时间:设置生存时间后,如果到期,则会自动删除该键值,

设置占用的内存空间:设置占用的内存空间后,如果内存空间已满,则会按照规则删除不需要的键值。

队列:可以使用Redis的list类型来实现队列,支持阻塞式读取,还支持发布/订阅模式。

RDB和AOF的区别

RDB

触发规则:

​ 1、在指定时间间隔内,执行指定次数的写操作。

​ 2、执行save命令(阻塞),或者bgsave命令(异步,不阻塞)。

​ 3、执行flushall命令,清空数据库所有数据。

​ 4、执行shutdown命令,保证服务器正常关闭而不丢失数据。

优点:

​ 1、适合大规模的数据恢复。

​ 2、如果业务对数据的完整性和一致性要求不高,可以使用RDB。

缺点:

​ 1、数据的完整性和一致性不高,因为RDB在进行最后一次备份时宕机了,导致最后一次的操作不能备份。

​ 2、备份时占用内存,RDB在备份时会单独创建一个进程,将数据写入一个临时文件中,再覆盖原来的备份文件。

AOF

采用日志的形式来记录写操作,并追加到文件中,Redis重启后会根据文件的内容将写指令从头到尾执行一次,以完成恢复数据的操作。

日志更新条件:

​ 1、always:同步持久化,每次发生数据变化会立刻写入磁盘中,性能较差但保存的完整性较好。(慢,安全)

​ 2、everysec:默认值,每秒记录一次(异步)。

​ 3、no:不同步。

AOF的重写机制:

​ 1、重写的原理:当AOF文件的大小达到设置的限制时,会对AOF文件的内容进行压缩。Redis会创建一个进程,去读取内存中的数据,并写入一个临时文件中,最后替换旧的AOF文件。

压缩的原理:
比如执行了
lpush a 1
lpush a 2
lpush a 3
lpush a 4
那么AOF文件中将会有4行信息记录a键的变化。
压缩主要是先从数据库读取a的值,然后用lpush a 1 2 3 4来保存写入操作,替换掉原来的4行。

​ 2、触发机制:当AOF文件大小是上次重写后的大小一倍且文件大小大于64M时触发。

auto-aof-rewrite-percentage 100//每个文件增长比例大小
auto-aof-rewrite-min-size 64mb//文件最大值

​ 3、如果在重写期间客户端更改了键值,那么AOF会将更改操作写入到AOF缓冲区和AOF重写缓冲区中,完成重写操作后,会将AOF重写缓冲区中的更改写入到AOF文件中,再用新的AOF文件替换旧的AOF文件。

优点:数据的完整性和一致性更高。

缺点:因为AOF文件的记录多,文件会越来越来大,数据恢复会变得慢。

使用list的阻塞

使用blpop list 0,表示阻塞式读取list中的数据,其中0表示阻塞时间限制,如果为0,表示无限制,单位为秒。如果list没有数据,则一直阻塞。
如果另外开启一个客户端,使用lpush list a b c d e,阻塞终止。

发布/订阅模式

使用subscribe value...可以实现订阅一个或多个通道

使用publish value message可以实现向某个通道发送消息

Redis的优点

快速:Redis的处理速度快,每秒能执行约11万集合,约80000多条记录。

多种数据类型:支持基本上所有的数据类型。

原子性:每个操作都是原子的,能够保证两个客户端同时访问Redis的时候能够取到最新的值。

多功能:可以用作缓存,消息中间件,队列。

Redis的缺点

单线程,耗内存。

Redis超时

通过expire key ttl设置失效。

Redis采用的定期失效+惰性失效策略。

Redis失效策略

被动触发:使用get获取key value的时候检查是否失效。

主动触发:后台每100毫秒进行10次定时任务,可以通过修改redis.conf的hz参数来修改每秒进行多少次,默认为10,是随机抽取来检查,如果有过期的key,才失效,没有检查到的话就不会失效。

使用定期失效+惰性失效策略就没问题了吗?

如果随机抽取检查没有检查到某个key,也没有使用get来获取这个key,久而久之,Redis占用的的内存会越来越大。

那么久应该采用内存淘汰机制

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错。应该没人用吧。

2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。推荐使用,目前项目在用这种。

3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。应该也没人用吧,你不删最少使用Key,去随机删。

4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key。这种情况一般是把redis既当缓存,又做持久化存储的时候才用。不推荐

5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key。依然不推荐

6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除。不推荐

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

使用Redis的场景

1、性能:碰到耗时较久或结果不会频繁改动的sql就可以使用Redis,将Redis作为缓存使用,使用户的请求迅速响应。

2、并发:在高并发情况下,所有的请求都去访问数据库,数据库会因为连接过多而异常,所以这时候需要用Redis做一个缓冲的操作,让请求先到Redis。

Redis配置文件

timeout 300:当客户端闲置多长时间后关闭连接。

loglevel notice:指定日志级别,默认是notice。

databases 16:设置数据库的数量,默认数据库为0,默认数据库数量为16。

rdbcompression yes:指定持久化时是否压缩数据,默认为yes,Redis使用LZF压缩。

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

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

requirepass password:设定Redis的连接密码,默认关闭,如果配置了连接配置密码,客户端在连接Redis时需要通过auth password命令提供密码。

maxclients 128:设置同一时间最大客户端连接数,默认无限制。如果设置0表示无限制。如果达到Redis的限制,Redis会拒绝连接并向请求连接的客户端返回max number of clients reached信息。

maxmemory:指定Redis可使用的最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的key,当此方法处理后,如果仍然达到最大内存限制,将无法进行写入操作,只能进行读操作。Redis新的vm机制,会把key放入内存,把value放入swap区。

maxmemory_policy:设置内存淘汰机制。

Redis中文显示错误

连接时使用redis-cli --raw命令。

简单动态字符串(SDS)

Redis中的键值底层都是使用SDS来实现的。没有用传统的C字符串来表示。

比如:set message "hello world"在底层中,Redis使用SDS来存储message,用SDS来存储hello world字符串。

AOF模式下的缓冲区也是使用SDS实现的。

SDS遵循C字符串以空字符结尾的惯例,目的是为了直接重用一部分C字符串函数库里面的函数。

SDS的结构体构造:

struct sdshdr{
    //记录保存字符串的长度
    int len;
    //记录buf数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[];
}

空间预分配

SDS在遇到字符数组满了的情况下,如果长度小于1M,那么程序将分配和len属性一样大小的未使用空间。

(比如某次修改之后,len变成了20字节,如果长度不够,那么SDS将自动增长20字节,最后SDS的字符串长度为20+20+1,最后的1字节用于保存空字符)。

如果长度大于等于1M,那么程序将分配1M空间。

(比如某次修改之后,len变成了30M,如果长度不够,那么SDS将自动增长1M空间,最后SDS的字符串长度为30M+1M+1byte)。

惰性空间释放

当SDS的字符串遇到缩小时,SDS不会马上将多出来的空位置释放掉,而是把free属性增大。如果将来要对SDS进行增加操作时,这些多出来的位置就能派上用场。

Redis高可用

在Redis中,实现高可用的技术主要包括持久化,复制,哨兵和集群。

​ 1、持久化:持久化是最简单的高可用方法,主要作用是数据备份,保证数据不会因为进程退出而丢失。

​ 2、复制:复制是高可用Redis的基础,哨兵和集群都是在复制的基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。

​ 复制的缺陷:

​ 1、故障恢复无法自动化。

​ 2、写操作无法负载均衡。

​ 3、存储能力受单机的限制。

​ 3、哨兵:在复制的基础上,实现了自动化的故障恢复。

​ 哨兵的缺陷:

​ 1、写操作无法负载均衡。

​ 2、存储能力受单机的限制。

​ 4、集群:通过集群,能够解决写操作无法负载均衡以及存储能力受单机的限制的问题,是目前较为完善的高可用方案。

复制

主从复制

主从复制只能从主复制到从。

特点:

​ 1、数据冗余:实现了数据的热备份,主从的一致性高。

​ 2、故障恢复:主宕机后可以继续使用从继续提供服务,实现数据的快速恢复。

​ 3、负载均衡:使用读写分离,由主提供写服务,从提供读服务,从而分担服务器的负载,在读少写多的情况下,可以使用多个从节点,可以大幅度提高Redis的并发量。

​ 4、高可用基石:主从复制是哨兵和集群能够使用的基础。

全量复制和部分复制

全量复制:

​ 1、主节点在接收到了从节点发送的全量复制命令后,执行bgsave命令,单独进行一个进程生成RDB文件,并使用一个复制缓冲区记录从现在开始执行的所有写命令。

​ 2、主节点的bgsave命令完成后,把RDB文件发送给从节点,从节点首先清除自己的数据,然后载入接收到的RDB文件,将数据库更新至主节点执行bgsave命令时的状态。

​ 3、主节点将复制缓冲区的所有写命令发送给从节点,从节点执行这些写命令,将主从保持一致性。

​ 4、如果从节点开启了AOF,那么会执行bgrewriteaof命令,将所有写操作同步至AOF文件中。

ps:

​ 1、主节点执行bgsave命令fork一个子进程进行数据的持久化,该过程非常消耗CPU、内存和硬盘IO。

​ 2、主节点通过网络将RDB文件发送给从节点,该操作也十分消耗主从节点的带宽。

​ 3、从节点清空数据,载入RDB文件,这个操作是阻塞的,无法响应客户端的命令。

​ 4、从节点执行bgrewriteaof命令也十分消耗系统资源。


部分复制:

​ 1、复制偏移量

​ 主节点和从节点分别维护一个复制偏移量,这个值表示的是主节点向从节点传输的字节数,主节点向从节点发送了N个字节数时,主节点的offset+N,从节点接收到主节点发送的字节数,从节点的offset+N。

​ offset用来判断主从的数据库状态是否一致,如果二者的offset相同,则一致,反之不一致。不一致时可以根据两个offset找到从节点缺少的那部分数据,比如主节点的offset是1000,从节点的offset是500,那么部分复制就将offset为501-1000的数据发送给从节点,而501-1000的数据的存储位置就是复制积压缓冲区。

​ 2、复制积压缓冲区

​ 复制积压缓冲区是由主节点维护的,固定长度的,先进先出的队列,默认大小是1M。当主节点开始有从节点的时候就创建,其作用是备份主节点最近发送给从节点的数据。ps:无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。

​ 在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区还会存储复制偏移量(offset),由于复制积压缓冲区的大小固定,且是先进先出的队列,所以它保存的是主节点最近的执行的写命令,时间较早的命令会被挤出队列。

​ 由于复制积压缓冲区的容量有限,因此它可以备份的写命令也有限,当主从节点的offset相差过大时,将无法进行部分复制,只能进行全量复制。因此,为了提高在网络中断时部分复制执行的效率,可以根据需要设置复制积压缓冲区的大小(repl-backlog-size)。比如,网络中断的平均时间是60s,而主节点每秒产生的写命令所占的大小是100kb,则复制积压缓冲区的大小应该设置为60*100,约等于6000kb(6M),保险起见,可以设置12M。

​ 从节点将它的offset发送给主节点之后,主节点根据从节点offset的值和缓冲区的大小来判断是否能执行部分复制。

​ 1、如果复制偏移量offset之后的数据还在复制积压缓冲区里,则执行部分复制。

​ 2、如果复制偏移量offset之后的数据已经被复制积压缓冲区挤出,则执行全量复制。


​ 3、服务器运行id

​ 每个主从节点在启动时都会生成一个随机ID(runid),由40位16进制字符组成,runid用来识别唯一一个Redis节点。

​ 主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来,当断线重连时,从节点会把这个runid发送给主节点,主节点根据runid来判断能否部分复制。

​ 1、如果保存的runid和主节点的runid相同,说明主从之前已经同步过,是否再次使用部分复制取决于从节点的复制偏移量offset和复制积压缓冲区。

​ 2、如果保存的runid和主节点的runid不同,说明从节点断线前同步的主节点不是当前的主节点,只能进行全量复制。


PSYNC进行全量复制和部分复制可能会遇到的情况

QQ截图20191101165953

从服务器发送PING命令时可能遇到的情况

QQ截图20191101170018

从服务器进行身份验证时可能遇到的情况

QQ截图20191101170024

心跳检测

在命令传播阶段(将复制积压缓冲区的写命令发送到从服务器执行),从服务器会以每秒一次的频率,向主服务器发送命令(REPLCONF ACK offset)。

发送这个命令主要有三个作用:

​ 1、检测主从服务器的连接状态

​ 如果主服务器超过一秒钟没有接收到从服务器发来的REPLCONF ACK命令,那么主服务器就知道主从服务器之间的连接出了问题。

​ 2、辅助实现min-slaves选项

​ Redis的min-slaves-to-write和min-slaves-max-lag两个选项可以防止主服务器不安全的情况下执行写命令,比如我们向主服务器提供以下配置

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

​ 那么在从服务器的数量小于3个,或者三个从服务器的延迟值都大于或等于10秒时,主服务器将拒绝执行写命令。

​ 3、检测命令丢失

​ 如果因为网络故障,主服务器传播给从服务器的写命令在半路丢失,那么从服务器向主服务器发送该命令时,主服务器会发觉从服务器当前的复制偏移量小于自己的复制偏移量,然后主服务器就会根据从服务器提交的复制偏移量,在复制积压缓冲区里找到从服务器丢失的数据,并将这些数据重新发送给从服务器。


哨兵(Sentinel)

Sentinel是Redis的高可用性的解决方案:由一个或多个Sentinel实例组成的Sentinel系统,可以监视任意多个主服务器,以及这些主服务器下面的所有从服务器。当监视的主服务器进入下线状态时并且下线时长超过用户设定的下线时长上限时,自动将下线的主服务器下面的某个从服务器升级为主服务器,当主服务器再次上线时,自动降级成它的从服务器。

集群

集群是一个可以再多个Redis节点之间进行数据共享的设施。

集群不支持同时处理多个键的Redis命令,因为执行这些命令会在多个Redis节点之间移动数据,在高负载的情况下,这些命令会降低集群的性能,并导致不可预知的错误。

集群通过分区来提供一定程度的可用性:即使集群有一部分节点失效,集群也可以处理这些请求。

集群的优点:

​ 1、将数据自动划分到多个节点。

​ 2、当集群中的一部分节点失效,仍然可以处理请求。

为什么要用16384个哈希槽

QQ截图20191101175320

分片

分片的作用

​ 1、允许使用更多的电脑的内存总和来支持更大的数据库,不使用分片则只能使用单机能支持的内存容量。

​ 2、允许将计算能力拓展到多台计算机上,并将网络带宽拓展到多台计算机和网络适配器上。

分片标准

比如,现在有4个Redis实例:r1,r2,r3,r4,代表用户键的名称:user:1,user:2。

​ 1、范围分片:让ID范围为0-1000的存储在r1上,1001-2000的存储在r2上,2001-3000的存储在r3上,3001-4000的存储在r4上。

​ 2、哈希分片:

​ 一个集群有16384个哈希槽,数据库的每个键都属于这16384个哈希槽的其中一个,集群利用公式CRC16(KEY) % 16384来计算当前key属于哪个槽,这个命令用于计算key的CRC16校验和。

​ 集群中的每个节点负责一部分哈希槽,比如,一个集群有三个哈希槽,其中:

​ 节点A处理0-5500

​ 节点B处理5501-11000

​ 节点C处理11001-16384

我现在想设置一个key,叫my_name:

set my_name zhangguoji

按照Redis Cluster的哈希槽算法,CRC16('my_name')%16384 = 2412 那么这个key就被分配到了节点A上
同样的,当我连接(A,B,C)的任意一个节点想获取my_name这个key,都会转到节点A上

分片标准的不同实现

1、客户端分片:由客户端选择正确的节点进行写操作或读取一个键。

2、代理辅助分片:客户端将请求发送给一个支持Redis协议的代理,而不是将请求发送给Redis实例。代理收到后将根据已经配置好的分区配置将请求发送给正确的Redis实例,之后将Redis实例的返回的值发送给客户端。

3、请求路由:将请求发送给任意一个Redis实例,这个实例确认后把这个请求发送给正确的Redis实例。

分片的缺点

1、涉及到多个键的命令不支持。因为执行这些命令会在多个Redis节点之间移动数据,在高负载的情况下,这些命令会降低集群的性能,并导致不可预知的错误。

2、无法使用涉及到多个键的事务。

3、分片的粒度是键,因此无法将一个很大的集合这样的大键分片到多个实例上。

4、使用了分片后,数据处理变得复杂,比如,不得不处理多个RDB/AOF文件,并且为了备份数据,需要将多个实例上的持久化文件进行组合。

其他

6、redis和数据库双写一致性问题

分析:一致性问题是分布式常见问题,还可以再分为最终一致性和强一致性。数据库和缓存双写,就必然会存在不一致的问题。答这个问题,先明白一个前提。就是如果对数据有强一致性要求,不能放缓存。我们所做的一切,只能保证最终一致性。另外,我们所做的方案其实从根本上来说,只能说降低不一致发生的概率,无法完全避免。因此,有强一致性要求的数据,不能放缓存。

回答:《分布式之数据库和缓存双写一致性方案解析》给出了详细的分析,在这里简单的说一说。首先,采取正确更新策略,先更新数据库,再删缓存。其次,因为可能存在删除缓存失败的问题,提供一个补偿措施即可,例如利用消息队列。

7、如何应对缓存穿透和缓存雪崩问题

分析:这两个问题,说句实在话,一般中小型传统软件企业,很难碰到这个问题。如果有大并发的项目,流量有几百万左右。这两个问题一定要深刻考虑。

回答:如下所示

缓存穿透,即黑客故意去请求缓存中不存在的数据,导致所有的请求都怼到数据库上,从而数据库连接异常。

解决方案:

(一)利用互斥锁,缓存失效的时候,先去获得锁,得到锁了,再去请求数据库。没得到锁,则休眠一段时间重试

(二)采用异步更新策略,无论key是否取到值,都直接返回。value值中维护一个缓存失效时间,缓存如果过期,异步起一个线程去读数据库,更新缓存。需要做缓存预热(项目启动前,先加载缓存)操作。

(三)提供一个能迅速判断请求是否有效的拦截机制,比如,利用布隆过滤器,内部维护一系列合法有效的key。迅速判断出,请求所携带的Key是否合法有效。如果不合法,则直接返回。

缓存雪崩,即缓存同一时间大面积的失效,这个时候又来了一波请求,结果请求都怼到数据库上,从而导致数据库连接异常。

解决方案:

(一)给缓存的失效时间,加上一个随机值,避免集体失效。

(二)使用互斥锁,但是该方案吞吐量明显下降了。

(三)双缓存。我们有两个缓存,缓存A和缓存B。缓存A的失效时间为20分钟,缓存B不设失效时间。自己做缓存预热操作。然后细分以下几个小点

I 从缓存A读数据库,有则直接返回
II A没有数据,直接从B读数据,直接返回,并且异步启动一个更新线程。
III 更新线程同时更新缓存A和缓存B。
8、如何解决redis的并发竞争key问题

分析:这个问题大致就是,同时有多个子系统去set一个key。这个时候要注意什么呢?大家思考过么。需要说明一下,博主提前百度了一下,发现答案基本都是推荐用redis事务机制。博主不推荐使用redis的事务机制。因为我们的生产环境,基本都是redis集群环境,做了数据分片操作。你一个事务中有涉及到多个key操作的时候,这多个key不一定都存储在同一个redis-server上。因此,redis的事务机制,十分鸡肋。

回答:如下所示

(1)如果对这个key操作,不要求顺序

这种情况下,准备一个分布式锁,大家去抢锁,抢到锁就做set操作即可,比较简单。

(2)如果对这个key操作,要求顺序

假设有一个key1,系统A需要将key1设置为valueA,系统B需要将key1设置为valueB,系统C需要将key1设置为valueC.

期望按照key1的value值按照 valueA-->valueB-->valueC的顺序变化。这种时候我们在数据写入数据库的时候,需要保存一个时间戳。假设时间戳如下

系统A key 1 {valueA 3:00}
系统B key 1 {valueB 3:05}
系统C key 1 {valueC 3:10}
那么,假设这会系统B先抢到锁,将key1设置为{valueB 3:05}。接下来系统A抢到锁,发现自己的valueA的时间戳早于缓存中的时间戳,那就不做set操作了。以此类推。

其他方法,比如利用队列,将set方法变成串行访问也可以。总之,灵活变通。

问题解决

redis单点故障的解决方案

1、使用哨兵,宕机之后方便迅速切换。

2、使用类似于@Cacheable注解的功能,查询数据库之前先看是否有缓存(宕机之后没有),直接查数据库。

3、使用Apollo等配置中心,在宕机之后方便迅速切换临时的Redis服务器。Apollo教程

使用云端Redis的优缺点

好处:

优点:

​ 1、部署快。

​ 2、性能可靠。

​ 3、成本低。

​ 4、安全性高,有专门的防DDOS攻击。

​ 5、无人工成本。

缺点:

​ 1、隐私安全问题。

​ 2、数据丢失风险。

​ 3、只能在服务商提供好的基础上再进行定制。

集群理论上最多可以有多少个节点

因为集群最多可以有16384个哈希槽,而哈希槽是均匀分配,所以最多可以有16384个节点。