共计 3670 个字符,预计需要花费 10 分钟才能阅读完成。
背景痛点分析
开发 skill 商店时,新手常会遇到几个典型问题:

- 商品状态同步延迟 :当多个用户同时购买同一技能时,库存状态更新可能不同步
- 技能权限控制缺失 :未购买用户可能通过接口直接访问付费技能内容
- 接口设计混乱 :返回数据结构不统一,导致前端处理逻辑复杂
- 高并发查询压力 :热门技能页面访问时数据库负载激增
这些问题往往在流量增长后集中爆发,因此前期架构设计尤为关键。
技术选型对比
Firebase 方案
- 优点 :
- 开箱即用的身份验证和实时数据库
- 无需维护服务器基础设施
-
快速原型开发
-
缺点 :
- vendor lock-in 风险
- 复杂查询性能较差
- 成本随用户量非线性增长
自建 Node.js+MongoDB 方案
- 决策依据 :
- MongoDB 的文档模型天然适合技能商品的多变属性
- Node.js 非阻塞 IO 适合高并发 I / O 密集型场景
- 完整的架构控制权
- 成本可预测性强
核心实现
商品 Schema 设计
interface ISkill {
title: string;
description: string;
category: 'programming' | 'design' | 'language';
pricingModel: {
type: 'one-time' | 'subscription';
price: number;
duration?: number; // 针对订阅类型
};
inventory: {
total: number;
available: number;
lock?: number; // 预占库存
};
requirements: string[];
createdAt: Date;
updatedAt: Date;
}
const skillSchema = new mongoose.Schema<ISkill>({// ... 字段定义}, {timestamps: true});
JWT 鉴权实现
// auth.middleware.ts
export const authenticate = async (req: Request, res: Response, next: NextFunction) => {
try {const token = req.header('Authorization')?.replace('Bearer', '');
if (!token) throw new Error('Authentication required');
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as {_id: string};
const user = await User.findById(decoded._id).select('-password');
if (!user) throw new Error('User not found');
req.user = user;
next();} catch (error) {if (error instanceof jwt.TokenExpiredError) {return res.status(401).json({
code: 'TOKEN_EXPIRED',
message: 'Token expired',
refreshUrl: '/auth/refresh'
});
}
res.status(401).json({error: 'Authentication failed'});
}
};
// 刷新令牌接口
router.post('/refresh', async (req: Request, res: Response) => {const { refreshToken} = req.body;
// ... 验证逻辑
const newAccessToken = generateToken(user._id, '15m');
res.json({accessToken: newAccessToken});
});
性能优化策略
Redis 缓存设计
// 获取技能详情带缓存
async function getSkillWithCache(skillId: string) {const cacheKey = `skill:${skillId}`;
try {
// 先查缓存
const cachedData = await redisClient.get(cacheKey);
if (cachedData) return JSON.parse(cachedData);
// 缓存未命中,查数据库
const skill = await Skill.findById(skillId).lean();
if (!skill) return null;
// 设置缓存(TTL 30 分钟)await redisClient.setEx(cacheKey, 1800, JSON.stringify(skill));
return skill;
} catch (error) {logger.error('Cache operation failed', { skillId, error});
// 降级直接查数据库
return await Skill.findById(skillId).lean();}
}
// 防缓存穿透
async function getSkillSafe(skillId: string) {if (!isValidObjectId(skillId)) return null;
const skill = await getSkillWithCache(skillId);
if (!skill) {
// 设置空值缓存防止穿透
await redisClient.setEx(`skill:${skillId}`, 60, 'null');
}
return skill;
}
分片集群部署
建议采用 3 分片集群:
- 按技能类别哈希分片
- 配置副本集确保高可用
- 查询路由通过 mongos 实现
避坑指南
事务处理要点
// 购买技能的事务处理
async function purchaseSkill(userId: string, skillId: string) {const session = await mongoose.startSession();
session.startTransaction();
try {
// 1. 检查技能可用性
const skill = await Skill.findById(skillId).session(session);
if (skill.inventory.available <= 0) {throw new Error('Out of stock');
}
// 2. 扣减库存(使用原子操作)const updated = await Skill.updateOne({ _id: skillId, 'inventory.available': { $gt: 0} },
{$inc: { 'inventory.available': -1} }
).session(session);
if (updated.modifiedCount === 0) {throw new Error('Concurrent modification');
}
// 3. 创建订单
const order = new Order({userId, skillId});
await order.save({session});
// 4. 提交事务
await session.commitTransaction();
// 5. 清除相关缓存
await redisClient.del(`skill:${skillId}`);
return order;
} catch (error) {await session.abortTransaction();
logger.error('Purchase failed', { userId, skillId, error});
throw error;
} finally {session.endSession();
}
}
技能交付幂等性
// 技能交付接口(幂等实现)router.post('/deliver', async (req: Request, res: Response) => {const { orderId, nonce} = req.body;
// 检查是否已处理过该请求
const processed = await redisClient.get(`delivery:${nonce}`);
if (processed) {return res.json({ status: 'already_delivered'});
}
// 标记请求已处理(24 小时过期)await redisClient.setEx(`delivery:${nonce}`, 86400, '1');
// 实际交付逻辑...
});
总结
构建高可用 skill 商店需要特别注意数据一致性和系统扩展性。通过合理的架构设计:
- 数据层 :MongoDB 分片集群处理增长
- 缓存层 :Redis 减轻数据库压力
- 应用层 :
- JWT 实现无状态认证
- 事务保证数据一致性
- 幂等设计避免重复操作
- 监控 :关键操作添加日志埋点
建议在开发初期就建立性能基准测试,随着用户增长逐步引入:
– CDN 加速静态资源
– 消息队列处理异步任务
– 服务网格管理微服务
这套架构已支撑我们平台日均 10 万 + 交易,希望这些实践对你有帮助。
正文完
