本文目录导读:

- 目录导读
- 计数器场景与选型痛点
- Redis计数器 vs DB计数器:核心对比
- 何时用Redis?最优场景与代码示例
- 何时用DB?持久化与事务场景分析
- 混合方案:Redis+数据库双写策略
- 常见误区与性能实录
- 问答 FAQ
- 总结:你的项目该选哪个?
Laravel计数器用Redis还是DB?性能、场景与实现详解
目录导读
- 引言:计数器场景与选型痛点
- Redis计数器 vs DB计数器:核心对比
- 何时用Redis?最优场景与代码示例
- 何时用DB?持久化与事务场景分析
- 混合方案:Redis+数据库双写策略
- 常见误区与性能实录
- 问答 FAQ
- 你的项目该选哪个?
计数器场景与选型痛点
在Laravel开发中,计数器几乎无处不在:文章阅读量、点赞数、商品库存、用户积分、API调用频次等,开发者常纠结于计数器到底用Redis还是MySQL?选错可能导致性能瓶颈、数据丢失或复杂度飙升。
本文基于实际项目与搜索引擎主流资料,从性能、一致性、持久化、开发成本四个维度,给出清晰决策树和可直接运行的代码。:Laravel计数器、Redis vs DB、性能对比、高并发。
Redis计数器 vs DB计数器:核心对比
| 维度 | Redis(推荐高并发) | MySQL/PostgreSQL(推荐低频+强持久) |
|---|---|---|
| 读写速度 | 单线程内存操作,QPS可达10万+ | 磁盘I/O,QPS约1k-5k(视索引) |
| 原子性 | INCR/DECR原生原子操作 |
需LOCK IN SHARE MODE或事务 |
| 持久化 | RDB/AOF可配置,但宕机可能丢失秒级数据 | 事务提交即持久,ACID强保证 |
| 数据丢失风险 | 较高(尤其是未启用AOF时) | 极低(主从+binlog) |
| 开发复杂度 | 简单,单行命令 | 需要建表、索引、锁处理 |
Redis适合写多读多、可接受少量丢失的计数器;DB适合必须零丢失、低频读写的计数器。
何时用Redis?最优场景与代码示例
1 典型场景
- 文章/视频实时的浏览量、点赞数
- 限流计数器(比如API每分钟请求次数)
- 排行榜(利用sorted set)
2 Laravel实现:Redis计数器服务
use Illuminate\Support\Facades\Redis;
class ArticleCounterService
{
public static function incrementViews(int $articleId): void
{
$key = "article:{$articleId}:views";
Redis::incr($key);
}
public static function getViews(int $articleId): int
{
$key = "article:{$articleId}:views";
return (int) Redis::get($key) ?? 0;
}
}
注意:生产环境中推荐使用predis或phpredis,并配置连接池。
3 限流计数器(防刷)
// 每用户每分钟最多10次
$key = "rate_limit:user:{$userId}:minute:" . now()->format('YmdHi');
if (Redis::incr($key) > 10 && Redis::ttl($key) == -1) {
Redis::expire($key, 60);
abort(429, 'Too Many Requests');
}
何时用DB?持久化与事务场景分析
1 必须使用DB的场景
- 资金/积分增减(要求事务一致,不能丢失)
- 库存超卖临界控制(需要行锁或乐观锁)
- 低频且需关联复杂查询(今日发帖量”同时需JOIN用户表)
2 Laravel实现:带锁的DB计数器
use Illuminate\Support\Facades\DB;
// 使用事务+行锁防止并发叠加
DB::transaction(function () use ($postId) {
$post = DB::table('posts')
->lockForUpdate()
->find($postId);
DB::table('posts')
->where('id', $postId)
->update(['likes' => $post->likes + 1]);
});
坏消息:这种写法在高并发下会阻塞,只适合QPS<100的场景。
3 优化方案:请求合并(减少写次数)
// 每10秒合并一次用户点赞请求到DB
$queueKey = "post_likes_queue";
Redis::incr($queueKey);
// 定时任务每10秒执行一次批量更新
$total = Redis::getset($queueKey, 0);
DB::table('posts')->whereIn('id', $ids)->increment('likes', $total);
混合方案:Redis+数据库双写策略
这是大型项目最推荐的方案:Redis做实时计数,DB做持久化最终一致性。
1 工作流
- 前端请求进来,直接
Redis::incr()(零延迟) - 定时任务(每分钟)或队列(异步)将Redis中的计数器写入DB
- 读取时优先读Redis,若不存在(Redis宕机恢复后)则从DB回填
2 Laravel队列实现
// 在App\Console\Kernel中注册定时任务
$schedule->call(function () {
$keys = Redis::keys("article:*:views");
foreach ($keys as $key) {
$articleId = explode(':', $key)[1];
$viewsInRedis = Redis::get($key);
// 原子替换,防止重复累加
DB::table('articles')
->where('id', $articleId)
->update(['views' => $viewsInRedis + DB::raw('views')]);
}
})->everyMinute();
注意:生产级别需要更细致的幂等处理(例如记录最后一次同步时间戳)。
常见误区与性能实录
1 误区1:DB计数器加索引就够快
- 测试结果:单机MySQL 8.0,100并发
UPDATE带索引行锁,QPS约2000;Redis同样条件下INCRQPS>80000。 - DB的磁盘I/O和锁冲突是硬伤,难以支撑秒级万次更新。
2 误区2:Redis持久化设置RDB即可
- 风险:RDB是快照模式,宕机可能丢失最后一次快照后的所有计数,如果资金积分用Redis+RDB,一旦宕机重启,计数器可能归零。
- 建议:关键业务至少开启AOF+fsync每秒一次,或使用混合方案。
3 误区3:Redis缓存数据一定不用回写DB
- 后果:若Redis持久化配置不当,重启后数据全丢,务必定时或通过队列将计数器写入DB作为冷备份。
问答 FAQ
Q1:阅读量用Redis还是DB?
A:推荐Redis,阅读量允许微小误差(比如差几十次),且高并发场景下DB会拖垮数据库,配合定时任务每5分钟将Redis值同步到DB即可。
Q2:用户积分增减能用Redis吗?
A:谨慎,若积分对应金钱或重要权益,必须使用DB事务保证一致性,若仅用于游戏积分等非实时金融场景,可使用Redis+定期落盘。
Q3:如何防止Redis宕机导致数据丢失?
A:使用Redis Sentinel或Cluster实现高可用,同时开启AOF持久化(appendonly yes,appendfsync everysec),但即便这样,仍有可能丢失1秒数据,保险做法:混合方案 + 定时写入DB。
Q4:DB计数器能否通过优化达到Redis速度?
A:很难,即使使用内存表(MySQL MEMORY引擎)或NoSQL类DB(如ScyllaDB),也难以在原子操作和延迟上完全匹敌Redis的单线程纯内存模型,如果业务能接受TokuDB或TiKV,可接近Redis性能,但运维成本骤增。
Q5:计数器的读写比例重要吗?
A:非常重要,若写少读多(如文章浏览数:写一次,读万次),DB加缓存依然可行,但若需要“写完立刻被其他人看到”(如直播实时点赞),则必须用Redis。
你的项目该选哪个?
| 业务类型 | 推荐方案 | 核心理由 |
|---|---|---|
| 实时直播点赞、访问量、排名 | Redis 纯内存计数 | 低延迟,高并发,接受少量误差 |
| 资金、库存、权限额度 | DB + 行锁 / 乐观锁 | 零丢失,事务强一致 |
| 高并发且要持久化的场景 | Redis + 队列定时落DB | 取长补短,最终一致性 |
| 低频管理后台统计 | DB 普通字段 | 开发简单,无需引入Redis |
最终建议:
- 不要神化Redis:如果QPS不到500,直接用DB计数器更简单可靠。
- 不要妖魔化DB:对于资源中心的计数器(如文章总浏览量),DB加上二级缓存(如
Cache::remember)也能应付日均百万级流量。 - 首选混合方案:95%的项目用“Redis实时 + DB定期同步”可以同时满足性能和持久化要求。
延伸阅读:如果你的Laravel项目需要更精细的限流(如基于用户的滑动窗口),可以查阅laravel-rate-limiter包或直接使用Redis的ZREMRANGEBYSCORE实现,对于分布式环境,考虑Redis Lock(如Redlock)防止计数器写覆盖。