手机号黑名单库查询逻辑与性能优化分享

行云流水
2026-02-27 / 0 评论 / 7 阅读 / 正在检测是否收录...
在高并发场景下,如何实现毫秒级的手机号黑名单校验?本文将深入剖析一个生产级黑名单系统的核心架构与技术实现。

管理后台

一、总体介绍

在短信通道、语音呼叫、风控审核等业务场景中,手机号黑名单校验是一项高频且关键的能力。想象一下,当用户发起呼叫或发送短信时,系统需要在毫秒级时间内判断目标号码是否在黑名单中——这直接关系到业务合规性和用户体验。

本文介绍的手机号黑名单库是一个面向运营/风控场景的完整解决方案,具备以下核心特性:

  • 双通道架构:Django 管理后台 + Go 高性能 API 服务
  • 毫秒级响应:依托 Redis 缓存实现单次查询 < 5ms
  • 百万级数据支持:轻松支撑千万级黑名单数据量
  • 灵活接入方式:支持单号查询、批量查询(最多 500 个)

系统架构

二、技术要点

1. Redis Set 数据结构:空间换时间的极致实践

是什么:使用 Redis 的 Set(集合)数据结构存储黑名单手机号,而非传统的 Hash 或 String。

为什么这么做

  • Set 的 SISMEMBER 命令时间复杂度为 O(1),无论数据量多大,查询性能恒定
  • 内存占用优化:存储 1000 万个 11 位手机号仅需约 400MB 内存
  • 天然去重:Set 自动处理重复号码,简化业务逻辑

核心价值:单机 Redis 可支撑 10 万+ QPS 的查询压力,满足绝大多数业务场景。

2. 冷热分离架构:PostgreSQL + Redis 双层存储

是什么:PostgreSQL 作为持久化存储(冷数据),Redis 作为查询缓存(热数据)。

为什么这么做

  • 数据安全:PostgreSQL 保证数据不丢失,支持事务和备份
  • 查询性能:Redis 避免频繁访问数据库,减轻 DB 压力
  • 水平扩展:Redis 可部署集群模式,支持数据分片

核心价值:兼顾数据可靠性和查询性能,实现"写入慢、读取快"的最优解。

redis加速

3. 原子切换机制:零停机数据更新

是什么:使用临时 Set + RENAME 命令实现黑名单数据的无缝切换。

为什么这么做

  • 避免更新过程中的数据不一致问题
  • 切换操作是原子性的,微秒级完成
  • 业务层无感知,零停机时间

核心价值:支持百万级数据的全量更新,而不影响线上查询服务。

4. Pipeline 批量查询:网络延迟优化

是什么:使用 Redis Pipeline 技术批量发送查询命令,减少网络往返次数。

为什么这么做

  • 单次网络 RTT 约 1-5ms,批量查询可将 500 个号码的查询时间从 2500ms 降至 10ms
  • 减少 Redis 服务器处理开销

核心价值:批量查询接口支持 500 个号码一次性校验,响应时间 < 50ms。

5. 签名验证机制:API 安全防护

是什么:基于 MD5 的参数签名验证,防止接口被恶意调用。

为什么这么做

  • 防止请求被篡改(如修改查询号码)
  • 防止重放攻击(时间戳有效期 5 分钟)
  • 身份认证(AppId + AppSecret 模式)

核心价值:确保只有授权用户才能访问黑名单查询服务。

验证流程

三、核心代码片段

1. Redis 批量查询实现

// BatchIsPhoneInBlacklistSet 使用 Pipeline 批量查询号码是否在黑名单中
func BatchIsPhoneInBlacklistSet(phones []string) (map[string]bool, error) {
    if len(phones) == 0 {
        return make(map[string]bool), nil
    }

    pipe := RedisClient.Pipeline()
    cmds := make(map[string]*redis.BoolCmd)

    // 批量发送 SISMEMBER 命令
    for _, phone := range phones {
        cmds[phone] = pipe.SIsMember(ctx, BlacklistSetKey, phone)
    }

    // 执行所有命令(一次网络往返)
    _, err := pipe.Exec(ctx)
    if err != nil {
        return nil, err
    }

    // 处理结果
    results := make(map[string]bool)
    for phone, cmd := range cmds {
        isMember, err := cmd.Result()
        if err != nil {
            // 单个查询出错,默认不在黑名单
            results[phone] = false
        } else {
            results[phone] = isMember
        }
    }

    return results, nil
}

核心逻辑解读

  • 使用 Pipeline 将多个 SISMEMBER 命令打包发送
  • 所有查询共享一次网络往返,大幅降低延迟
  • 结果逐个解析,单个失败不影响整体

2. 原子切换实现

// AtomicSwitchBlacklist 原子切换黑名单数据
func AtomicSwitchBlacklist() error {
    // 检查临时 Set 是否存在
    exists, err := RedisClient.Exists(ctx, BlacklistTempSetKey).Result()
    if err != nil {
        return err
    }
    
    if exists == 0 {
        return fmt.Errorf("temporary blacklist set does not exist")
    }
    
    // 原子操作:重命名临时 Set 为主 Set
    pipe := RedisClient.Pipeline()
    pipe.Rename(ctx, BlacklistTempSetKey, BlacklistSetKey)
    pipe.Incr(ctx, BlacklistVersionKey)
    
    _, err = pipe.Exec(ctx)
    return err
}

核心逻辑解读

  • RENAME 命令是原子操作,微秒级完成
  • 版本号递增,便于追踪数据更新状态
  • 切换期间查询不中断,业务零感知

3. 签名验证实现

// GenerateSignature 生成请求签名
func GenerateSignature(params SignParams) string {
    // 构建参数字典
    paramMap := make(map[string]string)
    paramMap["appId"] = params.AppId
    paramMap["callee"] = params.Callee
    paramMap["level"] = strconv.Itoa(params.Level)
    paramMap["timestamp"] = params.Timestamp

    // 按字典序排序(确保签名一致性)
    keys := make([]string, 0, len(paramMap))
    for k := range paramMap {
        keys = append(keys, k)
    }
    sort.Strings(keys)

    // 拼接签名字符串
    var signStr strings.Builder
    for i, key := range keys {
        if i > 0 {
            signStr.WriteString("&")
        }
        signStr.WriteString(key)
        signStr.WriteString("=")
        signStr.WriteString(paramMap[key])
    }
    signStr.WriteString("&appSecret=")
    signStr.WriteString(params.AppSecret)

    // MD5 加密
    hash := md5.Sum([]byte(signStr.String()))
    return hex.EncodeToString(hash[:])
}

核心逻辑解读

  • 参数按字典序排序,确保客户端和服务端生成相同签名
  • AppSecret 仅参与签名计算,不传输,防止泄露
  • MD5 算法兼顾安全性和计算性能

4. 批量查询接口处理

// ProcessBatchCheck 处理批量号码检查请求
func (s *BlacklistService) ProcessBatchCheck(req *models.BatchCheckRequest, clientIP string) (*models.BatchCheckResponse, error) {
    // 1. 验证 API 密钥和签名
    params := map[string]interface{}{
        "appId":     req.AppId,
        "callee":    req.Callee,
        "level":     req.Level,
        "timestamp": req.Timestamp,
    }
    apiKey, err := s.ValidateAndGetApiKey(req.AppId, req.Sign, params)
    if err != nil {
        return &models.BatchCheckResponse{
            Code: 405,  // 签名验证失败
            Msg:  "签名验证失败",
        }, nil
    }
    
    // 2. 检查访问权限(IP 白名单等)
    allowed, err := database.ValidateAccess(apiKey.CUserId, req.AppId, apiKey.ID, clientIP)
    if !allowed {
        return &models.BatchCheckResponse{
            Code: 403,
            Msg:  "访问被拒绝",
        }, nil
    }

    // 3. 解析并去重电话号码
    phones := strings.Split(req.Callee, ",")
    uniquePhones := make(map[string]bool)
    var validPhones []string
    
    for _, phone := range phones {
        phone = strings.TrimSpace(phone)
        if phone != "" && utils.IsValidPhoneNumber(phone) {
            if !uniquePhones[phone] {
                uniquePhones[phone] = true
                validPhones = append(validPhones, phone)
            }
        }
    }

    // 4. 限制批量查询数量
    if len(validPhones) > 500 {
        return &models.BatchCheckResponse{
            Code: 400,
            Msg:  "批量查询数量不能超过500个",
        }, nil
    }

    // 5. 执行批量查询(Level 1=黑名单, 2=白名单, 3=混合模式)
    results, hitCount, err := s.checkBatchNumbersWithResults(validPhones)
    
    // 6. 记录访问日志和消费记录(异步)
    s.recordAccessAndConsumption(apiKey, validPhones, hitCount)
    
    return &models.BatchCheckResponse{
        Code: 200,
        Msg:  "success",
        Data: results,
    }, nil
}

核心逻辑解读

  • 六层校验:签名验证 → 权限检查 → 参数解析 → 数量限制 → 黑名单查询 → 日志记录
  • 支持三种查询模式:黑名单、白名单、混合模式
  • 异步记录访问日志,不阻塞查询响应

四、总结

手机号黑名单库的核心设计思想可以概括为:"冷热分离保安全,Redis 缓存保性能,原子切换保稳定,签名验证保安全"

通过本文介绍的技术方案,我们实现了:

  • 单次查询延迟 < 5ms
  • 批量 500 个号码查询 < 50ms
  • 支持千万级黑名单数据
  • 零停机数据更新

这套方案已在生产环境稳定运行,日均处理查询请求数百万次。希望本文的技术分享能为你的黑名单系统设计提供参考。


本文基于 PBlack 手机号黑名单管理系统(Django + Go + Redis + PostgreSQL)生产实践整理。

评论 (0)

取消
只有登录/注册用户才可评论