10 - Redis进行缓存、限流、队列

发布时间:2026/6/29 19:13:49
10 - Redis进行缓存、限流、队列 第 1 步Redis 入门Redis 在你项目里干什么PostgreSQL管持久化数据用户、订单、库存掉电不丢。Redis管内存里的临时数据读多写少、允许过期减轻 DB 压力。本课用 String JSON以及 List 做队列。用途场景命令文件缓存GET /api/productsGET/SETEXsrc/product.ts限流登录 / 注册防刷INCR/EXPIREsrc/middleware/rateLimit.ts、src/auth.ts队列下单后 mock 发邮件LPUSH/BRPOPsrc/queue/emailQueue.ts、src/workers/emailWorker.tsPrisma 管 PGRedis 单独连模式同src/lib/prisma.ts。三项共用src/redis.ts和.env里的REDIS_URL。环境准备npminstallioredisnpminstall-Dtypes/ioredis.env增加REDIS_URLredis://localhost:6379启动 RedisDockerdockerrun-d--nameredis-study-p6379:6379 redis:7-alpine-p 6379:6379 本机 6379 映射到容器内 RedisNode 连redis://localhost:6379。dockerps# 看是否在跑dockerstart redis-study# 停止后再启dockerexec-itredis-study redis-cli PING# 应回 PONG创建redis客户端并尝试连接src/redis.tsimportRedisfromioredis;constredisUrlprocess.env.REDIS_URL??redis://localhost:6379;exportconstredisnewRedis(redisUrl);redis.on(connect,()console.log(Redis 已连接));redis.on(error,(err)console.error(Redis 连接错误:,err.message));Worker 进程需单独加载环境变量import dotenv/config;API 入口已有则 Worker 自己也要加作用是在程序启动时自动加载 .env 文件里的环境变量并写入 process.env。缓存Cache-Aside目的热点读走 Redis少打 PostgreSQL。HITMISSGET /api/products缓存命中?redis.get → JSON.parse → 200findMany → setex TTL → 200实现要点src/product.tsconstcacheKeyproducts:list;constCACHE_TTL60;constcachedawaitredis.get(cacheKey);if(cached)returnres.status(200).json(JSON.parse(cached));constproductsawaitprisma.product.findMany({orderBy:{id:asc}});// set expireawaitredis.setex(cacheKey,CACHE_TTL,JSON.stringify(products));res.json(products);验证docker start redis-study→npm run dev→ 连刷两次GET /api/products第二次应更快60 秒内走缓存。改数据后手动失效redis-cli DEL products:list。需掌握的概念概念含义常见应对TTLkey 到期自动删避免脏数据常驻Time To Live,设置的过期时间穿透查不存在的数据缓存拦不住缓存空值短 TTL击穿热点 key 过期瞬间并发打 DB互斥锁 / 逻辑过期雪崩大量 key 同时过期TTL 加随机抖动限流Rate Limit目的同一 IP 在固定窗口内最多 N 次超出返回429。算法固定窗口INCR ratelimit:login:{ip}→ 首次EXPIRE 60→count max则 429。请求行为第 1 次INCR → 1EXPIRE 60第 2~max 次放行第 max1 次42960 秒后key 过期重新计数与缓存的区别限流用INCR 计数超限拒绝请求缓存用GET/SETEX 存结果未命中才回源 DB。中间件src/middleware/rateLimit.ts核心constipreq.ip??req.socket.remoteAddress??unknown;constkey${keyPrefix}:${ip};constcountawaitredis.incr(key);// incrementif(count1)awaitredis.expire(key,windowSec);if(countmax)returnres.status(429).json({error:Too Many Requests, ...});next();挂载src/auth.ts— 写在 handler 前面rateLimit({windowSec:60,max:3,keyPrefix:ratelimit:register});// 注册rateLimit({windowSec:60,max:5,keyPrefix:ratelimit:login});// 登录验证60 秒内连发 6 次POST /api/auth/login第 6 次应 429。要点count 1才 EXPIRE不刷新窗口用 IP 标识来源生产还会加验证码、滑动窗口等。队列异步任务目的扣库存、写订单必须同步返回发邮件可异步不拖慢 HTTP。下单 → 事务成功 → LPUSH 任务毫秒→ 201 立刻返回 ↓ Worker BRPOP → mock 发邮件角色文件动作生产者src/order.ts事务外enqueueOrderEmail(...)队列Redis Listemail:queueLPUSH入队消费者src/workers/emailWorker.tsBRPOP阻塞出队LPUSH左进BRPOP右出FIFO。任务体存 JSON 字符串。入队src/queue/emailQueue.tsorder.ts// emailQueue.tsawaitredis.lpush(QUEUE_KEY,JSON.stringify(job));// order.ts — 事务成功后、return 201 之前if(order?.user?.email){awaitenqueueOrderEmail({orderId,userId,email});}消费者emailWorker.tswhile(true){constresultawaitredis.brpop(QUEUE_KEY,5);if(!result)continue;constjobJSON.parse(result[1]);console.log([Mock Email] 订单 #${job.orderId}→${job.email});}验证两个终端npmrun dev# 终端 1npmrun worker:email# 终端 2package.json 已配脚本登录后POST /api/orders→ API 快速 201Worker 控制台打印 mock 邮件。Worker 挂了任务不丢重启后继续消费。生产常用 BullMQ重试、延迟、面板本课手写 List 是为理解「入队 / 出队 / 独立进程」。三项对照 本地启动缓存限流队列解决读慢、DB 压力防刷HTTP 不等慢任务Redis 类型StringString计数List进程112API Workerdockerstart redis-studynpmrun devnpmrun worker:email# 测下单队列时需要