
这篇文章
不会谈论不负责任的员工,正如文章标题可能暗示的那样。 如果您创建分布式系统,我们将讨论可能会遇到的一种实际技术危险。
在一个企业系统中,有一个组件。 该组件从用户那里收集有关某种产品的数据,并将其记录在数据库中。 它由三个标准部分组成:用户界面,服务器上的业务逻辑和数据库中的表。
该组件运行良好,并且几年来没有人碰过它的代码。
但是有一次,无故,组件开始发生奇怪的事情。
与某些用户一起工作时,会话中间的组件突然开始引发错误。 它很少发生,但是像往常一样,在最不适当的时刻发生。 最不可理解的是,最初的错误出现在生产中的系统稳定版本中。 在几个月的版本中,根本没有更改任何组件。
我们开始分析情况,检查了重载下的组件。 它运作良好。 重复了相当广泛的集成测试。 在集成测试中,我们的组件运行良好。
一言以蔽之,错误何时何地却不清楚。
他们开始更深入地挖掘。 对日志文件的详细分析和比较表明,向用户显示的错误消息的原因是数据库中已提到的表中的主键中的约束违反。
该组件使用Hibernate将数据写入表中,有时Hibernate在尝试写入下一行时会报告约束冲突。
我不会向读者介绍更多的技术细节,而会立即告诉您错误的实质。 事实证明,不仅我们的组件写入了上表,而且有时(极其罕见)写入了其他一些组件。 她使用简单的SQL INSERT语句非常简单地完成了此操作。 编写以下内容时,默认情况下,Hibernate起作用。 为了优化写入过程,它会在索引中查询下一个主键一次,然后仅通过增加键值来写入几次(默认为10次)。 而且,如果在请求之后发生第二个组件卡在进程中,并使用以下主键值将数据写入表的情况,则随后尝试从Hibernate进行写入会导致约束冲突。
如果您对技术细节感兴趣,请在下面查看。
技术细节。
类代码开始如下:
@Entity @Table(name="PRODUCT_XXX") public class ProductXXX { @Id @Basic(optional=false) @Column( name="PROD_ID", columnDefinition="integer not null", insertable=true, updatable=false) @SequenceGenerator( name="GEN_PROD_ID", sequenceName="SEQ_PROD_ID", allocationSize=10) @GeneratedValue( strategy=GenerationType.SEQUENCE, generator="GEN_PROD_ID") private long prodId;
关于Stackoverflow的类似问题的讨论:
https://stackoverflow.com/questions/12745751/hibernate-sequencegenerator-and-allocationsize 碰巧的是,在更改第二个组件并在其中实现表中的条目之后的许多月中,编写第一个和第二个组件的过程从未在时间上重叠。 当在使用该系统的一个单元中,工作计划略有变化时,他们开始相交。
好吧,集成测试进行得很顺利,因为在集成测试中测试两个组件的时间间隔都不相交。
从某种意义上讲,我们可以说没有人为错误负责。
还是不是?
观察与思想
发现错误的真正原因后,已对其进行了纠正。
但是,我并不想以这个幸福的结局作为结尾,而是想回顾一下此错误,作为代表从单片系统到分布式系统过渡后越来越流行的大量错误的代表。
从所描述的企业系统中各个组件或服务的角度来看,一切都已做好,一切似乎都是正确的。 所有组件或服务都具有独立的生命周期。 并且,由于操作的重要性,当需要在第二个组件中写入表格时,务实地决定以最简单的方式直接在该组件中实现此功能,而不要碰到稳定工作的第一个组件。
但是,可惜的是,在分布式系统中经常发生的事情(而在单片系统中相对较少):在子系统之间
分散了对特定对象执行操作的责任。 当然,如果两个写操作都在同一微服务中实现,则将选择一种技术来实现它们。 然后,将不会发生所描述的错误。
分布式系统,尤其是微服务的概念,已经有效地帮助解决了单片系统固有的许多问题。 但是,自相矛盾的是,对单个服务的责任分离引发了相反的效果。 现在,组件尽可能“独立”运行。 不可避免地会有一种诱惑,那就是对一个组件进行大的更改,以“在这里拧紧”一些可以在另一个组件中更好地实现的功能。 这样可以快速达到最终效果,减少批准和测试的数量。 因此,从变化到变化,这些组件会以其不寻常的特征过度增长,相同的内部算法和功能被复制,问题解决(有时甚至是不确定性)的多样性出现。 换句话说,分布式系统会随着时间的推移而降级,但与整体系统不同。
对包含许多服务的大型系统中组件的“拖延”责任是现代分布式系统中典型且痛苦的问题之一。 共享优化子系统(例如缓存,后续操作的预测(预测)以及服务编排等)使情况变得更加复杂和混乱。
至少在单个库的级别上集中访问数据库的要求非常明显。 但是,许多现代分布式系统历来都是围绕数据库发展的,并且直接(通过SQL)而不是通过访问服务来使用存储在数据库中的数据。
“帮助”责任的传播以及诸如Hibernate之类的ORM框架和库。 使用它们,许多数据库访问服务的开发人员无意中都希望根据请求提供尽可能多的对象。 一个典型的示例是对用户数据的请求,以便将其显示在问候语中或带有验证结果的字段中。 这样的请求不是返回三个文本变量(first_name,mid_name,last_name)形式的用户名,而是返回具有许多属性和连接对象的成熟用户对象,例如所请求用户的角色列表。 反过来,这会使处理请求结果的逻辑变得复杂,在返回的对象的类型上生成处理程序的不必要依赖关系,并且...由于可能从负责此服务对象的外部实现与对象相关联的逻辑,招致了责任的拖尾。
怎么办 (建议)
遗憾的是,在某些情况下,抹黑责任有时是强制性的,有时甚至是不可避免和合理的。
但是,如果可能,您应该尝试遵守组件之间责任分配的原则。 一个组成部分就是一项责任。
好吧,如果不可能将操作严格地集中在一个系统中的某些对象上,则必须非常仔细地在系统范围的文档中记录这种拖尾现象(``超级组件''),作为组件对数据元素,域对象或彼此之间的特定依赖关系。
了解您对此事的观点以及实践中证实或反驳本文论点的案例,将是很有趣的。
感谢您阅读本文的结尾。
本文作者的插图“ Multimedia Mikher”。