Docker下RabbitMQ四部曲之三(细说java开发)

学向勤中得,萤窗万卷书。这篇文章主要讲述Docker下RabbitMQ四部曲之三:细说java开发相关的知识,希望能为你提供帮助。
欢迎访问我的GitHub

  • 本文是《Docker下RabbitMQ四部曲》系列的第三篇,实战两个基于SpringBoot的工程,分别用来生产和消费RabbitMQ消息;
前文链接
  • 前两章的内容是体验RabbitMQ服务,以及制作RabbitMQ镜像:
    1. 《Docker下RabbitMQ四部曲之一:极速体验(单机和集群)》;
    2. 《Docker下RabbitMQ四部曲之二:细说RabbitMQ镜像制作》;
本文内容简述
  • 今天的实战由以下几部分组成:
    1. 开发SpringBoot工程rabbitmqproducer,用来生产消息;
    2. 分析docker-compose.yml中对rabbitmqproducer镜像的使用;
    3. 开发SpringBoot工程rabbitmqconsumer,用来消费消息;
    4. 分析docker-compose.yml中对rabbitmqconsumer镜像的使用;
源码下载
  • 您可以在GitHub下载本文涉及到的文件和源码,地址和链接信息如下表所示:
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本章创建的两个工程分别在rabbitmqproducer、rabbitmqconsumer这两个文件夹下,如下图红框所示:
    Docker下RabbitMQ四部曲之三(细说java开发)

    文章图片
如何将SpringBoot的web工程制作成Docker镜像
  • 如果您想了解制作Docker镜像的更多细节,请参考以下三篇文章:
    1. 《maven构建docker镜像三部曲之一:准备环境》;
    2. 《maven构建docker镜像三部曲之二:编码和构建镜像》;
    3. 《maven构建docker镜像三部曲之三:推送到远程仓库(内网和阿里云)》;
生产消息的rabbitmqproducer
  • rabbitmqproducer是个普通的SpringBoot工程,pom.xml内容如下:
    < ?xml version="1.0" encoding="UTF-8"?> < project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> < modelVersion> 4.0.0< /modelVersion> < groupId> com.bolingcavalry< /groupId> < artifactId> rabbitmqproducer< /artifactId> < version> 0.0.1-SNAPSHOT< /version> < packaging> jar< /packaging> < name> rabbitmqproducer< /name> < description> Demo project for Spring Boot< /description> < parent> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-parent< /artifactId> < version> 2.0.1.RELEASE< /version> < relativePath/> < !-- lookup parent from repository --> < /parent> < properties> < project.build.sourceEncoding> UTF-8< /project.build.sourceEncoding> < project.reporting.outputEncoding> UTF-8< /project.reporting.outputEncoding> < java.version> 1.8< /java.version> < /properties> < dependencies> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-amqp< /artifactId> < /dependency> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-web< /artifactId> < /dependency> < dependency> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-starter-test< /artifactId> < scope> test< /scope> < /dependency> < /dependencies> < build> < plugins> < plugin> < groupId> org.springframework.boot< /groupId> < artifactId> spring-boot-maven-plugin< /artifactId> < /plugin> < plugin> < groupId> com.spotify< /groupId> < artifactId> docker-maven-plugin< /artifactId> < version> 0.4.12< /version> < !--docker镜像相关的配置信息--> < configuration> < !--镜像名,这里用工程名--> < imageName> bolingcavalry/$project.artifactId< /imageName> < !--TAG,这里用工程版本号--> < imageTags> < imageTag> $project.version< /imageTag> < /imageTags> < !--镜像的FROM,使用java官方镜像--> < baseImage> java:8u111-jdk< /baseImage> < !--该镜像的容器启动后,直接运行spring boot工程--> < entryPoint> ["java", "-jar", "/$project.build.finalName.jar"]< /entryPoint> < !--构建镜像的配置信息--> < resources> < resource> < targetPath> /< /targetPath> < directory> $project.build.directory< /directory> < include> $project.build.finalName.jar< /include> < /resource> < /resources> < /configuration> < /plugin> < /plugins> < /build> < /project>

  • 【Docker下RabbitMQ四部曲之三(细说java开发)】上述poml.xml中,有以下两点要注意:
    1. 依赖spring-boot-starter-amqp这个starter,用于RabbitMQ;
    2. 使用插件docker-maven-plugin将工程制作为docker镜像;
  • 接下来看看关键源码:
  • application.properties文件内容如下,是关于RabbitMQ的vhost和交换机名称的配置:
    mq.rabbit.virtualHost=/ mq.rabbit.exchange.name=bolingcavalryFanoutExchange

  • RabbitConfig类加了@Configuration注解,表示这是个配置类,代码如下:
    @Bean(name = "connectionFactory") public ConnectionFactory connectionFactory() CachingConnectionFactory connectionFactory = new CachingConnectionFactory(); connectionFactory.setUsername(username); connectionFactory.setPassword(password); connectionFactory.setVirtualHost(mqRabbitVirtualHost); connectionFactory.setPublisherConfirms(true); //该方法配置多个host,在当前连接host down掉的时候会自动去重连后面的host connectionFactory.setAddresses(address); return connectionFactory; @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //必须是prototype类型 public RabbitTemplate rabbitTemplate() RabbitTemplate template = new RabbitTemplate(connectionFactory()); return template; //Fanout交换器 @Bean FanoutExchange fanoutExchange() return new FanoutExchange(exchangeName);

    上述代码将RabbitTemplate对象和交换机对象的实例化分别通过两个加了@Bean注解的方法实现;
  • RabbitConfig类中有三个@Value注解配置的成员变量:address、username、password,这三个配置对应的值并没有写在application.properties中,因此SpringBoot会去系统环境变量中查找,找到后设置给它们;
  • SendController类,在收到http请求后,会向RabbitMQ发送一条消息,代码如下,调用rabbitTemplate.convertAndSend即可发送:
    @RequestMapping(value = "https://www.songbingjia.com/send/name/message", method = RequestMethod.GET) public @ResponseBody String send(@PathVariable("name") final String name, @PathVariable("message") final String message) SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String timeStr = simpleDateFormat.format(new Date()); String sendMessage = "hello, " + name + ", " + message+ ", " + timeStr; rabbitTemplate.convertAndSend(exchangeName,"", sendMessage); return "send message to [" +name + "] success (" + timeStr + ")";

  • 以上就是消息生产者的功能代码,在pom.xml所在目录下执行mvn clean package -U -DskipTests docker:build即可编译构建工程并生成docker镜像文件;
  • 接下来我们看看docker-compose.yml中是如何使用这个镜像的;
docker-compose.yml中对rabbitmqproducer镜像的使用
  • 在集群版的docker-compose.yml文件中,关于镜像的配置如下:
    producer: image: bolingcavalry/rabbitmqproducer:0.0.2-SNAPSHOT hostname: producer depends_on: - rabbit3 links: - rabbit1:rabbitmqhost ports: - "18080:8080" environment: - mq.rabbit.address=rabbitmqhost:5672 - mq.rabbit.username=admin - mq.rabbit.password=888888

  • 这些配置中有以下几点需要注意:
    1. links参数将rabbit1容器对应为别名rabbitmqhost,在environment参数中,mq.rabbit.address等于rabbitmqhost:5672,也就是rabbit1容器的IP地址;
    2. mq.rabbit.address、mq.rabbit.username、mq.rabbit.password都被设置到容器的环境变量中,因此RabbitConfig.java中的address、username、password就会被设置为mq.rabbit.address、mq.rabbit.username、mq.rabbit.password对应的值;
    3. 将当前电脑的18080端口映射到8080端口,假设当前电脑IP地址为192.168.119.155,那么要向容器发送http请求时,向这个地址发起http请求即可:http://192.168.119.155:18080/send/aaa/bbb
  • 至此,消息生产者的开发已经完成,接下来开始开发消息消费者吧;
消费消息的rabbitmqconsumer
  • rabbitmqconsumer是个普通的SpringBoot工程,pom.xml的内容和rabbitmqproducer的pom.xml是一样的(除了工程名不一样):
  • 接下来看看关键源码:
  • 配置类是RabbitConfig.java,和rabbitmqproducer的RabbitConfig.java基本一致,不同的是多了个成员变量queuename,用于表示队列名称,对应的值也是来自环境变量;
  • 作为RabbitMQ的消息消费者,rabbitmqconsumer要主动连接到RabbitMQ的队列上,以保持随时可以消费消息,对应的绑定代码如下:
    @Bean FanoutExchange fanoutExchange() return new FanoutExchange(exchangeName); //队列A @Bean public Queue fanoutQueue() return new Queue(queuename); //绑定对列到Fanout交换器 @Bean Binding bindingFanoutExchange(Queue fanoutQueue, FanoutExchange fanoutExchange) return BindingBuilder.bind(fanoutQueue).to(fanoutExchange);

  • 收到消息后,对消息的消费操作由FanoutReceiver类负责,在日志中将消息打印出来:
    @Component @RabbitListener(queues = "$mq.rabbit.queue.name") public class FanoutReceiver private static final Logger logger = LoggerFactory.getLogger(FanoutReceiver.class); @RabbitHandler public void process(String message) logger.info("receive message : " + message);

  • 以上就是消息消费者的功能代码,在pom.xml所在目录下执行mvn clean package -U -DskipTests docker:build即可编译构建工程并生成docker镜像文件;
特别注意的地方
  • 为了让rabbitmqconsumer的Docker镜像更通用,rabbitmq的账号、密码、队列名称等参数都没有写死在工程中,而是从系统环境变量中获取,对应的环境变量的名成分别是:mq.rabbit.username、mq.rabbit.password、mq.rabbit.queue.name,这些环境变量的值是谁负责设置的呢?是docker-compose.yml中的environment参数配置的,容器启动后docker就会在该容器中设置这些环境变量;
  • 在实际使用过程中,经常会出现rabbitmqconsumer进程在连接RabbitMQ时经常报错提示:ACCESS_REFUSED - Login was refused using authen
  • 目前的推测是:启动rabbitmqconsumer进程和环境变量设置并没有严格的先后顺序,导致了rabbitmqconsumer在使用mq.rabbit.username这些系统环境变量的时候,可能docker还没有设置好这些环境变量,因此导致连接RabbitMQ失败;
  • 针对这个问题,我在工程的启动类RabbitmqconsumerApplication的main方法中,先做了6秒的延时,再启动SpringApplication,目前经过多次测试问题已经不再出现,代码如下:
    @SpringBootApplication public class RabbitmqconsumerApplication public static void main(String[] args) try Thread.sleep(1000); catch(Exception e) e.printStackTrace(); SpringApplication.run(RabbitmqconsumerApplication.class, args);

  • 为什么rabbitmqproducer应用没有这个问题?
  • 查看容器的日志,重点是Attempting to connect to: [rabbitmqhost:5672]这段信息,会发现对于rabbitmqproducer应用,只有在第一次发消息的时候才会去连接RabbitMQ,这时候环境变量已经设置好了,所以连接不会有问题;
  • 但是rabbitmqconsumer应用由于是消费者,应用一启动就要主动去连接队列绑定交换机,所以一旦此时环境变量没有设置好,就会导致拿不到正确的参数信息;
  • 如果您不想使用延时这种笨办法,可以在application.properties中将mq.rabbit.username、mq.rabbit.password、mq.rabbit.queue.name这几个参数配置好,再去构建镜像,此时记得在docker-compose.yml中去掉对应的环境变量配置;
docker-compose.yml中对rabbitmqconsumer镜像的使用
  • 在集群版的docker-compose.yml文件中,关于镜像的配置如下:
    consumer1: image: bolingcavalry/rabbitmqconsumer:0.0.5-SNAPSHOT hostname: consumer1 depends_on: - producer links: - rabbit2:rabbitmqhost environment: - mq.rabbit.address=rabbitmqhost:5672 - mq.rabbit.username=admin - mq.rabbit.password=888888 - mq.rabbit.queue.name=consumer1.queue consumer2: image: bolingcavalry/rabbitmqconsumer:0.0.5-SNAPSHOT hostname: consumer2 depends_on: - consumer1 links: - rabbit3:rabbitmqhost environment: - mq.rabbit.address=rabbitmqhost:5672 - mq.rabbit.username=admin - mq.rabbit.password=888888 - mq.rabbit.queue.name=consumer2.queue

  • 这些配置中有以下几点需要注意:
    1. 有两个消费者镜像consumer1、consumer2;
    2. 由于交换器是fanout类型,生成者每生产一条消息,由consumer1、consumer2分别消费一次;
    3. consumer1、consumer2各自连接一个队列,这一点在第一章的截图中有过体现,如下图:
      Docker下RabbitMQ四部曲之三(细说java开发)

      文章图片
  • 至此,消息生产和消费对应的java代码就全部实战完毕了,希望能帮助您快速的开发RabbitMQ相关的java应用,下一章我们将实战RabbitMQ的高可用,通过docker stop命令模拟生产环境中的宕机,看看部分机器的故障是否影响正常的RabbitMQ服务;
欢迎关注51CTO博客:程序员欣宸

    推荐阅读