最近开发app后台的一些小结

最近负责一个app产品后台的开发,主要包括app所有rest数据接口层的开发、第三方接口数据的融合过滤、第三方应用的接入(比如IM聊天、群组、电话虚拟拨号等等)、系统后台的管理配置等等,通过这个app后台的开发,也踩过不少坑,挖过不少雷,下面主要做一下小结:

一、产品与技术是彼此成就

产品初期设计一个产品时一般都会描述这个产品的愿景(定位、受众、解决的痛点、达到的目标),所以起初这些描述性概念对应开发来讲就感觉毫无意义,然而开发过程中也必然会出现很多细节问题,当然也会扯皮,比如开发会质问产品怎么会设计这么鸡肋的功能,产品也会反驳各种说辞,总之有点各不相让的势头;主要是由于彼此的关注点不同,所以考虑的问题的角度也不同;后来大家都转变了一下思路,产品描述功能时,沉下心去聆听;因为产品设计一个功能肯定是做了充分的分析的(比如竞品分析、用户习惯、功能特性等等);并且在产品绩效考核上面也要做到目标一致,比如产品月活人数达到什么量级,绩效是什么;性能达到什么量级,绩效是什么等等;总之来讲产品和技术是彼此成就的,需要彼此摆正心态,大家拧成一股绳,荣辱与共,一起把这个产品做好,一起体现每个人不同的价值观(升职加薪、精神愉悦);

二、设计初期UI和UE一定要思路统一

产品在设计一个app产品时一般都会画出原型(简单的界面展现+完善的交互指引),为什么说需要完善的交互指引呢,因为后台开发可以根据每一个交互场景定义不同的数据封装基类,是使用继承还是使用组合等等都是根据交互进行定义的;项目评审会完成后,一般UI和UE就会着手开始具体的设计工作,两者一定要紧密配合,不然到后期容易出现各种细节问题,比如app中常见的图文数据列表页面,UI会设计好具体的界面,但是如果UE不及时参与的话,UI可能就会漏掉当后台接口无数据返回时的交互;当在开发阶段才发现这样问题的话,可能就会浪费时间;

三、各端的开发都要详细的参照UI进行开发

开发阶段,需要非常仔细的看看UI图,因为所有的功能点都在UI图上面体现,包括开发完成后,测试部门编写的测试用例都是基于UI图进行的;并且在看UI图的时候,需要抽取最小单元的数据集,然后定义各自的数据封装类,这些类就是rest接口的属性体现。所以在开发之前,先仔细分析UI图,可以避免大量的冗余类产生。

四、开发时多替客户端着想

我们在开发rest接口时,一定要充分的减轻客户端的工作量,要知道客户端展示不像平时开发的web界面展示那样,平时web界面展示数据时,页面中可能很多循环、逻辑判定等等;但是客户端则完全不能这么搞,不然客户端性能会非常的慢,并且代码会非常臃肿,后期难以维护,所以我们在提供rest接口时,后台能多做点事情就把它做了,比如一个app的ui上面,需要展示学生答题正确率;我们后台在提供接口时,就直接把这个正确率计算好返回到前端显示就行了,不要再给客户端返回一个分子、分母让客户端自己计算,不然客户端还要判断分母是否为0,还要处理小数点后面的位数等等;

还比如一些状态标示,有些图文内容点击阅读后,可能需要显示已读、未读标示,那么后台也直接把状态值和状态值对应的title在接口中返回就行了;这样客户端拿到接口结果集时只需要直接显示即可,不会做太多的逻辑判定;这样做完一个app产品下来,客户端的小伙伴会非常愿意给你配合,因为很多事情我们后台都帮他们做了。

五、效率 效率 效率 -缓存

app不像web开发,打开一个浏览器如果响应慢的情况下,可以再刷新一下,或者看其他的内容,在app端完全不是这样的,app端如有打开一下页面时,如果一直loading状态,可能会跟用户的感觉就是这个卡顿了,所以体验非常不好,要么app自动接收loading(一般都有一个超时值),并给出提示,或者迫不得已,用户自己通过杀进程的方式把app进程给kill掉。这样是体验极差的,所以当app调用rest接口向后台请求数据时,一定要非常快的把数据返回给客户端。

所以后台接口层面的数据调用能从缓存中获取就从缓存中获取,尽量不要直接读库操作(不管你是mysql也好,mongodb也好),不然会非常影响效率;当然缓存也不要单点的支撑整个系统,需要分布式的去支撑整个系统,比如采用codis等等;还有就是不可能把整个app的后台数据都存入缓存,所以把什么数据存入缓存,多长时间失效等等都需要根据不同的业务功能场景进行设定和平衡。

六、效率 效率 效率 -异步

同一个接口可能调用很多数据源,比如常见的app首页,这样的情况下面,效率可想而知,所以在已经使用缓存的基础上,采用异步线程机制,每个请求都采用异步加载的方式去获取数据并且更新缓存,异步加载也可以通过多线程、消息队列去实现;当然那些操作是可以采用异步,哪些操作是不能采用异步是有本质区别的,比如用户的收藏行为,这就可以在后台采用异步处理操作;登录操作肯定是不能采用异步处理的;所以我们需要区分场景和业务,并且异步也可能会出现数据丢失、数据不一致的风险。

七、DB层面的优化

之前问一个搞DBA的同学,如何才能让数据库查询跟快,他说了一句很NB的话,无论怎么优化都不如换上SSD!确实是很高端的感悟!这句话我们平时做开发的可能就不那么好理解,因为考虑问题的角度不同,比如某某单位有10个窗口,同时有100个人去排队办理业务;如果让DBA去优化可能就说再开10个窗口,而让开发去优化可能就会说将业务分类办理,一个窗口只办理同一类的业务(读写分类的概念)等等。

所以回归到app后台开发上面来讲,在DB层,如果访问量很大时,一定要先做读写分离,一般是通过多数据源去实现;做完读写分离后,如果基础数据量也很大时,一定要分表操作,但是分表根据什么维度去分,比如用户类型、用户所属地区码等等因素,一定要慎重去操作,不然后期容易发生跨表查询的问题。

app端的后台开发一般都是单表操作,不会太过分追求数据库设计三范式规范,所以适当的冗余设计表结构也将会带来不错的性能提升。

八、集群环境下的定时任务

一般服务器都是集群部署,肯定也有定时任务,所以一定要避免集群环境下面多定时任务重复执行的问题,我在这块可是栽过跟头,我们的使用场景是每天晚上10点中统计数据,然后发送IM消息到APP端,但是由于集群环境,所以到10点钟时,每台服务器上面的定时器都开始执行,所以app也就收到了多条重复数据,后来通过缓存加锁得以解决。

九、rest接口的版本

针对app接口的开发不可避免的就是版本问题,我们采用的策略就是通过接口path路径区分版本号,期初我们采用的是app端调用接口时,需要在header中传入版本号,但是后来发现当每一次发版,这个接口的业务都会变化的时候,简直就是一个噩梦,因为你需要在原来的接口方法中做很多适配操作,并且还要兼容老版本的app请求访问,一旦出现什么问题,排查起来将非常困难;所以后来我们就采用了path路径的方式区分适配不同版本的接口调用;一般rest层内部的service业务方法要最小粒度的实现其功能,这样的话,针对新版本的rest接口,我们只需要组合不同的service业务方法即可。

rest接口的兼容性要考虑周到,在开发rest接口时要站在产品的角度去考虑这个接口能否充分满足他们的要求,比如我们开发一个图文列表数据接口,用户点击某一条图文内容后,该条图文就是已读标识,那么当这个产品上线后,产品突然发现如果用户点击某条图文后,在回到列表时,应该把用户已经看过的那条图文给不显示;所以如果当时设计后台rest接口时,入参中刚好有一个状态参数,默认查询所有的,如果此时要满足看后消失的需求时,我们只需要在后台把这个接口的入参默认值改成未读状态就行了;改完后后台发一次版就ok了,这样的功能改动对APP端是无感知的,也避免了APP端的发版升级。

十、一定要知道别人对你做了什么

平时开发中一定要多打log,多埋点;不然当出现什么问题的时候,我们压根都无从查起,非常被动,因为你不知道用户执行了什么操作,产生了什么bug,这个bug是什么操作导致的等等;如果有条件的话就把ELK搭建上。如果在高大上一点就引入第三方专业做系统监控产品的公司。