渡渡鸟停泊的那一天。 同步脚本

Dodo IS是一个全球系统,可帮助您有效地管理Dodo Pizza的业务。 它可以解决披萨订购问题,帮助加盟商跟踪业务,提高员工效率,有时甚至跌倒。 最后对我们来说是最糟糕的。 这种下降的每一分钟都会导致利润损失,用户不满和开发人员无法入睡的夜晚。

但是现在我们睡得更好了。 我们学会了识别系统性的启示场景并进行处理。 下面我将告诉您我们如何提供系统稳定性。

有关Dodo IS *系统崩溃的一系列文章
1. 渡渡鸟停泊的一天。 同步脚本。
2. Dodo IS停止的日期。 异步脚本。

* 资料是根据我在莫斯科DotNext 2018上的表现编写的

渡渡鸟是


该系统是我们特许经营的巨大竞争优势,因为特许经营者可以获得现成的商业模型。 它们是ERP,HRM和CRM,合而为一。

第一家比萨店开业几个月后,该系统就出现了。 经理,客户,收银员,厨师,神秘购物者,呼叫中心员工都可以使用它-仅此而已。 按照惯例,Dodo IS分为两个部分。 首先是针对客户。 这包括网站,移动应用程序,联系中心。 对于加盟商合作伙伴来说,这是第二个,它有助于管理比萨店。 通过该系统,来自供应商的发票,人员管理,轮班人员,自动工资核算,人员在线培训,管理人员认证,质量控制系统和神秘买家都可以通过该系统。

系统性能


系统性能Dodo IS =可靠性=容错/恢复。 让我们细谈每一个要点。

可靠度


我们没有大量的数学计算:我们需要服务一定数量的订单,有一定的交付区域。 客户数量没有特别的变化。 当然,当它增长时我们会很高兴,但是这种情况很少会突然发生。 对我们来说,性能归结为发生故障的几率和系统的可靠性。

容错能力


一个组件可能依赖于另一组件。 如果一个系统发生错误,则另一个子系统一定不能掉线。

弹性


每天都有个别组件发生故障。 这很正常。 重要的是我们要从故障中恢复的速度。

同步系统故障方案


这是什么


大企业的本能是同时服务许多客户。 正如送货上的披萨外卖厨房一样,不可能像在家中厨房的家庭主妇一样工作,为同步执行而开发的代码也无法成功地为服务器上的大量客户服务。

在单个实例中执行算法与在大众服务中执行与服务器相同的算法之间存在根本区别。

看看下面的图片。 在左侧,我们看到了两个服务之间如何发生请求。 这些是RPC调用。 下一个请求在上一个请求之后结束。 显然,这种方法无法扩展-排列其他订单。

要处理许多订单,我们需要正确的选择:



同步应用程序中阻塞代码的操作受所使用的多线程模型(即抢先式多任务)的影响很大。 仅此一项就可能导致失败。

简化的抢先式多任务处理可以说明如下:



彩色块是CPU的实际工作,并且我们看到图中的绿色表示的有用工作在一般背景下很小。 我们需要唤醒流,使其进入睡眠状态,这是开销。 这种睡眠/唤醒在任何同步原语上的同步期间发生。

显然,如果通过大量同步来稀释有用的工作,CPU性能将会降低。 抢先式多任务处理对性能有多大影响?

考虑综合测试的结果:



如果同步之间的流间隔约为1000纳秒,则即使线程数等于核心数,效率也非常低。 在这种情况下,效率约为25%。 如果线程数增加4倍,效率将急剧下降至0.5%。

考虑一下,您在云中订购了具有72个内核的虚拟机。 它花费金钱,而您只使用不到一个内核的一半。 这正是在多线程应用程序中可能发生的情况。

如果任务较少,但持续时间较长,则效率会提高。 我们看到,在每秒5,000次操作下,两种情况下的效率均为80-90%。 对于多处理器系统,这非常好。



在我们的实际应用中,一次同步之间的一次操作的持续时间介于两者之间,因此这个问题很紧急。

这是怎么回事?


注意压力测试的结果。 在这种情况下,这就是所谓的“挤出测试”。



该测试的本质在于,使用装载架,我们向系统提交了越来越多的人工请求,并尝试每分钟尽可能多地下订单。 我们试图找到限制,在该限制之后,应用程序将拒绝处理超出其功能的请求。 直观地,我们希望系统运行到极限,丢弃其他请求。 例如,这确实发生在现实生活中-在拥挤顾客的餐厅里服务。 但是还有其他事情发生。 客户下了更多订单,系统开始减少了服务。 该系统开始服务的订单太少,因此可以认为是完全故障,故障。 这在许多应用程序中都会发生,但是应该吗?

在第二张图中,处理请求的时间增加了,在此间隔内,较少的请求得到服务。 较早到达的请求要晚得多。



为什么应用程序停止? 有一个算法,它起作用。 我们从本地计算机启动它,它运行非常快。 我们认为,如果我们使用功能强大的计算机一百倍并运行一百个相同的请求,那么它们应该在同一时间执行。 事实证明,来自不同客户端的请求发生冲突。 在它们之间,出现争用,这是分布式应用程序中的一个基本问题。 单独的请求争夺资源。

发现问题的方法


如果服务器无法正常工作,首先,我们将尝试查找并修复应用程序内部,数据库中以及文件I / O期间锁的琐碎问题。 网络交互中仍然存在一类问题,但是到目前为止,我们将自己局限于这三个问题,这足以学习识别类似的问题,并且我们主要对引起争用的问题(资源争夺)感兴趣。

进程内锁


这是阻止应用程序中的典型请求。

这是序列图的一种变体,描述了由于某些条件操作而导致应用程序代码与数据库交互的算法。 我们看到正在进行网络调用,然后数据库中发生了某些事情-数据库已被稍微使用。 然后发出另一个请求。 在整个期间,都使用数据库中的事务和所有请求共有的密钥。 它可以是两个不同的客户或两个不同的订单,但是一个和相同的餐厅菜单对象与客户订单存储在同一数据库中。 我们使用事务来保持一致性;两个查询在公共对象的键上都有争用。

让我们看看它如何扩展。

线程大部分时间处于休眠状态。 实际上,他什么也没做。 我们有一个会干扰其他进程的锁。 最烦人的是,锁定键的事务中最不有用的操作是在一开始就发生的。 它会及时延长范围事务。

我们将以这种方式战斗。
var fallback = FallbackPolicy<OptionalData> .Handle<OperationCancelledException>() .FallbackAsync<OptionalData>(OptionalData.Default); var optionalDataTask = fallback .ExecuteAsync(async () => await CalculateOptionalDataAsync()); //… var required = await CalculateRequiredData(); var optional = await optionalDataTask; var price = CalculatePriceAsync(optional, required); 

这就是最终的一致性。 我们假设我们的某些数据可能不是最近的。 为此,我们需要使用不同的代码。 我们必须接受数据的质量不同。 我们不会查看之前发生的情况-经理更改了菜单中的某些内容,或者客户单击了“结帐”按钮。 对于我们来说,两秒钟前哪个按钮按下没关系。 对于企业而言,没有区别。

没有区别,我们可以做这样的事情。 有条件地将其称为optionalData。 也就是说,我们无法做到的一些价值。 我们有一个备用广告-我们从缓存中获取的值或传递了一些默认值。 对于最重要的操作(必需的变量),我们将等待。 我们将坚定地等待他,直到那时我们才会等待对可选数据请求的响应。 这将使我们加快工作速度。 还有一个重要的意义-由于某种原因,此操作可能根本不执行。 假设此操作的代码不是最佳的,并且此刻存在错误。 如果操作失败,请回退。 然后,我们按照通常的含义进行处理。

DB锁


当我们重写异步并更改一致性模型时,我们得到的布局大致相同。

这里重要的不是请求的时间变得更快。 重要的是我们没有争用。 如果我们添加请求,那么只有图片的左侧被我们饱和。


这是一个阻止请求。 在这里线程重叠并且发生争用的密钥。 在右边,我们在数据库中根本没有任何事务,并且它们正在悄悄地执行。 正确的大小写可以无限期地在此模式下工作。 左将导致服务器崩溃。

同步io


有时我们需要文件日志。 出人意料的是,日志记录系统可能会给出此类令人不愉快的故障。 Azure中磁盘上的延迟-5毫秒。 如果我们连续写入文件,则每秒只有200个请求。 就是这样,应用程序已停止。


看到这只是您的头发直立-该应用程序中已经培育了2000多个线程。 所有线程中有78%是同一调用堆栈。 他们在同一地点停下来,试图进入显示器。 此监视器将限制对我们所有日志文件的访问。 当然,这必须削减。

这是您需要在NLog中进行配置的步骤。 我们创建一个异步目标并对其进行写入。 异步目标将写入实际文件。 当然,我们可能会在日志中丢失一定数量的消息,但是对于业务而言,更重要的是什么? 当系统故障10分钟时,我们损失了100万卢布。 最好在服务日志中丢失几条消息,这些消息会失败并重新启动。

一切都很糟糕


争用是多线程应用程序中的一个大问题,它不允许您简单地扩展单线程应用程序。 争用源必须能够识别和消除。 大量线程对应用程序来说是灾难性的,阻塞调用必须重写为异步。

我不得不重写许多传统,以阻止异步调用,我自己经常启动这种升级。 很多时候,有人来问:“听着,我们已经重写了两个星期,几乎都是异步的。 它将在多大程度上更快地工作? 伙计们,我会让你不高兴-运作速度不会更快。 它将变得更慢。 毕竟,TPL是一种竞争模型,而另一种竞争模型是-协作式多任务而非抢占式多任务,这是开销。 在我们的一个项目中-CPU使用率和GC负载大约增加5%。

还有一个坏消息-仅在异步上重写后,应用程序可能会工作得更糟,而没有意识到竞争模型的功能。 我将在下一篇文章中详细讨论这些功能。

这就提出了一个问题-是否需要重写?

同步代码是在异步上重写的,以解除流程竞争执行的模型(并发模型)的绑定,并摆脱抢先式多任务模型。 我们看到线程数量可能会对性能产生不利影响,因此您需要摆脱增加线程数量以提高并发性的需要。 即使我们拥有旧版,也不想重写此代码-这是重写它的主要原因。

最后的好消息是,我们现在对如何摆脱阻塞代码争用的琐碎问题有所了解。 如果您在阻塞的应用程序中发现了此类问题,那么该是在重写异步之前消除它们的时候了,因为在那里它们不会自行消失。

Source: https://habr.com/ru/post/zh-CN440676/


All Articles