当前位置:网络安全 > 【第124期】面试官:我们来谈谈微服务的数据库设计思想

【第124期】面试官:我们来谈谈微服务的数据库设计思想

  • 发布:2023-10-05 23:47

May 17, 2022 3:51 pm • 面试问题 • 读 0 点击上方“Java面试题精选”,关注公众号 面试时画图,查漏补缺 >>番外:往期面试题,10个为单位放在这个公众号菜单栏->面试题,有需要的欢迎阅读 阶段总结合集:++小旗实现、百道面试题总结++ 单独的数据库 微服务设计的关键是数据库设计。基本原理是每个服务都有自己独立的数据库,只有微服务本身才能访问这个数据库。其基于以下三个原因。 优化服务接口:微服务之间的接口越小越好。最好只有服务调用接口(RPC或消息),没有其他接口。如果微服务不能独占自己的数据库,那么数据库就成为接口的一部分,这就大大扩展了接口的范围。 错误诊断:生产环境中的大多数错误都与数据库有关。要么是数据有问题,要么是数据库的使用方式有问题。当您无法完全控制数据库访问时,可能会发生各种错误。可能是其他程序直接连接到你的数据库或者其他部门直接使用客户端访问数据库数据,而这些在程序中是找不到的,导致错误排查变得更加困难。如果是程序的问题,只要修改代码,就不会再出现错误了。至于上面提到的错误,你永远无法预测它们何时会再次发生。 性能调优:性能调优也是如此。您需要完全控制数据库以确保其性能。如果其他部门必须访问该数据库并且只能查询,您可以再创建一个只读数据库,让他们在另一个图书馆查询,以免影响您的图书馆。 理想的设计是你的数据库只能被你的服务访问,并且你只调用自己数据库中的数据。所有对其他微服务的访问都是通过服务调用来实现的。当然,在实际应用中,简单的服务调用可能无法满足性能或其他要求,不同的微服务需要共享一些数据。 共享数据 微服务之间的数据共享可以通过以下四种方式完成。 静态表 有一些静态数据库表,比如国家,可能会被很多程序使用,需要在程序内部连接国家表来生成最终用户的显示数据。这样调用微服务的方法效率不高,影响性能。一种方法是在每个微服务中配置这样一个表,该表是只读的,这样就可以进行数据库连接。当然,您需要保持数据同步。这种解决方案在大多数情况下是可以接受的,因为以下两点: 静态数据库表结构基本保持不变:一旦表结构发生变化,你不仅要更改所有微服务的数据库表,还要修改所有微服务的程序。 数据库表中的数据不经常变化:因此数据同步的工作量不大。此外,同步数据库时总会有延迟,如果数据不经常更改,那么您有很多同步选项可供选择。 更多面试题欢迎关注公众号java面试题精选 只读业务数据访问 如果需要读取其他数据库中的动态业务数据,理想的方式是使用服务调用。如果只是调用其他微服务做一些计算,性能一般可以接受。如果需要连接数据,可以用程序代码而不是SQL语句来完成。如果测试后性能不能满足要求,可以考虑在自己的数据库中构建一组只读数据表。数据同步的方法大致有两种。如果是事件驱动的话,通过发送消息来同步。如果是RPC,则使用数据库本身提供的同步方法或者第三方同步软件。 通常,您可能只需要其他数据库中的几个表,每个表只有几个字段。此时其他数据库才是最终的数据来源,控制着所有的写操作以及相应的业务验证逻辑。我们称之为主表。你的只读库可以称为从表。当一条数据写入主表时,会发送一条广播消息,所有拥有从表的微服务都会监听该消息并更新只读表中的数据。但是这个时候你要特别小心,因为它比静态表危险得多。首先,它的表结构会更频繁地变化,而且它的变化完全不受你的控制。其次,业务数据不像静态表。更新频繁,所以对数据同步的要求比较高。确定可接受的延迟程度取决于特定的业务需求。 此外,它还有两个问题: 数据容量:数据库中的数据量是影响性能的主要因素。由于这些数据是外部的,不利于掌握其流量模式,难以进行容量规划,也无法进行更好的性能调优。 接口泄漏:微服务之间的接口原本只有服务调用接口。此时,您可以对内部程序和数据库进行任何更改,而不会影响其他服务。现在数据库表结构也成为了界面的一部分。接口一旦发布就无法更改,这极大地限制了您的灵活性。幸运的是,因为已经建了另一组表,所以有缓冲区。当主表修改时,从表可能不需要同步更新。 除非你能通过服务调用来完成所有功能(无需本地只读数据库),否则无论你使用RPC还是事件驱动的方式进行微服务集成,上述问题都是不可避免的。但是,您可以通过适当规划数据库更改来减少上述问题的影响,这将在下面详细解释。 上期:100道面试题汇总 读写业务数据访问 这是最复杂的情​​况。通常,您有一个表为主表,其他表为从表。主表包含主要信息,这些主要信息被复制到从表,但是微服务会有额外的字段需要写入从表。这样本地微服务就可以对从表进行读和写操作。而且主表和从表是有顺序关系的。从表的主键来自于主表,所以必须先有主表,后有从表。 上图是一个例子。假设我们有两个与电影相关的微服务。其中之一是电影论坛,用户可以在其中发表有关电影的评论。另一个是电影店。 “电影”是共享表,左边那个是电影论坛库,它的“电影”表是主表。右边的是电影商店库,它的“电影”表就是从表。他们共享“id”字段(主键)。 主表是主要数据来源,但从表中的“数量”和“价格”字段不在主表中。向主表插入数据后,发送消息,从表接收消息,向本地“电影”表插入一条数据。并且表中的“数量”和“价格”字段也会从表中修改。在这种情况下,必须为每个字段分配一个唯一的源(微服务)。只有源端才有权主动更改该字段,其他微服务只能被动更改(收到源端的更改消息后更改)。 在本例中,“数量”和“价格”字段的来源是右表,其他字段的来源是左表。在本例中,“数量”和“价格”仅存在于从表中,因此数据写入是单向的,从主表到从表。如果主表也需要这些字段,那么就必须将它们写回,并且数据写入将变成双向的。 上期:100道面试题汇总 直接访问其他数据库 这种方法是绝对禁止的。生产环境中的许多错误和性能问题都是由这种方法引起的。以上三种方法都会创建一个新的本地只读数据库表,造成数据库的物理隔离,使得一个数据库的性能问题不会影响到另一个数据库。另外,当主库中的表结构发生变化时,可以暂时保持从库中的表不变,以便程序仍然可以运行。如果直接访问别人的库,一旦主库被修改,其他微服务程序就会立即报错。请参阅应用程序数据库。 向后兼容的数据库更新 从上面的讨论可以看出,数据库表结构的修改是一件影响广泛的事情。在微服务架构中,共享表也会在其他服务中拥有只读副本。现在当你想要改变表结构时,你还需要考虑对其他微服务的影响。在单体架构中,数据库更新向后兼容,以确保程序部署可以回滚。 兼容性的另一个原因是支持蓝绿部署。在此部署方法中,您同时拥有旧版本和新版本的代码,并且负载均衡器确定每个请求指向哪个版本。他们可以共享一个数据库(这要求数据库向后兼容),也可以使用不同的数据。简单来说,数据库更新有以下几种类型: 添加表或字段:如果字段可以采用空值,则此操作是向后兼容的。如果它是非空值,则将插入默认值。 删除表或字段:可以暂时保留已删除的表或字段,等几个版本后再删除。 修改字段名称:添加新字段,将旧字段的数据复制到新字段,使用数据库触发器(或程序)同步旧字段和新字段(过渡期间使用)。然后在几个版本后删除原始字段(请参阅在不停机的情况下更新数据库架构)。 修改表名:如果数据库支持可更新视图,最简单的方法是先修改表名,然后创建一个指向原表的可更新视图(参见进化数据库设计)。如果数据库不支持可更新视图,使用的方法与修改字段名类似。您需要新建一张表并进行数据同步。 修改字段类型:与修改字段名称几乎相同,只是复制数据时需要进行数据类型转换。 向后兼容的数据库更新的好处是,如果程序部署期间出现问题,可以回滚它们。只是回滚程序,而不是数据库。回滚时,一般只回滚一个版本。所有需要删除的表或字段在此部署期间不会被修改。等到一个或者几个版本之后确认没有问题再删除。另一个好处是它不会对其他微服务中的共享表产生立即的直接影响。当这个微服务升级时,其他微服务可以评估这些数据库更新的影响,并决定是否需要进行相应的程序或数据库修改。 跨服务的事情 微服务的一大难点是如何实现跨服务支持。 Two Phase Commit已经被证明无法满足性能要求,现在基本已经不用了。一致认可的方法称为Saga。 它的原理是为事务中的每个操作编写一个补偿事务(Compensating Transaction),然后在回滚阶段逐一执行每个补偿操作。示例如下所示。一个事务中有三个操作 T1、T2 和 T3。每个操作必须定义一个补偿操作C1、C2、C3。执行事物时,按正序先执行T1,回滚时,按逆序先执行C3。 Thing中的每一个操作(前向操作和补偿操作)都被封装成一个命令(Command),由Saga执行协调器(SEC)负责执行所有命令。在执行之前,所有命令都会按顺序存储在日志中,然后Saga执行协调器会从日志中检索命令并按顺序执行。当执行过程中出现错误时,该错误也会写入日志,并且停止所有正在执行的命令并开始回滚操作。 Saga放宽了对一致性(Consistency)的要求,它保证了最终一致性(Eventual Consistency),所以事物执行过程中数据不一致,这种不一致会被其他进程看到。生活中的大多数情况下,我们对一致性的要求并没有那么高,短期的不一致是可以接受的。例如,银行转账操作在执行过程中并不是在一次数据库事务中执行的。相反,它们使用会计方法分为两个动作,以确保最终的一致性。 Saga的原理看似简单,但要正确实现还是有一定难度的。其核心问题在于错误的处理。要完全理解它,你需要再写一篇文章。我现在只讲重点。网络环境不可靠,正在执行的命令可能长时间没有返回结果。在这种情况下,首先需要设置超时时间。其次,因为你不知道没有返回值的原因是命令已经完成但是网络出现了问题,或者还没完成就牺牲了,所以你不知道是否要执行补偿操作。此时正确的做法是重试原来的命令,直到获得完成确认,然后执行补偿操作。但对命令有一个要求,即操作必须是幂等的(Idempotity),也就是说可以执行多次,但最终的结果还是一样。 另外,有些操作的补偿操作是比较容易生成的,比如支付操作。您只需要退还钱即可。但对于某些操作,例如发送电子邮件,完成后无法返回到之前的状态。此时,您只能再发送一封电子邮件来更正之前的信息。因此,补偿操作并不一定会恢复到原来的状态,而是抵消了原来操作的效果。 上期:100道面试题汇总 拆分微服务 我们最初的大多数程序都是单体程序,但现在我们想将它们拆分为微服务。我们应该怎样做才能减少对现有应用程序的影响? 我们以上面的图片为例。它有两个程序,一个是“样式应用程序”,另一个是“仓库应用程序”。他们共享下图中的数据库。库中有三个表,“核心客户端”、“核心sku”和“核心项目”。 假设我们要拆分出一个名为“client-service”的微服务,它需要访问“core client”表。第一步是将程序从原始代码中拆分出来,将其变成服务。数据库没有移动,服务仍然指向原来的数据库。其他程序不再直接访问该服务管理的表,而是通过服务调用或创建新的共享表来获取数据。 第二步,拆分服务的数据库表。这时微服务就有了自己的数据库,不再需要原来的共享数据库。这时候就成了真正意义上的微服务了。 上面只讲了拆分一个微服务。如果有多个微服务需要拆分,则需要按照上面提到的方法一一进行。 此外,Martin Fowler 在他的文章“Break Monolith into Microservices”中提出了一个很好的建议。也就是说,当你将服务从单体程序中拆分出来时,不要只想着拆分代码。因为现在的需求可能和原来的不一样,原来的设计可能不适用。而且技术更新了,代码也需要相应修改。更好的做法是重写原来的功能(而不是重写原来的代码),专注于业务功能的拆分而不是代码的拆分,并使用新的设计和技术来实现这个业务功能。 综上所述 数据库设计是微服务设计的一个关键点。基本原理是每个微服务都有自己独立的数据库,只有微服务本身才能访问这个数据库。微服务之间的数据共享可以通过服务调用或者主从表来实现。共享数据时,找到正确的同步方法。在微服务架构中,数据库修改影响广泛,需要保证此类修改向后兼容。实现跨服务事物的标准方法是Saga。将单体程序拆分为微服务时,可以分步进行,以减少对现有程序的影响。 作者:倚天码农 www.sychzs.cn/code-craftsman/p/11702814.html 而不是在网上搜索问题?还不赶快关注我们吧~ 版权声明:本文内容由网友自愿贡献,本文所表达的观点仅代表作者自己的观点。本网站仅提供信息存储空间服务,不拥有任何所有权,也不承担相关法律责任。如果您发现本站有任何涉嫌侵权/非法内容,请发送邮件举报。一经核实,该网站将立即删除。本文由斑马博客整理。本文链接为:https://www.sychzs.cn/index.php/post/7073.html

相关文章