
在从高级开发向架构师转型的道路上,理论与实践的鸿沟往往是最难跨越的。拓薪教育的 Java 架构师课程实战项目,正是为了填补这道鸿沟而生。它不再局限于 CRUD 的增删改查,而是带你从零开始,构建一个具备高可用、高并发、可扩展特性的企业级分布式电商/秒杀系统。
在这场实战中,我深刻体会到了分布式系统的“痛”与“快”。痛的是复杂度呈指数级上升,快的是系统在精心设计下所能迸发出的性能红利。以下是我从该项目中提炼出的核心技术点与架构演进实录。
一、 总体架构:微服务拆分与治理 项目采用了 Spring Cloud Alibaba 技术栈,彻底摒弃了单体巨石架构。我们将系统垂直拆分为多个核心微服务:
product-service(商品服务):负责商品管理、库存扣减。 order-service(订单服务):负责交易流程、状态机流转。 user-service(用户服务):负责用户鉴权、收货地址。 gateway-service(网关服务):统一流量入口,负责鉴权、限流、熔断。 关键技术:Nacos(注册中心/配置中心)、OpenFeign(服务调用)、Sentinel(流量哨兵)。
二、 难点攻克:分布式事务与最终一致性 在微服务架构下,订单服务创建订单成功后,商品服务需要扣减库存。如果库存扣减失败,如何回滚订单?传统 @Transactional 失效了。项目中我们使用了 Seata 来解决这一难题。
实战代码:Seata AT 模式分布式事务 Seata 的 AT 模式对业务代码零侵入,是入门分布式事务的首选。
- 订单业务逻辑(发起方)
@Service public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductFeignClient productFeignClient;
// 重点:@GlobalTransactional 注解开启全局事务
@GlobalTransactional(name = "create-order-tx", rollbackFor = Exception.class)
@Override
public void createOrder(OrderDTO orderDTO) {
// 1. 扣减库存(远程调用 Feign)
// 注意:这里会自动介入 Seata 代理,记录 Undo Log
productFeignClient.deductStock(orderDTO.getProductId(), orderDTO.getCount());
// 2. 创建订单(本地事务)
Order order = new Order();
BeanUtils.copyProperties(orderDTO, order);
order.setOrderNo(UUID.randomUUID().toString().replace("-", ""));
orderMapper.insert(order);
// 模拟异常:如果这里抛出异常,Seata 会自动回滚上面的库存扣减和本地订单插入
if (orderDTO.getAmount() < 0) {
throw new RuntimeException("金额不能为负数");
}
}
} 2. 库存业务逻辑(参与者)
@RestController public class ProductController {
@Autowired
private ProductService productService;
@PostMapping("/product/deduct")
public Result deductStock(@RequestParam("productId") Long productId, @RequestParam("count") Integer count) {
// 本地事务方法,Seata 会自动拦截并代理
productService.deduct(productId, count);
return Result.success();
}
}
@Service public class ProductServiceImpl {
@Autowired
private ProductMapper productMapper;
@Transactional
public void deduct(Long productId, Integer count) {
// 检查库存
Product product = productMapper.selectById(productId);
if (product.getStock() < count) {
throw new RuntimeException("库存不足");
}
// 扣减数据库
productMapper.deductStock(productId, count);
}
} 架构收获:通过 Seata,我们以极低的代码成本实现了跨服务的 ACID 特性,保证了分布式环境下数据的一致性。
三、 性能压轴:高并发秒杀与 Redis 架构 秒杀是整个项目的性能大考。面对百万级的 QPS,直接打到数据库是必死无疑的。课程中我们构建了多级缓存架构:浏览器缓存 -> CDN -> Nginx -> Redis 预减库存 -> 数据库。
实战代码:Redis Lua 脚本实现原子性扣库存 为了保证高并发下“超卖”问题,单纯使用 decr 命令不够,因为扣减后还需要判断结果并决定是否返回成功,这就需要原子操作。我们引入了 Lua 脚本。
@Service public class SecKillService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// Lua 脚本:保证查库存和扣库存的原子性
// KEYS[1]: 库存 Key
// ARGV[1]: 扣减数量
private static final String STOCK_LUA_SCRIPT =
"if redis.call('get', KEYS[1]) == false then " + // 检查 Key 是否存在
" return -1 " +
"end " +
"local stock = tonumber(redis.call('get', KEYS[1])) " +
"if stock < tonumber(ARGV[1]) then " +
" return 0 " + // 库存不足
"end " +
"redis.call('decrby', KEYS[1], ARGV[1]) " + // 扣减成功
"return 1 ";
public boolean doSecKill(String productId, String userId) {
String key = "seckill:stock:" + productId;
// 执行 Lua 脚本,Redis 是单线程模型,脚本执行期间不会被其他命令插队
Long result = stringRedisTemplate.execute(
new DefaultRedisScript<>(STOCK_LUA_SCRIPT, Long.class),
Collections.singletonList(key),
"1"
);
if (result == 1) {
// 库存扣减成功,发送 MQ 消息异步创建订单
// sendOrderMessage(userId, productId);
return true;
} else if (result == 0) {
throw new RuntimeException("库存不足");
} else {
throw new RuntimeException("商品信息不存在");
}
}
} 架构收获:Redis 的 Lua 脚本是解决高并发下并发竞态问题的神器。将多条命令打包原子执行,消除了网络交互带来的竞态条件,极大地提升了吞吐量。
四、 进阶防御:分布式锁与缓存击穿 在热点数据缓存过期的瞬间,大量请求会穿透缓存直接打到数据库,这就是“缓存击穿”。课程中我们使用了 Redisson 实现分布式锁,确保护同一时刻只有一个线程去重建缓存。
实战代码:Redisson 分布式锁
@Service public class ProductCacheService {
@Autowired
private RedissonClient redissonClient;
@Autowired
private ProductDao productDao;
public Product getProductCache(Long productId) {
String cacheKey = "product:info:" + productId;
// 1. 先查 Redis
String json = redissonClient.getBucket(cacheKey).get();
if (json != null) {
return JSON.parseObject(json, Product.class);
}
// 2. 获取分布式锁
RLock lock = redissonClient.getLock("lock:product:" + productId);
try {
// 3. 尝试加锁(等待时间 0,持有时间 10秒)
boolean isLocked = lock.tryLock(0, 10, TimeUnit.SECONDS);
if (isLocked) {
// 4. 双重检查:防止等待锁期间已经有其他线程重建了缓存
json = redissonClient.getBucket(cacheKey).get();
if (json != null) {
return JSON.parseObject(json, Product.class);
}
// 5. 查询数据库
Product product = productDao.selectById(productId);
if (product != null) {
// 6. 写入 Redis
redissonClient.getBucket(cacheKey).set(JSON.toJSONString(product), 30, TimeUnit.MINUTES);
}
return product;
} else {
// 获取锁失败,稍后重试或返回降级数据
Thread.sleep(100);
return getProductCache(productId);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7. 释放锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
return null;
}
} 架构收获:使用成熟的框架 Redisson 而不是手写 setnx,因为它自动处理了锁的续期、可重入等复杂逻辑,架构师要学会“站在巨人的肩膀上”。
五、 总结:从 0 到 1 的蜕变 通过拓薪教育这个实战项目,我亲手搭建了一个包含注册中心、配置中心、网关、分布式事务、缓存、消息队列等组件的完整分布式系统。
核心领悟:
解耦是微服务的灵魂:通过 Feign 和 MQ 打破服务间的强耦合。 一致性是分布式的底线:Seata 让我们不再对数据不一致感到恐惧。 性能优化的本质是空间换时间:多级缓存和 Lua 脚本就是最好的证明。 这次实战不仅仅是代码量的积累,更是架构思维的质变。它让我明白,一个合格的 Java 架构师,不仅要代码写得溜,更要懂权衡、懂取舍、懂业务。












评论(0)