JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
1). 调用私有方法, 能!Class c1 = obj.getClass();
Method dm = c1.getDeclaredMethod("doSomingPrivate");
dm.setAccessible(true); //调用private方法关键设置,是false一样会报错。
dm.invoke(obj);
2). 反射机制优点:反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创和控制任何类的对象,无需提前硬编码目标类。可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例。
缺点:性能开销:反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。安全限制:使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如 Applet,那么这就是个问题了。内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。
1) html静态化:效率最高、消耗最小的就是纯静态化的html页面,所以我们尽可能使我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法。
同时可以将页面上常用的查询但更新量很小的数据(如:公告信息)缓存在页面,这样避免了大量的数据库访问请求。
2) 图片服务器分离:图片是最消耗资源的,于是我们有必要将图片与页面进行分离,这是基本上大型网站都会采用的策略,他们都有独立的、甚至很多台的图片服务器。这样的架构可以降低提供页面访问请求的服务器系统压力,并且可以保证系统不会因为图片问题而崩溃。
3) 缓存:为了避免每次都向数据库取数据,把用户常常访问到的数据放到内存中,甚至缓存十分大的时候我们可以把内存中的缓存放到硬盘中。还有高级的分布式缓存数据库使用,都可以增加系统的抗压力。
4) 分批传送:java+ajax实现数据分批加载到前端: 这里是java后台的一个action函数,JSONArray.fromObject(loadPageList).toString()
5) 数据库集群
6) 优化数据库连接, 应用程序和数据库最大连接数量之间要配套,根据用户并发量确定最大连接量
7) 程序代码优化
8) 优化日志输出:a):用户登录信息等重要事件日志存放在日志表中;b):系统运行时日志通过logger打印到日志文件中。
9) SQL优化:
a避免使用select * 查询;模糊查询时尽量百分号在模糊字段后;
b对查询尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
c应避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
d应避免在 where 子句中对字段进行null值判断,否则将导致引擎放弃用索引而全表扫描。
10) 负载均衡, 负载均衡概念:公司会建立很多的服务器,这些服务器组成了服务器集群,然后,当用户访问网站的时候,先访问一个中间服务器,再让这个中间服务器在服务器集群中选择一个压力较小的服务器,然后将该访问请求引入选择的服务器。这样都会保证服务器集群中的每个服务器压力趋于平衡,分担了服务器压力,避免了服务器崩溃的情况。
11) 使用消息队列, 消息队列中间件是分布式系统中重要的组件,主要解决应用耦合,异步消息,流量削锋等问题。实现高性能,高可用,可伸缩和最终一致性架构,使用较多的消息队列有ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ。
3. 说说双亲委派
任何一个类,都需要这个类本身和加载它的类加载器一同来确定其在虚拟机的唯一性。两个好处:1.类伴随它的类加载器一起具备了一种带有优先级的层次关系,确保了在各种加载环境的加载顺序。2.保证了运行的安全性,防止不可信类扮演可信任的类.
首先得先了解一下类加载阶段。类加载阶段分为加载、连接、初始化三个阶段,而加载阶段需要通过类的全限定名来获取定义了此类的二进制字节流。
Java特意把这一步抽出来用类加载器来实现。把这一步骤抽离出来使得应用程序可以按需自定义类加载器。并且得益于类加载器,OSGI、热部署等领域才得以在JAVA中得到应用。在Java中任意一个类都是由这个类本身和加载这个类的类加载器来确定这个类在JVM中的唯一性。
Java自身提供了3种类加载器: 1,启动类加载器(Bootstrap ClassLoader),它是属于虚拟机自身的一部分,用C++实现的,主要负责加载 <JAVA_HOME>\lib目录中或被-Xbootclasspath指定的路径中的并且文件名是被虚拟机识别的文件。它等于是所有类加载器的爸爸。2, 扩展类加载器(Extension ClassLoader),它是Java实现的,独立于虚拟机,主要负责加载<JAVA_HOME>\lib\ext目录中或被java.ext.dirs系统变量所指定的路径的类库。3,应用程序类加载器(Application ClassLoader),它是Java实现的,独立于虚拟机。主要负责加载用户类路径(classPath)上的类库,如果我们没有实现自定义的类加载器那这玩意就是我们程序中的默认加载器。双亲委派模型:
双亲委派的意思是如果一个类加载器需要加载类,那么首先它会把这个类请求委派给父类加载器去完成,每一层都是如此。一直递归到顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。这里的双亲其实就指的是父类,父类也不是我们平日所说的那种继承关系,只是调用逻辑是这样。
双亲委派模型不是一种强制性约束,也就是你不这么做也不会报错怎样的,它是一种JAVA设计者推荐使用类加载器的方式。
双亲委派有啥好处呢?它使得类有了层次的划分。就拿java.lang.Object来说,你加载它经过一层层委托最终是由Bootstrap ClassLoader来加载的,也就是最终都是由Bootstrap ClassLoader去找<JAVA_HOME>\lib中rt.jar里面的java.lang.Object加载到JVM中。这样如果有不法分子自己造了个java.lang.Object,里面嵌了不好的代码,如果我们是按照双亲委派模型来实现的话,最终加载到JVM中的只会是我们rt.jar里面的东西,也就是这些核心的基础类代码得到了保护。因为这个机制使得系统中只会出现一个java.lang.Object。不会乱套了。你想想如果我们JVM里面有两个Object,那岂不是天下大乱了。
Java就搞了个线程上下文类加载器,通过setContextClassLoader()默认情况就是应用程序类加载器然后Thread.current.currentThread().getContextClassLoader()获得类加载器来加载。
Tomcat的webappClassLoader加载自己的目录下的class文件,不会传递给父类加载器。tomcat之所以造了一堆自己的classloader,大致是出于下面三类目的:
对于各个 webapp中的 class和 lib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况,而对于许多应用,需要有共享的lib以便不浪费资源。
与 jvm一样的安全性问题。使用单独的 classloader去装载 tomcat自身的类库,以免其他恶意或无意的破坏;热部署。tomcat修改文件不用重启就自动重新装载类库惊叹吧。
独立消息服务最终一致性,目前较为主流的MQ,如ActiveMQ、RabbitMQ、Kafka、RocketMQ等,只有RocketMQ支持事务消息。早年阿里对MQ增加事务消息也是因为支付宝那边因为业务上的需求而产生的。因此,如果我们希望强依赖一个MQ的事务消息来做到消息最终一致性的话,在目前的情况下,技术选型上只能去选择RocketMQ来解决。分析事务消息所存在的异常情况:即MQ存储了待发送的消息,但是MQ无法感知到上游处理的最终结果。对于RocketMQ而言,它的解决方案非常的简单,就是其内部实现会有一个定时任务,去轮训状态为待发送的消息,然后给producer发送check请求,而producer必须实现一个check监听器,监听器的内容通常就是去检查与之对应的本地事务是否成功(一般就是查询DB),如果成功了,则MQ会将消息设置为可发送,否则就删除消息。
事务消息,由于传统的处理方式无法解决消息生成者本地事务处理成功与消息发送成功两者的一致性问题,因此事务消息就诞生了,它实现了消息生成者本地事务与消息发送的原子性,保证了消息生成者本地事务处理成功与消息发送成功的最终一致性问题。处理流程:
注意:由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。
不选择RocketMQ为系统的MQ做到消息的最终一致性呢?答案是可以。
基于本地消息的最终一致性方案的最核心做法就是在执行业务操作的时候,记录一条消息数据到DB,并且消息数据的记录与业务数据的记录必须在同一个事务内完成,这是该方案的前提核心保障。在记录完成后消息数据后,后面我们就可以通过一个定时任务到DB中去轮训状态为待发送的消息,然后将消息投递给MQ。这个过程中可能存在消息投递失败的可能,此时就依靠重试机制来保证,直到成功收到MQ的ACK确认之后,再将消息状态更新或者消息清除;而后面消息的消费失败的话,则依赖MQ本身的重试来完成,其最后做到两边系统数据的最终一致性。基于本地消息服务的方案虽然可以做到消息的最终一致性,但是它有一个比较严重的弊端,每个业务系统在使用该方案时,都需要在对应的业务库创建一张消息表来存储消息。针对这个问题,我们可以将该功能单独提取出来,做成一个消息服务来统一处理。
1).链表长度大于8。
2).当满足条件1以后调用treeifyBin方法转化红黑树。该方法中,数组如果长度小于MIN_TREEIFY_CAPACITY(64)就选择扩容,而不是转化为红黑树。
为什么链表是8次以后就转换为红黑树?红黑树插入为O(lgn),查询为O(lgn),链表插入为O(1),查询为O(n)。个数少时,插入删除成本高,用链表;个数多时,查询成本高,用红黑树。需要定一个值,比这个值大就转红黑树,比这个值小就转链表,而且要避免频繁的转换。根据泊松分布,在负载因子0.75(HashMap默认)的情况下,单个hash槽内元素个数为8的概率小于百万分之一,将7作为一个分水岭,等于7时不做转换,大于等于8才转红黑树,小于等于6才转链表。
6. rpc实现原理
RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP/IP或UDP,为通信程序之间携带信息数据。RPC将原来的本地调用转变为调用远端的服务器上的方法,给系统的处理能力和吞吐量带来了近似于无限制提升的可能。在OSI网络通信模型中,RPC跨域了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC架构,一个完整的RPC架构里面包含了四个核心的组件,分别是Client,Client Stub,Server以及Server Stub,这个Stub可以理解为存根。
• 客户端(Client),服务的调用方。
• 客户端存根(Client Stub),存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方。
• 服务端(Server),真正的服务提供者。
• 服务端存根(Server Stub),接收客户端发送过来的消息,将消息解包,并调用本地的方法。
RPC调用过程:
(1) 客户端(client)以本地调用方式(即以接口的方式)调用服务;
(2) 客户端存根(client stub)接收到调用后,负责将方法、参数等组装成能够进行网络传输的消息体(将消息体对象序列化为二进制);
(3) 客户端通过sockets将消息发送到服务端;
(4) 服务端存根( server stub)收到消息后进行解码(将消息对象反序列化);
(5) 服务端存根( server stub)根据解码结果调用本地的服务;
(6) 本地服务执行并将结果返回给服务端存根( server stub);
(7) 服务端存根( server stub)将返回结果打包成消息(将结果消息对象序列化);
(8) 服务端(server)通过sockets将消息发送到客户端;
(9) 客户端存根(client stub)接收到结果消息,并进行解码(将结果消息发序列化);
(10) 客户端(client)得到最终结果。
RPC的目标是要把2、3、4、7、8、9这些步骤都封装起来。注意:无论是何种类型的数据,最终都需要转换成二进制流在网络上进行传输,数据的发送方需要将对象转换为二进制流,而数据的接收方则需要把二进制流再恢复为对象。
RPC需要解决的问题:Call ID映射
我们怎么告诉远程机器我们要调用funA,而不是funB或者funC呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用funA,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。
所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。
【Note】当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。
常用的RPC框架:
gRPC是Google公布的开源软件,基于最新的HTTP2.0协议,并支持常见的众多编程语言。 我们知道HTTP2.0是基于二进制的HTTP协议升级版本,目前各大浏览器都在快马加鞭的加以支持。 这个RPC框架是基于HTTP协议实现的,底层使用到了Netty框架的支持。
Thrift是Facebook的一个开源项目,主要是一个跨语言的服务开发框架。它有一个代码生成器来对它所定义的IDL定义文件自动生成服务代码框架。用户只要在其之前进行二次开发就行,对于底层的RPC通讯等都是透明的。不过这个对于用户来说的话需要学习特定领域语言这个特性,还是有一定成本的。
Dubbo是阿里开源的一个极为出名的RPC框架,在很多互联网公司和企业应用中广泛使用。协议和序列化框架都可以插拔是及其鲜明的特色。同样的远程接口是基于Java Interface,并且依托于spring框架方便开发。可以方便的打包成单一文件,独立进程运行,和现在的微服务概念一致。
7.一个for循环找数组中的第二大数
栈(先进后出)常见操作有出栈(POP),从栈中弹出一个元素;入栈(PUSH),将一个元素压入栈中,访问栈顶元素(TOP),判断栈是否为空等。
可以选择数组或者链表来实现,它们各有特点,前者容量有限且固定,但操作简单,而后者容量理论上不受限,但是操作并不如数组方便,每次入栈要进行内存申请,出栈要释放内存,稍有不慎便造成内存泄露。
1). 浏览器向DNS服务器查找输入URL对应的IP地址。
2). DNS服务器根据IP地址与目标web服务器在80端口上建立TCP连接。
3). TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。
第一次:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
第二次:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
第三次:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。 完成三次握手,客户端与服务器开始传送数据。
4).浏览器获取请求页面的html代码,JSP是Servlet的一种特殊形式,每个JSP页面就是一个Servlet实例——JSP页面由系统编译成Servlet,Servlet再负责响应用户请求。html代码是通过out.write()形式传到浏览器。tomcat/work目录下有jsp生成的java文件
5).浏览器在显示的窗口内渲染html
6).浏览器窗口关闭时,终止与服务器的连接
HTTP是运行在TCP层之上的,而HTTPS则是在HTTP和TCP层直接多加了一个SSL/TSL层,SSL层向上提供加密和解密的服务,对HTTP来说是透明的。
加密和解密都使用同一种算法的加密方法,称之为对称加密。加密和解密使用不同的算法,则为非对称加密。对称加密需要一把钥匙就够了,因为加密和解密使用的是同一把钥匙。非对称加密算法需要两把钥匙,公钥和私钥,它们是一对。用公钥加密的密文只能用相应的私钥解开,用私钥加密的密文只能用相应的公钥解开。其中,公钥是公开的,私钥是不对外公开的。两者的主要区别在于密钥的长度不同,长度越长,相应的加/解密花费的时间就会更长,对称加密使用的密钥长度会短一些。SSL 结合了这两种加密算法的有点。利用非对称加密算法来协商生成对称加密的密钥,然后之后就用对称加密来进行通信。发送密文的一方使用对方的公钥进行加密处理“对称的密钥”,然后对方用自己的私钥解密拿到“对称的密钥”,这样可以确保交换的密钥是安全的前提下,使用对称加密方式进行通信。所以,HTTPS采用对称加密和非对称加密两者并用的混合加密机制。
解决报文可能遭篡改问题使用数字签名,将一段文本先用Hash函数生成消息摘要,然后用发送者的私钥加密生成数字签名,与原文文一起传送给接收者。接下来就是接收者校验数字签名的流程了。
HTTPS一般使用的加密与HASH算法如下:非对称加密算法:RSA,DSA/DSS
对称加密算法:AES,RC4,3DES。 HASH算法:MD5,SHA1,SHA256
Http工作之前,Web浏览器通过网络和Web服务器建立链连接,该连接是通过Tcp来完成的,该协议和Ip共同组成了Internet,即著名的Tcp/Ip协议族,因此Internet也被称为Tcp/Ip网络,Http是比Tcp更高的应用层协议,一般Tcp接口的端口好是80。这个过程中,http建立连接,Tcp经过了3次握手。终止协议的时候,tcp进行了4次握手。
http是直接与TCP进行数据传输,而https是经过一层SSL(OSI表示层),用的端口也不一样,前者是80(需要国内备案),后者是443。注意https加密是在传输层:https报文在被包装成tcp报文的时候完成加密的过程,无论是https的header域也好,body域也罢都是会被加密的。
10. scheduler如何保证单实例
若是scheduler与web配置在一起,在高可用的情况下,如果有多个web容器实例,scheduler会在多个实例上同时运行。
方法1:部署的时候,针对不同实例,使用不同的配置。比如tomcat_1打开scheduler,tomcat_2关闭。带来的问题是:增加部署成本。要是tomcat_1挂了,scheduler就不能运行了,高可用落空。
方法2:在task的基类加入一些逻辑,当开始运行时,将状态(运行机器的IP、时间等)写入数据库、缓存或者zk,运行结束时重置状态。其它实例看到有这样的数据,就直接返回。带来的问题是:需要所有实例上的机器时间同步,不然一个刚结束另一个才开始,状态的维护就没有用了。一定要保证结束运行后将状态重置,否则下一个运行周期,所有的task都会返回的。实在不行还得写一个task做这个事。因为读写状态并非原子操作,偶尔也会发生task同时运行的事。
方法3:将scheduler与web分开。这样还能避免后台任务影响web端。带来的问题是:增加部署成本。scheduler的高可用需要重新考虑。
SchedulerFactoryBean的代码,里面一个参数叫做:schedulerName,SchedulerFactoryBean通过 StdSchedulerFactory返回一个具体的Scheduler的。而且每个Scheduler是注册在 SchedulerRepository中的。 SchedulerRepository中的每个Scheduler都是放在一个MAP中的,根据名字作为KEY。
示例参考:RESTful API Design and header_Mynah886的博客-优快云博客
REST是一套风格约定,RESTful是它的形容词形式。比如一套实现了REST风格的接口,可以称之为RESTful接口。
URI 表示资源,资源一般对应服务器端领域模型中的实体类。是统一资源定位器。URI表示资源的两种方式:资源集合、单个资源。
URL 用来定位资源,跟要进行的操作区分开,这就意味着URL不该有任何动词。
12. webservice设计底层知道吗
webservice就是一个服务发现,服务查找,服务绑定和使用的三个模块。
webservice是用SOAP协议来传输的,实际上就是HTTP + XML,Http就是传输数据,而XML就是封装数据.SOAP协议是基于HTTP协议的,两者的关系就好比高速公路是基于普通公路改造的,在一条公路上加上隔离栏后就成了高速公路。商店的服务员只要收到了钱就给客户提供货物,商店服务员不用关心客户是什么性质的人,客户也不用关心商店服务员是什么性质的人。同样,WebService客户端只要能使用HTTP协议把遵循某种格式的XML请求数据发送给WebService服务器,WebService服务器再通过HTTP协议返回遵循某种格式的XML结果数据就可以了,WebService客户端与服务器端不用关心对方使用的是什么编程语言。
HTTP协议和XML是被广泛使用的通用技术,各种编程语言对HTTP协议和XML这两种技术都提供了很好的支持,WebService客户端与服务器端使用什么编程语言都可以完成SOAP的功能,所以,WebService的很容易实现跨编程语言,跨编程语言自然也就跨了操作系统平台。
WSDL文件:WebService客户端要调用一个WebService服务,首先要有知道这个服务的地址在哪,以及这个服务里有什么方法可以调用,所以,WebService务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,服务是什么(服务中有哪些方法,方法接受的参数是什么,返回值是什么),服务的网络地址用哪个url地址表示,服务通过什么方式来调用。
WSDL(WebServiceDescription语言)是基于XML格式的,它是WebService客户端和服务器端都能理解的标准格式,其中描述的信息可以分为什么,在哪里,如何等部分!
WSDL文件保存在网络服务器上,通过一个URL地址就可以访问到它。客户端要调用一个WebService的服务之前,要知道该服务的WSDL文件的地址.WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:
1.注册到UDDI服务器,以便被人查找
2.直接告诉给客户端调用者,例如,在自己网站给出信息或邮件告诉。
一般用户都是在服务端发布一个服务到指定的url上面进行注册,而客户端就根据当前的url来查找对应的服务,来绑定服务。图可以看出,SOA结构中共有三种角色:
1.服务提供者:服务提供商,发布自己的服务,并且对使用自身服务的请求进行响应。
2.服务经纪人:服务注册中心,注册已经发布的服务提供商,对其进行分类,并提供搜索服务,这是可搜索的服务描述注册中心,服务提供者在此发布他们的服务描述。在静态绑定开发或动态绑定执行期间,服务请求者查找服务并获得服务的绑定信息(在服务描述中)。对于静态绑定的服务请求者,服务注册中心是体系结构中的可选角色,因为服务提供者可以把描述直接发送给服务请求者。同样,服务请求者可以从服务注册中心以外的其它来源得到服务描述,例如本地文件,FTP站点,网站点,广告和服务发现(广告与发现) of Services,ADS)或发现Web服务(发现Web服务,DISCO)。
3.服务请求者:服务请求者,利用服务经纪人查找所需的服务,然后使用该服务.
Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于java实现。作为一个优秀的开源框架,Quartz具有以下特点:强大的调度功能、灵活的应用方式、分布式和集群能力,另外作为spring默认的调度框架,很容易实现与Spring集成,实现灵活可配置的调度功能。
Quartz框架的核心对象: Scheduler核心调度器,就是任务调度、分配的控制器Job任务,代表具体要执行的任务,是个接口,里面有默认方法,开发者需要实现该接口,并且业务逻辑写在默认的execute方法中, JobDetail任务描述,描述job的静态消息,是调度器需要的数据,跟Job区分开来,主要是为了一个Job可以在多台机器并行,每个调度器new一个Job的实现类, Trigger触发器,用于定义任务调度的时间规则。
Elastic-job要解决这个分布式的水平扩展、效率问题,我们知道需要引入注册中心进行协调,当当推出的Elastic-job就是Quartz的基础上,引入ZK做注册中心。并且在2.0版本后出现了两个相互独立的产品线:Elastic-job-lite和Elastic-job-cloud。Elastic-job-lite定位为轻量级无中心化的解决方案,使用jar包的形式提供分布式任务的协调服务,外部依赖仅依赖于zookeeper。
xxl-job是一个轻量级的分布式任务调度框架,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。设计思想为:
1). 将调度行为抽象形成“调度中心”公共平台,而平台自身并不承担业务逻辑,“调度中心”负责发起调度请求。
2). 将任务抽象成分散的JobHandler,交由执行器统一管理,执行器负责接收调度请求并执行对应的JobHandler中业务逻辑。 因此,“调度”和“任务”可以互相解偶,提高系统整体的稳定性和扩展性。
一, xxl_job中心部署
a.部署前需要初始化调度中心需要的几个表,这个脚本在源码的文件tables_xxl_job.sql中(如果mysql做主从,调度中心集群节点务必强制走主库)。
b.设置模板xxl-job-admin中的application.properties的服务端口,数据库信息和邮件配置信息。
c.可以打包模板xxl-job-admin为单个jar包,可以部署到对应的服务器中。登录页面。页面最常用的就是任务管理和调度日志页面,如果开发了新的任务,需要在任务管理页面添加。
二,开发执行器
a.首先,需要配置一个XxlJobSpringExecutor,可以在配置文件或者配置类中。如果想自动注册必须设置xxl.job.admin.addresses和xxl.job.executor.appname
b. Bean模式的有两种实现,类形式和方法形式。类形式,继承IJobHandler接口,实现execute方法,返回ReturnT实例。最后在类上添加XxlJob注解。
方法形式,要求方法的格式为:“public ReturnT<String> execute(String param)”,在方法上添加XxlJob注解。
三,在调度中心注册执行器和添加任务
a.打开admin页面,在执行器管理菜单中,添加一个执行器,可以是自动注册或者是手动注册。
b.注册好后,等待一会可以看到注册的信息,在OnLine 机器地址列,点击查看,可以看到注册的地址和端口。
c添加任务,在任务管理菜单点击添加,添加一个任务,我们选择Bean模式,JobHandler就是我们用XxlJob注解方法或者类的名称。
d.任务添加后,可以启动或者执行这个任务。
保障服务稳定的三大利器:缓存、限流、熔断降级。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。
1) 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。
一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流。限流算法有:计数器、漏桶、令牌桶。
计数器算法:采用计数器实现限流有点简单粗暴,一般是限制一秒钟的能够通过的请求数,比如限流qps为50,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了50,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,后面的990ms,只能把请求拒绝,这种现象称为“突刺现象”,不平滑。
漏桶,为了消除"突刺现象",可以采用漏桶算法实现限流,漏桶算法这个名字就很形象,算法内部有一个容器,类似生活中的漏斗,当请求进来时,相当于水倒入漏斗,然后从下端小口慢慢匀速的流出。不管上面流量多大,下面流出的速度始终保持不变。不管服务调用方多么不稳定,通过漏桶算法进行限流,每10毫秒处理一次请求。因为处理的速度是固定的,请求进来的速度是未知的,可能突然进来很多请求,没来得及处理的请求就先放在桶里,既然是个桶,肯定是有容量上限,如果桶满了,那么新进来的请求就丢弃。
算法实现思路:可以准备一个队列,用来保存请求,另外通过一个线程池定期从队列中获取请求并执行,可以一次性获取多个并发执行。弊端:无法应对短时间的突发流量。
令牌桶算法是对漏桶算法的一种改进,桶算法能够限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许一定程度的突发调用。在令牌桶算法中,存在一个桶,用来存放固定数量的令牌。算法中存在一种机制,以一定的速率往桶中放令牌。每次请求调用需要先获取令牌,只有拿到令牌,才有机会继续执行,否则选择等待可用的令牌、或者直接拒绝。放令牌这个动作是持续不断的进行,如果桶中令牌数达到上限,就丢弃令牌,所以就存在这种情况,桶中一直有大量的可用令牌,这时进来的请求就可以直接拿到令牌执行,比如设置qps为100,那么限流器初始化完成一秒后,桶中就已经有100个令牌了,这时服务还没完全启动好,等启动完成对外提供服务时,该限流器可以抵挡瞬时的100个请求。所以,只有桶中没有令牌时,请求才会进行等待,最后相当于以一定的速率执行。
算法实现思路:可以准备一个队列,用来保存令牌,另外通过一个线程池定期生成令牌放到队列中,每来一个请求,就从队列中获取一个令牌,并继续执行。通过Google开源的guava包,使用它可以很轻松的创建一个令牌桶算法的限流器。
2) 熔断,假设微服务A调用B和C,微服务B和C又调用其它的微服务。如果调用链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。熔断机制是应对雪崩效应的一种微服务链路保护机制。
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。熔段解决如下几个问题:
•当所依赖的对象不稳定时,能够起到快速失败的目的
•快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复
3) 服务降级,是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况。在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。
例如:当活动高峰,把无关交易的服务统统降级,如查看蚂蚁深林,查看历史订单,商品历史评论,只显示最后100条等等。