一、kafka数据分区和消费者的关系,kafka的数据offset读取流程,kafka内部如何保证顺序,结合外部组件如何保证消费者的顺序
1、kafka数据分区和消费者的关系:1个partition只能被同组的一个consumer消费,同组的consumer则起到均衡效果2、kafka的数据offset读取流程a.连接ZK集群,从ZK中拿到对应topic的partition信息和partition的Leader的相关信息b.连接到对应Leader对应的brokerc.consumer将⾃己保存的offset发送给Leaderd.Leader根据offset等信息定位到segment(索引文件和⽇志⽂文件)e.根据索引文件中的内容,定位到日志文件中该偏移量对应的开始位置读 取相应⻓度的数据并返回给consumer3、kafka内部如何保证顺序:kafka只能保证partition内是有序的,但是partition间的有序是没办法的。爱奇艺的搜索架构,是从业务上把需要有序的打到同⼀个partition。
二、cms垃圾回收机制
1、概念:CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器器,
2、使用场景:GC过程短暂停,适合对时延要求较高的服务,用户线程不允许⻓时间的停顿。
3、缺点:
a.服务长时间运⾏,造成严重的内存碎片化。
b.算法实现比较复杂(如果也算缺点的话)。
4、实现机制:根据GC的触发机制分为:
a.周期性Old GC(被动):2s执行一次;
b.主动Old GC:触发条件:
i. YGC过程发⽣生Promotion Failed,进⽽对老年代进行回收
ii.比如执行了System.gc(),前提是没有参数ExplicitGCInvokesConcurrent
iii. 其它情况
三、springcloud各个组件功能,内部细节,与dubbo区别,dubbo架构,dubbo负载策略略
1、springcloud各个组件功能:
a. Ribbon,客户端负载均衡,特性有区域亲和、重试机制。
b. Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。
c. Feign,声明式服务调用,本质上就是Ribbon+Hystrix
d. Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。
e. Bus,消息总线,配合Config仓库修改的一种Stream实现,
f. Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。
g. Eureka,服务注册中心,特性有失效剔除、服务保护。
h. Dashboard,Hystrix仪表盘,监控集群模式和单点模式,其中集群模式需要收集器Turbine配合。
i. Zuul,API服务网关,功能有路由分发和过滤。
j. Config,分布式配置中⼼,支持本地仓库、SVN、Git、Jar包内配置等模式,
2、dubbo负载策略:
Random LoadBalance
a.随机,按权重设置随机概率。
b.在一个截面上碰撞的概率高,但是调用量越大分布越均匀,而且按概率使用权重后也比较均匀,有利于动态调整提供者权重。
RoundRobin LoadBalance
a.轮询,按公约后的权重设置轮询比率。
b.存在慢的提供者累积请求的问题,比如:第二台机器很慢,但没挂,当请求调到第二台时就卡在那,久而久之,所有请求都卡在调到第二台上。
LeastActive LoadBalance
a.最少活跃调用数,相同活跃数的随机,活跃数指调用前后计数差。
b.让慢的提供者收到更少请求,因为越慢的提供者的调用前后计数差越大。
ConsistentHashLoadBalance
a.一致性Hash,相同参数的请求总是发到同一提供者。
b.当某一台提供者挂时,原本发生该提供者的请求,基于虚拟节点,平摊到其他提供者,不会引起剧烈变动。
c.缺省只对第一个参数Hash,如果要修改,请配置
<dubbo:parameter key="hash.arguments" value="0.1"/>
d.缺省用160份虚拟节点,如果要修改,请配置
<dubbo:parameter key="hash.nodes" value="320"/>
四、mapreduce原理
1、简介:mapreduce源⾃自google的一篇⽂章,将海量数据处理的过程拆分为map和reduce。mapreduce 成为了最早的分布式计算框架,这样即使不懂的分布式计算框架的内部运行机制的用户,也可以利用分布式的计算框架实现分布式的计算,并在hadoop上面运行。
2、设计思想:
hadoop ⽂件系统 ,提供了一个分布式的文件系统,但是hadoop文件系统读写的操作都涉及到大量的网络的操作,并不能很好的完成实时性比较强的任务。但是hadoop可以给上面的应用提供⼀个很好的支持。比如hadoop文件系统上⾯可以运行mapreduce。mapreduce是⼀个计算的框架,mapreduce是一个分布式的计算框架,这样mapreduce利用分布式的⽂件系统,将不同的机器上完成不同的计算,然后就计算结果返回。这样很好的利用了分布式的文件系统。数据分布式的存储,然后计算的时候,分布式的计算,然后将结果返回。这样的好处就是不会涉及到⼤量的⽹络传输数据。
3、优点:mapreduce的计算框架的优点是,极强的扩展能力,可以在数千台机器器上并发的执行。其次,有很好的容错性,另外,就是向上的接口简洁。⽤户只需要写map和reduce函数,即可完成大规模数据的并⾏处理。
4、缺点:mapreduce并不适合对实时性要求比较⾼的场景,比如交互式查询或者是流式计算。另外,也不适合迭代类的计算(⽐如机器学习类的应⽤)。
五、nio,bio,sellector/epoll,aio,netty自带编解码器,netty优势,java内存模型
Netty高性能:
1、NIO异步非阻塞通信
2、“零拷贝”
3、内存池ByteBuf
4、Netty提供了多种内存管理策略,通过在启动辅助类中配置相关参数,可以实现差异化的定制。
5、⾼效的Reactor线程模型:Reactor单线程(多线程、主从)模型,指的是所有的IO操作都在同⼀一个NIO线程上面完成
6、为了尽可能提升性能,Netty采⽤了串⾏⽆锁化设计,在IO线程内部进行串⾏操作,避免多线程竞争导致的性能下降。表⾯上看,串⾏化设计似乎CPU利用率不⾼,并发程度不够。但是,通过调整NIO线程池的线程参数,可以同时启动多个串行化的线程并行运行,这种局部无锁化的串行线程设计相比一个队列-多个工作线程模型性能更优。
7、高效的并发编程:Netty的⾼效并发编程主要体现在如下⼏点:
a.volatile的⼤量、正确使用;
b. CAS和原⼦类的广泛使用;
c. 线程安全容器的使用;
d. 通过读写锁提升并发性能。
e.高效的序列化框架:
f.灵活的TCP参数配置能力:合理设置TCP参数在某些场景下对于性能的提升可以起到显著的效果,例如SO_RCVBUF和SO_SNDBUF。如果设置不当,对性能的影响是非常⼤的。
六.akka模型
1、概念:Akka是一个构建在JVM上,基于Actor模型的的并发框架,为构建伸缩性强,有弹性的响应式并发应用提⾼更好的平台。
2、Actor模型:Akka的核心就是Actor,所以不得不说Actor,Actor模型我通俗的举个例⼦,假定现实中的两个人,他们只知道对⽅的地址,他们想要交流,给对⽅传递信息,但是又没有手机,电话,⽹络之类的其他途径,所以他们之间只能用信件传递消息,很像现实中的的邮政系统,你要寄一封信,只需根据地址把信投寄到相应的信箱中,具体它是如何帮你处理送达的,你就不需要了解了,你也有可能收到收信人的回复,这相当于消息反馈。上述例⼦中的信件就相当于Actor中的消息,Actor与Actor之间只能通过消息通信。
七、java arraylist,linkedlist区分及实现原理,hashmap和concurrenthashmap区分及实现原理,concurrenthashmap 1.7和1.8区分,实现细节linkedhashmap排序原理,应⽤如何保证数据幂等
1、java arraylist,linkedlist区分及实现原理:
a. ArrayList是实现了基于动态数组的数据结构,⽽LinkedList是基于链表的数据构;
b. 对于随机访问get和set,ArrayList要优于LinkedList,因为LinkedList要移动指针;
c. 对于添加和删除操作add和remove,⼀般大家都会说LinkedList要比ArrayList快,因为ArrayList要移动数据。
2、concurrenthashmap 1.7和1.8区分:
去除 Segment + HashEntry + Unsafe的实现,改为 Synchronized + CAS + Node + Unsafe的实现。其实 Node 和 HashEntry 的内容一样,但是HashEntry是⼀个内部类。用 Synchronized + CAS 代替 Segment ,这样锁的粒度更⼩了,并且不是每次都要加锁了,CAS尝试失败了在加锁。put()⽅法中 初始化数组大小时,1.8不用加锁,因为⽤了个 sizeCtl变量,将这个变量置为-1,就表明table正在初始化。
3、linkedhashmap排序原理:
public linkedHashMap(){
//调用HashMap的构造方法,其实就是初始化Entry[] table
super();
//这里是指是否基于访问排序,默认为false
accessOrder=false;
}
a.LinkedHashMap存储数据是有序的,⽽且分为两种:插⼊顺序和访问顺序。默认为插入顺序。
b.LinkedHashMap有自⼰的静态内部类Entry,它继承了HashMap.Entry,定义如下:
/**
*LinkedHashMap entry
*/
private static class Entry<K,V>extends HashMap.Entry<K,V>{
//These fieds comprise the doubly linked list used for iteration.
Entry<K,V>befor,after;
Entry(int hash,K key,V value,HashMap.Entry<K,V>next){
super(hash,key,value,next);
}
}
c.所以LinkedHashMap构造函数,主要就是调⽤HashMap构造函数初始化了⼀个Entry[] table,然后调⽤⾃身的init初始化了⼀个只有头结点的双向链表。
八、web.xml listener,filter,servlet加载顺序。
web.xml listener,filter,servlet加载顺序:context-param -> listener -> filter -> servlet
九、无穷数就top K问题,提供多个方案
1、最简单且最容易想到的算法是对数组进行排序(快速排序),然后取最大或最⼩的K个元素。总的时间复杂度为O(N*logN)+O(K)=O(N*logN)。该算法存在以下问题:
a. 快速排序的平均复杂度为O(N*logN),但最坏时间复杂度为O(n2),不不能始终保证较好的复杂度
b. 只需要前k大或k小的数,,实际对其余不需要的数也进行了排序,浪费了大量排序时间
总结:通常不会采取该⽅案。
2、虽然我们不会采用快速排序的算法来实现TOP-K问题,但我们可以利用快速排序的思想,在数组中随机找⼀一个元素key,将数组分成两部分Sa和Sb,其中Sa的元素>=key,Sb的元素<key,然后分析两种情况:
a. 若Sa中元素的个数大于或等于k,则在Sa中查找最大的k个数
b. 若Sa中元素的个数小于k,其个数为len,则在Sb中查找k-len个数字如此递归下去,不断把问题分解为更小的问题,直到求出结果。
3、寻找N个数中的第K大的数,可以将问题转化寻找N个数中第K大的问题。对于一个给定的数p, 可以在O(N)的时间复杂度内找出所有不小于P的数。
根据分析,可以使⽤二分查找的算法思想来寻找N个数中第K大的数。假设N个数中最⼤的数为Vmax,最⼩的数为Vmin, 那么N个数中第K大的数⼀定在区间[Vmin,Vmax]之间。然后在这个区间使用二分查找算法。
总结:该算法实际应⽤效果不佳,尤其是不同的数据类型需要确定max - min > delta,因此时间复杂度跟数据分布有关。整个算法的时间复杂度为O(N * log(Vmax-Vmin)/delta),在数据分布平均的情况下,时间复杂度为O(N * logN)。
4、上⾯⼏种解法都会对数据访问多次,那么就有⼀个问题,当数组中元素个数⾮常⼤时,如:100亿,这时候数据不能全部加载到内存,就要求我们尽可能少的遍历所有数据。针对这种情况,下面我们介绍⼀种针对海量数据的解决方案。
在学习堆排序的过程中,我们知道了堆这种数据结构。为了查找Top k大的数,我们可以使用大根堆来存储最⼤的K个元素。大根堆的堆顶元素就是最大K个数中最小的⼀个。每次考虑下一个数x时,如果x比堆顶元素小,则不需要改变原来的堆。如果想x比堆顶元素大,那么用x替换堆顶元素, 同时,在替换之后,x可能破坏最⼩堆的结构,需要调整堆来维持堆的性质。
总结:该算法只需要扫描所有的数据一次,且不会占⽤太多内存空间(只需要容纳K个元素的空间),尤其适合处理海量数据的场景。算法的时间复杂度为O(N * logk),这实际上相当于执行了部分堆排序。
5、TOP-K问题是一个经典的问题,这个问题是存在线性算法的,只不过算法的使用范围有⼀定的限制。如果所有N个数都是正整数,且他们的取值范围并不大,可以考虑申请空间,记录每个整数出现的次数,然后再从大到小取最大的K个。实际就是利⽤计数排序的思想。假设所有整数都在(0,maxN)区间,利⽤用一个数组count[maxN]来记录每个整数出现的次数。count[i]表示整数i在N个数中出现的次数。只需要扫描⼀遍就可以得到count数组,然后寻找第K大的元素。这是一个典型的以空间换取时间的做法。当数组中取值范围⽐较大时,是极其浪费空间的。如[3,1...9999],为了求出最大的K个元素,需要额外申请一个长度为10000的数组。极端情况下,如果 N 个整数各不相同,我们甚⾄只需要⼀个 bit 来存储这个整数是否存在,这样可节省很大的内存空间。
十、a,b,c三张表,做关联查询,如何优化,可做外键,只在c表加a表外键即可。
1. 对于要求全面的结果时,我们需要使用连接操作(LEFT JOIN / RIGHT JOIN / FULL JOIN);2. 不要以为使用MySQL的一些连接操作对查询有多么大的改善,核心是索引;3. 对被驱动表的join字段添加索引。
十一、.CourrentHashMap JDK1.7和JDK1.8有什么区别?
1. Java 7为实现并行访问,引⼊了Segment这一结构,实现了分段锁,理论上最大并发度与Segment个数相等。2. Java 8为进一步提⾼并发性,摒弃了分段锁的方案,⽽是直接使⽤一个大的数组。同时为了提⾼哈希碰撞下的寻址性能,Java 8在链表长度超过⼀定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红⿊树(寻址时间复杂度为O(long(N)))。其数据结构如下图所示
十二、线程a,b,c,d运行任务,怎么保证当a,b,c线程执行完再执行d线程?
1、CountDownLatch类一个同步辅助类,常用于某个条件发生后才能执⾏后续进程。给定计数初始化CountDownLatch,调用countDown()方法,在计数到达零之前,await⽅法⼀直受阻塞。重要方法为countdown()与await();
2、join⽅方法将线程B加⼊到线程A的尾部,当A执行完后B才执行。
public static void main(String[] args) throws Exception {
Th t = new Th("t1");
Th t2 = new Th("t2");
t.start();
t.join();
t2.start();
}
3、notify、wait⽅法,Java中的唤醒与等待⽅法,关键为synchronized代码块,参数线程间应相同,也常⽤Object作为参数。
十三、拆分微服务应该注意哪些地方,如何拆分?
1、业务方⾯面拆分:所有技术⽅⾯的考虑,包括架构设计和解耦拆分都要考虑业务的需要。在服务拆分时,先从业务角度确定拆分的方案。拆分的边界要充分考虑业务的独立性和专业性,⽐如搜索类服务、支付类服务、购物⻋车类服务,按服务的业务功能合理地划出拆分边界。
2、减少维护成本:拆分前的维护成本 - 拆分后的维护成本 ≧ 0
3、服务独立:确保拆分后的服务由相对独立的团队负责维护,尽量不要出现在不同服务之间的交叉调用。
4、系统扩展:拆分的⼀个重要理由也是最有价值的结果是提高了系统的扩展性。⽤户对不同的服务有不同的并发和性能方面的要求,因此服务具有不同的扩展性。把具有不同扩展性要求的服务拆分出来分别进行部署,可以降低成本,提高效率。
十四、SpringCloud全家桶包含哪些组件?
1. Ribbon,客户端负载均衡,特性有区域亲和、重试机制。
2. Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。
3. Feign,声明式服务调用,本质上就是Ribbon+Hystrix
4. Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。
5. Bus,消息总线,配合Config仓库修改的一种Stream实现。
6. Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。
7. Eureka,服务注册中心,特性有失效剔除、服务保护。
8. Dashboard,Hystrix仪表盘,监控集群模式和单点模式,其中集群模式需要收集器Turbine配合。
9. Zuul,API服务⽹关,功能有路由分发和过滤。
10. Config,分布式配置中心,⽀持本地仓库、SVN、Git、Jar包内配置等模式。
十五、有没了解Docker,Docker和虚拟机有什么区别?
1、虚拟机:我们传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有⾃己的操作系统,虚拟机一旦被开启,预分配给他的资源将全部被占⽤。,每⼀个虚拟机包括应用,必要的二进制和库,以及⼀个完整的用户操作系统。
2、Docker:容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。
3、对比:
1. docker启动快速属于秒级别。虚拟机通常需要几分钟去启动。
2. docker需要的资源更少,docker在操作系统级别进行虚拟化,docker容器和内核交互,几乎没有性能损耗,性能优于通过Hypervisor层与内核层的虚拟化。
3. docker更轻量,docker的架构可以共用⼀个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker运行的镜像数远多于虚拟机数量。对系统的利⽤率非常高
4. 与虚拟机相比,docker隔离性更弱,docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
5. 安全性:docker的安全性也更弱。Docker的租户root和宿主机root等同,⼀旦容器内的用户从普通用户权限提升为root权限,它就直接具备了宿主机的root权限,进⽽可进⾏⽆限制的操作。虚拟机租户root权限和宿主机的root虚拟机权限是分离的,并且虚拟机利用如Intel的VT-d和VT-x的ring-1硬件隔离技术,这种隔离技术可以防⽌虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离,这使得容器容易受到攻击。
6. 可管理性:docker的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如VMware vCenter提供完备的虚拟机管理能力。
7. ⾼可用和可恢复性:docker对业务的高可用⽀持是通过快速重新部署实现的。虚拟化具备负载均衡,高可⽤,容错,迁移和数据保护等经过生产实践检验的成熟保障机制,VMware可承诺虚拟机99.999%高可用,保证业务连续性。
8. 快速创建、删除:虚拟化创建是分钟级别的,Docker容器创建是秒级别的,Docker的快速迭代性,决定了无论是开发、测试、部署都可以节约⼤量时间。
9. 交付、部署:虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化;Docker在Dockerfile中记录了容器构建过程,可在集群中实现快速分发和快速部署;
十六、同一个宿主机中多个Docker容器之间如何通信?多个宿主机中Docker容器之间如何通信?
1、这里同主机不同容器之间通信主要使用Docker桥接(Bridge)模式。
2、不同主机的容器之间的通信可以借助于 pipework 这个工具。
十七、高并发系统如何做性能优化?如何防止库存超卖?
1、高并发系统性能优化:
优化程序,优化服务配置,优化系统配置
1).尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以⼤量减少与数据库的交互,提高性能。
2).用jprofiler等工具找出性能瓶颈,减少额外的开销。
3).优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句句(仅耗时较长的查询做优化)。
4).优化数据库结构,多做索引,提高查询效率。
5).统计的功能尽量做缓存,或按每天一统计或定时统计相关报表,避免需要时进行统计的功能。
6).能使用静态页面的地⽅尽量使用,减少容器的解析(尽量将动态内容⽣生成静态html来显示)。
7).解决以上问题后,使⽤服务器集群来解决单台的瓶颈问题。
2、防⽌库存超卖:
1)、悲观锁:在更新库存期间加锁,不不允许其它线程修改;
a、数据库锁:select xxx for update;
b、分布式锁;
2)、乐观锁:使用带版本号的更新。每个线程都可以并发修改,但在并发时,只有一个线程会修改成功,其它会返回失败。
a、redis watch:监视键值对,作⽤时如果事务提交exec时发现监视的监视对发⽣变化,事务将被取消。
3)、消息队列:通过 FIFO 队列,使修改库存的操作串行化。
4)、总结:总的来说,不能把压力放在数据库上,所以使⽤ "select xxx for update" 的⽅式在高并发的场景下是不可行的。FIFO同步队列的⽅式,可以结合库存限制队列⻓,但是在库存较多的场景下,又不太适用。所以相对来说,我会倾向于选择:乐观锁 / 缓存锁/ 分布式锁的方式。
十八、如何保证服务幂等性?
1、概念:接口的幂等性实际上就是接口可重复用,在调用方多次调⽤的情况下,接口最终得到的结果是⼀致的。有些接⼝可以天然的实现幂等性,比如查询接口,对于查询来说,你查询⼀次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
2、GET幂等:值得注意,幂等性指的是作用于结果而非资源本身。怎么理解呢?例如,这个HTTP GET方法可能会每次得到不同的返回内容,但并不影响资源。
3、POST非幂等:因为它会对资源本身⽣影响,每次调用都会有新的资源产生,因此不满足幂等性。
4、如何保证幂等性:
1)、全局唯一id:如果使用全局唯一ID,就是根据业务的操作和内容⽣成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。
从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如⼀台机器虽然把全局ID先写⼊了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个⽅案看起来很美但是实现起来比较麻烦,下面的⽅案适⽤于特定的场景,但是实现起来比较简单。
2)、去重表:这种方法适用于在业务中有唯一标的插入场景中,比如在以上的⽀付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。
3)、插入或更新:这种方法插⼊并且有唯一索引的情况,⽐如我们要关联商品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:
insert into goods_category (goods_id,category_id,create_time,update_time)
values(#{goodsId},#{categoryId},now(),now())
on DUPLICATE KEY UPDATE
update_time=now()
4)、多版本控制:这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等。
boolean updateGoodsName(int id,String newName,int version);
在实现时可以如下
update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
5、状态机控制:这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使⽤int类型,并且通过值类型的大小来做幂等,⽐如订单的创建为0,付款成功为100。付款失败为99
在做状态机更新时,我们就这可以这样控制
update `order` set status=#{status} where id=#{id} and status<#{status}