今年是回到广州工作的第一年,本年度主要参与了一个埋点数据平台以及其相关组件的建设工作,在工作中以及与同事的交流增长了很多项目经验,也接触了相关大数据的知识获得了成长,在这里简单总结一下本年度工作中完成了的项目,以及在其中得出的一些思考。
飞书论坛应用后台开发(java)
入职之后,公司并未直接分配正式的线上项目任务,而是安排我去参与一个内部项目的开发,内部项目的内容是设计一个飞书应用用于内部发表文章进行交流,换而言之,是开发一个具有论坛功能的飞书应用。在项目中,我被分配为对后端进行开发,开发内容为话题、帖子、评论的增删改查;评论点赞、积分系统以及角色权限系统,实际上我安排负责的是Android埋点相关组件开发,在入职之前,也主要做开发app相关的项目,而对Spring以及相关组件的掌握程度仅限于课程设计程度,所以在开始的开发阶段,需求完成的十分艰难,很多事情,包括建表规范、MVC结构等知识需要向其他同事请教,所幸和我合作的同事态度比较友好,经过两个星期code review和磨合,总算熟悉了java后端的相关开发技巧和流程,对于普通的CURD需求也可以轻松地应付了,这也为后来下半年参与后端工作中打下基础。
经过第一个月的学习和熟悉,到第二个月时,普通的需求已经是得心应手,飞书应用的项目也到了即将完工的阶段,但是由于自己的知识不足和时间问题,项目中的积分系统模块做得并不是很好,这里让我留下了比较深刻的印象,当时的需求是要对特定角色的特定操作进行积分计算和汇总展示,我当时由于贪图便利,仅仅按照需求去对指定的角色的行为进行积分计算,忽略一个问题,角色是可以转换的,而我只对当前用户为特定角色对他的积分操作进行记录,因此极大地限制了系统的扩展性,后来经过和同事沟通和自己思考,认为将所有成员的所有积分操作进行记录,才是一种更好的做法,即使以后可以改变积分的计算规则(即不仅仅对特定角色去计算,而是对更多的角色的行为进行积分记录),也可以实现迅速的切换,经过这次的实践,让我明白到要有对需求之外的思考,对功能或者架构的设计不能仅限于当前的需求,也要为长远的扩展而考虑;其次在积分计算时,我将积分计算的动作与业务动作进行了耦合,因此产生了很多冗余的代码,这个是我在下半年开发时反思的,因为在下半年也遇到了相似的问题,就想起来之前的代码的耦合问题,实际上在开发中,功能解耦也是很重要的一部分,将相同的功能引流的一个出口,对后期代码的维护也是很重要的。
这个项目主要是正式投入工作前的一个热身,但是通过和同事协作,还是令我熟悉了公司的工作流程以及重新学习了相关的java后端开发技术,为后面的工作打下基础。
Android埋点SDK开发(java + kotlin)
在结束内部任务后,我回到了组内的项目开发中,接手的是一个Android埋点SDK项目,项目实际逻辑不难,主要分为两个部分,第一个部分是配置下发,抽象来说就是一个http的get请求,然后获得一份json格式的配置,将存放在手机的应用空间中进行读取,此处主要值得注意的地方是需要控制手机对服务端接口的访问频次,毕竟接口访问频次过大对服务端和客户端来说都不是一件好事,因此这里的设计是每次手机从后台回到前台时,会将当前时间和上次拉取配置的时间取一个差值,当那个差值大于此时生成的一个随机时间数时,才会访问配置下发接口;第二个部分是数据上报,抽象来说也就是http的post请求,值得注意的只是失败重试机制的设计,这里采用数据库存放以及网络重试方法去保证,不作细说。
虽然基本需求比较简单,但是其他一些小模块,还是有值得思考和学习的地方,归纳如下:
SDK初始化优化
对于SDK的初始化,在开发时,当时的iOS同事给了我一个很好的启发,之前SDK初始化的设计,是需要手动将后台的初始化参数让业务方一个一个填入客户端代码中,但当时的同事提出了一个很好的方案,将所有需要初始化的参数编码成一个base64,让SDK将这个base64解码成所需要初始化的参数,这样做业务方只需要填入一串base64就可以完成初始化,大大减低初始化的成本。
debug模式
在完成基本需求后,产品提出了一个比较大的需求,SDK的数据埋点debug功能。从产品角度出发,这个功能在我看来具有十分大的意义,在没有SDK的数据埋点debug功能之前,业务方去验收埋点数据只能通过查询数据库、kafka或者到最下游的平台去查询,除了操作门槛较高外,也会产生脏数据对系统进行影响;而新增debug功能后,SDK的埋点数据直接在前端的页面进行显示,大大降低了业务方的操作门槛,同时数据仅作debug不入库,更方便研发侧去调试而不影响下游的组件。至于功能的实现,实际不是太难,不过需要后端、前端和客户端的三方联调,链路描述如下:后端生成一个二维码让前端显示,然后客户端扫码后利用唤醒协议去唤醒app,使应用进行到debug模式(实际上是更改数据发送体某个参数,以便后端识别),之后应用发送的数据会被后端视为debug数据,经过处理后在前端显示,功能不难,但是在产品上的思考,如何做出让更多人都能容易使用的产品,是值得记录和学习的。
业务对接
在debug功能开发完成后,项目工作重点基本注重在后端中,SDK这边直到年底才正式对接业务,在对接业务中还是得到了一些收获,首先第一个获就是不要太相信你的开发机,同一个程序在你的开发机没有问题,并不代表在所有手机都没有问题,对于耗时的调用,确实要注意和避免,该异步的操作还是要异步,不想过度相信手机性能,第二个收获是aar依赖包尽量还是使用远程依赖,使用本地依赖还是会给业务方带来集成上的压力,第三个就是集成包的冲突,业务方有可能集成了和你开发的包相似的依赖包,可能会有一些意料不到的冲突问题,在开发时,一些需要hook或者需要使用的关键命名还是最好具有独有性,以免被误hook造成冲突。
iOS埋点SDK开发(oc)
在七月底,埋点SDK开发基本完成时,准备推出到业务使用时,发生了一件事,负责iOS的同事离职了,但是当前还有debug模式的开发需求和游戏业务的对接需求需要承接,换而言之就是工作量的突然几倍增长,由于事出突然,当时只有一个星期去交接和熟悉相关的iOS开发内容。对于写惯java的人来说,oc还是不容易习惯的,其次的难题是iOS的打包方式,因为对iOS特性的不熟悉,在这方面也碰了不少壁,不过凭着百度、谷歌以及问大佬,还是把这轮需求和对接任务顶了过来,但是对于个人来说,实际上技术的提升不是很大,工作内容来说其实也只是代码的堆砌,只能说提高了一些对iOS的理解,但未达到能独立开发的程度。
Flutter Demo应用开发(go + flutter)
完成了原生的埋点SDK开发后,上级提出需要一个正式的demo展示应用的需求,开发的背景是现有的demo应用都是那种界面简单,仅有几个按钮的功能demo,对于对技术理解程度不深的业务方来讲,采用简陋的demo难以让他们理解实际的功能,因此需要一个拥有实际交互UI的demo应用去体现业务价值。
考虑到开发的便利性,技术选型选择了go后端+flutter客户端的架构,相较于java,go去开发后端显得更速度便利,而flutter相较于原生则排除了需要重复编写两端代码的问题,技术选型确实具有便利的特点,但是对于我来说却不太便利,正是因为我两种方法都没有接触过,又是一次需要学习的旅程。
在服务端方面,我采用了gim+gorm的框架以及github上im的代码借鉴,迅速完成了相关聊天功能和朋友圈功能的后端开发,在客户端方面,也是通过git上的组件的代码借鉴和堆砌完成UI的开发,然后利用flutter的dio框架完成网络交互,至此demo应用的基本模型基本开发完毕,即一个拥有聊天功能和动态发布功能的APP。
然后在动态广场模块方面,需要实现评论和点赞的推送功能,然而客户端对http的请求是短连接,需要客户端向服务端进行请求,才能得知后端的变化情况,针对处理这个场景,在服务端引入了websocket,通过websocket的双工通信,可以实现服务端向客户端发送消息,客户端只需要进行监听即可,而不需要进行轮询请求。
这个项目中,对go和flutter的开发有了一定理解,也算是拓展了一点相关的开发知识,降低了后续与以go语言开发服务端的业务和flutter开发客户端的业务对接时的沟通成本。
埋点管理平台服务端开发(java)
在SDK开发的期间,由于SDK的管理平台后端开发人数不足,导致进度滞后,因此我临时过去支撑了开发几个功能模块,没想到因为这个,下半年工作重心就从客户端转移到了服务端,一开始开发的模块还比较简单,坑比较少,都是基本的调用接口和CRUD,但是到后期逐步深入开发和业务的投入使用,还是觉得后端有很多地方是值得琢磨的,归纳如下:
数据规则不确定
在结束Flutter Demo应用的开发之后,我正式转入埋点管理平台后台的迭代之中,平台实际上功能不难,都是对一些埋点事件和埋点属性的CRUD,但是关键的是与下游的协调。因为这个埋点管理平台是从旧有的数据分析平台的埋点模块拆分出来,因此要遵循旧有的数据规则,然而这个规则的不确定性和产品经理的需求变更,给这里埋下了不少的坑,这个问题带出来的思考是,很多东西还是要在开发前决定,而不要在后面填坑,以及开发前一定要对上下游进行沟通理解,而不要按照自己的理解去盲目开发。
手写sql和联表查询重构
在接手项目后,发现项目中的数据操作大量使用了手写sql进行数据库操作而不是采用mybatis-plus这种封装框架,这对后面维护来说是一个很大困扰,尤其是联表查询,在数据量大的时候,会造成页面卡死,加上耦合性强,不好维护;所以在接手后,我首先对大量的手写sql进行重构,将大量的联表查询拆分为单表查询,且采用mybatis-plus进行数据库操作,降低手写sql的风险以及增加代码的可维护性。
底层功能统一出口
埋点管理平台的添加事件和属性有页面添加和excel导入两种方式,之前两种添加记录的方式,分别维护了两种数据库操作,当分别开发时,这样的行为是没有问题,但是当需要统一升级时,这里就体现难维护的问题,当时需要在数据库操作后加入发送kafka消息的操作,就造成了一边加完了,但是另一边没加完的操作,引发了下游的问题,为了日后代码的可维护性和扩展性,这里必须改造成一个出口进行数据库操作,解决的方案是将数据库操作抽离成一个方法,然后对这个方法根据页面添加和excel导入两种场景进行封装,而最底层的数据操作始终执行那个方法,将操作指向唯一出口,这样做的话,无论场景怎么变,都只是对执行数据库操作之前的数据体进行加工,而不影响最后的数据库操作逻辑。
这次问题中得出的思考是同一样的功能不要重写,要尽量进行抽象,将最关键的操作指向一个方法中,以此减少日后维护的成本。
kafka发送代码耦合
在统一出口的问题中,有提到数据库操作后需要加入发送kafka消息的操作,一开始的设计是,每次数据库事务结束后都会发送一次kafka消息去通知下游元数据变更,但是这样做出现了几个问题,第一个问题是kafka消息过多,下游因难以及时消费而处于阻塞状态,第二个问题是kafka消息发送失败难以追踪,只能去kafka里面去对比offset,第三个问题代码耦合,导致kafka操作的代码出现过多,并没有进行统一出口处理,后期难维护。
针对以上问题对该逻辑进行重构得出了一个新的实现方案,首先为了减少kafka消息发送量,将每次事务后发送一次消息,抽象成crud的每种操作只发送一次消息,对于重复的循环操作,只记录最早的操作的时间,将这个时间带给下游,让下游根据这个时间去同步之后的元数据情况,而不需要每次操作同步一次,以此降低下游的消费压力;对于第二个问题和第三个问题,解决的方案是,引入event-bus中间件和新增一个kafka消息表去解决,首先让数据库操作作为生产者,每次数据库操作后,会在kafka消息表中新增一条记录,但是会留空里面的partition和offset字段,然后向eventbus中post一个事件,eventbus接收到那个事件后,会进行kafka消息的发送,然后将partition和offset update回kafka消息表中,以此完成这个过程,通过这个这种重构,将kafka消息收束在eventbus的消费者中,而不是散落的各个模块的数据库操作中,况且这个是异步的操作,更加减少了系统的负担,其次通过kafka消息表可以查询到哪条数据库操作记录未发送kafka消息。
通过这次重构,对复杂耗时的功能,可以进行异步解耦,还是要进行异步解耦,不要在一个方法内堆积大量操作,要尽量做到高内聚低耦合,对难以查询的记录,可以尝试维护一个表去记录,以减少查询的麻烦程度。
附件场景重构
这个问题也是上面问题的类似,这里简单说一下,当时接到需求,说要给埋点事件增加一个截图功能,一开始的做法也是直接在事件那个表加个路径就算了,其实这里我又犯了欠扩展性的错误,后来经过上级提醒,还是要建一个附件的表,进行解耦,事件表里面就存附件id就好,这样做的好处是以后附件需要改动或者迁移时的成本比较低,其次是文件可以使用md5校验,可以节省存储的空间。
配置下发缓存
在Android SDK那个章节提过,会需要访问配置下发接口去拉取配置,接手服务一开始发现,服务中使用的是hashmap进行缓存,但是hashmap去做缓存,在数据量大的情况下,会造成内存不足的问题,因此接手后,我将其缓存换成了guava缓存,后面可考虑升级为caffeine或者redis,可以进一步提升性能,不过这点没有什么好说,对于老司机都是基操了,但是值得提醒自己的是在开发时,时刻要有缓存的思想,尽量可以上缓存去提升接口的性能,但是在引用缓存时也要去做好同步和一致性的设计。
数据采集器组件开发(java)
在年底,上级让我接手了一个数据采集器的组件,这组件的作用是从本地文件或kafka中提取数据进行加工,然后发送数据给采集后台,至于这个组件,我并未做太多开发,但是根据观察以及和同事交流还是发现了几点问题值得思考,第一点是配置困难,每次组件配置,我都需要在对其配置文件作大量更改,这样做既辛苦也容易出错,感觉这个后面可以像sdk一样采用一串base64去进行初始化或者采用远程拉取的方式去配置,减少工作量,第二点就是部署混乱,没有管理,导致不同机子配置的版本不一,造成排查问题上的困难,第三点就是发送数据体冗余的问题,组件在发送数据体时会带上当前的发送进度,但是当发送进度的记录太多时,会出现进度数据比业务数据更多的情况,从而造成数据体过大,曾经20g的原始数据在发送后得到了500g的结果数据,这个问题年后还需要看看项目源码再考虑方案。
总结
总的来说,今年做的项目还是很杂的,后端做了Java和Go,客户端Android iOS和Flutter都接触了,不过在其中还是拓宽了自己很多的知识面,也明白到很多问题,要在实际的项目中才能体现,东西只有用起来才会发现问题,从而才会得到提升。其次,在实践中,也要加强自己对细节的把控,对工作中犯了的错误要记录,平时做事也要更加仔细,在交付前要多加检查,以及在开发新代码的时候也要注意其对旧代码的影响。再者,与客户的沟通中,就注意沟通方式和结果导向。最后,在一个负责数据部门中工作,自己还是要掌握一些大数据知识,不然对接的时候面对同事说的一些大数据组件和大数据术语还是会懵b,在2022要加强这方面的学习。