Laravel计数器用Redis还是DB

wen PHP项目 13

本文目录导读:

Laravel计数器用Redis还是DB

  1. 目录导读
  2. 计数器场景与选型痛点
  3. Redis计数器 vs DB计数器:核心对比
  4. 何时用Redis?最优场景与代码示例
  5. 何时用DB?持久化与事务场景分析
  6. 混合方案:Redis+数据库双写策略
  7. 常见误区与性能实录
  8. 问答 FAQ
  9. 总结:你的项目该选哪个?

Laravel计数器用Redis还是DB?性能、场景与实现详解

目录导读


计数器场景与选型痛点

在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;
    }
}

注意:生产环境中推荐使用predisphpredis,并配置连接池。

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 工作流

  1. 前端请求进来,直接Redis::incr()(零延迟)
  2. 定时任务(每分钟)或队列(异步)将Redis中的计数器写入DB
  3. 读取时优先读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同样条件下INCR QPS>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 yesappendfsync 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)防止计数器写覆盖。

抱歉,评论功能暂时关闭!