案例介绍
微信有几亿的用户群,某一时刻可能有几千上万人同时在玩漂流瓶,对于这种高并发数据量小的服务,使用 Node.js 和 Redis 绝对是一个推荐的选择。
接口设计
扔一个漂流瓶
- 请求方法:
POST
- 请求路径:
/
- 请求体参数:
time
:漂流瓶扔出的时间戳,默认设置为Date.now()
owner
:漂流瓶主人(用户 id)type
:漂流瓶类型male
:男性female
:女性
content
:漂流瓶内容
- 返回:完整数据内容
捡一个漂流瓶
- 请求方法:
GET
- 请求路径:
/
- 查询参数:
user
:捡漂流瓶的人(用户 id)type
:漂流瓶类型all
:全部male
:男性female
:女性
- 返回数据:
{
"time": 1641883884765, // 漂流瓶扔出的时间戳
"owner": 1, // 漂流瓶主人
"type": "male", // 漂流瓶类型
"content": "hello world" // 漂流瓶内容
}
设计思路
可以将 Redis 想象成一片大海,Redis 中的每一条 Hash 类型的数据就是一个漂流瓶,每个漂流瓶都有独一无二的 id(即 Redis 中的键),里面包含了漂流瓶的一些信息(即 Redis 中键的值)。
使用两个数据库,分别存储两种 type
的漂流瓶,目的是为了方便使用 Redis 中的 RANDOMKEY
命令,该命令返回当前数据库中的一个随机键,不能加任何条件。
扔漂流瓶时,为其创建一个唯一 id(使用 uuidv4),以这个 id 为 key,漂流瓶的数据为值,添加一条 Hash 类型的数据,并设置 1 天有效期。
捡漂流瓶时,根据 type
决定从哪个数据库中随机读取数据,漂流瓶一经捡起,就从数据库中删除。
开始开发
初始化项目
mkdir drift-bottle
cd drift-bottle
npm init -y
npm i express ioredis uuidv4
app.js:
const { uuid } = require('uuidv4')
const Redis = require('ioredis')
const express = require('express')
const app = express()
app.use(express.json())
// 创建 Redis 实例
const connectRedis = () => {
return new Redis()
}
// 扔一个漂流瓶
app.post('/', (req, res, next) => {
try {
res.send('post /')
} catch (error) {
next(error)
}
})
// 捡一个漂流瓶
app.get('/', (req, res, next) => {
try {
res.send('get /')
} catch (error) {
next(error)
}
})
// 统一处理异常
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message
})
})
app.listen(3000, () => {
console.log('runnning')
})
扔一个漂流瓶
// 扔一个漂流瓶
app.post('/', async (req, res, next) => {
try {
const bottle = req.body
// 设置时间戳
bottle.time = bottle.time || Date.now()
// 为每个漂流瓶随机生成一个不重复的id
const bottleId = uuid()
const type = {
male: 0,
female: 1
}
await redis
.pipeline()
// 根据类型切换数据库
.select(type[bottle.type])
// 将数据存为 Hash
.hmset(bottleId, bottle)
// 设置 1 天有效期
.expire(bottleId, 24 * 60 * 60)
.exec()
res.status(201).json({
bottle: {
id: bottleId,
...bottle
}
})
} catch (error) {
next(error)
}
})
捡一个漂流瓶
// 捡一个漂流瓶
app.get('/', async (req, res, next) => {
try {
const query = req.query
const type = {
all: Math.round(Math.random()),
male: 0,
female: 1
}
query.type = query.type || 'all'
// 根据类型切换数据库
await redis.select(type[query.type])
// 随机获取一个 key
const bottleId = await redis.randomkey()
if (!bottleId) {
res.status(200).json({
message: '大海很干净...'
})
}
// 根据漂流瓶 id 获取完整的漂流瓶信息
const bottle = await redis.hgetall(bottleId)
res.status(201).json({
bottle
})
// 从 Redis 中删除捡到的漂流瓶
redis.del(bottleId)
} catch (error) {
next(error)
}
})