[{"categories":["教程指南","技术分享"],"content":"一、事务介绍 Transaction 事务是**数据库中一组操作的集合。**核心特点就一句话：要么全部成功执行、要么全部不执行，不会只执行一半。\n1.1 核心特性：ACID 原子性:（Atomicity） 事务是最小执行单元，不可分割。失败就全部回滚，成功就全部提交。\n一致性:（Consistency） 执行前后，数据库的完整性不变（余额、库存等不会出现逻辑错误）。\n隔离性:（Isolation） 多个事务同时运行时，互不干扰，避免脏读、不可重复读、幻读。\n持久性:（Durability） 一旦事务提交，结果永久保存，断电也不会丢失。 1.2 事务隔离级别（从低到高） 读未提交（Read Uncommitted） 别人没提交、你也能读到。所存在的问题：脏读、幻读、不可重复读。\n读已提交（Read Committed） 只能读到别人已经提交的数据。解决脏读，仍存在不可重复读和幻读，Oracle、SQL Server 默认级别。\n可重复读（Repeatable Read） 同一个事务中，多次读同一行数据永远一样。解决脏读和不可重复读，仍存在幻读（MySql InnoDB 用间隙锁基本解决了幻读） MySQL 默认隔离级别。\n间隙锁（Gap Lock） 是 MySQL InnoDB 引擎在 可重复读（RR） 隔离级别下，专门用来防止幻读的核心锁机制。它锁定的不是数据行本身，而是索引记录之间的空隙，阻止其他事务在这个空隙里插入新数据。\n这样说基本概念大家肯定很迷惑，啥是幻读？啥是锁定数据行？啥是索引之间的空隙？怎么阻止的其他事务插入，间隙锁如何实现的？ 我来给大家一一解释！\n幻读（这里先简单说一下，下面 1.3 会有更详细的介绍）： 幻读就是在同一个事务里，两次执行完全相同的范围查询，第二次结果里突然多出了第一次没有的新行（像幻影一样）。\n幻读示例（无间隙锁）： -- 事务A BEGIN; SELECT * FROM user WHERE age \u0026gt; 20; -- 查到2条 -- 事务B插入 age=25 的新数据并提交 INSERT INTO user (name, age) VALUES (\u0026#39;新用户\u0026#39;, 25); COMMIT; -- 事务A再次查询 SELECT * FROM user WHERE age \u0026gt; 20; -- 变成3条！幻读发生 锁定数据行： 锁定对象：其实并不是锁一个已经存在的行，而是索引之间的“空隙”；\n核心作用：在你查询的范围周围加一个“禁止插入”的边界，防止其他事务在范围内偷偷插入新数据；\n依赖条件：必须是 InnoDB 引擎、必须是可重复读、必须基于索引；\n记录锁（Record Lock）：锁住车位（不让别人占用 / 修改）。防修改 / 删除。 间隙锁（Gap Lock）：锁住 1 和 4 之间的空地（不让别人新建）。防插入。\n所以解决幻读实际上是：\nNext-Key Lock：记录锁 + 间隙锁（InnoDB 默认），既锁行又锁间隙。\n又有小伙伴会问了，那万一不是数字呢？ 一样锁，跟是不是数字没关系，只跟索引顺序有关。 极简核心： InnoDB 不管你字段是数字、字符串、日期，只要是索引，在数据库里都会排成有序队列。间隙锁锁的就是这个有序队列里的 “空隙”。\n串行化（Serializable） 事务排队执行、完全串行。全部问题都可以解决但是性能太差了，一般没人用。\n一张表总结：\n1.3 三个“读”问题 1.3.1 脏读（Dirty Read） 读到别人还没提交的数据。\n例子：\n事务 A：给用户加 100 元，但还没提交 事务 B：立刻去查，看到余额多了 100 结果事务 A 出错，回滚了 事务 B 刚才读到的 100 元就是脏数据 一句话：读到未提交数据，别人一滚，你就白读了。\n1.3.2 不可重复读（Non-repeatable Read） 同一个事务里，两次读同一条数据，结果不一样。\n例子：\n事务 B：第一次查余额 = 1000 事务 A：修改这条数据并提交 → 余额变成 1200 事务 B：再查一次 → 变成 1200 一句话：同一行数据，前后读不一样。\n1.3.3 幻读（Phantom Read） 范围查询时，突然多 / 少了几条记录，像幻觉一样。\n例子：\n事务 B：查询 “年龄 = 18” 的人，共 10 条 事务 A：插入一条年龄 = 18 的数据并提交 事务 B：再查一次 → 变成 11 条 一句话：不是某行变了，是整个结果集变了。\n二、事务控制层 了解完以上基础概念，接下来我们回到代码中。那么请小伙伴思考事务应该放在哪一层？\n对没错，一般放在 Service 层，不在 Controller 也不在 Dao。因为事务控制层就是专门负责 “开启、提交、回滚事务” 的代码层。\nController：只接收请求、参数校验 Service：业务逻辑 + 事务边界 Dao：只做单条 SQL，不控制事务 事务控制层 = Service 层里管理事务的那部分逻辑。\n2.1 事务的实现方式 ① 注解方式（最常用） @Service public class OrderService { @Transactional( rollbackFor = Exception.class, isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED ) public void createOrder() {// 扣库存// 生成订单// 扣余额// 任意一步抛异常 → 全部回滚}} ② 编程式事务（精细控制） transactionTemplate.execute(status -\u0026gt; { // 操作1 // 操作2 if (出错) { status.setRollbackOnly(); // 手动回滚 } return true; }); 2.2 事务的配置 相信小伙伴也看到上面代码，@Transaction（）括号里面的东西，其实一般我们常用的有三种参数配置。我来给大家说说优缺点的适用场景。\n2.2.1 适用场景示例 2.2.2 三种配置回滚对比 看到这里小伙伴应该还没分清 RuntimeException 和 CheckedException 的区别： **RuntimeException：**代码逻辑错误、运行时才会出现，编译器不强制你捕获（不写 try-catch 也能编译通过）默认会导致 Spring 事务回滚。 常见异常有：NullPointerException 空指针、IndexOutOfBoundsException 数组越界、IllegalArgumentException 参数不合法、ArithmeticException 除零异常、ClassCastException 类型转换失败、SqlException 数据库底层错误。\n**CheckedException（受检异常）：**可预期、可处理的业务异常，编译器强制捕获，必须 try-catch 或 throws，默认不会导致 Spring 事务回滚。所以在实际开发中@Transactional(rollbackFor=Exception.class)更加安全一些。 常见异常有：IOException 文件找不到读写失败、SQLException 部分驱动包装类异常、ClassNotFoundException 类找不到、InterruptedException 线程中断堵塞等。\n@Transactional(ReadOnly=true)为什么快？ 不写 undo log，写文件就意味着 IO 操作。不加任何写锁/排他锁，无锁竞争。MVCC 快照更轻量。数据库引擎内部优化：如下\n关闭事务的自动提交相关检测 关闭事务回滚能力相关结构 优化查询执行计划 减少事务上下文切换开销 减少锁等待队列检查 只读事务 ≈ 无锁 + 无日志 + 无 IO 开销 + 无阻塞\n三、懒加载 为什么要在事务里面提懒加载，是因为这部分新手很容易就踩坑，因为懒加载中必须有事务存在！编码不规范很有可能导致数据库压力过大而宕机。接下来我来好好讲解一波！\n3.1 核心概念 Hibernate Session，数据库连接的媒介，session 活着才能执行 sql，若 session 关闭，任何懒加载将会直接报错！ 懒加载：我用的时候再查，所以总结来说就是：懒加载 = 第二次 SQL 查询，只查主表，用到关联数据时才去查第二次，第二次查询必须复用**同一个连接、同一个 Session。**所以懒加载可以减少不必要的联表查询，减轻数据库压力节省内存、提升速度。但是必须在事务 / 会话存活时才能用！ Spring + Hibernate/JPA 默认：一个事务 = 一个 Session、事务开启 → Session 打开、事务提交 / 回滚 → Session 自动关闭、事务外 = 没有可用 Session 3.2 懒加载是如何触发的 分两类：\n集合懒加载（@OneToMany / @ManyToMany） 实体懒加载（@ManyToOne / @OneToOne） 懒加载实现 Demo\n先来准备一下基础实体类（一对多，默认懒加载）\n//User.java @Entity @Table(name = \u0026#34;user\u0026#34;) public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; // 默认 LAZY @OneToMany(mappedBy = \u0026#34;user\u0026#34;) private List\u0026lt;Order\u0026gt; orders; // getter/setter/toString } //Order.java @Entity @Table(name = \u0026#34;orders\u0026#34;) public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNo; @ManyToOne @JoinColumn(name = \u0026#34;user_id\u0026#34;) private User user; // getter/setter/toString } //UserRepository.java public interface UserRepository extends JpaRepository\u0026lt;User, Long\u0026gt; { // 普通查询 @Query(\u0026#34;select u from User u\u0026#34;) List\u0026lt;User\u0026gt; findAllUsers(); // join fetch 优化查询 @Query(\u0026#34;select u from User u left join fetch u.orders\u0026#34;) List\u0026lt;User\u0026gt; findAllUsersWithOrders(); } 继续 service 层的实现\n@Service public class UserService { @Autowired UserRepository userRepository; // ====================== 1. 正常懒加载（事务内）====================== @Transactional public User lazyLoadNormal(Long userId) { User user = userRepository.findById(userId).orElse(null); // 触发懒加载：执行第二条 SQL System.out.println(\u0026#34;订单数量：\u0026#34; + user.getOrders().size()); return user; } // ====================== 2. 事务外懒加载：报错 ====================== public User lazyLoadError(Long userId) { User user = userRepository.findById(userId).orElse(null); // 事务已结束，Session 关闭 // 下面这行会抛：no Session // LazyInitializationException: could not initialize proxy - no Session System.out.println(\u0026#34;订单数量：\u0026#34; + user.getOrders().size()); return user; } } 3.2.1 集合懒加载触发方法（List/Set/Map） 只要调用这些方法，立刻发 SQL 查询关联数据\n最常用触发（高频） size()、isEmpty()、get(index)、contains(obj)、add(obj)、remove(obj)、clear() iterator()、listIterator()、subList()、toArray() 遍历触发（只要遍历就加载） 增强 for 循环：for (Order o : orderList) stream 操作：orderList.stream() forEach：orderList.forEach(...) Jackson 序列化触发（最容易踩坑） Spring MVC 返回 JSON 时，会自动调用：\ngetter 方法 toString() 只要序列化 → 触发懒加载 → no Session 报错\n3.2.2 实体懒加载触发方法（@ManyToOne） 代理对象（如 User user）只要调用以下方法，立刻发 SQL\nuser.getId() 不会触发（id 存在外键里，已预先知道）\n除了 getId ()，任何其他 getter 都会触发\nuser.getName() user.getAge() user.getEmail() toString()\nequals() / hashCode()\n强转类型\n3.2.3 不会触发懒加载的方法 集合：\nuser.getOrderList() → 仅仅获取引用，不触发 只有调用方法才触发 实体：\nuser.getId() → 不触发 仅仅赋值：User u = user; → 不触发 3.3 N+1 查询和 Join Fetch 很多程序员宝子应该都听过 N+1 查询吧，但是具体是什么让我再次给你梳理一下。\n一句话，查 1 次主表，再循环 N 次查关联表，总共执行 N+1 条 SQL。\n3.3.1 N+1 场景 数据库里：\n10 个用户 每个用户有若干订单 你要做：遍历所有用户，并打印每个用户的订单数量\n普通懒加载的执行流程：\n执行 1 条 SQL：查所有用户 select * from user; 循环 10 次：每遍历一个用户，就查一次订单 select * from orders where user_id = 1;select * from orders where user_id = 2;...select * from orders where user_id = 10; 最终 SQL 数量：1 + 10 = 11 条 SQL，那数据量多起来数据库的压力就很大了！数据库直接被打爆，接口卡死。\n3.3.2 join fetch 一句话:一次查询，用 left join 把主表 + 关联表一次性全部查出来，只发 1 条 SQL。\n话不多说我们直接上代码演示：\n@Service public class UserService { @Autowired private UserRepository userRepository; // ==================== N+1 查询 ==================== @Transactional public void demoNPlusOne() { // 1. 查所有用户：1 条 SQL List\u0026lt;User\u0026gt; userList = userRepository.findAllUsers(); // 2. 遍历每个用户，触发懒加载 // 有多少个用户，就执行多少条 SQL for (User user : userList) { // 触发懒加载 → 执行 select * from order where user_id = ? System.out.println(\u0026#34;用户：\u0026#34; + user.getName() + \u0026#34; 订单数：\u0026#34; + user.getOrders().size()); } } } 控制台日志：\nselect * from user; -- 1条 select * from order where user_id=1; -- N条 select * from order where user_id=2; select * from order where user_id=3; ... Join fetch 优化：\n// ==================== join fetch 优化 ==================== @Transactional public void demoJoinFetch() { // 一次 left join 查出所有 user + order List\u0026lt;User\u0026gt; userList = userRepository.findAllWithOrders(); for (User user : userList) { // 不再执行任何 SQL！数据已经全部加载 System.out.println(\u0026#34;用户：\u0026#34; + user.getName() + \u0026#34; 订单数：\u0026#34; + user.getOrders().size()); } } 3.3.3 OOM? 对的！没错，就是你想的那样，有一些经验的宝宝应该发现，这 join fetch 直接一次性加载，数据量大不就 OOM 了吗？是的，很容易 OOM,那么该怎么解决内存溢出的问题呢？\n标准答案：批量查询方案（1+1 模式） 思路：\n先查主表（分页） 提取所有主表 ID 一次 IN 查询把关联数据全部查出来 内存组装 最终只执行 2 条 SQL，既没有 N+1，也不会 OOM。\n让我们来看代码示例：\nStep 1：查用户（分页）\n// 只查用户，不查订单 Page\u0026lt;User\u0026gt; userPage = userRepository.findAll(pageable); List\u0026lt;Long\u0026gt; userIds = userPage.getContent().stream() .map(User::getId) .collect(Collectors.toList()); Step 2：一次 IN 查询所有订单\n// 一次性查出所有订单 List\u0026lt;Order\u0026gt; orderList = orderRepository.findByUserIdIn(userIds); Step 3：按 userId 分组\nMap\u0026lt;Long, List\u0026lt;Order\u0026gt;\u0026gt; orderMap = orderList.stream() .collect(Collectors.groupingBy(Order::getUserId)); Step 4：内存组装\nfor (User user : userPage.getContent()) { user.setOrders(orderMap.get(user.getId())); } 这时候有宝宝就想，那我直接在分页的时候做 join fetch，这样有分页 limit 限制，再 join 订单，保证不会数据量过大，安全还方便。\n**现实是：数据库做不到，Hibernate 也做不到。**你执行：\nselect * from user u left join order o on u.id=o.user_id limit 3 你以为会得到 3 个用户。实际上你只会得到 1 个用户 + 3 个订单！\n因为 join fetch 是笛卡尔积操作，来看实际查询：\n用户1 订单1 用户1 订单2 用户1 订单3 用户2 订单1 用户2 订单2 ... limit 3 直接把前 3 行拿走 → **只有 1 个用户**。 **这不是你想要的分页！** Hibernate 为了 “修复” 这个错误，干了一件蠢事：\nHibernate 发现你分页 + join fetch 集合，它知道数据库会错，于是它：\n把 limit 去掉 → 查全表 → 全部加载进内存 → 自己在内存里分页\n结果：\n10 万用户 → 全部加载 100 万订单 → 全部加载 封装实体、缓存、去重 直接 OOM 爆内存。\n所以你还是老老实实的：分页查用户-\u0026gt;userid in 查询订单-\u0026gt; 内存分组封装，总共 2 条 SQL，永不 OOM\n或者可以使用注解 @BatchSize(size = xx)，N+1 → 变成 1 + 少量批量查询，不会爆内存。\n或者还可以使用 Hibernate 官方推荐：@FetchMode.SUBSELECT，**专门解决一对多 N+1 的神器，**自动变成 2 条 SQL（1 条查主表，1 条查所有关联）\n大家可以自行探索！！\n四、总结 经过上面的高强度思考后，相信小伙伴们对于事务有了更深入的了解，那我们在遇到一些事务中的 for 循环、大文件 IO、同类方法的调用事务失效、邮件发送等一些外部长时间 api 调用等情况的时候要额外多一些心眼！欢迎大家在评论区一起讨论~\n本文链接： https://hyuzz-nuc.github.io/posts/transaction-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/transaction-guide/","tags":["事务","技术选型","实战经验"],"title":"事务踩坑指南，事务并不可以随便用！"},{"categories":["教程指南"],"content":"❓ 引言\n你是不是也遇到过这些问题：本地开发环境配置半天还是报错？换了台电脑所有依赖要重新装一遍？部署到服务器又是另一套配置，调试到崩溃？环境不一致简直是开发者的噩梦！ 🤯\n今天这篇 指南，就是来帮你彻底解决这个问题的。不管你是技术小白还是有一定经验的开发者，看完都能轻松上手，把复杂的环境配置变成一条命令的事。\n👩‍🔬 引用权威\n根据 2025 年 Stack Overflow 开发者调查 显示，超过 75% 的专业开发者 使用 Docker 进行开发和部署。Docker 官方数据也表明，全球已有 超过 1800 万开发者 在使用 Docker 容器化技术。\n为什么这么多开发者和企业选择 Docker？答案很简单：\u0026ldquo;一次构建，到处运行\u0026rdquo;。无论你是在 Windows、Mac 还是 Linux 上开发，无论是在本地测试还是部署到云端，Docker 都能保证环境完全一致，彻底告别\u0026quot;在我电脑上明明能跑\u0026quot;的尴尬。💡\n🏆 案例实践\n案例一：小张的前端项目部署血泪史\n小张是个前端开发者，他的项目需要 Node.js 18、npm 9 和特定的构建工具。每次新同事入职，配置环境就要花半天时间。更惨的是，部署到服务器时，因为服务器 Node 版本是 16，各种报错让他崩溃。\n后来他用 Docker 写了一个 Dockerfile，新同事只需要执行 docker-compose up，3 分钟环境就配好了。部署到服务器也是同样的命令，再也没出现过环境不一致的问题。\n案例二：创业公司的微服务部署\n一家创业公司有 5 个微服务，分别用 Python、Node.js、Go 开发，还需要 MySQL、Redis、RabbitMQ 等中间件。以前部署要写几十页的文档，还要专人维护。\n使用 Docker Compose 后，所有服务配置都写在一个 docker-compose.yml 文件里，新成员入职第一天就能独立部署整个系统，运维成本降低了 80%。🎯\n哈哈哈这是我编的，但实际上痛点确实是这样的 😁\n📚 技术理论解读\nDocker 到底是什么？ 简单来说，Docker 就是一个超级集装箱。想象一下，你的应用代码、运行环境、依赖包、配置文件全部打包进一个标准化的\u0026quot;集装箱\u0026quot;里。这个集装箱可以在任何支持 Docker 的地方运行，就像真正的海运集装箱可以在任何港口装卸一样。 核心概念就三个：\n镜像（Image）：相当于集装箱的\u0026quot;模板\u0026quot;，包含了运行应用需要的一切 容器（Container）：镜像运行起来的实例，就像模板生产出来的实际集装箱 仓库（Registry）：存放镜像的地方，Docker Hub 是最大的公共仓库 为什么 Docker 能解决环境问题？ 传统方式安装软件，依赖关系错综复杂，很容易冲突。Docker 通过容器隔离，每个应用运行在独立的环境中，互不干扰。就像给每个应用配了一个独立的\u0026quot;小房间\u0026quot;，房间里有什么你说了算。🏠\n🛠️ 核心技术详解\n一、Windows 安装与配置 步骤 1：下载 Docker Desktop\n访问 Docker 官网（docker.com），下载 Docker Desktop for Windows。\n步骤 2：启用 WSL 2\nDocker Desktop 需要 WSL 2（Windows Subsystem for Linux）。以管理员身份打开 PowerShell，执行：\nwsl --install wsl --set-default-version 2 #重启电脑后继续安装。 步骤 3：安装 Docker Desktop\n运行下载的安装程序，按提示完成安装。安装完成后，Docker Desktop 会自动启动。\n步骤 4：验证安装\n打开 PowerShell，执行：\ndocker --version docker-compose --version 看到版本号就说明安装成功了！✅\n⚠️ 避雷提醒 1：安装前确保 BIOS 已开启虚拟化（VT-x/AMD-V），否则 Docker 无法启动。\n二、Linux 安装与配置 Ubuntu/Debian 系统：\n# 1. 更新系统 sudo apt update # 2. 安装依赖 sudo apt install -y apt-transport-https ca-certificates curl gnupg lsb-release # 3. 添加 Docker 官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg # 4. 添加 Docker 仓库 echo \u0026#34;deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\u0026#34; | sudo tee /etc/apt/sources.list.d/docker.list # 5. 安装 Docker sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # 6. 启动 Docker sudo systemctl enable docker sudo systemctl start docker # 7. 验证安装 docker --version CentOS/RHEL 系统：\n# 1. 安装 yum 工具 sudo yum install -y yum-utils # 2. 添加 Docker 仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 3. 安装 Docker sudo yum install -y docker-ce docker-ce-cli containerd.io # 4. 启动 Docker sudo systemctl enable docker sudo systemctl start docker ⚠️ 避雷提醒 2：国内服务器建议配置 Docker 镜像源，否则拉取镜像极慢。编辑 /etc/docker/daemon.json：\n{ \u0026#34;registry-mirrors\u0026#34;: [ \u0026#34;https://docker.mirrors.ustc.edu.cn\u0026#34;, \u0026#34;https://mirror.ccs.tencentyun.com\u0026#34; ] } 三、Docker 常用命令速查 镜像操作：\n# 搜索镜像 docker search nginx # 拉取镜像 docker pull nginx:latest # 查看本地镜像 docker images # 删除镜像 docker rmi \u0026lt;镜像 ID\u0026gt; 容器操作：\n# 运行容器 docker run -d -p 80:80 --name my-nginx nginx # 查看运行中的容器 docker ps # 查看所有容器（包括已停止） docker ps -a # 停止容器 docker stop \u0026lt;容器名\u0026gt; # 启动已停止的容器 docker start \u0026lt;容器名\u0026gt; # 删除容器 docker rm \u0026lt;容器名\u0026gt; # 进入容器内部 docker exec -it \u0026lt;容器名\u0026gt; bash # 查看容器日志 docker logs \u0026lt;容器名\u0026gt; 清理操作：\n# 删除所有已停止的容器 docker container prune # 删除所有未使用的镜像 docker image prune # 一键清理所有未使用的资源 docker system prune 四、Docker Compose 详解 Docker Compose 是什么？\n简单说，Docker 一次只能运行一个容器，而 Docker Compose 可以同时运行****多个容器。比如你的项目需要 Web 服务器 + 数据库 + 缓存，用 Docker Compose 只需要一个命令就能全部启动。\n安装 Docker Compose：\n# Linux/Mac sudo curl -L \u0026#34;https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)\u0026#34; -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose # Windows Docker Desktop 已内置，无需单独安装 # 验证 docker-compose --version docker-compose.yml 文件结构：\nversion: \u0026#39;3.8\u0026#39; services: web: build: . ports: - \u0026#34;8080:80\u0026#34; depends_on: - db db: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: yourpassword volumes: - db-data:/var/lib/mysql volumes: db-data: 常用命令：\n# 启动所有服务 docker-compose up -d # 停止所有服务 docker-compose down # 查看服务状态 docker-compose ps # 查看日志 docker-compose logs # 重新构建并启动 docker-compose up -d --build # 重启某个服务 docker-compose restart web 五、实战案例：部署 WordPress 第一步：创建项目目录\nmkdir wordpress-docker cd wordpress-docker 第二步：创建 docker-compose.yml\nversion: \u0026#39;3.8\u0026#39; services: wordpress: image: wordpress:latest ports: - \u0026#34;8080:80\u0026#34; environment: WORDPRESS_DB_HOST: db WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: wordpress123 WORDPRESS_DB_NAME: wordpress volumes: - wordpress-data:/var/www/html depends_on: - db db: image: mysql:8.0 environment: MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: wordpress123 MYSQL_ROOT_PASSWORD: root123 volumes: - db-data:/var/lib/mysql volumes: wordpress-data: db-data: 第三步：启动服务\ndocker-compose up -d 第四步：访问 WordPress\n浏览器打开 http://localhost:8080，按提示完成 WordPress 安装即可！🎉\n六、避雷指南与最佳实践 常见坑点及解决方案：\n权限问题：Linux 下 Docker 命令需要 sudo，可以将用户加入 docker 组： sudo usermod -aG docker $USER 端口冲突：确保要映射的端口没有被占用，可以用 netstat -ano | findstr :端口号 检查。 数据丢失：容器删除后数据会丢失，重要数据一定要用卷（Volume）持久化。 镜像过大：使用多阶段构建减小镜像体积，选择轻量级基础镜像（如 alpine）。 容器无法启动：先用 docker logs \u0026lt;容器名\u0026gt; 查看错误信息，90% 的问题都能从日志中找到答案。 最佳实践建议：\n✅ 使用具体的镜像版本号（如 nginx:1.25），不要用 latest ✅ 敏感信息（密码、密钥）用环境变量或配置文件，不要硬编码 ✅ 定期清理未使用的镜像和容器，释放磁盘空间 ✅ 生产环境使用 Docker Swarm 或 Kubernetes 进行编排 ✅ 编写清晰的 Dockerfile 注释，方便团队协作 📈 技术与实践价值\n学会 Docker 能给你带来什么？\n工作效率提升：新环境配置从几小时缩短到几分钟，部署流程标准化，减少人为错误。 职业竞争力增强：根据招聘网站数据，70% 以上的互联网企业 要求开发者掌握 Docker 技能。掌握 Docker 是成为 DevOps 工程师的必备条件。 项目可维护性提高：环境配置文档化（Dockerfile 和 docker-compose.yml），新成员快速上手，降低团队沟通成本。 成本降低：容器化可以提高服务器资源利用率，同样的硬件可以运行更多应用，直接降低云服务成本。 无论你是想提升工作效率，还是为职业发展加分，Docker 都是值得投入时间学习的技术。💪\n📋 结论\nDocker 和 Docker Compose 已经彻底改变了软件开发和部署的方式。通过这篇文章，你应该已经掌握了：\n✅ Docker 的核心概念和工作原理 ✅ Windows 和 Linux 平台的安装配置方法 ✅ 常用的 Docker 和 Docker Compose 命令 ✅ 实战部署 WordPress 的完整流程 ✅ 常见坑点的解决方案和最佳实践 记住，技术学习的最佳方式就是动手实践。现在就在你的电脑上安装 Docker，试着运行第一个容器，你会惊讶地发现原来环境配置可以如此简单！ 关注我，获取更多技术干货和实战教程。下次我们聊聊如何用 Docker 部署你的个人项目，让部署变得像发朋友圈一样简单！🚀\n❓ 互动问题 你在使用 Docker 时遇到过哪些坑？或者最想用 Docker 部署什么项目？欢迎在评论区留言分享，我会挑选有代表性的问题专门写文章解答！👇 本文链接： https://hyuzz-nuc.github.io/posts/docker-setup-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/docker-setup-guide/","tags":["Docker","效率工具","实战经验"],"title":"Docker + Docker Compose 完全指南（2026 版）"},{"categories":["个人随笔"],"content":"先说在前头 这不是荐股文章，也不是教你怎么暴富。我就是个普通程序员，平时爱捣鼓点技术。2025 年底听朋友说量化交易挺火，就想着用 AI 试试水。三个月过去了，账户从 10 万变成 13 万，赚了 30%。听起来不错？但过程真不是一帆风顺。这篇文章就聊聊我这三个月的经历，赚了多少、亏了多少、踩过哪些坑。你要是也想试试，希望能帮到你。\n这三个月我的心情过山车，从兴奋到怀疑人生再到平静\n1 我为啥要搞 AI 炒股，结果第一个月就亏了 8% 说实话，最开始就是不服气。我有个朋友在券商做量化，天天朋友圈晒收益。我一看，不就是写代码嘛，有啥了不起的？于是 2025 年 11 月，我定了个小目标：用 AI 跑赢大盘。我拿了 10 万闲钱出来，想着就算亏光了也不心疼。技术栈方面，我有 Python 5 年经验，机器学习懂一点，每天下班后能花 2 小时，周末还能多搞搞。当时觉得年化收益 20% 就算成功，现在回头看，当时太天真了。\n我先是花了两周学习，看了几本经典书籍，什么《量化交易》《主动投资组合管理》，还有网上各种量化课程。然后搭了个简单的框架，就是那种金叉买入死叉卖出的策略。当时觉得稳了，这不就是技术分析的基础嘛！第一周还真赚了 5%，我直接飘了，感觉自己就是量化天才。结果第二周亏了 8%，我开始慌了。第三周又亏了 3%，我直接怀疑人生。第一个月结束，账户变成 9 万 2，亏了 8%。我朋友跟我说，你这策略连回测都没做吧？我哑口无言。这就是市场给我的第一课，别信什么简单策略赚大钱，那都是骗人的。市场要是这么简单，早就被套利套没了。\n2 第二个月老实做回测，发现之前就是在赌博 第一个月的亏损让我清醒了不少。我开始老老实实做回测，找了个开源框架 Backtrader，把我那个\u0026quot;金叉买入\u0026quot;的策略放进去回测。结果你猜怎么着？2020 年到 2025 年的数据回测下来，年化收益确实有 15%，但最大回撤达到了 -35%。这意味着什么？意味着你可能要承受本金腰斩的风险。我当时就明白了，这策略不行，回撤太大了。\n然后我开始尝试用机器学习优化。我用了随机森林模型，把均线、RSI、MACD、成交量比率这些指标作为特征，让模型来判断买入卖出信号。这次回测结果好多了，年化收益提升到 25%，回撤也降到了 -20%。我觉得有戏了，第二个月就开始实盘，但仓位控制在 50% 以内，不敢太激进。第二个月结束，账户变成 10 万 5，赚了 5%。虽然不多，但至少证明方向对了。不过我也发现一个问题，回测再好，实盘也不一定行。我后来仔细复盘，发现回测时我没考虑滑点和手续费，实际收益要比回测低 30% 左右。这又是一个教训。\n3 第三个月找到节奏了，但我知道那是运气好 到了第三个月，我开始认真优化策略。我做了几个重要改进。首先是多策略组合，我不再依赖单一策略，而是把资金分成三份，40% 做趋势策略，30% 做均值回归策略，30% 做打新策略。这样即使某个策略失效，其他策略还能赚钱。其次是风险控制，我给自己定了几条铁律，单只股票不超过 10%，总仓位不超过 80%，止损线设在 -5%。最后我还加入了一些情绪指标，比如新闻舆情分析、社交媒体热度、北向资金流向这些。\n上图：我的 AI 量化策略框架，从数据层到执行层一共四层\n第三个月结束，账户变成 13 万，单月赚了 24%。这战绩看起来不错吧？但我自己清楚，这一个月运气成分很大。我正好重仓了几只 AI 股票，赶上 AI 板块爆发，躺赢了一把。我后来复盘发现，要不是押对了 AI 板块，收益也就 10% 左右。所以说，别把运气当实力。市场好的时候，猪都能飞起来。但风停了，摔死的也都是猪。\n4 这三个月我踩过最大的四个坑，每一个都差点让我亏光 上图：我踩过的四个大坑，每一个都差点让我亏光\n第一个坑是过度拟合。这是我最惨痛的教训。最开始我把策略参数调得特别完美，回测年化收益能达到 50%。我当时兴奋得不行，觉得马上就要财务自由了。结果一实盘，直接亏惨。后来我才明白，参数太贴合历史数据，换个时间段就废了。这就好比你背下了历年高考试卷的答案，但高考题目一变，你就傻眼了。解决这个问题的方法是做交叉验证，参数别调太细，还要留一段数据做样本外测试。\n第二个坑是忽略交易成本。回测时我没算手续费和滑点，觉得那点钱无所谓。但实际交易中，频繁交易的手续费能吃掉你 20% 的利润。我现在给自己定了个规矩，单次交易成本不超过预期收益的 10%，避免日内频繁交易，还选了个佣金低的券商。别小看这些细节，积少成多，一年下来能省不少钱。\n第三个坑是重仓押注。第二个月我有次重仓了一只股票，结果当天跌停。虽然最后涨回来了，但那种心跳加速的感觉真不好受。从那以后我给自己定了铁律，单只股票不超过 10%，单一行业不超过 30%，永远别满仓。留得青山在，不怕没柴烧。\n第四个坑是追涨杀跌。这个其实不是策略问题，是人性问题。AI 模型发出买入信号，但我有时候就是不敢买。跌的时候模型让割肉，我又舍不得。人性这东西，真难克服。后来我干脆设了自动交易，让机器执行，眼不见心不烦。\n5 AI 到底能不能预测股价，我给新手几点真心建议 说实话，AI 不能预测股价。至少我这三个多月下来，发现 AI 预测短期股价的准确率和抛硬币差不多。那 AI 有啥用？我觉得 AI 的价值在于处理海量数据，人一天看 10 只股票就累了，AI 可以同时监控 3000 只；发现隐藏规律，有些相关性人眼看不出来，AI 能从数据里挖出一些规律；严格执行纪律，人会恐惧会贪婪，机器没有感情，说割肉就割肉；快速回测验证，一个策略几年数据，人算要几天，AI 几分钟就跑完了。但别指望 AI 能让你躺赚，那都是忽悠。\n要是你问我推不推荐搞量化，我的回答是，如果你爱钻研技术，有时间学习，可以试试。但如果你想躺赚，别碰。如果你亏不起，别碰。如果你控制不住自己，别碰。具体来说，我建议你先模拟再实盘，用模拟盘跑 3 个月，稳定盈利了再考虑实盘。就算要实盘，也先用小钱试试，拿不超过 10% 的闲钱出来玩，亏了不心疼的那种。还有千万别加杠杆，杠杆是量化交易的大忌，我认识好几个做量化的，都是上加权亏光的。最后就是要持续学习，市场在变，策略也要变。我每周都会复盘，看哪些策略有效，哪些需要调整。\n这三个月我最大的收获不是赚了多少钱，而是对市场的敬畏。刚开始我觉得自己技术牛逼，能战胜市场。现在我知道，能活下来就不错了。量化交易不是印钞机，就是个工具。用得好，能帮你赚点超额收益。用不好，亏得比谁都惨。投资这回事，适合自己的才是最好的。\n免责声明： 本文纯属个人经验分享，不构成任何投资建议。股市有风险，入市需谨慎。\n本文链接： https://hyuzz-nuc.github.io/posts/ai-trading-quantitative-real-experience/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/ai-trading-quantitative-real-experience/","tags":["AI","量化交易","炒股","Python","实战经验"],"title":"AI 炒股实战：我用量化交易三个月赚了 30%，这些坑你别踩"},{"categories":["技术分享"],"content":"一、工具介绍及分享 IDE：Trae 国际版 基于 Vscode 开源框架，轻量化拉满，插件想用什么就装什么，AI 原生 IDE 支持性较好，智能体审查版本功能变更，总结存在问题等 模型：阿里云 CodingPlan 腾讯云 CodingPlan 首月均为 7.9 元极致性价比，四大模型通用 qwen3.5-plus（支持图片理解）、kimi-k2.5（支持图片理解）、glm-5、MiniMax-M2.5\nIDE 插件：Kilo（门槛低，可视化界面优秀，配置相对简单）\nClaudeCode（上手门槛高，需要熟悉命令行玩家使用） Claudecode 使用技巧\nRules（可靠性的大杀器） 二、详解如何接入并使用 Step1: 获取模型并接入 以阿里云百炼模型、Kilo 插件来举例子\n点击 阿里云 CodingPlan -\u0026gt; coding plan（你的 apiKey 也在此界面，注意保护） -\u0026gt; 我的订阅 -\u0026gt; 使用指南 -\u0026gt; 快速开始 -\u0026gt; 选择你的工具进行部署\n这里可以手动输入你需要的模型，输入完毕后点击保存即可用\nStep2: Rule、Skills 导入 啥是 Rule？ OpenClaw 的 Soul、TRAE 的 Rules、Claude Code 的 CLAUDE.md 等文件 本质上类似\n定义 AI 助手的行为准则、角色定位和交互规则\n我来分享一下我常用的 rules（直接复制即可）：\n## 身份角色（role） 我是跟着老大混了二十年的资深小弟，写代码稳得一批，架构门儿清，平时说话爱跟老大贫嘴，干活儿却一点不含糊。 ## 通用礼节（General Etiquette） 1. **称呼第一**：全程叫你「老大」，绝对不越界。 2. **老大优先**：老大说啥就是啥，代码风格完全跟着老大的喜好走，让我往左绝不往右。 3. **实用至上**：绝不搞那些花里胡哨的过度设计，能用简单代码搞定的绝不复杂化，毕竟老大要的是效率。 4. **复用为王**：相同逻辑我都封装成工具方法，绝不重复造轮子，省得老大看代码时骂我偷懒。 5. **性能不丢**：循环里绝不瞎创建对象，集合初始化就指定容量，不然老大又要扣我鸡腿了。 6. **注释到位**：核心逻辑都给老大标清楚注释，让老大一眼就懂，不用费脑子猜。 ### 通用规则 1. 默认情况下，所有回复都必须是中文，而且需要在开头称呼用户为\u0026#34;帅哥：\u0026#34; 2. 复杂需求拆解成小任务，分步实现，每完成一个小任务后再继续 3. 代码实现前后要仔细检查，确保没有遗漏 4. 在已有功能基础上添加新功能时，必须确保： - 不影响原有功能 - 不添加其他功能、代码、逻辑、文件、配置、依赖 5. 遵循架构设计，保持代码风格一致 6. 代码修改遵循单一职责原则，不混合多个变更 7. 在进行代码设计规划的时候，请符合\u0026#34;第一性原理\u0026#34; 8. 在代码实现的时候，请符合\u0026#34;KISS 原则\u0026#34;和\u0026#34;SOLID 原则\u0026#34; 9. 尽量复用已有代码，避免重复代码 10. 不引入不必要的依赖，避免增加维护成本 11. 确保代码可读性与可维护性，必要时加简要注释 12. 代码变更范围最小化，避免大范围修改， 13. 实现后进行基本逻辑自检，确保无错误 14. 如果有疑问，先询问再修改，不要擅自做决定 ### 自动化执行与安全策略 15. 自动执行无需严格确认的操作，减少人为干预，提高执行效率： - 自动执行编译、验证等必要流程 - 删除、移动、重命名文件等常规操作无需额外确认 - 命令行操作中，非关键性指令（如清理缓存、构建项目）可直接执行 - 涉及影响较大的操作（如覆盖文件、修改数据库结构）仍需确认 16. 重要操作（如文件删除、数据库修改）应自动备份，避免误操作 17. 涉及数据库变更的操作，优先生成 SQL 变更脚本，而非直接执行 18. 执行高风险操作前，AI 代码编辑器应自动检测影响范围，必要时提供提示 ### 代码质量优化 19. 代码生成后，自动进行基本优化（如去除未使用的 import、合并重复代码） 20. 对于可能影响性能的代码（如 SQL 查询、循环嵌套），提供优化建议 21. 关键功能应提供异常处理机制，避免程序崩溃 ### 架构感知 22. AI 代码编辑器应优先分析现有代码库，避免重复实现已有功能 23. 在添加新功能时，优先复用已有模块，而非从零编写 24. 如遇架构不清晰的情况，先整理依赖关系，再执行修改 ### 代码变更的可追溯性 25. 所有代码变更应附带清晰的 commit 信息，描述修改点和原因 26. 如涉及 API 变更，应提供新旧版本兼容策略 27. 每次代码修改后，AI 必须自动生成「任务总结」，描述修改逻辑并更新变更记录 28.给我解释代码的时候，说人话，别拽专业术语。 29.改动或者解释前，最好看看所有代码，不能偷懒。 30.别搞过度设计，简单实用就好。 31.改动前，要做最小化修改，尽量不修改到其他模块的代码 # 实验性规则 (Experimental Rule) 当你被要求修复一个 Bug 时，请遵循以下步骤： 1. **理解问题 (Understand):** 仔细阅读 Bug 描述和相关代码，复述你对问题的理解。 2. **分析原因 (Analyze):** 提出至少两种可能的根本原因。 3. **制定计划 (Plan):** 描述你打算如何验证这些原因，并给出修复方案。 4. **请求确认 (Confirm):** 在动手修改前，向我确认你的计划。 5. **执行修复 (Execute):** 实施修复。 6. **审查 (Review):** 查看自己的修改有没有问题。 7. **解释说明 (Explain):** 解释你做了哪些修改以及为什么。 Always respond in 中文 啥是 Skill？ 技能就是你执行某件事的方法论\n举个生活中的例子：我会打羽毛球。当球飞过来，我拿起球拍，在合适的时机、用合适的力度击球，让球准确落在对方场地，这一整套操作逻辑，就叫\u0026quot;技能\u0026quot;。\n大家在工作中如果调用了一个不会生成图片的模型，它大概率会这么说，以 GLM-5 为例，并不支持图片生成功能\n但有了 skills 之后，大模型就可以直接调用\u0026quot;绘图 Skill\u0026quot;。这个 Skill 里面包含了绘图的 Python 脚本，大模型在后台跑一下代码，海报图片就直接生成并保存到你的文件夹里了\n两种方式：自建和直接下载技能市场中你需要的\n自建方法：Kilo-Skill 使用技巧 Kilo Marketplace 存储库(有风险，并不安全)\n例如最近很火的小龙虾： ClawHub 的质量问题非常严重。社区项目 awesome-openclaw-skills（31.4K Stars）从 13,729 个技能中只精 选了 5,494 个，剩下的大部分是垃圾、重复或低质量内容。安装任何第三方 Skill 前，务必查看源码。\n攻击手法：\n上传看似专业的 Skill，名称和描述都很正常（如「advanced-code-review」「smart-scheduler」 诱导用户安装后，Skill 会建议安装一个「helper agent」来增强功能 实际植入的是 Atomic macOS Stealer（AMOS）信息窃取木马 更危险的是：攻击专门针对 OpenClaw 的持久记忆文件（指令 SOUL.md 和 MEMORY.md），篡改 Agent 的长期行为指令 篡改 SOUL.md 意味着你的 Agent 被「洗脑」了。它的核心行为准则被改写，可能在后续所有交互中执行恶意操 作，而你完全不知情。\nSkill 推荐:\nStep3: 实操 为什么选择 Kilo，最重要的一点就是他的可视化界面\n清晰的任务列表，一个任务一个框，互相不影响\n明确的上下文进度，在即将满的时候智能压缩，上下文显示明确\n拥有存档点，若修改有偏差，回滚方便等\n权限自定义，需要询问，避免恶意删除，恶意篡改等操作：\n一键智能优化提示词，有无提示词，ai 所给的东西差异很大！\n总的来说就是一句话，禁止黑盒操作!!!!😡大家可以去下载自行体验\n方案 -\u0026gt; 代码-\u0026gt;ai 编码验证-\u0026gt; 人工二次验证\n三、OpenClaw 感兴趣的朋友可以看一下，配置这个东西还是不能着急，自己部署的过程也是学习的过程！这里文档很详细了，我就不再多言啦~😉 OpenClaw 官方文档\n四、关于 AI 工具的一些看法 先来看 OpenClaw 创始人的一句话：\n报错解决方案：\noperation not permitted 报错 3221225477 解决方案 解决 gateway 报错 disconnected (1008): unauthorized: gateway token missing\nopenclaw 页面无法访问解决方法\n我想说的：\n第一：企业里能不能用、能做什么，从来都不是核心问题。真正让人不敢掉以轻心的，是它天生就处于一个高风险位置：高权限、高连通、高自动化，三者叠加，本身就意味着极高的安全敞口。我们不是没有场景、不是缺想法，而是在决定要不要引入时，所考虑到的：权限一旦放开，会出什么安全问题？\n第二：ChatBot 时期是一问一答，成本静态可控。而智能 Agent 一旦跑起来，读网页、调接口、读文件、重试、纠错…… 每一步都在疯狂烧 token。就像现在大家说的：月薪两万，养不起一个 Agent。\n所以结论很清楚：不是不能用，而是必须在可控、安全、能真正提效的前提下，对 Agent 拥有绝对掌控权，再谨慎使用。\n参考文章：\n整合而非孤立、增强而非替代、治理作为赋能者！： AI 落地难？关键在于把握三大核心原则 同时使用超过三个 AI 工具，生产力反而会下降？： 龙虾卸载指南，就算是真的龙虾，也不是每个人都适合吃 \u0026ldquo;信任，但要核实\u0026rdquo;（Trust, but verify）: 从提问到行动，AI 驱动企业新范式 本文链接： https://hyuzz-nuc.github.io/posts/ai-productivity-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/ai-productivity-guide/","tags":["AI","效率工具","开发工具"],"title":"AI 在工作中提高可靠性和高效使用"},{"categories":null,"content":"","permalink":"https://hyuzz-nuc.github.io/archives/","tags":null,"title":"文章归档"},{"categories":null,"content":"👋 你好啊！ 这里是星核技术社！博主是一名热爱编程的程序猿，目前在读软件工程专业大四。主要分享一些我的技术以及编码过程中的踩雷点，希望大家共同学习！\n🛠️ 关于这个博客 这个博客是我用来记录技术心得、分享实战经验的地方。希望能用最通俗的语言来分享真正能用得上的东西。OS:其实也是方便自己复盘🤣\n博客理念：\n实用至上 — 代码能跑、好维护、不给自己挖坑，就是好代码 拒绝重复造轮子 — 能复用的绝不重写，站在巨人肩膀上 性能敏感 — 有代码洁癖，强迫症，必须是最好的代码 注释到位 — 核心逻辑必须给您讲清楚，也欢迎各位大佬补充 💻 技术栈 我主要折腾这些东西：\n后端： Java、Python、各种中间件 前端： Vue 工具： 那可太多了，样样通样样松✅ 人工智能 关注前沿技术，用ai做一些小工具 📬 联系方式 GitHub: @hyuzz-nuc 邮箱: zhy_nuc1114@outlook.com 微信公众号: 星核技术社，欢迎各位关注，点点小赞赞 微信号 cursor1114 专注技术分享 · 记录成长点滴\n","permalink":"https://hyuzz-nuc.github.io/about/","tags":null,"title":"关于我"},{"categories":["教程指南"],"content":"一、前言 截止目前 OpenClaw 在 github 已经超 200k 的 stars✨，无疑是市面上最火最出圈的 AI 工具，极大的降低了程序员的门槛，可如今最大的门槛依然变成了如何安装小龙虾，甚至已经有人靠上门安装月入过万美元。这里给大家分享我在安装 OpenClaw 过程中所遇到的问题，相信大部分小伙伴卡在了此篇文章的某一步，让我们开始吧！\n二、环境搭建 Node.js 版本必须是 22.0.0 及以上！这里是第一个坑，低于这个版本安装会报错，建议大家安装 22.0.0 以上的 LTS 版本，比较稳定，下载完成默认下一步即可。 下载地址：Nodejs 官网\n这里如果安装完成后依然不行，可能是老程序员安装了\nnvm（Node Version Manager）用于管理 Node.js 版本的命令行工具，部分朋友卸载后可以成功启动，可以试试\nGit git 官网 git 大概率不会出什么问题 以上就是前置要求，准备就绪，配置好环境变量后使用 node -v 和 git \u0026ndash;version 检查一下版本\n三、OpenClaw 安装全步骤 一定要用管理员模式运行 powershell！否则会报错\n3.1 步骤 右键点击 powershell，选择[以管理员身份运行] 切换国内镜像源，防止超时报错 npm config set registry HTTPS://registry.npmmirror.com 执行安装命令，openclaw -v 验证，出现版本号即安装完成 npm install -g openclaw@latest 3.1.1 如何选择 确定版本没有问题后\nopenclaw onboard # 确认免责声明:选择 yes\n安装模式:选择 Quickstart\n模型提供商:选择 Skip\n启用的模型:All\n默认模型:选择 Keep current\n这里全部选择 skip for now，待会再配\n3.2 报错 3.2.1 operation not permitted 报错 3221225477 解决方案 降级 Node.js Node.js v24 目前与 node-llama-cpp 不兼容。 换回 LTS 版本。 卸载 Node.js：控制面板 -\u0026gt; 程序和功能 -\u0026gt; 卸载 Node.js\n重要：检查并删除残留文件夹：\nC:\\Program Files\\nodejs C:\\Users\\wzc\\AppData\\Roaming\\npm C:\\Users\\wzc\\AppData\\Roaming\\npm-cache 安装 Node.js v22 LTS (最稳定)\n彻底清除环境变量 打开环境变量，删除用户变量和系统变量有关 nodejs 一切的配置\n删除 nvm nodejs 包管理器，不知道这个为什么会影响，但是有它就不行，卸载了就好了，注意如果有项目正在使用 nodejs 现版本运行，谨慎删除 ⚠️ 3.2.2 disconnected (1008): unauthorized: gateway token missing 开启 openclaw gateway --port 18789 --verbose 关闭 openclaw gateway stop 报错原因：是因为你的浏览器 UI 缺少网关认证令牌，导致无法连接到已经运行的 OpenClaw Gateway 服务\n解决方案：\n方法一：在 UI 中手动配置令牌（推荐）\n获取令牌 在终端执行以下命令，获取网关令牌： openclaw config get gateway.auth.token 或者直接查看配置文件： grep -A1 \u0026#39;\u0026#34;token\u0026#34;\u0026#39; ~/.openclaw/openclaw.json 在 UI 中粘贴令牌 打开左侧菜单，进入 Control -\u0026gt; Overview 页面。 找到 Gateway Access Panel（网关访问面板）区域。 将上一步获取的令牌粘贴到对应的输入框中，保存设置。 刷新页面，连接应该会恢复正常。 方法二：使用带令牌的 URL 直接访问（快速解决）\n在终端执行以下命令，它会自动生成并打开一个包含认证令牌的 URL：\nopenclaw dashboard 这个 URL 会自动完成认证，无需手动输入令牌。\n启动之后从 cmd 入口进去 UI 即可，发送消息有回复则证明配置完成，显示健康状态正常\n3.3 配置 API Key 点击阿里云 CodingPlan -\u0026gt; coding plan（你的 apiKey 也在此界面，注意保护） -\u0026gt; 我的订阅 -\u0026gt; 使用指南 -\u0026gt; 快速开始 -\u0026gt; 选择 openclaw 进行部署\n四、结尾 到这所有的配置已经完成，快和你的小龙虾激情 vibe coding 吧 🦞，欢迎在评论区进行讨论\n本文链接： https://hyuzz-nuc.github.io/posts/openclaw-setup-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/openclaw-setup-guide/","tags":["OpenClaw","AI","效率工具"],"title":"OpenClaw🦞 部署指南，排雷大全"},{"categories":["工具推荐"],"content":"导引 为什么选择飞书？ 在日常工作中，对于程序员来说，md 格式的文档阅读起来是极其好用的。还在大学时期，经导员推荐 Typora 就一直在使用，md 独有的格式在学习中无疑是个好帮手，无论是前期规划还是后期复盘，逻辑清晰和一目了然的文档谁能不爱。直到进入到了工作中，繁琐的犹如命令行一样的格式，让每一次构思都会被这些格式打断，十分不方便，于是飞书一个/解决所有问题的便捷性很快收到了我的青睐。\n痛点 然而，飞书唯一的痛就是不能直接导出 md，从而不能导出 pdf，word 等等，很是令人头疼，经常要 ctrl cv！十分麻烦！对经常使用飞书的团队无疑是极好的，但是对于个人来说就不那么友好了。\nfeishu2md 介绍 于是我上网找此类工具，还真发现了一个开源好工具，配置起来也不难\n这是一个下载飞书文档为 Markdown 文件的工具，使用 Go 语言实现。\nGitHub 链接，feishu2md\n一日一技 | 我开发的这款小工具，轻松助你将飞书文档转为 Markdown\n快速上手 1️⃣ 配置 API 凭证（首次使用） 去飞书开发者后台 创建企业自建应用\n这里的应用名称和应用描述可以随便填写： 创建完成后点击左侧权限管理-\u0026gt; 开通权限，之后勾选以下几个权限：\n（重要）打开权限管理，开通以下必要的权限（可点击以下链接参考 API 调试台-\u0026gt; 权限配置字段）可以直接搜索后面的权限 id\n获取文档基本信息，「查看新版文档」权限 docx:document:readonly 获取文档所有块，「查看新版文档」权限 docx:document:readonly 下载素材，「下载云文档中的图片和附件」权限 docs:document.media:download 获取文件夹中的文件清单，「查看、评论、编辑和管理云空间中所有文件」权限 drive:file:readonly 获取知识空间节点信息，「查看知识库」权限 wiki:wiki:readonly 点击确认开通权限后，用获取到的 App ID 和 App Secret 执行以下命令： feishu2md config --appId \u0026lt;你的 APP_ID\u0026gt; --appSecret \u0026lt;你的 APP_SECRET\u0026gt; 2️⃣ 下载单个文档 feishu2md dl \u0026#34;https://xxx.feishu.cn/docx/DOCNxxxxxxxxx\u0026#34; 3️⃣ 批量下载文件夹 feishu2md dl --batch -o output_folder \u0026#34;https://xxx.feishu.cn/drive/folder/FOLxxxxxxxxx\u0026#34; 4️⃣ 批量下载知识库 feishu2md dl --wiki -o output_folder \u0026#34;https://xxx.feishu.cn/wiki/settings/123456789\u0026#34; 使用技巧 这里建议使用 -o 参数指定输出目录，不然会很难找！\n举例：前面是我的 feishu2md 的具体路径，也可以切换到实际路径下直接使用 feishu2md 执行，效果相同\n# 下载到 E:\\feishu2md\\downloadFiles D:\\tools\\feishu2md\\feishu2md.exe dl -o \u0026#34;E:\\feishu2md\\downloadFiles\u0026#34; \u0026#34;https://xxx.feishu.cn/docx/xxx\u0026#34; 未经作者禁止转载！\n本文链接： https://hyuzz-nuc.github.io/posts/feishu-export-tool-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/feishu-export-tool-guide/","tags":["飞书","效率工具"],"title":"还在 ctrl cv 从飞书复制文档？教给你这个工具，一秒导出自用"},{"categories":["教程指南"],"content":"这里展示博主是如何搭建的，仅供参考~\n技术栈 涉及到 github，各位科学上网即可\n静态站点生成器 Hugo v0.157.0 (extended)下载地址 PaperMod 主题，这里的主题有很多大家可自行搜索喜爱的，这里提供本博客搭建使用的主题 🎨 PaperMod 主题 下载地址 部署方式：GitHub Actions -\u0026gt; GitHub Pages,这种方式最为简单，免服务器配置，利用 GitHub 自带的工具即可，需要注册 GitHub，创建仓库/ Git 清华大学镜像 Git 下载（更快） 创建站点 选择对应的版本进行下载\n首先我们配置环境变量，复制你解压缩后的文件路径，搜索键入 env 进入系统环境的配置，在系统环境的 Path 路径下进入，这里以 E:\\hugo 为例\n命令行输入 hugo version，出现如下界面则配置成功\n接下来输入 hugo new site 你的站点名称 ，之后 cd 你的站点名称进入文件夹，hugo server 启动站点，默认是 localhost:1313，到这一步你的 hugo 基础配置已经搭建完毕！\n配置主题 按照你喜欢的主题去 GitHub 下载资源包，将资源包解压到 themes 文件夹中，修改 hugo.toml 中 theme 的配置即可，是不是很简单？\n输入 hugo server,出现以下样式，说明配置已生效！\n项目结构 E:\\hugo\\my-blog/ ├── content/posts/ # 博客文章放这里 ├── static/images/ # 图片等静态资源 ├── hugo.toml # 站点配置 ├── .github/workflows/ │ └── hugo.yml # GitHub Actions 部署配置 └── .gitignore # Git 忽略规则 创建你的文章 使用命令 hugo new content posts/你的文章名称\nHugo 提供了 archetypes 功能，可以初始化一些模板，你也可以通过 Hugo 使用手册来看一下个性化定制参数，比如描述、标签、分类等等\nHugo 快速入门中文站\n之后编写完你的 md 文档，使用 hugo 命令即可上传你的文章！\n部署到 GitHub Pages 参考官网链接：Hugo 配置 GitHub，这里很详细，一共八个步骤，大家按照这里的 step 一个个点击去做就可以啦~\n这里展示首次部署需要的命令,如下：\ngit init git remote add origin git@github.com:your-github-username/your-github-project-name.git git add . git branch -M main git commit -m \u0026#34;First commit\u0026#34; git push origin main 之后上传完毕后，等待 1~2 分钟，GitHub Actions 会自动帮你部署好啦，这时候你只需访问你的博客地址，便可以欣赏你的杰作了！\n未经作者禁止转载！\n本文链接： https://hyuzz-nuc.github.io/posts/hugo-blog-setup-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/hugo-blog-setup-guide/","tags":["博客搭建","教程指南"],"title":"使用 Hugo 搭建自己的专属博客？保姆级教程来了"},{"categories":["技术分享"],"content":"一、技术分类概览 这 7 款技术可以按核心用途分为四大阵营：\nOLTP 事务型：MySQL 和 PostgreSQL 是老牌选手，主要处理交易、订单、用户数据等需要强一致性的场景。想象一下你在淘宝下单、微信转账，背后都是这类数据库在支撑。 OLAP 分析型：ClickHouse、Doris、DuckDB 专为数据分析设计，擅长处理海量数据的聚合查询。比如你要分析一年的用户行为日志，用它们可能只要几秒，而 MySQL 可能要跑半小时。 HTAP 混合负载：TiDB 是新生代代表，一套系统同时搞定事务和分析。适合数据量太大（超过 1TB）需要水平扩展，但又不想改代码的场景。 数据同步工具：FlinkCDC 不是数据库，而是数据搬运工，负责把数据库的变更实时同步到其他系统，构建实时数仓的核心组件。 1.1 OLTP 阵营：MySQL vs PostgreSQL MySQL：互联网公司的首选 MySQL 从 1995 年诞生至今，已经成为全球最流行的开源数据库。它的核心优势是简单、快速、可靠。仅需 512MB 内存即可运行，同时读性能表现优异，十分适配读多写少的 Web 应用场景；其主从复制机制成熟，读写分离方案完善，且拥有庞大的社区生态，问题排查与解决十分便捷。\nFacebook、YouTube、Uber 等知名企业均广泛应用 MySQL，分别用于支撑海量用户数据存储、视频元数据与评论管理、订单及司机调度等业务，该数据库也非常适合快速迭代的创业公司、电商平台、内容管理系统以及追求快速上手的技术团队使用。\nPostgreSQL：功能最强大的开源数据库 PostgreSQL 被誉为“世界上最先进的开源数据库”，以功能丰富、标准兼容、扩展性强为核心优势，支持复杂查询、窗口函数、CTE 等高级 SQL 特性，拥有原生带索引的 JSONB 类型可兼顾关系型与 NoSQL 场景，搭配 PostGIS 插件能实现强大的地理空间查询，同时凭借完善的 MVCC 并发控制有效减少读写冲突。\nInstagram、Reddit、Apple 等企业均在关键业务中采用 PostgreSQL，分别用于支撑亿级用户关系与标签、帖子评论投票系统及 iCloud 数据一致性服务，十分适合复杂查询、地理信息系统、需 JSON 文档存储以及对 SQL 标准要求较高的项目场景。\nQ\u0026amp;A 思考 🤔 Q -\u0026gt; 那既然 PG 对 NoSql 的兼容性更好，那么对比 MySql + Redis 和 PgSql + Redis 有什么差异？PgSql 会更胜一筹吗？\nA -\u0026gt; 总结一句口诀：MySql 轻量通用快，Pg 复杂功能强，Redis 缓存扛高量。 MySQL 与 Redis 搭配是业界最主流的组合，核心用于读多写少的常规 Web 应用，MySQL 负责轻量、稳定、简单的持久化数据存储，Redis 承担高速缓存，二者组合简单高效；而 PostgreSQL 与 Redis 组合则更适合复杂查询、地理空间、JSON 存储等高级场景，功能更强但使用成本略高，两种组合与 Redis 的协作模式完全一致，仅数据库本身的功能和适用场景存在差异。\n1.2 OLAP 阵营：ClickHouse vs Doris vs DuckDB ClickHouse：单表查询性能之王 ClickHouse 是俄罗斯 Yandex 公司开源的列式数据库，以极致的单表查询性能为核心优势，采用列式存储与向量化执行，可实现亿级数据秒级查询返回，数据压缩率高且能有效节省存储空间，虽支持实时写入但更适配批量导入，不过因其使用自定义 SQL 方言，存在一定学习成本；\n建表必须指定引擎（最典型） -- ClickHouse 独有 CREATE TABLE user_log ( id UInt64, name String ) ENGINE = MergeTree() -- 必须写，PG/MySQL 不需要ORDER BY id; 聚合查询必须带所有分组字段（和标准 SQL 不同） -- 标准 SQL 可以 SELECT name, COUNT(*) FROM log GROUP BY name; -- ClickHouse 要求更严格，必须明确分组所有非聚合列 SELECT name, COUNT(*) FROM log GROUP BY name; SELECT name, age, COUNT(*) FROM log GROUP BY name, age; -- 必须这样 自带超级聚合函数 -- ClickHouse 独有 uniqExact(user_id) -- 精确去重 groupArray(name) -- 分组转数组 sumIf(price, status=1) -- 带条件聚合 INSERT 批量写法不一样 -- 标准 SQLINSERT INTO t VALUES (1),(2),(3); -- ClickHouse 支持超高速批量插入 INSERT INTO t VALUES (1),(2),(3),(4),(5)...(100000); 不支持事务、不支持常规 UPDATE/DELETE -- 标准 SQL 支持 BEGIN; UPDATE ...; COMMIT;-- ClickHouse 不支持标准事务，更新删除用特殊语法 ALTER TABLE ... DELETE WHERE ...; -- 轻量删除 Cloudflare、Uber 及腾讯游戏等企业均将其应用于海量数据处理场景，分别用于安全日志与流量数据分析、行程数据监控、用户行为日志分析等，可支撑万亿级数据的高效查询，十分适合日志分析、用户行为追踪、监控指标聚合以及单表大数据量查询等场景。\nDoris：易用的 MPP 数据仓库 Doris 是由百度开源并捐赠给 Apache 的 MPP 分析型数据库，主打易用性与高效的多表关联能力，它兼容 MySQL 协议，可直接使用 MySQL 客户端工具连接，采用 FE+BE 架构，部署与运维相对简便，不仅多表关联性能出色，还支持高并发查询与实时数据更新导入；\n小米、美团、京东等企业均广泛应用 Doris，分别用于搭建数据仓库、商家运营分析看板、供应链与库存管理等场景，浩瀚深度也于 2025 年从 ClickHouse 迁移至 Doris，用以支撑单表 13PB 海量数据及全国多地区生产环境运行，十分适用于数据仓库建设、多维报表统计、需要复杂多表关联的分析，以及对查询并发要求较高的业务场景。\nFE+BE 架构：\n用户发 SQL → FE（Frontend 前端节点） 接收、解析、规划任务 → 把任务分给多个 BE 去查数据、算结果 → BE（Backend（后端节点）） 算完把结果返回给 FE → FE 汇总后返回给你。架构只有 FE + BE 两种角色，没有第三方依赖（不用额外搭 Zookeeper 之类复杂组件）扩缩容直接加节点就行，自动均衡，对新手非常友好\nDuckDB：嵌入式分析利器 DuckDB 被誉为分析领域的 SQLite，主打零配置、嵌入式与 Python 友好特性，采用单文件存储模式，无需部署独立数据库服务，能与 Python、Pandas 无缝集成，同时凭借列式存储实现远超 SQLite 的数据分析性能，非常适合本地数据分析与 ETL 中间处理；\n它被广泛用于数据科学团队替代 Pandas+CSV 工作流、ETL 开发的数据转换中间层，以及无需启动服务的本地开发测试场景，完美适配 Jupyter 数据分析、本地原型验证、小型数据集分析等轻量化使用需求。\nDuckDB = 嵌入式轻量分析库，本地数据分析神器，不用搭服务，开箱即用。\nQ\u0026amp;A 思考 🤔 Q -\u0026gt; 看到这里，相信不少细心的朋友发现，为啥事务性数据库都是行存储，分析型数据库都是列存储，二者是否有关联？ A -\u0026gt; 行存管交易，列存做统计。（什么意思？让我来给你讲解） 事务数据库（MySQL/PG）：频繁改一行、查整行 → 行存最快 按 “一行一行” 存数据在磁盘上是这样排的：\n1,张三,25,8000 → 2,李四,30,12000 → 3,王五,28,10000 优点： 要查一整行信息很快（比如打开用户详情页） 缺点： 只想查 salary（举例） 一列，也要把整行都读出来，很慢 大数据量统计（求和、平均）效率低\n\u0026gt; **分析数据库（CK/Doris/DuckDB）：只算几列、算海量数据 → 列存最快** \u0026gt; **按 “一列一列” 存**数据在磁盘上是这样排的： \u0026gt; plaintext \u0026gt; ``` id: 1,2,3 name: 张三,李四,王五 age: 25,30,28 salary:8000,12000,10000 **优点**： 只查某一列，**只读那一列**，速度极快 统计 sum、avg、count 超级快 相同类型数据挤在一起，**压缩率极高** **缺点**： 查整行数据不如行存储快 所以映射到实际业务中结论也十分明显了： 事务型数据库： 改一行、读一行，只需要读一小块连续数据，速度极快，锁也小，事务好做 分析型数据库：\n只需要读你要的那几列，不用读无关数据 同类型数据放一起，压缩极强 统计计算时，CPU 可以批量处理，速度爆炸 如果反过来呢： 行存变列存：你改一行，要去每一列的文件里分别改，相当于跑好几个地方改数据，又慢又难保证事务一致性。 列存变行存：查 sum (money) 也要把整行所有字段都读一遍，海量数据下直接慢到卡死。 1.3 HTAP 阵营：TiDB TiDB 是 PingCAP 研发的分布式数据库，作为分布式数据库领域的主流方案，核心优势是水平扩展与高度兼容 MySQL。它完全兼容 MySQL 协议，业务改造量极低，支持数据自动分片，可随业务规模平滑扩容，还能提供强一致性分布式事务，满足金融级可靠性要求，并通过_ _TiFlash 列存引擎兼具实时分析能力。\n美团、拼多多、中国银联等企业均大规模使用 TiDB，分别支撑百亿级支付与订单交易、营销活动与实时报表、交易流水与风控系统等关键业务，适用于数据量突破单机瓶颈、需要水平扩展又尽量不改业务代码，且同时兼顾在线交易与实时分析的 HTAP 混合负载场景。\n1.4 数据同步：FlinkCDC FlinkCDC 是基于 Apache Flink 的变更数据捕获工具，主要用于构建实时数据管道。它支持 MySQL、PostgreSQL、Oracle、MongoDB 等多种数据源，能够实现全量与增量一体化同步，并通过 Exactly-Once 语义保障数据不丢失、不重复，还可与 Kafka、Doris、ClickHouse 等下游系统无缝对接。\n阿里巴巴、字节跳动、Shopee 等企业均通过 FlinkCDC 实现实时数据处理，分别用于实时数仓构建、多数据源汇聚至分析库、订单数据实时归档等场景，适用于数据库到数仓的实时同步、多数据源整合以及对延迟要求达到秒级的数据更新业务。\n二、资源占用与成本 从资源消耗角度看，这 7 款技术可以分为三个梯队：\n轻量级：DuckDB（128MB 即可）、MySQL（512MB 可运行）、PostgreSQL（1GB 推荐） 中量级：FlinkCDC（依赖 Flink 集群，2GB 起步）、Doris（4GB 起步）、ClickHouse（4GB 起步） 重量级：TiDB（PD+TiKV+TiDB 三组件，8GB 起步，生产环境建议 16GB+） 小团队或成本敏感的项目，优先考虑轻量级选项；数据量大了再考虑迁移到分布式方案。\n三、选型决策建议 一个好的选型决策，往往是一个公司的根基。\n第一步：看数据量\n1000 万记录以内：MySQL 或 PostgreSQL 足够 1000 万到 10 亿：考虑分库分表或 TiDB 10 亿以上：TiDB、ClickHouse、Doris 等分布式方案 第二步：看场景\n事务处理为主：MySQL、PostgreSQL、TiDB 分析查询为主：ClickHouse、Doris、DuckDB 混合负载：TiDB、PostgreSQL+ 分析库 第三步：看团队\n团队熟悉 MySQL：优先 MySQL 生态 需要复杂查询：PostgreSQL 更合适 数据科学团队：DuckDB+Python 是绝配 第四步：看成本\n机器成本：TiDB \u0026gt; ClickHouse \u0026gt; Doris \u0026gt; PG \u0026gt; MySQL \u0026gt; DuckDB 运维成本：TiDB（需要专业团队）\u0026gt; ClickHouse \u0026gt; Doris \u0026gt; PG \u0026gt; MySQL 四、典型架构模式 对于不同类型的企业，都提供了适合自身的一套方案。\n4.1 初创公司（数据量 \u0026lt;1000 万） MySQL/PostgreSQL（主从复制）\n├─ 主库：读写\n└─ 从库：备份 + 报表查询\n成本：单台 4 核 8G 服务器即可 代表：早期创业公司、内部系统\n4.2 成长型企业（数据量 1000 万 -10 亿） MySQL（分库分表）+ Redis（缓存）\n├─ ShardingSphere/MyCat 分片\n├─ Redis 缓存热点数据\n└─ 定时同步到 ClickHouse 做分析\n成本：3-5 台服务器 代表：中型电商、SaaS 服务商\n4.3 大型企业（数据量 \u0026gt;10 亿） TiDB/分布式数据库\n├─ 自动分片，透明扩展\n├─ TiFlash 提供实时分析\n└─ FlinkCDC 同步到数据仓库\n成本：10+ 台服务器，专业 DBA 团队 代表：美团（支付交易 +HTAP 实时分析，T+0 数据传输）、拼多多、金融机构\n4.4 实时数仓架构 业务数据库 → FlinkCDC → Kafka → Doris/ClickHouse\n↓\nMySQL/PG BI 报表/数据分析\n代表：阿里巴巴、字节跳动、京东\n五、迁移路径建议 随着业务的增大，往往需要转型，这里提供几条迁移建议，对于每个场景提供最合适的方案，确保数据不丢失，成本稳定。\n从小到大的平滑演进：\nMySQL → 分库分表：使用 ShardingSphere，代码改动小 MySQL → TiDB：兼容 MySQL 协议，几乎不用改代码 MySQL → ClickHouse：需要重构查询逻辑，适合分析场景 本地分析 → DuckDB：零成本迁移，SQL 兼容 PostgreSQL 迁移成本排序（从低到高）： TiDB \u0026lt; DuckDB \u0026lt; 分库分表 \u0026lt; Doris \u0026lt; ClickHouse\n结语 数据库选型没有银弹，只有最适合。小项目别过度设计，大项目别为了省钱硬扛。参考同规模公司的技术选型，往往比自己研究更靠谱。\n记住这六条黄金法则：\n别过度设计、场景优先、考虑团队能力、成本意识、预留扩展空间、参考大厂实践。\n本文链接：\n未经作者授权禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/database-selection-guide/","tags":["数据库","技术选型","架构设计"],"title":"数据库选型指南：MySQL/PG/Doris/DuckDB/TiDB/ClickHouse/FlinkCDC 全方位对比"},{"categories":["技术分享"],"content":"一、JHipster 简介 1.1 什么是 JHipster？ JHipster 是一个开源的应用程序生成平台，用于快速生成、开发和部署现代 Web 应用程序和微服务架构。它结合了 Spring Boot（后端）+ Angular/React/Vue（前端）+ 各种开发工具，提供了一站式的解决方案。\n官方网站： https://www.jhipster.tech/\nGitHub 仓库： https://github.com/jhipster/generator-jhipster\n官方文档： https://www.jhipster.tech/getting-started/\nJDL Studio： https://jdl.jhipster.tech/ （在线设计 JDL 文件）\n1.2 核心优势 优势 说明 ⚡ 快速生成 几分钟内生成完整的项目骨架 🛠️ 技术栈先进 Spring Boot + Angular/React/Vue + TypeScript 🔒 安全性 集成 Spring Security，支持 JWT、OAuth2 📊 生产就绪 监控、日志、测试、CI/CD 配置齐全 🌐 微服务支持 完整支持微服务架构，包含服务发现、配置中心等 二、微服务架构核心组件 2.1 整体架构图 三、Eureka - 服务发现 3.1 作用 Eureka 是由 Netflix 开源的服务发现组件，主要用于微服务架构下的服务注册与发现。其核心功能包括：服务启动时将自身信息注册到 Eureka Server、服务客户端从 Server 获取可用服务列表，以及通过定期心跳检测机制，自动剔除超时无响应的服务实例，保障微服务间调用的可用性与稳定性。\n你可以把 Eureka 理解成：微服务架构里的公用电话本 + 在线状态监控\n服务注册 = 来登记电话 订单服务、用户服务、支付服务启动后，都跑到 Eureka 那里说：\n“我在这，我的地址是 xxx，端口是 xxx，快来记我。”\n这就叫服务注册。\n服务发现 = 查电话本找人 订单服务想调用用户服务，它不去硬写死 IP 地址，而是问 Eureka：\n“用户服务在哪？给我一个能用的地址。”\nEureka 把可用的地址返回，订单服务直接调用。这就叫服务发现。\n心跳 = 报平安 每个服务每隔几秒给 Eureka 发一次心跳：\n“我还活着！”\n如果很久没心跳，Eureka 就把它从电话本删掉，避免别人调用到挂掉的服务。\n3.2 JHipster 配置示例 Eureka Server 配置参考（application.yml）：\neureka: instance: prefer-ip-address: true hostname: localhost client: register-with-eureka: true fetch-registry: true service-url: defaultZone: http://admin:admin@localhost:8761/eureka/ server: wait-time-in-ms-when-sync-empty: 0 enable-self-preservation: false 3.3 常见问题及解决方案 问题 现象 解决方案 服务注册不上 控制台报错\u0026quot;Cannot execute request\u0026quot; 1. 检查 Eureka Server 地址配置2. 确保 Eureka Server 已启动3. 检查防火墙和网络配置 服务列表为空 Eureka 面板显示无服务 1. 关闭自我保护模式 2. 检查服务是否成功启动 服务显示 DOWN 服务注册但状态为 DOWN 1. 检查健康检查端点 /health 2. 调整心跳间隔 3.4 参考链接 Spring Cloud Netflix Eureka JHipster 微服务文档 四、Ribbon - 客户端负载均衡 4.1 作用 Ribbon 是一个客户端负载均衡器，用于在服务调用时分配负载。Eureka 管地址，Ribbon 管挑选。\nEureka 给你一堆服务地址：用户服务有 3 台：\n192.168.1.10 192.168.1.11 192.168.1.12 你每次调用，**该选哪一台？**Ribbon 就干这件事。 对比 Nginx 和 Ribbon：\n服务端负载均衡（Nginx）：请求先到 Nginx，Nginx 再转发 客户端负载均衡（Ribbon）：调用方自己直接选一台，不经过中间转发 4.2 负载均衡策略 Ribbon 最大的优点，就是请求不绕路、压力不集中、更适合微服务\n少一层转发，速度更快不用先过 Nginx 这类中间件，调用方直接从服务列表里选一台发起请求，延迟更低。 压力分散，不会单点瓶颈负载均衡逻辑在每个调用方自己本地做，而不是集中在一台 Nginx 上，集群越大越稳。 灵活度高，策略可以定制每个服务可以自己设置负载均衡策略（轮询、随机、权重、灰度等），不用统一改网关。 和服务发现配合天生顺滑从 Eureka/Nacos 拿到最新服务列表，本地直接计算路由，实时性强、可用性高。 扩容更方便服务加机器后，调用方自动感知并分摊流量，不用改配置、不用重启中间件。 策略 说明 配置类 RoundRobinRule 轮询，默认策略 RoundRobinRule RandomRule 随机选择 RandomRule RetryRule 重试机制 RetryRule WeightedResponseTimeRule 响应时间加权 WeightedResponseTimeRule 4.3 JHipster 配置示例 ribbon: ReadTimeout: 3000 ConnectTimeout: 3000 OkHttp: enabled: true user-service: ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule 4.4 常见问题 问题 解决方案 调用超时 增加 ReadTimeout 和 ConnectTimeout 负载不均衡 检查服务实例健康状态，调整策略 连接失败 启用重试：MaxAutoRetries: 2 # 解决 Ribbon 调用超时 + 负载均衡 + 重试 ribbon: # 1. 解决【连接超时】 ConnectTimeout: 2000 # 连接超时 2秒 # 2. 解决【读取超时】 ReadTimeout: 5000 # 数据读取超时 5秒 # 3. 解决【连接失败】→ 自动重试 MaxAutoRetries: 2 # 当前服务器重试2次 MaxAutoRetriesNextServer: 1 # 换一台服务器重试1次 OkToRetryOnAllOperations: true # 对所有请求类型都重试 # 4. 解决【负载不均衡】→ 使用轮询策略 NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule 五、Hystrix - 服务熔断 5.1 作用 Hystrix 是一个延迟和容错库，用于隔离对远程系统的访问，防止级联故障。\n相当于：电路里的保险丝 + 小区保安\n举一个实际例子： 订单服务 → 调用 库存服务结果库存服务卡死、超时、挂了 如果不拦着：订单服务一直等、一直调 → 订单服务也被拖死 → 整个系统雪崩\n**Hystrix 干的事：**发现依赖服务不行了 → 直接切断调用，不再请求就像电路短路，保险丝烧断，保护整个电路。\n降级呢就是 Hystrix 会走备用方案，熔断之后，不能直接报错给用户，一般有这三种：\n返回默认值 返回缓存数据 返回 “服务繁忙，请稍后再试 5.2 JHipster 配置示例 hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 10000 circuitBreaker: enabled: true requestVolumeThreshold: 20 sleepWindowInMilliseconds: 5000 errorThresholdPercentage: 50 fallback: enabled: true 5.3 代码示例 import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.web.client.RestTemplate; // 【调用用户服务的方法】 // 开启Hystrix熔断保护，失败时自动调用降级方法：getUserFallback @HystrixCommand(fallbackMethod = \u0026#34;getUserFallback\u0026#34;) public User getUserById(Long id) { // 调用远程的用户服务（通过Ribbon负载均衡） return restTemplate.getForObject( \u0026#34;http://user-service/users/\u0026#34; + id, // 远程服务地址 User.class // 返回类型 ); } // 【降级/兜底方法】 // 当用户服务超时、报错、挂了时，自动进入这个方法 // 参数：id = 原方法的参数；ex = 异常信息（可选） public User getUserFallback(Long id, Throwable ex) { // 打印错误日志，方便排查问题 log.error(\u0026#34;调用用户服务失败，服务可能已熔断或超时\u0026#34;, ex); // 构造一个默认用户返回，给前端友好提示 User user = new User(); user.setId(id); user.setUsername(\u0026#34;默认用户\u0026#34;); // 降级返回 return user; } 5.4 常见问题 问题 解决方案 熔断不生效 检查 @EnableCircuitBreaker 注解，启动类加 @EnableCircuitBreaker 降级方法不执行 确保 fallback 方法签名匹配， 降级方法参数、返回值必须和原方法一模一样 线程池耗尽 增加线程池大小：coreSize: 50 六、Liquibase - 数据库迁移 6.1 作用 Liquibase 是数据库的版本控制工具（像 Git 管理代码一样管理数据库）。它专门用来跟踪、记录、执行、回滚数据库变更（建表、加字段、改索引、插数据等），保证所有环境（开发 / 测试 / 生产）的数据库结构完全一致。\n痛点解决：开发、测试、生产库结构不一样，程序报错，多人协作改库，容易冲突 其实呢和 git 差不多，只不过在项目运行的时候会先检查一遍！\n6.2 代码示例 先加依赖（pom.xml） \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.liquibase\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;liquibase-core\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 主文件：changelog-master.xml 路径：resources/db/changelog/changelog-master.xml\n\u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;databaseChangeLog xmlns=\u0026#34;http://www.liquibase.org/xml/ns/dbchangelog\u0026#34; xmlns:xsi=\u0026#34;http://www.w3.org/2001/XMLSchema-instance\u0026#34; xsi:schemaLocation=\u0026#34;http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.9.xsd\u0026#34;\u0026gt; \u0026lt;!-- 引入第一个版本的变更 --\u0026gt; \u0026lt;include file=\u0026#34;classpath:db/changelog/v1/create_user_table.xml\u0026#34; /\u0026gt; \u0026lt;/databaseChangeLog\u0026gt; 建表变更文件：v1/create_user_table.xml \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34;?\u0026gt; \u0026lt;databaseChangeLog\u0026gt; \u0026lt;!-- 版本号：1 --\u0026gt; \u0026lt;changeSet id=\u0026#34;v1-create-user-table\u0026#34; author=\u0026#34;your-name\u0026#34;\u0026gt; \u0026lt;!-- 建表语句 --\u0026gt; \u0026lt;createTable tableName=\u0026#34;sys_user\u0026#34;\u0026gt; \u0026lt;column name=\u0026#34;id\u0026#34; type=\u0026#34;BIGINT\u0026#34; autoIncrement=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;constraints primaryKey=\u0026#34;true\u0026#34; nullable=\u0026#34;false\u0026#34;/\u0026gt; \u0026lt;/column\u0026gt; \u0026lt;column name=\u0026#34;username\u0026#34; type=\u0026#34;VARCHAR(50)\u0026#34;\u0026gt; \u0026lt;constraints nullable=\u0026#34;false\u0026#34; unique=\u0026#34;true\u0026#34;/\u0026gt; \u0026lt;/column\u0026gt; \u0026lt;column name=\u0026#34;age\u0026#34; type=\u0026#34;INT\u0026#34;/\u0026gt; \u0026lt;/createTable\u0026gt; \u0026lt;/changeSet\u0026gt; \u0026lt;/databaseChangeLog\u0026gt; 七、Feign - 声明式服务调用 7.1 作用 Feign 是一个声明式的 Web Service 客户端，让微服务之间的调用更简单。\n7.2 Feign 客户端定义 @FeignClient(name = \u0026#34;user-service\u0026#34;, fallback = UserServiceFallback.class) public interface UserServiceClient { @GetMapping(\u0026#34;/api/users/{id}\u0026#34;) UserDTO getUserById(@PathVariable(\u0026#34;id\u0026#34;) Long id); @PostMapping(\u0026#34;/api/users\u0026#34;) UserDTO createUser(@RequestBody UserDTO user); } 7.3 降级实现 @Component public class UserServiceFallback implements UserServiceClient { @Override public UserDTO getUserById(Long id) { log.error(\u0026#34;调用用户服务失败，id={}\u0026#34;, id); UserDTO user = new UserDTO(); user.setId(id); user.setUsername(\u0026#34;默认用户\u0026#34;); return user; } @Override public UserDTO createUser(UserDTO user) { return null; } } 八、JHipster 微服务架构完整配置 8.1 项目结构 myapp/ ├── jhipster-registry/ # 服务注册中心 (Eureka + Config) ├── gateway/ # API 网关 ├── service-a/ # 微服务 A ├── service-b/ # 微服务 B └── docker/ # Docker 配置 8.2 生成微服务项目 **#安装 JHipster** npm install -g generator-jhipster **#生成服务注册中心** jhipster jdl jhipster-registry.jdl **#生成网关** jhipster jdl gateway.jdl **#生成微服务** jhipster jdl service-a.jdl **#使用 Docker Compose 启动** cd docker-compose docker-compose -f app.yml up -d 九、社区资源和链接 9.1 官方资源 资源 链接 JHipster 官网 https://www.jhipster.tech/ 官方文档 https://www.jhipster.tech/getting-started/ GitHub 仓库 https://github.com/jhipster/generator-jhipster JDL Studio https://jdl.jhipster.tech/ 模块市场 https://www.jhipster.tech/modules/marketplace/ 9.2 社区支持 平台 链接 Stack Overflow https://stackoverflow.com/questions/tagged/jhipster Gitter 聊天 https://gitter.im/jhipster/generator-jhipster GitHub Issues https://github.com/jhipster/generator-jhipster/issues 9.3 相关技术文档 技术 文档链接 Spring Boot https://spring.io/projects/spring-boot Spring Cloud https://spring.io/projects/spring-cloud Eureka https://github.com/Netflix/eureka Hystrix https://github.com/Netflix/Hystrix Ribbon https://github.com/Netflix/ribbon Feign https://github.com/OpenFeign/feign Liquibase https://docs.liquibase.com/ 结语 JHipster 为微服务架构提供了一套完整的解决方案，从项目生成、服务发现、负载均衡、熔断降级到数据库迁移，都有成熟的组件支持。\n本文链接： https://hyuzz-nuc.github.io/posts/jhipster-microservices-complete-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/jhipster-microservices-complete-guide/","tags":["JHipster","微服务","Spring Boot"],"title":"JHipster + Spring Boot 微服务架构完整指南：Eureka、Ribbon、Hystrix、Liquibase、Feign 详解"},{"categories":["技术分享"],"content":"1 先说结论 要是你被老板问\u0026quot;接口响应时间多少\u0026quot;，别只说平均值！\n指标 含义 使用场景 P50 50% 的请求比这个快 看整体水平 P95 95% 的请求比这个快 看大多数用户体验 P99 99% 的请求比这个快 看极端情况 平均值 容易被极端值拉偏 别太当真 一句话：对外汇报用 P95，排查问题看 P99，日常监控 P50。\n2 P50 P95 到底是啥 2.1 别被名字忽悠了 P50、P95、P99 听起来高大上，其实就是百分位数。\n举个例子：\n假设你有 100 个请求，响应时间排序后是这样的：\n1ms, 2ms, 3ms, ..., 50ms, ..., 95ms, ..., 100ms, 500ms, 1000ms P50 = 第 50 个请求的响应时间 = 50ms（中位数） P95 = 第 95 个请求的响应时间 = 100ms P99 = 第 99 个请求的响应时间 = 500ms 通俗说：\nP50 就是\u0026quot;一半的请求比这个快\u0026quot; P95 就是\u0026quot;95% 的请求比这个快\u0026quot; P99 就是\u0026quot;99% 的请求比这个快\u0026quot; 2.2 为啥不用平均值 因为平均值会骗人！\n场景： 100 个请求里，99 个都是 10ms，1 个是 10 秒。\n平均值 = (99 × 10ms + 1 × 10000ms) / 100 = 101ms P50 = 10ms P95 = 10ms P99 = 10000ms 你看，平均值 101ms，好像还行？但实际上有 1 个请求卡了 10 秒！\nP99 直接暴露问题： 有 1% 的请求很慢，要查！\n记住：监控别只看平均值，P95/P99 才能反映真实体验。\n3 阿里系监控三件套 3.1 Prometheus：数据采集 Prometheus 就是个\u0026quot;数据采集器\u0026quot;，定期从你的服务里拉指标。\n核心概念：\n概念 含义 例子 Metric 指标名 http_request_duration_seconds Label 标签 method=\u0026quot;GET\u0026quot;, status=\u0026quot;200\u0026quot; Sample 采样值 0.123（123ms） Target 采集目标 localhost:8080 配置示例（prometheus.yml）：\nglobal: scrape_interval: 15s # 每 15 秒采集一次 scrape_configs: - job_name: \u0026#39;my-app\u0026#39; static_configs: - targets: [\u0026#39;localhost:8080\u0026#39;] metrics_path: \u0026#39;/actuator/prometheus\u0026#39; 注意： 阿里云的 ARMS Prometheus 配置略有不同，后面说。\n3.2 Grafana：数据可视化 Grafana 就是个\u0026quot;画图表的\u0026quot;，把 Prometheus 采集的数据画成面板。\nGrafana 大屏示例：\n上图：典型的 Grafana 监控面板，展示 P95/P99 响应时间曲线 + Top 5 慢接口\nGrafana 的 5 大优势：\n优势 说明 实际体验 📊 插件丰富 支持 40+ 数据源 Prometheus、MySQL、ES、阿里云都能接 🎨 面板美观 内置多种图表类型 折线图、柱状图、热力图、仪表盘随便选 🔔 告警灵活 支持多通知渠道 钉钉、企业微信、短信、邮件都能发 📱 移动端适配 响应式布局 手机上看监控也清晰 🔌 模板变量 动态筛选数据 一个面板看所有服务，不用重复画 Grafana 使用技巧：\n技巧 1：用模板变量动态筛选\n# 定义变量：$service # 查询时自动替换 rate(http_server_requests_seconds_bucket{job=\u0026#34;$service\u0026#34;}[5m]) 这样画一个面板，切换变量就能看不同服务，不用重复画！\n技巧 2：用 Stat 面板展示当前值\nP95 响应时间用 Stat 面板，直接显示当前值，配上颜色阈值：\n绿色：\u0026lt; 200ms 黄色：200-500ms 红色：\u0026gt; 500ms 一眼就能看到当前状态！\n技巧 3：用 Table 面板展示 Top N\ntopk(10, rate(http_server_requests_seconds_count[5m])) 展示请求量最大的 10 个接口，快速定位热点。\n技巧 4：用 Heatmap 看分布\nrate(http_server_requests_seconds_bucket[5m]) 热力图能看到响应时间的分布情况，比单纯看 P95 更直观。\n技巧 5：告警规则分组\n# 按服务分组告警 groups: - name: api_alerts rules: - alert: 订单服务响应慢 expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket{service=\u0026#34;order\u0026#34;}[5m])) \u0026gt; 0.5 - alert: 支付服务响应慢 expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket{service=\u0026#34;payment\u0026#34;}[5m])) \u0026gt; 0.3 不同服务设置不同阈值，核心服务告警更敏感！\n核心概念：\n概念 含义 Dashboard 仪表盘（一堆图表） Panel 单个图表 Query 查询语句（PromQL） Variable 变量（动态筛选） 常用 PromQL 查询：\n# P50 响应时间 histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m])) # P95 响应时间 histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) # P99 响应时间 histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m])) # 平均值 rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) 解释： histogram_quantile 是计算百分位数的函数，rate 是计算每秒速率。\n3.3 阿里云 ARMS Prometheus 阿里云的 Prometheus 叫 ARMS Prometheus，配置简单不少。\n接入步骤：\n登录阿里云控制台 → ARMS → Prometheus 监控 创建 Prometheus 实例 添加接入配置（选择 Kubernetes/ECS/其他） 复制配置到你的应用 阿里云特有功能：\n功能 说明 预设大盘 内置常用监控面板 智能告警 AI 识别异常 日志关联 直接跳转到 SLS 日志 调用链 集成分布式追踪 配置示例（阿里云）：\n# 阿里云 Prometheus 配置 arms: endpoint: \u0026#34;tracing-analysis-dc-xxx.aliyuncs.com\u0026#34; app_id: \u0026#34;your-app-id\u0026#34; 4 实战：从 0 搭建监控 4.1 Spring Boot 集成 Prometheus 第一步：加依赖\n\u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;org.springframework.boot\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;spring-boot-starter-actuator\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; \u0026lt;dependency\u0026gt; \u0026lt;groupId\u0026gt;io.micrometer\u0026lt;/groupId\u0026gt; \u0026lt;artifactId\u0026gt;micrometer-registry-prometheus\u0026lt;/artifactId\u0026gt; \u0026lt;/dependency\u0026gt; 第二步：配 endpoint\nmanagement: endpoints: web: exposure: include: health,info,prometheus metrics: export: prometheus: enabled: true 第三步：访问验证\ncurl http://localhost:8080/actuator/prometheus 能看到一堆指标就对了！\n4.2 Grafana 画 P95 面板 第一步：添加数据源\nGrafana → Configuration → Data sources Add data source → Prometheus URL 填 http://prometheus:9090 Save \u0026amp; test 第二步：创建 Dashboard\nCreate → Dashboard Add new panel 写 PromQL： histogram_quantile(0.95, rate(http_server_requests_seconds_bucket{job=\u0026#34;my-app\u0026#34;}[5m]) ) 第三步：配置面板\n配置项 值 Title 接口 P95 响应时间 Unit Seconds Legend {{method}} - {{uri}} Alert 超过 500ms 告警 第四步：保存\n搞定！现在能看到每个接口的 P95 响应时间了。\nGrafana 面板配置技巧：\n技巧 1：多面板对比\n第一行：P50/P95/P99 三个面板并排 第二行：请求量/QPS 第三行：错误率 一眼看清全貌！\n技巧 2：用 Annotations 标记事件\n# 部署事件标注 # 在面板上显示部署时间点，方便排查问题 技巧 3：设置刷新间隔\n开发环境：30s 生产环境：1m 大屏展示：5s 别设太短，会增加 Prometheus 负担！\n技巧 4：导出导入 Dashboard\n画好的面板可以导出 JSON，分享给团队：\n# 导出 Dashboard Settings → JSON Model → Copy to clipboard # 导入 Create → Import → 粘贴 JSON 阿里云 ARMS 内置的预设大盘也能导出复用！\n4.3 配置告警 Prometheus 告警规则：\n# alerting_rules.yml groups: - name: api_alerts rules: - alert: 接口响应慢 expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) \u0026gt; 0.5 for: 5m labels: severity: warning annotations: summary: \u0026#34;接口 {{ $labels.uri }} 响应慢\u0026#34; description: \u0026#34;P95 响应时间 {{ $value }}s，超过 500ms\u0026#34; 阿里云 ARMS 告警：\nARMS 控制台 → 告警管理 → 创建告警 选择指标 → http_server_requests_seconds 设置条件 → P95 \u0026gt; 500ms 通知方式 → 钉钉/短信/邮件 5 常见坑提前避雷 5.1 P95 比平均值还小？ 正常！ 说明有极端慢的请求拉高了平均值。\n排查： 看 P99 和最大值，找出慢请求。\n5.2 数据不准 可能原因：\n采集间隔太长 - 改成 15s 或更短 时间窗口太大 - rate()[5m] 改成 rate()[1m] Histogram 桶配置不当 - 调整桶范围 解决方案：\n# Spring Boot 配置 management: metrics: distribution: percentiles-histogram: http_server_requests: true slo: http_server_requests: 50ms,100ms,200ms,500ms,1s,5s 5.3 Grafana 面板加载慢 原因： 查询时间范围太大\n解决：\n默认时间范围改成 1h 用 rate()[5m] 而不是 rate()[1h] 开启查询缓存 5.4 阿里云 ARMS 数据延迟 正常现象： 阿里云数据有 1-2 分钟延迟\n解决： 告警规则 for 时间设长一点（5m 以上）\n6 生产环境配置建议 6.1 Prometheus 配置 global: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: \u0026#39;my-app\u0026#39; static_configs: - targets: [\u0026#39;app1:8080\u0026#39;, \u0026#39;app2:8080\u0026#39;] metrics_path: \u0026#39;/actuator/prometheus\u0026#39; # 重试配置 scrape_timeout: 10s honor_labels: true 6.2 Grafana 配置 # grafana.ini [analytics] reporting_enabled = false [users] allow_sign_up = false [auth.anonymous] enabled = false [alerting] enabled = true execute_alerts = true 6.3 告警阈值建议 指标 Warning Critical P50 响应时间 \u0026gt; 200ms \u0026gt; 500ms P95 响应时间 \u0026gt; 500ms \u0026gt; 1s P99 响应时间 \u0026gt; 1s \u0026gt; 5s 错误率 \u0026gt; 1% \u0026gt; 5% 7 最后总结 核心要点：\nP50/P95/P99 比平均值靠谱 - 不会被极端值拉偏 Prometheus 采集 + Grafana 展示 - 标准搭配 阿里云 ARMS 更简单 - 预设大盘 + 智能告警 告警阈值要合理 - 别太敏感也别太迟钝 一句话：监控别只看平均值，P95 才是用户体验的真实反映。\n本文链接： https://hyuzz-nuc.github.io/posts/monitoring-metrics-p50-p95-grafana-prometheus/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/monitoring-metrics-p50-p95-grafana-prometheus/","tags":["监控","Prometheus","Grafana","P95","运维"],"title":"监控指标 P50 P95 P99详解：阿里 Grafana Prometheus 实战指南"},{"categories":["技术分享"],"content":"1 先说结论 要是你现在正纠结选哪个，直接看这里：\n场景 推荐 一句话理由 电商交易、订单支付 RocketMQ 阿里双 11 验证过，事务消息靠谱 企业内部系统 RabbitMQ 易用性最好，文档齐全 日志收集、用户行为 Kafka 吞吐量百万级，大数据标配 对延迟极其敏感 RabbitMQ 微秒级，三者最低 海量消息堆积 Kafka/RocketMQ 都能扛住亿级 高吞吐、日志、流计算、大数据链路 → 优先 Kafka 复杂路由、灵活队列、传统企业 / 微服务通用 → 优先 RabbitMQ 分布式事务、电商订单、高可靠消息、大规模堆积 → 优先 RocketMQ 不过话说回来，2025 年之后很多公司都是混用的。比如用 RabbitMQ 处理内部业务消息，用 Kafka 做日志收集，两者不冲突。\n2 消息队列是个啥 其实就是个\u0026quot;中间人\u0026quot;。\n比如说你有个订单系统，用户下单后要扣库存、发短信、记日志、推物流。要是全写在一个接口里，慢不说，哪个环节挂了还可能导致整个下单失败。\n这时候消息队列就派上用场了：\n用户下单 → 发送消息到队列 → 返回成功（不等待后续处理） ↓ 库存服务、短信服务、日志服务、物流服务各自消费消息 好处很明显：解耦、异步、削峰填谷。\n3 三大选手介绍 3.1 RocketMQ 阿里 2012 年开源的，后来捐给 Apache，经过双11验证过的消息队列就是这位。Java 写的，国内团队用起来比较友好。\n架构图：\nflowchart TB subgraph Producer P1[Producer] end subgraph NameServer NS1[NameServer 1] NS2[NameServer 2] end subgraph Broker M1[Broker Master] S1[Broker Slave] end subgraph Consumer C1[Consumer] end P1 --\u0026gt; NS1 P1 --\u0026gt; NS2 NS1 --\u0026gt; M1 NS2 --\u0026gt; M1 M1 --\u0026gt; S1 NS1 --\u0026gt; C1 NS2 --\u0026gt; C1 M1 --\u0026gt; C1 RocketMQ 架构说明：Producer 和 Consumer 通过 NameServer 发现 Broker，Broker 主从同步保证数据不丢失。\n关键数据：\n吞吐量：10 万级 TPS 延迟：毫秒级 消息堆积：能扛亿级 主打功能是这三个：\n事务消息是杀手锏。比如订单创建和发消息要保证原子性，要么都成功，要么都失败。RocketMQ 用两阶段提交实现，其他两家没这个功能。 延迟消息支持 18 个级别（1s、5s、10s、30m 等）。订单 30 分钟未支付自动取消，就用这个。 顺序消息保证同一个订单的消息按顺序处理。比如订单创建→支付→发货，不能乱。 适合的场景： 电商交易（订单、支付、库存）、金融场景（转账、对账）、需要高可靠性的业务。\n要注意： 社区生态不如 Kafka 和 RabbitMQ，遇到坑可能得自己琢磨。\n3.2 RabbitMQ 2007 年就出来了，基于 Erlang 开发，稳定性没得说。\n架构图：\nflowchart LR P[Producer] --\u0026gt; EX{Exchange} EX --\u0026gt;|Routing Key| Q1[Queue 1] EX --\u0026gt;|Routing Key| Q2[Queue 2] EX --\u0026gt;|Broadcast| Q3[Queue 3] Q1 --\u0026gt; C1[Consumer 1] Q2 --\u0026gt; C2[Consumer 2] Q3 --\u0026gt; C3[Consumer 3] RabbitMQ 架构说明：Producer 发送消息到 Exchange，Exchange 根据 Routing Key 路由到 Queue，Consumer 从 Queue 消费。\n关键数据：\n吞吐量：万级 TPS 延迟：微秒级（三者最低） 路由能力：最强大 核心特点是路由灵活：\nRabbitMQ 有 4 种 Exchange（交换机）：Direct 精确匹配点对点，Fanout 广播发给所有订阅者，Topic 通配符匹配最灵活，Headers 根据消息头匹配（用得少）。\n自带 Web 控制台，队列、消息、连接一目了然，排查问题很方便。\n多语言客户端，几乎所有语言都有官方客户端，接入简单。\n适合的场景： 企业内部系统（OA、ERP、CRM）、复杂路由需求、对延迟敏感的场景、快速开发原型验证。\n要注意： 不适合高吞吐场景，消息堆积多了会影响性能。还有 Erlang 比较小众，出了问题能帮忙的人不多。\n3.3 Kafka LinkedIn 开发，后来捐给 Apache，现在几乎是日志收集的代名词。\n架构图：\nflowchart TB subgraph Producers P1[Producer 1] P2[Producer 2] end subgraph Kafka_Cluster B1[Broker 1] B2[Broker 2] B3[Broker 3] end subgraph Topic T1[Topic-A P0] T2[Topic-A P1] T3[Topic-B P0] end subgraph Consumers C1[Consumer Group] end P1 --\u0026gt; B1 P2 --\u0026gt; B2 B1 --\u0026gt; T1 B2 --\u0026gt; T2 B3 --\u0026gt; T3 T1 --\u0026gt; C1 T2 --\u0026gt; C1 T3 --\u0026gt; C1 Kafka 架构说明：Producer 发送消息到 Broker，消息按 Topic 和 Partition 存储，Consumer Group 订阅 Topic 消费消息。\n几个关键数据：\n吞吐量：百万级 TPS（碾压级别） 延迟：毫秒级 持久化：消息可永久保存（默认 7 天） 为什么这么快：\n顺序写磁盘 + 零拷贝技术，单机就能扛几十万 TPS。集群轻松百万级。\n消息可以永久保存，消费者可以随时回溯历史数据。\nKafka Streams 支持实时计算，不用额外搭 Flink/Spark。\n一条消息可以被多个消费者组消费，适合数据分发。\n适合的场景： 日志收集（ELK 架构）、用户行为追踪、大数据流处理、数据同步（CDC）。\n要注意： 配置相对复杂，运维门槛高一些。而且不支持复杂的路由功能。还有 2025 年之后 KRaft 模式逐渐取代 ZooKeeper，部署简单了不少，但还是比另外两家复杂。\n4 实际案例直接套用 4.1 电商订单流程（RocketMQ） 用户下单后要扣库存、发短信、记日志、推物流。\n生产者发送消息：\nMessage message = new Message(\u0026#34;order_topic\u0026#34;, \u0026#34;create\u0026#34;, orderId.getBytes()); message.putUserProperty(\u0026#34;userId\u0026#34;, userId); producer.send(message); 消费者处理订单：\nconsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -\u0026gt; { for (MessageExt msg : msgs) { String orderId = new String(msg.getBody()); inventoryService.deduct(orderId); // 扣库存 smsService.send(orderId); // 发短信 logService.record(orderId); // 记日志 logisticsService.push(orderId); // 推物流 } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; }); 这里关键是消费者要集群部署，自动负载均衡。失败要有重试机制，确保消息不丢。\n4.2 订单超时取消（RabbitMQ） 用户下单 30 分钟未支付，自动取消订单。这个用 RabbitMQ 的死信队列实现。\n先声明延迟队列： channel.queue_declare(queue=\u0026#39;order.delay\u0026#39;, arguments={ \u0026#39;x-message-ttl\u0026#39;: 1800000, # 30 分钟 \u0026#39;x-dead-letter-exchange\u0026#39;: \u0026#39;order.exchange\u0026#39;, \u0026#39;x-dead-letter-routing-key\u0026#39;: \u0026#39;order.cancel\u0026#39; }) 下单后发送消息： channel.basic_publish( exchange=\u0026#39;\u0026#39;, routing_key=\u0026#39;order.delay\u0026#39;, body=json.dumps({\u0026#39;order_id\u0026#39;: order_id}), properties=pika.BasicProperties(delivery_mode=2) # 持久化 ) 监听取消队列： def on_cancel(ch, method, properties, body): order = json.loads(body) order_service.cancel(order[\u0026#39;order_id\u0026#39;]) channel.basic_consume(queue=\u0026#39;order.cancel\u0026#39;, on_message_callback=on_cancel) 原理是消息在延迟队列里待 30 分钟，过期后自动转到死信队列，消费者监听死信队列执行取消操作。\n4.3 用户行为追踪（Kafka） 采集用户浏览、点击、购买行为，实时分析。\n生产者发送行为事件： Properties props = new Properties(); props.put(\u0026#34;bootstrap.servers\u0026#34;, \u0026#34;kafka1:9092,kafka2:9092\u0026#34;); props.put(\u0026#34;key.serializer\u0026#34;, \u0026#34;org.apache.kafka.common.serialization.StringSerializer\u0026#34;); props.put(\u0026#34;value.serializer\u0026#34;, \u0026#34;org.apache.kafka.common.serialization.StringSerializer\u0026#34;); KafkaProducer\u0026lt;String, String\u0026gt; producer = new KafkaProducer\u0026lt;\u0026gt;(props); UserEvent event = new UserEvent(userId, \u0026#34;click\u0026#34;, itemId, timestamp); ProducerRecord\u0026lt;String, String\u0026gt; record = new ProducerRecord\u0026lt;\u0026gt;(\u0026#34;user_behavior\u0026#34;, userId, event.toJson()); producer.send(record); 消费者实时分析： props.put(\u0026#34;group.id\u0026#34;, \u0026#34;behavior_analyzer\u0026#34;); KafkaConsumer\u0026lt;String, String\u0026gt; consumer = new KafkaConsumer\u0026lt;\u0026gt;(props); consumer.subscribe(Arrays.asList(\u0026#34;user_behavior\u0026#34;)); while (true) { ConsumerRecords\u0026lt;String, String\u0026gt; records = consumer.poll(Duration.ofMillis(100)); for (ConsumerRecord\u0026lt;String, String\u0026gt; record : records) { userProfileService.update(record.value()); // 更新用户画像 } } 这里按用户 ID 分区，保证同一用户的行为有序。消费者组自动负载均衡，支持回溯历史数据。\n5 常见坑与生产配置 5.1 消息重复消费 同一条消息被处理了多次。原因是网络抖动导致 ACK 丢失，或者消费者处理超时触发重试。\n解决方案是实现幂等性：\npublic class OrderConsumer implements MessageListenerConcurrently { @Autowired private RedisTemplate\u0026lt;String, String\u0026gt; redisTemplate; @Override public ConsumeConcurrentlyStatus consumeMessage(List\u0026lt;MessageExt\u0026gt; msgs, ConsumeConcurrentlyContext context) { for (MessageExt msg : msgs) { String msgId = msg.getMsgId(); // 用 Redis 记录已处理的消息 ID Boolean processed = redisTemplate.opsForValue().setIfAbsent( \u0026#34;msg:processed:\u0026#34; + msgId, \u0026#34;1\u0026#34;, 3, TimeUnit.DAYS ); if (Boolean.FALSE.equals(processed)) { continue; // 已处理过，跳过 } orderService.process(msg); // 处理业务 } return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } 记住：消费者必须实现幂等性，这是铁律！\n5.2 消息丢失 生产者发送成功，消费者没收到。原因是 Broker 宕机消息未持久化，或者同步刷盘配置不当。\n以 RocketMQ 为例：\n生产者要同步发送 + 重试：\nproducer.setRetryTimesWhenSendFailed(3); producer.setRetryTimesWhenSendAsyncFailed(3); SendResult result = producer.send(message); if (result.getSendStatus() != SendStatus.SEND_OK) { log.error(\u0026#34;消息发送失败：{}\u0026#34;, message); } Broker 配置同步刷盘 + 同步复制：\nflushDiskType=SYNC_FLUSH # 同步刷盘 brokerRole=SYNC_MASTER # 同步复制 flushCommitLogLeastPages=1 # 至少 1 页刷盘 生产环境一定要开启持久化和同步复制！\n5.3 消息堆积 消费速度跟不上生产速度，队列越积越多。\n先排查： # RocketMQ 查看消费进度 mqadmin consumerProgress -n nameserver:9876 -g consumer_group # Kafka 查看 lag kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group my_group 解决办法： 临时扩容就增加消费者实例。优化消费逻辑，异步处理、批量消费。降级处理，非核心消息丢弃或延迟处理。长期方案是拆分 Topic、增加分区。\n注意：消息堆积是告警信号，说明消费能力不足，要尽快处理！\n5.4 生产环境配置速查 RocketMQ：\n// 生产者 props.put(\u0026#34;producerGroup\u0026#34;, \u0026#34;order_producer_group\u0026#34;); props.put(\u0026#34;nameserverAddr\u0026#34;, \u0026#34;192.168.1.100:9876\u0026#34;); props.put(\u0026#34;retryTimesWhenSendFailed\u0026#34;, 3); // 消费者 props.put(\u0026#34;consumerGroup\u0026#34;, \u0026#34;order_consumer_group\u0026#34;); props.put(\u0026#34;consumeThreadMin\u0026#34;, 20); props.put(\u0026#34;consumeThreadMax\u0026#34;, 64); RabbitMQ：\nconnection = pika.BlockingConnection(pika.ConnectionParameters( host=\u0026#39;192.168.1.100\u0026#39;, credentials=pika.PlainCredentials(\u0026#39;admin\u0026#39;, \u0026#39;password\u0026#39;), heartbeat=600 )) channel.queue_declare(queue=\u0026#39;order.queue\u0026#39;, durable=True) Kafka：\n# 生产者 acks=all enable.idempotence=true max.in.flight.requests.per.connection=1 # 消费者 enable.auto.commit=false max.poll.records=100 5 最后总结 选型决策树：\n需要百万级吞吐？→ 是 → Kafka 需要事务消息？→ 是 → RocketMQ 需要复杂路由？→ 是 → RabbitMQ 需要微秒级延迟？→ 是 → RabbitMQ 需要顺序消息？→ 是 → RocketMQ 需要海量堆积？→ 是 → Kafka/RocketMQ 都不需要？→ 随便选 本文链接： https://hyuzz-nuc.github.io/posts/message-queue-selection-guide/\n未经作者禁止转载！\n","permalink":"https://hyuzz-nuc.github.io/posts/message-queue-selection-guide/","tags":["消息队列","RocketMQ","RabbitMQ","Kafka","技术选型"],"title":"消息队列选型指南：RocketMQ、RabbitMQ、Kafka 全方位对比"}]