SakuraMotion 2021 - Java
type: channel
link: https://yingwu2021.xyz/index.php/category/Java/
items: 12 （优化）使用CompletableFuture优化接受服务提供端返回结果
items: 11 RPC 框架代码分析之其他模块
items: 什么是数据冷热分离？
items: 10 RPC 框架代码分析之注册中心模块
items: Kafka 基础
items: 09 RPC 框架代码分析之网络传输模块
items: JDK 命令行工具
items: 08 ZooKeeper常用命令+ Curator使用详解
items: 消息队列基础知识总结
items: 07 静态代理+JDK/CGLIB 动态代理实战

12 （优化）使用CompletableFuture优化接受服务提供端返回结果
type: item
date: 2025-12-02T18:00:00+08:00
description: AI 摘要 文章介绍将Netty RPC客户端接收服务端返回结果的方式，从阻塞的AttributeMap改为CompletableFuture，实现异步非阻塞调用，代码更清晰，使用更简单，并建议限制未处理请求Map容量防OOM。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结12 （优化）使用CompletableFuture优化接受服务提供端返回结果使用 AttributeMap 接受服务端返回结果最开始的时候是通过 AttributeMap 绑定到Channel上实现的，相关代码如下：NettyClientTransport.java（用来发送 RpcRequest 请求）NettyClientHandler.java （自定义客户端 ChannelHandler 来处理服务端发过来的数据）这种是实现的缺点是不清晰，而且你每次都要调用 channel.closeFuture().sync(); 阻塞来手动等待请求返回。使用 CompletableFuture 进行优化我使用 CompletableFuture 包装返回结果，对代码进行了重构，重要部分的代码如下：NettyClientTransport.java（用来发送 RpcRequest 请求）NettyClientHandler.java （自定义客户端 ChannelHandler 来处理服务端发过来的数据）UnprocessedRequests.java 存放了未处理的请求（建议限制 map 容器大小，避免未处理请求过多 OOM现在，在你只需要通过下面的方式就能成功接收到客户端返回的结果：CompletableFuture<RpcResponse> completableFuture = (CompletableFuture<RpcResponse>) clientTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get();

11 RPC 框架代码分析之其他模块
type: item
date: 2025-11-12T02:55:00+08:00
description: AI 摘要 文章剖析RPC框架的“其他模块”：用动态代理RpcClientProxy屏蔽网络细节，统一封装请求；借Spring BeanPostProcessor，通过@RpcService发布服务、@RpcReference注入代理，实现注解式注册与消费。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结11 RPC 框架代码分析之其他模块动态代理屏蔽网络传输细节我们在前面的章节讲到过我们需要用到动态代理来屏蔽复杂的网络传输细节。对应的代码: RpcClientProxy.java@Slf4j public class RpcClientProxy implements InvocationHandler { public <T> T getProxy(Class<T> clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) { } }当我们去调用一个远程的方法的时候，实际上是通过代理对象调用的。获取代理对象的方法如下： public <T> T getProxy(Class<T> clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class<?>[]{clazz}, this); }网络传输细节都被封装在了 invoke() 方法中。 public Object invoke(Object proxy, Method method, Object[] args) { log.info("invoked method: [{}]", method.getName()); RpcRequest rpcRequest = RpcRequest.builder().methodName(method.getName()) .parameters(args) .interfaceName(method.getDeclaringClass().getName()) .paramTypes(method.getParameterTypes()) .requestId(UUID.randomUUID().toString()) .group(rpcServiceProperties.getGroup()) .version(rpcServiceProperties.getVersion()) .build(); RpcResponse<Object> rpcResponse = null; if (rpcRequestTransport instanceof NettyRpcClient) { CompletableFuture<RpcResponse<Object>> completableFuture = (CompletableFuture<RpcResponse<Object>>) rpcRequestTransport.sendRpcRequest(rpcRequest); rpcResponse = completableFuture.get(); } if (rpcRequestTransport instanceof SocketRpcClient) { rpcResponse = (RpcResponse<Object>) rpcRequestTransport.sendRpcRequest(rpcRequest); } this.check(rpcResponse, rpcRequest); return rpcResponse.getData(); }通过注解注册/消费服务我们这里借用了 Spring 容器相关的功能。核心代码都放在了 ： src/main/java/github/javaguide/spring 包下面。我们定义两个注解：RcpService ：注册服务RpcReference ：消费服务**RcpService.java**@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Inherited public @interface RpcService { /** * Service version, default value is empty string */ String version() default …

什么是数据冷热分离？
type: item
date: 2025-11-01T03:27:00+08:00
description: AI 摘要 数据冷热分离按访问频率与业务价值将数据分为热、冷两类，分别存于高性能与低成本介质，以提升查询速度、节省成本；常用时间或访问频率划分，迁移可定时任务或监听binlog，冷数据可选MySQL、Hbase等存储。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: 数据冷热分离详解category: 高性能head:metaname: keywordscontent: 数据冷热分离,冷数据迁移,冷数据存储metaname: descriptioncontent: 数据冷热分离是指根据数据的访问频率和业务重要性，将数据分为冷数据和热数据，冷数据一般存储在存储在低成本、低性能的介质中，热数据高性能存储介质中。什么是数据冷热分离？数据冷热分离是指根据数据的访问频率和业务重要性，将数据分为冷数据和热数据，冷数据一般存储在存储在低成本、低性能的介质中，热数据高性能存储介质中。冷数据和热数据热数据是指经常被访问和修改且需要快速访问的数据，冷数据是指不经常访问，对当前项目价值较低，但需要长期保存的数据。冷热数据到底如何区分呢？有两个常见的区分方法：时间维度区分：按照数据的创建时间、更新时间、过期时间等，将一定时间段内的数据视为热数据，超过该时间段的数据视为冷数据。例如，订单系统可以将 1 年前的订单数据作为冷数据，1 年内的订单数据作为热数据。这种方法适用于数据的访问频率和时间有较强的相关性的场景。访问频率区分：将高频访问的数据视为热数据，低频访问的数据视为冷数据。例如，内容系统可以将浏览量非常低的文章作为冷数据，浏览量较高的文章作为热数据。这种方法需要记录数据的访问频率，成本较高，适合访问频率和数据本身有较强的相关性的场景。几年前的数据并不一定都是热数据，例如一些优质文章发表几年后依然有很多人访问，大部分普通用户新发表的文章却基本没什么人访问。这两种区分冷热数据的方法各有优劣，实际项目中，可以将两者结合使用。冷热分离的思想冷热分离的思想非常简单，就是对数据进行分类，然后分开存储。冷热分离的思想可以应用到很多领域和场景中，而不仅仅是数据存储，例如：邮件系统中，可以将近期的比较重要的邮件放在收件箱，将比较久远的不太重要的邮件存入归档。日常生活中，可以将常用的物品放在显眼的位置，不常用的物品放入储藏室或者阁楼。图书馆中，可以将最受欢迎和最常借阅的图书单独放在一个显眼的区域，将较少借阅的书籍放在不起眼的位置。……数据冷热分离的优缺点优点：热数据的查询性能得到优化（用户的绝大部分操作体验会更好）、节约成本（可以冷热数据的不同存储需求，选择对应的数据库类型和硬件配置，比如将热数据放在 SSD 上，将冷数据放在 HDD 上）缺点：系统复杂性和风险增加（需要分离冷热数据，数据错误的风险增加）、统计效率低（统计的时候可能需要用到冷库的数据）。冷数据如何迁移？冷数据迁移方案：业务层代码实现：当有对数据进行写操作时，触发冷热分离的逻辑，判断数据是冷数据还是热数据，冷数据就入冷库，热数据就入热库。这种方案会影响性能且冷热数据的判断逻辑不太好确定，还需要修改业务层代码，因此一般不会使用。任务调度：可以利用 xxl-job 或者其他分布式任务调度平台定时去扫描数据库，找出满足冷数据条件的数据，然后批量地将其复制到冷库中，并从热库中删除。这种方法修改的代码非常少，非常适合按照时间区分冷热数据的场景。监听数据库的变更日志 binlog ：将满足冷数据条件的数据从 binlog 中提取出来，然后复制到冷库中，并从热库中删除。这种方法可以不用修改代码，但不适合按照时间维度区分冷热数据的场景。如果你的公司有 DBA 的话，也可以让 DBA 进行冷数据的人工迁移，一次迁移完成冷数据到冷库。然后，再搭配上面介绍的方案实现后续冷数据的迁移工作。冷数据如何存储？冷数据的存储要求主要是容量大，成本低，可靠性高，访问速度可以适当牺牲。冷数据存储方案：中小厂：直接使用 MySQL/PostgreSQL 即可（不改变数据库选型和项目当前使用的数据库保持一致），比如新增一张表来存储某个业务的冷数据或者使用单独的冷库来存放冷数据（涉及跨库查询，增加了系统复杂性和维护难度）大厂：Hbase（常用）、RocksDB、Doris、Cassandra如果公司成本预算足的话，也可以直接上 TiDB 这种分布式关系型数据库，直接一步到位。TiDB 6.0 正式支持数据冷热存储分离，可以降低 SSD 使用成本。使用 TiDB 6.0 的数据放置功能，可以在同一个集群实现海量数据的冷热存储，将新的热数据存入 SSD，历史冷数据存入 HDD。案例分享如何快速优化几千万数据量的订单表 - 程序员济癫 - 2023海量数据冷热分离方案与实践 - 字节跳动技术团队 - 2022

10 RPC 框架代码分析之注册中心模块
type: item
date: 2025-10-23T05:49:00+08:00
description: AI 摘要 文章剖析自研RPC框架的注册中心模块：定义ServiceRegistry与ServiceDiscovery接口，基于ZooKeeper实现服务注册与发现，支持多版本、多分组、多实例，配合负载均衡策略完成地址选取。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结10 RPC 框架代码分析之注册中心模块我们之前在“如何自己实现一个 RPC 框架？”这篇文章中介绍到说：注册中心负责服务地址的注册与查找，相当于目录服务。 服务端启动的时候将服务名称及其对应的地址(ip+port)注册到注册中心，服务消费端根据服务名称找到对应的服务地址。有了服务地址之后，服务消费端就可以通过网络请求服务端了。简单来说注册中心就像是一个中转站，提供的作用就是根据调用的服务名称找到远程服务的地址（数据保存服务）。注册中心模块整体结构如下：我们定义了两个接口 ServiceDiscovery.java 和 ServiceRegistry.java，这两个接口分别定义了服务发现和服务注册行为。**ServiceRegistry.java**/** * 服务注册 * * @author shuang.kou * @createTime 2020年05月13日 08:39:00 */ public interface ServiceRegistry { /** * 注册服务到注册中心 * * @param rpcServiceName 完整的服务名称（class name+group+version） * @param inetSocketAddress 远程服务地址 */ void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress); }**ServiceDiscovery.java**/** * 服务发现 * * @author shuang.kou * @createTime 2020年06月01日 15:16:00 */ public interface ServiceDiscovery { /** * 根据 rpcServiceName 获取远程服务地址 * * @param rpcServiceName 完整的服务名称（class name+group+version） * @return 远程服务地址 */ InetSocketAddress lookupService(String rpcServiceName); }接下来，我们使用 zookeeper 作为注册中心的实现方式，并实现了这两个接口。**ZkServiceRegistry.java**/** * 服务注册（基于zookeeper实现） */ @Slf4j public class ZkServiceRegistry implements ServiceRegistry { @Override public void registerService(String rpcServiceName, InetSocketAddress inetSocketAddress) { String servicePath = CuratorUtils.ZK_REGISTER_ROOT_PATH + "/" + rpcServiceName + inetSocketAddress.toString(); CuratorFramework zkClient = CuratorUtils.getZkClient(); CuratorUtils.createPersistentNode(zkClient, servicePath); } }当我们的服务被注册进 zookeeper 的时候，我们将完整的服务名称 rpcServiceName （class name+group+version）作为根节点 ，子节点是对应的服务地址（ip+端口号）。class name : 服务接口名也就是类名比如：github.javaguide.HelloService。version : 服务版本。主要是为后续不兼容升级提供可能group :服务所在的组。主要用于处理一个接口有多个类实现的情况。一个根节点（rpcServiceName）可能会对应多个服务地址（相同服务被部署多份的情况）。如果我们要获得某个服务对应的地址的话，就直接根据完整的服务名称来获取到其下的所有子节点，然后通过具体的负载均衡策略取出一个就可以了。相关代码如下在 ZkServiceDiscovery.java中已经给出。**ZkServiceDiscovery.java**/** * 服务发现（基于zookeeper实现） */ @Sl…

Kafka 基础
type: item
date: 2025-10-08T15:47:00+08:00
description: AI 摘要 Kafka 是分布式流平台，兼具高吞吐消息队列与实时流处理能力。核心概念：Producer、Consumer、Broker、Topic、Partition；多副本+ISR 保障高可用。2.8 起支持无 Zookeeper 的 KRaft。顺序性靠单分区或 key 路由；防丢需 acks=all、手动提交、replica≥3；去重靠幂等或死信队列；默认重试 10 次可自定义，失败入死信队列。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: Kafka常见问题总结category: 高性能tag:消息队列Kafka 基础Kafka 是什么？主要应用场景有哪些？Kafka 是一个分布式流式处理平台。这到底是什么意思呢？流平台具有三个关键功能：消息队列：发布和订阅消息流，这个功能类似于消息队列，这也是 Kafka 也被归类为消息队列的原因。容错的持久方式存储记录消息流：Kafka 会把消息持久化到磁盘，有效避免了消息丢失的风险。流式处理平台： 在消息发布的时候进行处理，Kafka 提供了一个完整的流式处理类库。Kafka 主要有两大应用场景：消息队列：建立实时流数据管道，以可靠地在系统或应用程序之间获取数据。数据处理： 构建实时的流数据处理程序来转换或处理数据流。和其他消息队列相比,Kafka 的优势在哪里？我们现在经常提到 Kafka 的时候就已经默认它是一个非常优秀的消息队列了，我们也会经常拿它跟 RocketMQ、RabbitMQ 对比。我觉得 Kafka 相比其他消息队列主要的优势如下：极致的性能：基于 Scala 和 Java 语言开发，设计中大量使用了批量处理和异步的思想，最高可以每秒处理千万级别的消息。生态系统兼容性无可匹敌：Kafka 与周边生态系统的兼容性是最好的没有之一，尤其在大数据和流计算领域。实际上在早期的时候 Kafka 并不是一个合格的消息队列，早期的 Kafka 在消息队列领域就像是一个衣衫褴褛的孩子一样，功能不完备并且有一些小问题比如丢失消息、不保证消息可靠性等等。当然，这也和 LinkedIn 最早开发 Kafka 用于处理海量的日志有很大关系，哈哈哈，人家本来最开始就不是为了作为消息队列滴，谁知道后面误打误撞在消息队列领域占据了一席之地。随着后续的发展，这些短板都被 Kafka 逐步修复完善。所以，Kafka 作为消息队列不可靠这个说法已经过时！队列模型了解吗？Kafka 的消息模型知道吗？题外话：早期的 JMS 和 AMQP 属于消息服务领域权威组织所做的相关的标准，我在 JavaGuide的 《消息队列其实很简单》这篇文章中介绍过。但是，这些标准的进化跟不上消息队列的演进速度，这些标准实际上已经属于废弃状态。所以，可能存在的情况是：不同的消息队列都有自己的一套消息模型。队列模型：早期的消息模型使用队列（Queue）作为消息通信载体，满足生产者与消费者模式，一条消息只能被一个消费者使用，未被消费的消息在队列中保留直到被消费或超时。 比如：我们生产者发送 100 条消息的话，两个消费者来消费一般情况下两个消费者会按照消息发送的顺序各自消费一半（也就是你一个我一个的消费。）队列模型存在的问题：假如我们存在这样一种情况：我们需要将生产者产生的消息分发给多个消费者，并且每个消费者都能接收到完整的消息内容。这种情况，队列模型就不好解决了。很多比较杠精的人就说：我们可以为每个消费者创建一个单独的队列，让生产者发送多份。这是一种非常愚蠢的做法，浪费资源不说，还违背了使用消息队列的目的。发布-订阅模型:Kafka 消息模型发布-订阅模型主要是为了解决队列模型存在的问题。发布订阅模型（Pub-Sub） 使用主题（Topic） 作为消息通信载体，类似于广播模式；发布者发布一条消息，该消息通过主题传递给所有的订阅者，在一条消息广播之后才订阅的用户则是收不到该条消息的。在发布 - 订阅模型中，如果只有一个订阅者，那它和队列模型就基本是一样的了。所以说，发布 - 订阅模型在功能层面上是可以兼容队列模型的。Kafka 采用的就是发布 - 订阅模型。RocketMQ 的消息模型和 Kafka 基本是完全一样的。唯一的区别是 Kafka 中没有队列这个概念，与之对应的是 Partition（分区）。Kafka 核心概念什么是 Producer、Consumer、Broker、Topic、Partition？Kafka 将生产者发布的消息发送到 Topic（主题） 中，需要这些消息的消费者可以订阅这些 Topic（主题），如下图所示：上面这张图也为我们引出了，Kafka 比较重要的几个概念：Producer（生产者） : 产生消息的一方。Consumer（消费者） : 消费消息的一方。Broker（…

09 RPC 框架代码分析之网络传输模块
type: item
date: 2025-09-10T04:08:00+08:00
description: AI 摘要 文章剖析了自研 RPC 框架的网络传输模块：先定义 RpcRequest/Response 实体，再抽象 RpcRequestTransport 接口，分别用 Socket 与 Netty 实现客户端与服务端收发；Netty 版引入编解码器、自定义协议及心跳保活，并借助 CompletableFuture 实现异步回调，完成高性能远程调用。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结09 RPC 框架代码分析之网络传输模块以下提到的 服务端 指的是提供服务/方法的一端，客户端 指的是调用远程(服务端)服务/方法的一端。我们之前在“如何自己实现一个 RPC 框架？”这篇文章中介绍到说：既然我们要调用远程的方法，就要发送网络请求来传递目标类和方法的信息以及方法的参数等数据到服务端。 这就涉及到了网络传输！网络传输具体实现你可以使用 Socket （ Java 中最原始、最基础的网络通信方式。但是，Socket 是阻塞 IO、性能低并且功能单一）。你也可以使用同步非阻塞的 I/O 模型 NIO ，但是用它来进行网络编程真的太麻烦了。不过没关系，你可以使用基于 NIO 的网络编程框架 Netty ，它将是你最好的选择！guide-rpc-framework 使用了一种基于 Socket，一种基于 Netty 的方式（循序渐进）。网络传输模块整体结构如下：一共被分为了 4 个包constants : 存放一些网络传输模块共用的常量dto : 用于网络传输的类。handler : 里面只有一个用于处理 rpc 请求的类RpcRequestHandler（根据 rpc 请求调用目标类的目标方法）。transport : 用户网络传输相关类（真正传输网络请求的地方。提供了 Socket 和 Netty 两种网络传输方式）。网络传输实体类网络传输实体类在 dto 包下，主要有两个类。**RpcRequest.java**rpc 请求实体类。当你要调用远程方法的时候，你需要先传输一个 RpcRequest 给对方，RpcRequest 里面包含了要调用的目标方法和类的名称、参数等数据。另外，version 字段（服务版本）主要是为后续不兼容升级提供可能。group 字段主要用于处理一个接口有多个类实现的情况。@AllArgsConstructor @NoArgsConstructor @Getter @Builder @ToString public class RpcRequest implements Serializable { private static final long serialVersionUID = 1905122041950251207L; private String requestId; private String interfaceName; private String methodName; private Object[] parameters; private Class<?>[] paramTypes; private RpcMessageType rpcMessageType; private String version; private String group; public RpcServiceProperties toRpcProperties() { return RpcServiceProperties.builder().serviceName(this.getInterfaceName()) .version(this.getVersion()) .group(this.getGroup()).build(); } }**RpcResponse.java**既然有了 rpc 请求实体类，那肯定就要有 rpc 响应实体类了。当服务端通过 RpcRequest 中的相关数据调用到目标服务的目标方法之后，调用结果就通过 RpcResponse 返回给客户端。@AllArgsConstructor @NoArgsConstructor @Getter @Setter @Builder @ToString public class RpcResponse<T> implements Serializable { private static final long serialVersionUID = 715745410605631233L; private String requestId; /** * response code */ private Integer code; /** * response message */ private String mess…

JDK 命令行工具
type: item
date: 2025-08-28T12:10:00+08:00
description: AI 摘要 文章系统梳理了JDK自带监控与故障处理工具：命令行侧jps、jstat、jinfo、jmap、jhat、jstack分别用于查进程、看运行数据、调参数、抓堆转储、离线分析堆、线程快照；可视化侧JConsole、VisualVM提供内存、线程、GC等图形化监控，支持远程连接与插件扩展，可快速定位死锁、OOM等问题。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: JDK监控和故障处理工具总结category: Javatag:JVMJDK 命令行工具这些命令在 JDK 安装目录下的 bin 目录下：jps (JVM Process Status）: 类似 UNIX 的 ps 命令。用于查看所有 Java 进程的启动类、传入参数和 Java 虚拟机参数等信息；jstat（JVM Statistics Monitoring Tool）: 用于收集 HotSpot 虚拟机各方面的运行数据;jinfo (Configuration Info for Java) : Configuration Info for Java,显示虚拟机配置信息;jmap (Memory Map for Java) : 生成堆转储快照;jhat (JVM Heap Dump Browser) : 用于分析 heapdump 文件，它会建立一个 HTTP/HTML 服务器，让用户可以在浏览器上查看分析结果;jstack (Stack Trace for Java) : 生成虚拟机当前时刻的线程快照，线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合。jps:查看所有 Java 进程jps(JVM Process Status) 命令类似 UNIX 的 ps 命令。jps：显示虚拟机执行主类名称以及这些进程的本地虚拟机唯一 ID（Local Virtual Machine Identifier,LVMID）。jps -q：只输出进程的本地虚拟机唯一 ID。C:\Users\SnailClimb>jps 7360 NettyClient2 17396 7972 Launcher 16504 Jps 17340 NettyServerjps -l:输出主类的全名，如果进程执行的是 Jar 包，输出 Jar 路径。C:\Users\SnailClimb>jps -l 7360 firstNettyDemo.NettyClient2 17396 7972 org.jetbrains.jps.cmdline.Launcher 16492 sun.tools.jps.Jps 17340 firstNettyDemo.NettyServerjps -v：输出虚拟机进程启动时 JVM 参数。jps -m：输出传递给 Java 进程 main() 函数的参数。jstat: 监视虚拟机各种运行状态信息jstat（JVM Statistics Monitoring Tool） 使用于监视虚拟机各种运行状态信息的命令行工具。 它可以显示本地或者远程（需要远程主机提供 RMI 支持）虚拟机进程中的类信息、内存、垃圾收集、JIT 编译等运行数据，在没有 GUI，只提供了纯文本控制台环境的服务器上，它将是运行期间定位虚拟机性能问题的首选工具。jstat 命令使用格式：jstat - [-t] [-h<lines>] <vmid> [<interval> [<count>]]比如 jstat -gc -h3 31736 1000 10表示分析进程 id 为 31736 的 gc 情况，每隔 1000ms 打印一次记录，打印 10 次停止，每 3 行后打印指标头部。常见的 option 如下：jstat -class vmid：显示 ClassLoader 的相关信息；jstat -compiler vmid：显示 JIT 编译的相关信息；jstat -gc vmid：显示与 GC 相关的堆信息；jstat -gccapacity vmid：显示各个代的容量及使用情况；jstat -gcnew vmid：显示新生代信息；jstat -gcnewcapcacity vmid：显示新生代大小与使用情况；jstat -gcold vmid：显示老年代和永久代的行为统计，从 jdk1.8 开始,该选项仅表示老年代，因为永久代被移除了；jstat -gcoldcapacity vmid：显示老年代的大小；jstat -gcpermcapacity vmid：显示永久代大小，从 jdk1.8 开始,该选项不存在了，因为永久代被移除了；jstat -gcutil vmid：显示垃圾收集信息；另外，加上 -t参数可以在输出信息上加一个 Timestamp 列，显示程序的运行…

08 ZooKeeper常用命令+ Curator使用详解
type: item
date: 2025-08-28T01:41:00+08:00
description: AI 摘要 文章演示了Docker安装ZooKeeper及常用命令（create/set/get/ls/stat/delete），并详解Curator连接、节点增删改查、监听器的代码实现，覆盖持久/临时/顺序节点与重试策略。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结08 ZooKeeper常用命令+ Curator使用详解guide-rpc-framework 使用了 Zookeeper 来存储服务的相关信息 ，并且使用的是 ZooKeeper Java客户端 Curator 来对 ZooKeeper 进行增删改查等操作。所以，本文就简单介绍一下 ZooKeeper常用命令 以及 Curator 的基本使用。前言这篇文章简单给演示一下 ZooKeeper 常见命令的使用以及 ZooKeeper Java客户端 Curator 的基本使用。介绍到的内容都是最基本的操作，能满足日常工作的基本需要。如果文章有任何需要改善和完善的地方，欢迎在评论区指出，共同进步！ZooKeeper 安装和使用使用Docker 安装 zookeepera.使用 Docker 下载 ZooKeeperdocker pull zookeeper:3.5.8b.运行 ZooKeeperdocker run -d --name zookeeper -p 2181:2181 zookeeper:3.5.8连接 ZooKeeper 服务a.进入ZooKeeper容器中先使用 docker ps 查看 ZooKeeper 的 ContainerID，然后使用 docker exec -it ContainerID /bin/bash 命令进入容器中。b.先进入 bin 目录,然后通过 **./zkCli.sh -server 127.0.0.1:2181**命令连接ZooKeeper 服务root@eaf70fc620cb:/apache-zookeeper-3.5.8-bin# cd bin如果你看到控制台成功打印出如下信息的话，说明你已经成功连接 ZooKeeper 服务。常用命令演示查看常用命令(help 命令)通过 help 命令查看 ZooKeeper 常用命令创建节点(create 命令)通过 create 命令在根目录创建了 node1 节点，与它关联的字符串是"node1"[zk: 127.0.0.1:2181(CONNECTED) 34] create /node1 “node1”通过 create 命令在根目录创建了 node1 节点，与它关联的内容是数字 123[zk: 127.0.0.1:2181(CONNECTED) 1] create /node1/node1.1 123 Created /node1/node1.1更新节点数据内容(set 命令)[zk: 127.0.0.1:2181(CONNECTED) 11] set /node1 "set node1"获取节点的数据(get 命令)get 命令可以获取指定节点的数据内容和节点的状态,可以看出我们通过 set 命令已经将节点数据内容改为 "set node1"。set node1 cZxid = 0x47 ctime = Sun Jan 20 10:22:59 CST 2019 mZxid = 0x4b mtime = Sun Jan 20 10:41:10 CST 2019 pZxid = 0x4a cversion = 1 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 9 numChildren = 1查看某个目录下的子节点(ls 命令)通过 ls 命令查看根目录下的节点[zk: 127.0.0.1:2181(CONNECTED) 37] ls / [dubbo, ZooKeeper, node1]通过 ls 命令查看 node1 目录下的节点[zk: 127.0.0.1:2181(CONNECTED) 5] ls /node1 [node1.1]ZooKeeper 中的 ls 命令和 linux 命令中的 ls 类似， 这个命令将列出绝对路径 path 下的所有子节点信息（列出 1 级，并不递归）查看节点状态(stat 命令)通过 stat 命令查看节点状态[zk: 127.0.0.1:2181(CONNECTED) 10] stat /node1 cZxid = 0x47 ctime = Sun Jan 20 10:22:59 CST 2019 mZxid = 0x47 mtime = Sun Jan 20 10:22:59 CST 2019 pZxid = 0x4a cvers…

消息队列基础知识总结
type: item
date: 2025-08-18T06:09:00+08:00
description: AI 摘要 文章系统梳理分布式消息队列基础：定义、生产者-消费者模型，三大核心价值（异步提速、削峰限流、解耦），并指出可用性下降、复杂度上升、一致性风险等代价；对比JMS/AMQP协议差异，RPC与MQ场景定位，最后盘点Kafka、RocketMQ、RabbitMQ、Pulsar等主流产品选型要点。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: 消息队列基础知识总结category: 高性能tag:消息队列::: tip这篇文章中的消息队列主要指的是分布式消息队列。:::“RabbitMQ？”“Kafka？”“RocketMQ？”...在日常学习与开发过程中，我们常常听到消息队列这个关键词。我也在我的多篇文章中提到了这个概念。可能你是熟练使用消息队列的老手，又或者你是不懂消息队列的新手，不论你了不了解消息队列，本文都将带你搞懂消息队列的一些基本理论。如果你是老手，你可能从本文学到你之前不曾注意的一些关于消息队列的重要概念，如果你是新手，相信本文将是你打开消息队列大门的一板砖。什么是消息队列？我们可以把消息队列看作是一个存放消息的容器，当我们需要使用消息的时候，直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构，所以消费消息时也是按照顺序来消费的。参与消息传递的双方称为 生产者 和 消费者 ，生产者负责发送消息，消费者负责处理消息。我们知道操作系统中的进程通信的一种很重要的方式就是消息队列。我们这里提到的消息队列稍微有点区别，更多指的是各个服务以及系统内部各个组件/模块之前的通信，属于一种 中间件 。维基百科是这样介绍中间件的：中间件（英语：Middleware），又译中间件、中介层，是一类提供系统软件和应用软件之间连接、便于软件各部件之间的沟通的软件，应用软件可以借助中间件在不同的技术架构之间共享信息与资源。中间件位于客户机服务器的操作系统之上，管理着计算资源和网络通信。简单来说：中间件就是一类为应用软件服务的软件，应用软件是为用户服务的，用户不会接触或者使用到中间件。除了消息队列之外，常见的中间件还有 RPC 框架、分布式组件、HTTP 服务器、任务调度框架、配置中心、数据库层的分库分表工具和数据迁移工具等等。关于中间件比较详细的介绍可以参考阿里巴巴淘系技术的一篇回答：https://www.zhihu.com/question/19730582/answer/1663627873 。随着分布式和微服务系统的发展，消息队列在系统设计中有了更大的发挥空间，使用消息队列可以降低系统耦合性、实现任务异步、有效地进行流量削峰，是分布式和微服务系统中重要的组件之一。消息队列有什么用？通常来说，使用消息队列能为我们的系统带来下面三点好处：通过异步处理提高系统性能（减少响应所需时间）削峰/限流降低系统耦合性。如果在面试的时候你被面试官问到这个问题的话，一般情况是你在你的简历上涉及到消息队列这方面的内容，这个时候推荐你结合你自己的项目来回答。通过异步处理提高系统性能（减少响应所需时间）将用户的请求数据存储到消息队列之后就立即返回结果。随后，系统再对消息进行消费。因为用户请求数据写入消息队列之后就立即返回给用户了，但是请求数据在后续的业务校验、写数据库等操作中可能失败。因此，使用消息队列进行异步处理之后，需要适当修改业务流程进行配合，比如用户在提交订单之后，订单数据写入消息队列，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功，以免交易纠纷。这就类似我们平时手机订火车票和电影票。削峰/限流先将短时间高并发产生的事务消息存储在消息队列中，然后后端服务再慢慢根据自己的能力去消费这些消息，这样就避免直接把后端服务打垮掉。举例：在电子商务一些秒杀、促销活动中，合理使用消息队列可以有效抵御促销活动刚开始大量订单涌入对系统的冲击。如下图所示：降低系统耦合性使用消息队列还可以降低系统耦合性。我们知道如果模块之间不存在直接调用，那么新增模块或者修改模块就对其他模块影响较小，这样系统的可扩展性无疑更好一些。还是直接上图吧：生产者（客户端）发送消息到消息队列中去，接受者（服务端）处理消息，需要消费的系统直接去消息队列取消息进行消费即可而不需要和其他系统有耦合，这显然也提高了系统的扩展性。消息队列使用发布-订阅模式工作，消息发送者（生产者）发布消息，一个或多个消息接受者（消费者）订阅消息。 从上图可以看到消息发送者（生产者）和消息接受者（消费者）之间没有直接耦合，消息发送者将消息发送至分布式消息队列即结束对消息的处理，消息接受者从分布式消息队列获取该消息后进行后续处理，并不需要知道该消息从何而来。对新增业务，只要对该类消息感兴趣，…

07 静态代理+JDK/CGLIB 动态代理实战
type: item
date: 2025-07-16T07:20:00+08:00
description: AI 摘要 文章通过短信发送示例，对比演示静态代理、JDK动态代理与CGLIB动态代理的编码步骤、优缺点及适用场景：静态代理编译期硬编码，维护困难；JDK动态代理基于接口生成运行时代理类；CGLIB用继承生成子类，可代理无接口类；总结三者灵活性、性能与框架应用差异。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结07 静态代理+JDK/CGLIB 动态代理实战代理模式代理模式是一种比较好的理解的设计模式。简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问，这样就可以在不修改原目标对象的前提下，提供额外的功能操作，扩展目标对象的功能。代理模式的主要作用是扩展目标对象的功能，比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。举个例子：你找了小红来帮你问话，小红就可以看作是代理你的代理对象，代理的行为（方法）是问话。https://medium.com/@mithunsasidharan/understanding-the-proxy-design-pattern-5e63fe38052a代理模式有静态代理和动态代理两种实现方式，我们 先来看一下静态代理模式的实现。静态代理静态代理中，我们对目标对象的每个方法的增强都是手动完成的（_后面会具体演示代码_），非常不灵活（_比如接口一旦新增加方法，目标对象和代理对象都要进行修改_）且麻烦(_需要对每个目标类都单独写一个代理类_)。 实际应用场景非常非常少，日常开发几乎看不到使用静态代理的场景。上面我们是从实现和应用角度来说的静态代理，从 JVM 层面来说， 静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。静态代理实现步骤:定义一个接口及其实现类；创建一个代理类同样实现这个接口将目标对象注入进代理类，然后在代理类的对应方法调用目标类中的对应方法。这样的话，我们就可以通过代理类屏蔽对目标对象的访问，并且可以在目标方法执行前后做一些自己想做的事情。下面通过代码展示！1.定义发送短信的接口public interface SmsService { String send(String message); }2.实现发送短信的接口public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } }3.创建代理类并同样实现发送短信的接口public class SmsProxy implements SmsService { private final SmsService smsService; public SmsProxy(SmsService smsService) { this.smsService = smsService; } @Override public String send(String message) { //调用方法之前，我们可以添加自己的操作 System.out.println("before method send()"); smsService.send(message); //调用方法之后，我们同样可以添加自己的操作 System.out.println("after method send()"); return null; } }4.实际使用public class Main { public static void main(String[] args) { SmsService smsService = new SmsServiceImpl(); SmsProxy smsProxy = new SmsProxy(smsService); smsProxy.send("java"); } }运行上述代码之后，控制台打印出：before method send() send message:java after method send()可以输出结果看出，我们已经增强了 SmsServiceImpl 的send()方法。动态代理相比于静态代理来说，动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类，并且也不需要我们必须实现接口，我们可以直接代理实现类( _CGLIB 动态代理机制_)。从 JVM 角度来说，动态代理是在运行时动态生成类字节码，并加载到 JVM 中的。说到动态代理，Spring AOP、RPC 框架应该是两个不得不的提的，它们的实现都依赖了动态代理。动态代理在我们日常开发中使用的相对较小，但是在框架中的几乎是必…