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

news2024/11/17 11:25:19

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();

    // 固定间隔注册到Broker
    this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public 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 {
            // 发送心跳到Broker
            this.sendHeartbeatToAllBroker();
            // 上传过滤类源码到Filtersrv
            this.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() {

        @Override
        public 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);
        }

        @Override
        public 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.coloradmin.cn/o/1411008.html

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

相关文章

电容主要特点和作用,不同类型的电容区别

电容 两个相互靠近的金属板中间夹一层绝缘介质组成的器件&#xff0c;当两端存在电势差时&#xff0c;由于介质阻碍了电荷移动而累积在金属板上&#xff0c;衡量金属板上储存电荷的能力称之为电容&#xff0c;相应的器件称为电容器。 电容&#xff08;Capacitance&#xff09…

PyTorch 中的nn.Conv2d 类

nn.Conv2d 是 PyTorch 中的一个类&#xff0c;代表二维卷积层&#xff08;2D Convolution Layer&#xff09;。这个类广泛用于构建卷积神经网络&#xff08;CNN&#xff09;&#xff0c;特别是在处理图像数据时。 基本概念 卷积: 在神经网络的上下文中&#xff0c;卷积是一种特…

关于mysql默认禁用本地数据加载的情况处理(秒解决)

1.首先报错信息&#xff1a;ERROR 3948 (42000): Loading local data is disabled; this must be enabled on both the client and server sides 2.排查问题&#xff1a; 先检查local_infile的状态&#xff1a;使用sql指令 show global variables like local_infile; 这种情…

Keycloak - docker 运行 前端集成

Keycloak - docker 运行 & 前端集成 这里的记录主要是跟我们的项目相关的一些本地运行/测试&#xff0c;云端用的 keycloak 版本不一样&#xff0c;不过本地我能找到的最简单的配置是这样的 docker 配置 & 运行 keycloak keycloak 有官方(Red Hat Inc.)的镜像&#…

如何在ubuntu22.04安装ROS2

ubuntu22.04安装ROS2 教程 选择对应版本进行安装设置编码添加源安装ROS2设置环境变量 运行ROS2 选择对应版本 通过官方网站&#xff0c;查询Ubuntu与ros对应的版本&#xff0c;版本不一致也会出现安装不成功。 https://wiki.ros.org/ROS/Installation 每一个都可以进行点击&a…

Windows10上使Git Bash支持rsync命令操作步骤

rsync命令是linux上常用的工具之一&#xff0c;用于远程以及本地系统中拷贝/同步文件和文件夹。 Windows Git Bash默认并不支持rsync&#xff0c;如下图所示&#xff1a; 使Git Bash支持rsync命令操作步骤&#xff1a; 1.从https://repo.msys2.org/msys/x86_64/ 下…

flutter-相关个人记录

1、flutter 安卓打包打包报错 flutter build apk -v --no-tree-shake-icons 2、获取华为指纹证书命令 keytool -list -v -keystore ***.jks 3、IOS项目中私有方法查找隐藏文件中 1、cd 项目目录地址 2、grep -r xerbla. "xerbla"为需要查找的关键字 3…

在IDEA中创建SpringBoot项目

概述 SpringBoot是由Pivotal团队提供的全新的框架&#xff0c;其设计的目的是用来简化Spring应用的初始搭建以及开发过程。 传统方式构建Spring应用程序 导入依赖繁琐 依赖冲突 项目配置繁琐 SpringBoot特性 1、起步依赖 本质上就行一个Maven坐标&#xff0c;整合了完成一…

RockChip DRM Display Driver

资料来源: 《Rockchip_DRM_Display_Driver_Development_Guide_V1.0.pdf》 《Rockchip_Developer_Guide_DRM_Display_Driver_CN.pdf》 一:DRM概述 DRM(Direct Rendering Manager)直接渲染管理,buffer分配,帧缓冲。对应userspace库位libdrm,libdrm库提供了一系列友好的…

【NeRF】了解学习Neural Radiance Fields(神经辐射场)

文章目录 1.Definition of field&#xff08;场的定义&#xff09;1.1 shape representations&#xff08;各种形状表征方式&#xff09;1.2 Explicit surfaces and implicit surfaces1.3 Radiance Field(Implicit Surfaces)体素密度 2.Definition of Rendering&#xff08;渲染…

项目解决方案:非执法视频监控系统项目设计方案

目 录 一、概述 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;设计思路 &#xff08;三&#xff09;设计原则 1、实用性 2、可靠性 3、安全性 4、先进性 5、开放性 6、易管理、易维护 &#xff08;四&#xff09;设计依据 二、方案总…

数据可视化的未来:2024 年及以后_光点科技

随着我们进入数据驱动决策的下一个时代&#xff0c;数据可视化领域即将迎来一场变革性革命。随着信息的不断涌入和数据的复杂性不断增加&#xff0c;传统的可视化方法需要帮助跟上步伐。人工智能、机器学习和增强现实等新兴技术正在为新一代实时数据可视化工具铺平道路&#xf…

C语言-算法-线性dp

[USACO1.5] [IOI1994]数字三角形 Number Triangles 题目描述 观察下面的数字金字塔。 写一个程序来查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以走到左下方的点也可以到达右下方的点。 在上面的样例中&#xff0c;从 7 → 3 → 8 →…

centos手动下载配置redis并自启动

有些服务器不能自动安装配置redis&#xff0c;仓库找不到之类的问题&#xff0c;就需要手动下载配置redis&#xff0c;记录下&#xff0c;方便以后使用&#xff08;ps&#xff0c;如果报错可能是gcc缺失&#xff09; 1、下载 Redis 源码包&#xff1a;访问 Redis 官网或可信的…

前端学习生产环境、开发环境、测试环境

1、路径 定义是什么环境 NODE_ENVdevelopment 开发环境 2、.env 端口号 3、.env.development 开发环境 4、.env.production 生产环境 5、.env.test 测试环境 6、如何访问&#xff0c;通过process.env进行访问 学习中.......

acrobat调整pdf的页码和实际页码保持一致

Acrobat版本 具体操作 现在拿到pdf的结构如下&#xff1a; pdf页码实际页码1-10页无页码数11页第1页 操作&#xff0c;选择pdf第10页&#xff0c;右键点击 具体设置 最终效果

视频渲染靠cpu还是显卡 会声会影视频渲染的作用是什么

视频渲染最占用的资源就是CPU&#xff0c;多核心多线程&#xff0c;这样才能渲染快。渲染可以在时间线上实时平滑预览&#xff0c;便于编辑&#xff0c;最终导出成片的时候速度也会快一些&#xff0c;渲染就是对每桢的图像进行重新优化的过程。 渲染的作用主要是能够保证使用者…

64、ubuntu使用c++/python调用alliedvisio工业相机

基本思想&#xff1a;需要使用linux系统调用alliedvisio工业相机完成业务&#xff0c;这里只做驱动相机调用&#xff0c;具体不涉及业务开发 Alvium 相机选型 - Allied Vision 一、先用软件调用一下用于机器视觉和嵌入式视觉的Vimba X 软件开发包 - Allied Vision VimbaX_Set…

【vue ajax】封装ajax,可直接复用

在项目经过统一代理后&#xff0c;部分功能想直接发送请求&#xff0c;不用统一api&#xff0c;可以封装一个ajax进行网络传输 ajax(method, url, data) {return new Promise(function (resolve, reject) {const xhr new XMLHttpRequest();xhr.onreadystatechange function (…

HCIP实验6-交换接口实验

搭建实验拓扑图 实验开始 配置PC1 配置PC2 配置PC3 划分vlan 将sw1划分到vlan3 [sw1]interface e0/0/2 [sw1-Ethernet0/0/2]port link-type access [sw1-Ethernet0/0/2]port default vlan 3 将sw3划分到vlan4 [sw3]interface e0/0/2 [sw3-Ethernet0/0/2]port link-type a…