首页
美图
服务
付费
树洞
云主机
推荐
邻居
支付
开发
书单
更多
我的足迹
罗盘时钟
圈小猫
工作打分
给我留言
本站统计
推荐
M商城
欣悦云店
txt阅读器
VPS监控
证书监控
网址导航
在线工具
Search
1
docker和docker-compose一键安装脚本
9,703 阅读
2
采用Prometheus+Grafana 监控H3C交换机状态
8,729 阅读
3
WooCommerce对接第三方支付插件开发
6,912 阅读
4
docker下运行grafana和grafana Image Renderer
5,952 阅读
5
服务器(vps)性能测试脚本汇总
5,577 阅读
大模型
虚拟化
数据库
运维
基础知识
监控预警
数据展示
运维工具
web安全
系统服务
开发
python
php
java
shell
go
项目
博客
电商
工具
娱乐
综合
VPS相关
规范文档
知识总结
经验分享
读书笔记
关于
Search
标签搜索
django
python
支付对接
运维工具
电商平台
Joe主题
docker
wordpress
woocommerce
支付通道
zabbix
蓝鲸智云
运维
grafana
监控
运维知识
typecho
php
mysql
nginx
行云流水
累计撰写
335
篇文章
累计收到
386
条评论
首页
栏目
大模型
虚拟化
数据库
运维
基础知识
监控预警
数据展示
运维工具
web安全
系统服务
开发
python
php
java
shell
go
项目
博客
电商
工具
娱乐
综合
VPS相关
规范文档
知识总结
经验分享
读书笔记
关于
页面
美图
服务
树洞
云主机
邻居
支付
书单
给我留言
本站统计
推荐
M商城
txt阅读器
网址导航
搜索到
2
篇与
的结果
2026-03-07
从零搭建一个手机号过滤平台:PBlack 架构全解析
今天聊聊 PBlack——一个开源的手机号黑名单/白名单检测平台。我会从架构设计的角度,拆解它是如何解决这些痛点的,以及为什么选择 Vue + Django + Go 这样的技术组合。为什么需要手机号过滤?先说说背景。在金融风控、电商防刷、社交平台注册这些场景里,手机号是最基础的身份标识。但问题来了:骚扰电话营销:用户被各种推广电话轰炸,体验极差羊毛党批量注册:用虚拟号、接码平台批量薅羊毛欺诈风险:黑名单号码反复作案,平台损失巨大传统的解决方案要么简单粗暴(直接拒接所有陌生号),要么成本太高(每次都要调用第三方接口)。PBlack 的设计目标就是:既要快,又要准,还要省钱。整体架构:三层分离PBlack 采用前后端分离的微服务架构,核心分为三层:┌─────────────────────────────────────────────────────────┐ │ 前端层 (pfront) │ │ Vue 3 + TypeScript + Vite │ │ 管理界面、API 密钥管理、消费统计看板 │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ API 服务层 (papi) │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ phone-filter │ │ teddy-api │ │ pfront-api │ │ │ │ -api (Go) │ │ -proxy (Go) │ │ (Go) │ │ │ │ 黑名单检测 │ │ 上游代理 │ │ 认证/消息 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 数据与管理后台层 │ │ Django + PostgreSQL + Redis │ │ 用户管理、号码库、访问记录、缓存加速 │ └─────────────────────────────────────────────────────────┘这个分层不是拍脑袋决定的。每一层都有自己的职责,而且可以独立部署、独立扩展。技术选型:为什么这样组合?前端:Vue 3 + Vite选择 Vue 3 而不是 React,主要是考虑开发效率和团队熟悉度。Vite 的冷启动速度比 Webpack 快一个数量级,本地开发体验很好。TypeScript 是必须的——手机号过滤涉及大量的 API 接口对接,类型安全能避免很多低级错误。管理后台:DjangoDjango 的 admin 界面是开箱即用的。用户管理、API 密钥管理、黑白名单维护这些功能,用 Django 的 ModelAdmin 几行代码就能搞定。如果用 Go 写后台,光 CRUD 接口就要写一堆。Django 的 ORM 和 admin 在这种场景下是降维打击。核心检测服务:Go这是整个系统的性能瓶颈所在。手机号检测是高频操作,QPS 可能达到几千甚至上万。Go 的 goroutine + channel 模型非常适合这种高并发、低延迟的场景。实测下来,单机可以轻松支撑 10k+ QPS。// 核心检测逻辑:Redis Set 的 O(1) 查询 func (s *BlacklistService) CheckSingleNumber(phone string) (bool, error) { isBlacklisted, err := database.IsPhoneInBlacklistSet(phone) if err != nil { return false, err } s.updateStats(isBlacklisted) return isBlacklisted, nil }数据流转:一次检测请求的全流程来看看一个黑名单检测请求是怎么处理的:客户端 → phone-filter-api → 签名验证 → 用户鉴权 → Redis 查询 ↓ 客户端 ← 返回结果 ← 组装响应 ← 命中判定 ← 黑名单 Set关键设计点:Redis Set 存储:黑名单和白名单分别用 Redis 的 Set 数据结构存储,SISMEMBER 命令是 O(1) 复杂度,百万级号码也能毫秒级响应多级检测策略:level=1:只查黑名单,命中即拦截level=2:只查白名单,用于"只允许特定号码"的场景level=3:黑名单优先,未命中时调用上游 API 二次确认批处理优化:访问记录和消费记录不实时写入数据库,而是先放入内存队列,每 5 秒批量 flush 一次,大幅降低数据库压力// 后台批处理器,降低主链路延迟 type BatchProcessor struct { accessRecords []*models.AccessRecord consumptionRecords []*models.ConsumptionRecord mutex sync.Mutex batchSize int // 默认 100 条批量写入 } func (bp *BatchProcessor) startBackgroundProcessing() { ticker := time.NewTicker(5 * time.Second) for range ticker.C { bp.flushRecords() // 定时批量刷盘 } }安全设计:不只是黑名单PBlack 的安全机制是多层防护:1. 签名验证每个请求必须携带 appId、timestamp、sign 三个参数。签名算法如下:sign = MD5(appId + timestamp + appSecret)timestamp 必须在 5 分钟内,防止重放攻击。2. IP 白名单每个 API 密钥可以绑定允许的 IP 列表,即使密钥泄露,攻击者也无法从其他 IP 调用。3. 余额与配额控制每个用户有独立的余额和单价设置,每次检测按单价扣费。余额不足时自动拒绝服务。部署架构:从开发到生产本地开发时,用 Docker Compose 一键启动所有依赖:# postgres/docker-compose.yaml services: postgres: image: postgres:15-alpine ports: - "5432:5432" environment: POSTGRES_DB: phonedb POSTGRES_PASSWORD: uWNZugjBqbcf8dxC生产环境建议:Nginx 反向代理:SSL 终止、静态资源缓存、限流systemd 进程守护:API 服务和 Worker 分别托管PostgreSQL 主从:读写分离,查询走从库Redis Cluster:支持横向扩展性能数据在 4C8G 的云服务器上实测:指标数值单机 QPS12,000+平均响应时间3-5msRedis 命中率99.8%批处理延迟< 5s总结PBlack 的架构设计遵循几个核心原则:职责分离:Django 做管理、Go 做性能、Vue 做交互,各取所长缓存优先:Redis Set 是核心,数据库只是持久化备份批处理降载:非关键路径异步化,主链路保持轻量安全第一:签名 + IP 白名单 + 余额控制,多层防护这套架构已经在实际业务中跑了半年多,处理了几千万次检测请求。如果你也在做类似的风控系统,希望这些经验对你有帮助。下篇预告:《30 分钟跑起来:PBlack 本地开发环境搭建实战》
2026年03月07日
37 阅读
0 评论
0 点赞
2026-02-27
手机号黑名单库查询逻辑与性能优化分享
在高并发场景下,如何实现毫秒级的手机号黑名单校验?本文将深入剖析一个生产级黑名单系统的核心架构与技术实现。一、总体介绍在短信通道、语音呼叫、风控审核等业务场景中,手机号黑名单校验是一项高频且关键的能力。想象一下,当用户发起呼叫或发送短信时,系统需要在毫秒级时间内判断目标号码是否在黑名单中——这直接关系到业务合规性和用户体验。本文介绍的手机号黑名单库是一个面向运营/风控场景的完整解决方案,具备以下核心特性:双通道架构: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 可部署集群模式,支持数据分片核心价值:兼顾数据可靠性和查询性能,实现"写入慢、读取快"的最优解。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)生产实践整理。
2026年02月27日
64 阅读
0 评论
0 点赞