2016年6月29日星期三

Fuse_021:选择 Spring 还是 Blueprint ?

JBoss Fuse 内置支持两种依赖注入框架: Spring XML 和 Blueprint XML。
你可以使用任意一种, 或者两种都使用。
总的来说, Spring XML 和 Blueprint XML 提供的功能类似, 但 Blueprint 是一个更轻量级的框架, 由于 Blueprint 完全符合 OSGI 规范,因此更适合 OSGI 容器。
Spring 的配置文件位于 InstallDir/src/main/resources/META-INF/spring/*.xml。
Blueprint 的配置文件位于 InstallDir/src/main/resources/OSGI-INF/blueprint/*.xml。

Blueprint 强于 Spring 的另外一个特点是:运行时自动解决依赖。
使用 Blueprint 时,当通过 XML schema namespaces 加入新的依赖时, Blueprint 可以在运行时自动解决依赖,其原理如下:
当发现新的 XML schema namespaces 时,它搜索 classpath 中的 JAR 文件,看看谁实现了这个新的 namespace,并将其加载。
而使用 Spring 时,项目打包为 OSGi bundle 时,Spring 需要你在 maven-bundle-plugin 配置中显式增加依赖,这是因为 maven-bundle-plugin 不能通过扫描 Spring XML 文件中新增的 XML schema namespaces 自动发现依赖。

另外,Spring Dynamic Modules 可以简化应用和 OSGi container 之间的集成。Spring DM 特别提供了 XML elements 可以很容易的导出和消费 OSGi services。
但是,Spring DM 是 Spring Source(www.springsource.org/osgi/)提供的框架,目前已经处于死亡状态。
Blueprint 同样也可以简化应用和 OSGi container 之间的集成。Blueprint 也提供了 XML elements 可以很容易的导出和消费 OSGi services。

JBoss Fuse 6.2.1 版本的发布说明中明确指出:

SPRING DYNAMIC MODULES (SPRING-DM) IS DEPRECATED

Spring-DM (which integrates Spring XML with the OSGi service layer) is deprecated in 6.2.1 and you should use the Blueprint framework instead. Using Blueprint does not prevent you from using the Spring framework: the latest version of Spring is compatible with Blueprint.

结论:选择 Blueprint 作为 JBoss Fuse 依赖注入框架。

参考文献:
1. http://stackoverflow.com/questions/10008786/osgi-blueprint-vs-spring-dm
2. https://developer.jboss.org/thread/229349?tstart=0

Architect_023:怎样解决集群的脑裂问题?(摘录+整理)

脑裂问题说的是一个集群如果发生了网络故障,很可能出现一个集群分成了两部分,而这两个部分都不知道对方是否存活,不知道到底是网络问题还是机器宕机了,所以这两部分各自继续服务,即使在网络恢复后,还是会出现两个大脑各自分而治之,而导致整个集群的行为不一致。
解决脑裂有三种方式:
1. 超过半数的法定代表人选举 Leader 制度(Quorums)
2. 集群中采用多种冗余通信方式,防止一种通信方式失效导致集群中的节点无法通信。
3. 共享资源的方式(Fencing),能看到共享资源就表示在集群中,能够获得共享资源的锁的就是 Leader,看不到共享资源的,就不在集群中。

这里只介绍 Quorums。
Quorums 是怎么解决脑裂的问题的呢?
比如 100 个节点组成的集群,按照 Quorums的方式至少要 51 个节点才能保证选出 Leader。
如果网络问题导致分为两个部分,比如 50 个节点和 50 个节点,这样,因为无法选举出 Leader,整个集群是不可用的。
从而避免了脑裂问题。

// TODO 对客户端来说.....

那么为什么使用 Quorums,集群中的节点数为奇数?
回答:因为奇数的节点数组成的集群的容忍度(允许宕机的节点数)更高。
比如 3 个节点的集群,Quorums=2,集群允许 1 个节点失效,这时候还能选举出 Leader,集群还可用,如果2个节点失效,集群失效。
比如 4 个节点的集群,Quorums=3,集群允许 1 个节点失效,这时候还能选举出 Leader,集群还可用,如果2个节点失效,集群失效。
所以 4 个节点的集群的容忍度 = 3 个节点的集群的容忍度,但是 4 个节点的集群多了 1 个节点,相当于浪费了资源。

小结:使用 Quorums,整个集群是否可用,取决于整个集群是否能够选举出 Leader。

参考文献:
1. http://suroot.cn/306.html
2. http://blog.chinaunix.net/uid-20726500-id-5473292.html

Java_024:JAXB 批注介绍

1. 批注说明

1.1 @XmlRootElement
XML 文件内容的根元素,必须表明这个注解。
默认的名称是字段名称的全小写,也可以修改名称,并指定一个命名空间。
@XmlRootElement(name="b" nameSpace="http://test")

1.2 @XmlElement
表明该字段是一个 XML 元素。
默认的名称是字段名称的全小写,也可以修改名称。
@XmlElement(name="email-address")

1.3 @XmlAttribute
表明该字段是一个 XML 属性。

1.4 @XmlValue
表明该字段作为父 XML 元素的值输出。

1.5 @XmlAccessorType
是否对字段或 JavaBean 属性进行序列化。

@XmlAccessorType(XmlAccessType.PUBLIC_MEMBER)
可以设置的值有:
(1)FIELD 类中的每个非静态、非瞬态字段将会自动绑定到 XML,除非由 XmlTransient 注解。
(2)NONE 类中的所有字段或属性都不能绑定到 XML,除非使用 JAXB 注解专门对它们进行注解。
(3)PROPERTY 类中的每个get/set方法将会自动绑定到 XML,除非由 XmlTransient 注解。
(4)PUBLIC_MEMBER 每个公共get/set方法对和每个公共字段将会自动绑定到 XML,除非由 XmlTransient 注解。

1.6 @XmlTransient

2. 使用例子

(1)@XmlAccessorType(XmlAccessType.FIELD)

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Boy {
    String name = "CY";
}
生成的 XML 如下:
?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<boy>
  >name>CY</name>
</boy>

(2)@XmlAccessorType(XmlAccessType.PROPERTY)

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Boy {
    String name = "CY";
}
因为 name 没有对应的get/set方法,因此 name 不是属性(因为没有 get set方法),所以 name 不能转换成标签。
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<boy/>

(3)@XmlAccessorType(XmlAccessType.PROPERTY)

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Boy {
    String name = "CY";

    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<boy>
  <name>CY</name>
</boy>

(4)@XmlAccessorType(XmlAccessType.PROPERTY)

@XmlRootElement
@XmlAccessorType(XmlAccessType.PROPERTY)
public class Boy {
    @XmlElement
    int age = 10;
    String name = "CY";

    public String getName() {
       return name;
    }
    public void setName(String name) {
       this.name = name;
    }
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<boy>
  <age>10</age>
  <name>CY</name>
</boy>

(5)List 字段

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    private List<String> emailAddresses;

    public Customer() {
        emailAddresses = new ArrayList<String>();
    }

    public List<String> getEmailAddresses() {
        return emailAddresses;
    }

    public void setEmailAddresses(List<String> emailAddresses) {
        this.emailAddresses = emailAddresses;
    }
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <emailAddresses>janed@example.com</emailAddresses>
    <emailAddresses>jdoe@example.org</emailAddresses>
</customer>

(6)@XmlElementWrapper
@XmlElementWrapper 注解在原來字段对应的元素的外面再包一层元素。

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {

    @XmlElementWrapper(name="email-addresses")
    @XmlElement(name="email-address")
    private List<String> emailAddresses;

    public Customer() {
        emailAddresses = new ArrayList<String>();
    }

    public List<String> getEmailAddresses() {
        return emailAddresses;
    }

    public void setEmailAddresses(List<String> emailAddresses) {
        this.emailAddresses = emailAddresses;
    }
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <email-addresses>
        <email-address>janed@example.com</email-address>
        <email-address>jdoe@example.org</email-address>
    </email-addresses>
</customer>

(7)@XmlList
@XmlList 注解把 List 对象中的每个值,用空格间隔,输出在一行。

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    @XmlList
    private List<String> emailAddresses;
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
    <emailAddresses>janed@example.com jdoe@example.org</emailAddresses>
</customer>

(8)@XmlList + @XmlAttribute

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    @XmlList
    @XmlAttribute
    private List<String> emailAddresses;
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer
   emailAddresses="janed@example.com jdoe@example.org"/>
<customer>

(9)@XmlList + @XmlValue

@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Customer {
    @XmlList
    @XmlValue
    private List<String> emailAddresses;
}
生成的 XML 如下:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>janed@example.com jdoe@example.org</customer>

参考文献:
1. http://jackyrong.iteye.com/blog/1890669
2. http://desert3.iteye.com/blog/1570092

2016年6月23日星期四

Linux_104:常用命令之三十一:htpasswd

环境:OS X EI Capitan 10.11.5

htpasswd 命令用于建立和更新存储用户名、密码的文本文件, 用于对HTTP用户的Basic认证。
如果没有这个命令,可以用 # yum -y install httpd-tools 安装。


用法:
htpasswd [-cmdpsD] passwordfile username
htpasswd -b[cmdpsD] passwordfile username password

参数说明:
(1)-c 创建passwdfile。如果passwdfile 已经存在,那么它会重新写入并删去原有内容。
(2)-n 不更新passwordfile,直接显示密码。
(3)-m 使用MD5加密(默认)。
(4)-d 使用CRYPT加密(默认)。
(5)-p 使用普通文本格式的密码。
(6)-s 使用SHA加密。
(7)-b 命令行中一并输入用户名和密码而不是根据提示输入密码,可以看见明文,不需要交互。
(8)-D 删除指定的用户。

举例:
1. 创建一个新的存储用户名、密码的文本文件的同时创建一个用户
# htpasswd -c /etc/openshift/openshift-passwd testuser
回车后会提示输入口令

2. 在原有用户名、密码的文本文件中,增加一个用户
# htpasswd -b /etc/openshift/openshift-passwd developer openshift
注意,这里一定要把 -c 参数去掉,否则会覆盖原有的文件。

3. 如何不更新密码文件,只显示加密后的用户名和密码?
# htpasswd -n testuser2

4. 如何删除用户名和密码?
# htpasswd -D /etc/openshift/openshift-passwd testuser

5. 如何修改密码?
先删除指定用户,再重新添加一遍该用户。

参考文献:
1. https://blog.linuxeye.com/333.html

MAC_042:解决无法清空某些邮件的问题

环境:OS X EI Capitan 10.11.5

最近发现,删除邮件后,邮件进入废纸篓后,选择清空时,会报出如下错误:

然后怎么删除该邮件,都删除不了。

解决方法如下:
首先选中要清空的废纸篓的邮箱:

 然后,选择菜单:邮箱->重建
重新删除该邮件,现在应该可以删除了。

参考文献:
1. https://www.theinternetpatrol.com/fixing-the-message-could-not-be-moved-to-the-mailbox-trash-error-on-a-mac/

2016年6月12日星期日

Architect_022:如何把传统应用架构迁移到微服务架构?(摘录+整理)

1. 第一步:停止让单体式应用继续变大

相反,应该采取逐步迁移单体式应用的策略,通过逐步生成微服务新应用,与旧的单体式应用集成,随着时间推移,单体式应用在整个架构中比例逐渐下降直到消失或者成为微服务架构一部分。
当开发新功能时不应该为旧单体应用添加新代码,应该是将新功能开发成独立微服务。

除了新服务和传统应用,还有两个模块:
其一是请求路由器,负责处理入口(http)请求,有点像之前提到的API网关。路由器将新功能请求发送给新开发的服务,而将传统请求还发给单体式应用。
其二是胶水代码(glue code),将微服务和单体应用集成起来,微服务很少能独立存在,经常会访问单体应用的数据。胶水代码,可能在单体应或者为服务或者二者兼而有之,负责数据整合。微服务通过胶水代码从单体应用中读写数据,单体应用需要提供的远程API。

胶水代码也被称为容灾层(anti-corruption layer),这是因为胶水代码保护微服务全新域模型免受传统单体应用域模型污染。胶水代码在这两种模型间提供翻译功能。

将新功能以轻量级微服务方式实现由很多优点,例如可以阻止单体应用变的更加无法管理。微服务本身可以开发、部署和独立扩展。
然而,这方法并不解决任何单体式本身问题,为了解决单体式本身问题必须深入单体应用​做出改变。
 
2. 第二步:将前端和后端分离减小单体式应用复杂度的策略是将表现层和业务逻辑、数据访问层分开。典型的企业应用至少有三个不同元素构成:
  • 表现层 处理HTTP请求,要么响应一个RESTAPI请求,要么是提供一个基于HTML的图形接口。对于一个复杂用户接口应用,表现层经常是代码重要的部分。
  • 业务逻辑层 完成业务逻辑的应用核心​
  • 数据访问层 访问基础元素,例如数据库和消息代理​。
前端和后端分离后,表现层逻辑应用远程调用业务逻辑层应用,下图表示迁移前后架构不同:​


3. 第三步:抽取模块成为独立微服务
一个巨大的复杂单体应用由成十上百个模块构成,每个都是被抽取对象。
(1)可以先抽取容易的模块,让开发者积累足够经验,这些经验可以为后续模块化工作带来巨大好处。可以抽取经常变化的模块,这样获益较大。
(2)可以抽取资源消耗大的模块,例如,将内存数据库抽取出来成为一个微服务会非常有用,可以将其部署在大内存主机上。同样的,将对计算资源很敏感的算法应用抽取出来也是非常有益的,这种服务可以被部署在有很多CPU的主机上。
(3)可以抽取有有粗粒度边界的模块,例如,只与其他应用异步同步消息的模块。

那么,该如何抽取模块呢?

(1)定义好模块和单体应用之间粗粒度接口
由于单体应用需要微服务的数据,反之亦然,因此更像是一个双向API。
因为必须在负责依赖关系和细粒度接口模式之间做好平衡,因此开发这种API很有挑战性,尤其对使用域模型模式的业务逻辑层来说更具有挑战,因此经常需要改变代码来解决依赖性问题。

在本例中,准备抽取使用Y模块的Z模块,X模块引用了Z模块的对象,第一步是定义一套粗粒度APIs,第一个接口应该是被X模块使用的内部接口,用于激活Z模块;第二个接口是被Z模块使用的外部接口,用于激活Y模块。
(2)将模块转换成独立服务。
​一旦完成粗粒度接口,也就将此模块转换成独立微服务。为了实现,必须写代码使得单体应用和微服务之间通过使用进程间通信(IPC)机制的API来交换信息。

在本例中,将Z模块整合成一个微服务基础框架,例如服务发现。


参考文献:
1. http://dockone.io/article/1266

Architect_021:微服务如何部署?(摘录+整理)

一个微服务应用由上百个服务构成,服务采用不同语言和框架。每个服务可以有自己的部署、资源、扩展和监控需求。例如,可以根据服务需求运行若干个服务实例,除此之外,每个实例必须有自己的CPU,内存和I/O资源。尽管很复杂,但是更挑战的是服务部署必须快速、可靠和性价比高。

1. 单主机多服务实例模式  
使用单主机多服务实例模式,需要提供若干台物理或者虚拟机,每台机器上运行多个服务实例。
很多情况下,这是传统的应用部署方法。每个服务实例运行一个或者多个主机的well-known端口。
这种模式有一个参数代表每个服务实例由多少进程构成。
例如,可以在Tomcat 上部署一个Java服务实例作为web应用;而一个Node.js服务实例可能有一个父进程和若干个子进程构成。
这种模式有另外一个参数定义同一进程组内有多少服务实例运行。
例如,可以在同一个Tomcat上运行多个Java web应用,或者在同一个OSGI容器内运行多个OSGI捆绑实例。

单主机多服务实例模式的缺点之一是服务实例间很少或者没有隔离,除非每个服务实例是独立进程。如果想精确监控每个服务实例资源使用,就不能限制每个实例资源使用。因此有可能造成某个糟糕的服务实例占用了主机的所有内存或者CPU。

单主机多服务实例模式的缺点之二是运维团队必须知道如何部署的详细步骤。服务可以用不同语言和框架写成,因此开发团队肯定有很多需要跟运维团队沟通事项。其中复杂性增加了部署过程中出错的可能性。

2. 单主机单服务实例模式
使用单主机单实例模式,每个主机上服务实例都是各自独立的。
有两种不同实现模式:单虚拟机单实例和单容器单实例。

2.1 单虚拟机单服务实例模式
使用单虚拟机单实例模式,一般将服务打包成虚拟机 image。每个服务实例是一个使用此 image 启动的VM。下图展示了此架构:
单虚机虚拟机单实例模式的缺点如下:
  • 资源利用效率不高。每个服务实例占有整个虚机的资源,包括操作系统。
  • IaaS按照VM来收费,而不管虚机是否繁忙。
  • 部署服务新版本比较慢。虚机镜像因为大小原因创建起来比较慢,同样原因,虚机初始化也比较慢,操作系统启动也需要时间。
  • 运维团队有大量的创建和管理虚机的工作。
2.2 单容器单服务实例模式
使用单容器单服务实例模式时,每个服务实例都运行在各自容器中。容器是运行在操作系统层面的虚拟化机制。一个容器包含若干运行在沙箱中的进程。从进程角度来看,他们有各自的命名空间和根文件系统;可以限制容器的内存和CPU资源。某些容器还具有I/O限制,这类容器技术包括Docker和Solaris Zones。
下图展示了这种模式:

使用这种模式需要将服务打包成容器 image。一个容器image是一个运行包含服务所需库和应用的文件系统​。某些容器映像由完整的linux根文件系统组成,其它则是轻量级的。例如,为了部署Java服务,需要创建包含Java运行库的容器映像,也许还要包含Tomcat ,以及编译过的Java应用。

一旦将服务打包成容器映像,就需要启动若干容器。一般在一个物理机或者虚拟机上运行多个容器,可能需要集群管理系统,例如k8s或者Marathon,来管理容器。集群管理系统将主机作为资源池,根据每个容器对资源的需求,决定将容器调度到那个主机上。

容器的优点跟虚机很相似,服务实例之间完全独立,可以很容易监控每个容器消耗的资源。跟虚机相似,容器使用隔离技术部署服务。容器管理API也可以作为管理服务的API。
然而,跟虚机不一样,容器是一个轻量级技术。容器 image 创建起来很快,容器启动也很快。

参考文献:
1. http://dockone.io/article/1066

Travel_002:大佛光寺——中国仅存的四座唐代木建筑中最气势恢宏的一座寺庙

沿着五台山盘山公路开出去好久,过了一村又一村,大约45公里以后,终于在一个僻静的村落里找到了此行的目的地:佛光寺。
没有五台山上烟气缭绕的香供,没有熙熙攘攘的香客,距离当初梁思成、林徽因夫妇第一次发现佛光寺已经过去了79年,佛光寺依旧拒绝热闹。

“斗拱、梁枋、棋盘式的天花以及雕花的柱础都细看过了。无论是细部或者整体,它们都明白无误地显示了晚唐时期的特征。当我们爬进天花板上面的黑暗空间时,令我们大感惊奇。我在那里看到的屋顶架构,以前只是在唐代绘画中见过。”
这是梁思成先生的当年的笔记实录。

压抑着激动的心情,我寻阶而上,整个佛光寺建于一个山坡之上,而庙门、文殊殿、东大殿分别位于山坡的第一、二、三层,整个设计很自然地利用了山势,大工巧拙而不失庄重与大气。
当讲解员打开巨大而斑驳的暗红色殿门时,我犹疑了一下,我知道,下一步,即将跨入千年时空之门。

“巨大的殿门立即被我们用力地推开了。面宽七开间的室内,在昏暗中非常动人。在一个很大的平台上,有一尊佛的坐像,两边是普贤和文殊以及众多随侍的罗汉、胁侍菩萨,有如一座仙林。”
这是梁思成先生当年进入佛光寺东大殿的感受。

当年,梁林夫妇怀抱着为中国寻找唐代建筑的决心,走遍了大半个中国,但他们一次次地失望甚至于绝望:唐朝,那个一千多年前的大唐盛世,难道真的只存在于文字之中吗?
历史开了个让人心酸的玩笑,敦煌在20世纪初遭遇疯狂劫掠,斯坦因伯希和们没有放过那些精美绝伦的壁画和雕塑,其中就包括61洞窟的唐代壁画和雕塑。
随后,伯希和出版了《敦煌石窟》一书,正是在这部书中,梁林夫妇发现了两幅描绘佛教圣地五台山全景的唐代壁画,其中就有大佛光寺。

一千多年过去了,敦煌61洞窟五台山全景壁画沉默不语,五台县的大佛光寺沉默不语,它们在等待两个年轻的学者将它们的命运做一次奇妙的互联。

好吧,让我们把时光倒流回1937年,这一年全面爆发了中日战争,梁林夫妇在阎锡山的资助下,一路风尘仆仆,下了火车,由村民向导带领,骑着毛驴一路来到佛光寺。
这一年,梁思成36岁,林徽因33岁,这一次,一个全新而重大的发现在等待着他们。

从照片上可以看出,林徽因先生不顾危险,亲自攀登到梯子顶部,丈量经幢上部的尺寸,辨认经幢上面的文字,可见当时她的心情是怎样的喜出望外。

“在同一座大殿里,我们找到了唐代的绘画、书法、雕塑和建筑,其中的每一项都是稀世之珍,集中在一起,它们是独一无二的。”
这是在勘察结束之后,梁思成先生发表在《亚洲杂志》上的《中国最古老的木构建筑》一文中的总结文字。

唐大中十一年,公元 857 年,大佛光寺落成。

所有的中华文明,都是劫后余生。

 




附注:中国仅存的4座唐代木结构建筑:五台山佛光寺东大殿,五台山南禅寺、芮城县广仁王庙、平顺县天台庵。

2016年6月9日星期四

Architect_020:微服务如何管理数据?(摘录+整理)

使用微服务架构后,数据访问变得非常复杂,这是因为数据都是微服务私有的,唯一可访问的方式就是通过API。
不同的微服务经常使用不同的数据库,关系型数据库并不一定是最佳选择。某些场景,某个NoSQL数据库可能提供更方便的数据模型,提供更加的性能和可扩展性。例如,某个产生和查询字符串的应用采用例如Elasticsearch的字符搜索引擎。同样的,某个产生社交图片数据的应用可以采用图片数据库,例如,Neo4j。
因此,基于微服务的应用一般都使用SQL和NoSQL结合的数据库,也就是被称为polyglot persistence的方法。
分区的polyglot-persistent架构用于存储数据有许多优势,包括松耦合服务和更佳性能和可扩展性。然而,随之而来的则是分布式数据管理带来的挑战。

挑战一:如何完成一笔交易的同时保持多个服务之间数据一致性?
以一个在线B2B商店为例,客户服务维护包括客户的各种信息,例如credit lines。订单服务管理订单,需要验证某个新订单与客户的信用限制没有冲突。
在微服务架构下,订单和客户表分别是相对应服务的私有表,如下图所示:
订单服务不能直接访问客户表,只能通过客户服务发布的API来访问。

挑战二:如何从多个服务中搜索数据?
假设有个需求要显示客户和他的订单。而用户服务只接受用户信息,订单服务只支持私有主键来查询订单。

挑战三:多个服务访问同一数据时,如何保证数据的唯一性?
如果多个服务访问同一个数据,schema会更新访问时间,并在所有服务之间进行协调。

1. 事件驱动架构
事件驱动架构可以解决上述两个挑战。在这种架构中,当某件重要事情发生时,微服务会发布一个事件,例如更新一个业务实体。当订阅这些事件的微服务接收此事件时,就可以更新自己的业务实体,这个更新可能会触发更多的事件发布。

1.1 使用事件来实现跨多服务的业务交易
交易一般由一系列步骤构成,每一步骤都由一个更新业务实体的微服务和发布激活下一步骤的事件构成。
下图展现如何使用事件驱动方法,在创建订单时检查信用可用度,微服务通过消息代理(Messsage Broker)来交换事件。
(1)订单服务创建一个带有NEW状态的Order (订单),发布了一个“Order Created Event(创建订单)”的事件。
(2)客户服务消费Order Created Event事件,为此订单预留信用,发布“Credit Reserved Event(信用预留)”事件。
 (3)订单服务消费Credit Reserved Event,改变订单的状态为OPEN。
更复杂的场景可以引入更多步骤,例如在检查用户信用的同时预留库存等。

这种模式提供弱确定性,保证数据的最终一致性。其特点是先由每个服务原子性更新数据库和发布事件;然后通过消息Broker确保事件传递至少一次,从而完成跨多个服务的业务交易。

1.2 创建一个新的视图,维护此视图的服务订阅相关事件并且更新视图
例如,客户订单视图更新服务(维护客户订单视图)会订阅由客户服务和订单服务发布的事件。
当客户订单视图更新服务收到客户或者订单事件,就会更新 客户订单视图数据集。
可以使用文档数据库(例如MongoDB)来实现客户订单视图,为每个用户存储一个文档。
客户订单视图查询服务负责响应对客户以及最近订单(通过查询客户订单视图数据集)的查询。
小结:事件驱动架构可以使得交易跨多个服务且提供最终一致性,并且可以使应用维护最终视图;其缺点在于编程模式比较复杂:为了从应用层级失效中恢复,还需要完成补偿性交易,例如,如果信用检查不成功则必须取消订单;另外,应用必须应对不一致的数据,这是因为临时(in-flight)交易造成的改变是可见的,另外当应用读取未更新的最终视图时也会遭遇数据不一致问题。另外一个缺点在于订阅者必须检测和忽略冗余事 件。

2. 原子操作
事件驱动架构会遇到数据库更新和发布事件原子性问题。
例如,订单服务必须向ORDER表插入一行,然后发布Order Created event,这两个操作需要原子性。如果更新数据库后,服务瘫掉了,会造成事件未能发布,系统变成不一致状态。
确保原子操作的标准方式是使用一个分布式交易,其中包括数据库和消息代理。然而,基于以上描述的CAP理论,这却并不是我们想要的,因为这里我们不能接受最终一致性,必须all or nothing。

2.1 使用本地交易发布事件
获得原子性的一个方法是对发布事件应用采用 multi-step process involving only local transactions。
其技巧在于使用一个EVENT表,此表在存储业务实体数据库中起到消息列表功能。
应用发起一个(本地)数据库交易,更新业务实体状态,向EVENT表中插入一个事件,然后提交此次交易。
另外一个独立应用进程或者线程查询此EVENT表,向消息代理发布事件,然后使用本地交易标志此事件为已发布,如下图所示: 
订单服务向ORDER表插入一行,然后向EVENT表中插入Order Created event,事件发布线程或者进程查询EVENT表,请求未发布事件,发布它们,然后更新EVENT表标志此事件为已发布。
笔者注:我觉得还是有问题,发布事件和更新EVENT表必须要在一个事务里,如何保证?


2.2 挖掘数据库交易日志
应用更新数据库,在数据库交易日志中产生变化,交易日志挖掘进程或者线程读这些交易日志,将日志发布给消息代理。如下图所见:

成功案例:LinkedIn Databus 项目,Databus 挖掘Oracle交易日志,根据变化发布事件,LinkedIn使用Databus来保证系统内各记录之间的一致性。

交易日志挖掘的优点是将发布事件和应用业务逻辑分离,缺点在于交易日志对不同数据库有不同格式,甚至不同数据库版本也有不同格式;而且很难从底层交易日志更新记录转换为高层业务事件。

2.3 使用事件源
Event sourcing (事件源)保存业务实体一系列状态改变事件,而不是存储实体现在的状态。应用可以通过重放事件来重建实体现在状态。只要业务实体发生变化,新事件就会添加到时间表中。因为保存事件是单一操作,因此肯定是原子性的。
为了理解事件源工作方式,考虑事件实体作为一个例子。订单服务以事件状态改变方式存储一个订单:创建的,已批准的,已发货的,取消的;每个事件包括足够数据来重建订单状态。
事件是长期保存在事件数据库中,提供API添加和获取实体事件。事件存储跟之前描述的消息代理类似,提供API来订阅事件。事件存储将事件递送到所有感兴趣的订阅者,事件存储是事件驱动微服务架构的基干。
笔者注:事件数据库是事件微服务使用的数据库,那么创建订单服务和生成事件记录如何保证在一个事务中?
参考文档:
1. http://dockone.io/article/936

Architect_019:如何发现微服务?(摘录+整理)

微服务的实例的网络位置都是动态分配的,而且因为扩展、失效和升级等需求,服务实例会经常动态改变,因此,需要一种更加复杂的服务发现机制。



目前有两大类服务发现模式:客户端发现和服务端发现。

1. 客户端发现模式
当使用客户端发现模式时,客户端负责决定相应服务实例的网络位置,并且对请求实现负载均衡。客户端从一个服务注册服务中查询,其中是所有可用服务实例的库。客户端使用负载均衡算法从多个服务实例中选择出一个,然后发出请求。
服务实例的网络位置在启动时注册到服务注册表中,并且在服务终止时从注册表中删除。
服务实例注册信息一般是使用心跳机制来定期刷新的。

客户端发现模式相对比较直接,除了服务注册表,没有做其它改变。除此之外,因为客户端知道可用服务注册表信息,因此客户端可以通过使用哈希一致性(hashing consistently)变得更加聪明,更加有效的负载均衡。
这种模式最大的缺点是需要针对不同的编程语言注册不同的服务,在客户端需要为每种语言开发不同的服务发现逻辑。
成功案例:Netflix OSS 使用了客户端发现模式。
Netflix Eureka 是一个服务注册表,为服务实例注册管理和查询可用实例提供了REST API接口。Netflix Ribbon 是一种IPC客户端,与Eureka 合作工作实现对请求的负载均衡。

2. 服务端发现模式
客户端通过负载均衡器向某个服务提出请求,负载均衡器向服务注册表发出请求,将每个请求转发给可用的服务实例。跟客户端发现一样,服务实例在服务注册表中注册或者注销。
服务端发现模式最大的优点是客户端无需关注发现的细节,客户端只需要简单的向负载均衡器发送请求,实际上减少了编程语言框架需要完成的发现逻辑。

3. 服务注册表
服务注册表是服务发现很重要的部分,它是包含服务实例网络地址的数据库。服务注册表需要高可用而且随时更新。客户端可以缓存从服务注册表获得的网络地址。然而,这些信息最终会变得过时,客户端也无法发现服务实例。因此,服务注册表由若干使用复制协议保持同步的服务器构成。
 

服务注册表例子:
  • etcd – 是一个高可用,分布式的,一致性的,键值表,用于共享配置和服务发现。两个著名案例包括Kubernetes和Cloud Foundry。
  • consul – 是一个用于发现和配置的服务。提供了一个API允许客户端注册和发现服务。Consul可以用于健康检查来判断服务可用性。
  • Apache ZooKeeper – 是一个广泛使用,为分布式应用提供高性能整合的服务。
4. 服务注册方式

4.1 自注册方式当使用自注册模式时,服务实例负责在服务注册表中注册和注销,并且,服务实例也要发送心跳来保证注册信息不会过时。下图描述了这种架构: 
自注册模式的优点是相对简单,不需要其他系统功能。
自注册模式的缺点是把服务实例跟服务注册表联系起来,必须在每种编程语言和框架内部实现注册代码。

4.2 第三方注册方式
当使用第三方注册模式时,服务实例并不负责向服务注册表注册,而是由另外一个系统模块,叫做服务管理器,负责注册。
服务管理器通过查询部署环境或订阅事件来跟踪运行服务的改变。
当管理器发现一个新可用服务,会向注册表注册此服务。
服务管理器也负责注销终止的服务实例。
下图是这种模式的架构图。

参考文献:
1. http://dockone.io/article/771

Architect_018:微服务架构的进程间通信(摘录+整理)

1. 进程间通信(IPC)
在单体式应用中,各个模块之间的调用是通过编程语言级别的方法或者函数来实现的。但是一个基于微服务的分布式应用是运行在多台机器上的。
一般来说,每个服务实例都是一个进程。因此,如下图所示,服务之间的交互必须通过进程间通信(IPC)来实现。
2. 客户端与服务端的交互模式
交互模式可以从两个维度进行归类。
(1)第一个维度是一对一还是一对多:
  • 一对一:每个客户端请求有一个服务实例来响应。
  • 一对多:每个客户端请求有多个服务实例来响应。
(2)第二个维度是这些交互式同步还是异步:
  • 同步模式:客户端请求需要服务端即时响应,甚至可能由于等待而阻塞。
  • 异步模式:客户端请求不会阻塞进程,服务端的响应可以是非即时的。
(3)一对一的交互模式有以下几种方式:
  • 请求/响应:一个客户端向服务器端发起请求,等待响应。客户端期望此响应即时到达。在一个基于线程的应用中,等待过程可能造成线程阻塞。
  • 通知(也就是常说的单向请求):一个客户端请求发送到服务端,但是并不期望服务端响应。
  • 请求/异步响应:客户端发送请求到服务端,服务端异步响应请求。客户端不会阻塞,而且被设计成默认响应不会立刻到达。
(4)一对多的交互模式有以下几种方式:
  • 发布/订阅模式:客户端发布通知消息,被零个或者多个感兴趣的服务消费。
  • 发布/异步响应模式:客户端发布请求消息,然后等待从感兴趣服务发回的响应。
下表显示了不同交互模式:

每个服务都是以上这些模式的组合,对某些服务,一个IPC机制就足够了;而对另外一些服务则需要多种IPC机制组合。下图展示了在一个打车服务请求中服务之间是如何通信的。

上图中的服务通信使用了通知、请求/响应、发布/订阅等方式。例如,乘客通过移动端给『行程管理服务』发送通知,希望申请一次出租服务。『行程管理服务』发送请求/响应消息给『乘客服务』以确认乘客账号是有效的。紧接着创建此次行程,并用发布/订阅交互模式通知其他服务,包括定位可用司机的调度服务。

3. 客户端与服务端的接口API
不管选择了什么样的IPC机制,重要的是使用某种交互式定义语言(IDL)来精确定义一个服务的接口API。
接口API的定义实质上依赖于选择哪种IPC。如果使用消息机制,API则由消息频道(channel)和消息类型构成;如果选择使用HTTP机制,API则由URL和请求、响应格式构成。
API的变化是不可避免的,微小的改变可以和之前版本兼容。比如,你可能只是为某个请求和响应添加了一个属性。这时,客户端使用旧版API应该也能和新版本一起工作。但是有时候,API需要进行大规模的改动,并且可能与之前版本不兼容。因为你不可能强制让所有的客户端立即升级,所以支持老版本客户端的服务还需要再运行一段时间。如果你正在使用基于基于HTTP机制的IPC,例如REST,一种解决方案是把版本号嵌入到URL中。每个服务都可能同时处理多个版本的API。或者,你可以部署多个实例,每个实例负责处理一个版本的请求。

4. 处理部分失败
分布式系统中部分失败是普遍存在的问题。因为客户端和服务端是都是独立的进程,一个服务端有可能因为故障或者维护而停止服务,或者此服务因为过载而停止或者反应很慢。
假设推荐服务无法响应请求,那客户端就会由于等待响应而阻塞,这不仅会给客户带来很差的体验,而且在很多应用中还会占用很多资源,比如线程,以至于到最后由于等待响应被阻塞的客户端越来越多,线程资源被耗费完了。如下图所示:
Netfilix Hystrix提供了一个比较好的解决方案,具体的应对措施包括:
  • 网络超时:当等待响应时,不要无限期的阻塞,而是采用超时策略。使用超时策略可以确保资源不会无限期的占用。
  • 限制请求的次数:可以为客户端对某特定服务的请求设置一个访问上限。如果请求已达上限,就要立刻终止请求服务。
  • 断路器模式(Circuit Breaker Pattern):记录成功和失败请求的数量。如果失效率超过一个阈值,触发断路器使得后续的请求立刻失败。如果大量的请求失败,就可能是这个服务不可用,再发请求也无意义。在一个失效期后,客户端可以再试,如果成功,关闭此断路器。
  • 提供回滚:当一个请求失败后可以进行回滚逻辑。例如,返回缓存数据或者一个系统默认值。
5. IPC实现技术
服务之间的通信采用同步的请求/响应模式,可以选择基于HTTP的REST或者Thrift。
服务之间的通信采用异步的、基于消息的通信模式,可以选择AMQP或者STOMP。大量开源消息中间件可供选择,比如RabbitMQ、Apache Kafka、Apache ActiveMQ和NSQ。
消息格式可以选择基于文本的,比如 JSON和XML;二进制格式(效率更高)的,比如Avro和Protocol Buffer。

5.1 采用异步的、基于消息的通信模式的例子,下图展示了打车软件如何使用发布/订阅:
行程管理服务在发布-订阅channel内创建一个行程消息,并通知调度服务有一个新的行程请求,调度服务发现一个可用的司机然后向发布-订阅channel写入司机建议消息(Driver Proposed message)来通知其他服务。

5.2 采用同步的、基于请求/响应的通信模式的例子:下图展示了打车软件如何使用REST:
乘客通过移动端向行程管理服务的/trips资源提交了一个POST请求。行程管理服务收到请求之后,会发送一个GET请求到乘客管理服务以获取乘客信息。当确认乘客信息之后,紧接着会创建一个行程,并向移动端返回201状态码响应。

使用基于HTTP的协议的好处:
  • HTTP非常简单并且大家都很熟悉。
  • 可以使用浏览器扩展(比如Postman)或者curl之类的命令行来测试API。
  • 内置支持请求/响应模式的通信。
  • HTTP对防火墙友好。
  • 不需要中间代理,简化了系统架构。
使用基于HTTP的协议的不足之处:
  • 只支持请求/响应模式交互。可以使用HTTP通知,但是服务端必须一直发送HTTP响应才行。
  • 因为客户端和服务端直接通信(没有代理或者buffer机制),在交互期间必须都在线。
  • 客户端必须知道每个服务实例的URL。客户端必须使用服务实例发现机制。
参考文献:
1. http://dockone.io/article/549
2. http://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/

Architect_017:客户端与微服务如何通信:API Gateway(摘录+整理)

1. 手机产品页面
这个产品页面展示了非常多的信息:
  • 产品基本信息(名字、描述和价格)
  • 购物车中的物品数
  • 下单历史
  • 用户评论
  • 低库存警告
  • 快递选项
  • 各式各样的推荐,包括经常跟这个物品一起被购买的产品、购买该物品的其他顾客购买的产品以及购买该产品的顾客还浏览了哪些产品。
  • 可选的购物选项
如果采用一个单体式应用架构,一个移动客户端将会通过一个REST请求(GET api.company.com/productdetails/productId)来获取这些数据。
然后通过一个负载均衡将请求分发到多个应用实例之一。
应用将查询各种数据库并返回请求给客户端。

如果采用微服务架构,最终页上的数据会分布在不同的微服务上。比如:
  • 购物车服务 -- 购物车中的物品数
  • 下单服务 -- 下单历史
  • 分类服务 -- 基本产品信息,如名字、图片和价格
  • 评论服务 -- 用户评论
  • 库存服务 -- 低库存警告
  • 快递服务 -- 快递选项、截止时间、来自不同快递API的成本计算
  • 推荐服务 -- 推荐产品
2. 客户端与微服务通信方式

2.1 客户端与到微服务直接通信

从理论上讲,一个客户端可以直接给多个微服务中的任何一个发起请求。每一个微服务都会有一个对外服务端,比如:https://serviceName.api.company.name。这个 URL 可能会映射到微服务的负载均衡上,它再转发请求到具体节点上。为了搜索产品细节,移动端需要向上述微服务逐个发请求。

不幸的是,这个方案有很多困难和限制。
(1)客户端的需求量与每个微服务暴露的细粒度 API 数量的不匹配。如图中,客户端需要7次单独请求。在更复杂的场景中,可能会需要更多次请求。例如,亚马逊的产品最终页要请求数百个微服务。
(2)客户端直接请求微服务的协议可能并不是web友好型。一个服务可能是用 Thrift的 RPC 协议,而另一个服务可能是用 AMQP 消息协议。它们都不是浏览或防火墙友好的,并且最好是内部使用。应用应该在防火墙外采用类似 HTTP 或者 WebSocket 协议。
(3)很难重构微服务。随着时间的推移,我们可能需要改变系统微服务目前的切分方案。例如,我们可能需要将两个服务合并或者将一个服务拆分为多个。但是,如果客户端直接与微服务交互,那么这种重构就很难实施。

鉴于上述三个问题,客户端直接与服务器端通信的方式很少在实际中使用。

2.2 采用API Gateway
API Gateway 是一个服务器,也可以说是进入系统的唯一节点。跟面向对象设计模式中的Facade模式很像。API Gateway 负责请求转发、请求合成和协议转换,封装内部系统的架构,并且提供API给各个客户端。它还有其他功能,如授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理、通过返回缓存或者默认值的方式来掩盖后端服务的错误等。下图展示了一个适应当前架构的API Gateway。


API Gateway负责请求转发、合成和协议转换。所有来自客户端的请求都要先经过API Gateway,然后路由这些请求到对应的微服务。API Gateway将经常通过调用多个微服务来处理一个请求以及聚合多个服务的结果。它可以在web协议与内部使用的非Web友好型协议间进行转换,如HTTP协议、WebSocket协议。

API Gateway可以提供给客户端一个定制化的API。它暴露一个粗粒度API给移动客户端。以产品最终页这个使用场景为例。API Gateway提供一个服务提供点(/productdetails?productid=xxx)使得移动客户端可以在一个请求中检索到产品最终页的全部数据。API Gateway通过调用多个服务来处理这一个请求并返回结果,涉及产品信息、推荐、评论等。

成功案例:Netfix API Gateway。
Netflix流服务提供数百个不同的微服务,包括电视、机顶盒、智能手机、游戏系统、平板电脑等。采用一个API Gateway来提供容错性高的API,针对不同类型设备有相应代码。一个适配器处理一个请求平均要调用6到8个后端服务。Netflix API Gateway每天处理数十亿的请求。

3. 如何设计和开发API Gateway

3.1 高可用性
API Gateway 是一个高可用的组件,必须要开发、部署和管理。它可能成为系统的一个瓶颈。开发者必须更新API Gateway来提供新服务提供点来支持新暴露的微服务。更新API Gateway时必须越轻量级越好。否则,开发者将因为更新Gateway而排队列。

3.2 性能和可扩展性
API Gateway 必须要支持同步、非阻塞I/O。
如果是基于Java开发,可以采用基于NIO技术的框架,比如:Netty,Vertx,Spring Reactor或者JBoss Undertow。
如果是基于Node.js,它是一个在Chrome的JavaScript引擎基础上建立的平台。
一个可选的方案是Nginx Plus。Nginx Plus提供一个成熟的、可扩展的、高性能web服务器和反向代理,它们均容易部署、配置和二次开发。Nginx Plus可以管理授权、权限控制、负载均衡、缓存并提供应用健康检查和监控。

3.3 采用反应性编程模型
对于有些请求,API Gateway可以通过直接路由请求到对应的后端服务上的方式来处理。对于另外一些请求,它需要调用多个后端服务并合并结果来处理。对于一些请求,例如产品最终页面请求,发给后端服务的请求是相互独立的。为了最小化响应时间,API Gateway应该并发的处理相互独立的请求。但是,有时候请求之间是有依赖的。API Gateway可能需要先通过授权服务来验证请求,然后在路由到后端服务。类似的,为了获得客户的产品愿望清单,需要先获取该用户的资料,然后返回清单上产品的信息。这样的一个API 组件是Netflix Video Grid。

利用传统的同步回调方法来实现API合并的代码会使得你进入回调函数的噩梦中。这种代码将非常难度且难以维护。一个优雅的解决方案是采用反应性编程模式来实现。类似的反应抽象实现有Scala的Future,Java8的CompletableFuture和JavaScript的Promise。基于微软.Net平台的有Reactive Extensions(Rx)。Netflix为JVM环境创建了RxJava来使用他们的API Gateway。同样地,JavaScript平台有RxJS,可以在浏览器和Node.js平台上运行。采用反应编程方法可以帮助快速实现一个高效的API Gateway代码。

3.4 服务调用
一个基于微服务的应用是一个分布式系统,并且必须采用线程间通信的机制。有两种线程间通信的方法。一种是采用异步机制,基于消息的方法。这类的实现方法有JMS和AMQP。另外的,例如ZeroMQ 属于服务间直接通信。还有一种线程间通信采用同步机制,例如Thrift和HTTP。事实上一个系统会同时采用同步和异步两种机制。由于它的实现方式有很多种,因此API Gateway就需要支持多种通信方式。

延伸阅读:
Apache Thrift 是 Facebook 实现的一种高效的、支持多种编程语言的远程服务调用的框架。

3.5 服务发现
API Gateway需要知道每一个微服务的IP和端口。在传统应用中,你可能会硬编码这些地址,但是在现在云基础的微服务应用中,这将是个简单的问题。基础服务通常会采用静态地址,可以采用操作系统环境变量来指定。但是,探测应用服务的地址就没那么容易了。应用服务通常动态分配地址和端口。同样的,由于扩展或者升级,服务的实例也会动态的改变。因此,API Gateway需要采用系统的服务发现机制,要么采用服务端发现,要么是客户端发现。如果采用客户端发现服务,API Gateway必须要去查询服务注册处,也就是微服务实例地址的数据库。

3.6 处理部分失败
在实现API Gateway过程中,另外一个需要考虑的问题就是部分失败。这个问题发生在分布式系统中当一个服务调用另外一个服务超时或者不可用的情况。API Gateway不应该被阻断并处于无限期等待下游服务的状态。但是,如何处理这种失败依赖于特定的场景和具体服务。例如,如果是在产品详情页的推荐服务模块无响应,那么API Gateway应该返回剩下的其他信息给用户,因为这些信息也是有用的。推荐部分可以返回空,也可以返回固定的顶部10个给用户。但是,如果是产品信息服务无响应,那么API Gateway就应该给客户端返回一个错误。

在缓存有效的时候,API Gateway应该能够返回缓存。例如,由于产品价格变化并不频繁,API Gateway在价格服务不可用时应该返回缓存中的数值。这类数据可以由API Gateway自身来缓存,也可以由Redis或Memcached这类外部缓存实现。通过返回缓存数据或者默认数据,API Gateway来确保系统错误不影响到用户体验。

Netflix Hystrix对于实现远程服务调用代码来说是一个非常好用的库。Hystrix记录那些超过预设定的极限值的调用。它实现了circuit break模式,使得可以将客户端从无响应服务的无尽等待中停止。如果一个服务的错误率超过预设值,Hystrix将中断服务,并且在一段时间内所有请求立刻失效。Hystrix可以为请求失败定义一个fallback操作,例如读取缓存或者返回默认值。如果你在用JVM,就应该考虑使用Hystrix。如果你采用的非JVM环境,那么应该考虑采用类似功能的库。

延伸阅读:
Hystrix 供分布式系统使用,提供延迟和容错功能,隔离远程系统、访问和第三方程序库的访问点,防止级联失败,保证复杂的分布系统在面临不可避免的失败时,仍能有其弹性。

参考文献:
1. http://dockone.io/article/482
2. http://www.ibm.com/developerworks/cn/java/j-lo-apachethrift/

2016年6月8日星期三

Architect_016:把传统三层架构应用改造成微服务架构应用(摘录+整理)

1. 出租车调度软件
经过需求分析,会手动或使用基于Rails、Spring Boot、Play或者Maven的生成器开始这个新项目,它的六边形架构是模块化的 ,架构图如下:



应用核心是业务逻辑,由定义服务、域对象和事件的模块组成。围绕着核心的是与外界打交道的适配器。适配器包括数据库访问组件、生产和处理消息的消息组件,以及提供API或者UI访问支持的web模块等。

尽管也是模块化逻辑,但是最终它还是会打包并部署为单体式应用。具体的格式依赖于应用语言和框架。例如,许多Java应用会被打包为WAR格式,而另外一些Java应用会被打包成自包含的JAR格式,Rails和Node.js会被打包成层级目录。

2. 改造成微服务架构


每一个应用功能区都使用微服务完成,另外,Web应用会被拆分成一系列简单的Web应用(比如一个对乘客,一个对出租车驾驶员)。这样的拆分对于不同用户、设备和特殊应用场景部署都更容易。

每一个后台服务开放一个REST API,许多服务本身也采用了其它服务提供的API。比如,驾驶员管理使用了告知驾驶员一个潜在需求的通知服务。UI服务激活其它服务来更新Web页面。

所有服务都是采用异步的,基于消息的通讯。

一些REST API也对乘客和驾驶员采用的移动应用开放。这些应用并不直接访问后台服务,而是通过API Gateway来传递中间消息。API Gateway负责负载均衡、缓存、访问控制、API 计费监控等等任务,可以通过 nginx 方便实现。

3. 微服务的三向扩展
应用基本可以用以上三个维度来表示:
  1. X轴  代表运行多个隐藏在负载均衡器之后的微服务实例(应用的多个副本)。
  2. Y轴  代表将应用分解为各个微服务。
  3. Z轴  代表将需求路由到相关服务。
以行程管理服务为例俩说,运行时,行程管理服务由多个服务实例构成。每一个服务实例都运行在一个Docker 容器中。服务实例的前端是一层诸如nginx的负载均衡器,负责在各个实例间分发请求。负载均衡器也同时处理其它请求,例如缓存、权限控制、API统计和监控。

不像传统多个服务共享一个数据库,微服务架构每个服务都有自己的数据库这种架构需要这种松耦合。
每种服务可以用更适合自己的数据库类型,也被称作多语言一致性架构。比如,驾驶员管理(发现哪个驾驶员更靠近乘客),必须使用支持地理信息查询的数据库。

下图展示了应用数据库架构:



参考文献:
1. http://nginx.com/blog/introduction-to-microservices/
2. http://dockone.io/article/394

Architect_015:微服务架构介绍(摘录+整理)

1. 什么是微服务?
微服务是指开发一个单个、小型、具备有业务功能的服务。其特点如下:
  • 每个服务运行在自己的进程中,通过轻量的通讯机制(基于HTTP/REST API)联系。
    其中,使用 REST API 更好些,因为 REST本身就是 Web,而不是基于 Web:“Be of the web, not behind the web”。
  • 每个服务可以使用不同的编程语言编写。
  • 每个服务提供一个模块边界,服务上下文。
  • 每个服务都有一个用RPC-或者消息驱动API定义清楚的边界。
  • 每个服务能够通过自动化方式独立地部署在单个或多个服务器上。
  • 每个服务能够通过自动化方式弹性扩展伸缩。
  • 每个服务能够独立更换、独立升级,而不影响其它服务。
  • 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库。

 2. 传统应用架构与微服务架构的区别
 
单体式应用的需求的一个小改变会导致整个系统重新构建重新部署,难以让变化只影响在一个模块内,进行扩展伸缩时也只能整个系统扩展,而不能针对其一部分扩展其资源能力。

单体式应用在不同模块发生资源冲突时,扩展将会非常困难。比如,一个模块对CPU敏感,另外一个内存数据库模块对内存敏感。然而,由于这些模块部署在一起,因此不得不在硬件选择上做一个妥协。

单体式应用另外一个问题是可靠性。因为所有模块都运行在一个进程中,任何一个模块中的一个BUG,比如内存泄露,将会有可能弄垮整个进程。除此之外,因为所有应用实例都是唯一的,这个BUG 将会影响到整个应用的可靠性。

3. 康威定律:设计系统的组织,最终产生的设计会反映出组织内部和组织之间的沟通结构。

“organizations which design systems ... are constrained to produce designs which are copies of the communication structures of these organizations”
—— Melvin Conway 1960’s

传统的MVC架构,会导致任何一个需求改变都必须要跨团队交流。


微服务这种极度松耦合的架构需要极度松耦合的组织团队。
按业务而不是按功能划分服务,每个服务可以使用不同的技术堆栈,每个服务是跨功能的,团队成员是拥有全部的技能。
成功案例:www.comparethemarket.com
跨功能团队负责每个产品的建立,每个产品被分离成许多独立的服务,之间通过消息总线通讯。

4. 微服务与SOA的区别
从表面上看,微服务架构模式有点像SOA,它们都是由多个服务构成。
从业务角度看,微服务是具备有业务功能的服务,而SOA中Web服务可能是个非业务功能的原子服务。
从技术角度看,微服务架构模式是一个不包含Web服务(WS-)和 ESB服务的SOA。微服务应用采用简单轻量级协议,比如REST,而不是WS-,在微服务内部避免使用ESB以及ESB类似功能。微服务架构模式也 拒绝使用canonical schema等SOA概念。

延伸阅读:Canonical Schema Pattern
The Canonical Schema pattern ensures that services are built with contracts capable of sharing business documents based on standardized data models (schemas).
The application potential of Canonical Schema can become one of the fundamental influential factors that determine the scope and complexion of a service inventory architecture.

参考文献:
1. http://www.jdon.com/46204
2. http://www.jdon.com/soa/microservice-architecture.html
3. http://www.bettersoftwaredesign.org/Design-Patterns/SOA-Service-Patterns/Foundational-Inventory-Patterns/Canonical-Schema-Pattern

2016年6月4日星期六

Marathonn_009:2016挑战100(10 X 10)马拉松接力

2016-05-29 红旋风慢跑团

第一次在正午32度高温下跑10公里,腿软乏力,好在凭着毅力坚持下来了。