在聊ICE以前,咱们说说目前主流的几个微服务架构方案。php
因为 Spring 社区的影响力和 Netflix 的背书,目前能够认为是构建 Java 微服务的一个社区标准,Spring Boot 目前在 GitHub 上有超过 20k 星。前端
基于 Spring 的框架本质上能够认为是一种 RESTful 框架
(不是 RPC 框架),序列化协议主要采用基于文本的 JSON,通信协议通常基于 HTTP。RESTful 框架自然支持跨语言,任何语言只要有 HTTP 客户端均可以接入调用,可是客户端通常须要本身解析 payload。目前 Spring 框架也支持 Swagger 契约编程模型,可以基于契约生成各类语言的强类型客户端,极大方便不一样语言栈的应用接入,可是由于 RESTful 框架和 Swagger 规范的弱契约特性,生成的各类语言客户端的互操做性仍是有很多坑的。vue
Dubbo 是阿里多年构建生产级分布式微服务的技术结晶,服务治理能力很是丰富,在国内技术社区具备很大影响力,目前 github 上有超过 16k 星。Dubbo 本质上是一套基于 Java 的 RPC 框架
,当当 Dubbox 扩展了 Dubbo 支持 RESTful 接口暴露能力。java
Dubbo 主要面向 Java 技术栈,跨语言支持不足是它的一个弱项,另外由于治理能力太丰富,以致于这个框架比较重,彻底用好这个框架的门槛比较高,可是若是你的企业基本上投资在 Java 技术栈上,选 Dubbo 可让你在服务框架一块站在较高的起点上,不论是性能仍是企业级的服务治理能力,Dubbo 都作的很出色。新浪微博开源的 Motan(GitHub 4k stars)也不错,功能和 Dubbo 相似,能够认为是一个轻量裁剪版的 Dubbo。python
gRPC 是谷歌近年新推的一套 RPC 框架
,基于 protobuf 的强契约编程模型,能自动生成各类语言客户端,且保证互操做。支持 HTTP2 是 gRPC 的一大亮点,通信层性能比 HTTP 有很大改进。Protobuf 是在社区具备悠久历史和良好口碑的高性能序列化协议,加上 Google 公司的背书和社区影响力,目前 gRPC 也比较火,GitHub 上有超过 13.4k 星。ios
目前看 gRPC 更适合内部服务相互调用场景,对外暴露 RESTful 接口能够实现,可是比较麻烦(须要 gRPC Gateway 配合),因此对于对外暴露 API 场景可能还须要引入第二套 RESTful 框架做为补充。整体上 gRPC 这个东西还比较新,社区对于 HTTP2 带来的好处还未造成一致认同,建议谨慎投入,能够作一些试点。git
ZeroC IceGrid做为一种微服务架构,它基于RPC框架发展而来,具备良好的性能与分布式能力。不过尴尬的是,在国内,彷佛使用它的案例并很少,就我所知,目前Skpye内部一些地方在使用Ice。不过这并不影响它的优势,那就是它的性能很不错,如下是源自网上的性能测试:es6
以下所示是它的总体示意图:github
IceGrid具有微服务架构的以下明显特征:web
微服务架构须要一个集中的服务注册中心,以及某种服务发现机制。IceGrid服务注册采用XML文件来定义,其服务注册中心就是Ice Registry,这是一个独立的进程,而且提供了HA高可用机制;对应的服务发现机制就是命名查询服务,即LocatorService提供的API,能够根据服务名查询对应的服务实例可用地址。
微服务架构中的每一个微服务一般会被部署为一个独立的进程,当无状态服务时,通常会由多个独立进程提供服务。对应在IceGrid里,一个IceBox就是一个单独的进程,当一个IceBox只封装一个Servant时,就是一个典型的微服务进程了。
微服务架构中一般都须要内嵌某种负载均衡机制。在 IceGrid 里是经过客户端API内嵌的负载均衡算法实现的,相对于采用中间件Proxy转发流量的方式(如SpringCloud),IceGrid的作法更加高效,但增长了平台开发的工做量与难度,由于采用各类语言的客户端都须要实现一遍负载均衡的算法逻辑。
一个好的微服务架构平台应该简化和方便应用部署。咱们看到 IceGrid提供了grid.xml来描述与定义一个基于微服务架构的Application,一个命令行工具一键部署这个Application,还提供了发布二进制程序的辅助工具——icepatch2。下图显示icepatch2的工做机制,icepatch2server相似于FTP Sever,用于存放要发布到每一个Node上的二进制代码与配置文件,而位于每一个Node上的icepatch2client则从icepatch2server上拉取文件,这个过程当中采用了压缩传输及差量传输等高级特性,以减小没必要要的文件传输过程。客观地评价,在Docker技术以前,icepatch2这套作法仍是很先进与完备的,也大大减小了分布式集群下微服务系统的运维工做量。
若是基于IceGrid开发系统,则一般有三种典型的技术方案,下图展现了这三种技术方案:
其中方案一是比较符合传统Java Web项目的一种渐进改造方案,Spring Boot里只有Controller组件而没有数据访问层与Service对象,这些Controller组件经过Ice RPC方式调用部署在IceGrid里的远程的Ice微服务,面向前端包装为REST服务。此方案的总体思路清晰,分工明确。Leader在开源项目中给出了这种方式的一个基本框架以供参考:github.com/MyCATApach.…
方案二与方案三则比较适合前端JavaScript能力强的团队,好比很擅长Node.js的团队能够考虑方案二,即用JavaScript来替代Spring Boot实现REST服务。主要作互联网App的系统则能够考虑方案三,浏览器端的JavaScript以HTML5的WebSocket技术与Ice Glacier2直接通讯,总体高效敏捷。
IceGrid在3.6版本以后还增长了容器化的运行方式,即Ice Node与Ice Registry能够经过Docker容器的方式启动,这就简化了IceGrid在Linux上的部署。对于用Java编写的Ice微服务架构系统,咱们还能够借助Java远程类加载机制,让每台Node自动从某个远程HTTP Server下载指定的Jar包并加载相关的Servant类,从而实现相似Docker Hub的机制。下图显示了前面提到mycat-ice开源项目时给出的具体实现方案。
如下是我画的一个简单运做流程图:
如下是我以前写的Ice调用ElasticSearch中间件的代码,仅作参考:
package com.bigdata.cloudshield.pentration.adapter;
import com.bigdata.cloudshield.pentration.adapter.entity.EsInsertRequest;
import com.bigdata.cloudshield.pentration.adapter.entity.EsRequest;
import com.bigdata.cloudshield.pentration.adapter.entity.EsResponseException;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.ice.common.CommonServicePrx;
import com.ice.common.CommonServicePrxHelper;
/** * Description: ice工具类 <BR> * * @author ran.chunlin * @date 2018/8/20 11:00 */
class IceUtil {
/** * 初始化参数 */
private static String[] iniParams = new String[]{"--Ice.MessageSizeMax=2097152", "--Ice.ThreadPool.Client.Size=4", "--Ice.ThreadPool.Client.SizeMax=4"};
/** * Communicator */
private static Ice.Communicator ic = Ice.Util.initialize(iniParams);
/** * 代理对象 */
private static Ice.ObjectPrx pro = ic.stringToProxy(EsConfig.EsDaoConf.getEsDaoPath());
/** * 服务 */
private static CommonServicePrx proxy = CommonServicePrxHelper.checkedCast(pro);
/** * json转换器 */
private static JsonParser jsonParser = new JsonParser();
/** * 返回码 */
private static final String RESULTCODE = "resultCode";
/** * 结果 */
private static final String RESULT = "result";
/** * cat结果 */
private static final String CATRESULT = "catResult";
/** * 异常 */
private static final String EXCEPTION = "exception";
/** * 正常状态码 */
private static final int OK_CODE = 200;
/** * 通用 * * @param request 请求实体 * * @return json结果 * * @throws EsResponseException 返回异常 */
public static String getResponseJson(EsRequest request) throws EsResponseException {
String json = GsonUtil.toJson(request);
String result = proxy.request(1500, json);
JsonObject response = jsonParser.parse(result).getAsJsonObject();
if (response.get(RESULTCODE).getAsInt() == OK_CODE) {
String resultStr = response.get(RESULT).toString();
response = null;
return resultStr;
} else {
handleCode(response);
return null;
}
}
/** * insert * * @param request 请求实体 * * @throws EsResponseException 返回异常 */
public static void getResponseForInsert(EsInsertRequest request) throws EsResponseException {
String json = GsonUtil.toJson(request);
String result = proxy.request(1501, json);
JsonObject response = jsonParser.parse(result).getAsJsonObject();
handleCode(response);
}
/** * 处理返回码 * * @param response 结果 * * @throws EsResponseException 返回异常 */
private static void handleCode(JsonObject response) throws EsResponseException {
int code = response.get(RESULTCODE).getAsInt();
String message = response.get(EXCEPTION).getAsString();
switch (code) {
case 200:
break;
case 101:
throw new EsResponseException("参数中缺乏必要key!\n" + message);
case 102:
throw new EsResponseException("将参数以字符串格式的json转换成map的时候异常!\n" + message);
case 103:
throw new EsResponseException("xpack token 认证失败!\n" + message);
case 104:
throw new EsResponseException("新增删除修改dataList无数据!\n" + message);
case 105:
throw new EsResponseException("修改数据时未能找到数据id!\n" + message);
default:
if (message.contains("no such index")) {
throw new EsResponseException("索引不存在!\n" + message);
} else {
throw new EsResponseException("捕获到的未知异常!\n" + message);
}
}
response = null;
}
/** * update * * @param request 请求实体 * * @throws EsResponseException 返回异常 */
public static void getResponseForUpdate(EsInsertRequest request) throws EsResponseException {
String json = GsonUtil.toJson(request);
String result = proxy.request(1502, json);
JsonObject response = jsonParser.parse(result).getAsJsonObject();
handleCode(response);
}
/** * cat查询 * * @param request 请求实体 * * @return json结果 * * @throws EsResponseException 返回异常 */
public static String getResponseForCat(EsRequest request) throws EsResponseException {
String json = GsonUtil.toJson(request);
String result = proxy.request(1504, json);
JsonObject response = jsonParser.parse(result).getAsJsonObject();
if (response.get(RESULTCODE).getAsInt() == SUCCESS_CODE) {
return response.get(CATRESULT).getAsString();
} else {
handleCode(response);
return null;
}
}
}
复制代码
如下是我以前写的Ice调用elasticsearch中间件的代码,仅作参考:
# -*- coding:utf-8 -*-
import sys
import traceback
import Ice
import os
import json
from cn.localhost.Config import es_dao_address, xpackToken
# 动态加载slice文件并编译
Ice.loadSlice(os.path.dirname(os.path.realpath(__file__)) + './Es.ice')
from com.ice import *
reload(sys)
sys.setdefaultencoding('utf8')
class EsUtil:
__ic = Ice.initialize(['--Ice.MessageSizeMax=0'])
__ObjectPrx = __ic.stringToProxy(es_dao_address)
__proxy = common.CommonServicePrx.checkedCast(__ObjectPrx)
if not __proxy:
raise RuntimeError('init ice_proxy error')
@staticmethod
def __search(endpoint, query={}, source_arr=[], sort={}, size=2000):
try:
params = {'xpackToken': xpackToken, 'method': 'POST', 'endpoint': endpoint,
'params': {'query': query, '_source': source_arr, 'sort': sort, 'size': size}}.__str__().replace(
'\'', '"').replace(': u"', ': "')
return json.loads(EsUtil.__proxy.request(1500, params))
except:
print traceback.format_exc()
@staticmethod
def __search_scroll(endpoint, time, scroll_id):
try:
params = {'xpackToken': xpackToken, 'method': 'POST', 'endpoint': endpoint,
'params': {'scroll': time, 'scroll_id': scroll_id}}.__str__().replace(
'\'', '"').replace(': u"', ': "')
return json.loads(EsUtil.__proxy.request(1500, params))
except:
print traceback.format_exc()
@staticmethod
def search(index, query={}, source_arr=[], sort={}, size=2000):
return EsUtil.__search('/' + index + '/_search', query, source_arr, sort, size)
@staticmethod
def search_by_scroll(index, query={}, source_arr=[], sort={}, size=2000, time='5m'):
result_array = []
try:
# 1.首次读取
es_result = EsUtil.__search('/' + index + '/_search?scroll=' + time, query, source_arr, sort, size)[
'result']
if len(es_result['hits']['hits']) == 0:
return result_array
# 2.写入结果集
for hit in es_result['hits']['hits']:
record = []
for field in source_arr:
if field in hit['_source']:
record.append(hit['_source'][field])
result_array.append(record)
# 3.判断是否还存在未读scroll数据
if '_scroll_id' in es_result:
scroll_id = es_result['_scroll_id']
else:
return result_array
# 4.循环scroll读取
while True:
es_result = EsUtil.__search_scroll('/_search/scroll', time, scroll_id)['result']
if len(es_result['hits']['hits']) == 0:
break
# 写入结果集
for hit in es_result['hits']['hits']:
record = []
for field in source_arr:
if field in hit['_source']:
record.append(hit['_source'][field])
result_array.append(record)
# 判断是否还存在未读scroll数据
if '_scroll_id' in es_result:
scroll_id = es_result['_scroll_id']
else:
break
except:
print traceback.format_exc()
return result_array
@staticmethod
def destroy_ice():
if EsUtil.__ic:
EsUtil.__ic.destroy()
复制代码
摘录: