作为一名经历过多次系统重构的老兵,我至今记得第一次面对公司核心支付系统祖传代码时的震撼。那是一个阳光明媚的下午,我被告知要修复一个"小问题"——系统在处理超过100笔订单时会崩溃。当我打开那个被注释为"创建即完美,无需修改"的PaymentProcessor类时,扑面而来的代码风格让我瞬间理解了什么是真正的"技术债务"。
这段2004年编写的代码完美展现了当时的技术局限性:
java复制public class PaymentProcessor {
private static PaymentProcessor instance = new PaymentProcessor();
private Vector<PaymentRequest> queue = new Vector<>();
public synchronized void process(PaymentRequest req) {
queue.add(req);
// ... 200行业务逻辑
}
public synchronized void cancel(String orderId) {
// ... 另一个200行方法
}
}
这段代码有三个致命问题:
当时我用排队论解释了为什么系统会在100笔订单时崩溃。设:
根据M/M/1队列模型:
code复制ρ = λ/μ = 1 (系统处于临界状态)
平均队列长度 Lq = ρ²/(1-ρ) = 1/(1-1) → ∞
这意味着任何瞬时流量波动都会导致请求无限堆积。而实际上由于锁竞争,情况更糟——当N个线程竞争锁时,有效吞吐量会下降为原来的1/N。
面对这样的祖传代码,直接重写往往不是最佳选择。我采用了渐进式重构策略:
java复制private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public void process(PaymentRequest req) {
writeLock.lock();
try {
// 处理逻辑
} finally {
writeLock.unlock();
}
}
在没有成熟连接池的2004年,我实现了一个简易版:
java复制public class SimpleConnectionPool {
private LinkedList<Connection> pool = new LinkedList<>();
private int maxSize;
public SimpleConnectionPool(int size) throws SQLException {
maxSize = size;
for(int i=0; i<size; i++){
pool.add(createConnection());
}
}
public Connection getConnection() throws InterruptedException {
synchronized(pool) {
while(pool.isEmpty()) {
pool.wait();
}
return pool.removeFirst();
}
}
public void releaseConnection(Connection conn) {
synchronized(pool) {
pool.addLast(conn);
pool.notify();
}
}
}
这个实现虽然简单,但解决了频繁创建连接的问题。实测显示,在100并发下,支付成功率从60%提升到98%。
我引入了异常分类体系:
code复制PaymentException
├── PaymentValidationException
├── PaymentProcessingException
└── PaymentSystemException
并制定了处理规范:
在缺乏现代监控工具的环境下,我设计了三级日志体系:
code复制[2024-03-20T14:30:45] [INFO] [PaymentService] [process] [txId=PAY-1234]
- 开始处理支付 | userId=567, amount=100.00
code复制[METRIC] PaymentStats
- qps=120
- avgTime=85ms
- p95=210ms
- errorRate=0.2%
code复制[ALERT] DatabaseConnection
- active=95
- max=100
- warning=连接池接近饱和
在没有Hystrix的年代,手动实现熔断器:
java复制public class CircuitBreaker {
private enum State { CLOSED, OPEN, HALF_OPEN }
private State state = State.CLOSED;
private int failures = 0;
private long lastFailureTime;
private static final int THRESHOLD = 5;
private static final long TIMEOUT = 30000;
public boolean allowRequest() {
if(state == State.OPEN) {
return System.currentTimeMillis() - lastFailureTime > TIMEOUT;
}
return true;
}
public void recordSuccess() {
state = State.CLOSED;
failures = 0;
}
public void recordFailure() {
failures++;
if(failures >= THRESHOLD) {
state = State.OPEN;
lastFailureTime = System.currentTimeMillis();
}
}
}
改造前后的对比数据:
| 指标 | 原系统 | 重构后 | 提升幅度 |
|---|---|---|---|
| 最大QPS | 50 | 300 | 600% |
| 平均延迟 | 450ms | 120ms | 73%↓ |
| 错误率 | 8% | 0.5% | 94%↓ |
| CPU使用率 | 90% | 60% | 33%↓ |
| 内存消耗 | 2GB | 1.2GB | 40%↓ |
关键优化点贡献度分析:
那个2004年的支付系统,如果按原架构发展:
这次重构证明了:
我从这次经历总结出三条铁律:
如果今天重构同样的系统,技术选型会大不相同:
| 2004方案 | 2024等效方案 |
|---|---|
| synchronized | Redis分布式锁 |
| Vector | ConcurrentHashMap |
| 自定义连接池 | HikariCP |
| 文件日志 | ELK + Prometheus |
| 手动熔断 | Resilience4j |
| 单机部署 | Kubernetes + Service Mesh |
但核心思想不变:
这次重构经历让我深刻认识到,优秀的架构师必须同时具备三种能力:
每当面对祖传代码时,我都会想起2004年那个阳光明媚的下午——技术会过时,但解决问题的思维永远闪光。