资讯动态

SpringBoot整合RabbitMQ:版本兼容与Docker部署的完整避坑指南

发布时间:2026/10/9 10:42:19 来源:尧图企业网站定制
1. 先搞清楚为什么SpringBoot项目最终都会走向消息队列我最初接触RabbitMQ的时候其实是带着抵触心理的——项目里明明用HTTP接口调得好好的为什么非要在中间塞一个消息中间件直到有一次做订单系统改造用户下单后要同步调库存服务、积分服务、短信服务一个接口的响应时间从50ms飙到1.2秒高峰期数据库连接池直接被拖垮。那时候我才意识到同步调用在系统规模上去之后一定会成为瓶颈。RabbitMQ解决的核心问题简单说就四个异步、解耦、削峰、广播。拿电商下单来说用户点完提交订单按钮订单服务只需要把订单消息丢到交换机Exchange然后立刻返回下单成功。至于库存扣减、积分赠送、短信通知这些消费者各自去队列里拉消息处理谁快谁慢互不影响。用户感知到的下单耗时几乎没变化但后端服务的压力被均匀地分散到了不同的队列消费者上。SpringBoot整合RabbitMQ之所以成为热门话题是因为SpringBoot的自动配置机制把RabbitMQ的绝大部分重复劳动都封装好了。传统做法是写一个ConnectionFactory、写一个RabbitTemplate、再写一堆Listener容器现在只需要在配置文件里写好连接信息注入一个RabbitTemplate就能发消息加一个RabbitListener注解就能收消息。这也是为什么网上搜索springboot rabbitmq的教程特别多——这确实是现代化Java后端项目里最常用的一套消息方案。不过网上90%的教程都只停留在本地发送一条Hello World的水平。真正到了测试和部署上线阶段会遇到非常多文档里不会写的问题Erlang版本和RabbitMQ版本到底怎么匹配、springboot版本太高导致的兼容性报错、本地能跑但上线后消费者连不上、Docker部署时端口和持久化怎么配置。这篇博文我就把我从开发环境到生产环境完整踩过一遍的流程写出来每个环节都给出可复现的步骤和排错思路。适合阅读这篇博文的读者我直接说清楚如果你刚接触RabbitMQ可以把这篇文章当成从零到上线的完整路线图如果你已经在项目里用上了RabbitMQ但总在测试部署阶段翻车那重点看版本兼容、报错排查和Docker部署这三块这些都是网上教程里很少系统讲过的内容。2. 环境准备Erlang版本陷阱与RabbitMQ的两种装法2.1 Erlang和RabbitMQ的版本匹配这是第一个大坑很多人在rabbitmq启动失败上卡住八成都是Erlang版本不匹配导致的。RabbitMQ是Erlang语言写的它对Erlang的版本有严格的兼容范围装太新或太旧的Erlang启动时要么报错要么直接闪退。我自己第一次装的时候就踩过这个坑下载了当时最新的Erlang 26然后配了一个老版本的RabbitMQ 3.8.x启动时一直报distribution port之类的错误折腾了一晚上才明白是版本兼容问题。下面这个表格是我实测稳定可用的版本组合直接照抄就行RabbitMQ版本对应的Erlang版本范围推荐组合3.13.x较新26.0及以上Erlang 26.2 RabbitMQ 3.13.73.12.x25.3 ~ 26.2Erlang 25.3 RabbitMQ 3.12.143.11.x23.2 ~ 25.3Erlang 24.3 RabbitMQ 3.11.243.8.x老项目在用21.3 ~ 23.2Erlang 23.2 RabbitMQ 3.8.34判断版本匹配最权威的方式是去RabbitMQ官网的Installing on Windows页面那里面每个RabbitMQ版本都标了对应的Erlang版本范围。不要自己瞎猜。Windows环境下安装顺序是先装Erlang再装RabbitMQ。安装完之后RabbitMQ默认会注册成Windows服务并且有一个自带的Web管理界面插件但需要手动启用。在命令行里进入RabbitMQ的sbin目录比如C:\Program Files\RabbitMQ Server\rabbitmq_server-3.13.7\sbin执行# 启用Web管理插件 rabbitmq-plugins enable rabbitmq_management # 重启RabbitMQ服务让配置生效 rabbitmqctl stop net stop RabbitMQ net start RabbitMQ然后浏览器访问http://localhost:15672用默认账号guest/guest登录就能看到管理界面。这里有个细节guest账号默认只能在localhost本机登录如果你远程访问管理界面会提示登录被拒绝。解决方法是新建一个管理员账号后面代码里也统一用新账号不要用guest裸奔。2.2 更干净的做法用Docker装RabbitMQ如果你的开发机或者服务器上已经有了Docker我强烈推荐用Docker方式装RabbitMQ原因有三个第一Docker镜像里已经搭配好了合适的Erlang版本不存在手动装Erlang的版本匹配问题第二想换版本就是改一个tag再docker pull的事第三生产环境用Docker部署SpringBoot项目时RabbitMQ也用Docker跑网络配置比较好统一管理。一条命令就能起一个带Web管理界面的RabbitMQdocker run -d \ --name rabbitmq \ -p 5672:5672 \ -p 15672:15672 \ -e RABBITMQ_DEFAULT_USERadmin \ -e RABBITMQ_DEFAULT_PASSadmin123 \ rabbitmq:3.13-management注意选带management标签的镜像这个镜像默认已经启用了Web管理插件。端口说明5672是AMQP协议端口给Java程序连接用的15672是Web管理界面端口给人看的。这两个端口理解清楚后面部署排查才不慌。启动后用docker ps看容器状态再访问http://服务器IP:15672用刚才的环境变量里设置的admin/admin123登录。用Docker方式启动还有一个额外福利自动设置了默认账号不用像Windows安装那样去改guest权限。2.3 一个容易被忽略的点Spring Boot版本与Spring AMQP的兼容搜索引擎里springboot版本太高这个热词背后对应的是一批真实踩坑经历。SpringBoot 2.x时代用的是javax命名空间SpringBoot 3.x全面切换成了jakarta命名空间同时Spring Boot 3.x要求JDK 17以上。如果你项目是SpringBoot 3.x但引用了老教程里的RabbitMQ依赖写法或者配置类里还在用javax.annotation这些旧包编译期就会直接报错。这里给出一个稳妥的依赖配置SpringBoot 3.x和2.x都适用dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-amqp/artifactId /dependencySpringBoot的starter会根据你当前SpringBoot版本自动适配对应版本的Spring AMQP一般不需要手动指定spring-rabbit版本。但如果你在pom.xml里手动覆盖过amqp-client版本那就得注意和RabbitMQ服务端的版本兼容。amqp-client的低版本连接高版本RabbitMQServer一般没问题反过来很可能报协议错误。3. 项目里的心脏三种交换机模式与核心配置3.1 为什么是交换机-队列-绑定而不是直接发队列RabbitMQ和普通消息队列最大的区别就是生产者不直接把消息丢进队列而是先发给交换机Exchange再由交换机根据路由规则把消息投递到一个或多个队列。这个设计初看绕了一圈实际是为了彻底解耦生产者只关心消息类型不关心谁去消费。新增一个下游消费者生产者代码一行都不用改。我见过不少人上来就写rabbitTemplate.convertAndSend(queueName, message)——这个写法不是不能用但它是走了一个默认的空字符串交换机路由key直接写队列名。这样用相当于把RabbitMQ当成一个简单的点对点队列交换机的价值完全没体现出来。对于简单场景确实够用但一旦业务复杂起来比如同一条订单消息既要给库存系统、又要给财务系统、还要给数据分析系统你就必须理解交换机的作用。交换机有三种常用类型它们决定了消息的投递策略类型投递逻辑典型使用场景Direct路由key完全匹配才投递到绑定的队列订单消息发给订单队列库存消息发给库存队列一个交换机管所有精确路由Fanout不判断路由key广播给所有绑定的队列一条用户登录事件让审计、日志、风控三个系统都收到Topic路由key支持通配符*匹配一个词和#匹配零个或多个词一条物流消息logistics.zhongtong.created可以同时命中logistics.#和logistics.*.created等模糊匹配规则开发和生产环境最常用的是Direct和TopicFanout适合做广播通知。本篇教程我以Direct模式为主因为逻辑最清晰容易理解改造成Topic也不复杂只要把绑定关系从精确路由改成通配符即可。3.2 配置文件与队列定义在SpringBoot项目的application.yml里把连接信息配好。下面这个配置我把虚拟主机virtual-host也显式写出来了RabbitMQ的虚拟主机相当于数据库里的schema不同业务的队列可以靠虚拟主机隔离。默认的/虚拟主机是给本地测试用的生产环境建议建独立的虚拟主机。spring: rabbitmq: host: 127.0.0.1 port: 5672 username: admin password: admin123 virtual-host: /mall publisher-confirm-type: correlated publisher-returns: true listener: simple: acknowledge-mode: manual prefetch: 10这里几个参数提前说明一下很多人不知道它们为什么存在publisher-confirm-type: correlated开启消息发送确认模式生产者发消息后可以收到Broker的ack回执这是确保消息没有丢的第一步。publisher-returns: true开启消息无法被路由时的回调。就是你发了消息但交换机找不到匹配的队列这个消息会退回给生产者。acknowledge-mode: manual消费者手动确认。改成手动确认意味着消费者处理完后要主动告诉Broker我处理完了否则Broker会重新投递。这是确保消息不丢的第二步但代码量会多一些。然后定义队列、交换机以及绑定关系。我习惯用一个配置类统一管理Spring AMQP提供了DirectExchange、Queue、BindingBuilder这些现成的APIimport org.springframework.amqp.core.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; Configuration public class RabbitMQConfig { public static final String EXCHANGE mall.order.exchange; public static final String QUEUE_ORDER mall.order.queue; public static final String ROUTING_KEY_ORDER order.create; Bean public DirectExchange orderExchange() { return new DirectExchange(EXCHANGE, true, false); } Bean public Queue orderQueue() { return new Queue(QUEUE_ORDER, true); } Bean public Binding orderBinding() { return BindingBuilder.bind(orderQueue()) .to(orderExchange()) .with(ROUTING_KEY_ORDER); } }new DirectExchange(name, durable, autoDelete)里第二个参数durabletrue表示交换机持久化第三个autoDeletefalse表示所有队列解绑后交换机不自动删除new Queue(name, durable)里的true同样表示队列持久化。这两个持久化开关非常关键如果设成falseRabbitMQ重启之后交换机和队列全都消失生产环境会出大事故。3.3 生产端与消费端的完整代码生产端只需要注入RabbitTemplate一行代码发消息。配合前面配置里的publisher-confirm-type还能拿到发送确认结果import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.stereotype.Component; import java.util.UUID; Component public class OrderMessageSender { private final RabbitTemplate rabbitTemplate; public OrderMessageSender(RabbitTemplate rabbitTemplate) { this.rabbitTemplate rabbitTemplate; } public void sendOrderMessage(Object messageBody) { CorrelationData correlationData new CorrelationData(UUID.randomUUID().toString()); rabbitTemplate.convertAndSend( RabbitMQConfig.EXCHANGE, RabbitMQConfig.ROUTING_KEY_ORDER, messageBody, correlationData ); // 获取确认结果 System.out.println(消息已发送确认ID: correlationData.getId()); } }convertAndSend方法内部会自动把Java对象转换成字节数组发送默认用JDK序列化。生产环境建议改成Jackson序列化否则消息体是二进制乱码在Web管理界面上根本没法直接查看内容排查问题非常痛苦。改法是在配置类里定义一个Jackson2JsonMessageConverter注入到容器中Spring会自动用它替代默认的SimpleMessageConverter。消费端更简单一个RabbitListener注解加一个方法参数就搞定了import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.core.Message; import com.rabbitmq.client.Channel; import org.springframework.stereotype.Component; Component public class OrderMessageConsumer { RabbitListener(queues RabbitMQConfig.QUEUE_ORDER) public void handleOrderMessage(String messageBody, Message message, Channel channel) throws Exception { try { System.out.println(收到订单消息: messageBody); // 业务处理代码在这里 // 处理成功手动ack channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); } catch (Exception e) { // 处理失败可以requeue重新回到队列或者不requeue进入死信/丢弃 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); } } }手动确认模式写起来确实比自动确认多一点代码但换来的是绝对不会因为消费者宕机而丢消息。自动确认模式下Broker把消息发给消费者就直接删除了消费者万一在业务代码执行到一半崩了那条消息就彻底没了。手动确认是你告诉我处理好了我才删这是金融类、电商类项目必须掌握的知识点。4. 本地测试全流程从Web管理界面到并发压测的实战验证4.1 先在Web管理界面里做一次手动全流程无论你代码写得多么熟练我都建议先打开Web管理界面把交换机、队列、绑定关系这三层结构亲手点一遍因为你只有看过RabbitMQ的结构长什么样才能理解程序里那些配置到底干了什么。登录管理界面后依次做以下操作点Exchanges选项卡能看到当前虚拟主机下所有交换机确认我们自己定义的mall.order.exchange已经存在Type列显示为directFeatures列有D标记表示durable持久化。点Queues选项卡确认mall.order.queue存在且有1个消费者Ready状态为0。点进队列名称拉到最底部在Bindings部分能看到它绑定的交换机信息和路由key。在交换机详情页的Publish message区域填入路由keyorder.create在Payload框输入一段JSON点击Publish message。如果绑定关系正确切到队列详情页会看到消息数从0变成了1点Get messages选Ack mode为Automatic ack就能把刚才发的测试消息拉出来看内容。这个操作的意义是先排除掉代码问题验证RabbitMQ服务端的基本功能。4.2 基于Spring Boot Test的单元集成测试在项目里我习惯用Spring Boot Test配合Spring AMQP的测试组件写一个快速模拟生产与消费的集成测试类。这样可以不打完整包、不启动整个应用就能验证消息路由是否正确。引入测试依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency然后写一个测试类直接注入RabbitTemplate发送消息后等待一小段时间再断言消费端日志是否输出import org.junit.jupiter.api.Test; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; SpringBootTest public class RabbitMQFlowTest { Autowired private RabbitTemplate rabbitTemplate; Test public void testSendAndReceive() throws InterruptedException { String message {\orderId\:\10001\,\amount\:99.5}; rabbitTemplate.convertAndSend( RabbitMQConfig.EXCHANGE, RabbitMQConfig.ROUTING_KEY_ORDER, message ); // 等待消费者处理完成 Thread.sleep(2000); // 这里可以配合日志断言或者查询数据库判断业务是否执行 System.out.println( 消息发送完成请检查消费者日志 ); } }这种测试方法的局限性在于它依赖一个真实可连接的RabbitMQ没有自动装配的隔离性。如果你追求更高的测试质量可以引入TestContainers用Docker起一个临时RabbitMQ容器测试用完即销毁。这对CI/CD流水线来说更干净但考虑到主题长度这里先不展开有兴趣的可以自行搜索Testcontainers RabbitMQ。4.3 并发测试模拟生产峰值场景单条消息没问题不等于高并发下没问题。RabbitMQ的消费者可以通过配置prefetch控制每次从Broker拉取多少条消息到本地缓存。我遇到过一种情况消费者处理一条消息要300msprefetch默认是250一下拉200多条消息到本地全卡在内存里直接导致消费者进程OOM。RabbitMQ本质上不是推送消息而是拉取消息prefetch就是设置消费者自己每次拉多少条。模拟并发场景时我一般用一个简单的循环发送工具类直接发2000条消息到交换机然后观察消费者的处理速度Test public void testBulkSend() { for (int i 0; i 2000; i) { String message {\seq\: i ,\content\:\bulk test\}; rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE, RabbitMQConfig.ROUTING_KEY_ORDER, message); } System.out.println(2000条消息已发送); }然后在管理界面Queues页面观察Ready和Unacked的数量变化Ready表示队列中还堆积多少条Unacked表示正在被消费者处理中的数量。如果Ready持续上涨说明消费者处理能力跟不上如果Unacked数值很大说明prefetch拉取量太大或者消费者被阻塞。本地测出结论后把acknowledge-mode、prefetch、消费者线程数调到一个平衡点再刷新到配置上。这里给一组参考起步值单消费者prefetch10处理一条消息在200ms左右时消费者线程池最小2最大8能平滑处理每秒40~80条消息的瞬时洪峰。如果单条消息处理耗时超过1秒建议用另一个消费者专门处理这部分慢业务不要混在同一个队列里互相拖累。5. 部署上线的七个关键点从clean channel shutdown到Docker编排5.1 clean channel shutdown报错的完整排查链路热词列表里有一条rabbitmq cause: clean channel shutdown; protocol method: #method(reply-code完整报错一般是ShutdownSignalException: Clean channel shutdown; protocol method: #methodchannel.close(reply-code406, reply-textPRECONDITION_FAILED ...)。我处理过不下五次这个报错每次都是同一个根因交换机或队列的元数据在代码中和RabbitMQ服务端里面已有的不一致。最典型的场景是你开发的代码里把交换机声明为durabletrue、autoDeletefalse但某一次测试时用Docker容器跑过一遍容器重启后数据清了或者你之前用管理界面手动创建了一个非持久化队列现在改成代码里声明持久化队列。RabbitMQ检查到已存在的同名队列和你新声明的不一致就直接抛出406 PRECONDITION_FAILED直接把channel关闭。排查步骤按顺序来打开Web管理界面找到报错的交换机或队列查看它的Features列是否为D标记。如果队列不存在那就是声明顺序或名称写错了。如果确实存在且Features不一致删掉那个旧的交换机/队列前提是确认没有重要消息堆积。删除后重启应用让代码里的声明重新生效。另外一个次高频的报错是reply-code403, reply-textACCESS_REFUSED这通常就是账号没有操作该虚拟主机的权限。检查你在配置类里有没有设置正确的虚拟主机名以及该虚拟主机里是否给当前用户配了权限。生产上我遇到过一次是运维把虚拟主机的读写权限设成了^$不给任何权限排查了半天才定位到。5.2 配置和代码分离环境不同配置不变这个理念要落地本地测试用的RabbitMQ连接信息和生产环境完全不同。终极目标应该是同一套jar包能根据运行环境自动加载不同的配置不需要改代码重新打包。SpringBoot的application-{profile}.yml机制正好解决这个问题。我推荐的文件组织方式src/main/resources/ ├── application.yml # 公共配置 ├── application-dev.yml # 开发环境 ├── application-test.yml # 测试环境 └── application-prod.yml # 生产环境每个环境文件里面单独的spring.rabbitmq配置段# application-dev.yml spring: rabbitmq: host: 192.168.1.100 port: 5672 username: dev_user password: dev_pass virtual-host: /mall_dev # application-prod.yml spring: rabbitmq: host: 10.0.1.5 port: 5672 username: prod_user password: ${RABBITMQ_PASSWORD} virtual-host: /mall_prod生产环境的密码不要明文写在配置文件里用环境变量${RABBITMQ_PASSWORD}引用部署时在服务器环境变量里注入。启动时指定使用哪个profilejava -jar mall-order-service.jar --spring.profiles.activeprod这种做法的好处是同一个构建产物可以在测试环境验证充分后再上生产避免了测试没问题、上线就炸的经典尴尬。5.3 Docker Compose编排SpringBoot和RabbitMQ一体部署生产环境我推荐用Docker Compose把应用和RabbitMQ编排在一起。下面这个是实测可用的docker-compose.ymlversion: 3.8 services: rabbitmq: image: rabbitmq:3.13-management container_name: rabbitmq restart: always ports: - 5672:5672 - 15672:15672 environment: TZ: Asia/Shanghai RABBITMQ_DEFAULT_USER: prod_user RABBITMQ_DEFAULT_PASS: prod_pass_strong volumes: - rabbitmq_data:/var/lib/rabbitmq - rabbitmq_log:/var/log/rabbitmq mall-order-service: image: mall/order-service:1.0.0 container_name: mall-order-service restart: always depends_on: - rabbitmq ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod RABBITMQ_HOST: rabbitmq RABBITMQ_PORT: 5672 RABBITMQ_USERNAME: prod_user RABBITMQ_PASSWORD: prod_pass_strong volumes: - /etc/localtime:/etc/localtime:ro volumes: rabbitmq_data: rabbitmq_log:这里几个设计很关键照着写就行depends_on只保证容器启动顺序不保证RabbitMQ完全就绪所以SpringBoot应用启动时要做连接重试机制。Spring AMQP的spring.rabbitmq.addresses连接失败时应用不会立刻崩掉但首条消息发送会报错。我习惯在发送端加一个RetryTemplate或者简单的Retryable重试防止应用刚启动、RabbitMQ还没就绪的那几秒钟偶发失败。容器的volumes持久化了RabbitMQ的数据和日志目录否则容器一旦删除重建所有队列、交换机、消息全部丢失这是生产环境绝对不可接受的事情。配置文件里RABBITMQ_HOST对应的是application-prod.yml里的${RABBITMQ_HOST}引用。Docker内部网络直接用服务名rabbitmq访问不需要去记IP地址。6. 给不同规模项目的部署扩展建议如果你是小团队的中小型项目上面这套方案已经能用了。但如果项目规模再往上走一点有几个点我会建议你再深入一步。第一死信队列DLX几乎是必须要配置的。消费者处理失败的消息如果一直requeuetrue会无限循环重新投递每次失败都重新执行一遍轻则日志刷屏重则把正常消息堵住。我见过一个真实案例某个不稳定的第三方接口导致一条消息连续重试了上千次整个队列积压到几百万条最终影响面波及所有正常订单。正确做法是消费者在重试一定次数后把消息投递到专门的死信交换机再由独立的消费者去处理失败的消息比如标记人工审核、或者延迟重试。Spring AMQP的RabbitListener配合Retryable可以做到重试3次之后进死信这部分代码量不大但逻辑非常重要。第二消息幂等性设计。RabbitMQ的投递语义是至少一次at least once这意味同一条消息可能被消费者重复处理。我处理过的一个坑是库存系统重复扣减了两次库存就是因为消费端没有做幂等判断。解法是在消费前先查询Redis里的消息消费状态或者依赖数据库唯一键约束// 伪代码消费前先判断是否处理过 if (redisTemplate.hasKey(consumed: messageId)) { // 已经处理过直接ack channel.basicAck(deliveryTag, false); return; } // 业务处理 // ... // 处理成功后标记 redisTemplate.opsForValue().set(consumed: messageId, 1, 24, TimeUnit.HOURS);第三监控告警。RabbitMQ管理界面的Web API可以程序化拿到队列积压量定期轮询/api/queues/{vhost}/{queueName}队列messages字段大于某个阈值就触发钉钉或企业微信告警。这个监控脚本本身很简单我最初就是写了个定时任务每两分钟拉一次接口做判断后续才接入了Prometheus。如果你想用现成的RabbitMQ官方也有Prometheus插件rabbitmq_prometheus在3.8及以后的版本已内置直接配个prometheus.yml就能采集指标。7. 我踩过的最后几个坑希望你绕开把这篇博文涉及的所有流程走完一遍之后你会发现RabbitMQ本身的搭建和SpringBoot整合并不算特别难真正烦人的全在环境兼容、持久化配置、消息确认和排错链路这些细节上。最后把我印象深刻的小经验集中列一列别在Windows生产环境跑RabbitMQ。Windows上RabbitMQ的守护进程偶尔会因为文件句柄问题挂掉还是容器化部署在Linux服务器上最稳。RabbitMQ的默认心跳是60秒。如果客户端网络有间歇性抖动建议在配置文件里显式设置spring.rabbitmq.requested-heartbeat: 30心跳太长会在断网时拖很长时间才触发重连。监听消费者方法的参数类型定义要前后一致。如果你发送端用的是Map类型消费端方法参数写了个自定义的DTO即使字段一样也可能因为Jackson呼吸器解析失败而报错。消息结构是团队内部约定一定要形成文档固定下来。虚拟主机尽量在项目里内聚声明不要靠人肉去管理界面上创建。每次新环境上线我都会写一个启动时自动声明交换机、队列和绑定的初始化组件上面的RabbitMQConfig其实顺带干了这个事上线过程才不会漏配置。就拿我们现在这个项目来说现在上线新环境我从构建jar包到启动RabbitMQ容器再到服务全部拉起来只需要跑两三条命令十分钟全部搞定基本不会再出那种本地没问题一上线就白屏的局面。这套流程如果你按着走一遍相信也能做到这个状态。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑