精华内容
下载资源
问答
  • 微服务数据库设计

    千次阅读 多人点赞 2019-10-19 09:14:13
    微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。 优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口...

    单独的数据库:

    微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。

    优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口(RPC或消息),没有其他接口。如果微服务不能独享自己的数据库,那么数据库也变成了接口的一部分,这大大拓展了接口范围。
    错误诊断:生产环境中的错误大部分都是和数据库有关的,要么是数据出了问题,要么是数据库的使用方式出了问题。当你不能完全控制数据库的访问时,会有各种各样的错误发生。它可能是别的程序直接连到你的数据库或者是其他部门直接用客户端访问数据库的数据,而这些都是在程序中查不到的,增加了错误排查难度。如果是程序中的问题,只要修改了代码,那么这个错误就不会再有。而上面提到的错误,你永远都没法预测它们什么时候还会再次发生。
    性能调优:性能调优也是一样,你需要对数据库有全权控制才能保证它的性能。如果其他部门一定要访问数据库,而且只是查询的话,那么可以另外创建一份只读数据库,让他们在另一个库中查询,这样才不会影响到你的库。

    理想的设计是你的数据库只有你的服务能访问,你也只调用自己数据库中的数据,所有对别的微服务的访问都通过服务调用来实现(请参阅“微服务之间调用的最佳设计“)。当然,在实际应用中,单纯的服务调用可能不能满足性能或其他要求,不同的微服务都多少需要共享一些数据。

    共享数据:

    微服务之间的数据共享可以有下四种方式。

    静态表:

    有一些静态的数据库表,例如国家,可能会被很多程序用到,而且程序内部需要对国家这个表做连接(join)生成最终用户展示数据,这样用微服务调用的方式就效率不高,影响性能。一个办法是在每个微服务中配置一个这样的表,它是只读的,这样就可以做数据库连接了。当然你需要保证数据同步。这个方案在多数情况下都是可以接受的,因为以下两点:

    1. 静态的数据库表结构基本不变:因为一旦表结构变了,你不但要更改所有微服务的数据库表,还要修改所有微服务的程序。
    2. 数据库表中的数据变化不频繁:这样数据同步的工作量不大。另外当你同步数据库时总会有延迟,如果数据变化不频繁那么你有很多同步方式可供选择。

    只读业务数据访问:

    如果你需要读取别的数据库里的动态业务数据, 理想的方式是服务调用。如果你只是调用其他微服务做一些计算,一般情况下性能都是可以接受的。如果你需要做数据的连接,那么你可以用程序代码来做,而不是用SQL语句。如果测试之后性能不能满足要求,那你可以考虑在自己的数据库里建一套只读数据表。数据同步方式大致有两种。如果是事件驱动方式,就用发消息的方式进行同步,如果是RPC方式,就用数据库本身提供的同步方式或者第三方同步软件。

    通常情况下,你可能只需要其他数据库的几张表,每张表只需要几个字段。这时,其他数据库是数据的最终来源,控制所有写操作以及相应的业务验证逻辑,我们叫它主表。你的只读库可以叫从表。 当一条数据写入主表后,会发一条广播消息,所有拥有从表的微服务监听消息并更新只读表中的数据。但这时你要特别小心,因为它的危险性要比静态表大得多。第一它的表结构变更会更频繁,而且它的变更完全不受你控制。第二业务数据不像静态表,它是经常更新的,这样对数据同步的要求就比较高。要根据具体的业务需求来决定多大的延迟是可以接受的。

    另外它还有两个问题:

    1. 数据的容量:数据库中的数据量是影响性能的主要因素。因为这个数据是外来的,不利于掌握它的流量规律,很难进行容量规划,也不能更好地进行性能调优。
    2. ** 接口外泄**:微服务之间的接口本来只有服务调用接口,这时你可以对内部程序和数据库做任何更改,而不影响其他服务。现在数据库表结构也变成了接口的一部分。接口一旦发布之后,基本是不能更改的,这大大限制了你的灵活性。幸运的是因为另外建了一套表,有了一个缓冲,当主表修改时,从表也许不需要同步更新。

    除非你能用服务调用(没有本地只读数据库)的方式完成所有功能,不然不管你是用RPC方式还是事件驱动方式进行微服务集成,上面提到的问题都是不可避免的。但是你可以通过合理规划数据库更改,来减少上面问题带来的影响,下面将会详细讲解。

    读写业务数据访问:

    这是最复杂的一种情况。一般情况下,你有一个表是主表,而其他表是从表。主表包含主要信息,而且这些主要信息被复制到从表,但微服务会有额外字段需要写入从表。这样本地微服务对从表就既有读也有写的操作。而且主表和从表有一个先后次序的关系。从表的主键来源于主表,因此一定先有主表,再有从表。

    file

    图片来源

    上图是例子。假设我们有两个与电影有关的微服务,一个是电影论坛,用户可以发表对电影的评论。另一个是电影商店。“movie”是共享表,左边的一个是电影论坛库,它的“movie”表是主表。右边的是电影商店库,它的“movie”表是从表。它们共享“id”字段(主键)。主表是数据的主要来源,但从表里的“quantity”和“price”字段主表里面没有。主表插入数据后,发消息,从表接到消息,插入一条数据到本地“movie”表。并且从表还会修改表里的“quantity”和“price”字段。在这种情况下,要给每一个字段分配一个唯一源头(微服务),只有源头才有权利主动更改字段,其他微服务只能被动更改(接收源头发出的更改消息之后再改)。在本例子中, “quantity”和“price”字段的源头是右边的表,其他的字段的源头都是左边的表。本例子中“quantity”和“price”只在从表中存在,因此数据写入是单向的,方向是主表到从表。如果主表也需要这些字段,那么它们还要被回写,那数据写入就变成双向的。

    直接访问其它数据库:

    这种方式是要绝对禁止的。生产环境中的许多程序错误和性能问题都是由这种方式产生的。上面的三种方式由于是另外新建了本地只读数据库表,产生了数据库的物理隔离,这样一个数据库的性能问题不会影响到另一个。另外,当主库中的表结构更改时,你可以暂时保持从库中的表不变,这样程序还可以运行。如果直接访问别人的库,主库一修改,别的微服务程序马上就会报错。请参阅ApplicationDatabase

    向后兼容的数据库更新:

    从上面的论述可以看出,数据库表结构的修改是一个影响范围很广的事情。在微服务架构中,共享的表在别的服务中也会有一个只读的拷贝。现在当你要更改表结构时,还需要考虑到对别的微服务的影响。当在单体(Monolithic)架构中,为了保证程序部署能够回滚,数据库的更新是向后兼容的。需要兼容性的另一个原因是支持蓝绿发布(Blue-Green Deployment)。在这种部署方式中,你同时拥有新旧版本的代码,由负载均衡来决定每一个请求指向那个版本。它们可以共享一个数据库(这就要求数据库是向后兼容的),也可以使用不同的数据。数据库的更新简单来讲有以下几种类型:

    增加表或字段:如果字段可取空值,这个操作是向后兼容的。如果是非空值就要插入一个缺省值。
    删除表或字段:可先暂时保留被删除表或字段,经过几个版本之后再删除。
    修改字段名:新增加一个字段,把数据从旧字段拷贝到新字段,用数据库触发器(或程序)同步旧字段和新字段(供过渡时期使用)。 然后再在几个版本之后把原来的字段删除(请参阅Update your Database Schema Without Downtime)。
    修改表名:如果数据库支持可更新视图,最简单的办法是先修改表的名字,然后创建一个可更新视图指向原来的表(请参阅Evolutionary Database Design )。如果数据库不支持可更新视图,使用的方法与修改字段名相似,需要创建新的表并做数据同步。
    修改字段类型:与修改字段名几乎相同,只是在拷贝数据时,需要做数据类型转换。

    向后兼容的数据库更新的好处是,当程序部署出现问题时,如需进行回滚。只要回滚程序就行了,而不必回滚数据库。回滚时一般只回滚一个版本。凡是需要删除的表或字段在本次部署时都不做修改,等到一个或几个版本之后,确认没有问题了再删除。它的另一个好处就是不会对其他微服务中的共享表产生立刻的直接影响。当本微服务升级后,其他微服务可以评估这些数据库更新带来的影响再决定是否需要做相应的程序或数据库修改。

    跨服务事物:

    微服务的一个难点是如何实现跨服务的事物支持。两阶段提交(Two-Phase Commit)已被证明性能上不能满足需求,现在基本上没有人用。被一致认可的方法叫Saga。它的原理是为事物中的每个操作写一个补偿操作(Compensating Transaction),然后在回滚阶段挨个执行每一个补偿操作。示例如下图,在一个事物中共有3个操作T1,T2,T3。每一个操作要定义一个补偿操作,C1,C2,C3。事物执行时是按照正向顺序先执行T1,当回滚时是按照反向顺序先执行C3。 事物中的每一个操作(正向操作和补偿操作)都被包装成一个命令(Command),Saga执行协调器(Saga Execution Coordinator (SEC))负责执行所有命令。在执行之前,所有的命令都会按顺序被存入日志中,然后Saga执行协调器从日志中取出命令,依次执行。当某个执行出现错误时,这个错误也被写入日志,并且所有正在执行的命令被停止,开始回滚操作。

    file

    图片来源

    Saga放松了对一致性(Consistency)的要求,它能保证的是最终一致性(Eventual Consistency),因此在事物执行过程中数据是不一致的,并且这种不一致会被别的进程看到。在生活中,大多数情况下,我们对一致性的要求并没有那么高,短暂的不一致性是可以接收的。例如银行的转账操作,它们在执行过程中都不是在一个数据库事物里执行的,而是用记账的方式分成两个动作来执行,保证的也是最终一致性。

    Saga的原理看起来很简单,但要想正确的实施还是有一定难度的。它的核心问题在于对错误的处理,要把它完全讲明白需要另写一遍文章,我现在只讲一下要点。网络环境是不可靠的,正在执行的命令可能很长时间都没有返回结果,这时,第一,你要设定一个超时。第二,因为你不知道没有返回值的原因是,已经完成了命令但网络出了问题,还是没完成就牺牲了,因此不知道是否要执行补偿操作。这时正确的做法是重试原命令,直到得到完成确认,然后再执行补偿操作。但这对命令有一个要求,那就是这个操作必须是幂等的(Idempotent),也就是说它可以执行多次,但最终结果还是一样的。

    另外,有些操作的补偿操作比较容易生成,例如付款操作,你只要把钱款退回就可以了。但有些操作,像发邮件,完成之后就没有办法回到之前的状态了,这时就只能再发一个邮件更正以前的信息。因此补偿操作不一定非要返回到原来的状态,而是抵消掉原来操作产生的效果。如果想了解更多,请看这里.

    微服务的拆分:

    我们原来的程序大多数都是单体程序,但现在要把它拆分成微服务,应该怎样做才能降低对现有应用的影响呢?

    file

    图片来源

    我们用上面的图来做例子。它共有两个程序,一个是“Styling app”,另一个是“Warehouse app”,它们共享图中下面的数据库,库里有三张表,“core client”,“core sku”,“core item”。

    file

    图片来源

    假设我们要拆分出来一个微服务叫“client-service”,它需要访问“core client”表。第一步,我们先把程序从原来的代码里拆分出来,变成一个服务. 数据库不动,这个服务仍然指向原来的数据库。其他程序不再直接访问这个服务管理的表,而是通过服务调用或另建共享表来获取数据。

    file

    图片来源

    第二步,再把服务的数据库表拆分出来,这时微服务就拥有它自己的数据库了,而不再需要原来的共享数据库了。这时就成了一个真正意义上的的微服务。

    上面只讲了拆分一个微服务,如果有多个需要拆分,则需一个一个按照上面讲的方法依次进行。

    另外,Martin Fowler在他的文章"Break Monolith into Microservices"里有一个很好的建议。那就是,当你把服务从单体程序里拆分时,不要只想着把代码拆分出来。因为现在的需求可能已经跟原来有所不同,原先的设计可能也不太适用了。而且,技术也已更新,代码也要作相应的改造。更好的办法是重写原来的功能(而不是重写原来的代码),把重点放在拆分业务功能上,而不是拆分代码上,用新的设计和技术来实现这个业务功能。

    结论:

    数据库设计是微服务设计的一个关键点,基本原则是每个微服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。微服务之间的数据共享可以通过服务调用,或者主、从表的方式实现。在共享数据时,要找到合适的同步方式。在微服务架构中,数据库的修改影响广泛,需要保证这种修改是向后兼容的。实现跨服务事物的标准方法是Saga。当把单体程序拆分成微服务时,可以分步进行,以减少对现有程序的影响。

    索引:

    1. 微服务之间调用的最佳设计
    2. ApplicationDatabase
    3. Update your Database Schema Without Downtime
    4. Evolutionary Database Design
    5. Fault-Tolerance and Data Consistency Using Distributed Sagas
    6. Distributed Sagas: A Protocol for Coordinating Microservices - Caitie McCaffrey - JOTB17
    7. Managing Data in Microservices
    8. How to break a Monolith into Microservices

    本文由博客一文多发平台 OpenWrite 发布!

    展开全文
  • 谈谈微服务数据库设计思路吧 单独的数据库 微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。 优化服务接口:微服务...

    谈谈微服务的数据库设计思路吧

    单独的数据库

    微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。

    优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口(RPC或消息),没有其他接口。如果微服务不能独享自己的数据库,那么数据库也变成了接口的一部分,这大大拓展了接口范围。

    错误诊断:生产环境中的错误大部分都是和数据库有关的,要么是数据出了问题,要么是数据库的使用方式出了问题。当你不能完全控制数据库的访问时,会有各种各样的错误发生。它可能是别的程序直接连到你的数据库或者是其他部门直接用客户端访问数据库的数据,而这些都是在程序中查不到的,增加了错误排查难度。如果是程序中的问题,只要修改了代码,那么这个错误就不会再有。而上面提到的错误,你永远都没法预测它们什么时候还会再次发生。

    性能调优:性能调优也是一样,你需要对数据库有全权控制才能保证它的性能。如果其他部门一定要访问数据库,而且只是查询的话,那么可以另外创建一份只读数据库,让他们在另一个库中查询,这样才不会影响到你的库。

    理想的设计是你的数据库只有你的服务能访问,你也只调用自己数据库中的数据,所有对别的微服务的访问都通过服务调用来实现。当然,在实际应用中,单纯的服务调用可能不能满足性能或其他要求,不同的微服务都多少需要共享一些数据。

    共享数据

    微服务之间的数据共享可以有下四种方式。

    静态表

    有一些静态的数据库表,例如国家,可能会被很多程序用到,而且程序内部需要对国家这个表做连接(join)生成最终用户展示数据,这样用微服务调用的方式就效率不高,影响性能。一个办法是在每个微服务中配置一个这样的表,它是只读的,这样就可以做数据库连接了。当然你需要保证数据同步。这个方案在多数情况下都是可以接受的,因为以下两点:

    静态的数据库表结构基本不变:因为一旦表结构变了,你不但要更改所有微服务的数据库表,还要修改所有微服务的程序。

    数据库表中的数据变化不频繁:这样数据同步的工作量不大。另外当你同步数据库时总会有延迟,如果数据变化不频繁那么你有很多同步方式可供选择。

    更多面试题,欢迎关注公众号 Java面试题精选

    只读业务数据访问

    如果你需要读取别的数据库里的动态业务数据, 理想的方式是服务调用。如果你只是调用其他微服务做一些计算,一般情况下性能都是可以接受的。如果你需要做数据的连接,那么你可以用程序代码来做,而不是用SQL语句。如果测试之后性能不能满足要求,那你可以考虑在自己的数据库里建一套只读数据表。数据同步方式大致有两种。如果是事件驱动方式,就用发消息的方式进行同步,如果是RPC方式,就用数据库本身提供的同步方式或者第三方同步软件。

    通常情况下,你可能只需要其他数据库的几张表,每张表只需要几个字段。这时,其他数据库是数据的最终来源,控制所有写操作以及相应的业务验证逻辑,我们叫它主表。你的只读库可以叫从表。当一条数据写入主表后,会发一条广播消息,所有拥有从表的微服务监听消息并更新只读表中的数据。但这时你要特别小心,因为它的危险性要比静态表大得多。第一它的表结构变更会更频繁,而且它的变更完全不受你控制。第二业务数据不像静态表,它是经常更新的,这样对数据同步的要求就比较高。要根据具体的业务需求来决定多大的延迟是可以接受的。

    另外它还有两个问题:

    数据的容量:数据库中的数据量是影响性能的主要因素。因为这个数据是外来的,不利于掌握它的流量规律,很难进行容量规划,也不能更好地进行性能调优。

    接口外泄:微服务之间的接口本来只有服务调用接口,这时你可以对内部程序和数据库做任何更改,而不影响其他服务。现在数据库表结构也变成了接口的一部分。接口一旦发布之后,基本是不能更改的,这大大限制了你的灵活性。幸运的是因为另外建了一套表,有了一个缓冲,当主表修改时,从表也许不需要同步更新。

    除非你能用服务调用(没有本地只读数据库)的方式完成所有功能,不然不管你是用RPC方式还是事件驱动方式进行微服务集成,上面提到的问题都是不可避免的。但是你可以通过合理规划数据库更改,来减少上面问题带来的影响,下面将会详细讲解。

    读写业务数据访问

    这是最复杂的一种情况。一般情况下,你有一个表是主表,而其他表是从表。主表包含主要信息,而且这些主要信息被复制到从表,但微服务会有额外字段需要写入从表。这样本地微服务对从表就既有读也有写的操作。而且主表和从表有一个先后次序的关系。从表的主键来源于主表,因此一定先有主表,再有从表。
    在这里插入图片描述

    上图是例子。假设我们有两个与电影有关的微服务,一个是电影论坛,用户可以发表对电影的评论。另一个是电影商店。“movie”是共享表,左边的一个是电影论坛库,它的“movie”表是主表。右边的是电影商店库,它的“movie”表是从表。它们共享“id”字段(主键)。

    主表是数据的主要来源,但从表里的“quantity”和“price”字段主表里面没有。主表插入数据后,发消息,从表接到消息,插入一条数据到本地“movie”表。并且从表还会修改表里的“quantity”和“price”字段。在这种情况下,要给每一个字段分配一个唯一源头(微服务),只有源头才有权利主动更改字段,其他微服务只能被动更改(接收源头发出的更改消息之后再改)。

    在本例子中, “quantity”和“price”字段的源头是右边的表,其他的字段的源头都是左边的表。本例子中“quantity”和“price”只在从表中存在,因此数据写入是单向的,方向是主表到从表。如果主表也需要这些字段,那么它们还要被回写,那数据写入就变成双向的。

    直接访问其它数据库

    这种方式是要绝对禁止的。生产环境中的许多程序错误和性能问题都是由这种方式产生的。上面的三种方式由于是另外新建了本地只读数据库表,产生了数据库的物理隔离,这样一个数据库的性能问题不会影响到另一个。另外,当主库中的表结构更改时,你可以暂时保持从库中的表不变,这样程序还可以运行。如果直接访问别人的库,主库一修改,别的微服务程序马上就会报错。请参阅ApplicationDatabase。

    向后兼容的数据库更新

    从上面的论述可以看出,数据库表结构的修改是一个影响范围很广的事情。在微服务架构中,共享的表在别的服务中也会有一个只读的拷贝。现在当你要更改表结构时,还需要考虑到对别的微服务的影响。当在单体(Monolithic)架构中,为了保证程序部署能够回滚,数据库的更新是向后兼容的。

    需要兼容性的另一个原因是支持蓝绿发布(Blue-Green Deployment)。在这种部署方式中,你同时拥有新旧版本的代码,由负载均衡来决定每一个请求指向那个版本。它们可以共享一个数据库(这就要求数据库是向后兼容的),也可以使用不同的数据。数据库的更新简单来讲有以下几种类型:

    增加表或字段:如果字段可取空值,这个操作是向后兼容的。如果是非空值就要插入一个缺省值。

    删除表或字段:可先暂时保留被删除表或字段,经过几个版本之后再删除。

    修改字段名:新增加一个字段,把数据从旧字段拷贝到新字段,用数据库触发器(或程序)同步旧字段和新字段(供过渡时期使用)。然后再在几个版本之后把原来的字段删除(请参阅Update your Database Schema Without Downtime)。

    修改表名:如果数据库支持可更新视图,最简单的办法是先修改表的名字,然后创建一个可更新视图指向原来的表(请参阅Evolutionary Database Design )。如果数据库不支持可更新视图,使用的方法与修改字段名相似,需要创建新的表并做数据同步。

    修改字段类型:与修改字段名几乎相同,只是在拷贝数据时,需要做数据类型转换。

    向后兼容的数据库更新的好处是,当程序部署出现问题时,如需进行回滚。只要回滚程序就行了,而不必回滚数据库。回滚时一般只回滚一个版本。凡是需要删除的表或字段在本次部署时都不做修改,等到一个或几个版本之后,确认没有问题了再删除。它的另一个好处就是不会对其他微服务中的共享表产生立刻的直接影响。当本微服务升级后,其他微服务可以评估这些数据库更新带来的影响再决定是否需要做相应的程序或数据库修改。

    跨服务事物

    微服务的一个难点是如何实现跨服务的事物支持。两阶段提交(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),也就是说它可以执行多次,但最终结果还是一样的。

    另外,有些操作的补偿操作比较容易生成,例如付款操作,你只要把钱款退回就可以了。但有些操作,像发邮件,完成之后就没有办法回到之前的状态了,这时就只能再发一个邮件更正以前的信息。因此补偿操作不一定非要返回到原来的状态,而是抵消掉原来操作产生的效果。

    往期:100期面试题汇总

    微服务的拆分

    我们原来的程序大多数都是单体程序,但现在要把它拆分成微服务,应该怎样做才能降低对现有应用的影响呢?

    在这里插入图片描述

    我们用上面的图来做例子。它共有两个程序,一个是“Styling app”,另一个是“Warehouse app”,它们共享图中下面的数据库,库里有三张表,“core client”,“core sku”,“core item”。

    在这里插入图片描述

    假设我们要拆分出来一个微服务叫“client-service”,它需要访问“core client”表。第一步,我们先把程序从原来的代码里拆分出来,变成一个服务. 数据库不动,这个服务仍然指向原来的数据库。其他程序不再直接访问这个服务管理的表,而是通过服务调用或另建共享表来获取数据。

    在这里插入图片描述

    第二步,再把服务的数据库表拆分出来,这时微服务就拥有它自己的数据库了,而不再需要原来的共享数据库了。这时就成了一个真正意义上的的微服务。

    上面只讲了拆分一个微服务,如果有多个需要拆分,则需一个一个按照上面讲的方法依次进行。

    另外,Martin Fowler在他的文章"Break Monolith into Microservices"里有一个很好的建议。那就是,当你把服务从单体程序里拆分时,不要只想着把代码拆分出来。因为现在的需求可能已经跟原来有所不同,原先的设计可能也不太适用了。而且,技术也已更新,代码也要作相应的改造。更好的办法是重写原来的功能(而不是重写原来的代码),把重点放在拆分业务功能上,而不是拆分代码上,用新的设计和技术来实现这个业务功能。
    高质量编程视频:shangyepingtai.xin
    结论

    数据库设计是微服务设计的一个关键点,基本原则是每个微服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。微服务之间的数据共享可以通过服务调用,或者主、从表的方式实现。在共享数据时,要找到合适的同步方式。在微服务架构中,数据库的修改影响广泛,需要保证这种修改是向后兼容的。实现跨服务事物的标准方法是Saga。当把单体程序拆分成微服务时,可以分步进行,以减少对现有程序的影响。

    展开全文
  • 一 什么是微服务微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有...

       一   什么是微服务?

           微服务是一种架构风格,一个大型复杂软件应用由一个或多个微服务组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。

         可以在“自己的程序”中运行,并通过“轻量级设备与HTTP型API进行沟通”。关键在于该服务可以在自己的程序中运行。通过这一点我们就可以将服务公开与微服务架构(在现有系统中分布一个API)区分开来。在服务公开中,许多服务都可以被内部独立进程所限制。如果其中任何一个服务需要增加某种功能,那么就必须缩小进程范围。在微服务架构中,只需要在特定的某种服务中增加所需功能,而不影响整体进程。

        二  数据库为什么要分库分表?

        

                  当一张表的数据达到几千万时,查询一次所花的时间会变长。这时候,如果有联合查询的话,可能会卡死在那儿,甚至把系统给拖垮。

           而分库分表的目的就在于此:减小数据库的负担,提高数据库的效率,缩短查询时间。另外,因为分库分表这种改造是可控的,底层还是基于RDBMS,因此整个数据库的运维体系以及相关基础设施都是可重用的。

          目前我们系统将近20亿数据,每张表最大的接近600w条/表,每条数据大约3k,每个表将近1.5G的数据。查询经常超时,单条SQL执行count(*)查询时间达到了最大260ms,0.26s(标准是超过0.1s的数据为慢SQL)。

          数据库中的数据量不一定是可控的,在未进行分库分表的情况下,随着时间和业务的发展,库中的表会越来越多,表中的数据量也会越来越大,相应地,数据操作,增删改查的开销也会越来越大;另外,由于无法进行分布式式部署,而一台服务器的资源(如:CPU、磁盘、内存、IO等)是有限的,最终数据库所能承载的数据量、数据处理能力都将遭遇瓶颈。此时就要采取分库分表的方式。

    三 分库分表的实施策略。

        分库分表有垂直切分和水平切分两种。
        1 何谓垂直切分,即将表按照功能模块、关系密切程度划分出来,部署到不同的库上。例如,我们会建立定义数据库workDB、商品数据库payDB、用户数据库userDB、日志数据库logDB等,分别用于存储项目数据定义表、商品定义表、用户数据表、日志数据表等。
      2  何谓水平切分,当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如userID散列,进行划分,然后存储到多个结构相同的表,和不同的库上。例如,我们的userDB中的用户数据表中,每一个表的数据量都很大,就可以把userDB切分为结构相同的多个userDB:part0DB、part1DB等,再将userDB上的用户数据表userTable,切分为很多userTable:userTable0、userTable1等,然后将这些表按照一定的规则存储到多个userDB上。
      3 应该使用哪一种方式来实施数据库分库分表,这要看数据库中数据量的瓶颈所在,并综合项目的业务类型进行考虑。
    如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清晰、低耦合,那么规则简单明了、容易实施的垂直切分必是首选。
          而如果数据库中的表并不多,但单表的数据量很大、或数据热度很高,这种情况之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估,考虑数据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。
           在现实项目中,往往是这两种情况兼而有之,这就需要做出权衡,甚至既需要垂直切分,又需要水平切分。

    四 分库分表存在的问题。

        1 事务问题。
          在执行分库分表之后,由于数据存储到了不同的库上,数据库事务管理出现了困难。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价;如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
    2 跨库跨表的join问题。
    在执行了分库分表之后,难以避免会将原本逻辑关联性很强的数据划分到不同的表、不同的库上,这时,表的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表,结果原本一次查询能够完成的业务,可能需要多次查询才能完成。
    3 额外的数据管理负担和数据运算压力。
    额外的数据管理负担,最显而易见的就是数据的定位问题和数据的增删改查的重复执行问题,这些都可以通过应用程序解决,但必然引起额外的逻辑运算,例如,对于一个记录用户成绩的用户数据表userTable,业务要求查出成绩最好的100位,在进行分表之前,只需一个order by语句就可以搞定,但是在进行分表之后,将需要n个order by语句,分别查出每一个分表的前100名用户数据,然后再对这些数据进行合并计算,才能得出结果。

    展开全文
  • 微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。 优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口...

    单独的数据库

    微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。

    • 优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口(RPC或消息),没有其他接口。如果微服务不能独享自己的数据库,那么数据库也变成了接口的一部分,这大大拓展了接口范围。

    • 错误诊断:生产环境中的错误大部分都是和数据库有关的,要么是数据出了问题,要么是数据库的使用方式出了问题。当你不能完全控制数据库的访问时,会有各种各样的错误发生。它可能是别的程序直接连到你的数据库或者是其他部门直接用客户端访问数据库的数据,而这些都是在程序中查不到的,增加了错误排查难度。如果是程序中的问题,只要修改了代码,那么这个错误就不会再有。而上面提到的错误,你永远都没法预测它们什么时候还会再次发生。

    • 性能调优:性能调优也是一样,你需要对数据库有全权控制才能保证它的性能。如果其他部门一定要访问数据库,而且只是查询的话,那么可以另外创建一份只读数据库,让他们在另一个库中查询,这样才不会影响到你的库。

    理想的设计是你的数据库只有你的服务能访问,你也只调用自己数据库中的数据,所有对别的微服务的访问都通过服务调用来实现。当然,在实际应用中,单纯的服务调用可能不能满足性能或其他要求,不同的微服务都多少需要共享一些数据。

    共享数据

    微服务之间的数据共享可以有下四种方式。

    静态表

    有一些静态的数据库表,例如国家,可能会被很多程序用到,而且程序内部需要对国家这个表做连接(join)生成最终用户展示数据,这样用微服务调用的方式就效率不高,影响性能。一个办法是在每个微服务中配置一个这样的表,它是只读的,这样就可以做数据库连接了。当然你需要保证数据同步。这个方案在多数情况下都是可以接受的,因为以下两点:

    • 静态的数据库表结构基本不变:因为一旦表结构变了,你不但要更改所有微服务的数据库表,还要修改所有微服务的程序。

    • 数据库表中的数据变化不频繁:这样数据同步的工作量不大。另外当你同步数据库时总会有延迟,如果数据变化不频繁那么你有很多同步方式可供选择。

    更多面试题,欢迎关注公众号 Java面试题精选

    只读业务数据访问

    如果你需要读取别的数据库里的动态业务数据, 理想的方式是服务调用。如果你只是调用其他微服务做一些计算,一般情况下性能都是可以接受的。如果你需要做数据的连接,那么你可以用程序代码来做,而不是用SQL语句。如果测试之后性能不能满足要求,那你可以考虑在自己的数据库里建一套只读数据表。数据同步方式大致有两种。如果是事件驱动方式,就用发消息的方式进行同步,如果是RPC方式,就用数据库本身提供的同步方式或者第三方同步软件。

    通常情况下,你可能只需要其他数据库的几张表,每张表只需要几个字段。这时,其他数据库是数据的最终来源,控制所有写操作以及相应的业务验证逻辑,我们叫它主表。你的只读库可以叫从表。当一条数据写入主表后,会发一条广播消息,所有拥有从表的微服务监听消息并更新只读表中的数据。但这时你要特别小心,因为它的危险性要比静态表大得多。第一它的表结构变更会更频繁,而且它的变更完全不受你控制。第二业务数据不像静态表,它是经常更新的,这样对数据同步的要求就比较高。要根据具体的业务需求来决定多大的延迟是可以接受的。

    另外它还有两个问题:

    • 数据的容量:数据库中的数据量是影响性能的主要因素。因为这个数据是外来的,不利于掌握它的流量规律,很难进行容量规划,也不能更好地进行性能调优。

    • 接口外泄:微服务之间的接口本来只有服务调用接口,这时你可以对内部程序和数据库做任何更改,而不影响其他服务。现在数据库表结构也变成了接口的一部分。接口一旦发布之后,基本是不能更改的,这大大限制了你的灵活性。幸运的是因为另外建了一套表,有了一个缓冲,当主表修改时,从表也许不需要同步更新。

    除非你能用服务调用(没有本地只读数据库)的方式完成所有功能,不然不管你是用RPC方式还是事件驱动方式进行微服务集成,上面提到的问题都是不可避免的。但是你可以通过合理规划数据库更改,来减少上面问题带来的影响,下面将会详细讲解。

    往期:100期面试题汇总

    读写业务数据访问

    这是最复杂的一种情况。一般情况下,你有一个表是主表,而其他表是从表。主表包含主要信息,而且这些主要信息被复制到从表,但微服务会有额外字段需要写入从表。这样本地微服务对从表就既有读也有写的操作。而且主表和从表有一个先后次序的关系。从表的主键来源于主表,因此一定先有主表,再有从表。

     

    上图是例子。假设我们有两个与电影有关的微服务,一个是电影论坛,用户可以发表对电影的评论。另一个是电影商店。“movie”是共享表,左边的一个是电影论坛库,它的“movie”表是主表。右边的是电影商店库,它的“movie”表是从表。它们共享“id”字段(主键)。

    主表是数据的主要来源,但从表里的“quantity”和“price”字段主表里面没有。主表插入数据后,发消息,从表接到消息,插入一条数据到本地“movie”表。并且从表还会修改表里的“quantity”和“price”字段。在这种情况下,要给每一个字段分配一个唯一源头(微服务),只有源头才有权利主动更改字段,其他微服务只能被动更改(接收源头发出的更改消息之后再改)。

    在本例子中, “quantity”和“price”字段的源头是右边的表,其他的字段的源头都是左边的表。本例子中“quantity”和“price”只在从表中存在,因此数据写入是单向的,方向是主表到从表。如果主表也需要这些字段,那么它们还要被回写,那数据写入就变成双向的。

    往期:100期面试题汇总

    直接访问其它数据库

    这种方式是要绝对禁止的。生产环境中的许多程序错误和性能问题都是由这种方式产生的。上面的三种方式由于是另外新建了本地只读数据库表,产生了数据库的物理隔离,这样一个数据库的性能问题不会影响到另一个。另外,当主库中的表结构更改时,你可以暂时保持从库中的表不变,这样程序还可以运行。如果直接访问别人的库,主库一修改,别的微服务程序马上就会报错。请参阅ApplicationDatabase。

    向后兼容的数据库更新

    从上面的论述可以看出,数据库表结构的修改是一个影响范围很广的事情。在微服务架构中,共享的表在别的服务中也会有一个只读的拷贝。现在当你要更改表结构时,还需要考虑到对别的微服务的影响。当在单体(Monolithic)架构中,为了保证程序部署能够回滚,数据库的更新是向后兼容的。

    需要兼容性的另一个原因是支持蓝绿发布(Blue-Green Deployment)。在这种部署方式中,你同时拥有新旧版本的代码,由负载均衡来决定每一个请求指向那个版本。它们可以共享一个数据库(这就要求数据库是向后兼容的),也可以使用不同的数据。数据库的更新简单来讲有以下几种类型:

    • 增加表或字段:如果字段可取空值,这个操作是向后兼容的。如果是非空值就要插入一个缺省值。

    • 删除表或字段:可先暂时保留被删除表或字段,经过几个版本之后再删除。

    • 修改字段名:新增加一个字段,把数据从旧字段拷贝到新字段,用数据库触发器(或程序)同步旧字段和新字段(供过渡时期使用)。然后再在几个版本之后把原来的字段删除(请参阅Update your Database Schema Without Downtime)。

    • 修改表名:如果数据库支持可更新视图,最简单的办法是先修改表的名字,然后创建一个可更新视图指向原来的表(请参阅Evolutionary Database Design )。如果数据库不支持可更新视图,使用的方法与修改字段名相似,需要创建新的表并做数据同步。

    • 修改字段类型:与修改字段名几乎相同,只是在拷贝数据时,需要做数据类型转换。

    向后兼容的数据库更新的好处是,当程序部署出现问题时,如需进行回滚。只要回滚程序就行了,而不必回滚数据库。回滚时一般只回滚一个版本。凡是需要删除的表或字段在本次部署时都不做修改,等到一个或几个版本之后,确认没有问题了再删除。它的另一个好处就是不会对其他微服务中的共享表产生立刻的直接影响。当本微服务升级后,其他微服务可以评估这些数据库更新带来的影响再决定是否需要做相应的程序或数据库修改。

    跨服务事物

    微服务的一个难点是如何实现跨服务的事物支持。两阶段提交(Two-Phase Commit)已被证明性能上不能满足需求,现在基本上没有人用。被一致认可的方法叫Saga。

    它的原理是为事物中的每个操作写一个补偿操作(Compensating Transaction),然后在回滚阶段挨个执行每一个补偿操作。示例如下图,在一个事物中共有3个操作T1,T2,T3。每一个操作要定义一个补偿操作,C1,C2,C3。事物执行时是按照正向顺序先执行T1,当回滚时是按照反向顺序先执行C3。

    事物中的每一个操作(正向操作和补偿操作)都被包装成一个命令(Command),Saga执行协调器(Saga Execution Coordinator (SEC))负责执行所有命令。在执行之前,所有的命令都会按顺序被存入日志中,然后Saga执行协调器从日志中取出命令,依次执行。当某个执行出现错误时,这个错误也被写入日志,并且所有正在执行的命令被停止,开始回滚操作。

    【124期】面试官:谈谈微服务的数据库设计思路吧

    Saga放松了对一致性(Consistency)的要求,它能保证的是最终一致性(Eventual Consistency),因此在事物执行过程中数据是不一致的,并且这种不一致会被别的进程看到。在生活中,大多数情况下,我们对一致性的要求并没有那么高,短暂的不一致性是可以接收的。例如银行的转账操作,它们在执行过程中都不是在一个数据库事物里执行的,而是用记账的方式分成两个动作来执行,保证的也是最终一致性。

    Saga的原理看起来很简单,但要想正确的实施还是有一定难度的。它的核心问题在于对错误的处理,要把它完全讲明白需要另写一遍文章,我现在只讲一下要点。网络环境是不可靠的,正在执行的命令可能很长时间都没有返回结果,这时,第一,你要设定一个超时。第二,因为你不知道没有返回值的原因是,已经完成了命令但网络出了问题,还是没完成就牺牲了,因此不知道是否要执行补偿操作。这时正确的做法是重试原命令,直到得到完成确认,然后再执行补偿操作。但这对命令有一个要求,那就是这个操作必须是幂等的(Idempotent),也就是说它可以执行多次,但最终结果还是一样的。

    另外,有些操作的补偿操作比较容易生成,例如付款操作,你只要把钱款退回就可以了。但有些操作,像发邮件,完成之后就没有办法回到之前的状态了,这时就只能再发一个邮件更正以前的信息。因此补偿操作不一定非要返回到原来的状态,而是抵消掉原来操作产生的效果。

    往期:100期面试题汇总

    微服务的拆分

    我们原来的程序大多数都是单体程序,但现在要把它拆分成微服务,应该怎样做才能降低对现有应用的影响呢?

    【124期】面试官:谈谈微服务的数据库设计思路吧

    我们用上面的图来做例子。它共有两个程序,一个是“Styling app”,另一个是“Warehouse app”,它们共享图中下面的数据库,库里有三张表,“core client”,“core sku”,“core item”。

     

    假设我们要拆分出来一个微服务叫“client-service”,它需要访问“core client”表。第一步,我们先把程序从原来的代码里拆分出来,变成一个服务. 数据库不动,这个服务仍然指向原来的数据库。其他程序不再直接访问这个服务管理的表,而是通过服务调用或另建共享表来获取数据。

    【124期】面试官:谈谈微服务的数据库设计思路吧

    第二步,再把服务的数据库表拆分出来,这时微服务就拥有它自己的数据库了,而不再需要原来的共享数据库了。这时就成了一个真正意义上的的微服务。

    上面只讲了拆分一个微服务,如果有多个需要拆分,则需一个一个按照上面讲的方法依次进行。

    另外,Martin Fowler在他的文章"Break Monolith into Microservices"里有一个很好的建议。那就是,当你把服务从单体程序里拆分时,不要只想着把代码拆分出来。因为现在的需求可能已经跟原来有所不同,原先的设计可能也不太适用了。而且,技术也已更新,代码也要作相应的改造。更好的办法是重写原来的功能(而不是重写原来的代码),把重点放在拆分业务功能上,而不是拆分代码上,用新的设计和技术来实现这个业务功能。

    结论

    数据库设计是微服务设计的一个关键点,基本原则是每个微服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。微服务之间的数据共享可以通过服务调用,或者主、从表的方式实现。在共享数据时,要找到合适的同步方式。在微服务架构中,数据库的修改影响广泛,需要保证这种修改是向后兼容的。实现跨服务事物的标准方法是Saga。当把单体程序拆分成微服务时,可以分步进行,以减少对现有程序的影响。

    展开全文
  • 虽然已经红了很久,但是“微服务架构”正变得越来越重要,也将继续火下去。各个公司与技术人员都在分享微服务架构的相关知识与实践经验,但我们发现,目前网上的这些相关文章中,要么上来就是很有借鉴意义的干货,...
  • 如果其他部门一定要访问数据库,而且只是查询的话,那么可以另外创建一份只读数据库,让他们在另一个库中查询,这样才不会影响到你的库。 理想的设计是你的数据库只有你的服务能访问,你也只调用自己数据库中的数据...
  • 在分布式架构中,往往会经常遇到跨数据库查询的场景,即两个有关联的表不在一个数据库中。 一般的解决方案有: 分别查询两张表的数据,然后进行组装 (弊端:麻烦,复杂查询条件无法实现分页查询 优点:实时保证...
  • 加关注,不迷路!前言在服务做微服务改造后,原先单库join查询已经不能满足要求,每个拆分的微服务对应一个数据库实例,而且部署在不同的服务器上,那么解决“查询”就势在必行了。以笔者实战...
  • 微服务设计的一个关键是数据库设计,基本原则是每个服务都有自己单独的数据库,而且只有微服务本身可以访问这个数据库。它是基于下面三个原因。 优化服务接口:微服务之间的接口越小越好,最好只有服务调用接口...
  • 微服务跨服务事务的实现

    千次阅读 2016-12-16 15:32:13
    首先,实现主要业务的服务,必须...这样带来一个问题,保存到数据库和向消息队列发布消息无法做到事务一致性。比如你数据库操作完成了,MQ连接不上怎么办?我们可以在操作数据库的事务中,增加一个插入事件数据的任务
  • 传统单体应用典型的架构是几点个应用程序服务器, 连接后台的 Oracle 或 MySQL 服务器, 彼此之间也可以使用数据库来共享数据. 随着业务的增长, 后台数据库会变得越来越庞大, 不得已进行分表分库, 不同的租户或应用...
  • ![图片说明](https://img-ask.csdn.net/upload/201905/29/1559100889_22438.png) consumer调用provider并返回B ps:灵魂画手
  • 日前,某电商用户由于业务发展迅猛,访问量极速增长,导致数据库容量及性能遭遇瓶颈。为降低数据库大小,提升性能,用户决定对架构进行垂直拆分。根据不同的表来进行拆分,对应用程序的影响也更小,拆分规则也会比较...
  • 在软件开发的初期,所有微服务的开发只需要进行一次数据库的开发,大幅提高开发速度。单一数据库的开发、维护都易于操作。 1.2、缺点: 开发时间耦合——例如,一个负责订单服务的开发者需要和其他服务的开发者...
  • 微服务中的查询

    2021-03-02 19:45:06
    需要查询的数据分散在各个微服务中 方案: API组合模式 / The API composition pattern:比较简单的模式,推荐使用。客户端分别调用各个服务,组织结果。 命令查询职责分离模式 / The Command query responsibility ...
  • 通过aop 解决微服务服连表查询

    万次阅读 热门讨论 2019-01-18 16:35:10
    又需要这些服务一起提供数据,这时可能就需要服务进行关联查询。 具体例子: 把数据库层划分为: 基础数据服 订单数据服 现有一个具体业务: 查询订单信息 分析: 订单主表信息是在订单数据...
  • 微服务项目学习的第二天,做了数据的查询与缓存(redis和springcache)。 1、数据库操作的三种方式 使用jpa自带的方法在服务层直接调用; 在dao(数据库操作)层自定义方法,然后在服务层调用;(由Jpa自己生成sql...
  • MongoDB数据库和吐槽微服务MongoDB数据库和吐槽微服务1 MongoDB的特点和体系结构2 MongoDB命令3 运用Java操作...MongoDB:一个平台的,面向文档的数据库,nosql数据库。支持数据结构松散,是类似于JSON的BSON格...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 25,518
精华内容 10,207
关键字:

微服务跨数据库查询