火热的云原生到底是什么?一文了解云原生四要素!

阿娇 发表了文章 • 0 个评论 • 214 次浏览 • 2019-05-30 18:33 • 来自相关话题

所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。 随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应 ...查看全部
所谓云原生,它不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。

随着虚拟化技术的成熟和分布式架构的普及,用来部署、管理和运行应用的云平台被越来越多的提及。IaaS、PaaS和SaaS是云计算的3种基本服务类型,它们是关注硬件基础设施的基础设施即服务、关注软件和中间件平台的平台即服务以及关注业务应用的软件即服务。

在容器技术、可持续交付、编排系统等开源社区的推动下,以及微服务等开发理念的带动下,应用上云已经是不可逆转的趋势。随着云化技术的不断进展,云原生的概念也应运而生。
#云原生概念的诞生

云原生(Cloud Native)的概念,由来自Pivotal的MattStine于2013年首次提出,被一直延续使用至今。这个概念是Matt Stine根据其多年的架构和咨询经验总结出来的一个思想集合,并得到了社区的不断完善,内容非常多,包括DevOps、持续交付(Continuous Delivery)、微服务(MicroServices)、敏捷基础设施(Agile Infrastructure)和12要素(The Twelve-Factor App)等几大主题,不但包括根据业务能力对公司进行文化、组织架构的重组与建设,也包括方法论与原则,还有具体的操作工具。采用基于云原生的技术和管理方法,可以更好地把业务生于“云”或迁移到云平台,从而享受“云”的高效和持续的服务能力。
1.jpg

The Twelve-Factor App

顾名思义,云原生是面向“云”而设计的应用,因此技术部分依赖于传统云计算的3层概念,基础设施即服务(IaaS)、平台即服务(PaaS)和软件即服务(SaaS),例如,敏捷的不可变基础设施交付类似于IaaS,用来提供计算网络存储等基础资源,这些资源是可编程且不可变的,直接通过API可以对外提供服务;有些应用通过PaaS服务本来就能组合成不同的业务能力,不一定需要从头开始建设;还有一些软件只需要“云”的资源就能直接运行起来为云用户提供服务,即SaaS能力,用户直接面对的就是原生的应用。
##云原生并不是一个产品

最近讨论云原生应用越来越多。关于云原生应用,简单地说,就是大多数传统的应用,不做任何改动,都是可以在云平台运行起来,只要云平台支持这个传统应用所运行的计算机架构和操作系统。只不过这种运行模式,仅仅是把虚拟机当物理机一样使用,不能够真正利用起来云平台的能力。

云并非把原先在物理服务器上跑的东西放到虚拟机里跑,真正的云化不仅是基础设施和平台的事情,应用也要做出改变,改变传统的做法,实现云化的应用——应用的架构、应用的开发方式、应用部署和维护技术都要做出改变,真正的发挥云的弹性、动态调度、自动伸缩……一些传统IT所不具备的能力。这里说的“云化的应用”也就是“云原生应用”。云原生架构和云原生应用所涉及的技术很多,如容器技术、微服务、可持续交付、DevOps等。
2.jpg

而云原生应用最大的特点就是可以迅速部署新业务。在企业里,提供新的应用程序环境及部署软件新版本通常所需时间以日、周甚至以月计算。这种速度严重限制了软件发布所能承受的风险,因为犯错及改错也需要花费同样的时间成本,竞争优势就会由此产生。

所以云原生不是一个产品,而是一套技术体系和一套方法论,而数字化转型是思想先行,从内到外的整体变革。更确切地说,它是一种文化,更是一种潮流,是云计算的一个必然导向。意义在于让云成为云化战略成功的基石,而不是障碍。它可以根据商业能力对公司进行重组的能力,既包含技术、也包含管理,可以说是一系列云技术和企业管理方法的集合,通过实践及与其他工具相结合更好地帮助用户实现数字化转型。
##云原生计算基金会(CNCF)

CNCF,即云原生计算基金会,2015年由谷歌牵头成立,基金会成员目前已有一百多企业与机构,包括亚马逊、微软、思科等巨头。

目前CNCF所托管的应用已达14个,下图为其公布的Cloud Native Landscape,给出了云原生生态的参考体系。
3.jpg

Cloud Native Landscape新版

CNCF(云原生计算基金会)认为云原生系统需包含的属性:

* 容器化封装:以容器为基础,提高整体开发水平,形成代码和组件重用,简化云原生应用程序的维护。在容器中运行应用程序和进程,并作为应用程序部署的独立单元,实现高水平资源隔离。
* 自动化管理:统一调度和管理中心,从根本上提高系统和资源利用率,同时降低运维成本。
* 面向微服务:通过松耦合方式,提升应用程序的整体敏捷性和可维护性。

正因为如此,你可以专注于创新,解决业务问题,而不是把时间花在“静态、不灵活的传统架构”存在的许多技术问题。
#云原生的四要素:持续交付、DevOps、微服务、容器

从云原生的概念中,我们总是能看到持续交付、DevOps、微服务、容器等技术的出现,那么它们到底是什么,这里引用Pivotal台湾云计算资深架构师的部分观点,为大家逐一揭开他们的神秘面纱!
4.jpg

##持续交付——缩小开发者认知,灵活开发方向

首先是持续交付,什么样的时候客户要求持续交付?敏捷开发要求持续交付,因为敏捷开发要求随时有一个版本可以上到大群环境,所以要持续交付。

而换句话说,持续交付就是不误时开发。举一个例子,有些公司非常喜欢谈需求,谈很久,可是开发只剩1/3时间就开发完成,然后交付,再上线运营。这就会碰到一个问题,就是你开始谈需求到最后交付产品的时间,短则三月,长则半年,这中间市场已经变化了,需求也随之变化了。因此市场上出现了新的想法,即是不是能够小步快跑,把交付的周期缩短一点,我可以实现快速交付,每次交付都可以重新确认方向,这样尽量避免与未来期待的落差。
5.jpg

用小步快跑的方式,打破瀑布式开发流程

那么问题来了,持续交付对于开发的人谈的需求、开发的方式有改变,那它对于开发有影响吗?如果说公司的开发团队一天可以交付五次,那研发团队要帮忙部署一次吗?现在公司大部分部署都是研发团队帮忙部署应用的,研发团队部署五次,要改版五次就需要部署一次,这是无法实现的。而且每次部署的时候都要面对停机,而实际公司的应用经不起一天停机五次部署,在互联网的思维之下,零宕机时间已经是现在企业的基本要求。于是“蓝绿部署”的概念营运而生。即在一个环境里面,第一版还在线上服务,第二版先做封测,封测完成后,让外面的流量进来一些,看log是不是开发人员要的,确认后再把全部的流量导到新的版本上。
6.jpg

蓝绿(Blue-Green)部署

但“蓝绿部署”在系统过多过复杂的情况下,在传统架构上实现非常困难,所以企业要做到zero down time的持续交付就需要有良好的平台與工具协助。因此,持续交付的优势在于,它可以缩小开发者认知,重新确认开发方向。
##微服务——内聚更强,更加敏捷

第二部分是微服务。微服务是什么?有客户表示,提供商出产品,客户把应用全部放上去,结果就是一个微服务。这种认知是错误的,因为微服务是一个架构的改变。那么微服务是怎么做的呢?它所面临的最大挑战是什么?

是切割。那么如何切割呢?其实这件事情早在1968年康威就提出了——康威定律,系统的服务划分应该是根据组织架构的功能来划分。1968年康威就提出了这个想法,我认为拿来做微服务的切割非常适用。
7.jpg

Going Agile - Breaking the monolith Conway's Law and Microservices

这样按照组织架构划分的优势在于:

  1. 内聚更强,所有遵循同一种业务准则的人内聚在一起,就容易解决问题。
  2. 服务解耦,变更容易,更加敏捷。当做到解耦合的时候,要变更就容易。所以微服务应该是切分成这个样子,由上而下来切,根据Function来切。

另外一个划分微服务的技巧,可以运用领域驱动设计(Domain Driven Design)的理论,而领域驱动设计亦可算是面向物件的一种设计思维;聚合可以让微服务划分更有依据,也让未來的系統变更具有弹性。值得一提的是领域驱动设计,也提供微服务中的事物问题。因为过去巨石应用进行两个报数的阶段,相当容易也常见,但在微服务架构中,如何在分散的服务中进行事物就显得相当困难。利用领域驱动设计的Event Souring进行设计,是目前最好的解決办法。

那么在什么情况下需要微服务?我认为有三个标准:

  1. 有HA(High Available)的需求需要微服务。
  2. 有性能调校的需求(例如:图片的呈现或者搜寻)需要微服务。
  3. 经常变更的需要微服务。

实际上,微服务需要关注的源代码范围比较小,使得各个服务解耦、变更容易,内聚更强,因为都会集中在服务里。另外,它更容易单独改版,因为微服务之间是用RESTful间接起来的,用RESTful只要API的界面不改,原则上则不会错,也更敏捷。

但微服务也会留下一些问题,例如App团队如何分工?环境怎么配合?如何实现自动化部署?
##容器技术——使资源调度、微服务更容易

再来看看容器。在机器上运行的容器只是主机操作系统上的一个进程,与任何其他进程无异。那么,为什么容器如此受欢迎呢?原因在于这个进程被隔离和限制的方式。这种方式很特殊,可简化开发和运维。

其实1979年就有容器技术,很多人会以为说Docker是不是等于容器,其实Docker不等于容器。容器的历史可追溯到Linux操作系统。容器利用了Linux的内核功能。Linux中容器的核心概念(cgroup、namespaces和filesystems)在独立的区域运行。容器的神奇之处在于将这些技术融为一体,以实现最大的便利性。

VMware之前的技术专家在2011年发展出一个技术,把这个技术贡献出来成立了一个Cloud Foundry基金会。Docker在2013年才开始有,而且它第一版是用SLC的技术去做的。后来陆续一路成长,使得为服务的实现更容易了。
8.jpg

从 Infra 角度来看技术演进

从上面这个表中可以看出,从左边开始,IaaS,虚拟化技术有了之后,刚刚提到的所谓第三代平台,这四个区块开发人员交付的内容不一样。所有的IaaS、CaaS、PaaS、FaaS一路的变化演进,对于客户的负担越到后面越小,而对于开发人员的想象力则愈发抽象。

大家一定会遇到下列这些计算,一个是所谓的单体应用,或者翻译成巨石应用。此外,你们一定会有一些批次的管理,另外就是所谓的数据库的部分,开始可能会有容器技术,像Kubernetes、Docker。

Docker是软件行业最受欢迎的软件容器项目之一。思科、谷歌和IBM等公司在其基础设施和产品中使用Docker容器。

Kubernetes是软件容器领域的另一个值得关注的项目。Kubernetes是一个允许自动化部署、管理和伸缩容器的工具。为了便于管理其容器,谷歌建立了Kubernetes。它提供了一些强大的功能,例如容器之间的负载均衡,重启失败的容器以及编排容器使用的存储。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
9.jpg

容器生态图

容器为云原生应用程序增加了更多优势。使用容器,你可以将微服务及其所需的所有配置、依赖关系和环境变量移动到全新的服务器节点上,而无需重新配置环境,这样就实现了强大的可移植性。
##DevOps——以终为始,运维合一

10.png

最后让我们走向DevOps,它不是一种工具,DevOps其实要谈的是运维合一。

DevOps如果从字面上来理解只是Dev(开发人员)+Ops(运维人员),实际上,它是一组过程、方法与系统的统称,其概念从2009年首次提出发展到现在,内容也非常丰富,有理论也有实践,包括组织文化、自动化、精益、反馈和分享等不同方面。

首先,组织架构、企业文化与理念等,需要自上而下设计,用于促进开发部门、运维部门和质量保障部门之间的沟通、协作与整合,简单而言组织形式类似于系统分层设计。

其次,自动化是指所有的操作都不需要人工参与,全部依赖系统自动完成,比如上述的持续交付过程必须自动化才有可能完成快速迭代。再次,DevOps的出现是由于软件行业日益清晰地认识到,为了按时交付软件产品和服务,开发部门和运维部门必须紧密合作。

总之,DevOps强调的是高效组织团队之间如何通过自动化的工具协作和沟通来完成软件的生命周期管理,从而更快、更频繁地交付更稳定的软件。在内部沟通上,你可以想象DevOps是一个敏捷思維,是一个沟通的文化。当运营和研发有良好的沟通效率,才可以有更大的生产力。如果你的自动化程度够高,可以自主可控,工作负担降低,DevOps能够带来更好的工作文化、更高的工作效率。
#总结

综上所述,云原生的DevOps、平台、持续交付、微服务都是云原生不可或缺的一部分,需要以全局地眼光看待问题,脱离任何一个元素,对于企业来说都是“管中窥豹”、“一叶障目”,只有加以整合才能见到云原生的全局风貌。

面对业态各异的业务上云以及碎片化的物联网解决方案部署,利用云原生思维和模式,构建基于云原生的物联网平台以及解决方案,势必将加速企业,甚至整个社会的数字化转型。

原文链接:https://mp.weixin.qq.com/s/RaAyjfGacHc7xpRahpfv8Q

Spring Cloud微服务如何设计异常处理机制?

大卫 发表了文章 • 0 个评论 • 208 次浏览 • 2019-05-30 12:58 • 来自相关话题

#前言 今天和大家聊一下在采用Spring Cloud进行微服务架构设计时,微服务之间调用时异常处理机制应该如何设计的问题。我们知道在进行微服务架构设计时,一个微服务一般来说不可避免地会同时面向内部和外部提供相应的功能服务接口。面向外部提供的服务 ...查看全部
#前言

今天和大家聊一下在采用Spring Cloud进行微服务架构设计时,微服务之间调用时异常处理机制应该如何设计的问题。我们知道在进行微服务架构设计时,一个微服务一般来说不可避免地会同时面向内部和外部提供相应的功能服务接口。面向外部提供的服务接口,会通过服务网关(如使用Zuul提供的apiGateway)面向公网提供服务,如给App客户端提供的用户登陆、注册等服务接口。

而面向内部的服务接口,则是在进行微服务拆分后由于各个微服务系统的边界划定问题所导致的功能逻辑分散,而需要微服务之间彼此提供内部调用接口,从而实现一个完整的功能逻辑,它是之前单体应用中本地代码接口调用的服务化升级拆分。例如,需要在团购系统中,从下单到完成一次支付,需要交易系统在调用订单系统完成下单后再调用支付系统,从而完成一次团购下单流程,这个时候由于交易系统、订单系统及支付系统是三个不同的微服务,所以为了完成这次用户订单,需要App调用交易系统提供的外部下单接口后,由交易系统以内部服务调用的方式再调用订单系统和支付系统,以完成整个交易流程。如下图所示:
1.png

这里需要说明的是,在基于SpringCloud的微服务架构中,所有服务都是通过如consul或eureka这样的服务中间件来实现的服务注册与发现后来进行服务调用的,只是面向外部的服务接口会通过网关服务进行暴露,面向内部的服务接口则在服务网关进行屏蔽,避免直接暴露给公网。而内部微服务间的调用还是可以直接通过consul或eureka进行服务发现调用,这二者并不冲突,只是外部客户端是通过调用服务网关,服务网关通过consul再具体路由到对应的微服务接口,而内部微服务则是直接通过consul或者eureka发现服务后直接进行调用。如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
#异常处理的差异

面向外部的服务接口,我们一般会将接口的报文形式以JSON的方式进行响应,除了正常的数据报文外,我们一般会在报文格式中冗余一个响应码和响应信息的字段,如正常的接口成功返回:
 {
"code" : "0",
"msg" : "success",
"data" : {
"userId" : "zhangsan",
"balance" : 5000
}
}

而如果出现异常或者错误,则会相应地返回错误码和错误信息,如:
 {
"code" : "-1",
"msg" : "请求参数错误",
"data" : null
}

在编写面向外部的服务接口时,服务端所有的异常处理我们都要进行相应地捕获,并在controller层映射成相应地错误码和错误信息,因为面向外部的是直接暴露给用户的,是需要进行比较友好的展示和提示的,即便系统出现了异常也要坚决向用户进行友好输出,千万不能输出代码级别的异常信息,否则用户会一头雾水。对于客户端而言,只需要按照约定的报文格式进行报文解析及逻辑处理即可,一般我们在开发中调用的第三方开放服务接口也都会进行类似的设计,错误码及错误信息分类得也是非常清晰!

而微服务间彼此的调用在异常处理方面,我们则是希望更直截了当一些,就像调用本地接口一样方便,在基于Spring Cloud的微服务体系中,微服务提供方会提供相应的客户端SDK代码,而客户端SDK代码则是通过FeignClient的方式进行服务调用,如:而微服务间彼此的调用在异常处理方面,我们则是希望更直截了当一些,就像调用本地接口一样方便,在基于Spring Cloud的微服务体系中,微服务提供方会提供相应的客户端SDK代码,而客户端SDK代码则是通过FeignClient的方式进行服务调用,如:
@FeignClient( value = "order", configuration = OrderClientConfiguration.class, fallback = OrderClientFallback.class )
public interface OrderClient {
/[i] 订单(内) [/i]/
@RequestMapping( value = "/order/createOrder", method = RequestMethod.POST )
OrderCostDetailVo orderCost( @RequestParam(value = "orderId") String orderId,
@RequestParam(value = "userId") long userId,
@RequestParam(value = "orderType") String orderType,
@RequestParam(value = "orderCost") int orderCost,
@RequestParam(value = "currency") String currency,
@RequestParam(value = "tradeTime") String tradeTime )
}

而服务的调用方在拿到这样的SDK后就可以忽略具体的调用细节,实现像本地接口一样调用其他微服务的内部接口了,当然这个是FeignClient框架提供的功能,它内部会集成像Ribbon和Hystrix这样的框架来实现客户端服务调用的负载均衡和服务熔断功能(注解上会指定熔断触发后的处理代码类),由于本文的主题是讨论异常处理,这里暂时就不作展开了。

现在的问题是,虽然FeignClient向服务调用方提供了类似于本地代码调用的服务对接体验,但服务调用方却是不希望调用时发生错误的,即便发生错误,如何进行错误处理也是服务调用方希望知道的事情。另一方面,我们在设计内部接口时,又不希望将报文形式搞得类似于外部接口那样复杂,因为大多数场景下,我们是希望服务的调用方可以直截了的获取到数据,从而直接利用FeignClient客户端的封装,将其转化为本地对象使用。
@Data
@Builder
public class OrderCostDetailVo implements Serializable {
private String orderId;
private String userId;
private int status; /[i] 1:欠费状态;2:扣费成功 [/i]/
private int orderCost;
private String currency;
private int payCost;
private int oweCost;
public OrderCostDetailVo( String orderId, String userId, int status, int orderCost, String currency, int payCost,
int oweCost )
{
this.orderId = orderId;
this.userId = userId;
this.status = status;
this.orderCost = orderCost;
this.currency = currency;
this.payCost = payCost;
this.oweCost = oweCost;
}
}

如我们在把返回数据就是设计成了一个正常的VO/BO对象的这种形式,而不是向外部接口那么样额外设计错误码或者错误信息之类的字段,当然,也并不是说那样的设计方式不可以,只是感觉会让内部正常的逻辑调用,变得比较啰嗦和冗余,毕竟对于内部微服务调用来说,要么对,要么错,错了就Fallback逻辑就好了。

不过,话虽说如此,可毕竟服务是不可避免的会有异常情况的。如果内部服务在调用时发生了错误,调用方还是应该知道具体的错误信息的,只是这种错误信息的提示需要以异常的方式被集成了FeignClient的服务调用方捕获,并且不影响正常逻辑下的返回对象设计,也就是说我不想额外在每个对象中都增加两个冗余的错误信息字段,因为这样看起来不是那么优雅!

既然如此,那么应该如何设计呢?
#最佳实践设计

首先,无论是内部还是外部的微服务,在服务端我们都应该设计一个全局异常处理类,用来统一封装系统在抛出异常时面向调用方的返回信息。而实现这样一个机制,我们可以利用Spring提供的注解@ControllerAdvice来实现异常的全局拦截和统一处理功能。如:
@Slf4j
@RestController
@ControllerAdvice
public class GlobalExceptionHandler {
@Resource
MessageSource messageSource;
@ExceptionHandler( { org.springframework.web.bind.MissingServletRequestParameterException.class } )
@ResponseBody
public APIResponse processRequestParameterException( HttpServletRequest request,
HttpServletResponse response,
MissingServletRequestParameterException e )
{
response.setStatus( HttpStatus.FORBIDDEN.value() );
response.setContentType( "application/json;charset=UTF-8" );
APIResponse result = new APIResponse();
result.setCode( ApiResultStatus.BAD_REQUEST.getApiResultStatus() );
result.setMessage(
messageSource.getMessage( ApiResultStatus.BAD_REQUEST.getMessageResourceName(),
null, LocaleContextHolder.getLocale() ) + e.getParameterName() );
return(result);
}

@ExceptionHandler( Exception.class )
@ResponseBody
public APIResponse processDefaultException( HttpServletResponse response,
Exception e )
{
/[i] log.error("Server exception", e); [/i]/
response.setStatus( HttpStatus.INTERNAL_SERVER_ERROR.value() );
response.setContentType( "application/json;charset=UTF-8" );
APIResponse result = new APIResponse();
result.setCode( ApiResultStatus.INTERNAL_SERVER_ERROR.getApiResultStatus() );
result.setMessage( messageSource.getMessage( ApiResultStatus.INTERNAL_SERVER_ERROR.getMessageResourceName(), null,
LocaleContextHolder.getLocale() ) );
return(result);
}

@ExceptionHandler( ApiException.class )
@ResponseBody
public APIResponse processApiException( HttpServletResponse response,
ApiException e )
{
APIResponse result = new APIResponse();
response.setStatus( e.getApiResultStatus().getHttpStatus() );
response.setContentType( "application/json;charset=UTF-8" );
result.setCode( e.getApiResultStatus().getApiResultStatus() );
String message = messageSource.getMessage( e.getApiResultStatus().getMessageResourceName(),
null, LocaleContextHolder.getLocale() );
result.setMessage( message );
/[i] log.error("Knowned exception", e.getMessage(), e); [/i]/
return(result);
}

/**
* 内部微服务异常统一处理方法
*/
@ExceptionHandler( InternalApiException.class )
@ResponseBody
public APIResponse processMicroServiceException( HttpServletResponse response,
InternalApiException e )
{
response.setStatus( HttpStatus.OK.value() );
response.setContentType( "application/json;charset=UTF-8" );
APIResponse result = new APIResponse();
result.setCode( e.getCode() );
result.setMessage( e.getMessage() );
return(result);
}
}


如上述代码,我们在全局异常中针对内部统一异常及外部统一异常分别作了全局处理,这样只要服务接口抛出了这样的异常就会被全局处理类进行拦截并统一处理错误的返回信息。

理论上我们可以在这个全局异常处理类中,捕获处理服务接口业务层抛出的所有异常并统一响应,只是那样会让全局异常处理类变得非常臃肿,所以从最佳实践上考虑,我们一般会为内部和外部接口分别设计一个统一面向调用方的异常对象,如外部统一接口异常我们叫ApiException,而内部统一接口异常叫InternalApiException。这样,我们就需要在面向外部的服务接口controller层中,将所有的业务异常转换为ApiException;而在面向内部服务的controller层中将所有的业务异常转化为InternalApiException。如:
@RequestMapping( value = "/creatOrder", method = RequestMethod.POST )
public OrderCostDetailVo orderCost(
@RequestParam(value = "orderId") String orderId,
@RequestParam(value = "userId") long userId,
@RequestParam(value = "orderType") String orderType,
@RequestParam(value = "orderCost") int orderCost,
@RequestParam(value = "currency") String currency,
@RequestParam(value = "tradeTime") String tradeTime ) throws InternalApiException
{
OrderCostVo costVo = OrderCostVo.builder().orderId( orderId ).userId( userId ).busiId( busiId ).orderType( orderType )
.duration( duration ).bikeType( bikeType ).bikeNo( bikeNo ).cityId( cityId ).orderCost( orderCost )
.currency( currency ).strategyId( strategyId ).tradeTime( tradeTime ).countryName( countryName )
.build();
OrderCostDetailVo orderCostDetailVo;
try {
orderCostDetailVo = orderCostServiceImpl.orderCost( costVo );
return(orderCostDetailVo);
} catch ( VerifyDataException e ) {
log.error( e.toString() );
throw new InternalApiException( e.getCode(), e.getMessage() );
} catch ( RepeatDeductException e ) {
log.error( e.toString() );
throw new InternalApiException( e.getCode(), e.getMessage() );
}
}

如上面的内部服务接口的controller层中将所有的业务异常类型都统一转换成了内部服务统一异常对象InternalApiException了。这样全局异常处理类,就可以针对这个异常进行统一响应处理了。

对于外部服务调用方的处理就不多说了。而对于内部服务调用方而言,为了能够更加优雅和方便地实现异常处理,我们也需要在基于FeignClient的SDK代码中抛出统一内部服务异常对象,如:
@FeignClient( value = "order", configuration = OrderClientConfiguration.class, fallback = OrderClientFallback.class )
public interface OrderClient {
/[i] 订单(内) [/i]/
@RequestMapping( value = "/order/createOrder", method = RequestMethod.POST )
OrderCostDetailVo orderCost( @RequestParam(value = "orderId") String orderId,
@RequestParam(value = "userId") long userId,
@RequestParam(value = "orderType") String orderType,
@RequestParam(value = "orderCost") int orderCost,
@RequestParam(value = "currency") String currency,
@RequestParam(value = "tradeTime") String tradeTime ) throws InternalApiException
};

这样在调用方进行调用时,就会强制要求调用方捕获这个异常,在正常情况下调用方不需要理会这个异常,像本地调用一样处理返回对象数据就可以了。在异常情况下,则会捕获到这个异常的信息,而这个异常信息则一般在服务端全局处理类中会被设计成一个带有错误码和错误信息的json数据,为了避免客户端额外编写这样的解析代码,FeignClient为我们提供了异常解码机制。如:
@Slf4j
@Configuration
public class FeignClientErrorDecoder implements feign.codec.ErrorDecoder {
private static final Gson gson = new Gson();
@Override
public Exception decode( String methodKey, Response response )
{
if ( response.status() != HttpStatus.OK.value() )
{
if ( response.status() == HttpStatus.SERVICE_UNAVAILABLE.value() )
{
String errorContent;
try {
errorContent = Util.toString( response.body().asReader() );
InternalApiException internalApiException = gson.fromJson( errorContent, InternalApiException.class );
return(internalApiException);
} catch ( IOException e ) {
log.error( "handle error exception" );
return(new InternalApiException( 500, "unknown error" ) );
}
}
}
return(new InternalApiException( 500, "unknown error" ) );
}
}

我们只需要在服务调用方增加这样一个FeignClient解码器,就可以在解码器中完成错误消息的转换。这样,我们在通过FeignClient调用微服务时就可以直接捕获到异常对象,从而实现向本地一样处理远程服务返回的异常对象了。

作者:若丨寒
链接:https://www.jianshu.com/p/9fb7684bbeca

聊一聊 Kafka 应用实践与生态集成的相关内容

齐达内 发表了文章 • 0 个评论 • 184 次浏览 • 2019-05-30 11:38 • 来自相关话题

#前言 Apache Kafka 发展至今,已经是一个很成熟的消息队列组件了,也是大数据生态圈中不可或缺的一员。Apache Kafka 社区非常的活跃,通过社区成员不断的贡献代码和迭代项目,使得 Apache Kafka 功能越发丰富、性能越发稳 ...查看全部
#前言
Apache Kafka 发展至今,已经是一个很成熟的消息队列组件了,也是大数据生态圈中不可或缺的一员。Apache Kafka 社区非常的活跃,通过社区成员不断的贡献代码和迭代项目,使得 Apache Kafka 功能越发丰富、性能越发稳定,截止本篇文章 Apache Kafka 发布了 V2.2.0 版本。

那么,今天就来聊一聊 Kafka 应用实践与生态集成的相关内容。
#如何知道 Kafka 是否适合你?
项目立项时,会做技术调研,那么如何知道你选择的 Kafka 技术是否能够满足你?据 Confluent 公司调研报告可知,Apache Kafka 在过去几年中在功能和覆盖范围方面取得了很大成就。它被财富500强中的三分之一用于生产,包括全球十大银行中的七家,十大保险公司中的八家,以及美国十大电信公司中的九家。接下来,为大家介绍 Kafka 示例来帮助大家了解常见的使用模式。并且希望大家能找到与自己的工作流程有交集的地方,这样大家就可以开始利用 Kafka 的强大功能了。
1.jpg

下面让先来看看 Kafka 提供的两个核心功能:
##消息系统
消息系统常见的两种模式:

* 队列:队列消费者充当了工作组的角色,每条消息记录只传递给一个工作进程,从而有效的划分工作流程;
* 发布与订阅:订阅者通常是彼此独立的,每个订阅者都可以获得每条消息的副本。

这两种模式都是有效和实用的,通过队列将工作内容分开,用于容错和扩展;发布与订阅能够允许多租户,来使系统解耦。而 Apache Kafka 的有点之一在于它将队列、发布与订阅结合到了一个强大的消息系统中。
##流处理
Apache Kafka 拥有强大,可扩展的消息系统,只需要一种简单的方法来处理消息流。而在 Kafka 中,Stream API 提供这一功能,它是一个 Java 客户端类库,提供比 Producer 和 Consumer 更高级别的抽象 API 。

这使得它使用起来非常的方便:

* 无状态操作,例如过滤和转换流消息;
* 有状态操作,例如时间窗口上的连接和聚合。

Stream API 处理消息的序列化与反序列化,同时维护有状态操作所需要的状态。
##典型的 Kafka 案例
旅游行业:例如,在一个旅游网站,酒店和航班的价格是一直在变化的,系统的一些组件 ( 价格告警、分析等 ) 需要了解这些变化。你在 Kafka 的 Topic 上发布更改,并且需要通知的每个组件都充当一个消费者。每个消费者应用所组成的节点形成一个消费者组。给消费者组所消费的 Topic 的发送消息动态记录,这样每个消费者均可获取消息记录,同时每个消费者内能够有效的划分工作内容。

用户分析:页面查看、搜索、用户行为分析等,这些实际上是 Kafka 在 LinkedIn 设计的原始初衷。用户点击网站活动内容,每个活动类型均有一个 Topic,可以实时的反馈,以便深入了解用户参与度、下载量、页面流量等。

GPS:例如,能够实时获取智能手机设备的位置数据,并且希望能够实时处理这些数据来显示车辆路径、行驶距离等。传入数据到 Kafka 的 Topic 中,并使用 Stream API 来进行处理。当需要在特定时间段内提取和处理给定用户的所有位置数据时,使用窗口进行状态处理会有不错的效果。
#Kafka 的内部存储工作原理是什么?
如何你确定了 Kafka 技术适合你当前的项目,满足你的业务需求。你可能会很好奇,Kafka 的内部存储工作原理是什么呢?接下来,将给大家分析 Kafka 是如何存储其数据的。
##Kafka存储单元是分区
Topic 中的分区是有序写入的,且不可变的消息序列。分区不能跨多个 Broker 或者多个磁盘来进行分割。
2.jpg

##保留策略来管理 Topic 中消息
在你创建的 Topic 中,你可以指定保留多少数据或者保留多长时间的数据,然后 Kafka 会按照顺序来清除这些消息 ( 不管消息是否有被使用 ) 。
##分区片段
Kafka 需要定期查找磁盘上待清除的数据,对于分区消息单个非常长的文件,该操作会很慢并且容易出错。为了解决这个问题,Kafka 实行了分区分片策略。当 Kafka 将消息写入分区时,它会写入到一个片段,如果该片段到达阀值,则会新开一个新的片段来写入。片段以偏移量来命名,片段的偏移量是大于前一个片段的偏移量且小于或者等于当前片段中的偏移量。
3.jpg

##片段日志是存储消息的位置
每条消息都包含值、偏移量、时间戳、主键 ( KEY ) 、消息大小、压缩编解码器、校验、以及消息格式的版本。磁盘上的数据格式与 Broker 通过网络从 Producer 端接收的格式完全相同,然后由 Consumer 去获取数据,这使得 Kafka 能够通过零拷贝技术有效的传输数据。
##片段索引将消息偏移量映射到它们在日志中的位置
4.jpg

索引文件是内存映射的,偏移量查找时使用二进制搜索来查找小于或等于最近的目标偏移量。索引文件由8个字节组成,4个字节用于存储基本偏移量,另外4个字节来存储位置。
##Kafka 将压缩的消息包装在一起
发送压缩消息的 Producer 端会将压缩批处理,并将其作为包装消息的有效负载发送。和之前一样,磁盘上的数据与 Broker 通过网络从 Producer 端接收并发送给其 Consumer 的数据完全相同。
5.jpg

##Kafka 内部存储工作原理小结

* Kafka 的存储单元是分区;
* 分区通过片段来进行分割;
* 片段包含两个文件:索引和日志文件;
* 索引将每个偏移量映射到它们所在日志中的消息位置,用于查找消息;
* 压缩消息批处理作为包装消息的有效负载;
* 存储在磁盘上的数据与 Broker 通过网络从 Producer 端接收并发给 Consumer 的数据相同。

#Kafka API 之间的选择与竞争
Kafka 的核心尽管在一段时间内保持相对的稳定,但是 Kafka 生态圈仍然在快速的发展。最初的 Kafka,包含 Producer 和 Consumer,很容易理解。现在 Kafka 处理 Producer 和 Consumer,还有 Kafka Connect 、Kafka Streams 、以及 KSQL。
6.jpg

##如何正确的选择 Kafka API
Kafka Producer API:应用直接生成数据,例如移动设备、PC 、其他硬件等。

Kafka Connect Source API:应用程序桥接在我们无法控制的数据存储介质,例如 MongoDB 、ElasticSearch、RESTAPI 等。

Kafka Streams API/KSQL:如果希望像 SQL 一样操作实时流数据,可以通过 KSQL 来完成;如果需要编写复杂的业务逻辑,可以使用 Kafka Streams API 来完成。

Kafka Consumer API:直接读取流数据,并对其执行实时操作,例如推送商品促销活动、发送邮件、获取游戏行为等。

Kafka Connect Sink API:读取实时流并将其存储到目标介质中,例如 Kafka 到 S3、Kafka 到 HDFS、Kafka 到 HBase 等。

选择不同的 API 来实现不同的业务需求,例如,如果希望为实现的需求编写大量的自定义代码,Kafka Consumer API 和 Kafka Connect Sink API 完全是可以互换的。总而言之,上述 API 可以帮助你在实际的业务中以最少的代码量来实现最有效的工作流程。
##各个API的优势和局限
Kafka Producer API

优势: Kafka Producer API 使用起来非常的简单,异步发送数据,获取回调结果。非常适合直接发送数据流的应用程序,例如日志、点击流、物联网等。

局限:可以扩展和构建 Kafka Producer API 以便执行更多的操作,但是这需要开发人员编写更多的附加逻辑。例如,试图使用 Kafka Producer API 在数据库和 Kafka 之间执行 ETL 操作时,如何跟踪偏移量 ( 即当 Producer 端停止后,如何正确恢复你的 Producer 应用程序 ) ?如何在若干个 Producer 之间分配 ETL 的负载?这种情况,我们使用 Kafka Connect Source API 会更好一些。

Kafka Connect Source API

优势:Kafka Connect Source API 是一个构建在 Kafka Producer API 之上的完整框架。它的构建是为了让开发人员能够获得更好的 API,以便为并行处理生成并分配任务。另外,可以使用各种各样的连接器,利用这些连接器来处理大多数数据介质,且无需编写任何代码。

局限:适配的数据源连接器均是专属的,如果你当前的数据源在已有的连接器中不包含,需要自行编写连接器来进行适配。

Kafka Consumer API

优势:Kafka Consumer API 非常简单,可以使用 Consumer Groups,因此可以并行使用 Topic 。新版本的 Kafka ( V2.2.0 ) 对于偏移量的管理和提交、Balance 、幂等性等无需开发者关心。

局限:在 ETL 场景中,Kafka Connect Sink 更加合适,因为它们会避免针对外部数据源编写复杂的逻辑。

Kafka Connect Sink API

优势:与 Kafka Connect Source API 类似,Kafka Connect Sink API 允许利用现有的 Kafka 连接器的生态系统来执行流式 ETL,且无需编写任何代码。Kafka Connect Sink API 建立在 Kafka Consumer API 的基础之上,但是与它有所不同。

局限:如果写入的数据源没有可用的适配器,那么需要自行编写 Kafka Connect 连接器,并且调试过程会有些复杂。

Kafka Streams API

优势:对于流处理场景,Kafka 中附带 Kafka Streams API,并且能够编写高级 DSL ( 类似于函数式编程或者Spark类型的程序 ) 或偏底层的 API ( 类似于Storm ) 。Kafka Streams API 完全隐藏了 Producer 和 Consumer 的复杂性,让开发者更加专注于流处理的逻辑实现上。同时,它还具有连接、聚合、一次性处理等功能。

局限:使用 Kafka Streams API 会让编码门槛提高,同时也可能让你业务逻辑变得复杂。

KSQL

优势:KSQL 不是 Kafka API 的直接组成部分,而是 Kafka Streams 之上的包装器。这里还是值得一说的,虽然 Kafka Streams 允许编写一些复杂的 Topology,但它还是需要一些实质性的编程知识,尤其是新手来说。KSQL 希望通过提供与现有的 SQL 语义类似来抽象出这种复杂性。对于开发者来说,KSQL 是非常具有诱惑力的,它使得流处理器变得轻而易举。

局限:对于复杂的业务场景,对数据进行复杂的转换操作,或一些特定的需求,可能还是需要使用 Kafka Streams 来完成。
#Kafka 与 Kubernetes 结合是否效率更高?
7.jpg

##介绍
Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。Kubernetes 旨在运行无状态工作负载,这些工作负载通常采用微服务架构形式,轻量级、水平扩展。而 Kafka 的本质上是一个分布式的存储介质,这意味着你在使用时必需处理状态,它比微服务更重要。尽管 Kubernetes 支持有状态的工作负载,但还是需要谨慎使用。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

那么,应该在 Kubernetes 上运行 Kafka 吗?如何让 Kafka 和 Kubernetes 互相补充,以及如何避免可能遇到的“坑”?
##基础指标
进程:Kafka Broker 对 CPU 很友好,TLS 的引入可能会产生一些开销。Kafka Client 如果使用加密会需要更多的 CPU,但是这并不会影响 Broker。

内存:Kafka Broker 的 JVM 通常可以设置为 4GB-8GB 之间,但是由于 Kafka 大量使用了页面缓存,因此还是需要有足够的系统内存。在 Kubernetes 中,相应的设置容器资源限制和请求。

存储:容器中的存储是暂时的,重启后数据将会丢失,但是可以对 Kafka 数据使用空目录卷。因此,需要使用持久化存储,存储必须是非本地的,以便 Kubernetes 在重启后或重新定位后更加灵活的选择另一个节点。

网络:与大多数分布式系统一样,Kafka 性能在很大程度上取决于低网络延迟和高带宽。建议不要把所有的 Broker 放在同一个节点,因为这样会降低可用性。如果 Kubernetes 节点出现故障,那么整个 Kafka 集群都会出现故障。
##性能
安装 Kafka 之前,做 POC 测试是非常重要的。这样做的好处是,在遇到有关性能瓶颈问题时,可以提供帮助。而 Kafka 附带了两个 POC 测试工具,它们分别是:kafka-producer-perf-test.sh 和 kafka-consumer-perf-test.sh。

监控:监控 Kafka 指标是非常有必要的,能够让我们及时的掌握 Kafka、Zookeeper 集群的健康状态,例如使用 Kafka Eagle 来监控和管理 Kafka Topic(http://www.kafka-eagle.org/ )。

日志:日志是一个比较关键的部分,确保 Kafka 安装中所有的容器都记录到 stdout 和 stderr 中,并确保 Kubernetes 集群日志能集中管理,例如输送到 ElasticSearch。

动态更新:StatefulSets 支持自动更新,RollingUpdate 策略将一次更新一个 Kafka Pod,来实现零停机维护,这也是 Kubernetes 的优势之一。

扩容:Kubernetes 可以很容易的将 Pod 缩放到一定数量的副本,这意味着可以声明性的定义所需数量的 Kafka Broker 。

备份&还原:Kafka 部署在 Kubernetes 中,这样 Kafka 的可用性就取决于 Kubernetes 的可用性,如果 Kubernetes 集群出现故障,那么 Kafka 的可用性就会下降,同时,也会出现数据丢失的风险,因此需要做好数据备份策略,例如 MirrorMaker,或是 S3 进行连接备份。
##对于 Kubernetes 的选择
对于中小型的 Kafka 集群,将 Kafka 部署到 Kubernetes 是一个不错的选择,因为它提供了更大的灵活性、且简化了操作。如果在延迟或吞吐量方面有较高的功能性要求,独立部署的方式可能会更好。
#总结
本篇文章,介绍了 Kafka 应用实践与生态集成,通过参考本篇文章的内容,大家可以做出合理、有效的选择。

原文链接:https://mp.weixin.qq.com/s/KWkMGd7bvEYhWXRn_udiNg

云原生之下的Java

尼古拉斯 发表了文章 • 0 个评论 • 169 次浏览 • 2019-05-30 10:22 • 来自相关话题

自从公司的运行平台全线迁入了 Kubenetes 之后总是觉得 DevOps 变成了一个比以前更困难的事情,反思了一下,这一切的困境居然是从现在所使用的 Java 编程语言而来,那我们先聊聊云原生。 Cloud Native 在我的理 ...查看全部
自从公司的运行平台全线迁入了 Kubenetes 之后总是觉得 DevOps 变成了一个比以前更困难的事情,反思了一下,这一切的困境居然是从现在所使用的 Java 编程语言而来,那我们先聊聊云原生。

Cloud Native 在我的理解是,虚拟化之后企业上云,现在的企业几乎底层设施都已经云化之后,对应用的一种倒逼,Cloud Native 是一个筐,什么都可以往里面扔,但是有些基础是被大家共识的,首先云原生当然和编程语言无关,说的是一个应用如何被创建/部署,后续的就引申出了比如 DevOps 之类的新的理念,但是回到问题的本身,Cloud Native 提出的一个很重要的要求,应用如何部署 这个问题从以前由应用决定,现在变成了,基础设施 决定 应用应该如何部署。如果你想和更多Kubernetes技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

让我们回到一切的开始,首先云原生亦或者是 DevOps 都有一个基础的要求,当前版本的代码能够在任何一个环境运行,看起来是不是一个很简单的需求,但是这个需求有一个隐喻所有的环境的基础设施是一样的,显然不能你的开发环境是 Windows 测试环境 Debian 生产环境又是 CentOS 那怎么解决呢,从这一环,我们需要一个工具箱然后往这个工具箱里面扔我们需要的工具了。首先我们需要的就是 Cloud Native 工具箱中最为明显的产品 Docker/Continar,经常有 Java 开发者问我,Docker 有什么用,我的回答是,Docker 对 Java 不是必须的,但是对于其他的语言往往是如果伊甸园中的苹果一样的诱人,打个比方,一个随系统打包的二进制发行版本,可以在任何地方运行,是不是让人很激动,对于大部分的 Java 开发者可能无感,对于 C 语言项目的编写者,那些只要不是基于虚拟机的语言,他们都需要系统提供运行环境,而系统千变万化,当然开发者不愿意为了不同的系统进行适配,在以前我们需要交叉编译,现在我们把这个复杂的事情交给了 Docker,让 Docker 如同 Java 一样,一次编写处处运行,这样的事情简直就像是端了 Java 的饭碗,以前我们交付一个复杂的系统,往往连着操作系统一起交付,而客户可能买了一些商业系统,为了适配有可能还要改代码,现在你有了Docker,开发者喜大普奔,而这里的代价呢?C&C++&GO 他们失去的是枷锁,获得全世界,而 Java 如同被革命一般,失去了 Once Code,Everywhere Run,获得的是更大的 Docker Image Size,获得被人诟病的 Big Size Runtime。

当我们从代码构建完成了镜像,Cloud Navtive 的故事才刚刚开始,当你的 Team Leader 要求你的系统架构是 MicroServices 的,你把原来的项目进行拆分了,或者是开发的就拆分的足够小的时候,你发现因为代码拆分开了,出现了一点点的代码的重复,有适合也避免不了的,你的依赖库也变的 xN,隔壁 Go 程序员想了想,不行我们就搞个 .so 共享一部分代码吧,然后看了构建出来的二进制文件才 15MB,运维大手一挥,这点大小有啥要共享的,Java 程序员望了望了自己的 Jar 包,60MB 还行吧,维护镜像仓库的运维同事这个时候跑出来,你的镜像怎么有 150MB 了, 你看看你们把磁盘都塞满了,只能苦笑,运维小哥坑次坑次的给打包机加了一块硬盘,顺便问你马上部署了,你需要多大的配额,你说道 2C4G,运维一脸嫌弃的问你,为什么隔壁 Go 项目组的同事才需要 0.5C512MB。你当然也不用告诉他,SpringBoot 依赖的了 XXX,YYY,ZZZ 的库,虽然一半的功能你都没用到。

部署到线上,刚刚准备喘口气,突然发现新的需求又来了,虽然是一个很小的功能,但是和现在的系统内的任何一个服务都没有什么直接关联性,你提出再新写一个服务,运维主管抱怨道,现在的服务器资源还是很紧张,你尝试着用现在最流行的 Vertx 开发一个简单的 Web 服务,你对构建出来的 jar 只有 10MB 很满意,可是镜像加起来还是有 60 MB,也算一种进步,你找到 QA 主管,准备 Show 一下你用了 Java 社区最酷的框架,最强的性能,QA 主管找了一个台 1C2G 的服务让你压测一下,你发现你怎么也拼不过别人 Go 系统,你研究之后发现,原来协程模型在这样的少核心的情况下性能要更好,你找运维希望能升级下配置,你走到运维门口的时候,你停了下来,醒醒吧,不是你错了,而是时代变了。

云原生压根不是为了 Java 存在的,云原生的时代已经不是 90 年代,那时候的软件是一个技术活,每一个系统都需要精心设计,一个系统数个月才会更新一个版本,每一个功能都需要进行完整的测试,软件也跑在了企业内部的服务器上,软件是IT部分的宝贝,给他最好的环境,而在 9012 年,软件是什么?软件早就爆炸了,IT 从业者已经到达一个峰值,还有源源不断的人输入进来,市场的竞争也变的激烈,软件公司的竞争力也早就不是质量高,而是如何更快的应对市场的变化,Java 就如同一个身披无数荣光的二战将军,你让他去打21世纪的信息战,哪里还跟着上时代。

云原生需要的是,More Fast & More Fast 的交付系统,一个系统开发很快的系统,那天生就和精心设计是违背的,一个精心设计又能很快开发完的系统实在少见,所以我们从 Spring Boot 上直接堆砌业务代码,最多按照 MVC 进行一个简单的分层,那些优秀的 OOP 理念都活在哪里,那些底层框架,而你突然有一天对 Go 来了兴趣,你按照学 juc 的包的姿势,想要学习下 Go 的优雅源码,你发现,天呐,那些底层库原来可以设计的如此简单,Cache 只需要使用简单的 Map 加上一个 Lock 就可以获得很好的性能了,你开始怀疑了,随着你了解的越深入,你发现 Go 这个语言真是充满了各种各样的缺点,但是足够简单这个优势简直让你羡慕到不行,你回想起来,Executors 的用法你学了好几天,看了好多文章,才把自己的姿势学完,你发现 go func(){} 就解决你的需求了,你顺手删掉了 JDK,走上了真香之路。虽然你还会怀念 SpringBoot 的方便,你发现 Go 也足够满足你 80% 的需求了,剩下俩的一点点就捏着鼻子就好了。你老婆也不怪你没时间陪孩子了,你的工资也涨了点,偶尔翻开自己充满设计模式的 Old Style 代码,再也没有什么兴趣了。

原文链接:http://blog.yannxia.top/2019/05/29/fxxk-java-in-cloud-native/

gRPC 使用 protobuf 构建微服务

JetLee 发表了文章 • 0 个评论 • 163 次浏览 • 2019-05-30 09:24 • 来自相关话题

#微服务架构 ##单一的代码库 以前使用 Laravel 做 Web 项目时,是根据 MVC 去划分目录结构的,即 Controller 层处理业务逻辑,Model 层处理数据库的 CURD,View 层处理数据渲染与页面交互。以及 ...查看全部
#微服务架构

##单一的代码库

以前使用 Laravel 做 Web 项目时,是根据 MVC 去划分目录结构的,即 Controller 层处理业务逻辑,Model 层处理数据库的 CURD,View 层处理数据渲染与页面交互。以及 MVP、MVVM 都是将整个项目的代码是集中在一个代码库中,进行业务处理。这种单一聚合代码的方式在前期实现业务的速度很快,但在后期会暴露很多问题:

* 开发与维护困难:随着业务复杂度的增加,代码的耦合度往往会变高,多个模块相互耦合后不易横向扩展
* 效率和可靠性低:过大的代码量将降低响应速度,应用潜在的安全问题也会累积

##拆分的代码库

微服务是一种软件架构,它将一个大且聚合的业务项目拆解为多个小且独立的业务模块,模块即服务,各服务间使用高效的协议(protobuf、JSON 等)相互调用即是 RPC。这种拆分代码库的方式有以下特点:

* 每个服务应作为小规模的、独立的业务模块在运行,类似 Unix 的 Do one thing and do it well
* 每个服务应在进行自动化测试和(分布式)部署时,不影响其他服务
每个服务内部进行细致的错误检查和处理,提高了健壮性

##二者对比

本质上,二者只是聚合与拆分代码的方式不同。
1.png

参考:微服务架构的优势与不足
#构建微服务

##UserInfoService 微服务

接下来创建一个处理用户信息的微服务:UserInfoService,客户端通过 name 向服务端查询用户的年龄、职位等详细信息,需先安装 gRPC 与 protoc 编译器:
go get -u google.golang.org/grpc
go get -u github.com/golang/protobuf/protoc-gen-go

目录结构
├── proto
│ ├── user.proto // 定义客户端请求、服务端响应的数据格式
│ └── user.pb.go // protoc 为 gRPC 生成的读写数据的函数
├── server.go // 实现微服务的服务端
└── client.go // 调用微服务的客户端

##调用流程

2.png

##Protobuf 协议

每个微服务有自己独立的代码库,各自之间在通信时需要高效的协议,要遵循一定的数据结构来解析和编码要传输的数据,在微服务中常使用 protobuf 来定义。

Protobuf(protocal buffers)是谷歌推出的一种二进制数据编码格式,相比 XML 和 JSON 的文本数据编码格式更有优势:

* 读写更快、文件体积更小
* 它没有 XML 的标签名或 JSON 的字段名,更为轻量,更多参考

3.png

语言中立

只需定义一份 .proto 文件,即可使用各语言对应的 protobuf 编译器对其编译,生成的文件中有对 message 编码、解码的函数。

对于 JSON

* 在 PHP 中需使用 json_encode() 和 json_decode() 去编解码,在 Golang 中需使用 json 标准库的 Marshal() 和 Unmarshal() … 每次解析和编码比较繁琐
* 优点:可读性好、开发成本低
* 缺点:相比 protobuf 的读写速度更慢、存储空间更多

对于 Protobuf

.proto 可生成 .php 或 .pb.go … 在项目中可直接引用该文件中编译器生成的编码、解码函数
* 优点:高效轻量、一处定义多处使用
* 缺点:可读性差、开发成本高

定义微服务的 user.proto 文件
syntax = "proto3";	// 指定语法格式,注意 proto3 不再支持 proto2 的 required 和 optional
package proto; // 指定生成的 user.pb.go 的包名,防止命名冲突


// service 定义开放调用的服务,即 UserInfoService 微服务
service UserInfoService {
// rpc 定义服务内的 GetUserInfo 远程调用
rpc GetUserInfo (UserRequest) returns (UserResponse) {
}
}


// message 对应生成代码的 struct
// 定义客户端请求的数据格式
message UserRequest {
// [修饰符] 类型 字段名 = 标识符;
string name = 1;
}


// 定义服务端响应的数据格式
message UserResponse {
int32 id = 1;
string name = 2;
int32 age = 3;
repeated string title = 4; // repeated 修饰符表示字段是可变数组,即 slice 类型
}

编译 user.proto 文件
# protoc 编译器的 grpc 插件会处理 service 字段定义的 UserInfoService
# 使 service 能编码、解码 message
$ protoc -I . --go_out=plugins=grpc:. ./user.proto

生成 user.pb.go
package proto

import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)

// 请求结构
type UserRequest struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}

// 为字段自动生成的 Getter
func (m *UserRequest) GetName() string {
if m != nil {
return m.Name
}
return ""
}

// 响应结构
type UserResponse struct {
Id int32 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
Age int32 `protobuf:"varint,3,opt,name=age" json:"age,omitempty"`
Title []string `protobuf:"bytes,4,rep,name=title" json:"title,omitempty"`
}
// ...

// 客户端需实现的接口
type UserInfoServiceClient interface {
GetUserInfo(ctx context.Context, in [i]UserRequest, opts ...grpc.CallOption) ([/i]UserResponse, error)
}


// 服务端需实现的接口
type UserInfoServiceServer interface {
GetUserInfo(context.Context, [i]UserRequest) ([/i]UserResponse, error)
}

// 将微服务注册到 grpc
func RegisterUserInfoServiceServer(s *grpc.Server, srv UserInfoServiceServer) {
s.RegisterService(&_UserInfoService_serviceDesc, srv)
}
// 处理请求
func _UserInfoService_GetUserInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {...}

##服务端实现微服务

实现流程
4.png

代码参考
package main
import (...)

// 定义服务端实现约定的接口
type UserInfoService struct{}
var u = UserInfoService{}

// 实现 interface
func (s [i]UserInfoService) GetUserInfo(ctx context.Context, req [/i]pb.UserRequest) (resp *pb.UserResponse, err error) {
name := req.Name

// 模拟在数据库中查找用户信息
// ...
if name == "wuYin" {
resp = &pb.UserResponse{
Id: 233,
Name: name,
Age: 20,
Title: []string{"Gopher", "PHPer"}, // repeated 字段是 slice 类型
}
}
err = nil
return
}

func main() {
port := ":2333"
l, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("listen error: %v\n", err)
}
fmt.Printf("listen %s\n", port)
s := grpc.NewServer()

// 将 UserInfoService 注册到 gRPC
// 注意第二个参数 UserInfoServiceServer 是接口类型的变量
// 需要取地址传参
pb.RegisterUserInfoServiceServer(s, &u)
s.Serve(l)
}

运行监听:
5.png

##客户端调用

实现流程
6.png

代码参考
package main
import (...)

func main() {
conn, err := grpc.Dial(":2333", grpc.WithInsecure())
if err != nil {
log.Fatalf("dial error: %v\n", err)
}
defer conn.Close()

// 实例化 UserInfoService 微服务的客户端
client := pb.NewUserInfoServiceClient(conn)

// 调用服务
req := new(pb.UserRequest)
req.Name = "wuYin"
resp, err := client.GetUserInfo(context.Background(), req)
if err != nil {
log.Fatalf("resp error: %v\n", err)
}

fmt.Printf("Recevied: %v\n", resp)
}

运行调用成功:
7.png

#总结

在上边 UserInfoService 微服务的实现过程中,会发现每个微服务都需要自己管理服务端监听端口,客户端连接后调用,当有很多个微服务时端口的管理会比较麻烦,相比 gRPC,go-micro 实现了服务发现(Service Discovery)来方便的管理微服务,下节将随服务的 Docker 化一起学习。如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

原文链接:https://wuyin.io/2018/05/02/protobuf-with-grpc-in-golang/

工业微服务实现工业APP高效开发和运行

玻璃樽 发表了文章 • 0 个评论 • 146 次浏览 • 2019-05-30 07:38 • 来自相关话题

【编者的话】工业微服务架构为工业互联网平台的知识转化和复用提供了最佳技术手段,算法、模型、知识等模块化组件能够以“搭积木”的方式被调用和编排,实现低门槛、高效率的工业App开发。 微服务最早由Martin Fowler与James L ...查看全部
【编者的话】工业微服务架构为工业互联网平台的知识转化和复用提供了最佳技术手段,算法、模型、知识等模块化组件能够以“搭积木”的方式被调用和编排,实现低门槛、高效率的工业App开发。

微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
#什么是工业微服务
工业微服务是工业互联网平台的载体,是以单一功能组件为基础,通过模块化组合方式实现“松耦合”应用开发的软件架构。一个微服务就是一个面向单一功能、能够独立部署的小型应用,将多个不同功能、相互隔离的微服务按需组合在一起并通过API集实现相互通信,就构成了一个功能完整的大型应用系统。以产品生产为例,就可将其拆解为供应链管理、设备运行状态可视化、生产排程、产线数据分析、操作记录等多个微服务功能模块。

在工业互联网领域,由于工业知识繁杂、工业应用复杂程度高等问题,业内人士普遍认为,使用微服务架构将成为开发工业APP的主流方式。国外主流的工业互联网平台,如西门子的Mindsphere、施耐德Eco Struxure等,都通过云平台支持工业微服务组件的开发、部署和管理,从而达到简化工业APP开发的目的。如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
#工业微服务架构和传统开发模式区别
先来看看传统的web开发方式,一般被称为Monolithic(单体式开发)。所有的功能打包在一个 WAR包里,基本没有外部依赖(除了容器),部署在一个JEE容器(Tomcat,JBoss,WebLogic)里,包含了 DO/DAO,Service,UI等所有逻辑。
1.png

单体架构(Monolithic)优缺点:
2.png

微服务架构与单体架构相比较,微服务架构恰恰弥补了单体架构的不足,通过有效的拆分应用,实现敏捷开发和部署:

  1. 由多个独立的微服务共同组成系统
  2. 微服务单独部署,运行在自己的进程中
  3. 每个微服务为独立的业务开发
  4. 分布式管理
  5. 非常强调隔离性

3.png

关于微服务的一个形象表达:
4.jpg


* X轴:运行多个负载均衡器之后的运行实例
* Y轴:将应用进一步分解为微服务(分库)
* Z轴:大数据量时,将服务分区(分表)

#工业微服务架构的特点
5.png

之所以主流的工业互联网平台都将微服务架构作为开发工业APP的主流方式,是因为微服务架构与传统的架构相比,具备两个显著特点:
##工业微服务开发和维护具有高度灵活性
每个微服务可以由不同团队运用不同语言和工具进行开发和维护,任何修改、升级都不会对应用的其他部分功能产生影响;而传统的统一整体式框架下对软件的任何修改都有可能对整个应用产生意料之外的影响。
##工业微服务运行去中心化分布式执行
不同微服务能够分布式并行执行,应用资源占用率相对较小,且微服务间的数据和资源相互物理隔离,单个服务的故障只会导致单个功能的受损而不会造成整个应用的崩溃。
#微服务支撑工业互联网平台颠覆创新
##工业微服务颠覆传统工业软件研发方式
在企业里,CAD、CAE、DCS、MES、ERP、SCM等传统工业应用软件往往是面向基础的流程或服务进行设计和研发,并在部署阶段根据用户实际情况进行调整,整个软件研发的成本投入较大、研发周期较长,且不能灵活地响应用户个性化需求。而在工业互联网平台中,则可采用工业微服务的方式将上述软件拆解成独立的功能模块,实现对原有生产体系的解构,随后在平台中构建起富含各类功能与服务的微服务组件池,并按照实际需求来调用相应的微服务组件,进行高效率和个性化的面向用户的工业App研发,整个软件研发的技术门槛和投入成本大大降低。原来需要专业团队和雄厚资金支持的精英化软件研发开始向大众化研发转变。
##工业微服务打破工业知识封闭传承体系
过去,工业领域中很多经验知识都停留在老师傅、老专家的脑子里,由于个人精力和地域空间的限制,这些经验知识通常只能在很小的范围里发挥作用,而且还存在易出错、易流失、难推广、难传承等问题。如今,当这些老师傅、老专家将自己的经验知识用软件代码的方式固化下来,转化为平台中的工业微服务之后,由于平台所具备的积累沉淀和开放共享特性,这些经验知识就变成了整个企业、整个行业的宝贵财富,能够被更多的人分享学习和使用,创造出更多的价值。同时,新的专业技术人员还能够在充分消化吸收原有知识的基础上实现进一步提升和创新,推动整个工业知识体系的传递延续和迭代更新。
##工业微服务创造全新平台开放价值生态
随着工业互联网平台中微服务组件池的构建和行业经验知识的持续积累,整个平台既能够为广大第三方开发者提供众多低门槛、易操作、高效率的开发支持手段,形成以工业App开发为核心的平台创新生态,也能够为制造业用户提供以工业微服务为基础的定制化、高可靠、可扩展工业App或解决方案,形成以价值挖掘提升为核心的平台应用生态。最终,构建出以工业互联网平台为桥梁、以工业微服务为载体的相互促进、双向迭代生态体系。
#工业微服务在工业互联网平台的作用
工业微服务实现机理模型算法的模块化、软件化,支撑工业互联网平台中的工业App开发运行。在工业互联网平台中,工业微服务正发挥着承上启下的关键作用。
##独立调试、运行和升级,提升易用性和可维护性
基于不同行业、不同领域经验知识所提炼出来的各类原始机理算法模型通常缺少对外调用的接口,也往往难以进行独立的调试、运行和升级,需要用工业微服务的方式将这些机理算法模型集成起来,封装成可独立调试运行的单一功能或服务模块,提升易用性和可维护性。
##满足工业APP快速运维、持续迭代和个性化定制的需要
在工业互联网平台中基于工业微服务模块进行工业App开发,既能够借助工业微服务并行开发、分布运行的特点,有效发挥平台海量开发者接入、资源弹性配置、云化部署运行等优势,又能够利用工业微服务独立隔离、灵活调用的特点,克服工业App所面临的快速运维、持续迭代、个性化定制等问题。
##无需专业知识,平台调用工业微服务开发工业APP
工业互联网平台发展的核心目标是通过行业经验知识的积累沉淀和复用推广来带动产业整体水平的提升,并打造繁荣创新的开放价值生态。而工业微服务能够将专业知识和IT技术融合起来,变成不需要关心实现细节的“黑盒”,开发者甚至不需要任何专业知识,就可通过调用平台中各类工业微服务的方式开发出解决行业问题的工业App。
##工业微服务具有通用化共享能力,便于复制和应用推广
在此基础上,平台将原来处于企业内部的封闭性专业能力转化为面向行业和社会的通用化共享能力,实现在工业微服务能力复制和应用推广,从而成为服务行业、服务区域的发动机和助推器。
#结语
工业微服务本质是经验知识的软件化和工具化,借助专业化的工具打造通用化的平台。工业微服务架构为工业互联网平台的知识转化和复用提供了最佳技术手段,算法、模型、知识等模块化组件能够以“搭积木”的方式被调用和编排,实现低门槛、高效率的工业App开发,驱动了工业软件开发方式的变革,促进了平台创新生态的形成,工业微服务能力构建已经成为当前工业互联网平台发展的首要任务。

原文链接:https://mp.weixin.qq.com/s/9bSAyBBdl_m534p8Um-59w

微服务架构下的分布式事务基础入门

翔宇 发表了文章 • 0 个评论 • 176 次浏览 • 2019-05-29 22:51 • 来自相关话题

众所周知,数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行,要么全都不执行。这里特别强调了本地事务,也就是目前的数据库只能支持同一个数据库中的事务。但现在的系统往往采用微服务架构,业务系统拥有独立的数据库,因此就出现了跨多个数据 ...查看全部
众所周知,数据库能实现本地事务,也就是在同一个数据库中,你可以允许一组操作要么全都正确执行,要么全都不执行。这里特别强调了本地事务,也就是目前的数据库只能支持同一个数据库中的事务。但现在的系统往往采用微服务架构,业务系统拥有独立的数据库,因此就出现了跨多个数据库的事务需求,这种事务即为“分布式事务”。那么在目前数据库不支持跨库事务的情况下,我们应该如何实现分布式事务呢?本文首先会为大家梳理分布式事务的基本概念和理论基础,然后介绍几种目前常用的分布式事务解决方案。废话不多说,那就开始吧~
#什么是事务?
事务由一组操作构成,我们希望这组操作能够全部正确执行,如果这一组操作中的任意一个步骤发生错误,那么就需要回滚之前已经完成的操作。也就是同一个事务中的所有操作,要么全都正确执行,要么全都不要执行。
#事务的四大特性 ACID
说到事务,就不得不提一下事务著名的四大特性。

* 原子性,原子性要求,事务是一个不可分割的执行单元,事务中的所有操作要么全都执行,要么全都不执行。
* 一致性,一致性要求,事务在开始前和结束后,数据库的完整性约束没有被破坏。
* 隔离性,事务的执行是相互独立的,它们不会相互干扰,一个事务不会看到另一个正在运行过程中的事务的数据。
* 持久性,持久性要求,一个事务完成之后,事务的执行结果必须是持久化保存的。即使数据库发生崩溃,在数据库恢复后事务提交的结果仍然不会丢失。

注意:事务只能保证数据库的高可靠性,即数据库本身发生问题后,事务提交后的数据仍然能恢复;而如果不是数据库本身的故障,如硬盘损坏了,那么事务提交的数据可能就丢失了。这属于『高可用性』的范畴。因此,事务只能保证数据库的『高可靠性』,而『高可用性』需要整个系统共同配合实现。
#事务的隔离级别
这里扩展一下,对事务的隔离性做一个详细的解释。

在事务的四大特性ACID中,要求的隔离性是一种严格意义上的隔离,也就是多个事务是串行执行的,彼此之间不会受到任何干扰。这确实能够完全保证数据的安全性,但在实际业务系统中,这种方式性能不高。因此,数据库定义了四种隔离级别,隔离级别和数据库的性能是呈反比的,隔离级别越低,数据库性能越高,而隔离级别越高,数据库性能越差。如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
##事务并发执行会出现的问题
我们先来看一下在不同的隔离级别下,数据库可能会出现的问题:

  1. 更新丢失,当有两个并发执行的事务,更新同一行数据,那么有可能一个事务会把另一个事务的更新覆盖掉。 当数据库没有加任何锁操作的情况下会发生。
  2. 脏读,一个事务读到另一个尚未提交的事务中的数据。 该数据可能会被回滚从而失效。 如果第一个事务拿着失效的数据去处理那就发生错误了。
  3. 不可重复读,不可重复度的含义:一个事务对同一行数据读了两次,却得到了不同的结果。它具体分为如下两种情况:

* 虚读:在事务1两次读取同一记录的过程中,事务2对该记录进行了修改,从而事务1第二次读到了不一样的记录。
* 幻读:事务1在两次查询的过程中,事务2对该表进行了插入、删除操作,从而事务1第二次查询的结果发生了变化。

不可重复读与脏读的区别?:

脏读读到的是尚未提交的数据,而不可重复读读到的是已经提交的数据,只不过在两次读的过程中数据被另一个事务改过了。
##数据库的四种隔离级别
数据库一共有如下四种隔离级别:

  1. Read uncommitted 读未提交,在该级别下,一个事务对一行数据修改的过程中,不允许另一个事务对该行数据进行修改,但允许另一个事务对该行数据读。 因此本级别下,不会出现更新丢失,但会出现脏读、不可重复读。
  2. Read committed 读提交,在该级别下,未提交的写事务不允许其他事务访问该行,因此不会出现脏读;但是读取数据的事务允许其他事务的访问该行数据,因此会出现不可重复读的情况。
  3. Repeatable read 重复读 ,在该级别下,读事务禁止写事务,但允许读事务,因此不会出现同一事务两次读到不同的数据的情况(不可重复读),且写事务禁止其他一切事务。
  4. Serializable 序列化,该级别要求所有事务都必须串行执行,因此能避免一切因并发引起的问题,但效率很低。

隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
#什么是分布式事务?
到此为止,所介绍的事务都是基于单数据库的本地事务,目前的数据库仅支持单库事务,并不支持跨库事务。而随着微服务架构的普及,一个大型业务系统往往由若干个子系统构成,这些子系统又拥有各自独立的数据库。往往一个业务流程需要由多个子系统共同完成,而且这些操作可能需要在一个事务中完成。在微服务系统中,这些业务场景是普遍存在的。此时,我们就需要在数据库之上通过某种手段,实现支持跨数据库的事务支持,这也就是大家常说的“分布式事务”。

这里举一个分布式事务的典型例子——用户下单过程。

当我们的系统采用了微服务架构后,一个电商系统往往被拆分成如下几个子系统:商品系统、订单系统、支付系统、积分系统等。整个下单的过程如下:

  1. 用户通过商品系统浏览商品,他看中了某一项商品,便点击下单
  2. 此时订单系统会生成一条订单
  3. 订单创建成功后,支付系统提供支付功能
  4. 当支付完成后,由积分系统为该用户增加积分

上述步骤2、3、4需要在一个事务中完成。对于传统单体应用而言,实现事务非常简单,只需将这三个步骤放在一个方法A中,再用Spring的@Transactional注解标识该方法即可。Spring通过数据库的事务支持,保证这些步骤要么全都执行完成,要么全都不执行。但在这个微服务架构中,这三个步骤涉及三个系统,涉及三个数据库,此时我们必须在数据库和应用系统之间,通过某项黑科技,实现分布式事务的支持。
#CAP理论
CAP理论说的是:在一个分布式系统中,最多只能满足C、A、P中的两个需求。

CAP的含义:

C:Consistency 一致性
同一数据的多个副本是否实时相同。

A:Availability 可用性
可用性:一定时间内 & 系统返回一个明确的结果 则称为该系统可用。

P:Partition tolerance 分区容错性
将同一服务分布在多个系统中,从而保证某一个系统宕机,仍然有其他系统提供相同的服务。

CAP理论告诉我们,在分布式系统中,C、A、P三个条件中我们最多只能选择两个。那么问题来了,究竟选择哪两个条件较为合适呢?

对于一个业务系统来说,可用性和分区容错性是必须要满足的两个条件,并且这两者是相辅相成的。业务系统之所以使用分布式系统,主要原因有两个:

* 提升整体性能,当业务量猛增,单个服务器已经无法满足我们的业务需求的时候,就需要使用分布式系统,使用多个节点提供相同的功能,从而整体上提升系统的性能,这就是使用分布式系统的第一个原因。
* 实现分区容错性,单一节点 或 多个节点处于相同的网络环境下,那么会存在一定的风险,万一该机房断电、该地区发生自然灾害,那么业务系统就全面瘫痪了。为了防止这一问题,采用分布式系统,将多个子系统分布在不同的地域、不同的机房中,从而保证系统高可用性。

这说明分区容错性是分布式系统的根本,如果分区容错性不能满足,那使用分布式系统将失去意义。

此外,可用性对业务系统也尤为重要。在大谈用户体验的今天,如果业务系统时常出现“系统异常”、响应时间过长等情况,这使得用户对系统的好感度大打折扣,在互联网行业竞争激烈的今天,相同领域的竞争者不甚枚举,系统的间歇性不可用会立马导致用户流向竞争对手。因此,我们只能通过牺牲一致性来换取系统的可用性和分区容错性。这也就是下面要介绍的BASE理论。
#BASE理论
CAP理论告诉我们一个悲惨但不得不接受的事实——我们只能在C、A、P中选择两个条件。而对于业务系统而言,我们往往选择牺牲一致性来换取系统的可用性和分区容错性。不过这里要指出的是,所谓的“牺牲一致性”并不是完全放弃数据一致性,而是牺牲强一致性换取弱一致性。下面来介绍下BASE理论。

* BA:Basic Available 基本可用

* “一定时间”可以适当延长,当举行大促时,响应时间可以适当延长
* 给部分用户返回一个降级页面,给部分用户直接返回一个降级页面,从而缓解服务器压力。但要注意,返回降级页面仍然是返回明确结果。
* 整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果。只不过“基本可用”和“高可用”的区别是:

* S:Soft State:柔性状态,同一数据的不同副本的状态,可以不需要实时一致。
* E:Eventual Consisstency:最终一致性,同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的。

#酸碱平衡
ACID能够保证事务的强一致性,即数据是实时一致的。这在本地事务中是没有问题的,在分布式事务中,强一致性会极大影响分布式系统的性能,因此分布式系统中遵循BASE理论即可。但分布式系统的不同业务场景对一致性的要求也不同。如交易场景下,就要求强一致性,此时就需要遵循ACID理论,而在注册成功后发送短信验证码等场景下,并不需要实时一致,因此遵循BASE理论即可。因此要根据具体业务场景,在ACID和BASE之间寻求平衡。
#分布式事务协议
下面介绍几种实现分布式事务的协议。
##两阶段提交协议 2PC
分布式系统的一个难点是如何保证架构下多个节点在进行事务性操作的时候保持一致性。为实现这个目的,二阶段提交算法的成立基于以下假设:

* 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Cohorts)。且节点之间可以进行网络通信。
* 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。
* 所有节点不会永久性损坏,即使损坏后仍然可以恢复。

第一阶段(投票阶段):

  1. 协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应。
  2. 参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)
  3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。

第二阶段(提交执行阶段):

当协调者节点从所有参与者节点获得的相应消息都为"同意"时:

  1. 协调者节点向所有参与者节点发出"正式提交(commit)"的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"完成"消息。
  4. 协调者节点受到所有参与者节点反馈的"完成"消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为"中止",或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

  1. 协调者节点向所有参与者节点发出"回滚操作(rollback)"的请求。
  2. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送"回滚完成"消息。
  4. 协调者节点受到所有参与者节点反馈的"回滚完成"消息后,取消事务。

不管最后结果如何,第二阶段都会结束当前事务。

二阶段提交看起来确实能够提供原子性的操作,但是不幸的事,二阶段提交还是有几个缺点的:

  1. 执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
  2. 参与者发生故障。协调者需要给每个参与者额外指定超时机制,超时后整个事务失败。(没有多少容错机制)
  3. 协调者发生故障。参与者会一直阻塞下去。需要额外的备机进行容错。(这个可以依赖后面要讲的Paxos协议实现HA)
  4. 二阶段无法解决的问题:协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。

为此,Dale Skeen和Michael Stonebraker在“A Formal Model of Crash Recovery in a Distributed System”中提出了三阶段提交协议(3PC)。
##三阶段提交协议 3PC
与两阶段提交不同的是,三阶段提交有两个改动点。

* 引入超时机制。同时在协调者和参与者中都引入超时机制。
* 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。

也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

CanCommit阶段

3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。

  1. 事务询问,协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
  2. 响应反馈,参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No

PreCommit阶段

协调者根据参与者的反应情况来决定是否可以记性事务的PreCommit操作。根据响应情况,有以下两种可能。

假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。

  1. 发送预提交请求,协调者向参与者发送PreCommit请求,并进入Prepared阶段。
  2. 事务预提交,参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
  3. 响应反馈 ,如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。

假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

  1. 发送中断请求,协调者向所有参与者发送abort请求。
  2. 中断事务,参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。

doCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

执行提交:

  1. 发送提交请求,协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
  2. 事务提交,参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
  3. 响应反馈,事务提交完之后,向协调者发送Ack响应。
  4. 完成事务,协调者接收到所有参与者的ack响应之后,完成事务。

中断事务:

协调者没有接收到参与者发送的ACK响应(可能是接受者发送的不是ACK响应,也可能响应超时),那么就会执行中断事务。

  1. 发送中断请求,协调者向所有参与者发送abort请求
  2. 事务回滚,参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
  3. 反馈结果,参与者完成事务回滚之后,向协调者发送ACK消息
  4. 中断事务,协调者接收到参与者反馈的ACK消息之后,执行事务的中断。

原文链接:https://mp.weixin.qq.com/s/VbFOscil4s4XEbhznisvhQ

走进微服务的世界

老马 发表了文章 • 0 个评论 • 132 次浏览 • 2019-05-29 22:32 • 来自相关话题

#什么是微服务? 我们首先给出微服务的定义,然后再对该定义给出详细的解释。 微服务就是一些可独立运行、可协同工作的小的服务。 从概念中我们可以提取三个关键词:可独立运行、可协同工作、小。这三个词高 ...查看全部
#什么是微服务?
我们首先给出微服务的定义,然后再对该定义给出详细的解释。

微服务就是一些可独立运行、可协同工作的小的服务。

从概念中我们可以提取三个关键词:可独立运行、可协同工作、小。这三个词高度概括了微服务的核心特性。下面我们就对这三个词作详细解释。

* 可独立运行,微服务是一个个可以独立开发、独立部署、独立运行的系统或者进程。
* 可协同工作,采用了微服务架构后,整个系统被拆分成多个微服务,这些服务之间往往不是完全独立的,在业务上存在一定的耦合,即一个服务可能需要使用另一个服务所提供的功能。这就是所谓的“可协同工作”。与单服务应用不同的是,多个微服务之间的调用时通过RPC通信来实现,而非单服务的本地调用,所以通信的成本相对要高一些,但带来的好处也是可观的。
* 小而美,微服务的思想是,将一个拥有复杂功能的庞大系统,按照业务功能,拆分成多个相互独立的子系统,这些子系统则被称为“微服务”。每个微服务只承担某一项职责,从而相对于单服务应用来说,微服务的体积是“小”的。小也就意味着每个服务承担的职责变少,根据单一职责原则,我们在系统设计时,要尽量使得每一项服务只承担一项职责,从而实现系统的“高内聚”。

#微服务的优点
##易于扩展
在单服务应用中,如果目前性能到达瓶颈,无法支撑目前的业务量,此时一般采用集群模式,即增加服务器集群的节点,并将这个单服务应用“复制”到所有的节点上,从而提升整体性能。然而这种扩展的粒度是比较粗糙的。如果只是系统中某一小部分存在性能问题,在单服务应用中,也要将整个应用进行扩展,这种方式简单粗暴,无法对症下药。而当我们使用了微服务架构后,如果某一项服务的性能到达瓶颈,那么我们只需要增加该服务的节点数即可,其他服务无需变化。这种扩展更加具有针对性,能够充分利用计算机硬件/软件资源。而且只扩展单个服务影响的范围较小,从而系统出错的概率也就越低。如果你想和更多微服务技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态
##部署简单
对于单服务应用而言,所有代码均在一个项目中,从而导致任何微小的改变都需要将整个项目打包、发布、部署,而这一系列操作的代价是高昂的。长此以往,团队为了降低发布的频率,会使得每次发布都伴随着大量的修改,修改越多也就意味着出错的概率也越大。

当我们采用微服务架构以后,每个服务只承担少数职责,从而每次只需要发布发生修改的系统,其他系统依然能够正常运行,波及范围较小。此外,相对于单服务应用而言,每个微服务系统修改的代码相对较少,从而部署后出现错误的概率也相对较低。
##技术异构性
对于单服务应用而言,一个系统的所有模块均整合在一个项目中,所以这些模块只能选择相同的技术。但有些时候,单一技术没办法满足不同的业务需求。如对于项目的算法团队而言,函数试编程语言可能更适合算法的开发,而对于业务开发团队而言,类似于Java的强类型语言具有更高的稳定性。然而在单服务应用中只能互相权衡,选择同一种语言,而当我们使用微服务结构后,这个问题就能够引刃而解。我们将一个完整的系统拆分成了多个独立的服务,从而每个服务都可以根据各自不同的特点,选择最为合适的技术体系。

当然,并不是所有的微服务系统都具备技术异构性,要实现技术异构性,必须保证所有服务都提供通用接口。我们知道,在微服务系统中,服务之间采用RPC接口通信,而实现RPC通信的方式有很多。有一些RPC通信方式与语言强耦合,如Java的RMI技术,它就要求通信的双方都必须采用Java语言开发。当然,也有一些RPC通信方式与语言无关,如基于HTTP协议的REST。这种通信方式对通信双方所采用的语言没有做任何限制,只要通信过程中传输的数据遵循REST规范即可。当然,与语言无关也就意味着通信双方没有类型检查,从而会提高出错的概率。所以,究竟选择与语言无关的RPC通信方式,还是选择与语言强耦合的RPC通信方式,需要我们根据实际的业务场景合理地分析。

原文链接:https://mp.weixin.qq.com/s/eSe6r698vMryIaoh1eGtcg

Knative 核心概念介绍:Build、Serving 和 Eventing 三大核心组件

Andy_Lee 发表了文章 • 0 个评论 • 131 次浏览 • 2019-05-29 22:16 • 来自相关话题

【编者的话】初识 Knative:跨平台的 Serverless 编排框架 让我们对于 Knative 有了初步了解,Knative 主要由 Build、Serving 和 Eventing 三大核心组件构成。Knative 正是依靠这三个核心组件,驱动着 K ...查看全部
【编者的话】初识 Knative:跨平台的 Serverless 编排框架 让我们对于 Knative 有了初步了解,Knative 主要由 Build、Serving 和 Eventing 三大核心组件构成。Knative 正是依靠这三个核心组件,驱动着 Knative 这艘 Serverless 巨轮前行,本文就来分别介绍一下这三个核心组件。
#Build

Knative Build 是基于现有的 Kubernetes 能力之上,提供的一套标准化、可移植、可复用的容器镜像构建方式。通过在 Kubernetes 上运行复杂的构建任务,Knative Build 使你不必再单独开发和重复这些镜像构建过程, 从而通过系统化、工程化的方式,减少了镜像构建时间及成本。如果你想和更多 Kubernetes 技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

Build 通过 Kubernetes 自定义资源定义(CRD)实现。 通过 Build 你可以自定义一个从运行到结束的构建流程。例如,可以使用 Knative Build 来获取、构建和打包代码。Build 具备以下功能:

* 支持 Source 源挂载,目前支持的 Source 源包括:

* Git 代码仓库
* 任意容器镜像

* 支持通过 BuildTemplate 创建可重复执行构建的模板
* 支持 Kubernetes ServiceAccount 身份验证

典型的 Build 示意图:
1.jpg

虽然目前 Knative Build 并不提供完整的独立 CI/CD 解决方案,但它却提供了一个底层的构建模块,用户可单独使用该构建模块在大型系统中实现集成和利用。
#Serving

Knative 作为 Severless 框架最终是用来提供服务的, 那么 Knative Serving 应运而生。

Knative Serving 构建于 Kubernetes 和 Istio 之上,为 Serverless 应用提供部署和服务支持。其特性如下:

* 快速部署 Serverless 容器
* 支持自动扩缩容和缩至为 0 实例
* 基于 Istio 组件,提供路由和网络编程
* 支持部署快照

Knative Serving 中定义了以下 CRD 资源:

* Service:自动管理工作负载整个生命周期。负责创建 Route、Configuration 以及 Revision 资源。通过 Service 可以指定路由流量使用最新的 Revision 还是固定的 Revision
* Route:负责映射网络端点到一个或多个 Revision。可以通过多种方式管理流量,包括灰度流量和重命名路由
* Configuration:负责保持 Deployment 的期望状态,提供了代码和配置之间清晰的分离,并遵循应用开发的 12 要素。修改一次 Configuration 产生一个 Revision
* Revision:Revision 资源是对工作负载进行的每个修改的代码和配置的时间点快照。Revision 是不可变对象,可以长期保留

资源关系图:
2.png

#Eventing

Knative Eventing 旨在满足云原生开发中通用需求, 以提供可组合的方式绑定事件源和事件消费者。其设计目标:

* Knative Eventing 提供的服务是松散耦合,可独立开发和部署。服务可跨平台使用(如 Kubernetes,VMs,SaaS 或者 FaaS)
* 事件的生产者和事件的消费者是相互独立的。任何事件的生产者(事件源)可以先于事件的消费者监听之前产生事件,同样事件的消费者可以先于事件产生之前监听事件
* 支持第三方的服务对接该 Eventing 系统
* 确保跨服务的互操作性

事件处理示意图:
3.png

如上图所示,Eventing 主要由事件源(Event Source)、事件处理(Flow)以及事件消费者(Event Consumer)三部分构成。
##事件源(Event Source)

当前支持以下几种类型的事件源:

* ApiserverSource:每次创建或更新 Kubernetes 资源时,ApiserverSource 都会触发一个新事件
* GitHubSource:GitHub 操作时,GitHubSource 会触发一个新事件
* GcpPubSubSource: GCP 云平台 Pub/Sub 服务会触发一个新事件
* AwsSqsSource:Aws 云平台 SQS 服务会触发一个新事件
* ContainerSource:ContainerSource 将实例化一个容器,通过该容器产生事件
* CronJobSource:通过 CronJob 产生事件
* KafkaSource:接收 Kafka 事件并触发一个新事件
* CamelSource:接收 Camel 相关组件事件并触发一个新事件

##事件接收/转发(Flow)

当前 Knative 支持如下事件接收处理:

直接事件接收

通过事件源直接转发到单一事件消费者。支持直接调用 Knative Service 或者 Kubernetes Service 进行消费处理。这样的场景下,如果调用的服务不可用,事件源负责重试机制处理

通过事件通道(Channel)以及事件订阅(Subscriptions)转发事件处理

这样的情况下,可以通过 Channel 保证事件不丢失并进行缓冲处理,通过 Subscriptions 订阅事件以满足多个消费端处理

通过 brokers 和 triggers 支持事件消费及过滤机制

从 v0.5 开始,Knative Eventing 定义 Broker 和 Trigger 对象,实现了对事件进行过滤(亦如通过 ingress 和 ingress controller 对网络流量的过滤一样)通过定义 Broker 创建 Channel,通过 Trigger 创建 Channel 的订阅(subscription),并产生事件过滤规则。

事件消费者(Event Consumer)

为了满足将事件发送到不同类型的服务进行消费, Knative Eventing 通过多个 Kubernetes 资源定义了两个通用的接口:

* Addressable 接口提供可用于事件接收和发送的 HTTP 请求地址,并通过status.address.hostname字段定义。作为一种特殊情况,Kubernetes Service 对象也可以实现 Addressable 接口
* Callable 接口接收通过 HTTP 传递的事件并转换事件。可以按照处理来自外部事件源事件的相同方式,对这些返回的事件做进一步处理

当前 Knative 支持通过 Knative Service 或者 Kubernetes Service 进行消费事件。

另外针对事件消费者,如何事先知道哪些事件可以被消费? Knative Eventing 在最新的 0.6 版本中提供 Registry 事件注册机制, 这样事件消费者就可以事先通过 Registry 获得哪些 Broker 中的事件类型可以被消费。
#总结

Knative 使用 Build 提供云原生“从源代码到容器”的镜像构建能力,通过 Serving 部署容器并提供通用的服务模型,同时以 Eventing 提供事件全局订阅、传递和管理能力,实现事件驱动。这就是 Knative 呈现给我们的标准 Serverless 编排框架。

原文链接:https://mp.weixin.qq.com/s/8JZyYfxia3uMG9f7owl3ow

容器云平台基础架构方案设计思考

aoxiang 发表了文章 • 0 个评论 • 246 次浏览 • 2019-05-29 22:02 • 来自相关话题

【编者的话】本文是作者作为一名云计算运维工程师在参与容器云平台建设中的一些有感记录,此次主要针对基础架构技术方案设计方面谈谈自己的一些认知,如有描述不妥之处,还请多包涵并给予指正。 如今,在移动互联的大时代下,互联网公司金融业务的爆炸 ...查看全部
【编者的话】本文是作者作为一名云计算运维工程师在参与容器云平台建设中的一些有感记录,此次主要针对基础架构技术方案设计方面谈谈自己的一些认知,如有描述不妥之处,还请多包涵并给予指正。

如今,在移动互联的大时代下,互联网公司金融业务的爆炸式发展给传统金融行业带来了巨大的冲击和挑战,金融行业也纷纷顺应发展趋势,大力发展移动端业务和互金类型业务,因此对业务需求的响应速度有了更高的要求,越来越多传统应用架构,为了适应不断变化的业务需求,难以预估的访问量,而开始进行分布式改造、微服务改造,实现持续集成、持续部署、支持弹性伸缩、灰度发布、蓝绿部署等能力,容器云平台恰恰可以很好的支撑上述需求。容器技术是近些年来最火热的技术弄潮儿,我们关注他,肯定不止是因为它足够火热,需要任何一项技术,一定都是以更好服务于应用,让用户使用感受越来越美好为目的。

笔者在容器平台的建设过程中,参与了大量的技术讨论和思维碰撞,一个非常大的感触就是,容器云的知识栈非常长,想对平台有一个全局的透彻理解,要学习的东西非常之多,而且很多是跨领域的。作为一名云计算运维工程师,在这里也简单聊聊在平台基础架构设计中自己的一些认知。
#平台选型
在做IaaS时,即使经过了深度的定制化自动化改造,IaaS上的流程走完时也普遍是在交付时把带有应用软件及软件配置的一台虚拟机交到申请者手中,申请者要自己通过IP登录去部署应用,更不用说各应用组件之间的配合设置。而在容器平台里,从代码开发集成,到一个容器镜像里打包了应用和应用的运行时环境,加上容器的配置文件,一套完整流程走下来时,应用已经可以上线了,负载均衡、安全策略都可以具备了,可以说容器云平台是DevOps理论的最佳实现。所以,对于容器平台的建设,从初期就需要各技术团队紧密结合,了解互相的需求,平台相关技术的原理机制,才能共同设计好一个容器平台。如果你想和更多容器技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

而对于传统应用容器化改造,应用接入容器云平台一定是一个循序渐进的过程,更是一个不断演讲的过程。为了让应用尽可能平滑的接入,平台设计也应适当考虑业务原场景和使用习惯,尽可能的避免大幅度修改逻辑,不希望为了容器化而容器化,还是要因地制宜,互相找到一个平衡。虽做了很多技术实践经验的调研,但真正在自己的环境中落地时,大家都是摸着石头过河,要通过试点应用,积累应用改造的经验,运维的经验。

对于容器平台,Docker已经成了容器引擎公认的事实标准,而Kubernetes也是公认的容器编排最佳调度平台,早在一两年前声音还没有如此的一致,记得最初做容器平台技术调研时,还要关注Mesos,Swarm等,但现在无论是商业产品还是纯自研,基本都转向了Kubernetes的阵营,说到这里我来简单谈谈Docker和Kubernetes最吸引人,或者说最值得应用的地方。
##Docker
Docker我认为最有价值的地方是优秀的可移植性,我们日常中最常见的问题就是应用部署的环境差异,操作系统版本差异,生产测试环境不一致等,往往这种差异会导致应用部署调试消耗大量的人力,还容易出现安全隐患,即使很多部署工作已经自动化,但也对基础环境有一定要求,这点比虚拟机有了更友好的移植能力,Docker能够将应用程序和它依赖的操作系统、类库以及运行时环境整体打包,统一交付,真正抹去了应用程序多个运行实例间的环境和依赖版本差异,同时也把对运维人员的重度依赖解耦开来。Docker有一句响亮的口号不是白叫的“Build once,Run anywhere”。

还有一点就是快,秒级启动,虚拟机虽说是分钟级的启动,其实也已经非常快了,但从单体来看,当出现故障重启(Docker是重新生成),这种差别就是非常明显的,容器甚至可以做到外界无感知,尤其对于当需要应用扩容弹新的节点时,这种秒与分钟效率的差别,对应用的使用对象就是天壤之别的使用体验了,这里还不说虚拟机应急扩节点时应用配置的复杂度和配置时长,单是创建和启动时间,可能就是一个外部用户无感知,一个外部用户感觉到慢甚至短时间内服务不可用了。
1.jpg

##Kubernetes
Kubernetes,这个单词来自于希腊语,含义是舵手或领航员,简称K8S。2014年以Google内部用了很久的Brog为原型孵化出来贡献给开源社区的一个自动化部署、伸缩和编排容器的开源平台,其功能强大,Kubernetes实现了IP地址管理、负载均衡、服务发现和DNS名称注册,还原生提供运维人员更为关注的高可用特性及资源智能调度,在我看来Kubernetes对管理基础设施的抽象解耦比虚拟化技术更为彻底、极致,其对CPU兼容性、系统兼容性更为优秀。Kubernetes的自动扩缩容、负载均衡和重新启动应用程序的功能,这些也是使用IaaS或裸机时要二次定制开发的。

这里也要说下,Kubernetes不只支持Docker,它支持符合CRI(containerruntime interface)接口的容器引擎,CoreOS的rkt就是一大代表。

当然构成一套容器云平台,不只是Docker和Kubernetes就够了,他们只是应用运行和调度的最核心的载体,还要有一系列CI\CD、镜像管理、安全、监控、网络、存储等组件,才能构成一个完整的解决方案。
2.jpg

#计算方案
##应该部署在物理机还是虚拟机上?
这是架构设计时一定会讨论的一个问题,从容器云的架构设计来看,我觉得没有绝对的谁更好的答案,物理机或虚拟化平台均可以,前面也说到了,其核心组件K8S将基础设施抽象的更为极致,所以我认为要综合企业自身基础设施建设现状和内部制度流程等综合因素权衡。

如果之前已经有了IaaS平台建设,并且已经有成熟的运维规范和配套工具,那么就部署于IaaS之上,享受IaaS建设的红利。融入了自助服务、内部流程审批、应用软件安装等自动化流程及规范,且IaaS平台有一些对于运维人员爱不释手的功能——热迁移、快照、HA、快速创建虚拟机等,IaaS平台在易管理性和资源弹性上相比物理机还是优势巨大的。

如果没有现成的IaaS建设,那么我认为首选物理机,没有必要再去投入人力去设计IAAS基础设施,K8S原生解耦了基础设施的依赖,提供了智能调度和高可用能力,针对物理机去定制一些满足自身的管理功能和运维的自动化手段也是理想之选,毕竟建设一套适合自身企业需求的IAAS本身也是个巨大的工程,而且少了一层虚拟化,从架构来看更为清晰简洁,troubleshooting时理论故障点也会少些,虚拟化层的性能损耗也不用考虑了。
#网络方案
在容器平台的基础架构层面,网络方案的设计一般都是涉及讨论最多,意见碰撞最多的地方,毕竟网络是底层的基础设施,牵一发动全身,所以定会格外谨慎。underlay、overlay、routing的网络模型都比较了遍,当然这些方案都是要Kubernetes CNI(Container Network Interface)插件标准的,主要关注的有:ipvlan(underlay)、Macvlan(underlay)、Flannel(overlay)、Calico(routing)、NSX-T(routing)。

对于underlay的方案,对于传统的网络架构可以无缝适配,网络的管理模式(IP资源管理,流量管理等)也可以保持一致,但从Kubernetes的管理功能和生态圈来看,underlay的网络方案都不是方向,更多是一种适配传统网络架构的过渡方案,对于ip的管理还是要在外部完成,而且Kubernetes也失去了对于容器的隔离控制能力,此外,例如Macvlan要求启用混杂模式,ipvlan要求linux内核版本要在4.1之上等刚性要求,也不适合绝大多数企业的网络管理规范及操作系统使用现状。对于Overlay的方案,对于传统网络和容器通讯及跨Kubernetes集群的容器通讯需求,该类型方案均存在很大弊端,而且基于vxlan的数据封装传输,对于容器ip的流量监控也不易实现(除非支持解析 vxlan 数据包),对于vxlan的解封包,在性能上也会一定损失,性能表现亦不占优。所以,综合应用的通信需求、运维的管理需求、功能的完善度、技术趋势、性能、安全等多方面的因素,我认为routing的网络模型方案更优,routing模式对传统应用适配,接受传统组织结构都更友好。

接下来我也将routing方案的主要代表介绍下:
##Calico
Calico是综合对比后,各方面功能需求最为满足的开源网络方案,而且其在社区的活跃度非常高,通过调研了解到一些容器平台厂商也开始或已经在产品中集成此方案。

Calico是一个纯三层的数据中心网络方案(不需要Overlay),基于BGP协议通过路由转发的方式实现容器的跨主机通信。Calico 将每个节点(Node)虚拟为一个“路由器”,并为之分配独立的虚拟网段,该路由器为当前节点上的容器提供路由服务,完全利用路由规则实现动态组网,通过BGP协议通告路由,小规模部署可以直接互联,大规模下可通过指定的BGProutereflector来完成。

Calico在每一个计算节点实现了Linux内核级的vRouter来负责数据转发,所以性能优异。

其相较其他开源方案,还有一大优势是安全能力,基于iptables提供了丰富而灵活的network policy,支持很细致的ACL控制。

Calico主要由Felix、etcd、BGP client以及BGPRoute Reflector组成:

  1. Felix,Calico Agent,跑在每台需要运行Workload的节点上,主要负责为容器配置IP,路由及ACLs(iptable规则)等信息来确保Endpoint的连通状态;
  2. etcd,分布式键值存储,主要负责网络元数据一致性,确保Calico网络状态的准确性;
  3. BGP Client(BIRD),主要负责把Felix写入Kernel的路由信息分发(广播)到当前Calico网络( 通过BGP协议),确保Workload间的通信的有效性;
  4. BGP Route Reflector(BIRD),大规模集群的分级路由分发,摒弃所有节点互联的Mesh模式,通过一个或者多个BGP Route Reflector来完成集中式的路由分发。

3.jpg

#NSX-T
NSX-T是VMware的企业级网络虚拟化商业解决方案,是2016年5月在NSX-V/NSX-MH之后推出的新产品,到现在是2.3版本,T版本主要是加入容器网络支持和兼容裸金属物理机这两个重要功能,未来也会逐步将NSX-V/NSX-MH的代码整合完成替代。同Calico一样,也是内核级的数据转发,性能优异,而且产品自身提供了良好的负载均衡及安全能力。对于商业软件,企业级的服务支持和可视化的管理界面是商业方案优势较为明显的地方。

NSX-T我认为最显著的特点是,与传统网络有个南北向清晰地边界——Edge节点,而东西向全部在自己的自制范围内。

这里还有两个我认为比较重要的点应该了解一下,NSX-T与K8S的适配方式,是通过NSX-T Container Plug-in(NCP)插件与其集成,包括其他的容器平台解决方案也是通过此插件进行集成,例如Pivotal Cloud Foundry等。

还有一点是从NSX-T开始,VMware已经从NSX-V使用的基于vxlan的封装转移,并采用了更新的“Geneve”封装。Geneve是由VMware,Microsoft,Red Hat和Intel共同撰写的新版封装。Geneve将当前最佳的封装协议(如VXLAN,STT和NVGRE)整合到一个协议中。封装头的MTU为1600,所以对NSX-T自治域内的MTU值要求大于1600。
4.jpg

#存储方案
存储也是基础架构的重要一环,尤其是对于有状态的应用,数据持久化需求的支持。

我觉得在Kubernetes里,对于基础资源(CPU、内存、网络、存储)的管理,存储使用的设计较为别致,使用方式有别于常见的思路,还需要认真去理解。

这里简单介绍下在K8S中存储设计的四个重要概念:Volume、PV(PersistentVolume)、PVC(PersistentVolumeClaim)、Storage Class。
##Volume
Volume是最基础的存储抽象,其支持多种类型,包括本地存储、NFS、FC以及众多的云存储,也可以通过flex volume driver或CSI(contaioner storage Interface)编写自己的存储插件来支持特定的存储系统。

Volume可以被Pod直接使用,也可以被PV使用。普通的Volume和Pod之间是一种静态的绑定关系,在定义Pod的同时,通过volume属性来定义存储的类型,通过volumeMount来定义容器内的挂载点。

Volume有一个重要属性是,它与所属的Pod具有相同的生命周期。

Pod是Kubernetes的最小工作单元,每个 Pod 包含一个或多个容器
##PV
PV与普通的Volume不同,它是Kubernetes中的一个资源对象,创建一个PV相当于创建了一个存储资源对象,向用户屏蔽了具体的存储实现形式,而且这个资源的使用要通过PVC来请求。PV定义的内容包含存储的类型,存储的大小和访问模式等。

PV的生命周期独立于Pod,即当使用它的Pod销毁时对PV没有影响。
##PVC
PVC是用户对存储资源PV的请求,请求信息包含存储大小,访问模式等。根据PVC中指定的条件Kubernetes寻找系统中的PV资源并进行绑定。PVC消耗PV资源,PV和PVC是一一对应的。
##StorageClass
StorageClass的引入是为了解决个性化的存储需求动态供给的问题,集群管理员可以先将存储资源定义为不同类型的资源,比如高性能存储、常规存储等,之后通过StorageClass的定义动态的去创建PV,然后通过PVC来请求PV绑定。

对于多数银行业的企业,都有丰富的SAN和NAS的存储管理及运维经验,结合应用的存储需求、平台镜像方案的设计,以及银行业的应用系统普遍有多中心部署的监管需求,我认为采用NFS类型的存储支持容器数据持久化以及镜像服务的相关存储需求对容器平台在银行业的落地不失为一个不错的选择,此外还应同时开展对象存储的研究与测试,以给应用对数据存储的使用方式更多选择。

容器云平台建设会是一个不断完善、迭代积累的过程,同时容器相关技术的发展变化非常之快,在平台的建设中要持续保持新技术的敏锐嗅觉,来提升应用服务、运维管理、安全监管等各方面的水平。

原文链接:https://mp.weixin.qq.com/s/O6Za5JGywmnZ-Rswlu5WzA