客户端ack模块的实现

news2025/1/22 9:10:25

文章目录

  • 背景
  • 第一版设计
  • 第二版设计
  • 第三、四版设计
  • 写在最后

背景

所谓客户端ack模块是在我们推送服务中一个技术需求,本文主要介绍其迭代过程。

首先简单介绍下推送服务的架构,如下图。用户请求ws服务,建立ws长连接,并通过login和subscribe请求建立订阅关系。使用uid代表用户,topic代表用户订阅的数据类型。业务服务使用connector sdk通过grpc stream同ws服务建立双向通信,将业务数据推送至ws服务。

为了减少connector和ws服务间的冗余通信,在connector中维护了用户的订阅状态以做前置的过滤。当用户有上下线以及订阅取消订阅行为时,ws服务会将用户状态同步至connector。为了保证ws同connector间状态的最终一致性,需要在ws服务增加客户端ack模块来确保connector收到状态同步消息。

第一版设计

拿到这个技术需求后,首先去了解了当前状态同步的协议,发现其为增量同步。当某个用户状态发生变化时,ws会将变化的部分发送至connector sdk。示意如下。

针对增量同步的方式,我们的ack模块需要面对两个问题:

  1. 需要为每个消息增加唯一的标识;
  2. ack模块需要实现exactly one;

第一个问题很容易解决,使用全局递增的request id就足够解决。

第二个问题是ack中常见的问题,因为涉及到重试。

单纯的客户端ack机制只能保证at least once,需要协同服务端的消息幂等性保证才能实现exactly one。在增量同步的协议,通常需要依赖唯一的request id来实现服务端的幂等,收到消息检验request id是否已经收到过,然后保存request id。kafka的exactly once就采用了这种方式,但是在我们的场景下需要在server端(connector)维护额外的消息状态,会引入过多的复杂度,这是我不想要的。

针对以上的问题,我选择调整状态同步的协议,将增量同步的方式更改为uid维度的全量同步。每当用户的订阅状态发生变化时,ws会将uid的当前状态全量同步至connector sdk。示意如下。

更改协议后,我们可以使用uid作为消息的标识,因为消息本身就具有uid维度的幂等性,所以上面的两个问题就解决了。

同时,因为同一uid的消息是具备覆盖特性的,对于同一uid的消息,我们只需要关注最近发生的一条是否到达connector sdk即可。所以在消息中增加了timestamp标识消息的新旧,同时纳秒级的timestamp也可以帮助我们在server端解决ABA的问题。

实现参见ack_v1.0.0,实现比较简单,就不贴代码了。在1.0.0中,基于上述描述实现了基本的功能。能够记录最新的消息,删除收到响应的消息,给出duration(duration不建议太小,5-10s是比较好的选择)时间内没有收到响应的消息。另外,还有一些小的优化点:

  • 提供方法对map进行重新分配以防止大map的内存泄漏;
  • 封装了分段的结构,以减少锁的粒度,提高并发度;

第二版设计

上述第一版的设计,在常见的client\server架构下是没有问题的。所谓常见的client\server架构是指client和server之间为单向通信,client发出请求,server给出响应。

但是在我们的推送服务中,connector作为server不仅给出状态同步请求的响应,同时会推送大量的业务数据。并且在目前的设计上,响应和业务数据是共享同一条grpc stream以及同一个loop receiver的goroutine。如果在收到状态同步请求的响应去ack时出现锁的阻塞,会阻塞整个推送服务。

在一次新业务接入的切流过程中,就出现了因为ack时锁的阻塞导致进程假死的现象。当然个人觉得不是ack模块的设计问题,主要原因有两个:

  • 切流时根本没有进行评估,cpu被打满(个人觉得是主要原因);
  • 在对ack消息进行重发时,会重复记录消息(会造成写放大);

虽然导致阻塞假死的原因是其他因素造成的,但是还是希望通过一些代码设计上的优化能够尽量兼容这些边界情况。提出的解决方案有两个:

  • 在业务层,将状态同步消息的处理和业务消息的处理进行隔离,拆分到不同的grpc stream,由不同的goroutine处理;
  • ack模块提供异步处理的能力;

我其实更倾向于第一种方案,隔离地更加彻底。但是站在ack模块的角度,针对这种大流量双向通信的场景,提供异步操作的模式是更完善的能力。所以在ack_v1.0.1中,提供了异步处理的模式,可以通过参数控制。

需要注意的是,当异步处理的buffer满时,会将消息丢弃掉,此时会造成可能的状态不一致。针对这种情况,我的建议是:

  • 根据场景合理的选择分段锁的粒度,增加并发度;
  • 根据场景合理的设置buffer的大小;
  • 通过可观测性的手段(比如metrics)将丢弃的消息告警出来,通过人工补偿的手段去处理;

第三、四版设计

在ack_v1.0.1中,除了异步的设计,还引入了msg的概念,以适应更通用的场景,并在ack_v1.1.0中升级为泛型版本。

其中value用来保存实际的业务消息。flag用来在ack时确认是否能够ack,比如我们上述业务场景下flag是时间戳。只有时间戳大于等于当前保存的时间戳才能被ack,否则忽略,保证只有消息不会被更久的响应ack。

写在最后

整个ack模块的设计比较简单,但是当我真正想把代码做得通用、好用、简单用时,发现并不是那么简单,所以写篇博客记录一下,代码也放到github上,欢迎大家提出宝贵建议。

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

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

相关文章

类和对象(C++11)

目录 一、类的定义 1.定义与声明放一起 2.定义与声明分开 二、类的访问限定符及封装 1.类的访问限定符 2.类的封装 三、类的实例化 四、类对象 1.类对象的存储方式 2.计算类对象的大小 面试题 1.结构体怎么对齐? 为什么要进行内存对齐? 2.如…

Python数据结构与算法-树

一、树的概念详情见 https://blog.csdn.net/little_limin/article/details/129845592 Python数据结构与算法-堆排序(NB组)—— 一、树的基础知识二、树的实例:模拟文件系统1、树的存储树结构也是链式存储的,与链表的结构相似&…

java通过URLClassLoader类加载器加载外部jar

相信在实际工作中,大家可能会遇到这种需求,这个jar是外部的,并没有添加到项目依赖中,只能通过类加载器加载并调用相关方法。 这种jar加载,其实也简单,我们通过普通的URLClassLoader就可以加载。代码如下所示…

netfilter filter表

iptables是linux下常用的一个防火墙软件,可以实现对网络访问的各种限制。iptables相当于防火墙的客户端,与用户进行交换,其后台依赖于内核的netfilter模块。iptables的各种配置,最终都是netfilter模块来实现的。 iptables分为4个…

Python-DQN代码阅读(12)

目录 1.代码 1.1代码解读 1.2 代码分解 1.2.1 latest_checkpoint tf.train.latest_checkpoint(checkpoint_dir) 1.2.2 saver.restore(sess, latest_checkpoint) 1.2.3 sess.run(tf.global_variables_initializer()) 1.2.4 deep_q_learning() 1.3 输出结果 1.4 问题 1…

v-for比v-if优先级更高?

前言 v-if和v-for哪个优先级更高呢?这是面试官常常问到的一个问题,如果是在三年前,我会毫不犹豫的回答当然是v-for哩,但在3202的今天,如果还这么答,显然是低估了前端技术的日新月异啰。下面我们就来结合编…

第十四届蓝桥杯大赛软件赛省赛 C/C++ 大学 A 组 E 题

颜色平衡树问题描述格式输入格式输出样例输入样例输出评测用例规模与约定解析参考程序问题描述 格式输入 输入的第一行包含一个整数 n ,表示树的结点数。 接下来 n 行,每行包含两个整数 Ci , Fi,用一个空格分隔,表示第 i 个结点 …

动态内存管理【下篇】

文章目录⚙️5.C/C程序的内存开辟⚙️6.柔性数组🔔6.1.柔性数组的特点🔔6.2.柔性数组的使用⚙️5.C/C程序的内存开辟 C/C程序内存分配的几个区域: 🔴1.栈区(stack):在执行函数时,函数…

2023软件测试最难求职季,哪些测试技能更容易拿到offer?

在一线大厂,没有测试这个岗位,只有测开这个岗位。这几年,各互联网大厂技术高速更新迭代,软件测试行业也正处于转型期。传统的功能测试技术逐步淘汰,各种新的测试技术层出不穷,测试人员的薪资也水涨船高。与…

【刷题之路】LeetCode 2389. 和有限的最长子序列

【刷题之路】LeetCode 2389. 和有限的最长子序列一、题目描述二、解题1、方法——二分法1.1、思路分析1.2、代码实现一、题目描述 原题连接: 2389. 和有限的最长子序列 题目描述: 给你一个长度为 n 的整数数组 nums ,和一个长度为 m 的整数数…

UR5构型机械臂正逆运动学

前言 整理之前的一个项目,当时看着一个博客硬生生计算了差不多一个星期。尝试用MatLab符号推导工具箱化简一部分工作。我使用的大象机器人一款开源入门级协作机器人产品myCobot,开发文档十分完善,但是有部分技术没有开源,如正逆运…

数据分析师 ---- SQL强化(2)

数据分析师 ---- SQL强化(2) 文章目录数据分析师 ---- SQL强化(2)题目一:SQL实现文本处理题目二:语种播放量前三高所有歌曲总结:题目一:SQL实现文本处理 现有试卷信息表examination_info(exam_id试卷ID, tag试卷类别,…

钢铁侠材质制作——2、线条轮廓部分的制作

钢铁侠Unlit光照Shader,三种效果变化返回目录大家好,我是阿赵,这里是钢铁侠材质制作第二部分,线条轮廓部分的制作 为了实现这个效果,可以把细节拆分成以下几个部分: 1、轮廓光 1.效果分析 这是一个很基…

时间序列 | MATLAB实现CNN-BiLSTM-Attention时间序列预测

时间序列 | MATLAB实现CNN-BiLSTM-Attention时间序列预测 目录时间序列 | MATLAB实现CNN-BiLSTM-Attention时间序列预测预测效果基本介绍模型描述程序设计参考资料预测效果 基本介绍 MATLAB实现CNN-BiLSTM-Attention时间序列预测,CNN-BiLSTM结合注意力机制时间序列预…

语言模型ChatGPT,为什么能引领各行各业的AI技术革命

为什么ChatGPT这样一个语言模型的发展能引发所有行业的AI技术革命呢? 答案就在于它能理解我们的自然语言, 并能将我们的语言转换成计算机能够完全理解的特征。 自然语言与计算机理解 ChatGPT之所以能引领技术革命,关键在于它能理解我们的…

GPT、科技、人类的生产、知识与未来(上)

本文将继续结合GPT探讨人工智能技术升级可能对人类社会带来的影响。主要还是侧重历史、社会、文化、经济、政治等角度。 问题的提出:ChatGPT等工具会提高人的工作效率和产出。但它会让人类使用者自身变得更“聪明”,还是“更笨”?更“强”&am…

CVE-2017-16995 Ubuntu 16.04 漏洞复现

目录 1.背景介绍 2.目的: 3.环境 4.操作: 工具下载地址: 1.背景介绍 Ubuntu 16.04版本存在本地提权漏洞,该漏洞存在于Linux内核带有的eBPF bpf(2)系统调用中,当用户提供恶意BPF程序使eBPF验证器模块产生计算错误&…

JDBC03-批处理、连接池、DBUtils、事物、DAO通用方法

1. 封装 JDBCUtils 【关闭、得到连接】 1.1 说明 1.2 代码实现 工具类 JDBCUtils package com.hspedu.jdbc.utils;import java.io.FileInputStream; import java.io.IOException; import java.sql.*; import java.util.Properties; /** * 这是一个工具类,完成 my…

【Microsoft Edge】安装详解

文章目录一、下载 Edge1.1 下载网址1.2 版本分类二、安装 Edge2.1 可能的异常情况2.2 安装目录详解2.2.1 Edge 非 Canary 版2.2.2 Edge Canary 版一、下载 Edge Edge 的安装包其实是一个简易安装包,里面封装了一个安装的配置文件,提供真正的安装包下载链…