设为首页 | 收藏本站欢迎来到卓越网络免费免备案CDN加速,DDoS和CC攻击防御,高防CDN管理平台!

已阅读

CDN加速中承载的POPPER的设计和优化

作者:cdnfine      来源:cdnfine      发布时间:2019-08-27

在2019年08月17日举办的 Gopher Meetup活动上,来自POP 团队的杨文进行了一场题为《 POPPER 的设计和优化》的演讲。杨文,POP后端负责人,TiDB contributor,GO夜读发起人,以下为演讲实录。

 

前言

大家下午好,首先感谢大家抽出时间来参加这次Go相关的分享。今天给大家带来的分享话题是《POPPER的设计和优化》。首先自我介绍一下,我叫杨文,现在在POP负责后端,我是一个开源爱好者,TiDB的Contributors之一,同时也是Go夜读发起人。在此之前可能有一些人已经了解过Go 夜读,今天也有一些 Go 夜读的小伙伴到场。这是我们的一个开源项目。

这个是今天分享的大纲,主要分为以下四个部分:

  • POPPER 是什么

  • POPPER 设计与实现

  • POPPER 性能优化

  • 总结

一.POPPER 是什么

POPPER 是什么呢?POPPER 其实就是一个头像,头像就是标识用户身份的一张图片,它跟账户名、别名联系在一起即可指代一个互联网用户。对于我们而言,我们每个人在网络上都是一个互联网用户,我们每个人都会有自己的一张头像照片,那我们希望这个头像是怎么样的呢?好看,然后是有个性、高逼格,能够凸显自己,即让这个头像有足够的辨识度,当然如果还能够加以生动、活泼、有趣、可爱等等特征就更好了。

应用场景 

接下来我将介绍一下我们 POPPER 的应用场景。大家先熟悉了解一下。首先是用户头像,这个就是我的一个用户头像,再一个就是专属的群头像,我们在 POPPER 里面是可以建群的,每个群会根据活跃度将最近活跃的 3 个群成员的用户头像合成一个群头像。同时你也可以给这个群设置不同的主题,可以自由选择。

还有一个核心应用场景是你的头像会随着聊天的内容自动地变换,这有两张截图,是分别应对 POPPER 里面不同的场景,这个是在群聊天里面,当输入文字“我爱你”时,大家可以看到用户的头像就会变成“我爱你”。然后就是吃东西,比方说输入“去吃寿司”,这个头像很形象进行相应的变化。当我们在拍 POPPER 也就是我们去摄像、去制作内容表达我们当时的心情时,我们的头像也会根据我们输入内容做一些变化。还有一个应用场景是在节假日或者特殊日期的活动定制,比如在高考或者其他考试期间,可能会被问到成绩怎么样;图中是我们当时春节做的一个场景,回家被问到成绩怎么样的头像;上面这个是万圣节,万圣节也会定制一些样式,大家可以去装扮自己,相信大家在万圣节期间经过深圳之窗地铁站的话就知道,在万圣节期间时不时地在地铁里面可以看到一些打扮奇异的人,当然我们还有很多其他的场景,比如说@好友,也包括一些H5页面或者小程序。

CDN加速中承载的POPPER的设计和优化

二. POPPER 设计与实现

刚刚看到很多 POPPER 的应用场景,那我们要去满足这些场景,我们头像需要怎么样去设计呢?首先需要考虑的一个问题就是:这应该由客户端实现还是服务器端实现。

今天分享的肯定是服务器端实现,那为什么要放在服务器端呢?大家也可以想一下这个问题,带着问题来看我们是怎么样思考和选择的。

灵活

第一点,灵活,可随时更新。刚刚我们就看到了,我们随时会针对一些热点事件推行一些活动,会时时更新我们的资源素材,这就需要很高的灵活性。刚刚可以看到在聊天那里,我们要怎么样去丰富我们的聊天内容,去渲染这个头像,其实是很能够体现这个灵活性要求的。下面这个是客户端的实现部分,在服务端去实现客户端肯定需要同步配置,我们叫Meta,里面有不同的模块。我们看一下这边的图,这个是我们产品的一个图,这是预览图,下面是模块,模块有不同的层次,比方说五官,还有基本部分,就是你的衣服什么的。下面就是一些组件,选组件去精装,这些组件就是代码中的 Items。客户端就是根据这样的配置展现出来的。然后这个出来的结果,大家看到一些图其实都是服务端给到客户端的,客户端只需要通过访问就可以渲染出来,看到的这些都是从服务端给的。

CDN加速中承载的POPPER的设计和优化

逻辑复杂

第二点,POPPER 头像的逻辑很复杂。以头像中的头发为例,头发可能分了好几层,大家可以形象理解,比方说我们一个女生的长头发可能会披到肩这里,肩前和肩后都可能有,可能还有不同造型,所以它会分层,这不是简单的一个头发放上去就可以了。绘制头像你要有一个画布,然后是基本部分,比方说身体、衣服,还有眼睛眉毛鼻子,还有胡子,女同胞可能还有一些腮红什么的。脸外有头发、眼镜,装饰(比方说帽子和头饰等)。这些东西怎么样摆放,才不会影响 POPPER 上面的头像呢?比如说不会出现错位或者前后顺序感觉很明显不对的那种,这个就是分层。

CDN加速中承载的POPPER的设计和优化

以下是服务器端的代码,就是刚刚我们看到的分层,大概是这样子的,这些代码是伪代码。首先会有预览图,预览图就是标识出它的尺寸,如果是预览图尺寸就小一点,还有就是描白边,白边的用途我简单说一下,当一个头像在一个背景里面怎么去区分,就是通过白边的方式去凸显你跟周围其他事物的融合,去通过白边去标识。然后就是性别,画图基本的组件背景,还有身体的各个组件部分。 

CDN加速中承载的POPPER的设计和优化

避免多端重复实现

第三点就是避免多端重复去实现渲染。如果是客户端做可能就是 Android、iOS、H5,都要去实现,而且 H5 实现效果也不太好;

我们看一下渲染的整个流程。这个开始是渲染流程了,现在的排版像是避免多端重复实现下面了。因为这个 POPPER 是跟用户相关的,每个用户有他自己的喜好,他们选择不同的样式,就会有不同的参数,所以我们要去渲染的话其实就是解析传递的参数,也就是 parse这个模块,还有我们会去准备渲染库。第二个就是预处理。刚刚我们看到有分层,每个层我们都 要去准备,这个大概会分为几类,这里列了三个,背景、五官、脸外分这些层次,还有可能会做更高层次的一些预准备。第三个就是当我们准备数据后,去渲染我们整个涂层,渲染可能会有刚刚提到的描白边,然后就根据我们前面所准备的一些属性元素去画这个图,然后转成PNG图片,整个流程下来就是用户选择的一些参数配置到服务器,服务器会经过这样的流程生成一张你所选择的图片,然后给到用户。这样复杂的逻辑就不用在所有的前端实现一遍了,能够有效地提升开发的速度。

CDN加速中承载的POPPER的设计和优化

可扩展性好

最后一点就是系统的可扩展性,后续可能有一些新功能加入到其中。

三. POPPER 性能优化

下一步的话会涉及到 POPPER 性能优化部分。第一个问题就是为什么会有性能问题?大家觉得会有性能问题吗?刚刚陈述了它应用场景下大概的设计实现,你们觉得会有性能问题吗?首先它肯定是有性能问题的,我来给大家粗略计算一下 POPPER 的数据规模。刚刚我们看到一个分层,大概有10个左右,其实后面还会有更多的细分,比如我刚刚说的头发,它可能会分好几层,目前应该是20以上的层次。然后就是数据量,每一层的数据量,我们以衣服作为例子,假如我们有270套衣服,衣服的颜色有54种,那我们要去组合的话会有1万多种情况,因为每一种情况都可以自由组合。每一层又会有很多种元素,那我们整个 POPPER 的数据是怎样的呢?粗略算了一下,1.37亿亿亿亿亿种,这已经是海量了,我们来看一下,它所占用的空间,如果每一个图都算高清图100KB。给大家一个参考,1BB,表示一千亿亿亿字节。其实这个空间是完全不能想象的。如果我们要完全去把用户选择的这些参数组合成 POPPER,组合成用户的头像,它的可能性是无限大的。

CDN加速中承载的POPPER的设计和优化

那么如果我们想要提升用户访问的速度,首先肯定要做缓存,如果做全量缓存的话肯定是不可能。那我们要做哪些优化来让用户的访问速度能尽量地快呢,接下来将我们目前采用的一些优化的工具和方案跟大家分享一下。

第一个是CDN,第二个是优化过程中使用到的 Profiling、Jaeger、SLS、Redis。首先讲CDN,我们做 AB 端产品应该都会有用户头像,其实大家方案都差不多,一般都是自己去选图片然后生成头像。即使是你去捏头像,捏完了之后也是一张固定的头像,和你选一个头像是一样的,然后你把它上传到 CDN,生成 URL 提供给用户访问。

接下来就是 Profiling,这是我们在用 Go 开发程序的时候会用到的,不作为重点去讲,可以看一下,Goland 提供这样一个工具,就是你写的 Benchmark,生成服务过程函数用来分析性能问题。这个是我们 Benchmark 的一部分,大家可以看到这里它占比较高的其实还是在image这块,而内存 Memory 方面,也是在 image 这块。刚刚看到的那个过程是在处理或者在生成图像的时候。

CDN加速中承载的POPPER的设计和优化

现在是 Jaeger,我们整个服务是有接入,这个是我们的一个基本配置,如果要引用 Jaeger的话基本上都是这样配的。

CDN加速中承载的POPPER的设计和优化

这里想给大家说一个点是,当时在线上有一次更新,我们把这边把 Sampler 直接删除了,当时觉得好像没有什么用就直接删了,代码也可以精简一点,但删掉之后,我们发现Jaeger上报好像日志没有了,我们就找原因,才发现其实Jaeger的源码中,当它没有配置的时候会赋予一个(remote),但是我们服务端其实是没配的,这就衍生出一个问题,当我们在去做变更的时候,不知道的东西还是要多看一下源代码,以防止出现问题,这是我们早期犯的一个错误。

CDN加速中承载的POPPER的设计和优化

下面我们看一下Jaeger在整个优化过程中的作用,帮助我们做了哪些事情。我们这里举一个例子,正常prepare时可能要13毫秒,当我们做完优化后,就只需要0.02ms了,其实这个优化也很简单,无非就是预处理,将资源文件预加载到内存,然后再将资源文件Decode成image头像,因为我们准备这些资源就是为了最后一步去处理成image对象使用。

CDN加速中承载的POPPER的设计和优化

在redis中这个地方我们用到了LRU策略,因为它比较简单。假设我们现在这里有图层,这是每一层,每一层它所需要的时间,如果每一次访问都需要的。这里有四层,大家看到的只有四层,其实我们刚刚说到了可能有20几层,可以想象这个耗时是怎样的。

CDN加速中承载的POPPER的设计和优化

这里的优化使用,当时我们也想了一些办法,也去调研要不要自己去实现一个缓存,当看到了Redis可以做LRU缓存,在当时来看其实还是比较适合的,Redis LRU缓存,或者里面的一些LRU策略,其实是可以满足很多场景的。我们很多的业务是可以直接用Redis去做缓存,如果你的缓存量比较大也可以考虑先用LRU,然后再加上自己业务层的一些策略,来保证你的服务性能。这个是它LRU缓存配置,下来是它的配置策略,有个LRU有个LFU,下面加了以后其实很明显,因为你用缓存,直接拿就很快。这是Redis的一个驱逐策略,大家有兴趣可以看一下。我这里就不念了,我们用到的是这个,简单来说你就是要用LRU缓存,但是你又不知道用哪个策略,你用它就可以了。 

CDN加速中承载的POPPER的设计和优化

这个是Redis一个统计监控,我们都知道Redis里面有info命令,info里面有基本元素的一些统计,我们可以基于这个去做我们需要的一些统计数据,这个时候它是偏Redis本体的,如果跟业务有关还得自己要去做一些统计。 

CDN加速中承载的POPPER的设计和优化

我们看这里,是一个比重,这是数量。这个监控整个其实很简单,就是三行命令就搞定。当然这个在线上你得要结合自己产品或者基础服务去做一些安全性的处理,但是如果自己要去测试的话,其实真的很简单,这个配置下载下来就可以看到这个数据。

我们在用的时候还遇到过一个问题,就是Redis宕机,刚刚看到这些内容,缓存32G,当我们用超了,就遇到几次宕机。我这里举个例子,简单说一下 Redis配置项。比方说我配了24G,这里有rss,就用到了29.49,这个是什么意思呢?操作系统为rss所分配的,也就是你指定的是24G,但是它远远超过24G。这个差额5.49GB是什么?碎片,其实就是说其它有一些是没有清理掉的,所以这个是导致我们Redis宕机的原因,因为我们作为一个缓存的服务,当时是没有是去做恢复处理的。

在我们使用LRU Cache,它其实是不稳定的,它会导致我们服务响应耗时,抖动会比较大。我们看两个监控图,右边是这个服务响应的耗时,可以看到波动很大,其实主要就是因为它比较低,去实时渲染肯定是比较耗时的。这两个图我们都是基于阿里SLS我们自己去统计、上报,然后上传出来这样一个图表。

CDN加速中承载的POPPER的设计和优化

然后看代码层,前面这里,我们这有个流程,可能会去做排序,然后去逐层渲染,因为你画的话,你不可能先把前面头发画好,你这个时候再去贴个脸,那你的头发就没有了,所以它是有一定顺序的,而且也是可以优化的,这是伪代码。我们可以对一些整个五官和我们的涂层去做分组,就哪一些是可以聚合一起,哪一些是不可以的,它的一个顺序去并发,然后渲染,就相当于我同时去处理某一组,把它渲染好了,再把这一组拿出来再去画,这样它的效率就会提高一些。但是这远远不够。因为太多了,刚刚我们看到数据量太大。

CDN加速中承载的POPPER的设计和优化

对于我们来说,现在素材量不是很大的情况下,数据量也是非常大的,那怎么办?只能不断优化,可能我们这个POPPER会一直优化,后面涉及到一些优化的话,它不可能把全量的POPPER去缓存,当然缓存是最好的,性能是最好的,但是这是不可能,我们必须考虑成本问题。那我们还有哪些优化呢?一方面是我们已经整整做的图层渲染优化,在后面可能会针对用户群体,把它们常用的一些头像去提前渲染去处理。

在这里也涉及到一些资料,大家看到也可以去学习一下,可以看看这个Bitmoji,它是国外的一个头像开源产品,我们也跟他们学习了一些。额外的一些补充,在图像处理这里,首先是矩阵,然后是gg图像处理库。

这一部分,今天不做展开讨论,后面的话可能会在Go夜读里面去做深入的分享,因为它涉及到源代码本体的一些阅读。

1. 使用 Redis 作为 LRU 缓存 http://www.searchdoc.cn/redis/redis.io/topics/lru-cache.com.coder114.cn.html

2. LRU Cache 学习 https://www.bcoder.top/2018/08/01/LRU-Catch%E5%AD%A6%E4%B9%A0/

3. Jaeger sampling https://www.jaegertracing.io/docs/1.12/sampling/

4. Bitmoji

总结

分层思想:这在我们整个设计里面是最重要的,如果没有分层或者说我们在做设计的时候没有把层次分好,那我们在渲染的时候是不好去聚拢的。

全局思维:为什么要选择服务器端去做,这是全局思维,为我们当前场景的优化以及后续场景的增加做准备。

合适的才是最好的:为什么这么说呢?刚开始我们做缓存调研的时候,也想过要不要自己实现,但是考虑到Redis已经足够好,适合我们当前的应用场景,虽然说现在容量不太够,但那是另外一个问题了。

Keywords: 免费CDN加速 免备案CDN加速 高防CDN加速