分享

e袋洗的微服务架构之路与Docker实践

fc013 发表于 2016-5-14 16:27:45 [显示全部楼层] 只看大图 回帖奖励 阅读模式 关闭右栏 1 11117

问题导读:

1.为什么要拆成微服务的架构?
2.单体架构有什么问题?
3.拆分微服务可能会带来的问题?




编者按:微服务是当前很热的技术,互联网应用由于业务线众多,业务发展速度快,对于微服务有很高的需求,但国内真正实现了微服务架构的公司并不多。e袋洗是灵雀云一个典型的O2O客户,本文来自e袋洗架构师邸跃在3月19日线下活动的分享,介绍了e袋洗在微服务架构与Docker的实践经验,详细介绍了如何进行微服务拆分,拆分后可能带来的问题和解决方案,以及灵雀云和Docker在其中发挥的作用。


e袋洗是一款基于移动互联网的O2O洗衣服务产品,为用户提供了一种全新概念的洗衣模式,用户只需将待洗衣物装进指定的洗衣袋内,通过微信,APP预约,2小时内有专门取送专人员上门取件,按袋计费,全天候上门服务,极大程度地节省了用户的时间和金钱 。

作者简介:邸跃,e袋洗架构师。信息学硕士,毕业于德国亚琛工业大学,曾就职于乐元素,负责公司对欧美手机和社交平台的业务。后个人创业,研发过亚马逊国际购物比价引擎,欧洲航线比价引擎,欧洲旅游比价引擎等。2014年加入e袋洗,担任架构师和后端开发负责人一职。


微服务架构之路
e袋洗初期系统只包含基础的客户和订单管理功能,所有的功能都在一套php构建的系统内,邸跃到e袋洗之后开始着手将整个架构拆分为微服务架构。

>>>>为什么要拆成微服务的架构?

640_006.jpg

  • 首先,e袋洗的后端需要支持的业务类型繁多,包括销售、订单、商品、客户信息,物流系统,调度系统,客服系统等,同时需要对加工店、调度人员,物流人员等进行管理和授权;
  • 另外,由于e袋洗是一个基于位置的O2O服务,基于位置的自动分单功能也是必不可少的;整个系统既有销售的功能,又有物流的功能,还有生产管理的功能,复杂度较高,功能差异大,时间敏感型请求和计算敏感型请求同时存在。
  • e袋洗是创业型公司,对开发速度有很高的要求,所以整个系统尽可能是并行开发的。为了让系统架构尽快地搭建起来,我们对整个系统进行了微服务拆分。


>>>>单体架构有什么问题?

  • 如果采用单体架构构建我们的系统,单点的异常可能会导致整个系统崩溃。举个例子,例如像微信发红包这种服务,叠加上春节晚上红包的流量,如果用单块架构,可能导致整个微信都不能发,但如果用微服务架构隔离开的话,即使单点崩溃,也不会影响到其它系统的稳定性。
  • 不同代码之间的功能边界比较模糊,比如两个团队分别开发订单和优惠券系统,这里面很多模型是共用的,并行开发的时候代码冲突的概率较高,如果开始的时候就把它严格拆分成两个系统,通过接口来通讯,冲突是可以避免的。
  • 不同类型的应用,有些是内存型的,有些是计算型的,如果都打包在一起,在并行扩容的时候,不能很好的分配服务器资源,容易造成浪费。


>>>>拆分微服务前的准备:

  • 业务层应该做到模型化、抽象化,功能尽量单一,避免代码耦合。比如说很多系统都会依赖订单系统存在,如果耦合性很大,订单系统需要更改的时候,上层很多系统都需要变化,代价很大。所以在拆分微服务系统的时候,第一步就需要把它的业务层模型解耦;
  • 需要一个业务之间的通信框架,通讯可以分为同步请求和异步请求,同步请求可以基于Http等协议,异步请求基本需要一个消息队列来支撑消息存储与多种模式的消息订阅;
  • 需要服务发现和服务注册的机制,因为拆分成微服务之后,有可能在多台服务器上部署同一个服务,比如订单服务部署在两台或者三台服务器,怎么样找到这个资源,怎么做负载?这是从单一系统切换到分布式系统肯定会遇到的问题;
  • 最后,需要内部统一的通信规范,拆分服务,定义接口的时候,所有人遵循同一规范来做,才有可能能降低开发成本,沟通成本和维护成本。


>>>>拆分微服务可能会带来的问题:

  • 首先服务调用从方法调用转变为API调用,一定会增加服务端和客户端开发维护成本;
  • 每次API调用必然会带来IO时间开销;
  • 请求批量数据的时候,对于多维度的过滤需求,接口开发复杂度较高。比如我们做订单查询的时候,可能会依据客户的维度来查询,也可能从时间维度来查询,还可能会依据订单种类查询,如果没有一致化的查询规范的话,写接口的时候会比较累,也难以维护,所有可能的维度都要考虑,有可能是单维请求,有可能几个纬度叠加一起的请求,可能性是指数级增加的。


>>>>e袋洗的解决方案:

  • 首先,项目间共享的单元是模型,而不是项目。比如说订单服务,从属于订单项目,订单本身是一个Class,作为一个服务模型暴露给别的项目来使用。使用这个服务的时候单元是模型,而不是项目;
  • 服务发现基于模型,不基于项目。使用方不用管模型到底是那个项目的,只需要问服务发现怎么调用到这个数据,服务发现负责管理模型到底在哪里,属于哪个服务器,会带有项目信息,但是不绑定,如果这个模型从原项目迁出到另一个项目里,只需要通知服务发现,现在要到这些服务器上找到这个服务就好。这么做对于老项目逐步拆分微服务的时候尤其有用。
  • 模型共享的方法,可以分为类方法和实例方法,类方法多数为对模型的批量操作,比如依据城市过滤所有订单,或者批量更新某些属性;实例方法是对模型的实例进行操作,仍然用订单模型举例,对于确定的订单,把这个订单标记为已支付,已完成等状态的操作都属于实例方法。
  • 对多维度Query提供类似ActiveRecord的请求框架,允许定义Scope,允许在返回结果的时候,Select某些字段,直接把需要的过滤出来,可以节省IO成本和计算资源。
  • 拆分模型到项目的时候,调用相关度高的模型分在一个服务内。比如说商品和订单之间经常会互相访问,做Join查询,一旦拆分Join的成本是很高的,这类模型应该从属于同一项目。如果拆分后经常需要做跨项目Join,应该考虑一下是否拆分的逻辑不够合理。


>>>>服务共享框架Zombie

e袋洗在实践中发现,在做服务型接口调用的时候,由于服务间最小的单位是基于模型的,多数接口调用是直接下穿到模型的方法调用,多数方法的返回结果就是接口的返回结果,只是一些情况下需要封装Exception等附加信息。

有了统一的服务共享规范,为了降低刚才讲到的接口产生的开发成本和维护成本,e袋洗又开发了一个服务共享框架Zombie,服务提供端可以基于定义文件自动生成接口,服务使用端可以基于生成的接口自动反射成类和实例。

640.jpg

这是e袋洗现在的架构图,最外层是APP或者是微信,用户下单主要通过这两个渠道,这两个渠道会把请求发送到客户端接口上面,客户端接口不承载任何实际数据和业务逻辑,它基于相应的微服务模型来完成业务流程,主要的作用是依据访问的功能调用微服务模型,以及根据接口返回参数规范加工模型提供的数据。与之类似的还有物流和加工店接口。


除了对外的接口以外,还有一些后端管理的服务,比如客服,调度,财务等,原则上这些后端Web服务同样不承载实际业务逻辑,同样也是微服务的模型来运转的。最底层是各种微服务项目,每个项目包含一个以上微服务模型。

640_011.jpg

下面聚焦到物流微服务项目,它主要提供三个服务,TransTask管理物流流程里的最小交接单元,Courier管理物流人员信息,Schedule管理物流人员排班。中央有一层基于Openresty实现的服务发现路由,调用的时候由服务路由中转结果反馈给调用者,调用者可能是我们的接口服务,后端Web服务,也可能是其它微服务。

>>>>代码示例

下面是定义微服务模型的代码,实际作用是获取城市列表,因为功能较简单,所有的功能直接由ORM层的CityClass来支撑。允许在调用时反射所有的属性,提供多维度过滤方法,这些方法可以做级联调用,所以属于特殊的chain_methonds方法类型。其他还暴露了一些查找单个模型的类方法,以及激活单个城市的set_active方法。

640_010.jpg

下面是在没有Zombie框架的时候为了实现类似功能的接口代码。

640_004.jpg

在Zombie框架之前,城市这个接口的定义需要126行代码,现在只需要15行定义暴露给外面调用就可以了。

640_008.jpg

客户端调用的时候,只需要调用Zombie命名空间下面的City模型,模型会自动由服务发现找到相应的定义,在运行时映射成Class,之后就可以使用相应的方法了。

640_009.jpg

>>>>异步请求

上面讲的主要是同步请求的部分,下面的部分会讲到异步请求,异步请求是基于Kafka消息队列实现的。

640_002.jpg

>>>>哪些事件应该做成异步请求?

仍然用订单举例,在下单的时候,同步需要触发确认短信,自动派单,积分发放,推送订单信息给加工厂等动作,这几个动作虽然跟下订单相关,但都不属于订单模型的核心业务,最好做成异步事件,一方面不影响订单模型的效率,另一方面充分解耦。

640_003.jpg

e袋洗的异步的事件同样基于微服务模型,异步事件的发起方只负责通知消息队列,告诉它已经发生了订单创建的消息。消息采用1发多收的模式,消息头为(Model_name).(function_name),消息附带Payload为订单详情。上面讲的配套动作监听所有订单创建消息,独立采取行动。

Docker实践

以上是e袋洗对微服务的思考和配套实现的架构。随着微服务项目越来越多,微服务架构也带来了一些管理成本,比如,在开发,发布和测试的时候会面临的问题:

  • 需要安装很多依赖,比如客服后台的客户详情页需要显示订单信息如果订单服务不跑起来,本地就没法调试这个功能。
  • 开发环境需要管理一堆项目的配置;
  • 开发,测试,线上环境不一样可能会带来一些不兼容的问题,而且这些问题有可能等到测试或上线的时候才暴露出来
  • 多个测试环境的维护和配置需要很大的精力。


为了解决这些问题,e袋洗开始使用Docker技术,这也是微服务和Docker能够天然绑定到一起的原因。总结一下各个团队在实践中对Docker的需求:

开发团队对Docker的需求是:对需要的项目可以获取到某个指定的版本,最好可以一键启动所有依赖服务,最好类似ES,Kafka等基础依赖也可以跑在Docker里面,以便节省维护环境的成本。同时数据应该在本地管理,包括数据库文件、日志等。


测试环境对Docker需求有:

  • 环境最好和开发环境一致;
  • 不同使用者之间的环境完全隔离;
  • 部分基础服务应该最好跑在Docker上面,因为测试环境中,不同的测试人员可能要求自己有一套独立的测试环节;
  • 最好数据可以方便地做同步,备份和替换;
  • 尽量节省机器资源;
  • 可以方便的查阅日志。


>>>>Docker容器的分类

要使用Docker,首先要明确Docker容器内封装的服务,并制作Docker镜像。e袋洗的Docker容器根据功能可以分为外部访问型,内部服务型,数据型:

  • 外部访问型可以提供对公网的服务,比如接口或后台管理平台等;
  • 内部服务型只提供对内网接口,对应微服务的各种模型项目;
  • 数据型Docker对应基础服务,比如Mysql或ElasticSearch。 这类的Docker需要本地存储空间,一般只在开发和测试环境使用,以便节省搭环境的时间,线上的环境不建议这样用,这类基础服务在生产环境最好独立的跑,不应该放在Docker里。


>>>>Docker镜像的制作策略

在e袋洗,单个Docker镜像的制作理念是:不用依赖其他的系统环境可以独立的运行起来,比如下图这个运行了RubyWeb微服务的Docker容器,应该包括Nginx,Passenger,Ruby On Rails等整套服务,策略上会和用虚拟机有一些区别:虚拟机为了节省资源,一个Nginx可能会带多个服务跑,Docker里每个Nginx首先在Docker里,只带一个服务跑。

640_007.jpg

写Dockerfile的时候,因为Docker支持分层,而项目间会有一些共性的依赖,为了降低打包成本可以把共性的这部分做成底层镜像,其余的服务依赖这个镜像,这样可以实现在代码更新的时候只Pull最上面的一层,通信量比较小,速度较快。在e袋洗的项目里,系统,Nginx,Passenger,和Ruby环境是项目间公用的底层镜像,上面在用一层构建各自的项目。

640_005.jpg

>>>>配置管理

项目的配置文件管理在我们这里可以分为两层,下层是Settings.yml文件,用key-value的方式承载所有实际的配置项目。这个文件可以根据环境的不同加载production.yml,development.yml,test.yml等覆盖各个环境中的配置差异部分。上层是使用配置文件进行初始化操作的各种文件,文件内不保存实际的配置参数,而是通过Settings.yml文件读取。这套配置管理在Docker内会通过Entrypoints文件覆盖Settings.yml里相应的配置来适应Docker的环境。


>>>>持续集成机制

目前e袋洗所有的镜像都托管在灵雀云。灵雀云提供了GitHub的钩子服务,如果单纯拉取文件依据Dockerfile进行打包的话,只需要做相应的配置就可以了。不过由于e袋洗有一些依赖库是自己的私有仓库,需要另外配置key文件进行访问,就没办法用这套服务了,e袋洗的解决方案是专门设立一台持续集成的CI机器做打包,使用Jenkins管理整个服务。CI机器存储代码仓库的令牌,CI机器OS版本,Ruby版本和Docker保持一致;Ruby依赖库通过path设定直接写在项目目录下;打包时在CI本地拉取最新代码,更新所有的Ruby依赖库,运行所有测试代码,然后编译所有静态文件;一次性把项目目录拷贝如项目层容器,标记分支和commit版本,push到灵雀云的镜像仓库。






已有(1)人评论

跳转到指定楼层
恋枫缩影 发表于 2016-5-16 01:24:58
学习下微服务
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

推荐上一条 /2 下一条