聊下DSP的一些技术挑战

2014-08-23

早想说下这个话题的,一直没写。直到今天老大做了个分享讲了下公司的产品线以及目前的一些技术挑战,趁热写下吧~

DSP,全称是Demand Side Platform,即需求方平台。虽然我喜欢开玩笑称DSP是比ASP高端一点点的东西,但其实完全不是一个东西啦。我们的客户是广告主,即那些想投放广告的人。

但是我们并没有广告位,我们是参与竞价,购买广告位曝光机会,然后帮客户将广告放出去。你们看到的那些讨厌的小广告,就是我们帮客户放的啦...

这里又引出两个名词,一个是AdExchange,即广告交易中心。另一个是RTB,实时竞价。这是一种新兴的广告模式,AdExchange那边掌握着广告位曝光机会,用户打开网页,但是看到什么样的广告,目前是未知的。广告交易中心就是拿着这次广告展示的机会去拍卖。每一次的用户访问广告位,就会产生一条竞价请求。AdExchange会将这条竞价请求发给所有接入的DSP,然后各个DSP会出价,购买这次的广告位曝光机会。谁出价高,说就可以抢到,最终将自己的广告展示给用户。

我认为DSP最核心的部分,一块是算法,另一块是RTB。算法重要性不言而喻,实现的是精准广告的基础。如果我知道用户信息,就可以知道为他展示什么的广告。比如,某用户昨天刚在淘宝上看过某首饰,那么就为他推荐首饰广告(所以那些总是弹出很黄很暴力的广告的,你们可以去面壁了,你看你都访问了些什么)。至于RTB这边,就是系统性能方面的一些挑战了。

用户每次打开带广告的网页,就会产生竞价请求过来,到达我们系统。想想同时有多少人在上网,就知道这边的请求量有多么恐怖。我们每天要处理70-100亿的请求数。如果你没什么感性的认识,可以类比一下抢火车票。N多用户同时进来,然后系统就挂了。但是我们又有些不同的地方,首先,我们流量是一直都很大的,不是说某个时刻秒杀一下。其次,我们对响应要求很苛刻。不像秒杀场景,顶多排个队,用户感觉慢就慢呗,实在处理不过来的时候直接给404,只要保证一致性就好了,反正系统不出错,不挂掉,用户体验不爽就骂呗,像12306被骂着也习惯了。

一般AdExchange竞价请求后,要求DSP在120ms以内给予回复,进行出价。如果超时了,就判定默认不出价。如果经常超时,说明这个系统做的烂,那么AdExchange就会对它降级,给的流量就少,DSP就赚不到钱咯。所以实时竞价这一块是DSP关键之一。说到这个120ms,是指从AdExchange发出消息,到AdExchange收到DSP返回的消息,那么中间还有一次网络来回的开销。为了在网络抖动的情况下也能成功竞价,那么留给DSP的实际处理时间在大概30ms以内。

响应时间波动是很难应对的,因为各种因素累积起来,比如跑着的另外的进行占用CPU过高了,影响了竞价服务的进程。比如某个周期性的任务占了网络IO,这些是无法控制的。实际上目前我们现在的系统平均处理时间大概在10ms以内,这样在发生波动也能尽量保证不超时。亮出秘密武器:我们系统用Go语言写的。Go语言很好用,不用花多少力气就可以轻松发挥出多核CPU的优势,并且在网络处理方面做得非常好。但是这并不是免费的午餐,Go会有垃圾回收,这时会Stop the world。无疑会造成响应时间波动。

请求量不是最高峰时刻,可能都会有15w/s以上。我们有很多的竞价进程,单个进程处理峰值大概在2000qps,每个机器跑了4个进程,单机大概6000-8000qps。那么可以算一下,我们有100多个进程,如何在分布式环境里把这些进程管理好,系统设计上面以及运维方面都有很多工作可以做。

上次跟一个网友聊,说这些数据,他好像嗤之以鼻的样子,2000qps好像很低,redis单线程都有5-6w的tps,我只能呵呵了。为什么呢?这并不是一个来个请求,查次redis,然后返回的服务。竞价服务是一个计算密集型的,中间涉及到很多计算处理。当一个请求过来,我们需要查当前的活动数据库,每一条记录是一个活动。活动下面包括产品,产品下面是创意包,创意包下是创意。我们要从这些活动中进行筛选,会有很多过滤条件。比如根据IP过滤,根据区域过滤,有些活动是有时段的,会有时间上的过滤。然后会有广告类型,有黑名单白名单。每一步都是有计算开销的,不要小看这些,比如浏览器类型过滤,会需要根据user agent字段解析出浏览器类型,这中间的字符串处理就很耗时。在我刚来公司的时候还没优化之前,观察到的峰值时刻的单进程CPU使用率甚至超过700%,跑满7个核。所以如何降低CPU占用是这边的很有趣的技术挑战之一。

cookie map的存储是另一块难点。AdExchange那边会有一个用户cookie,而我们这边也会有一个用户cookie,cookie map做的就是一个映射关系把两者联系起来。这样就可以通过AdExchange那边的cookie,就查到我们的用户cookie,进而通过这个cookie查询用户特征数据库,继而进行更精准的广告投放。这中间涉及了两次的数据库查询。另外,竞价流程中还会有一个频次控制会查一次数据库,所以每一次请求会有三次数据库查询。如果Go的垃圾回收有5ms左右的影响(实际上如果对象过多以后远不止),要求系统处理时间10ms以内,那么留给一次查询时间就要在1ms。

也许有人并不了解数据库响应时间1ms是什么概念,那么我说下数据。redis不穿越网络,在本机环境下,无压力条件下测试,读大概在800us,写大概在900us。而如果在高负载下,78.3%能在3ms内响应,98.7%能在4ms内响应。因为是竞价服务这种特殊场景,所以对数据库的要求是低延时,高tps。目前我们用的redis,全内存的,加机器硬扛的。但是有个很变态的地方:数据量。如果做全网cookie,数据记录大概30亿条,存储空间400-500G。这只是对一个渠道。单个redis大概10多G,每个库会有若干个redis节点,整个cookie map集群将会T级的,内存!!!

这里引入两个问题,每一个是redis集群的管理,redis本身的分布式是做得很差的。另一个是存储量,全内存肯定是吃不消的,然而我们的使用场景又对响应时间和tps要求特别高。有没有什么好的方案呢?SSD也许是一个方向。今天先写到这里了,下次可能会分析下这方面的内容。

DSP