如何自主实现一个简单微服务架构

Posted 深圳技术研究会

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何自主实现一个简单微服务架构相关的知识,希望对你有一定的参考价值。


---项目背景介绍---


有一个X项目,意在将散落在系统各处的某一类服务,整合为一个独立系统,为外部各系统提供统一的服务接口。

对外提供服务的方式有两种,一种是通过消息中间件,第二种是通过服务远程调用。本文将要分析的方案就是为了满足第二种方式:服务远程调用。

所谓的服务远程调用,即为本地调用一个服务,而服务本身的运算执行是在远端服务器,通过网络来传输最初参数以及最终结果。为了方便调用远程服务,通常采用一些组件来完成底层通信的封装,使得底层通信对应用层是透明的,调用远程服务就像是调用本地服务一样方便简洁。

支持服务远程调用的方式有很多,不同方式的区别主要体现在通信协议上,例如RMISOAPBinary-RPC等,对于一些协议而言,是可以跨语言进行远程调用的。

传统的服务远程调用,为直接点对点交互,服务提供方,通过暴露自己的IP以及端口供调用方调用,在服务提供者数量变大以后,对于调用方而言,需要维护所有提供方的信息,服务治理容易出现混乱。同时一旦服务提供方更换了部署位置,调用方需要同步更新代码或者配置,耦合程度仍然略高。

随着微服务架构理念的提出,一个系统中需要被远程调用的服务数量急剧上升,传统的点对点服务远程调用模式,不再满足业务需求,因此就出现了微服务框架,以支持大规模的远程服务调用。

一个合格的微服务框架提供的核心功能可以总结为如下三个方面:

  1. 负载监控 + 负载均衡:负载监控可以监控所有服务提供者的被调用时长以及次数,当一个服务有多个提供者时,根据负载监控的信息,为服务调用者分配一个合理的服务提供者。负载监控如果能以UI界面展示更佳。

  2. 多通信协议支持、服务熔断、服务调用链trace等功能

  简单的示意图如下


如何自主实现一个简单微服务架构


目前比较出名微服务框架为dubbo以及Spring cloud,这两个框架都满足了上述微服务架构所需要的核心功能。

X项目最初设想是直接引入dubbo框架,也展开了测试工作,并且对相关的研究成果进行了分享和文档化记录。在后期的讨论中,有一个问题引起了大家的关注:

微服务,是一种架构设计理念和模式,倾向于将应用模块化为子系统,子系统再进行分割,分割后的功能点之间依然是服务化的远程调用,即服务的粒度被切割的非常小。这和X项目当前的需求是一致的么?X项目确实需要服务远程调用,但是主要是对外提供服务,供外部的系统进行服务调用,并非自身需要设计为微服务模式。那么此时这些微服务框架对于X项目而言,是否太过于重型化了?

对上述问题进行深入讨论后,达成如下统一意见:是否考虑自主实现一个简单版本的类似微服务框架的功能,参考dubbo等框架的设计理念,自主开发一个适合我们自己使用的轻量级框架。接下来则是介绍该框架如何实现,仅为测试版本。


---框架代码示例---


采用了以下设计,实现了一个简约的测试版本微服务调用框架:采用已经存在并使用的zookeeper集群作为注册中心,用作服务注册以及服务发现,同时开发一个网页UI连接zookeeper,用以展示服务的注册情况。服务间远程调用的协议为RMI,支持负载均衡。

下文中的示例代码,仅为测试用例,用来验证技术可行性,后续可优化空间还有很大,比如服务远程调用的协议可以新增支持httprestful等跨语言的,服务熔断机制的引入,负载均衡算法的优化,负载监控的新增等。如果这些功能均自主开发,那么技术可控程度会比采用第三方框架要高,为了提高代码健壮性以及代码质量,可以参考第三方开源实现。

下面是测试代码的简单示例

如何自主实现一个简单微服务架构


代码分为四部分:

client为服务调用者,与注册中心交互,完成远程调用

frame为微服务框架代码,提供了服务注册以及发现功能、提供了负载均衡

server为服务提供者,在注册中心完成注册,将service中的服务发布,等待被调用

service中实现了服务的方法

测试代码需要引入zookeeperlog4jjar包。

Service.java代码如下:

如何自主实现一个简单微服务架构



ServiceImp.java代码如下:

如何自主实现一个简单微服务架构



服务端Server的代码如下:

如何自主实现一个简单微服务架构



首先服务提供者Server启动,启动后会在zookeeper上注册自己,成功启动后,如下:

如何自主实现一个简单微服务架构



当有多个server启动,提供相同的服务时,则会在同一个服务名称下注册多个实例

如何自主实现一个简单微服务架构



启动客户端Client,服务调用者通过需要调用的服务名称,可以直接得到远程服务对象,然后就像调用本地代码一样,完成本次调用。

客户端Client的代码如下:



运行结果如下:

,至此完成了一次服务远程调用。

其中serviceFrame模块,则是按照如下内容实现:

 

public class ServiceFrame {

       /*zooKeeper对象 */

       private ZooKeeper zooKeeper;

      

       /* ServiceFrame的单例对象 */

       Private static ServiceFrame instance;

      

       /*单例锁*/

       Private static byte[] lock = new byte[0];

      

       privateList<ACL>acls;

      

       public static ServiceFramegetInstance(String zooStr,String passwd){

              if(instance!=null){

                     returninstance;

              }else{

                     synchronized(lock){

                           if(instance!=null){

                                  returninstance;

                           }else{

                                  instance = new ServiceFrame(zooStr, passwd);

                                  returninstance;

                           }

                     }

              }

       }

      

       privateServiceFrame(String zooStr,String passwd){

              try {

                     this.zooKeeper = newZooKeeper(zooStr,60000,null);

                     this.zooKeeper.addAuthInfo("digest", passwd.getBytes());

                     Id adminId = new Id("digest",DigestAuthenticationProvider.generateDigest(passwd));

                     ACL acl1 = new ACL(ZooDefs.Perms.ALL,adminId);

                     this.acls = new ArrayList<ACL>();

                     this.acls.add(acl1);

              } catch (Exception e) {

                     // TODO自动生成 catch

                     e.printStackTrace();

              }

       }

      

/*提供了在zookeeper上注册服务的功能*/

       Public voidregistry(String serviceName,String url){

              try {

                     String service_root = "/registryService";

                     Stat stat = zooKeeper.exists(service_root, false);

                     if(stat == null){

                           zooKeeper.create(service_root, newbyte[0], acls, CreateMode.PERSISTENT);

                     }

                    

                     String servicePath =service_root + "/" + serviceName;

                     stat = zooKeeper.exists(servicePath, false);

                     if(stat == null){

                           zooKeeper.create(servicePath, newbyte[0], acls, CreateMode.PERSISTENT);

                     }

                    

                     String serviceInstance =servicePath + "/url";

                     zooKeeper.create(serviceInstance, url.getBytes(), acls, CreateMode.EPHEMERAL_SEQUENTIAL);

              } catch (KeeperException e) {

                     // TODO自动生成 catch

                     e.printStackTrace();

              } catch (InterruptedException e) {

                     // TODO自动生成 catch

                     e.printStackTrace();

              }

             

       }

      

/*通过服务名称在zookeeper上查询注册信息*/

       public StringgetServiceUrl(String serviceName){

              byte[]result = null;

             

              try {

                     String servicePath = "/registryService" + "/" + serviceName;

                     List<String>allService = zooKeeper.getChildren(servicePath, true);

                     /*最简单的随机分配负载均衡算法*/

                     Random rand = new Random();

                     int locate = rand.nextInt(allService.size());

                    

                     String serviceInstance =servicePath + "/" + allService.get(locate);

                     result = zooKeeper.getData(serviceInstance, null, null);

                    

                     } catch (KeeperException e) {

                           // TODO自动生成 catch

                           e.printStackTrace();

                     } catch (InterruptedException e) {

                           // TODO自动生成 catch

                           e.printStackTrace();

                     }

             

              return result == null ? "" : new String(result);

       }

      

/*通过服务名称获取注册信息后,在远端服务器获取远程对象*/

       public RemotegetService(String serviceName) throwsMalformedURLException, RemoteException, NotBoundException{

              String url =getServiceUrl(serviceName);

              return Naming.lookup(url);

       }

}

通过这种方式实现的服务调用,逻辑简单,方式简洁,能充分利用现有的系统模块和架构。并且RMI的远程调用,效率很高。并且可以较为方便的将该功能整合到现在的框架中,后续可以定制化的加入需要的功能,完全自主开发能够做到非常高掌控力度,缺陷就是重复造了轮子,且外面的轮子经历了千万用户的使用和测试,稳定性以及可靠性比自己开发会更胜一筹。

同时还做了一些关于将spring cloud中的组件精准引入,来实现接入其微服务功能的代码示例,篇幅所限,就下次讲述。


以上是关于如何自主实现一个简单微服务架构的主要内容,如果未能解决你的问题,请参考以下文章

微服务架构的 10个 最佳实践 !

技术选型看这里|微服务架构下该如何技术选型?建议收藏

什么是微服务架构?主流的微服务如何实现?

老司机避坑指南:如何快速搞定微服务架构?

15种微服务架构框架汇总

微服务架构洁介绍及开源框架