之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的实践经验其实可以复刻到其它项目上,在行业内可能称为脚手架,因此决定将此java基础脚手架的搭建总结下来,分享给大家使用。
注意:由于框架不同版本改造会有些使用的不同,因此本次系列中主要使用基本框架是 spring-boo-2.3.12.RELEASE和spring-cloud.-Hoxton.SR12,所有代码都在commonFramework项目上:https://github.com/forever1986/commonFramework/tree/master
目录
- 1 消息队列
- 1.1 基本概念
- 1.2 spring-boot-starter-amqp的组件
- 1.3 代码示例
- 2 对象存储(OSS)
- 2.1 代码演示
- 2.1.1 配置自动化的OSS对象存储公共包
- 2.1.2 business-biz引用
- 2.1.3 注意坑点
- 2.2 实践经验
1 消息队列
消息队列在我们实际应用中的作用包括提高响应速度、流量控制和系统解耦,因此在我们实际中还是比较重要的。这里通过脚手架集成方式展现RabbitMQ在实践中的使用。
1.1 基本概念
RabbitMQ是一个比较常用的消息队列,有关它的几个概念可能要弄清楚:
交换机(Exchange):交换机是消息的分发中心,它接收生产者发送的消息并根据一定的规则将消息路由到一个或多个队列中。(记住:生产者就是发送消息给交换机)RabbitMQ提供了不同类型的交换机,包括:
- 直连交换机(Direct Exchange):根据消息的路由键将消息发送到特定队列。
- 主题交换机(Topic Exchange):根据消息的路由键和通配符匹配将消息发送到多个队列。
- 扇出交换机(Fanout Exchange):将消息广播到与交换机绑定的所有队列。
- 头交换机(Headers Exchange):根据消息的自定义头部属性进行匹配路由。
队列(Queue):队列是消息的容器,它存储消息直到消费者准备好接收和处理它们。消息通过交换机路由到队列,消费者可以从队列中读取消息。(记住:消费者就是从队列获取消息)每个队列都有一个名称,它们可以绑定到一个或多个交换机,并指定了消息的路由规则。
路由键(Routing Key):路由键是生产者在发布消息时指定的一个关键字,它告诉交换机将消息路由到哪个队列。路由键的意义取决于交换机的类型。(记住:主要是Direct Exchange和Topic Exchange使用)在直连交换机中,路由键通常与队列的绑定键一致;在主题交换机中,路由键可以使用通配符进行匹配。
头部信息(Headers):保存在消息的头部信息,在headers Exchange中可以通过头部信息进行分发到不同队列。较少使用,其原理和使用场景与routing key没有太大区别
1.2 spring-boot-starter-amqp的组件
SpringBoot 已经提供了对 AMQP 协议完全支持的 spring-boot-starter-amqp 依赖,引入此依赖即可快速方便的在 SpringBoot 中使用 RabbitMQ。它提供了编程式和注解式的使用方式,但是有些核心组件还是要弄清楚:
- RabbitAdmin:主要用于交换机、队列的创建、删除,它们之间的绑定作用
- RabbitTemplate:是 SpringBoot AMQP 提供的快速发 RabbitMQ 消息的模板类,与 RestTemplate 有类似之处,意指方便、简单、快速的发 RabbitMQ 消息。
- MessageConverter:就是用来在收发消息时自动转换 AMQP 内部消息和 Java 对象的。
- @RabbitListener:注解式的使用消费者监听,用于标记一个方法或一个类,使其成为消息队列的监听器,即这个方法或类负责接收来自RabbitMQ的消息。
- @Rabbithandler:注解式的使用消费者监听,注解用于标记一个方法,作为特定类型的消息的处理器。
1.3 代码示例
下面以spring-boot-starter-amqp为基础,再次封装更为方便使用的生产者和消费者。
参考common-amqp子模块和business-biz子模块
1)在common子模块下创建子模块common-amqp,该子模块以spring.factories方式发布
2)定义RabbitmqSender封装生产者的配置
3)定义ConfigurationProperties和QueueInfo读取receivers接收者的配置信息(配置在yaml文件中)
4)在RabbitmqConfig中注册队列并绑定队列到交换机,其配置在yaml文件中说明
receivers: # 本示例配置消费者信息test-direct-queue:queueName: test-direct-queue # 队列名称exchange: test-direct # 队列绑定交换机exchangeType: DIRECT # 交换机类型DIRECT、HEADER、TOPIC、FANOUTroutingKey: error, info # routingKey,只有DIRECT和TOPIC有效acknowledgeMode: MANUAL # 手动还是自动确认
5)子模块business-biz引用common-amqp子模块
6)在子模块business-biz中,配置生产者:在yaml文件中配置senders,同时直接使用类似以下代码即可发生消息:
TestMessage testMessage = new TestMessage(1,str,new Timestamp((new Date()).getTime()));
rabbitmqSender.send(testMessage, test_direct, "error");
7)在子模块business-biz中,配置消费者:在yaml文件中配置receiver,同时直接使用类似以下代码即可发生消息:
/*** Direct消息消费者,同时手动确认模式*/
@RabbitListener(queues="${spring.rabbitmq.receivers.test-direct-queue.queueName}", ackMode = "${spring.rabbitmq.receivers.test-direct-queue.acknowledgeMode}")
public void receive(TestMessage testMessage, Channel channel, @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {log.info("handleDirectMessage:{}", JSON.toJSONString(testMessage));channel.basicAck(tag,true);
}
2 对象存储(OSS)
对象存储(Object Storage)是一种存储架构,它以对象为单位来处理、存储和检索数据。与传统的文件存储和块存储不同,对象存储将数据作为对象进行管理,每个对象都包含了数据本身、元数据以及一个全局唯一的标识符。这种独特的存储方式使得对象存储在处理大量、非结构化的数据时具有明显的优势。
对象存储通过API(应用程序编程接口)调用进行数据的读写,通常基于HTTP或HTTPS协议。对象存储系统将数据分布在多个硬件设备上,并且能够自动处理数据的冗余备份和扩展性问题,这使得它在大规模数据存储方面表现出色。
下面以minIO为基础,引入io.minio依赖,封装一个对象存储的公共包
2.1 代码演示
参考common-oss子模块和business-biz子模块
2.1.1 配置自动化的OSS对象存储公共包
1)在common子模块下新建common-oss子模块,以spring.factories发布
2)引入以下依赖:
<dependency><groupId>io.minio</groupId><artifactId>minio</artifactId>
</dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId>
</dependency>
<dependency><groupId>org.example</groupId><artifactId>common-core</artifactId><version>${project.version}</version>
</dependency>
<dependency><groupId>org.example</groupId><artifactId>common-exception</artifactId><version>${project.version}</version>
</dependency>
3)配置OssProperties读取yaml文件的对象存储相关配置
4)配置MinioConfiguration,注册OssTemplate(访问oss)和OssEndpoint(对外接口)
5)新建OssTemplate,作为封装oss底层操作
6)新建OssEndpoint,作为对外的API接口
2.1.2 business-biz引用
1)引入common-oss子模块
<dependency><groupId>org.example</groupId><artifactId>common-oss</artifactId><version>${project.version}</version>
</dependency>
2)在yaml文件配置以下信息
oss:enabled: true # 是否开启oss存储配置api: true # 自动开启API接口endpoint: http://127.0.0.1:9005 # MinIO服务器的URLaccess-key: minioadmin # 访问密钥secret-key: minioadmin # 密钥密码bucket-name: works # 默认的Bucket名称
3)直接就可以使用OssEndpoint的接口操作oss
2.1.3 注意坑点
1)坑1:okhttp版本冲突,如果项目引入openfeign或者springcloud中使用okhttp与io.minio里面的okhttp可能会发生冲突,需要在父项目commonFramework声明okhttp统一版本
<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>${okhttp.version}</version>
</dependency>
2)坑2:调创建等操作oss时,报错:DateTimeParseException: TemporalAccessor。
这是jdk1.8早期版本的一个bug,需要升级到jdk1.8的小版本到22之后,比如我原先是jdk-8u5,升级为jdk-8u431即可
2.2 实践经验
对象存储的优势就是使用其简单的API方式即可进行存储操作,而且在我们实际项目应用中,往往是用来存储图片、音频、视频、文件等信息。所以其操作流程其实更是直接在前端对对象存储进行操作,然后将存储信息存入后端才是一种比较好的实践方式。
比如AWS中有一个案例就是将图片存储到OSS中,利用CDN加速,前端直接操作图片和加载图片。除非有更深层的业务操作,才会经过后端接口,不然往往只是增加网络流量以及后端服务压力。