redis应用场景---黑名单碰撞

1. 场景说明

你手头有一份黑名单,是几万个电话号,你的系统需要提供一个功能,给你若干个电话号,比如10个,你需要判断这10个电话号哪些处于黑名单中。

2. 思路分析

2.1 使用集合

考虑将这些电话号都存入到一个集合中,redis的集合支持判断一个key是否在集合中,但场景要求支持批量查询,这个时候就不要考虑一个电话查询一次了,时间都浪费在了网络IO上,是非常不划算的,必须找到一种方法,一次性的将这10个电话发送到redis进行判断。

可以将这10个电话写入到一个新的集合中,然后用这个新集合与黑名单集合做交集,交集里的内容正是所需要的结果,这个过程需要三个步骤来完成

  1. 创建新集合
  2. 交集运算
  3. 删除新集合
import time
from random import sample
from redis.client import Redis

r = Redis(host='127.0.0.1', port=6379, db=0, password='密码')

def create_data_for_set():
    for i in range(10000, 50001):
        r.sadd('black_set', i)

def test_set_ex(count):
    lst = sample(range(10000, 50001), count)
    t1 = time.time()

    for i in range(10):
        r.sadd('black_set_2', *lst)  # 将数据存入一个新的集合
        res = r.sinter(['black_set', 'black_set_2'])
        r.delete('black_set_2')   # 删除临时集合

    t2 = time.time()
    print(f'批量查询{count}个key耗时: ' + str((t2 - t1) / 10))

def test_set():
    test_set_ex(5)
    test_set_ex(10)
    test_set_ex(50)
    test_set_ex(100)
    test_set_ex(200)
    test_set_ex(300)
    test_set_ex(400)
    test_set_ex(500)
    test_set_ex(600)
    test_set_ex(700)
    test_set_ex(800)
    test_set_ex(900)
    test_set_ex(1000)
    test_set_ex(1500)
    test_set_ex(2000)

先执行函数create_data_for_set ,准备好数据,向集合中写入5万个key, 执行test_set函数,测试不同数量的批量查询对性能的影响,得到结果如下

批量查询5个key耗时: 0.01289970874786377
批量查询10个key耗时: 0.00995328426361084
批量查询50个key耗时: 0.010499835014343262
批量查询100个key耗时: 0.012500858306884766
批量查询200个key耗时: 0.013652372360229491
批量查询300个key耗时: 0.01479964256286621
批量查询400个key耗时: 0.016852569580078126
批量查询500个key耗时: 0.0216951847076416
批量查询600个key耗时: 0.02585453987121582
批量查询700个key耗时: 0.028419804573059083
批量查询800个key耗时: 0.026099658012390135
批量查询900个key耗时: 0.0303267240524292
批量查询1000个key耗时: 0.027204418182373048
批量查询1500个key耗时: 0.03538084030151367
批量查询2000个key耗时: 0.04379713535308838

2. 使用string

一个电话号用一个key来存储,批量查询时,用mget批量操作

def create_data_for_mget():
    for i in range(10000, 50001):
        key = f'black::{i}'
        r.set(key, 1)

def test_mget_ex(count):
    lst = sample(range(10000, 50001), count)
    t1 = time.time()
    for i in range(10):
        res = r.mget([f'black::{item}' for item in lst])
    t2 = time.time()

    print(f'批量查询{count}个key耗时: ' + str((t2-t1)/10))

def test_mget():
    test_mget_ex(5)
    test_mget_ex(10)
    test_mget_ex(50)
    test_mget_ex(100)
    test_mget_ex(200)
    test_mget_ex(300)
    test_mget_ex(400)
    test_mget_ex(500)
    test_mget_ex(600)
    test_mget_ex(700)
    test_mget_ex(800)
    test_mget_ex(900)
    test_mget_ex(1000)
    test_mget_ex(1500)
    test_mget_ex(2000)

test_mget()

得到的结果

批量查询5个key耗时: 0.006318092346191406
批量查询10个key耗时: 0.0033003568649291994
批量查询50个key耗时: 0.004325366020202637
批量查询100个key耗时: 0.004499959945678711
批量查询200个key耗时: 0.0066995382308959964
批量查询300个key耗时: 0.00950002670288086
批量查询400个key耗时: 0.011799359321594238
批量查询500个key耗时: 0.011200904846191406
批量查询600个key耗时: 0.013999748229980468
批量查询700个key耗时: 0.014699149131774902
批量查询800个key耗时: 0.01600046157836914
批量查询900个key耗时: 0.02149970531463623
批量查询1000个key耗时: 0.02039961814880371
批量查询1500个key耗时: 0.030499577522277832
批量查询2000个key耗时: 0.03730003833770752

3. 使用hash

拥电话号做hash的field,批量查询时使用hmget方法

def create_data_for_hmget():
    for i in range(10000, 50001):
        key = f'black'
        r.hset(key, f'black::{i}', 1)


def test_hmget_ex(count):
    lst = sample(range(10000, 50001), count)
    t1 = time.time()
    for i in range(10):
        res = r.hmget('black', [f'black::{item}' for item in lst])
    t2 = time.time()

    print(f'批量查询{count}个key耗时: ' + str((t2-t1)/10))

def test_hmget():
    test_hmget_ex(5)
    test_hmget_ex(10)
    test_hmget_ex(50)
    test_hmget_ex(100)
    test_hmget_ex(200)
    test_hmget_ex(300)
    test_hmget_ex(400)
    test_hmget_ex(500)
    test_hmget_ex(600)
    test_hmget_ex(700)
    test_hmget_ex(800)
    test_hmget_ex(900)
    test_hmget_ex(1000)
    test_hmget_ex(1500)
    test_hmget_ex(2000)
test_hmget()

得到结果如下

批量查询5个key耗时: 0.005600094795227051
批量查询10个key耗时: 0.0027003049850463866
批量查询50个key耗时: 0.003399515151977539
批量查询100个key耗时: 0.0038997173309326173
批量查询200个key耗时: 0.005700159072875977
批量查询300个key耗时: 0.007200384140014648
批量查询400个key耗时: 0.008800601959228516
批量查询500个key耗时: 0.010499835014343262
批量查询600个key耗时: 0.012400221824645997
批量查询700个key耗时: 0.013699531555175781
批量查询800个key耗时: 0.01600005626678467
批量查询900个key耗时: 0.016799330711364746
批量查询1000个key耗时: 0.01909959316253662
批量查询1500个key耗时: 0.02709958553314209
批量查询2000个key耗时: 0.03717978000640869

4. 三种方法对比

对比3种方法的响应时间,string和hash近乎相同,略高于使用集合的方案。我个人更喜欢使用hash,相比于string,可以避免生成大量的key。

扫描关注, 与我技术互动

QQ交流群: 211426309

加入知识星球, 每天收获更多精彩内容

分享日常研究的python技术和遇到的问题及解决方案