目录
一、概述
学习常见的分布式系统架构,主要学习系统的应用场景、关键难点、系统架构、功能模块及部署应用。
以下SOA与微服务的内容主要来自Kim Clark的《微服务、SOA 和 API:是敌是友?》,ESB内容主要来自Zato文档《ESB和SOA到底是什么?》和Mule ESB文档《What is an ESB?》
1 关于SOA
1.1 简介
面向服务的架构(Service-Oriented Architecture,SOA)的简单定义是:应用程序的功能公开为更容易访问的服务接口,使得在下一代应用程序中使用它们的数据和逻辑变得更容易,它更适用于面向企业范围内、业务系统间的彼此通信。
1.2 SOA模式
两种常见的SOA模式,一种是靠集成引导,另一种是靠业务引导。
第一种以集成为目的的模式,通常“需要深入集成到现有系统的复杂的专用数据格式、协议和传输机制中。然后需要使用标准化的机制(比如 SOAP/HTTP 或最近的 JSON/HTTP)来公开它们,使它们更易于在新应用程序中重用”。如下图的右侧图。此观点的部分或全部通常被称为 企业服务总线 (ESB) 模式。执行深入集成(集成中心或适配器)和以标准化方式(公开网关)将这些集成公开为服务或 API 的需求是必不可少的。这与集成挑战密切相关,和微服务没有任何关系。
另一种以业务为目的的模式,通常“需要重构功能,以便公开一些业务人员可切实构建到未来解决方案中的东西,为下一代应用程序提供所需的东西”,在 SOA 参考架构中,这些应用程序通常称为“服务组件”,上图的右侧图。这种观点表达了与应用程序设计模式(与微服务架构类似)和单独功能组件分解模式。
在某种程度上,在大型企业中,SOA 通常被认为是失败的。这种想法可能是对的,因为它们未能提供最终的业务价值,尽管付出了巨大努力来改进记录系统的可访问性。但是,在较小的公司(或大型公司中更受限的环境)中,SOA 常常声称真正取得了业务成功,因为它们可快速克服集成问题,并将此转变为功能收益。
随着移动终端设备倍增,API 服务更受欢迎,应用程序开发人员需要借助API简单地访问后端功能和数据,API 的关注点不是 SOA 所关注的重用和成本节省。而是可使用性以及API 提供者为吸引开发人员的注意而参与 API 经济中的竞争。
API 通常代表着低级编程代码接口,表示通过 HTTP 提供的简单接口。通常它等同于 REST 接口,这些接口使用 JSON 数据格式(有时为 XML)来提供数据,使用 HTTP 动词 PUT、GET、POST 和 DELETE 来描述创建、读取、更新和删除操作。与早期 SOA 中更流行的基于 SOAP 的 Web 服务标准相比,这些协议和数据格式在使用上更加简单。另外,它们更适合 JavaScript 等在创建 API 请求时常用的语言。
API 的关注点是作为某种向外部用户公开的功能;API 与内部 SOA 服务之间的分界线已变得很明确。随着 API 管理技术日渐成熟,API 已带来了诸如易用性和自我管理等收益。如 图 3 所示。SOA Web 服务和 API 之间的界线现在已经变得有些模糊,而且几乎无关紧要。它们在起源、向哪些用户公开和使用的数据模型上不同,但许多 SOA “服务” 也可以描述为内部 API。
2 关于Microservices
2.1 简介
微服务架构(Microservices)的简单定义是:一种构造应用程序的替代性方法。应用程序被分解为更小、完全独立的组件,这使得它们拥有更高的敏捷性、可伸缩性和可用性。它更适用于应用程序的范围,仅关注一个应用程序内的结构和组件。
2.2 特点
从基本角度讲,微服务是构建 应用程序的替代性架构。它们提供了更好的方法来解耦应用程序边界 内的组件。事实上,如果将微服务称为 “微型组件”,它们的实际性质会更加明确。尽管应用程序在内部被分解为不同的微服务组件,但从外部来看,应用程序仍是相同的。基于微服务的应用程序公开的 API 的数量和粒度不应与将 API 构建为孤立应用程序有任何不同。微服务中的第一个词 “微型” 表示内部组件的粒度,而不是公开的接口的粒度。如下图:
在应用程序内从逻辑上分离组件不是一个新概念。多年以来,大量不同的技术被开发出来,用于实现整个应用程序的各部分的干净分离。应用服务器可在其内部长期运行多个应用程序组件,如下图的中图所示。但微服务更进一步,在这些应用程序组件之间进行了绝对隔离。它们变成网络上单独运行的流程,如下图中的右侧所示。为了实现解耦,微服务开发者还应分割数据模型来与微服务保持一致。
微服务的优势体现在:
- 敏捷性和生产力:采用最适合的语言或框架来编写,并采用最合适的持久性机制。可显著减少要编写的代码量,使维护得到显著简化;
- 可伸缩性:最佳布置,以便充分利用弹性功能,以富有成本效益的方式访问大量资源的原生云环境;
- 恢复能力:独立的运行时可以立即提供与其他组件中的故障独立的恢复能力。借助小心地解耦的设计,比如避免同步依赖关系和使用断路器模式,可以编写每个微服务组件来满足自己的可用性需求,容器等技术和轻量型运行时使微服务组件能够快速且独立地失败,而不是让所有不相关的功能区域都失效。同样地,它们是以一种高度无状态的方式编写的,以便可以立即重新分布工作负载并几乎同时地调出新运行时。
2.3 考虑因素
- 新技术模式:需要大量实验、技能和经验;
- 应用程序适合性:微服务并不适合每个应用程序,将一个复杂的现有应用程序重构到微服务架构中是一项繁重的工作,在以传统方式编写的应用程序达到复杂性的拐点之前,不要使用微服务。但是,要让此方法发挥作用,则需要从一开始就编写一个适当构建的应用程序,并选择在正确的时刻执行过渡;
- 不同的设计范例:
- 接受最终的一致性模型(非强一致);
- 理解如何处理没有中央操作数据存储的事件源应用程序(分布式);
- 如果需要利用重要的快速可伸缩性优势,请确保您的应用程序逻辑是无状态的(无状态);
- 如果与下游组件分离,则需要熟悉异步通信的细微的潜在副作用;
- 理解实现断路器模式的逻辑后果;
- 认识到 HTTP/JSON 通信相比于进程中通信的错误处理限制;
- 考虑链式交互中的网络延迟。
- DevOps 成熟性:微服务需要一种成熟的交付能力。持续集成、部署和全自动测试都必不可少。
2.4 应用场景
场景一:新型创业公司
最初没有现有的架构可用,必须创建一套新应用程序来满足该业务的独特方面该公司的格局可能会从头建立,该公司可以选择将非核心增值业务的部分业务外包,并使用软件即服务 (SaaS) 应用程序来提供客户关系管理功能。主要关注点可能是它在一个持续可用的环境。该公司可能想根据无法预测的客户需求来实现灵活的伸缩(即扩展和精减)。它可能希望实现一种全天候的、高度可用的在线存在感。采用的微服务架构逻辑:
场景二:发展成熟的大型公司
一家已经发展起来的大型公司,可能拥有数百或者甚至数千个重要应用,该公司可能是通过并购竞争对手发展起来的。在该格局中,您会发现应用程序之间存在大量的数据重复,数据可能分散在许多系统中。在此环境中,SOA 的一项艰巨任务就是将后端系统进行集成整合。存在协议、传输和数据格式上的挑战。另外,将数据和功能改造为更加以业务为中心的功能。他们需要确定如何满足移动等新渠道,这些渠道需要使用与传统应用程序完全不同的服务粒度。为了实现这些方面,公司需要提供当前系统中可能没有的响应能力、可用性和可伸缩性。必须编写应用程序,以一种特殊风格满足这些新渠道,这种风格支持快速的敏捷变更,提供了极高的可伸缩性,而且提供了卓越的可用性。大型企业中对微服务的初始使用专注于新的互动参与体系应用程序。如下图:
场景三:微服务、SOA 和 API 组合
深入集成化、服务公开化和服务组件化,如下图。
3 关于ESB
3.1 简介
企业服务总线(Enterprise Service Bus, ESB),由一系列实现标准化集成模式的服务组成的软件架构,通过统一与服务总线通信从而集成不同的应用系统。它可以实现系统间的解耦,通过消除点对点的集成来达到健壮和管理方便的目的。它可以提供组织灵活性,通过系统的“可插拔”特性实现系统的可扩展。但一般采用集中式转发请求,适合大量异构系统集成,侧重任务的编排,性能问题可通过异构的方式来进行规避,无法支持特别大的并发。
3.2 背景
不同应用系统间通过点对点的服务调用,数量较少时还可以管理,如下图6个应用系统:
但当应用系统增多时,服务的管理、协调和维护变的异常复杂,如下图的30个应用系统:
可以使用SOA架构,用ESB提供服务,如下图:
ESB的工作就是提供和调用集成系统的服务。使用了ESB,在大多情况下,每个系统和ESB之间,只需要定义一个访问方法,一个接口。如果这样,像上面的表一样,你有8个系统,就会有16个接口(1个方向1个)需要被创建、维护、管理和关注。如果没有ESB,你就需要56个接口需要去思考和处理。(假设每个系统都需要跟其他系统对话)少了40个接口意味着少花时间和金钱。
3.3 实现要点
- 总线用来解耦不同的应用系统,通常借助消息队列服务器实现,例如JMS或AMQP;
- 数据通常以一种标准的数据格式来进行传输,通常为XML;
- 在总线和应用系统之间存在“适配器”,并封装两者之间的数据;
- “适配器”负责与应用系统通讯,并将应用的数据转化为总线的格式,也可以之心其他活动,例如消息路由、安全验证、监控和错误处理等;
- ESB通常是无状态的,通过总线的消息都是内嵌状态的;
- 系统间约定规范化的消息格式,规范化格式意味着总线中消息格式是一致的,总线中的应用系统可以借助它相互通讯。
3.4 考虑因素
ESB选型的参考因素如下:
- 轻量级:安装简单、能够快速增加/更改功能;
- 不仅仅担中介:还具备二次开发、扩展实现业务逻辑的功能;
- 可访问性:开发者能够借助熟悉的组件,如Maven, Eclipse, JUnit 和 Spring等进行快速开发;
- 伸缩性:可以实现水平扩展,能够在通常的Tomcat/JBosWAS快速部署;
- 消息无关性:不仅支持通用的XML,还支持JSON、无格式文件、二进制、附件、文件流和Java 对象等格式;
- 云化能力:能够在云平台上快速部署、监控及扩展。
3.5 争议
企业服务总线是由一系列实现标准化集成模式的服务组成的软件架构,它的使用程度似乎在下降。纵观历史,ESB一直用于促进中心化,呈辐射型系统的开发。ESB的功能常常用于构建并部署SOA。ESB和SOA一直与缓慢实现的大型系统的关系密切。从web开发人员的角度看,大量的微服务部署到轻量级的Karaf 容器中,这就符合了ESB的定义。微服务是行业发展的方向,Lynden 的软件架构师Rob Terpilowski如是说。它正在使用少量的,不需要企业总线的微型服务进行来来回回的数据传递。“我认为ESB已死,” Terpilowski说。“很长时间之前,我写过关于他们的事。”另外,Java咨询师Matt Brasier和Jeff Genender 说,ESB这一术语已经过时了,SOA也一样,但这些技术却仍然实用。“没有一项技术是真正的死亡,” C2B2咨询公司首席咨询师Matt Brasier说,相反,ESB已经有一小步的发展了。“这只是一次品牌再造运动,例如集成技术,给他们起一个新的名字,然后在这个新名称出售他们。”Java Champion的Genender观察到,ESB现在是集成中间件的一部分,这样许多不同的组织部分可以以松散耦合的方式放到一起。作为复杂实施,ESB术语有消极的一面,但ESB的功能却还是很有价值。
虽然ESB是一个了不起的想法,但很不幸,只是简单的安装ESB不会解决太多问题。最好的方法,需要整理系统架构。如果像下图一样,即使用了ESB也得不到任何好处。
(如果如上图构建你的系统),那么你的it维护人员就会厌恶这个系统(ESB),管理层虽然一开始会容忍ESB,认为他作为一个新系统,总会出点小问题,但是后来它就会成为一个笑柄(系统难以维护)。“什么?这就是你的新杀手锏?哈哈!”。如果ESB不是作为你IT大计划中的重要组成部分来推动事情发展的,那么这种结果不可避免。
二、常见的分布式系统框架
1 Dubbo/Dubbox
1.1 应用需求
- 服务增多时,服务URL的配置管理困难、硬件负载均衡器压力上升;
- 服务依赖关系复杂,需要理清服务依赖关系;
- 服务调用量增加,服务容量增加,需要实时监控服务容量,用于容量规划
1.2 Dubbo架构
节点角色说明:
- Provider: 暴露服务的服务提供方。
- Consumer: 调用远程服务的服务消费方。
- Registry: 服务注册与发现的注册中心。
- Monitor: 统计服务的调用次调和调用时间的监控中心。
- Container: 服务运行容器。
调用关系说明:
- 0. 服务容器负责启动,加载,运行服务提供者。
- 1. 服务提供者在启动时,向注册中心注册自己提供的服务。
- 2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
- 3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
- 4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
- 5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。
(1) 连通性:
- 注册中心负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只在启动时与注册中心交互,注册中心不转发请求,压力较小
- 监控中心负责统计各服务调用次数,调用时间等,统计先在内存汇总后每分钟一次发送到监控中心服务器,并以报表展示
- 服务提供者向注册中心注册其提供的服务,并汇报调用时间到监控中心,此时间不包含网络开销
- 服务消费者向注册中心获取服务提供者地址列表,并根据负载算法直接调用提供者,同时汇报调用时间到监控中心,此时间包含网络开销
- 注册中心,服务提供者,服务消费者三者之间均为长连接,监控中心除外
- 注册中心通过长连接感知服务提供者的存在,服务提供者宕机,注册中心将立即推送事件通知消费者
- 注册中心和监控中心全部宕机,不影响已运行的提供者和消费者,消费者在本地缓存了提供者列表
- 注册中心和监控中心都是可选的,服务消费者可以直连服务提供者
(2) 健状性:
- 监控中心宕掉不影响使用,只是丢失部分采样数据
- 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务
- 注册中心对等集群,任意一台宕掉后,将自动切换到另一台
- 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯
- 服务提供者无状态,任意一台宕掉后,不影响使用
- 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复
(3) 伸缩性:
- 注册中心为对等集群,可动态增加机器部署实例,所有客户端将自动发现新的注册中心
- 服务提供者无状态,可动态增加机器部署实例,注册中心将推送新的服务提供者信息给消费者
(4) 升级性:
- 当服务集群规模进一步扩大,带动IT治理结构进一步升级,需要实现动态部署,进行流动计算,现有分布式服务架构不会带来阻力:
1.3 Dubbox的新特性
当当网根据自身的需求,为Dubbo实现了一些新的功能,并将其命名为Dubbox,特性如下:
- 支持REST风格远程调用(HTTP + JSON/XML);
- 支持基于Kryo和FST的Java高效序列化实现;
- 支持基于嵌入式Tomcat的HTTP remoting体系;
- 升级Spring;
- 升级ZooKeeper客户端;
2 Spring Cloud
TODO
3 Apache Thrift
TODO
4 Twitter Finagle
TODO
三、部署实践
1 Dubbo实践
1.1 部署环境
- 10.10.10.231 dubbo-admin zookeeper
- 10.10.10.232 dubbo-monitor
- 10.10.10.234 dubbo-consumer
- 10.10.10.233 dubbo-provider
1.2 步骤
1.2.1 zookeepler安装
下载安装包zookeeper-3.4.10.tar.gz,解压,修改配置文件,
1 2 3 4 5 | wget http://mirror.bit.edu.cn/apache/zookeeper/stable/zookeeper-3.4.10.tar.gz tar zxvf zookeeper-3.4.10.tar.gz cd zookeeper-3.4.10/conf/ mv zoo_sample.cfg zoo.cfg vim zoo.cfg |
内容如下:
1 2 3 4 5 6 7 | tickTime=2000 initLimit=10 syncLimit=5 dataDir=/opt/zookeeper-3.4.10/data dataLogDir=/opt/zookeeper-3.4.10/log clientPort=2181 server.1=10.10.10.231:2888:3888 |
创建数据及日志目录,并在在数据目录中创建myid文件指定当前服务器的ID,ID需conf/zoo.cfg中的server.ID对应,
1 2 3 | mkdir /opt/zookeeper-3.4.10/data mkdir /opt/zookeeper-3.4.10/log echo "1" > /opt/zookeeper-3.4.10/data/myid |
启动服务器,并检查状态
1 2 3 4 5 6 7 8 | [root@dubbo-admin zookeeper-3.4.10]# ./bin/zkServer.sh start ZooKeeper JMX enabled by default Using config: /opt/zookeeper-3.4.10/bin/../conf/zoo.cfg Starting zookeeper ... STARTED [root@dubbo-admin zookeeper-3.4.10]# ./bin/zkServer.sh status ZooKeeper JMX enabled by default Using config: /opt/zookeeper-3.4.10/bin/../conf/zoo.cfg Mode: standalone |
查看Daemon输出:
1 2 | [root@dubbo-admin zookeeper-3.4.10]# telnet localhost 2181 [root@dubbo-admin zookeeper-3.4.10]# tail -f zookeeper.out |
1.2.2 dubbo各组件的安装
首先下载dubbo源码,编译,打包,这里跳过单元测试。
1 2 3 | [root@dubbo-admin ~]# git clone https://github.com/alibaba/dubbo.git [root@dubbo-admin ~]# cd dubbo/ [root@dubbo-admin dubbo]# mvn package -Dmaven.skip.test=true |
如果有编译错误,尝试解决,直到成功。
1.2.2.1 dubbo-admin
dubbo-admin管控平台安装需要将编译生成的dubbo-admin-2.5.3.war放到tomcat下,启动Tomcat,修改其中的dubbo配置文件,制定zookeeper地址即可。重启Tomcat,浏览器访问dubbo-admin地址:http://10.10.10.231:8080/dubbo-admin-2.5.3/。
1 2 3 4 | [root@dubbo-admin webapps]# vim dubbo-admin-2.5.3/WEB-INF/dubbo.properties dubbo.registry.address=zookeeper://10.10.10.231:2181 dubbo.admin.root.password=root dubbo.admin.guest.password=guest |
用户密码root:root,如下。
1.2.2.2 dubbo-provider
解压安装包,修改配置,制定zookeeper注册中心,启动服务提供者即可
1 2 3 4 5 | [root@dubbo-provider dubbo]# tar zxvf dubbo-demo-provider-2.5.4-SNAPSHOT.tar.gz [root@dubbo-provider dubbo]# cd dubbo-demo-provider-2.5.4-SNAPSHOT/ [root@dubbo-provider dubbo-demo-provider-2.5.4-SNAPSHOT]# vim conf/dubbo.properties [root@dubbo-provider dubbo-demo-provider-2.5.4-SNAPSHOT]# ./bin/start.sh [root@dubbo-provider dubbo-demo-provider-2.5.4-SNAPSHOT]# tail -f logs/stdout.log |
在dubbo-admin中可以查看到服务提供者进驻。
1.2.2.3 dubbo-consumer
同提供者部署相同,如下:
1 2 3 4 5 6 7 | [root@dubbo-consumer dubbo]# cd dubbo-demo-consumer-2.5.4-SNAPSHOT/ [root@dubbo-consumer dubbo-demo-consumer-2.5.4-SNAPSHOT]# vim conf/dubbo.properties [root@dubbo-consumer dubbo-demo-consumer-2.5.4-SNAPSHOT]# ./bin/start.sh Starting the demo-consumer ....OK! PID: 6291 STDOUT: logs/stdout.log [root@dubbo-consumer dubbo-demo-consumer-2.5.4-SNAPSHOT]# tail -f logs/stdout.log |
在dubbo-admin中可以查看到服务消费者进驻。
1.2.2.4 dubbo-monitor
同上面部署,修改配置文件,制定注册中心和端口,然后启动服务。
1 2 3 4 5 6 7 8 | [root@controller01 dubbo]# cd dubbo-monitor-simple-2.5.4-SNAPSHOT/ [root@controller01 dubbo-monitor-simple-2.5.4-SNAPSHOT]# vim conf/dubbo.properties [root@controller01 dubbo-monitor-simple-2.5.4-SNAPSHOT]# ./bin/start.sh Starting the simple-monitor ....OK! PID: 32445 STDOUT: logs/stdout.log [root@controller01 dubbo-monitor-simple-2.5.4-SNAPSHOT]# tail -f logs/stdout.log |
浏览器访问,可以看到
1.3 微服务拆分实践
TODO
2 Zato ESB
2.1 简单部署
为了下载方便,直接下载Zato的Docker镜像,运行容器即可。
1 | # docker pull zatosource/zato-2.0.7-quickstart |
创建容器
1 2 | # docker run --name="zato-quickstart" -dit zatosource/zato-2.0.7-quickstart \ /bin/bash |
创建Zato快速部署环境
1 | # docker exec -t -u root zato-quickstart /opt/zato/create_quickstart.sh |
提交变化的镜像
1 2 3 | # docker commit \ -m "Created a Zato quickstart environment and generated new passwords" \ -a "admin" zato-quickstart zatosource/zato-2.0.7-quickstart:env |
获取web管理系统admin和zato帐号密码,结果会输出到STDOUT,
1 2 | # docker run zatosource/zato-2.0.7-quickstart:env /bin/bash \ -c 'cat /opt/zato/web_admin_password /opt/zato/zato_user_password' |
比如这里会输出admin和zato用户的登录密码
1 2 | 67a8d09e-772f-49d7-8577-6ea549b7700a b3b29408-c191-4afd-8c89-6b2175424aac |
创建运行者Zato组件的镜像
1 2 | docker run -it -p 22 -p 6379:6379 -p 8183:8183 -p 17010:17010 -p 17011:17011 \ -p 11223:11223 zatosource/zato-2.0.7-quickstart:env /bin/bash -c "/opt/zato/start.sh" |
这里环境已经部署好了,本环境为高可用架构,运行着两个server,端口分别为17010和17011,具体端口映射如下:
我们可以浏览器访问web管理系统:http://localhost:8183,将localhost换成宿主机IP,比如:http://192.168.100.81:8183/,利用admin:67a8d09e-772f-49d7-8577-6ea549b7700a登录,
2.2 服务集成实践
2.2.1 简单服务注册
下面根据Zato教程,创建简单服务通过HTTP或Zero MQ集成3个外部系统,并使用JSON或XML作为数据交换格式。消息流向图如下所示:
这里类似一个银行的场景,各部分功能如下:
- 客户端程序:利用用户ID( CUST_ID)获取信息,包括:
- 用户姓名;
- 客户端最后付款时间;
- 付款金额;
- CRM:保存用户数据;
- 付款:保存在其他系统中;
- 欺诈检测系统Frauds: 检测用户的操作,它只能读取用户相关数据,不能真实的在银行账户中转账,我们的服务需要遵循这个原则。
客户端程序可能比较多样,可以是各种形式,但他们都调用Zato的服务,这里我们统一利用curl模拟。
首先进入Zato容器,eb34ac910a61为zatosource/zato-2.0.7-quickstart:env容器运行的容器ID,可以通过docker ps查到:
1 | docker exec -it eb34ac910a61 /bin/bash |
然后在镜像中进行下面的服务创建操作:
服务创建采用热部署的方式,可以通过web-admin或命令行方式,将服务部署到集群中的任何一个节点(server1或server2),命令行热部署方式只需将Python模块拷贝任何一个节点pick-up目录,后台会自动监测到新服务,在不重启任何节点的情况下完成服务的注册。
首先创建my_service.py,内容如下:
1 2 3 4 5 | from zato.server.service import Service class GetClientDetails(Service): def handle(self): self.log_input() |
服务Service为zato.server.service.Service的子类,这里的服务功能非常简单,只打印输入结果。
将服务拷贝到server1的pick-up目录,
1 | root@eb34ac910a61:/opt/zato# cp my_service.py env/qs-1/server1/pickup-dir/ |
查看server1的后台日志,发现已经完成服务的上传:
1 | INFO - zato.hot-deploy.create:33 - Uploaded package id:[1], payload_name:[my_service.py] |
下面通过web-admin将该服务暴露给调用该服务的其他外部系统,访问http://192.168.100.81:8183/,
首先查找下刚才上传的服务,输入get-client或*,查找服务:
通过HTTP暴露服务,在web-admin中,选择Connections -> Channels -> Plain HTTP,点击“Create a new Plain HTTP channel”,输入如下信息:
点击确定,那么这个服务就已经暴露出去了,服务地址就是上面定义的:localhost:11223/tutorial/first-service
现在我们利用curl进行服务调用,如下:
1 | root@eb34ac910a61:/opt/zato# curl localhost:11223/tutorial/first-service |
没有任何输出,这是因为这个服务功能只输出到了结果日志中,我们查看server1的日志可以看到这个服务已经被调用。
1 2 | root@eb34ac910a61:/opt/zato# cat env/qs-1/server1/logs/server.log INFO - 107:Dummy-49 - my-service.get-client-details:800 - {u'impl_name': u'my_service.GetClientDetails', u'name': u'my-service.get-client-details', u'cid': u'K0407DJECVZE2R3PDDFFC9Q2HZEX', u'invocation_time': datetime.datetime(2017, 7, 12, 2, 53, 57, 721805), u'job_type': None, u'data_format': u'json', u'slow_threshold': 99999, u'request.payload': u'', u'wsgi_environ': {u'zato.http.POST': <QueryDict: {}>, u'zato.http.remote_addr': '127.0.0.1', u'zato.request_timestamp_utc': <Arrow [2017-07-12T02:53:57.719914+00:00]>, 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'Zato', u'zato.http.channel_item': Bunch(audit_enabled=False, audit_max_payload=0, audit_repl_patt_type=None, connection=u'channel', data_format=u'json', has_rbac=False, host=None, id=532L, impl_name=u'my_service.GetClientDetails', is_active=True, is_internal=False, match_target=u':::/tutorial/first-service', match_target_compiled=<Matcher at 0x7f352c459c50 :::/tutorial/first-service regex.Regex(u':::/tutorial/first-service$', flags=regex.U | regex.V0)>, merge_url_params_req=True, method=u'', name=u'Get Client Details', params_pri=u'channel-params-over-msg', ping_method=None, pool_size=None, replace_patterns_json_pointer=[], replace_patterns_xpath=[], service_id=375L, service_impl_name=u'my_service.GetClientDetails', service_name=u'my-service.get-client-details', soap_action=u'', soap_version=None, transport=u'plain_http', url_params_pri=u'qs-over-path', url_path=u'/tutorial/first-service'), 'SCRIPT_NAME': '', 'wsgi.input': <gunicorn.http.body.Body object at 0x7f352c542910>, 'REQUEST_METHOD': 'GET', 'HTTP_HOST': 'localhost:11223', 'PATH_INFO': '/tutorial/first-service', 'wsgi.multithread': False, 'QUERY_STRING': '', 'HTTP_CONNECTION': 'close', u'zato.oauth.post_data': {}, 'HTTP_ACCEPT': '*/*', u'zato.local_tz': <StaticTzInfo 'Etc/UTC'>, 'HTTP_USER_AGENT': 'curl/7.35.0', 'wsgi.version': (1, 0), 'REMOTE_PORT': '80', 'RAW_URI': '/tutorial/first-service', 'REMOTE_ADDR': '127.0.0.1', 'wsgi.run_once': False, 'wsgi.errors': <open file '<stderr>', mode 'w' at 0x7f354e28d1e0>, 'wsgi.multiprocess': False, u'zato.request_timestamp': datetime.datetime(2017, 7, 12, 2, 53, 57, 719914, tzinfo=<StaticTzInfo 'Etc/UTC'>), u'zato.http.GET': {}, 'wsgi.url_scheme': 'http', 'gunicorn.socket': <socket at 0x7f352c459dd0 fileno=41 sock=127.0.0.1:17010 peer=127.0.0.1:51310>, 'SERVER_NAME': 'localhost', 'SERVER_PORT': '11223', u'zato.http.response.headers': {u'X-Zato-CID': u'K0407DJECVZE2R3PDDFFC9Q2HZEX'}, 'HTTP_X_FORWARDED_FOR': '127.0.0.1', 'wsgi.file_wrapper': <class gunicorn.http.wsgi.FileWrapper at 0x7f35405f9188>}, u'environ': {}, u'usage': 1, u'channel': u'http-soap'} |
在web-admin中服务详情中也能看到my-service.get-client-details的一些调用信息:
2.2.2 外部系统服务
下面通过HTTP, ZeroMQ 和 JSON让Zato使用外部系统,除非坚持像第一部分的手动注册,服务从不知道调用的确切urls,他们通常被“ outgoing connections”层屏蔽这些信息。下面是 outgoing connections的架构说明:
如上图所示,
- 每一个服务都可以通过一系列的协议(比如HTTP、FTP、SQL DB等)自动获取外部资源。
- 基于HTTP的发布/订阅为应用程序间的直接消息提供了另外一种选择,详细如下:
- 发布/订阅为REST应用提供了消息功能,通过JSON和XML清晰的分割了生产者消息和应用感兴趣的消费消息,从而分割异步操作形成了松散耦合的网络。
- 使用常规REST HTTP 的发布消息通过Zato存储在基于Redis的队列中;
- 这可以形成多个生产者产生topic消息,多个消费者定于topic消息;
- 当新消息到达消息队列时,消费者可以通过HTTP轮询或者为Zato提供REST回调URL来调用;
- Zato保留topics中的消息,直到有消费者准备好消费他们,或者,直到消息过期;
- 消息可以基于FIFO、LIFO、优先级等方式消费;
- 消息一般持久化,取决于Redis存储服务器的配置,重启Zato服务不会影响topics的状态。
- 其中有些协议是同步的, 但通过REST通信变成了异步的;
- 记住要时刻考虑HA高可用,特别是使用AMQP或JMS WebSphere MQ的outgoing connections。
返回来继续谈URL的问题,现在CRM和服务之间可以发布连接请求,当CRM改变了自己的URL,但服务并未发生重新配置,可以通过web-admin输入CRM的新地址,下次服务连接时就会自动更新URL链接。
现在通过 http://tutorial.zato.io/get-customer 和 http://tutorial.zato.io/get-last-payment模拟CRM和Payments,稍后通过ZeroMQ连接欺诈检测系统。
登录Web-admin,选择Connections -> Outgoing -> Plain HTTP ,创建两个新的outgoing connections。
重新编辑my_service.py,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | # Zato from zato.server.service import Service class GetClientDetails(Service): def handle(self): self.logger.info('Request: {}'.format(self.request.payload)) self.logger.info('Request type: {}'.format(type(self.request.payload))) # Fetch connection to CRM crm = self.outgoing.plain_http.get('CRM') # Fetch connection to Payments payments = self.outgoing.plain_http.get('Payments') # Grab the customer info .. response = crm.conn.send(self.cid, self.request.payload) cust = response.data # .. and last payment's details response = payments.conn.send(self.cid, self.request.payload) last_payment = response.data self.logger.info('Customer details: {}'.format(cust)) self.logger.info('Last payment: {}'.format(last_payment)) response = {} response['first_name'] = cust['firstName'] response['last_name'] = cust['lastName'] response['last_payment_date'] = last_payment['DATE'] response['last_payment_amount'] = last_payment['AMOUNT'] self.logger.info('Response: {}'.format(response)) # And return response to the caller self.response.payload = response |
将服务拷贝到server1的pick-up目录,
1 | root@eb34ac910a61:/opt/zato# cp my_service.py env/qs-1/server1/pickup-dir/ |
再次通过curl客户端调用服务:
1 2 3 4 | # curl localhost:11223/tutorial/first-service -d '{"cust_id":123, "cust_type":"A"}' {"first_name": "Sean", "last_name": "O'Brien", "last_payment_date": "2013-05-14T10:42:14.401555", "last_payment_amount": "357"} |
查看server1日志:
1 2 3 4 5 6 7 8 | INFO - Request: {u'cust_id': 123L, u'cust_type': u'A'} INFO - Request type: <type 'dict'> INFO - Customer details: {u'lastName': u"O'Brien", u'firstName': u'Sean'} INFO - Last payment: {u'DATE': u'2013-05-14T10:42:14.401555', u'AMOUNT': u'357'} INFO - Response: {'last_payment_amount': u'357', 'first_name': u'Sean', 'last_name': u"O'Brien", 'last_payment_date': u'2013-05-14T10:42:14.401555'} |
outgoing connections知道如何读取/写入JSON解序列化/序列化request和respose,我们直接传Python的字典类型给CRM和Payments就可以。Channel会序列化response为JSON格式。
TODO:发送异步消息
四、总结
TODO
五、参考文档
微服务与SOA:IBM Developer
Dubbo用户手册: http://dubbo.io/User+Guide-zh.htm
Dubbox Wiki: Home · dangdangdotcom/dubbox Wiki · GitHub
Zato ESB:
https://zato.io/docs/admin/guide/install/dockerhub.html
Spring Cloud: Spring Cloud · GitHub
Thrift官网: Apache Thrift - Home
ESB理解:
Zato | What ESB and SOA are anyway
MuleSoft | Integration Platform for Connecting SaaS and Enterprise Applications