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处理
-
为什么要函数式编程?
不用在每个地方都写“先查缓存 → 再查数据库 → 再写缓存”的重复逻辑。
Supplier<T> 是 Java 8 的函数式接口,表示一个 无参返回值的函数。
getFromCacheOrDb(cacheKey, () -> groupBuyActivityDao.queryValidGroupBuyActivityId(activityId));
-
在系统里,通常数据会先查 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();
}
}
119

被折叠的 条评论
为什么被折叠?



