高并发场景下用户购票数量的优化限制方案
【摘要】 有了限制规则后,接下来就是如何实现限制了,有的小伙伴可能回想,在用户生成订单时,直接从数据中查询数量做验证不就可以了吗要是普通的项目确实没什么问题,但此项目是一切以 高并发 为前提的,从数据库中查询并发的效率又降下来了,所以还是要借助Redis来实现,并且设计的键值操作起来也要高效才可以验证的逻辑其实不难,从Redis中查询数量,然后验证数量是否符合限制内即可,关键是要什么时机放入Redis...
有了限制规则后,接下来就是如何实现限制了,有的小伙伴可能回想,在用户生成订单时,直接从数据中查询数量做验证不就可以了吗
要是普通的项目确实没什么问题,但此项目是一切以 高并发 为前提的,从数据库中查询并发的效率又降下来了,所以还是要借助Redis来实现,并且设计的键值操作起来也要高效才可以
验证的逻辑其实不难,从Redis中查询数量,然后验证数量是否符合限制内即可,关键是要什么时机放入Redis呢?这时,要从业务特点来入手:
- 这是一个下订单的验证功能,所以肯定是在用户生成订单的逻辑中执行的,既然用户都生成订单了,那么肯定是在查询节目详情之后的操作,而且还必须是登录状态
- 其实登录的用户也不一定是要下单的,但是不登录的用户一定不会下单的
这里也就提取到了两个关键字,就是 查询节目详情 和 登录状态
也就是说 在查询节目详情时,用户并且登录的状态下,将这个订单的统计数量放入Redis中
放进去了,但还有个问题,很有可能用户买了这次节目后,下一次可能要很久才会再购买别的节目,这样一直放在Redis中不太合适,需要设置个过期时间才行
那么设置过期时间多长合适呢?很简单,就是 用户登录的时间,当用户登录时间达到限制后,就可以将此统计数量从Redis中清除了
在查询节目详情的getDetail方法和getDetailV2方法中都执行了预先加载用户节目订单数量的操作
preloadAccountOrderCount(programVo.getId());
private void preloadAccountOrderCount(Long programId){
//获取userId
String userId = BaseParameterHolder.getParameter(USER_ID);
//平台code
String code = BaseParameterHolder.getParameter(CODE);
if (StringUtil.isEmpty(userId) || StringUtil.isEmpty(code)) {
return;
}
//获取用户登录状态
Boolean userLogin =
redisCache.hasKey(RedisKeyBuild.createRedisKey(RedisKeyManage.USER_LOGIN, code, userId));
//如果不是登录状态,则直接返回
if (!userLogin) {
return;
}
//异步执行
BusinessThreadPool.execute(() -> {
try {
//如果redis中不存在,则调用订单服务
if (!redisCache.hasKey(RedisKeyBuild.createRedisKey(RedisKeyManage.ACCOUNT_ORDER_COUNT,userId,programId))) {
AccountOrderCountDto accountOrderCountDto = new AccountOrderCountDto();
accountOrderCountDto.setUserId(Long.parseLong(userId));
accountOrderCountDto.setProgramId(programId);
ApiResponse<AccountOrderCountVo> apiResponse = orderClient.accountOrderCount(accountOrderCountDto);
if (Objects.equals(apiResponse.getCode(), BaseCode.SUCCESS.getCode())) {
//调用订单服务成功,则将数据放入到redis中
Optional.ofNullable(apiResponse.getData())
.ifPresent(accountOrderCountVo -> redisCache.set(
RedisKeyBuild.createRedisKey(RedisKeyManage.ACCOUNT_ORDER_COUNT,userId,programId),
accountOrderCountVo.getCount(), tokenExpireManager.getTokenExpireTime() + 1,
TimeUnit.MINUTES));
}else {
log.warn("orderClient.accountOrderCount 调用失败 apiResponse : {}",JSON.toJSONString(apiResponse));
}
}
}catch (Exception e) {
log.error("预热加载账户订单数量失败",e);
}
});
}
- 首先获取从gateway网关传过来的userId和code
- 异步执行更新逻辑
- 用户必须是登录中的状态
- 如果redis中不能存在,则调用订单服务数据
- 然后将数据更新到缓存中
调用订单服务统计
@ApiOperation(value = "账户下某个节目的订单数量(不提供给前端调用,只允许内部program服务调用)")
@PostMapping(value = "/account/order/count")
public ApiResponse<AccountOrderCountVo> accountOrderCount(@Valid @RequestBody AccountOrderCountDto accountOrderCountDto) {
return ApiResponse.ok(orderService.accountOrderCount(accountOrderCountDto));
}
public AccountOrderCountVo accountOrderCount(AccountOrderCountDto accountOrderCountDto) {
AccountOrderCountVo accountOrderCountVo = new AccountOrderCountVo();
//通过userId和programId查询出订单数量
accountOrderCountVo.setCount(orderMapper.accountOrderCount(accountOrderCountDto.getUserId(),
accountOrderCountDto.getProgramId()));
return accountOrderCountVo;
}
<mapper namespace="com.damai.mapper.OrderMapper">
<select id="accountOrderCount" resultType="java.lang.Integer">
select
count(*)
from d_order
where ( order_status = 1 or order_status = 3)
and user_id = #{userId,jdbcType=BIGINT}
and program_id = #{programId,jdbcType=BIGINT}
</select>
</mapper>
根据userId和programId从订单表中查询出数量,注意 订单表中的user_id和program_id这两个字段一定要建立好索引
在订单服务中查询好结果后,就可以返回给节目服务了
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)