微信号:infoqchina

介绍:有内容的技术社区媒体

铁面“网警”——知乎反作弊系统悟空演变之路

2016-05-20 08:00 黄典典
本文是知乎反作弊系统悟空设计者黄典典在QCon 2016 北京站上的分享整理。速记整理很不容易,错漏也会有不少,请大家不要打小Q……
黑色产业有哪些?

反作弊对应的一个产业就叫黑色产业,从我开始做这个工作到现在为止包括以下几点:第一,诈骗类,互联网诈骗现在很流行。第二,薅羊毛类,做电商大家都比较清楚,每次有什么促销或者折扣的时候,好的东西到不了用户手里,大多被“羊毛党”刷到自己手上,然后通过其它途径达到自己获利的目标。

第三推广类,每个网站都会有私信、公共流域流通区域,同时每天网上又有成千上万的产品,或者什么信息需要推广出去。这些人会借助已经有的平台和流量把信息给无节制发布到用户眼钱,然后对用户形成一定的影响。推广类可能是正常推广应用和网站,还可能推广黄色信息,色情的信息,引诱大家做奇怪的事情。第四类骚扰类,大家在平台上收到过约会的私信。

黑产在知乎的表现是什么?

那么在知乎会遇到哪些问题呢?因为知乎并不是电商,所以羊毛比较少一些。

主要包含几点,第一色情信息,可能发布到回答,发布到提问,发布到评论,甚至点对点推送到私信里。第二产品推新,知乎搜索引擎权重还是比较高的,用户量也在逐渐上升,会有很多的产品在上面给用户发私信,或者通过提问回答的方式宣传自己的产品。

第三骚扰私信,只要有人的地方都会存在的问题。第四合伙刷赞,就是要推广一个东西先自己提问或者回答,把回答发上去,让我的同伙给我点赞,然后这些信息是真是假无从知道,但是会获得很多赞,很可能排在前面。

怎样揪出坏人?

基于以上的几种情况接下来要做的事情,怎么把坏人找出来呢?

首先举一个例子,这个例子说过很多次了。挑选苹果,某一天下班走在路上突然感觉想吃苹果,走到水果摊的时候会想要不要买,你怎么决定要不要买?会看这个苹果成色是不是红的,其次看圆不圆,考虑水果摊离你家有多远,拿回去重不重,最后算出来买还是不买。

要找坏人跟这件事情很相似,首先得找到坏人所有的维度,可能他的ID是不可信誉的ID,UA跟其它用户UA不一样。通过加权可以算出来这个人,或者这次行为是正常还是不正常的。但是不是有更多维度就可以了?我刚讲的几点维度就算是很简单的维度,但是存在的问题是很容易被模仿欺骗。随便换个邮箱域是很简单的,做出来域名几美元的成本。

所以要找到更多有用的维度,就是需要一定的门槛才能模仿或者造成的维度,比如键盘的轨迹,鼠标的轨迹,或者甚至有摄像头的摄像头的数据之类的。定位到你是谁,或者你拥有某一个东西的维度。

但这样的维度通常会有问题,存在获取成本很高,如果所有工作都基于这样维度做的话,可能绝大部分精力都会花在这些维度的采集上,并且这些维度易错性也挺拔一高。

最后一点迷惑你的对手。大家经常会觉得这行是敌暗我明的情况,但实际上并不是。通过访问你的网站产生他自己不知道的数据,他的一些行为模式,甚至他可能自己也不会在意,所以我觉得是敌明我暗的情况。除了更有用的维度以外,还可以做的事情通过很多种低门槛的维度的组合,通过组合的套路识别出你的对手,然后找到你想要找的坏人。

知乎反作弊系统悟空


悟空是我们的反作弊系统,最开始不叫悟空,那时候所面对的情况网站刚上线,流量并不大,有一个邀请码发给你注册访问,解决问题就很简单了。

悟空 V0 


首先数据量少,第二坏人本身也不多,第三形式也比较简单,可能就是简单的文本信息或者简单推广信息。这个时候所对应解决问题的流程也很简单,数据通过知乎组站或者API,通过日志系统,我们之前开源过一款日志系统。通过把日志拿到以后,通过日志名基本的维度,用户的ID、IP、UA,一些浏览器的头进行简单的规则组合,一分钟之内IP访问多少次,一分钟之内这个内容命中多少次关键词,通过简单的串行的处理,标注这个内容或者这个人是不是垃圾,如果是就干掉,如果不是就放过去。

这种简单优美的结构能解决的问题也很有限,随着业务不断发展,流量不断升高,同时我们所面对的人又越来越复杂,不得已进入V1时代。

悟空 V1


这是一张V1时代的概览图,后面我会针对每个部分进行简单的描述。我们中间分成了几个部分,第一个这边还是把整个主站和正常访问流程单独画了,用户请求会通过在线可以计算简单的规则和策略进行简单判断,IP的策略,命中关键词与否,甚至简单的对比。进行简单对比之后会通过内部的消息系统进入到离线模块,第一个模块把接收过来的消息进行协议的解析,解析成标准的格式。这是任务队列,解析完以后的任务会被放在任务队列里去,后面是N多个消费者执行更复杂或者数据量更大的任务。计算完以后会落地到不同的存储里去,这张图大致概览是这样。


第一个模块从消息进行格式化的解析,我本来这边贴了代码上去,但是我换成了字,比如谁,什么时间,什么地点,做了什么事情,产生了什么内容,对谁。写作文一样,描述一个事情基本也就从这些维度,第一个模块做的事情也就这样。把接收消息以后按照这个格式做解析,相当于从消息转变成任务,然后相对应放到后面任务队列里去。


放到任务队列里面去以后会有无数个猎人进行消费,主要做哪几件事情?

第一特征拓展,我讲的原则是有了维度不错,但是需要更多的有用的维度。第二部分规则描述强化表达能力,规则描述强化表达能力,意识就是这些消息不大可能来了一条针对每种场景,针对每条策略把所有的策略写在代码里,每次一变就改代码上线,那样的话整个成本和变更起来的效率都会很低。所以就要对外暴露一套规则,规则可用维度,把维度进行组合,类似于做了策略语言的东西,强化它的表达能力,这样的话每次有业务变更只需要改变策略预值。

策略管理平滑上下线,这里是跟坏人对抗的问题,在对抗或者变化过程中一旦出现问题以后可能希望你这个策略改完以后能够立刻上下线,不需要系统重启或者停机,我们通过单独的系统专门管理策略的模块,每次策略更新以后会把更新通到这个模块里去,从这里拿到最新的策略。  
STORAGE是缓存,将关键RPC结果缓存到redis中。为什么这里面会加上缓存呢?因为这个业务场景里对于数据的实时性要求和一致性要求并不那么高。第二事件的存储,拿到历史存储事件,这个数据首先格式化的,其次还要支持索引查询,我们这边使用的是TUKOMX,对各维度建立索引。
这就是策略。这里执行策略分为三个部分,第一个叫ITEM,原始数据下我刚讲的维度,比如IP、UA。trigger就是触发器,是什么时候当触发之后可能得出它是个,这个行为有没有问题,或者有问题的比例是多少,这个人有没有问题,有问题的比例是多少。第三部分是执行,触发器触发之后的操作。组成部分是包括这三方面。

有了这三点之后,这个策略每次难道要在代码里进行上线更新吗?因为知乎用到了利用动态语言特性,自己封装了砂箱,对于里面可能用到的数据方法进行了封装。这就是策略的图,第一个是名字。第二个是触发器,这条策略表达的意思是过去一个小时之内同一天注册用户是同一个邮箱域,并且行为都是问题创建的时候会触发这样的触发器。触发之后执行下面的操作,首先删除所有的问题,然后把所有人给账号禁用。分装砂箱目的是安全性考虑的,如果内部系统不考虑这个问题的,甚至砂箱也不需要,把这些全部分到上下文。

到这里V1就把V0遇到的问题得到一定程度上的解决,首先从V1串行处理变成了任务型队列处理,这样队列处理所带来的延迟也肯定可以接受的。同时队列的异步处理还增大了吞吐量,并且在这里面,因为我加入了事件的存储,可以让我使用的纬度和事件的历史更长,而不必每次从线上数据库找到处用的数据,我对自己数据进行管理。同时加入了缓存的层次,使得处理时候的性能得到提升,不必要每次通过RPC查询,或者其它方面的外部数据源解决了。

悟空 V2


V1也有问题,策略越写越复杂,本来一个程序员的理想是把这套东西开放给产品和运营用的,后来发现自己都写不动了。但理想不能放弃,V2想解决这样的问题,同时把性能得到更强。因为流量是永恒的话题,面对对手的成长也是永恒的话题,所以说流量、复杂性、策略语言数学是下一个版本里着重想解决的问题。

首先流量经过在线的RPC,在线RPC作用很弱,只是做了在线的判断,判断了就放过去了,就不管了,也没有任何操作。放过去之后如果有问题就把这次请求存储了,也不会有后续操作。仅仅是判断的层。刚刚有个前提没有讲,随着业务越来越多,很多业务想接入反作弊系统面临的问题,首先要接RPC,其次还要发一堆消息,对于业务来说接入点就有2-3个,而且发的消息可能还有格式上的要求,可能还会引发一些问题。所以说这个版本要解决另外的问题,如何把接入点控制到一起。


结合刚刚讲到的,第二个版本里把主站和api消息队列清理掉了,所有的请求依然从线上IPC服务走一次。这里就不再只是承担在线判断的任务,除了在线判断以外还会把各个接口请求过来的消息进行简单的处理和解析,给放到一个队列里面去。其实就是用这个队列,用我自己队列替换了别人使用的公共队列。同时这么做的好处在于说,在这里进行初步判断以后的结果还可以把这个结果分装到消息里面去,这个结果在后面的判断和策略使用中就可以把结果给用上,有些数据对于后期拿会不太方便。

第一简化业务接入反作弊系统的复杂性,第二将整个处理流程数据串联起来,第三实现反作弊系统开关功能。我只要把这里接口一段,整个反作弊系统对于外部来说就不工作了,这种情况主要在出了问题,或者一些阶段特殊情况下。


Storage,这个版本里改变了,本来想缓存rides,缓存更多东西又不想用更多内存,所以换了一种缓存的存续叫SSDB,基于磁盘的存储。是不是只是为了把缓存增大,进行性能提升?不是。既然用了,能支持磁盘,支持更大的存储,那它就可以缓存更多的东西。刚刚在处理过程中有个最大的性能瓶颈在于每次来了消息获取历史信息都要查文档数据库,当并发越来越高的时候,性能会有上线。当读数据更多,还会造成写过死。

加入SSDB有了更大可以缓存的机制,把每次从文档数据库查询出来的东西建了索引,我把它可能使用到的行为放到一个索引的集合里去,相当于取出来结果。每次结果上层来了之后先对应给写到不同索引集合里去,然后才落地到文档数据库。


这样逐步发生扭转情况对于文档数据库读取场景由原来写少读多,到了写1读到了200的比例,那这个情况逐渐转化成了写少读多。如果场景变了写多读少,就不想用SSDB了,它的高并发处理能力,以及对于使用策略,以及对于内存占用量可能是我一直以来忍受的度。

有了SSDB有了提升,同时又了分布式存储之后,这时候交到产品上的需求。以前策略只能处理过去一小时,或者处理当前这条策略周围一段时间的任务。比如说我现在突然新上策略,想清理过去建站开始到现在的怎么办?就是想把这条策略当作离线使用,每次策略有更新了对历史数据进行清理,这时候怎么办?为了能够解决这样的问题加了outer loop,就是每次有一个策略更新或者有一次新的策略上线以后策略变更,所谓变更会被自动推送到一个服务里面去,这个服务会把对应的策略进行打包。


做完之后突然发现还有另外一个功能,就是之前一直没有的一个功能,我这个策略要上线了,上线之前靠谱不靠谱,策略是根据之前一个星期的时候数据进行制定,现在线上情况还是这个样子吗?会不会造成大量误伤,让用户骂我。

发现这个东西上线以后解决了这个问题,每次有了策略,一个策略即将上线之前我先提交这个平台,选取过去的时间段,只是召回不处理,处理结果进行评估,神奇解决了测试问题。当结果进行了简单评估之后,发现可以才正式让它处理事情。

测试问题得到了完美的解决,现在我们的整个策略流程可能会变成首先举报发现问题,我们对这个问题进行分析,分析完之后制定相应的策略,策略制定完之后到系统上拿过去一段时间进行自动运行测试,测试完之后会有对应测试数据放在平台上查看,觉得可移植后策略上到正式环境上进行召回,也许一个测试过程就减少了实际策略上线之后造成的误伤。

产品和运营同学可以自己写策略,很明显V1时代如果让他们写是不可能的。V2的时候想尝试解决这个问题,目的还是没有解决,首先简化策略的书写。我们参考了函数式语言的特性,首先从形式上可以看得出来,后面策略变成了,我们分装了数据结构,就变成了数据库里拿到过去一段时间的数据,数据本身拿出来就是数据结构里包含了几种方法。

为什么做成这样的设计?当然也不是仅仅觉得它美。是我们发现大多数策略场景就是过滤器场景,我针对某一个策略某几个策略进行过滤总归都是过滤。所谓策略处理也就是针对过滤出来所有行为做完全相同的操作,比如对所有行为进行map,把行为里实体删掉,所有人进行冻结。

还有场景是聚合场景了,算一些生产出来的队列里面有多少消息。我们发现经过这样以后仍然能满足我们的需求,而且变成链式调用,好处就是针对简单链式调用,针对侧学语言做自己的切割,一些分析,后面更方便基于策略语言做的性能优化,甚至可以并行做一些操作之类的。

总结

第一,所谓的架构是为了解决特定的问题,我经常碰见一些人跟我讨论问题时候观点觉得架构好不好,觉得那个公司用的那个架构好不好,这个问题怎么说呢?我也回答不了,我都不知道公司的业务场景客户是什么样的,怎么评判呢。可能对于每个公司来说大家都会找到适合自己的架构,核心点为了解决特定的问题,并不是为了让大家拿出来比谁的好或者谁的不好,能够解决问题的架构就是一个好架构。

第二,架构师需要更多的是克制。大家每天关注很多杂志,很多新闻,会了解到最前沿的技术。但对最前沿的技术是不是立马要用在生产或者企业系统里,这要打问号,这个时候一个架构师表现出来的就是克制,需要经过很仔细,很谨慎的选形,很谨慎的评估,跟自己业务匹配程度才能决定这个东西要不要做。

第三点,造轮子可以,但是别占用公司资源。不想造轮子的工程师不是好工程师,但是如果花了部门大半年精力造轮子解决这个问题里的1%,重要性很可能也都不到10%,这个时候就要深入考虑一下投入产出比的问题。当我们用的轮子,生态变坏了,跟我们匹配点不多的时候我们就大胆造。当现在轮子还算圆的时候,我们还是安安心心解决我们自己的问题。

小团队的任务在于组合优质的服务,这个观点也是不久之前才学习到的。每个公司都会有验证码,当时花了2-3个月时间做验证码投入两三个人做,对应前端和产品,讨论可不可行,安全性怎么样,调研市场上可能有的问题。调研一全做完上线一条破了,成功率99.5%,完全不能接受。但是不甘心,我们又做,进行了努力提升,搞了一个月又做出来一版,说我们解决这个问题,上线解决了5天,5天时间达到了99.5%以上。小团队任务在于组合优质服务,跟某大公司团队聊天,说他们有验证码服务,我们考虑要不要接,他们有自己的设计师,工程师,然后就接了。

经验

数据主导,工程师经常做很多事情的时候会有种冲动,就是看见坏人想弄死他,就通过一些简单粗暴的,发现大家IP一样就封掉,最后会有人找你做你不想做的事情。一个更理智的方式就是出了问题以后及时把那段时间问题所对应用户行为数据拿出来做最简单的频率,或者最简单的数据分析,知己知彼,知道了用户行为特征以后,才能对它进行相应的处理,才能说我了解了它。这时候即使误伤用户,别人质疑你也可以解释,我误伤率是保证多少以下的。

疏导和对抗的关系。我现在慢慢做安全方面的事情,对这点的认识教训也越来越多。我前面进的全是对抗,怎么找到他们,怎么把他们干死。

但是我们的产品同学比较有经验,发现了这批人目标点并不在于是在知乎站内流通,而是通过ICU方式在各种信息推广不同搜索引擎上,这点是我们之前没有想到的,既然想落地搜索引擎上那为什么要弄死,只要搜不到就行了。后面我们就做了识别不需要那么及时了,搜索引擎找不到,坏人利益点消失之后他们热情也会下降,我们工作也得到一个完美的结局。

延展阅读(点击标题):


 
InfoQ 更多文章 年前挖的坑都填了吗?技术债务偿还计划 程序员VS武林高手:技术为外功,思维乃内力 腾讯游戏大数据服务场景与应用(附PPT) 偷师饿了么:怎样用HTTP/2优化iOS APP网络层次架构? 作为高颜值的女程序员是一种怎样的体验?
猜您喜欢 10.17京东产品经理开放日PPT实录1 多线程开发关键技术 —— Java进阶(三) 公司里的 Data Scientist(数据科学家) 10个你应该学习使用的PHP特性 话题 | 云上那些事,那些年你为IT掏的冤枉钱