自己随便记的,比较乱。
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster
最重要的意思就是:
Redis是一种开放源代码(BSD许可)的内存中数据结构存储,用作数据库,缓存和消息代理。我们常用他作为缓存。Redis是单线程的
所以我们使用redis做缓存
在前面学习我们都知道Redis不可能把所有的数据都缓存起来(内存昂贵且有限),所以Redis需要对数据设置过期时间,并采用的是惰性删除+定期删除两种策略对过期键删除。Redis对过期键的策略+持久化
如果缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。
这就是缓存雪崩:
缓存雪崩如果发生了,很可能就把我们的数据库搞垮,导致整个服务瘫痪!
对于“对缓存数据设置相同的过期时间,导致某段时间内缓存失效,请求全部走数据库。”这种情况,非常好解决:
对于“Redis挂掉了,请求全部走数据库”这种情况,我们可以有以下的思路:
缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
解决缓存穿透也有两种方案:
由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层!
当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。
如果仅仅查询的话,缓存的数据和数据库的数据是没问题的。但是,当我们要更新时候呢?各种情况很可能就造成数据库和缓存的数据不一致了。
从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。因为只要缓存数据过期了,就会被删除。随后读的时候,因为缓存里没有,就可以查数据库的数据,然后将数据库查出来的数据写入到缓存中。
除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。
操作缓存也有两种方案:
一般我们都是采取删除缓存缓存策略的,原因如下:
基于这两点,对于缓存在更新时而言,都是建议执行删除操作!
正常的情况是这样的:
如果原子性被破坏了:
如果在高并发的场景下,出现数据库与缓存数据不一致的概率特别低,也不是没有:
要达成上述情况,还是说一句概率特别低:
因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。
对于这种策略,其实是一种设计模式:Cache Aside Pattern
删除缓存失败的解决思路:
正常情况是这样的:
如果原子性被破坏了:
看起来是很美好,但是我们在并发场景下分析一下,就知道还是有问题的了:
所以也会导致数据库和缓存不一致的问题。
并发下解决数据库与缓存不一致的思路:
我们可以发现,两种策略各自有优缺点:
先删除缓存,再更新数据库
先更新数据库,再删除缓存(Cache Aside Pattern
设计模式)
删除策略可分为三种
定时删除(对内存友好,对CPU不友好)
惰性删除(对CPU极度友好,对内存极度不友好)
定期删除(折中)
Redis采用的是惰性删除+定期删除两种策略,所以说,在Redis里边如果过期键到了过期的时间了,未必被立马删除的!
如果定期删除漏掉了很多过期key,也没及时去查(没走惰性删除),大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?
我们可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。
Redis的内存淘汰机制有以下几种:
一般场景:
使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用allkeys-lru淘汰策略,将最近最少使用的数据淘汰
Redis是基于内存的,如果不想办法将数据保存在硬盘上,一旦Redis重启(退出/故障),内存的数据将会全部丢失。
Redis提供了两种不同的持久化方法来讲数据存储到硬盘里边:
Redis服务器是一个事件驱动程序,主要处理以下两类事件:
Redis开发了自己的网络事件处理器,这个处理器被称为文件事件处理器。
文件事件处理器由四部分组成:
文件事件处理器使用I/O多路复用程序来同时监听多个Socket。当被监听的Socket准备好执行连接应答(accept)、读取(read)等等操作时,与操作相对应的文件事件就会产生,根据文件事件来为Socket关联对应的事件处理器,从而实现功能。
要值得注意的是:Redis中的I/O多路复用程序会将所有产生事件的Socket放到一个队列里边,然后通过这个队列以有序、同步、每次一个Socket的方式向文件事件分派器传送套接字。也就是说:当上一个Socket处理完毕后,I/O多路复用程序才会向文件事件分派器传送下一个Socket。
首先,IO多路复用程序首先会监听着Socket的AE_READABLE
事件,该事件对应着连接应答处理器
SocketServet.accpet()
此时,一个名字叫做3y的Socket要连接服务器啦。服务器会用连接应答处理器处理。创建出客户端的Socket,并将客户端的Socket与命令请求处理器进行关联,使得客户端可以向服务器发送命令请求。
Socket s = ss.accept();
,创建出客户端的Socket,然后将该Socket关联命令请求处理器客户端请求连接,服务器创建出客户端Scoket,关联命令请求处理器
假设现在客户端发送一个命令请求set Java3y "关注、点赞、评论"
,客户端Socket将产生AE_READABLE
事件,引发命令请求处理器执行。处理器读取客户端的命令内容,然后传给对应的程序去执行。
客户端发送完命令请求后,服务端总得给客户端回应的。此时服务端会将客户端的Scoket的AE_WRITABLE
事件与命令回复处理器关联。
客户端的Scoket的AE_WRITABLE事件与命令回复处理器关联
最后客户端尝试读取命令回复时,客户端Socket产生AE_WRITABLE事件,触发命令回复处理器执行。当把所有的回复数据写入到Socket之后,服务器就会解除客户端Socket的AE_WRITABLE事件与命令回复处理器的关联。
最后以《Redis设计与实现》的一张图来概括:
持续运行的Redis服务器会定期对自身的资源和状态进行检查和调整,这些定期的操作由serverCron函数负责执行,它的主要工作包括:
Redis服务器将时间事件放在一个链表中,当时间事件执行器运行时,会遍历整个链表。时间事件包括:
在《Redis设计与实现》中各用了一章节来写客户端与服务器,我看完觉得比较底层的东西,也很难记得住,所以我决定总结一下比较重要的知识。如果以后真的遇到了,再来补坑~
服务器使用clints链表连接多个客户端状态,新添加的客户端状态会被放到链表的末尾
客户端章节中主要讲解了Redis客户端的属性(客户端状态、输入/输出缓冲区、命令参数、命令函数等等)
typedef struct redisClient{
//客户端状态的输入缓冲区用于保存客户端发送的命令请求,最大1GB,否则服务器将关闭这个客户端
sds querybuf;
//负责记录argv数组的长度。
int argc;
// 命令的参数
robj **argv;
// 客户端要执行命令的实现函数
struct redisCommand *cmd, *lastcmd;
//记录了客户端的角色(role),以及客户端所处的状态。 (REDIS_SLAVE | REDIS_MONITOR | REDIS_MULTI)
int flags;
//记录客户端是否通过了身份验证
int authenticated;
//时间相关的属性
time_t ctime; /* Client creation time */
time_t lastinteraction; /* time of the last interaction, used for timeout */
time_t obuf_soft_limit_reached_time;
//固定大小的缓冲区用于保存那些长度比较小的回复
/* Response buffer */
int bufpos;
char buf[REDIS_REPLY_CHUNK_BYTES];
//可变大小的缓冲区用于保存那些长度比较大的回复
list *reply; //可变大小缓冲区由reply 链表和一个或多个字符串对象组成
//...
}
服务器章节中主要讲解了Redis服务器读取客户端发送过来的命令是如何解析,以及初始化的过程。
服务器从启动到能够处理客户端的命令请求需要执行以下的步骤:
总的来说是这样子的:
def main():
init_server();
while server_is_not_shutdown();
aeProcessEvents()
clean_server();
从客户端发送命令道完成主要包括的步骤:
因为Redis如果只有一台服务器的话,那随着请求越来越多:
显然,出现的上述问题是因为一台Redis服务器不够,所以多搞几台Redis服务器就可以了
主从架构特点:
为了保证数据的一致性:
同步(sync)
命令传播(command propagate)
从服务器对主服务器的同步又可以分为两种情况:
哨兵(Sentinel)机制主要用于实现Redis的高可用性,主要的功能如下:
Monitoring. Sentinel constantly checks if your master and slave instances are working as expected.
Notification. Sentinel can notify the system administrator, another computer programs, via an API, that something is wrong with one of the monitored Redis instances.
Automatic failover. If a master is not working as expected, Sentinel can start a failover process where a slave is promoted to master, the other additional slaves are reconfigured to use the new master, and the applications using the Redis server informed about the new address to use when connecting.
Configuration provider. Sentinel acts as a source of authority for clients service discovery: clients connect to Sentinels in order to ask for the address of the current Redis master responsible for a given service. If a failover occurs, Sentinels will report the new address.
丢失数据有两种情况:
异步复制导致的数据丢失
脑裂导致的数据丢失
丢失数据有两种情况:
异步复制导致的数据丢失
脑裂导致的数据丢失