共计 5242 个字符,预计需要花费 14 分钟才能阅读完成。
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_205
猝死,又见猝死,可怜无定河边骨,犹是春闺梦里人!每当有年老的生命逝去,咱们就会感到心中某种撕裂的感觉,惆怅万千,疼痛不已。审核专员,一个咱们既相熟又生疏的岗位,他们的疲乏,不仅仅体现在肉体上反复工作的折磨,而更多的,是精力上处于一种无知无觉的疲乏,设想一下,作为审核员,千帆阅尽之后,打动过你的所有不再打动你,吸引过你的所有不再吸引你,甚至激怒过你的所有都不再激怒你,麻痹和迷惘充斥着你的工作和生存,只剩下疲于奔命,苦心孤诣。而造成审核员审核过劳的因素之一,就是海量内容审核零碎的设计问题。
谁也不能否定,对于 UGC(User Generated Content,即用户产出内容) 内容类产品来说,内容审核是必不可少的环节之一。因为 UGC 在产出大量优质内容的同时,也会产生诸如政治敏感、色情低俗、暴恐血腥等危险内容,所以,对于产出的数据,须要对应的审核员对其进行审批,而审核效率低下往往是因为审核零碎的低效设计导致,而低效的诱因又往往是因为没有审核的“缓冲区”。
对于“缓冲区”的概念,咱们能够举个理论生存中的例子,每天早上,大巷上随处可见的早点摊:
是的,排队买早点,就算您不食人间烟火,也应该在全国大街小巷中见识过此类情景,油条尽管好吃,然而不能马上就能吃到,须要排队,这里油条是被早点摊老板生产进去的,而顾客则须要生产这些油条,但在生产过程中呈现了问题,也就是在早点摊这一场景中,生产者生产油条的速度如果过慢,那么消费者会呈现闲暇闲置的状况,导致资源节约;而如果生产者生产油条的速度过快,消费者生产油条的速度很慢,那么生产者就必须期待消费者生产完了油条才可能持续生产油条,这就是最奢侈的生产者消费者问题。
而审核零碎恰好就符合生产者消费者模型,平台用户负责产生数据,这些数据由另一个模块来负责解决(此处的解决是狭义的,能够是审核、公布等动作 )。产生数据的模块,就形象地称为生产者; 而解决数据的模块,就称为消费者。
单单形象出生产者和消费者,还够不上是生产者消费者模型。该模式还须要有一个上文提到的“缓冲区”处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。
如此一来,缓冲区的呈现能够帮咱们解决上面的问题:
解耦:升高消费者和生产者之间的耦合度。有了缓冲区,生产者不用和消费者一一对应,用户产生的内容人不对审核员产生任何依赖,如果某一天审核员换人了,对于须要内容审核的用户也没有影响。假如生产者和消费者别离是两个对象。如果让生产者间接调用消费者的某个办法,那么生产者对于消费者就会产生依赖(也就是耦合 )。未来如果消费者的逻辑发生变化,可能会真接影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不间接依赖,耦合度也就相应升高了。
并发:生产者消费者数量不对等,仍然可能放弃失常良好的通信。因为办法调用是同步的 (或者叫阻塞的),在消费者的办法没有返回之前,生产者只好始终等在那边。万一消费者解决数据很慢,生产者只能等着浪费时间。应用了生产者消费者模式之后,生产者和消费者能够是两个独立的并发主体。生产者把制作进去的数据间接放到缓冲区,就能够再去产生下一条内容。基本上不必依赖消费者的处理速度。即炸油条的人间接把油条扔进缓冲区之后就不必管了。
缓存:生产者的生产速度和消费者的生产速度不匹配,能够将产出的内容进行暂存。如果平台用户的账号同时在两台电脑上公布多条内容,而审核专员无奈同时进行审批动作,就能够把多进去的内容暂存在缓存区。也就是生产者短时间内生产数据过快,消费者来不及生产,未解决的数据能够临时存在缓冲区中。
这里,咱们以 redis 作为缓冲区媒介,制作一个缓冲区容器:
import redis
class AuditQueue:
def __init__(self,name,**redis_kwargs):
self.r = redis.Redis(**redis_kwargs,decode_responses=True)
self.key = name
# 审核工作入队
def put(self,item):
# 用列表实现
self.r.rpush(self.key,item)
# 返回队列长度
def qsize(self):
return self.r.llen(self.key)
# 查看队列是否存在
def exist(self):
return self.r.exists(self.key)
# 审核工作出队
def get_wait(self,timeout=None):
# 取值
item = self.r.blpop(self.key,timeout=timeout)
return item
当用户产出内容后,并不间接进行审批动作,而是进入“缓冲区”,遵循 FIFO(first in first out) 准则,无论消费者的生产速度如何,工作全副会保留在“缓冲区”中,直到工作被消费者生产。
当然了,该容器队列并非惟一,因为“先进先出”只是相对而言,如果有加急工作也能够进行加权解决:
import redis
class AuditQueue:
def __init__(self,name,**redis_kwargs):
self.r = redis.Redis(**redis_kwargs,decode_responses=True)
self.key = name
# 审核工作入队
def put(self,item):
# 用列表实现
self.r.rpush(self.key,item)
# 返回队列长度
def qsize(self):
return self.r.llen(self.key)
# 查看队列是否存在
def exist(self):
return self.r.exists(self.key)
# 审核工作出队
def get_wait(self,timeout=None):
# 取值
item = self.r.blpop(self.key,timeout=timeout)
return item
# 加权入队
def put_vip(self,key,item):
# 用列表实现
self.r.rpush(key,item)
# 加权 出队
def get_wait_vip(self,key,timeout=None):
# 取值
item = self.r.blpop([key,self.key],timeout=timeout)
return item
这样多个队列之间会有“程序”,不便管制出队的逻辑。
接着看看生产的问题,这里咱们能够采取“并发”出队的业务逻辑,即一个生产者产出能够被多个消费者进行生产:
import threading
rq = AuditQueue("rq")
# 异步出队
def doout(id):
print("出队")
uid = rq.get_wait(1)
print("解决审批逻辑")
# 多线程生产
def dojob_threads():
threads = []
for x in range(2):
t = threading.Thread(target=doout,args=(x,))
threads.append(t)
# 执行批量生产
[x.start() for x in threads]
[x.join() for x in threads]
dojob_threads()
这样的缓冲区设计中,咱们的生产者和消费者都持有一个对缓冲区对象 AuditQueue 的援用,这样的设计模式实际上在很多设计模式都有用到,比方咱们的装璜者模式等等,它们独特的目标都是为了达到解耦和复用的成果。
然而,这样的设计方案也会引发另外一些问题:怎么保障缓冲容器中数据状态的一致性,当一个消费者执行了 rq.get\_wait(1) 办法之后,如果此时容器为空,然而还没来得及更新容器的 size, 那么另外一个消费者来了之后认为 size 不等于 0,那么继续执行 rq.get\_wait(1), 从而就造成了了状态的不一致性。
为了保障当缓冲容器外面没有工作的时候,消费者不会持续 rq.get\_wait(1),此时消费者开释锁,容器处于阻塞状态;并且一旦生产者增加了一条工作之后,此时从新唤醒消费者,消费者从新获取到容器的锁,继续执行 rq.get\_wait(1):
rq = RedisQueue("rq")
lock = threading.Lock()
# 异步出队办法
def doout(id):
# 获取锁
lock.acquire()
uid = rq.get_wait(1)
# 解决审批逻辑
# 出对后开释锁
lock.release()
令人遗憾的是,Python3 的多线程因为全局解释器锁的存在并不能并行,而是单线程执行的分时复用模式。藉此,零碎的多核 CPU 就成了陈设,而咱们晓得,一个线程能够领有多个协程,这样在 Python 中就能应用多核 CPU,协程同时能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态,线程是过程的一个实体, 是 CPU 调度和分派的根本单位, 它是比过程更小的能独立运行的根本单位,所以咱们能够思考应用协程来进行生产动作:
import asyncio
async def doout(id=1):
print(id)
rq = RedisQueue("rq")
uid = rq.get_wait(1)
print("开始出队")
# 生产动作
async def main():
# 建设协程对象
task1 = asyncio.ensure_future(doout(4))
task2 = asyncio.ensure_future(doout(7))
tasks = [task1,task2]
# 异步执行
await asyncio.gather(*tasks)
以上,一个简略的内容审核零碎的雏形就搭建实现了,该设计通过缓冲区概念均衡生产者的生产能力和消费者的生产能力来晋升整个零碎的运行效率,这是生产者消费者模型最重要的作用。
随后,产品构造确定之后,一些性能上的细节也能够帮忙咱们进步审核效率。
针对平台用户:
能够通过平台用户的账号进行解决,平台用户的所有行为都能阐明用户想干什么,是什么样的用户,所见即所得,针对用户能够建设一套账号根底信息、用户分值零碎、用户危险监控零碎。用户根底信息能够有用户年龄、性别、地理位置、设施、ip 地址、应用时长、交易信息、用户公布各类信息等。用户数据是对用户进行剖析的根底,有些数据能够在肯定水平上反映用户信用。用户分值零碎则是通过用户根底数据进行剖析,并数值化,能够按权重累加、按总分值加总均可。
公布过一个违规内容的用户,则分值升高等。仅分值零碎可能不够,比方分值高的用户,然而还是有可能公布不好的内容。因而须要再通过其余策略解决,比方高中低危险用户制度、黑白名单制度等。
一个用户公布了一个违规内容被检测进去后,和分值解耦的另外一个平台定义其为高风险用户,该用户后续内容将屡次放到人工审核机制中。
针对审批专员:
也能够对审批专员的账号进行用户画像,留存和剖析审批员的行为,钻研审批人的审批动作爱好以及工作效率或者审批的频率,当审批效率降落后,能够针对该审批人的账号进行“冷冻”解决,强制审核员进行劳动,相似高速公路上的休息区,避免“疲劳驾驶”。
诚然,时代在倒退,人工智能技术针对图像、文本、语音、视频等多媒体内容,笼罩涉政、涉黄、涉恐、广告检测等全方位的智能 AI 审核能力能够无效地晋升审批效率,然而,最终审批权还是在审核员手里,尤为波及有些“合规”的内容,AI 临时并不能代替人类。
举个例子,上世纪七十年代,文怀沙学生已经写了一首诗送给江青,《供奉李公衔女士命招抚,诗以报之。》:
“ 沙翁敬谢李龟年,无尾乞摇女主前。九死甘心了江壑,不随鸡犬上青天。”
这首藏锋诗后两句,别离含 ” 江 ”,” 青 ” 二字,正对应后面说的 ” 女主 ”, 表明本人不愿学汉代的李龟年,在女主背后无尾乞摇,宁愿死也不当江青身边因一人升天而得道的鸡犬,同时,每句后两字连起来赫然正是“龟主江青”四个字。试问这种既藏锋又藏尾的“讽诗”,那种算法可能辨认进去?就算有对应的模型,训练集和测试集又去哪里找呢?
结语:罗翔老师已经表白过一种观点:“我感觉人最大的苦楚,就是无奈逾越晓得和做到的那个鸿沟”。陀思妥耶夫斯基也说过:“要爱具体的人,不要总是想着爱形象的人”,没错,每一个在电脑前苦苦寻求答案的,并不是那个头像,并不是那三两行代码,而是一个活生生的,有血有肉的,有本人三观的,经验了大风大浪的,在社会上苟延残喘的人,而每一个审核专员,他们的近况,他们的诉求,咱们晓得,然而咱们能做什么?回到猝死的问题上,如果说生存中还有什么确定的事件,如果说历史教会了咱们什么,那就是:活下去。同时,作为一个简简单单的普通人,兴许能做的,就是在巨大叙事之外,关注弱势群体,给他们以心愿,最初,以一句话和诸君共勉:
如果活着的人都还活着,那么死去的人就不会死去。
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_205