2019独角兽企业重金招聘Python工程师标准>>>

前言

今天是中国传统佳节“猿宵节”,是程序猿通宵赶代码的佳节。
AI考拉的技术小伙伴志在打破传统,以“我们不加班”为口号,以“我们提早下班”为指导中心,在这里安利技术知识给大家,祝大家节日快乐,提前下班,过真正的元宵节!

需求

在金融业务系统里面,判断用户是否是黑名单,这种场景应该很常见。

假设我们系统里面有一百万个黑名单用户,用手机号表示,现在有一个人想借款,我们要判断他是否在黑名单中,怎么做?

一般方法

最直接的方法,是在数据库中查询,目前数据库上实现的索引,虽然可以做到 O(logn) 或者理论O(1) 的时间复杂度,但毕竟是磁盘操作,跟内存操作不是一个数量级的。

于是,我们可以把黑名单中的手机号缓存到内存中,用一个数组储存起来,这种方法有两个问题,一是查找时间复杂度是 O(n),非常慢,二是占用大量内存。

查找速度上可以再优化,将数组变成Set,内部实现可以选择平衡二叉树或者哈希,这样子插入和查找的时间复杂度能做到 O(logn)或者理论O(1),但是带来的是空间上的灾难,比使用数组会更占用空间。

现在来看一下代码,对比一下这两种方法:

import random
import sysdef generate_random_phone():"""随机生成11位的字符串"""phone = ''for j in range(0, 11):phone += str(random.randint(0, 9))return phone# 10万个黑名单用户
black_list = []
for i in range(0, 100000):black_list.append(generate_random_phone())# 转成集合
black_set = set(black_list)
print(len(black_list), len(black_set))
# 看一下两种数据结构的空间占用
print("size of black_list: %f M" % (sys.getsizeof(black_list) / 1024 / 1024))
print("size of black_set: %f M" % (sys.getsizeof(black_set) / 1024 / 1024))def brute_force_find():"""直接列表线性查找,随机查一个存在或者不存在的元素, O(n)"""if random.randint(0, 10) % 2:target = black_list[random.randint(0, len(black_list))]return __brute_force_find(target)else:return __brute_force_find(generate_random_phone())def __brute_force_find(target):for i in range(0, len(black_list)):if target == black_list[i]:return Truereturn Falsedef set_find():"""集合查找,随机查一个存在或者不存在的元素, O(1)"""if random.randint(0, 10) % 2:target = black_list[random.randint(0, len(black_list))]return __set_find(target)else:return __set_find(generate_random_phone())def __set_find(target):return target in black_setprint(brute_force_find())
print(set_find())

可以看到,数组和集合的长度相等,说明元素都是唯一的。列表的空间占用为0.78M,而集合的空间占用为4M,主要是因为哈希表的数据结构需要较多指针连接冲突的元素,空间占用大概是列表的5倍。这是10w个手机号,如果有1亿个手机号,将需要占用3.9G的空间。

下面来看一下性能测试:

import timeitprint(timeit.repeat('brute_force_find()', number=100, setup="from __main__ import brute_force_find"))
print(timeit.repeat('set_find()', number=100, setup="from __main__ import set_find"))
[0.0016423738561570644, 0.0013590981252491474, 0.0014535998925566673]

可以看到,直接线性查询大概需要0.85s, 而集合的查询仅需要0.0016s,速度上是质的提升,但是空间占用太多了!

有没有一种数据结构,既可以做到集体查找的时间复杂度,又可以省空间呢?

答案是布隆过滤器,只是它有误判的可能性,当一个手机号经过布隆过滤器的查找,返回属于黑名单时,有一定概率,这个手机号实际上并不属于黑名单。 回到我们的业务中来,如果一个借款人有0.001%的概率被我们认为是黑名单而不借钱给他,其实是可以接受的,用风控的一句话说: 宁可错杀一百,也不放过一个。说明,利用布隆过滤器来解决这个问题是合适的。

布隆过滤器原理

原理非常简单,维护一个非常大的位图,设长度为m,选取k个哈希函数。

初始时,这个位图,所有元素都置为0。 对于黑名单中的每一个手机号,用k个哈希函数计算出来k个索引值,把位图中这k个位置都置为1。 当查询某个元素时,用k个哈希函数计算出来k个索引值,如果位图中k个位置的值都为1,说明这个元素可能存在,如果有一个位置不为1,则一定不存在。

这里的查询,说的可能存在,是因为哈希函数可能会出现冲突,一个不存在的元素,通过k个哈希函数计算出来索引,可能跟另外一个存在的元素相同,这个时间就出现了误判。所以,要降低误判率,明显是通过增大位图的长度和哈希函数的个数来实现的。

来看一下代码:

from bitarray import bitarray
import mmh3class BloomFilter:def __init__(self, arr):# 位图长度暂定为20倍黑名单库的大小self.SIZE = 20 * len(arr)self.bit_array = bitarray(self.SIZE)self.bit_array.setall(0)for item in arr:for pos in self.get_positions(item):self.bit_array[pos] = 1def get_positions(self, val):# 使用10个哈希函数,murmurhash算法,返回索引值return [mmh3.hash(val, i) % self.SIZE for i in range(40, 50)]def find(self, val):for pos in self.get_positions(val):if self.bit_array[pos] == 0:return Falsereturn TruebloomFilter = BloomFilter(black_list)
print("size of bloomFilter's bit_array: %f M" % (sys.getsizeof(bloomFilter.bit_array) / 1024 / 1024))def get_error_rate():# 用1w个随机手机号,测试布隆过滤器的错误率size = 10000error_count = 0for i in range(0, size):phone = generate_random_phone()bloom_filter_result = bloomFilter.find(phone)set_result = __set_find(phone)if bloom_filter_result != set_result:error_count += 1return error_count / sizeprint(get_error_rate())
size of bloomFilter's bit_array: 0.000092 M
0.0001

可以看到,虽然位图的长度是原数据的20倍,但是占用的空间却很小,这是因为位图的8个元素才占用1个字节,而原数据列表中1个元素就占用了将近11个字节。

错误率大约为0.0001,可以尝试不同的位图长度,比如改成30倍,错误率就会降低到0。

最后来看一下3种算法的性能测试:

def bloom_filter_find():if random.randint(0, 10) % 2:target = black_list[random.randint(0, len(black_list))]return bloomFilter.find(target)else:return bloomFilter.find(generate_random_phone())print(timeit.repeat('brute_force_find()', number=100, setup="from __main__ import brute_force_find"))
print(timeit.repeat('set_find()', number=100, setup="from __main__ import set_find"))
print(timeit.repeat('bloom_filter_find()', number=100, setup="from __main__ import bloom_filter_find"))
[0.70748823415488, 0.7686979519203305, 0.7785645266994834]
[0.001686999574303627, 0.002007704693824053, 0.0013333242386579514]
[0.001962156966328621, 0.0018132571130990982, 0.0023592300713062286]

可以看到,布隆过滤器的查找速度接近集合的查找速度,有时候甚至更快,在很低的误判率可以接受的情况下,选用布隆过滤器是即省时间又省空间的,是最佳的选择。

参考链接

  1. 布隆过滤器的原理和实现

著作权归本文作者所有,未经授权,请勿转载,谢谢。

转载于:https://my.oschina.net/kalengo/blog/3011940

AI考拉技术分享--布隆过滤器实战相关推荐

  1. AI考拉技术分享会—手把手教你入门UI 自动化测试 Appium

    2019独角兽企业重金招聘Python工程师标准>>> AI考拉日常技术分享会,前有众多前后端以及android大神分享,这次测试老司机Eric也放招,手把手带devs入门UI自动化 ...

  2. AI考拉技术分享会--IDE 常用功能 for Node.js

    前言 今天是属于大家的1024节,程序猿的日常工作离不开IDE的支持,在今天这个值得庆祝的日子,考拉的coders整理了工作中常用的IDE功能,大家节日快乐, 向程序猿致敬,向加班SAY NO! 一. ...

  3. 视频教程- 19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合-Java

    19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合 7年的开发架构经验,曾就职于国内一线互联网公司,开发工程师,现在是某创业公司技术负责人, 擅长语言有nod ...

  4. 加入AI考拉大家庭,是一种怎样的体验?--来自考拉码农的心声

    hi,各位帅气又多金,外表冷酷实则内心热情的技术猿: 这是考拉拉第二次踏入这片专属程序猿的天地.为了让大家更加全面了解考拉拉背后的大家庭-AI考拉,我们随机采访了公司的程序猿萌,让他们简单描(tu)述 ...

  5. Redis 布隆过滤器实战「缓存击穿、雪崩效应」

    Java高级互联网架构 2019-03-22 13:52:38 为什么引入 我们的业务中经常会遇到穿库的问题,通常可以通过缓存解决. 如果数据维度比较多,结果数据集合比较大时,缓存的效果就不明显了. ...

  6. 磐创AI - 专注机器学习技术分享

    微信公众号推荐 磐创AI 编辑 安可 今天给大家推荐一个微信公众号「磐创AI」,是一个从三大深度学习框架Tensorflow.Keras与PyTorch的角度剖析AI行业最新动态,机器学习干货文章,深 ...

  7. 【将门创投】AI 往期技术分享

    计算机视觉 1. 嘉宾:商汤科技CEO 徐立 文章回顾:计算机视觉的完整链条,从成像到早期视觉再到识别理解 2. 嘉宾:格灵深瞳CTO 赵勇 文章回顾:计算机视觉在安防.交通.机器人.无人车等领域的应 ...

  8. Java技术分享:NIO实战教程!

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式.NIO可以理解为非阻塞IO,传统的 ...

  9. 考拉海购技术支持的前世今生

    本文来自考拉海购技术支持中心负责人--书渊的分享,想和大家聊一聊考拉技术支持的前世今生,在这个发展历程的介绍当中,大家也可以此对考拉窥一斑而知全豹.当然,既然是聊我们的家常("黑历史&quo ...

最新文章

  1. R语言break函数和next函数实战
  2. 1. golang 语言环境安装
  3. Android EditText不弹出输入法焦点问题的总结
  4. python【力扣LeetCode算法题库】7- 整数反转
  5. 如何查看其他人的ABAP authorization check log
  6. php 打印行数,php/html-按行和列配置钻石数量的打印格式
  7. ruby 覆盖率测试_Ruby方法覆盖
  8. oracle安全性规则,[ORACLE ]安全性
  9. J2ME结构与相关规范介绍
  10. mysql远程访问授权命令_mysql远程访问授权
  11. Linux操作Oracle(7)—连接Oracle12C 或 OracleRac 出现 ORA-28040: No matching authentication protocol,没有匹配的验证协议
  12. jQuery实现一个简单的选项卡效果
  13. CImageList
  14. 微信公众号号开发小记(六)使用开源框架开发
  15. 华为鸿蒙os logo,华为鸿蒙 OS Logo :Powered by HarmonyOS
  16. 通过exchangelib库连接到公司exchange邮箱
  17. 【随机算法梗概】遗传算法通俗的讲解案例~~
  18. 【深度学习】实验5答案:滴滴出行-交通场景目标检测
  19. javascript--贪食蛇(完整版-逻辑思路)
  20. QQ音乐 最新歌曲源 API(稳定)

热门文章

  1. 能够支持python开发的环境_python集成开发环境哪个好?老男孩Python
  2. 有赞个性化推荐能力的演进与实践
  3. 地下排水管道的最佳铺设问题
  4. 解决[Vue warn]: Failed to resolve directive: loading
  5. 在vue中使用腾讯地图绘制围栏功能
  6. FFmpeg的视频format滤镜介绍
  7. 怎样做一个优秀的(懒惰的)系统管理员
  8. 与其临渊羡鱼 不如退而结网
  9. 基于另一个单元格值的条件格式
  10. android ndk 段错误,利用NDK崩溃日志查找BUG