RocketMQ源码阅读-十-事务消息

RocketMQ源码阅读-十-事务消息

  • 交互流程
  • 事务消息发送
    • Producer发送事务消息
    • Broker处理结束事务请求
    • Broker 生成 ConsumeQueue
  • 事务消息回查
    • Broker发起回查
    • Producer 接收回查
  • 总结

交互流程

事务消息交互流程图如下:
image.png
事务消息发送步骤如下:

  1. 生产者将半事务消息发送至云消息队列 RocketMQ 版服务端。
  2. 云消息队列 RocketMQ 版服务端将消息持久化成功之后,向生产者返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 生产者开始执行本地事务逻辑。
  4. 生产者根据本地事务执行结果向服务端提交二次确认结果(Commit或是Rollback),服务端收到确认结果后处理逻辑如下:
    • 二次确认结果为Commit:服务端将半事务消息标记为可投递,并投递给消费者。
    • 二次确认结果为Rollback:服务端将回滚事务,不会将半事务消息投递给消费者。
  5. 在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。

事务消息回查步骤如下:

  1. 生产者收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
  2. 生产者根据检查得到的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行处理。

事务消息发送

Producer发送事务消息

Producer发送事务消息的代码在类DefaultMQProducerImpl#sendMessageInTransaction方法中,源码如下:

/**
 * 发送事务消息
 *
 * @param msg 消息
 * @param tranExecuter 【本地事务】执行器
 * @param arg 【本地事务】执行器参数
 * @return 事务发送结果
 * @throws MQClientException 当 Client 发生异常时
 */
public TransactionSendResult sendMessageInTransaction(final Message msg, final LocalTransactionExecuter tranExecuter, final Object arg)
throws MQClientException {
    if (null == tranExecuter) {
        throw new MQClientException("tranExecutor is null", null);
    }
    Validators.checkMessage(msg, this.defaultMQProducer);

    // 发送【Half消息】
    SendResult sendResult;
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    try {
        sendResult = this.send(msg);
    } catch (Exception e) {
        throw new MQClientException("send message Exception", e);
    }

    // 处理发送【Half消息】结果
    LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
    Throwable localException = null;
    switch (sendResult.getSendStatus()) {
            // 发送【Half消息】成功,执行【本地事务】逻辑
        case SEND_OK: {
            try {
                if (sendResult.getTransactionId() != null) { // 事务编号。目前开源版本暂时没用到,猜想ONS在使用。
                    msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
                }

                // 执行【本地事务】逻辑
                localTransactionState = tranExecuter.executeLocalTransactionBranch(msg, arg);
                if (null == localTransactionState) {
                    localTransactionState = LocalTransactionState.UNKNOW;
                }

                if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
                    log.info("executeLocalTransactionBranch return {}", localTransactionState);
                    log.info(msg.toString());
                }
            } catch (Throwable e) {
                log.info("executeLocalTransactionBranch exception", e);
                log.info(msg.toString());
                localException = e;
            }
        }
            break;
            // 发送【Half消息】失败,标记【本地事务】状态为回滚
        case FLUSH_DISK_TIMEOUT:
        case FLUSH_SLAVE_TIMEOUT:
        case SLAVE_NOT_AVAILABLE:
            localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            break;
        default:
            break;
    }

    // 结束事务:提交消息 COMMIT / ROLLBACK
    try {
        this.endTransaction(sendResult, localTransactionState, localException);
    } catch (Exception e) {
        log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
    }

    // 返回【事务发送结果】
    TransactionSendResult transactionSendResult = new TransactionSendResult();
    transactionSendResult.setSendStatus(sendResult.getSendStatus());
    transactionSendResult.setMessageQueue(sendResult.getMessageQueue());
    transactionSendResult.setMsgId(sendResult.getMsgId());
    transactionSendResult.setQueueOffset(sendResult.getQueueOffset());
    transactionSendResult.setTransactionId(sendResult.getTransactionId());
    transactionSendResult.setLocalTransactionState(localTransactionState);
    return transactionSendResult;
}

事务的最后结束,执行Commit或者rollback,调用的方法:

/**
 * 结束事务:提交消息 COMMIT / ROLLBACK
 *
 * @param sendResult 发送【Half消息】结果
 * @param localTransactionState 【本地事务】状态
 * @param localException 执行【本地事务】逻辑产生的异常
 * @throws RemotingException 当远程调用发生异常时
 * @throws MQBrokerException 当 Broker 发生异常时
 * @throws InterruptedException 当线程中断时
 * @throws UnknownHostException 当解码消息编号失败是
 */
public void endTransaction(//
    final SendResult sendResult, //
    final LocalTransactionState localTransactionState, //
    final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    // 解码消息编号
    final MessageId id;
    if (sendResult.getOffsetMsgId() != null) {
        id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
    } else {
        id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
    }

    // 创建请求
    String transactionId = sendResult.getTransactionId();
    final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    requestHeader.setTransactionId(transactionId);
    requestHeader.setCommitLogOffset(id.getOffset());
    switch (localTransactionState) {
        case COMMIT_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
            break;
        case ROLLBACK_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
            break;
        default:
            break;
    }
    requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    requestHeader.setMsgId(sendResult.getMsgId());
    String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;

    // 提交消息 COMMIT / ROLLBACK。!!!通信方式为:Oneway!!!
    this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark, this.defaultMQProducer.getSendMsgTimeout());
}

至此,Producer对于事务消息的处理就结束了,后面的逻辑交给Broker处理。

Broker处理结束事务请求

Broker处理事务消息在类EndTransactionProcessor中:

/**
 * 结束事务,提交/回滚消息
 *
 * @param ctx ctx
 * @param request 请求
 * @return 响应
 * @throws RemotingCommandException 当解析请求失败时
 */
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws RemotingCommandException {
    final RemotingCommand response = RemotingCommand.createResponseCommand(null);
    final EndTransactionRequestHeader requestHeader = (EndTransactionRequestHeader) request.decodeCommandCustomHeader(EndTransactionRequestHeader.class);

    // 打印日志(只处理 COMMIT / ROLLBACK)
    if (requestHeader.getFromTransactionCheck()) {
        switch (requestHeader.getCommitOrRollback()) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE: {
                LOGGER.warn("check producer[{}] transaction state, but it's pending status."
                            + "RequestHeader: {} Remark: {}",
                            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                            requestHeader.toString(),
                            request.getRemark());
                return null;
            }

            case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {
                LOGGER.warn("check producer[{}] transaction state, the producer commit the message."
                            + "RequestHeader: {} Remark: {}",
                            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                            requestHeader.toString(),
                            request.getRemark());

                break;
            }

            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {
                LOGGER.warn("check producer[{}] transaction state, the producer rollback the message."
                            + "RequestHeader: {} Remark: {}",
                            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                            requestHeader.toString(),
                            request.getRemark());
                break;
            }
            default:
                return null;
        }
    } else {
        switch (requestHeader.getCommitOrRollback()) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE: {
                LOGGER.warn("the producer[{}] end transaction in sending message,  and it's pending status."
                            + "RequestHeader: {} Remark: {}",
                            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                            requestHeader.toString(),
                            request.getRemark());
                return null;
            }

            case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {
                break;
            }

            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {
                LOGGER.warn("the producer[{}] end transaction in sending message, rollback the message."
                            + "RequestHeader: {} Remark: {}",
                            RemotingHelper.parseChannelRemoteAddr(ctx.channel()),
                            requestHeader.toString(),
                            request.getRemark());
                break;
            }
            default:
                return null;
        }
    }

    // 查询提交的消息
    final MessageExt msgExt = this.brokerController.getMessageStore().lookMessageByOffset(requestHeader.getCommitLogOffset());
    if (msgExt != null) {
        // 校验 producerGroup
        final String pgroupRead = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);
        if (!pgroupRead.equals(requestHeader.getProducerGroup())) {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark("the producer group wrong");
            return response;
        }

        // 校验 队列位置
        if (msgExt.getQueueOffset() != requestHeader.getTranStateTableOffset()) {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark("the transaction state table offset wrong");
            return response;
        }

        // 校验 CommitLog物理位置
        if (msgExt.getCommitLogOffset() != requestHeader.getCommitLogOffset()) {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark("the commit log offset wrong");
            return response;
        }

        // 生成消息
        MessageExtBrokerInner msgInner = this.endMessageTransaction(msgExt);
        msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
        msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
        msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
        msgInner.setStoreTimestamp(msgExt.getStoreTimestamp());
        if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
            msgInner.setBody(null);
        }

        // 存储生成消息
        final MessageStore messageStore = this.brokerController.getMessageStore();
        final PutMessageResult putMessageResult = messageStore.putMessage(msgInner);

        // 处理存储结果
        if (putMessageResult != null) {
            switch (putMessageResult.getPutMessageStatus()) {
                // Success
                case PUT_OK:
                case FLUSH_DISK_TIMEOUT:
                case FLUSH_SLAVE_TIMEOUT:
                case SLAVE_NOT_AVAILABLE:
                    response.setCode(ResponseCode.SUCCESS);
                    response.setRemark(null);
                    break;
                // Failed
                case CREATE_MAPEDFILE_FAILED:
                    response.setCode(ResponseCode.SYSTEM_ERROR);
                    response.setRemark("create maped file failed.");
                    break;
                case MESSAGE_ILLEGAL:
                case PROPERTIES_SIZE_EXCEEDED:
                    response.setCode(ResponseCode.MESSAGE_ILLEGAL);
                    response.setRemark("the message is illegal, maybe msg body or properties length not matched. msg body length limit 128k, msg properties length limit 32k.");
                    break;
                case SERVICE_NOT_AVAILABLE:
                    response.setCode(ResponseCode.SERVICE_NOT_AVAILABLE);
                    response.setRemark("service not available now.");
                    break;
                case OS_PAGECACHE_BUSY:
                    response.setCode(ResponseCode.SYSTEM_ERROR);
                    response.setRemark("OS page cache busy, please try another machine");
                    break;
                case UNKNOWN_ERROR:
                    response.setCode(ResponseCode.SYSTEM_ERROR);
                    response.setRemark("UNKNOWN_ERROR");
                    break;
                default:
                    response.setCode(ResponseCode.SYSTEM_ERROR);
                    response.setRemark("UNKNOWN_ERROR DEFAULT");
                    break;
            }

            return response;
        } else {
            response.setCode(ResponseCode.SYSTEM_ERROR);
            response.setRemark("store putMessage return null");
        }
    } else {
        response.setCode(ResponseCode.SYSTEM_ERROR);
        response.setRemark("find prepared transaction message failed");
        return response;
    }

    return response;
}

Broker 生成 ConsumeQueue

事务消息,提交(COMMIT)后才生成 ConsumeQueue。

/**
 * 执行调度请求
 * 1. 非事务消息 或 事务提交消息 建立 消息位置信息 到 ConsumeQueue
 * 2. 建立 索引信息 到 IndexFile
 *
 * @param req 调度请求
 */
public void doDispatch(DispatchRequest req) {
    // 非事务消息 或 事务提交消息 建立 消息位置信息 到 ConsumeQueue
    final int tranType = MessageSysFlag.getTransactionValue(req.getSysFlag());
    switch (tranType) {
        case MessageSysFlag.TRANSACTION_NOT_TYPE: // 非事务消息
        case MessageSysFlag.TRANSACTION_COMMIT_TYPE: // 事务消息COMMIT
            DefaultMessageStore.this.putMessagePositionInfo(req.getTopic(), req.getQueueId(), req.getCommitLogOffset(), req.getMsgSize(),
                req.getTagsCode(), req.getStoreTimestamp(), req.getConsumeQueueOffset());
            break;
        case MessageSysFlag.TRANSACTION_PREPARED_TYPE: // 事务消息PREPARED
        case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: // 事务消息ROLLBACK
            break;
    }
    // 建立 索引信息 到 IndexFile
    if (DefaultMessageStore.this.getMessageStoreConfig().isMessageIndexEnable()) {
        DefaultMessageStore.this.indexService.buildIndex(req);
    }
}
/**
 * 建立 消息位置信息 到 ConsumeQueue
 *
 * @param topic 主题
 * @param queueId 队列编号
 * @param offset commitLog存储位置
 * @param size 消息长度
 * @param tagsCode 消息tagsCode
 * @param storeTimestamp 存储时间
 * @param logicOffset 队列位置
 */
public void putMessagePositionInfo(String topic, int queueId, long offset, int size, long tagsCode, long storeTimestamp,
                                   long logicOffset) {
    ConsumeQueue cq = this.findConsumeQueue(topic, queueId);
    cq.putMessagePositionInfoWrapper(offset, size, tagsCode, storeTimestamp, logicOffset);
}

事务消息回查

在断网或者是生产者应用重启的特殊情况下,若服务端未收到发送者提交的二次确认结果,或服务端收到的二次确认结果为Unknown未知状态,经过固定时间后,服务端将对消息生产者即生产者集群中任一生产者实例发起消息回查。

Broker发起回查

暂时不看

Producer 接收回查

Producer接收事务消息回查的代码在DefaultMQProducerImpl#checkTransactionState:

/**
 * 检查【事务状态】状态
 *
 * @param addr broker地址
 * @param msg 消息
 * @param header 请求
 */
@Override
public void checkTransactionState(final String addr, final MessageExt msg, final CheckTransactionStateRequestHeader header) {
    Runnable request = new Runnable() {
        private final String brokerAddr = addr;
        private final MessageExt message = msg;
        private final CheckTransactionStateRequestHeader checkRequestHeader = header;
        private final String group = DefaultMQProducerImpl.this.defaultMQProducer.getProducerGroup();

        @Override
        public void run() {
            TransactionCheckListener transactionCheckListener = DefaultMQProducerImpl.this.checkListener();
            if (transactionCheckListener != null) {
                // 获取事务执行状态
                LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
                Throwable exception = null;
                try {
                    localTransactionState = transactionCheckListener.checkLocalTransactionState(message);
                } catch (Throwable e) {
                    log.error("Broker call checkTransactionState, but checkLocalTransactionState exception", e);
                    exception = e;
                }

                // 处理事务结果,提交消息 COMMIT / ROLLBACK
                this.processTransactionState(//
                    localTransactionState, //
                    group, //
                    exception);
            } else {
                log.warn("checkTransactionState, pick transactionCheckListener by group[{}] failed", group);
            }
        }

        /**
         * 处理事务结果,提交消息 COMMIT / ROLLBACK
         *
         * @param localTransactionState 【本地事务】状态
         * @param producerGroup producerGroup
         * @param exception 检查【本地事务】状态发生的异常
         */
        private void processTransactionState(//
            final LocalTransactionState localTransactionState, //
            final String producerGroup, //
            final Throwable exception) {
            final EndTransactionRequestHeader thisHeader = new EndTransactionRequestHeader();
            thisHeader.setCommitLogOffset(checkRequestHeader.getCommitLogOffset());
            thisHeader.setProducerGroup(producerGroup);
            thisHeader.setTranStateTableOffset(checkRequestHeader.getTranStateTableOffset());
            thisHeader.setFromTransactionCheck(true);

            // 设置消息编号
            String uniqueKey = message.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
            if (uniqueKey == null) {
                uniqueKey = message.getMsgId();
            }
            thisHeader.setMsgId(uniqueKey);

            thisHeader.setTransactionId(checkRequestHeader.getTransactionId());
            switch (localTransactionState) {
                case COMMIT_MESSAGE:
                    thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
                    break;
                case ROLLBACK_MESSAGE:
                    thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
                    log.warn("when broker check, client rollback this transaction, {}", thisHeader);
                    break;
                case UNKNOW:
                    thisHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
                    log.warn("when broker check, client does not know this transaction state, {}", thisHeader);
                    break;
                default:
                    break;
            }

            String remark = null;
            if (exception != null) {
                remark = "checkLocalTransactionState Exception: " + RemotingHelper.exceptionSimpleDesc(exception);
            }

            try {
                // 提交消息 COMMIT / ROLLBACK
                DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark,
                    3000);
            } catch (Exception e) {
                log.error("endTransactionOneway exception", e);
            }
        }
    };

    // 提交执行
    this.checkExecutor.submit(request);
}

该方法会调用发送事务消息时,创建的事务消息监听器,来查询本地事务的状态:

/**
 * 【事务消息回查】检查监听器
 */
public interface TransactionCheckListener {

    /**
     * 获取(检查)【本地事务】状态
     *
     * @param msg 消息
     * @return 事务状态
     */
    LocalTransactionState checkLocalTransactionState(final MessageExt msg);

}

根据本地事务的状态执行Commit或者Rollback。

总结

本篇分析了RocketMQ关于事务消息的实现。

  • Producer发送事务消息会先发送一个half消息,并注册一个事务消息回查监听器
  • 本地事务提交后,Producer会发送half消息提交消息
  • 本地事务回滚,Producer会发送half消息回滚消息
  • 若因网路等问题,迟迟没有收到提交或回滚消息,Broker会发起事务消息反查
  • Producer收到事务消息反查请求,执行创建事务消息时注册的监听器,查询本地消息的状态,根据本地消息的状态决定是Commit还是Rollback事务消息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

40元一碗的面,卖不动了?

一、在熙熙攘攘的商场中,两家门店“格格不入” 周五(1月19日)下午,人群从写字楼向购物中心转移。6点前后,北京合生汇商场的多个过道、上下行扶梯已经熙熙攘攘,B1、B2层的美食街区更是热闹。 一片喧哗中&…

GIS项目实战07:Eclipse资源分享

官网下载:Eclipse Downloads | The Eclipse Foundation 百度网盘分享: 链接:https://pan.baidu.com/s/1YBKw8k0a0DouSWZmDg8fYw 提取码:1234 (链接失效请私信) 无需安装,解压即可使用

小程序系列--12使用 npm 包

一、Vant Weapp 1. 什么是 Vant WeappVant Weapp 是有赞前端团队开源的一套小程序 UI 组件库,助力开发者快速搭建小程序应用。它所使用的是 MIT 开源许可协议,对商业使用比较友好。 官方文档地址 https://youzan.github.io/vant-weapp 2. 安装 Vant 组…

Power BI - 5分钟学习新增度量值

每天5分钟,今天介绍Power BI新增度量值 在 Power BI Desktop 中,你可以创建度量值。度量值用于计算表达式的结果。 在创建自己的度量值时,需要使用DAX语言。 DAX包括超过200个函数、运算符等,几乎可以计算任何数据分析所需的结果…

智能风控体系之divergence评分卡简介

评分卡模型的出现据说最早是在20世纪40年代,Household Finance and Spiegel和芝加哥邮购公司第一次尝试在贷款决策过程中使用信用评分.但是这两家公司都终止了这项业务。后来,在20世纪50年代末,伊利诺伊州的美国投资公司(AIC&…

【C语言】素数的N种代码形式

The words written in front 大家好,我是xiaoxie,希望你看完之后对你能有所帮助,不足之处,请批评指正! 希望可以和大家一起交流学习进步! Introduction 大家都知道:“质数又称素数。一个大于1的自然数&a…

ECharts实现简单饼图和柱状图

1.JAVA 1.饼图 前端使用vue&#xff0c;后端使用SpringBoot <template><div><div class"card" style"padding: 15px">数据可视化分析&#xff1a;图书类型销量</div><div style"display: flex; margin: 10px 0"&g…

Github 2024-01-21 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-21统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目7Cuda项目1HTML项目1Jupyter Notebook项目1非开发语言项目1 高级英语学习指南 创建周期&#xff…

Oracle1 数据库管理

Oracle的安装 一、基础表的创建 1.1 切换到scott用户 用sys 账户 登录 解锁scott账户 alter user scott account unlock;conn scott/tiger;发现并不存在scott账户&#xff0c;自己创建一个&#xff1f; 查找资料后发现&#xff0c;scott用户的脚本需要自己执行一下 C:\ap…

【Fooocus 深度学习】SDXL,AIGC生图,源码解读

文章目录 使用通配符增加prompt多样性Fooocus的风格实现 使用通配符增加prompt多样性 prompt和negative_prompt都可以通过apply_wildcards函数来实现通配符替换&#xff0c;apply_wildcards会从txt中随机找一个出来。 promptsunshine, river, trees, __artist__ task_prompt …

Adobe Acrobat DC软件安装后右键菜单点击无反应报错解决办法

安装了Adobe Acrobat DC软件后&#xff0c;Adobe Acrobat DC软件会修改系统的右键菜单&#xff0c;如果一旦Adobe Acrobat DC软件出现问题&#xff0c;你的右键菜单也就不能使用了&#xff0c;出现未响应的状态。 那如何恢复系统原来的右键菜单呢&#xff0c;办法很简单&#x…

在线海报图片设计器、图片编辑器源码/仿照稿定设计源码

在线海报设计系统素材设计源码是一个漂亮且功能强大的在线海报图片设计器&#xff0c;仿照稿定设计而成。该系统适用于多种场景&#xff0c;包括海报图片生成、电商分享图、文章长图、视频/公众号封面等。用户无需下载软件&#xff0c;即可轻松实现创意&#xff0c;迅速完成排版…

全网诚招工程项目管理平台城市合伙人

我们需要全力打造适用于建筑装饰、机电安装、光伏、钢结构工程项目的项目管理平台&#xff1b;专门针对建筑施工企业&#xff0c;为施工企业提升数字化工程管理水平&#xff0c;提升施工管理效率&#xff0c;节约成本&#xff0c;控制施工过程风险&#xff0c;严格管理施工过程…

arcgis实现截图/截屏功能

arcgis实现截图/截屏功能 文章目录 arcgis实现截图/截屏功能前言效果展示相关代码 前言 本篇将使用arcgis实现截图/截屏功能&#xff0c;类似于qq截图 效果展示 相关代码 <!DOCTYPE html> <html> <head><meta charset"utf-8"><meta nam…

289. 生命游戏

根据 百度百科 &#xff0c; 生命游戏 &#xff0c;简称为 生命 &#xff0c;是英国数学家约翰何顿康威在 1970 年发明的细胞自动机。 给定一个包含 m n 个格子的面板&#xff0c;每一个格子都可以看成是一个细胞。每个细胞都具有一个初始状态&#xff1a; 1 即为 活细胞 &am…

嵌入式工程师有什么推荐学习路径?

嵌入式工程师有什么推荐学习路径&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff…

阿赵UE学习笔记——12、植物系统

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的用法。这次需要使用植物系统在地形上添加一些草和石头的装饰。 一、素材准备 之前介绍过&#xff0c;可以在Quixel上面获取免费的资源&#xff0c;所以我这里就下载了一些资源&#xff0c;有草和石头的…

MySQL(下)

四、事务 一、概念 对数据库的一次执行中有多条sql语句执行。这多条sql在一次执行中&#xff0c;要么都成功执行&#xff0c;要么都不执行。保证了数据完整性。MySQL中只有innodb引擎支持事务。 二、特性 事务是必须满足 4 个条件&#xff08;ACID&#xff09;&#x…

【mongoDB】集合的创建和删除

目录 1.集合的创建 2. 查看所有集合 3.删除集合 1.集合的创建 格式&#xff1a; db.createCollection ( name ) 例如创建一个名为 bbb 的集合 还可以通过传递一个选项对象来指定集合的属性&#xff0c;例如最大文档的大小&#xff0c;索引选项等 例如 这样创建了一个名为 cc…