分布式分为分布式缓存(Redis)、分布式锁(Redis或Zookeeper)、分布式服务(Dubbo或SpringCloud)、分布式服务协调(Zookeeper)、分布式消息队列(Kafka、RabbitMq)、分布式Session、分布式事务、分布式搜索(elastaticSearch)等。
不可能所有分布式内容都熟悉,一定要在某个领域有所专长。
分布式理论
Q:分布式有哪些理论?
CAP、BASE。
分布式CAP理论,任何一个分布式系统都无法同时满足Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性) 这三个基本需求。最多只能满足其中两项。
而Partition tolerance(分区容错性) 是必须的,因此一般是CP,或者AP。
Q:你怎么理解分布式一致性?
数据一致性通常指关联数据之间的逻辑关系是否正确和完整。
在分布式系统中,数据一致性往往指的是由于数据的复制,不同数据节点中的数据内容是否完整并且相同。
一致性还分为强一致性,弱一致性,还有最终一致性。
强一致性就是马上就保持一致。
最终一致性是指经过一段时间后,可以保持一致。
分布式事务
Q:你怎么理解分布式事务?分布式事务的协议有哪些?
分布式事务是指会涉及到操作多个数据库的事务。目的是为了保证分布式系统中的数据一致性。
分布式事务类型:二阶段提交2PC,三阶段提交3PC。
2PC:第一阶段:准备阶段(投票阶段)和第二阶段:提交阶段(执行阶段)。
3PC:三个阶段:CanCommit、PreCommit、DoCommit
Q:讲一下TCC
T(try):锁资源。锁定某个资源,设置一个预备类的状态,冻结部分数据。
比如,订单的支付状态,先把状态修改为"支付中(PAYING)"。
比如,本来库存数量是 100,现在卖出了2个,不要直接扣减这个库存。
在一个单独的冻结库存的字段,比如prepare_remove_stock字段,设置一个 2。也就是说,有 2 个库存是给冻结了。
积分服务的也是同理,别直接给用户增加会员积分。你可以先在积分表里的一个预增加积分字段加入积分。
比如:用户积分原本是 1190,现在要增加 10 个积分,别直接 1190 + 10 = 1200 个积分啊!
你可以保持积分为 1190 不变,在一个预增加字段里,比如说 prepare_add_credit 字段,设置一个 10,表示有 10 个积分准备增加。
C(comfirm):
在各个服务里引入了一个 TCC 分布式事务的框架,事务管理器可以感知到各个服务的 Try 操作是否都成功了。
假如都成功了,TCC 分布式事务框架会控制进入 TCC 下一个阶段,第一个 C 阶段,也就是 Confirm 阶段。
此时,需要把Try阶段锁住的资源进行处理。
比如,把订单的状态设置为“已支付(Payed)”。
比如,扣除掉相应的库存。
比如,增加用户积分。
C(cancel):
在 Try 阶段,假如某个服务执行出错,比如积分服务执行出错了,那么服务内的 TCC 事务框架是可以感知到的,然后它会决定对整个 TCC 分布式事务进行回滚。
TCC 分布式事务框架只要感知到了任何一个服务的 Try 逻辑失败了,就会跟各个服务内的 TCC 分布式事务框架进行通信,然后调用各个服务的 Cancel 逻辑。
也就是说,会执行各个服务的第二个 C 阶段,Cancel 阶段。
比如,订单的支付状态,先把状态修改为"closed"状态。
比如,冻结库存的字段,prepare_remove_stock字段,将冻结的库存2清零。
比如,预增加积分的字段,prepare_add_credit 字段,将准备增加的积分10清零。
详情见:https://www.cnblogs.com/jajian/p/10014145.html
Q:事务管理器宕掉了,怎么办?
做冗余,设置多个事务管理器,一个宕掉了,其他的还可以用。
Q:怎么保证分布式系统的幂等性?
状态机制。版本号机制。
Redis
Q:Redis有哪些优势?
1.速度快,因为数据存在内存中
2.支持丰富数据类型,支持string,list,set,sorted set,hash
3.支持事务,操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行
4.丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除
5.单线程,单进程,采用IO多路复用技术。
Q:Redis的存储结构是怎样的?
key-value键值对。
Q:Redis支持哪些数据结构?
string(字符串),hash(哈希),list(队列),set(集合)及zset(sorted set:有序集合)。
Q:Redis的数据结构,有哪些应用场景?
string,简单地get/set缓存。
hash,可以缓存用户资料。比如命令: hmset user1 name "lin" sex "male" age "25" ,缓存用户user1的资料,姓名为lin,性别为男,年龄25。
list,可以做队列。往list队列里面push数据,然后再pop出来。
zset,可以用来做排行榜。
详情见:https://www.cnblogs.com/expiator/p/10274151.html
Q:Redis怎么保证可靠性?
Q:Redis的持久化方式有哪些?有哪些优缺点?
一个可靠安全的系统,肯定要考虑数据的可靠性,尤其对于内存为主的redis,就要考虑一旦服务器挂掉,启动之后,如何恢复数据的问题,也就是说数据如何持久化的问题。
aof,就是备份操作记录。aof由于是备份操作命令,备份快,恢复慢。
AOF的优缺点。
优点: AOF更好保证数据不会被丢失,最多只丢失一秒内的数据。
另外重写操作保证了数据的有效性,即使日志文件过大也会进行重写。
AOF的日志文件的记录可读性非常的高。
对于相同数量的数据集而言,AOF文件通常要大于RDB文件。
rdb,就是备份所有数据,使用了快照。rdb恢复数据比较快。
Q:aof文件过大,怎么处理?
会进行aof文件重写。
1.随着AOF文件越来越大,里面会有大部分是重复命令或者可以合并的命令
2.重写的好处:减少AOF日志尺寸,减少内存占用,加快数据库恢复时间。
执行一个 AOF文件重写操作,重写会创建一个当前 AOF 文件的体积优化版本。
详情见: https://blog.csdn.net/stevendbaguo/article/details/82855726
Q:讲一下Redis的事务
先以 MULTI 开始一个事务, 然后将多个命令入队到事务中, 最后由 EXEC 命令触发事务, 一并执行事务中的所有命令。如果想放弃这个事务,可以使用DISCARD命令。
Redis事务无法回滚,那怎么处理?
Q:怎么设置Redis的key的过期时间?
key的的过期时间通过EXPIRE key seconds命令来设置数据的过期时间。返回1表明设置成功,返回0表明key不存在或者不能成功设置过期时间。
Q:Redis的过期策略有哪些?
惰性删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key,并按照key不存在去处理。
惰性删除,对内存不太好,已经过期的key会占用太多的内存。
定期删除:每隔一段时间,就会对Redis进行检查,主动删除一批已过期的key。
Q:为什么Redis不使用定时删除?
定时删除,就是在设置key的过期时间的同时,创建一个定时器,让定时器在过期时间来临时,立即执行对key的删除操作。
定时删除,会占用CPU,影响服务器的响应时间和性能。
Q:Redis 的内存回收机制都有哪些?
当前已用内存超过maxmemory限定时,会触发主动清理策略,也就是Redis的内存回收策略。
LRU、TTL。
noeviction:默认策略,不会删除任何数据,拒绝所有写入操作并返回客户端错误信息,此时Redis只响应读操作。
volatitle-lru:根据LRU算法删除设置了超时属性的键,知道腾出足够空间为止。如果没有可删除的键对象,回退到noeviction策略。
allkeys-lru:根据LRU算法删除键,不管数据有没有设置超时属性,直到腾出足够空间为止。
allkeys-random:随机删除所有键,知道腾出足够空间为止。
volatitle-random:随机删除过期键,知道腾出足够空间为止。
volatitle-ttl:根据键值对象的ttl属性,删除最近将要过期数据。如果没有,回退到noeviction策略
Q:手写一下LRU算法 。
Q:Redis的搭建有哪些模式?
主从模式、哨兵模式、Cluster(集群)模式。
最好是用集群模式。
详情见:https://new.qq.com/omn/20180126/20180126G00THE.html
Q:你用过的Redis是多主多从的,还是一主多从的?集群用到了多少节点?用到了多少个哨兵?
集群模式。三主三从。
Q:Redis采用多主多从的集群模式,各个主节点的数据是否一致?
Q:Redis集群有哪些特性?
master和slaver。主从复制。读写分离。哨兵模式。
Q:Redis是怎么进行水平扩容的?
Q:Redis集群数据分片的原理是什么?
Redis数据分片原理是哈希槽(hash slot)。
Redis 集群有 16384 个哈希槽。 每一个 Redis 集群中的节点都承担一个哈希槽的子集。
哈希槽让在集群中添加和移除节点非常容易。例如,如果我想添加一个新节点 D,我需要从节点 A,B, C 移动一些哈希槽到节点 D。同样地,如果我想从集群中移除节点 A,我只需要移动 A 的哈希槽到 B 和 C。 当节点 A 变成空的以后,我就可以从集群中彻底删除它。 因为从一个节点向另一个节点移动哈希槽并不需要停止操作,所以添加和移除节点,或者改变节点持有的哈希槽百分比,都不需要任何停机时间(downtime)。
Q:讲一下一致性Hash算法。
一致性Hash算法将整个哈希值空间组织成一个虚拟的圆环, 我们对key进行哈希计算,使用哈希后的结果对2^32取模,
hash环上必定有一个点与这个整数对应。依此确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
一致性Hash算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
比如,集群有四个节点 Node A、B、C、D,增加一台节点 Node X。Node X 的位置在 Node B 到 Node C 直接,那么受到影响的仅仅是 Node B 到 Node X 间的数据,它们要重新落到 Node X 上。
所以一致性哈希算法对于容错性和扩展性有非常好的支持。
Q:为什么Redis Cluster分片不使用Redis一致性Hash算法?
一致性哈希算法也有一个严重的问题,就是数据倾斜。
如果在分片的集群中,节点太少,并且分布不均,一致性哈希算法就会出现部分节点数据太多,部分节点数据太少。也就是说无法控制节点存储数据的分配。
Q:集群的拓扑结构有没有了解过?集群是怎么连接的?
无中心结构。Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
Q:讲一下Redis主从复制的过程。
从机发送SYNC(同步)命令,主机接收后会执行BGSAVE(异步保存)命令备份数据。
主机备份后,就会向从机发送备份文件。主机之后还会发送缓冲区内的写命令给从机。
当缓冲区命令发送完成后,主机执行一条写命令,就会往从机发送同步写入命令。
更详细的步骤见: https://www.cnblogs.com/expiator/p/9881989.html
Q:讲一下Redis哨兵机制。
下面是Redis官方文档对于哨兵功能的描述:
监控(Monitoring):哨兵会不断地检查主节点和从节点是否运作正常。
自动故障转移(Automatic Failover):当主节点不能正常工作时,哨兵会开始自动故障转移操作,它会将失效主节点的其中一个从节点升级为新的主节点,并让其他从节点改为复制新的主节点。
配置提供者(Configuration Provider):客户端在初始化时,通过连接哨兵来获得当前Redis服务的主节点地址。
通知(Notification):哨兵可以将故障转移的结果发送给客户端。
Q:讲一下布隆过滤器。
布隆过滤器的主要是由一个很长的二进制向量和若干个(k个)散列映射函数组成。因为每个元数据的存储信息值固定,而且总的二进制向量固定。所以在内存占用和查询时间上都远远超过一般的算法。当然存在一定的不准确率(可以控制)和不容易删除样本数据。
布隆过滤器的优点: 大批量数据去重,特别的占用内存。但是用布隆过滤器(Bloom Filter)会非常的省内存。
布隆过滤器的特点:当布隆过滤器说某个值存在时,那可能就不存在,如果说某个值不存在时,那肯定就是不存在了。
布隆过滤器的应用场景:新闻推送(不重复推送)。解决缓存穿透的问题。
缓存
Q:缓存雪崩是什么?
如果缓存数据设置的过期时间是相同的,并且Redis恰好将这部分数据全部删光了。这就会导致在这段时间内,这些缓存同时失效,全部请求到数据库中。这就是缓存雪崩。
怎么解决缓存雪崩?
解决方法:在缓存的时候给过期时间加上一个随机值,这样就会大幅度的减少缓存在同一时间过期。
Q:缓存穿透是什么?
缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并且出于容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
怎么解决缓存穿透?
Q:什么是缓存与数据库双写一致问题?
Q:如何保证缓存与数据库的一致性?
读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
先删除缓存,再更新数据库。
Q:为什么是先删除缓存,而不是先更新缓存?
Q:先更新数据库,再删除缓存,会有什么问题?
先更新数据库,再删除缓存。可能出现以下情况:
如果更新完数据库,java服务提交了事务,然后挂掉了,那Redis还是会执行,这样也会不一致。
如果更新数据库成功,删除缓存失败了,那么会导致数据库中是新数据,缓存中是旧数据,数据就出现了不一致。
先删除缓存,再更新数据库。
如果删除缓存失败,那就不更新数据库,缓存和数据库的数据都是旧数据,数据是一致的。
如果删除缓存成功,而数据库更新失败了,那么数据库中是旧数据,缓存中是空的,数据不会不一致。因为读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
分布式锁
Q:Redis如何实现分布式锁?
使用set key value ex nx 命令。
当key不存在时,将 key 的值设为 value ,返回1。若给定的 key 已经存在,则setnx不做任何动作,返回0。
当setnx返回1时,表示获取锁,做完操作以后del key,表示释放锁,如果setnx返回0表示获取锁失败。
详细的命令如下:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)。
示例如下:
set name fenglin ex 100 nx
详情见:https://blog.csdn.net/qq_30038111/article/details/90696233
Q:为什么不先set nx,然后再使用expire设置超时时间?
我们需要保证setnx命令和expire命令以原子的方式执行,否则如果客户端执行setnx获得锁后,这时客户端宕机了,那么这把锁没有设置过期时间,导致其他客户端永远无法获得锁了。
Q:使用Redis分布式锁,key和value分别设置成什么?
value可以使用json格式的字符串。
示例:
{
"count":1,
"expireAt":147506817232,
"jvmPid":22224,
"mac":"28-D2-44-0E-0D-9A",
"threadId":14
}
Q:Redis实现的分布式锁,如果某个系统获取锁后,宕机了怎么办?
系统模块宕机的话,可以通过设置过期时间(就是设置缓存失效时间)解决。系统宕机时锁阻塞,过期后锁释放。
Q:设置缓存失效时间,那如果前一个线程把这个锁给删除了呢?
Q:如果加锁和解锁之间的业务逻辑执行的时间比较长,超过了锁过期的时间,执行完了,又删除了锁,就会把别人的锁给删了。怎么办?
这两个属于锁超时的问题。
可以将锁的value设置为Json字符串,在其中加入线程的id或者请求的id,在删除之前,get一下这个key,判断key对应的value是不是当前线程的。只有是当前线程获取的锁,当前线程才可以删除。
Q:Redis分布式锁,怎么保证可重入性?
可以将锁的value设置为Json字符串,在其中加入线程的id和count变量。
当count变量的值为0时,表示当前分布式锁没有被线程占用。
如果count变量的值大于0,线程id不是当前线程,表示当前分布式锁已经被其他线程占用。
如果count变量的值大于0,线程id是当前线程的id,表示当前线程已经拿到了锁,不必阻塞,可以直接重入,并将count变量的值加一即可。
这种思路,其实就是参考了ReentrantLock可重入锁的机制。
Q:Redis做分布式锁,Redis做了主从,如果设置锁之后,主机在传输到从机的时候挂掉了,从机还没有加锁信息,如何处理?
可以使用开源框架Redisson,采用了redLock。(待补充)
Q:讲一下Redis的redLock。
Q:Zookeeper是怎么实现分布式锁的?
分布式锁:基于Zookeeper一致性文件系统,实现锁服务。锁服务分为保存独占及时序控制两类。
保存独占:将Zookeeper上的一个znode看作是一把锁,通过createznode的方式来实现。所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端也即拥有了这把锁。用完删除自己创建的distribute_lock 节点就释放锁。
时序控制:基于/distribute_lock锁,所有客户端在它下面创建临时顺序编号目录节点,和选master一样,编号最小的获得锁,用完删除,依次方便。
更详细的回答如下:
其实基于Zookeeper,就是使用它的临时有序节点来实现的分布式锁。
原理就是:当某客户端要进行逻辑的加锁时,就在Zookeeper上的某个指定节点的目录下,去生成一个唯一的临时有序节点, 然后判断自己是否是这些有序节点中序号最小的一个,如果是,则算是获取了锁。如果不是,则说明没有获取到锁,那么就需要在序列中找到比自己小的那个节点,并对其调用exist()方法,对其注册事件监听,当监听到这个节点被删除了,那就再去判断一次自己当初创建的节点是否变成了序列中最小的。如果是,则获取锁,如果不是,则重复上述步骤。
当释放锁的时候,只需将这个临时节点删除即可。
Zookeeper
Q:Zookeeper的原理是什么?
Q:Zookeeper是怎么保证一致性的?
zab协议。
zab协议有两种模式,它们分别是恢复模式(选主)和广播模式(同步)。当服务启动或者在领导者崩溃后,zab就进入了恢复模式,当领导者被选举出来,且大多数server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和server具有相同的系统状态。
Q:Zookeeper有哪些应用场景?
Zookeeper可以作为服务协调的注册中心。还可以做分布式锁(如果没有用过分布式锁就不要说)
Q:Zookeeper为什么能做注册中心?
Zookeeper的数据模型是树型结构,由很多数据节点组成,zk将全量数据存储在内存中,可谓是高性能,而且支持集群,可谓高可用。
另外支持事件监听(watch命令)。
Zookeeper可以作为一个数据发布/订阅系统。
Q:Zookeeper的节点有哪些类型?有什么区别?
临时节点,永久节点。 更加细分就是临时有序节点、临时无序节点、永久有序节点、永久无序节点。
临时节点: 当创建临时节点的程序停掉之后,这个临时节点就会消失,存储的数据也没有了。
Q:Zookeeper做为注册中心,主要存储哪些数据?存储在哪里?
ip、端口,还有心跳机制。
数据存储在Zookeeper的节点上面。
Q:心跳机制有什么用?
Q:Zookeeper的广播模式有什么缺陷?
广播风暴。
Q:讲一下Zookeeper的读写机制。
Leader主机负责读和写。
Follower负责读,并将写操作转发给Leader。Follower还参与Leader选举投票,参与事务请求Proposal投票。
Observer充当观察者的角色。Observer和Follower的唯一区别在于:Observer不参与任何投票。
Q:讲一下Zookeeper的选举机制。
Leader不可用时,会重新选举Leader。超过半数的Follower选举投票即可,Observer不参与投票。
Q:你们的zookeeper集群配置了几个节点?
3个节点。注意,zookeeper集群节点,最好是奇数个的。
集群中的zookeeper节点需要超过半数,整个集群对外才可用。
这里所谓的整个集群对外才可用,是指整个集群还能选出一个Leader来,zookeeper默认采用quorums来支持Leader的选举。
如果有2个zookeeper,那么只要有1个死了zookeeper就不能用了,因为1没有过半,所以2个zookeeper的死亡容忍度为0;同理,要是有3个zookeeper,一个死了,还剩下2个正常的,过半了,所以3个zookeeper的容忍度为1;同理你多列举几个:2->0;3->1;4->1;5->2;6->2会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,所以为了更加高效,何必增加那一个不必要的zookeeper呢。
Q:zookeeper的集群节点,如果不是奇数的,可能会出现什么问题?
可能会出现脑裂。
假死:由于心跳超时(网络原因导致的)认为master死了,但其实master还存活着。
脑裂:由于假死会发起新的master选举,选举出一个新的master,但旧的master网络又通了,导致出现了两个master ,有的客户端连接到老的master 有的客户端链接到新的master。
详情见:https://my.oschina.net/wangen2009/blog/2994188
消息队列
Q:为什么使用消息队列?消息队列有什么优点和缺点?Kafka、ActiveMQ、RabbitMq、RocketMQ 都有什么优点和缺点?
消息队列解耦,削峰,限流。
Q:如何保证消息队列的高可用?(多副本)
Q:如何保证消息不被重复消费?(如何保证消息消费的幂等性)
Q:如何保证消息的可靠性传输?(如何处理消息丢失的问题)
Q:如何保证消息的顺序性?
Q:如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,说说怎么解决?
Q:如果让你写一个消息队列,该如何进行架构设计啊?说一下你的思路。
Kafka
Q:Kafka相对其他消息队列,有什么特点?
持久化:Kafka的持久化能力比较好,通过磁盘持久化。而RabbitMQ是通过内存持久化的。
吞吐量:Rocket的并发量非常高。
消息处理:RabbitMQ的消息不支持批量处理,而RocketMQ和Kafka支持批量处理。
高可用:RabbitMQ采用主从模式。Kafka也是主从模式,通过Zookeeper管理,选举Leader,还有Replication副本。
事务:RocketMQ支持事务,而Kafka和RabbitMQ不支持。
Q:Kafka有哪些模式?
如果一个生产者或者多个生产者产生的消息能够被多个消费者同时消费的情况,这样的消息队列称为"发布订阅模式"的消息队列
Q:Kafka作为消息队列,有哪些优势?
分布式的消息系统。
高吞吐量。即使存储了许多TB的消息,它也保持稳定的性能。
数据保留在磁盘上,因此它是持久的。
Q:Kafka为什么处理速度会很快?kafka的吞吐量为什么高?
零拷贝:Kafka 实现了"零拷贝"原理来快速移动数据,避免了内核之间的切换。
消息压缩、分批发送:Kafka 可以将数据记录分批发送,从生产者到文件系统(Kafka 主题日志)到消费者,可以端到端的查看这些批次的数据。
批处理能够进行更有效的数据压缩并减少 I/O 延迟。
顺序读写:Kafka 采取顺序写入磁盘的方式,避免了随机磁盘寻址的浪费。
Q:讲一下Kafka中的零拷贝。
数据的拷贝从内存拷贝到kafka服务进程那块,又拷贝到socket缓存那块,整个过程耗费的时间比较高,kafka利用了Linux的sendFile技术(NIO),省去了进程切换和一次数据拷贝,让性能变得更好。
Q:Kafka的偏移量是什么?
消费者每次消费数据的时候,消费者都会记录消费的物理偏移量(offset)的位置。等到下次消费时,他会接着上次位置继续消费
Q:Kafka的生产者,是如何发送消息的?
生产者的消息是先被写入分区中的缓冲区中,然后分批次发送给 Kafka Broker。
生产者的消息发送机制,有同步发送和异步发送。
同步发送消息都有个问题,那就是同一时间只能有一个消息在发送,这会造成许多消息无法直接发送,造成消息滞后,无法发挥效益最大化。
异步发送消息的同时能够对异常情况进行处理,生产者提供了Callback 回调。
Q:Kafka生产者发送消息,有哪些分区策略?
Kafka 的分区策略指的就是将生产者发送到哪个分区的算法。
有顺序轮询、随机轮询、key-ordering 策略。
key-ordering 策略:Kafka 中每条消息都会有自己的key,一旦消息被定义了 Key,那么你就可以保证同一个 Key 的所有消息都进入到相同的分区里面,由于每个分区下的消息处理都是有顺序的,故这个策略被称为按消息键保序策略。
Q:Kafka为什么要分区?
实现负载均衡和水平扩展。
Kafka可以将主题(Topic)划分为多个分区(Partition),会根据分区规则选择把消息存储到哪个分区中,只要如果分区规则设置的合理,那么所有的消息将会被均匀的分布到不同的分区中,这样就实现了负载均衡和水平扩展。另外,多个订阅者可以从一个或者多个分区中同时消费数据,以支撑海量数据处理能力。
Q:Kafka,是如何在Broker间分配分区的?
在broker间平均分布分区副本;
假设有6个broker,打算创建一个包含10个分区的Topic,复制系数为3,那么Kafka就会有30个分区副本,它可以被分配给这6个broker,这样的话,每个broker可以有5个副本。
要确保每个分区的每个副本分布在不同的broker上面;
假设Leader分区0会在broker1上面,Leader分区1会在broker2上面,Leder分区2会在broker3上面。
接下来会分配跟随者副本。如果分区0的第一个Follower在broker2上面,第二个Follower在broker3上面。
分区1的第一个Follower在broker3上面,第二个Follower在broker4上面。。
Q:Kafka如何保证消息的顺序性?
Kafka 可以保证同一个分区里的消息是有序的。也就是说消息发送到一个Partition 是有顺序的。
Q:Kafka的消费者群组Consumer Group订阅了某个Topic,假如这个Topic接收到消息并推送,那整个消费者群组能收到消息吗?
http://kafka.apache.org/intro
Kafka官网中有这样一句"Consumers label themselves with a consumer group name, and each record published to a topic is delivered to one consumer instance within each subscribing consumer group. "
表示推送到topic上的record,会被传递到已订阅的消费者群组里面的一个消费者实例。
Q:如何提高Kafka的消费速度?
Q:kafka出现消息积压,有哪些原因??怎么解决?
出现消息积压,可能是因为消费的速度太慢。
扩容消费者。之所以消费延迟大,就是消费者处理能力有限,可以增加消费者的数量。
扩大分区。一个分区只能被消费者群组中的一个消费者消费。消费者扩大,分区最好多随之扩大。
Q:Kafka消息消费者宕机了,怎么确认有没有收到消息?
ack机制,如果接收方收到消息后,会返回一个确认字符。
Q:讲一下Kafka的ack机制。
acks 参数指定了要有多少个分区副本接收消息,生产者才认为消息是写入成功的。此参数对消息丢失的影响较大。
如果 acks = 0,就表示生产者也不知道自己产生的消息是否被服务器接收了,它才知道它写成功了。如果发送的途中产生了错误,生产者也不知道,它也比较懵逼,因为没有返回任何消息。这就类似于 UDP 的运输层协议,只管发,服务器接受不接受它也不关心。
如果 acks = 1,只要集群的 Leader 接收到消息,就会给生产者返回一条消息,告诉它写入成功。如果发送途中造成了网络异常或者 Leader 还没选举出来等其他情况导致消息写入失败,生产者会受到错误消息,这时候生产者往往会再次重发数据。因为消息的发送也分为 同步 和 异步,Kafka 为了保证消息的高效传输会决定是同步发送还是异步发送。如果让客户端等待服务器的响应(通过调用 Future 中的 get() 方法),显然会增加延迟,如果客户端使用回调,就会解决这个问题。
如果 acks = all,这种情况下是只有当所有参与复制的节点都收到消息时,生产者才会接收到一个来自服务器的消息。不过,它的延迟比 acks =1 时更高,因为我们要等待不只一个服务器节点接收消息。
参考资料: https://juejin.im/post/5ddf5659518825782d599641
Q:Kafka如何避免消息丢失?
1.生产者丢失消息的情况:
生产者(Producer) 调用send方法发送消息之后,消息可能因为网络问题并没有发送过去。
所以,我们不能默认在调用send方法发送消息之后消息消息发送成功了。为了确定消息是发送成功,我们要判断消息发送的结果。
可以采用为其添加回调函数的形式,获取回调结果。
如果消息发送失败的话,我们检查失败的原因之后重新发送即可!
可以设置 Producer 的retries(重试次数)为一个比较合理的值,一般是 3 ,但是为了保证消息不丢失的话一般会设置比较大一点。
设置完成之后,当出现网络问题之后能够自动重试消息发送,避免消息丢失。
2.消费者丢失消息的情况:
当消费者拉取到了分区的某个消息之后,消费者会自动提交了 offset。自动提交的话会有一个问题,
试想一下,当消费者刚拿到这个消息准备进行真正消费的时候,突然挂掉了,消息实际上并没有被消费,但是 offset 却被自动提交了。
手动关闭闭自动提交 offset,每次在真正消费完消息之后之后再自己手动提交 offset 。
3.Kafka丢失消息:
a.假如 leader 副本所在的 broker 突然挂掉,那么就要从 follower 副本重新选出一个 leader ,但是 leader 的数据还有一些没有被
follower 副本的同步的话,就会造成消息丢失。
因此可以设置ack=all。
b.设置 replication.factor >= 3
为了保证 leader 副本能有 follower 副本能同步消息,我们一般会为 topic 设置 replication.factor >= 3。这样就可以保证每个
分区(partition) 至少有 3 个副本。虽然造成了数据冗余,但是带来了数据的安全性。
详情参考:https://blog.csdn.net/qq_34337272/article/details/104903388?fps=1&locationNum=2
Q:Kafka怎么保证可靠性?
多副本,以及ISR机制。
在Kafka中主要通过ISR机制来保证消息的可靠性。
ISR(in sync replica):是Kafka动态维护的一组同步副本,在ISR中有成员存活时,只有这个组的成员才可以成为leader,内部保存的为每次提交信息时必须同步的副本(acks = all时),每当leader挂掉时,在ISR集合中选举出一个follower作为leader提供服务,当ISR中的副本被认为坏掉的时候,会被踢出ISR,当重新跟上leader的消息数据时,重新进入ISR。
详情见: https://www.jianshu.com/p/ebeaa7593d83
Q:什么是HW?
HW(high watermark):副本的高水印值,replica中leader副本和follower副本都会有这个值,通过它可以得知副本中已提交或已备份消息的范围,leader副本中的HW,决定了消费者能消费的最新消息能到哪个offset。
Q:什么是LEO?
LEO(log end offset):日志末端位移,代表日志文件中下一条待写入消息的offset,这个offset上实际是没有消息的。不管是leader副本还是follower副本,都有这个值。
Q:Kafka怎么保证一致性?(存疑)
一致性定义:若某条消息对client可见,那么即使Leader挂了,在新Leader上数据依然可以被读到。
HW-HighWaterMark: client可以从Leader读到的最大msg offset,即对外可见的最大offset, HW=max(replica.offset)
对于Leader新收到的msg,client不能立刻消费,Leader会等待该消息被所有ISR中的replica同步后,更新HW,此时该消息才能被client消费,这样就保证了如果Leader fail,该消息仍然可以从新选举的Leader中获取。
对于来自内部Broker的读取请求,没有HW的限制。同时,Follower也会维护一份自己的HW,Folloer.HW = min(Leader.HW, Follower.offset)
详情见:https://www.jianshu.com/p/f0449509fb11
Q:Kafka怎么处理重复消息?怎么避免重复消费?
偏移量offset:消费者每次消费数据的时候,消费者都会记录消费的物理偏移量(offset)的位置。等到下次消费时,他会接着上次位置继续消费。
一般情况下,kafka重复消费都是由于未正常提交offset造成的,比如网络异常,消费者宕机之类的。
使用的是spring-Kafka,所以把Kafka消费者的配置enable.auto.commit设为false,禁止Kafka自动提交offset,从而使用spring-Kafka提供的offset提交策略。
spring-Kafka中的offset提交策略可以保证一批消息数据没有完成消费的情况下,也能提交offset,从而避免了提交失败而导致永远重复消费的问题。
怎么避免重复消费:将消息的唯一标识保存起来,每次消费时判断是否处理过即可。
Q:如何保证消息不被重复消费?(如何保证消息消费的幂等性)
怎么保证消息队列消费的幂等性?其实还是得结合业务来思考,有几个思路:
比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧。
比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
如果是复杂一点的业务,那么每条消息加一个全局唯一的 id,类似订单 id 之类的东西,然后消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?
如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
Q:Kafka消息是采用pull模式,还是push模式?
pull模式。
Q:pull模式和push模式,各有哪些特点?
pull模式,准确性?可以较大保证消费者能获取到消息。
push模式,即时性?可以在broker获取消息后马上送达消费者。
Q:Kafka是如何存储消息的?
Kafka使用日志文件的方式来保存生产者和发送者的消息,每条消息都有一个 offset 值来表示它在分区中的偏移量。
Kafka中存储的一般都是海量的消息数据,为了避免日志文件过大,
一个分片并不是直接对应在一个磁盘上的日志文件,而是对应磁盘上的一个目录。
数据存储设计的特点在于以下几点:
(1)Kafka把主题中一个分区划分成多个分段的小文件段,通过多个小文件段,就容易根据偏移量查找消息、定期清除和删除已经消费完成的数据文件,减少磁盘容量的占用;
(2)采用稀疏索引存储的方式构建日志的偏移量索引文件,并将其映射至内存中,提高查找消息的效率,同时减少磁盘IO操作;
(3)Kafka将消息追加的操作逻辑变成为日志数据文件的顺序写入,极大的提高了磁盘IO的性能;
Q:讲一下Kafka集群的Leader选举机制。
Kafka在Zookeeper上针对每个Topic都维护了一个ISR(in-sync replica---已同步的副本)的集合,集合的增减Kafka都会更新该记录。如果某分区的Leader不可用,Kafka就从ISR集合中选择一个副本作为新的Leader。
分库分表
Q:数据库如何处理海量数据?
分库分表,主从架构,读写分离
Q:数据库分库分表,何时分?怎么分?
水平分库/分表,垂直分库/分表
水平分库/表,各个库和表的结构一模一样。
垂直分库/表,各个库和表的结构不一样。
Q:读写分离怎么做?
主机负责写,从机负责读。
系统设计
分布式、高并发场景
遇到高并发场景,可以使用Redis缓存、Redis限流、MQ异步、MQ削峰等。
Q:在实践中,遇到过哪些并发的业务场景?
秒杀。比如抢商品,抢红包。
秒杀
Q:如何设计一个秒杀/抢券系统?
可以通过队列配合异步处理实现秒杀。
使用redis的list,将商品push进队列,pop出队列。
异步操作不会阻塞,不会消耗太多时间。
Q:如何提高抢券系统的性能?
使用多个list。
使用多线程从队列中拉取数据。
集群提高可用性。
MQ异步处理,削峰。
Q:秒杀怎么避免少卖或超卖?
redis是单进程单线程的,操作具有原子性,不会导致少卖或者超卖。
另外,也可以设置一个版本号version,乐观锁机制。
Q:考勤打卡,假如高峰期有几万人同时打卡,那么怎么应对这种高并发?
使用Redis缓存。
员工点击签到,可以在缓存中set状态。将工号作为key,打卡状态作为value,打卡成功为01,未打卡或者打卡失败为00,
然后再将数据异步地写入到数据库里面就可以了。
Q:如何应对高峰期的超高并发量?
Redis限流。
Redis可以用计数器限流。使用INCR命令,每次都加一,处理完业务逻辑就减一。然后设置一个最大值,当达到最大值后就直接返回,不处理后续的逻辑。
Redis还可以用令牌桶限流。使用Redis队列,每十个数据中push一个令牌桶,每个请求进入后会先从队列中pop数据,如果是令牌就可以通行,不是令牌就直接返回。
短链接
Q:如何将长链接转换成短链接,并发送短信?
短URL从生成到使用分为以下几步:
有一个服务,将要发送给你的长URL对应到一个短URL上.例如www.baidu.com -> www.t.cn/1
把短url拼接到短信等的内容上发送.
用户点击短URL,浏览器用301/302进行重定向,访问到对应的长URL.
展示对应的内容.
Q:长链接和短链接如何互相转换?
思路是建立一个发号器,每次有一个新的长URL进来,我们就增加一,并且将新的数值返回.第一个来的url返回"www.x.cn/0",第二个返回"www.x.cn/1".
Q:长链接和短链接的对应关系如何存储?
如果数据量小且qps低,直接使用数据库的自增主键就可以实现.
还可以将最近/最热门的对应关系存储在K-V数据库中,这样子可以节省空间的同时,加快响应速度.
系统架构与设计
Q:如何提高系统的并发能力?
使用分布式系统。
部署多台服务器,并做负载均衡。
使用缓存(Redis)集群。
数据库分库分表 + 读写分离。
引入消息中间件集群。
Q:设计一个红包系统,需要考虑哪些问题,如何解决?(本质上也是秒杀系统)
Q:如果让你设计一个消息队列,你会怎么设计?
项目经验及数据量
Q:这个项目的亮点、难点在哪里?
Q:如果这个模块挂掉了怎么办?
Q:你们的项目有多少台机器?
Q:你们的项目有多少个实例?
4个实例。
Q:你们的系统QPS(TPS)是多少?
QPS,每秒查询量。
QPS为几百/几千,已经算是比较高的了。
TPS,每秒处理事务数。TPS即每秒处理事务数,包括:”用户请求服务器”、”服务器自己的内部处理”、”服务器返回给用户”,这三个过程,每秒能够完成N个这三个过程,TPS也就是3。
Q:一个接口,多少秒响应才正常?
快的话几毫秒。慢的话1--2秒。异常情况,可能会10几秒。
最好保证99%以上的请求是正常的。
Q:这个接口的请求时间,大概多久?主要耗时在哪里?
Q:系统的数据量多少?有没有分库分表?
正常情况下,几百万的数据量没有必要分库分表。
只有超过几千万才需要分库分表。
Q:插入/更新一条数据要多久?更新十万/百万条数据要多久?
插入/更新一条数据一般要几毫秒。
更新十万条数据最好在10秒以内。
百万条数据最好在50-100秒以内。