作者寄语:作为一个在Java战场上摸爬滚打10年的老兵,我见过太多"年久失修"的系统。今天,咱们就来聊聊如何把一台"老爷车"改装成"超级跑车"!🏎️
📚 目录
🎭 前言:老系统的"七宗罪" {#前言}
什么是SSH框架?
SSH不是你用来远程登录服务器的那个SSH!这里说的是:
- Struts(展示层框架,2000年代的网红)
- Spring(IoC容器,唯一还活跃的)
- Hibernate(ORM框架,对象关系映射)
生活类比:SSH框架就像诺基亚手机,在它的时代是王者,但现在... 你懂的 😅
老系统的"七宗罪"
- 傲慢(Monolithic):一个War包打天下,动辄几百MB
- 贪婪(Memory):内存占用像无底洞
- 懒惰(Deployment):部署一次要半小时,改一行代码重启半天
- 嫉妒(Technology):看着别人用Spring Boot、微服务,只能干瞪眼
- 愤怒(Bug):改个Bug就像拆炸弹,不知道哪里会爆
- 暴食(Database):数据库连接池耗尽,全局事务一锅端
- 色欲(Coupling):代码耦合得像意大利面,扯不清楚
真实案例:
某电商系统(基于SSH)
├── war包大小:380MB 😱
├── 启动时间:8分钟 ⏰
├── 单次部署:25分钟 💀
├── 代码行数:50万+ 📚
└── 维护人员:已跑路3批 🏃
🔍 战前侦察:全面体检 {#战前侦察}
第一步:画出"家谱图"(系统架构图)
工具推荐:
- 架构图:Draw.io、PlantUML、Excalidraw
- 代码分析:SonarQube、IDEA的依赖关系图
- 调用链路:Arthas、JProfiler
典型SSH系统的"家谱":
graph TD
A[浏览器] --> B[Struts Action]
B --> C[Service层]
C --> D[DAO层]
D --> E[(MySQL数据库)]
C --> F[其他Service]
F --> D
C --> G[定时任务]
G --> D
style A fill:#f9f,stroke:#333
style B fill:#ff9,stroke:#333
style E fill:#9f9,stroke:#333
生活类比:这就像给你家画户型图,知道哪个房间是厨房,哪个是卧室,水电线路怎么走。
第二步:找出"病灶"(问题盘点)
🔧 技术维度分析
| 维度 | 老系统现状 | 问题指数 | 比喻 |
|---|---|---|---|
| 启动速度 | 5-10分钟 | ⭐⭐⭐⭐⭐ | 像老爷爷起床,要慢慢来 |
| 部署频率 | 周/月级别 | ⭐⭐⭐⭐⭐ | 像坐绿皮火车,一天一班 |
| 扩展性 | 无法水平扩展 | ⭐⭐⭐⭐ | 房子太小,没法加房间 |
| 技术栈 | JDK 6/7 + Tomcat 7 | ⭐⭐⭐⭐⭐ | 用着Windows XP的电脑 |
| 代码质量 | 圈复杂度>50 | ⭐⭐⭐⭐ | 代码像迷宫 |
| 测试覆盖 | <20% | ⭐⭐⭐⭐⭐ | 裸奔状态 |
📊 业务维度分析
// 典型的SSH代码 - 看着就头疼 😫
public class UserAction extends ActionSupport {
private UserService userService; // Spring注入
public String execute() {
// 业务逻辑、数据验证、权限检查全混在一起
HttpServletRequest request = ServletActionContext.getRequest();
String userId = request.getParameter("userId");
// 没有参数验证
User user = userService.getUserById(userId);
// 直接在Action里写业务逻辑 🤦
if(user.getType() == 1) {
// 一大堆if-else...
}
// Session操作
request.getSession().setAttribute("user", user);
return SUCCESS;
}
}
第三步:量化评估(给系统打分)
健康度评估表:
系统健康度评分卡
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📈 性能指标: 35/100 ❌
- 响应时间:>3秒
- 并发支持:<500 QPS
- CPU使用率:峰值>80%
🏗️ 架构质量: 25/100 ❌
- 模块化程度:低
- 代码耦合度:高
- 可扩展性:差
🔧 维护成本: 30/100 ❌
- Bug修复周期:>3天
- 新功能开发:困难
- 文档完善度:无
🚀 技术先进性: 20/100 ❌
- 技术栈年龄:>10年
- 社区活跃度:低
- 安全漏洞:多
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
总分:27.5/100 - 濒危状态 🆘
🎯 战略规划:制定改造蓝图 {#战略规划}
策略一:绞杀者模式(Strangler Fig Pattern)🌳
什么是绞杀者模式?
在热带雨林中,有一种榕树叫"绞杀榕"。它的种子落在大树上,生根发芽,逐渐包围宿主,最终取而代之。
技术实现:
graph LR
A[用户请求] --> B{API网关}
B -->|新功能| C[新系统<br/>Spring Boot]
B -->|老功能| D[老系统<br/>SSH]
C --> E[(新数据库)]
D --> F[(老数据库)]
style C fill:#9f9,stroke:#333
style D fill:#f99,stroke:#333
实施步骤:
第一阶段:建立"包围圈"
// 1. 先搭建API网关(使用Spring Cloud Gateway)
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// 新功能路由到新系统
.route("new_user_service", r -> r
.path("/api/v2/users/**")
.uri("http://new-user-service:8080"))
// 老功能继续走老系统
.route("legacy_system", r -> r
.path("/api/v1/**")
.uri("http://legacy-ssh-system:8080"))
.build();
}
}
第二阶段:逐步"蚕食"
优先级排序(先吃软柿子):
-
✅ 无状态的查询接口(最简单)
- 用户查询
- 商品列表
- 订单详情
-
✅ 独立的业务模块(相对简单)
- 消息通知
- 文件上传
- 日志记录
-
⚠️ 核心业务逻辑(需谨慎)
- 订单创建
- 支付流程
- 库存管理
-
🔥 强耦合模块(最后啃)
- 分布式事务
- 复杂工作流
- 历史数据迁移
生活类比:就像吃披萨,先从边缘吃起,最后才吃中间有肉的部分!🍕
策略二:分层改造法(Layer by Layer)
架构演进路线图:
阶段1:保留外壳,改造内核 🥚
┌─────────────────────┐
│ Struts (保留) │
├─────────────────────┤
│ Service (改造) │ ← 重构为Spring Boot Service
├─────────────────────┤
│ DAO (改造) │ ← MyBatis Plus替代Hibernate
├─────────────────────┤
│ Database │
└─────────────────────┘
阶段2:替换表示层 🎨
┌─────────────────────┐
│ RESTful API (新) │ ← Spring Boot + Vue/React
├─────────────────────┤
│ Service Layer │
├─────────────────────┤
│ DAO Layer │
├─────────────────────┤
│ Database │
└─────────────────────┘
阶段3:拆分微服务 🧩
┌──────┐ ┌──────┐ ┌──────┐
│用户服务│ │订单服务│ │商品服务│
└───┬──┘ └───┬──┘ └───┬──┘
│ │ │
└─────────┴─────────┘
│
┌─────┴─────┐
│ API网关 │
└───────────┘
策略三:并行开发法(Parallel Development)
场景:业务压力大,不能停,又想快速见效
方案:新老系统并行,双写验证
/**
* 并行开发:新老系统同时运行,对比结果
*/
@Service
public class DualSystemService {
@Autowired
private LegacyUserService legacyService; // 老系统
@Autowired
private NewUserService newService; // 新系统
public UserDTO getUser(Long userId) {
// 同时调用新老系统
UserDTO oldResult = legacyService.getUser(userId);
try {
UserDTO newResult = newService.getUser(userId);
// 对比结果,记录差异
if (!Objects.equals(oldResult, newResult)) {
log.warn("结果不一致!Old: {}, New: {}",
oldResult, newResult);
// 发送告警
alertService.send("数据不一致告警");
}
} catch (Exception e) {
log.error("新系统调用失败", e);
}
// 先返回老系统结果(保险)
return oldResult;
}
}
灰度发布策略:
# 流量分配配置
traffic:
legacy: 90% # 老系统90%流量
new: 10% # 新系统10%流量(小白鼠)
# 逐步提升
week-1: 老90% vs 新10%
week-2: 老70% vs 新30%
week-3: 老50% vs 新50%
week-4: 老30% vs 新70%
week-5: 老10% vs 新90%
week-6: 完全切换到新系统 🎉
🛠️ 技术选型:挑选你的"武器库" {#技术选型}
核心框架选择
1️⃣ Spring Boot 3.x(必选,没有之一)
为什么选它?
- ✅ 约定大于配置,开箱即用
- ✅ 内嵌Tomcat,告别War包
- ✅ 自动装配,省心省力
- ✅ 生态丰富,轮子多
对比SSH:
| 特性 | SSH | Spring Boot |
|---|---|---|
| 配置文件 | XML地狱 😈 | 几行YAML 😊 |
| 启动时间 | 5-10分钟 ⏰ | 10-30秒 ⚡ |
| 部署方式 | War包+Tomcat 📦 | 独立Jar 🚀 |
| 学习曲线 | 陡峭 📈 | 平缓 📉 |
代码对比:
// ❌ SSH时代:配置文件100行起步
// applicationContext.xml
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/db"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>User.hbm.xml</value>
</list>
</property>
</bean>
// ✅ Spring Boot时代:几行配置搞定
// application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/db
username: root
password: 123456
jpa:
hibernate:
ddl-auto: update
2️⃣ 数据库持久层:MyBatis Plus vs JPA
选择指南:
MyBatis Plus JPA/Hibernate
↓ ↓
┌──────────────────┐ ┌──────────────────┐
│ 适合场景: │ │ 适合场景: │
│ • 复杂SQL多 │ │ • 简单CRUD │
│ • 性能要求高 │ │ • 快速开发 │
│ • 老项目迁移 │ │ • 标准化优先 │
│ • 动态SQL │ │ • 跨数据库 │
└──────────────────┘ └──────────────────┘
↓ ↓
学习成本:中 学习成本:高
性能:优秀 性能:一般
灵活性:极高 灵活性:中
MyBatis Plus示例(推荐!):
// 实体类
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
}
// Mapper接口(自带CRUD,无需写SQL)
public interface UserMapper extends BaseMapper<User> {
// 就这么简单!!!
}
// Service层
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 复杂查询:链式调用,优雅!
public List<User> getActiveUsers() {
return lambdaQuery()
.eq(User::getStatus, 1)
.gt(User::getCreateTime, LocalDateTime.now().minusDays(30))
.orderByDesc(User::getCreateTime)
.list();
}
}
3️⃣ API网关:Spring Cloud Gateway
为什么需要网关?
生活类比:API网关就像小区门卫 👮
没有网关:
每个外卖员都直接上楼敲门 → 乱套了!
有了网关:
外卖员 → 门卫(验证身份)→ 分配到对应楼栋 → 业主
↓
统一管理
- 身份验证
- 限流熔断
- 日志记录
- 路由转发
代码示例:
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
// 用户服务路由
.route("user-service", r -> r
.path("/api/users/**")
.filters(f -> f
.stripPrefix(1) // 去掉/api前缀
.addRequestHeader("X-Gateway", "true")
.circuitBreaker(c -> c.setName("userCB")) // 熔断
.retry(3)) // 重试3次
.uri("lb://user-service")) // 负载均衡
// 订单服务路由
.route("order-service", r -> r
.path("/api/orders/**")
.filters(f -> f
.requestRateLimiter(r2 -> r2 // 限流
.setRateLimiter(redisRateLimiter())))
.uri("lb://order-service"))
.build();
}
}
4️⃣ 服务注册与发现:Nacos
为什么选Nacos?
Nacos = Naming + Configuration + Service
↓
注册中心 + 配置中心 + 服务管理
一个组件顶三个,省钱!💰
使用示例:
# application.yml
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
namespace: production
config:
server-addr: localhost:8848
file-extension: yml
// 服务提供者
@RestController
@RequestMapping("/users")
public class UserController {
// 自动注册到Nacos,其他服务可发现
}
// 服务消费者
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public User getUser(Long userId) {
// 通过服务名调用,自动负载均衡
return restTemplate.getForObject(
"http://user-service/users/" + userId,
User.class
);
}
}
完整技术栈地图 🗺️
┌─────────────────────────────────────────────────┐
│ 前端层 (Frontend) │
│ Vue 3 / React 18 + Ant Design / Element Plus │
└───────────────────┬─────────────────────────────┘
│ HTTP/HTTPS
┌───────────────────┴─────────────────────────────┐
│ API网关 (Gateway) │
│ Spring Cloud Gateway + OAuth2 │
│ 功能:路由、限流、熔断、认证 │
└───────────────────┬─────────────────────────────┘
│
┌───────────┼───────────┬─────────────┐
│ │ │ │
┌───────┴──┐ ┌────┴────┐ ┌──┴──────┐ ┌──┴──────┐
│ 用户服务 │ │ 订单服务 │ │ 商品服务 │ │ 支付服务 │
│ User │ │ Order │ │ Product │ │ Payment │
└────┬─────┘ └────┬────┘ └────┬────┘ └────┬────┘
│ │ │ │
│ Spring Boot 3 + MyBatis Plus │
│ │ │ │
┌────┴─────────────┴─────────────┴─────────────┴───┐
│ 中间件层 (Middleware) │
│ • Nacos (注册中心+配置中心) │
│ • Redis (缓存+分布式锁) │
│ • RocketMQ/Kafka (消息队列) │
│ • Elasticsearch (搜索引擎) │
└─────────────────────┬─────────────────────────────┘
│
┌─────────────────────┴─────────────────────────────┐
│ 数据层 (Data) │
│ • MySQL 8.0 (主数据库) │
│ • MongoDB (日志/文档) │
│ • MinIO/OSS (对象存储) │
└───────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────┐
│ 基础设施 (Infrastructure) │
│ • Docker + Kubernetes (容器编排) │
│ • Jenkins/GitLab CI (CI/CD) │
│ • Prometheus + Grafana (监控) │
│ • ELK Stack (日志分析) │
│ • SkyWalking (链路追踪) │
└───────────────────────────────────────────────────┘
⚔️ 实战演练:重构的N种姿势 {#实战演练}
场景一:用户登录模块改造(热身)
原SSH代码(地狱难度):
// UserAction.java - 什么都往里塞
public class UserAction extends ActionSupport {
private UserService userService;
private String username;
private String password;
public String login() {
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = request.getSession();
// 验证参数(没有统一验证)
if(username == null || "".equals(username.trim())) {
request.setAttribute("error", "用户名不能为空");
return ERROR;
}
// 查询用户(SQL注入风险)
User user = userService.findByUsername(username);
// 业务逻辑混乱
if(user != null) {
if(user.getPassword().equals(password)) { // 明文密码!!!
session.setAttribute("currentUser", user);
// 日志记录(格式不统一)
System.out.println("用户登录:" + username);
return SUCCESS;
}
}
request.setAttribute("error", "用户名或密码错误");
return ERROR;
}
}
// UserService.java
public class UserServiceImpl {
private UserDAO userDAO;
public User findByUsername(String username) {
// HQL查询
String hql = "from User where username = '" + username + "'";
// SQL注入漏洞!!!
return (User)userDAO.getSession()
.createQuery(hql)
.uniqueResult();
}
}
新Spring Boot代码(天堂体验):
// 1. 实体类(使用Lombok简化代码)
@Data
@TableName("t_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20")
private String username;
private String password; // 存储BCrypt加密后的密码
private Integer status; // 0-禁用 1-正常
private LocalDateTime lastLoginTime;
}
// 2. DTO(数据传输对象)
@Data
public class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@NotBlank(message = "密码不能为空")
private String password;
}
@Data
public class LoginResponse {
private String token;
private UserDTO userInfo;
}
// 3. Controller层(清爽)
@RestController
@RequestMapping("/api/auth")
@Slf4j
public class AuthController {
@Autowired
private AuthService authService;
@PostMapping("/login")
public Result<LoginResponse> login(
@Valid @RequestBody LoginRequest request) { // 自动参数验证
log.info("用户登录请求:{}", request.getUsername());
LoginResponse response = authService.login(request);
return Result.success(response);
}
}
// 4. Service层(业务逻辑)
@Service
@Slf4j
public class AuthService {
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder; // BCrypt加密
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public LoginResponse login(LoginRequest request) {
// 1. 查询用户(防SQL注入)
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getUsername, request.getUsername())
);
// 2. 验证用户
if (user == null) {
throw new BusinessException("用户不存在");
}
if (user.getStatus() == 0) {
throw new BusinessException("用户已被禁用");
}
// 3. 验证密码(BCrypt自动防暴力破解)
if (!passwordEncoder.matches(
request.getPassword(), user.getPassword())) {
// 记录失败次数(防暴力破解)
String key = "login:fail:" + user.getId();
Long failCount = redisTemplate.opsForValue()
.increment(key, 1);
redisTemplate.expire(key, 15, TimeUnit.MINUTES);
if (failCount > 5) {
throw new BusinessException("失败次数过多,请15分钟后再试");
}
throw new BusinessException("密码错误");
}
// 4. 生成Token
String token = jwtTokenUtil.generateToken(user.getId());
// 5. 缓存用户信息(减少数据库查询)
redisTemplate.opsForValue().set(
"user:token:" + token,
user,
7, TimeUnit.DAYS
);
// 6. 更新最后登录时间
userMapper.update(null,
new LambdaUpdateWrapper<User>()
.eq(User::getId, user.getId())
.set(User::getLastLoginTime, LocalDateTime.now())
);
// 7. 记录日志(结构化日志)
log.info("用户登录成功:userId={}, username={}, ip={}",
user.getId(), user.getUsername(),
RequestUtil.getClientIp());
// 8. 发送登录通知(异步)
eventPublisher.publishEvent(
new LoginSuccessEvent(user.getId()));
// 9. 返回结果
LoginResponse response = new LoginResponse();
response.setToken(token);
response.setUserInfo(BeanUtil.copyProperties(
user, UserDTO.class));
return response;
}
}
对比总结:
| 维度 | SSH版本 | Spring Boot版本 |
|---|---|---|
| 代码行数 | ~50行 | ~80行(但功能多10倍) |
| 参数验证 | 手写 | 注解自动验证 ✅ |
| SQL注入 | 有风险 ❌ | 自动防护 ✅ |
| 密码安全 | 明文 ❌ | BCrypt加密 ✅ |
| 防暴力破解 | 无 ❌ | Redis计数器 ✅ |
| 日志规范 | println ❌ | 结构化日志 ✅ |
| 异常处理 | 返回字符串 | 统一异常 ✅ |
| 可测试性 | 差 | 优秀 ✅ |
场景二:订单创建(困难模式)
挑战点:
- 涉及多个表(订单、订单详情、库存、积分)
- 需要事务一致性
- 性能要求高
老系统问题:
// 全局事务,锁表时间长
@Transactional
public void createOrder(Order order) {
// 1. 保存订单
orderDAO.save(order);
// 2. 保存订单详情
for(OrderItem item : order.getItems()) {
orderItemDAO.save(item);
}
// 3. 扣减库存(锁表!)
for(OrderItem item : order.getItems()) {
Product product = productDAO.findById(item.getProductId());
product.setStock(product.getStock() - item.getQuantity());
productDAO.update(product); // 全表锁,性能杀手!
}
// 4. 增加用户积分
User user = userDAO.findById(order.getUserId());
user.setPoints(user.getPoints() + order.getAmount() / 10);
userDAO.update(user);
// 任何一步失败,全部回滚 - 导致订单失败率高
}
新系统方案(微服务+异步):
// 订单服务
@Service
@Slf4j
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RocketMQTemplate rocketMQTemplate;
/**
* 创建订单(同步处理核心流程)
*/
@Transactional(rollbackFor = Exception.class)
public OrderDTO createOrder(CreateOrderRequest request) {
// 1. 参数验证
validateOrderRequest(request);
// 2. 检查库存(调用商品服务)
checkStock(request.getItems());
// 3. 创建订单(只操作订单表)
Order order = buildOrder(request);
orderMapper.insert(order);
// 4. 保存订单详情
saveBatchOrderItems(order.getId(), request.getItems());
// 5. 发送消息(异步处理)
OrderCreatedEvent event = new OrderCreatedEvent();
event.setOrderId(order.getId());
event.setUserId(request.getUserId());
event.setItems(request.getItems());
event.setAmount(order.getTotalAmount());
// 发到消息队列,解耦!
rocketMQTemplate.syncSend(
"order-created-topic",
MessageBuilder.withPayload(event).build()
);
log.info("订单创建成功:orderId={}", order.getId());
return convert(order);
}
/**
* 调用商品服务检查库存
*/
private void checkStock(List<OrderItemRequest> items) {
// 使用Feign调用商品服务
StockCheckRequest stockRequest = new StockCheckRequest();
stockRequest.setItems(items.stream()
.collect(Collectors.toMap(
OrderItemRequest::getProductId,
OrderItemRequest::getQuantity
)));
Result<Boolean> result = productFeignClient
.checkStock(stockRequest);
if (!result.isSuccess() || !result.getData()) {
throw new BusinessException("库存不足");
}
}
}
// 库存服务(监听消息,异步扣减)
@Component
@RocketMQMessageListener(
topic = "order-created-topic",
consumerGroup = "stock-consumer-group"
)
public class StockDeductListener
implements RocketMQListener<OrderCreatedEvent> {
@Autowired
private StockService stockService;
@Override
public void onMessage(OrderCreatedEvent event) {
try {
// 扣减库存(使用乐观锁)
for (OrderItem item : event.getItems()) {
boolean success = stockService.deduct(
item.getProductId(),
item.getQuantity()
);
if (!success) {
// 库存不足,发送补偿消息
sendCompensateMessage(event);
return;
}
}
log.info("库存扣减成功:orderId={}", event.getOrderId());
} catch (Exception e) {
log.error("库存扣减失败", e);
// 重试机制
throw e;
}
}
}
// 积分服务(监听消息,异步增加积分)
@Component
@RocketMQMessageListener(
topic = "order-created-topic",
consumerGroup = "points-consumer-group"
)
public class PointsAddListener
implements RocketMQListener<OrderCreatedEvent> {
@Autowired
private PointsService pointsService;
@Override
public void onMessage(OrderCreatedEvent event) {
// 计算积分
int points = event.getAmount().intValue() / 10;
// 增加积分
pointsService.addPoints(event.getUserId(), points);
log.info("积分增加成功:userId={}, points={}",
event.getUserId(), points);
}
}
// 库存扣减(乐观锁实现)
@Service
public class StockService {
@Autowired
private ProductMapper productMapper;
/**
* 乐观锁扣减库存
*/
public boolean deduct(Long productId, Integer quantity) {
int rows = productMapper.deductStock(productId, quantity);
return rows > 0;
}
}
// Mapper
@Mapper
public interface ProductMapper extends BaseMapper<Product> {
/**
* 乐观锁扣减库存
* UPDATE product
* SET stock = stock - #{quantity}, version = version + 1
* WHERE id = #{productId}
* AND stock >= #{quantity}
* AND version = #{version}
*/
@Update("UPDATE t_product SET " +
"stock = stock - #{quantity}, " +
"version = version + 1 " +
"WHERE id = #{productId} " +
"AND stock >= #{quantity}")
int deductStock(@Param("productId") Long productId,
@Param("quantity") Integer quantity);
}
架构对比:
老系统(同步强一致):
┌──────────────────────────────────────┐
│ 创建订单(10秒) │
│ ├─ 保存订单 (0.1s) │
│ ├─ 保存详情 (0.2s) │
│ ├─ 扣减库存 (5s) ← 锁表,慢! │
│ ├─ 增加积分 (0.5s) │
│ └─ 发送通知 (4s) ← HTTP调用,慢! │
└──────────────────────────────────────┘
任何一步失败,全部回滚 ❌
新系统(异步最终一致):
┌──────────────────────┐
│ 创建订单(0.3秒) │ ← 只保存订单
│ ├─ 保存订单 (0.1s) │
│ ├─ 保存详情 (0.2s) │
│ └─ 发送消息 (0.01s) │ ← 发到MQ就返回
└──────────────────────┘
│
├──→ 库存服务(异步扣减)
├──→ 积分服务(异步增加)
└──→ 通知服务(异步发送)
性能提升:33倍 🚀
用户体验:秒级响应 ✅
场景三:分布式事务(地狱模式)
问题:订单支付后,需要:
- 更新订单状态
- 扣减库存
- 增加积分
- 发送通知
如何保证一致性?
方案一:Seata AT模式(推荐)
// 引入依赖
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
// 订单服务
@Service
public class OrderService {
@Autowired
private ProductFeignClient productClient;
@Autowired
private PointsFeignClient pointsClient;
/**
* @GlobalTransactional 开启分布式事务
* Seata会自动协调各个服务的事务提交/回滚
*/
@GlobalTransactional(
name = "create-order",
rollbackFor = Exception.class
)
public void createOrderWithDistributedTransaction(
CreateOrderRequest request) {
// 1. 创建订单(本地事务)
Order order = createLocalOrder(request);
// 2. 扣减库存(远程调用,也在分布式事务中)
Result<Void> stockResult = productClient.deductStock(
order.getId(),
request.getItems()
);
if (!stockResult.isSuccess()) {
// 抛异常,Seata自动回滚所有服务的事务
throw new BusinessException("库存扣减失败");
}
// 3. 增加积分(远程调用)
Result<Void> pointsResult = pointsClient.addPoints(
order.getUserId(),
order.getTotalAmount() / 10
);
if (!pointsResult.isSuccess()) {
// 抛异常,自动回滚
throw new BusinessException("积分增加失败");
}
// 全部成功,Seata自动提交所有事务 ✅
}
}
方案二:可靠消息最终一致性(推荐)
/**
* 本地消息表方案
*/
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private LocalMessageMapper messageMapper;
@Autowired
private RocketMQTemplate rocketMQTemplate;
@Transactional(rollbackFor = Exception.class)
public void createOrder(CreateOrderRequest request) {
// 1. 创建订单
Order order = buildOrder(request);
orderMapper.insert(order);
// 2. 保存本地消息(同一个事务)
LocalMessage message = new LocalMessage();
message.setOrderId(order.getId());
message.setContent(JSON.toJSONString(order));
message.setStatus(0); // 待发送
messageMapper.insert(message);
// 本地事务提交
}
/**
* 定时任务:扫描本地消息表,发送消息
*/
@Scheduled(fixedDelay = 5000)
public void scanLocalMessages() {
List<LocalMessage> messages = messageMapper.selectList(
new LambdaQueryWrapper<LocalMessage>()
.eq(LocalMessage::getStatus, 0)
.lt(LocalMessage::getRetryCount, 3)
);
for (LocalMessage message : messages) {
try {
// 发送到MQ
rocketMQTemplate.syncSend(
"order-created-topic",
message.getContent()
);
// 更新状态为已发送
message.setStatus(1);
messageMapper.updateById(message);
} catch (Exception e) {
// 发送失败,增加重试次数
message.setRetryCount(message.getRetryCount() + 1);
messageMapper.updateById(message);
}
}
}
}
// 库存服务(消费消息,保证幂等)
@Component
@RocketMQMessageListener(
topic = "order-created-topic",
consumerGroup = "stock-consumer"
)
public class StockListener implements RocketMQListener<String> {
@Override
public void onMessage(String message) {
Order order = JSON.parseObject(message, Order.class);
// 幂等性检查(防止重复消费)
String key = "stock:deduct:" + order.getId();
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 1, TimeUnit.DAYS);
if (Boolean.FALSE.equals(success)) {
log.info("重复消息,忽略:{}", order.getId());
return;
}
// 扣减库存
stockService.deduct(order.getItems());
}
}
🛡️ 风险管控:别把系统搞炸了 {#风险管控}
风险一:新老系统数据不一致
问题场景:
用户在老系统下单 → 库存在新系统扣减 → 数据不同步 💥
解决方案:双写+对账
@Service
public class ProductService {
@Autowired
private LegacyProductDAO legacyDAO; // 老系统DAO
@Autowired
private ProductMapper newMapper; // 新系统Mapper
/**
* 更新商品(双写)
*/
@Transactional(rollbackFor = Exception.class)
public void updateProduct(Product product) {
// 1. 写新系统
try {
newMapper.updateById(product);
} catch (Exception e) {
log.error("新系统写入失败", e);
throw e; // 失败直接抛异常
}
// 2. 写老系统(降级处理)
try {
legacyDAO.update(convertToLegacy(product));
} catch (Exception e) {
// 记录失败,但不影响新系统
log.error("老系统写入失败,记录到补偿队列", e);
compensationService.record(product);
}
}
/**
* 定时对账任务
*/
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点
public void reconciliation() {
// 1. 查询新系统所有商品
List<Product> newProducts = newMapper.selectList(null);
// 2. 对比老系统数据
for (Product newProduct : newProducts) {
Product legacyProduct = legacyDAO.findById(
newProduct.getId()
);
// 3. 发现不一致,记录并告警
if (!isEqual(newProduct, legacyProduct)) {
log.error("数据不一致!new={}, legacy={}",
newProduct, legacyProduct);
// 发送告警
alertService.send(
"数据不一致",
String.format("商品ID=%d数据不一致",
newProduct.getId())
);
// 以新系统为准,修复老系统数据
legacyDAO.update(convertToLegacy(newProduct));
}
}
}
}
风险二:性能下降
监控指标:
/**
* 自定义性能监控
*/
@Aspect
@Component
@Slf4j
public class PerformanceMonitorAspect {
@Around("@annotation(com.example.annotation.Monitor)")
public Object monitor(ProceedingJoinPoint joinPoint)
throws Throwable {
String methodName = joinPoint.getSignature().getName();
long startTime = System.currentTimeMillis();
Object result = null;
try {
result = joinPoint.proceed();
return result;
} finally {
long costTime = System.currentTimeMillis() - startTime;
// 记录到监控系统
MetricsCollector.record(methodName, costTime);
// 超过阈值告警
if (costTime > 3000) {
log.warn("方法执行超时!method={}, cost={}ms",
methodName, costTime);
alertService.send(
"性能告警",
String.format("%s执行耗时%dms",
methodName, costTime)
);
}
}
}
}
// 使用
@Service
public class OrderService {
@Monitor // 自动监控性能
public OrderDTO createOrder(CreateOrderRequest request) {
// ...
}
}
风险三:新系统Bug导致线上事故
方案:灰度发布+快速回滚
# Kubernetes灰度发布配置
apiVersion: v1
kind: Service
metadata:
name: user-service
spec:
selector:
app: user-service
ports:
- port: 8080
---
# 老版本 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-v1
spec:
replicas: 9 # 90%流量
selector:
matchLabels:
app: user-service
version: v1
template:
metadata:
labels:
app: user-service
version: v1
spec:
containers:
- name: user-service
image: user-service:v1.0.0
---
# 新版本 Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service-v2
spec:
replicas: 1 # 10%流量(小白鼠)
selector:
matchLabels:
app: user-service
version: v2
template:
metadata:
labels:
app: user-service
version: v2
spec:
containers:
- name: user-service
image: user-service:v2.0.0
快速回滚脚本:
#!/bin/bash
# rollback.sh - 一键回滚脚本
echo "开始回滚到v1版本..."
# 1. 缩容新版本到0
kubectl scale deployment user-service-v2 --replicas=0
# 2. 扩容老版本
kubectl scale deployment user-service-v1 --replicas=10
# 3. 验证
echo "等待Pod启动..."
sleep 10
kubectl get pods -l app=user-service
# 4. 健康检查
curl http://user-service:8080/actuator/health
echo "回滚完成!"
风险四:数据迁移失败
方案:分批迁移+校验
/**
* 数据迁移服务
*/
@Service
@Slf4j
public class DataMigrationService {
@Autowired
private LegacyUserDAO legacyDAO;
@Autowired
private UserMapper newMapper;
/**
* 分批迁移用户数据
*/
public void migrateUsers() {
int pageSize = 1000;
int pageNum = 1;
int totalMigrated = 0;
int totalFailed = 0;
while (true) {
// 1. 分页查询老数据
List<LegacyUser> legacyUsers = legacyDAO.findByPage(
pageNum, pageSize
);
if (legacyUsers.isEmpty()) {
break; // 迁移完成
}
// 2. 转换并插入新系统
for (LegacyUser legacyUser : legacyUsers) {
try {
// 数据转换
User newUser = convertUser(legacyUser);
// 插入新系统
newMapper.insert(newUser);
// 标记为已迁移
legacyDAO.markAsMigrated(legacyUser.getId());
totalMigrated++;
} catch (Exception e) {
log.error("用户迁移失败:id={}",
legacyUser.getId(), e);
// 记录失败原因
migrationErrorMapper.insert(
new MigrationError(
legacyUser.getId(),
e.getMessage()
)
);
totalFailed++;
}
}
log.info("第{}页迁移完成,成功{},失败{}",
pageNum, totalMigrated, totalFailed);
pageNum++;
// 3. 休息一下,别把数据库搞挂了
Thread.sleep(1000);
}
log.info("数据迁移完成!总成功:{},总失败:{}",
totalMigrated, totalFailed);
}
/**
* 数据校验
*/
public void validateMigration() {
log.info("开始数据校验...");
// 1. 统计新老系统数据量
long legacyCount = legacyDAO.count();
long newCount = newMapper.selectCount(null);
log.info("老系统用户数:{},新系统用户数:{}",
legacyCount, newCount);
// 2. 抽样对比
List<Long> sampleIds = legacyDAO.randomSampleIds(100);
for (Long userId : sampleIds) {
LegacyUser legacyUser = legacyDAO.findById(userId);
User newUser = newMapper.selectById(userId);
if (!isEqual(legacyUser, newUser)) {
log.error("数据不一致!userId={}", userId);
}
}
log.info("数据校验完成!");
}
}
🚀 持续优化:让系统永葆青春 {#持续优化}
建立完善的监控体系
监控金字塔:
┌──────────────┐
│ 业务监控 │ ← 订单量、支付成功率
├──────────────┤
│ 应用监控 │ ← 接口响应时间、错误率
├──────────────┤
│ 中间件监控 │ ← Redis、MySQL、MQ
├──────────────┤
│ 基础设施监控 │ ← CPU、内存、磁盘、网络
└──────────────┘
Prometheus + Grafana配置:
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'spring-boot-apps'
metrics_path: '/actuator/prometheus'
static_configs:
- targets:
- 'user-service:8080'
- 'order-service:8081'
- 'product-service:8082'
// Spring Boot应用暴露指标
@Configuration
public class MetricsConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
@RestController
public class OrderController {
// 自动记录接口响应时间
@Timed(value = "api.order.create",
description = "创建订单接口")
@PostMapping("/orders")
public Result<OrderDTO> createOrder(
@RequestBody CreateOrderRequest request) {
// ...
}
}
建立CI/CD流水线
# .gitlab-ci.yml
stages:
- build
- test
- deploy
# 编译阶段
build:
stage: build
script:
- mvn clean package -DskipTests
artifacts:
paths:
- target/*.jar
# 测试阶段
test:
stage: test
script:
- mvn test
- mvn sonar:sonar # 代码质量检查
coverage: '/Total.*?([0-9]{1,3})%/'
# 部署到测试环境
deploy-test:
stage: deploy
script:
- docker build -t user-service:test .
- kubectl apply -f k8s/test/
environment:
name: test
only:
- develop
# 部署到生产环境
deploy-prod:
stage: deploy
script:
- docker build -t user-service:$CI_COMMIT_TAG .
- kubectl apply -f k8s/prod/
environment:
name: production
only:
- tags
when: manual # 手动触发
持续技术学习
学习路线图:
第一季度:微服务基础
├─ Spring Boot深入
├─ Spring Cloud组件
└─ Docker容器化
第二季度:分布式系统
├─ 分布式事务(Seata)
├─ 分布式锁(Redis/Zookeeper)
└─ 消息队列(RocketMQ/Kafka)
第三季度:性能优化
├─ JVM调优
├─ MySQL优化
└─ 缓存策略
第四季度:DevOps
├─ Kubernetes
├─ CI/CD
└─ 监控告警
🎉 总结:重构是一场马拉松
关键要点回顾
-
评估现状 🔍
- 全面分析,量化评估
- 不要盲目开干
-
制定策略 🎯
- 选对方法(绞杀者/分层/并行)
- 循序渐进,不要激进
-
技术选型 🛠️
- 选择成熟稳定的技术栈
- 考虑团队能力和学习成本
-
风险管控 🛡️
- 灰度发布,随时回滚
- 监控告警,快速响应
-
持续优化 🚀
- 建立监控体系
- 完善CI/CD流程
- 技术债务定期清理
时间规划参考
小型系统(<10万行代码):
- 准备期:1-2个月
- 重构期:3-6个月
- 稳定期:2-3个月
- 总计:6-11个月
中型系统(10-50万行代码):
- 准备期:2-3个月
- 重构期:6-12个月
- 稳定期:3-6个月
- 总计:11-21个月
大型系统(>50万行代码):
- 准备期:3-6个月
- 重构期:12-24个月
- 稳定期:6-12个月
- 总计:21-42个月
心态建设
重构是一场马拉松,不是百米冲刺!
压力曲线
高 │ ╱╲
│ ╱ ╲
│ ╱ ╲___
低 │______╱ ╲____
└─────────────────────→
启动期 高峰期 稳定期
☕ 保持耐心
🤝 团队协作
📚 持续学习
💪 不要放弃
最后的忠告
-
不要想着一次重写所有代码
- 大爆炸式重构,成功率<10%
- 渐进式重构,成功率>80%
-
不要忽视业务需求
- 重构不是目的,业务才是
- 边重构边迭代
-
不要单打独斗
- 拉上团队一起
- 建立Code Review机制
-
不要追求完美
- 80分的方案能落地,胜过100分的PPT
- 先跑起来,再优化
-
不要忘记文档
- 架构文档
- API文档
- 部署文档
- 运维手册
📚 参考资料
经典书籍
- 《重构:改善既有代码的设计》- Martin Fowler
- 《微服务设计》- Sam Newman
- 《领域驱动设计》- Eric Evans
- 《凤凰项目》- Gene Kim
在线资源
- Spring Boot官方文档:spring.io/projects/sp…
- Spring Cloud官方文档:spring.io/projects/sp…
- MyBatis Plus:baomidou.com/
- Nacos:nacos.io/
社区
- Spring中国社区
- 掘金技术社区
- InfoQ
- GitHub优秀开源项目
🎊 结语
从"拖拉机"到"特斯拉"的改造之旅虽然漫长,但当你看到系统焕然一新的那一刻,所有的努力都是值得的!
记住:没有完美的系统,只有持续改进的系统!
祝你重构顺利,升职加薪!🎉🎉🎉
作者按:本文基于我10年的Java开发和架构经验总结而成。如果对你有帮助,请给个赞👍!有问题欢迎在评论区交流~
版本:v1.0
最后更新:2025-10-20
适用场景:中大型单体系统现代化改造
难度等级:⭐⭐⭐⭐ (进阶)
#系统重构 #微服务 #SpringBoot #技术选型 #架构升级