Chapter3-用适合的方式发送和接收消息

news2024/12/23 13:31:41

3.1 不同类型的消费者

        消费者可分为两种类型。 一个是DefaultMQPushConsumer ,由系统控制读取操作,收到消息后自动调用传人的处理方法来处理;另 一个是 DefaultMlConsumer ,读取操作中的大部分功能由使用者自主控制 。

3.1.1 DefaultMQPushConsumer 的使用

        使用 DefaultMQPushConsumer 主要是设置好各种参数和传人处理消息的函数。 系统收到消息后自动调用处理函数来处理消息,自动保存 Offset ,而且加入新的 DefaultMQPushConsumer 后会自动做负载均衡。 
Consumer.javahttps://github.com/apache/rocketmq/blob/develop/example/src/main/java/org/apache/rocketmq/example/quickstart/Consumer.java

        DefaultMQPushConsumer 需要设置三个参数 :GroupName 、NameServer、Topic

1 ) Consumer 的 GroupName 用 于把 多个 Consumer 组织到一起 , 提高并发处理能力,GroupName 需要和消息模式 ( MessageModel )配合使用 。

        RocketMQ 支持两种消息模式 :Clustering 和 Broadcasting 。

  • 在 Clustering 模式下,同一个 ConsumerGroup ( GroupName 相同 ) 里的每个 Consumer 只消费所订阅消息的一部分内容, 同一个 ConsumerGroup里所有的 Consumer 消 费的内容合起来才是所订阅 Topic 内容的整体 ,从而达到负载均衡的目的 。
  •  在 Broadcasting 模式下,同一个 ConsumerGroup 里的每个 Consumer 都能消费到所订阅 Topic 的全部消息,也就是一个消息会被多次分发,被多个 Consumer 消费 。

2) NameServer 的地址和端口号,可以填写多个 ,用分号隔开,达到消除单点故障的目的 , 比如 “ ip 1 :port;ip2:port;ip3 :port ” 。 

 3 ) Topic 名称用来标识消息类型 , 需要提前创建。(在启动broker 传入入参数也可以 autoCreateTopicEnable=true) 如果不需要消费某个 Topic 下的所有消息,可以通过指定消息的 Tag 进行消息过滤,比如:Consumer. subscribe ("TopicTest", "tag1 || tag2 || tag3"), 表示这个 Consumer 要消费“ TopicTest ”下带有 tagl 或 tag2 或 tag3 的消息( Tag 是在发送消息时设置 的标签) 。 在填写 Tag 参数的位置,用 null 或者 "*" 表示要消费这个 Topic的所有消息 。

3.1.2 DefaultMQPushConsumer 的处理流程 

 DefaultMQPushConsumer 主要功能实现在 DefaultMQPushConsumerlmpl类中,消息的处理逻辑是在 pullMessage 这个函数里的 PullCallBack 中 。 在PullCallBack 函数里有个 switch 语句,根据从 Broker 返回的消息类型做相应的处理:

         DefaultMQPushConsuer 的源码中有很多 PullRequest 语句,比如

Default­MQPushConsumerlmpl.this.executePullRequestlmmediately(pullRequest)。 为什么“PushConsumer ”中使用“ PullRequest ”呢? 这是通过“长轮询”方式达到Push 效果的方法,长轮询方式既有 Pull 的优点,又兼具 Push 方式的实时性。

        Push 方式是 Server 端接收到消息后,主动把消息推送给 Client 端,实时性高。 对于一个提供队列服务的 Server 来说,用 Push 方式主动推送有很多弊端:首先是加大 Server 端的工作量,进而影响 Server 的性能;其次, Client 的处理能力各不相同, Client 的状态不受 Server 控制,如果 Client 不能及时处理Server 推送过来的消息,会造成各种潜在问题

        Pull 方式是 Client 端循环地从 Server 端拉取消息,主动权在 Client 手里,自己拉取到一定量消息后,处理妥当了再接着取。 Pull 方式的问题是循环拉取消息的间隔不好设定,间隔太短就处在一个 “ 忙等”的状态,浪费资源;每个Pull 的时间间隔太长 Server 端有消息到来时 有可能没有被及时处理。

        “长轮询”方式通过 Client 端和 Server 端的配合,达到既拥有 Pull 的优点,又能达到保证实时性的目的 。 

         源码中有这一行设置语句 requestHeader.setSuspendTimeoutMillis (brokerSus-
pendMaxTimeMillis ),作用是设置 Broker 最长阻塞时间 ,默认设置是 15 秒,注意是 Broker 在没有新消息的时候才阻塞,有消息会立刻返回 。

         从 Broker 的源码中可以看 出,服务端接到新消息请求后, 如果队列里没有新消息,并不急于返回,通过一个循环不断查看状态,每次 waitForRunning一段 时间 (默认是 5 秒 ) , 然后后 再 Check 。默认情况下当 Broker 一直没有新消息, 第三次 Check 的时候, 等待时间超过Request 里面的 Broker­SuspendMaxTimeMillis , 就返回空结果。 在等待的过程中, Broker 收到了新的消息后会直接调用 notifyMessageArriving 函数返回请求结果 。 “长轮询”的核心 是, Broker 端 HOLD 住客户端过来的请求一小段时间,在这个时间内有新消息到达,就利用现有的连接立刻返回消息给 Consumer 。 “长 轮询”的 主动权还是掌握在 Consumer 手中, Broker 即使有大量消息积压 ,也不会主动推送给Consumer 。

        长轮询方式的局限性,是在 HOLD 住 Consumer 请求的时候需要占用资源,它适合用在消息队列这种客户端连接数可控的场景中

3.1.3 DefaultMQPushConsumer 的流量控制 

        PushConsumer 的核心还是 Pull方式

         上二张图片“PullMessages.pullMessages(request)”是在run方法中运行的(新的线程)

        Pull 获得的消息,如果直接提交到线程池里执行,很难监控和控制 ,比如,如何得知当前消息堆积的数量?如何重复处理某些消息? 如何延迟处理某些消息? RocketMQ 定义了一个快照类 Process Queue 来解决这些问题,在Push Consumer 运行的时候, 每个 Message Queue 都会有个对应的 Proces s Queue 对象,保存了这个 Message Queue 消息处理状态的快照 。

        ProcessQueue 对象里主要的内容是一个 TreeMap 和一个读写锁。 TreeMap里以 Message Queue 的 Offset 作为 Key ,以消息内容的引用为 Value ,保存了所有从 MessageQueue 获取到,但是还未被处理的消息; 读写锁控制着多个线程对 TreeMap 对象的并发访问 。

        有了 ProcessQueue 对象,流量控制就方便和灵活多了,客 户 端在每次 Pull请求前会做下面三个判断来控制流量。

         从代码中可以看出,PushConsumer 会判断获取但还未处理的消息个数 、消息总大小、Offset 的跨度,任何一个值超过设定的大小就隔一段时间再拉取消息,从而达到流量控制的目的。 此外 ProcessQueue 还可以辅助实现顺序消费的逻辑 。

 3.1.4 DefaultMQPullConsumer

        使用 DefaultMQPullConsumer 像使用 DefaultMQPushConsumer 一样需要设置各种参数,写处理消息的函数,同时还需要做额外的事情 。  

PullConsumer.javahttps://github.com/apache/rocketmq/blob/develop/example/src/main/java/org/apache/rocketmq/example/simple/PullConsumer.java

( 1 )获取 Message Queue 并遍历

        一 个 Topic 包括多个 MessageQueue ,如果这个 Consumer 需要获取 Topic下所有的消息,就要遍历多有的 MessageQueue 。 如果有特殊情况,也可以选择某些特定的 Message Queue 来读取消息 。

( 2 )维护 Offsetstore

        从一个 Message Queue 里拉取消息的时候,要传入 Offset 参数( long 类型的值),随着不断读取消息 , Offset 会不断增长 。 这个时候由用户负责把 Offset存储下来,根据具体情况可以存到内存里、写到磁盘或者数据库里等。

 ( 3 )根据不同的消息状态做不同的处理

        拉取消息的请求发出后,会返回: FOUND 、 NO_MATCHED_MSG 、 NO_NEW_MSG 、 OFFSET_ILLEGAL 四种状态,需要根据每个状态做不同的处理。比较重要的两个状态是 FOUNT 和 NO_NEW_MSG ,分别表示获取到消息和没有新的消息 。

         实际情况中可以把 while (true )放到外层,达到无限循环的目的 。 因为 Pull Consumer 需要用户自己处理遍历 Message Queue 、保存 Offset ,所以Pull Consumer 有更多的自主性和灵活性。

 3.1.5 Consumer 的启动、关闭流程

         Consumer 分为 Push 和 Pull 两种方式,对于 PullConsumer 来说,使用者主动权很高,可以根据实际需要暂停、停止、启动消费过程。 需要注意的是Offset 的保存,要在程序的异常处理部分增加把 Offset 写入磁盘方面的处理,记准了每个 MessageQueue 的 Offset ,才能保证消息消费的准确性 。

        DefaultMQPushConsumer 的退出, 要调用 shutdown() 函数, 以便释放资源、保存 Offset 等 。 这个调用要加到 Consumer 所在应用的退出逻辑中 。PushConsumer 在启动的时候 ,会做各种配置检查,然后连接 NameServer获取 Topic 信息,启动时如果遇到异常,比如无法连接NameServer,程序仍然可以正常启动不报错(日志里有 WARN 信息 ) 。 在单机环境下可以测试这种情况,启动 DefaultMQPushConsumer 时故意 把 NameServer 地址填错,程序仍然可以正常启动,但是不会收到消息

         为什么 DefaultMQPushConsumer 在无法连接 NameServer 时不直接报错退出呢? 这和分布式系统的设计有关, RocketMQ 集群可以有多个 NameServer 、Broker ,某个机器出异常后整体服务依然可用 。 所以 DefaultMQPushConsumer被设计成当发现某个连接异常时不立刻退出,而是不断尝试重新连接。 可以进行这样一个测试,在 DefaultMQPushConsumer 正常运行的时候,手动 kill 掉Broker 或 NameServer ,过一会儿再启动 。 会发现 DefaultMQPushConsumer 不会出错退出,在服务恢复后正常运行,在服务不可用的这段时间 ,仅仅会在日志里报异常信息 。

        如果需要在 DefaultMQPushConsumer 启动的时候,及时暴露配置问题,该如何操作呢? 可以 在 Consumer.start()语句后调用: Consumer .fetchSubscribeMessageQueues ("TopicName") ,这时如果配置信息写得不准确,或者当前服务不可用,这个语句会报MQClient-Exception 异常 。

 3.2 不同类型的生产者

       生产者向消息队列里写人消息,不 同的业务场景需要生产者采用不同的写入策略 。 比如同步发送、异步发送、 延迟发送、 发送事务消息等

3.2.1 DefaultMQProducer 
Producer.javahttps://github.com/apache/rocketmq/blob/develop/example/src/main/java/org/apache/rocketmq/example/quickstart/Producer.java

         发送消息要经过五个步骤 :

1 )设置 Producer 的 GroupName 。

 2 )设置 lnstanceName ,当一个 Jvm 需要启动多个 Producer 的时候,通过设置不同的 InstanceName 来区分,不设置的话系统使用默认名称“DEFAULT ” 。

3 )设置发送失败重试次数,当网络出现异常的时候,这个次数影响消息的重复投递次数。 想保证不丢消息,可以设置多重试几次。

        producer.setRetryTimesWhenSendFailed(3);

4 )设置 NameServer 地址。

5 )组装消息并发送 。

        消息的发送有同步和异步两种方式,上面的代码使用的是异步方式。 在第 2 章的例子中用的是同步方式。 消息发送的返回状态有如下四种 : FLUSHDISK_TIMEOUT 、 FLUSH_SLAVE_ TIMEOUT 、 SLAVE_NOT_AVAILABLE 、SEND_OK ,不同状态在不同的刷盘策略和同步策略的配置下含义是不同的 。

  • FLUSH_DISK_TIMEOUT : 表示没有在规定时间内完成刷盘(需要Broker 的刷盘策设置成 SYNC_FLUSH 才会报这个错误) 。
  • FLUSH_SLAVE_TIMEOUT :表示在主备方式下,并且 Broker 被设置成 SYNC_MASTER 方式,没有在设定时间内完成主从同步。
  • SLAVE_NOT_AVAILABLE : 这个状态产生的场景和 FLUSH_SLAVE _TIMEOUT 类似, 表示在主备方式下,并且 Broker 被设置成 SYNC_MASTER ,但是没有找到被配置成 Slave 的 Broker 。
  • SEND_OK :表示发送成功,发送成功的具体含义,比如消息是否已经被存储到融盘?消息是否被同步到了 Slave 上?消息在 Slave 上是否被写人磁盘?需要结合所配置的刷盘策略、主从策略来定。 这个状态还可以简单理解为,没有发生上面列出的三个问题状态就是 SEND_ OK。

        写一个高质量的生产者程序,重点在于对发送结果的处理,要充分考虑各种异常,写清对应的处理逻辑。

3.2.2 发送延迟消息 

        延迟消息的使用方法是在创建 Message 对象时,调用 setDelayTimeLevel ( int level ) 方法设置延迟时间, 然后再把这个消息发送 出 去 。 目前延迟的时间不支持任意设置,仅支持预设值的时间长度 ( 1s/5s/10s/30s/1m/2m/3m/4m/5m/6m/7m/8m/9m/10m/20m/30m/1h/2h ) 。 比如 setDelayTimeLevel(3 ) 表示延迟 10s 。 

 3.2.3 自定义消息发送规则

        一个 Topic 会有多个 MessageQueue ,如 果使用 Producer 的 默认配置 ,这个 Producer 会轮流向各个 MessageQueue 发送消息 。 Consumer 在消费消息的时候,会根据负载均衡策略,消费被分配到的 MessageQueue ,如果不经过特定的设置,某条消息被发l哪个 MessageQueue ,被哪个 Consumer 消费是未知的 。

        如果业务需 要我们把消息发送到指定的 MessageQueue 里,比如把同一类型的消息都发相同的 MessageQueue , 该怎么办呢?可以用 Message­QueueSelector 

        发送消息的时候,把 MessageQueueSelector 的对象作为参数,使用 public SendResult send ( Message msg, MessageQueueSelector selector, Object arg )函数发送消息即可 。 在 MessageQueueSelector 的实现中,根据传入的 Object 参数,或者根据 Message 消息内容确定把消息发往那个 Message Queue ,返回被选中的 MessageQueue 。 

 3.2.4 对事务的支持

        RocketMQ 的事务消息,是指发送消息事件和其他事件需要同时成功或同时失败。 比如银行转账, A 银行的某账户要转一万元到 B 银行的某账户 。 A 银行发送“B 银行账户增加一万元” 这个消息,要和“从 A 银行账户扣除一万元”这个操作同时成功或者同时失败 。

        RocketMQ 采用两阶段提交的方式实现事务消息TransactionMQProducer处理上面情况的流程是,先发一个“准备从 B 银行账户增加一万元”的消息,发送成功后做从 A 银行账户扣除一万元的操作 ,根据操作结果是否成功,确定之前的“准备从 B 银行账户增加一万元”的消息是做 commit 还是 rollback ,具体流程如下:

在这里插入图片描述

事务消息发送及提交

         1 )发送方向 RocketMQ 发送“待确认”消息 。

        2) RocketMQ 将收到的“待确认” 消息持久化成功后, 向发送方回复消息已经发送成功,此时第一阶段消息发送完成。

        3 )发送方开始执行本地事件逻辑。

        4 )发送方根据本地事件执行结果向 RocketMQ 发送二次确认( Commit 或是 Rollback ) 消息 , RocketMQ 收到 Commit 状态则将第一阶段消息标记为可投递,订阅方将能够收到该消息;收到 Rollback 状态则删除第一阶段的消息,订阅方接收不到该消息 。

事务补偿

         5 )如果出现异常情况,步骤 4 )提交的二次确认最终未到达 RocketMQ,服务器在经过固定时间段后将对“待确认”消息、发起回查请求 。

        6 )发送方收到消息回查请求后(如果发送一阶段消息的 Producer 不能工作,回查请求将被发送到和 Producer 在同一个 Group 里的其他 Producer ),通过检查对应消息的本地事件执行结果返回 Commit 或 Roolback 状态 。

        7) RocketMQ 收到回查请求后,按照步骤 4 ) 的逻辑处理。

事务消息状态

事务消息共有三种状态,提交状态、回滚状态、中间状态:

  • TransactionStatus.CommitTransaction: 提交事务,它允许消费者消费此消息。

  • TransactionStatus.RollbackTransaction: 回滚事务,它代表该消息将被删除,不允许被消费。

  • TransactionStatus.Unknown: 中间状态,它代表需要检查消息队列来确定状态。

        上面的逻辑似乎很好地实现了 事务消息功能 ,它也是 RocketMQ 之前的版本实现事务消息 的逻辑。 但是因为 RocketMQ 依赖将数据顺序写到磁盘这个特征来提高性能,步骤 4 )却需要更改第一阶段消息的状态,这样会造成磁盘Catch 的脏页过多, 降低系统的性能。 所以 RocketMQ 在 4.x 的版本中将这部分功能去除。 系统中的一些上层 Class 都还在,用户可以根据实际需求实现自己的事务功能 。

        客户端支持用户实现事务消息有,LocalTransactionExecuter(过期) 、Transaction-CheckListener(过期)、 TransactionMQProduce与TransactionListenertransactionhttps://github.com/apache/rocketmq/tree/develop/example/src/main/java/org/apache/rocketmq/example/transaction
事务消息发送 | RocketMQ (apache.org)https://rocketmq.apache.org/zh/docs/4.x/producer/06message5

 3.3  如何存储队列位置信息

         RocketMQ 中, 一种类型的消息会放到一个 Topic 里,为了能够并行, 一般一个 Topic 会有多个 Message Queue (也可以设置成一个), Offset 是指某个 Topic 下的一条消息在某个 Message Queue 里的位置,通过 Offset 的值可以定位到这条消息,或者指示 Consumer 从这条消息开始向后继续处理

         Offset 的类结构,主要分为本地文件类型Broker 代存的类型两种 。 对于 DefaultMQPushConsurner 来说,默认是 CLUSTERING 模式,也就是同一个 Consumer group 里的多个消费者每人消费一部分,各自收到的消息内容不一样。 这种情况下,由 Broker 端存储和控制 Offset 的值,使用RemoteBrokerOffsetStore 结构 org.apache.rocketmq.client.consumer.store.RemoteBrokerOffsetStore

                在 DefaultMQPushConsumer 里的 BROADCASTING 模式下,每个 Consumer都收到这个 Topic 的全部消息,各个 Consumer 间相互没有干扰, RocketMQ 使用 LocalfileOffsetStore ,把 Offset 存到本地。 org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore

 {

        "OffsetTable":[

                {"brokerNarne":"localhost","QueueId":1,"Topic":"broker1"},

                {"brokerNarne":"localhost","QueueId":2,"Topic":"broker1"},

                {"brokerNarne":"localhost","QueueId":0,"Topic":"broker1"}

        ]

}


import org.apache.rocketmq.client.consumer.store.OffsetSerializeWrapper;
import org.apache.rocketmq.client.consumer.store.ReadOffsetType;
import org.apache.rocketmq.common.MixAll;
import org.apache.rocketmq.common.message.MessageQueue;

import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 参照 org.apache.rocketmq.client.consumer.store.LocalFileOffsetStore
 * @author Jay
 */
public class LocalFileOffsetStore {
    private final String groupName;
    private final String storePath;
    private ConcurrentHashMap<MessageQueue, AtomicLong> offsetTable = new ConcurrentHashMap<MessageQueue, AtomicLong>();

    public LocalFileOffsetStore(String groupName, String storePath) {
        this.groupName = groupName;
        this.storePath = storePath;
    }

    /**
     * Load
     */
    void load() {
        OffsetSerializeWrapper offsetSerializeWrapper = this.readLocalOffset();
        if (offsetSerializeWrapper == null || offsetSerializeWrapper.getOffsetTable() == null)
            return;
        ConcurrentMap<MessageQueue, AtomicLong> currentOffsetTable = offsetSerializeWrapper.getOffsetTable();
        this.offsetTable.putAll(currentOffsetTable);
        for (MessageQueue mq : currentOffsetTable.keySet()) {
            long l = currentOffsetTable.get(mq).get();
            System.out.printf("load Consumer's Offset, %s %s %d \n", this.groupName, mq, l);
        }
    }

    /**
     * 更新偏移量,将其存储在内存中
     *
     * @param mq           MessageQueue
     * @param offset       偏移量
     * @param increaseOnly
     */
    void updateOffset(final MessageQueue mq, final long offset, final boolean increaseOnly) {
        if (mq == null)
            return;
        AtomicLong offsetOld = this.offsetTable.get(mq);
        if (offsetOld == null) {
            this.offsetTable.putIfAbsent(mq, new AtomicLong(offset));
        } else {
            offsetOld.set(offset);
        }
    }

    /**
     * 从本地存储获取偏移量
     *
     * @param mq   MessageQueue集合
     * @param type
     * @return 获取的偏移量
     */
    long readOffset(final MessageQueue mq, final ReadOffsetType type) {
        if (mq == null || this.offsetTable.get(mq) == null)
            return 0;
        return this.offsetTable.get(mq).get();
    }

    /**
     * 在本地存储保留所有偏移量
     *
     * @param mqs MessageQueue集合
     */
    void persistAll(final Set<MessageQueue> mqs) {
        if (mqs == null || mqs.isEmpty())
            return;
        OffsetSerializeWrapper offsetSerializeWrapper = new OffsetSerializeWrapper();
        for (Map.Entry<MessageQueue, AtomicLong> entry : this.offsetTable.entrySet()) {
            if (mqs.contains(entry.getKey())) {
                AtomicLong offset = entry.getValue();
                offsetSerializeWrapper.getOffsetTable().put(entry.getKey(), offset);
            }
        }
        String jsonString = offsetSerializeWrapper.toJson(true);
        if (jsonString == null)
            return;
        try {
            MixAll.string2File(jsonString, this.storePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从文件中加载 offsetTable 信息
     *
     * @return offsetTable
     */
    private OffsetSerializeWrapper readLocalOffset() {
        String content = null;
        try {
            content = MixAll.file2String(this.storePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (content == null || content.length() == 0)
            return null;

        OffsetSerializeWrapper offsetSerializeWrapper = null;
        try {
            offsetSerializeWrapper =
                    OffsetSerializeWrapper.fromJson(content, OffsetSerializeWrapper.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return offsetSerializeWrapper;
    }

}

        DefaultMQPushConsumer 类里有个函数用来设置从哪儿开始消费消息:

         注意设置读取位置不是每次都有效,它的优先级默认在 Offset Store 后面 ,比如 在 DefaultMQPushConsumer 的 BROADCASTING 方式下 ,默认是 从Broker 里读取某个 Topic 对应 ConsumerGroup 的 Offset , 当读取不到 Offset 的时候, ConsumeFromWhere 的设置才生效 。 大部分情况下这个设置在 ConsumerGroup 初次启动时有效。 如果 Consumer 正常运行后被停止, 然后再启动, 会接着上次的 Offset 开始消费, ConsumeFromWhere 的设置无效 。

 3.4 自定义日志输出

          RocketMQ 的 默认 Log 存储位置是:$ {user.home }/Logs/rocketmqLogs, Lo g 配置文件的设置可以通过 JVM启动参数、 环境变量、 代码中的设置语句这三种方式来配置 。

        可以在程序,中使用 System . setProperty(”rocketmq.Client.Log.loadconfig ”,” false ”) 语 句, 或者在 JVM 启动时使用 - D 参数来设置。然后把 Logback.xml 放到 maven 项目的
resources 文件夹下 。 在 Logback .xml 示例配置里,在原有 RocketMQ 日志的基础上,增加了 STDOUT 输出,这样可以把 RocketMQ 的日志输出到应用系统console 中,便于调试时发现问题

第 3 章:配置 (qos.ch)https://logback.qos.ch/manual/configuration.html

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

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

相关文章

uni-app:登录与支付--用户信息

用户信息 实现用户头像昵称区域的基本布局 在 my-userinfo 组件中&#xff0c;定义如下的 UI 结构&#xff1a; <template><view class"my-userinfo-container"><!-- 头像昵称区域 --><view class"top-box"><image src"…

如何通过小程序容器技术实现App的灰度发布

在当今移动应用市场竞争激烈的环境下&#xff0c;如何更快地发布新版本、更精确地测试和调整、更好地了解用户需求和行为&#xff0c;成为了每个App开发者面临的重要挑战。在这个背景下&#xff0c;灰度发布和小程序容器技术成为了越来越受欢迎的解决方案。 灰度发布是指将新版…

【Linux高性能服务器编程】I/O复用的高级应用

文章目录一、基于 select 的非阻塞 connect二、基于 poll 的聊天室程序2.1 客户端2.2 服务器三、基于 epoll 实现同时处理 TCP 和 UDP 服务一、基于 select 的非阻塞 connect connect系统调用的 man 手册中有如下一段内容&#xff1a; EINPROGERESS The socket is nonblocking…

ChatGPT既然这么火,有没有弊端呢?

介绍 在现代社会中&#xff0c;人们越来越依赖技术来解决问题。聊天机器人是一种最新的技术趋势&#xff0c;这种技术可以为人们带来很多便利。而ChatGPT聊天机器人则是其中的一种&#xff0c;它使用了大型的语言模型GPT&#xff08;Generative Pre-trained Transformer&#…

实操干货丨ChatGPT+XMind,效率爆炸,天下无敌

日常办公中&#xff0c;常用工具我们最熟悉的莫过于office铁三角&#xff1a;Word/Excel/PPT&#xff0c;除了这哥仨&#xff0c;再让你推荐一个。 XMind&#xff0c;绝对位列其中。 今天给大家分享一个 ChatGPTXMind的绝佳玩法&#xff0c;不需要下载安装任何额外的工具。 …

【MySQL | 基础篇】05、MySQL 事务详解

目录 一、事务简介 二、事务操作 2.1 未控制事务 2.2 控制事务一 2.3 控制事务二 三、事务四大特性 四、并发事务问题 五、事务隔离级别 六、并发事务演示 6.1 脏读演示 6.2 不可重复读演示 6.3 幻读演示 一、事务简介 事务是一组操作的集合&#xff0c;它是一个不…

【云原生】Kubernetes 中容器跨主机网络是怎么样的?

文章目录前言什么是 FlannelFlannel 的后端实现有哪些UDPVXLANHost-gw基于 Flannel UDP 模式的实现跨主通信UDP 模式案例实现基于 Flannel VXLAN 模式的跨主通信VXLAN 模式案例实现总结前言 在云原生领域&#xff0c;Kubernetes 已经成为了最主流的容器管理工具。Kubernetes 支…

JMM内存模型详解

1、概要 JMM全称叫Java Memory Model&#xff08;Java内存模型&#xff09;&#xff0c;什么是JMM&#xff0c;为什么要设置JMM&#xff0c;要弄清楚这个&#xff0c;咱们要先从计算机硬件存储体系说起。 2、计算机硬件存储体系 Window任务管理器Mac资源管理器window和mac是两…

Flink 优化 (二) --------- 状态及 Checkpoint 调优

目录一、RocksDB 大状态调优1. 开启 State 访问性能监控2. 开启增量检查点和本地恢复3. 调整预定义选项4. 增大 block 缓存5. 增大 write buffer 和 level 阈值大小6. 增大 write buffer 数量7. 增大后台线程数和 write buffer 合并数8. 开启分区索引功能9. 参数设定案例二、Ch…

拐点!智能座舱破局2023

“这是我们看到的整个座舱域控渗透率&#xff0c;2022年是8.28%&#xff0c;主力的搭载车型仍然是30-35万区间。”3月29日&#xff0c;2023年度&#xff08;第五届&#xff09;高工智能汽车市场峰会上&#xff0c;高工智能汽车研究院首发《2022-2025年中国智能汽车产业链市场数…

WRF中替换LAI数据

WRF中替换LAI数据 本次下载的LAI数据是GLASS中的AVHRR(1981-2018)(V50)&#xff0c;可以从这个这里获取数据GLASS_AVHRR_LAI数据集。由于我需要做一个时间序列的模拟&#xff0c;总共下载了从1990-2018年灌溉季3-9月份对应的天数为73&#xff0c;105&#xff0c;137&#xff0…

人工智能大时代——AIGC综述

生成式AI分类 模型按照输入输出的数据类型分类&#xff0c;目前主要包括9类。 有趣的是&#xff0c;在这些已发布大模型的背后&#xff0c;只有六个组织&#xff08;OpenAI, Google, DeepMind, Meta, runway, Nvidia&#xff09;参与部署了这些最先进的模型。 其主要原因是&am…

11.基于粒子群算法的含风光燃储微网优化调度(论文复现)

说明书 相关代码资源&#xff1a;基本算法智能微电网粒子群优化算法,微源&#xff1a;光伏、风机、发电机、储能等 基于多目标算法的冷热电联供型综合能源系统运行优化 基于多目标粒子群算法冷热电联供综合能源系统运行优化 MATLAB代码&#xff1a;基于粒子群算法的含风光燃…

0成本 使用home assistant远程开关机电脑

环境&#xff1a;dockerwin10HACS 问题&#xff1a;在外网手机上远程开关机家中电脑 解决办法&#xff1a;开机&#xff1a;WOL&#xff0c;关机ssh命令 背景&#xff1a;在部署HACS后&#xff0c;便想用HACS中的命令来开关机windows电脑&#xff0c;开机很简单&#xff0c;使用…

暴力破解之验证码识别

文章目录背景操作步骤1、安装python模块2、安装Captcha-killer模块3、尝试进行验证码识别背景 渗透测试过程中&#xff0c;现在验证码越来越多&#xff0c;这对测试的时候遇到的阻力不小&#xff0c;一位大佬给我安利了一个burp插件&#xff0c;Captcha-killer&#xff0c;可以…

ROS开发之如何使用ICM20948 IMU模块?

文章目录0.引言1.创建工作空间2.获取IMU功能包并编译3.检查IMU端口4.启动launch显示IMU测量结果0.引言 笔者研究课题涉及多传感器融合&#xff0c;除了前期对ROS工具的学习&#xff0c;还需要用IMU获取数据&#xff0c;对其他传感器的姿态纠正。本文使用IMU模块获取姿态数据。I…

华为乾坤王辉:新一代网络安全融合体系,筑牢企业数字化转型基石丨2023 INSEC WORLD

科技云报道原创。 随着数字化时代的到来&#xff0c;网络安全形势持续动荡。 围绕产业未来发展趋势、信息安全产业可持续发展、信息安全技术发展路径等话题&#xff0c;一场信息安全行业年度盛会——INSEC WORLD世界信息安全大会在西安盛大召开。 本届大会汇聚了近50位海内…

大数据技术(入门篇) --- 使用 Spring Boot 操作 CDH6.2.0 Hadoop

前言 本人是web后端研发&#xff0c;习惯使用spring boot 相关框架&#xff0c;因此技术选型直接使用的是spring boot&#xff0c;目前并未使用 spring-data-hadoop 依赖&#xff0c;因为这个依赖已经在 2019 年终止了&#xff0c;可以点击查看 &#xff0c;所以我这里使用的是…

防火墙的IPSECVPN点到点实验 dsvpn多层分支实验

目录 防火墙的IPSECVPN点到点实验 dsvpn多层分支实验 ​编辑 防火墙的IPSECVPN点到点实验 配置路由器接口IP 配置接口防火墙IP 写放通的策略 ping对端防火墙的接口看是否能ping通 ipsec进行配置 配置往返流量 dsvpn多层分支实验 先配置IP 2&#xff0c;配置静态IP 3&#xf…

拦截导弹 导弹防御系统

拦截导弹 & 导弹防御系统拦截导弹导弹防御系统拦截导弹 题目链接:acwing1010. 拦截导弹 题目描述&#xff1a; 输入输出: 分析&#xff1a; 第一个问题为输出最长递减子序列&#xff0c;由于导弹数在1000以内所以采用时间复杂度为O(n2)O(n^2)O(n2)或者O(nlogn)O(nlogn)O…