[译]Sentry:如何从数据存储中获得更强的一致性

news2024/11/19 6:38:03

翻译自:How to Get Stronger Consistency Out of a Datastore
地址:https://blog.sentry.io/2019/09/17/how-to-get-stronger-consistency-out-of-a-datastore

Sentry的首要工作是接收、解析用户的异常信息。当用户异常信息大量上报时,Sentry的流量将达到高峰。同时,提供近实时的错误追踪,对于用户是有帮助的。

这里有两个相互排斥的地方:

  • 事件(Event)提取服务必须在各种负载的情况下都具有响应快速且可伸缩的能力。
  • Sentry用户必须近实时地可以看到异常信息。

为了能够应对流量高峰,Sentry从客户端接收事件,并异步执行一系列处理。 因此,这些处理不一定客户端接收到HTTP响应之前完成。

其中两个阶段包括在Sentry主存储器(ClickHouse)上保存事件和发送通知、调用插件等的后处理任务(post process task)。

下文中的后处理指的都是post process

在这里插入图片描述

在事件保存到ClickHouse之前,我们先将事件插入到Kafka主题中。一些消费者从该主题中读取并以批量插入的方式写入到ClickHouse中。

在保存了事件之后,我们触发了上面讨论的后处理任务,它需要从ClickHouse读取最新的事件(比如历史数据和我们刚刚处理的事件)才能正常工作。

等等。“事件保存后?”。在尝试读取事件之前,我们如何确定该事件已被保存到ClickHouse中?好吧,我们不能完全确定。最后,事件提取任务只是将事件保存到Kafka主题中。

为了确保事件在我们尝试读取之前已经被存储(持久化),我们需要一个提供顺序一致性模型的存储系统(假设事件提取和后处理发生在单独的进程中),或者读写一致性模型,如果两个操作在同一进程中发生。 (有关一致性模型的更多信息。)

如果一个存储系统的写操作是异步发生的,而读操作与写操作过程不是同步的,那么这个存储系统就不能提供这些保证。因此,我们需要用不同的架构来缓解这个问题。

我们可以把这个问题分成两部分:

  • 当后期处理任务尝试读取时,事件可能根本没有到达ClickHouse。作为一种解决方案,后期处理任务需要等待事件通过Kafka到达ClickHouse之后再进行。
  • 即使在对ClickHouse的写操作发生之后再运行处理后任务,我们也依赖于ClickHouse的一致性保证。ClickHouse是分布式和有副本的,默认情况下不提供自己的读后写一致性。我们需要确保从已经接收到我们想要读取的事件的副本中读取。

写入ClickHouse后,触发后处理程序

解决这个问题相当于在运行后处理逻辑之前,等待一个主题一个Kafka消费者消费并提交事件。

对于这个问题,有下面几种解决办法:

  • 使用分布式锁框架使后处理程序等待。虽然这可以工作,但它需要我们在架构中添加另一个分布式系统框架,从而增加复杂性。
  • 在写入ClickHouse之后,让Snuba触发后处理任务。这个解决方案有一个重要的架构含义,Snuba将依赖于Sentry(而不是相反)。添加这种附加依赖关系不是我们所期望的。
  • 使用Kafka __consumer_offset主题使后处理任务等待。 该策略与我们构建的解决方案没有什么不同。主要问题是实践。例如,我们用来访问Kafka的库并没有提供一个抽象来解码关于该主题的消息。
  • 将完整的事件传递给需要它的后处理任务,而不是从数据库中重新加载数据。我们实际上是这样做的。不幸的是,这还不够,因为后处理任务需要运行需要最新存储的聚合查询。

同步消费

我们没有使用这四个解决方案,而是构建了一个系统,该系统允许Kafka消费者暂停自己并等待另一个消费者(在独立消费组上)提交偏移量,然后再使用相同的消息。

在这里插入图片描述

如上图所示,消费事件主题中的事件将触发后处理任务。我们希望只有当Snuba事件消费者将事件存储到ClickHouse时,处理后任务组件才能处理事件,以便处理后任务可以读取事件。

为了实现这一点,我们需要为Snuba提供一种方式,以便在事件存储到ClickHouse时进行广告(advertise)。广告(advertisement)通过另一个Kafka主题(commit log topic)来传递。Snuba事件使用者在提交偏移量后在提交主题上写入。

以下代码段是有关提交日志主题的有效负载示例。消息的关键字通过提供主题,分区和组来标识事件主题。有效负载本身就是要提交的偏移量。

key: events:0:snuba-consumers #topic:partition:group
payload: 70

启动同步消费者时,需要从提交日志主题重新加载所有分区的状态(最后提交的偏移量)。为了使此过程快速进行,提交日志主题是一个压缩的主题,并且初始偏移量设置为最早的(最早的提交偏移量)。

由于我们仅在Kafka消息存储在ClickHouse中时才提交它们,因此这是我们需要的解决方案;它是由我们的批处理Kafka消费者执行的。

现在,我们在Kafka中有一个主题,它告诉我们通过Snuba Consumer从每个分区消费的偏移量是多少。因此,只有在消费者处理了偏移量后,我们才需要对事件进行后处理。

同步消费者处理协调。 该消费者同时读取事件主题和提交日志主题(在两个独立的线程上)。 它在内部运行状态机,该状态机跟踪与提交日志的最大偏移,并仅从事件主题消费直到追上提交日志中的偏移(watermark)。

当同步消费者赶上分区的提交日志中的最新偏移量时,它将停止消费该分区中的事件,直到提交了更多事件为止。

这个解决方案是可行的,但是,作为这个问题的任何解决方案,在可用性方面都存在妥协。如果流的存储部分被延迟,读取就会被暂停,处理后任务也是如此。

确保ClickHouse在副本上复制了我们的事件

ClickHouse是一个分布式数据库,具有多主、最终一致的异步复制。这句话很很宽泛,所以让我们来分析一下它与当前问题的关联:

  • 分布式数据库→在这种情况下,“分布式数据库”意味着我们将表分区到多个节点上。当ClickHouse接收到一批要写入的行时,默认情况下,它将对这些行进行分区并异步地将它们发送到正确的分区。当写入端收到响应时,可能未将批处理写入所有分区。
  • 复制是异步的,并且最终是一致的→读取可以发生在任何副本上,使用默认的负载平衡模式。在读取时,我们可能会遇到过时的数据,因为我们读取的副本可能未收到最新的写入数据。
  • 多主复制→写可以发生在任何节点上,并且可能不会以相同的顺序应用在所有副本上。最后,合并处理确保所有副本上的数据是一致的。在写入一个节点和一个副本之后,ClickHouse返回(默认情况下)。

即使我们在开始后处理之前等待Kafka提交(如上所述),我们仍然可以从ClickHouse副本中读到不是最新的数据;以上的解决方案仍然不够。

幸运的是,ClickHouse的灵活性更高,它使我们能够为每个查询提供更强的一致性保证,而不会损害不需要强一致性的查询的性能。

最初的想法是结合insert_quorum和select_sequential_consistency设置,这将保证给定数量的副本在返回之前收到更新。该组合设置可以保证我们查询的是最新的副本。

主要问题是select_sequential_consistency不能保证负载平衡器选择一个最新的副本。相反,如果所选择的副本在任何写入过程中都不是最新的,查询就会失败——这对我们不起作用。

因此,我们将问题一分为二,以探索不同的解决方案:

如何确保读取的副本是最新的?

当我们需要这种保证时(不是所有查询都需要),我们使用in_order负载均衡模式。该模式驱动负载均衡器按照配置中定义的顺序选择健康的副本。因此,只要第一个副本启动并运行,负载均衡器就会选择它。我们可以对读和写都这样做,本质上是对同一个副本进行读写,只要它运行状况良好。这个副本显然是最新的。

我们如何确保所有分区在读取前都已写入批处理?

在写入时,ClickHouse为我们提供了另一个方便的选项:insert_distributed_sync,它将分区设置为同步运行(而不是异步运行)。客户端只有在所有分区都收到写操作之后才会收到确认,这样就消除了读取器从非最新分区读取的风险。

顺序一致性

这两个解决方案产生了一个一致性模型,该模型表面上类似于顺序一致性。通过通过同一个节点进行读写,所有的写操作看起来都是按一个总的顺序进行的。客户端读取事件,读取的都是最新的状态。

我们是否打破了CAP定理?没有。

我们选择一致性并牺牲可用性了吗?不,我们仍然依赖可用性。只要所有副本都在运行,这就是系统提供的一致性模型(这意味着系统不保证此一致性模型)。如果我们在副本上进行写操作并且在读取之前就死了,则不能保证负载均衡器转到已经收到该写操作的副本。我们不能说我们保证顺序一致性或读写一致性,尽管这是我们经常达到的结果。

这是性能问题吗?在一定程度上是的。对同一个副本的读写显然会将存储性能绑定到该副本,但到目前为止,它已经足够好了。

并非所有读取都需要保持一致,因此查询时何时使用in_order负载平衡由客户端决定。这种解决方案允许在不过度降低性能的情况下实现足够好的一致性。


到目前为止,我们讨论了如何在大多数情况下从最终一致的数据库中获得更强的一致性。在另一个领域,我们尝试做一些ClickHouse不打算做的事情:在记录写入数据库后更新记录。ClickHouse最适合不可变数据,而Sentry在大多数情况下是不可变的,有一些操作确实需要更新记录的能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1081847.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux部署kubeedge 1.4

文章目录 一、机器信息二、环境准备(所有节点操作)2.1. 修改主机名2.2. 开启路由转发2.3.安装Docker(所有节点)2.4.部署K8S集群(单机版,云端节点) 2.5.安装Mosquitto(只在边缘节点安装)三、安装kubeedge 1.…

数据库基础篇二

函数 约束 概述 概念:约束是作用于表字段上的规则,用于限制存储在表中的数据。目的:保证数据库中数据的正确、有效性和完整性。分类: 外键约束 外键用来让两张表的数据之间建立连接,从而保证数据的一致性和完整性…

2023全国大学生软件测试大赛开发者测试练习题满分答案(PairingHeap2023)

2023全国大学生软件测试大赛开发者测试练习题满分答案(PairingHeap2023) 题目详情题解代码(直接全部复制到test类中即可) 提示:该题只需要分支覆盖得分即可,不需要变异得分 题目详情 题解代码(…

Kubernetes使用OkHttp客户端进行网络负载均衡

在一次内部Java服务审计中,我们发现一些请求没有在Kubernetes(K8s)网络上正确地实现负载均衡。导致我们深入研究的问题是HTTP 5xx错误率的急剧上升,由于CPU使用率非常高,垃圾收集事件的数量很多以及超时,但…

linux开发环境下出现Segmentation fault问题排查一

一、检测代码中是否有数组越界情况 更改以上数组为128*60后,正常。确认是数组溢出导致越界。 二、分析:一般情况下打印的字符刚好在50以内,但是在其它状态下测试,数据字符数据增加从而导致溢出 打印命令如下: sprin…

2.线性表——数据结构学习

零个或多个数据元素的有限序列。 有序 (每个元素有且只有一个前驱与后继) -> 一对一的关系 前驱 (第一个元素无前驱)后继(最后一个元素无后继) 有限 线性表元素个数:n (n ≥ \geq ≥ 0)&a…

D1s芯片启动流程(BROM System)分析

1、D1S芯片介绍 2、BROM介绍 (1)BROM(boot rom):存放启动代码的ROM,该ROM一般在芯片内部集成,是芯片上电执行的最开始代码; (2)BROM里存放的程序主要功能&…

【Java 进阶篇】JavaScript 介绍及其发展史

JavaScript是一门广泛应用于Web开发的编程语言。它是一种高级的、解释性的脚本语言,主要用于改善用户与Web页面的互动体验。本篇博客将为你详细介绍JavaScript的基础知识、历史背景和它在Web开发中的重要作用。我们还将讨论JavaScript的发展史,从它的起源…

9. 一个SpringBoot项目运行

新手如何运行一个SpringBoot项目 1.SpringBoot项目运行 新创建的SpringBoot项目如何运行 2.启动lombok注解 点击该按钮,启动lombok注解支持 3.展示说明

进阶JAVA篇-Object类与Objects类、包装类的常用API(一)

目录 API 1.0 API概念 2.0 Object类的常用API 2.1 Object 中的 tostring() 方法 表示返回对象的字符串表达形式 2.2 Object 中的 equals(Object o) 方法 ,判断两个对象的是否相等 2.2.1深入了解如何重写Object 中的 equals(Object o) 方法 2.2.2 对重写Object 中的…

html css实战之学成在线项目

html css实战之学成在线项目 项目链接:https://download.csdn.net/download/weixin_39451323/88416213 效果图:

4年测试经验,面试却突破不了20K,真是太卷了····

先说一个插曲:上个月我有同学在深圳被裁员了,和我一样都是软件测试,不过他是平安外包,所以整个组都撤了,他工资和我差不多都是14K。 现在IT互联网已经比较寒冬,特别是软件测试,裁员先裁测试&am…

【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Prompt Encoder

论文:Segment Anything   代码:https://github.com/facebookresearch/segment-anything 上一篇:【技术追踪】SAM(Segment Anything Model)代码解析与结构绘制之Image Encoder 本篇示例依然采用上一篇的狗狗图像运行代…

深度学习笔记之优化算法(八)Adam算法的简单认识

深度学习笔记之优化算法——Adam算法的简单认识 引言回顾:基于Nesterov动量的RMSProp算法Adam算法的简单认识一阶矩、二阶矩修正偏差的功能Adam的算法过程描述Adam示例代码 引言 上一节介绍了基于 Nesterov \text{Nesterov} Nesterov动量与 RMSProp \text{RMSProp}…

文字与视频结合效果

效果展示 CSS 知识点 mix-blend-mode 属性的运用 实现整体页面布局 <section class"sec"><video autoplay muted loop><source src"./video.mp4" type"video/mp4" /></video><h2>Run</h2><!-- 用于切…

【Java 进阶篇】JavaScript 与 HTML 的结合方式

JavaScript是一种广泛应用于Web开发中的脚本语言&#xff0c;它与HTML&#xff08;Hypertext Markup Language&#xff09;结合使用&#xff0c;使开发人员能够创建交互式和动态的网页。在这篇博客中&#xff0c;我们将深入探讨JavaScript与HTML的结合方式&#xff0c;包括如何…

Web知识:markupsafe.escape() 函数的作用

1、markupsafe.escape() 函数是 MarkupSafe 库中的一个函数&#xff0c;它的作用是对字符串进行 HTML 转义&#xff0c;以防止在 HTML 文档中引起意外的解析结果或安全漏洞。 2、在 Web 开发中&#xff0c;如果用户提供的数据直接插入到 HTML 页面中&#xff0c;而没有经过转义…

SpringBoot调用存储过程(入参,返参)(亲测有效!!!)

存储过程和函数是有区别的&#xff01;&#xff01;&#xff01; 创建函数&#xff0c;只是演示&#xff0c;以下函数不完整&#xff01;&#xff01;&#xff01;(只是看P_xxx参数) CREATE OR REPLACE PROCEDURE SP_TICKET_CHECKE_ONLINE_TEST (p_transcode IN OUT VA…

Java反射获取抽象类方法属性问题讲解

Java反射获取抽象类方法属性问题讲解 结论一、案例准备二、测试方法&#xff1a;使用反射获取抽象类私有方法和私有属性具体操作&#xff08;获取私有方法&#xff09;具体操作&#xff08;获取私有属性&#xff09; 结论 Java 通过反射可以获得抽象类的任何修饰符&#xff08…

Vue2 Watch的语法

Watch语法 一、监听普通数据类型&#xff08;1&#xff09;把要监听的msg值看作方法名&#xff0c;来进行监听。&#xff08;2&#xff09;把要监听的msg值看作对象&#xff0c;利用hanler方法来进行监听 二、监听对象&#xff1a;&#xff08;1&#xff09;监听对象需要用到深…