目录
缓存的概念:
缓存击穿:
对于一些设置了过期事件的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。缓存在这个时间点过期的时候,恰好这个时间点对于这个key有大量的并发请求过来,该key又没有了,大量请求穿透到数据库服务器。
解决方案:
对于热点数据,要慎重过期时间的考虑,确保热点期间key不过期,甚至有些可以设置永不过期。
使用互斥锁(比如java的多线程锁机制),第一个线程访问key的时候就会被锁住,等查询数据库返回后,把值插入到缓存后再释放锁。
缓存雪崩:
大量的key设置了相同的过期时间,导致缓存在同一时刻全部失效,造成瞬时DB请求量大、压力骤增,引起雪崩,如果缓存服务器宕机,也会造成缓存雪崩。
解决方案:
不同的key设置不同的过期时间(随机值),让缓存失效的时间点尽量均匀。
数据预热,通过缓存 reload 机制,预选去更新缓存,再即将发生大并发访问前手动触发加载缓存不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
使用高可用的分布式缓存集群,确保缓存的高可用性(做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时可以去访问A2)。
缓存穿透:
访问一个不一定存在的key,缓存起不到作用,请求会穿透到DB,流量大时DB会挂掉。
解决方案:
在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
缓存空值或者默认值,当我们线上业务发现缓存穿透的现象时,可以针对查询的数据,在缓存中设置一个空值或者默认值,这样后续请求就可以从缓存中读取到空值或者默认值,返回给应用,而不会继续查询数据库。
布隆过滤器:使用一个足够大的bitmap,用于存储可能访问的key,不存在的key直接被过滤掉,避免对底层数据存储系统照成压力,访问key未在DB查询到值也将空值写进缓存,但是可以设置较小的过期时间。
缓存一致性:
当数据时效性要求比较高时,需要保证缓存中的数据与数据库中的保持一致,需要保证缓存节点和副本中的数据也保持一致,需要进行集群同步,不能出现差异现象。
原因:
对同一个数据进行读写,在数据库层面并发的读写并不能保证顺序。
发生了写请求A,A的第一步淘汰了cache,A的第二步写数据库,发出修改请求。
发生了读请求B,B的第一步读取了cache,发现cache是空的,B的第二步读取数据库,发出读取请求。
如果A的第二部写数据还没完成,B就会读出了一个脏数据入了cache。
解决方案:
Redis:
简介:
是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存、消息中间件。它支持多种类型的数据结构,如字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets与范围查询,bitmaps,hyperlogs和地理空间geospatial索引半径查询。Redis内置了复制replication,LUA脚本Lua scripting,LRU驱动事件LRU eviction,事务transactions和不同级别的磁盘持久化persistence,并通过Redis哨兵sentinel和自动分区cluster提高高可用性high availabllity。
LRU:
内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做 LRU,操作系统会根据哪些数据属于 LRU 而将其移除内存而腾出空间来加载另外的数据。
支持的数据类型:
string、hash、list、set、sorted set
Redis常见场景:
Redis HyperLogLog是用来做基数统计的算法,HyperLogLog的优点是在输入元素的数量或者体积非常大时,计算基数所需的空间总是固定的、很小的。在Redis中,每个HyperLogLog键只需要花费12kb内存,就可以计算接近2^64个不同元素的基数,这和计算基数时,元素越多,耗费内存越多的集合形成鲜明对比。
GEO功能在Redis3.2版本时提供,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能,geo的数据类型为zset。
Redis字符串操作命令:
操作Key:
keys查询是否存在指定的key,keys a*模糊查找a开头的key。
操作String:
操作hash:
操作list:
lrange获取list类型数据start起始下标,end结束下标,包含关系。
操作set:
操作Sorted set:
sorted set是通过分数值来进行排序的,分数值越大越靠后。
zadd需要将Float或者Double类型分数值参数放置到值参数之前。
zrevrange例如zrevrange douyuhours 0 2,拿出榜单中的前三。
操作namespace:
例如mset user:01 zhangsan,这个user就是命名空间。
操作失效时间:
Redis有四个不同的命令可以用于设置键的生存时间(键可以存活多久)或过期时间(键什么时候会被删除)。
①EXPIRE<key> <ttl>:用于将键值key的生存时间设置为ttl秒。
②PEXPIRE<key> <ttl>:用于将键key的生存时间设置为ttl毫秒。
③EXPIREAT<key> <ttl>:用于将键key的过期时间设置为timestamp所指定的秒数时间戳。
④PEXPIREAT<key> <ttl>:用于将键key的过期时间设置为timestamp所指定的豪秒数时间戳。
TTL:获取的值未-1说明此key没有设置有效期,当前为-2证明过了有效期了。
删除指定类型数据的值:
LRU算法动态删除不用的数据:
allkeys-lru:查询所有的key,对最近最不常使用的数据进行删除,这是应用最广泛的策略。
volatile-lru:在设定超时时间的数据中,删除最不常使用的数据。
volatile-ttl:查询全部设定超时时间的数据,之后排序,将马上要过期的数据进行删除操作。
volatile-random:在已经设定了超时的数据中随机删除。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键。
allkeys-random:查询所有的key,之后随机删除。
noeviction:如果设置为该属性,则不会进行删除操作,如果内存溢出则报错返回。
Redis的事务机制:
Redis事务是一个单独的隔离操作,事务中的所有命令都会序列化、按顺序地i执行。事务在执行过程中,不会被其他客户端发送来的命令请求所打断。
Multi、Exec、discard:
输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行。
输入Exec后Redis会将之前的命令队列中的命令依次执行。
事务的错误处理:
组队阶段某个命令出现了报告错误,执行时整个的所有队列都会被取消。
执行阶段某个命令抛错,只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
事务锁机制:
Redis就是利用这种check-and-set机制实现事务的:WATCH的键会被监视,并会发觉这些键是否被改动过了,如果有至少一个被监视的键在EXEC执行之前被修改了,那么整个事务都会被取消,EXEC返回空多条批量回复(null multi-bulk reply)来表示事务已经失败。
Redis的数据持久化:
它是一个内存数据库,数据保存在内存中,虽然内存的数据读取速度快,但是也很容易丢失。Redis提供了持久化机制,分别是RDB(Redis Database)和AOF(Append only File)。
RDB:
RDB持久化可以在指定的时间间隔内生成数据集的时间点快照,这是默认的持久化方式。这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
执行bgsave命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
Redis进程执行fork操作创建子进程,RDB持久化过程由子进程负责,完成后自动结束,阻塞只发生在fork阶段,一般时间很短。
默认为yes,当启用了RDB且最后依次后台保存数据失败,Redis是否停止接受数据。
默认值是yes,对于存储到磁盘中的快照,可以设置是否进行压缩存储。
默认值是yes,在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但这样会增加大约10%的性能消耗。
设置快照的文件存放路径,这个配置项一定是一个目录,而不是一个文件名。默认是./。
生成RDB文件的适合,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
当进行快照持久化时,会开启一个子进程专门负责快照持久化,子进程会拥有父进程的内存数据。
所以在快照持久化期间,修改的数据不会被保存,可能会丢失数据。
AOF(Append Only File):
全量备份总是耗时的,有时候我们提供一种更加高效的方式AOF,工作机制很简单,Redis会将每一个收到的命令都通过write函数追加到文件中,通俗的理解就是日志记录。
重写日志,减少日志文件的大小,Redis提供了bgrewriteaof命令。
将内存中的数据以命令的方式保存到临时文件中,同时会fork出一个新进程来将文件重写。
将整个内存中的数据库内容以命令的方式重新了一个新的aof文件。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
同步持久化,每次发生数据变更会被记录到磁盘,性能较差但数据完整性比较好。
AOF可以更好的保护数据不丢失,一般AOF会每隔1秒通过一个后台线程执行依次fsync,最多丢失1秒的数据。
AOF日志文件没有任何磁盘寻址的开销,写入性能非常高,文件不容易破损。
AOF日志文件即使过大时,出现后台重新操作,也不会影响客户端的读写。
AOF日志文件的命令通过非常刻度的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复。
对于同一份数据来讲,AOF日志文件通常比RDB数据快照文件更大。
AOF支持的写QPS(Queries Per Second,每秒查询率)会比RDB支持的写QPS低。
RDB与AOF的选择:
如果同时使用了AOF和RDB,那么启动时以AOF为恢复数据的模板。
Redis的数据同步机制:
Redis的主从结构可以才用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步,主从刚刚连接的时候,进行全量同步,全同步结束后进行增量同步。
全量同步:
1、Redis全量复制一般发生在Slave的初始化阶段,这时Slave需要将Master上的所有数据都复制一份。
3、主服务器接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件并使用缓冲记录此后执行的所有写命令。
4、主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间记录被执行的写命令。
6、主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令。
7、从服务器完成对快照的载入,开始接收命令请求,并执行来至主服务器缓冲区的写命令。
增量同步:
Redis增量复制是指SLave初始化后开始正常工作时主服务器发送的写操作同步到从服务器的过程。
增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的命令,从服务器接收并执行收到的写命令。
主从复制的异步特性:
主从复制对于主Redis服务器来说是非阻塞的,这意味着当从服务器在进行主从复制同步过程中,主Redis任然可以处理外界的访问请求。
主从复制对于从Redis服务器来说是非阻塞的,意味着即使从Redis在进行主从复制过程中也可以接受外界的查询请求,只不过这时候从Redis返回的是以前老的数据。
服务器断线重连:
Redis2.8开始,如果遭遇连接断开,重新连接后可以从中断处继续进行复制,而不必重新同步。
部分同步的实现依赖于在master服务器内存中给每个slave服务器维护了一份同步日志和同步标识。
每个slave服务器在跟master服务器进行同步时都会携带自己的同步标识和上次同步的最后位置。
当主从连接断开之后,slave服务器隔段时间(默认是1s)主动尝试和master服务器进行连接。
如果从服务器携带的偏移量标识还在master服务器上的同步备份的日志中,那么就从slave发送的偏移量开始继续上次的同步操作,如果slave发送的偏移量以及不在master的同步备份日志中,则必须进行一次全量更新。
Redis哨兵:
Redis的主从复制模式下,一旦主节点由于故障不能提供故服务,需要手动将从节点晋升为主节点,同事还要通知客户端更新主节点地址。Sentine哨兵是Redis官方提供的高可用方案,可以用它来监控多个Redis服务实例的运行情况。
哨兵功能与作用:
Sentinel会不断地检查主服务器和从服务器是否运作正常。
当被监控地某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,将失效主服务器地其中一个从服务器升级为新主服务器,并让失效主服务器地其他从服务器改为复制新地主服务器,当客户端试图连接失效地主服务器时,集群也会向客户端返回新主服务器地地址,使得集群可以使用新主服务器代替失效的服务器。
哨兵的工作原理:
1、在Redis Sentinel中,一共有3个定时任务,通过这些任务,来发现新增节点和节点的状态。
每10秒每个sentinel节点对master节点和salve节点执行info操作。
每2秒每个sentinel节点通过master节点的channel(sentinel:hello)交换信息。
每1秒每个sentinel节点对master节点和slave节点以及其余的sentinel节点执行ping操作。
2、主观下线(SDOWN):当前sentinel节点认为某个redis节点不可用。
如果一个实例(instance)距离最后一次有效回复PING命令的时间超过down-after-milliseconds所指定的值,那么这个实例会被sentinel标记为主观下线。
如果一个主服务器被标记为主观下线,那么正在监视这个主服务器的所有sentinel节点,要以每秒一次的频率确认主服务器的确进入了主观下线状态。
3、客观下线(ODOWN)一定数量sentinel节点认为某个redis节点不可用。
如果一个主服务被标记为主观下线,并且有足够数量的sentinel(至少要达到配置文件指定的数量)在指定的时间范围内同意这一判断,那么这个主服务器被标记为客观下线。
在一般情况下,每个sentinel会以10秒每次的频率,向它已知的所有主服务器和从服务器发送INFO命令,当一个主服务器被sentinel标记为客观下线时,sentinel向下线主服务器的所有从服务器发送INFO命令的频率会从10秒改为1秒一次。
sentinel和其他sentinel协商主节点的状态,如果主节点处于ODOWN状态,则投票自动选出新的主节点,将剩余的从节点之乡新的主节点进行数据复制。
4、当没有足够数量的sentinel同意主服务器下线时,主服务器的客观下线状态会被移除,当主服务器重更新向sentinel的PING命令返回有效回复时,主服务器的主观下线状态就会被移除。
故障转移流程:
每个做主观下线的sentinel节点向其他sentinel节点发送上面那条命令,要求将它设置为领导者。
收到命令的sentinel节点如果还没同意过其他的sentinel发送的命令(还未投过票),那么就会同意,否则拒绝。
如果改sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者。
如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举。
选举出新的master节点,其余的节变更为新的master节点的slave节点。
原有的master节点重新上线,成为新的master节点的slave节点。
当所有节点配置结束后,sentinel会通知客户端节点变更信息。
Redis的高可用:
在Redis中,实现高可用的技术主要包括持久化、复制、哨兵、集群。
持久化:是最简单的高可用方法,它的主要作用是数据备份,即将数据存储在磁盘,保证数据不会因进程退出而丢失。
复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用。复制主要实现了数据的多机备份以及对于读操作的负载均衡和简单的故障恢复。缺陷是故障恢复无法自动化,写操作无法负载均衡、存储能力受到单机的限制。
哨兵:在复制的基础上,哨兵实现了自动化的故障恢复,缺陷是写操作无法负载均衡,存储能力收到单机的限制。
集群:通过集群,Redis解决了写操作无法负载均衡以及存储能力受到单机限制的问题,实现了较为晚上的高可用方案。
一致性Hash:
算法原理:
整个空间按顺时针防线组织,圆环的正上方的点代表0,0点右侧的第一个点代表1,以此类推,2、3、...、直到2^32-1。
将各个服务使用Hash进行也给哈希,这样每台服务器就能确定其在哈希环上的位置。
数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置。
沿环顺时针“行走”,第一台遇到的服务器就是数据应该定位到的服务器。
算法容错性:
当我们添加服务器或者删除服务器,它只影响处理节点的下一个节点。
数据倾斜与虚拟节点:
均匀一致性hash的目标是,如果服务器有N台,客户端的hash值有M个,那么每个服务器应该处理大概M/N个用户的。也就是每台服务器负载尽量均衡。
Redis的Slot槽:
Redis集群使用数据分片(sharding)而非一致性哈希(consistencyhashing)来实现。
一个Redis集群包含16384个哈希槽,数据库中的每个键都属于这16384个哈希槽的其中一个,集群使用公式CRC16(key)%16384来计算键key属于哪个槽,其中CRC16(key)用于计算键的CRC16校验和。
将一个哈希槽从一个节点移动到另一个节点不会照成节点阻塞,所以无论是添加新节点还是移除已有节点,又或者改变某个节点包含的哈希槽数量,都不会照成集群下线。
对象保存到Redis之前先经过CRC16哈希到一个指定的Node上。
每个Node被平均分配了一个Slot段,对应着0-16383,Slot不能重复也不能缺失,否则会导致对象重复存储或者无法存储。
Node之间也互相监听,一旦有Node退出或者加入,会按照Slot为单位做数据的迁移,例如Node1忽然掉线了,那么0-5640这些slot会平均分摊到Node2和Node3上。
将Redis的写操作分摊到了多个节点上,提高写的并发能力,扩容简单。
每个Node承担着互相监听、高并发数据写入、高并发数据读出,导致工作任务繁重。
额外补充点:
RedLock:
RedLock是Redis官方站提出了一种权威的基于 Redis 实现分布式锁的方式名叫 Redlock,此种方式比原先的单节点的方法更安全。它可以保证以下特性:
避免死锁:最终Client都可能拿到锁,不会出现死锁的情况,即使原本锁住某资源的Client Crash 了或者出现了网络分区。