笔记2025.11.26

MQ发送、消费消息

为什么由同步 HTTP 回调进化为异步 MQ 解耦?

痛点:企业内部微服务之间,如果采用 HTTP 同步调用,耦合度高,且在高并发下容易阻塞。

库表设计:notify_task:用于记录待执行的通知任务。当拼团成功时,先写库,再异步执行发送,确保事务一致性。group_buy_order:新增 notify_type 字段(枚举值:MQ 或 HTTP)(兜底机制)

使用分布式锁(Redis)防止多实例部署时重复执行同一个 notify_task。

public String groupBuyNotify(NotifyTaskEntity notifyTask) throws Exception {
    // 锁键设计:基于任务ID,确保针对单个任务的互斥
    RLock lock = redisService.getLock(notifyTask.lockKey());
    try {
        // tryLock(waitTime, leaseTime, unit)
        // waitTime=3: 允许短暂等待锁(应对网络抖动)
        // leaseTime=8: 锁持有8秒后自动释放(防止宕机死锁)
        if (lock.tryLock(3, 8, TimeUnit.SECONDS)) { 
            try {
                // 业务逻辑:判断是走 HTTP 还是 MQ
                // ...
            } finally {
                // 严谨释放锁:不仅要判断是否锁定,还要判断是否是当前线程持有的锁
                if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
        return NotifyTaskHTTPEnumVO.NULL.getCode();
    } catch (Exception e) {
        // ...
    }
}

Guava 没法让不同实例间负载消费(也就是A1应用发送的消息,A2应用不能消费。而分布式架构技术栈 MQ 是可以的)

小型支付商城(s-pay-mall)需要处理来自两个不同维度的业务通知: 外部对接拼团交易:

exchange: group_buy_market_exchange (这里是拼团服务的交换机)。

routing_key: topic.team_success

一句话总结:拼团服务告诉商城服务:“团满了,你可以结算了”。 内部解耦 (处理发货/后续动作):

exchange: s_pay_mall_exchange(这里是商城自己的交换机)

  • 场景:拼团成功后,或者普通订单支付成功后。

  • 动作

    • 商城服务发送 topic_order_pay_success 消息。

    • 商城服务自己监听该 Topic (OrderPaySuccessListener)。

    • 收到消息后,执行 changeOrderDealDone(模拟发货、充值、返利等),将订单最终状态置为 DEAL_DONE

独占锁和无锁化场景运用

任务调度场景(互备)

  • 分布式系统中有多台应用实例同时部署。

  • 每台实例都会尝试执行定时任务(比如拼团结算通知)。

  • 为了避免重复执行,需要一个 独占锁:谁抢到锁谁执行,执行完释放锁,下一轮再抢。

  • 目的:保证任务在多实例互备的情况下只执行一次。

库存抢占场景(拼团锁单

  • 用户下单时,需要抢占库存。

  • 如果用独占锁,所有用户要排队等待锁释放,吞吐量会很低。

  • 所以这里采用 无锁化设计(乐观锁

    • 通过 Redis 的 incr 原子操作来扣减库存。

    • 每次操作都会生成一个唯一值,基本避免冲突。

    • 如果出现异常(比如集群配置问题导致重复值),再用一个兜底的小锁来保证一致性。

  • 目的:提高并发性能,减少数据库行锁压力。

函数式数据缓存和降级到DB处理

  1. 为什么要函数式编程?

不用在每个地方都写“先查缓存 → 再查数据库 → 再写缓存”的重复逻辑。

Supplier<T> 是 Java 8 的函数式接口,表示一个 无参返回值的函数

getFromCacheOrDb(cacheKey, () -> groupBuyActivityDao.queryValidGroupBuyActivityId(activityId));
  1. 在系统里,通常数据会先查 Redis 缓存,缓存没有再查 数据库。 但有些场景下,缓存可能:

  • 出现故障(Redis 集群挂掉、网络异常)。

  • 数据不可靠(缓存未及时更新、脏数据)。

  • 需要强一致性(比如活动配置必须实时准确)。

这时就需要一个 降级开关

@DCCValue("cacheSwitch:0") 
private String cacheOpenSwitch; 
public boolean isCacheOpenSwitch() 
{ return "0".equals(cacheOpenSwitch); }

通用缓存处理方法 * 优先从缓存获取,缓存不存在则从数据库获取并写入缓存

protected <T> T getFromCacheOrDb(String cacheKey, Supplier<T> dbFallback, long expired) {

    if(dccService.isCacheOpenSwitch()){
        T cacheResult = redisService.getValue(cacheKey);

        if (cacheResult != null) {
            return cacheResult;
        }
        T dbResult = dbFallback.get();

        if (dbResult == null) {
            return null;
        }
        redisService.setValue(cacheKey, dbResult,expired);
        return dbResult;
    }else{
        logger.warn("缓存降级:{}", cacheKey);
        return dbFallback.get();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值