在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
样例
输入数组:
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
如果输入查找数值为7,则返回true,
如果输入查找数值为5,则返回false。
1 | class Solution { |
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
样例
输入数组:
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
如果输入查找数值为7,则返回true,
如果输入查找数值为5,则返回false。
1 | class Solution { |
二分是分治的一种;问题收敛速度最快的一种。
通过每次把问题搜索空间排除一半,进而得到lgN的时间复杂度。
将区间分为两部分,第一部分满足某一个特征;第二部分满足另一个特征
通过二分来解决的问题包括:
二分答案:答案具有“单调性”,外层花费$logN$的时间转化为判定性问题.
答案具有“单调性”:注意是先0后1函数,还是先1后0函数,和二分的实现形式,是一直保持小于,还是能累加就累加?
一般定义判定是“是否存在一个小于等于、是否存在一个大于等于”,这么定义是显然有单调性的
二分出的答案一般对check的进行有所帮助
1 | //整数二分算法模板 https://www.cnblogs.com/smallocean/p/11913963.html |
题目分类 | 题目名称 | 考察点 | 其他说明 |
---|---|---|---|
查第一个出现的位置 | |||
递增找number与index相等 | |||
重复数字出现次数 | |||
旋转数组找最值 | |||
求两个有序数组前K大的数 | 考虑A的中间元素m/2和B的中间元素n/2 | ||
多路归并 | 求m个有序数组前K大的数 | 维护一个大小为m的堆 |
参考:
https://zhuanlan.zhihu.com/p/157779732
https://www.dazhuanlan.com/2019/12/16/5df6e82127d88/
https://blog.csdn.net/weixin_34355715/article/details/94465888
https://blog.csdn.net/weixin_43626741/article/details/104364387
设计目标:
实现方式:
并发 = rt * qps
N表示系统中同时活动的用户,包括正在处理中和队列中的;X表示用户相继到达系统的速率,在平衡状态时即为系统吞吐量(到达=离开);R表示每个用户在系统中平均的驻留时间
eg. 一个请求在系统中的停留时间,1s; 一秒钟平均过来100个请求(当然一秒钟也会离开100个请求)那么系统同时处理的请求数是多少?
应该是1s * 100 (个/s) = 100个。
n表示同时在线;L表示平均停留时间;T表示高峰期持续时间;C为并发用户数量。
eg. 一个用户在系统中的停留时间,30分钟;晚高峰5~7点,有500在线;问系统的并发用户是多少?
假设系统后端维护session,那么这个session的长度即为30分钟,L=30minutes;用户活跃时长已经得知是从5点到7点共2个小时,T=2 hours;那么并发用户数C=nL/T=50030/(5*60)=50
我们再来从另外的角度分解Eric的估算公式:
C=nL/T 可以表示为 C=(n/T)L
n/T 是不是和我们刚才在上面Little中第一步一样,是计算到达率X的。
而L(session时长)不就是R(驻留时间)吗?都等同于session时间长度。
也就是说C=(n/T)L=XR=N
结论:由以上得知,Eric’s 估算公式跟Little定律是等价的.
参考:https://www.cnblogs.com/hundredsofyears/p/3360305.html
并发 = 到达率X * 停留时间R
并发 = (同时在线人数c / 高峰期持续时长T) * 平均停留时间L
服务的处理能力是有客观上限的。当请求速度超过服务的处理速度时,服务就会过载。
如果服务持续过载,会导致越来越多的请求积压,最终所有的请求都必须等待较长时间才能被处理,从而使整个服务处于瘫痪状态。
与之相对的,如果直接拒绝掉一部分请求,反而能够让服务能够”及时”处理更多的请求。 我们称这种做法为限流。限流是过载保护的手段之一,目的在于保护系统不被超过服务吞吐能力的流量(或突发)打死。
常见的限流手段有固定窗口、滑动窗口、令牌桶、漏桶、BBR、brpc自适应限流等。
下面挨个介绍每一种限流算法的原理以及优缺点。
原理:维护一个窗口的两个信息:窗口开始时间、经过窗口的请求数。如果窗口内超出限制数则拒绝。如果已经进入新的窗口,则reset计数。
不足:存在临界点(窗口reset前后的瞬时)的qps会飙高,从而打死系统。
原理:将总的时间窗口划分为N个小格,并单独维护每一个小格的计数。通过这种方式,可以把过去一段时间内的计数信息也用上,从而让限流的统计更精确一些。
不足:窗口不可能无限划分
原理:漏桶具有固定容量,出水速率是固定常量(流出请求)如果桶是空的,则不需流出水滴。可以以任意速率流入水滴到漏桶(流入请求)如果流入水滴超出了桶的容量,则流入的水滴溢出(新请求被拒绝)
不足:无法应对突发流量
原理:假如用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中。假设桶最多可以存发b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃。当一个n个字节的数据包到达时,就从令牌桶中删除n个令牌,并且数据包被发送到网络。如果令牌桶中少于n个令牌,那么不会删除令牌,并且认为这个数据包在流量限制之外。
算法允许最长b个字节的突发,但从长期运行结果看,数据包的速率被限制成常量r。
上面的几种算法,一定程度上确实能够保护系统不被拖垮, 但不管漏斗桶还是令牌桶, 其防护思路都是设定一个指标, 当超过该指标后就阻止或减少流量的继续进入,当系统负载降低到某一水平后则恢复流量的进入。但其通常都是被动的,其实际效果取决于限流阈值设置是否合理,但往往设置合理不是一件容易的事情。具体有如下几个弊端:
底层原理:并发 = qps * latency
自适应限流能动态调整服务的最大并发,在保证服务不过载的前提下,让服务尽可能多的处理请求。
具体逻辑:如果cpu > 800 && inflight > rt * max_qps,则启用限流
为什么要用 CPU\IOPS 作为启发值呢?
因为自适应限流与 TCP 拥塞控制还存在不同之处,TCP 中客户端可以控制发送率,从而探测到 maxPass,但是 RPC 线上无法控制流量的速率,所以必须以 CPU 作为标准,当 CPU 快满载的时候再开启,这时我们认为之前探测到的 maxPass 已经接近了系统的瓶颈,乘以 minRtt 就可以得到 InFlight
服务的noload_latency并非是一成不变的,自适应限流必须能够正确的探测noload_latency的变化。当noload_latency下降时,是很容感知到的,因为这个时候latency也会下降。难点在于当latency上涨时,需要能够正确的辨别到底是服务过载了,还是noload_latency上涨了。
参考:
通过cosume group,巧妙的解决了广播问题。
SendFile、zero copy
Kafka为了卸载MQ本身的复杂性,为了其真正无状态的设计,它将状态维护机制这口锅完全甩给了消费者,因此取消息的问题就转化成了消费者拿着一个offset索引来Kafka存储器里取消息的问题,这就涉及到了性能。But 如何能查的更快?How?
但实际上,
文件的命名相当于查找的稀疏索引,省去索引文件
每个segment索引又是一个稀疏索引减少索引文件的大小
在 Kafka 中,生产者写入消息、消费者读取消息的操作都是与 leader 副本进行交互的,从 而实现的是一种主写主读的生产消费模型。
Kafka 并不支持主写从读,因为主写从读有 2 个很明 显的缺点:
(1)数据一致性问题。数据从主节点转到从节点必然会有一个延时的时间窗口,这个时间 窗口会导致主从节点之间的数据不一致。某一时刻,在主节点和从节点中 A 数据的值都为 X, 之后将主节点中 A 的值修改为 Y,那么在这个变更通知到从节点之前,应用读取从节点中的 A 数据的值并不为最新的 Y,由此便产生了数据不一致的问题。
(2)延时问题。类似 Redis 这种组件,数据从写入主节点到同步至从节点中的过程需要经 历网络→主节点内存→网络→从节点内存这几个阶段,整个过程会耗费一定的时间。而在 Kafka 中,主从同步会比 Redis 更加耗时,它需要经历网络→主节点内存→主节点磁盘→网络→从节 点内存→从节点磁盘这几个阶段。对延时敏感的应用而言,主写从读的功能并不太适用。
2.1 消息备份
Kafka允许同个Partition存在多个消息副本(Replica),每个Partition的副本通常由1个Leader及0个以上的Follower组成,产者将消息直接发往对应Partition的Leader,Follower会周期地向Leader发送同步请求,Kafka的Leader机制在保障数据致性地同时降低了消息备份的复杂度。同Partition的Replica应存储在同一个Broker上,因为一旦该Broker宕机,对应Partition的所有Replica都无法作,这就达不到高可用的效果。为做好负载均衡并提容错能,Kafka会尽将所有的Partition以及各Partition的副本均匀地分配到整个集群上。举个例,当集群中部署3台Broker,TopicA共有4个Partition,每个Partition均有3个Replica时下图就是种合理的分布方式。
ISR:In-Sync Replicas 副本同步队列
ISR(In-Sync Replicas)指的是个Partition中与Leader“保持同步”的Replica列表(实际存储的是副本所在Broker的BrokerId),这的 保持同步是指与Leader数据保持完全一致,只需在replica.lag.time.max.ms时间内与Leader保持有效连接,官方解释如下If a follower hasn’t sent any fetch requests or hasn’t consumed up to the leaders log end offset for at least this time, the leader will remove the follower from isr,( default value =10000 )Follower周期性地向Leader发送FetchRequest请求(数据结构见下),发送时间间隔配置在replica.fetch.wait.max.ms中,默认值为 500。
AR:Assigned Replicas 所有副本
ISR是由leader维护,follower从leader同步数据有一些延迟(包括延迟时间replica.lag.time.max.ms和延迟条数replica.lag.max.messages两个维度, 当前最新的版本0.10.x中只支持replica.lag.time.max.ms这个维度),任意一个超过阈值都会把follower剔除出ISR, 存入OSR(Outof-Sync Replicas)列表,新加入的follower也会先存放在OSR中。AR=ISR+OSR。
ISR数据同步、ACK选项(全部ack、只有一个ack)
Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制。
完全同步复制要求All Alive Follower都复制完,这条消息才会被认为commit,这种复制方式极大的影响了吞吐率。
而异步复制方式下,Follower异步的从Leader复制数据,数据只要被Leader写入log就被认为已经commit,这种情况下,如果leader挂掉,会丢失数据。
kafka使用ISR的方式很好的均衡了确保数据不丢失以及吞吐率。Follower可以批量的从Leader复制数据,而且Leader充分利用磁盘顺序读以及send file(zero copy)机制,这样极大的提高复制性能,内部批量写磁盘,大幅减少了Follower与Leader的消息量差。
leader会维护一个与其基本保持同步的Replica列表,该列表称为ISR(in-sync Replica),每个Partition都会有一个ISR,而且是由leader动态维护 ,如果一个follower比一个leader落后太多,或者超过一定时间未发起数据复制请求,则leader将其重ISR中移除
当前broker收到的最新offset
已经同步到其他slave中的offset
由于follower的HW的更新,需要一轮额外的消息拉取,如果folloer很多的话,就需要多轮拉取Leader 副本高水位更新和 Follower 副本高水位更新在时间上是存在错配的,会导致数据的
不一致,所以Leader epoch登场。
Epoch,一个单调增加的版本号。每当leader发生变更时,都会增加该版本号。小版本号的Leader 被认为是过期 Leader,不能再行使 Leader权力。
起始位移,Leader 副本在该 Epoch 值上写入的首条消息的位移类似于zookeper的leader机制,通过leader epoch的单调递增,以此避免副本宕机重启导致的消息同步错乱
参考:
https://blog.csdn.net/dog250/article/details/79588437
https://www.jianshu.com/p/469ec6dcdc02
https://blog.csdn.net/qq_28900249/article/details/90346599
可能的一些作用:
具体可以用于:
系统可用性降低:系统引入的外部依赖越多,越容易挂掉,本来你就是A系统调用BCD三个系统的接口就好了,人ABCD四个系统好好的,没啥问题,你偏加个MQ进来,万一MQ挂了咋整?MQ挂了,整套系统崩溃了,你不就完了么。
系统复杂性提高:硬生生加个MQ进来,你怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?头大头大,问题一大堆,痛苦不已
一致性问题:A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。
特性 | ActiveMQ | RabbitMQ | RocketMQ | kafka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了1个数量级 | 同ActiveMQ | 10万级,可支撑高吞吐 | 10w量级 |
topic数量对吞吐量的影响 | topic可达几百几千,吞吐小幅下降,一大优势 | topic从几十到上百,吞吐大幅下降;单机支持topic不宜过多,如有需要可以加更多机器 | ||
MasterSlave | 主-从 | 主-从 | 物理Master-Slave | 逻辑上Master-Slave,按照Partition |
分布式消息事务 | 支持 | 支持 | 不支持 | |
延迟消息 | 支持 | 不支持 | ||
消息投递实时性 | RocketMQ使用长轮询,同Push方式实时性一致,消息的投递延时通常在几个毫秒 | Kafka使用短轮询方式,实时性取决于轮询间隔时间 | ||
消费失败重试 | RocketMQ消费失败支持定时重试,每次重试间隔时间顺延 | Kafka消费失败不支持重试 | ||
消息顺序 | RocketMQ支持严格的消息顺序,在顺序消息场景下,一台Broker宕机后,发送消息会失败,但是不会乱序 | 支持 | Kafka支持消息顺序,但是一台Broker宕机后,就会产生消息乱序 | |
主从选举 | 不需要选举NameServer | KRaft选举 | ||
时效性 | ms | us,最大特点,延迟低 | ms | ms |
可用性 | 高,基于主从架构实现高可用性 | 同ActiveMQ | 非常高,分布式架构 | 非常高,分布式,单数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 较低概率丢数据 | 经过参数优化配置,可以做到0丢失 | 同RocketMQ | |
功能支持 | 极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为元善,还是分布式的,扩展性好 | 功能较为简单 |
总体优劣势 | 早期使用,社区不活跃 | 吞吐低,语言不熟,开源社区活跃,小公司 | 阿里品牌保障(利弊)要求技术研发力量,大公司 | 大数据领域 |
Master/Slave概念差异
Kafka:Master/Slave 是个逻辑概念,1 台机器同时是 Master 和 Slave。
RocketMQ:Master/Slave 是个物理概念,1 台机器只能是 Master 或者 Slave。
参考:https://blog.csdn.net/wdj_yyds/article/details/123534023
分布式消息队列提供更好的可靠性,主要体现在:
可扩展性体现在访问量和数据量两个方面:
访问量:分布式消息队列服务,会随着访问量的增减而自动增减逻辑处理服务器;
数据量:当数据量扩大时,后端分布式存储会自动扩容。
安全体现在以下两个方面:
同时使用消息队列的业务之间不会互相干扰。如果有多个业务同时在使用消息队列,对于单机的消息队列服务,一个业务的消息操作可能会影响其他业务的正常运行。比如,一个业务的消息操作特别频繁,占据了消息队列的绝大部分服务时间,也占据了这台服务器的绝大部分网络IO,导致其他业务无法正常地与消息队列通信。而且甚至可能由于服务控制不当,导致机器崩溃,服务停止,业务也跟着停止。分布式消息队列则不会出现这个问题:
(1)监控措施完善,系统性能指数会控制在一定范围之内,而且有任何异常也会报警;
(2)当访问量和数据量增大时,分布式消息队列服务可以自动扩展。
各业务的消息内容是安全存储的,其他业务不能访问到非自身业务的数据。
一方面是业务需要密钥来访问消息队列;另一方面,消息是被加密存储的。
简单实用体现在:
生产端如何保证不丢消息呢?确保生产的消息能到达存储端。
如果是RocketMQ消息中间件,Producer生产者提供了三种发送消息的方式,分别是:
生产者要想发消息时保证消息不丢失,可以:
如何保证存储端的消息不丢失呢? 确保消息持久化到磁盘。大家很容易想到就是刷盘机制。
刷盘机制分同步刷盘和异步刷盘:
生产者消息发过来时,只有持久化到磁盘,RocketMQ的存储端Broker才返回一个成功的ACK响应,这就是同步刷盘。它保证消息不丢失,但是影响了性能。
异步刷盘的话,只要消息写入PageCache缓存,就返回一个成功的ACK响应。这样提高了MQ的性能,但是如果这时候机器断电了,就会丢失消息。
Broker一般是集群部署的,有master主节点和slave从节点。消息到Broker存储端,只有主节点和从节点都写入成功,才反馈成功的ack给生产者。这就是同步复制,它保证了消息不丢失,但是降低了系统的吞吐量。与之对应的就是异步复制,只要消息写入主节点成功,就返回成功的ack,它速度快,但是会有性能问题。
消费者执行完业务逻辑,再反馈会Broker说消费成功,这样才可以保证消费阶段不丢消息。
消息队列保证顺序性整体思路就是这样啦。比如Kafka的全局有序消息,就是这种思想的体现: 就是生产者发消息时,1个Topic只能对应1个Partition,一个 Consumer,内部单线程消费。
但是这样吞吐量太低,一般保证消息局部有序即可。在发消息的时候指定Partition Key,Kafka对其进行Hash计算,根据计算结果决定放入哪个Partition。这样Partition Key相同的消息会放在同一个Partition。然后多消费者单线程消费指定的Partition。
下面是RabbitMQ/RocketMQ消息事务的实现机制
集成:在服务提供端或者调用端,如何集成注册中心?应用内 OR 应用外
测活:服务注册之后,如何对服务进行测活以保证服务的可用性
负载均衡:当存在多个服务提供者时,如何均衡各个提供者的负载?
运行时依赖:引入注册中心之后,对应用的运行时环境有何影响?
可用性:如何保证注册中心本身的可用性,特别是消除单点故障?
状态获取:(1)主动探测(2)心跳上报
一些可能问题:
目前比较流行的解决方案包括:
对比维度 | euraka | consul | ZK |
---|---|---|---|
实现思路 | 去中心化,通过复制来同步数据,但不保证一致性。只要有一个节点可用,系统整体就可用。 | 内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案 | 使用ZK节点临时节点来维护服务器列表,ZK支持watch节点变更通知机制 |
测活 | 客户端心跳 | TCP/HTTP/gRPC/Cmd | 自研-客户端心跳 |
负载均衡 | Ribbon | Fabio | 自研-负载均衡 |
雪崩保护 | 有,灾难态进入自我保护模式 | 无 | 自研 |
自动注销实例 | 支持 | 支持 | 进程不可用,临时节点自动销毁 |
访问协议 | HTTP | HTTP/DNS | 自研 |
监听支持 | 支持 | 支持 | WATCH NODE变更 |
多数据中心 | 支持 | 支持 | 不支持 |
跨注册中心同步 | 支持 | 支持 | 不支持 |
框架集成 | SpingCloud继承 | Spring、k8s集成 | 无 |
优点 | 去中心化,高可用 | 功能相对完善 | 简单容易实现,适用于初创期 |
不足 | 一致性差 | 可用性无保证 | 服务可用性无保障 ZK跨机房集群支持不佳 |
CAP | AP | CP | CP |
一致性协议 | 仅复制,非强一致 | raft | zab,一种类paxos协议 |
为什么要转向微服务架构(收益)
转向微服务架构需要克服什么困难(成本)
从单体到微服务,也许可以有一个过度:工程的拆分
微服务拆分的大原则
业务优先,组织结构,质量维度
SRP Single Responsibilty 单一指责原则
CCP Common Closure Principle 共同闭包原则,包中包含的所有类应该是同类的变化的一个集合,也就是说,如果对包作出修改,需要调整的类应该都在这个包之内。
你的团队成员结构是什么样的,你的架构就会长成啥样。
团队按照业务边界来拆分;确保团队不要太大
因为微服务就是为了减少研发成本,而包括沟通成本。
服务拆分带来的问题
入口网关:隔离客户端与微服务,协议转换、安全策略、认证、限流、熔断等
出口网关:调用外部API,统一认证,授权、授权、访问控制等
性能:IO多路复用、异步非阻塞、线程池
设计要点:性能、扩展性(责任链模式)
隔离性:针对接口对线程池进行分类
zookeeper, consul, euraker
RoundRobin, Hash, Weight
配置项的读取-变更推送如何实现:
client的高性能实现逻辑:
一个小原则:配置系统的旁路化,不要因为配置系统挂了,你的程序启动不了;
可以做两层缓存:内存缓存、文件保存
不同的配置类型:节点类型 》机房类型 》 全局配置
配置项的存储-配置系统的高可用
核心指标在于可用性!!!5个9?
trace_id + span_id来标识链路的调用关系。
对trace_id采样,而不要随机采样。
span 基本工作单元,一次链路调用(可以是RPC,DB等没有特定的限制)创建一个span,通过一个64位ID标识它,uuid较为方便,span中还有其他的数据,例如描述信息,时间戳,key-value对的(Annotation)tag信息,parent_id(基于parent_span_id来维护树形结构)等,其中parent-id可以表示span调用链路来源。
trace_id 类似于 树结构的Span集合,表示一次完整的跟踪,从请求到服务器开始,服务器返回response结束,跟踪每次rpc调用的耗时,存在唯一标识trace_id。比如:你运行的分布式大数据存储一次Trace就由你的一次请求组成。
Annotation 注解,用来记录请求特定事件相关信息(例如时间),一个span中会有多个annotation注解描述。通常包含四个注解信息:
方案:普罗米修斯、Graphna
APM:端到端的监控体系
如何防止消息被篡改:对消息体 + 消息头 进行加密,生成一个签名
如何对数据进行加密:
使用非对称加密的公钥对 “对称加密的私钥-OriginPrivate“ 进行加密,得到SecretPrivate
然后服务端利用非对称加密的私钥,对 SecretPrivate进行解密,得到OriginPrivate
然后再使用OriginPrivate对加密之后的消息体(SecretContext)进行解密,得到Contenxt
监控哪些东西:网络卡顿率、做某件事情的失败率等
考虑暂存 + retry来应对网络状况不佳的情况
压测的原则-尽量模拟真实情况;压测的注意点:
(1)使用线上数据与线上数据
(2)使用线上流量(流量拷贝)
(3)流量应该从尽量靠近用户的CDN发起
如何搭建:
(1)流量的隔离(区分压测流量与正式流量)
(2)风险控制(尽量避免压测对正常用户的影响)
压测数据的产生:
拷贝真实流量(可以从访问日志、可以抓取某个端口的数据等)
打上压测标签
放在合适的机房(尽量接近用户)
数据隔离:
针对读请求,针对某些不能压测(例如推荐、数据分析等)的组件进行mock
对于写请求,把流量产生的数据写入影子库(数据库-拷贝一份库表和数据;缓存-加压测前缀;ES-多搞一份索引)
压力测试的实施
持续放大,做好系统过载的识别(例如超时率、resp time等)