Wednesday, December 2, 2015

DSP System - 竞价服务的预算控制模块



http://www.zenlife.tk/bid-budget-control.md
广告实时竞价这边,出价是要涉及到钱的。客户在我们这边建一个广告投放活动。这个活动有预算,如果预算用完了,那么这个活动就不应该继续参与竞价了。预算控制是竞价服务其中一个模块。
那么现在存在什么问题呢?根本控制不住!预算会超!这让我们很不好跟客户交待。
为什么会超呢?活动预算相关的数据是在另一个数据库中,从数据库到竞价服务,数据是有延迟的。竞价服务这边并不知道某活动的预算已经用完了,继续出价。由于竞价请求的QPS很高,稍微一点点的延迟,就超预算了。
这个问题还要从架构演化谈起。
据说起初(我还没来这里之前)竞价服务是每次去活动数据库中取数据。这样数据是一致的,但是竞价服务对响应时间要求非常高,取一条数据要去访问数据库一次数据库根本没法玩,即使是redis也不行。
活动这一部分的数据量并不大,所以后来就在竞价进程内部做了个活动数据库,然后做一个dispatch服务负责周期性地从数据库同步到竞价进程内,相当于是做一个本地缓存。
预算数值变化的流程是这样的:
  • 由dispatch将活动数据同步到竞价进程中
  • 预算没超,竞价进程让活动参与竞价(读)
  • 获胜通知的进程与数据库交互,修改预算(写)
  • 如此反复
这里有几个点需要注意。一个是数据流动是异步的,修改预算并不是由竞价进程执行,竞得后的处理是其它进程。另一个是竞价进程是分布的,有许许多多进程。我们要尽量的让竞价进程是"无状态"的,它是一个计算型服务,性能方面要求很高。前面有ngnix做负载均衡,把竞价请求转发到各个竞价进程中。
这个流程,数据延迟不可避免,于是预算控制就控不住了。
说到底,这是一个性能跟一致性的矛盾,为了竞价进程的性能,必须本地缓存数据。本地缓存数据之后,每个竞价进程不知道其它竞价进程对数据的修改。另外,反馈也是异步的过程,最终竞价进程拿到的都是非真实的预算在做决策。
说一说解决,只是提下思路。
如果是从业务层面想办法,可以用一个概率算法,在快要接近预算耗尽时,就降低此活动出价的概率,这样子速度慢下来,就可以缓解预算不会超得太多。
架构层想办法,现在的同步是由dispatch周期性地拉。如果能改成一个主动推,实时性就会提高。获胜通知那边,如果发现预算已经不多了,除了写数据库,同时还向竞价服务推更新消息。
http://www.zenlife.tk/%E5%86%8D%E7%9C%8B%E9%A2%84%E7%AE%97%E6%8E%A7%E5%88%B6%E6%A8%A1%E5%9D%97.md
这几天看了一下redis集群,老大让看看把这部分数据迁到集群中。一看吓一跳:算钱相关的,居然redis用作存储,这胆子也是够肥的!
数据量其实很小,不过之前还是业务层做的hash分到多个redis实例中的,但是没有主从。如果redis挂了,预算就完全不受控,然后就没法跟客户交待了。
首先明确一点:redis是一个AP的系统,不保证C,而且...是会丢数据的。如果切换到redis集群可以解决的是容错和可用性问题。但是不会解决的是一致性以及数据不丢。
redis为什么会丢数据?从单机的角度,写aof是周期性的,只有fsync刷盘之后,数据才是真正的持久化了,期间进程挂掉或者突然断什么等意外都可能导致数据丢失。升到集群之后,由于是先回复请求,再异步写副本的,数据也会丢。比如说master回复client写ok,然后master挂了,数据还没有写到slave。接着slave提升为master,这次写就丢失了。
对redis集群做了一个简单的测试,跟单个实例相比,读的性能基本上没有损失。但是写操作性能降低很大。
因为要多一份异步写副本操作,平均到每个实例的处理能力下降了好多。比如在我们内网机器上测试单实例的set操作,不开pipeline,无slave,大概是7-8w的qps。测试3主3从的redis集群,整集群的吞吐量大概才12w,平均到每个实例不到4w了。3个master的CPU跑满之后,slave的CPU大概在40~50%的样子。
回头再看我们的业务,每投出一次广告,都要修改预算数据。而读是周期性(比如10秒)由dispatch读出,再走消息队列同步到竞价服务那边。所以,这是一个写远高于读场景。
总结一下,换到集群是满足了可用性。但是一致性方面,不适合算钱的业务。另外业务模型上面主要是写操作对redis集群也不算啥优势,虽然以现在的的压力完全没任何问题。做肯定是能做,不过这里并不是一个比较适合redis集群的场景。
http://www.zenlife.tk/dsp-challenge.md
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。无疑会造成响应时间波动。
cookie map的存储是另一块难点。AdExchange那边会有一个用户cookie,而我们这边也会有一个用户cookie,cookie map做的就是一个映射关系把两者联系起来。这样就可以通过AdExchange那边的cookie,就查到我们的用户cookie,进而通过这个cookie查询用户特征数据库,继而进行更精准的广告投放。这中间涉及了两次的数据库查询。

竞价流程中还会有一个频次控制会查一次数据库,所以每一次请求会有三次数据库查询。如果Go的垃圾回收有5ms左右的影响(实际上如果对象过多以后远不止),要求系统处理时间10ms以内,那么留给一次查询时间就要在1ms。
这里引入两个问题,每一个是redis集群的管理,redis本身的分布式是做得很差的。另一个是存储量,全内存肯定是吃不消的,然而我们的使用场景又对响应时间和tps要求特别高。有没有什么好的方案呢?SSD也许是一个方向


Labels

Review (572) System Design (334) System Design - Review (198) Java (189) Coding (75) Interview-System Design (65) Interview (63) Book Notes (59) Coding - Review (59) to-do (45) Linux (43) Knowledge (39) Interview-Java (35) Knowledge - Review (32) Database (31) Design Patterns (31) Big Data (29) Product Architecture (28) MultiThread (27) Soft Skills (27) Concurrency (26) Cracking Code Interview (26) Miscs (25) Distributed (24) OOD Design (24) Google (23) Career (22) Interview - Review (21) Java - Code (21) Operating System (21) Interview Q&A (20) System Design - Practice (20) Tips (19) Algorithm (17) Company - Facebook (17) Security (17) How to Ace Interview (16) Brain Teaser (14) Linux - Shell (14) Redis (14) Testing (14) Tools (14) Code Quality (13) Search (13) Spark (13) Spring (13) Company - LinkedIn (12) How to (12) Interview-Database (12) Interview-Operating System (12) Solr (12) Architecture Principles (11) Resource (10) Amazon (9) Cache (9) Git (9) Interview - MultiThread (9) Scalability (9) Trouble Shooting (9) Web Dev (9) Architecture Model (8) Better Programmer (8) Cassandra (8) Company - Uber (8) Java67 (8) Math (8) OO Design principles (8) SOLID (8) Design (7) Interview Corner (7) JVM (7) Java Basics (7) Kafka (7) Mac (7) Machine Learning (7) NoSQL (7) C++ (6) Chrome (6) File System (6) Highscalability (6) How to Better (6) Network (6) Restful (6) CareerCup (5) Code Review (5) Hash (5) How to Interview (5) JDK Source Code (5) JavaScript (5) Leetcode (5) Must Known (5) Python (5)

Popular Posts