跟我一起学开源设计第3节: 开源的服务端用户埋点SDK源码设计与实现分析

一、背景

在之前的分享中,我们通过引入用户行为分析Growing IO的客户端SDK,介绍了Spring Boot Starter的开发方法,同时也介绍了Spring Boot Starter中的常见的几项高级配置的玩法:

跟我一起学开源设计第1节:封装埋点GrowingIO Spring Boot Starter组件

跟我一起学开源设计第2节:SpringBoot Starter组件中的4项高级设计

本文来介绍该客户端SDK埋点的Java源码设计,希望可以借助这个源码分析和设计,可以让大家在日后的工作中,对于业务性的服务端的埋点上报之类的功能设计思想有所了解,能够自主的学会如何开发一个服务端的埋点上报的SDK组件,正如之前所说,这个东西其实不需要我们依赖服务端的运行,我们直接在客户端上面进行模拟数据上报这个操作即可。也可以自行登录到growingio网站上注册个试用账号。

二、基本概念

源码分析前,我们有必要了解下这种的业务知识与概念,有助于帮助我们统一语言与概念,通过如下文档,可以知道该SDK是如何使用的,建议先自己花费5分钟过一遍,同时思考下自己不明白和不理解的地方,同时也给自己提出几个问题。

docs.growingio.com/v3/develope...

在该网页文档中的代码使用案例可以知道,对于一个客户端SDK埋点的使用,需要提供一个服务器端的URL、项目ID、传输的队列大小和间隔、网络超时时间、日志等,同时需要再客户端构造出如下的变量数据:

(1)、事件时间:即一个行为操作上报或触发的时间点,需要告诉服务端,什么时候发生的。

(2)、事件标识:即一个业务场景的标识,比如创建订单事件、支付事件、用户注册事件、取消订单事件,可以为每种场景定义一个唯一标识:

(3)、用户ID:即触发的用户是谁,既然是用户行为分析,肯定是要把用户ID上报给服务端

(4)、事件级别变量:即当前业务场景的核心参数、核心数据,服务端可以根据该数据进行分组统计分析

SDK使用的示例代码如下:

java 复制代码
//事件行为消息体
GIOEventMessage eventMessage = new GIOEventMessage.Builder()
    .eventTime(System.currentTimeMillis())            // 事件时间,默认为系统时间(选填)
    .eventKey("BuyProduct")                           // 事件标识 (必填)
    .loginUserId("417abcabcabcbac")                   // 登录用户ID (必填)
    .addEventVariable("product_name", "苹果")          // 事件级变量 (选填)
    .addEventVariable("product_classify", "水果")      // 事件级变量 (选填)
    .addEventVariable("product_price", 14)            // 事件级变量 (选填)
    .build();
​
//上传事件行为消息到服务器
GrowingAPI.send(eventMessage);

请大家牢记如下4个内容,这是一个基本的用户行为分析埋点SDK的基本元素,如果然你去设计一个埋点SDK,这四个必不可少,对于学习其他家的埋点SDK源码也是如此。

梳理的一个图如下所示:

此时,我们最好思考几个问题,比如:

(1)、SDK是如何构造事件消息的

(2)、SDK是同步上报数据还是异步上报数据

(3)、SDK是如何使用高性能上报数据

(4)、SDK上报的数据格式是什么样的

(5)、SDK这个埋点上报过程中都做了哪些事

为什么要梳理问题呢,其实,这也是一种源码学习方法,在源码学习方法中,其中一个方法就是带着问题/目标去学习,在持续的梳理和跟进中,一步步的来解决自己的问题。

三、源码分析

3.1、源码下载与编译

1、首先在Github上面,找到该SDK的仓库,下载源码:

github.com/growingio/g...

2、然后再IDEA中,导入该项目,选择Maven的编译:

3、导入工程后,可以发现,其实该SDK的源码设计并不多,但是也是值得我们学习和思考的:

4、然后写个测试类,运行一下简单的结果,可以看到控制台输出了一个日志消息,同时对消息结构封装为了数组结构。

看到这里不知道大家是什么感觉,我这里个人感觉是,个人完全可以模拟一个服务端的接口,来接收这个数据,从而体验一下客户端数据埋点上报。

3.2、源码分析

源码分析,我们先从程序入口开始。

3.2.1、GIOEventMessage源码分析

1、事件消息的构造GIOEventMessage类,该类本质就是一消息数据结构,从源码中可以看到几个关键点:

(1)、定义了非常精简的HTTP事件请求参数名称:

(2)、定义了2个map存储基本事件参数和变量级别的事件参数

(3)、存储和更新变量数据:

3.2.2、GrowingAPI源码分析

接下来就是核心的消息时间发送方法: GrowingAPI.send(msg),先来看下GrowingAPI这个类都做了那些内容:

(1)、服务端初始化参数配置,并校验服务端的URL是否可用:

(2)、封装对外提供的发送方法,将消息传递给消息存储策略处理器中。

这样growingapi的分析就结束了。然后它在这里为我们引入了一个新的组件:StoreStrategyClient,也就是说当我们调用send方法发送的时候,SDK将一个个的消息,传递给了StoreStrategyClient类。

3.2.3、StoreStrategyClient源码分析

通过刚才的类可以知道当发送了消息数据,通过 StoreStrategyClient.getStoreInstance().push(msg);代码将消息传递给了消息存储策略处理器,我们接下来分析这个:

(1)、通过单例,提供了一个默认的存储策略

(2)、push消息的时候,父类的抽象逻辑,定义了一个数量的线程池,具体如何push交给子类实现:

(3)、当前工程中只有一个默认存储实现DefaultStoreStrategy,我们来分析一下他,这个也是整个SDK中最核心的设计,非常值得每个人学习,也是一种典型的生产消费模式的代码设计:

(4)、消息加入内存队列,在存储其中维护了这几个发送相关的参数:阻塞队列、阻塞队列的大小、发送间隔、发送器、批量发送数量等

接下来的方法是非常重要的,多多理解:

scss 复制代码
static {
        sendMsgSchedule.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                while (!queue.isEmpty()) {//队列不为空则执行
                    if (currentBatchMsgSize() < sendMsgBatchSize) {// 当前小于批量发送数量
                        GIOMessage gioMessage = queue.poll();// 从队列中取出来
                        if (gioMessage != null) {
                            String projectId = gioMessage.getProjectId();
                            if (batchMsgMap.containsKey(projectId)) {//通过项目ID区分批量消息
                                List<GIOMessage> list = batchMsgMap.get(projectId);//将消息放到某个项目标识下
                                list.add(gioMessage);
                            } else {
                                List<GIOMessage> list = new ArrayList<GIOMessage>();
                                list.add(gioMessage);//消息不存在滴1次增加一个
                                batchMsgMap.put(projectId, list);
                            }
                        }
                    } else {break;}// 大于批量阈值就终止循环,进入发送流程
                }
                // 队列为空时,取出批量消息数据结构中的数据,开始调用发送器批量发送
                for (Map.Entry<String, List<GIOMessage>> entry : batchMsgMap.entrySet()) {
                    if (entry.getValue() != null && !entry.getValue().isEmpty()) {
                        sender.sendMsg(entry.getKey(), entry.getValue());
                    }
                }
                batchMsgMap.clear();// 发送完清除数据
            }
        }, sendInterval, sendInterval, TimeUnit.MILLISECONDS);
    }

通过以上逻辑可以看到,这里采用了一个异步的定时任务线程池,每隔100ms检测一次,当前内存队列中是否存在消息,如果消息小于批量发送的数量就取出来,大于批量发送数量,就开始批量发送。如果说100ms产生了10条消息,则10条消息会整体发送。

3.2.4、消息发送器

通过上一步知道了,消息是每隔100ms异步传递给消息发送器的,然后我们接着分析,看到了发送器的实现,它又是一个线程池异步的调用网络进行发送。

看到它调用了网络发送器,网络发送的源码如下,这里就是纯HTTP发送了,然后也可以看到它发送了网络IO错误后也会进行重试处理。

这样一个消息从构建到发送,我们就已经分析完了,我们回顾一下消息的流程,如下图所示:

通过这8个步骤,可以非常清晰的看到它是如何将一个消息发送到服务端的。

四、可以学习到的知识点

通过本文我们可以学习到的知识点如下:

建造者设计模式、策略模式、模板方法模式、单例模式、生产者消费者模式、固定线程池/调度线程池、高性能埋点数据上报、批量数据异步发送上报、扩展点设计等。

大家是否清楚对于上面的知识点都是如何在源码中体现的吗?

可以多花费一点时间来品味一下哦。

本文这个SDK其实源码设计的非常不错了了,经常听到很多人说多线程这项技术无法再实际工作中使用,其实业务功能可能确实很少使用,但是提到高性能和中间件类型的工具、项目、SDK一般都会大量的使用,来完成异步、批量这种处理。

希望本篇文章可以帮助到大家,学习到一种客户端SDK的埋点的设计。

我相信看完了这个SDK设计,大家应该也可以自己手写一个客户端埋点的SDK工具包了,我相信这个对自己的设计、技术也有一定的提升的。如果未来公司的业务,是XXX上报类的,我相信这个SDK的精髓一定会帮助到你。本文没有讲述源码中关于压缩数据上报的内容,感兴趣的可以自己研究下。

五、总结

本文也是开源设计专题的第三篇文章,感兴趣的小伙伴,在阅读后,可以尝试基于该SDK进行下源码的深入理解和学习。

同时,本文也分享了一种源码学习方法,即:带着问题/目标去学习,针对XX问题,进行跟踪和分析。通过最近3篇的文章,希望大家可以具备了:

Spring Boot Starter的设计能力

埋点类的SDK设计能力

一种基于队列的高性能处理能力

异步/批量思想的能力。

六、练习

感兴趣的小伙伴,既然学习了,也不能白学习,可以尝试实现和完成如下内容:

(1)、抽取这个项目中的精髓,封装出自己的埋点SDK Client和埋点Server端,然后进行一些基本的数据展示和分析。

(2)、使用typora文档中提供的mermaid这个东西来梳理当前SDK的业务时序交互流程图

七、最后

本文也是作者首发于个人知识星球【觉醒的新世界程序员】中的一篇文章,也是在星球内正在分享的《开源设计系列专题》中的中的内容,如果想了解相关内容,可以来了解下,与我一同学习交流,星球内每周更新一篇总结文档。

非常期待与大家一起学习、进步,喜欢的老铁可以收藏、点赞、关注、分享哦。

欢迎在评论区一起交流哦。

相关推荐
菜鸟233号21 分钟前
力扣513 找树左下角的值 java实现
java·数据结构·算法·leetcode
9ilk25 分钟前
【C++】--- C++11
开发语言·c++·笔记·后端
小小测试开发28 分钟前
提升WebUI自动化效率与性能:从脚本到架构的全链路优化指南
运维·架构·自动化
Neoest1 小时前
【EasyExcel 填坑日记】“Syntax error on token )“: 一次编译错误在逃 Runtime 的灵异事件
java·eclipse·编辑器
自在极意功。1 小时前
Web开发中的分层解耦
java·microsoft·web开发·解耦
是一个Bug1 小时前
ConcurrentHashMap的安全机制详解
java·jvm·安全
断剑zou天涯1 小时前
【算法笔记】bfprt算法
java·笔记·算法
番石榴AI1 小时前
java版的ocr推荐引擎——JiaJiaOCR 2.0重磅升级!纯Java CPU推理,新增手写OCR与表格识别
java·python·ocr
码事漫谈2 小时前
VSCode CMake Tools 功能解析、流程与最佳实践介绍
后端
鸽鸽程序猿2 小时前
【项目】【抽奖系统】抽奖
java·spring