很多APP里的都有好友关系,那么一般都会有这样的需求,用户可以为自己的好友设置备注,类似微信里的“备注名”,这个功能很实用,很常见,常见到大家会下意识觉得这个功能好简单,实际上细想一下这个功能还挺伤脑筋的呢。

首先数据结构比较简单,好友关系其实就是多对多的用户关联,好友关系表里再加个备注字段就可以了。存储比较容易,难就难在怎么展示。备注信息假如只能在好友详细信息页面查看,对用户来说就太不实用了,产品经理会要求所有出现好友昵称的地方都要显示为备注名(谁让微信是这么做的呢),比如下面这些页面:


就单个页面来说,显示备注并不难,SQL关联查询一下即可,但是页面太多了,一大批接口要修改,尤其是涉及到好友信息的列表,都要带上当前用户id去查询好友的备注。有的列表本身查询就特别复杂了,就为了显示这个备注,还得再关联一张表,想想就恶心。代码咬咬牙就写出来了,但是关联太多表性能是个问题呀,怎么办?有这么几种方法可以解决:

1、服务端缓存

关联查表有性能问题的话,那就不查表了嘛,把备注信息保存在缓存里,user_id、friend_id简单拼接一下就可以作为key了,确保惟一就行,系统改造的时候写一个工具方法,按照user_id、friend_id把结果集里的昵称批量替换为备注。

这个方案,对原有接口改动比较小(相对于改SQL),性能增加微乎其微,列表数据一般都是分页的,数据量小,循环一下问题不大;代价是要增加一点缓存空间,redis、memcache都行,缓存更新机制也很清晰,修改备注的时候同步更新缓存即可。

2、客户端处理

客户端动态处理也是个不错的方案,本质上就是把上面方案里的缓存分布式存储到各个客户端里了。有的人觉得业务都应该交给后台,APP只需要做UI方面的处理,不喜欢这种方案。

方案实现不复杂,因为好友数肯定有上限,微信是5040个,大部分APP都没有这么多,完全可以在APP启动的时候同步好友信息。微信在首次登录的时候做全量更新,会弹出的进度条提示,以后都是做增量更新,没有进度条提示了。

微信正是因为本地存储了用户信息,所以才如此灵活的展示用户备注。我们可以很容易的测试,把手机断网后,修改好友备注,可以看到朋友圈点赞列表里的好友备注也随即改变了。客户端处理可以大大减小服务端的处理压力,很难想像如果微信点赞列表里的备注由服务端生成返回,微信后台架构会复杂到什么程度。

把部分数据处理放在客户端上做可以减轻服务端压力,降低服务端架构复杂度,但是有一个容易被忽视的难点,那就是数据一致性问题,如何保证客户端与服务端数据同步的可靠性。什么时候全量同步?什么时候增量同步?如果增量同步数据量大,要不要切换为全量?同步失败重试机制要怎么设计?又是一堆伤脑筋的问题。

考虑到这些问题,咱又得膜拜微信了,微信是个特别强大的联网的单机软件,微信的通信队列做得特别牛,一般的APP都是请求网络通信成功后修改UI数据,微信是本地处理,然后同步到服务端,比如有时进电梯发消息失败,出电梯网络恢复后微信会自动重发消息,从这点看,微信其实就是个非常复杂的网盘。

3、业务数据冗余

上面两种方案都是借助缓存,一个缓存在服务端,一个缓存在客户端。业务数据冗余其实也是缓存的思想,空间换时间嘛。业务数据冗余用来解决某些业务场景还是相当顺手的。比如上面第三张图片显示的预约列表,医生只能查看预约自己的患者列表,所以完全可以在保存预约记录的时候把用户的备注做为快照一起存储,查询的时候就不需要关联其他表了,直接显示冗余数据即可。

这种方案有个大问题是,如果用户备注修改了,校正历史数据比较麻烦。当然这个要看具体的需求了,有的时候历史数据会具有“快照”的特性,必须要显示当时的数据,那么这种情况下存储冗余数据就太合适了。

我记得以前的校内网就是这样做的,浏览好友的回复记录的时候,有时候翻着翻着发现好友的头像忽然变了,从某条回复开始往前的回复旁边显示的都是旧的头像,显然就是在回复表里冗余存储了回复人的头像信息,个人觉得这样搞用户体验不太好。但这确实是一种解决问题的方法。

总结

得益于社交软件的普及,好友备注被认为是个很简单的功能,但实际上并不简单。软件开发中会遇到很多这样看似简单,实际巨复杂的问题。有时候开发人员遇到这样问题会有点尴尬,觉得这么简单的问题不好意思问别人;有时候想问别人吧,又不知道该从哪问起,去搜索引擎搜索都不知道以什么关键词来搜。总觉得别人也会遇到这样的问题,但是就是找不到方案,好尴尬。问题解决的方法可能很多,我也就想出来这三个就黔驴技穷了,把文章写出来是想做个记录,启发自己思考,也希望看到文章的好朋友们给我指点一下迷津。

另外就是感慨,好友备注之所以觉得简单,还不是因为微信做得太好,太深入人心了么。只顾着照搬UI,不考虑底层架构,都是耍流氓。产品经理不了解这些情有可原,但开发人员要尽到自己的职责,把能做的事情做好,不能做的事情,大胆说出自己的理由。毕竟,理越辩越明,道越论越清。