「springcloud 2021 系列」RocketMQ 如何快速实现微服务消息机制
RocketMQ 介绍
详解了解可以查看如下文档:rocketmq 基础知识
RocketMQ 是一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。同时,广泛应用于多个领域,包括异步通信解耦、企业解决方案、金融支付、电信、电子商务、快递物流、广告营销、社交、即时通信、移动应用、手游、视频、物联网、车联网等。
具有以下特点:
- 能够保证严格的消息顺序
- 提供丰富的消息拉取模式
- 高效的订阅者水平扩展能力
- 实时的消息订阅机制
- 亿级消息堆积能力
如何选择 RocketMQ 实现
Spring Cloud Stream
Spring Cloud Stream 是一个用于构建基于消息的微服务应用框架,使用 Spring Integration 与 Broker 进行连接。
一般来说,消息队列中间件都有一个 Broker Server(代理服务器),消息中转角色,负责存储消息、转发消息
例如在 RocketMQ 中,Broker 负责接收从生产者发送来的消息并存储、同时为消费者的拉取请求作准备。另外,Broker 也存储消息相关的元数据,包括消费者组、消费进度偏移和主题和队列消息等
Spring Cloud Stream 提供了消息中间件的统一抽象,推出了 publish-subscribe、consumer groups、partition 这些统一的概念。
Spring Cloud Stream 内部有两个概念:Binder 和 Binding
Binder,跟消息中间件集成的组件,用来创建对应的 Binding。各消息中间件都有自己的 Binder 具体实现
- Kafka 实现了 KafkaMessageChannelBinder
- RabbitMQ 实现了 RabbitMessageChannelBinder
- RocketMQ 实现了 RocketMQMessageChannelBinder
Binding,包括 Input Binding 和 Output Binding。Binding 在消息中间件与应用程序提供的 Provider 和 Consumer 之间提供了一个桥梁,实现了开发者只需使用应用程序的 Provider 或 Consumer 生产或消费数据即可,屏蔽了开发者与底层消息中间件的接触
RocketMQ 基本使用
本项目演示如何使用 RocketMQ Binder 完成 Spring Cloud 应用消息的订阅和发布。
源码地址:https://github.com/langyastudio/langya-tech/tree/master/spring-cloud
依赖环境
引入 Spring Cloud Alibaba RocketMQ 相关依赖
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-stream-rocketmq</artifactId>
</dependency>
Producer 生产者
配置文件
server:port: 28081spring:application:name: rocketmq-producecloud:stream:# 对应 BindingProperties Mapbindings:output-common:#RocketMQ Topicdestination: topic-common-01content-type: application/jsonoutput-tx:destination: topic-tx-01content-type: application/json# Spring Cloud Stream RocketMQ 配置项rocketmq:# 对应 RocketMQBinderConfigurationProperties 类binder:name-server: 192.168.123.22:9876# RocketMQ 自定义 Binding 配置项,对应 RocketMQBindingProperties Mapbindings:output-common:# RocketMQ Producer 配置项,对应 RocketMQProducerProperties 类producer:#生产者分组group: group-common#同步发送消息sync: true#最大字节数maxMessageSize: 8249344#超时时间sendMessageTimeout: 3000tx-output:producer:#group namegroup: group-tx#发送事务消息transactional: truelogging:level:com:alibaba:cloud:stream:binder:rocketmq: DEBUG
spring.cloud.stream.bindings
为 Binding 配置项Binding 分成 Input 和 Output 两种类型,但是在配置项中并不能体现出来,而是要在稍后搭配
@Input
or@Output
注解,才会区分spring.cloud.stream.rocketmq.binder
为 RocketMQ Binder 配置项。name-server
:RocketMQ Namesrv 地址。名称服务充当路由消息的提供者。生产者或消费者能够通过名字服务查找各主题相应的 Broker IP 列表。多个 Namesrv 实例组成集群,但相互独立,没有信息交换。
@Output 发送消息
public interface OutputSource
{@Output("output-common")MessageChannel sendCommon();@Output("output-tx")MessageChannel sendTx();
}
通过 @Output
注解,声明了一个名字为 output-common
的 Output Binding。注意,这个名字要和配置文件中的 spring.cloud.stream.bindings
配置项对应上
同时,@Output
注解的方法的返回结果为 MessageChannel 类型,可以使用它发送消息。
SenderService 消息发送服务
通过组装 Message
消息体并调用 OutputSource
接口,实现消息发送的逻辑
@Service
public class SenderService
{@Autowiredprivate OutputSource source;/*** 发送消息** @param msg 消息内容* @param tag 标签* @param delay 设置延迟级别为x秒后消费* @return* @throws Exception*/public <T> boolean sendObject(T msg, String tag, Integer delay) throws Exception{Message<T> message = MessageBuilder.withPayload(msg).setHeader(MessageConst.PROPERTY_TAGS, tag).setHeader(MessageConst.PROPERTY_DELAY_TIME_LEVEL, delay).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();return source.sendCommon().send(message);}public <T> boolean sendTransactionalMsg(T msg, int num) throws Exception{Message<T> message = MessageBuilder.withPayload(msg).setHeader("tx-state", String.valueOf(num)).setHeader(MessageConst.PROPERTY_TAGS, "binder").setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();return source.sendTx().send(message);}
}
Controller 接口定义
提供发送消息的 HTTP 接口。代码如下:
@RestController
@RequestMapping("/produce")
public class ProduceController
{@Autowiredprivate SenderService senderService;@GetMapping("/send1")public boolean output1(@RequestParam("msg") String msg) throws Exception{int msgId = new SecureRandom().nextInt();return senderService.sendObject(new FooMsg(msgId, msg), "tagObj", 0);}@GetMapping("/send3")public boolean output3() throws Exception{// unknown messagesenderService.sendTransactionalMsg("transactional-msg1", 1);// rollback messagesenderService.sendTransactionalMsg("transactional-msg2", 2);// commit messagesenderService.sendTransactionalMsg("transactional-msg3", 3);return true;}
}
Application 入口
启动应用。代码如下:
@SpringBootApplication
@EnableBinding({OutputSource.class})
public class RocketMQProduceApplication
{public static void main(String[] args){SpringApplication.run(RocketMQProduceApplication.class, args);}
}
使用 @EnableBinding
注解,声明指定接口开启 Binding 功能,扫描其 @Input
和 @Output
注解。
消费者
配置文件
server:# 随机端口,方便启动多个消费者port: ${random.int[10000,19999]}spring:application:name: rocketmq-consumecloud:# Spring Cloud Stream 配置项,对应 BindingServiceProperties 类stream:# Binding 配置项,对应 BindingProperties Mapbindings:input-common-1:# 目的地。这里使用 RocketMQ Topicdestination: topic-common-01content-type: application/json## 消费者分组, 命名规则:group+topic名+xxgroup: group-common-1input-common-2:destination: topic-common-01content-type: application/jsonconsumer:concurrency: 20maxAttempts: 1group: group-common-2input-common-3:destination: topic-common-01content-type: application/jsonconsumer:concurrency: 20group: group-common-3input-tx-1:destination: topic-tx-01content-type: text/jsonconsumer:concurrency: 5group: group-tx-1# Spring Cloud Stream RocketMQ 配置项rocketmq:binder:name-server: 192.168.123.22:9876bindings:input-common-1:# RocketMQ Consumer 配置项consumer:# 是否开启消费,默认为 trueenabled: true# 是否使用广播消费,默认为 false 使用集群消费,如果要使用广播消费值设成truebroadcasting: falseorderly: trueinput-common-2:consumer:orderly: falseinput-common-3:consumer:tags: tagObj
spring.cloud.stream.rocketmq.bindings
enabled
:是否开启消费,默认为true
。在日常开发时,如果在本地环境不想消费,可以通过设置enabled
为false
进行关闭broadcasting
: 是否使用广播消费,默认为false
使用的是集群消费。集群消费(Clustering):集群消费模式下,相同 Consumer Group 的每个 Consumer 实例平均分摊消息
广播消费(Broadcasting):广播消费模式下,相同 Consumer Group 的每个 Consumer 实例都接收全量消息
@Input 接收消息
public interface InputBinding
{@Input("input-common-1")SubscribableChannel inputCommon1();@Input("input-common-2")SubscribableChannel inputCommon2();@Input("input-common-3")SubscribableChannel inputCommon3();@Input("input-tx-1")SubscribableChannel inputTx1();
}
通过 @Input
注解,声明了一个名字为 input-common-1
Input Binding。注意,这个名字要和配置文件中的spring.cloud.stream.bindings
配置项对应上。
同时,@Input
注解的方法的返回结果为 SubscribableChannel 类型,可以使用它订阅消息来消费。
public interface SubscribableChannel extends MessageChannel {boolean subscribe(MessageHandler handler); // 订阅boolean unsubscribe(MessageHandler handler); // 取消订阅}
ReceiveService 接收消息服务
通过组装 StreamListener
,实现消息接收的逻辑
@Service
public class ReceiveService
{@StreamListener("input-common-1")public void receiveInput1(@Payload FooMsg receiveMsg){System.out.println("input1 receive: " + receiveMsg);}@StreamListener("input-common-2")public void receiveInput2(@Payload FooMsg receiveMsg){System.out.println("input2 receive: " + receiveMsg);}@StreamListener("input-common-3")public void receiveInput3(@Payload FooMsg receiveMsg){System.out.println("input3 receive: " + receiveMsg);}@StreamListener("input-tx-1")public void receiveTransactionalMsg(String transactionMsg){System.out.println("input tx receive transaction msg: " + transactionMsg);}}
在方法上,添加 @StreamListener
注解,声明对应的 Input Binding。
因为消费的消息是 POJO 类型,所以需要添加 @Payload
注解,声明需要进行反序列化成 POJO 对象。
Application 入口
启动应用。代码如下:
@SpringBootApplication
@EnableBinding({InputBinding.class})
public class RocketMQConsumerApplication
{public static void main(String[] args){SpringApplication.run(RocketMQConsumerApplication.class, args);}
}
使用 @EnableBinding
注解,声明指定接口开启 Binding 功能,扫描其 @Input
和 @Output
注解
测试单集群多实例的场景
- 执行 ProducerApplication,启动生产者的实例
- 执行 RocketMQConsumerApplication,启动消费者的实例
之后,请求 http://localhost:28081/produce/send1?msg=发送短信-0248 接口三次,发送三条消息。此时在 IDEA 控制台看到消费者打印日志如下:
[onMessage][input-common-2 线程编号:99 消息内容:FooMsg(id=-1996543838, bar=发送短信-0248)]
[onMessage][input-common-3 线程编号:98 消息内容:FooMsg(id=-1996543838, bar=发送短信-0248)]
[onMessage][input-common-1 线程编号:97 消息内容:FooMsg(id=-1996543838, bar=发送短信-0248)]
符合预期。从日志可以看出,每条消息被同一个分组仅被消费一次
定时消息
定时消息,是指消息发到 Broker 后,不能立刻被 Consumer 消费,要等待特定的时间后才能被消费
RocketMQ 暂时不支持任意的时间精度的延迟,而是固化了 18 个延迟级别。如下表格:
延迟级别 | 时间 | 延迟级别 | 时间 | 延迟级别 | 时间 |
---|---|---|---|---|---|
1 | 1s | 7 | 3m | 13 | 9m |
2 | 5s | 8 | 4m | 14 | 10m |
3 | 10s | 9 | 5m | 15 | 20m |
4 | 30s | 10 | 6m | 16 | 30m |
5 | 1m | 11 | 7m | 17 | 1h |
6 | 2m | 12 | 8m | 18 | 2h |
消费重试
不过要注意,只有 集群消费 模式下,才有消息重试
RocketMQ 提供消费重试的机制。在消息消费失败的时候,RocketMQ 会通过消费重试机制,重新投递该消息给 Consumer ,让 Consumer 有机会重新消费消息,实现消费成功。
当然,RocketMQ 并不会无限重新投递消息给 Consumer 重新消费,而是在默认情况下,达到 16 次重试次数时,Consumer 还是消费失败时,该消息就会进入到死信队列。
max-attempts 次数
因为 Spring Cloud Stream 提供的重试间隔,是通过 sleep 实现,会占掉当前线程,影响 Consumer 的消费速度,所以这里并不推荐使用,因此设置 max-attempts 配置项为 1,禁用 Spring Cloud Stream 提供的重试功能,使用 RocketMQ 提供的重试功能。
delay-level-when-next-consume 策略
- -1:不重复,直接放入死信队列
- 0:RocketMQ Broker 控制重试策略
- >0:RocketMQ Consumer 控制重试策略
每条消息的失败重试,是有一定的间隔时间。第一次重试消费按照延迟级别为 3 开始。所以,默认为 16 次重试消费,也非常好理解,毕竟延迟级别最高为 18 呀。
消费异常处理机制
如果异常处理方法成功,没有重新抛出异常,会认定为该消息被消费成功,所以就不会进行消费重试。
在 Spring Cloud Stream 中,提供了通用的消费异常处理机制,可以拦截到消费者消费消息时发生的异常,进行自定义的处理逻辑。
有两种方式来实现异常处理:
- 局部的异常处理:通过订阅指定错误 Channel -
<destination>.<group>.errors
- 全局的异常处理:通过订阅全局错误 Channel - errorChannel
消费消息发生异常时,会发送错误消息 ErrorMessage 到对应的错误 Channel 中。同时,所有错误 Channel 都桥接到了 Spring Integration 定义的全局错误 Channel。
//<destination>.<group>.errors//局部错误@ServiceActivator(inputChannel = "topic-common-01.group-common-3.errors")public void handleError(ErrorMessage errorMessage){System.out.printf("[handleError][payload:%s] %n", ExceptionUtils.getRootCauseMessage(errorMessage.getPayload()));System.out.printf("[handleError][originalMessage:%s] %n", errorMessage.getOriginalMessage());System.out.printf("[handleError][headers:%s] %n", errorMessage.getHeaders());}// errorChannel// 全局错误@StreamListener(IntegrationContextUtils.ERROR_CHANNEL_BEAN_NAME)public void globalHandleError(ErrorMessage errorMessage){System.out.printf("[globalHandleError][payload:%s] %n",ExceptionUtils.getRootCauseMessage(errorMessage.getPayload()));System.out.printf("[globalHandleError][originalMessage:%s] %n", errorMessage.getOriginalMessage());System.out.printf("[globalHandleError][headers:%s] %n", errorMessage.getHeaders());}
广播消息
广播消费模式下,相同 Consumer Group 的每个 Consumer 实例都接收全量的消息。
例如说,我们基于 WebSocket 实现了 IM 聊天,在我们给用户主动发送消息时,因为我们不知道用户连接的是哪个提供 WebSocket 的应用,所以可以通过 RocketMQ 广播消费,每个应用判断当前用户是否是和自己提供的 WebSocket 服务连接,如果是,则推送消息给用户。
设置 broadcasting
配置项为 true
顺序消息
RocketMQ 提供了两种顺序级别:
- 普通顺序消息:Producer 将相关联的消息发送到相同的消息队列
- 完全严格顺序:在【普通顺序消息】的基础上,Consumer 严格顺序消费
消息有序,指的是一类消息消费时,能按照发送的顺序来消费。例如:一个订单产生了三条消息分别是订单创建、订单付款、订单完成。消费时要按照这个顺序消费才能有意义,但是同时订单之间是可以并行消费的。RocketMQ 可以严格的保证消息有序。
分区顺序就是普通顺序消息,全局顺序就是完全严格顺序
- 全局顺序:对于指定的一个 Topic,所有消息按照严格的先入先出(FIFO)的顺序进行发布和消费。适用场景:性能要求不高,所有的消息严格按照 FIFO 原则进行消息发布和消费的场景
- 分区顺序:对于指定的一个 Topic,所有消息根据 Sharding key 进行区块分区。 同一个分区内的消息按照严格的 FIFO 顺序进行发布和消费。Sharding key 是顺序消息中用来区分不同分区的关键字段,和普通消息的 Key 是完全不同的概念。适用场景:性能要求高,以 Sharding key 作为分区字段,在同一个区块中严格的按照 FIFO 原则进行消息发布和消费的场景
分区顺序:
生产者
添加
partition-key-expression
配置项,设置 Producer 发送顺序消息的 Sharding keysync: true
是否同步发送消息,默认为 false 异步server:port: 28081spring:application:name: rocketmq-producecloud:stream:# Spring Cloud Stream 配置项,对应 BindingProperties Mapbindings:output-common:#RocketMQ Topicdestination: topic-common-01content-type: application/json# Producer 配置项,对应 ProducerProperties 类producer:# 分区 key 表达式。该表达式基于 Spring EL,从消息中获得分区 keypartition-key-expression: payload['id']# Spring Cloud Stream RocketMQ 配置项rocketmq:# RocketMQ Binder 配置项,对应 RocketMQBinderConfigurationProperties 类binder:name-server: 192.168.123.22:9876# RocketMQ 自定义 Binding 配置项,对应 RocketMQBindingProperties Mapbindings:output-common:# RocketMQ Producer 配置项,对应 RocketMQProducerProperties 类producer:#生产者分组group: group-common#同步发送消息sync: true
消费者
添加
orderly
配置项,设置 Consumer 顺序消费消息server:# 随机端口,方便启动多个消费者port: ${random.int[10000,19999]}spring:application:name: rocketmq-consumecloud:# Spring Cloud Stream 配置项,对应 BindingServiceProperties 类stream:# Binding 配置项,对应 BindingProperties Mapbindings:input-common-1:#重试次数max-attempts: 1# 目的地。这里使用 RocketMQ Topicdestination: topic-common-01content-type: application/json## 消费者分组, 命名规则:group+topic名+xxgroup: group-common-1# Spring Cloud Stream RocketMQ 配置项rocketmq:binder:name-server: 192.168.123.22:9876bindings:input-common-1:# RocketMQ Consumer 配置项consumer:# 顺序接收消息orderly: true
消息过滤
RocketMQ 提供了两种方式给 Consumer 进行消息的过滤:
基于 Tag 过滤
标签(Tag):为消息设置的标志,用于同一主题下区分不同类型的消息。来自同一业务单元的消息,可以根据不同业务目的在同一主题下设置不同标签。标签能够有效地保持代码的清晰度和连贯性,并优化 RocketMQ 提供的查询系统。消费者可以根据 Tag 实现对不同子主题的不同消费逻辑,实现更好的扩展性
基于 SQL92 过滤,示例:https://www.jianshu.com/p/5b13868f4451
Tag 过滤比较常见,需要设置如下:
生产者
发送带 tag 的消息,如:
public <T> boolean sendObject(T msg, String tag) throws Exception {Message<T> message = MessageBuilder.withPayload(msg).setHeader(MessageConst.PROPERTY_TAGS, tag).setHeader(MessageHeaders.CONTENT_TYPE, MimeTypeUtils.APPLICATION_JSON).build();return source.sendCommon().send(message); }
消费者
订阅 tag 过滤的消息,如:
server:# 随机端口,方便启动多个消费者port: ${random.int[10000,19999]}spring:application:name: rocketmq-consumecloud:# Spring Cloud Stream 配置项,对应 BindingServiceProperties 类stream:# Binding 配置项,对应 BindingProperties Mapbindings:input-common-3:destination: topic-common-01content-type: application/jsonconsumer:concurrency: 20group: group-common-3# Spring Cloud Stream RocketMQ 配置项rocketmq:binder:name-server: 192.168.123.22:9876bindings:input-common-3:consumer:# 基于 Tag 订阅,多个 Tag 使用 || 分隔,默认为空tags: tagObj
事务消息
在分布式消息队列中,目前唯一提供完整的事务消息的,只有 RocketMQ 。关于这一点,还是可以鼓吹下的。
考虑一个极端的情况,在本地数据库事务已经提交的时时候,如果因为网络原因,又或者崩溃等等意外,导致事务消息没有被 commit ,最终导致这条事务消息丢失,分布式事务出现问题。
配置选项
RocketMQ Binder Properties
spring.cloud.stream.rocketmq.binder.name-server
RocketMQ NameServer 地址(老版本使用 namesrv-addr 配置项)。Default:
127.0.0.1:9876
spring.cloud.stream.rocketmq.binder.access-key
阿里云账号 AccessKey。Default: null
spring.cloud.stream.rocketmq.binder.secret-key
阿里云账号 SecretKey。Default: null
spring.cloud.stream.rocketmq.binder.enable-msg-trace
是否为 Producer 和 Consumer 开启消息轨迹功能 Default:
true
spring.cloud.stream.rocketmq.binder.customized-trace-topic
消息轨迹开启后存储的 topic 名称。Default:
RMQ_SYS_TRACE_TOPIC
RocketMQ Provider Properties
下面的这些配置是以 spring.cloud.stream.rocketmq.bindings.<channelName>.producer.
为前缀的 RocketMQ Producer 相关的配置。
enable
是否启用 Producer。默认值:
true
group
Producer group name。默认值: empty
maxMessageSize
消息发送的最大字节数。默认值:
8249344
transactional
是否发送事务消息。默认值:
false
sync
是否使用同步得方式发送消息。默认值:
false
vipChannelEnabled
是否在 Vip Channel 上发送消息。默认值:
true
sendMessageTimeout
发送消息的超时时间(毫秒)。默认值:
3000
compressMessageBodyThreshold
消息体压缩阀值(当消息体超过 4k 的时候会被压缩)。默认值:
4096
retryTimesWhenSendFailed
在同步发送消息的模式下,消息发送失败的重试次数。默认值:
2
retryTimesWhenSendAsyncFailed
在异步发送消息的模式下,消息发送失败的重试次数。默认值:
2
retryNextServer
消息发送失败的情况下是否重试其它的 broker。默认值:
false
RocketMQ Consumer Properties
下面的这些配置是以 spring.cloud.stream.rocketmq.bindings.<channelName>.consumer.
为前缀的 RocketMQ Consumer 相关的配置。
enable
是否启用 Consumer。默认值:
true
.tags
Consumer 基于 TAGS 订阅,多个 tag 以
||
分割。默认值: empty.sql
Consumer 基于 SQL 订阅。默认值: empty.
broadcasting
Consumer 是否是广播消费模式。如果想让所有的订阅者都能接收到消息,可以使用广播模式。默认值:
false
.orderly
Consumer 是否同步消费消息模式。默认值:
false
.delayLevelWhenNextConsume
异步消费消息模式下消费失败重试策略:-1,不重复,直接放入死信队列0,broker 控制重试策略>0,client 控制重试策略默认值:
0
.suspendCurrentQueueTimeMillis
同步消费消息模式下消费失败后再次消费的时间间隔。默认值:
1000
.
参考
RocketMQ-Docker安装
Spring Cloud Alibaba RocketMQ
RocketMQ Example
RocketMQ 与 Spring Cloud Stream整合
「springcloud 2021 系列」RocketMQ 如何快速实现微服务消息机制相关推荐
- 「springcloud 2021 系列」sentinel实现熔断与限流 原来这么简单
Sentinel 简介 随着微服务的流行,服务和服务之间的稳定性变得越来越重要.Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制.熔断降级.系统自适应保护等多个维度 ...
- 「springcloud 2021 系列」Spring Cloud Gateway + OAuth2 + JWT 实现统一认证与鉴权
通过认证服务进行统一认证,然后通过网关来统一校验认证和鉴权. 将采用 Nacos 作为注册中心,Gateway 作为网关,使用 nimbus-jose-jwt JWT 库操作 JWT 令牌 理论介绍 ...
- 「性能优化系列」APP内存优化理论与实践
当一个应用同时运行越来越多的任务以及复杂的业务,Android系统的内存管理机制已经无法满足内存的释放与回收,为了应用的稳定性与性能,去控制内存的创建和回收就成为了一个重要的命题. 本篇文章主要涉及内 ...
- 「DLP-KDD 2021征文」及上届论文全集,包含深度学习推荐/广告系统、多目标、模型服务等
「DLP-KDD 2021征文」及上届论文全集,包含深度学习推荐/广告系统.多目标.模型服务等 在DLP-KDD 2021征稿之际,为大家准备了DLP-KDD2020的全部文章和资源列表,内容涵盖了几 ...
- 「真香系列」新物种首发亮相 聚划算爆款孵化玩法升级
从普通商品到优质好物,差的便是那一句"真香". 2022聚划算99划算节,聚划算首发「真香系列」,为消费者精选了一批价格香.品质香.服务香的「十三香」好物.不过,聚划算的" ...
- 「想法题系列」逗比三角形-二分
传送门:「想法题系列」逗比三角形-hzwer 题解 因为限制了盒子的宽,所以贪心让每个三角形尽量高(即让最短边紧贴盒子底面所在直线). 考虑把每个三角形竖着剖分成宽为 d d d的矩形. 将所有矩形降 ...
- 「春招系列」30张图理解HTTP在面试中所有会出现的题
前言 又是一年金三银四,春招与跳槽热闹的开展着,而在面试过程中,HTTP 被提问的概率还是非常高的. 我搜集了 5 大类 HTTP 面试常问的题目,同时这 5 大类题跟 HTTP 的发展和演变关联性是 ...
- 499、Java分布式和集群12 -【SpringCloud视图微服务 - 消息总线Bus】 2021.06.01
目录 0.RabbitMQ 1.先运行,看到效果,再学习 2.pom.xml 3.bootstrap.yml 4.application.yml 5.ProductDataServiceApplica ...
- SpringCloud学习一(回顾之前学的微服务知识点、springcloud入门概述、服务提供者和消费者)
一.回顾之前,如何学习springcloud 回顾之前的知识? JavaSE 数据库 前端 Servlet Http Mybatis Spring SpringMVC SpringBoot Dubbo ...
最新文章
- 深入理解计算机系统-之-内存寻址(四)--linux中分段机制的实现方式
- 算法练习day8——190326(猫狗队列、转圈打印矩阵、旋转正方形矩阵、反转单向双向链表、数N的加法组合)
- 认知NumPy数学运算库
- CentOS 使用yum update 更新时保留特定版本的软件
- java背包算法_背包算法java版
- 怎么实现两周联动加减速_猎魂觉醒与仁王开启联动,猎魂觉醒团队还与“仁王”制作人聊了聊...
- 全球气象数据的网站集合数据包含(大气数据、海洋数据等各种数据)
- SPOJ-SUBST1 New Distinct Substrings (求不同子串数量)(后缀数组模板)
- python for line in sys.stdin解析文件调用方法
- 微信小程序Tab切换,滑动切换
- Win10版本怎么转换?小编教你一键切换Windows版本
- matlab运行时间特别长,Matlab运行时间过长
- 【Quartz】 JobListener、Triggerlistener、SchedulerListener介绍与使用
- 网络工程师必知的几款网络故障排除工具
- Vue - 判断访问网页客户端设备是手机移动端还是 PC 电脑端(判断设备类型是否是移动端手机)
- 装满了自我提升的33个学习平台
- title_activity_dist is not translated in zh-rCN (Chinese: China)
- 常用到的宏定义!!!!!
- Linux安装回收站trash-cli
- 大致总结一下 安卓各大版本的差异(安卓4以上版本)
热门文章
- R 单因素协方差分析
- 单片机c语言99秒的定时器,通过51单片机定时器1实现99秒倒计时
- uBuntu下安装Win程序
- MouseWithoutBorders无界鼠标安装配置教程
- 关于undefined reference to `WinMain@16`是发生了什么呢
- 英语里面关于钱数量的单词有哪些
- Spring Boot“内存泄漏”?看看大牛是如何排查的
- 窥探晶体世界的奥秘 —— 230种空间群晶体结构模型全在这里
- 安装haroopad
- XXXX is not in the sudoers file. This incident will be reported解决方法