一、背景
在之前的分享中,我们通过引入用户行为分析Growing IO的客户端SDK,介绍了Spring Boot Starter的开发方法,同时也介绍了Spring Boot Starter中的常见的几项高级配置的玩法
本文来介绍该客户端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端,然后进行一些基本的数据展示和分析。文章来源:https://www.toymoban.com/news/detail-807494.html
(2)、使用typora文档中提供的mermaid这个东西来梳理当前SDK的业务时序交互流程图文章来源地址https://www.toymoban.com/news/detail-807494.html
到了这里,关于跟我一起学开源设计第3节: 开源的服务端用户埋点SDK源码设计与实现分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!