本文目录导读:

针对PHP项目批量拉黑名单同步到多节点,推荐以下几种成熟方案:
Redis + 消息队列(推荐)
批量导入黑名单接口
<?php
// BatchBlacklistController.php
class BatchBlacklistController {
public function import($request) {
// 1. 接收批量数据(CSV/Excel/JSON)
$file = $request->file('blacklist');
$data = $this->parseFile($file);
// 2. 数据校验
$validData = $this->validate($data);
// 3. 写入Redis Set(原子操作)
$redis = new Redis();
$redis->sAddArray('global:blacklist', $validData);
// 4. 发布消息到队列
$this->publishSyncMessage([
'action' => 'batch_add',
'data' => $validData,
'timestamp' => time()
]);
return json(['success' => true, 'count' => count($validData)]);
}
private function publishSyncMessage($message) {
// 使用Redis Stream或RabbitMQ
$redis = new Redis();
$redis->xAdd('blacklist_sync', '*', $message);
}
}
多节点同步消费者
<?php
// BlacklistSyncWorker.php
class BlacklistSyncWorker {
public function consume() {
$redis = new Redis();
$redis->connect('redis-master', 6379);
// 每个节点独立消费
$group = 'blacklist_sync_group';
$consumer = 'node_' . gethostname();
while (true) {
$messages = $redis->xReadGroup(
$group,
$consumer,
['blacklist_sync' => '>'],
1, // 每次读取1条
1000 // 阻塞1秒
);
if (!empty($messages)) {
foreach ($messages as $stream => $msgs) {
foreach ($msgs as $id => $msg) {
$this->processMessage($msg);
$redis->xAck('blacklist_sync', $group, [$id]);
}
}
}
}
}
private function processMessage($msg) {
$action = $msg['action'];
$data = json_decode($msg['data'], true);
switch ($action) {
case 'batch_add':
// 更新本地内存/文件/数据库
$this->updateLocalBlacklist($data);
break;
case 'remove':
$this->removeFromLocal($data);
break;
}
}
}
使用Redis集群(最简单)
单节点写入,集群自动同步
<?php
// 使用Redis Cluster自动同步
class BlacklistManager {
private $redis;
public function __construct() {
// 连接Redis集群
$this->redis = new RedisCluster(null, [
'192.168.1.1:6379',
'192.168.1.2:6379',
'192.168.1.3:6379'
]);
}
public function batchAdd(array $users) {
// 使用Pipeline批量写入
$pipe = $this->redis->multi(Redis::PIPELINE);
foreach ($users as $user) {
$pipe->sAdd('global:blacklist', $user);
}
$pipe->exec();
}
public function isBlacklisted($userId) {
return $this->redis->sIsMember('global:blacklist', $userId);
}
}
文件同步 + inotify(适合小规模)
主节点生成文件
<?php
// Master节点
class BlacklistFileSync {
public function exportFile() {
$data = $this->getAllBlacklistUsers();
$content = implode("\n", $data);
// 写入共享存储(NFS/GFS/S3)
file_put_contents('/shared/blacklist/blacklist_' . date('Ymd_Hi') . '.txt', $content);
// 生成MD5校验
file_put_contents('/shared/blacklist/blacklist.md5', md5($content));
}
}
// 从节点监听
class BlacklistFileWatcher {
public function watch() {
// 使用inotify监控文件变化
$inotify = inotify_init();
$watch = inotify_add_watch($inotify, '/shared/blacklist', IN_CLOSE_WRITE);
while (true) {
$events = inotify_read($inotify);
foreach ($events as $event) {
if ($event['name'] === 'blacklist.md5') {
$this->reloadBlacklist();
}
}
}
}
public function reloadBlacklist() {
// 重新加载到内存/Redis/APC
$content = file_get_contents('/shared/blacklist/blacklist_latest.txt');
$users = explode("\n", $content);
// 更新到本地内存缓存
apcu_store('blacklist', $users);
// 或写入本地Redis
// $redis->del('local:blacklist');
// $redis->sAddArray('local:blacklist', $users);
}
}
数据库 + 轮询(适合低频率)
共享数据库 + 本地缓存
<?php
class BlacklistDatabaseSync {
// 写入主库
public function batchAdd(array $users) {
DB::table('blacklists')->insert($users);
// 更新同步时间戳
Cache::put('blacklist_last_update', time());
}
// 各节点定时获取增量
public function syncFromDb() {
$lastSync = Cache::get('local_blacklist_sync_time', 0);
$newRecords = DB::table('blacklists')
->where('created_at', '>', date('Y-m-d H:i:s', $lastSync))
->get();
if ($newRecords->isNotEmpty()) {
// 更新本地缓存
foreach ($newRecords as $record) {
Redis::sAdd('local:blacklist', $record->user_id);
}
Cache::put('local_blacklist_sync_time', time());
}
}
}
各方案对比
| 方案 | 延迟 | 复杂度 | 可靠性 | 适合场景 |
|---|---|---|---|---|
| Redis + MQ | <100ms | 中 | 高 | 大型分布式系统 |
| Redis Cluster | <1ms | 低 | 高 | 已有Redis集群 |
| 文件同步 | 1-5s | 低 | 中 | 小规模、非实时 |
| 数据库轮询 | 10-30s | 低 | 高 | 低频率更新 |
最佳实践建议
- 本地缓存+远程同步:各节点保留本地内存+Redis双重缓存
- 增量同步:避免全量同步造成性能问题
- 健康检查:定期验证各节点黑名单一致性
- 降级方案:同步失败时使用本地文件兜底
// 本地缓存 + Redis双写
class BlacklistChecker {
public function isBlocked($userId) {
// 先查本地内存(最快)
if (in_array($userId, $this->localBlacklist)) {
return true;
}
// 再查Redis(次快)
if ($this->redis->sIsMember('global:blacklist', $userId)) {
return true;
}
// 最后查数据库(兜底)
return DB::table('blacklists')->where('user_id', $userId)->exists();
}
}
选择方案时考虑你的系统规模、实时性要求和运维能力,Redis + MQ方案是大多数生产环境的最佳选择。