Java知识点总结
遇到过什么线上问题,如何解决的
1.一个定时任务的调度项目,有时经过一个月没有访问,就会出现接口不正常的现象,查看Linux进程是在的,项目启动中…
默认情况下是触发时间先后顺序排序,触发时间比较前的先执行任务,但如果一个或多个任务同时在相同时间触发下,触发器设置优先级越高越先执行,如果优先级相同,则跟任务的存储方式有关,RAMJobStore时与TriggerKey排序有关,既按触发器名的字母序;如果是JdbcStore则跟数据库查询的默认排序有关了。Trigger优先级默认为5,数值越大优先级越高,所以 在相同触发条件下 并且执行数量有限的情况下,他被淘汰了,
由于定时任务我使用的是quartz执行线程数量太少10个的问题,要做的就是增加quartz执行线程数量,在SchedulerFactoryBean中对quartz.properties进行复写 并且改变线程数量为100,以及Trigger检查器 默认每次只Acquire一个Trigger。
1、线程池资源获取等待定时任务过期作废机制。
2、Quartz框架的定时任务执行是绝对时间触发的,所以存在“过期不候”的现象。
3、在使用Quartzs框架时,一定要预先计算好triggers数量与线程池大小的匹配程度,资源一定要够,或者任务执行密度不能太大,否则等到线程任务释放完,trigger早已过期,就无法按预期时间触发了。
4、在进行业务代码开发过程中 尽量一个定时任务中处理多种业务代码,做异步线程处理,对定时任务进行归类 抽取
5.根据业务情况 不要一上来就增加定时任务,靠定时任务处理问题
1.什么情况下会发生栈内存溢出。
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用类型如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常,方法递归调用产生这种结果。
如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一个OutOfMemory 异常。(线程启动过多)参数 -Xss 去调整JVM栈的大小
2.详解JVM内存模型
程序计数器:当前线程所执行的字节码的行号指示器,用于记录正在执行的虚拟机字节指令地址,线程私有。
Java虚拟栈:存放基本数据类型、对象的引用、方法出口等,线程私有。
Native方法栈:和虚拟栈相似,只不过它服务于Native方法,线程私有。
Java堆:java内存最大的一块,所有对象实例、数组都存放在java堆,GC回收的地方,线程共享。
方法区:存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。(即永久带),回收目标主要是常量池的回收和类型的卸载,各线程共享
3.JVM内存为什么要分成新生代,老年代,持久代。新生代中为什么要分为Eden和Survivor。
1)共享内存区划分
共享内存区 = 持久带 + 堆
持久带 = 方法区 + 其他
Java堆 = 老年代 + 新生代
新生代 = Eden + S0 + S1
2)一些参数的配置
默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ,可以通过参数 –XX:NewRatio 配置。
默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定)
Survivor区中的对象被复制次数为15(对应虚拟机参数 -XX:+MaxTenuringThreshold)
3)为什么要分为Eden和Survivor?为什么要设置两个Survivor区?
如果没有Survivor,Eden区每进行一次Minor GC,存活的对象就会被送到老年代。老年代很快被填满,触发Minor GC.老年代的内存空间远大于新生代,进行一次Full GC消耗的时间比Minor GC长得多,所以需要分为Eden和Survivor。
Survivor的存在意义,就是减少被送到老年代的对象,进而减少Full GC的发生,Survivor的预筛选保证,只有经历16次Minor GC还能在新生代中存活的对象,才会被送到老年代。
设置两个Survivor区最大的好处就是解决了碎片化,刚刚新建的对象在Eden中,经历一次Minor GC,Eden中的存活对象就会被移动到第一块survivor space S0,Eden被清空;等Eden区再满了,就再触发一次Minor GC,Eden和S0中的存活对象又会被复制送入第二块survivor space S1(这个过程非常重要,因为这种复制算法保证了S1中来自S0和Eden两部分的存活对象占用连续的内存空间,避免了碎片化的发生)
4.JVM中一次完整的GC流程是怎样的,对象如何晋升到老年代
Java堆 = 老年代 + 新生代新生代 = Eden + S0 + S1当 Eden 区的空间满了, Java虚拟机会触发一次 Minor GC,以收集新生代的垃圾,存活下来的对象,则会转移到 Survivor区。大对象(需要大量连续内存空间的Java对象,如那种很长的字符串)直接进入老年态;
如果对象在Eden出生,并经过第一次Minor GC后仍然存活,并且被Survivor容纳的话,年龄设为1,每熬过一次Minor GC,年龄+1,若年龄超过一定限制(15),则被晋升到老年态。即长期存活的对象进入老年态。
老年代满了而无法容纳更多的对象,Minor GC 之后通常就会进行Full GC,Full GC 清理整个内存堆 – 包括年轻代和年老代。
Major GC 发生在老年代的GC,清理老年区,经常会伴随至少一次Minor GC,比Minor GC慢10倍以上。
说一个你了解的垃圾回收器
1)几种垃圾收集器:
Serial收集器: 单线程的收集器,收集垃圾时,必须stop the world,使用复制算法。ParNew收集器: Serial收集器的多线程版本,也需要stop the world,复制算法。Parallel Scavenge收集器: 新生代收集器,复制算法的收集器,并发的多线程收集器,目标是达到一个可控的吞吐量。如果虚拟机总共运行100分钟,其中垃圾花掉1分钟,吞吐量就是99%。Serial Old收集器: 是Serial收集器的老年代版本,单线程收集器,使用标记整理算法。Parallel Old收集器: 是Parallel Scavenge收集器的老年代版本,使用多线程,标记-整理算法。CMS(Concurrent Mark Sweep) 收集器: 是一种以获得最短回收停顿时间为目标的收集器,标记清除算法,运作过程:初始标记,并发标记,重新标记,并发清除,收集结束会产生大量空间碎片。G1收集器: 标记整理算法实现,运作流程主要包括以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,可以精确地控制停顿。
2)CMS收集器和G1收集器的区别:
CMS收集器是老年代的收集器,可以配合新生代的Serial和ParNew收集器一起使用;G1收集器收集范围是老年代和新生代,不需要结合其他收集器使用;CMS收集器以最小的停顿时间为目标的收集器;G1收集器可预测垃圾回收的停顿时间CMS收集器是使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片。
JVM内存模型的相关知识了解多少,比如重排序,内存屏障,happen-before,主内存,工作内存。
Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
1)指令重排序。
在实际运行时,代码指令可能并不是严格按照代码语句顺序执行的。大多数现代微处理器都会采用将指令乱序执行(out-of-order execution,简称OoOE或OOE)的方法,在条件允许的情况下,直接运行当前有能力立即执行的后续指令,避开获取下一条指令所需数据时造成的等待。通过乱序执行的技术,处理器可以大大提高执行效率。而这就是指令重排。
2)内存屏障
内存屏障,也叫内存栅栏,是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。
LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。 在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。
3)happen-before原则
单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
volatile的happen-before原则:对一个volatile变量的写操作 happen-before对此变量的任意操作(当然也包括写操作了)。
happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
线程中断的happen-before原则 :对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
线程终结的happen-before原则: 线程中的所有操作都happen-before线程的终止检测。
对象创建的happen-before原则: 一个对象的初始化完成先于他的finalize方法调用。
简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。- 什么是类加载器?
类加载器 就是根据指定全限定名称将class文件加载到JVM内存,转为Class对象。
启动类加载器(Bootstrap ClassLoader):由C++语言实现(针对HotSpot),负责将存放在 3)为什么需要双亲委派模型? 4)怎么打破双亲委派模型? 1)堆栈配置相关 -Xmx3550m: 最大堆大小为3550m。 -XX:+UseParallelGC: 选择垃圾收集器为并行收集器。 3)辅助信息相关 -XX:+PrintGC 输出形式: [GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs] -XX:+PrintGCDetails 输出形式: [GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs] [GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs 输入jps,获得进程号。top -Hp pid 获取本进程中所有线程的CPU耗时性能jstack pid命令查看当前java进程的堆栈状态或者 jstack -l > /tmp/output.txt 把堆栈信息打到一个txt文件。可以使用fastthread 堆栈定位,fastthread.io/
三次握手和四次挥手说一下
序列号seq:占4个字节,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态; 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。 四次挥手过程理解 答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。 答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。 3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。 TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。 输入一段URL之后,比如输入www.google.com后浏览器首先通过DNS服务会对这个域名进行解析,也就是转换为IP地址的形式,然后向该IP对应的web服务器发送请求,建立tcp连接等浏览器发送完HTTP Request包后,服务器接收到请求包之后开始处理请求,服务器调用自身服务,返回HTTP Response包,客户端收到来自服务器的相应后开始渲染这个Response包里面的主体,等收到全部的内容后断开与该服务器的TCP连接。 DNS是域名系统的缩写,它主要作用就是将域名转换为IP地址: 拿到域名对应的IP地址之后,浏览器会以一个随机端口(1024<端口<65535)向服务器的WEB程序(常用的有httpd,nginx等)80端口发起TCP的连接请求。这个连接请求到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡,然后是进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接。 Zuul包含了对请求的路由和过滤两个最主要的功能: 其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验、服务聚合等功能的基础. Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他微服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得. zuul的工作原理 1、过滤器机制 Zuul提供了一个框架,可以对过滤器进行动态的加载,编译,运行。 Zuul的过滤器是由Groovy写成,这些过滤器文件被放在Zuul Server上的特定目录下面,Zuul会定期轮询这些目录,修改过的过滤器会动态的加载到Zuul Server中以便过滤请求使用。 下面有几种标准的过滤器类型: Zuul大部分功能都是通过过滤器来实现的。Zuul中定义了四种标准过滤器类型,这些过滤器类型对应于请求的典型生命周期。 (1) PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。 (2) ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。 (3) POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。 (4) ERROR:在其他阶段发生错误时执行该过滤器。 内置的特殊过滤器 zuul还提供了一类特殊的过滤器,分别为:StaticResponseFilter和SurgicalDebugFilter StaticResponseFilter:StaticResponseFilter允许从Zuul本身生成响应,而不是将请求转发到源。 SurgicalDebugFilter:SurgicalDebugFilter允许将特定请求路由到分隔的调试集群或主机。 自定义的过滤器 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。 如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。 2、过滤器的生命周期 4.zuul 的作用 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求。 不同点: Nginx–c语言开发 6.zuul限流 漏桶: leakey bucket,原理:桶的下方的小孔会以一个相对恒定的速率漏水,而不管入桶的水流量,这样就达到了控制出水口的流量 网关限流 2、网关接收到请求后,从注册中心(Eureka)获取可用服务 3、由Ribbon进行均衡负载后,分发到后端具体实例 4、微服务之间通过Feign进行通信处理业务 5、Hystrix负责处理服务超时熔断 6、Turbine监控服务间的调用和熔断相关指标 Eureka包含两个组件:Eureka Server和Eureka Client。 Application Service Application Client Eureka Server架构原理 Eureka Server CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(数据一致性)、 Availability(服务可用性)、Partition tolerance(分区容错性),三者不可兼得。CAP由Eric Brewer在2000年PODC会议上提出。该猜想在提出两年后被证明成立,成为我们熟知的CAP定理。 1.动态代理有2种方式,基于接口的JDK动态代理 相同之处 不同之处 存储形式 Kafka中不需要设置从broker,所有的broker都可以收发消息。负载均衡也做的更好。 SendFile的工作原理 第一次,是通过 DMA,从硬盘直接读到操作系统内核的读缓冲区里面。 RocketMQ零拷贝——Mmap 通过mmap也有一个很明显的缺陷——不可靠,写到mmap中的数据并没有被真正地写到磁盘,操作系统会在程序主动调用flush的时候才把数据真正写到磁盘。 RocketMQ主要通过MappedByteBuffer对文件进行读写操作。其中,利用了NIO中的FileChannel模型将磁盘上的物理文件直接映射到用户态的内存地址中(这种Mmap的方式减少了传统IO将磁盘文件数据在操作系统内核地址空间的缓冲区和用户应用程序地址空间的缓冲区之间来回进行拷贝的性能开销),将对文件的操作转化为直接对内存地址进行操作,从而极大地提高了文件的读写效率(正因为需要使用内存映射机制,故RocketMQ的文件存储都使用定长结构来存储,方便一次将整个文件映射至内存)。 总结 producer producer发送数据时,由于重试,可能会出现重复消息。这对于重复消息,kafka的解决思路时通过sequenceID解决。每个partition对应一个sequenceID,producer发送数据时带上这个sequenceID,如果sequenceID相同或者比服务器端的sequenceID小,则说明数据重复,服务端忽略掉数据即可,如果producer sequenceID比服务器的sequenceID大1,则刚刚好,如果大2,则说明漏了数据,让producer重新发送即可。rocketmq没有这类机制,所以rocketmq可能会出现重复消息。 事务消息。有些业务可能需要保证消息的事务性,一批数据要么全部成功,要么全部失败。rocketmq采用2PC的方式处理事物消息。 1、client执行事务前会向服务端发送一个半事务消息 2、服务端接收到消息后会回复一个ack 3、client接收到ack后,执行本地事务 4、client执行事务后会向服务端发送commit或者rollback 我们知道2PC方式会有个问题就是执行到第三步时client挂掉,其实这个时候服务器是不知道如何处理这个消息的。为了解决这个问题,服务端本地有个消息回查线程,针对于这些未提交的消息向client询问消息的状态,然后根据client的返回结果对消息做相应的处理。 consumer kafka通过zookeeper来实现元数据的同步。kafka的broker中有个controller组件,当broker启动的时候,都会创建KafkaController对象,但是集群中只能有一个leader对外提供服务,这些每个节点上的KafkaController会在指定的zookeeper路径下创建临时节点,只有第一个成功创建的节点的KafkaController才可以成为leader,其余的都是follower。当leader故障后,所有的follower会收到通知,再次竞争在该路径下创建节点从而选举新的leader。Controller会从zk 上面读取其他节点注册的数据(通过监听机制),生成集群的元数据信息,之后把这些信息都分发给其他的服务器,让其他服务器能感知到集群中其它成员的存在。此时模拟一个场景,我们创建一个topic(其实就是在 Zookeeper 上 /topics/topicA 这样创建一个目录而已),Kafka 会把分区方案生成在这个目录中。此时 Controller 就监听到了这一改变,它会去同步这个目录的元信息,然后同样下放给它的从节点,通过这个方法让整个集群都得知这个分区方案,此时从节点就各自创建好目录等待创建分区副本即可。这也是整个集群的管理机制。 而rockmq在共享元数据时做的比较简单。rocketmq中有一个nameservice的组件,NameServer 中维护着 Producer 集群、Broker 集群、 Consumer 集群的服务状态。NameServer 可以部署多个,多个NameServer互相独立,不会交换消息。各节点通过定时向各个nameserver发送心跳数据包进行维护更新各个服务的状态。当有新的Producer 加入集群时,通过上报自身的服务信息,及获取各个 Broker 的信息(Broker 地址、Topic、Queue 等信息),达到获取集群元数据的目的,这样就可以决定把对应的Topic消息存储到那个Broker、哪个Queue 上。 kafka依赖zk,选取一个leader节点,leader将元数据共享给各个follower,是中心化的方案。rocketmq依赖nameservice,各节点定时向nameserive同步自身信息,并获取其他节点的信息,去中心化的方案。 kafka中一个topic可以分为多个partition,每个partition又可以有多个副本,那么就会出现Partition leader角色。producer向Partition leader发送写请求,leader会将数据同步到其他几个副本,然后向producer发送ack。这里有个问题是何时发送ack?确保有 Follower 与 Leader 同步完成,Leader 再发送 ACK,这样才能保证 Leader 挂掉之后,能在 Follower 中选举出新的 Leader 而不丢数据。所有 Follower 完成同步,Producer 才能继续发送数据,设想有一个 Follower 因为某种原因出现故障,那 Leader 就要一直等到它完成同步。这个问题怎么解决?Leader维护了一个动态的 in-sync replica set(ISR):和 Leader 保持同步的 Follower 集合。 rocketmq中针对副本这个做的比较简单,基于broker来做,而不是基于partition来做。集群部署时一般都为主备,备机实时从主机同步消息,如果其中一个主机宕机,备机提供消费服务,但不提供写服务。所有发往broker的消息,有同步刷盘和异步刷盘机制;同步刷盘时,消息写入物理文件才会返回成功,异步刷盘时,只有机器宕机,才会产生消息丢失。
2)双亲委派模型
双亲委派模型工作过程是:
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
在这里,先想一下,如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,为什么需要双亲委派模型?防止内存中出现多份同样的字节码
打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法。java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-XX:MaxPermSize=16m -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxTenuringThreshold=0
-Xms3550m: 设置初始堆大小为3550m。
-Xmn2g: 设置年轻代大小为2g。
-Xss128k: 每个线程的堆栈大小为128k。
-XX:MaxPermSize: 设置持久代大小为16m
-XX:NewRatio=4: 设置年轻代(包括Eden和两个Survivor区)与年老代的比值(除去持久代)。
-XX:SurvivorRatio=4: 设置年轻代中Eden区与Survivor区的大小比值。设置为4,则两个Survivor区与一个Eden区的比值为2:4,一个Survivor区占整个年轻代的1/6
-XX:MaxTenuringThreshold=0: 设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代。
2)垃圾收集器相关-XX:+UseParallelGC
-XX:ParallelGCThreads=20
-XX:+UseConcMarkSweepGC
-XX:CMSFullGCsBeforeCompaction=5
-XX:+UseCMSCompactAtFullCollection:
-XX:ParallelGCThreads=20: 配置并行收集器的线程数
-XX:+UseConcMarkSweepGC: 设置年老代为并发收集。
-XX:CMSFullGCsBeforeCompaction:由于并发收集器不对内存空间进行压缩、整理,所以运行一段时间以后会产生“碎片”,使得运行效率降低。此值设置运行多少次GC以后对内存空间进行压缩、整理。
-XX:+UseCMSCompactAtFullCollection: 打开对年老代的压缩。可能会影响性能,但是可以消除碎片-XX:+PrintGC
-XX:+PrintGCDetails
确认号ack:占4个字节,期待收到对方下一个报文段的第一个数据字节的序号;序列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
确认ACK:占1位,仅当ACK=1时,确认号字段才有效。ACK=0时,确认号无效
同步SYN:连接建立时用于同步序号。当SYN=1,ACK=0时表示:这是一个连接请求报文段。若同意连接,则在响应报文段中使得SYN=1,ACK=1。因此,SYN=1表示这是一个连接请求,或连接接受报文。SYN这个标志位只有在TCP建产连接时才会被置1,握手完成后SYN标志位被置0。
终止FIN:用来释放一个连接。FIN=1表示:此报文段的发送方的数据已经发送完毕,并要求释放运输连接
PS:ACK、SYN和FIN这些大写的单词表示标志位,其值要么是1,要么是0;ack、seq小写的单词表示序号。
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
1.请求一旦发起,浏览器首先要做的事情就是解析这个域名,一般来说,浏览器会首先查看本地硬盘的host文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用host文件里面的IP地址;
2.如果本地host没有查询到该域名对应的IP,那么浏览器会发出一个DNS请求到DNS服务器。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动;
3.查询你输入的网址的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存记录中有此记录,就可以直接返回结果,此过程是递归方式进行查询,如果没有,本地DNS服务器还要向DNS根服务器进行查询;
4.根DNS服务器没有记录具体的域名和IP地址的关系,而是告诉本地DNS服务器,你可以到域服务器上继续查询,并给出域服务器的地址。这种过程是迭代的;
5.本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器,.com域服务器收到请求后,也不会直接返回域名和IP地址对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址;
6.最后本地DNS服务器向域名的解析服务器发出请求,这是就能收到一个域名和IP地址的对应关系,本地DNS服务器不仅要把IP地址返回给电脑,还要把这个对应的关系保存在缓存中,以备下次访问。
zuul用来做限流,但是为什么要限流?
防止不需要频繁请求服务的请求恶意频繁请求服务,造成服务器资源浪费。
防止不法分子恶意攻击系统,击穿系统盗取数据,防止数据安全隐患。
防止系统高峰时期,对系统对频繁访问,给服务器带来巨大压力。
限流策略
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。
zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。
Zuul的过滤器之间没有直接的相互通信,他们之间通过一个RequestContext的静态类来进行数据传递的。RequestContext类中有ThreadLocal变量来记录每个Request所需要传递的数据。
Zuul请求的生命周期如图,该图详细描述了各种类型的过滤器的执行顺序。
Zuul可以通过加载动态过滤机制,从而实现以下各项功能:
审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为我们带来准确的生产状态结论。
动态路由: 以动态方式根据需要将请求路由至不同后端集群处。
压力测试: 逐渐增加指向集群的负载流量,从而计算性能水平。
负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
静态响应处理: 在边缘位置直接建立部分响应,从而避免其流入内部集群。
多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽可能接近。
5.Zuul和Nginx的区别
相同点:Zuul和Nginx都可以实现负载均衡、反向代理(隐藏真实ip地址),过滤请求,实现网关的效果
Zuul–java语言开发
Zuul负载均衡实现:采用ribbon+eureka实现本地负载均衡
Nginx负载均衡实现:采用服务器实现负载均衡
Nginx相比zuul功能会更加强大,因为Nginx整合一些脚本语言(Nginx+lua)
Nginx适合于服务器端负载均衡
Zuul适合微服务中实现网关
流算法
令牌桶: token bucket,原理:以相对恒定的速率向桶中加入令牌,请求来时于桶中取令牌,取到了就放行,没能取到令牌的请求则丢弃
限流粒度
粗粒度
单个服务
7.细粒度
user: 认证用户或者匿名,针对某个用户粒度进行限流
origin: 客户机的IP,针对请求客户机的IP进行限流
url: 特定url,针对请求的url粒度进行限流
serviceId: 特定服务,针对某个服务的id粒度进行限流
Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Server本身也是一个服务,默认情况下会自动注册到Eureka注册中心。
如果搭建单机版的Eureka Server注册中心,则需要配置取消Eureka Server的自动注册逻辑。毕竟当前服务注册到当前服务代表的注册中心中是一个说不通的逻辑。
Eureka Server通过Register、Get、Renew等接口提供服务的注册、发现和心跳检测等服务。
Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Client分为两个角色,分别是:Application Service(Service Provider)和Application Client(Service Consumer)
服务提供方,是注册到Eureka Server中的服务。
服务消费方,通过Eureka Server发现服务,并消费。
在这里,Application Service和Application Client不是绝对上的定义,因为Provider在提供服务的同时,也可以消费其他Provider提供的服务;Consumer在消费服务的同时,也可以提供对外服务。
Register(服务注册):把自己的IP和端口注册给Eureka。
Renew(服务续约):发送心跳包,每30秒发送一次。告诉Eureka自己还活着。
Cancel(服务下线):当provider关闭时会向Eureka发送消息,把自己从服务列表中删除。防止consumer调用到不存在的服务。
Get Registry(获取服务注册列表):获取其他服务列表。
Replicate(集群中数据同步):eureka集群中的数据复制与同步。
Make Remote Call(远程调用):完成服务的远程调用。
Eureka Server既是一个注册中心,同时也是一个服务。那么搭建Eureka Server的方式和以往搭建Dubbo注册中心ZooKeeper的方式必然不同,那么首先搭建一个单机版的Eureka Server注册中心。
2.基于实现的CGLIB(Code Generation Library)代理,Spring默认使用JDK动态代理,如果原生类没有接口,就会选择CGLIB代理,这个也可以手动配置
3.先说说JDK动态代理,我们如果要为一个类生成代理类,我们需要写一个 InvocationHandler 的实现,这个handler中需要持有原生类,
这个handler有一个invoke方法需要复写,参数是(代理类,被调用方法,方法参数) ,代码增强就在这个方法中增强,
比如事务:就可以 在第一行开启事物,然后调用原生类方法,然后提交事物,可以try一下回滚事务
两者底层原理有很多相似之处,RocketMQ借鉴了Kafka的设计。
两者均利用了操作系统Page Cache的机制,同时尽可能通过顺序io降低读写的随机性,将读写集中在很小的范围内,减少缺页中断,进而减少了对磁盘的访问,提高了性能。
Kafka采用partition,每个topic的每个partition对应一个文件。顺序写入,定时刷盘。但一旦单个broker的partition过多,则顺序写将退化为随机写,Page Cache脏页过多,频繁触发缺页中断,性能大幅下降。
RocketMQ采用CommitLog+ConsumeQueue,单个broker所有topic在CommitLog中顺序写,Page Cache只需保持最新的页面即可。同时每个topic下的每个queue都有一个对应的ConsumeQueue文件作为索引。ConsumeQueue占用Page Cache极少,刷盘影响较小。存储可靠性
RocketMQ支持异步刷盘,同步刷盘,同步Replication,异步Replication。
Kafka使用异步刷盘,异步Replication。顺序消息
Kafka和RocketMQ都仅支持单topic分区有序。RocketMQ官方虽宣称支持严格有序,但方式为使用单个分区。延时消息
RocketMQ支持固定延时等级的延时消息,等级可配置。
kfaka不支持延时消息。消息重复
RocketMQ仅支持At Least Once。
Kafka支持At Least Once、Exactly Once消息过滤
RocketMQ执行过滤是在Broker端,支持tag过滤及自定义过滤逻辑。
Kafka不支持Broker端的消息过滤,需要在消费端自定义实现。消息失败重试
RocketMQ支持定时重试,每次重试间隔逐渐增加。
Kafka不支持重试。DLQ(dead letter queue)
RocketMQ通过DLQ来记录所有消费失败的消息。
Kafka无DLQ。Spring等第三方工具有实现,方式为将失败消息写入一个专门的topic。回溯消费
RocketMQ支持按照时间回溯消费,实现原理与Kafka相同。
Kafka需要先根据时间戳找到offset,然后从offset开始消费。事务
RocketMQ支持事务消息,采用二阶段提交+broker定时回查。但也只能保证生产者与broker的一致性,broker与消费者之间只能单向重试。即保证的是最终一致性。
Kafka从0.11版本开始支持事务消息,除支持最终一致性外,还实现了消息Exactly Once语义(单个partition)。服务发现
RocketMQ自己实现了namesrv。
Kafka使用ZooKeeper。高可用
RocketMQ在高可用设计上粒度只控制在Broker。其保证高可用是通过master-slave主从复制来解决的。
Kafka控制高可用的粒度是放在分区上。每个topic的leader分区和replica分区都可以在所有broker上负载均衡的存储。
Kafka的这种设计相比RocketMQ这种主从复制的设计有以下好处:
Kafka的分区选举是自动做的,RocketMQ需要自己指定主从关系。
Kafka分区的复制份数指定为N,则可以容忍N-1个节点的故障。发生故障只需要分区leader选举下即可,效率很高。
Kafka零拷贝——SendFile
Kafka的代码调用了Java NIO库,具体是FileChannel里面的transferTo方法(底层是。我们的数据并没有读到中间的应用内存里面,而是直接通过Channel,写入到对应的网络设备里。并且对于Socket的操作,也不是写入到Socket的Buffer里面,而是直接根据描述符(Descriptor)写入到网卡的缓冲区里面。于是,在这个过程中,只进行了两次数据传输。(由于没有在用户态内存层里面去Copy数据,干掉了两次CPU的Copy,所以我们将之称为零拷贝(Zero-Copy)
系统调用sendfile()通过DMA把磁盘数据拷贝到kernel buffer(read buffer),然后数据被kernel直接拷贝到另外一个与socket相关的kernel buffer(socket buffer)。这样就没有用户态和内核态之间的切换,从内核中直接完成了从一个buffer到另一个buffer的拷贝,因为数据就在kernel里。
第二次,则是根据 Socket 的描述符信息,直接从读缓冲区里面,写入到网卡的缓冲区里面。
这是Kafka目前实时数据传输管道的标准解决方案,也是Kafka高吞吐的秘密之一,零拷贝。
Mmap全称Memory Mapped Files。简单描述其作用就是:将磁盘文件映射到内存,用户通过修改内存就能修改磁盘文件。
它的工作原理是直接利用操作系统的Page来实现文件到物理内存的直接映射,完成映射之后你对物理内存的操作会被同步到磁盘上(操作系统在适当的时候)。
CPU比I/O性能好很多,应当尽力给CPU让步,让它去做更多的事情,于是就有了DMA。DMA对CPU说,“你告诉我搬什么数据,搬到哪里,搬好了我告诉你,你先去忙别的”
我们发现,在数据传输过程中,有两次CPU的Copy可以省去,于是就有了内存映射技术(Mmap)、SendFile技术(内核数据Copy),让CPU不需要再白忙活了
市面上熟知的优秀中间件如RocketMQ使用的零拷贝技术是Mmap、Kafka使用的则是SendFile
详细的区别
发送到消息队列的业务数据正常情况下都会要求数据顺序一致。比如说消息队列接收到用户下单,支付,收货三条消息,存储到本地也一定要是下单,支付,收货的顺序。针对于这种需求,kafka和rocketmq的处理方案都是一致的,producer生产数据时指定分区的key,保证同一id的数据发送到同一个partition。
kafka不支持事务消息。
consumer消费数据时,有两个事需要确定,从哪开始读数据,读哪个partition的数据?
从哪开始读数据
这个kafka和rocketmq和处理机制都差不多。二者都可以保存消费的offset,支持用户从头,从最新,从指定位置开始读数据。唯一的区别是kafka将offset存在topic中,rocketmq将offset存在本地文件。
读哪个partition的数据
一个topic可能包含多个partition。consumer读哪个partition的数据,kafka和rocketmq的做法有些区别。kafka的做法是一个consumer-group中选一个coordinator,由coordinator将partition分配给consumer。rocketmq的做法更简单,各个consumer从nameservice那里获取到consumer-group的信息,大家按照约定好的算法算出自己的partition,然后消费即可。
个人感觉rocketmq的做法更好,去中心节点,对外部依赖少,更容易扩展。broker
broker是一个分布式的集群,存储各个topic信息,需要保证数据高可用,一致性等问题。既然是一个集群,那么就面临的一些常见分布式系统固有的问题,比如说多节点如何共享元数据?多副本如何保持一致?节点挂掉,副本如何切换等
我们先看下kafka和rockmq如何多节点如何共享元数据。
当 ISR 集合中的 Follower 完成数据的同步之后,Leader 就会给 Follower 发送 ACK。如果 Follower 长时间未向 Leader 同步数据,则该 Follower 将被踢出 ISR 集合,该时间阈值由 replica.lag.time.max.ms 参数设定。Leader 发生故障后,就会从 ISR 中选举出新的 Leader。此时是不会影响集群服务的。



