提交 8656596d 编写于 作者: 雪洛's avatar 雪洛

docs: uniCloud redis snap

上级 294400bb
...@@ -232,7 +232,9 @@ uniCloud的每个云函数是一个独立进程,不存在云函数级别的多 ...@@ -232,7 +232,9 @@ uniCloud的每个云函数是一个独立进程,不存在云函数级别的多
### 高并发下简单的防止超卖 ### 高并发下简单的防止超卖
高并发时很多用户同时对一条数据读写,很容易造成数据混乱,表现在秒杀抢购等场景就是超卖。以秒杀为例,开发者可以从扣除库存这步入手对超卖进行很大程度的限制,下面是一个简单的示例 > uniCloud阿里云现已支持redis,开通并使用redis请参考:[redis开通和使用](uniCloud/redis.md),如何使用redis防止超卖请参考:[redis高并发抢购](uniCloud/redis.md?id=snap-over-sell)
高并发时很多用户同时对一条数据读写,很容易造成数据混乱,表现在秒杀抢购等场景就是超卖。以秒杀为例,开发者可以从扣除库存这步入手对超卖进行很大程度的限制,下面是一个简单的示例(**注意以下代码未使用事务**
```js ```js
// 云函数 // 云函数
...@@ -258,6 +260,7 @@ exports.main = async function(event){ ...@@ -258,6 +260,7 @@ exports.main = async function(event){
} }
``` ```
### 云存储、数据库还没有使用就多了几次 ### 云存储、数据库还没有使用就多了几次
关于云存储:这里的读写次数,并不一定是针对文件的:包括:上传文件、修改Policy、修改ACL、修改CORS 等操作,都会被认为是COS写。环境初始化时也会执行很多次初始化操作,写入 policy/acl/cors 等配置信息。用户每次操作 修改安全域名、修改静态域名等,也会触发 CORS 的写入。 关于云存储:这里的读写次数,并不一定是针对文件的:包括:上传文件、修改Policy、修改ACL、修改CORS 等操作,都会被认为是COS写。环境初始化时也会执行很多次初始化操作,写入 policy/acl/cors 等配置信息。用户每次操作 修改安全域名、修改静态域名等,也会触发 CORS 的写入。
......
...@@ -769,4 +769,97 @@ const [operationType, currentValue] = await redis.eval(`local val = redis.call(' ...@@ -769,4 +769,97 @@ const [operationType, currentValue] = await redis.eval(`local val = redis.call('
- 云函数本地调试 - 云函数本地调试
目前不支持本地运行使用了Redis扩展能力的云函数,请上传到云端测试 目前不支持本地运行使用了Redis扩展能力的云函数,请上传到云端测试
\ No newline at end of file
## 最佳实践
### 高并发下抢购逻辑@snap-over-sell
可以利用redis的原子操作保证在高并发下不会超卖,以下为一个简单示例
在抢购活动开始前可以将商品库存同步到redis内,实际业务中可以通过提前访问一次抢购页面加载所有商品来实现。下面通过一个简单的演示代码来实现
```js
const redis = uniCloud.redis()
const goodsList = [{ // 商品库存信息
id: 'g1',
stock: 100
}, {
id: 'g2',
stock: 200
}, {
id: 'g3',
stock: 400
}]
const stockKeyPrefix = 'stock_'
async function init() { // 抢购活动开始前在redis初始化库存
for (let i = 0; i < goodsList.length; i++) {
const {
id,
stock
} = goodsList[i];
await redis.set(`${stockKeyPrefix}${id}`, stock)
}
}
init()
```
抢购的逻辑见以下代码
```js
exports.main = async function (event, context) {
// 一些判断抢购活动是否开始/结束的逻辑,未开始直接返回
const cart = [{ // 购物车信息,此处演示购物车抢购的例子,如果抢购每次下单只能购买一件商品,可以适当调整代码
id: 'g1', // 商品id
amount: parseInt(Math.random() * 5 + 5) // 购买数量
}, {
id: 'g2',
amount: parseInt(Math.random() * 5 + 5)
}]
/**
* 使用redis的eval方法执行lua脚本判断各个商品库存能否满足购物车购买的数量
* 如果不满足,返回库存不足的商品id
* 如果满足,返回一个空数组
*
* eval为原子操作可以在高并发下保证不出错
**/
let checkAndSetStock = `
local cart = {${cart.map(item => `{id='${item.id}',amount=${item.amount}}`).join(',')}}
local amountList = redis.call('mget',${cart.map(item => `'${stockKeyPrefix}${item.id}'`).join(',')})
local invalidGoods = {}
for i,cartItem in ipairs(cart) do
if (cartItem['amount'] > tonumber(amountList[i])) then
table.insert(invalidGoods, cartItem['id'])
break
end
end
if(#invalidGoods > 0) then
return invalidGoods
end
for i,cartItem in ipairs(cart) do
redis.call('decrby', '${stockKeyPrefix}'..cartItem['id'], cartItem['amount'])
end
return invalidGoods
`
const invalidGoods = await redis.eval(checkAndSetStock, 0)
if (invalidGoods.length > 0) {
return {
code: -1,
message: `以下商品库存不足:${invalidGoods.join(',')}`, // 此处为简化演示代码直接返回商品id给客户端
invalidGoods // 返回库存不足的商品列表
}
}
// redis库存已扣除,将库存同步到数据库。为用户创建订单
// 此处代码省略...
return {
code: 0,
message: '下单成功,跳转订单页面'
}
}
```
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册