初见微服务

来源:互联网 发布:java视频教程马士兵 编辑:程序博客网 时间:2024/05/29 13:05

初见微服务之架构概述

一、应用背景

随着计算技术的进步,内存、CPU、磁盘等资源不再是稀缺的,计算机作为应用程序的载体从单服务器转变为多服务器,集中计算演化为分布式计算。原有的“巨石”应用难以适应业务的发展速度,可扩展、自适应的能力不足,程序员面对着数以万计的源代码文件抓耳挠腮(O M G!),越来越多的工程师渴望小而美、易于扩展的架构体系,微服务应运而生。自2005年首次由Peter Rodgers提出微服务概念以来,风靡一时,随着近些年的发展,已经逐步被千万企业所采用,多数为互联网企业。我于工作半年之后有幸参与到后端微服务架构迁移的产品设计和开发中,有些体会,与大家分享。

二、设计原则

  • SRP(Single Responseble Principle)即单一职责原则,每一个服务提供者仅暴露自己职责范围内的接口,操作职责范围内的DB,不继承其他服务提供者的协议。简单点说,该你干的才去干,不该干的调用其他人的服务来干。
  • RESTful,作为Web Service的替代者,其面向资源的特性注定是为微服务而生的,接口设计符合REST设计原则将使服务易于理解和接受。需要注意的是,灵活的使用,而不是生硬的照搬REST设计,如保持请求风格一致,GET/POST,少用DELETE/PUT(仁者见仁,智者见智);保证同类资源前缀的一致性等。REST和RPC的优劣如表2-1。说一下可扩展性吧,RPC多以client拥有server的interface来实现通信,当接口变动后,client必须马上升级,而REST的接口协议可以不强制升级。 
    响应时间
    用户友好程度
    可扩展性
    REST

    ***

    *****

    *****

    RPC

    *****

    **

    ****

  • 协议,也就是实体或者Bean了,作为各个服务组件共有的内容,是组件之间调用的桥梁。服务组件之间的调用通过协议进行调用,协议尽量不去彼此继承。如果说微服务是个分布式的世界,那么协议就是这个世界的法律文书(感觉很严肃的样子)。

三、架构图

jiagou

 

关键点:

1.服务注册中心,zookeeper集群作为分布式调度中心,各个服务注册在zookeeper之上,注册内容包括服务的请求url,请求ip地址和端口,服务组件名称,注册时间等;

2.Gateway,服务网关作为系统的入口,具有服务转发、授权验证、负载均衡等作用,使用高并发框架netty实现高速转发等;

3.访问控制服务,用户首先需要获得系统授权才可以访问业务服务,授权通过token来实现;

4.业务服务1~N,是系统内具体业务组件的划分;

5.用户服务,实现用户信息的注册、修改、共享等;

6.配置中心,整个系统的配置均位于配置中心,组件通过访问配置中心获取配置参数;

7.监控系统,检测ECS、RDS、zookeeper集群等的各项状态;

8.Kafka消息队列系统,支撑其他系统。

 

初见微服务之服务注册与发现

1.什么是服务注册与发现

   微服务将传统的"巨石"应用拆分成一个一个的组件应用,每个组件应用提供特定的服务,可以是一个,也可以是多个,并且组件所含服务应该是可以动态扩展的,随着时间推移、系统进化,可任意拆分、合并。

   组件化应用和颗粒化的服务,遍布在系统的各个角落,由不同的项目成员进行维护,微服务的核心是化整为零、各司其职,这就要求开发人员不得操作其业务或服务范围以外的数据模型等资源,只能通过接口的访问,使用某一服务。

   由于服务的跨度很大(公司很大的情况下)、数量很多(数以百计甚至更多),为保障系统的正常运行,必然需要有一个中心化的组件完成对各个服务的整合,即将分散于各处的服务进行汇总,汇总的信息可以是提供服务的组件名称、地址、数量等,每个组件拥有一个监听设备,当本组件内的某个服务的状态变化时报告至中心化的组件进行状态的更新。服务的调用方在请求某项服务时首先到中心化组件获取可提供该项服务的组件信息(IP、端口等),通过默认或自定义的策略选择该服务的某一提供者进行访问,实现服务的调用。

   随着分布式系统的发展,出现了越来越多的分布式调度系统,典型的有ZookeeperConsuletcd,在分布式系统中需要解决的一个问题即拜占庭将军问题,参考网站8btc(比特币咨询网站)《拜占庭将军问题深入探讨》http://www.8btc.com/baizhantingjiangjun

  其中Zookeeper最为成熟,是Yahoo贡献给Apache基金会的一个顶级开源项目,基于Paxos算法,参考维基百科条目Paxos (computer science) https://en.wikipedia.org/wiki/Paxos_(computer_science),是HadoopHBase的重要组件。

   下面一段是官网对于Zk的介绍。

ZooKeeper is a high-performance coordination service for distributed applications. It exposes common services - such as naming, configuration management, synchronization, and group services - in a simple interface so you don't have to write them from scratch. You can use it off-the-shelf to implement consensus, group management, leader election, and presence protocols. And you can build on it for your own, specific needs.

       Zk是为分布式应用设计的一个高性能协调服务,提供了如下的通用服务,如命名、配置管理、通过锁和分组服务,封装成简单易用的接口而无需开发人员从头编写代码。可以拿来即用,应用的领域有取得共识、分组管理、领导者选举和协议呈现。还可以按需自定义功能。

Zketcd的比较如下表

 

语言

算法

存储方式

量级

Zookeeper

Java

Paxos

名称空间(文件系统)

etcd

Go

Raft

K-V存储

 

2.Zookeeper集群配置

    2.1 Standalone单机部署

      Zookeeper的配置文件在 conf目录下,这个目录下有 zoo_sample.cfg log4j.properties,你需要做的就是将 zoo_sample.cfg改名为 zoo.cfg,因为 Zookeeper在启动时会找这个文件作为默认配置文件。下面详细介绍一下,这个配置文件中各个配置项的意义。

tickTime=2000 dataDir=D:/devtools/zookeeper-3.2.2/build clientPort=2181

tickTime:这个时间是作为 Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime时间就会发送一个心跳。

dataDir:顾名思义就是 Zookeeper保存数据的目录,默认情况下,Zookeeper将写数据的日志文件也保存在这个目录里。

clientPort:这个端口就是客户端连接 Zookeeper服务器的端口,Zookeeper会监听这个端口,接受客户端的访问请求。

当这些配置项配置好后,你现在就可以启动 Zookeeper了,启动后要检查 Zookeeper是否已经在服务,可以通过 netstat ano命令查看是否有你配置的 clientPort端口号在监听服务。

    2.2 集群部署

Zk进行集群部署时,需保证集群的数量为奇数个,即357…

Zookeeper的集群模式除了上面的三个配置项还要增加下面几个配置项:

复制代码
initLimit=5 syncLimit=2 server.1=192.168.211.1:2888:3888 server.2=192.168.211.2:2888:3888
复制代码

initLimit:这个配置项是用来配置 Zookeeper接受客户端(这里所说的客户端不是用户连接 Zookeeper服务器的客户端,而是 Zookeeper服务器集群中连接到 Leader Follower服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10个心跳的时间(也就是 tickTime)长度后 Zookeeper服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 5*2000=10

syncLimit:这个配置项标识 Leader Follower之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime的时间长度,总的时间长度就是 2*2000=4

server.A=BCD:其中 A 是一个数字,表示这个是第几号服务器;B是这个服务器的 ip地址;C表示的是这个服务器与集群中的 Leader服务器交换信息的端口;D表示的是万一集群中的 Leader服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B都是一样,所以不同的 Zookeeper实例通信端口号不能一样,所以要给它们分配不同的端口号。

除了修改 zoo.cfg配置文件,集群模式下还要配置一个文件 myid,这个文件在 dataDir目录下,这个文件里面就有一个数据就是 A的值,Zookeeper启动时会读取这个文件,拿到里面的数据与 zoo.cfg里面的配置信息比较从而判断到底是那个 server

3.Zookeeper服务注册

Zookeeper新增、删除节点的操作可以通过zk提供的基础api进行操作,也可以选择一些框架,方便我们的使用,这里采用的是Curator

服务url路径,举例如下:

myapp/service/user/info myapp/service/configuration

存储为zk集群如下名称格式

myapp/service/              | user/info              | configuration

提供服务的节点信息: 

复制代码
public class ServiceProvider {         // 提供服务的实例id       private Integer instanceId;     // 提供服务的实例ip     private String ip;     // 提供服务的实例端口号    private Integer port;     // 实例注册时间             private Date registTime;      省略getter、setter方法    public byte[] toBytes(){               try {             return JSONObject.fromObject(this).toString().getBytes("UTF-8");        } catch (UnsupportedEncodingException e) {             throw new SystemException();         }     }}
复制代码

注册服务的方法如下:

复制代码
public void register(String url, ServiceProvider node) {        CuratorFramework client = CuratorFrameworkFactory.newClient(url, sessionTimeout, connectionTimeout, new ExponentialBackoffRetry(1000, 3));        client.getConnectionStateListenable().addListener(new ConnectionStateListener() {            @Override           public void stateChanged(CuratorFramework client, ConnectionState newState) {               watcher.process();           }        });        client.start();          

          logger.info("正在注册 zookeeper服务节点:path=" + path + ", data=" + node);

          client.create().creatingParentsIfNeeded().forPath(path, node.toBytes());

}

复制代码

logger.info("正在注册 zookeeper服务节点:path=" + path + ", data=" + node);

4.Zookeeper服务发现

服务发现的策略可以自定义,如随机分发、定比例分发、根据服务器状态分发等等,其中某种分发策略需要注册时提供额外的服务器负载信息等。

监听器如下:

复制代码
public class ZookeeperGlobalCacheWatcher { public void watch(Closeable client, String path){       cache = new TreeCache((CuratorFramework)client, path);       cache.getListenable().addListener(new TreeCacheListener() {            @Override            public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {                publishEvent(new CacheWatcherEvent(event));             }         });         try {            cache.start();             logger.info("启动treeCache watcher");         } catch (Exception e) {             throw new SystemException();         }     } }
复制代码

服务发现: 
复制代码
public List<ServiceProvider> getServiceProvider() {     if(treeCache == null){         // 监听器zookeeperGlobalCacheWatcher,监听zookeeper的状态        treeCache = (TreeCache)zookeeperGlobalCacheWatcher.getCache();     }             List<ServiceProvider> providers = new ArrayList<ServiceProvider>();      try {            // 数据转换            if(treeCache.getCurrentChildren(path) != null){                                 for(Entry<String, ChildData> item : treeCache.getCurrentChildren(path).entrySet()){                                          ChildData data = item.getValue();                     if(data != null && data.getData() != null && data.getData().length > 0){                         providers.add(JsonUtil.json2Object(new String(item.getValue().getData(), "UTF-8"), ZookeeperServiceProvider.class));                     }                 }             }       } catch (Exception e) {             throw new SystemException();       }               return providers; }
复制代码

   服务注册和发现是整个微服务软件架构的核心,zookeeper成熟稳定的性能广受青睐,在系统技术选型和开发过程中,zookeeper都是占有这绝对的优势。

初见微服务之RESTful API

1. REST名称由来

    REST全称为Representational State Transfer,即表述性状态转移,最早由Roy Feilding博士在世纪之交(2000年)提出,喜欢追根溯源的朋友可以读一下他的博士论文《Architectural Styles and the Design of Network-based Software Architectures》,这时距HTTP1.1协议标准正式发布(1999年6月)仅一年的时间。

    岁月的痕迹跨越了十多年,技术的进步日新月异,所有的人都在谈论着应用容器化、服务解耦、DevOps开发运维文化等等。我们变得喜新厌旧,技术成了快餐,框架是越来越多的舶来品。此时,我们是否应该静一静,看看技术的起源,想想我们如何成为软件的设计师,而不是代码的奴隶、资本的工具?REST作为历史的宝藏,被越来越多的人挖掘、归纳、推陈出新,近几年占领了几乎所有的大型互联网公司的开放API,国外如google(https://developers.google.com/apis-explorer)、facebook,国内的有豆瓣、腾讯的公众平台等。

    在这里,我要替SOAP说几句话,技术的进步始终是从无到有,由繁入简的。在一定的时间里SOAP满足了web服务的设计要求,达到了对外提供服务的目的,尽管十分的(协议)晦涩、(解析)生硬。企业级的软件依然有很多保留着SOAP式的服务,我工作过程中对接的一些政府如卫生计划委员会、医疗HIS系统其实依然是保有SOAP的,它活在计算机构建的这一社会的血液里、空气里。

2. 什么是REST?

    需要注意的是REST并不是一个标准或者协议,而是一种设计风格,或者说是一个设计web服务的最佳实践,其要点如下:

    1) 面向资源的URI设计,如user/register;

    2) 对资源的操作包括增、删、改、查(和数据库层的操作极为相似);

    3) 连接具有无状态性,即每一次的响应只依赖于这一次的请求;

    4) 利用HTTP协议实现以上的设计思想。

    非RESTful的设计示意图如下:

    image

    RESTful的设计示意图如下:

    image

3. REST设计

    REST的设计利用了HTTP协议的请求option,如GET、POST、PUT、DELETE。设计的简单示意图如下:

    REST设计

    我工作过程中的一些最佳实践是:

    1) 对option的选择不应过多,不应死板教条,常用的有GET、POST即可;

    2) URI的设计应已名词为主、动词为辅,层次清晰;

    3) 参数的设计应已单词为主,少用多个词的驼峰连接形式;

    4) 功能与URI或者参数设计冲突时,应以功能实现为主。

4. REST的劣势

    a. 一千个读者,一千个哈姆雷特,在设计评审粗糙的情况下,面向资源的URI设计五花八门;

    b. URI泛滥,版本管理困难;

    c. HTTP option使用不当;

    d. REST API 参数、返回值设计不当;

    ……

5. 微服务为什么选择REST?

      随着组件拆分、服务解耦,各组件之间的调用均是通过接口实现,REST可以让这些拆分后的服务风格统一、便于维护和管理,目前我所参与设计和开发的系统存在100+的服务接口,如果不统一风格、调用方式,想必将是一场灾难。服务层次化的前提是组件的拆分,如用户组件URI前缀需要以 /user 开头,配置系统的前缀需要以 /configuration开头,用程序自动对前缀校验。

    RESTful API的入参、出参,均已Java Bean的形式提现,称之为接口协议。业务接口协议可以继承用户组件协议,各业务接口协议之间不可以继承和聚合,这些规则的设定,用冗余的思想达到解耦的目的。

    当然微服务从来不是银弹,REST也不是救世主,这是一个发现问题、解决问题的过程,从来没有最完美的,只有最合适的。

原创粉丝点击