【JMicro】微服务开发及使用
JMicro是一个用Java语言实现的开源微服务全家桶,
源码地址:https://github.com/mynewworldyyl/jmicro,
Demo地址:http://124.70.152.7 。
摘要
假设你已经按照前面分享的文章下载JMicro源码并编译成功。现在开始开发一个JMicro微服务,并通过Java客户端及JS调用此微服务,Java支持同步和异步调用,JS目前只支持异步调用。
服务提供者和消费者模式
上图是最基本的服务提供者和消费者关系图,服务方实现服务接口,消费方通过接口使用服务,面向接口编程,服务方与消费方没有直接依赖。另一方面,JMicro为了给消费方提供异步调用支持,在消费方与服务接口之间自动加入一个异步接口,以提供异步调用支持,如下图:
在ISimpleRpc$JMAsyncClient接口中,每个方法都与ISimpleRpc中的方法一一对应,ISimpleRpc$JMAsyncClient完全由JMicro代码生成工具自动生成,不需要服务实现方及消息方做任何额外工作,非常便。异步接口后缀$JMAsyncClient及异步方法名后缀JMAsync是框架固定后缀,所以写代码时尽量避免使用,以免混淆。
开发服务提供者
1)建立接口项目
因为ISimpleRpc接口同时被服务实现方及服务消费方依赖,所以需要单独建立一个项目存放此接口
新建接口对应的Maven项目,并在POM加主如下依赖
<dependency>
<groupId>cn.jmicro</groupId>
<artifactId>api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
新建接口类如下所示
package cn.jmicro.example.api.rpc; import cn.jmicro.api.test.Person;
import cn.jmicro.codegenerator.AsyncClientProxy; @AsyncClientProxy
public interface ISimpleRpc { String hello(String name); String hi(Person p); String linkRpc(String msg); }
@AsyncClientProxy注解指示JMicro为此接口生成异步接口,即ISimpleRpc$JMAsyncClient,在此项目根目录执行mvn clean install,成功后将可在src/main/gen目录下找到cn/jmicro/example/api/rpc/genclient/ISimpleRpc$JMAsyncClient.java接口,及此接口实现类cn/jmicro/example/api/rpc/genclient/SimpleRpc$JMAsyncClientImpl.java,此实现类只用于服务提供方,消费方用不到。
2)新建服务提供者项目
新建一个Maven项目,并在POM文件加入如下依赖
<dependency>
<groupId>cn.jmicro</groupId>
<artifactId>all</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency> <dependency>
<groupId>cn.jmicro</groupId>
<artifactId>example.api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
基中example.api即为上面建立的接口项目
新建服务接口实现类,如下:
package cn.jmicro.example.rpc.impl; import java.util.Random; import cn.jmicro.api.JMicroContext;
import cn.jmicro.api.annotation.Component;
import cn.jmicro.api.annotation.Reference;
import cn.jmicro.api.annotation.SBreakingRule;
import cn.jmicro.api.annotation.SMethod;
import cn.jmicro.api.annotation.Service;
import cn.jmicro.api.async.IPromise;
import cn.jmicro.api.monitor.MC;
import cn.jmicro.api.monitor.SF;
import cn.jmicro.api.service.IServiceAsyncResponse;
import cn.jmicro.api.test.Person;
import cn.jmicro.common.Constants;
import cn.jmicro.example.api.rpc.ISimpleRpc;
import cn.jmicro.example.api.rpc.genclient.IRpcA$JMAsyncClient; @Service(namespace="simpleRpc", version="0.0.1", monitorEnable=1, maxSpeed=-1,debugMode=1,
baseTimeUnit=Constants.TIME_SECONDS, clientId=1000,external=true)
@Component
public class SimpleRpcImpl implements ISimpleRpc { @Reference(namespace="rpca", version="0.0.1")
private IRpcA$JMAsyncClient rpca; private Random r = new Random(100); @Override
@SMethod(
//breakingRule="1S 50% 500MS",
//1秒钟内异常超50%,熔断服务,熔断后每80毫秒做一次测试
breakingRule = @SBreakingRule(enable=true,percent=50,checkInterval=5000),
logLevel=MC.LOG_DEBUG,
testingArgs="[\"test args\"]",//测试参数
monitorEnable=1,
timeWindow=5*60000,//统计时间窗口5分钟
slotInterval=100,
checkInterval=5000,//采样周期2S
timeout=5000,
retryInterval=1000,
debugMode=1,
maxSpeed=1000,
baseTimeUnit=Constants.TIME_MILLISECONDS
)
public String hello(String name) {
if(SF.isLoggable(MC.LOG_DEBUG)) {
SF.eventLog(MC.MT_PLATFORM_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, name);
}
/*int rv = r.nextInt();
if(rv < 50) {
throw new CommonException("test breaker exception");
}*/
System.out.println("Server hello: " +name);
return "Server say hello to: "+name;
} @Override
@SMethod(
//breakingRule="1S 50% 500MS",
//1秒钟内异常超50%,熔断服务,熔断后每80毫秒做一次测试
breakingRule = @SBreakingRule(enable=true,percent=50,checkInterval=5000),
logLevel=MC.LOG_DEBUG,
testingArgs="[{\"username\":\"Zhangsan\",\"id\":\"1\"}]",//测试参数
monitorEnable=1,
timeWindow=5*60000,//统计时间窗口5分钟
slotInterval=100,
checkInterval=5000,//采样周期2S
timeout=5000,
retryInterval=1000,
debugMode=0,
maxSpeed=1000,
baseTimeUnit=Constants.TIME_MILLISECONDS
)
public String hi(Person person) {
if(SF.isLoggable(MC.LOG_DEBUG)) {
SF.eventLog(MC.MT_PLATFORM_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, person.getUsername());
}
return "Server say hello to: " + person.toString();
} @Override
public String linkRpc(String msg) {
if(SF.isLoggable(MC.LOG_DEBUG)) {
SF.eventLog(MC.MT_APP_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, "linkRpc call IRpcA with: " + msg);
} System.out.println("linkRpc: " + msg);
//return this.rpca.invokeRpcA(msg+" linkRpc => invokeRpcA"); //IPromise<String> p = PromiseUtils.callService(this.rpca, "invokeRpcA","linkRpc call IRpcA with: " + msg); IPromise<String> p = this.rpca.invokeRpcAJMAsync("invokeRpcA");
JMicroContext cxt = JMicroContext.get();
if(cxt.isAsync()) {
IServiceAsyncResponse cb = cxt.getParam(Constants.CONTEXT_SERVICE_RESPONSE,null);
p.then((rst,fail) -> {
cb.result(rst);
});
return null;
} else {
return p.getResult();
} }
}
此服务实现类包含微服务相关的很多细节,如限流,超时,重试,监听,日志,熔断,异步。 另外服务路由和负载均衡保持默认没有额外配置。下面对每行代码做概要解释
从第5到18行是jmicro框架相关类导入,你可以看到,JMicro实现微服务核心应用没有任何第三方依赖。
第20行Service注解提示此类是一个服务实现类,其属性说明如下:
namespace和version分别是服务命名空间和版本号,服务名称不需要指定,固定是服务接口名全称,3个值有值构成服务的唯一标识,使用服务时需要通过此3个值。
monitorable:此服务是否可监控,启用监控后,可以在后台管理页面看到服务调用的实时细节,比如QPS,失败数,成功数,超时数,及各指标占比等等;
maxSpeed:服务最大qps,用于服务限流;
debugMode:是否开启调试模式,在调试模式下,RPC会输出更多日志,铺助问题排查。
baseTimeUnit:此服务与时间相关的属性的基本时间单位,可以是微秒,毫秒,秒,分,小时等;
external:是否可通过Api网关供外部调用,如果false则只能内部调用,外部不能访问;
clientId:原本是用于服务隔离,但现在使用过程中觉得不尽如人间,所以后面大概率做调整或去除。
第21行Component注解告诉JMicro容器,这个类是一个组件,在JMicro容器启动时自动做实例化,以供其他组件使用,功能和Spring的Component注解类似。
23行为类声明,实现ISimpleRpc服务接口
25行和26行,Reference声明26行这个成员变量是一个服务引用,引用服务名称空间和版本分别为rpca及0.0.1,IRpcA$JMAsyncClient是另外一个服务接口的异步接口引用,JMicro内部使用另外一个服务时,只要这两行代码就能完成,相当方便。
第31到45行为服务方法注解,其很多属性和Service注解相同,JMicro优先使用SMethod注解的属性,如果SMethod属性为-1,则使用Service注解的同名属性,如果SMthod对应属性值为0表示禁用,1表示启用。
breakingRule : 服务方法的熔断规则,enable=true表示启用熔断规则;percent=50表示服务失败百分比超过50%时,开启服务熔断器,从而禁用服务;checkInterval=5000表示当服务熔断后,间隔多长时间做一次自动调用服务,其单位由baseTimeUnit确定。业界大部份微服务实现中,都定义一个半熔断状态,在半熔断状态中,开启部份RPC请求,以测试服务是否可以使用,这样会导致部份请求失败。在JMicro中,服务迷熔断后,JMicro自动开启服务调用,从而使熔断器断继收到服务RPC成功率数据,当失败率小于percent时,熔断器自动关闭,服务进入正常使用状态,这样完全不影响正常服务使用。
logLevel:此方法日志级别,debug,info,warn,error,nolog;
testArgs:熔断器做自动测试时使用的参数,其值可以是JSON格式,也可以是JMIcro编码密文,一般当需要通过Api网关调用时,需要人工查看时使用JSON,如果只是内部服务调用,则使用JMicro编码比较高率。
timeWindow:JMicro监控为每个服务方法启动一个环形“旋转时钟”,这个环被分为N个Slot,每个Slot所占时间长度为slotInterval,即时钟的一个“滴答”,则timeWindow=slotInterval*N,在每个“滴答”时间片内产生的统计数据归类到这个时间Slot里,当数据所属时间片超出timeWindow时,Slot所代表的数据超时丢弃。这样在timeWindow内的统计数据就能够”平滑“过度而不出现”跳跃“现像。源代码实现核心类路径为:https://github.com/mynewworldyyl/jmicro/blob/master/api/src/main/java/cn/jmicro/api/monitor/ServiceCounter.java
checkInterval: 当需要周其性地采集数据时,此值表示采集周期,如熔断器多久采集一次成功或失败率,限速器多久统计一次服务QPS等
timeout: 服务超时时间,消费者等待服务提供者返回结果等待时间,一般针对同步RPC调用,对异步RPC调用无效,注意,JMicro中,同步和异步是指客户端调用RPC服务方法的方式,不针对服务方法,因为服务方法可以同时支持同步调用和异步调用,没有同步方法和异步方法这一说。
retryInterval:发生超时时,间隔多久做一次重试;
retryCnt:发生超时间,需要连续重试多少次,但不包括第一次,如retryCnt=2,则最多重试两次,加上第一次,最多调用3次;
其他和Service相同属性不再重复
第48,49行向监控服务器发一条日志信息,eventLog方法第1个参数表示事件类型,JMicro监控服务根据事件类型做统计和分发,
第76行即上一章测试使用的RPC方法,Person是一个自定义的实体类,其定义(省略getter setter等代码)如下所示
@SO
public final class Person {
private String username ="Yeu";
private int id = 222;
}
其被@SO注解,JMicro序列化工具会对全部类路径下的@SO注解的类做序列化增强,以提高序列化类实例性能,这就是为什么每个服务启动时需要加-javaagent:/home/ubuntu0/jmicro/resp/jmicro-agent-0.0.1-SNAPSHOT.jar参数的原因。
具体方式是通过javassist注入encode和decode两个方法,但是运行过程中发现javassist占用大量内存,如下图,按理说只是在JVM启动时类加载过程中使用javassist,生成目标class后javassist应该释放这些内存,但是现在即使JVM因内存不足而退出,这部份内存都没能释放!有知道原因的大神吗?
JMIcro RPC方法支持的参数类型定义如下:
a.Java支持的8种基本数据类型极其封装类型;
b. String类型,Date类型,ByteBuffer类型
c. 由a,b构成的数组类型,及由a,b为元素构成的List,Set,Map类型
d. 由a,b,c作为成员变量构成的自定义类类型,必须要SO注解;
e. 由a,b,c,d作为元素构成的数组,List,Set,Map类型;
第94到104行在一个服务方法里面调用另一个RPC服务方法,并且根据上下配异步配置情况使用同步和异步调用。
到此简单的服务提供者开发完成,对使用到的一些重点细节做简单解释。
部署服务提供者
编译
cd ${base_dir}/example/example.provider
mvn clean install -Dmaven.test.skip=true
在${base_dir}\example\example.provider\target找到jmicro-example.provider-0.0.1-SNAPSHOT-jar-with-dependencies.jar文件待用
上传Jar包
以http://124.70.152.7 环境为例,在浏览器中打开此页面,右上角点LOGIN,输入用户名:jmicro,密码:jmicro123 登陆系统,只有登陆后,才可以上传Jar包及部署操作。
登陆后除了自己建立的数据外,请不要做删除和停止类的操作,特别是配置信息,以免影响系统正常运行。
选 择菜单 deployment -> resposotory --> ADD,如下图
弹出如下对话框,选择要上传的Jar文件,一定要选对如图路径下的可执行Jar文件
输入名称,默认名称和所选文件同名。如果仓库中已经存在同名文件,上传会失败,所以一定要输入一个不重名的名称,下一步部署配置时需要用到此名称。
按确定开始上传,如下图
上传成功后,在资源列表中查看如下
配置部署描述
选择deployment --> deploy desc --> ADD ,如下图所示
弹出对话框,输入如下图所示配置信息,JAR文件即为刚才上传成功的jar包文件名,勾选Enable表示启用此部署,最后加-Xmx32m -Xms8m限制一下内存,否则会因内存申请失败而部署失败。其他的选项先不用填或保持默认即可,如下图
按确定后,等待10秒左右,如下图打开进程列表页面,可以看到启动了新的JVM进程
服务调用
我们现在开始从Web前端调用这个服务方法,选择Monitor --》 Service菜单,如下图
弹出侧栏服务列表,如下图,右上角选择Refresh刷新一下列表,刚刚部署的服务才显示出来
点选hi结点后,在编辑区打开此方法的配置,如下图。这些服务配置和上面源代码中Service及SMethod注解的属性基本上一一对应,并且可以动态修改,实时生效。
比如启用和禁用监控,debugMode等。
在打开的服务方法配置页面往下拖到最后,看到Testing选项卡,如下图,在Testing Args框输入 [{"username":"Zhangsan","id":"1"}],点击选项卡右上角Start按钮,方法返回值输出在Testing Result框中,如图:
可以按相同的操作步聚调用别的远程方法,注意方法参数要输正确,正确的参数请查看上面接口定义,也可以通过Github查看其他的接口源码。
通过Java API调用远程服务
以Maven为例,首先在POM文件中引入如下依赖,example.api为服务接口所在项目
<dependency>
<groupId>cn.jmicro</groupId>
<artifactId>gateway.client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>cn.jmicro</groupId>
<artifactId>example.api</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
写Java main函数直接调用RPC源始同步方法
public static void main(String[] args) {
ApiGatewayClient socketClient = new ApiGatewayClient(new ApiGatewayConfig(Constants.TYPE_HTTP,"124.70.152.7",80));
ISimpleRpc srv = socketClient.getService(ISimpleRpc.class,"simpleRpc", "0.0.1");
System.out.println(srv.hi(new Person()));
}
ApiGatewayConfig为Api网关客户端配置类,支持3个参数,分别为连接类型,支持Http和Socket,另外两个为Api网关IP和端口。ISimpleRpc为服务接口(接口,不是实现类),通过Api网关客户端取得此远程接口代理实现类,然后就可以像本地调用一样调用远程方法,完全不用关注HTTP协议细节,更不用关注Socket底层细节,完全面向Java接口及自定义参数的方法调用,当然,原生的Java 8种基本数据类型胶封装类型也是无差别支持的。
异步方式调用
public static void main(String[] args) {
ApiGatewayClient socketClient = new ApiGatewayClient(new ApiGatewayConfig(Constants.TYPE_HTTP,"124.70.152.7",80));
ISimpleRpc$JMAsyncClient srv = socketClient.getService(ISimpleRpc$JMAsyncClient.class,"simpleRpc", "0.0.1");
srv.hiJMAsync(new Person()).then((val,fail) -> {
System.out.println("Hi: " +val);
//System.out.println(fail);
}); JMicro.waitForShutdown();
}
这次直接通过ISimpleRpc$JMAsyncClient.class调用getService方法获取异步代理实例。第7行调用异步方法hiJMAsync并得到IPromise实例,调用IPromise的then方法,并传入方法引用接收异步结果。异步响应式编程相关内容请自行网上查看相关资源。
第12行让线程等待,不然JVM在RPC结果没返回前就退出了,正常的服务进程不需要这行代码。
JS前端调用
在HTML头部引入JS文件
<script type="text/javascript" src="js/utils.js"></script>
<script type="text/javascript" src="js/ws.js"></script>
<script type="text/javascript" src="js/rpc.js"></script>
这几个JS文件可以在源码路径下的mng.web/public/js下找到,需要在rpc.js文件中修改API网关IP和端口,如下
JS调用ISimpleRpc的hi方法
function hi(person) {
let req = {};
req.serviceName ="cn.jmicro.example.api.rpc.ISimpleRpc"; //服务接口全名
req.namespace = "simpleRpc";//服务名称空间
req.version = "0.0.1";//服务版本
req.method = "hi"; //服务方法名称
req.args = [person]; //服务参数,一定要包装在一个数组里面
//jm.mng.callRpc返回一个Promise实例,异步返回结果
jm.mng.callRpc(req,jm.rpc.Constants.PROTOCOL_JSON, jm.rpc.Constants.PROTOCOL_JSON)
.then((msg)=>{
//异步返回结果
console.log(msg);
alert(msg);
}).catch(err =>{
throw err;
});
} //调用hi方法
hi({"username":"Zhangsan","id":"1"});
通过上面代码可以看出,JS这种网格和Java异步网格非常相像。
nodejs中可以使用同样的代码实现调用JMicro服务的方法。
【JMicro】微服务开发及使用的更多相关文章
- JMicro微服务之超时&重试
JMicro是本人开发的基于Java实现的微服务框架,当前正式版本为0.0.3,并已发布到maven中央仓库.项目源码github:https://github.com/mynewworldyyl/j ...
- 构建微服务开发环境4————安装Docker及下载常用镜像
[内容指引] 下载Docker: Mac下安装Docker: Windows下安装Docker; 下载常用docker镜像. 一.下载Docker 1.Mac适用Docker下载地址:https:// ...
- 构建微服务开发环境8————Hello 微服务
[内容指引] 1.用IDEA打开微服务项目; 2.更新Maven依赖: 3.IntelliJ IDEA JDK配置; 4.修改代码: 5.运行微服务: 6.将代码变更提交到Github. 经过前面的努 ...
- 【13】JMicro微服务-ID生成与Redis
如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl 往下看前,建议完成前面1到12小节 1. 微服务中ID地位 如果说前面小节的功能点是微服务的大脑,那么全局唯一ID则是微服务 ...
- 【12】JMicro微服务-Zookeeper
如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl 往下看前,建议完成前面1到11小节 1. CuratorFramework支持 JMicro目前基于Zookeeper实现统 ...
- 【11】JMicro微服务-配置管理
如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl 往下看前,建议完成前面1到10小节 JMicro目前仅支持基于Zookeeper做配置管理,全部配置信息可以在ZK做增删改查 ...
- 【10】JMicro微服务-API网关
如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl 往下看前,建议完成前面1到9小节 1. Api网关基本特性: Api网关作为对外网提供服务的基本入口,地位类似于NGINX, ...
- 【8】JMicro微服务-JMicro ZKUI
ZKUI是一个开源项目,是一个查看,修改ZK数据非常方便的工具.JMicro基于ZK做服务治理,配置管理,因此使用ZKUI会提供非常大的方便. Github地址:https://github.com/ ...
- 【2】JMicro微服务-Hello World
如非授权,禁止用于商业用途,转载请注明出处作者:mynewworldyyl 1. 首先完成 JMicro微服务-RPC体验 的1到5步. 按默认方式启动ZK及Redis: JDK需要Java8及以上. ...
随机推荐
- 【2003、2004 NOIp 入门组错题报告】
2003: T4: 题目大意: 讲这么多话,其实就是求比当前序列大的序列中第m小的一个.可以每次找出比当前序列大的最小的一个序列.我们可以从后往前扫描,当当前这个数比后一个数小时,我们把它与它后面的 ...
- 一行一行源码分析清楚AbstractQueuedSynchronizer
“365篇原创计划”第二十四篇. 今天呢!灯塔君跟大家讲: 一行一行源码分析清楚AbstractQueuedSynchronizer 在分析 Java 并发包 java.util.concurren ...
- JVM源码分析之深入分析Object类finalize()方法的实现原理
原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 “365篇原创计划”第十篇. 今天呢!灯塔君跟大家讲: 深入分析Object类finalize()方法的实现原理 finalize 如果 ...
- MySQL实验 内连接优化order by+limit 以及添加索引再次改进
MySQL实验 内连接优化order by+limit 以及添加索引再次改进 在进行子查询优化双参数limit时我萌生了测试更加符合实际生产需要的ORDER BY + LIMIT的想法,或许我们也可以 ...
- JavaWeb基础(day11)
HTML HTML是超文本标记语言.HTML就 是普通的文本文件,只不过在文本中的内容如果被一些 特殊的标签进行包裹就有了特殊的含义,这些被那些标签标记文本,就成了超文本. 网页的组成 网页的组成 H ...
- Python 读取Excel之xlrd篇
上一期给大家分享了如何用Python读取文本,这次给大家分享如何读取Excel表格内容,拿最常见的.xlsx和.xls格式来讲解. 本章主要知识点有: 读取整篇excel返回list[list[lis ...
- Drf06 /drf总结
Drf06 /drf总结 目录 Drf06 /drf总结 1. restful规范 2. drf组件认证的实现过程? 3. drf组件中权限的实现过程? 4. drf组件中节流的实现方式? 5. 什么 ...
- python 面向对象专题(四):封装、多态、鸭子类型、类的约束、super
https://www.cnblogs.com/liubing8/p/11321099.html 目录 Python面向对象04 /封装.多态.鸭子类型.类的约束.super 1. 封装 2. 多态 ...
- Python 实现邮件发送功能(初级)
在我们日常项目中,会经常使用到邮件的发送功能,如何利用Python发送邮件也是一项必备的技能.本文主要讲述利用Python来发送邮件的一些基本操作. 本章主要包含知识点: 邮件发送原理简述即常用smt ...
- Spring Boot 2.x基础教程:进程内缓存的使用与Cache注解详解
随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一.Spring 3开始提供了强大的基于注解的缓 ...