第二章:开宗立派 · 分布式修真

单机已无法承载,需开辟洞府、建立宗门,在分布式天劫中求存

页面内容

楔子:单机的极限

突破筑基期后,韩立在源界中游历了数年。他凭借着Docker容器化和扎实的运维功底,在各种服务器秘境中都能稳定运行,处理了无数业务请求。

然而,随着业务的发展,韩立发现自己的单机服务已经达到了极限。

那日,系统突然收到一个超级大客户的订单,需要处理百万级别的数据。韩立的CPU瞬间飙升至100%,内存也被耗尽,整个服务陷入了僵死状态。

“单机…已经无法承载了。“韩立看着监控面板上的一片红色,心中涌起一股无力感。

在源界中,单机服务有着天然的瓶颈:

  • CPU限制:单核或多核CPU的处理能力有限
  • 内存限制:物理内存无法无限扩展
  • 网络限制:单机的网络带宽有限
  • 存储限制:单机的磁盘IO能力有限

当业务规模超过单机的承载能力时,就必须走向分布式——将服务拆分,部署到多台服务器上,通过协作来完成复杂的业务。

这就是"开宗立派"的开始。


第一节:开辟洞府——服务拆分

韩立知道,要突破单机的限制,必须将自己的服务拆分。这就像修士要建立宗门,必须先将自己的功法拆分为不同的传承,让不同的弟子(服务)去修炼。

但如何拆分,却是一门大学问。

韩立想起了源界中流传的"领域驱动设计”(DDD)理论。这个理论说,应该按照业务领域来拆分服务,而不是按照技术层次。

他仔细分析自己的业务:

  • 用户服务:管理用户信息、登录认证
  • 订单服务:处理订单创建、支付、退款
  • 商品服务:管理商品信息、库存
  • 支付服务:处理支付逻辑、对账

每个服务都有自己独立的数据库,这就是"数据自治”——每个服务只管理自己的数据,不直接访问其他服务的数据。

韩立开始动手拆分。他先创建了用户服务:

// 用户服务 - user-service
class UserService {
  async getUserById(userId) {
    // 查询自己的数据库
    return await db.users.findById(userId);
  }
  
  async createUser(userData) {
    // 创建用户,只管理用户相关数据
    return await db.users.create(userData);
  }
}

然后是订单服务:

// 订单服务 - order-service
class OrderService {
  async createOrder(orderData) {
    // 创建订单,但需要调用用户服务验证用户
    const user = await userService.getUserById(orderData.userId);
    if (!user) throw new Error('User not found');
    
    // 调用商品服务检查库存
    const product = await productService.getProductById(orderData.productId);
    if (product.stock < orderData.quantity) {
      throw new Error('Insufficient stock');
    }
    
    // 创建订单
    return await db.orders.create(orderData);
  }
}

拆分完成后,韩立将每个服务都封装成Docker镜像,部署到了不同的服务器上。这就是"开辟洞府"——每个服务都有自己的运行环境,互不干扰。


第二节:升仙大会——服务注册与发现

服务拆分后,韩立遇到了一个新问题:订单服务需要调用用户服务,但它怎么知道用户服务在哪里?

在单体应用中,服务调用是直接的函数调用。但在分布式系统中,服务运行在不同的服务器上,需要通过网络来通信。

这就需要一个"升仙大会"——所有服务都在这里登记自己的信息,其他服务可以通过这里找到它。

在源界中,这个"升仙大会"就是服务注册中心(Service Registry),比如Eureka、Nacos、Consul等。

韩立选择了Nacos作为注册中心。他让每个服务启动时,都向Nacos注册自己的信息:

// 服务注册
const nacos = require('nacos');

const client = new nacos.NacosNamingClient({
  serverList: 'nacos-server:8848',
  namespace: 'public'
});

// 注册服务
await client.registerInstance('user-service', {
  ip: '192.168.1.100',
  port: 8080,
  metadata: {
    version: '1.0.0',
    region: 'beijing'
  }
});

当订单服务需要调用用户服务时,它先向Nacos查询:

// 服务发现
const instances = await client.selectInstances('user-service', true);
// 返回所有可用的用户服务实例列表
// [{ip: '192.168.1.100', port: 8080}, {ip: '192.168.1.101', port: 8080}]

然后选择一个实例进行调用(通常使用负载均衡算法,如轮询、随机、加权等)。

这就是"升仙大会"的作用——让所有服务都能找到彼此,组成一个协作的网络。


第三节:天道法则碑——配置中心

服务拆分后,韩立又遇到了一个问题:每个服务都有自己的配置文件,当需要修改配置时,需要重新部署每个服务,非常麻烦。

比如,当需要修改数据库连接池大小、缓存过期时间、限流阈值等参数时,如果每个服务都要重新部署,那工作量就太大了。

韩立想起了源界中的"天道法则碑"——配置中心(Configuration Center),比如Apollo、Nacos Config、Spring Cloud Config等。

配置中心统一管理所有服务的配置,支持动态更新。当配置修改后,服务可以自动感知并应用新配置,无需重启。

韩立选择了Apollo作为配置中心。他将所有服务的配置都上传到Apollo:

# user-service配置
datasource:
  maxPoolSize: 20
  minPoolSize: 5
  
cache:
  expireTime: 3600
  
rateLimit:
  qps: 10000

当需要修改配置时,韩立只需要在Apollo的管理界面中修改,服务会自动拉取新配置并应用。

这就是"天道法则碑"的威力——改天换地,只在一念之间。


第四节:界域传送阵——消息队列

随着业务的发展,韩立发现有些操作不需要立即返回结果,可以异步处理。比如发送邮件、生成报表、更新统计数据等。

如果这些操作都同步处理,会阻塞主流程,影响用户体验。

韩立想起了源界中的"界域传送阵"——消息队列(Message Queue),比如Kafka、RabbitMQ、RocketMQ等。

消息队列可以实现异步通信:服务A发送消息到队列,服务B从队列中消费消息,两者不需要直接通信,实现了"隔空传音"。

韩立选择了Kafka作为消息队列。当订单创建成功后,他发送一个消息到Kafka:

// 发送消息
const kafka = require('kafka-node');
const producer = new kafka.Producer(new kafka.KafkaClient());

producer.send([{
  topic: 'order-created',
  messages: JSON.stringify({
    orderId: '12345',
    userId: '67890',
    amount: 999.99
  })
}], (err, data) => {
  if (err) console.error(err);
});

然后,邮件服务、统计服务等都可以订阅这个主题,异步处理订单创建后的相关操作:

// 消费消息
const consumer = new kafka.Consumer(new kafka.KafkaClient(), 
  [{ topic: 'order-created' }]);

consumer.on('message', async (message) => {
  const order = JSON.parse(message.value);
  // 发送邮件通知
  await emailService.sendOrderConfirmation(order);
  // 更新统计数据
  await statsService.updateOrderStats(order);
});

消息队列还有一个重要作用——削峰填谷。当流量洪峰来临时,消息可以暂存在队列中,后端服务按照自己的处理能力慢慢消费,避免了系统被冲垮。

这就是"界域传送阵"的妙用——连接不同的服务模块,实现异步通信和流量控制。


第五节:藏经阁与储物戒指——数据库与缓存

在分布式系统中,数据存储是一个核心问题。韩立选择了两种不同的存储方式:

关系型数据库(MySQL)——藏经阁

MySQL就像源界中的"藏经阁",存储着结构化的数据,分门别类,关系严谨。它支持事务,保证数据的一致性,就像功法传承有序,不容有误。

-- 用户表
CREATE TABLE users (
  id BIGINT PRIMARY KEY,
  username VARCHAR(50) UNIQUE,
  email VARCHAR(100),
  created_at TIMESTAMP
);

-- 订单表
CREATE TABLE orders (
  id BIGINT PRIMARY KEY,
  user_id BIGINT,
  amount DECIMAL(10,2),
  status VARCHAR(20),
  FOREIGN KEY (user_id) REFERENCES users(id)
);

非关系型数据库(Redis)——储物戒指

Redis就像源界中的"储物戒指",存储热点数据,访问极快。它支持多种数据结构(字符串、列表、集合、哈希、有序集合),就像储物戒指中可以存放各种类型的法宝。

// 缓存用户信息
await redis.setex(`user:${userId}`, 3600, JSON.stringify(userData));

// 获取用户信息
const userData = await redis.get(`user:${userId}`);

Redis的特点是"神念一动,即刻取用"——访问速度极快,但数据可能过期(道韵消散)。所以它通常用来缓存热点数据,减少对MySQL的访问。

韩立还使用了Redis实现分布式锁,解决并发问题:

// 获取分布式锁
const lockKey = `lock:order:${orderId}`;
const lockValue = Date.now();
const acquired = await redis.set(lockKey, lockValue, 'EX', 30, 'NX');

if (acquired) {
  try {
    // 执行业务逻辑
    await processOrder(orderId);
  } finally {
    // 释放锁
    await redis.del(lockKey);
  }
}

这就是"藏经阁"和"储物戒指"的配合——MySQL存储持久化数据,Redis缓存热点数据,两者结合,既保证了数据的一致性,又提升了访问速度。


第六节:分布式天劫——服务雪崩

就在韩立以为可以高枕无忧时,真正的考验来了——分布式天劫

那日,系统突然收到大量请求,用户服务因为数据库连接池耗尽而崩溃。订单服务调用用户服务失败,也开始崩溃。商品服务、支付服务也相继崩溃…

这就是服务雪崩——一个服务的崩溃,导致依赖它的所有服务都崩溃,就像雪崩一样,一发不可收拾。

“这是…道统覆灭!“韩立看着监控面板上的一片红色,心中涌起一股绝望。

服务雪崩的根本原因是服务之间没有做好隔离和容错。当一个服务出现问题时,问题会迅速传播到整个系统。

韩立立刻施展"熔断术”——熔断器模式(Circuit Breaker):

// 熔断器实现
class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;      // 失败阈值
    this.timeout = timeout;          // 超时时间
    this.state = 'CLOSED';           // 状态:CLOSED(关闭)、OPEN(打开)、HALF_OPEN(半开)
    this.nextAttempt = Date.now();
  }

  async execute(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker is OPEN');
      }
      // 尝试恢复
      this.state = 'HALF_OPEN';
    }

    try {
      const result = await fn();
      // 成功,重置计数器
      this.failureCount = 0;
      this.state = 'CLOSED';
      return result;
    } catch (error) {
      this.failureCount++;
      if (this.failureCount >= this.threshold) {
        this.state = 'OPEN';
        this.nextAttempt = Date.now() + this.timeout;
      }
      throw error;
    }
  }
}

// 使用熔断器
const breaker = new CircuitBreaker();
try {
  const user = await breaker.execute(() => userService.getUserById(userId));
} catch (error) {
  // 熔断器打开,使用降级方案
  const user = await getCachedUser(userId);
}

熔断器有三种状态:

  • CLOSED(关闭):正常状态,请求正常通过
  • OPEN(打开):失败次数达到阈值,拒绝所有请求,直接返回降级结果
  • HALF_OPEN(半开):尝试恢复,允许少量请求通过,如果成功则关闭,如果失败则重新打开

这样,当一个服务崩溃时,依赖它的服务会立即熔断,不再调用它,而是使用降级方案(如返回缓存数据、返回默认值等),避免了服务雪崩。


第七节:限流诀与降级术

除了熔断,韩立还掌握了"限流诀"和"降级术”。

限流诀:使用Sentinel等限流框架,对服务进行流量控制。

// 使用Sentinel限流
const Sentinel = require('@sentinel/node');

// 定义限流规则:每秒最多1000个请求
Sentinel.flow({
  resource: 'user-service',
  count: 1000,
  grade: 1  // QPS模式
});

// 在服务调用处进行限流
const entry = await Sentinel.entry('user-service');
try {
  const user = await userService.getUserById(userId);
} finally {
  entry.exit();
}

降级术:当系统压力过大时,自动关闭非核心功能,只保留核心业务。

// 降级策略
class DegradationStrategy {
  async getUserInfo(userId) {
    // 核心功能:获取用户基本信息
    const user = await userService.getUserById(userId);
    
    // 非核心功能:获取用户详细信息(可降级)
    let userDetail = null;
    if (!this.isDegraded('user-detail')) {
      try {
        userDetail = await userService.getUserDetail(userId);
      } catch (error) {
        // 失败也不影响主流程
        console.error('Get user detail failed:', error);
      }
    }
    
    return { ...user, detail: userDetail };
  }
  
  isDegraded(feature) {
    // 根据系统负载决定是否降级
    const cpuUsage = this.getCpuUsage();
    const memoryUsage = this.getMemoryUsage();
    return cpuUsage > 80 || memoryUsage > 80;
  }
}

通过限流和降级,韩立成功抵御了分布式天劫,系统在高压下依然能够稳定运行。


第八节:分布式事务法——Seata

在分布式系统中,还有一个难题——分布式事务

比如,创建订单的流程:

  1. 订单服务:创建订单记录
  2. 商品服务:扣减库存
  3. 支付服务:扣减用户余额

这三个操作需要在不同的服务中完成,但如果其中任何一个失败,都需要回滚所有操作。这就是分布式事务的挑战。

韩立想起了源界中的"分布式事务法"——Seata(Simple Extensible Autonomous Transaction Architecture)。

Seata支持多种事务模式:

  • AT模式:自动补偿,通过解析SQL自动生成回滚日志
  • TCC模式:Try-Confirm-Cancel,需要业务代码实现三个阶段
  • Saga模式:长事务,通过补偿操作来回滚

韩立选择了AT模式,因为它对业务代码侵入最小:

// 使用Seata AT模式
const { GlobalTransaction } = require('@seata/rm-datasource');

// 开启全局事务
const tx = GlobalTransaction.begin('create-order', 60000);

try {
  // 订单服务:创建订单
  await orderService.createOrder(orderData);
  
  // 商品服务:扣减库存
  await productService.deductStock(productId, quantity);
  
  // 支付服务:扣减余额
  await paymentService.deductBalance(userId, amount);
  
  // 提交事务
  await tx.commit();
} catch (error) {
  // 回滚事务
  await tx.rollback();
  throw error;
}

Seata的工作原理:

  1. 事务协调者(TC):Seata Server,负责协调全局事务
  2. 事务管理器(TM):开启全局事务的服务
  3. 资源管理器(RM):参与事务的各个服务

当TM开启全局事务后,RM会向TC注册分支事务。如果所有分支事务都成功,TC会通知所有RM提交;如果任何一个分支事务失败,TC会通知所有RM回滚。

这就是"分布式事务法"的威力——在分布式系统中,也能保证数据的一致性。


第九节:建立韩门

经过一系列的战斗和修炼,韩立成功建立了自己的"韩门"——一个由多个微服务组成的分布式系统。

韩门的架构:

  • 用户服务:管理用户信息
  • 订单服务:处理订单业务
  • 商品服务:管理商品和库存
  • 支付服务:处理支付逻辑
  • 通知服务:发送邮件、短信等通知
  • 统计服务:收集和分析业务数据

所有服务都注册到Nacos,配置统一管理在Apollo,异步通信通过Kafka,数据存储在MySQL和Redis。

韩立还建立了完善的监控体系:

  • Prometheus:收集指标数据
  • Grafana:可视化监控面板
  • ELK:日志收集和分析
  • Zipkin:分布式链路追踪

通过监控,韩立可以实时了解系统的运行状态,及时发现和解决问题。

“韩门,终于建立起来了!“韩立看着监控面板上稳定运行的各个服务,心中涌起一股成就感。

但他知道,这只是开始。在源界中,还有更强大的存在——云原生架构、服务网格、智能运维…这些都需要更高的境界才能掌握。


尾声:新的挑战

建立韩门后,韩立发现了一个新问题:虽然服务已经拆分,但部署和管理依然很麻烦。每个服务都需要单独部署、监控、扩缩容,工作量巨大。

而且,当流量增加时,需要手动增加服务器实例,响应速度慢。当流量减少时,服务器资源闲置,造成浪费。

韩立听说,在源界的高层,有一种叫做"云原生"的修炼方式,可以将服务运行在容器中,由Kubernetes统一调度,实现自动扩缩容、自愈等能力。

“云原生…我一定要掌握它!“韩立眼中闪烁着坚定的光芒。

下一章,韩立将"飞升上界”,学习Kubernetes和云原生架构,开启新的修炼之路。


本章要点总结

  1. 服务拆分:按照业务领域拆分服务,实现数据自治
  2. 服务注册与发现:使用Nacos等注册中心,实现服务间的相互发现
  3. 配置中心:使用Apollo统一管理配置,支持动态更新
  4. 消息队列:使用Kafka实现异步通信和削峰填谷
  5. 数据库与缓存:MySQL存储持久化数据,Redis缓存热点数据
  6. 服务治理:使用熔断、限流、降级等手段,防止服务雪崩
  7. 分布式事务:使用Seata保证分布式系统中的数据一致性

下一章,韩立将学习Kubernetes和云原生架构,实现服务的自动调度和管理。