[RocketMQ] Consumer 负载均衡服务 RebalanceService入口源码 (十五)

  • RocketMQ一个消费者组中可以有多个消费者, 在集群模式下他们共同消费topic下的所有消息, RocketMQ规定一个消息队列仅能被一个消费者消费, 但是一个消费者可以同时消费多个消息队列。
  • 需要负载均衡服务RebalanceService来进行消息队列分配的重平衡。
  • 使用负载均衡服务RebalanceService来专门处理多个消息队列和消费者的对应关系, 如何分配消息队列给这些消费者。

RocketMQ消费者负载均衡服务RebalanceService的入口代码

文章目录

      • 1.负载均衡或者重平衡
        • 1.1 RebalanceService自动重平衡
        • 1.2 Consumer启动重平衡
        • 1.3 Broker请求重平衡
        • 1.4 Broker处理心跳请求
          • 1.4.1 registerConsumer注册消费者
          • 1.4.2 客户端处理重平衡请求

1.负载均衡或者重平衡

有三种情况会触发Consumer进行负载均衡或者说重平衡。

  1. RebalanceService服务是一个线程任务, 由MQClientInstance启动, 每隔20s自动进行一次自动负载均衡。
  2. Broker触发的重平衡:
    1. Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
    2. 如果某个客户端连接出现连接异常事件EXCEPTION、连接断开事件CLOSE、或者连接闲置事件IDLE, 则Broker同样会发送重平衡请求给消费者组下面的所有消费者。
  3. 新的Consumer服务启动的时候, 主动调用rebalanceImmediately唤醒负载均衡服务rebalanceService。

1.1 RebalanceService自动重平衡

RebalanceService#run方法, 最多每隔20s执行一次重平衡。

/*** RebalanceServicede 方法*/
@Override
public void run() {log.info(this.getServiceName() + " service started");/** 运行时逻辑* 如果服务没有停止,则在死循环中执行负载均衡*/while (!this.isStopped()) {//等待运行,默认最多等待20s,可以被唤醒this.waitForRunning(waitInterval);//执行重平衡操作this.mqClientFactory.doRebalance();}log.info(this.getServiceName() + " service end");
}

1.2 Consumer启动重平衡

新的Consumer服务启动的时候, 主动调用rebalanceImmediately唤醒负载均衡服务rebalanceService。

/*** MQClientInstance的方法* 立即重平衡*/
public void rebalanceImmediately() {//唤醒重平衡服务,立即重平衡this.rebalanceService.wakeup();
}

1.3 Broker请求重平衡

  1. Broker收到心跳请求之后如果发现消息中有新的consumer连接或者consumer订阅了新的topic或者移除了topic的订阅, Broker发送Code为NOTIFY_CONSUMER_IDS_CHANGED的请求给该group下面的所有Consumer, 要求进行一次负载均衡。
  2. 如果某个客户端连接出现连接异常事件EXCEPTION、连接断开事件CLOSE、或者连接闲置事件IDLE, 则Broker同样会发送重平衡请求给消费者组下面的所有消费者。处理入口方法为ClientHousekeepingService# doChannelCloseEvent方法。

新的Consumer和Producer启动的时候, 发送心跳给Broker, MQClientInstance的内部的服务也会定时30s发送心跳信息给Broker。

心跳请求的Code为HEART_BEAT, 该请求最终被Broker的ClientManageProcessor处理器处理。

/*** ClientManageProcessor的方法, Broker处理心跳方法*/
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request)throws RemotingCommandException {switch (request.getCode()) {//客户端心跳请求case RequestCode.HEART_BEAT://客户端心跳请求return this.heartBeat(ctx, request);case RequestCode.UNREGISTER_CLIENT:return this.unregisterClient(ctx, request);case RequestCode.CHECK_CLIENT_CONFIG:return this.checkClientConfig(ctx, request);default:break;}return null;
}

1.4 Broker处理心跳请求

Broker的ClientManageProcessor#heartBeat该方法用于Broker处理来自客户端, consumer和producer的请求。

  1. 解码消息中的信息成为HeartbeatData对象。
  2. 循环遍历处理consumerDataSet集合, 对ConsumerData信息进行注册或者更改, 如果consumer信息修改的话, Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端, 进行重平衡操作。
  3. 循环遍历处理consumerDataSet集合, 对ProducerData信息注册或者更改。
/*** ClientManageProcessor的方法* <p>* 处理客户端心跳请求*/
public RemotingCommand heartBeat(ChannelHandlerContext ctx, RemotingCommand request) {//构建响应命令对象RemotingCommand response = RemotingCommand.createResponseCommand(null);//解码HeartbeatData heartbeatData = HeartbeatData.decode(request.getBody(), HeartbeatData.class);//构建客户端连接信息对象ClientChannelInfo clientChannelInfo = new ClientChannelInfo(ctx.channel(),heartbeatData.getClientID(),request.getLanguage(),request.getVersion());/** 1 循环遍历处理consumerDataSet,即处理consumer的心跳信息*/for (ConsumerData data : heartbeatData.getConsumerDataSet()) {//查找broker缓存的当前消费者组的订阅组配置SubscriptionGroupConfig subscriptionGroupConfig =this.brokerController.getSubscriptionGroupManager().findSubscriptionGroupConfig(data.getGroupName());boolean isNotifyConsumerIdsChangedEnable = true;//如果已存在订阅组if (null != subscriptionGroupConfig) {//当consumer发生改变的时候是否支持通知同组的所有consumer,默认true,即支持isNotifyConsumerIdsChangedEnable = subscriptionGroupConfig.isNotifyConsumerIdsChangedEnable();int topicSysFlag = 0;if (data.isUnitMode()) {topicSysFlag = TopicSysFlag.buildSysFlag(false, true);}//尝试创建重试topicString newTopic = MixAll.getRetryTopic(data.getGroupName());this.brokerController.getTopicConfigManager().createTopicInSendMessageBackMethod(newTopic,subscriptionGroupConfig.getRetryQueueNums(),PermName.PERM_WRITE | PermName.PERM_READ, topicSysFlag);}/** 注册consumer,返回consumer信息是否已发生改变* 如果发生了改变,Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端,要求进行重平衡操作*/boolean changed = this.brokerController.getConsumerManager().registerConsumer(data.getGroupName(),clientChannelInfo,data.getConsumeType(),data.getMessageModel(),data.getConsumeFromWhere(),data.getSubscriptionDataSet(),isNotifyConsumerIdsChangedEnable);if (changed) {//如果consumer信息发生了改变,打印日志log.info("registerConsumer info changed {} {}",data.toString(),RemotingHelper.parseChannelRemoteAddr(ctx.channel()));}}/** 2 循环遍历处理producerDataSet,即处理producer的心跳信息*/for (ProducerData data : heartbeatData.getProducerDataSet()) {/** 注册producer*/this.brokerController.getProducerManager().registerProducer(data.getGroupName(),clientChannelInfo);}//返回响应response.setCode(ResponseCode.SUCCESS);response.setRemark(null);return response;
}
1.4.1 registerConsumer注册消费者

循环遍历处理consumerDataSet集合, 对ConsumerData信息进行注册或者更改, 如果consumer信息修改的话, Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端, 进行重平衡操作。

/*** ConsumerManager的方法* <p>* 注册consumer,返回consumer信息是否已发生改变* 如果发生了改变,Broker会发送NOTIFY_CONSUMER_IDS_CHANGED请求给同组的所有consumer客户端,要求进行重平衡操作** @param group                            消费者组* @param clientChannelInfo                客户端连接信息* @param consumeType                      消费类型,PULL or PUSH* @param messageModel                     消息模式,集群 or 广播* @param consumeFromWhere                 启动消费位置* @param subList                          订阅信息数据* @param isNotifyConsumerIdsChangedEnable 一个consumer改变时是否通知该consumergroup中的所有consumer进行重平衡* @return 是否重平衡*/
public boolean registerConsumer(final String group, final ClientChannelInfo clientChannelInfo,ConsumeType consumeType, MessageModel messageModel, ConsumeFromWhere consumeFromWhere,final Set<SubscriptionData> subList, boolean isNotifyConsumerIdsChangedEnable) {//获取当前group对应的ConsumerGroupInfoConsumerGroupInfo consumerGroupInfo = this.consumerTable.get(group);//如果为null,那么新建一个ConsumerGroupInfo并存入consumerTableif (null == consumerGroupInfo) {ConsumerGroupInfo tmp = new ConsumerGroupInfo(group, consumeType, messageModel, consumeFromWhere);ConsumerGroupInfo prev = this.consumerTable.putIfAbsent(group, tmp);consumerGroupInfo = prev != null ? prev : tmp;}/** 1 更新连接*/boolean r1 =consumerGroupInfo.updateChannel(clientChannelInfo, consumeType, messageModel,consumeFromWhere);/** 2 更新订阅信息*/boolean r2 = consumerGroupInfo.updateSubscription(subList);/** 2 如果连接或者订阅信息有更新,并且允许通知,那么通知该consumergroup中的所有consumer进行重平衡*/if (r1 || r2) {if (isNotifyConsumerIdsChangedEnable) {//CHANGE事件this.consumerIdsChangeListener.handle(ConsumerGroupEvent.CHANGE, group, consumerGroupInfo.getAllChannel());}}// 3.注册订阅信息到ConsumerFilterManagerthis.consumerIdsChangeListener.handle(ConsumerGroupEvent.REGISTER, group, subList);return r1 || r2;
}

1.updateChannel更新连接

更新ConsumerGroup组对应的ConsumerGroupInfo的一些属性, 还会判断当前连接是否是新连接, 如果Broker此前没有该连接的信息, 那么表示有新的consumer连接到此broker, 需要通知当前ConsumerGroup的所有consumer进行重平衡。

/*** ConsumerGroupInfo的方法* <p>* 更新连接** @param infoNew          新连接信息* @param consumeType      消费类型,PULL or PUSH* @param messageModel     消息模式,集群 or 广播* @param consumeFromWhere 启动消费位置* @return 是否通知*/
public boolean updateChannel(final ClientChannelInfo infoNew, ConsumeType consumeType,MessageModel messageModel, ConsumeFromWhere consumeFromWhere) {boolean updated = false;//更新信息this.consumeType = consumeType;this.messageModel = messageModel;this.consumeFromWhere = consumeFromWhere;//根据当前连接获取channelInfoTable缓存中的连接信息ClientChannelInfo infoOld = this.channelInfoTable.get(infoNew.getChannel());//如果缓存中的连接信息为null,说明当前连接是一个新连接if (null == infoOld) {//存入缓存ClientChannelInfo prev = this.channelInfoTable.put(infoNew.getChannel(), infoNew);//长期按没有该连接信息,那么表示有新的consumer连接到此broekr,那么需要通知if (null == prev) {log.info("new consumer connected, group: {} {} {} channel: {}", this.groupName, consumeType,messageModel, infoNew.toString());updated = true;}infoOld = infoNew;} else {//异常情况if (!infoOld.getClientId().equals(infoNew.getClientId())) {log.error("[BUG] consumer channel exist in broker, but clientId not equal. GROUP: {} OLD: {} NEW: {} ",this.groupName,infoOld.toString(),infoNew.toString());this.channelInfoTable.put(infoNew.getChannel(), infoNew);}}//更新更新时间this.lastUpdateTimestamp = System.currentTimeMillis();infoOld.setLastUpdateTimestamp(this.lastUpdateTimestamp);return updated;
}

2.updateSubscription更新订阅信息

更新此ConsumerGroup组对应的订阅信息集合, 如果存在新增订阅的topic, 或者移除某个topic的订阅, 那么需要通知当前ConsumerGroup的所有consumer进行重平衡。

RocketMQ需要保证组内的所有消费者订阅的topic都必须一致。

  1. 方法首先遍历当前请求传递的订阅信息集合, 然后对于每个订阅的topic从subscriptionTable缓存中尝试获取, 获取不到则表示新增了topic订阅信息, 存入subscriptionTable。
  2. 遍历subscriptionTable集合, 判断每一个topic是否存在于当前请求传递的订阅信息集合中, 如果不存在, 表示consumer移除了topic的订阅, 那么当前topic的订阅信息会从subscriptionTable集合中被移除。
/*** ConsumerGroupInfo的方法* 更新订阅信息** @param subList 订阅信息集合*/
public boolean updateSubscription(final Set<SubscriptionData> subList) {boolean updated = false;//遍历订阅信息集合for (SubscriptionData sub : subList) {//根据订阅的topic在ConsumerGroup的subscriptionTable缓存中此前的订阅信息SubscriptionData old = this.subscriptionTable.get(sub.getTopic());//如果此前没有关于该topic的订阅信息,那么表示此topic为新增订阅if (old == null) {//存入subscriptionTableSubscriptionData prev = this.subscriptionTable.putIfAbsent(sub.getTopic(), sub);//此前没有关于该topic的订阅信息,那么表示此topic为新增订阅,那么需要通知if (null == prev) {updated = true;log.info("subscription changed, add new topic, group: {} {}",this.groupName,sub.toString());}} else if (sub.getSubVersion() > old.getSubVersion()) {//更新数据if (this.consumeType == ConsumeType.CONSUME_PASSIVELY) {log.info("subscription changed, group: {} OLD: {} NEW: {}",this.groupName,old.toString(),sub.toString());}this.subscriptionTable.put(sub.getTopic(), sub);}}/** 遍历ConsumerGroup的subscriptionTable缓存*/Iterator<Entry<String, SubscriptionData>> it = this.subscriptionTable.entrySet().iterator();while (it.hasNext()) {Entry<String, SubscriptionData> next = it.next();//获取此前订阅的topicString oldTopic = next.getKey();boolean exist = false;//判断当前的subList是否存在该topic的订阅信息for (SubscriptionData sub : subList) {//如果存在,则退出循环if (sub.getTopic().equals(oldTopic)) {exist = true;break;}}//当前的subList不存在该topic的订阅信息,说明consumer移除了对于该topic的订阅if (!exist) {log.warn("subscription changed, group: {} remove topic {} {}",this.groupName,oldTopic,next.getValue().toString());//移除数据it.remove();//那么需要通知updated = true;}}this.lastUpdateTimestamp = System.currentTimeMillis();return updated;
}

3.consumerIdsChangeListener.handle监听器通知

通知监听器处理对应的事件, 事件为ConsumerGroupEvent.CHANGE。

  1. 如果运行通知, 则遍历该ConsumerGroup的连接集合, 然后对每个连接调用notifyConsumerIdsChanged方法通知对应的客户端消费者执行负载均衡。

  2. notifyConsumerIdsChanged: broker发送客户端一个重平衡请求, Code为NOTIFY_CONSUMER_IDS_CHANGED。

/*** DefaultConsumerIdsChangeListener的方法* * 处理监听到的事件* @param event 事件* @param group 消费者组* @param args 参数*/
@Override
public void handle(ConsumerGroupEvent event, String group, Object... args) {if (event == null) {return;}switch (event) {//改变事件,需要通知该消费者组的每一个消费者case CHANGE:if (args == null || args.length < 1) {return;}//获取参数List<Channel> channels = (List<Channel>) args[0];//如果允许通知if (channels != null && brokerController.getBrokerConfig().isNotifyConsumerIdsChangedEnable()) {//遍历连接集合for (Channel chl : channels) {//通知该消费者客户端执行负载均衡this.brokerController.getBroker2Client().notifyConsumerIdsChanged(chl, group);}}break;case UNREGISTER:this.brokerController.getConsumerFilterManager().unRegister(group);break;case REGISTER:if (args == null || args.length < 1) {return;}Collection<SubscriptionData> subscriptionDataList = (Collection<SubscriptionData>) args[0];this.brokerController.getConsumerFilterManager().register(group, subscriptionDataList);break;default:throw new RuntimeException("Unknown event " + event);}
}/*** Broker2Client的方法* <p>* 通知消费者变更** @param channel       连接* @param consumerGroup 消费者组*/
public void notifyConsumerIdsChanged(final Channel channel,final String consumerGroup) {if (null == consumerGroup) {log.error("notifyConsumerIdsChanged consumerGroup is null");return;}//构建请求头NotifyConsumerIdsChangedRequestHeader requestHeader = new NotifyConsumerIdsChangedRequestHeader();requestHeader.setConsumerGroup(consumerGroup);//构建远程命令对象,请求code为NOTIFY_CONSUMER_IDS_CHANGEDRemotingCommand request =RemotingCommand.createRequestCommand(RequestCode.NOTIFY_CONSUMER_IDS_CHANGED, requestHeader);try {//发送单向请求,无需等待客户端回应this.brokerController.getRemotingServer().invokeOneway(channel, request, 10);} catch (Exception e) {log.error("notifyConsumerIdsChanged exception. group={}, error={}", consumerGroup, e.toString());}
}
1.4.2 客户端处理重平衡请求

broker的请求在客户端是通过ClientRemotingProcessor#processRequest处理的。

NOTIFY_CONSUMER_IDS_CHANGED请求通过客户端的ClientRemotingProcessor#notifyConsumerIdsChanged方法处理。

/*** ClientRemotingProcessor的方法* <p>* 处理来自远程服务端的请求*/
@Override
public RemotingCommand processRequest(ChannelHandlerContext ctx,RemotingCommand request) throws RemotingCommandException {switch (request.getCode()) {case RequestCode.CHECK_TRANSACTION_STATE:return this.checkTransactionState(ctx, request);case RequestCode.NOTIFY_CONSUMER_IDS_CHANGED://处理NOTIFY_CONSUMER_IDS_CHANGED请求return this.notifyConsumerIdsChanged(ctx, request);case RequestCode.RESET_CONSUMER_CLIENT_OFFSET:return this.resetOffset(ctx, request);case RequestCode.GET_CONSUMER_STATUS_FROM_CLIENT:return this.getConsumeStatus(ctx, request);case RequestCode.GET_CONSUMER_RUNNING_INFO:return this.getConsumerRunningInfo(ctx, request);case RequestCode.CONSUME_MESSAGE_DIRECTLY:return this.consumeMessageDirectly(ctx, request);case RequestCode.PUSH_REPLY_MESSAGE_TO_CLIENT:return this.receiveReplyMessage(ctx, request);default:break;}return null;
}

notifyConsumerIdsChanged客户端重平衡

客户端接口到broker的重平衡请求之后, 调用这个方法。内部仅仅是调用我们之前讲的rebalanceImmediately 方法唤醒负载均衡服务rebalanceService, 进行重平衡。

public RemotingCommand notifyConsumerIdsChanged(ChannelHandlerContext ctx,RemotingCommand request) throws RemotingCommandException {try {//解析请求头final NotifyConsumerIdsChangedRequestHeader requestHeader =(NotifyConsumerIdsChangedRequestHeader) request.decodeCommandCustomHeader(NotifyConsumerIdsChangedRequestHeader.class);//打印日志log.info("receive broker's notification[{}], the consumer group: {} changed, rebalance immediately",RemotingHelper.parseChannelRemoteAddr(ctx.channel()),requestHeader.getConsumerGroup());//熟悉的方法,立即进行重平衡this.mqClientFactory.rebalanceImmediately();} catch (Exception e) {log.error("notifyConsumerIdsChanged exception", RemotingHelper.exceptionSimpleDesc(e));}return null;
}

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

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

相关文章

stm32 mpu6050 cubemx DMP法读取角度

文章目录 前言一、相关文件二、cubemx配置三、代码变量初始化主循环 总结 前言 文件 记录使用dmp库来读取mpu6050的角度。 这是参考文件 参考1–主要参考 github参考 参考2 参考三 一、相关文件 相关文件在这里下载&#xff08;未填&#xff0c;不过可以在上面的git中下载&a…

LPython:最新的高性能Python实现、速度极快且支持多后端

LPython 是最新的开源 Python 实现&#xff0c;目标是打造高性能版本的 Python。 LPython 官网写道&#xff0c;它一直作为 Python 编译器在开发&#xff0c;能够生成优化的机器代码。LPython 的后端支持 LLVM、C/C 翻译&#xff0c;甚至还支持 WebAssembly (WASM)。 LPython 主…

从0到1开发go-tcp框架【3-读写协程分离、引入消息队列、进入连接管理器、引入连接属性】【基础篇完结】

从0到1开发go-tcp框架【3-读写协程分离、引入消息队列、进入连接管理器、引入连接属性】 1 读写协程分离[v0.7] 添加一个Reader和Writer之间通信的channel添加一个Writer goroutineReader由之前直接发送给客户端改为发送给通信channel启动Reader和Writer一起工作 zinx/znet/co…

使用火山云搜索ESCloud服务构建图文检索应用(以文搜图/以图搜图)

图文检索在生活中具有广泛的应用&#xff0c;常见的图片检索包括基于文本内容搜索和基于图片内容搜索。用户通过输入文字描述或上传图片就可以在海量的图片库中快速找到同款或者相似图片&#xff0c;这种搜索方式被广泛应用于电商、广告、设计以及搜索引擎等热门领域。 本文基…

在CSDN学Golang场景化解决方案(微服务架构设计)

一&#xff0c;聚合器微服务设计模式 在Golang微服务架构设计中&#xff0c;聚合器&#xff08;Aggregator&#xff09;微服务设计模式是一种常见的应用程序体系结构模式。该模式旨在简化客户端与后端微服务之间的通信&#xff0c;并支持更高级别的操作&#xff0c;例如聚合多…

Vue3文本省略(Ellipsis)

APIs 参数说明类型默认值必传maxWidth文本最大宽度number | string‘100%’falseline最大行数numberundefinedfalsetrigger展开的触发方式‘click’undefinedfalsetooltip是否启用文本提示框booleantruefalsetooltipMaxWidth提示框内容最大宽度&#xff0c;单位px&#xff0c;…

亚马逊云科技发布Amazon HealthScribe,使用生成式AI技术实现临床文档的自动生成

近日&#xff0c;亚马逊云科技在纽约峰会上推出了Amazon HealthScribe&#xff0c;该服务符合HIPAA&#xff08;《健康保险流通与责任法案》&#xff09;的相关要求&#xff0c;可为医疗软件供应商提供一种基于语音和文字识别的生成式AI技术&#xff0c;帮助其创建能够自动生成…

简-理解Python的装饰器、迭代器和生成器底层原理

装饰器、迭代器和生成器是 Python 中的高级功能&#xff0c;它们分别用于扩展函数或方法的功能、遍历容器元素和创建简洁的迭代器。 装饰器 装饰器是一个用于修改或扩展函数或方法的功能的函数。装饰器接受一个函数作为参数&#xff0c;并返回一个新的函数&#xff0c;新函数通…

APUE学习62章终端(二): stty命令特殊字符终端标志

1. stty命令 stty命令的英文解释: 很明显stty有一个-F参数 所以准确的说: stty命令是设置当前终端驱动程序(也有可能直接配置了硬件&#xff0c;这点目前不清楚)的属性&#xff0c;使当前终端的驱动程序能够使能/去使能一些特殊字符的识别与处理等等 2. stty命令的结构 3. 终端…

使用vuex让购物车联动

// 1.vuex点击加减触发函数提交仓库把我们请求的数据存到仓库 2.在仓库定义这个函数和对象 把我们存进去的数据存起来 // 3。在我们需要的页面拿出数据&#xff0c;然后循环就可以 // 4.当我们点击加号就触发函数然后在vuex对这个数据进行处理 // 5.对我们点进来的数据进行一个…

【SLAM】LoFTR知多少

1. LoFTR: Detector-Free Local Feature Matching with Transformers PAPER 论文 | LoFTR: Detector-Free Local Feature Matching with Transformers 代码 | CODE: 关键词 | detector-free, local feature matching LoFTR知多少 1. LoFTR: Detector-Free Local Feature M…

O3DE的Pass

Pass介绍 Pass是具有输入和输出的渲染过程。 在最终渲染帧中看到的每个细节都是通过一系列Pass&#xff08;前一个Pass的输出是下一个Pass的输入&#xff09;计算出来的。Pass可以生成图像&#xff08;作为纹理、缓冲区或渲染目标&#xff09;。每个图像都包含关于场景的特定…

不藏了!极狐GitLab 向你介绍一位研发效能「六边形战士」

怎么用数字说清研发效能&#xff1f; 总是觉得研发资源不够用&#xff1f; 高效工作全靠领导盯&#xff1f; 不问不知道项目推迟发布&#xff1f; 代码泄漏了才紧急采取措施&#xff1f; 是时候甩开这些“研发人的 PTSD”了&#x1f623; 极狐星&#xff0c;给专业的你更…

Unity Shader:常用的C#与shader交互的方法

俗话说久病成医&#xff0c;虽然不是专业技术美术&#xff0c;但代码写久了自然会积累一些常用的shader交互方法。零零散散的&#xff0c;总结如下&#xff1a; 1&#xff0c;改变UGUI的材质球属性 有时候我们需要改变ui的一些属性&#xff0c;从而实现想要的效果。通常UGUI上…

Spring如何通过三级缓存解决循环依赖问题?

目录 一、什么是Spring 二、循环依赖问题 三、三级缓存机制 四、如何通过三级缓存解决循环依赖问题 一、什么是Spring Spring框架是一个开源的Java应用程序开发框架&#xff0c;提供了一种全面的、一致的编程模型&#xff0c;用于构建企业级应用程序和服务。它由Rod Johnso…

多个List 合并变成一个List+一个List 根据某个字段相等的另一个字段相加,并排序变成新的List

List<CurveTimeAndValueDomain> curves new ArrayList<>();for (int i 0; i < columnNames.size(); i){if (columnNames.get(i).equals(PlantConstant.TENDOWNFX) || columnNames.get(i).equals(PlantConstant.TENDOWNQP)) {//10千伏以下 数据 进行缓慢处理cu…

代码随想录算法训练营第五十三天 | 1143.最长公共子序列、1035.不相交的线、53.最大子数组和

文章目录 一、1143.最长公共子序列二、1035.不相交的线三、最大子数组和 一、1143.最长公共子序列 题目链接 代码如下&#xff1a; class Solution { public:int longestCommonSubsequence(string text1, string text2) {vector<vector<int>> dp (text1.size() …

Stable Diffusion中人物生成相关的negative prompts

下面是常用的negative prompt&#xff0c;在使用stable Diffusion webui等工具生成时可以填入。 bad anatomy, bad proportions, blurry, cloned face, deformed, disfigured, duplicate, extra arms, extra fingers, extra limbs, extra legs, fused fingers, gross proporti…

深度学习(32)——CycleGAN(1)

深度学习&#xff08;32&#xff09;——CycleGAN&#xff08;1&#xff09; 文章目录 深度学习&#xff08;32&#xff09;——CycleGAN&#xff08;1&#xff09;1. GAN原理2. CycleGAN&#xff08;1&#xff09;原理&#xff08;2&#xff09;核心思想&#xff08;3&#xf…

git回退到指定版本

#首先使用该方法查看版本号&#xff0c;后面可以接-n,n为数量&#xff0c;指定展示前几个版本记录。 git log &#xff08;或 git reflog&#xff09;#xxx换成你想回退的版本号。 git reset --hard xxx#强制推送到远程仓库&#xff0c;让远程仓库版本号一致。 git push origin…