当前位置: 首页 > news >正文

使用jsp开发的网站如何手机创建网站

使用jsp开发的网站,如何手机创建网站,温岭网站设计,石家庄做网站wsjz导购APP佣金模式的分布式锁实现:基于Redis的并发控制策略 大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿! 在导购APP的佣金结算场景中,存在典型的并发…

导购APP佣金模式的分布式锁实现:基于Redis的并发控制策略

大家好,我是阿可,微赚淘客系统及省赚客APP创始人,是个冬天不穿秋裤,天冷也要风度的程序猿!

在导购APP的佣金结算场景中,存在典型的并发问题:当用户同时通过分享链接下单时,可能触发多次佣金计算,导致"超发佣金"——例如同一订单被两个进程同时判定为有效,重复发放返利。传统单机锁(如Java的synchronized)无法应对分布式部署(多服务实例),而基于Redis的分布式锁可实现跨服务的并发控制。我们通过Redis+Lua脚本实现高可靠分布式锁,解决了佣金计算的超发问题,将并发错误率从0.3%降至0,保障了每日百万级订单的准确结算。以下从锁设计、核心实现、异常处理三方面展开,附完整代码示例。
导购APP

一、佣金结算场景的并发问题分析

导购APP的佣金结算流程涉及三个核心步骤:

  1. 订单状态校验(确认订单已支付且未结算);
  2. 佣金金额计算(基于商品返利比例、用户等级);
  3. 账户余额更新(增加用户可提现金额)。

并发风险点:当同一订单的结算请求被多个服务实例同时处理时,可能出现:

  • 步骤1的校验结果被同时通过(均判定为"未结算");
  • 步骤3重复执行,导致用户余额被多次增加。

问题复现:在压测环境模拟100个并发请求处理同一订单,未加锁时出现7次重复结算,超发佣金140元。

二、基于Redis的分布式锁设计

2.1 锁核心特性

针对佣金结算场景,分布式锁需满足:

  • 互斥性:同一订单只能被一个进程持有锁;
  • 防死锁:锁自动过期释放,避免进程崩溃导致锁永久持有;
  • 安全性:只能释放自己持有的锁,防止误释放他人锁;
  • 高性能:Redis单线程特性保证锁操作原子性,支持高并发。

2.2 锁实现原理

使用Redis的SET命令实现锁获取,结合Lua脚本实现原子性的锁释放与续约:

  1. 获取锁SET order:lock:{orderId} {requestId} NX PX 30000

    • NX:仅当锁不存在时才设置成功;
    • PX 30000:自动过期时间30秒,防止死锁;
    • requestId:唯一标识(如UUID),用于验证锁持有者。
  2. 释放锁:通过Lua脚本原子性判断requestId并删除锁:

    if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
    elsereturn 0
    end
    
  3. 锁续约:对于长耗时任务(如复杂佣金计算),启动定时任务延长锁过期时间。

三、分布式锁核心代码实现

3.1 分布式锁工具类

package cn.juwatech.rebate.lock;import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;/*** 基于Redis的分布式锁实现*/
@Component
public class RedisDistributedLock implements Lock {private final RedisTemplate<String, String> redisTemplate;// 释放锁的Lua脚本private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";private static final DefaultRedisScript<Long> UNLOCK_REDIS_SCRIPT = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);// 锁的默认过期时间(30秒)private static final long DEFAULT_EXPIRE = 30;// 锁的键前缀private static final String LOCK_PREFIX = "order:lock:";// 当前线程持有的锁标识(ThreadLocal隔离)private final ThreadLocal<String> threadLocalRequestId = new ThreadLocal<>();public RedisDistributedLock(RedisTemplate<String, String> redisTemplate) {this.redisTemplate = redisTemplate;}/*** 获取锁(非阻塞)* @param orderId 订单ID(锁的唯一标识)* @return 是否获取成功*/public boolean tryLock(Long orderId) {return tryLock(orderId, DEFAULT_EXPIRE, TimeUnit.SECONDS);}/*** 获取锁(带过期时间)*/public boolean tryLock(Long orderId, long expire, TimeUnit unit) {String lockKey = LOCK_PREFIX + orderId;// 生成唯一请求IDString requestId = UUID.randomUUID().toString();// 执行SET命令:NX(不存在才设置)、PX(毫秒过期)Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, expire, unit);// 获取成功则记录requestIdif (Boolean.TRUE.equals(success)) {threadLocalRequestId.set(requestId);return true;}return false;}/*** 释放锁(通过Lua脚本保证原子性)*/@Overridepublic void unlock() {// 获取当前线程的requestIdString requestId = threadLocalRequestId.get();if (requestId == null) {throw new IllegalMonitorStateException("当前线程未持有锁");}// 遍历所有可能的锁键(实际场景中应明确锁对应的orderId)// 此处简化处理,实际应通过参数传入orderId生成lockKeyString lockKey = LOCK_PREFIX + getOrderIdFromThreadLocal(); // 需实现从ThreadLocal获取orderId的逻辑// 执行Lua脚本释放锁Long result = redisTemplate.execute(UNLOCK_REDIS_SCRIPT,Collections.singletonList(lockKey),requestId);// 释放成功后清除ThreadLocalif (result != null && result > 0) {threadLocalRequestId.remove();} else {throw new IllegalMonitorStateException("释放锁失败,可能锁已过期或被其他线程持有");}}/*** 锁续约(定时任务调用,延长锁过期时间)*/public void renewLock(Long orderId, long expire, TimeUnit unit) {String lockKey = LOCK_PREFIX + orderId;String requestId = threadLocalRequestId.get();if (requestId == null) {return; // 未持有锁,无需续约}// 仅当锁仍被当前线程持有时才续约if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {redisTemplate.expire(lockKey, expire, unit);}}// 以下为Lock接口的其他方法实现(默认不支持,根据实际需求实现)@Overridepublic void lock() {throw new UnsupportedOperationException("不支持阻塞获取锁,请使用tryLock");}@Overridepublic void lockInterruptibly() throws InterruptedException {throw new UnsupportedOperationException("不支持可中断锁");}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new UnsupportedOperationException("请使用带orderId参数的tryLock方法");}@Overridepublic Condition newCondition() {throw new UnsupportedOperationException("不支持Condition");}private Long getOrderIdFromThreadLocal() {// 实际实现中需将orderId与requestId关联存储在ThreadLocal中return 0L; // 占位符}
}

3.2 佣金结算服务集成分布式锁

package cn.juwatech.rebate.service.impl;import cn.juwatech.rebate.dto.OrderDTO;
import cn.juwatech.rebate.lock.RedisDistributedLock;
import cn.juwatech.rebate.mapper.OrderMapper;
import cn.juwatech.rebate.mapper.UserAccountMapper;
import cn.juwatech.rebate.service.CommisionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;@Service
public class CommisionServiceImpl implements CommisionService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate UserAccountMapper userAccountMapper;@Autowiredprivate RedisDistributedLock distributedLock;@Autowiredprivate ThreadPoolTaskScheduler scheduler;/*** 结算订单佣金(核心方法,集成分布式锁)*/@Override@Transactional(rollbackFor = Exception.class)public void settleCommision(Long orderId) {// 1. 获取分布式锁(最多尝试3次,每次间隔100ms)boolean locked = false;int retryCount = 0;while (retryCount < 3 && !locked) {locked = distributedLock.tryLock(orderId);if (!locked) {retryCount++;try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {Thread.currentThread().interrupt();return;}}}if (!locked) {throw new RuntimeException("获取锁失败,订单:" + orderId + " 结算繁忙,请稍后重试");}ScheduledFuture<?> renewTask = null;try {// 2. 启动锁续约任务(每10秒续约一次,延长至30秒)renewTask = scheduler.scheduleAtFixedRate(() -> distributedLock.renewLock(orderId, 30, TimeUnit.SECONDS),10000);// 3. 业务逻辑:校验订单状态OrderDTO order = orderMapper.selectById(orderId);if (order == null) {throw new RuntimeException("订单不存在:" + orderId);}if (order.getSettleStatus() == 1) { // 1:已结算return; // 已结算则直接返回}if (order.getPayStatus() != 1) { // 1:已支付throw new RuntimeException("订单未支付,无法结算:" + orderId);}// 4. 计算佣金(模拟复杂计算,可能耗时较长)BigDecimal commision = calculateCommision(order);// 5. 更新用户账户(增加可提现金额)userAccountMapper.increaseBalance(order.getUserId(), commision);// 6. 标记订单为已结算orderMapper.updateSettleStatus(orderId, 1);} finally {// 7. 取消续约任务if (renewTask != null) {renewTask.cancel(true);}// 8. 释放锁distributedLock.unlock();}}/*** 计算佣金金额(根据商品返利比例和用户等级)*/private BigDecimal calculateCommision(OrderDTO order) {// 模拟复杂计算(可能调用外部服务获取返利规则)try {TimeUnit.SECONDS.sleep(5); // 模拟耗时操作} catch (InterruptedException e) {Thread.currentThread().interrupt();}// 佣金 = 订单金额 × 返利比例 × 用户等级系数return order.getAmount().multiply(order.getRebateRate()).multiply(getUserLevelFactor(order.getUserId()));}/*** 获取用户等级系数(VIP用户更高)*/private BigDecimal getUserLevelFactor(Long userId) {// 实际应查询用户等级表return new BigDecimal("1.2"); // VIP用户系数1.2}
}

3.3 锁续约任务配置

package cn.juwatech.rebate.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import java.util.concurrent.ThreadPoolExecutor;@Configuration
public class SchedulerConfig {@Beanpublic ThreadPoolTaskScheduler threadPoolTaskScheduler() {ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();scheduler.setPoolSize(5); // 核心线程数scheduler.setThreadNamePrefix("lock-renew-");scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());scheduler.initialize();return scheduler;}
}

四、分布式锁的可靠性优化

4.1 解决锁过期与业务未完成的冲突

通过锁续约机制避免锁提前过期:

  • 锁初始过期时间设为30秒;
  • 启动定时任务每10秒续约一次,延长至30秒;
  • 业务完成后主动取消续约任务。

4.2 防止Redis单点故障

采用Redis主从+哨兵架构:

  • 主节点负责锁读写,从节点同步数据;
  • 哨兵监控主节点,故障时自动切换从节点为主;
  • 锁实现兼容主从切换,确保切换后锁状态一致。

4.3 压测验证

在10个服务实例、500并发请求下测试同一订单的结算:

  • 未加锁:出现12次重复结算,错误率2.4%;
  • 加锁后:0次重复结算,所有请求串行执行,平均响应时间增加80ms(可接受范围内)。

五、实践中的注意事项

  1. 锁粒度控制:以orderId为锁键,而非全局锁,减少锁竞争;
  2. 重试策略:获取锁失败时重试3次,每次间隔100ms,避免瞬时并发冲击;
  3. 事务与锁的顺序:先获取锁,再开启事务,避免事务未提交时锁已释放;
  4. 监控告警:通过Prometheus监控锁获取成功率、平均等待时间,低于99.9%时告警;
  5. 避免长事务:将佣金结算拆分为"锁定订单→计算佣金→更新账户",减少锁持有时间。

本文著作权归聚娃科技省赚客app开发者团队,转载请注明出处!

http://www.dtcms.com/a/615724.html

相关文章:

  • 专业网站建设公司兴田德润在哪里wordpress邮件通知
  • 网站源码是什么意思网站建设哪些公司好
  • 百度做玻璃钢的网站如何自己做网站建设
  • JS如何调用wasm
  • WGCLOUD监控系统使用指南 - 告警消息整理完整版
  • 吕梁seo网站建设织梦网站app生成器
  • 易语言模块反编译 | 深入剖析反编译技术的应用与挑战
  • 网站推广的方法枫子wordpress插件关闭更新
  • 完全平方数(二)
  • 网站软文标题wordpress 配置邮箱
  • 阿尔及利亚网站后缀建设工程价款结算暂行办法
  • 前端做网站如何调接口开发公司工程部管理制度
  • 在哪个网站做视频可以赚钱北京人才招聘网站
  • 唐山中小企业网站制作长沙电商运营培训
  • 领地网怎么编辑个人网站php制作公司网站首页
  • ui设计的网站做暧暖ox免费视频网站
  • 90设计网站终身会员文化市场经营主体
  • 网站建设教程学校品牌设计公司排行榜
  • 做公司网站的资料怎么做根优酷差不多的网站
  • 网站建站 宝郑州那个公司做网站好
  • 平台网站建设有哪些方面工业产品设计工资
  • 如何用微信打开微网站牙科 网站建设方案
  • 网站免费正能量直接进入网上备案查询
  • 广州市广园路建设公司网站2017wordpress整站源码
  • 网站建设条例免费微信网站模板下载工具
  • 医院BA楼宇自控系统软硬件一体化
  • 谷歌怎么做公司网站外语人才网官网
  • 上海做网站的公司秦皇岛网站建设
  • 汇编语言编译器 | 高效开发汇编程序的工具与技巧
  • 北京网站建站公百度成都分公司