系统设计
文章目录
- 系统设计
- 一、缓存设计
- 1、Redis 缓存Key回收策略?
- 1.1、Redis缓存Key过期策略
- 1.2、Redis缓存Key回收策略
- 2、Redis缓存击穿如何解决?
- 2.1、导致Redis缓存穿透原因有那些?
- 3、Redis缓存雪崩如何解决?
- 4、Redis缓存击穿如何解决?
- 5、热Key问题如何解决?
- 6、热节点问题如何解决?
- 7、缓存如何和数据库同步?
- 8、100万级别流量如何处理?
- 9、大Key如何删除?
- 9.1、低峰期删除
- 9.2、scan分批
- 10、缓存备份与恢复?
- 二、消息设计
- 1、消息中间件框架选型?
- 2、消息是选择推模式还是拉模式?
- 3、Kafka专题
- 3.1、消息如何做到生产不重复不丢失?
- 3.2、消息如何做到消费不重复不丢失?
- 4、消息堵塞
- 4.1、快速恢复方案
- 4.2、事后复盘方案
- 5、消费超时异常
- 6、导致kafka rebalance场景
- 7、kafka事务消息
- 消息生产者
- 8、kafka ISR OSR
- 1.概念
- 2.ISR踢出replica
- kafka 副本失效原因
- 9、kafka顺序消费
- 10、RocketMq专题
- 4.1、RocketMq默认消费策略
- 4.2、RocketMq 3种发送消息方式
- 4.3、RocketMq刷盘方式
- 4.4、nameserver理解
- 4.5、消息如何做到生产不重复不丢失?
- 一、RocetMq集群选择
- 二、消息发送方式选择
- 三、刷盘方式
- 4.2、消息如何做到消费不重复不丢失?
- 4.3、死信队列、延时队列?
- 4.4、顺序消费
- 4.5、自动创建主题的弊端
- 三、分布式事务设计
- 四、分布式锁设计
- 五、服务熔断限流降级
- 1、熔断限流服务降级定义
- 熔断定义
- 限流定义
- 服务降级定义
- 2、计数原理
- 滑动窗口算法
- 漏桶算法
- 令牌桶算法
- 线程池算法
- 3、产品对比
- Sentinel介绍
- 支持功能:
- Hystrix介绍
- 六、雪花算法设计
- 1、为什么雪花算法
- 2、雪花算法数据结构
- 2.1、雪花算法结构
- 2.2、算法数据结构解释
- 2.3、算法优缺点
- 2.4、常见问题
- 七、接口幂等设计
- 八、日志打印规范
- 1、日志重要性
- 2、日志打印规范
- 八、数据同步
- 九、系统设计
- 1、高并发系统设计?
- 十、场景设计
- 1、抖音视频点赞
- 2、云音乐海量评论功能设计?
- 3、微博千万粉丝博主,粉丝列表设计?
- 4、秒杀系统设计?
- 5、12306系统如何设计?
- 6、设计dubbo框架?
- 7、排行榜?
- 8、微信抢红包?
- 9、微信扫码登录设计?
- 10、微信朋友圈设计?
- 11、微信群聊&单独个人聊天设计
- 12、短链接系统设计?
- 13、用户岗位数据权限设计?
- 14、IOT&物联网系统设计?
- 15、限流的设计思路?
- 16、单点登录实现思路,4A系统如何设计?
- 23、订单到期未支付自动关闭?
- 24、海量商品收索设计
- 24、海量商品收索设计
一、缓存设计
1、Redis 缓存Key回收策略?
1.1、Redis缓存Key过期策略
如果Redis服务器打开了maxmemory
选项,并且服务器占用的内存数超过了maxmemory
选项所设置的上限值时,会进行内存淘汰,常见的淘汰策略如下:
volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰
volatile-lfu:从已设置过期时间的数据集挑选使用频率最低的数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-lfu:从数据集(server.db[i].dict)中挑选使用频率最低的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据,这也是默认策略。意思是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失
1.2、Redis缓存Key回收策略
- 定时删除
原理:在设置键的过期时间的同时,创建一个定时器(timer),让定时器在键的过期时间来临时,立即执行对键的删除操作
优点:能够很及时的删除过期的Key,能够最大限度的节约内存
缺点:对CPU时间不友好,如果过期的Key比较多时,可能会占用相当一部分CPU时间,对服务器的响应时间和吞吐量造成影响 - 惰性删除
原理:在取出键时才对键进行过期检查,如果发现过期了就会被删除
优点:对CPU友好,能够最大限度的节约CPU时间
缺点:对内存不友好,过期的Key会占用内存,造成浪费 - 定期删除
原理:定期删除策略是定时删除策略和惰性删除策略的一个折中。定期删除策略每隔一段时间执行一次删除过期键的操作,并通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响
优点:对CPU时间和内存空间的一种权衡,可以根据实际使用情况来调整删除操作执行的时长和频率
缺点:确定删除操作执行的时长和频率很难。如果删除操作执行的太频繁,或者执行的时间太长,退化成定时删除策略;如果删除操作执行的太少,或者执行时间太短,退化成惰性删除策略
Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,服务器可以很好地在合理使用CPU时间和避免浪费内存空间之间取得平衡。Redis默认每隔100ms随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。
2、Redis缓存击穿如何解决?
2.1、导致Redis缓存穿透原因有那些?
大量的请求访问不存在的Key导致缓存穿透,穿透对象指的是穿透缓存。 导致Redis缓存击穿可能有下面几种情况。缓存未初始化、代码没有对空缓存处理。
解决办法:
- 对访问数据库的方法加锁,防止缓存穿透,导致大并发打垮数据库
- 对查询不到的结果进行缓存,防止其下次请求继续访问数据库
- 加强风控能力,对频发请求不存在Key的Ip关注其规则,及时加入黑名单
3、Redis缓存雪崩如何解决?
缓存Key集中到期,大量过期导致缓存雪崩
解决办法:
- 对Key设置时间加一个随机数,防止Key集中过期
- 对访问数据库的方法加锁,防止缓存穿透,导致大并发打垮数据库
4、Redis缓存击穿如何解决?
Redis中一个热点key过期(大量用户访问该热点key,但是热点key过期)
解决办法:
- 进行预先的热门词汇的设置,进行key时长的调整
- 实时调整,监控哪些数据是热门数据,实时的调整key的过期时长
- 对访问数据库的方法加锁,防止缓存穿透,导致大并发打垮数据库
5、热Key问题如何解决?
Redis hot key会导致请求分配不均衡,大量的流量集中在几个热Key上面可能导致热节点问题。 Redis基于LFU的热点key发现机制Redis 4.0以上的版本支持了每个节点上的基于LFU的热点key发现机制,使用redis-cli –hotkeys即可,执行redis-cli时加上–hotkeys选项。可以定时在节点中使用该命令来发现对应热点key。
redis-cli –hotkeys
解决办法:
- 本地二级缓存 本地缓存也是一个最常用的解决方案,既然我们的一级缓存扛不住这么大的压力,就再加一个二级缓存吧。由于每个请求都是由service发出的,这个二级缓存加在service端是再合适不过了,因此可以在服务端每次获取到对应热key时,使用本地缓存存储一份,等本地缓存过期后再重新请求,降低redis集群压力。
- 热key拆分 当放入缓存时就将对应业务的缓存key拆分成多个不同的key。如下图所示,我们首先在更新缓存的一侧,将key拆成N份,比如一个key名字叫做"good_100",那我们就可以把它拆成四份,“good_100_ip1”、“good_100_ip2”、“good_100_ip3”、“good_100_ip4”,每次更新和新增时都需要去改动这N个key,这一步就是拆key。
- 热Key限流 限流对业务有一定的损失不可取
6、热节点问题如何解决?
热节点问题,一般就是由于热Key问题导致,导致流量分配不均衡导致。
7、缓存如何和数据库同步?
缓存和数据库数据会有脏数据的可能性。 如果,先更新数据库,在更新缓存,可能存在缓存更新失败的场景。反之,数据库可能更新失败导致. 目前业界有下面几种解决办法。
- 先更新数据库在更新缓存 虽然可能导致缓存数据更新失败,缓存都设置超时时间,到超时时间还是会正确的更新缓存的。如果追求近实时,可以加入canal监听缓存表数据变更,在次通知修改缓存从,这块要注意消息的时效和顺序,防止旧消息更新覆盖新缓存。
- 二提交法 更新缓存前先记录待更新内容,轮询直到redis和Mysql更新成功为止。这个是强制缓存和Mysql数据一致性,对业务有入侵,对性能也不友好,除非特殊场景很少见使用。
8、100万级别流量如何处理?
- 本地缓存
我们Redis缓存单节点并发是10W QPS,百万的请求的确会对Redis集群有很大的压力。 本地缓存一般比分布式缓存快一个数量级别。 本地缓存特点是,速度快、效率高,但分布式环境下容易出现数据不一致的问题、且缓存的数据量一般不能太大,常见的比如guava、ehcache、caffeine。 - 解决热Key问题
按照5方案解决
9、大Key如何删除?
大Key直接删除会导致Redis阻塞,应为redis是单线程,如果阻塞会导致业务暂停。业界删除Redis大Key有以下方案。
9.1、低峰期删除
最简单的方式就是在业务低峰期进行删除,比如大部分场景在凌晨4点左右比较低峰,这时候执行删除,造成的影响比较小。当然这种方式也是无法避免阻塞期间的请求,一般适用执行期间QPS非常小的业务。
9.2、scan分批
既然大key不能一下删除,那么我们就分批删除。分配删除有String、hset、set、zset、list不同数据结构删除方法不同。
hset
对于hset,我们hsan分批删除。每次取个100条,然后删除
# 伪代码
HSCAN key 0 COUNT 100
HDEL key fields
123
set
对于set,我们可以每次随机取一批数据,然后删除
# 伪代码
SRANDMEMBER key 10
SREM key fields
123
zset
对于zset,每次可以直接删除一批数据
伪代码
ZREMRANGEBYRANK key 0 10
12
list
对于list,直接pop
伪代码
i:=0
for {
lpop key
i++
if i%100 == 0 {
sleep(1ms)
}
}
9.3、Lazy Free删除方式
为了解决redis使用del命令删除大体积的key,或者使用flushdb、flushall删除数据库时,造成redis阻塞的情况,在redis 4.0版本中引入了lazy free机制,可将删除操作放在后台,让后台子线程(bio)执行,避免主线程阻塞。使用lazy delete free的方式,删除大键的过程不会阻塞正常请求。
Lazy Free的使用分为两类:第一类是与DEL命令对应的主动删除,第二类是过期key删除、maxmemory key驱逐淘汰删除。
- 主动删除
UNLINK命令是与DEL一样删除key功能的lazy free实现。唯一不同时,UNLINK在删除集合类键时,如果集合键的元素个数大于64个,会把真正的内存释放工作,交给单独的bio来操作,这样就不会阻塞 Redis 主线程了。
示例如下:使用UNLINK命令删除一个大键mylist, 它包含200万个元素,但用时只有0.03毫秒。
// 查询mylist中元素个数
127.0.0.1:7000> LLEN mylist
(integer) 2000000
// 使用UNLINK命令删除mylist
127.0.0.1:7000> UNLINK mylist (integer) 1
// 获取慢查询日志
127.0.0.1:7000> SLOWLOG get
1) (integer) 1
2) (integer) 1505465188
3) (integer) 30 // 命令耗时30微妙,0.03毫秒
4) "UNLINK"
5) "mylist"
- 被动删除
lazy free应用于被动删除中,目前有4种场景,每种场景对应一个配置参数,默认都是关闭的(no)。
#异步删除默认策略
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del no
slave-lazy-flush no
lazyfree-lazy-eviction:针对redis中内存使用达到maxmeory,并设置有淘汰策略的键,在被动淘汰时,是否采用lazy free机制。此场景开启lazy free,可能出现如下问题:因为内存释放不及时,导致redis内存超用,超过maxmemory的限制。此场景使用时,请结合业务测试。
lazyfree-lazy-expire:针对设置有TTL的键,到达过期时间,被redis删除时是否采用lazy free机制。此场景建议开启,因TTL本身是自适应调整的速度。
lazyfree-lazy-server-del:针对有些指令在处理已存在的键时,会带有一个隐式的DEL键的操作。如rename命令,当目标键已存在,redis会先删除目标键,如果这些目标键是一个bigkey,那就会引入阻塞删除的性能问题。此参数设置就是解决这类问题,建议开启。
slave-lazy-flush:主从之间进行全量数据同步时,slave(从)在加载master(主)的RDB文件之前,会运行flushall来清理自己的数据。这个参数设置决定同步过程中是否采用异常flush机制,如果内存变动不大,建议开启,可减少全量同步耗时。
10、缓存备份与恢复?
Redis缓存备份主要通过RDB和AOF两种方式,生产环境我们一般采用混合部署的方式。 redis为了性能不管是aof还是rdb场景都不是实时刷盘。
如果对缓存可靠性要求很高的场景可以开始实时刷盘,牺牲性能。 例如:使用Redis分布式锁,就需要开启这个参数否则会导致锁重复获取。 比如:集群5台Redis其中3台获取锁,有3台机器挂一台,此时Redis不满足半数条件应该不能持有锁。
appendfsync always //每次写操作都flush,影响性能
appendfsync everysec //每秒flush
appendfsync no //消极等待OS刷新(一般30s),可能丢失数据
二、消息设计
1、消息中间件框架选型?
1、适用场景
Kafka适合日志处理;
RocketMQ适合业务处理。
**结论:**根据具体业务定,如果是大数据业务就使用kafka否则使用RocketMq。
2、性能
Kafka单机写入 TPS 号称在百万条/秒;
RocketMQ 大约在10万条/秒。
**结论:**追求性能的话,Kafka单机性能更高, 消息中间件性能都很高,我们实际业务一般很难达到他们入门水平。
3、可靠性
RocketMQ支持异步/同步刷盘;异步/同步Replication;
Kafka使用异步刷盘方式,异步Replication。
**结论:**RocketMQ所支持的同步方式提升了数据的可靠性,但是也进一步降低其性能。
4、实时性
均支持pull长轮询,RocketMQ支持消息推送,可能会满足对实时性要求更高的场景。
**结论:**RocketMQ提供一种选择性。
5、写性能
Kafka每个topic的分区都有一个队列,可能导致其碎文件相对多。网传Kafka单机超过64个队列/分区,消息发送性能降低严重,不过这点我并不认可,如果怀疑这点可以实际测试。
RocketMQ 单机支持最高5万个队列,其设计原理是一个topic的多分区对应一个队列,由于RocketMQ消息是顺序的,其全局持久化方法 commitlog 写入的时候是加锁的,这点也是导致RocketMQ 性能降低的原因。
**结论:**性能和业务是要权衡的,我们实际开发种,很多业务场景是要求消息顺序的。
6、消息顺序性
Kafka kafka单分区消息是顺序的,topic级别消息是乱序的;
RocketMQ 支持严格的消息顺序
**结论:**RocketMQ天然支持顺序消息
7、消费失败重试机制
Kafka消费失败不支持重试
RocketMQ消费失败支持定时重试,每次重试间隔时间顺延。
8、定时/延时消息
Kafka不支持定时消息;
RocketMQ支持定时消息(内部实现是时间轮)
9、分布式事务消息
都支持
10、消息查询机制
Kafka查询历史消息比较费劲。
RocketMQ支持根据Message Id查询消息,也支持根据消息内容查询消息。
11、消息回溯
Kafka理论上可以按照Offset来回溯消息
RocketMQ支持按照时间来回溯消息,精度毫秒,例如从一天之前的某时某分某秒开始重新消费消息
总结:如果不是非kafka不可,建议选择RocektMq
2、消息是选择推模式还是拉模式?
推送模式:消息服务端将生产的消息推送给客户端,客户端的消费能力可能不足,容易出现丢失消息问题。推送模式消息响应比较快实时性更好。
拉取模式:客户端定时拉取,可以根据客户端消息处理能力定量拉取。缺点是实时性不好。
长连接模式:结合拉取和推送的优点,既保证了消息的实时性,也兼顾拉取模式消息的可控性。 长连接原理是客户端请求服务端不立刻回复,检查下数据判断是否响应,如果长连接超市者重新建立连接。
3、Kafka专题
3.1、消息如何做到生产不重复不丢失?
//消息重试次数
retries=Integer.MAX_VALUE
//生产者消息排队队列大小为1防止乱序
max.in.flight.requests.per.connection=1 (Kafka版本>= v0.11 & < v1.1)
//0:发送消息无需等待服务返回 1:需要等待producer返回 all:需要等待producer、follower返回
acks=all
//开启幂等
enable.idempotence = true
消息重试解释
有时候Producer发送Message失败可能并不是因为Broker挂了,可能是因为网络问题,没有连接到Broker等等。这种问题可能在很短暂的时间内就会自动修复,那么在这种情况下,我们希望Producer在发送失败后能重新尝试发送。这里就需要设置retries这个参数,意思就是重试的次数,默认是0次,可以根据实际业务情况设置。 当设置了retries参数大于0后,有可能会带来新的问题。假如我们需要相同Key的Message进入特定的Partition,并且是要严格按照Producer生产Message的顺序排序。那么此时如果第一条Message发送失败,第二条Message发送成功了,第一条通过重试发送成功了,那Message的顺序就发生了变化。
消息顺序解释
这里又会引出一个参数max.in.flight.requests.per.connection,这个参数默认是5,意思是在被Broker阻止前,未通过acks确认的发送请求最大数,也就是在Broker处排队等待acks确认的Message数量。所以刚才那个场景,第一条和第二条Message都在Broker那排队等待确认放行,这时第一条失败了,等重试的第一条Message再来排队时,第二条早都通过进去了,所以排序就乱了。 如果想在设置了retries还要严格控制Message顺序,可以把max.in.flight.requests.per.connection设置为1。让Broker处永远只有一条Message在排队,就可以严格控制顺序了。但是这样做会严重影响性能(接收Message的吞吐量)。
消息幂等设置
enable.idempotence 开启生产者幂等配置。
3.2、消息如何做到消费不重复不丢失?
前提保证消费接口幂等,采用手动提交模式,如果消息没有处理完毕不要提交偏移量。
4、消息堵塞
消息堵塞是消息中间件经常遇到的问题,关键是我们要做好消息堵塞预案,能快速恢复系统可用性。
4.1、快速恢复方案
快速恢复方案重点是,保证当前新增业务不受影响,受影响的业务也要快速恢复。
auto.offset.reset:
earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费
latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据
none:topic各分区都存在已提交的offset时,从offset后开始消费;只要有一个分区不存在已提交的offset,则抛出异常
1、新加入新消费节点:修改auto.offset.reset=latest,修改consumer.group.id换成新增的组,保证新业务正常。
2、新加入老消费节点:保证快速处理掉历史消息
4.2、事后复盘方案
- 消费能力不足
加节点法适合对消息顺序不关注的场景。合理评估集群最大TPS,如果发现集群最大消费能力出现不足,要及时扩容节点。注意点:topic的分区数要大于等于消费节点数,否则会出现部分节点不消费。 - 消费能力递减
业务刚开始的时候集群环境都不会出现问题,随着数据规模变大。导致整个系统的反应变慢,导致消费能力减弱。解决办法:业务的性能边际永远要小于压测边际,否则重新评估系统 - 消费报错异常
如果没有做好异常捕获处理,异常可能会中断消费线程,导致消费无法进行导致消息堵塞。解决办法:消费者测试要多场景测试(并发情况下、异常情况下、正常情况下)
5、消费超时异常
- 超时导致重复消费
kafka消费者采用批量拉取的方式,一次拉取一批记录来消费,如果消费者线程消费超时会导致整批消息的回滚,从而导致已经消费过的数据再消费一遍,消费者不幂等会出大问题,这也是为啥要强调使用消费队列一定要考虑幂等性的原因。细思极恐的是:这次在规定的时间内消费不完,你能保证下次就能消费完吗?有可能进入拉取->超时->回滚->拉取的无限循环中. 原因:集群以为消费线程挂了,触发了rebanlance(这一批已经给别的消费者线程消费了)。当前消费者线程业务逻辑执行完了再去同步游标报错了,没有提交成功,这就导致了两个消费者线程把同一批消息消息了两遍。
- 涉及消费超时的配置
max.poll.interval.ms 拉取时间间隔 默认值:300s 每次拉取的记录必须在该时间内消费完
max.poll.records 每次拉取条数 默认值:500条 这个条数一定要结合业务背景合理设置
fetch.max.wait.ms 每次拉取最大等待时间 时间达到或者消息大小谁先满足条件都触发,没有消息但时间达到返回空消息体
fetch.min.bytes 每次拉取最小字节数 时间达到或者消息大小谁先满足条件都触发
heartbeat.interval.ms 向协调器发送心跳的时间间隔 默认值:3s 建议不超过session.timeout.ms的1/3
session.timeout.ms 心跳超时时间 默认值:30s 配置太大会导致真死消费者检测太慢
- max.poll.interval.ms 和session.timeout.ms区别
KIP-62(kafka规范)前只有session.timeout.ms参数
KIP-62后不通过poll()方法发送心跳,而是后台另起一个心跳线程,这就允许单次poll处理更长时间。不会因为单次处理超时假死引发不必要的rebanlance
max.poll.interval.ms 检测消费者处理线程死亡
session.timeout.ms 检测整个消费者死亡
- max.poll.records和max.poll.interval.ms如何配置
一定要结合具体业务背景,预估消费能力,合理设置【max.poll.records】和【max.poll.interval.ms】。 默认情况下max.poll.records=500条,max.poll.interval.ms=300秒, 也就是说300s必须消费完500条,否则超时回滚!将【session.timeout.ms】设置为【heartbeat.interval.ms】的三倍。即连续三次收不到心跳认为消费者挂了。
- poll(long timeout)中timeout和max.poll.interval.ms区别
poll(long timeout)如果消费者从buffer中经历timeout毫秒后拉不到数据,就返回个空消息。 max.poll.interval.ms每次拉取的记录必须在该时间内消费完。
6、导致kafka rebalance场景
rebalance是kafka集群自我保护的一种策略,当组内消费者、生产者、topic、分区发生变化会触发rebalance。
导致rebalance原因:
heartbeat.interval.ms设置过大
kafka consumer心跳周期设置过大,导致kafka接收客户端超时导致频繁rbalance,合理设置: heartbeat.interval.ms必须设置时间为session.timeout.ms的1/3内。max.poll.interval.ms(poll时间间隔)设置过小或者max.poll.records(poll记录数)设置过大当kafka consumer在指定的时间无法处理掉poll的消息,Consumer会主动发起 “离开组” 的请求。
7、kafka事务消息
消息生产者
通过事务可以弥补幂等性不能跨多个分区的缺陷,且可以保证对多个分区写入操作的原子性在使用Kafka事务前,需要开启幂等特性,生产者参数设置: enable.idempotence = true。
8、kafka ISR OSR
1.概念
1.AR(Assigned Repllicas):一个分区里面所有的副本(不区分leader和follower)
2.ISR(In-Sync Replicas):能够和leader保持同步的follower+leader本身组成的集合
3.OSR(Out-Sync Replicas):不能和leader保持同步的follower集合
公式: AR=ISR+OSR
1、kafka只会保证ISR集合中的所有副本保持完全同步
2、kafka一定会保证leader接收到消息然后完全同步给ISR中的所有副本
3、ISR的机制保证了处于ISR内部的follower都可以和leader保持同步,一旦出现故障或者延迟(在一定时间内没有同步),就会被提出ISR
2.ISR踢出replica
# 默认10000 即 10秒
replica.lag.time.max.ms
# 允许 follower 副本落后 leader 副本的消息数量,超过这个数量后,follower 会被踢出 ISR
replica.lag.max.messages
一个是基于时间间隔,一个是基于消息条数,0.9版本之后移除了消息条数配置 replica.lag.max.messages
replica.lag.time.max.ms的正确理解
只要在 replica.lag.time.max.ms 时间内 follower 有同步消息,即认为该 follower 处于 ISR 中。千万不要这么认为,因为这里还涉及一个速率问题(你理解为蓄水池一个放水一个注水的问题)。
如果leader副本的消息流入速度大于follower副本的拉取速度时,你follower就是实时同步有什么用?
典型的出工不出力,消息只会越差越多,这种follower肯定是要被踢出ISR的。
当follower副本将leader副本的LEO之前的日志全部同步时,则认为该follower副本已经追赶上leader副本。
此时更新该副本的lastCaughtUpTimeMs标识。
Kafka的副本管理器(ReplicaManager)启动时会启动一个副本过期检测的定时任务,
会定时检查当前时间与副本的lastCaughtUpTimeMs差值是否大于参数replica.lag.time.max.ms指定的值。
所以replica.lag.time.max.ms的正确理解是:
follower在过去的replica.lag.time.max.ms时间内,已经追赶上leader一次了。
kafka 副本失效原因
两个方面,一个是Kafka自身的问题,另一个是外部原因
Kafka源码注释中说明了一般有两种情况会导致副本失效:
1、follower副本进程卡住,在一段时间内根本没有想leader副本发起同步请求,比如频繁的Full GC。
2、follower副本进程同步过慢,在一段时间内都无法追赶上leader副本,比如IO开销过大。
1、通过工具增加了副本因子,那么新增加的副本在赶上leader副本之前也都是处于失效状态的。
2、如果一个follower副本由于某些原因(比如宕机)而下线,之后又上线,在追赶上leader副本之前也是出于失效状态。
什么情况下OSR中的replica会重新假如ISR
当replica追上leader,就会回到ISR集合当中
9、kafka顺序消费
发送顺序保证、消费顺序保证(单分区,不能多线程,多节点拉取锁等待)
10、RocketMq专题
4.1、RocketMq默认消费策略
CONSUME_FROM_LAST_OFFSET 默认策略,从该队列最尾开始消费,即跳过历史消息
CONSUME_FROM_FIRST_OFFSET 从队列最开始开始消费,即历史消息(还储存在broker的)全部消费一遍
CONSUME_FROM_TIMESTAMP 从某个时间点开始消费,和setConsumeTimestamp()配合使用,默认是半个小时以前
4.2、RocketMq 3种发送消息方式
1.同步发送:Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
2.异步发送:Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。
3.Oneway发送:Oneway 方式只负责发送请求,不等待应答,Producer只负责把请求发出去,而不处理响应结果。
4.3、RocketMq刷盘方式
SYNC_FLUSH, 同步刷盘
ASYNC_FLUSH 异步刷盘(默认)
4.4、nameserver理解
NameServer是一个独立的注册中心,RocketMq Broker每隔30秒向所有NameServer发送心跳,心跳包含了自身的topic配置信息。 NameServer每隔10秒,扫描所有还存活的broker连接,如果某个连接的最后更新时间与当前时间差值超过2分钟,则断开此连接,NameServer也会断开此broker下所有与slave的连接。同时更新topic与队列的对应关系,但不通知生产者和消费者。
Consumer随机与一个NameServer建立长连接,如果该NameServer断开,则从NameServer列表中查找下一个进行连接。Consumer主要从NameServer中根据Topic查询Broker的地址,查到就会缓存到客户端,并向提供Topic服务的Master、Slave建立长连接,且定时向Master、Slave发送心跳。如果Broker宕机,则NameServer会将其剔除,而Consumer端的定时任务MQClientInstance.this.updateTopicRouteInfoFromNameServer每30秒执行一次,将Topic对应的Broker地址拉取下来,此地址只有Slave地址了,此时Consumer从Slave上消费。
消费者与Master和Slave都建有连接,在不同场景有不同的消费规则。
4.5、消息如何做到生产不重复不丢失?
一、RocetMq集群选择
可以采用,多Master多Slave模式同步双写的模式,方式RocketMq写入丢场景。
1、单Master模式
这种方式风险较大,一旦Broker重启或者宕机时,会导致整个服务不可用。不建议线上环境使用,可以用于本地测试。
2、多Master模式
一个集群无Slave,全是Master,例如2个Master或者3个Master,这种模式的优缺点如下
优点:配置简单,单个Master宕机或重启维护对应用无影响,在磁盘配置为RAID10时,即使机器宕机不可恢复情况下,由于RAID10磁盘非常可靠,消息也不会丢(异步刷盘丢失少量消息,同步刷盘一条不丢),性能最高;
缺点:单台机器宕机期间,这台机器上未被消费的消息在机器恢复之前不可订阅,消息实时性会受到影响。
多Master多Slave模式-异步复制每个Master配置一个Slave,有多对Master-Slave,HA采用异步复制方式,主备有短暂消息延迟(毫秒级),这种模式的优缺点如下
优点:即使磁盘损坏,消息丢失的非常少,且消息实时性不会受影响,同时Master宕机后,消费者仍然可以从Slave消费,而且此过程对应用透明,不需要人工干预,性能同多Master模式几乎一样;
缺点:Master宕机,磁盘损坏情况下会丢失少量消息(非同步刷盘的情况下)3、多Master多Slave模式-同步双写
每个Master配置一个Slave,有多对Master-Slave,HA采用同步双写方式,即只有主备都写成功,才向应用返回成功,这种模式的优缺点如下:
优点:数据与服务都无单点故障,Master宕机情况下,消息无延迟,服务可用性与数据可用性都非常高;
缺点:性能比异步复制模式略低(大约低10%左右),发送单个消息的RT会略高,且目前版本在主节点宕机后,备机不能自动切换为主机。如果是想不存在消息丢失的情况,那么在多Master的情况下要配置消息同步刷盘,而在 多Master多Slave模式-同步双写 的情况下配置同步刷盘。二、消息发送方式选择
同步发送消息方式,Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
三、刷盘方式
SYNC_FLUSH, 同步刷盘方式
4.2、消息如何做到消费不重复不丢失?
消息消费完毕后,在提交事务。不要在消息拉下来就提交事务。
4.3、死信队列、延时队列?
死信队列:用于处理无法被正常消费的消息。当一条消息初次消费失败,消息队列会自动进行消息重试;达到最大重试次数后,若消费依然失败,则表明消费者在正常情况下无法正确地消费该消息,此时,消息队列 不会立刻将消息丢弃,而是将其发送到该消费者对应的特殊队列中。
延时队列: 用于定时任务场景,消息定时消费,比如:会员到期。延时队列背后使用的是时间轮技术,如何构建时间轮。 例如构建1小时时间轮,1、时间/3600 2、每秒使用一个队列存放消息 3、消息先进先出模式。
4.4、顺序消费
使用单分区消费模式拉取消息,串行消费,消费完成后在拉取消息。
4.5、自动创建主题的弊端
自动创建主题那么有可能该主题的消息都只会发往一台 Broker,起不到负载均衡的作用。
因为创建新 Topic 的请求到达 Broker 之后,Broker 创建对应的路由信息,但是心跳是每 30s 发送一次,所以说 NameServer 最长需要 30s 才能得知这个新 Topic 的路由信息。
假设此时发送方还在连续快速的发送消息,那 NameServer 上其实还没有关于这个 Topic 的路由信息,所以有机会让别的允许自动创建的 Broker 也创建对应的 Topic 路由信息,这样集群里的 Broker 就能接受这个 Topic 的信息,达到负载均衡的目的,但也有个别 Broker 可能,没收到。
如果发送方这一次发了之后 30s 内一个都不发,之前的那个 Broker 随着心跳把这个路由信息更新到 NameServer 了,那么之后发送该 Topic 消息的 Producer 从 NameServer 只能得知该 Topic 消息只能发往之前的那台 Broker ,这就不均衡了,如果这个新主题消息很多,那台 Broker 负载就很高了。
4.5、事务消息
三、分布式事务设计
tcc、saga、反向sql回滚、
四、分布式锁设计
reddsion 优点并发大。
1、缺点一、分布式锁使用redlock算法不可靠,比如5台redis集群,此刻已经获取到锁,如果已经挂3台,集群还认为自己获取到锁,应为符合n/2+1条件。
2、缺点二、redis刷盘默认不是每次写入就刷一条记录的,可能丢失记录导致锁不准。 比如:5台redis,写入的时候挂3台,丢失key。然后在锁周期全部重启,此时锁丢失。
五、服务熔断限流降级
1、熔断限流服务降级定义
熔断定义
熔断状态下,服务进入不可用状态,此时有少量的流量侦探服务状态,如果服务没有恢复继续熔断
限流定义
服务访问量如果超过某个阈值,超过限制部分的流量直接拒绝掉
服务降级定义
熔断状态或者主动关闭服务部分模块,保留主要功能的方式,就是服务降级的一种方式
2、计数原理
滑动窗口算法
动时间窗口算法是目前比较流行的限流算法,主要思想是把时间看做是一个向前滚动的窗口。然后统计其窗口的总请求数。 例如:如图,滑动时间窗口将每秒钟时间(1000ms)切分为2个窗口,W1永远指向前半秒(前500ms),w2永远指向后半秒(后500ms),初始时,W1指向[0,500)ms区间,W2指向[500,1000)ms区间,当时间往后走到1001ms时,w1会去指向[1000,1500)ms区间。W2同理,会不断的替换为新的区间,以实现窗口滑动。用W1+W2两个区间的计数之和进行QPS计算,这样就解决了固定时间窗口下的临界流量问题。 使用滑动窗口方案:阿里Sentinel 熔断方案
漏桶算法
漏桶算法进行限流,例如每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。 实现方案:漏桶实现原理简单用AOP+队列即可。 使用漏桶方案:nginx
令牌桶算法
从某种意义上讲,令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。 在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。 使用令牌桶方案:Redisson.RateLimter、Guava.RateLimter、阿里Sentinel限流方案
线程池算法
线程词方案为每一个依赖服务维护一个独立的线程池,或者是信号量,当线程池已满时,直接拒绝对这个服务的调用,线程池代表产品是Hystrix。
3、产品对比
Sentinel介绍
Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。
支持功能:
- 流量控制 ,原理:令牌桶
- 熔断降级, 原理:滑动窗口
- 系统自适应保护
- 集群流量控制
- 网关流量控制
- 热点参数限流
- 来源访问控制
StatisticSlot 则用于记录、统计不同纬度的 runtime 指标监控信息;
FlowSlot 则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;
AuthoritySlot 则根据配置的黑白名单和调用来源信息,来做黑白名单控制;
DegradeSlot 则通过统计信息以及预设的规则,来做熔断降级;
SystemSlot 则通过系统的状态,例如 load1 等,来控制总的入口流量;
Hystrix介绍
Hystrix 提供了两个请求命令:HystrixCommand
、HystrixObservableCommand
可以使用这两个对象来包裹待执行的任务。HystrixCommand
用与依赖服务返回单个操作结果的场景,而HystrixObservableCommand
用于依赖服务返回多个操作结果的场景, Hystrix服务调用的内部逻辑如下图所示:
- 构建Hystrix的Command对象, 调用执行方法.
- Hystrix检查当前服务的熔断器开关是否开启, 若开启, 则执行降级服务getFallback方法.
- 若熔断器开关关闭, 则Hystrix检查当前服务的线程池是否能接收新的请求, 若超过线程池已满, 则执行降级服务getFallback方法.
- 若线程池接受请求, 则Hystrix开始执行服务调用具体逻辑run方法.
- 若服务执行失败, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.
- 若服务执行超时, 则执行降级服务getFallback方法, 并将执行结果上报Metrics更新服务健康状况.
- 若服务执行成功, 返回正常结果.
- 若服务降级方法getFallback执行成功, 则返回降级结果.
- 若服务降级方法getFallback执行失败, 则抛出异常.
六、雪花算法设计
1、为什么雪花算法
雪花算法对数据库主键索引友好,不容易触发索引再平衡。 雪花算法ID是一类有序、递增的数字,不会像其他字符串类索引一样导致索引结构跳跃。 雪花算法有序、递增在MySQL B+树遍历data节点也有优势,有序的数据结构二分法遍历时间复杂度更低。
2、雪花算法数据结构
2.1、雪花算法结构
讲雪花算法结构前,先介绍下10进制,二进制转换关系吧。雪花算法本质是一个long数字,展示其规则是按照位的方式展示。 snowflake算法生成ID是一个64bit大小的整数,它的二进制结构如下图:
Long num =Long.MAX_VALUE;
String binary = Long.toBinaryString(num);
System.out.println(num+"的二进制为:"+binary);
long num1 = Long.parseLong(binary,2);
System.out.println(binary+"的十进制为:"+num1);
输出:
9223372036854775807的二进制为:111111111111111111111111111111111111111111111111111111111111111
111111111111111111111111111111111111111111111111111111111111111的十进制为:9223372036854775807
2.2、算法数据结构解释
[1]、1位,不用,二进制中的最高位是符号位,1表示负数,0表示正数,由于我们生成的雪花算法都是正整数,所以这里是0
[2]、41位,这里的时间戳是表示的是从起始时间算起,到生成id时间所经历的时间戳,也就是(当前时间戳-起始时间戳(固定)) 这里一共是41位,范围就是(0~ 2^41),这么大的毫秒数转化成时间就是大约69年.
/**
* 时间戳最大值:2<<41
* 每年多少秒:365*24*3600
* 1秒有多少毫秒:1000
* */
//算法有效时长
double year = Math.pow(2, 41) /(365*24*3600) / 1000;
year = 69
[3]、10位,这里的10位代表工作机器id,一共可以部署在(2^10=1024)台机器上面,10位又可以分为前面五位是数据中心id(0~31),后面五位是机器id(0-31)
[4]、共12位,序列位,一共可用(0 ~ 2^12)共4096个数字
double qps = Math.pow(2, 12) * 1000;
qps = 409.6万
2.3、算法优缺点
优点:
● 方案高效,扩展方便,通过设置不同长度的数据工作位可以调整雪花算法的QPS。
● 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的。可以根据自身业务特性分配 bit 位,非常灵活
● 高QPS 理论qps可达400万,算法有效期69年,比较靠谱。
缺点:
● 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。 算法要记录最后异常生成雪花算法的时间,如果发生时钟回拨拒绝服务。
2.4、常见问题
1、snowflake为啥用64位,是否可以扩容?
不能扩容,64位是long的极限。
2、snowflake是否可以改变其QPS?
可以降低时间戳的位数,增加工作区域位数,但是同时也降低了算法有效的时间。
3、snowflake是否可以扩展位长度?
算法角度上,snowflake是可以扩展位长度,这样可以获取更大的key不重复使用年限及QPS,但是snowflake生成的是数字,在java中最大的数字就是long,故不能扩容. 有人肯定想mysql主键设置为String突破Long长度限制,这种设计虽然突破了限制.但是会导致mysql索引问题,可能导致B树重新平衡、大字段做主键会浪费MySQl宝贵的内存资源
七、接口幂等设计
接口幂等是系统数据安全及治理的一个基础保障。删除、修改接口是天然幂等,新增接口没有做幂等,由于网络的不可靠性都会放大产生生产损失。比如:网络抖动保存多条数据、消息中间件的重复消费、网络超时导致保存多条记录。
解决方案:
- Token令牌解决幂等 在保存场景先获取Token令牌并把令牌保存在Redis,然后带着令牌值调用保存接口,如果令牌存在Redis中存在,则说明其未保存,然后执行保存动作并且删除Redis中的值。如果不存在则说明业务已经保存过。 查询令牌是否存在要加锁,否则在多线程环境下回有脏数据。
- 数据库主键解决幂等 在保存场景先获取ID中心的主键数字,用户在调用保存方法时候带上这个值,如果数据库不存在则保存成功,如果数据库存在则报主键冲突,此方案不会有多线程问题。
总结:1、token令牌的方式对代码 ‘0入侵’是一个比较好的解决方案。此外这2套方案在使用的时候都应该框架化降低对业务代码入侵。 2、注意调用方式的不同 web调用、服务调用做好功能包装。
八、日志打印规范
1、日志重要性
日志能证明我们的执行过程是否按照预设规则进行
日志可以溯源程序执行过程
日志可以证明生产环境那个环节出现问题
日志让我们重现问题提供参数依据
日志可以对程序提供监控预警支持
2、日志打印规范
规范一: 【强制】级别只允许使用ERROR、WARN、INFO、DEBUG,定义如下:
级别 | 定义 |
ERROR | 表示应用系统出现异常或故障,需要预警并及时解决,否则该功能将无法正常运行并提供服务能力。 |
WARN | 表示应用系统出现不符合预期的现象,但服务并未受损,可根据实际情况选择性预警,解决时效要求不高,但需要额外关注。 |
INFO | 表示用于记录系统运行过程或重要信息点,主要为故障定位、过程追溯、数据分析等提供辅助能力。 |
DEBUG | 表示用于在测试或本地的非生产环境中使用,主要为了方便开发调试程序,而在生产环境中禁止使用。 |
规范二: 【强制】禁止使用 Logback/Log4j2 等的API,应使用SLF4J的API。
规范三: 【强制】在接口/方法的入口/出口处,打印请求及响应参数日志。
规范四: 【强制】ERROR级别日志需打印堆栈,而非ERROR级别日志则不需要。
规范五: 【强制】禁止在代码循环体中直接打印非DEBUG级别的日志。
规范六: 【强制】禁止日志打印内容中仅打印特殊字符或数字的情况。
规范七: 【建议】日志内容中应包含关键特征类信息,例如:用户标识或流水号。
规范八: 【建议】应采用异步打印模式,且打印时建议关闭打印位置信息。
规范九: 【建议】日志打印若出现堵塞,建议至少丢弃INFO级别以上的日志。
规范十: 【建议】不要再异常中打印日志在抛出异常。
规范十一: 【强制】禁止不要重复打印日志影响机器性能
规范十二: 【建议】日志文件按照类型分离容易排查问题
规范十三: 【建议】日志文件按照时间分片降低查找日志时间开销及日志写开销
规范十四: 【建议】最少大于日志时间,线程id等核心日志信息,在排查问题可以通过线程id串起来上下文
规范十五: 【建议】核心模块日志要多打印,做到考日志复原业务场景
八、数据同步
实时同步、增量同步
九、系统设计
1、高并发系统设计?
如何理解高并发系统
所谓设计高并发系统,就是设计一个系统,保证它整体可用的同时,能够处理很高的并发用户请求,能够承受很大的流量冲击。 我们要设计高并发的系统,那就需要处理好一些常见的系统瓶颈问题,如内存不足、磁盘空间不足,连接数不够,网络宽带不够等等,以应对突发的流量洪峰。
1、分而治之,横向扩展
如果你只部署一个应用,只部署一台服务器,那抗住的流量请求是非常有限的。并且,单体的应用,有单点的风险,如果它挂了,那服务就不可用了。 因此,设计一个高并发系统,我们可以分而治之,横向扩展。也就是说,采用分布式部署的方式,部署多台服务器,把流量分流开,让每个服务器都承担一部分的并发和流量,提升整体系统的并发能力。
2、微服务拆分(系统拆分)
要提高系统的吞吐,提高系统的处理并发请求的能力。除了采用分布式部署的方式外,还可以做微服务拆分,这样就可以达到分摊请求流量的目的,提高了并发能力。
所谓的微服务拆分,其实就是把一个单体的应用,按功能单一性,拆分为多个服务模块。比如一个电商系统,拆分为用户系统、订单系统、商品系统等等。
3、分库分表
当业务量暴增的话,MySQL单机磁盘容量会撑爆。并且,我们知道数据库连接数是有限的。在高并发的场景下,大量请求访问数据库,MySQL单机是扛不住的!高并发场景下,会出现too many connections报错。
所以高并发的系统,需要考虑拆分为多个数据库,来抗住高并发的毒打。而假如你的单表数据量非常大,存储和查询的性能就会遇到瓶颈了,如果你做了很多优化之后还是无法提升效率的时候,就需要考虑做分表了。一般千万级别数据量,就需要分表,每个表的数据量少一点,提升SQL查询性能。
当面试官问要求你设计一个高并发系统的时候,一般都要说到分库分表这个点。
4、池化技术
在高并发的场景下,数据库连接数可能成为瓶颈,因为连接数是有限的。 我们的请求调用数据库时,都会先获取数据库的连接,然后依靠这个连接来查询数据,搞完收工,最后关闭连接,释放资源。如果我们不用数据库连接池的话,每次执行SQL,都要创建连接和销毁连接,这就会导致每个查询请求都变得更慢了,相应的,系统处理用户请求的能力就降低了。因此,需要使用池化技术,即数据库连接池、HTTP 连接池、Redis 连接池等等。使用数据库连接池,可以避免每次查询都新建连接,减少不必要的资源开销,通过复用连接池,提高系统处理高并发请求的能力。同理,我们使用线程池,也能让任务并行处理,更高效地完成任务。
5、主从分离
通常来说,一台单机的MySQL服务器,可以支持500左右的TPS和10000左右的QPS,即单机支撑的请求访问是有限的。因此你做了分布式部署,部署了多台机器,部署了主数据库、从数据库。
但是,如果双十一搞活动,流量肯定会猛增的。如果所有的查询请求,都走主库的话,主库肯定扛不住,因为查询请求量是非常非常大的。因此一般都要求做主从分离,然后实时性要求不高的读请求,都去读从库,写的请求或者实时性要求高的请求,才走主库。这样就很好保护了主库,也提高了系统的吞吐。
当然,如果回答了主从分离,面试官可能扩展开问你主从复制原理,问你主从延迟问题等等,这块大家需要**全方位掌握好。
6、使用缓存
无论是操作系统,浏览器,还是一些复杂的中间件,你都可以看到缓存的影子。我们使用缓存,主要是提升系统接口的性能,这样高并发场景,你的系统就可以支持更多的用户同时访问。
常用的缓存包括:Redis缓存,JVM本地缓存,memcached等等。就拿Redis来说,它单机就能轻轻松松应对几万的并发,你读场景的业务,可以用缓存来抗高并发。缓存虽然用得爽,但是要注意缓存使用的一些问题:缓存与数据库的一致性问题,缓存雪崩,缓存穿透,缓存击穿。
7、CDN,加速静态资源访问
商品图片,icon等等静态资源,可以对页面做静态化处理,减少访问服务端的请求。如果用户分布在全国各地,有的在上海,有的在深圳,地域相差很远,网速也各不相同。为了让用户最快访问到页面,可以使用CDN。CDN可以让用户就近获取所需内容。
8、使用双写
对于数据精准要求很高的系统,且要保证故障快速切换,可以考虑到双写方案。双写方案却别于主从方案,主从在主出现故障后,可能有部分数据未同步到从服务器,可能导致脏数据。双写虽然保证数据故障切换数据安全性,但是也会降低系统一部分性能。
2、低延时系统设计?
游戏行业
- 大数据型系统设计?
报表类、电商类 - 高并发+大数据型系统设计?
- 高并发+低延时系统设计?
十、场景设计
1、抖音视频点赞
1、用户点赞+1,取消点赞-1,发送到消息中间件
2、批量消费消息中间件点赞,统计每一个视频的点赞数结果汇总,记录在redis中
3、将点赞的关系记录在Mysql数据库中
总结:设计做到流量隔离+全程无锁设计,性能高
2、云音乐海量评论功能设计?
1、使用redis list数据结构缓存评论信息
2、如果很超级热门评论可以放在应用二级缓存
3、微博千万粉丝博主,粉丝列表设计?
1、使用redis list数据结构缓存评论信息
2、如果很超级热门明星可以放在应用二级缓存
4、秒杀系统设计?
5、12306系统如何设计?
页面日均浏览量:560亿次 ,余票查询量:750亿次 , 发售火车票:1300万次 ,一小时点击最高次数:60亿次 ,QPS = 0.4亿/秒,使用Gemfrie缓存方案。
缓存不一致性:
余票查询可以不一致,余票查询缓存定时刷新,降低系统负载。
多缓存集群:
6、设计dubbo框架?
1、RPC重点是服务发现、长连接通讯、同步调用、异步通知
2、服务发现,基于zk的节点订阅
3、长连接通讯,基于netty提供通讯框架
4、同步调用,基于object的wait、notify阻塞唤醒。消费者每次消费要带上唯一id给服务端,服务端返回的时候也要带上,无服务段返回id,查找消费者阻塞对象然后唤醒对象,给其返回结果。
5、异步通知,dubbo通讯过程是异步的,此时接口返回结果是null
6、基于Future、CompletableFuture实现对任务阻塞,然后在threadlocal返回结果
7、排行榜?
1、使用redis list数据结构缓存评论信息
2、如果很超级热门评论可以放在应用二级缓存
8、微信抢红包?
1、红包随机拆分成n份
2、用户抢红包的时候锁定红包id,获取分布式锁实现抢红包。一定要锁红包id,可以提高分布式锁性能
9、微信扫码登录设计?
1、技术原理基于长轮询方案
2、前端可以使用websocekt,后端使用netty
3、用户扫码建立长连接,前端发心跳维持连接,如果用户不超时,一直维持长连接
4、用户扫码成功后,返回结果给前端,终端长连接
10、微信朋友圈设计?
1、朋友圈问题如下,千人千面系统缓存命中率; 分页展示属于数据库不友好型;
2、业务方案:为了防止大缓存及朋友圈刷屏对其他用户干扰,朋友圈没人每天最大发送数量是500条。
3、后端方案:使用redis list数据结构,key = userId+time, 每个用户一天发送的朋友圈都存储在这个key中
4、获取数据的时候,传递朋友关系 userId及用户刷朋友圈的时间,获取缓存的结果,在应用中合并;这个时候可能涉及另外一个问题,获取的结果太多,一次返回可能导致用户端网络阻塞,这个时候我们需要优化,将这次请求的结果存在集合中,然后用户分批拿去结果。直接到hashmap为空,在继续根据时间拿去缓存内容。
5、后端的缓存可以设置个生命周期,如果过了热点期可以走数据库。
6、前端方案: 前端要做好数据收集存储,降低后端的请求量
方案点评: 缓存全部覆盖,算法时间复杂度O(1)
11、微信群聊&单独个人聊天设计
12、短链接系统设计?
13、用户岗位数据权限设计?
#接口维度的权限控制
1、数据权限细化到接口。
2、角色绑定接口权限
3、岗位绑定角色,岗位也可以绑定接口权限,比如副经理在即将升级为正经理,然后代劳正经理的权限
4、人与角色绑定,权限更灵活,做到千人千面的权限
#数据维度的权限控制
1、我们每一条数据都记录一个最小组织、岗位的字段
2、可以对某个人、组织、岗位分配数据权限
3、人在查询的时候,自动带上自己的数据权限达到过滤效果
14、IOT&物联网系统设计?
#实时性
1、交易型物联网系统设计,这类系统对交互要求比较高,对延时难容忍
2、心跳设计: 心跳要频繁点,快速发现客户端掉线,掉线后要重新连接系统,如果网络原因掉线,要告知客户系统不可用(典型场景就是自动贩卖机)
3、机器规模:每台网关连接的机器不要太多,太多机器就代表太多的消息量,对实时性不好
4、垃圾回收器: 垃圾回收器选择CMS、G1、ZGC等
#吞吐型
1、这类系统追求高吞吐性,对延时不很敏感,数据量比较大
未完成::
15、限流的设计思路?
限流系统思路同,五、服务熔断限流降级
16、单点登录实现思路,4A系统如何设计?
1、单点登录一般
17、手机令牌如何生成密钥?
18、如何设计count服务?
19、请设计一个短链系统?
20、Open API接口设计?
21、如何设计无界实时数据流的Join?
22、网关系统如何设计?
23、订单到期未支付自动关闭?
我们在电商平台购物时,下单之后会有一个付款倒计时,如果在规定的时间内未付款,订单就会自动关闭。类似这样的场景还有很多,比如优惠劵到期失效,下单后自动发消息等。
1、定时任务
通过定时任务关闭订单,是一种成本很低,实现也很容易的方案。通过简单的几行代码,写一个定时任务,定期扫描数据库中的订单,如果时间过期,就将其状态更新为关闭即可。
优点:实现容易,成本低,基本不依赖其他组件。
缺点:时间可能不够精确。由于定时任务扫描的间隔是固定的,所以可能造成一些订单已经过期了一段时间才被扫描到,订单关闭的时间比正常时间晚一些。增加了数据库的压力。随着订单的数量越来越多,扫描的成本也会越来越大,执行时间也会被拉长,可能导致某些应该被关闭的订单迟迟没有被关闭。总结:采用定时任务的方案比较适合对时间要求不是很敏感,并且数据量不太多的业务场景。
2、JDK延迟队列DelayQueue
DelayQueue是JDK提供的一个无界队列,我们可以看到,DelayQueue队列中的元素需要实现Delayed,它只提供了一个方法,就是获取过期时间。
用户的订单生成以后,设置过期时间比如30分钟,放入定义好的DelayQueue,然后创建一个线程,在线程中通过while(true)不断的从DelayQueue中获取过期的数据。
优点:不依赖任何第三方组件,连数据库也不需要了,实现起来也方便。
缺点:因为DelayQueue是一个无界队列,如果放入的订单过多,会造成JVM OOM。DelayQueue基于JVM内存,如果JVM重启了,那所有数据就丢失了。
总结:DelayQueue适用于数据量较小,且丢失也不影响主业务的场景,比如内部系统的一些非重要通知,就算丢失,也不会有太大影响。
3、redis过期监听
redis是一个高性能的KV数据库,除了用作缓存以外,其实还提供了过期监听的功能。在redis.conf中,配置notify-keyspace-events Ex即可开启此功能。然后在代码中继承KeyspaceEventMessageListener,实现onMessage就可以监听过期的数据量。
优点:由于redis的高性能,所以我们在设置key,或者消费key时,速度上是可以保证的。
缺点:由于redis的key过期策略原因,当一个key过期时,redis无法保证立刻将其删除,自然我们的监听事件也无法第一时间消费到这个key,所以会存在一定的延迟。另外,在redis5.0之前,订阅发布中的消息并没有被持久化,自然也没有所谓的确认机制。所以一旦消费消息的过程中我们的客户端发生了宕机,这条消息就彻底丢失了。
总结:redis的过期订阅相比于其他方案没有太大的优势,在实际生产环境中,用得相对较少。
4、Redisson分布式延迟队列RDelayedQueue
Redisson是一个基于redis实现的Java 驻内存数据网格,它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
Redisson除了提供我们常用的分布式锁外,还提供了一个分布式延迟队列RDelayedQueue,他是一种基于zset结构实现的延迟队列,其实现类是RedissonDelayedQueue。
优点:使用简单,并且其实现类中大量使用lua脚本保证其原子性,不会有并发重复问题。
缺点:需要依赖redis(如果这算一种缺点的话)。
总结:Redisson是redis官方推荐的JAVA客户端,提供了很多常用的功能,使用简单、高效,推荐大家尝试使用。
5、RocketMQ延迟消息
延迟消息,当消息写入到Broker后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理的消息,称为延时消息。在订单创建之后,我们就可以把订单作为一条消息投递到rocketmq,并将延迟时间设置为30分钟,这样,30分钟后我们定义的consumer就可以消费到这条消息,然后检查用户是否支付了这个订单。通过延迟消息,我们就可以将业务解耦,极大地简化我们的代码逻辑。
优点:可以使代码逻辑清晰,系统之间完全解耦,只需关注生产及消费消息即可。另外其吞吐量极高,最多可以支撑万亿级的数据量。
缺点:相对来说mq是重量级的组件,引入mq之后,随之而来的消息丢失、幂等性问题等都加深了系统的复杂度。
总结:通过mq进行系统业务解耦,以及对系统性能削峰填谷已经是当前高性能系统的标配。
24、海量商品收索设计
淘宝巅峰听说有800万卖家,每个商家假设1000键商品,意味这淘宝有 80亿个商品。
监听的功能。在redis.conf中,配置notify-keyspace-events Ex即可开启此功能。然后在代码中继承KeyspaceEventMessageListener,实现onMessage就可以监听过期的数据量。
优点:由于redis的高性能,所以我们在设置key,或者消费key时,速度上是可以保证的。
缺点:由于redis的key过期策略原因,当一个key过期时,redis无法保证立刻将其删除,自然我们的监听事件也无法第一时间消费到这个key,所以会存在一定的延迟。另外,在redis5.0之前,订阅发布中的消息并没有被持久化,自然也没有所谓的确认机制。所以一旦消费消息的过程中我们的客户端发生了宕机,这条消息就彻底丢失了。
总结:redis的过期订阅相比于其他方案没有太大的优势,在实际生产环境中,用得相对较少。
4、Redisson分布式延迟队列RDelayedQueue
Redisson是一个基于redis实现的Java 驻内存数据网格,它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。
Redisson除了提供我们常用的分布式锁外,还提供了一个分布式延迟队列RDelayedQueue,他是一种基于zset结构实现的延迟队列,其实现类是RedissonDelayedQueue。
优点:使用简单,并且其实现类中大量使用lua脚本保证其原子性,不会有并发重复问题。
缺点:需要依赖redis(如果这算一种缺点的话)。
总结:Redisson是redis官方推荐的JAVA客户端,提供了很多常用的功能,使用简单、高效,推荐大家尝试使用。
5、RocketMQ延迟消息
延迟消息,当消息写入到Broker后,不会立刻被消费者消费,需要等待指定的时长后才可被消费处理的消息,称为延时消息。在订单创建之后,我们就可以把订单作为一条消息投递到rocketmq,并将延迟时间设置为30分钟,这样,30分钟后我们定义的consumer就可以消费到这条消息,然后检查用户是否支付了这个订单。通过延迟消息,我们就可以将业务解耦,极大地简化我们的代码逻辑。
优点:可以使代码逻辑清晰,系统之间完全解耦,只需关注生产及消费消息即可。另外其吞吐量极高,最多可以支撑万亿级的数据量。
缺点:相对来说mq是重量级的组件,引入mq之后,随之而来的消息丢失、幂等性问题等都加深了系统的复杂度。
总结:通过mq进行系统业务解耦,以及对系统性能削峰填谷已经是当前高性能系统的标配。
24、海量商品收索设计
淘宝巅峰听说有800万卖家,每个商家假设1000键商品,意味这淘宝有 80亿个商品。