分享嘉宾:李世昌 贝壳 资深工程师

编辑整理:申新兴

出品平台:DataFunTalk

导读: DMP是一个大家讨论已久的话题,尤其广告领域,是以DMP为基础来展开工作的。由于每个公司所面临的业务场景不同、问题不同,所以在具体落地时的做法也不尽相同。今天主要和大家分享贝壳如何进行DMP落地。

主要内容包括:

  • 贝壳为什么做DMP?
  • 贝壳DMP的整体设计
  • DMP平台效果
  • 本次总结

01 为什么做DMP?

从几个案例出发介绍

1. APP消息推送

DMP的数据与贝壳APP的推送系统整合后,可以做到千人前面来推送消息,避免用户收到的推送消息千篇一律、一模一样。比如,有用户偏好北京回龙观总价400万的两居室,那我们可以在推送文案以及文案的落地页上加入跟用户感兴趣房子相关的一些数据,这样用户点击的意愿就会被提高很多。

2. DSP广告

贝壳的推广有一部分是DSP广告,也即我们在刷百度信息流的时候会刷到贝壳的广告。最早的投放方式是基于城市维度,每个人看到的都是和自己所在城市相关,比如北京的用户只看到了北京的一些广告文案。但是和DMP结合之后,就会跟兴趣挂钩。比如你昨天浏览了一个小区,那么第二天系统推送的时候,就会把这个小区的文案放在推送内容里面,这样用户的点击率意愿就会提高很多。进而整个广告的CTR也提升了,大概提升五到十倍左右,效果还是非常不错的。

3. 站内推荐

APP的首页有三部分:第一部分是专属二手房源,属于导购页的一个分发,是一个导购专题,里面都是关于房子的一些专题信息,这些专题信息也是和用户兴趣挂钩的;第二部分是列表页,业务方会根据用户不同的兴趣给出不同的排版策略,排新房、排租房与用户兴趣相关,这里的策略就是依据DMP的数据做的;第三部分是整个房源列表,这块的展示也是基于DMP做到千人千面,把用户最感兴趣的一些房子第一时间展示给用户,吸引用户发生浏览、进而提升留存和转换。

4. 搜索

搜索,跟推荐类似,都是把用户最感兴趣的一些房源展示给用户,促进用户留存。

5. 潜客召回

我们通过一些数据分析会发现,用户跟经纪人产生联系之后,它后续转委托以及转带看的概率就会高很多。所以我们会实时去计算用户的数据,当用户的搜索、浏览房源、以及查看经纪人的相关信息等行为量达到一定程度后,我们计算认为他的行为足够丰富,这个时候就会做潜客召回,也即给用户弹一个框,引导用户去留资,留资完后就把其信息分发给经纪人,这样经纪人通过电话就可以与用户产生联系。

6. 商机引导

这是IM场景下的一个例子,就是当用户跟经纪人产生联系后,我们会把用户的画像数据推送给经纪人,经纪人可以直观地了解用户的偏好,方便其更好的去与客户进行沟通,如果沟通效果不错,则客户会留下手机号,之后顺带的就会产生一次委托,成为委托客。客户成为委托客后,经纪人就可以在委托客分析B端查看到客户更详细的一些信息,比如说最近的活跃状态,最近的一些行为数据,浏览了哪些小区,所浏览小区房价的变化趋势,还有客户喜欢什么时间在线上浏览、喜欢什么时间出来带看,这些信息可以辅助经纪人做好后续的约带看安排。

从上面这些案例可以看出,无论是站外的老客召回,还是站内的精细化运营,DMP都在发挥着非常重要的一些作用。

那么到底什么是DMP呢?

其实DMP就是把用户各种各样的数据,包括结构化数据以及非结构化数据,进行整合计算然后标签化,通过标签来描述刻画用户I,理解用户。比如,通过标签,我们解读到一个北京的用户,想看郑州的房子,喜欢400万的两居室。通过用户的标签,我们可以非常直观的了解用户。然后基于这些结构化的标签数据,我们也可以很方便地跟各个下游系统做对接,实现站外或者站内的精细化运营。

02 DMP的整体设计

1. DMP整体设计

贝壳DMP的整个架构设计,从下往上总共分成了五层:

① 最下面一层,也即第五层是数据收集层:收集层主要负责采集两类数据。第一类是用户在APP上的各种行为数据,比如搜索什么样的房子,浏览了什么样的房子,以及关注了什么样的房子等等。第二类是业务DB的各种线下数据,比如线下的带看、转委托等。这两类数据都会收集到hive里面。APP上用户的行为数据采集,通过系统罗盘来实现,这个系统是贝壳专门用来进行埋点管理和埋点数据收集的。

② 第四层是数据加工层:数据采集到仓库里面后,会对数据进行各种的加工,然后产生相应的主题宽表。比如针对用户,我们会建一张用户主题宽表,将用户所有线上线下的数据打通,然后将数据整合到宽表里,基于这个宽表,我们可以做相关的数据分析以及模型计算。最终产出人房客的基础标签数据。

③ 第三层是应用数据存储层:数据加工后产生的标签数据都是在hive里面的,大家知道hive其实是一个分析型的数据库,它的查询速度非常慢,是不能够支撑各种业务上高速查询的需求。所以我们需要一个应用存储层。目前对于存储,我们做了三种:第一种是Hbase,主要满足高并发场景下的KV高并发的查询;第二种是clickhouse,这是比较新的一种OLAP引擎,主要做SQL形式的人群圈包和人群的洞察;第三种是Mongo,在圈人群包之后,我们会将各种ID数据同步到Mongo,然后与业务系统对接满足业务上的查询需求。比如我们给用户推送消息时,push系统会把我们生产的人群包里面的设备数据拉走,然后按照设备给用户做推送,这里就会涉及到高稳定性高并发的分页查询。

④ 第二层是应用层:基于存储层,我们搭建了应用层,主要提供的功能如下:

  • 标签管理,主要支持把hive数据快速的导入到CK里面或者Hbase里面,以及快速把数据上线到标签层;
  • 标签集市,可以让大家快速的了解我们现在都有什么样的标签;
  • 人群圈选,支持以可视化的拖拽形式来自由的组合标签来圈选用户;
  • 人群洞察,在圈选人群包之后,可以通过人群洞察来看人群的构成是什么样子,比如地域分布、性别分布等等;
  • 人群拓展,这个功能在广告领域是用的比较多的一个功能,它可以通过一个少量的种子用户,然后扩展出一个海量的用户群体。

⑤ 最上面的一层就是API层:API层主要做的是一个统一的数据输出功能,且包含了鉴权、流控、容灾等各种控制。基于API层,我们可以对接各种业务系统,如推荐搜索、人群分析、push系统。另外,从数据层到API层,我们做了一个比较完整的监控报警功能,这样可以保证我们整个数据的可用性和API的高可用性。

2. 逐层介绍

接下来我们逐层看一下每层是怎么做的,以及遇到的问题和相应的解决方案。

① 数据加工层:

在数据加工层,我们一共产出4种数据,2份偏基础的数据-基础数据、行为数据,2份偏核心的数据-偏好数据、预测数据。

  • 第1种是基础数据,包括地理位置、APP相关、活跃相关。比如用户的常驻地,这个是根据IP解析获得,而工作地与居住地的商圈划分,则根据GPS在时间上的分配来确定,白天时间段多的定为工作地,晚上时间段多的定为居住地。比如用户是否安装APP,是IOS还是安卓,对应的版本是什么,使用习惯是什么,以及何时注册、何时激活、最后一次活跃的时间。
  • 第2种是行为数据,主要是统计一段时间内行为的累计情况,通过一些简单的统计分析就能得到这些数据。这些累计数据主要是围绕着贝壳整个的找房路径来就展开了,比如:用户进入APP后产生搜索、然后浏览房源、关注房源、……、一直到最后的成交。相应的数据有:近X天搜索次数、近X天浏览次数、近X天带看次数等。
  • 第3种是偏好数据,这块数据是最核心的一块数据,前面讲到的各种系统应用,其实都是偏好数据在发挥各种各样的作用。我们主要围绕用户对房子上的各种各样的偏好(如商圈、价格、面积等),通过一个公式来计算其偏好;这个公式是比较通用的,是通过行为次数乘以行为权重再乘以衰减因子然后得到一个得分,也即偏好得分。这里的行为就是围绕着找房路径的那些行为。不同行为的权重会分别不同,比如一个400电话可能相当于五次浏览,一次带看可能相当于十次浏览等,权重的初期值可以与业务方沟通确定一个,后期的话可以依赖算法的能力去计算。衰减因子主要用来刻画用户兴趣渐变的过程,用户的兴趣是不停变化的,用户越近的一些数据,行为权重可能会越高,越早远的一些数据,权重就会越低一些。
  • 第4种是预测类数据,这部分数据主要是通过一些机器学习、一些计算模型来获取的,用来阐述用户未来发生某种行为的概率。比如用户未来几天内产生商机的概率、产生委托、产生带看、成交的一些概率。前面说到的潜客召回,就利用了这里商机概率的数据。
  • 另外,除了以上用户角度的四份数据之外,我们还有经纪人的数据,有了用户数据,有了经纪人数据,我们就可以做各种匹配和使用,比如给用户推荐经纪人,比如经纪人任务系统打通,然后通过一个调度系统来调度经纪人以提高效率。

在建设这些数据的过程中,我们遇到了很多问题。比较核心和突出的问题有三个。

  • 第一个问题:用户数据如何做归一,这个问题是所有做DMP或者做用户数据绕不开的一个问题,比如一个用户有两个手机,一个IOS,一个安卓,安卓手机上链家 APP和贝壳APP,IOS手机上面安装了贝壳APP,如果没有做用户归一,我们可能会把这个用户识别成三个用户,这样刻画用户显然是不完整不精确的。我们的解决方式是:不同设备的按照手机号做归一、同一设备上不同APP的按照手机设备号IMEI归一,具体的实现方式是:通过Spark把用户所有的节点数据加载在一起,然后使用GraphX来去构建图关系,根据图关系的连通性生成用户的唯一实体。
  • 第二个问题:DMP的数据是用180天的数据来计算的,每次计算可能会涉及到上百亿的数据量,在这种海量的数据量下,我们如何保证数据产出的及时性?这里我们主要用了四种方法,第一种方法是列裁剪,主题宽表的字段非常多,但是标签计算只需要其中某些字段,因此我们基于宽表生成很多临时表,只把所需字段给裁剪出来;第二种方法是预聚合,每天我们都需要计算产生很多的行为统计数据,但没必要每次都从明细数据算,可以每天预聚合一次,通过每天的预聚合来合成所需要的统计数据;通过列裁剪和预聚合,整个数据量降低到原先的1/10到1/20,进而相应的数据计算量也减少了很多;第三种方法是增量计算,我们每天都是横跨近180天的数据来计算,采用增量计算来处理,每次把最新的加进来,把最老的给去掉,这样就没必要每次都计算180天的数据;第四种方法是集群资源隔离,最早的时候DMP任务使用公共队列,公共队列有个问题,就是如果新上一批任务,它的效率可能会非常差,然后整个队列任务会变得非常卡,DMP数据产出的稳定性就会受到影响,所以我们为DMP单独申请了一个独立的队列,这样产出及时性就得到了保障。
  • 第三个问题:50+的偏好标签如何在迭代中快递开发和上线。房屋的字段是非常多的,因为涉及到二手房、新房、房屋租赁等业务,而且每个业务线有其各自特点,所以标签数据就非常多。虽然数据很多,但是计算公式其实是相同的,也即指标是类似的,每次计算只是维度不同而已,比如有些是按照价格段维度计算,有些是按照商圈维度计算,因此我们可以实现配置化,通过一个数据库来去维护所有标签的维度信息,根据这些信息动态的生成SQL,然后通过Spark运行这些SQL来生成各种各样的标签数据,也即标签化。当标签计算要增加一种用户行为的话,通过配置化修改数据库的生成逻辑即可完成一次标签上线。

② 存储层:

在存储层我们面对的问题主要有四个。

  • 第一个是:亿级调用量下数据查询的稳定性如何保障?现在整个DMP调用量是非常大的,基本上用户在APP里每一次行为都会涉及到调用DMP,通过DMP来做业务决策和APP端的策略响应。
  • 第二个是:秒级人群预估。运营同学在做投放等运营时,需要预估计算人群的用户数量,通常的做法是不断建人群包来查看人群包的大小直到人群包的大小达到预期,这种做法效率非常低下。是否可以有人群预估的功能,支持通过拖拽的形式,快速的预估到人群包的大小。
  • 第三个是:分钟级的人群计算,我们每天大概需要计算200到500个左右的人群包,我们怎么去保障这些人群包可以尽快的被计算出来然后投入到业务使用中?
  • 第四个是:目前我们有1300+的标签,如何才能做到标签快速上线?

基于上述这些问题,我们首先看一下整体存储层的设计。

  • 第一部分:最左边是hive里的四份核心的数据,然后通过BulkLoad把数据导入到HBase,BulkLoad的导入方式能实现效率的最大化,同时可以避免性能上的开销;数据汇总到一起后,我们通过API的方式对外提供服务。这个地方我们做了几点优化,一个是为HBase申请了SSD的磁盘,HBase主要是支持海量数据的写入,数据查询的话,基本上都在几十毫秒到几百毫秒,无法满足我们五毫秒左右的查询时间需求,我们希望可以通过缩短查询时间,来给业务策略争取更多的时间;另一个是为API做了Redis预缓存,业务频繁调用的用户数据基本上是最近活跃的用户数据,所以我们每天通过Spark把hive里最近七天活跃的用户导入到Redis里做预缓存,同时也做了流控、容灾控制等来保障API的高可用。
  • 第二部分:通过Spark将hive里的数据导入到ClickHouse,然后再通过SQL的形式做圈人群包和人群洞察。最早的圈包是使用SQL来做,也即通过标签的拖拽,对应到标签的与或非,然后再翻译成ClickHouse的and操作或者or操作。但是ClickHouse对join支撑并不好,join的时候会带来一些问题,比如当标签非常复杂的时候,如20到50个标签组合圈选一个人群包,对应的SQL发送到ClickHouse,要么把ClickHouse查挂,要么查询非常慢而不能满足业务方的性能需求。这个地方我们通过引入Bitmap来解决,它是做人群圈选常用的一个技术。首先我们构建一个用户ID表,然后通过Spark加载用户的各种标签数据,加载之后通过各种维度来聚合,生成各种维度上的Bitmap数据,也即通过Spark生成ClickHouse底层所需要的Bitmap数据,其实ClickHouse底层Bitmap数据的存储结构是Roaring Bitmap,因此我们通过Spark生成的是Roaring Bitmap数据,再经过序列化之后会存储到ClickHouse里的Bitmap表里。举例说明下如何使用,比如性别有男女,我们会给男生生成一个Bitmap,给女生生成一个Bitmap,如果圈选用户时选择城市是北京、性别是男,我们只要把性别是男的Bitmap查出来,再把城市是北京的Bitmap查出来,然后做一次与运算,就能得到这个圈选人群了,而且通过bitmapCardinality函数可以很快得到人群的基数,这样就可以实现秒级的人群预估,这就是Bitmap的实现方式。但是这里有一个问题需要我们考虑,就是我们有各种各样的行为的数据是连续的,比如用户近几天浏览了多少套房子,对这种连续性数据,我们该怎么做呢?整个底层的实现的话,我们对浏览了8套房子的用户,会生成一个大于等于8的Bitmap进行存储,同时会生成大于等于7一直到大于等于0的Bitmap且一并存储。这样的话我们做标签与或非的时候,如果选择小于8的用户,我们就可以把大于等于0和大于等于7的用户做一个异或,然后得到小于7的用户群体。通过这种方式,我们就可以对应的翻译成异或的形式来实现各种大于、等于、小于的逻辑操作。利用Bitmap我们确实取得了一个非常大的进步,把人群包的圈选从原先的15分钟到20分钟,降低到现在的1分钟,整个人群包(200-500个)的构建时间也从原来的十几个小时压缩到现在的4-5个小时。

实时画像

另外,说一下实时画像。关于实时画像,大家都比较质疑,对于房产领域这种长周期的业务,到底有没有做实时画像的必要。在我们做数据分析的时候,发现用户生命周期是分几个阶段的,在早期的时候他的需求是不确定的,可能会随着时间的变化快速地发生变化,只有经过一段时间的沉淀之后,他的偏好才会稳定下来。如果没有实时画像的话,就无法捕获到用户前期兴趣的快速变化,在做差异化服务的时候效果就会打折扣,因此是有必要做实时画像的。具体的操作实践为:把线上的所有行为数据收集到kafka里面,线下所有的数据通过binlog的形式收集到kafka里面,然后通过行为聚合模块(也即Spark Streaming)消费kafka的数据,再加上房屋的各种数据,来统计汇总用户在各种各样行为上的次数。比如浏览了3次回龙观、浏览了5次400万以上的房等。基于这些次数,再结合kafka消费传递过来的用户信息,通过偏好计算模块和行为模块就可以得到用户的偏好数据。偏好数据的计算方式就是之前讲过的一个公式,用户行为次数乘以行为的权重再乘以时间衰减。表示偏好的得分数据最终存储到Redis里面,通过API对外提供服务。补充说明下,上述过程中的行为次数统计数据是存储在HBase中的,且汇总为小时级,每个小时会存一份数据,为了使偏好计算模块可以快速的查询数据,采用宽表的形式进行存储。实时画像主要应用在推荐和搜索,业务效果明显, CTR、CVR提升幅度在3%~10%+。

最后讲一下标签的快速上线,也就