【JMICRO】 微服务简介及异步RPC体验

走远了吗. 提交于 2020-08-13 01:02:42

一,为什么写JMicro

印象中初次接触微服务大概是2011年,那会做Eclpise插件开发,网上查看好多关于OSGI的技术文章,发现Spring新出了一个叫Spring-boot的框架,那会没太上心,只是了解了点皮毛,工作又太忙,之后就没下文了。

直到大概2015年的某天,碰到一个小项目,没什么难度,都用老套路去玩,没什么意思,得玩点新东西才行,也不枉一翻付出,于是选择用GO语言实现,选择GO主要是想体验一下GO,看是不是真如传说中的那样无敌。经过一翻折腾,最终确定GOGIN+GOMICRO实现。是的,从那会开始,通过学心和使用GOMICRO,从此迷上微服务。后来因为工作需要,再没什么机会在项目中接触GO。

后面也曾试图去用Dubbo和Spring-Cloud做项目,但也止于浅尝则止,没能深入。一方面项目时间太紧,折腾不起。另一方面,也是最重要的,项目组成员根本不愿意去学新东西,在很多成员心中,微服务,Spring-Cloud太深奥,玩不起,没时间,至于Dubbo,写个服务,做个RPC也是从百度复制下来的,跑起来就完事了,没人关心个中的原理,经常碰到问题就抓狂!

从那时候起,就一直琢磨着能不能用Java写一个像GoMicro一样简单的微服务框架,这个框架要确保足够简单,入门和使用成本要底,就像写HelloWord一样,但微服务的基本功能要全。因此项目依赖不能多,打出来的运行包也要小,能不用的第三方包坚决不用,最好使用JDK库就行!

 

二.RPC开始

微服务的基础是RPC,而反过来单单有RPC却不能叫微服务,微服务是在RPC基础上,实现一系列的基础性功能支持后才能叫微服务。没有超时,限流,熔断这些基础支持,服务“微”了之后,随时都可能挂机,这就是为什么微服务之前存在很多RPC框架,但却没人说那是微服务。

坚持从JDK库入手,使用原生NIO SOCKET做了个简单的RPC,并且兼容了HTTP方式,完后做压力测试发现性能太差,然后退一步吧,选择Mina做底层通信重新再折腾一遍,结果还是不尽如人间,性能还可以,就是自觉得太繁琐臃肿,还是不满意,最终又改为Netty才完成RPC基本功能开发。很多时候,路就是一步一个脚印走出来的,如果前面碰到大山大河,实在跨不过去,回头绕个湾也是必须的,否则一步没跨过去就OVER了。最优的不是选择最好的,而是选择合适的。

接着做IOC,超时,熔断,限速,配置管理,服务注理,监控服务,负载均衡,服务路由,API网关,HTTP,WEB Socket,全局ID,链路追踪,消息服务,异步RPC,基于VUE做的后台管理,服务编排。。。。,到现下图功能基本上都有实现,并且正常可用,后期肯定会不断优化。

图中最底层是JMicro依赖服务,没做任何修改,Redis用于做高速一至性ID生成及消息缓存,Zookeeper做配置管理及服务注册中心,应用可以通过JMicro IOC容器直接获得Redis和Zookeeper实例组件并使用,非常方便;Netty做RPC底层数据通信,同时支持HTTP及Websocket,应此支持Web端直接访问微服务;Mongodb是一个可选依赖,用于存储RPC监控日志和事件信息;Vue+IView实现后台管理的前端页面。

智能控制:目前没有实现,之所以列出来,是因为一直把他当作JMicro的核心功能,目的是通过监控获取到的系统数据训练一个人工智能大脑,让其自动完成JMicro系统的日常维护。

IOC容器:之所以没用现成的Spring等容器实现,就如一开始所说的,他们太复杂,不够轻量(即使他们一直坚持认为他们是轻量级容器),对于JMicro来说,他们还是大重了,也不利于JMicro对微服务的量身定制。JMIcro IOC使用非常简单,在99%情况,通过Component注解组件,通过Inject注解字段就够了,就这么简单。

编码解码:如果说RPC是微服务的基础,那么编码解码则是微服务的基石,基石不牢,服务的大厦随时都可能倒塌。我将JMicro实现的编码解码实命名为前缀类型编码解码器,首先对类对进行编码,然后将类的编码信息和要传输的数据一起序列化到二进制数据中,解码时则将编码转为类信息,然后反序列化二进制数据为类对象,当然,编码考虑了高频使用的类并确保其编码最短,从而达到序列化的数据冗余最底。测试效果还是相当不错的,对比ProtoBuffer性能不相上下,某此情况差此,但某些情况比ProtoBuffer还好,甩JDK原生序列化及Kryo序列化几条街。 

别的功能待以后再细说。

 

实现以上功能的全部第三方依赖如下:

其中

javapoet是编译时做代码生成用的,在最终运行包中并不需要

guava前期实现限流试验性用到,最后限速并不理想,实际上已经排除

 

Curator后期打算去除,只保留Zookeeper.

 

三,目前最满意的一个特性:异步RPC

 

大家都知道,NodeJS单线程,但其性能却很强劲,因为异步;Redis性能也很强劲,因为异步;无数的库或服务都强调通过异步提高性能。

在对JMIcro做压力测试过程中,我深深地体会到一个同步的代价(性能底还不说,高并发时还会死锁)而异步的必要性。在JMicro中,异步体现在很多模块中。其中主要以下几个:

  1. 消息服务,一个针对JMicro量身定制的异步消息中间件,服务可以通过其实现消息发布/消息订阅; 还可以做异步RPC,比如发布一个RPC服务方法,使其自动订阅特定主题的消息,消息中间件能识别此种类型的服务方法,并将匹配的消息发送给此服务的服务方法。
  2. 以1作为基础,可以在客户端及服务端做异步RPC,比如在客户端过直接将消息发送到消息中间件,让消息中间件转发给目标服务;也可以在服务端收到消息后(比如API网关,同步转异步,绡峰限流),将消息发送到消息服务器,让消息服务器转发给目标方法。
  3. 代码级别的异步RPC调用,让Java代码像Scala,NodeJS等语言一样做异步编程,以下对此做详细的Demo

下面片段代码摘自

https://github.com/mynewworldyyl/jmicro/blob/master/example/example.comsumer/src/main/java/cn/jmicro/example/comsumer/ServiceComsumer.java

ISimpleRpcAsyncClient src = (ISimpleRpcAsyncClient)of.get(ISimpleRpc.class);

         //最外层异步RPC调用

        src.helloAsync("Hello JMicro").then((rst, fail)->{

           //第一次异步返回结果

System.out.println(rst);

//做一次同步调用

           String r = src.hello("Hello two");

           //同步返回结果

System.out.println(r);

           //再做一次异步调用

           src.helloAsync("Hello two").then((rst1, fail1)->{

               //异步返回结果

               System.out.println(rst);

            });

        });

我觉得这种异步RPC风格是最有杀伤力的,这使得我们的远程RPC调用不再阻塞线程,包括工作线程和网络IO线程,只要我们的线程占用CPU,他都是在做有用的工作!

 

四,体验JMiro异步RPC

前面吹了这么多,不来点实际可见的操作,肯定不能让人信服,现在手把手教你体验一把JMicro是不是真的做了远程RPC调用,是不是真的是异步调用。

1. 环境准备

首先确保本机安装了Java,Maven,Zookeeper监听在2181端口,Redis工作在6379端口,保持默认,所有配置都省了。  

2. 下载JMicro代码

https://github.com/mynewworldyyl/jmicro下载代码到特定目录,下面以${basedir}指代此目录

3. 打开一个命令行窗口,cd进入到${basedir}\codegenerator目录,执行

mvn clean install -Dmaven.test.skip=true

4. 上面命令执行成功后,cd进入到${basedir}目录,执行mvn clean install -Dmaven.test.skip=true

5. 确保3和4成功后,cd进入到${basedir}\example目录, 执行mvn clean install-Dmaven.test.skip=true

6.  通过3,4,5步确保相关依赖包都已经安装在Maven本地仓库中,打包可执行的服务Jar包,cd进入到${basedir}\example\example.provider目录, 执行mvn mvn clean install -Pbuild-main -Dmaven.test.skip=true

7. 运行服务端

java -jar target/jmicro-example.provider-0.0.1-SNAPSHOT-jar-with-dependencies.jar -javaagent: ${basedir}\target\jmicro-agent-0.0.1-SNAPSHOT.jar

看到以下输出说明服务启动成功

 

8. 打包可执行的测试客户端Jar包,cd进入到${basedir}\example\example.comsumer目录, 执行mvn clean install -Pbuild-main -Dmaven.test.skip=true

9. 运行客户端

java -jar target/jmicro-example.comsumer-0.0.1-SNAPSHOT-jar-with-dependencies.jar -javaagent: ${basedir}\target\jmicro-agent-0.0.1-SNAPSHOT.jar

以下是客户端两次异步调用,一次同步调用返回的结果

 

对应服务器的3个被调用的输出

 

以上样例对应的服务接口及实现类分别为:

https://github.com/mynewworldyyl/jmicro/blob/master/example/example.api/src/main/java/cn/jmicro/example/api/rpc/ISimpleRpc.java

 

@Service  //指定此接口是一个远程接口,对外提供RPC服务

@AsyncClientProxy //编译时注解,Javac编译器调用特定注解处理器生成客户端访问代码

public interface ISimpleRpc {

    //我们测试用的方法

    String hello(String name);

   

    //IPromise<String> helloAsync(String name);

    //POJO对象为参数的RPC方法

    String hi(Person p);

    //测试RPC链路

    String linkRpc(String msg);

}

 

https://github.com/mynewworldyyl/jmicro/blob/master/example/example.provider/src/main/java/cn/jmicro/example/rpc/impl/SimpleRpcImpl.java

服务实现类代码有点多,在此只贴我们调用的方法,别的请查看源代码

public String hello(String name) {

        if(SF.isLoggable(MC.LOG_DEBUG)) {

//向监控服务发送一条日志事件,请放心,调用此方法只是把日志存于本地并立即返回,不影响正常的性能

    SF.eventLog(MC.MT_PLATFORM_LOG,MC.LOG_DEBUG,SimpleRpcImpl.class, name);

        }

        System.out.println("Server hello: " +name);

//返回一条信息给客户端

        return "Server say hello to: "+name;

}

 

客户端测试类

https://github.com/mynewworldyyl/jmicro/blob/master/example/example.comsumer/src/main/java/cn/jmicro/example/comsumer/ServiceComsumer.java

 

完整类图

 

 

简化类图

 

 

 

 

五,实现原理

 

建议适当修改上面代码然后编译运行测试,比如修改服务端返回值,修改客户端调用参数,服务端返回改为同步,或再调用别的服务(可以同步调用,也可异步调用)。

 

启动多个example.provider实例(理论上,你可以启动无数个服务实例,那怕都在同一台机器上)多次运行客户端调用看看调用了那个服务实例(服务路由及负载均衡)

 

如果你对微服务有兴趣,并且有足够的耐心,你应该能从中获得乐趣。

如果认真查看了原代码,应该看到以下几个关键的注解,而使用JMIcro,基本上使用这几个注解就够了:

 

 

 

1. Service:如果注解在接口上,意思是告诉JMicro容器,这个一个服务接实口,客户端使用接口类为参数get实例时,请返回服务代理实例;如果注解在实现类上,告诉JMicro容器,这是一个RPC服务,请将服务信息发布到服务注册中心,让客户端知道我的存在。

2. AsyncClientProxy:注解服务接口,告诉编译器在编译全部源代码前,请调用注解处理器生成服务客户端代理接口及实现类,供客户端直接使用,让客户端觉得自己就好像获得了服务实现类的直接引用一样,完全不知道是跨JVM的远程调用。

 

3. Component:JMIcro容器组件,JMicro容器启动时实例化,供容器中的其他组件使用,是的,服务本身也是一个组件,只不过其实现了服务接口,并向注册中心注册自己才变成了远程服务,同时其本身也是一个组件,可以被同一个容器中的其他组件依赖并被自动注入。

4. Reference:注解在组件的字段上,告诉JMicro容器,我需要一个远程服务代理实例,请将这个实例值赋给我注解的字段。

 

 

 

5. SMethod注解在方法上,告诉JMicro容器,我是一个服务方法,并指定服务参数,比如超时时间,熔断策略,是否可监控,日志级别等。

6. 每个服务都由三个值唯一确定,分别是服务接口全称(serviceName),名称空间(namespace),版本(version),并使用在Service及Reference注解上。

 

后面有时间会对JMicro的细节做更多介绍,以实战为主,微服务概念为铺。

 附一个后台管理的前端链路日志查询页面

 

 

 

大家有任何问题欢迎评论。

如果你对这个项目感兴趣,也欢迎参与开发,共同完善。

 后面资金允许后,买个服务器在公网上部署一套,让大家体验!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!