Zato是一个用Python编写的开源ESB和应用服务器。按照设计,它用于构建后端应用程序(即仅是API)和在SOA中整合系统。
\查看Zato的项目文档点击这里,查看其GitHub页点击这里。
\Zato的目标用户是使用Python或者Ruby和PHP等其它动态语言的开发人员,或者是那些考虑在工作中尝试动态语言的技术团队,后者或是因为看到动态语言在其它地方使用,或是因为愿意尝试用其中一种动态语言编写的非前端系统。
\该平台是轻量级但完整的,它涵盖了架构师、程序员或者系统管理员的所有视角,对许多特性提供开箱即用的支持,包括HTTP、SON、SOAP、SQL、AMQP、JMS WebSphere MQ、ZeroMQ、Redis NoSQL、FTP、基于浏览器的GUI、CLI、API、安全、统计、作业调度、负载均衡和热部署。另外,它还提供了大量指南和参考样式方面的文档。
\它的最初版本于2013年5月18日发布,最新的1.1版在6月初发布。
\Zato环境是一个或者多个集群的集合。每个集群由多个共享同一个SQL和Redis数据库的服务器构成。这些服务器的前端是集群专属的HA HTTP负载均衡器。
\所有的服务器始终处于活动状态,并且总是运行同一组服务。为了实现active-standby设定,负载均衡器可以根据需要把任意服务器离线。
\负载均衡器是一个嵌入式的HAProxy实例,管理员通过命令行或者通过GUI调用SSL XML-RPC对其进行远程控制,该过程可以用也可以不用客户端证书。用户可以给服务器赋予权重,以及使用HAProxy自身提供的其它功能,如连接ACL或速率限制。
\服务器基于gunicorn/gevent项目构建。该项目是一个联合体,它使用libevent来选择每个平台支持的最佳异步事件通知库,如Linux平台上的epoll。
\为了充分利用单台计算机能够提供的所有CPU,Zato按照设定好的数值预先生成一定数量的工作进程,每个进程使用选定的异步网络连接库来处理连接,所有进程监听相同的套接字。负载均衡器用于在不同的计算机之间分配负载及提供HA。
\集群中的一台服务器担当启动AMQP/JMS WebSphere MQ/ZeroMQ资源调度器和连接器的角色。如果这台服务器意外宕机,那么始终处于活动状态的ping机制可以保证另外一台服务器接管这个角色。
\应用程序可以使用多种协议进行集成,包括HTTP(对JSON/SOAP和纯XML有特殊支持)、FTP、AMQP、JMS WebSphere MQ(用于实现与现有的MQ Java应用程序之间的无缝互通性)、Redis和SQL。其中,HTTP协议是同步调用Zato服务的唯一方式。这种情况下,请求应用程序会进入阻塞状态等待响应。
\程序员可以使用任何Python库。如果Zato自身尚未提供某个功能,也可以使用其它技术实现,如XML-RPC或者SMTP,该过程仅是一个导入Python内置包的问题而已。
\集群管理使用基于浏览器的GUI和CLI。前者主要用于管理处于运行状态的集群,后者则用于在操作系统中安装Zato组件,如服务器。
\集群配置信息存储在Redis和SQL操作型数据库中。Redis存储快速变化和频繁更新的数据,如统计信息或用户的运行时信息,而SQL ODB存储可以简单地映射成关系结构的数据。
\虽然主要使用GUI进行环境配置,但也可以将集群的配置信息以JSON格式导出/导入,而且导出结果可以存储到一个外部的配置版本库,从而可以对其进行版本管理、标记或者版本间差异比较。
\带有GUI的内置调度器可以用于一次性作业或者循环作业(也可以用Cron语法)。
\服务器和服务只通过Redis和SQL ODB进行状态共享。没有自定义的协议或者数据格式用于保持服务器状态的一致性。
\Zato使用超过160个自带的管理服务来进行自我管理,其中每一项都可以通过命令行或者在HTTP上以JSON/SOAP方式调用公共API获得。GUI和CLI工具本身都是这些服务的客户端。
\Zato已经为Python应用程序创建了方便的客户端,所以用Python编写的应用程序在与Zato暴露的服务进行通信时还是只能使用Python对象。
\Zato服务是实现了单个特定方法的Python类。它可以接收输入和产生输出,也可以只接收输入或者只产生输出。
\服务可以从GUI或者命令行以静态的或者热部署的方式安装。安装过程会自动将服务编译成字节码。
\服务可以使用任何数据格式,但是Zato对JSON、SOAP和普通XML提供更多支持。如果使用了其中任何一种,序列化和反序列化都会在后台完成,开发人员只需通过点号就可以使用纯Python对象,而不必基于诸如XSD那样的模式创建Bean/模型/存根/类——尽管这意味着将没有代码完成。
\同一服务可以在HTTP、AMQP、JMS WebSphere MQ、ZeroMQ和调度器上暴露,而不需要修改任何代码,也不需要重启服务器。特别地,只有在HTTP上暴露的服务可以进行同步调用。
\SimpleIO(SIO)是一种声明式语法,用于表示简单的请求和响应。选用该语法,服务可以在不修改代码的情况下通过JSON或者XML/SOAP暴露。SIO不能处理复杂文档,它不接受任意嵌套的结构。任何结构的任何文档都可以用于Zato,只是有时不能用于SIO。
\下面是一个基础服务的示例,该服务使用Yahoo YQL/JSON和Google’s XML API获取一家公司的市场资本总额。
\该服务接收一个股票代码(例如GOOG或RHT),发出两个HTTP请求,然后清理响应并把它们组合成一个通用格式。根据请求格式的不同,组合结果以JSON或XML格式返回。
\\# anyjson\from anyjson import loads \\# bunch\from bunch import bunchify \\# decimal\from decimal import Decimal \\# lxml\from lxml.objectify import fromstring \\# Zato\from zato.server.service import Service \\class GetMarketCap(Service):\\"\"\" 根据公司股票代码返回其市场资本总额,单位是10亿USD。 \\\"\"\"\class SimpleIO:\response_elem = 'market_cap'\input_required = ('symbol',)\output_required = ('provider', 'value') \\def handle(self): \\# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\# Yahoo\# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\# 通过名称获取到Y!的连接\yahoo = self.outgoing.plain_http.get('Yahoo! YQL') \\# 创建用于YQL查询的URL参数。\q = 'select * from yahoo.finance.quotes where symbol=\"{}\"'.format(\self.request.input.symbol)\url_params = {'q':q, 'format':'json', \\'env':'http://datatables.org/alltables.env'} \\# 调用 Y!,并从JSON响应创建一个bunch实例,这样就可以用点号\# 引用这些元素。\yahoo_response = bunchify(loads(yahoo.conn.get(self.cid, url_params).text)) \\# 清理Y!的响应——如果有业务响应,就去掉最后一个字符。\# 假设响应总是以10亿为单位。\if yahoo_response.query.results.quote:\value1 = yahoo_response.query.results.quote.MarketCapitalization\value1 = Decimal(value1[:-1]) if value1 else 'n/a'\else:\value1 = 'n/a' \\# 一个新的响应条目会附加到条目列表,根据服务调用方式的不同,\#Zato会把它序列化为JSON或者XML。 \\item1 = {'provider':'Yahoo!', 'value': str(value1)}\self.response.payload.append(item1) \\# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\# Google\# . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . \\# 通过名称获取到Google的连接\google = self.outgoing.plain_http.get('Google Finance') \\# 创建用于调用Google的URL参数\url_params = {'stock':self.request.input.symbol} \\# 调用 Google并从XML响应创建一个Objectify实例,这样就可以\# 用点号引用这些元素。\google_response = fromstring(google.conn.get(self.cid, \\url_params).text) \\# 清理Google的响应——如果有业务响应,就将百万转换成十亿。\if hasattr(google_response.finance, 'market_cap'):\value2 = Decimal(google_response.finance.market_cap.get('data')) / 1000\else:\value2 = 'n/a' \\# 此外,将一个纯Python字典(hashmap)附加到响应对象,并由Zato \# 完成序列化\item2 = {'provider':'Google', 'value': str(value2)}\self.response.payload.append(item2) \\
这是一个非常简单的集成示例,而并不是所有的场景都允许使用SIO,但是不管服务多复杂,有一点需要强调,就是不应该回避使用Python进行编码。除了可执行外,从许多方面看来,它都非常像伪代码——这里有更多的使用示例可以说明这一点。
\事实上,这正是使用Python创建Zato的原因——作为一种十分高级的语言,Python非常令人满意,它尽可能的减少麻烦和语言怪癖,使开发人员可以专注于集成。同时,它还是一种真正通用的编程语言,而不是一种有局限性、可能图形化和领域专属的第四代语言。
\回到上面的例子,可以使用GUI创建需要的资源——本例中是“Yahoo!YQL”和“Google财经”的传出连接。也可以从命令行使用JSON配置完成上述操作,但还是会显示图形界面。
\ \ \这些独有的API不需要安全性保证,但是如果需要,则可以使用“HTTP基础认证(HTTP Basic Auth)”、“WS-Security用户名令牌(WS-Security Username Tokens)”或者技术账户(与HTTP基础认证类似,但是不需要BASE64)。
\现在,服务可以通过GUI或者命令行进行热部署了。此处使用后者:
\\$ cp stockmarket.py /opt/server1/pickup-dir\\
确认信息会写到服务器日志:
\\2013-06-20 19:25:16,115 - INFO - Uploaded package id:[53],\payload_name:[stockmarket.py]\\
接下来,可以使用Zato的CLI从命令行调用它,也可以从GUI调用它。这里采用前一种方法,并使用JSON和纯HTML两种格式:
\\$ zato service invoke /opt/server1 stockmarket.get-market-cap \\\--payload '{\"symbol\":\"GOOG\"}'\{u'market_cap': [\{u'value': u'298.8', u'provider': u'Yahoo!'},\{u'value': u'298.81505', u'provider': u'Google'}\]}\$ \\
\$ zato service invoke /opt/server1/ stockmarket.get-market-cap --data-format \\xml \\\--transport soap --payload '\\\\u0026lt;soapenv:Envelope \\xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" \\\xmlns:zato=\"https://zato.io/ns/20130518\"\u0026gt; \\\\u0026lt;soapenv:Body\u0026gt; \\\\u0026lt;zato:request\u0026gt; \\\\u0026lt;zato:symbol\u0026gt;IBM\u0026lt;/zato:symbol\u0026gt; \\\\u0026lt;/zato:request\u0026gt; \\\\u0026lt;/soapenv:Body\u0026gt; \\\\u0026lt;/soapenv:Envelope\u0026gt;' \\\u0026lt;market_cap\u0026gt;\\u0026lt;zato_env\u0026gt;\\u0026lt;cid\u0026gt;K255124128065587321859442392853212603320\u0026lt;/cid\u0026gt;\\u0026lt;result\u0026gt;ZATO_OK\u0026lt;/result\u0026gt;\\u0026lt;/zato_env\u0026gt;\\u0026lt;item_list\u0026gt;\\u0026lt;item\u0026gt;\\u0026lt;provider\u0026gt;Yahoo!\u0026lt;/provider\u0026gt;\\u0026lt;value\u0026gt;8.763\u0026lt;/value\u0026gt;\\u0026lt;/item\u0026gt;\\u0026lt;item\u0026gt;\\u0026lt;provider\u0026gt;Google\u0026lt;/provider\u0026gt;\\u0026lt;value\u0026gt;8.76324\u0026lt;/value\u0026gt;\\u0026lt;/item\u0026gt;\\u0026lt;/item_list\u0026gt;\\u0026lt;/market_cap\u0026gt;\$ \\
CLI只是一种管理员或者开发人员快速访问服务的方式,而实际上,服务现在已经加载到一个安全的HTTP通道上,客户端应用程序可以通过该通道访问服务。服务器不需要重启。
\服务使用Python编写,但如果需要,也可以使用C或C++创建服务。
\服务已从传输层解耦,因此,除非开发人员坚持深入研究底层细节,否则他们可以专注于数据校验、扩展、转换、路由或者调用其它服务。服务管理可以使用基于浏览器的GUI、CLI或者JSON配置。
\Zato还将服务与安全机制隔离——服务遵循这样的假设,如果它们被调用,那么前一层已经处理了授权/认证。
\一个服务可以同时通过多种通道暴露,每一个通道使用不同的数据格式、安全定义或传输协议。
\如果使用JSON/XML/SOAP,服务会收到一个美观的Python对象作为输入,程序员可以通过常规的点号访问该对象(例如,request.customer.payment.date),而不需要手动序列化/反序列化。如果使用SimpleIO(SIO),不管使用哪种数据格式,都会产生相同的对象,所以相同的服务可以通过多种通道暴露给客户端应用程序,并且每种通道都可以使用三种数据格式中的任意一种。
\“钩子(Hook)”可以用来影响服务的生命周期,实现跨服务的代码共用(这是除Python类继承之外的另一种代码共用方式)。
\任何同步调用都发生在同一操作系统级别的进程和线程,因此,操作系统可以捕获服务抛出的任何异常,并进行现场回溯(堆栈跟踪)。
\异步调用需要通过Redis路由。消息首先发布到Redis上,另一个服务——可能在另一台服务器上——取得该请求。这也是调度器的工作原理,作业(服务)运行请求发布到Redis上,目标服务接收该请求作为输入。
\有一点需要注意,所有围绕数据来源、格式和传输协议的抽象编程仅仅是为开发人员提供方便。例如,如果有一个需求,需要实现一个Zato自身没有提供的功能,那么开发人员总是可以直接访问原始的字节/字符串消息。
\在活动集群上所做的几乎所有操作,如部署或重新配置,都不需要重启服务器和中断消息流。
\GUI是在Django中使用手写HTML/CSS/jQuery的方式编写而成。开发过程遵循诸如Stephen Few那样的信息设计和可用性专家所制定的指南。
\它有如下主要特征:
\服务
\CLI
\命令行接口可以用于执行一系列管理操作,包括创建集群组件、对组件进行完整性检查、启动、停止或更新组件或管理加密资料。
\下文会对两个命令进行说明。第一个针对正在运行的组件取得其操作系统级的信息。另一个用于创建完整的工作环境,该环境将包含两个服务器、Web管理界面和一个负载均衡器,每个组件均使用随机产生的加密资料,所有的组件都已经完成配置和设置工作。
\\$ zato info /opt/z1/load-balancer\+--------------------------------+--------------------------------+\| Key | Value |\+=========================+================+\| component_details | {\"created_user_host\": |\| | \"dev1@box1\