168人参与 • 2024-08-06 • mongodb
kafka中有那些地方需要选举?这些地方的选举策略又有哪些?
“消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确
zab(zookeeper atomic broadcast)协议
redis 的读写速度快主要归功于以下几个因素:
**内存存储:** redis 是基于内存存储的数据库系统,数据完全存储在内存中,这使得数据的读写速度非常快,因为内存的读写速度通常远远快于磁盘。
**单线程模型:** redis 使用单线程模型来处理客户端请求,这意味着它在任何时候都只有一个线程在处理请求,避免了多线程的上下文切换和锁竞争带来的开销。单线程模型简化了并发控制,降低了系统的复杂度,使得 redis 在处理大量并发请求时依然能够保持高效率。
**非阻塞 i/o:** redis 使用非阻塞的 i/o 处理方式,可以在一个线程中处理多个并发的网络连接,通过事件轮询机制实现高效的 i/o 处理。这使得 redis 能够充分利用系统资源,提高了并发处理能力。
**优化的数据结构:** redis 内置了丰富的数据结构,如字符串、列表、哈希表、集合和有序集合等,这些数据结构在设计上都经过了优化,能够高效地支持各种数据操作,从而提高了读写的速度。
**持久化策略:** redis 支持多种持久化策略,如快照和日志追加(aof),通过将内存中的数据定期或实时地写入到磁盘中,保证数据的持久性,同时尽量减少对性能的影响。
综合以上因素,redis 能够实现快速的读写操作,使其成为许多应用场景下的首选缓存和数据存储解决方案。
redis 提供了不同级别的持久化方式:
在aof重写策略上做了优化
在重写aof文件时,4.x版本以前是把内存数据集的操作指令落地,而新版本是把内存的数据集以rdb的形式落地
这样重写后的aof依然追加的是日志,但是,在恢复的时候是先rdb再增量的日志,性能更优秀
setnx 格式:setnx key value 将 key 的值设为 value ,当且仅当 key 不存在。 若给定的 key 已经存在,则 setnx 不做任何动作,操作失败。
setnx 是『set if not exists』(如果不存在,则 set)的简写。
加锁:set key value nx ex 10s
释放锁:delete key
情况1:加锁,没有释放锁。需要加释放锁的操作。比如delete key。
情况2:加锁后,程序还没有执行释放锁,程序挂了。需要用的key的过期机制。
假设有两个服务a、b都希望获得锁,执行过程大致如下:
step1: 服务a为了获得锁,向redis发起如下命令: set productid:lock 0xx9p03001 nx ex 30000 其中,"productid"由自己定义,可以是与本次业务有关的id,"0xx9p03001"是一串随机值,必须保证全局唯一,“nx"指的是当且仅当key(也就是案例中的"productid:lock”)在redis中不存在时,返回执行成功,否则执行失败。"ex 30000"指的是在30秒后,key将被自动删除。执行命令后返回成功,表明服务成功的获得了锁。
step2: 服务b为了获得锁,向redis发起同样的命令: set productid:lock 0000111 nx ex 30000 由于redis内已经存在同名key,且并未过期,因此命令执行失败,服务b未能获得锁。服务b进入循环请求状态,比如每隔1秒钟(自行设置)向redis发送请求,直到执行成功并获得锁。
step3: 服务a的业务代码执行时长超过了30秒,导致key超时,因此redis自动删除了key。此时服务b再次发送命令执行成功,假设本次请求中设置的value值为0000222。此时需要在服务a中对key进行续期,watch dog。
step4: 服务a执行完毕,为了释放锁,服务a会主动向redis发起删除key的请求。注意: 在删除key之前,一定要判断服务a持有的value与redis内存储的value是否一致。比如当前场景下,redis中的锁早就不是服务a持有的那一把了,而是由服务2创建,如果贸然使用服务a持有的key来删除锁,则会误将服务2的锁释放掉。此外,由于删除锁时涉及到一系列判断逻辑,因此一般使用lua脚本,具体如下:
if redis.call("get", keys[1])==argv[1] then
return redis.call("del", keys[1])
else
return 0
end
redis 可以被用作异步队列(async queue)和延时队列(delayed queue),提供了可靠且高效的消息队列解决方案。下面分别说明 redis 如何作为异步队列和延时队列:
使用 redis 作为异步队列时,通常采用 list 数据结构来实现。生产者向 list 中 push 消息,而消费者则通过 pop 或者 blocking pop 的方式来获取消息,实现生产者和消费者之间的解耦和异步处理。
**生产者(producer):**
1. 生产者将任务或消息 push 到 redis 的 list 中,作为队列的尾部。
2. 生产者可以根据需要向队列中不断 push 新的消息。
**消费者(consumer):**
1. 消费者通过 pop 命令或 brpop(blocking pop)命令从队列的头部获取消息。
2. 消费者处理完消息后,可以继续获取下一个消息。
通过 redis list 实现的异步队列,生产者和消费者之间实现了解耦,可以提高系统整体的可靠性和性能。
使用 redis 作为延时队列时,通常需要借助 redis 的 sorted set 数据结构和一些定时任务处理的机制来实现延时消息的投递和消费。
**生产者(producer):**
1. 生产者将消息 push 到 redis 的 sorted set 中,将消息的到期时间作为 score。
2. 消息即时加入 sorted set 中,但在到达到期时间之前不会被取出。
**消费者(consumer):**
1. 消费者定时轮询 sorted set,获取 score 小于当前时间的消息。
2. 消费者根据到期时间取出消息,开始处理延时消息。
通过 redis 的 sorted set 结构,延时队列可以实现消息的有序存储和自动过期,消费者可以按照一定规则获取到期的消息,实现延时消息的可靠处理。
总之,redis 作为异步队列和延时队列的解决方案,通过合理地使用 list 和 sorted set 数据结构,结合生产者和消费者的配合,可以实现高效、可靠的消息队列系统,提高系统的性能和可靠性。
在 redis 2.4 及以前版本,过期期时间可能不是十分准确,有0-1秒的误差。
从 redis 2.6 起,过期时间误差缩小到0-1毫秒。
keys的过期时间使用unix时间戳存储(从redis 2.6开始以毫秒为单位)。这意味着即使redis实例不可用,时间也是一直在流逝的。
要想过期的工作处理好,计算机必须采用稳定的时间。 如果你将rdb文件在两台时钟不同步的电脑间同步,有趣的事会发生(所有的 keys装载时就会过期)。
即使正在运行的实例也会检查计算机的时钟,例如如果你设置了一个key的有效期是1000秒,然后设置你的计算机时间为未来2000秒,这时key会立即失效,而不是等1000秒之后。
redis keys过期有两种方式:被动和主动方式。
当一些客户端尝试访问它时,key会被发现并主动的过期。
当然,这样是不够的,因为有些过期的keys,永远不会访问他们。 无论如何,这些keys应该过期,所以定时随机测试设置keys的过期时间。所有这些过期的keys将会从密钥空间删除。
具体就是redis每秒10次做的事情:
这是一个平凡的概率算法,基本上的假设是,我们的样本是这个密钥控件,并且我们不断重复过期检测,直到过期的keys的百分百低于25%,这意味着,在任何给定的时刻,最多会清除1/4的过期keys。
noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但del和几个例外)
allkeys-lru: 尝试回收最少使用的键(lru),使得新添加的数据有空间存放。
volatile-lru: 尝试回收最少使用的键(lru),但仅限于在过期集合的键,使得新添加的数据有空间存放。
allkeys-random: 回收随机的键使得新添加的数据有空间存放。
volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(ttl)较短的键,使得新添加的数据有空间存放。
volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
allkeys-lfu:从所有键中驱逐使用频率最少的键
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过redisinfo命令输出以便调优。
一般的经验规则:
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
为了获得正确的行为而不牺牲一致性,当一个key过期,del
将会随着aof文字一起合成到所有附加的slaves。在master实例中,这种方法是集中的,并且不存在一致性错误的机会。
然而,当slaves连接到master时,不会独立过期keys(会等到master执行del命令),他们任然会在数据集里面存在,所以当slave当选为master时淘汰keys会独立执行,然后成为master。
redis 红锁(redis redlock)是一种分布式锁解决方案,主要用于在分布式环境下实现互斥访问保护,防止多个客户端同时修改共享资源而引发的数据一致性问题。
红锁的概念是由 redis 的作者 antirez 在他的博客中提出的,它通过对多个独立的 redis 实例进行加锁操作来提供更高的可靠性和安全性。
下面是红锁的基本原理:
选择 n 个 redis 实例:为了提高可靠性,红锁使用了多个独立的 redis 实例。实例之间可以在不同的主机或集群中部署。
加锁操作:
锁的有效性:
解锁操作:
判断锁的获取情况:
通过使用红锁,客户端可以通过多个独立的 redis 实例来实现分布式锁的高可靠性和安全性。但需要注意,红锁并不是一个由 redis 官方提供的内置实现,而是一个由社区推广的解决方案,需要根据具体的业务场景和需求进行实现和安全性评估。
绝对时间点过期
相对时间点过期
时钟轮算法
io模型使用了多路复用器,在linux系统中使用的是epoll
类似netty的boss,worker使用一个eventloopgroup(threads=1)
单线程的reactor模型,每次循环取socket中的命令然后逐一操作,可以保证socket中的指令是按顺序的,不保证不同的socket也就是客户端的命令的顺序性
命令操作在单线程中顺序操作,没有多线程的困扰不需要锁的复杂度,在操作数据上相对来说是原子性质的
自身的内存存储数据,读写操作不设计磁盘io
redis除了提供了value具备类型还为每种类型实现了一些操作命令
实现了计算向数据移动,而非数据想计算移动,这样在io的成本上有一定的优势
且在数据结构类型上,丰富了一些统计类属性,读写操作中,写操作会o(1)负载度更新length类属性,使得读操作也是o(1)的
sentinel,哨兵是 redis 集群中非常重要的一个组件,主要有以下功能:
集群监控:负责监控 redis master 和 slave 进程是否正常工作。
消息通知:如果某个 redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。
哨兵用于实现 redis 集群的高可用,本身也是分布式的,作为一个哨兵集群去运行,互相协同工作。 故障转移时,判断一个 master node 是否宕机了,需要大部分的哨兵都同意才行,涉及到了分布 式选举 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的 哨兵通常需要 3 个实例,来保证自己的健壮性。 哨兵 + redis 主从的部署架构,是不保证数据零丢失的,只能保证 redis 集群的高可用性。 对于哨兵 + redis 主从这种复杂的部署架构,尽量在测试环境和生产环境,都进行充足的测试和演 练。
redis cluster是一种服务端sharding技术,3.0版本开始正式提供。采用slot(槽)的概念,一共分成 16384个槽。将请求发送到任意节点,接收到请求的节点会将查询请求发送到正确的节点上执行 方案说明 通过哈希的方式,将数据分片,每个节点均分存储一定哈希槽(哈希值)区间的数据,默认分配了 16384 个槽位
每份数据分片会存储在多个互为主从的多节点上 数据写入先写主节点,再同步到从节点(支持配置为阻塞同步) 同一分片多个节点间的数据不保持强一致性 读取数据时,当客户端操作的key没有分配在该节点上时,redis会返回转向指令,指向正确的节点 扩容时需要需要把旧节点的数据迁移一部分到新节点 在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端 口号,比如 16379。 16379 端口号是用来进行节点间通信的,也就是 cluster bus 的通信,用来进行故障检测、配置更新、 故障转移授权。
优点
无中心架构,
支持动态扩容,
对业务透明
具备sentinel的监控和自动failover(故障转移)能力
客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
高性能,客户端直连redis服务,免去了proxy代理的损耗
缺点
运维也很复杂,
数据迁移需要人工干预
布式逻辑和存储模块耦合等
缓存预热指在用户请求数据前先将数据加载到缓存系统中,用户查询事先被预热的缓存数据,以提高系统查询效率。缓存预热一般有系统启动加载、定时加载等方式。
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并且出于容错考虑,如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。在流量大时,可能db就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。
有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。
另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。
对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。
缓存在某个时间点过期的时候,恰好在这个时间点对这个key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端db加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端db压垮。
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到db,db瞬时压力过重雪崩。
redis 缓存雪崩是指由于大量缓存同时失效或因其他原因导致请求直接落到数据库上,对数据库造成瞬间的压力增大,甚至宕机。为了防止缓存雪崩,可以采取以下方案:
通过以上方案的综合应用,可以有效地减少缓存雪崩对系统的影响,保障系统的稳定性和性能。
穿透:缓存不存在,数据库不存在,高并发,少量key
击穿:缓存不存在,数据库存在,高并发,少量key
雪崩:缓存不存在,数据库存在,高并发,大量key
multi 、 exec 、 discard 和 watch 是 redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
exec 命令负责触发并执行事务中的所有命令:
如果客户端在使用 multi 开启了一个事务之后,却因为断线而没有成功执行 exec ,那么事务中的所有命令都不会被执行。
另一方面,如果客户端成功在开启事务之后执行 exec ,那么事务中的所有命令都会被执行。
当使用 aof 方式做持久化的时候, redis 会使用单个 write(2) 命令将事务写入到磁盘中。
然而,如果 redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。
如果 redis 在重新启动时发现 aof 文件出了这样的问题,那么它会退出,并汇报一个错误。
使用redis-check-aof程序可以修复这一问题:它会移除 aof 文件中不完整事务的信息,确保服务器可以顺利启动。
从 2.2 版本开始,redis 还可以通过乐观锁(optimistic lock)实现 cas (check-and-set)操作,具体信息请参考文档的后半部分。
使用事务时可能会遇上以下两种错误:
事务在执行 exec 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
命令可能在 exec 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。
对于发生在 exec 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 queued ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。
不过,从 redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 exec 命令时,拒绝执行并自动放弃这个事务。
在 redis 2.6.5 以前, redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。
至于那些在 exec 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。
如果你有使用关系式数据库的经验, 那么 “redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
有种观点认为 redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 incr 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 incr , 回滚是没有办法处理这些情况的。
主从复制集群
分片集群
主从复制集群,手动切换
带有哨兵的ha的主从复制集群
客户端实现路由索引的分片集群
使用中间件代理层的分片集群
redis自身实现的cluster分片集群
当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave , :包括客户端的写入、key 的过期或被逐出等等。
当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。
当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。
redis 使用异步复制,slave 和 master 之间异步地确认处理的数据量
一个 master 可以拥有多个 slave
slave 可以接受其他 slave 的连接。除了多个 slave 可以连接到同一个 master 之外, slave 之间也可以像层叠状的结构(cascading-like structure)连接到其他 slave 。自 redis 4.0 起,所有的 sub-slave 将会从 master 收到完全一样的复制流。
redis 复制在 master 侧是非阻塞的。这意味着 master 在一个或多个 slave 进行初次同步或者是部分重同步时,可以继续处理查询请求。
复制在 slave 侧大部分也是非阻塞的。当 slave 进行初次同步时,它可以使用旧数据集处理查询请求,假设你在 redis.conf 中配置了让 redis 这样做的话。否则,你可以配置如果复制流断开, redis slave 会返回一个 error 给客户端。但是,在初次同步之后,旧数据集必须被删除,同时加载新的数据集。 slave 在这个短暂的时间窗口内(如果数据集很大,会持续较长时间),会阻塞到来的连接请求。自 redis 4.0 开始,可以配置 redis 使删除旧数据集的操作在另一个不同的线程中进行,但是,加载新数据集的操作依然需要在主线程中进行并且会阻塞 slave 。
复制既可以被用在可伸缩性,以便只读查询可以有多个 slave 进行(例如 o(n) 复杂度的慢操作可以被下放到 slave ),或者仅用于数据安全。
可以使用复制来避免 master 将全部数据集写入磁盘造成的开销:一种典型的技术是配置你的 master redis.conf 以避免对磁盘进行持久化,然后连接一个 slave ,其配置为不定期保存或是启用 aof。但是,这个设置必须小心处理,因为重新启动的 master 程序将从一个空数据集开始:如果一个 slave 试图与它同步,那么这个 slave 也会被清空。
任何时候数据安全性都是很重要的,所以如果 master 使用复制功能的同时未配置持久化,那么自动重启进程这项应该被禁用。
每一个 redis master 都有一个 replication id :这是一个较大的伪随机字符串,标记了一个给定的数据集。每个 master 也持有一个偏移量,master 将自己产生的复制流发送给 slave 时,发送多少个字节的数据,自身的偏移量就会增加多少,目的是当有新的操作修改自己的数据集时,它可以以此更新 slave 的状态。复制偏移量即使在没有一个 slave 连接到 master 时,也会自增,所以基本上每一对给定的
都会标识一个 master 数据集的确切版本。
当 slave 连接到 master 时,它们使用 psync 命令来发送它们记录的旧的 master replication id 和它们至今为止处理的偏移量。通过这种方式, master 能够仅发送 slave 所需的增量部分。但是如果 master 的缓冲区中没有足够的命令积压缓冲记录,或者如果 slave 引用了不再知道的历史记录(replication id),则会转而进行一个全量重同步:在这种情况下, slave 会得到一个完整的数据集副本,从头开始。
下面是一个全量同步的工作细节:
master 开启一个后台保存进程,以便于生产一个 rdb 文件。同时它开始缓冲所有从客户端接收到的新的写入命令。当后台保存完成时, master 将数据集文件传输给 slave, slave将之保存在磁盘上,然后加载文件到内存。再然后 master 会发送所有缓冲的命令发给 slave。这个过程以指令流的形式完成并且和 redis 协议本身的格式相同。
你可以用 telnet 自己进行尝试。在服务器正在做一些工作的同时连接到 redis 端口并发出 sync 命令。你将会看到一个批量传输,并且之后每一个 master 接收到的命令都将在 telnet 回话中被重新发出。事实上 sync 是一个旧协议,在新的 redis 实例中已经不再被使用,但是其仍然向后兼容:但它不允许部分重同步,所以现在 psync 被用来替代 sync。
之前说过,当主从之间的连接因为一些原因崩溃之后, slave 能够自动重连。如果 master 收到了多个 slave 要求同步的请求,它会执行一个单独的后台保存,以便于为多个 slave 服务。
正常情况下,一个全量重同步要求在磁盘上创建一个 rdb 文件,然后将它从磁盘加载进内存,然后 slave以此进行数据同步。
如果磁盘性能很低的话,这对 master 是一个压力很大的操作。redis 2.8.18 是第一个支持无磁盘复制的版本。在此设置中,子进程直接发送 rdb 文件给 slave,无需使用磁盘作为中间储存介质。
怎么发现热key 方法一:凭借业务经验,进行预估哪些是热key 其实这个方法还是挺有可行性的。比如某商品在做秒杀,那这个商品的key就可以判断出是热key。缺点很明显,并非所有业务都能预估出哪些key是热key。
方法二:在客户端进行收集 这个方式就是在操作redis之前,加入一行代码进行数据统计。那么这个数据统计的方式有很多种,也可以是给外部的通讯系统发送一个通知信息。缺点就是对客户端代码造成入侵。
方法三:在proxy层做收集 有些集群架构是下面这样的,proxy可以是twemproxy,是统一的入口。可以在proxy层做收集上报,但是缺点很明显,并非所有的redis集群架构都有proxy。
方法四:用redis自带命令 (1)monitor命令,该命令可以实时抓取出redis服务器接收到的命令,然后写代码统计出热key是啥。当然,也有现成的分析工具可以给你使用,比如redis-faina。但是该命令在高并发的条件下,有内存增暴增的隐患,还会降低redis的性能。 (2)hotkeys参数,redis 4.0.3提供了redis-cli的热点key发现功能,执行redis-cli时加上–hotkeys选项即可。但是该参数在执行的时候,如果key比较多,执行起来比较慢。
方法五:自己抓包评估 redis客户端使用tcp协议与服务端进行交互,通信协议采用的是resp。自己写程序监听端口,按照resp协议规则解析数据,进行分析。缺点就是开发成本高,维护困难,有丢包可能性 (1)利用jvm本地缓存 比如利用ehcache,或者一个hashmap都可以。在你发现热key以后,把热key加载到系统的jvm中。 针对这种热key请求,会直接从jvm中取,而不会走到redis层。 假设此时有十万个针对同一个key的请求过来,如果没有本地缓存,这十万个请求就直接怼到同一台redis上了。 现在假设,你的应用层有50台机器,ok,你也有jvm缓存了。这十万个请求平均分散开来,每个机器有2000个请求,会从jvm中取到value值,然后返回数据。避免了十万个请求怼到同一台redis上的情形。
(2)备份热key 这个方案也很简单。不要让key走到同一台redis上不就行了。我们把这个key,在多个redis上都存一份不就好了。接下来,有热key请求进来的时候,我们就在有备份的redis上随机选取一台,进行访问取值,返回数据。 假设redis的集群数量为n,步骤如下图所示
理解回收进程如何工作是非常重要的:
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
先做一个说明,从理论上来说,给缓存设置过期时间,是保证最终一致性的解决方案。这种方案下,我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力更新即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存。因此,接下来讨论的思路不依赖于给缓存设置过期时间这个方案。 在这里,我们讨论三种更新策略:
先读缓存,如果缓存没有,才从数据库读取。
这套方案,大家是普遍反对的。为什么呢?有如下两点原因。 原因一(线程安全角度) 同时有请求a和请求b进行更新操作,那么会出现 (1)线程a更新了数据库 (2)线程b更新了数据库 (3)线程b更新了缓存 (4)线程a更新了缓存 这就出现请求a更新缓存应该比请求b更新缓存早才对,但是因为网络等原因,b却比a更早更新了缓存。这就导致了脏数据,因此不考虑。 原因二(业务场景角度) 有如下两点: (1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。 (2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。
接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。
该方案会导致不一致的原因是。同时有一个请求a进行更新操作,另一个请求b进行查询操作。那么会出现如下情形: (1)请求a进行写操作,删除缓存 (2)请求b查询发现缓存不存在 (3)请求b去数据库查询得到旧值 (4)请求b将旧值写入缓存 (5)请求a将新值写入数据库 上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。 那么,如何解决呢?采用延时双删策略
(1)先淘汰缓存 (2)再写数据库(这两步和原来一样) (3)休眠1秒,再次淘汰缓存 这么做,可以将1秒内所造成的缓存脏数据,再次删除。 那么,这个1秒怎么确定的,具体该休眠多久呢? 针对上面的情形,读者应该自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百ms即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。 如果你用了mysql的读写分离架构怎么办? ok,在这种情况下,造成数据不一致的原因如下,还是两个请求,一个请求a进行更新操作,另一个请求b进行查询操作。 (1)请求a进行写操作,删除缓存 (2)请求a将数据写入数据库了, (3)请求b查询缓存发现,缓存没有值 (4)请求b去从库查询,这时,还没有完成主从同步,因此查询到的是旧值 (5)请求b将旧值写入缓存 (6)数据库完成主从同步,从库变为新值 上述情形,就是数据不一致的原因。还是使用双删延时策略。只是,睡眠时间修改为在主从同步的延时时间基础上,加几百ms。 采用这种同步淘汰策略,吞吐量降低怎么办? ok,那就将第二次删除作为异步的。自己起一个线程,异步删除。这样,写的请求就不用沉睡一段时间后了,再返回。这么做,加大吞吐量。 第二次删除,如果删除失败怎么办? 这是个非常好的问题,因为第二次删除失败,就会出现如下情形。还是有两个请求,一个请求a进行更新操作,另一个请求b进行查询操作,为了方便,假设是单库: (1)请求a进行写操作,删除缓存 (2)请求b查询发现缓存不存在 (3)请求b去数据库查询得到旧值 (4)请求b将旧值写入缓存 (5)请求a将新值写入数据库 (6)请求a试图去删除,请求b写入对的缓存值,结果失败了。 ok,这也就是说。如果第二次删除缓存失败,会再次出现缓存和数据库不一致的问题。 如何解决呢?
首先,先说一下。老外提出了一个缓存更新套路,名为《cache-aside pattern》。其中就指出
另外,知名社交网站facebook也在论文《scaling memcache at facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。 这种情况不存在并发问题么? 不是的。假设这会有两个请求,一个请求a做查询操作,一个请求b做更新操作,那么会有如下情形产生 (1)缓存刚好失效 (2)请求a查询数据库,得一个旧值 (3)请求b将新值写入数据库 (4)请求b删除缓存 (5)请求a将查到的旧值写入缓存 ok,如果发生上述情况,确实是会发生脏数据。 然而,发生这种情况的概率又有多少呢? 发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)。可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短,这一情形很难出现。 假设,有人非要抬杠,有强迫症,一定要解决怎么办? 如何解决上述并发问题? 首先,给缓存设有效时间是一种方案。其次,采用策略(2)里给出的异步延时删除策略,保证读请求完成以后,再进行删除操作。 还有其他造成不一致的原因么? 有的,这也是缓存更新策略(2)和缓存更新策略(3)都存在的一个问题,如果删缓存失败了怎么办,那不是会有不一致的情况出现么。比如一个写数据请求,然后写入数据库了,删缓存失败了,这会就出现不一致的情况了。这也是缓存更新策略(2)里留下的最后一个疑问。 如何解决? 提供一个保障的重试机制即可,这里给出两套方案。
方案一: 如下图所示
流程如下所示
(1)更新数据库数据;
(2)缓存因为种种问题删除失败
(3)将需要删除的key发送至消息队列
(4)自己消费消息,获得需要删除的key
(5)继续重试删除操作,直到成功 然而,该方案有一个缺点,对业务线代码造成大量的侵入。于是有了方案二,在方案二中,启动一个订阅程序去订阅数据库的binlog,获得需要操作的数据。在应用程序中,另起一段程序,获得这个订阅程序传来的信息,进行删除缓存操作。
方案二:
流程如下图所示:
(1)更新数据库数据
(2)数据库会将操作信息写入binlog日志当中
(3)订阅程序提取出所需要的数据以及key
(4)另起一段非业务代码,获得该信息
(5)尝试删除缓存操作,发现删除失败
(6)将这些信息发送至消息队列
(7)重新从消息队列中获得该数据,重试操作。
**备注说明:**上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。至于oracle中,博主目前不知道有没有现成中间件可以使用。另外,重试机制,博主是采用的是消息队列的方式。如果对一致性要求不是很高,直接在程序中另起一个线程,每隔一段时间去重试即可,这些大家可以灵活自由发挥,只是提供一个思路。
redis和mongodb是两种不同类型的数据库,它们在很多方面有所不同:
数据存储方式:
数据模型:
持久化:
查询语言:
适用场景:
数据一致性:
扩展性:
acid特性:
总体来说,redis更注重性能和速度,适用于需要快速存取数据的场景,而mongodb更注重数据结构和查询能力,适用于需要复杂查询和数据分析的场景。在选择数据库时,需要根据具体的业务需求和数据特点来进行权衡和选择。
总的来说,redis更适合作为缓存或会话存储使用,而mongodb更适合作为存储大量文档型数据和进行复杂查询操作。根据具体的需求和场景选择合适的数据库系统是非常重要的。
什么是kafka
kafka 最初是由 linkedin 即领英公司基于 scala 和 java 语言开发的分布式消息发布-订阅系统,现已捐献给apache 软件基金会。其具有高吞吐、低延迟的特性,许多大数据实时流式处理系统比如 storm、spark、flink等都能很好地与之集成。
总的来讲,kafka 通常具有 3 重角色:
• **存储系统:**通常消息队列会把消息持久化到磁盘,防止消息丢失,保证消息可靠性。kafka 的消息持久化机制和多副本机制使其能够作为通用数据存储系统来使用。
• **消息系统:**kafka 和传统的消息队列比如 rabbitmq、rocketmq、activemq 类似,支持流量削峰、服务解耦、异步通信等核心功能。
• **流处理平台:**kafka 不仅能够与大多数流式计算框架完美整合,并且自身也提供了一个完整的流式处理库,即 kafka streaming。kafka streaming 提供了类似 flink 中的窗口、聚合、变换、连接等功能。
一句话概括:**kafka** 是一个分布式的基于发布**/订阅模式的消息中间件,在业界主要应用于大数据实时流式计算领域,起解耦合和削峰填谷的作用。**
kafka的几个特色: 消息系统,具有系统解耦,冗余存储,流量消峰,缓存,可恢复的功能(mq的作用,解耦,异步削峰) 存储系统,可以把消息持久化到磁盘,保证消息不丢失 特点
kafka的组件:
如下图所示,producer生产的消息通过网络发送给kafka cluster,而consumer从其中消费消息
topic:kafka中的消息是以主题为单位进行归类,生产者负责将消息发送到特定的主题topic,消费者负责订阅主题进行消费。 主题是一个逻辑上的概念,它可以分成很多的分区,也就是partitions,一个分区只属于一个主题,很多时候也把分区叫做主题分区,同一个主题下的不同分区的内容是不同,分区在存储层面可以看做是一个可以追加的日志文件,消息在被追加到分区日志文件的时候,会分配一个偏移量offset。offset是消息在分区中的唯一标识,kafka通过offset保证消息在分区中的顺序,offset不跨域分区, kafka保证的是分区有序而不是主题有序 消息发送时都被发送到一个topic,其本质就是一个目录,而topic由是由一些partition logs(分区日志)组成,其组织结构如下图所示:
主题有4个分区,消息被顺序写入到每个分区文件的末尾,kafka的分区跨域分布在不同的broker上面,也就是一个topic可以横跨多个broker。 我们可以看到,每个partition中的消息都是有序的,生产的消息被不断追加到partition log上,其中的每一个消息都被赋予了一个唯一的offset值。
每条消息在发送到kafka之前,会根据分区规则选择存储到哪个具体的分区,如果分区规则设置的合理,所有的消息都可以均匀的分配到不同的分区中。如果一个主题只对应一个文件,那么这个文件所在的机器的io将会成为这个主题的瓶颈,分区解决了这个问题。在创建主题的时候,可以指定分区的个数,当然也可以在创建完主题之后修改分区的数量,通过增加分区的数量可以进行水平的扩展。 kafka为分区引入了多副本的机制,通过增加副本数量,可以提高容灾能力,同一个分区的不同副本中保存的消息是相同的(同一时刻,副本之间的消息并非完全一致),副本之间是 一主多从的关系,其中leader副本负责处理读写请求,follower副本只负责与leader副本的消息同步,副本处于不同的broker中,当leader副本发送故障,会从follower副本中重新选择新的leader副本对外提供服务。 kafka通过多副本机制,实现了故障自动转移,当kafka集群中的某个broker失效,依然能保证对外提供服务。 。 下图中kafka集群有4个broker,某个主题有3个分区,而且副本因子也是3个,因此每个分区便有了一个leader副本和两个follower 副本。生产者和消费者只与leader副本进行交互,follower副本只负责消息的同步,但是很多时候follower副本的消息会滞后于leader副本。
kafka消费者也具备容灾的能力,当消费者使用pull从服务端拉去消息,并且保存了消费的具体offset,当消费者宕机恢复后,会根据之前保存的消费者位置重新进行消费,这样就不会造成消息丢失。 分区中的所有副本统称ar(assigned replicas),所有与leader副本保持一定程度同步的副本(包括leader副本)组成isr(in-sync replicas),isr是ar集合的一个子集。消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本有一定程度的滞后,这里说的一定程度是指可以忍受的范围内,这个参数可以通过配置。与leader副本同步滞后过多的副本(不包含leader副本)组成osr(out-of-snyc-replicas),也就是ar=isr+osr,正常情况下,所有的follower副本都应该与该leader副本保持一定程度的同步,也就是ar=isr。 leader副本负责维护和跟踪isr集合中所有follower副本的滞后状态,当follower落后太多或者失效时,leader副本会把它从isr中删除,如果osr中的follower副本追上了leader副本,那么leader副本会把它从osr移到isr中。默认情况下,在leader副本发送故障,只有在isr集合中副本才有机会被选中为leader,在osr集合中的副本没有任何机会。 isr和hw和leo也有密切关系,hw是高水位,标识了一个特定的消息的偏移量,消费者只能拉取到这个offset之前的消息
它代表一个日志文件的一个分区,这个日志文件分区中有9条消息,第一条消息的offset为0,最后一条消息的offset为8,offset为9的消息用虚线表示,代表下一条待输入的消息的offset,日志文件的hw为6,表示消费者只能拉去到offset为0至5之间的消息,而offset为6的消息对消费者是不可见的。 leo是log end offset,它标识当前日志文件中下一条待写入消息的offset,上图中的9为leo位置。分区isr集合中的每个副本都会维护自身的leo,而isr集合中最小的leo为分区的hw,对消费者而言,只能消费hw之前的消息。 为了更好理解isr集合,以及hw和leo之间的关系,通过下图说明
假设某个分区的isr集合中有3个副本,也就是一个leader副本和两个follower副本,此时分区的leo和hw都是3,消息3和消息4从生产者发出之后先被存入leader副本,在消息写入leader之后,follower副本会发送拉去消息请求。
在同步过程中,不同的follower副本同步的效率也不同,如下图
在某一时刻follower1完全跟上了leader,但是follower2只同步了消息3,因此leader副本的leo值为5, follower1的leo为5,follower2的leo为4,那么当前的hw为4,所以消费者只能消费offset为0-3的消息。 所有的副本都成功的写入了消息3和消息4,这个分区的hw和leo都是5,因此消费者可以消费到offset为0-4之间的消息了。
在 kafka 中,broker 和 controller 是不同的概念,它们代表着 kafka 集群中的不同角色和职责。
因此,broker 和 controller 是 kafka 集群中两个不同的概念和角色。broker 是 kafka 集群中实际运行的服务器节点,负责存储数据并处理消息传输;而 controller 是一个特殊的 kafka 服务器角色,负责管理和协调整个 kafka 集群的状态和操作。在一个 kafka 集群中,每个 broker 都可以扮演普通节点和 controller 的角色。
语义概念
1 broker
kafka 集群包含一个或多个服务器,服务器节点称为broker。
broker存储topic的数据。如果某topic有n个partition,集群有n个broker,那么每个broker存储该topic的一个partition。
如果某topic有n个partition,集群有(n+m)个broker,那么其中有n个broker存储该topic的一个partition,剩下的m个broker不存储该topic的partition数据。
如果某topic有n个partition,集群中broker数目少于n个,那么一个broker存储该topic的一个或多个partition。在实际生产环境中,尽量避免这种情况的发生,这种情况容易导致kafka集群数据不均衡。
2 topic
每条发布到kafka集群的消息都有一个类别,这个类别被称为topic。(物理上不同topic的消息分开存储,逻辑上一个topic的消息虽然保存于一个或多个broker上但用户只需指定消息的topic即可生产或消费数据而不必关心数据存于何处)
类似于数据库的表名
3 partition
topic中的数据分割为一个或多个partition。每个topic至少有一个partition。每个partition中的数据使用多个segment文件存储。partition中的数据是有序的,不同partition间的数据丢失了数据的顺序。如果topic有多个partition,消费数据时就不能保证数据的顺序。在需要严格保证消息的消费顺序的场景下,需要将partition数目设为1。
4 producer
生产者即数据的发布者,该角色将消息发布到kafka的topic中。broker接收到生产者发送的消息后,broker将该消息追加到当前用于追加数据的segment文件中。生产者发送的消息,存储到一个partition中,生产者也可以指定数据存储的partition。
5 consumer
消费者可以从broker中读取数据。消费者可以消费多个topic中的数据。
6 consumer group
每个consumer属于一个特定的consumer group(可为每个consumer指定group name,若不指定group name则属于默认的group)。这是kafka用来实现一个topic消息的广播(发给所有的consumer)和单播(发给任意一个consumer)的手段。一个topic可以有多个cg。topic的消息会复制-给consumer。如果需要实现广播,只要每个consumer有一个独立的cg就可以了。要实现单播只要所有的consumer在同一个cg。用cg还可以将consumer进行自由的分组而不需要多次发送消息到不同的topic。
7 leader
每个partition有多个副本,其中有且仅有一个作为leader,leader是当前负责数据的读写的partition。
8 follower
follower跟随leader,所有写请求都通过leader路由,数据变更会广播给所有follower,follower与leader保持数据同步。如果leader失效,则从follower中选举出一个新的leader。当follower与leader挂掉、卡住或者同步太慢,leader会把这个follower从“in sync replicas”(isr)列表中删除,重新创建一个follower。
9 offset
kafka的存储文件都是按照offset.kafka来命名,用offset做名字的好处是方便查找。例如你想找位于2049的位置,只要找到2048.kafka的文件即可。当然the first offset就是00000000000.kafka
kafka天生是分布式的,满足akf的xyz轴特点,扩展性,可靠性,高性能是没得说
而且,kafka具备自己的特色,比如动态isr集合,是在强一致性,过半一致性之外的另一个实现手段
kafka 保持高可靠性的主要机制和策略包括:
通过以上机制和策略,kafka 能够保持高可靠性,即使在节点故障、网络分区等异常情况下,依然能够保证数据的完整性和可用性,从而满足高性能、高吞吐量的消息传输和数据处理需求。
在 apache kafka 中,isr 和 ar 分别代表以下概念:
分区中的所有副本统称为ar(assigned replicas)。所有与leader副本保持一定程度同步的副本(包括leader副本在内)组成isr(in-sync replicas),isr集合是ar集合中的一个子集。
在 kafka 中,isr 和 ar 是数据复制和高可用性的重要概念,对确保消息传递的可靠性和完整性起着关键作用。
消息会先发送到leader副本,然后follower副本才能从leader副本中拉取消息进行同步,同步期间内follower副本相对于leader副本而言会有一定程度的滞后。前面所说的“一定程度的同步”是指可忍受的滞后范围,这个范围可以通过参数进行配置。与leader副本同步滞后过多的副本(不包括leader副本)组成osr(out-of-sync replicas),由此可见,ar=isr+osr。
partition leader(isr),controller(先到先得)
不能及时与leader同步,暂时踢出isr,等其追上leader之后再重新加入
生产者在生产过程中的消息丢失
broker在故障后的消息丢失
消费者在消费过程中的消息丢失
kafka 是一种高可靠的消息系统,但在特定情况下仍可能发生消息丢失的情况。以下是一些可能导致 kafka 消息丢失的场景:
为了尽量避免消息丢失,可以采取一些措施,如:
ack有3个可选值,分别是1,0,-1。
简单来说就是,producer发送一次就不再发送了,不管是否发送成功。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事it行业的老鸟或是对it行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
mg-blog.csdnimg.cn/direct/990f13ad9bdd473387ef3d89b72a5baa.png)
partition leader(isr),controller(先到先得)
不能及时与leader同步,暂时踢出isr,等其追上leader之后再重新加入
生产者在生产过程中的消息丢失
broker在故障后的消息丢失
消费者在消费过程中的消息丢失
kafka 是一种高可靠的消息系统,但在特定情况下仍可能发生消息丢失的情况。以下是一些可能导致 kafka 消息丢失的场景:
为了尽量避免消息丢失,可以采取一些措施,如:
ack有3个可选值,分别是1,0,-1。
简单来说就是,producer发送一次就不再发送了,不管是否发送成功。
[外链图片转存中…(img-oug8gfhp-1714342108066)]
[外链图片转存中…(img-egtathz4-1714342108066)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事it行业的老鸟或是对it行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论