JSF客户端使用手册
说明
名词解释:
注册中心(Registry Server):提供服务信息的注册订阅,配置管理,配置下发等功能。
注册中心寻址服务(Index Server):简单的http的注册中心寻址。
注册中心管理端(jsf.jd.com):管理端,用户可以登录管理自己的服务。
服务(Interface):就是一个接口。
服务提供者(Provider):启动一个监听端口,并发布服务。
服务调用者(Consumer):连接Provider启动的端口,并调用服务。
监控中心(Monitor Server):收集服务质量的监控中心服务。
JSF客户端(jsf.jar):使用了jsf.jar的程序,不管是服务提供者和服务调用者。
功能列表
配置方式
配置项参考:JSF配置参考手册
目前支持三种配置方式:
名称
|
配置
|
说明
|
---|---|---|
Spring+XML(推荐) |
基于spring的schema校验做了扩展。推荐使用这种方式。 |
代码参见: JSF入门指南 JSF入门指南#2.DEMO下载 |
Spring+annotation(不推荐) |
使用注解方式,代码入侵,开发比较方便。 但是上线后修改必须重新打jar包,可扩展非常差。 |
代码参见: JSF入门指南#2.DEMO下载 |
API方式 |
普通的javabean方式,代码入侵。 |
代码参见: |
服务注册与订阅
JSF可配置多个注册中心(不过暂未开放)。如果用户未指定任何注册中心,JSF会默认生成一个注册中心。
< jsf:registry id = "jsfRegistry" index = "i.jsf.jd.com" />
< jsf:provider registry = "jsfRegistry" /> <!-- 发注册中心jsfRegistry -->
< jsf:provider /> <!-- 未配置"registry" 则默认发全部jsf:registry 即注册中心1,2 -->
|
在启动Provider,会自动去指定注册中心注册一条Provider信息,同时订阅接口配置。
(不推荐)可以通过如下配置实现不注册和不订阅。
< jsf:provider register = "false" subscribe = "false" />
|
在启动Consumer的时候,会自动去注册中心注册一条Consumer信息,同时订阅服务列表,订阅接口配置。
(不推荐)可以通过如下配置实现不注册和不订阅。
< jsf:consumer register = "false" subscribe = "false" />
|
集群策略
Consumer调用时,可以设置调用集群策略,配置如下:
< jsf:consumer cluster = "failover" />
|
目前支持如下几种集群策略:
名称
|
配置
|
说明
|
---|---|---|
失败重试(默认) | failover |
|
失败忽略 | failfast |
|
|
|
|
定点调用 | pinpoint |
|
负载均衡
在Consumer调用,会对服务列表进行软负载,配置如下:
< jsf:consumer loadbalance = "random" />
|
目前支持如下几种策略。
名称
|
配置
|
版本
|
优点
|
缺点
|
---|---|---|---|---|
随机(默认) | random | 1.0.0 |
|
|
轮询 | roundrobin | 1.0.0 |
|
如果某个Provider较慢,可能会积压请求 |
最少并发优先 | leastactive | 1.0.0 |
|
目前此问题已解决,Provider抛异常会降低选中概率,最低到10%,等Provider恢复并调用成功后,选中概率回恢复到100%。 |
一致性hash |
consistenthash | 1.2.0 |
|
可能调用分布不均匀。 |
本机IP调用优先 | localpref | 1.5.1 |
|
计算相同IP稍微增加点耗时。 |
服务依赖检查
Consumer启动时候会去连Provider,默认Consumer是不强依赖与Provider的。
也就是说即使没有服务提供者,服务调用者也照常启动(建长连接失败可能会打印些异常)。只不过是调用时报错。
如果Consumer是强依赖于Provider的,可以通过如下配置进行检查。
< jsf:consumer check = "true" /> <!-- 默认 check="false" -->
|
这样的话,如果没有可用的Provider,Consumer就启动失败,上线时就能提前暴露出问题所在。
直连调用
Consumer调用的时候可以不通过注册中心,直接指定Provider地址进行远程调用。
这样就无法从注册中心拿到新的服务列表地址,可扩展性差。
可用于开发和测试环境,不推荐线上生产环境使用。 |
参考:
<!-- 格式为 协议://ip:port/?key=value, 多个用英文逗号或英文分号隔开-->
< jsf:consumer url = "jsf://127.0.0.1:11090,jsf://127.0.0.1:11091" />
|
也可以强制调一个别的别名(适用于动态分组等场景)。
例如:服务提供者别名是yyy,动态追加一个分组xxx,老的xxx需要调用这个分组时得带上原始别名。
<!-- 格式为 协议://ip:port/?key=value, 多个用英文逗号或英文分号隔开-->
< jsf:consumer alias = "xxx" url = "jsf://127.0.0.1:11090?alias=yyy" />
|
异步调用
异步调用指的Consumer发起调用后,不是等待拿到结果,而是返回null同时可以拿到一个Future,过一会再拿Future去取。
适用于可以一次性并行发起多个请求的环境(例如某些集成页面,后台同时调用多个系统),总耗时等于最长时间的调用。
例如:一个页面要同时调用A(耗时600ms),B(耗时500ms),C(耗时100ms) 三个服务,
如果采取线性调用,则需要1200ms;采取异步调用则只需要600ms。
使用方式为:在调用端配置加入如下代码:
< jsf:consumer async = "true" /> <!-- 所有方法异步 默认 async="false" />
< jsf:consumer >
< jsf:method name = "xxx" async = "true" /> <!-- 指定方法异步 默认 async="false" />
</ jsf:consumer >
|
在调用的时候采用如下代码:
String result1 = service.echoStr( "aaaa" ); // 发起一次调用 此时返回null
ResponseFuture<String> future1 = RpcContext.getContext().getFuture(); // 得到第一次调用的Future
String result2 = service.echoStr( "bbbb" ); // 发起第二次调用 此时返回null
ResponseFuture<String> future2 = RpcContext.getContext().getFuture(); // 得到第二次调用的Future
String result3 = service.echoStr( "ccc" ); // 发起第三次调用 此时返回null
ResponseFuture<String> future3 = RpcContext.getContext().getFuture(); // 得到第三次调用的Future
// 依次拿到结果,一定要get,否则会造成结果未消费导致内存溢出。
try {
result1 = future1.get(); // 可能会抛异常,请捕获
} catch (Throwable e) {
}
try {
result2 = future2.get(); // 可能会抛异常,请捕获
} catch (Throwable e) {
}
try {
result3 = future3.get(); // 可能会抛异常,请捕获
} catch (Throwable e) {
}
|
如果future1.get()的时候时间较长,则会阻塞等待结果,不过没关系,如果future1时间最长,那么future2和3能直接拿到结果,无需等待了。
New! 新增加函数式方法
ResponseFuture<String> future4 = ResponseFuture.build( new ResponseFuture.AsyncCall() {
public void invoke() throws Throwable {
service.get( 1 ); // 业务调用代码
}
});
try {
String result4 = future4.get();
} catch (Throwable e) {
}
|
异步回调
异步调用的时候,大多数情况,我们是知道什么时候拿Future是get结果的,比如上面的例子,我们知道发完请求就可以拿结果。
但是有的时候,我们希望异步调用有结果回来的时候,再触发下调用者的一些逻辑。
异步回调的到时候,最好是限制发送的频率,要不然发送太快,一直不停的发,会导致内存溢出等。 |
做法如下:
1.需要实现一个Listener实现com.jd.jsf.msg.ResponseListener接口,实现handleResult和catchException方法。
package com.jd.testjsf.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jd.jsf.msg.ResponseListener;
public class TestResponseListener implements ResponseListener {
private final static Logger LOGGER = LoggerFactory.getLogger(TestResponseListener. class );
public void handleResult(Object result) {
LOGGER.info( "interface listener get result :{}" , result);
}
public void catchException(Throwable e) {
LOGGER.info( "interface listener catch exceprion :{} {}" , e.getClass().getCanonicalName(), e.getMessage());
}
}
|
2.在接口级注入Listener。Listener是否单例可以自己通过bean控制。
<bean id= "responseListener" class = "com.jd.testjsf.listener.TestResponseListener" />
<jsf:consumer onreturn= "responseListener" async= "true" /> <!-- 异步 且有回调监听器 -->
|
或者方法级注入。
<jsf:consumer async= "true" >
<jsf:method name= "get" onreturn= "responseListener" /> <!-- get方法有回调监听器 -->
</jsf:consumer>
|
3. 调用的时候和异步的一样,返回值为null,等到有结果返回会调用注入的responseListener
String result1 = service.echoStr( "aaaa" ); // 发起一次调用 此时返回null
// 无需再getFuture,等有结果会自动回调ResponseListener里的方法
//Future<String> future1 = RpcContext.getContext().getFuture();
|
4. 如果想每次请求都采用新的ResponseListener,而不像如果采用spring配置的方式那样,方法全局一个的话,可以采用以下方式。注意只支持API的方式,无法通过配置注入。
此特性需要1.6.1以上版本
RpcContext.getContext().getAttachments().put(Constants.INTERNAL_KEY_ONRETURN, new ResponseListener() {
private int t = times;
@Override
public void handleResult(Object result) {
System.out.println( "method--" +t+ "次:result:" +result);
}
@Override
public void catchException(Throwable e) {
System.out.println( "method exception--" +e.getMessage());
}
}); //在方法调用前注入responseListener实例
service.echoStr( "test method response listener" );
|
此操作会分发到调用端对应的回调线程池上执行,线程池大小和队列可以通过<jsf:parameter /> 配置。 |
如果要自定义回调线程池的大小,可以使用如下参数:
< jsf:parameter key = "callback.pool.coresize" value = "20" />
< jsf:parameter key = "callback.pool.maxsize" value = "200" />
< jsf:parameter key = "callback.pool.queue" value = "1024" />
|
泛化调用
在网关等场景下,调用者是拿不到服务端的class或者jar包,那此时发起调用,该如何处理?
JSF支持泛化调用,只需要指定接口、方法、参数类型、参数值,就可以完成调用。
参见 Generic调用说明
injvm调用
在Consumer进行调用的时候,如果在同一JVM内有Provider,则优先调用本地JVM内的Provider,不走远程调用。
injvm调用不会影响统计结果。
可以通过如下配置关闭此特性。
< jsf:consumer injvm = "false" /> <!-- 默认injvm="true" -->
|
callback调用
JSF协议为长连接,好处是可以进行服务端到调用端的callback调用。
使用方法如下:
1、方法上增加一个参数,类型为com.jd.jsf.transport.Callback<T>,需要指定回调的对象。
public interface HelloService {
public void callBackString(String request, Callback<String> resultListener);
}
|
2、调用端调用的时候,直接注入一个Callback实现对象。
Callback callback = new Callback<String>() {
public void notify(String result) {
logger.info( "notify callback string : {}" , result);
}
};
service.callBackString( "hello" , callback ); // 每次使用同一个callback,服务端也得到同一个。(推荐)
// service.callBackString("hello", new Callback() ); // 每次new一个callback,服务端每次拿到一个新的callback(不推荐)
|
如果Consumer调用的时候使用的时候同一个Callback对象,那么Provider端也只会生成一个Callback对象。
如果Consumer调用的时候使用的时候每次new一个Callback对象,那么Provider端每次得到一个新的Callback对象。客户端同一个接口类有限制,最多存在2000个Callback。
3、 服务端实例类里面可以使用callback对象回调。
public class HelloServiceImpl implements HelloService {
public void callBackString(String request, Callback<String> resultListener) {
resultListener.notify( "callback from server" );
}
|
也可以将Callback对象缓存起来,一直使用。(有点像注册机制)
如果连接已断开,Callback回调时会失败,抛出com.jd.jsf.error.CallbackStubException异常。如果已缓存可以删除。 |
多协议支持
目前jsf支持多种协议,只需要通过配置就可以完成发布和调用。
支持的协议如下:
协议
|
配置
|
连接方式
|
说明
|
备注
|
---|---|---|---|---|
jsf协议 | jsf(同时支持tcp+http) |
tcp长连接 |
自定义协议,默认采用msgpack序列化 |
兼容dubbo协议调用JSF与SAF的兼容与比较 |
http协议 | jsf(同时支持tcp+http) | http短连接 | 使用HTTP调用JSF接口 | |
|
|
|
|
请使用saf发布dubbo接口 |
rest协议 | rest | http短连接 | 通过resteasy发布webservice | 使用JSF发布REST协议接口 |
webservices | webservice/jaxws | http短连接 | 通过cxf发布webservice | 使用JSF发布Webservice协议接口 |
不建议使用JSF发布dubbo协议的服务端,因为JSF和SAF可以在项目中共存。参见JSF与SAF的兼容
Provider通过配置指定协议。
< jsf:server id = "myjsf" protocol = "jsf" />
< jsf:server id = "myrest" protocol = "rest" />
< jsf:provider server = "myjsf,myrest" /> <!-- 一个接口同时发两种协议 -->
|
Consumer通过配置指定协议。
< jsf:consumer protocol = "jsf" />
|
非守护启动
服务发布的时候,默认服务端的线程是守护线程,那么Provider在export之后,需要手动的hold住主线程,要不然会程序会退出。
如果希望启动的是非守护线程,可以通过如下配置实现:
< jsf:provider daemon = "false" /> <!-- 默认true -->
|
延迟启动
服务发布的时候,spring在加载到jsf:provider的时候默认会马上发布服务。
如果需要数据预热等需求,需要延迟一段时间再发布服务,可以通过如下配置实现:
< jsf:provider delay = "10000" /> <!-- 表示延迟十秒发布,1.6.0版本默认值-1;1.5.5版本默认值:0 -->
|
在Spring集成的发布方式下,还支持一种spring加载完毕后再发布服务,配置如下:
< jsf:provider delay = "-1" /> <!-- 表示Spring加载完毕后再发布服务 -->
|
延迟连接
在调用端拿到服务列表的时候,回去建立长连接,默认为初始化的时候创建,
如果希望第一次调用的时候创建,可以加入如下配置:
< jsf:consumer lazy = "true" /> <!-- 默认 lazy="false" -->
|
长连接会在第一次调用的时候再进行创建。
粘滞连接
调用端拿到服务列表建立连接后,在进行调用的时候,每次都会根据负载均衡算法(例如随机,轮询)等对可用连接进行重新选择。
如果希望每次都调一个服务端,直到出异常为止,再调下一个,可以加入如下配置:
< jsf:consumer sticky = "true" /> <!-- 默认 sticky="false" -->
|
参数校验
JSF在Consumer调用之前(推荐)可以进行参数校验操作,不通过的请求不能发给Provider。
同时也可以在Provider调业务代码之前进行参数校验操作,不通过的请求不调业务代码。
参见 使用JSF的参数校验(Validation)功能
请求上下文
在调用过程中,会有一个RpcContext记录一些请求的相关信息。
RpcContext是基于ThreadLocal的,每次调用都会重新清空。所以Consumer每次调用前都要设置,Provider每次要接到请求就先取值。 |
例如Provider可以获取获取远程地址信息(调用端IP):
InetSocketAddress address = RpcContext.getContext().getRemoteAddress();
String ip = RpcContext.getContext().getRemoteHostName();
|
异步调用的时候可以获取Future对象信息:
Future future = RpcContext.getContext().getFuture();
|
可以获取当前服务的所处角色:
boolea isProvider = RpcContext.getContext().isProviderSide();
boolea isConsumer = RpcContext.getContext().isConsumerSide();
|
Provider可以获取Consumer的自动部署appId信息(Provider1.5.2+,Consumer1.2.0+且接入自动部署,其实读取Consumer启动脚本里的 -Ddeploy.app.id=4556 -Ddeploy.instance.id=12345 -Ddeploy.app.name=jsf-web)。
String appId = (String) RpcContext.getContext().getAttachment(Constants.HIDDEN_KEY_APPID); // 自动部署应用Id
String appName = (String) RpcContext.getContext().getAttachment(Constants.HIDDEN_KEY_APPNAME); // 自动部署应用名称
String appInsId = (String) RpcContext.getContext().getAttachment(Constants.HIDDEN_KEY_APPINSID); // 自动部署应用实例Id
|
如果Consumer不是自动部署,或者需要在Consumer代码里指定 appId、appName、appInsId,见如下代码片段:
JSFContext.putIfAbsent(JSFContext.KEY_APPID, "1111" );
JSFContext.putIfAbsent(JSFContext.KEY_APPNAME, "jsfAppName" );
JSFContext.putIfAbsent(JSFContext.KEY_APPINSID, "22222" );
|
注意:
JSFContext : 客户端用JSF上下文设置appId、appName、appInsId;
RpcContext : 服务端使用RPC上下文获取appId、appName、appinsId;
另外:也可以自定义设置和获取一些隐藏的参数信息,参见下面的隐式传参
隐式传参
在Consumer调用的时候,除了发送方法需要的参数以为,还支持发送一个隐藏的Map<String,String>到服务端。
Consumer目前可以通过XML配置(API配置)指定每次调用都发送的固定的隐藏参数,或者在代码中每次调用都重新设置的动态的远程参数。
Consumer端:
1.spring配置方式传参如下:此类隐藏参数在每次调用的时候都会传给Provider。
< jsf:consumer id = "myHello1" interface = "com.jd.jsf.test.HelloService" alias = "ZG1" protocol = "jsf" >
< jsf:parameter key = "user" value = "zhanggeng" /> <!-- 接口级参数 -->
< jsf:parameter key = "passwd" value = "11112222" hide = "true" /> <!-- 接口级参数,标记为hide表示只能在filter取到,服务端业务代码取不到 -->
< jsf:method name = "echo" >
< jsf:parameter key = "aaa" value = "aaaaaaa" hide = "true" /> <!-- 方法级参数 -->
< jsf:parameter key = "bbb" value = "bbbbbbb" /> <!-- 方法级参数,标记为hide表示只能在filter取到,服务端业务代码取不到 -->
</ jsf:method >
</ jsf:consumer >
|
2. 调用端代码中动态设置。
RpcContext.getContext().setAttachment( "user" , "zhanggeng" );
RpcContext.getContext().setAttachment( ".passwd" , "11112222" ); // "."开头的对应上面的hide=true
xxxService.yyy(); // 再开始调用远程方法
// 重要:下一次调用要重新设置,之前的属性会被删除
RpcContext.getContext().setAttachment( "user" , "zhanggeng" );
RpcContext.getContext().setAttachment( ".passwd" , "11112222" ); // "."开头的对应上面的hide=true
xxxService.zzz(); // 再开始调用远程方法
|
try {
// 当前线程设置一次即可
RpcContext.getContext().setSessionAttribute( "user" , "zhanggeng" );
RpcContext.getContext().setSessionAttribute( ".passwd" , "11112222" ); // "."开头的对应上面的hide=true
xxxService.yyy(); // 再开始调用远程方法
xxxService.zzz(); // 再开始调用远程方法
} finally {
RpcContext.getContext().clearSession(); // 重要:需要主动清理
}
|
Provider端:
1. filter中直接获取,包括标记为hiden的参数。通过Rpccontext无法获取。
String consumerToken = (String) invocation.getAttachment( ".passwd" );
|
2. 服务端业务代码中直接获取
String user = RpcContext.getContext().getAttachment( "user" );
|
String user = RpcContext.getContext().getSessionAttribute( "user" );
|
3. Provider也可以自己在spring配置中传参,也会带到每个请求中(此项设置意义不大)。
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService" ref = "helloService" server = "jsf" alias = "JSF_0.0.1" >
< jsf:parameter key = "yourname" value = "zhangxx" /> <!-- 每次都能在代码拿到 -->
</ jsf:provider >
|
其它:
调用链中的隐式传参
注意:在调用链例如A–>B–>C,A和B都要隐私传参的时候,由于是同一个线程,会出现数据污染。例如A发参数P1给B,B收到请求拿到P1同时要发参数P2给C,那么C会直接拿到P1,P2。 |
Token调用
token认证基于隐式传参实现,Provider和Consumer需要指定同一个token才能进行调用。
Provider配置如下:加入隐藏key:token。
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService" ref = "helloService" server = "jsf" alias = "JSF_0.0.1" >
< jsf:parameter key = "token" value = "1qaz2wsx" hide = "true" />
</ jsf:provider >
|
Consumer配置如下:同样加入隐藏key:token
< jsf:consumer id = "helloService" interface = "com.jd.testjsf.HelloService" alias = "JSF_0.0.1" protocol = "jsf" >
< jsf:parameter key = "token" value = "1qaz2wsx" hide = "true" />
</ jsf:consumer >
|
双方一致才能完成调用,否则抛出异常。
API方式:(API方式为com.jd.jsf.util.Constants#HIDDEN_KEY_TOKEN) 就是 .token 前面带点
providerConfig.setParameter(com.jd.jsf.gd.util.Constants.HIDDEN_KEY_TOKEN, "123456");
consumerConfig.setParameter(com.jd.jsf.gd.util.Constants.HIDDEN_KEY_TOKEN, "123456");
|
JSF调SAF:JSF调SAF的token服务参见:JSF与SAF的兼容与比较#JSF调用SAF(dubbo)
指定发布IP
如果服务端存在多网卡,且都能连注册中心的情况,就可能出现绑定的IP不固定的情况,此时需要手动指定发布的IP地址。
< jsf:server id = "jsf" protocol = "jsf" host = "10.12.113.111" /> <!--默认连注册中心取,没有注册中心从网卡里取一个 -->
|
指定注册IP
目前显示虚拟机出现 机器IP 和 机器物理网卡IP 不对应的情况,这是需要指定一个虚拟IP。 |
例如虚拟机IP为172.17.11.11(其它机器通过此IP访问),该虚拟机对应的宿主物理机IP为192.168.22.22。可是在虚拟机上并没有IP为17.17.11.11的网卡信息,反而有一个192.168.22.22的网卡信息。
此时发布jsf服务,是必须绑定到网卡上的,所以绑定的地址为 192.168.22.22:22000(绑定172.17.11.11会出现异常,因为没有网卡)。
那么如果告诉注册中心此Provider地址为192.168.22.22:22000,其它机器又无法访问,这时候怎么办呢?
可以通过如下配置,指定注册ip,注册ip和绑定ip可以不同。
< jsf:server id = "jsf" protocol = "jsf" host = "192.168.22.22" virtualhost = "172.17.11.11" />
|
如果不想每个都设置,可以保存到一个文件,例如 echo "172.17.11.11" > /etc/jsfvirtualhost,然后每次读取这个文件
< jsf:server id = "jsf" protocol = "jsf" virtualhostfile = "/etc/jsfvirtualhost" />
|
指定端口
jsf发布服务端时候可以指定发布的端口。
< jsf:server id = "jsf" protocol = "jsf" port = "11090" /> <!--默认22000 -->
|
如果端口被占用,则自动加1进行重试,直到发布成功或无可用端口。
port可以配置为"-1"表示随机端口,随机端口的节点如果一段时间无心跳会被删除。(注:目前为了端口范围可控也已经控制为从22000开始叠加)。
方法限制
jsf支持一个接口下只发布部分方法(不推荐)
不推荐这样发布,如果不让调用最好从接口里删除该方法 |
配置接口下的include白名单和exclude黑名单即可,多个方法用逗号隔开。
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService" ref = "helloService" server = "jsf" alias = "JSF_0.0.1"
include = "*" exclude = "get" > <!-- include代码 -->
</ jsf:provider >
|
内置Filter
目前Provider和Consumer内置了一些过滤器,符合条件会自动加载,无需手动开启,顺序和列表如下。
范围
|
过滤器
|
中文名
|
别名
|
可独立关闭
|
备注
|
---|---|---|---|---|---|
provider/consumer | List<AbstractFilter> | 全部内置过滤器 |
* |
是 | 强制关闭全部 |
provider/consumer | List<AbstractFilter> | 全部内置过滤器 | default | 是 | 强制关闭全部 |
provider | com.jd.jsf.gd.filter.SystemTimeCheckFilter | 系统时间检查过滤器 | systemTimeCheck | 是 | linux下检查系统时间变化,防止定时器失效 |
provider | com.jd.jsf.gd.filter.ExceptionFilter | 异常过滤器 | exception | 是(1.5.2+) | 关闭后抛出未知异常,调用端可能无法反序列化 |
provider | com.jd.jsf.gd.filter.ProviderContextFilter | 服务端上下文过滤器 | 否 | ||
provider | com.jd.jsf.gd.filter.ProviderGenericFilter | 服务端泛化调用过滤器 | providerGeneric | 是 | 关闭后无法支持泛化调用 |
provider | com.jd.jsf.gd.filter.ProviderHttpGWFilter | 服务端网关调用过滤器 | providerHttpGW | 是 | 关闭后HTTP网关调用异常返回有问题 |
provider | com.jd.jsf.gd.filter.TokenFilter | token检查过滤器 | token | 是 | 配置了token才加载,关闭后无法支持自带token认证 |
provider | com.jd.jsf.gd.filter.ProviderMethodCheckFilter | 方法调用验证器 | providerMethodCheck | 是 | 关闭后无法支持按方法名限制调用 |
provider | com.jd.jsf.gd.filter.ProviderTimeoutFilter | 服务端超时打印过滤器 | providerTimeout | 是 | 关闭后无法提示服务端超时信息 |
provider | com.jd.jsf.gd.filter.ValidationFilter | 参数校验过滤器 | validation | 是 | 配置了validation="true"才加载 |
provider | com.jd.jsf.gd.filter.CacheFilter | 结果缓存过滤器 | cache | 是 | 配置了cacheref才加载 |
provider | com.jd.jsf.gd.filter.ProviderConcurrentsFilter | 服务端并发控制器过滤器 | providerConcurrents | 是 | 配置了concurrents>=0才加载 |
provider | T extends com.jd.jsf.gd.filter.AbstractFilter | 自定义过滤器 | |||
provider | T extends com.jd.jsf.gd.filter.AbstractFilter | 自定义全局过滤器 | |||
consumer | com.jd.jsf.gd.filter.SystemTimeCheckFilter | 系统时间检查过滤器 | systemTimeCheck | 是 | linux下检查系统时间变化,防止定时器失效 |
consumer | com.jd.jsf.gd.filter.ExceptionFilter | 异常过滤器 | exception | 是(1.5.2+) | 关闭后抛出未知异常,调用端可能无法反序列化 |
consumer | com.jd.jsf.gd.filter.ConsumerGenericFilter | 调用端泛化调用过滤器 | consumerGeneric | 是 | 配置了generic="true"才加载 |
consumer | com.jd.jsf.gd.filter.ConsumerContextFilter | 调用端上下文过滤器 | 否 | ||
consumer | com.jd.jsf.gd.filter.CacheFilter | 结果缓存过滤器 | cache | 是 | 配置了cacheref才加载 |
consumer | com.jd.jsf.gd.filter.MockFilter | Mock调用过滤器(服务降级) | mock | 是 | |
consumer | com.jd.jsf.gd.filter.ConsumerInvokeLimitFilter | 按APP限制调用次数过滤器 | consumerInvokeLimit | 是 |
集成自动部署获取AppId才加载 |
consumer | com.jd.jsf.gd.filter.ValidationFilter | 参数校验过滤器 | validation | 是 | 配置了validation="true"才加载 |
consumer | com.jd.jsf.gd.filter.ConsumerConcurrentsFilter | 调用端并发控制器过滤器 | consumerConcurrents | 是 |
配置了concurrents>=0才加载 |
consumer | com.jd.jsf.gd.filter.ConsumerMonitorFilter | 调用端监控过滤器 | consumerMonitor | 否 | |
consumer | T extends com.jd.jsf.gd.filter.AbstractFilter | 自定义过滤器 | |||
consumer | T extends com.jd.jsf.gd.filter.AbstractFilter | 自定义全局过滤器 |
可以通过如下配置关闭(jsf-1.2.0+)内置过滤器。 通过“-xxx” 关闭内置的别名为xxx的过滤器。
配置参考:
<!-- 例如不开启全部内置过滤器-->
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService"
filter = "-default" ref = "helloService" server = "jsf" alias = "ZG110_0.0.1" >
</ jsf:provider >
<!-- 例如不开启调用端并发过滤器和结果缓存过滤器-->
< jsf:consumer id = "helloService2" interface = "com.jd.testjsf.HelloService" alias = "ZG110_0.0.1"
filter = "-consumerConcurrents,-cache" protocol = "jsf" >
</ jsf:consumer >
|
自定义Filter
JSF在调用端调用前和服务端都会有一个FilterChain,里面已经内置了一些Filter,运行用户扩展一些自定义Filter。
使用方式如下:
1、继承com.jd.jsf.filter.AbstractFilter,实现具体的逻辑。
package com.jd.jsf.filter;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jd.jsf.msg.RequestMessage;
import com.jd.jsf.msg.ResponseMessage;
public class EchoFilter extends AbstractFilter {
private final static Logger LOGGER = LoggerFactory.getLogger(EchoFilter. class );
@Override
public ResponseMessage invoke(RequestMessage request) {
Map<String, Object> configContext = super .getConfigContext(); // 可以拿到一些配置里的信息
LOGGER.info( "before invoke.." ); // 在getNext().invoke(request)前加的代码,将在远程方法调用前执行
ResponseMessage response = null ;
if ( "true" .equals(configContext.get( "mock" ))) {
response = getNext().invoke(request); // 调用链自动往下层执行
} else {
response = MessageBuilder.buildResponse(request); // 也可以自己构造返回对象
response.setResponse( "xxx" ); // 返回结果
// response.setException(); // 返回异常
}
LOGGER.info( "after invoke.." ); // 在getNext().invoke(request)后加的代码,将在远程方法调用后执行
return response;
}
}
|
2. 通过spring配置或者api方式注入到Provider或Consumer中。
过滤器不能是单例。因为过滤器有next对象,如果是单例,会被覆盖。请通过scope="prototype" 设置 |
Provider端配置参考
< bean id = "echoFilter" class = "com.jd.jsf.filter.EchoFilter" scope = "prototype" /> <!-- 过滤器不能是单例 -->
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService" ref = "helloService" server = "jsf" alias = "JSF_0.0.1"
filter = "echoFilter" > <!-- 多个filter用逗号隔开 -->
</ jsf:provider >
|
Consumer端配置参考
< bean id = "echoFilter" class = "com.jd.jsf.filter.EchoFilter" scope = "prototype" /> <!-- 过滤器不能是单例 -->
< jsf:consumer id = "helloService" interface = "com.jd.testjsf.HelloService" protocol = "jsf" alias = "JSF_0.0.1"
filter = "echoFilter" > <!-- 多个filter用逗号隔开 -->
</ jsf:consumer >
|
New! 1.2.0版本开始,增加一种全局过滤器配置方式
<!-- 全部provider和consumer走此逻辑 -->
< jsf:filter id = "globalfilter" class = "com.jd.testjsf.customfilter.EchoFilter" />
<!-- 指定provider和consumer走此逻辑 -->
< jsf:filter id = "globalfilter" class = "com.jd.testjsf.customfilter.EchoFilter" providers = "" consumers = "helloService2" />
<!-- 全局filter 从1.6.1版本开始支持ref属性指定filter bean 在ref和class属性都指定的情况下已ref指定的bean为准-->
< jsf:filter id = "globalfilter" ref = "echoFilter" />
|
New! 1.6.0版本增加了通过注解的方式加载自定义filter(适用于通用框架,不建议普通使用者使用)
jsf 客户端会加载META-INF/jsf/ 和 META-INF/services/ 下的配置文件
具体步骤:
自定义filter的实现如上文档所述,不同的地方在配置这块,不需要在xml中配置。在自定义filter上加上注解。
/**
* Extensible value:filter的标识 . order 定义filter 加载filter的先后顺序
*
*AutoActive providerSide 为true 则代表provider都会走这个filter,consumerSide 为false代表调用端不会走这个filter
*/
@Extensible(value = "testext",order = 2)
@AutoActive(providerSide = true,consumerSide = false)
public class ExtensionFilter extends AbstractFilter {
private static final Logger logger = LoggerFactory.getLogger(ExtensionFilter.class);
@Override
public ResponseMessage invoke(RequestMessage request) {
logger.info("extension filter invoked...");
return getNext().invoke(request);
}
}
在工程配置的/META-INF/jsf 或/META-INF/services 目录下配置文件名为com.jd.jsf.gd.filter.Filter内容示例如下
com.jd.jsf.gd.config.ext.ExtensionFilter # test auto active on provider side
配置这两步后就可以自动加载自定义filter了。
连接监听
Provider的Server监听一个端口,当Consumer连接和断开的时候,可以触发注入的连接监听器。
此操作会分发到服务端端口对应的业务线程池上执行,建议执行一些快速的操作,不要执行复杂的业务逻辑。 |
配置如下:
1、写一个实现类,继承com.jd.jsf.msg.ConnectListener接口
package com.jd.testjsf.listener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.jd.jsf.msg.ConnectListener;
import io.netty.channel.ChannelHandlerContext;
public class TestConnectListener implements ConnectListener {
private final static Logger LOGGER = LoggerFactory.getLogger(TestConnectListener. class );
public void connected(ChannelHandlerContext ctx) { // 建立连接时
LOGGER.info( "conneted.... from {}" , ctx.channel().remoteAddress());
}
public void disconnected(ChannelHandlerContext ctx) { // 断开连接时
LOGGER.info( "disconneted.... from {}" , ctx.channel().remoteAddress());
}
}
|
2、注入到Provider的配置中
< bean id = "listener1" class = "com.jd.testjsf.listener.TestConnectListener" />
< jsf:server id = "jsf" protocol = "jsf" onconnect = "listener1" /> <!-- 多个用逗号隔开 -->
|
服务端动态分组
管理端操作,Provider启动后可以在管理端进行重新分组。
1、服务信息 - 服务管理 - 接口点进去 - Provider列表
2、勾选Provider端,选择动态分组按钮,填入新分组。
追加模式是指:老alias和新的alias都会被调到
替换模式是指:老alias不再被调,新alias被调到
注意:1.6.0及以上版本,不支持对同一alias下全部实例进行动态分组,使用动态替换所有实例,16.0及以上版本客户端依然能调用到老别名
3、系统会自动复制选择的Provider(IP端口等除了别名都一样)到新的alias。
4、注册中心加载到变化后,会将alias下新的Provider列表推送给Consumer,包含这些动态分组Provider的原始alias和新的alias。
5、Consumer调用到动态分组时,会自动处理。
调用端切换分组
管理端操作,下发配置到调用端
1、服务信息 - 服务管理 - 接口点进去 - Consumer列表下面配置下发按钮。
2、选择一个Consumer,填写新的alias点下发。
3、等待注册中心加载数据后下方给Consumer。
4、Consumer得到新配置后,重新生成代理类。此时会调用新的分组。
服务质量监控
目前只收集Provider数据。
Provider默认开启监控服务端,按每分钟每方法像MonitorServer发送监控数据和异常数据,发送失败数据丢弃。
可以在管理端(服务信息 - 服务管理 - 属性配置按钮点进去)进行设置是否打开、设置收集机器的白名单。
也可以在配置中强行关闭此功能。不推荐
< jsf:provider id = "helloServiceExport" interface = "com.jd.testjsf.HelloService" ref = "helloService" server = "jsf" alias = "JSF_0.0.1" >
< jsf:parameter key = "monitor" value = "false" hide = "true" /> <!-- 强行关闭monitor 不推荐 -->
</ jsf:provider >
|
Telnet运维
服务端发布接口后,监听一个端口。默认运行直接telnet到该端口进行操作。
可以通过配置关闭此功能。 注意:关闭后,部分依赖telnet的功能(例如管理端查看服务列表,invoke等)将不能使用。
< jsf:server telnet = "false" />
|
命令输入telnet 10.12.11.11 22000 回车;
然后看到"jsf>" 后,支持如下命令执行:
命令 | 含义 | 起始版本 | 备注 |
---|---|---|---|
help |
help 查看支持的命令列表 |
1.0.0 | |
help ls 查看ls的使用说明 | 1.0.0 | ||
version | 查看当前jsf的版本 | 1.0.0 | |
ls |
ls 查看全部发布的接口信息 |
1.0.0 | |
ls -l 查看接口明细 | 1.0.0 | ||
ls com.jd.testjsf.HelloService 查看接口下方法 | 1.0.0 | ||
ls -l com.jd.testjsf.HelloService 查看接口下方法明细(含参数) | 1.0.0 | ||
ps |
ps 查看全部建立的长连接, |
1.0.0 | |
ps 22000 表示查看22000端口下长连接明细 | 1.0.0 | ||
ps -c 22000 表示查看22000端口下长连接数量 | 1.6.0 | ||
conf |
conf -p Show provider configs |
1.0.0 | |
conf -c Show consumer configs | 1.0.0 | ||
conf -r Show jsf context | 1.0.0 | ||
conf -s Show all interface settings | 1.0.0 | ||
conf -g Show global settings | 1.0.0 | ||
conf -a Show all above | 1.0.0 | ||
sudo | 后面传入超级管理员密码(存在注册中心中),可以开启高级功能。 | 1.0.0 | |
invoke | 直接发起调用,传入接口方法参数值进行调用 | 1.0.0 |
1.本机127.0.0.1或者没走注册中心可直接访问 2.其它需要超级权限,或者需要密码 |
invoke -p passwd com.jd.testjsf.HelloService.echoStr("111zg222") | 1.0.0 | ||
invoke -t 1qaz2wsx com.jd.testjsf.HelloService.echoStr("111zg222") | 1.0.0 | 如果方法带token | |
tp |
tp 查看回调线程池和系统上下文信息 |
1.0.0 | |
tp 22000 查看22000端口下的线程池信息 | 1.0.0 | ||
tp 22000 1000 10 查看22000端口下的线程池信息,每隔1秒,连续10次 | 1.6.1 | ||
debug | 开启debug模式。”debug on“打开,”debug off“关闭 | 1.5.0 | |
info | info com.jd.testjsf.HelloService 查看接口描述 | 1.2.0 | |
method | method com.jd.testjsf.HelloService.put 查看方法描述 | 1.2.0 | |
exit | 退出 | 1.0.0 | |
jvm | 查看jvm的内存和线程信息 | 1.0.0 | |
reset | 重置部分功能, 例如rest sechudle | 1.2.0 | |
msgpack | -ls 查询所有模板信息 | 1.6.2 | |
-da classname 查询指定class的模板信息 | 1.6.2 | ||
-da -p classname 查询指定class在父模板缓存里的模板信息 | 1.6.2 |
在JSF管理端-服务管理-接口点进去-Provider列表- 右边有个详情按钮,里面可以手动输入命令。
服务列表备份
Consumer在注册中心订阅了服务列表后,会将服务列表信息备份到本地文件,防止注册中心全部死掉,也能保证有一份最近的服务列表。
默认备份路径为 ${user.home}/jsf文件夹下。
可以通过配置进行修改。
< jsf:registry id = "jsfRegistry" protocol = "jsfRegistry" index = "i.jsf.jd.com" file = "/home/admin/jsfbak" />
|
代理类生成方式
支持JDK和javassist(默认)两种代理类生成方式。
主要作用就是在调用端拦截下业务代码的本地调用,转为调用远程服务端。
可通过配置进行设置。
< jsf:provider proxy = "jdk" />
< jsf:consumer proxy = "javassist" />
|
序列化方式
目前JSF默认使用msgpack进行序列化。msgpack较hessian速度更快,数据包更小,同样支持多语言,而且支持更多数据结构(例如Set)。 参见MsgPack和Hessian序列化的区别
自定义的bean最好实现 java.io.Serializable 接口,最好有空的构造函数。
不想序列化的字段加上 transient 修饰符即可。
注:修饰符为static、transient或者加了@Ignore注解的字段,都将不序列化。
Msgpack序列化保证字段的顺序,不保证字段的名字对应。 |
其它还支持 hessian、json 和 java (Provider端根据请求自动判断,Consumer端配置即可):
jsf协议兼容msgpack和hessian,无需配置 若发布为dubbo协议,则必须指定使用hessian进行序列化。 |
1.6.0开始 增加 protobuf支持(不支持无参数或者无返回值方法,不支持参数和返回值是null)。
线程名称和数量
线程池 | 名称 | 默认 | 可配置 | 说明 | |
---|---|---|---|---|---|
服务端 | Boss线程池(IO) | JSF-SEV-BOSS- | max(4,cpu/2) | 否 | 同一种协议的 Server 共用一个 Boss 线程池 |
Worker线程池(IO) | JSF-SEV-WORKER- | max(8,cpu+1) | jsf:server的iothreads,同一种协议以第一个配置为准 | 同一种协议的 Server 共用一个 Worker 线程, | |
业务线程池 | JSF-BZ- | 20-200 | 配置参考下面的线程池类型 |
一个端口一个业务线程池,如果一个端口下多个服务, 那么这些服务共用这一个业务线程池。 |
|
调用端 | Worker线程池(IO) | JSF-CLI-WORKER- | max(6, cpu+1) |
不走注册中心可配置,可在第一个jsf:consumer 中配置iothreads属性 |
全局的调用端共用一个 Woker 线程池 |
重连断开Provider节点线程 | JSF-CLI-RC- | 1 | 否 | 一个consumer一个 | |
给已连接Provider发心跳线程 | JSF-CLI-HB- | 1 | 否 | 一个consumer一个 | |
回调线程池 | JSF-CLI-CB- | 20-200 | JSF配置参考手册-<jsf:parameter> | 全局共享,有callback时才会初始化 | |
注册中心 | 注册中心心跳+重试线程 | JSF-jsfRegistry-HB&Retry | 1 | 否 | 走注册中心才加载 |
注册中心定时检查数据线程 | JSF-jsfRegistry-Check | 1 | 否 | 走注册中心才加载 | |
注册中心回调处理线程 |
JSF-jsfRegistry-CB |
1 | 否 | 走注册中心才加载 | |
其它 | 发送Monitor数据线程 | JSF-MNTR-SEND | 1 | 否 | 开启监控才初始化 |
切分Monitor数据线程 | JSF-MNTR-SLICE | 1 | 否 | 开启监控才初始化 | |
检查超时Future线程 | JSF-Future-Checker | 1 | 否 | ||
检查超时Callback Future线程 | JSF-Future-Checker-CB | 1 | 否 |
线程池类型
一个端口(即一个jsf:server)后有一个业务线程池。
目前业务线程池支持固定(fixed)和伸缩的(cached)两种线程池类型。通过配置threadpool线程池类型,通过threads配置最大线程数。
业务线程池同时支持队列,可通过queuetype配置线程池队列类型,通过queues参数配置队列大小。
参考配置:
threadpool |
初始线程数 |
threads
最大线程数 |
queues
队列大小 |
说明
|
优点
|
缺点
|
|
---|---|---|---|---|---|---|---|
伸缩有队列线程池 | cached | 20 | 100 | 256 |
任务来了先丢到队列中,队列满了才会增加线程,直到线程满, 得不到执行线程抛异常 |
节约线程资源,空闲一分钟自动回收,需要时重建; 队列带来一定的并发缓冲功能 |
队列带来一定的执行延迟 |
伸缩无队列线程池(默认) | cached | 20 | 200 | 0 | 任务来了直接分配线程,直到线程池满,得不到执行线程抛异常 | 节约线程资源,空闲一分钟自动回收,需要时重建; | 并发突然变大无缓冲 |
固定有队列线程池 | fixed | 100 | 100 | 256 | 线程数量固定,没有拿到线程丢到队列里,得不到执行线程抛异常 |
没有线程伸缩带来的性能问题; 队列带来一定的并发缓冲功能 |
队列带来一定的执行延迟 |
固定无队列线程池 | fixed | 200 | 200 | 0 | 线程数量固定,得不到执行线程抛异常 | 没有线程伸缩带来的性能问题 | 并发突然变大无缓冲 |
默认为伸缩线程池cached,1分钟回收,线程数最小20,最大200,队列为0的普通队列。
< jsf:server id = "jsf" protocol = "jsf" threads = "200" threadpool = "cached" queuetype = "normal" queues = "0" />
|
参考的固定线程池配置
< jsf:server id = "jsf" protocol = "jsf" threads = "400" threadpool = "fixed" />
|
当Provider的业务线程池满了,无可用线程池的时候,会返回一个异常给Consumer,告知Consumer该Provider线程池已耗尽。
- 此时需要使用jstack等排除线程情况,找到真正的性能瓶颈。
- 如果的确是压力太大撑不住,就进行Provider的水平扩容。
关于线程池的配置,其实没用一个统一或者可推荐的配置可以套用; |
事件分发类型
服务端有 boss线程,IO线程(work线程),业务线程;调用端有IO线程和业务线程
正常的调用包的header解析在IO线程,body的序列化和反序列化在业务线程执行。
心跳包直接在IO线程返回。
ResponseListener和Callback事件在callback线程池上执行。
Mock调用(1.0.x版本)
支持方法级的Mock调用。当启动Mock调用的时候:
对于Consumer来说,将不走远程调用,而是走本地模拟实现。可用于开发和测试环境下,不依赖服务端的调用。 或者线上环境的服务降级操作。对于Provider来说,将不走具体业务实现,而是直接走本地模拟实现,可用于服务降级,或者异常的屏蔽(例如数据库异常,先返回固定数据)。
使用方式如下:
1. 不管是Provider还是Consumer,使用Mock调用的时候,需要集成调用的接口实现一个模拟实现类。这个类和业务实现类的分开的。
public class HelloServiceMock implements HelloService {
public String get(Integer id) throws MyException {
return "get from mock" ;
}
public boolean put( int id, String str) {
return true ;
}
public String echoStr(String str) {
return "echo from mock" ;
}
}
|
2.配置的时候将此实现类注入到ProviderConfig或者ConsumerConfig的"mockRef"属性中。。
3.配置接口级或者方法级的"mock"属性。
Provider端配置
<!-- 业务实现 -->
< bean id = "helloServiceImpl" class = "com.jd.testjsf.HelloServiceImpl" />
<!-- 模拟实现 -->
< bean id = "helloServicemock" class = "com.jd.testjsf.mock.HelloServiceMock" />
< jsf:provider ref = "helloServiceImpl" mockref = "helloServicemock" mock = "true" > <!-- 默认mock="false",配置为true代表接口下方法mock为true -->
< jsf:method name = "echoStr" mock = "false" /> <!-- 除了此方法其它方法走mock -->
</ jsf:provider >
|
Consumer配置
<!-- 模拟实现 -->
< bean id = "helloServicemock" class = "com.jd.testjsf.mock.HelloServiceMock" />
< jsf:consumer mockref = "helloServicemock" mock = "false" > <!-- 默认mock="false" -->
< jsf:method name = "echoStr" mock = "true" /> <!-- 除了此方法其它方法不走mock -->
</ jsf:consumer >
|
Mock调用(1.2.x版本)
从1.2.0版本开始,mock调用改为在管理端动态配置,使用的时候无需在本地生成实现类。
在管理端的服务管理--接口列表--接口点进去-- 下面有个mock设置
然后选择方法,开启,设置下mock返回值(这个值是符合json格式的,字符串用双引号,list/数组用[],map/对象用{}等)
等到注册中心将这个配置发给客户端,客户端就会不发起远程调用,直接走配置的返回值。
服务降级(1.0.x版本)
依赖JSF调用端的Mock调用功能。
从管理端下发配置
1、首先得在Provider和Consumer中加入mock功能(mockref不能为空),同时设置mock为fasle先不启用。参见Mock调用
2、再JSF管理端的 服务信息-服务管理-接口点进去 的页面。
可以勾选服务列表里的Provider,点击配置下发按钮,弹出mock选择是或者否。Provider收到配置下发后,会自动开启mock调用功能,从而实现服务端降级。
同样勾选服务列表里的Consumer,点击配置下发按钮,弹出mock选择是或者否。Consumer收到配置下发后,会自动开启mock调用功能,从而实现服务端降级。
数据包大小限制
默认数据包大小为8M,即服务的请求和返回值序列化后默认不超过8M,要不然数据接收方会丢弃此数据。
数据较大,建议开启调用压缩,如果压缩后数据还大,可以进行自定义数据包配置。
如果请求值数据较大,则服务提供者进行配置。
< jsf:server payload = "16777216" /> <!-- 默认是8388608=8*1024*1024 -->
|
如果是返回值数据较大,则服务调用者进行配置。
注意:由于JSF是长连接复用的,请把特殊配置的consumer放到其它consumer之前加载,防止配置覆盖。
<jsf:consumer payload= "16777216" /> <!-- 默认是 8388608 = 8 * 1024 * 1024 -->
|
调用压缩
在Consumer发送请求和Provider返回响应的时候,都可以开启调用压缩。
目前支持多种算法:snappy,lzma,
1、如果Consumer端配置了压缩,且请求的数据大于2048B,那么请求数据将被压缩后再发给Provider。
< jsf:consumer compress = "snappy" />
|
服务端针对压缩过的请求,返回响应的时候也会判断是否启动压缩。(但是:虽然客户端配了压缩,如果请求小于2048B,还是需要服务端配置压缩才触发)
2、如果Provider端配置了压缩,那么不管请求时是不是带压缩标识,返回响应的时候也都会检查数据大小,如果数据大小超过阀值,则将响应数据压缩后再发给Consumer。
< jsf:provider compress = "snappy" />
|
最佳实践:请求数据大的客户端配置,响应数据大的服务端配置。
并发控制
Provider和Consumer都支持方法级别的并发控制(Mock调用不受控制)。
通过concurrents属性进行配置,方法级的配置可覆盖接口级的配置,接口级没配置则走默认。
参见: JSF配置参考手册 ,配置-1关闭并发过滤器,等于0表示开启过滤但是不限制
Provider端:
主要控制的是业务线程池的使用数量。
例如:一个Server业务线程池默认最大200,下面有2个接口,每个接口有5个方法,那么这10个方法的请求是共享这200个业务线程池的。
为了防止一个方法(例如出异常的时候)占用了全部线程池,则要求每个方法最多使用50个线程(不一定要均分,不是200/10=20这样子,也可以超过20)。
Provider端如果来请求时,超过并发大小,调用就会立即抛出RPC异常给客户端:
配置如下:
< jsf:server threads = "200" /> <!-- 默认这个server最大200个线程 -->
< jsf:provider concurrents = "50" > <!-- 接口下每个方法控制在50并发,即最大50个业务线程同时执行-->
< jsf:method name = "getHashSet" concurrents = "30" /> <!-- 这个方法比较特别,控制在30并发,最大30个业务线程同时执行-->
</ jsf:provider >
|
Consumer端:
主要控制调用者自己的业务线程同时发起的请求数。
Consumer如果来请求时,超过并发大小,则会等待执行直到超时为止:
< jsf:consumer concurrents = "50" > <!-- 接口下每个方法控制在50并发,即最大50个业务线程在调用-->
< jsf:method name = "getHashSet" concurrents = "30" /> <!-- 这个方法比较特别,控制在30并发,最大30个业务线程在调用-->
</ jsf:consumer >
|
灰度部署
服务发布后,默认立即上线, 调用端可以马上发起调用。
如果需要做到灰度上线,即上线后不立即提供服务,等验证后再在管理端手动上线,可以通过如下配置实现。
< jsf:provider dynamic = "false" /> <!-- 指定接口灰度部署 -->
|
结果缓存
在Consumer的cache在远程调用之前,或者Provider的cache逻辑在调用业务代码之前支持配置cache,用于调用结果的缓存。如果从缓存中拿到调用结果,则不往后执行。
此配置支持方法级的cache。
请自行设置缓存过期策略,保证数据的准确性和实时性。 |
Cache使用参考:
1、实现一个Cache实现类,实现接口com.jd.jsf.gd.filter.cache.Cache。主要实现buildKey,put,get三个方法。
public class LruCacheImpl implements Cache {
/**
* slf4j Logger for this class
*/
private final static Logger LOGGER = LoggerFactory.getLogger(LruCacheImpl. class );
private final Map<Object, Object> cache;
public LruCacheImpl() {
//最近访问的最前面 固定大小1024,保证内存使用量
this .cache = Collections.synchronizedMap( new LRUHashMap( 1024 ));
}
/**
* 可以加其它方法自行实现 销毁缓存 等操作
* @param key
*/
public void invalidateCache(Object key) {
cache.remove(key);
}
/**
* 通过调用参数获得唯一的key,返回null以后将不从cache中load
*
* @param interfaceId
* 接口名
* @param methodName
* 方法名
* @param args
* 方法参数
* @return 关键字,可以返回null
*/
@Override
public Object buildKey(String interfaceId, String methodName, Object[] args) {
String key;
if ( "put" .equals(methodName)) {
key = String.valueOf(args[ 0 ]); // put方法取参数1作为值
} else if ( "echoStr" .equals(methodName)) {
key = RpcContext.getContext().getRemoteHostName(); // echoStr方法用ip做缓存
} else {
key = null ; // 返回null以后将不从cache中load
}
return key;
}
/**
* 放入缓存
*
* @param key
* 方法参数得到的关键字
* @param result
* 缓存的调用结果
*/
@Override
public void put(Object key, Object result) {
cache.put(key, result);
LOGGER.info( "put into cache {} : {}" , key, result);
}
/**
* 拿出缓存
*
* @param key
* 方法参数得到的关键字
* @return 缓存的调用结果
*/
@Override
public Object get(Object key) {
Object value = cache.get(key);
LOGGER.info( "load from cache {} : {}" , key, value);
return value;
}
}
|
2、注入到Provider或者Consumer配置里
< bean id = "cacheImpl" class = "com.jd.testjsf.cache.LruCacheImpl" />
< jsf:provider >
< jsf:method name = "put" cacheref = "cacheImpl" cache = "true" /> <!-- 只有put方法走缓存 -->
</ jsf:provider >
< jsf:consumer cacheref = "cacheImpl" cache = "true" > <!-- 全部方法走缓存 -->
</ jsf:consumer >
|
3、在调用的时候,会先根据请求参数调用cache.buildKey方法;
如果返回的key不为null,通过这个key去寻址调用结果;
如果找到结果则返回,找不到则往后执行。
黑白名单
如果通过注册中心调用,就可以使用调用的黑白名单功能,粒度为接口+IP级。
在管理端 服务信息-服务管理-接口点进去。可以看到黑白名单的设置界面。
设置黑白名单后,注册中心会加载到最新的黑白名单设置。
注册中心将服务列表+黑白名单计算后,推送给调用者。如果Consumer在黑名单中,将调用不到任何Provider。
同时黑白名单也会通过注册中心下发到服务端,服务端在接到请求的时候也会进行校验。
路由配置
JSF的路由功能目前有IP路由和参数路由。
IP路由:主要在管理端和注册中心完成,不需要将规则下发到调用端。
在管理端配置路由功能后,注册中心读取到路由,计算出新的服务端列表,将新服务端列表下发到调用端。
Consumer从注册中心拿到的就是经过运算后的注册中心列表。
{ "router.item.key" : "172.17.39.117" , "router.item.value" : "172.17.39.116" }
|
参数路由:在管理端进行配置,注册中心加载配置,将规则下发给调用端。
Consumer在选择Provider进行调用的时候,会实时根据路由规则进行计算。
目前可以根据 方法名 和 参数值(toString())进行路由,支持的运算有>,>=,<,<=,==,!=。
{ "router.item.key" : "handleSimple.arg1==1" , "router.item.value" : "172.17.39.115" }
|
具体参见 exmaple:http://source.jd.com/app/jsf-example/tree/master/src/main/java/com/jd/testjsf/router
序列化黑名单配置
JSF客户端在1.6.5开始支持序列化黑名单配置,并且已经内置了部分黑名单。
支持黑名单配置的序列化方式有:msgpack、json、hessian、java(在1.6.5开始默认不开启java序列化)
配置方式如下:
1、jvm启动参数配置方式:
-Djsf.serializable.blacklist="xxx.xxx.xx1,xxx.xxx.xx2" //所有序列化方式生效黑名单配置,若在次配置中加入黑名单限制的java类,将在msgpack、json、hessian、java中都生效
-Djsf.msgpack.serializable.blacklist="xxx.xxx.xx1,xxx.xxx.xx2" //msgpack序列化的黑名单配置
-Djsf.json.serializable.blacklist="xxx.xxx.xx1,xxx.xxx.xx2" //json序列化的黑名单配置
-Djsf.hessian.serializable.blacklist="xxx.xxx.xx1,xxx.xxx.xx2" //hessian序列化的黑名单配置
-Djsf.java.serializable.blacklist="xxx.xxx.xx1,xxx.xxx.xx2" //java序列化的黑名单配置
|
2、全局参数配置方式:
JSFContext.putGlobalVal(JSFContext.KEY_SERIALIZABLE_BLACKLIST, "xxx.xxx.xx1,xxx.xxx.xx2" ); //所有序列化方式生效黑名单配置,若在次配置中加入黑名单限制的java类,将在msgpack、json、hessian、java中都生效
JSFContext.putGlobalVal(JSFContext.KEY_MSGPACK_SERIALIZABLE_BLACKLIST, "xxx.xxx.xx1,xxx.xxx.xx2" ); //msgpack序列化的黑名单配置
JSFContext.putGlobalVal(JSFContext.KEY_JSON_SERIALIZABLE_BLACKLIST, "xxx.xxx.xx1,xxx.xxx.xx2" ); //json序列化的黑名单配置
JSFContext.putGlobalVal(JSFContext.KEY_HESSIAN_SERIALIZABLE_BLACKLIST, "xxx.xxx.xx1,xxx.xxx.xx2" ); //hessian序列化的黑名单配置
JSFContext.putGlobalVal(JSFContext.KEY_JAVA_SERIALIZABLE_BLACKLIST, "xxx.xxx.xx1,xxx.xxx.xx2" ); //java序列化的黑名单配置
|
3、spring+xml配置方式:
< jsf:parameter key = "jsf.serializable.blacklist" value = "xxx.xxx.xx1,xxx.xxx.xx2" /> <!--所有序列化方式生效黑名单配置,若在次配置中加入黑名单限制的java类,将在msgpack、json、hessian、java中都生效-->
< jsf:parameter key = "jsf.msgpack.serializable.blacklist" value = "xxx.xxx.xx1,xxx.xxx.xx2" /> <!--msgpack序列化的黑名单配置-->
< jsf:parameter key = "jsf.json.serializable.blacklist" value = "xxx.xxx.xx1,xxx.xxx.xx2" /> <!--json序列化的黑名单配置-->
< jsf:parameter key = "jsf.hessian.serializable.blacklist" value = "xxx.xxx.xx1,xxx.xxx.xx2" /> <!--hessian序列化的黑名单配置-->
< jsf:parameter key = "jsf.java.serializable.blacklist" value = "xxx.xxx.xx1,xxx.xxx.xx2" /> <!--java序列化的黑名单配置-->
|
功能成熟度
功能 | 子功能 | 成熟度 | 优点 | 缺点 | 建议 | 特定用户 |
---|---|---|---|---|---|---|
配置方式 | spring+xml | 大量应用 | 无代码入侵,可扩展性强。 | 可用于生产环境 | ||
api | 大量应用 | 适用于动态创建发布和调用的系统,例如网关系统。 | 代码入侵 | 可用于生产环境 | ||
spring+annotation | 已测试 | 开发较方便 | 代码入侵,可扩展性差 |
可用于开发、测试环境, |
||
集群策略 | failover | 大量应用 | 多用于读服务,失败重试,增加用户体验;配置灵活 | 失败重试会增加调用耗时 | 可用于生产环境 | |
failfast | 大量应用 | 多用于非幂等的写服务,失败立即报错,只一次调用 | 调用失败率增加 | 可用于生产环境 | ||
|
|
|
|
|
|
|
pinpoint | 已测试 | 指定调用 | 只支持直连,无法做到负载 | 特定场景使用 | JSF管理端 | |
负载均衡 | random | 大量应用 | 根据权重随机。请求量越大分布越均匀,可以灵活调整权重决定结果 | 可用于生产环境 | ||
roundrobin | 大量应用 | 均匀请求 | 无法调整权重,如果某些机器慢,可能会积压请求 | 可用于生产环境 | ||
leastactive | 少量应用 | 快速的Provider收到更多请求 | 抛异常时也认为是快速,需要慢慢恢复 | 可用于生产环境 | ||
服务依赖检查 | 大量应用 | 可用于生产环境 | ||||
直连调用 | 大量应用 | 开发测试时定向调用,防止乱调。 | 无法动态的更新服务列表 |
可用于开发、测试环境, 不推荐线上环境 |
||
异步调用 | 少量应用 | 无需多线程即可并行发起调用,减少调用耗时 | 可用于生产环境 | |||
异步回调 | 已测试 | 可用于生产环境 | ||||
泛化调用 | 少量应用 | 网关场景方便调用,无需服务端接口 | 需要自己维护接口定义等信息 | 可用于生产环境 | JOS | |
功能 | 子功能 | 成熟度 | 优点 | 缺点 | 建议 | |
injvm调用 | 已测试 | 无需发起rpc调用走本地。 | 服务端和调用端一起部署,这种设计就有问题 |
可用于开发、测试环境, 不推荐线上环境 |
||
callback调用 | 已测试 | 服务端主动回调到调用端 | 长连接端口后要重新注册callback | 可用于生产环境 | JSF注册中心 | |
多协议支持 | jsf | 大量应用 | 基于TCP的自定义协议、速度快、兼容dubbo协议 | 无法跨语言 | 可用于生产环境 | |
dubbo | 大量应用 | 基于TCP的自定义协议、速度快 | 无法跨语言 | 可用于生产环境 | ||
rest | 少量应用 | 跨语言,基于HTTP | 框架耗时比tcp慢2/3,未算业务时间 | 可用于生产环境 | ||
webservice | 少量应用 | 跨语言,基于HTTP | 可用于生产环境 | |||
延迟启动 | 少量应用 | 给予数据准备时间 | 可用于生产环境 | |||
延迟连接 | 少量应用 | 减少非必要的长连接 | 可用于生产环境 | |||
粘滞连接 | 已测试 | 减少负载均衡算法时间 | 可用于生产环境 | |||
参数校验 | 已测试 | 调用端增加校验减少错误数据给服务端带来的压力 | 增加耗时 | 可用于生产环境 | ||
请求上下文 | 少量应用 | 方便的获取上下文信息 | 可用于生产环境 | |||
隐式传参 | 少量应用 | 方便的传入隐藏参数 | 可用于生产环境 | |||
Token调用 | 少量应用 | 简易有效的token认证 | 改动oken双方都得同时改 | 可用于生产环境 | ||
指定发布IP | 少量应用 | 解决了多网卡情况 | 可用于生产环境 | |||
指定注册IP | 少量应用 | 解决了虚拟机无网卡情况 | 可用于生产环境 | |||
功能 | 子功能 | 成熟度 | 优点 | 缺点 | 建议 | |
指定端口 | 大量应用 | 解决了端口冲突情况 | 可用于生产环境 | |||
方法限制 | 少量应用 | 方便的限制方法是否可以被调用 | 这种设计也有问题,不能调的方法应该从接口中删除 |
可用于开发、测试环境, 不推荐线上环境 |
||
自定义filter | 少量应用 | 自定义扩展一些拦截功能 | 可用于生产环境 | |||
连接监听 | 已测试 | 监听连接数的变化 | IO线程执行 | 可用于生产环境 | JSF注册中心 | |
服务动态分组 | 已测试 | 可用于生产环境 | ||||
调用分组映射 | 已测试 | 可用于生产环境 | ||||
服务质量监控 | 少量应用 | 可以定时收集数据到hbase,生成统计报表或者预警。 | 统计需要耗时,约占框架耗时的1%~2% | 可用于生产环境 | ||
Telnet运维 | 少量应用 | 方便的运维调试功能 | 有安全风险 | 可用于生产环境 | JSF管理端 | |
服务列表备份 | 大量应用 | 容灾的一部分 | 可用于生产环境 | |||
代理类 | javasssit | 大量应用 | 比原生更加强大、更加快速的代理类 | 可用于生产环境 | ||
jdk | 已测试 | 原生的代理类 | 可用于生产环境 | |||
序列化 | msgpack | 大量应用 | 跨语言,比hessian更快速 | 增加字段需要配置。 | 可用于生产环境 | |
hessian | 大量应用 | 目跨语言,兼容性强,支持增减字段 | 做了dubbo兼容;不支持set;速度较慢。 | 可用于生产环境 | ||
java | 已测试 | 做了dubbo兼容;速度慢;不跨语言。 |
可用于开发、测试环境, 不推荐线上环境 |
|||
线程池类型 | fixed | 大量应用 | 固定线程池,初始化后不会照成性能问题 | 可用于生产环境 | ||
cached | 大量应用 | 有最小和最大值,空闲一分钟自动删除线程,需要再重建 | 可用于生产环境 | |||
功能 | 子功能 | 成熟度 | 优点 | 缺点 | 建议 | |
事件分发类型 | 稳定 | 可用于生产环境 | ||||
mock调用 |
已测试 | 用于开发测试使用,或者线上服务降级 | 可用于生产环境 | |||
|
|
|
|
|
||
调用压缩 | 已测试 | 压缩后数据较小 | 可用于生产环境 | |||
并发控制 | 已测试 | 按方法级的并发控制,防止瞬间的大流量 | 可用于生产环境 | |||
灰度部署 | 少量应用 | 验证后再手动上线 | 可用于生产环境 | |||
结果缓存 | 已测试 | 减少发起的远程调用次数 | 需要自行实现cache失效和清理策略 | 可用于生产环境 | ||
黑白名单 | 已测试 | 拦截恶意调用 | 可用于生产环境 | |||
路由配置 |
JSF客户端使用手册相关推荐
- Bugfree 客户端使用手册
BugFree基于PHP和MySQL开发,是免费且开发源代码的缺陷管理系统.服务器端在Linux和Windows平台上都可以运行:客户端无需安装任何软件,通过IE,FireFox等浏览器就可以自由使用 ...
- CVS 客户端使用手册
IRLab 2003年2月10日 什么是CVS CVS(Version Control System.)即版本控制系统.用来记录源文件的历史信息.甚至二进制文件,媒体文件等. 例如,当软件修改时有时会 ...
- linux下git客户端使用手册,linux下git客户端命令总结.docx
linux下git客户端命令总结 如果不特殊说明,'$'表示在linux终端命令行下执行一. 安装gitlinux Fedora系列安装命令:$ sudo yum install gitlinux D ...
- TortoiseSVN (Subversion客户端) 使用手册(中文) (五)
完整(已排版)下载:https://download.csdn.net/download/zjyzfn/14973947 4.20 追溯 有时你不仅要知道哪一行做了修改,还要精确地知道谁修改了一个文件 ...
- TortoiseSVN (Subversion客户端) 使用手册(中文) (六)
完整(已排版)下载:https://download.csdn.net/download/zjyzfn/14973947 第5章 版本库瘦身 版本库瘦身的目的是为了节省存储空间.操作方法是通过命令行备 ...
- TortoiseSVN (Subversion客户端) 使用手册(中文) (四)
完整(已排版)下载:https://download.csdn.net/download/zjyzfn/14973947 4.4 提交修改 提交修改:将工作副本的修改更新到版本库的操作,提交修改的相关 ...
- Qt 多线程TCP服务端一键关闭所有客户端
Qt 多线程TCP服务端一键关闭所有客户端 任务描述: 实现多线程TCP服务端一键关闭所有客户端的连接. 解决过程: 1.Qt的服务端提供了close的功能,但是只用来不响应新接入的客户端. 手册中是 ...
- 腾讯通服务器保存传输文件,RTX腾讯通客户端使用小结
腾讯通客户端使用手册 默认是指在WINXP系统下.WIN7系统稍有不同(ACCOUNT文件夹位置不一样). 一.安装与设置 1.安装过程. i.直接双击运行RTX源程序. ii.安装过程中,在如下步骤 ...
- bugfree使用手册
Bugfree 客户端使用手册 UNITEQ Co, ltd 版本历史: 版本 时间 修改备注 作者 部门 V1.0 2011-1-4 Clark 研发 目录 一. ...
最新文章
- kangle web server源代码安装简明教程
- 大多数比特币 混币交易属合法交易
- mysql 统计做饼状图_PHP+mysql+Highcharts实现饼状统计图
- AI:2020年6月23日北京智源大会顶级大佬邝子平、李开复 、陆奇、张亚勤、曹勖文进行云上圆桌论坛《探讨AI与创业》
- 【数据挖掘实例】构建Xgboost模型,在电力用户的95598工单数据中的电费敏感用户预测(高敏用户模型)
- git submodule使用
- ATS读小文件(内存命中)
- springBoot中自定义的yml文件引用的方式
- struts:file 提交给action后获取文件信息
- C++ 输出单个字符
- 黑客勒索遭遇霸气回应:不怕,我们有备份
- csv逗号分隔符转换_机器学习Python实践——数据导入(CSV)
- 苹果从来不飙配置,也从不关注配置,即使一般的配置也能卖好价钱,为啥没人喷?
- oracle批量新增字段工具,mybatis 中oracle 批量新增三种方法
- [个人笔记] origin学习 入门教程
- 测试前的准备:搭建测试环境
- YOLO Air:科研改进论文推荐 | 改进组合上千种搭配,包括Backbone,Neck,Head,注意力机制,适用于YOLOv5、YOLOv7、YOLOX等算法
- 你还有“不撞南墙不回头”之心吗?
- Teradata天睿公司任命王波为大中华区总裁
- 放牛奶的冰箱(二分答案)
热门文章
- 淘宝美工前景怎么样?淘宝美工有什么重要性?
- input 禁止输入法
- 使用Java POI读取excel文档
- CapstoneCS5216|CapstoneCS5218|DP转HDMI转换方案设计与应用
- 使用sed命令替换文本,文本中含有“/”的处理方法
- 人效九宫格|三个提升路径,三种管理模式,让人效实时可量化
- 考试教训总结[学员-路过]
- 第52期《SCI论文插图排版与绘制》课程欢迎你!
- 啥都不会 搬运别人的东西发一下 数据结构与程序设计——C++语言描述<翻译:笑死的猪头>
- 线性代数笔记2——向量1(向量简介)