中间件:高可用、高性能、可扩展三大核心设计原则

中间件:高可用、高性能、可扩展三大核心设计原则 分布式系统的浪潮下中间件早已成为企业级架构的核心基础设施。从RPC框架、消息队列到缓存、数据库中间件每一个支撑海量流量的系统背后都离不开稳定可靠的中间件。而决定一个中间件能走多远、能扛多大压力的核心永远绕不开三个灵魂级的设计原则高可用、高性能、可扩展。一、高可用设计原则让系统在故障中依然屹立不倒高可用的核心本质是系统在面对各类异常场景硬件故障、网络波动、流量洪峰、程序bug时依然能够持续对外提供服务的能力。行业内通常用SLA服务等级协议来量化这一能力核心目标是减少非计划停机时间阻止故障扩散。这里需要明确一个常见的认知误区高可用≠多实例。很多人以为部署两个节点就实现了高可用实则不然。如果两个节点共用同一个单点数据库数据库挂了两个节点都会失效如果没有故障隔离机制一个节点故障导致流量全量打到第二个节点直接把健康节点也打垮引发雪崩这根本算不上真正的高可用。1.1 故障隔离把故障锁在最小范围内分布式系统的故障是必然发生的我们无法杜绝故障但可以阻止故障的扩散。这就像轮船的舱壁设计一个舱室进水不会导致整艘船沉没这就是经典的舱壁模式Bulkhead Pattern。故障隔离的核心实现方案分为三类线程池隔离不同的服务/接口使用独立的线程池避免一个慢接口占满整个服务的线程资源导致其他正常接口无法响应进程/容器隔离不同的业务模块部署在独立的进程/容器中避免一个模块OOM导致整个服务宕机熔断隔离当依赖的服务出现大量异常时自动熔断请求不再调用故障服务避免线程被故障服务的超时等待占满引发雪崩代码实例基于Resilience4j的舱壁与熔断隔离实现项目基础依赖?xml version1.0 encodingUTF-8? project xmlnshttp://maven.apache.org/POM/4.0.0 xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd modelVersion4.0.0/modelVersion parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.4/version relativePath/ /parent groupIdcom.jam/groupId artifactIdmiddleware-demo/artifactId version0.0.1-SNAPSHOT/version namemiddleware-demo/name dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId version1.18.32/version scopeprovided/scope /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.5.0/version /dependency dependency groupIdio.github.resilience4j/groupId artifactIdresilience4j-spring-boot3/artifactId version2.2.0/version /dependency dependency groupIdcom.google.guava/groupId artifactIdguava/artifactId version33.1.0-jre/version /dependency dependency groupIdcom.alibaba.fastjson2/groupId artifactIdfastjson2/artifactId version2.0.49/version /dependency dependency groupIdio.netty/groupId artifactIdnetty-all/artifactId version4.1.115.Final/version /dependency dependency groupIdcom.lmax/groupId artifactIddisruptor/artifactId version4.0.0/version /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.6/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.3.0/version scoperuntime/scope /dependency dependency groupIdorg.apache.shardingsphere/groupId artifactIdshardingsphere-jdbc-core-spring-boot3-starter/artifactId version5.4.1/version /dependency dependency groupIdcom.google.protobuf/groupId artifactIdprotobuf-java/artifactId version3.25.3/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin /plugins /build /project业务服务实现package com.jam.demo.service; import io.github.resilience4j.bulkhead.annotation.Bulkhead; import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * 订单服务 * author ken */ Slf4j Service public class OrderService { /** * 创建订单核心接口 * param orderId 订单ID * return 订单创建结果 */ Bulkhead(name orderService, type Bulkhead.Type.THREADPOOL) CircuitBreaker(name orderService, fallbackMethod createOrderFallback) public String createOrder(String orderId) { log.info(开始创建订单订单ID{}, orderId); // 核心订单创建逻辑 return 订单创建成功订单ID orderId; } /** * 订单创建降级方法 * param orderId 订单ID * param e 异常对象 * return 降级结果 */ private String createOrderFallback(String orderId, Exception e) { log.error(订单创建失败触发降级订单ID{}, orderId, e); return 当前系统繁忙请稍后重试; } }接口层实现package com.jam.demo.controller; import com.jam.demo.service.OrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** * 订单接口 * author ken */ RestController RequestMapping(/order) RequiredArgsConstructor Tag(name 订单管理, description 订单相关接口) public class OrderController { private final OrderService orderService; /** * 创建订单接口 * param orderId 订单ID * return 订单创建结果 */ PostMapping(/create) Operation(summary 创建订单, description 创建新的订单) public String createOrder( Parameter(description 订单ID, required true) RequestParam String orderId) { return orderService.createOrder(orderId); } }1.2 冗余备份与故障自动转移单点是高可用的天敌任何单点故障都会导致整个系统不可用。冗余备份的核心是消除单点而故障自动转移则是在节点故障时无需人工干预自动把流量切换到健康的备份节点保障服务持续可用。核心实现方案分为三个环节多副本冗余通过主从架构、集群架构实现数据与服务的多副本备份副本分为同步副本强一致性优先和异步副本高可用性优先心跳检测通过定时心跳机制检测节点健康状态精准判断节点是否故障核心是平衡心跳超时时间避免误判与故障转移不及时的问题故障自动转移Failover检测到主节点故障后自动从副本节点中选举出新的主节点更新路由信息将流量无缝切换到新主节点整个过程对调用方透明故障转移核心流程1.3 限流与降级兜底系统的承载能力永远有上限当流量超过系统承载阈值时最有效的保护方式就是限流只放行系统能承载的流量超出的流量直接拒绝。同时对非核心业务进行降级释放资源给核心业务确保核心服务的可用性。核心实现方案分为两类限流算法固定窗口限流、滑动窗口限流、令牌桶限流、漏桶限流。其中令牌桶适合应对突发流量漏桶适合平滑流量是目前最常用的两种限流算法降级策略熔断降级、开关降级、兜底降级。核心是优先保障核心业务非核心业务在流量高峰时可以降级返回兜底数据代码实例令牌桶限流实现package com.jam.demo.limit; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicLong; /** * 令牌桶限流实现 * author ken */ Slf4j public class TokenBucket { /** * 令牌桶容量 */ private final long capacity; /** * 令牌生成速率(个/秒) */ private final long rate; /** * 当前令牌数量 */ private final AtomicLong currentTokens; /** * 上次令牌生成时间 */ private final AtomicLong lastRefillTime; public TokenBucket(long capacity, long rate) { this.capacity capacity; this.rate rate; this.currentTokens new AtomicLong(capacity); this.lastRefillTime new AtomicLong(System.currentTimeMillis()); } /** * 尝试获取令牌 * param requiredTokens 需要的令牌数量 * return 是否获取成功 */ public synchronized boolean tryAcquire(long requiredTokens) { refillTokens(); long current currentTokens.get(); if (current requiredTokens) { currentTokens.addAndGet(-requiredTokens); return true; } return false; } /** * 补充令牌 */ private void refillTokens() { long now System.currentTimeMillis(); long lastTime lastRefillTime.get(); long elapsedTime now - lastTime; if (elapsedTime 0) { long newTokens (elapsedTime * rate) / 1000; if (newTokens 0) { long totalTokens Math.min(currentTokens.get() newTokens, capacity); currentTokens.set(totalTokens); lastRefillTime.set(now); } } } }1.4 数据一致性保障高可用不能以数据丢失为代价冗余备份的同时必须保障数据的一致性。核心是在一致性与可用性之间找到平衡根据业务场景选择合适的复制策略。核心复制策略对比同步复制写操作必须同步到所有副本后才返回成功强一致性保障但可用性低任意副本故障都会导致写操作失败异步复制写操作在主节点执行成功后立即返回异步同步到副本可用性高但主节点故障时可能丢失数据半同步复制写操作至少同步到一个副本后才返回成功平衡了一致性与可用性是目前主流数据库、消息队列采用的复制策略这里需要明确CAP定理的核心分区容错性P是分布式系统的前提网络分区必然会发生因此只能在一致性C和可用性A之间做平衡不存在绝对的CA系统。二、高性能设计原则把硬件性能压榨到极致高性能的核心本质是在有限的硬件资源下系统实现更低的请求延迟、更高的并发吞吐量。核心目标是减少无效开销最大化硬件资源的利用率。这里需要纠正一个常见的认知误区高性能≠高并发。高并发是系统能同时处理的请求数高性能是高并发的基础没有高性能的设计高并发只是空中楼阁。同时高性能也不是一味堆硬件而是通过合理的设计把硬件的性能发挥到极致。2.1 网络IO模型优化网络通信是中间件性能的第一个瓶颈80%的中间件性能问题都出在网络IO模型上。从BIO到NIO再到IO多路复用本质上都是为了减少线程阻塞降低线程上下文切换的开销最大化CPU的利用率。四种核心IO模型的核心差异BIO阻塞IO每个连接对应一个线程线程在读写数据时全程阻塞并发量上来后线程数会爆炸上下文切换开销极大仅能支撑数百级别的并发NIO非阻塞IO线程发起IO请求后立即返回无需阻塞等待通过轮询检查数据是否就绪但轮询会消耗大量CPU资源IO多路复用用一个或少量线程监听多个连接的IO事件仅当连接有IO事件就绪时才通知线程处理是目前高性能网络组件的核心模型Linux下的epoll、Java中的Selector都是对该模型的封装异步IOAIOIO操作完全交给操作系统操作系统完成IO操作后再通知线程处理Linux下的AIO实现不够成熟目前工业界应用最广泛的还是IO多路复用基于IO多路复用的Reactor模型是目前高性能网络组件的标准实现分为三种经典模式单Reactor单线程所有IO操作和业务处理都在一个线程中完成适合业务逻辑极简单的场景典型代表是Redis单Reactor多线程Reactor线程仅负责监听和分发IO事件业务处理交给线程池适合业务逻辑较复杂的场景但单Reactor在高并发下会成为瓶颈主从Reactor多线程主Reactor仅负责监听连接的建立建立完成的连接交给从Reactor监听IO事件业务处理交给线程池是目前性能最优的模型Netty就是基于该模型实现的主从Reactor模型架构代码实例基于Netty的主从Reactor高性能服务端实现package com.jam.demo.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; import lombok.extern.slf4j.Slf4j; /** * Netty高性能服务端 * author ken */ Slf4j public class NettyServer { private final int port; public NettyServer(int port) { this.port port; } /** * 启动服务端 * throws InterruptedException 中断异常 */ public void start() throws InterruptedException { // 主Reactor线程组仅负责处理连接建立 EventLoopGroup bossGroup new NioEventLoopGroup(1); // 从Reactor线程组负责处理IO事件 EventLoopGroup workerGroup new NioEventLoopGroup(); try { ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 1024) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { ch.pipeline() .addLast(new StringDecoder(CharsetUtil.UTF_8)) .addLast(new StringEncoder(CharsetUtil.UTF_8)) .addLast(new ServerHandler()); } }); ChannelFuture future bootstrap.bind(port).sync(); log.info(Netty服务端启动成功端口{}, port); future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws InterruptedException { new NettyServer(8080).start(); } }package com.jam.demo.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import lombok.extern.slf4j.Slf4j; /** * 服务端业务处理器 * author ken */ Slf4j public class ServerHandler extends SimpleChannelInboundHandlerString { Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { log.info(收到客户端消息{}, msg); ctx.writeAndFlush(服务端已收到消息 msg); } Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { log.error(通道发生异常, cause); ctx.close(); } }2.2 内存管理优化Java的GC是影响系统性能的核心因素频繁的Full GC会导致STWStop The World引发系统卡顿、吞吐量下降。同时数据在内存与磁盘、网络之间的拷贝也会带来大量的性能开销。内存优化的核心就是减少GC开销减少数据拷贝次数。核心实现方案分为三类堆外内存JVM堆内内存受GC管理堆外内存Direct Memory不受GC管理生命周期由手动控制大幅降低GC压力。同时在网络传输时无需从堆内拷贝到堆外减少一次数据拷贝Netty的ByteBuf就是基于堆外内存实现的零拷贝技术减少数据在用户态和内核态之间的拷贝次数。Linux下的sendFile系统调用可直接把文件从内核缓冲区拷贝到Socket缓冲区无需经过用户态Java中的FileChannel.transferTo()就是对sendFile的封装mmap技术把文件映射到用户态内存无需read系统调用直接操作内存Java中的MappedByteBuffer是对mmap的封装对象池与内存池频繁创建和销毁对象会导致YGC频繁通过对象池复用对象减少对象的创建和销毁内存池提前申请一块连续内存后续内存申请都从该内存块分配避免频繁向操作系统申请和释放内存Netty的PooledByteBufAllocator就是内存池的成熟实现2.3 锁与并发优化多线程并发是提升CPU利用率的核心但线程之间的锁竞争会导致线程阻塞、上下文切换开销增大甚至引发死锁。并发优化的核心就是降低锁竞争的粒度甚至实现无锁设计。核心实现方案分为四类无锁设计基于CASCompare And Swap实现无锁操作CAS是CPU硬件级别的原子操作无需加锁避免了锁竞争的开销Java中的Atomic系列类就是基于CAS实现的分段锁把整个数据结构分成多个段每个段独立加锁不同段的操作不会产生锁竞争JDK1.7的ConcurrentHashMap就是基于分段锁实现的读写锁读多写少的场景下使用ReentrantReadWriteLock读操作共享锁写操作独占锁读与读之间不会产生竞争大幅提升并发量无锁队列基于环形数组的无锁队列典型代表是Disruptor通过CAS实现无锁的生产消费模型吞吐量比LinkedBlockingQueue高两个数量级以上是RocketMQ、Storm等高性能中间件的核心组件代码实例基于Disruptor的无锁生产消费模型实现package com.jam.demo.disruptor; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; import lombok.Data; import lombok.extern.slf4j.Slf4j; /** * 消息事件 * author ken */ Data public class MessageEvent { private String message; }package com.jam.demo.disruptor; import com.lmax.disruptor.EventHandler; import lombok.extern.slf4j.Slf4j; /** * 消息事件处理器 * author ken */ Slf4j public class MessageEventHandler implements EventHandlerMessageEvent { Override public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) { log.info(消费消息{}序列号{}, event.getMessage(), sequence); } }package com.jam.demo.disruptor; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; import lombok.extern.slf4j.Slf4j; /** * Disruptor服务启动类 * author ken */ Slf4j public class DisruptorServer { public static void main(String[] args) { // 环形缓冲区大小必须是2的幂 int bufferSize 1024 * 1024; // 创建Disruptor实例 DisruptorMessageEvent disruptor new Disruptor( MessageEvent::new, bufferSize, DaemonThreadFactory.INSTANCE ); // 设置事件处理器 disruptor.handleEventsWith(new MessageEventHandler()); // 启动Disruptor disruptor.start(); // 获取环形缓冲区 RingBufferMessageEvent ringBuffer disruptor.getRingBuffer(); // 生产消息 for (int i 0; i 100; i) { long sequence ringBuffer.next(); try { MessageEvent event ringBuffer.get(sequence); event.setMessage(测试消息- i); } finally { ringBuffer.publish(sequence); } } // 关闭Disruptor disruptor.shutdown(); } }2.4 序列化与反序列化优化分布式系统中网络传输的对象必须经过序列化和反序列化这个过程的性能开销直接影响整个系统的吞吐量和延迟。序列化优化的核心就是减小序列化后的体积提升序列化/反序列化的速度。核心实现方案分为两类选择高性能的序列化框架JSON类框架可读性好但性能一般序列化后体积大二进制序列化框架Protobuf、Hessian2、Kryo性能更优体积更小其中Protobuf跨语言兼容性好性能稳定是gRPC等主流RPC框架的默认序列化方案序列化优化技巧避免序列化大对象拆分大对象为多个小对象仅序列化需要传输的字段过滤无用字段使用基本类型代替包装类型减少序列化开销2.5 数据结构与算法优化数据结构是程序的基础不同的数据结构在不同场景下性能差异能达到几个数量级。算法优化的核心就是选择时间复杂度和空间复杂度最优的数据结构适配业务场景。核心优化方案分为三类跳表SkipList平衡树的替代方案插入、删除、查询的时间复杂度均为O(logn)但实现更简单无需旋转平衡并发控制更容易Redis的Sorted Set、LevelDB、RocksDB都采用了跳表实现布隆过滤器Bloom Filter基于位图实现的概率型数据结构用于快速判断一个元素是否存在于集合中不存在的判断100%准确存在的判断有可控的误判率适合用于减少缓存穿透、无效数据库查询Guava提供了成熟的布隆过滤器实现环形数组比链表更高效的队列实现内存连续CPU缓存命中率高无需频繁创建和销毁节点Disruptor、ArrayBlockingQueue都是基于环形数组实现的代码实例基于布隆过滤器解决缓存穿透问题package com.jam.demo.bloom; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; /** * 商品实体 * author ken */ Data TableName(t_product) public class Product { TableId(type IdType.AUTO) private Long id; private String productName; private Long price; private Integer stock; }package com.jam.demo.bloom; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; /** * 商品Mapper * author ken */ Mapper public interface ProductMapper extends BaseMapperProduct { }package com.jam.demo.bloom; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.List; /** * 商品服务 * author ken */ Slf4j Service RequiredArgsConstructor public class ProductService { private final ProductMapper productMapper; private final RedisTemplateString, Object redisTemplate; private BloomFilterLong bloomFilter; private static final String PRODUCT_KEY_PREFIX product:; private static final long EXPECTED_INSERTIONS 1000000L; private static final double FPP 0.001; PostConstruct public void initBloomFilter() { log.info(开始初始化布隆过滤器); ListLong productIdList productMapper.selectList(null).stream().map(Product::getId).toList(); bloomFilter BloomFilter.create(Funnels.longFunnel(), EXPECTED_INSERTIONS, FPP); for (Long productId : productIdList) { bloomFilter.put(productId); } log.info(布隆过滤器初始化完成共加载{}个商品ID, productIdList.size()); } /** * 根据ID查询商品 * param id 商品ID * return 商品信息 */ public Product getProductById(Long id) { // 布隆过滤器拦截不存在的ID避免缓存穿透 if (!bloomFilter.mightContain(id)) { log.info(布隆过滤器拦截不存在的商品ID{}, id); return null; } // 查询缓存 String key PRODUCT_KEY_PREFIX id; Product product (Product) redisTemplate.opsForValue().get(key); if (product ! null) { return product; } // 查询数据库 product productMapper.selectById(id); if (product ! null) { redisTemplate.opsForValue().set(key, product); } return product; } }MySQL表结构CREATE TABLE t_product ( id bigint NOT NULL AUTO_INCREMENT COMMENT 商品ID, product_name varchar(255) NOT NULL COMMENT 商品名称, price bigint NOT NULL COMMENT 商品价格(分), stock int NOT NULL DEFAULT 0 COMMENT 商品库存, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci COMMENT商品表;三、可扩展设计原则让系统适配业务的无限变化可扩展的核心本质是系统在面对需求变化、流量增长、功能迭代时无需重构核心架构仅通过最小化的代码改动就能快速支撑新的需求。核心目标是解耦依赖实现开闭原则——对扩展开放对修改关闭。这里需要纠正一个常见的认知误区可扩展≠可伸缩。可伸缩是指通过增加机器就能线性提升系统性能属于水平扩展是可扩展的一个子集可扩展还包括功能的垂直扩展新增功能无需修改核心代码。3.1 SPI机制与插件化架构SPIService Provider Interface是一种服务发现机制核心是把核心接口和实现分离核心系统仅依赖接口具体的实现由第三方插件提供运行时动态加载实现了核心逻辑和扩展逻辑的完全解耦是插件化架构的核心基础。Java原生的SPI机制通过META-INF/services目录下的配置文件指定接口的实现类由ServiceLoader动态加载Spring的SPI机制通过META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件加载Dubbo的SPI机制在原生SPI的基础上增加了IOC和AOP的支持功能更强大。代码实例基于Java SPI的插件化实现核心接口定义package com.jam.demo.spi; /** * 序列化接口 * author ken */ public interface Serializer { /** * 序列化 * param obj 待序列化对象 * return 序列化后的字节数组 */ byte[] serialize(Object obj); /** * 反序列化 * param data 字节数组 * param clazz 目标类 * return 反序列化后的对象 * param T 泛型类型 */ T T deserialize(byte[] data, ClassT clazz); /** * 获取序列化类型名称 * return 类型名称 */ String getName(); }Fastjson2实现类package com.jam.demo.spi.impl; import com.alibaba.fastjson2.JSON; import com.jam.demo.spi.Serializer; /** * Fastjson2序列化实现 * author ken */ public class Fastjson2Serializer implements Serializer { Override public byte[] serialize(Object obj) { return JSON.toJSONBytes(obj); } Override public T T deserialize(byte[] data, ClassT clazz) { return JSON.parseObject(data, clazz); } Override public String getName() { return fastjson2; } }Protobuf实现类package com.jam.demo.spi.impl; import com.google.protobuf.Message; import com.jam.demo.spi.Serializer; /** * Protobuf序列化实现 * author ken */ public class ProtobufSerializer implements Serializer { Override public byte[] serialize(Object obj) { if (!(obj instanceof Message)) { throw new IllegalArgumentException(Protobuf序列化对象必须实现Message接口); } return ((Message) obj).toByteArray(); } Override SuppressWarnings(unchecked) public T T deserialize(byte[] data, ClassT clazz) { if (!Message.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException(Protobuf反序列化目标类必须实现Message接口); } try { Message defaultInstance (Message) clazz.getMethod(getDefaultInstance).invoke(null); return (T) defaultInstance.newBuilderForType().mergeFrom(data).build(); } catch (Exception e) { throw new RuntimeException(Protobuf反序列化失败, e); } } Override public String getName() { return protobuf; } }SPI配置文件在resources/META-INF/services目录下创建文件com.jam.demo.spi.Serializer内容如下com.jam.demo.spi.impl.Fastjson2Serializer com.jam.demo.spi.impl.ProtobufSerializerSPI服务加载器package com.jam.demo.spi; import com.google.common.collect.Maps; import java.util.Map; import java.util.ServiceLoader; /** * 序列化器工厂 * author ken */ public class SerializerFactory { private static final MapString, Serializer SERIALIZER_MAP Maps.newHashMap(); static { // 加载所有SPI实现 ServiceLoaderSerializer serviceLoader ServiceLoader.load(Serializer.class); for (Serializer serializer : serviceLoader) { SERIALIZER_MAP.put(serializer.getName(), serializer); } } /** * 根据名称获取序列化器 * param name 序列化器名称 * return 序列化器实例 */ public static Serializer getSerializer(String name) { return SERIALIZER_MAP.get(name); } }3.2 微内核架构微内核架构也叫插件化架构把系统分为两部分核心系统微内核和插件模块。核心系统仅负责插件的生命周期管理、插件之间的通信、核心配置的管理是系统最稳定的部分不会轻易改动所有的业务逻辑、功能扩展都以插件的形式存在插件之间相互独立可插拔新增功能仅需开发新的插件无需修改核心系统。微内核架构的核心优势稳定性高核心系统改动极少可扩展性强新增功能仅需新增插件可维护性高插件之间完全解耦出问题仅需禁用对应插件不影响整个系统。Eclipse、IDEA、Dubbo、RocketMQ都采用了微内核的设计思想。3.3 接口与协议标准化可扩展的前提是标准化只有统一的接口规范、统一的协议格式才能实现不同实现之间的无缝替换新增扩展不会影响现有系统。协议设计的核心是预留扩展字段保证向前兼容新增功能无需修改协议的核心结构旧版本客户端能正常访问新版本服务端新版本服务端也能兼容旧版本客户端。核心实现方案分为两类接口标准化基于面向接口编程所有扩展点都定义统一的接口接口一旦确定就不会轻易修改新增功能通过新增接口实现类完成而非修改接口协议可扩展设计协议头预留扩展字段采用TLVTag-Length-Value格式存储扩展内容新增扩展字段不会影响现有字段的解析保证协议的向前兼容TCP、HTTP、Dubbo协议都采用了类似的设计3.4 集群水平扩展当系统流量增长单节点性能达到瓶颈时最有效的扩展方式就是水平扩展通过增加机器节点线性提升系统的承载能力。水平扩展的核心前提是无状态设计节点之间没有依赖任何一个节点都能处理所有请求请求可以分发到任意一个节点新增节点仅需注册到集群中就能承接流量无需修改任何配置。核心实现方案分为三类无状态设计所有的状态数据用户会话、业务数据都存储在分布式存储中节点本身不存储任何状态节点之间完全对等可随时替换、新增、删除分片/分区机制对于有状态的系统通过分片机制把数据分散到多个节点上每个节点仅负责一部分数据新增节点仅需迁移部分分片数据就能线性提升系统的存储和处理能力典型代表是Redis Cluster的哈希槽分片、MySQL的分库分表一致性哈希算法解决传统哈希取模分片新增节点时需要迁移大量数据的问题。一致性哈希把哈希空间组成一个环形每个节点对应环上的一个位置数据根据哈希值落到环上的对应位置顺时针找到第一个节点存储新增节点时仅需迁移相邻节点的部分数据大幅减少数据迁移量是分布式缓存、负载均衡的核心算法代码实例一致性哈希负载均衡实现package com.jam.demo.hash; import com.google.common.collect.Lists; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.List; import java.util.SortedMap; import java.util.TreeMap; /** * 一致性哈希实现 * author ken */ public class ConsistentHash { /** * 虚拟节点数量 */ private static final int VIRTUAL_NODE_COUNT 160; /** * 哈希环 */ private final SortedMapLong, String hashRing new TreeMap(); /** * 真实节点列表 */ private final ListString realNodes Lists.newArrayList(); /** * 添加节点 * param node 节点地址 */ public void addNode(String node) { realNodes.add(node); // 添加虚拟节点 for (int i 0; i VIRTUAL_NODE_COUNT; i) { String virtualNodeName node VN i; long hash hash(virtualNodeName); hashRing.put(hash, node); } } /** * 移除节点 * param node 节点地址 */ public void removeNode(String node) { realNodes.remove(node); // 移除虚拟节点 for (int i 0; i VIRTUAL_NODE_COUNT; i) { String virtualNodeName node VN i; long hash hash(virtualNodeName); hashRing.remove(hash); } } /** * 根据key获取对应的节点 * param key 路由key * return 节点地址 */ public String getNode(String key) { if (hashRing.isEmpty()) { return null; } long hash hash(key); // 顺时针查找第一个节点 SortedMapLong, String subMap hashRing.tailMap(hash); Long targetHash subMap.isEmpty() ? hashRing.firstKey() : subMap.firstKey(); return hashRing.get(targetHash); } /** * MD5哈希算法 * param key 待哈希的key * return 哈希值 */ private long hash(String key) { try { MessageDigest md5 MessageDigest.getInstance(MD5); byte[] digest md5.digest(key.getBytes()); return ((long) (digest[3] 0xFF) 24) | ((long) (digest[2] 0xFF) 16) | ((long) (digest[1] 0xFF) 8) | (digest[0] 0xFF); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(MD5算法不存在, e); } } }四、三大原则的协同与平衡很多开发者会陷入一个误区认为三个原则是相互独立的甚至是相互冲突的。比如为了高可用做了多副本同步复制会影响性能为了高性能做了很多定制化优化会影响可扩展性。实则不然三个原则是相辅相成的优秀的中间件设计一定是在三个原则之间找到了最优的平衡。高可用是高性能的基础如果系统经常宕机再高的性能也没有意义高可用保障了系统能持续提供服务高性能才能发挥价值高性能是可扩展的前提如果单节点性能很差即使水平扩展也需要大量的机器成本极高。把单节点性能优化到极致再通过水平扩展提升整体承载能力才是最优的方案可扩展是高可用和高性能的长期保障业务在不断发展流量在不断增长只有可扩展的架构才能支撑业务的长期发展持续优化高可用和高性能的能力而不是每次业务变化都要重构架构Kafka就是三大原则完美平衡的典范通过多副本机制、ISR同步、故障自动转移实现高可用通过顺序写磁盘、零拷贝、批量传输实现高性能通过分区水平扩展、插件化架构实现可扩展最终成为了工业界公认的高性能、高可靠消息队列。写在最后不管你是在使用开源中间件想要深入理解背后的原理排查线上问题还是想要自研中间件打造适合自己业务的基础设施都要围绕这三个原则来思考从底层逻辑出发结合业务场景选择最合适的实现方案。