一次并发处理过程, 基于 Redis
场景复现
- APP: 牌桌游戏APP
- 业务流程:
- 每一局牌桌有四个人, 每一局牌桌结束后, 多个APP客户端同时上传游戏中的用户id信息;
- 后端给每个用户分配称号, 有多种称号, 称号等级越高, 获得的概率越低.
- 称号会存到数据库, 一定时间段会重置.
- 后端分配称号后存储用户称号, 存储牌局结果, 响应结果给客户端
- 问题:
- 牌局结束后, 多个客户端会同时上传相同的牌局结果, 因为不是完全从数据库获取数据(有些用户是新用户, 会重新随机出来并写入用户表中, 有些用户称号被定时随机了)
- 并发状态下会造成多客户端获取的结果不一致, 同时也会写入多条牌局结果到数据库中(正确情况下应该是只有一条), 重复写入多条用户数据到用户表中.
处理方案
方案1: 由于是多客户端同时请求, 客户端要求的响应时间不能太长, 数据库加锁的方式太慢, 不采用
方案2: 使用 redis setnx, 保证同一个用户的称号在不同的客户端请求中, 结果一致.
处理过程
遍历用户数据时, 对每一个用户, 首先生成用户称号, 并使用redis setnx设置key, value 为用户称号, 保证多客户端的用户称号是相同的.
因为多客户端上传数据相同, 还要保证写数据库时数据是唯一的(每个用户只有一条用户数据, 每一局比赛只有一条数据)
代码:
<?php
use Illuminate\Support\Facades\Redis;
class UserTitle
{
public function generateTitle($users, $tableId)
{
foreach($users as $k=>$v)
{
$title = 0; //称号id
$user = DB::table('users')->where('user_id', $v)->first();
if(empty($user))
{
$title = $this->getRand(); // 用户不存在, 随机获取一个称号
} else {
$title = $user->title;
}
// 使用redis setnx, 给当前用户id加锁, 其他客户端的该用户可以获得数据
$userKey = 'user_title:'.$v; //使用用户id作为key
Redis::setNX($userKey, $newTitleId); //
Redis::expire($userKey, 2); //没有使用事务, 会导致多个客户端重复延长该值
$newTitleId = Redis::get($userKey); //重新从redis获取key, 防止多个客户端值不一致的问题
// 数据库添加/更新用户title等操作, 一个用户id只能有一条用户数据
}
// 整个比赛结果落库, 只能有一条比赛数据
$matchKey = 'user_title:'.$tableId; //多个客户端的table_id是相同的
$matchLock = Redis::get($dbKey); //获取值
if (empty($matchLock))
{
Redis::setNX($matchKey, 'lock_match');
Redis::expire($matchKey, 20);
// 数据库操作, 执行 updateOrInsert 方法
}
}
}
总结
redis 可以做的东西很多, 设计中肯定还有不完善的地方, 如果您有更好的方案, 请不吝赐教, 谢谢!
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: