RocketMQ源码阅读-九-自定义过滤规则Flitersrv

RocketMQ源码阅读-九-自定义过滤规则Flitersrv

  • 什么是Filtersrv
  • Filtersrv注册到Broker
  • 过滤类
    • Consumer发起订阅设置过滤类代码
    • Consumer上传过滤类代码
    • Flitersrv编译过滤类代码
  • 过滤消息
    • Consumer 从 Filtersrv 拉取消息
    • Flitersrv从Broker拉取消息
  • Flitersrv的高可用
  • 总结

什么是Filtersrv

Filtersrv ,负责自定义规则过滤 Consumer 从 Broker 拉取的消息。
其在系统中的位置如下图:
image.png

Filtersrv优点:

  1. 减少了 Broker 的负担
  2. 减少了 Consumer 接收无用的消息

缺点:

  1. 多了一层 Filtersrv 网络开销

Filtersrv注册到Broker

Flitersrv与Broker的对应关系为:

  • 一个Flitersrv对应一个Broker
  • 一个Broker对应多个Flitersrv

Flitersrv的高可用:

  • 启动多个Flitersrv
  • Flitersrv注册失败会自动退出

Flitersrv注册到Broker的核心代码在FiltersrvController类中,其初始化源码如下:

public boolean initialize() {MixAll.printObjectProperties(log, this.filtersrvConfig);this.remotingServer = new NettyRemotingServer(this.nettyServerConfig);this.remotingExecutor =Executors.newFixedThreadPool(nettyServerConfig.getServerWorkerThreads(),new ThreadFactoryImpl("RemotingExecutorThread_"));this.registerProcessor();// 固定间隔注册到Brokerthis.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {FiltersrvController.this.registerFilterServerToBroker();}}, 15, 10, TimeUnit.SECONDS);this.defaultMQPullConsumer.setBrokerSuspendMaxTimeMillis(this.defaultMQPullConsumer.getBrokerSuspendMaxTimeMillis() - 1000);this.defaultMQPullConsumer.setConsumerTimeoutMillisWhenSuspend(this.defaultMQPullConsumer.getConsumerTimeoutMillisWhenSuspend() - 1000);this.defaultMQPullConsumer.setNamesrvAddr(this.filtersrvConfig.getNamesrvAddr());this.defaultMQPullConsumer.setInstanceName(String.valueOf(UtilAll.getPid()));return true;
}

调用了FiltersrvController#registerFilterServerToBroker:

public void registerFilterServerToBroker() {try {RegisterFilterServerResponseHeader responseHeader =this.filterServerOuterAPI.registerFilterServerToBroker(this.filtersrvConfig.getConnectWhichBroker(), this.localAddr());this.defaultMQPullConsumer.getDefaultMQPullConsumerImpl().getPullAPIWrapper().setDefaultBrokerId(responseHeader.getBrokerId());if (null == this.brokerName) {this.brokerName = responseHeader.getBrokerName();}log.info("register filter server<{}> to broker<{}> OK, Return: {} {}",this.localAddr(),this.filtersrvConfig.getConnectWhichBroker(),responseHeader.getBrokerName(),responseHeader.getBrokerId());} catch (Exception e) {log.warn("register filter server Exception", e);log.warn("access broker failed, kill oneself");System.exit(-1); // 异常退出}
}

此方法会去 注册Filtersrv 到 Broker,如果注册失败,会关闭Filtersrv
其中注册Flitersrv到Broker调用的FilterServerOuterAPI的registerFilterServerToBroker方法:

public RegisterFilterServerResponseHeader registerFilterServerToBroker(final String brokerAddr,final String filterServerAddr
) throws RemotingCommandException, RemotingConnectException, RemotingSendRequestException,
RemotingTimeoutException, InterruptedException, MQBrokerException {RegisterFilterServerRequestHeader requestHeader = new RegisterFilterServerRequestHeader();requestHeader.setFilterServerAddr(filterServerAddr);RemotingCommand request =RemotingCommand.createRequestCommand(RequestCode.REGISTER_FILTER_SERVER, requestHeader);RemotingCommand response = this.remotingClient.invokeSync(brokerAddr, request, 3000);assert response != null;switch (response.getCode()) {case ResponseCode.SUCCESS: {RegisterFilterServerResponseHeader responseHeader =(RegisterFilterServerResponseHeader) response.decodeCommandCustomHeader(RegisterFilterServerResponseHeader.class);return responseHeader;}default:break;}throw new MQBrokerException(response.getCode(), response.getRemark());
}

此方法构建注册请求,将filterServerAddr注册到Broker。

过滤类

Consumer会上传过滤类代码给Flitersrv,然后Flitersrv编译过滤类代码使用。其关系如下图:
image.png

Consumer发起订阅设置过滤类代码

Consumer在发起订阅时,可以进行过滤类代码的设置,DefaultMQPushConsumer#subscribe:

// 将主题订阅到消费订阅。
// 参数:
// topic – 要消费的主题。 
// fullClassName – 全类名,必须扩展 org.apache.rocketmq.common.filter。消息过滤器 
// filterClassSource – 类源代码,采用UTF-8文件编码,必须对你的代码安全负责
@Override
public void subscribe(String topic, String fullClassName, String filterClassSource) throws MQClientException {this.defaultMQPushConsumerImpl.subscribe(topic, fullClassName, filterClassSource);
}

Consumer上传过滤类代码

在 Consumer 心跳注册到 Broker 的同时,上传 过滤类代码 到 Broker 对应的所有 Filtersrv。类MQClientInstance核心代码如下:

public void sendHeartbeatToAllBrokerWithLock() {if (this.lockHeartbeat.tryLock()) {try {// 发送心跳到Brokerthis.sendHeartbeatToAllBroker();// 上传过滤类源码到Filtersrvthis.uploadFilterClassSource();} catch (final Exception e) {log.error("sendHeartbeatToAllBroker exception", e);} finally {this.lockHeartbeat.unlock();}} else {log.warn("lock heartBeat, but failed.");}
}

上传过滤类源码到Filtersrv调用方法MQClientInstance#uploadFilterClassSource:

/*** 上传过滤类到Filtersrv*/
private void uploadFilterClassSource() {Iterator<Entry<String, MQConsumerInner>> it = this.consumerTable.entrySet().iterator();while (it.hasNext()) {Entry<String, MQConsumerInner> next = it.next();MQConsumerInner consumer = next.getValue();if (ConsumeType.CONSUME_PASSIVELY == consumer.consumeType()) {Set<SubscriptionData> subscriptions = consumer.subscriptions();for (SubscriptionData sub : subscriptions) {if (sub.isClassFilterMode() && sub.getFilterClassSource() != null) {final String consumerGroup = consumer.groupName();final String className = sub.getSubString();final String topic = sub.getTopic();final String filterClassSource = sub.getFilterClassSource();try {this.uploadFilterClassToAllFilterServer(consumerGroup, className, topic, filterClassSource);} catch (Exception e) {log.error("uploadFilterClassToAllFilterServer Exception", e);}}}}}
}

Flitersrv编译过滤类代码

Filtersrv 编译使用 Consumer 上传的 过滤类代码。核心代码在FilterClassManager#registerFilterClass:

/*** 注册过滤类** @param consumerGroup 消费分组* @param topic Topic* @param className 过滤类名* @param classCRC 过滤类源码CRC* @param filterSourceBinary 过滤类源码* @return 是否注册成功*/
public boolean registerFilterClass(final String consumerGroup, final String topic,final String className, final int classCRC, final byte[] filterSourceBinary) {final String key = buildKey(consumerGroup, topic);// 判断是否要注册新的过滤类boolean registerNew = false;FilterClassInfo filterClassInfoPrev = this.filterClassTable.get(key);if (null == filterClassInfoPrev) {registerNew = true;} else {if (this.filtersrvController.getFiltersrvConfig().isClientUploadFilterClassEnable()) {if (filterClassInfoPrev.getClassCRC() != classCRC && classCRC != 0) { // 类有变化registerNew = true;}}}// 注册新的过滤类if (registerNew) {synchronized (this.compileLock) {filterClassInfoPrev = this.filterClassTable.get(key);if (null != filterClassInfoPrev && filterClassInfoPrev.getClassCRC() == classCRC) {return true;}try {FilterClassInfo filterClassInfoNew = new FilterClassInfo();filterClassInfoNew.setClassName(className);filterClassInfoNew.setClassCRC(0);filterClassInfoNew.setMessageFilter(null);if (this.filtersrvController.getFiltersrvConfig().isClientUploadFilterClassEnable()) {String javaSource = new String(filterSourceBinary, MixAll.DEFAULT_CHARSET);// 编译新的过滤类Class<?> newClass = DynaCode.compileAndLoadClass(className, javaSource);// 创建新的过滤类对象Object newInstance = newClass.newInstance();filterClassInfoNew.setMessageFilter((MessageFilter) newInstance);filterClassInfoNew.setClassCRC(classCRC);}this.filterClassTable.put(key, filterClassInfoNew);} catch (Throwable e) {String info = String.format("FilterServer, registerFilterClass Exception, consumerGroup: %s topic: %s className: %s",consumerGroup, topic, className);log.error(info, e);return false;}}}return true;
}

此方法,将传进来的源代码,编译生成一个MessageFilter示例,保存在filterClassTable中。

过滤消息

再次回顾Flitersrv在整体中的位置:

Consumer从Broker拉取消息的时候,会经过Flitersrv过滤消息。实际上是从Flitersrv拉取消息。

Consumer 从 Filtersrv 拉取消息

Consumer 拉取 使用过滤类方式订阅 的消费消息时,从 Broker 对应的 Filtersrv 列表随机选择一个拉取消息。如果选择不到 Filtersrv,则无法拉取消息。因此,Filtersrv 一定要做高可用。
拉取消息的核心代码在类PullAPIWrapper#pullKernelImpl方法中:

/*** 拉取消息核心方法** @param mq 消息队列* @param subExpression 订阅表达式* @param subVersion 订阅版本号* @param offset 拉取队列开始位置* @param maxNums 批量拉取消息数量* @param sysFlag 拉取系统标识* @param commitOffset 提交消费进度* @param brokerSuspendMaxTimeMillis broker挂起请求最大时间* @param timeoutMillis 请求broker超时时间* @param communicationMode 通讯模式* @param pullCallback 拉取回调* @return 拉取消息结果。只有通讯模式为同步时,才返回结果,否则返回null。* @throws MQClientException 当寻找不到 broker 时,或发生其他client异常* @throws RemotingException 当远程调用发生异常时* @throws MQBrokerException 当 broker 发生异常时。只有通讯模式为同步时才会发生该异常。* @throws InterruptedException 当发生中断异常时*/
protected PullResult pullKernelImpl(final MessageQueue mq,final String subExpression,final long subVersion,final long offset,final int maxNums,final int sysFlag,final long commitOffset,final long brokerSuspendMaxTimeMillis,final long timeoutMillis,final CommunicationMode communicationMode,final PullCallback pullCallback
) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {// 获取Broker信息FindBrokerResult findBrokerResult =this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),this.recalculatePullFromWhichNode(mq), false);if (null == findBrokerResult) {this.mQClientFactory.updateTopicRouteInfoFromNameServer(mq.getTopic());findBrokerResult =this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(),this.recalculatePullFromWhichNode(mq), false);}// 请求拉取消息if (findBrokerResult != null) {int sysFlagInner = sysFlag;if (findBrokerResult.isSlave()) {sysFlagInner = PullSysFlag.clearCommitOffsetFlag(sysFlagInner);}PullMessageRequestHeader requestHeader = new PullMessageRequestHeader();requestHeader.setConsumerGroup(this.consumerGroup);requestHeader.setTopic(mq.getTopic());requestHeader.setQueueId(mq.getQueueId());requestHeader.setQueueOffset(offset);requestHeader.setMaxMsgNums(maxNums);requestHeader.setSysFlag(sysFlagInner);requestHeader.setCommitOffset(commitOffset);requestHeader.setSuspendTimeoutMillis(brokerSuspendMaxTimeMillis);requestHeader.setSubscription(subExpression);requestHeader.setSubVersion(subVersion);// 若订阅topic使用过滤类,使用filtersrv获取消息String brokerAddr = findBrokerResult.getBrokerAddr();if (PullSysFlag.hasClassFilterFlag(sysFlagInner)) {brokerAddr = computPullFromWhichFilterServer(mq.getTopic(), brokerAddr);}PullResult pullResult = this.mQClientFactory.getMQClientAPIImpl().pullMessage(brokerAddr,requestHeader,timeoutMillis,communicationMode,pullCallback);return pullResult;}// Broker信息不存在,则抛出异常throw new MQClientException("The broker[" + mq.getBrokerName() + "] not exist", null);
}

代码第68行,计算从哪个Flitersrv拉取消息,PullAPIWrapper#computPullFromWhichFilterServer:

/*** 计算filtersrv地址。如果有多个filtersrv,随机选择一个。** @param topic Topic* @param brokerAddr broker地址* @return filtersrv地址* @throws MQClientException 当filtersrv不存在时*/
private String computPullFromWhichFilterServer(final String topic, final String brokerAddr)
throws MQClientException {ConcurrentHashMap<String, TopicRouteData> topicRouteTable = this.mQClientFactory.getTopicRouteTable();if (topicRouteTable != null) {TopicRouteData topicRouteData = topicRouteTable.get(topic);List<String> list = topicRouteData.getFilterServerTable().get(brokerAddr);if (list != null && !list.isEmpty()) {return list.get(randomNum() % list.size());}}throw new MQClientException("Find Filter Server Failed, Broker Addr: " + brokerAddr + " topic: "+ topic, null);
}

Flitersrv从Broker拉取消息

Flitersrv 向 Broker 拉取消息时,实际使用的 DefaultMQPullConsumer.java 的方法和逻辑:DefaultRequestProcessor#pullMessageForward:

/*** 拉取消息** @param ctx 拉取消息context* @param request 拉取消息请求* @return 响应* @throws Exception 当发生异常时*/
private RemotingCommand pullMessageForward(final ChannelHandlerContext ctx, final RemotingCommand request) throws Exception {final RemotingCommand response = RemotingCommand.createResponseCommand(PullMessageResponseHeader.class);final PullMessageResponseHeader responseHeader = (PullMessageResponseHeader) response.readCustomHeader();final PullMessageRequestHeader requestHeader =(PullMessageRequestHeader) request.decodeCommandCustomHeader(PullMessageRequestHeader.class);final FilterContext filterContext = new FilterContext();filterContext.setConsumerGroup(requestHeader.getConsumerGroup());response.setOpaque(request.getOpaque());DefaultMQPullConsumer pullConsumer = this.filtersrvController.getDefaultMQPullConsumer();// 校验Topic过滤类是否完整final FilterClassInfo findFilterClass = this.filtersrvController.getFilterClassManager().findFilterClass(requestHeader.getConsumerGroup(), requestHeader.getTopic());if (null == findFilterClass) {response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark("Find Filter class failed, not registered");return response;}if (null == findFilterClass.getMessageFilter()) {response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark("Find Filter class failed, registered but no class");return response;}// 设置下次请求从 Broker主节点。responseHeader.setSuggestWhichBrokerId(MixAll.MASTER_ID);MessageQueue mq = new MessageQueue();mq.setTopic(requestHeader.getTopic());mq.setQueueId(requestHeader.getQueueId());mq.setBrokerName(this.filtersrvController.getBrokerName());long offset = requestHeader.getQueueOffset();int maxNums = requestHeader.getMaxMsgNums();final PullCallback pullCallback = new PullCallback() {@Overridepublic void onSuccess(PullResult pullResult) {responseHeader.setMaxOffset(pullResult.getMaxOffset());responseHeader.setMinOffset(pullResult.getMinOffset());responseHeader.setNextBeginOffset(pullResult.getNextBeginOffset());response.setRemark(null);switch (pullResult.getPullStatus()) {case FOUND:response.setCode(ResponseCode.SUCCESS);List<MessageExt> msgListOK = new ArrayList<MessageExt>();try {for (MessageExt msg : pullResult.getMsgFoundList()) {// 使用过滤类过滤消息boolean match = findFilterClass.getMessageFilter().match(msg, filterContext);if (match) {msgListOK.add(msg);}}if (!msgListOK.isEmpty()) {returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, msgListOK);return;} else {response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);}} catch (Throwable e) {final String error =String.format("do Message Filter Exception, ConsumerGroup: %s Topic: %s ",requestHeader.getConsumerGroup(), requestHeader.getTopic());log.error(error, e);response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark(error + RemotingHelper.exceptionSimpleDesc(e));returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);return;}break;case NO_MATCHED_MSG:response.setCode(ResponseCode.PULL_RETRY_IMMEDIATELY);break;case NO_NEW_MSG:response.setCode(ResponseCode.PULL_NOT_FOUND);break;case OFFSET_ILLEGAL:response.setCode(ResponseCode.PULL_OFFSET_MOVED);break;default:break;}returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);}@Overridepublic void onException(Throwable e) {response.setCode(ResponseCode.SYSTEM_ERROR);response.setRemark("Pull Callback Exception, " + RemotingHelper.exceptionSimpleDesc(e));returnResponse(requestHeader.getConsumerGroup(), requestHeader.getTopic(), ctx, response, null);return;}};// 拉取消息pullConsumer.pullBlockIfNotFound(mq, null, offset, maxNums, pullCallback);return null;
}

此方法通过DefaultMQPullConsumer执行消息拉取,在回调方法PullCallback中(上述代码62行),执行过滤逻辑,没被过滤的,才保存。

Flitersrv的高可用

通过多个Flitersrv完成高可用,多个Flitersrv部署的示意图如下:
image.png

  • 一个Consumer对应多个Flitersrv
  • 一个Flitersrv对应一个Broker
  • 一个Broker对应多个Flitersrv

Consumer会从所有Broker对应的Flitersrv中随即选择一个进行消息拉取。

总结

Flitersrv为了减少Broker的负担,且减少Consumer接收无用消息而生。
Flitersrv作为中间层,Consumer订阅时传过滤类给Broker,Broker将过滤类传给Flitersrv,Flitersrv处理并实例化过滤类。
消息拉取时,Consumer向Flitersrv拉取消息,Flitersrv先向Broker拉取消息,然后经过过滤类的过滤,再将满足条件的消息传给Consumer。

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

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

相关文章

【C++】内存分区模型

目录 1.程序运行前 2.程序运行后 3. new操作符 3.1 基本语法 3.2 开辟数组 C程序在执行时&#xff0c;将内存大方向划分为4个区域 代码区&#xff1a;存放函数体的二进制代码&#xff0c;由操作系统进行管理的 全局区&#xff1a; 存放全局变量和静态变量以及常量栈区 …

时序分解 | MATLAB实现CEEMDAN+SE自适应经验模态分解+样本熵计算

时序分解 | MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算 目录 时序分解 | MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现CEEMDANSE自适应经验模态分解样本熵计算 包括频谱图 附赠案例数据 可直接运行 …

喜报|「云原生数据库PolarDB」、「阿里云瑶池一站式数据管理平台」揽获“2023技术卓越奖”

日前&#xff0c;国内知名IT垂直媒体&技术社区IT168公布2023年“技术卓越奖”评选结果&#xff0c;经由行业CIO/CTO大咖、技术专家及IT媒体三方的联合严格评审&#xff0c;阿里云瑶池数据库揽获两项大奖&#xff1a;云原生数据库PolarDB荣获“2023年度技术卓越奖”&#xf…

YOLOv5全网独家首发:Powerful-IoU更好、更快的收敛IoU,效果秒杀CIoU、GIoU等 | 2024年最新IoU

💡💡💡本文独家改进:Powerful-IoU更好、更快的收敛IoU,是一种结合了目标尺寸自适应惩罚因子和基于锚框质量的梯度调节函数的损失函数 💡💡💡MS COCO和PASCAL VOC数据集实现涨点 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/category_1251193…

《SPSS统计学基础与实证研究应用精解》视频讲解:数据加权处理

《SPSS统计学基础与实证研究应用精解》4.7 视频讲解 视频为《SPSS统计学基础与实证研究应用精解》张甜 杨维忠著 清华大学出版社 一书的随书赠送视频讲解4.7节内容。本书已正式出版上市&#xff0c;当当、京东、淘宝等平台热销中&#xff0c;搜索书名即可。本书旨在手把手教会使…

Redis 面试题 | 11.精选Redis高频面试题

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

CNN卷积理解

1 卷积的步骤 1 过滤器&#xff08;卷积核&#xff09;&#xff08;Filter或Kernel&#xff09;&#xff1a; 卷积层使用一组可学习的过滤器来扫描输入数据&#xff08;通常是图像&#xff09;。每个过滤器都是一个小的窗口&#xff0c;包含一些权重&#xff0c;这些权重通过训…

小红书如何做混部?

作者&#xff1a;宋泽辉&#xff08;小红书&#xff09;、张佐玮&#xff08;阿里云&#xff09; 编者按&#xff1a; Koordinator 是一个开源项目&#xff0c;是基于阿里巴巴内部多年容器调度、混部实践经验孵化诞生&#xff0c;是行业首个生产可用、面向大规模场景的开源混…

在 Linux 上搭建 Java 环境

目录 一、安装jdk 1. 挑选 jdk 版本 2. 安装 3. 验证 jdk 二、安装tomcat 1. 下载压缩包 2. 上传压缩包给 Linux &#xff08;需要用到 rz 命令&#xff09; 3. 解压压缩包&#xff08;需要用到 unzip&#xff09; 4. 进入 bin 目录 5. 给启动脚本增加可执行权限 6. 启…

Internet Download Manager 6.42.3 (IDM) 中文破解免激活绿色版

Internet Download Manager 6.42.3中文破解版&#xff0c;全球最佳下载利器。Internet Download Manager (简称IDM) 是一款Windows 平台功能强大的多线程下载工具&#xff0c;国外非常受欢迎。支持断点续传&#xff0c;支持嗅探视频音频&#xff0c;接管所有浏览器&#xff0c;…

将AWS iot消息数据发送S3

观看此文章之前&#xff0c;请先学习AWS iot的数据收集&#xff1a; 使用Linux SDK客户端向AWS Iot发送数据-CSDN博客 上述的文章向大家展示了如何从客户端向AWS iot发送数据&#xff0c;那么数据收到之后&#xff0c;我们如何通过AWS的服务进行数据处理或者保存呢&#xff1…

jmeter之接口测试实现参数化(利用函数助手),参数值为1-9(自增的数字)

1.前言 思考&#xff1a;为什么不用postman&#xff0c;用postman的话就得导入csv文件/json文件 如果不想导入文件&#xff0c;postman是实现不了&#xff0c;因为postman每次只会运行一次 2.jmeter函数助手实现参数化 &#xff08;1&#xff09;新建“线程组”--新建“http…

cartopy在地图中添加经纬线

文章目录 gridlines方法定义 gridlines gridlines可以根据坐标系&#xff0c;自动绘制网格线&#xff0c;这对于普通绘图来说显然不必单独拿出来说说&#xff0c;但在地图中&#xff0c;经纬线几乎是必不可少的&#xff0c;而随着投影方式的不同&#xff0c;经纬线未必与坐标框…

EasyCVR视频融合平台雪亮工程视频智能监控方案设计与应用

随着科技的不断发展&#xff0c;视频监控已经成为城市安全防范的重要手段之一。为了提高城市安全防范水平&#xff0c;各地纷纷开展“雪亮工程”&#xff0c;即利用视频智能监控技术&#xff0c;实现对城市各个角落的全方位、全天候监控。本文将介绍一种雪亮工程视频智能监控方…

【赠书第19期】跨平台机器学习:ML.NET架构及应用编程

文章目录 前言 1 ML.NET架构 1.1 简介 1.2 架构特点 2 应用编程 2.1 数据处理 2.2 模型训练 2.3 模型评估 3 优势与展望 3.1 优势 3.2 展望 4 推荐图书 5 粉丝福利 前言 随着大数据时代的来临&#xff0c;机器学习技术在各个领域的应用越来越广泛。为了满足不同平…

龙年快乐,大烟花

烟花秀&#xff1a; 源码&#xff1a; <!DOCTYPE html> <html lang"en" > <head><meta charset"UTF-8"><title>2024新年快乐&#xff01;万事如意&#xff01;</title><meta name"viewport" content&q…

二、mongoose的使用,实现用户集合的操作

前言 mongodb&#xff1a;为了在node应用中与MongoDB交互&#xff0c;开发者需要使用MongoDB的驱动程序&#xff0c;所以安装的mongodb就是其驱动程序&#xff1b; mongoose: 是一个用于 MongoDB 的对象建模工具&#xff0c;提供了一个丰富的查询语言和许多其他功能&#xff0c…

Javadoc的讲解使用

概述&#xff1a;JavaDoc 是用于生成 Java 代码文档的工具。通过编写 JavaDoc 注释&#xff0c;可以为代码中的类、接口、方法、字段等元素添加文档注释&#xff0c;这些注释将被 JavaDoc 工具解析并生成相应的 HTML 文档。 目录 讲解 使用 结果 讲解 下面是一些关于 Java…

VScode通过SSH连接远程服务器

一. 在VScode上安装SSH插件 直接在VScode应用商店搜索安装即可: 二. 登陆服务器的root用户 使用命令"su -"或者"sudo -i -u root"都可以。 三.用vim编辑器打开服务器的SSH配置文件,把PasswordAuthentication后面的no改为yes&#xff0c;表示SSH允许远程密…

day21 事件流、事件捕获、事件冒泡、阻止冒泡、解绑事件

目录 事件流总结&#xff1a; 事件捕获示例&#xff1a; 事件冒泡阻止冒泡&#xff08;阻断事件流动传播&#xff0c;不止在冒泡阶段有效&#xff0c;捕获阶段也有效&#xff09;解绑事件鼠标经过事件的区别&#xff1a; 事件流 事件流指的是事件完整执行过程中的流动路径。 事…