微服务设计的一个关键是数据库设计,基本原则是每一个服务都有本身单独的数据库,并且只有微服务自己能够访问这个数据库。它是基于下面三个缘由。数据库
理想的设计是你的数据库只有你的服务能访问,你也只调用本身数据库中的数据,全部对别的微服务的访问都经过服务调用来实现。固然,在实际应用中,单纯的服务调用可能不能知足性能或其余要求,不一样的微服务都多少须要共享一些数据。网络
微服务之间的数据共享能够有下四种方式。架构
有一些静态的数据库表,例如国家,可能会被不少程序用到,并且程序内部须要对国家这个表作链接(join)生成最终用户展现数据,这样用微服务调用的方式就效率不高,影响性能。一个办法是在每一个微服务中配置一个这样的表,它是只读的,这样就能够作数据库链接了。固然你须要保证数据同步。这个方案在多数状况下都是能够接受的,由于如下两点:app
若是你须要读取别的数据库里的动态业务数据, 理想的方式是服务调用。若是你只是调用其余微服务作一些计算,通常状况下性能都是能够接受的。若是你须要作数据的链接,那么你能够用程序代码来作,而不是用SQL语句。若是测试以后性能不能知足要求,那你能够考虑在本身的数据库里建一套只读数据表。数据同步方式大体有两种。若是是事件驱动方式,就用发消息的方式进行同步,若是是RPC方式,就用数据库自己提供的同步方式或者第三方同步软件。负载均衡
一般状况下,你可能只须要其余数据库的几张表,每张表只须要几个字段。这时,其余数据库是数据的最终来源,控制全部写操做以及相应的业务验证逻辑,咱们叫它主表。你的只读库能够叫从表。 当一条数据写入主表后,会发一条广播消息,全部拥有从表的微服务监听消息并更新只读表中的数据。但这时你要特别当心,由于它的危险性要比静态表大得多。第一它的表结构变动会更频繁,并且它的变动彻底不受你控制。第二业务数据不像静态表,它是常常更新的,这样对数据同步的要求就比较高。要根据具体的业务需求来决定多大的延迟是能够接受的。数据库设计
另外它还有两个问题:ide
除非你能用服务调用(没有本地只读数据库)的方式完成全部功能,否则无论你是用RPC方式仍是事件驱动方式进行微服务集成,上面提到的问题都是不可避免的。可是你能够经过合理规划数据库更改,来减小上面问题带来的影响,下面将会详细讲解。微服务
这是最复杂的一种状况。通常状况下,你有一个表是主表,而其余表是从表。主表包含主要信息,并且这些主要信息被复制到从表,但微服务会有额外字段须要写入从表。这样本地微服务对从表就既有读也有写的操做。并且主表和从表有一个前后次序的关系。从表的主键来源于主表,所以必定先有主表,再有从表。性能
上图是例子。假设咱们有两个与电影有关的微服务,一个是电影论坛,用户能够发表对电影的评论。另外一个是电影商店。“movie”是共享表,左边的一个是电影论坛库,它的“movie”表是主表。右边的是电影商店库,它的“movie”表是从表。它们共享“id”字段(主键)。主表是数据的主要来源,但从表里的“quantity”和“price”字段主表里面没有。主表插入数据后,发消息,从表接到消息,插入一条数据到本地“movie”表。而且从表还会修改表里的“quantity”和“price”字段。在这种状况下,要给每个字段分配一个惟一源头(微服务),只有源头才有权利主动更改字段,其余微服务只能被动更改(接收源头发出的更改消息以后再改)。在本例子中, “quantity”和“price”字段的源头是右边的表,其余的字段的源头都是左边的表。本例子中“quantity”和“price”只在从表中存在,所以数据写入是单向的,方向是主表到从表。若是主表也须要这些字段,那么它们还要被回写,那数据写入就变成双向的。测试
这种方式是要绝对禁止的。生产环境中的许多程序错误和性能问题都是由这种方式产生的。上面的三种方式因为是另外新建了本地只读数据库表,产生了数据库的物理隔离,这样一个数据库的性能问题不会影响到另外一个。另外,当主库中的表结构更改时,你能够暂时保持从库中的表不变,这样程序还能够运行。若是直接访问别人的库,主库一修改,别的微服务程序立刻就会报错。
从上面的论述能够看出,数据库表结构的修改是一个影响范围很广的事情。在微服务架构中,共享的表在别的服务中也会有一个只读的拷贝。如今当你要更改表结构时,还须要考虑到对别的微服务的影响。当在单体(Monolithic)架构中,为了保证程序部署可以回滚,数据库的更新是向后兼容的。须要兼容性的另外一个缘由是支持蓝绿发布(Blue-Green Deployment)。在这种部署方式中,你同时拥有新旧版本的代码,由负载均衡来决定每个请求指向那个版本。它们能够共享一个数据库(这就要求数据库是向后兼容的),也可使用不一样的数据。数据库的更新简单来说有如下几种类型:
向后兼容的数据库更新的好处是,当程序部署出现问题时,如需进行回滚。只要回滚程序就好了,而没必要回滚数据库。回滚时通常只回滚一个版本。凡是须要删除的表或字段在本次部署时都不作修改,等到一个或几个版本以后,确认没有问题了再删除。它的另外一个好处就是不会对其余微服务中的共享表产生马上的直接影响。当本微服务升级后,其余微服务能够评估这些数据库更新带来的影响再决定是否须要作相应的程序或数据库修改。
微服务的一个难点是如何实现跨服务的事物支持。两阶段提交(Two-Phase Commit)已被证实性能上不能知足需求,如今基本上没有人用。被一致承认的方法叫Saga。它的原理是为事物中的每一个操做写一个补偿操做(Compensating Transaction),而后在回滚阶段挨个执行每个补偿操做。示例以下图,在一个事物中共有3个操做T1,T2,T3。每个操做要定义一个补偿操做,C1,C2,C3。事物执行时是按照正向顺序先执行T1,当回滚时是按照反向顺序先执行C3。 事物中的每个操做(正向操做和补偿操做)都被包装成一个命令(Command),Saga执行协调器(Saga Execution Coordinator (SEC))负责执行全部命令。在执行以前,全部的命令都会按顺序被存入日志中,而后Saga执行协调器从日志中取出命令,依次执行。当某个执行出现错误时,这个错误也被写入日志,而且全部正在执行的命令被中止,开始回滚操做。
Saga放松了对一致性(Consistency)的要求,它能保证的是最终一致性(Eventual Consistency),所以在事物执行过程当中数据是不一致的,而且这种不一致会被别的进程看到。在生活中,大多数状况下,咱们对一致性的要求并无那么高,短暂的不一致性是能够接收的。例如银行的转帐操做,它们在执行过程当中都不是在一个数据库事物里执行的,而是用记帐的方式分红两个动做来执行,保证的也是最终一致性。
Saga的原理看起来很简单,但要想正确的实施仍是有必定难度的。它的核心问题在于对错误的处理,要把它彻底讲明白须要另写一遍文章,我如今只讲一下要点。网络环境是不可靠的,正在执行的命令可能很长时间都没有返回结果,这时,第一,你要设定一个超时。第二,由于你不知道没有返回值的缘由是,已经完成了命令但网络出了问题,仍是没完成就牺牲了,所以不知道是否要执行补偿操做。这时正确的作法是重试原命令,直到获得完成确认,而后再执行补偿操做。但这对命令有一个要求,那就是这个操做必须是幂等的(Idempotent),也就是说它能够执行屡次,但最终结果仍是同样的。
另外,有些操做的补偿操做比较容易生成,例如付款操做,你只要把钱款退回就能够了。但有些操做,像发邮件,完成以后就没有办法回到以前的状态了,这时就只能再发一个邮件更正之前的信息。所以补偿操做不必定非要返回到原来的状态,而是抵消掉原来操做产生的效果。
咱们原来的程序大多数都是单体程序,但如今要把它拆分红微服务,应该怎样作才能下降对现有应用的影响呢?
咱们用上面的图来作例子。它共有两个程序,一个是“Styling app”,另外一个是“Warehouse app”,它们共享图中下面的数据库,库里有三张表,“core client”,“core sku”,“core item”。
假设咱们要拆分出来一个微服务叫“client-service”,它须要访问“core client”表。第一步,咱们先把程序从原来的代码里拆分出来,变成一个服务. 数据库不动,这个服务仍然指向原来的数据库。其余程序再也不直接访问这个服务管理的表,而是经过服务调用或另建共享表来获取数据。
第二步,再把服务的数据库表拆分出来,这时微服务就拥有它本身的数据库了,而再也不须要原来的共享数据库了。这时就成了一个真正意义上的的微服务。
上面只讲了拆分一个微服务,若是有多个须要拆分,则需一个一个按照上面讲的方法依次进行。
另外,Martin Fowler有一个很好的建议。那就是,当你把服务从单体程序里拆分时,不要只想着把代码拆分出来。由于如今的需求可能已经跟原来有所不一样,原先的设计可能也不太适用了。并且,技术也已更新,代码也要做相应的改造。更好的办法是重写原来的功能(而不是重写原来的代码),把重点放在拆分业务功能上,而不是拆分代码上,用新的设计和技术来实现这个业务功能。
数据库设计是微服务设计的一个关键点,基本原则是每一个微服务都有本身单独的数据库,并且只有微服务自己能够访问这个数据库。微服务之间的数据共享能够经过服务调用,或者主、从表的方式实现。在共享数据时,要找到合适的同步方式。在微服务架构中,数据库的修改影响普遍,须要保证这种修改是向后兼容的。实现跨服务事物的标准方法是Saga。当把单体程序拆分红微服务时,能够分步进行,以减小对现有程序的影响。