Flink StreamGraph生成过程

news2025/1/17 6:06:53

文章目录

    • 概要
    • SteramGraph 核心对象
    • SteramGraph 生成过程

概要

在 Flink 中,StreamGraph 是数据流的逻辑表示,它描述了如何在 Flink 作业中执行数据流转换。StreamGraph 是 Flink 运行时生成执行计划的基础。
在这里插入图片描述
使用DataStream API开发的应用程序,首先被转换为 Transformation,再被映射为StreamGraph,在客户端进行StreamGraph、JobGraph的转换,提交JobGraph到Flink集群后,Flink集群负责将JobGraph转换为ExecutionGraph,之后进入调度执行阶段。

SteramGraph 核心对象

  • StreamNode
    StreamNode 是 StremGraph 中的节点 ,从 Transformation 转换而来,可以简单理解为一个 StreamNode 表示一个算子,从逻辑上来说,SteramNode 在 StreamGraph 中存在实体和虚拟的 StreamNode。StremNode 可以有多个输入,也可以有多个输出。
    实体的 StreamNode 会最终变成物理算子。虚拟的 StreamNode 会附着在 StreamEdge 上。
  • StreamEdge
    StreamEdge 是 StreamGraph 中的边,用来连接两个 StreamNode,一个 StreamNode 可以有多个出边、入边,StreamEdge 中包含了旁路输出、分区器、字段筛选输出等信息。

SteramGraph 生成过程

StreamGraph 在 FlinkClient 中生成,由 FlinkClient 在提交的时候触发 Flink 应用的 main 方法,用户编写的业务逻辑组装成 Transformation 流水线,在最后调用 StreamExecutionEnvironment.execute() 的时候开始触发 StreamGraph 构建。
StreamGraph在Flink的作业提交前生成,生成StreamGraph的入口在StreamExecutionEnvironment中

    @Internal
    public StreamGraph getStreamGraph() {
        return this.getStreamGraph(this.getJobName());
    }

    @Internal
    public StreamGraph getStreamGraph(String jobName) {
        return this.getStreamGraph(jobName, true);
    }

    @Internal
    public StreamGraph getStreamGraph(String jobName, boolean clearTransformations) {
        StreamGraph streamGraph = this.getStreamGraphGenerator().setJobName(jobName).generate();
        if (clearTransformations) {
            this.transformations.clear();
        }

        return streamGraph;
    }

    private StreamGraphGenerator getStreamGraphGenerator() {
        if (this.transformations.size() <= 0) {
            throw new IllegalStateException("No operators defined in streaming topology. Cannot execute.");
        } else {
            RuntimeExecutionMode executionMode = (RuntimeExecutionMode)this.configuration.get(ExecutionOptions.RUNTIME_MODE);
            return (new StreamGraphGenerator(this.transformations, this.config, this.checkpointCfg, this.getConfiguration())).setRuntimeExecutionMode(executionMode).setStateBackend(this.defaultStateBackend).setChaining(this.isChainingEnabled).setUserArtifacts(this.cacheFile).setTimeCharacteristic(this.timeCharacteristic).setDefaultBufferTimeout(this.bufferTimeout);
        }
    }

StreamGraph实际上是在StreamGraphGenerator中生成的,从SinkTransformation(输出向前追溯到SourceTransformation)。在遍历过程中一边遍历一遍构建StreamGraph,如代码清单所示


@Internal
public class StreamGraphGenerator {
    private final List<Transformation<?>> transformations;
    private StateBackend stateBackend;
    private static final Map<Class<? extends Transformation>, TransformationTranslator<?, ? extends Transformation>> translatorMap;
    protected static Integer iterationIdCounter;
    private StreamGraph streamGraph;
    private Map<Transformation<?>, Collection<Integer>> alreadyTransformed;

    public StreamGraphGenerator(List<Transformation<?>> transformations, ExecutionConfig executionConfig, CheckpointConfig checkpointConfig) {
        this(transformations, executionConfig, checkpointConfig, new Configuration());
    }

    public StreamGraphGenerator(List<Transformation<?>> transformations, ExecutionConfig executionConfig, CheckpointConfig checkpointConfig, ReadableConfig configuration) {
        this.chaining = true;
        this.timeCharacteristic = DEFAULT_TIME_CHARACTERISTIC;
        this.jobName = "Flink Streaming Job";
        this.savepointRestoreSettings = SavepointRestoreSettings.none();
        this.defaultBufferTimeout = -1L;
        this.runtimeExecutionMode = RuntimeExecutionMode.STREAMING;
        this.transformations = (List)Preconditions.checkNotNull(transformations);
        this.executionConfig = (ExecutionConfig)Preconditions.checkNotNull(executionConfig);
        this.checkpointConfig = new CheckpointConfig(checkpointConfig);
        this.configuration = (ReadableConfig)Preconditions.checkNotNull(configuration);
    }

    public StreamGraph generate() {
        this.streamGraph = new StreamGraph(this.executionConfig, this.checkpointConfig, this.savepointRestoreSettings);
        this.shouldExecuteInBatchMode = this.shouldExecuteInBatchMode(this.runtimeExecutionMode);
        this.configureStreamGraph(this.streamGraph);
        this.alreadyTransformed = new HashMap();
        Iterator var1 = this.transformations.iterator();

        while(var1.hasNext()) {
            Transformation<?> transformation = (Transformation)var1.next();
            this.transform(transformation);
        }

        StreamGraph builtStreamGraph = this.streamGraph;
        this.alreadyTransformed.clear();
        this.alreadyTransformed = null;
        this.streamGraph = null;
        return builtStreamGraph;
    }
 
    private Collection<Integer> transform(Transformation<?> transform) {
        if (this.alreadyTransformed.containsKey(transform)) {
            return (Collection)this.alreadyTransformed.get(transform);
        } else {
            LOG.debug("Transforming " + transform);
            if (transform.getMaxParallelism() <= 0) {
                int globalMaxParallelismFromConfig = this.executionConfig.getMaxParallelism();
                if (globalMaxParallelismFromConfig > 0) {
                    transform.setMaxParallelism(globalMaxParallelismFromConfig);
                }
            }

            transform.getOutputType();
            TransformationTranslator<?, Transformation<?>> translator = (TransformationTranslator)translatorMap.get(transform.getClass());
            Collection transformedIds;
            if (translator != null) {
                transformedIds = this.translate(translator, transform);
            } else {
                transformedIds = this.legacyTransform(transform);
            }

            if (!this.alreadyTransformed.containsKey(transform)) {
                this.alreadyTransformed.put(transform, transformedIds);
            }

            return transformedIds;
        }
    }

    private Collection<Integer> legacyTransform(Transformation<?> transform) {
        Collection transformedIds;
        if (transform instanceof FeedbackTransformation) {
            transformedIds = this.transformFeedback((FeedbackTransformation)transform);
        } else {
            if (!(transform instanceof CoFeedbackTransformation)) {
                throw new IllegalStateException("Unknown transformation: " + transform);
            }

            transformedIds = this.transformCoFeedback((CoFeedbackTransformation)transform);
        }

        if (transform.getBufferTimeout() >= 0L) {
            this.streamGraph.setBufferTimeout(transform.getId(), transform.getBufferTimeout());
        } else {
            this.streamGraph.setBufferTimeout(transform.getId(), this.defaultBufferTimeout);
        }

        if (transform.getUid() != null) {
            this.streamGraph.setTransformationUID(transform.getId(), transform.getUid());
        }

        if (transform.getUserProvidedNodeHash() != null) {
            this.streamGraph.setTransformationUserHash(transform.getId(), transform.getUserProvidedNodeHash());
        }

        if (!this.streamGraph.getExecutionConfig().hasAutoGeneratedUIDsEnabled() && transform instanceof PhysicalTransformation && transform.getUserProvidedNodeHash() == null && transform.getUid() == null) {
            throw new IllegalStateException("Auto generated UIDs have been disabled but no UID or hash has been assigned to operator " + transform.getName());
        } else {
            if (transform.getMinResources() != null && transform.getPreferredResources() != null) {
                this.streamGraph.setResources(transform.getId(), transform.getMinResources(), transform.getPreferredResources());
            }

            this.streamGraph.setManagedMemoryUseCaseWeights(transform.getId(), transform.getManagedMemoryOperatorScopeUseCaseWeights(), transform.getManagedMemorySlotScopeUseCases());
            return transformedIds;
        }
    }

    private <T> Collection<Integer> transformFeedback(FeedbackTransformation<T> iterate) {
        if (this.shouldExecuteInBatchMode) {
            throw new UnsupportedOperationException("Iterations are not supported in BATCH execution mode. If you want to execute such a pipeline, please set the '" + ExecutionOptions.RUNTIME_MODE.key() + "'=" + RuntimeExecutionMode.STREAMING.name());
        } else if (iterate.getFeedbackEdges().size() <= 0) {
            throw new IllegalStateException("Iteration " + iterate + " does not have any feedback edges.");
        } else {
            List<Transformation<?>> inputs = iterate.getInputs();
            Preconditions.checkState(inputs.size() == 1);
            Transformation<?> input = (Transformation)inputs.get(0);
            List<Integer> resultIds = new ArrayList();
            Collection<Integer> inputIds = this.transform(input);
            resultIds.addAll(inputIds);
            if (this.alreadyTransformed.containsKey(iterate)) {
                return (Collection)this.alreadyTransformed.get(iterate);
            } else {
                Tuple2<StreamNode, StreamNode> itSourceAndSink = this.streamGraph.createIterationSourceAndSink(iterate.getId(), getNewIterationNodeId(), getNewIterationNodeId(), iterate.getWaitTime(), iterate.getParallelism(), iterate.getMaxParallelism(), iterate.getMinResources(), iterate.getPreferredResources());
                StreamNode itSource = (StreamNode)itSourceAndSink.f0;
                StreamNode itSink = (StreamNode)itSourceAndSink.f1;
                this.streamGraph.setSerializers(itSource.getId(), (TypeSerializer)null, (TypeSerializer)null, iterate.getOutputType().createSerializer(this.executionConfig));
                this.streamGraph.setSerializers(itSink.getId(), iterate.getOutputType().createSerializer(this.executionConfig), (TypeSerializer)null, (TypeSerializer)null);
                resultIds.add(itSource.getId());
                this.alreadyTransformed.put(iterate, resultIds);
                List<Integer> allFeedbackIds = new ArrayList();
                Iterator var10 = iterate.getFeedbackEdges().iterator();

                while(var10.hasNext()) {
                    Transformation<T> feedbackEdge = (Transformation)var10.next();
                    Collection<Integer> feedbackIds = this.transform(feedbackEdge);
                    allFeedbackIds.addAll(feedbackIds);
                    Iterator var13 = feedbackIds.iterator();

                    while(var13.hasNext()) {
                        Integer feedbackId = (Integer)var13.next();
                        this.streamGraph.addEdge(feedbackId, itSink.getId(), 0);
                    }
                }

                String slotSharingGroup = this.determineSlotSharingGroup((String)null, allFeedbackIds);
                if (slotSharingGroup == null) {
                    slotSharingGroup = "SlotSharingGroup-" + iterate.getId();
                }

                itSink.setSlotSharingGroup(slotSharingGroup);
                itSource.setSlotSharingGroup(slotSharingGroup);
                return resultIds;
            }
        }
    }

    private <F> Collection<Integer> transformCoFeedback(CoFeedbackTransformation<F> coIterate) {
        if (this.shouldExecuteInBatchMode) {
            throw new UnsupportedOperationException("Iterations are not supported in BATCH execution mode. If you want to execute such a pipeline, please set the '" + ExecutionOptions.RUNTIME_MODE.key() + "'=" + RuntimeExecutionMode.STREAMING.name());
        } else {
            Tuple2<StreamNode, StreamNode> itSourceAndSink = this.streamGraph.createIterationSourceAndSink(coIterate.getId(), getNewIterationNodeId(), getNewIterationNodeId(), coIterate.getWaitTime(), coIterate.getParallelism(), coIterate.getMaxParallelism(), coIterate.getMinResources(), coIterate.getPreferredResources());
            StreamNode itSource = (StreamNode)itSourceAndSink.f0;
            StreamNode itSink = (StreamNode)itSourceAndSink.f1;
            this.streamGraph.setSerializers(itSource.getId(), (TypeSerializer)null, (TypeSerializer)null, coIterate.getOutputType().createSerializer(this.executionConfig));
            this.streamGraph.setSerializers(itSink.getId(), coIterate.getOutputType().createSerializer(this.executionConfig), (TypeSerializer)null, (TypeSerializer)null);
            Collection<Integer> resultIds = Collections.singleton(itSource.getId());
            this.alreadyTransformed.put(coIterate, resultIds);
            List<Integer> allFeedbackIds = new ArrayList();
            Iterator var7 = coIterate.getFeedbackEdges().iterator();

            while(var7.hasNext()) {
                Transformation<F> feedbackEdge = (Transformation)var7.next();
                Collection<Integer> feedbackIds = this.transform(feedbackEdge);
                allFeedbackIds.addAll(feedbackIds);
                Iterator var10 = feedbackIds.iterator();

                while(var10.hasNext()) {
                    Integer feedbackId = (Integer)var10.next();
                    this.streamGraph.addEdge(feedbackId, itSink.getId(), 0);
                }
            }

            String slotSharingGroup = this.determineSlotSharingGroup((String)null, allFeedbackIds);
            itSink.setSlotSharingGroup(slotSharingGroup);
            itSource.setSlotSharingGroup(slotSharingGroup);
            return Collections.singleton(itSource.getId());
        }
    }

    private Collection<Integer> translate(TransformationTranslator<?, Transformation<?>> translator, Transformation<?> transform) {
        Preconditions.checkNotNull(translator);
        Preconditions.checkNotNull(transform);
        List<Collection<Integer>> allInputIds = this.getParentInputIds(transform.getInputs());
        if (this.alreadyTransformed.containsKey(transform)) {
            return (Collection)this.alreadyTransformed.get(transform);
        } else {
            String slotSharingGroup = this.determineSlotSharingGroup(transform.getSlotSharingGroup(), (Collection)allInputIds.stream().flatMap(Collection::stream).collect(Collectors.toList()));
            Context context = new StreamGraphGenerator.ContextImpl(this, this.streamGraph, slotSharingGroup, this.configuration);
            return this.shouldExecuteInBatchMode ? translator.translateForBatch(transform, context) : translator.translateForStreaming(transform, context);
        }
    }

    private List<Collection<Integer>> getParentInputIds(@Nullable Collection<Transformation<?>> parentTransformations) {
        List<Collection<Integer>> allInputIds = new ArrayList();
        if (parentTransformations == null) {
            return allInputIds;
        } else {
            Iterator var3 = parentTransformations.iterator();

            while(var3.hasNext()) {
                Transformation<?> transformation = (Transformation)var3.next();
                allInputIds.add(this.transform(transformation));
            }

            return allInputIds;
        }
    }

    private String determineSlotSharingGroup(String specifiedGroup, Collection<Integer> inputIds) {
        if (specifiedGroup != null) {
            return specifiedGroup;
        } else {
            String inputGroup = null;
            Iterator var4 = inputIds.iterator();

            while(var4.hasNext()) {
                int id = (Integer)var4.next();
                String inputGroupCandidate = this.streamGraph.getSlotSharingGroup(id);
                if (inputGroup == null) {
                    inputGroup = inputGroupCandidate;
                } else if (!inputGroup.equals(inputGroupCandidate)) {
                    return "default";
                }
            }

            return inputGroup == null ? "default" : inputGroup;
        }
    }

    static {
        DEFAULT_TIME_CHARACTERISTIC = TimeCharacteristic.ProcessingTime;
        Map<Class<? extends Transformation>, TransformationTranslator<?, ? extends Transformation>> tmp = new HashMap();
        tmp.put(OneInputTransformation.class, new OneInputTransformationTranslator());
        tmp.put(TwoInputTransformation.class, new TwoInputTransformationTranslator());
        tmp.put(MultipleInputTransformation.class, new MultiInputTransformationTranslator());
        tmp.put(KeyedMultipleInputTransformation.class, new MultiInputTransformationTranslator());
        tmp.put(SourceTransformation.class, new SourceTransformationTranslator());
        tmp.put(SinkTransformation.class, new SinkTransformationTranslator());
        tmp.put(LegacySinkTransformation.class, new LegacySinkTransformationTranslator());
        tmp.put(LegacySourceTransformation.class, new LegacySourceTransformationTranslator());
        tmp.put(UnionTransformation.class, new UnionTransformationTranslator());
        tmp.put(PartitionTransformation.class, new PartitionTransformationTranslator());
        tmp.put(SideOutputTransformation.class, new SideOutputTransformationTranslator());
        tmp.put(ReduceTransformation.class, new ReduceTransformationTranslator());
        tmp.put(TimestampsAndWatermarksTransformation.class, new TimestampsAndWatermarksTransformationTranslator());
        tmp.put(BroadcastStateTransformation.class, new BroadcastStateTransformationTranslator());
        tmp.put(KeyedBroadcastStateTransformation.class, new KeyedBroadcastStateTransformationTranslator());
        translatorMap = Collections.unmodifiableMap(tmp);
        iterationIdCounter = 0;
    } 
}

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

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

相关文章

【Selenium】selenium介绍及工作原理

一、Selenium介绍 用于Web应用程序测试的工具&#xff0c;Selenium是开源并且免费的&#xff0c;覆盖IE、Chrome、FireFox、Safari等主流浏览器&#xff0c;通过在不同浏览器中运行自动化测试。支持Java、Python、Net、Perl等编程语言进行自动化测试脚本编写。 官网地址&…

ROS从入门到精通4-2:Docker安装ROS、可视化仿真与终端复用

目录 0 专栏介绍1 Docker安装ROS2 Docker可视化仿真2.1 显示配置2.2 启动容器 3 终端复用工具3.1 session操作3.2 window操作3.3 pane操作3.4 其他操作 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0c;掌握ROS底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS…

Outlook邮箱后缀如何修改?怎么添加后缀?

Outlook邮箱后缀是什么&#xff1f;Outlook邮箱后缀可以改吗&#xff1f; Outlook邮箱广泛应用于企业和个人用户之间。在使用过程中&#xff0c;有时我们可能会因为某些原因需要修改Outlook邮箱后缀。那么&#xff0c;Outlook邮箱后缀如何修改呢&#xff1f;下面&#xff0c;A…

应用程序并行配置不正确怎么办?

当出现应用程序的并行配置不正确的问题时&#xff0c;通常也无法打开目标应用程序了&#xff0c;应该如何解决此问题呢&#xff1f;下面我们一起来了解一下。 1、重装出现问题的应用 如果是某个应用程序出现问题&#xff0c;那么卸载它再进行重装是很好的方法。 具体步骤&…

手写分布式配置中心(三)增加实时刷新功能(短轮询)

要实现配置自动实时刷新&#xff0c;需要改造之前的代码。代码在https://gitee.com/summer-cat001/config-center​​​​​​​ 服务端改造 服务端增加一个版本号version&#xff0c;新增配置的时候为1&#xff0c;每次更新配置就加1。 Overridepublic long insertConfigDO(…

技术指标和振荡器大全(二)

原文&#xff1a;stockcharts.com/school/doku.php?idchart_school:technical_indicators 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 成交量加权平均价格&#xff08;VWAP&#xff09; 目录 成交量加权平均价格&#xff08;VWAP&#xff09; 介绍 Tick 与 Minu…

项目中spring security与jwt.腾讯面试分享

写这篇文章是为了记录我面试pcg时平时没有留意或者钻研的地方。 面试是根据项目问的问题&#xff1a; 为什么采用jwt存储token&#xff1f; 我的项目是微服务项目&#xff0c;里面部署了资源服务和认证服务&#xff0c;这里选择jwt作为token一方面是可以存储用户的信息&#…

【DPDK】基于dpdk实现用户态UDP网络协议栈

文章目录 一.背景及导言二.协议栈架构设计1. 数据包接收和发送引擎2. 协议解析3. 数据包处理逻辑 三.网络函数编写1.socket2.bind3.recvfrom4.sendto5.close 四.总结 一.背景及导言 在当今数字化的世界中&#xff0c;网络通信的高性能和低延迟对于许多应用至关重要。而用户态网…

C++指针(四)

个人主页&#xff1a;PingdiGuo_guo 收录专栏&#xff1a;C干货专栏 前言 相关文章&#xff1a;C指针&#xff08;一&#xff09;、C指针&#xff08;二&#xff09;、C指针&#xff08;三&#xff09; 本篇博客是介绍函数指针、函数指针数组、回调函数、指针函数的。 点赞破六…

文件批量重命名神器:按长度与区间智能管理,让文件整理更高效!

在数字化时代&#xff0c;电脑中堆积如山的文件常常让我们头疼不已。命名不规范、杂乱无章的文件不仅占用了大量的存储空间&#xff0c;更在关键时刻让我们难以迅速找到所需内容。现在&#xff0c;有了这款文件批量改名神器&#xff0c;一切烦恼将烟消云散&#xff01; 首先&a…

时隔n年再度会看Vue,Git

时隔n年再度会看Vue,Git 曾经沧海难为水&#xff0c;除却巫山不是云。不知道这句话用在这里合不合适&#xff0c;好多东西在记忆中都淡化了。但是互联网确是有记忆的。研究以前项目的时候&#xff0c;翻看到gitee码云上托管的项目&#xff0c;就像是自己的孩子重新又回来了一样…

观其大略之HybridCLR学习笔记

问题背景 1 现有热更方案的开发效率、性能没有到达极限&#xff0c;还有提升的空间 2 ios多平台政策导致热更新受限问题&#xff0c;ios禁止jit。根据我查找的资料&#xff0c;ios的代码段启动的时候就确定了&#xff0c;不能增加新的代码段。IOS封了内存&#xff08;或者堆&…

如何摆脱水印困扰?三款神器助您清爽无烦恼!

水印常常成为我们图片处理的一大难题&#xff0c;让我们苦恼不已。那么&#xff0c;如何能轻松摆脱这些烦人的水印呢&#xff1f;本文将向您推荐三款强大的去水印工具&#xff0c;让您清爽无烦恼&#xff0c;图片重焕光彩&#xff01; 1. 水印云 如何快速而准确地去除各类水印…

Stable Diffusion 解析:探寻 AI 绘画背后的科技神秘

AI 绘画发展史 在谈论 Stable Diffusion 之前&#xff0c;有必要先了解 AI 绘画的发展历程。 早在 2012 年&#xff0c;华人科学家吴恩达领导的团队训练出了当时世界上最大的深度学习网络。这个网络能够自主学习识别猫等物体&#xff0c;并在短短三天时间内绘制出了一张模糊但…

【RK3288 Android6, T8PRO 快捷按键 gpio 配置上拉输入】

文章目录 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】需求开发过程尝试找到没有用的上拉gpio尝试修改pwm1的gpio的默认上拉模式 改动 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】 需求 T8pro想要模仿T10 的 快捷按键&#xff…

嵌入式开发的常用软件、学习资源网站推荐

1、软件推荐 1.1、文本编辑软件 ——Notepad 1、适合编写和查看文本文件&#xff0c;也可以安装插件来查看二进制文件、对比文件 2、参考博客&#xff1a;《Notepad实用小技巧》&#xff1b; 1.2、PDF文件阅读软件——福昕PDF阅读器 福昕PDF阅读器&#xff0c;在官网就可以下载…

电商店群系统的搭建需要用到的官方接口如何申请?

电商电子商务企业往往都会需要再很多平台上面铺货&#xff0c;上传商品。 高科技的今天&#xff0c;我们已经不需要手动一个个品去上传了。那通过官方接口&#xff0c;如何实现快速铺货呢&#xff1f; 1688官方开放平台的API接口类型众多&#xff0c;并不是所有的企业都能申请…

springboot3.x集成nacos踩坑,并实现多环境配置

一、nacos安装部署 springboot3.x集成Nacos首先需要将Nacos从1.x升级到2.x&#xff0c;建议直接安装2.x版本&#xff0c;手动将1.x的配置信息迁移到2.x中&#xff0c;先并行一段时间&#xff0c;待全部迁移完成稳定运行之后再停掉1.x&#xff0c;升级和安装、操作请查看官方文…

HBuilder X删除之前登录的账号

打开目录 C:\Users\Administrator\AppData\Roaming\HBuilder X 用 HBuilder X 打开文件 prefs 将账号删除 保存文件 重启HBuilder X即可

开发手札:unity2022+vscode1.87联合开发

不得不说&#xff0c;时间的力量是很强大的&#xff0c;同时熵增理论适用于任何地方。 在现在的公司干了五年多了&#xff0c;五年前配置的内网开发机&#xff0c;i7 870016g1t hddgtx1080已经卡爆了&#xff0c;特别是硬盘掉速严重&#xff0c;开机开软件没有一两分钟都…