40 个定时任务,带你理解 RocketMQ 设计精髓!

news2024/11/15 12:58:30

大家好,我是君哥。

今天来分享 RocketMQ 的定时任务。通过这些定时任务,能让我们更加理解 RocketMQ 的消息处理机制和设计理念。

从 RocketMQ 4.9.4 的源代码上看,RocketMQ 的定时任务有很多,今天主要讲解一些核心的定时任务。

1 架构回顾

首先再来回顾一下 RocketMQ 的架构图:

Name Server 集群部署,但是节点之间并不会同步数据,因为每个节点都会保存完整的数据。因此单个节点挂掉,并不会对集群产生影响。

Broker 可以采用主从集群部署,实现多副本存储和高可用。每个 Broker 节点都要跟所有的 Name Server 节点建立长连接,定义注册 Topic 路由信息和发送心跳。

Producer 和 Consumer 跟 Name Server 的任意一个节点建立长连接,定期从 Name Server 拉取 Topic 路由信息。

2 Producer 和 Consumer

2.1 获取 NameServer 地址

Producer 和 Consumer 要跟 Name Server 建立连接,就必须首先获取 Name Server 地址。Producer 和 Consumer 采用定时任务每两分钟获取 Name Server 地址并更新本地缓存。代码如下:

//MQClientInstance类this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   MQClientInstance.this.mQClientAPIImpl.fetchNameServerAddr();  } catch (Exception e) {   log.error("ScheduledTask fetchNameServerAddr exception", e);  } }}, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);

2.2 更新路由信息

Producer 和 Consumer 会定时从 Name Server 获取定时订阅信息,更新本地缓存,默认间隔是 30s(可以配置)。代码如下:

//MQClientInstance类this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   MQClientInstance.this.updateTopicRouteInfoFromNameServer();  } catch (Exception e) {   log.error("ScheduledTask updateTopicRouteInfoFromNameServer exception", e);  } }}, 10, this.clientConfig.getPollNameServerInterval(), TimeUnit.MILLISECONDS);

2.3 向 Broker 发送心跳

Producer 和 Consumer 会从本地缓存的 Broker 列表中定时清除离线的 Broker,并且向 Broker 发送心跳,默认间隔是 30s(可以配置)。代码如下:

//MQClientInstance类this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   MQClientInstance.this.cleanOfflineBroker();   MQClientInstance.this.sendHeartbeatToAllBrokerWithLock();  } catch (Exception e) {   log.error("ScheduledTask sendHeartbeatToAllBroker exception", e);  } }}, 1000, this.clientConfig.getHeartbeatBrokerInterval(), TimeUnit.MILLISECONDS);

2.4 持久化 Offset

消费者需要定时持久化 MessageQueue 的偏移量,默认每 5s 更新一次(可以配置)。

注意:集群模式需要向 Broker 发送持久化消息,因为集群模式偏移量保存在 Broker 端,而广播模式只需要把偏移量保存在消费者本地文件。代码如下:

//MQClientInstance类this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   MQClientInstance.this.persistAllConsumerOffset();  } catch (Exception e) {   log.error("ScheduledTask persistAllConsumerOffset exception", e);  } }}, 1000 * 10, this.clientConfig.getPersistConsumerOffsetInterval(), TimeUnit.MILLISECONDS);

2.5 调整核心线程数

对于消费者采用推模式的情况,消费者会根据未消费的消息数量,定期更新核心线程数,默认每 1m 一次。

注意:在 4.9.4 这个版本,更新核心线程数的代码并没有实现,只是预留了接口。代码如下:

//MQClientInstance类this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   MQClientInstance.this.adjustThreadPool();  } catch (Exception e) {   log.error("ScheduledTask adjustThreadPool exception", e);  } }}, 1, 1, TimeUnit.MINUTES);

2.6 失效过期请求

Producer 和 Consumer 会定时扫描缓存在本地的请求,如果请求开始时间加超时时间(再加 1s)小于当前时间,则这个请求过期。通过定时任务(3s 一次)让过期请求失效,并且触发回调函数。

//NettyRemotingClient.javathis.timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() {  try {   NettyRemotingClient.this.scanResponseTable();  } catch (Throwable e) {   log.error("scanResponseTable exception", e);  } }}, 1000 * 3, 1000);

2.7 生产者

2.7.1 性能记录

生产者发送消息后,会对成功失败的状态、花费时间进行记录,以此来计算吞吐量 TPS,响应时间 RT,代码如下:

//Producer.javaexecutorService.scheduleAtFixedRate(new TimerTask() { @Override public void run() {  snapshotList.addLast(statsBenchmark.createSnapshot());  if (snapshotList.size() > 10) {   snapshotList.removeFirst();  } }}, 1000, 1000, TimeUnit.MILLISECONDS);executorService.scheduleAtFixedRate(new TimerTask() { private void printStats() {  if (snapshotList.size() >= 10) {   doPrintStats(snapshotList,  statsBenchmark, false);  } } @Override public void run() {  try {   this.printStats();  } catch (Exception e) {   e.printStackTrace();  } }}, 10000, 10000, TimeUnit.MILLISECONDS);

2.8 消费者

2.8.1 MessageQueue 加锁

对于顺序消息,要保证同一个 MessageQueue 只能被同一个 Consumer 消费。消费者初始化的时候,会启动一个定时任务,定时(默认 20s,可以配置)地向 Broker 发送锁定消息,Broker 收到请求后,就会把 MessageQueue、group 和 clientId 进行绑定,这样其他客户端就不能从这个 MessageQueue 拉取消息。

代码如下:

//ConsumeMessageOrderlyService.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   ConsumeMessageOrderlyService.this.lockMQPeriodically();  } catch (Throwable e) {   log.error("scheduleAtFixedRate lockMQPeriodically exception", e);  } }}, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);

注意:Broker 的加锁是有时效的(默认 60s,可以配置),过期后,有可能被其他 Consumer 进行消费。

2.8.2 性能快照

Consumer 每秒会记录一次性能快照,比如消息从创建到消费花费的时间,消息从保存到消费花费的时间,接收到消息的总数量,失败总数量。代码如下:

//Consumer.javaexecutorService.scheduleAtFixedRate(new TimerTask() { @Override public void run() {  snapshotList.addLast(statsBenchmarkConsumer.createSnapshot());  if (snapshotList.size() > 10) {   snapshotList.removeFirst();  } }}, 1000, 1000, TimeUnit.MILLISECONDS);

上面记录了性能快照后,Consumer 会每隔 10s 进行性能参数计算和打印。代码如下:

//Consumer.javaexecutorService.scheduleAtFixedRate(new TimerTask() {private void printStats() { if (snapshotList.size() >= 10) {  Long[] begin = snapshotList.getFirst();  Long[] end = snapshotList.getLast();  final long consumeTps =   (long) (((end[1] - begin[1]) / (double) (end[0] - begin[0])) * 1000L);  final double averageB2CRT = (end[2] - begin[2]) / (double) (end[1] - begin[1]);  final double averageS2CRT = (end[3] - begin[3]) / (double) (end[1] - begin[1]);  final long failCount = end[4] - begin[4];  final long b2cMax = statsBenchmarkConsumer.getBorn2ConsumerMaxRT().get();  final long s2cMax = statsBenchmarkConsumer.getStore2ConsumerMaxRT().get();  statsBenchmarkConsumer.getBorn2ConsumerMaxRT().set(0);  statsBenchmarkConsumer.getStore2ConsumerMaxRT().set(0);  System.out.printf("Current Time: %s TPS: %d FAIL: %d AVG(B2C) RT(ms): %7.3f AVG(S2C) RT(ms): %7.3f MAX(B2C) RT(ms): %d MAX(S2C) RT(ms): %d%n",    System.currentTimeMillis(), consumeTps, failCount, averageB2CRT, averageS2CRT, b2cMax, s2cMax  ); }}

通过性能参数的日志输出,可以很方便的对 RocketMQ 的消费者进行监控。

2.8.3 清除过期消息

消费者会定期检查本地拉取的消息列表,如果列表中的消息已经过期(默认 15 分钟过期,可以配置),则把过期消息再次发送给 Broker,然后从本地消息列表删除。代码如下:

//ConsumeMessageConcurrentlyService.javathis.cleanExpireMsgExecutors.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   cleanExpireMsg();  } catch (Throwable e) {   log.error("scheduleAtFixedRate cleanExpireMsg exception", e);  } }}, this.defaultMQPushConsumer.getConsumeTimeout(), this.defaultMQPushConsumer.getConsumeTimeout(), TimeUnit.MINUTES);

2.8.4 清除过期消息

消费者会每隔 30s 向 NameServer 拉取 MessageQueue 信息,然后跟本地保存的进行比较,如果不一致,则更新本地缓存信息。代码如下:

//DefaultLitePullConsumerImpl.javascheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   fetchTopicMessageQueuesAndCompare();  } catch (Exception e) {   log.error("ScheduledTask fetchMessageQueuesAndCompare exception", e);  } }}, 1000 * 10, this.getDefaultLitePullConsumer().getTopicMetadataCheckIntervalMillis(), TimeUnit.MILLISECONDS);

3 Broker

3.1 状态采样

Broker 端会对状态进行采用,比如一个 Topic、MessageQueue、Group 总共发送了多少条消息,Topic 总共发送的消息大小。Broker 会对这些状态按照秒、分钟、小时为单位进行采样并且定时打印,这里一共有 6 个定时任务。比如下面是按照秒进行采样的定时任务:

//StatsItemSet.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   samplingInSeconds();  } catch (Throwable ignored) {  } }}, 0, 10, TimeUnit.SECONDS);

3.2 记录消息延时

Broker 读取消息时会记录消息从保存磁盘到被读取的时间差并定时打印。定时任务代码如下:

//MomentStatsItemSet.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   printAtMinutes();  } catch (Throwable ignored) {  } }}, Math.abs(UtilAll.computeNextMinutesTimeMillis() - System.currentTimeMillis()), 1000 * 60 * 5, TimeUnit.MILLISECONDS);

3.3 持久化数据

Broker 会定时持久化消费偏移量、Topic 配置、定阅组配置等,默认 10s 一次(可以配置)。代码如下:

//ScheduleMessageService.javathis.deliverExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   if (started.get()) {    ScheduleMessageService.this.persist();   }  } catch (Throwable e) {   log.error("scheduleAtFixedRate flush exception", e);  } }}, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS);

3.4 失效过期请求

Broker 会定时扫描缓存在本地的请求,如果请求开始时间加超时时间(再加 1s)小于当前时间,则这个请求过期。通过定时任务(3s 一次)让过期请求失效,并且触发回调函数。

//NettyRemotingServer.javathis.timer.scheduleAtFixedRate(new TimerTask() { @Override public void run() {  try {   NettyRemotingServer.this.scanResponseTable();  } catch (Throwable e) {   log.error("scanResponseTable exception", e);  } }}, 1000 * 3, 1000);

3.5 过滤服务

消费者可能会向 Broker 注册 filterClass 用来过滤消息。Broker 收到消费者注册的 filterClass 后会用定时任务来创建 FilterServer。代码如下:

//FilterServerManager.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   FilterServerManager.this.createFilterServer();  } catch (Exception e) {   log.error("", e);  } }}, 1000 * 5, 1000 * 30, TimeUnit.MILLISECONDS);

这样消费者拉取消息时首先从 FilterServer 拉取消息,FilterServer 从 Broker 拉取消息后进行过滤,只把消费者感兴趣的消息返回给消费者。一个 Broker 可以有多个 FilterServer。如下图:

3.6 记录消息总量

Broker 每天会记录前一天收发消息的总数量,定时任务如下(period 是 1 天):

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.getBrokerStats().record();  } catch (Throwable e) {   log.error("schedule record error.", e);  } }}, initialDelay, period, TimeUnit.MILLISECONDS);

3.7 持久化 Offset

Broker 默认每隔 5s(可以配置) 会持久化一次消息的 Offset,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.consumerOffsetManager.persist();  } catch (Throwable e) {   log.error("schedule persist consumerOffset error.", e);  } }}, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS);

3.8 持久化过滤参数

上面提到过,消费者可能会向 Broker 注册 filterClass,Broker 解析消费者注册的 filterClass 后,会把解析后的 FilterData 持久化到文件,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.consumerFilterManager.persist();  } catch (Throwable e) {   log.error("schedule persist consumer filter error.", e);  } }}, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS);

3.9 Broker 自我保护

当消费者读取消息缓慢时,Broker 为了保护自己,会把这个消费者设置为不允许读取的状态,这样这个消费组就不能再拉取消息了,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.protectBroker();  } catch (Throwable e) {   log.error("protectBroker error.", e);  } }}, 3, 3, TimeUnit.MINUTES);

3.10 Broker 打印水位

Broker 会每隔 1s 打印一次水位,包括发送消息的延迟、接收消息的延迟、事务消息的延迟、查询消息的延迟,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.printWaterMark();  } catch (Throwable e) {   log.error("printWaterMark error.", e);  } }}, 10, 1, TimeUnit.SECONDS);

3.11 Broker 打印 Offset 差

Broker 会定时打印最新的消息 Offset 和已经分发给 MessageQueue 和 Index 索引的 Offset 差距,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   log.info("dispatch behind commit log {} bytes", BrokerController.this.getMessageStore().dispatchBehindBytes());  } catch (Throwable e) {   log.error("schedule dispatchBehindBytes error.", e);  } }}, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS);

3.12 获取 NameServer 地址

Broker 会定期获取 NameServer 的地址,并更新本地缓存,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.brokerOuterAPI.fetchNameServerAddr();  } catch (Throwable e) {   log.error("ScheduledTask fetchNameServerAddr exception", e);  } }}, 1000 * 10, 1000 * 60 * 2, TimeUnit.MILLISECONDS);

3.13 打印主从偏移量差距

Broker 会定时打印 master 节点和 slave 节点消息 Offset 的差距,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.printMasterAndSlaveDiff();  } catch (Throwable e) {   log.error("schedule printMasterAndSlaveDiff error.", e);  } }}, 1000 * 10, 1000 * 60, TimeUnit.MILLISECONDS);

3.14 向 NameServer 注册

Broker 会定时向(默认 30s,可配置,最高不超过 60s)所有 NameServer 发送注册消息,代码如下:

//BrokerController.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.registerBrokerAll(true, false, brokerConfig.isForceRegister());  } catch (Throwable e) {   log.error("registerBrokerAll Exception", e);  } }}, 1000 * 10, Math.max(10000, Math.min(brokerConfig.getRegisterNameServerPeriod(), 60000)), TimeUnit.MILLISECONDS);

3.15 同步 Slave

Broker 的 Master 节点会每间隔 10s 向 Slave 节点同步数据,包括 Topic 配置、消费偏移量、延迟偏移量、消费组配置,代码如下:

//BrokerController.javaslaveSyncFuture = this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   BrokerController.this.slaveSynchronize.syncAll();  }  catch (Throwable e) {   log.error("ScheduledTask SlaveSynchronize syncAll error.", e);  } }}, 1000 * 3, 1000 * 10, TimeUnit.MILLISECONDS);

3.16 删除过期文件

Broker 会周期性(默认 10s,可以配置)地执行删除任务,删除过期的 CommitLog 文件和 ConsumeQueue 文件,代码如下:

//DefaultMessageStore.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  DefaultMessageStore.this.cleanFilesPeriodically(); }}, 1000 * 60, this.messageStoreConfig.getCleanResourceInterval(), TimeUnit.MILLISECONDS);

3.17 文件大小检查

Broker 会每隔 10 分钟检查 CommitLog 文件和 ConsumeQueue 文件,用当前文件的最小(起始) Offset 减去上一个文件最小(起始) Offset,如果不等于一个文件的大小,就说明文件存在问题。代码如下:

//DefaultMessageStore.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  DefaultMessageStore.this.checkSelf(); }}, 1, 10, TimeUnit.MINUTES);

3.18 保存堆栈映射

Broker 会每隔 1s 记录所有存活线程的堆栈映射信息,前提是 debugLockEnable 开关配置是打开的。代码如下:

//DefaultMessageStore.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  if (DefaultMessageStore.this.getMessageStoreConfig().isDebugLockEnable()) {   try {    if (DefaultMessageStore.this.commitLog.getBeginTimeInLock() != 0) {     long lockTime = System.currentTimeMillis() - DefaultMessageStore.this.commitLog.getBeginTimeInLock();     if (lockTime > 1000 && lockTime < 10000000) {      String stack = UtilAll.jstack();      final String fileName = System.getProperty("user.home") + File.separator + "debug/lock/stack-"       + DefaultMessageStore.this.commitLog.getBeginTimeInLock() + "-" + lockTime;      MixAll.string2FileNotSafe(stack, fileName);     }    }   } catch (Exception e) {   }  } }}, 1, 1, TimeUnit.SECONDS);

3.19 检查物理磁盘

Broker 会每隔 10s 检查保存 CommitLog 的磁盘空间是否达到阈值,如果达到,会打印 error 级别的日志。代码如下:

//DefaultMessageStore.javathis.diskCheckScheduledExecutorService.scheduleAtFixedRate(new Runnable() { public void run() {  DefaultMessageStore.this.cleanCommitLogService.isSpaceFull(); }}, 1000L, 10000L, TimeUnit.MILLISECONDS);

3.20 持久化延时消息偏移量

RocketMQ 的延时消费分为 18 个级别,定义如下:

//ScheduleMessageService.javaprivate String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";

RocketMQ 会为每个延时级别定义要给 ConsumeQueue,每隔 ConsumeQueue 都会有一个 Offset,通过 offsetTable(ConcurrentMap) 来记录不同延时级别对应的 Offset。

RocketMQ 会周期性地(默认 10s,可以配置)把 offsetTable 中保存的 Offset 持久化到文件。代码如下:

//DefaultMessageStore.javathis.deliverExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   if (started.get()) {    ScheduleMessageService.this.persist();   }  } catch (Throwable e) {   log.error("scheduleAtFixedRate flush exception", e);  } }}, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval(), TimeUnit.MILLISECONDS);

3.21 关闭异常连接

Broker 会定时扫描所有的长连接,主要包括生产者、消费者和 FilterServer,如果连接不活跃,则关闭该连接,并从本地连接列表中移除。代码如下:

//ClientHousekeepingService.javathis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   ClientHousekeepingService.this.scanExceptionChannel();  } catch (Throwable e) {   log.error("Error occurred when scan not active client channels.", e);  } }}, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS);

3.22 清理过期消息

如果 Broker 配置了允许快速失败(brokerFastFailureEnable),则会每隔 10ms 定时清理过期请求,包括要发送的消息、接收的消息、心跳消息、要结束的事务消息。代码如下:

scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() {  try {   fetchTopicMessageQueuesAndCompare();  } catch (Exception e) {   log.error("ScheduledTask fetchMessageQueuesAndCompare exception", e);  } }}, 1000 * 10, this.getDefaultLitePullConsumer().getTopicMetadataCheckIntervalMillis(), TimeUnit.MILLISECONDS);

注意:清理消息前会判断是否系统繁忙,如果系统繁忙,会给发送队列中的消息直接返回系统繁忙,暂时不做过期消息清理。

4 NameServer

4.1 检查过期 Broker

在 3.14 节中讲过,Broker 会跟 NameServer 建立长连接,定时向 NameServer 发送注册消息。NameServer 会在本地维护一个 Broker 列表,定时任务会轮询本地保存的 Broker 列表,检查注册消息是否过期(超过 120s),如果注册消息过期,则关闭长连接,从本地缓存删除这个 Broker。代码如下:

//NamesrvController.javathis.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.routeInfoManager::scanNotActiveBroker, 5, 10, TimeUnit.SECONDS);

4.2 打印配置

NameServer 启动时,会加载 KV 格式的配置文件到 configTable 这个变量,NameServer 客户端也可以发送一个 KV 配置请求给 NameServer,NameServer 收到请求后也会保存到 configTable。

NameServer 会定时打印 configTable 中的配置,代码如下:

//NamesrvController.javathis.scheduledExecutorService.scheduleAtFixedRate(NamesrvController.this.kvConfigManager::printAllPeriodically, 1, 10, TimeUnit.MINUTES);

5 总结

RocketMQ 的定时任务很多,这些定时任务的加入让 RocketMQ 的设计更加完备,包括业务处理、监控日志、心跳、清理任务、关闭连接、持久化数据等。通过对定时任务的理解,能够更深入地理解 RocketMQ 的设计理念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/171184.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智云通CRM:如何做好销售复盘,提升业绩?

我们在销售拜访中经常听见客户这样说&#xff1a;“不好意思啊&#xff0c;洛经理&#xff0c;我最近没有时间&#xff0c;等过一段时间不忙了&#xff0c;我们再约。”“洛经理&#xff0c;谢谢你大老远跑一趟&#xff0c;给我介绍这个产品。我很满意&#xff0c;不过我需要和…

RPC框架泛调用原理及转转的实践

RPC框架泛化调用功能在网关、接口测试等场景下有着广泛的需求&#xff0c;本文给各位读者介绍一下主流的泛化调用实现方式及原理&#xff0c;比较各种实现方案的优缺点&#xff0c;并分享泛化调用在转转的实践。一方面有助于RPC框架使用方理解泛化调用&#xff0c;更好地使用泛…

论文投稿指南——中文核心期刊推荐(原子能技术)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

spring mvc配置类简介及放静态资源释放

配置文件ApplicationContext.xml 基于spring的项目资源都是通过DispatcherServlet作为拦截器&#xff0c;DispatcherServlet是前置控制器&#xff0c;配置在web.xml文件中的。拦截匹配的请求&#xff0c;Servlet拦截匹配规则要自己定义&#xff0c;把拦截下来的请求&#xff0…

JS中Math.random()方法的使用总结

&#x1f525; 前言 Math.random() 这个方法相信大家都知道&#xff0c;是用来生成随机数的。不过一般的参考手册时却没有说明如何用这个方法来生成指定范围内的随机数。下面就来详细的介绍一下Math.random()&#xff0c;以及如何用它来生成制定范围内的随机数。 &#x1f525…

位运算__

异或运算相同为0&#xff0c;不同为1&#xff0c;相当于无进位相加0 ^ N NN ^ N 0异或运算满足交换律和结合律一、打印一个数的二进制题目打印一个数的二进制代码package bitoperation;public class PrintBinary {public static void printBinary(int num) {for (int i 32; …

代码重构之路 --我的2022年总结

2022年是我正式参加工作的第10个年头&#xff0c;也是我在CSDN上写博客的第11个年头。在这10余年的时间里&#xff0c;虽然在工作上遇到了各种情况&#xff0c;但我一直坚持输出、坚持分享&#xff0c;一共在CSDN上发表了530多篇原创博文。在这些文章中&#xff0c;大部分都是与…

论文投稿指南——中文核心期刊推荐(能源与动力工程)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

一篇五分生信临床模型预测文章代码复现——Figure 4-6 临床模型构建(八)

之前讲过临床模型预测的专栏,但那只是基础版本,下面我们以自噬相关基因为例子,模仿一篇五分文章,将图和代码复现出来,学会本专栏课程,可以具备发一篇五分左右文章的水平: 本专栏目录如下: Figure 1:差异表达基因及预后基因筛选(图片仅供参考) Figure 2. 生存分析,…

gateway整合sentinel限流不生效排查

问题 线上的sentinel 在测试压测时候可以正常被限流 但是在正常的流量中 发现被限流的接口很少 &#xff08;我发誓肯定都配置了限流规则&#xff09; 约定 文中的 服务名称以及地址 都被改写了 排查步骤 1.检查相关配置 以及 pom依赖配置 发现SentinelGatewayFilter 重复…

[论文阅读] (26) 基于Excel可视化分析的论文实验图表绘制总结——以电影市场为例

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

【iOS】—— 内存的五大分区

内存的五大分区 文章目录内存的五大分区五大分区分别是1.栈区优点&#xff1a;2.堆区优点&#xff1a;3.常量区4.静态区5.代码区static、extern、const关键字比较1.static关键字static关键字作用&#xff1a;全局静态变量局部静态变量2.extern全局变量对内的全局变量外部全局变…

25.Isaac教程--Carter机器人

Carter机器人 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html Carter 是一个机器人平台&#xff0c;使用 Segway 的差分底座、用于 3D 范围扫描的 Velodyne P16、ZED 相机、IMU 和 Jetson TX2 作为系统的核心。 与定制安装支架一起&#xff0c;它…

WSL2 Ubuntu+gnome图形界面的安装血泪史(亲测有效)

WSL2 Ubuntugnome图形界面写在最前面&#xff08;血泪史&#xff09;1.wsl2 安装2.Ubuntu安装3.VcXsrv安装与启动4. gnome图形界面的配置5.显卡驱动的升级写在最前面&#xff08;血泪史&#xff09; 前因 近期遇到一个问题&#xff0c;需要在ubuntu虚拟机上用opengl3.3以上的版…

导航相关产品调研

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 TODO:写完再整理 文章目录系列文章目录前言1、现有的业务产品2、常做的导航规控业务前言 认知有限&#xff0c;望大家多多包涵&#xff0c;有什么问题也希望能够与大家多…

“水果零售第二股”百果园上市首日市值近百亿

“水果零售第二股”来了&#xff01;今日&#xff08;1月16日&#xff09;&#xff0c;国内最大水果零售商深圳百果园实业&#xff08;集团&#xff09;股份有限公司&#xff08;下称“百果园”&#xff0c;02411.HK&#xff09;正式挂牌港交所。▲图源:百果园官网百果园此次IP…

8.框架Spring

一、基本概念 Spring 是 Java EE 编程领域的一款轻量级的开源框架&#xff0c;由被称为“Spring 之父”的 Rod Johnson 于 2002 年提出并创立&#xff0c;它的目标就是要简化 Java 企业级应用程序的开发难度和周期。 导入依赖&#xff1a; <dependency><groupId>o…

操作系统(一)

操作系统&#xff08;一&#xff09; 1、什么是操作系统 操作系统是指控制和管理整个计算机系统的硬件与软件资源&#xff0c;合理的组织、调度计算机的工作与资源分配&#xff0c;进而为用户和其他软件提供方便接口与环境的程序集合。操作系统是计算机系统中最基本的系统软件…

Android 深入系统完全讲解(24)

https://blog.csdn.net/weixin_41101173/article/details/80374455 理解了绘制内容&#xff0c;解决了这部分&#xff0c;下来我们来说说关于音视频的其他部分。 所有的文件&#xff0c;都有一个叫做格式的东西存在&#xff0c;MP3&#xff0c;WAV&#xff0c;而处理这个格式的…

zookeeper集群安装

zookeeper集群安装 1. 安装环境介绍 下载地址&#xff1a; http://archive.apache.org/dist/zookeeper/ 安装版本&#xff1a; 3.7.0 安装包&#xff1a; apache-zookeeper-3.7.0-bin.tar.gz 安装目录&#xff1a; /opt 机器IP&#xff1a; 10.28.19.107 10.28.19.108 10…