Squid Web Cache Wiki

Squid Web Cache 文档

🔗 特性:热配置

🔗 详细信息

Squid 目前通过模拟关闭、重新加载配置文件和重启的方式进行配置重载。

这会导致许多可见的问题:

相关 Bug 报告

🔗 Squid-3.1 之前的状态

在 3.0 初期开始的计划是重写现有的 squid.conf 解析器,将语法解析交给每个对需求有更好了解的组件。

上述计划因目前未知的原因而中断,并且未完成。它似乎将解析过于深入地分解到组件中,难以理解。这也会导致代码重复,因为后续开发人员会忽略(未记录的!)细微的解析函数,并在每个组件级别上重新实现它们。这不是一个好的状态,需要在重新设计中简化。

旧的 Squid-2 解析器具有更清晰的设计,但存在严重的组件相互依赖问题。因此,它至少需要沿着 3.0 设计的路线部分分解。

此外,当前解析器设计都无法完全解决这些问题,因此需要一个新设计来取代两者。集成 Squid-2 解析器的简单选项处理和 Squid-3 解析器的按组件拆分。

🔗 Squid-3.1+ 的新设计

Squid-3.1 中已开始并正在与 Features/SourceLayout 集成的工作,采用了 Squid-3.0 的模块化组件配置的解析器思路(每个子库都有自己的 XX::Config 对象,其中仅包含该组件的配置设置)。

但也保留了遗留解析器按类型解析的功能。这导致出现两个层级:

目前,通过将遗留解析器处理程序的接口定义为 XX::Config 对象方法的包装函数/宏来将它们链接在一起。

计划在此模块化设计的基础上进行构建,并创建两个主对象:

🔗 XX::Config 模板或父对象

该对象提供虚拟函数,用于注册一个处理程序,以解析组件可以解析的每个 squid.conf 选项(包括当前、已弃用和已废弃的选项)。

选项处理程序

它还必须为启动、关闭、重载和重新配置过程提供 API。其中重新配置最需要工作。

在我看来,重新配置的重要部分是,在重新配置之前和之后都要调用每个组件,以便它能够执行任何必要的准备工作来解析配置。并在解析后进行清理/重启(如果需要)。

主要组件可能需要在重新配置期间将解析结果处理到影子配置中,并进行热交换,允许现有请求继续使用旧配置详细信息,而新请求则使用更新后的配置。

一些对象需要被引用计数,以防止在更新后的配置被热交换到位时消失。

每个组件的配置对象都需要有一个组件启用/禁用标志或设置。这将允许运行时启用和禁用组件,并进一步改进和扩展关闭/重启逻辑,并添加异步路径。

🔗 注册式解析器/组件管理器

一个管理器对象,用于接收处理程序注册,并处理处理 squid.conf 所需的低级文件读取。

可能还负责控制目前由 main.cc 在全局级别执行的核心启动/关闭/重启/重载/重新配置操作。

🔗 结果

完成后:

🔗 讨论

:warning: 回答请使用主菜单中的“讨论”链接。

请参阅 讨论页面

感谢你创建这个页面,Amos。这里有很多好想法。

我建议的一个概念性改变是分离解析和配置。如果你看看你的笔记,其中很多都与如何解析以及谁在解析有关。这可能很重要,但与热重配置无关(也不应该有关)。

我们需要一组由解析器创建的“哑巴”配置保存对象。这些对象不应与 Squid 的运行时状态有关。在重新配置期间,新创建的配置对象将被馈送给它们的模块。模块决定如何处理重新配置,但它们在做出决定时不应处理解析问题。它们应该操作和使用配置对象。

用伪 C++ 表示,并做一些简化:

    // lifecycle
    void Module::init();
    void Module::configure(const Module::Config &cfg);
    void Module::reconfigure(const Module::Config &newCfg);
    void Module::shutdown();

    // parsing; probably static to avoid module creation
    Module::Config *Module::Parse(const SquidDotConfTokenizer &text);

-- AlexRousskov

说得好,我能理解切换场景需要那种数据与处理程序的分离。不过,我不喜欢需要将配置对象传递给它们。管理器将是进行调用的者,我们不希望管理器处理单个 Config 对象,而只处理当前的处理状态和当前的 squid.conf 行字符串。

NP:我设想启动只是重新配置的一个特殊情况,其中默认值内置于 ::Config 构造函数中,并在最初几秒钟内使用,直到本地配置。

我的模型是,具有 API 方法的对象要么继承自一个主/共享对象,该对象提供基本的标记化方法,要么从中模板化。类似于 Rob 早期的解析器尝试,但调用嵌套不会超出当前正在解析的对象。(是的,我必须对 ACL 列表做一个例外,因为它们附加在很多东西的末尾,但在仔细浏览配置后,我没看到其他地方了)。

解析器与热配置紧密结合,因为热配置是解析设计中一个期望且计划好的效果,而不是反过来。这是为了给共享于旧配置和新配置的对象进行引用计数。解析器控制并使用当前和未来的哑巴 ::Config 来确定什么将进入未来(当前对象的引用计数克隆或新的分配)。实际上,解析器控制是存在一个未来的对象,还是它正在原地编辑当前对象(考虑错误目录位置、大小限制和其他与状态无关的设置)。

-- Amos Jeffries

我担心这里表达的几个想法,但我可能只是误解了你的意思。我将提供具体的草图,希望让这次讨论不那么模糊,更具结构性。

我不同意将解析器与配置保持紧密耦合是个好主意。当然有办法解决(见我的草图)。如果我们让模块同时进行解析和配置,我们将极大地增加整个设计的复杂性。此外,如果你想编写模块特定的测试用例,能够创建和(重新)配置模块而无需解析将非常有价值。

在我看来,引用计数与此问题无关。引用计数只是一种低级节省内存的机制,我们正在讨论的是一个高级 API(无论是否有引用计数都可以工作)。

解析器*不应该*“原地”编辑。它应该始终创建并填充全新的哑巴配置对象。与当前的“将所有东西塞进一个全局变量”设计相比,它将花费更多的内存,但花费相对较少,是暂时的,并且会带来几个显著的优势,包括能够编写清晰的重新配置代码,该代码可以比较旧配置对象和新配置对象,识别差异,并决定是否可以进行热重配置。

我认为一开始就使用默认值,然后重新配置为实际配置值不是一个好主意。这只会增加复杂性,因为你仍然需要编写初始配置代码(应用默认值的代码),然后执行可能很复杂的重新配置。

拥有初始配置代码还可以让你在必要时避免实现真正的热重配置。

    // a sketch of a possible default implementation for modules
    void Module::reconfigure(const Config &newCfg) {
        shutdown();
        init();
        configure(newCfg);
    }

这种方法允许从不支持热重配置的代码平稳迁移到支持热重配置的代码。最低要求是实现 init/shutdown/configure,这是我们无论如何都需要的东西。

最后,我不确定顶层的(重新)配置管理器应该是什么样子,但我不太理解你提到的“处理单个模块配置”的问题。像这样的草图可能有效:

    int main() {
        addModule(new Module1); // calls init() and registers
        addModule(new Module2); // calls init() and registers
        addModule(new Module3); // calls init() and registers
        addModule(new Module4); // calls init() and registers
        ...

        SquidConfig *cfg = parse();
        configure(*cfg);

        mainLoop();

        while (registered module container is not empty) {
            delete popModule(); // calls shutdown() and deregisters popped m
        }
    }

    // note how parsing is unaware of individual module and configuration
    // types
    SquidConfig *parse() {
        // I do not like the Tokenizer name here; the class does more than
        // just tokenizing because it allows to search for relevant lines
        SquidDotConfTokenizer tokenizer(...);

        SquidConfig *cfg = new SquidConfig; // collection of Module cfgs
        for each registered module m {
            // the module will find lines that belong to it by searching
            // for module-specific option names
            Config *moduleCfg = m->parse(tokenizer);
            cfg->add(moduleCfg);
        }

        if (tokenizer.unusedLines())
            throw "unclaimed config options";

        return cfg;
    }

    void configure(const SquidConfig &cfg) {
        for each registered module m {
            // find module configuration; hide this inside m->configure()?
            Config *mcfg = cfg.find(m);

            // configure the module
            m->configure(*mcfg);
        }
    }

    void reconfigure() {
        SquidConfig *newCfg = parse(...);

        for each registered module m {
            // find module configuration; hide this inside m->reconfigure()?
            Config *mcfg = newCfg->find(m);

            // reconfigure the module
            m->reconfigure(*mcfg);
        }

        delete newCfg; // or replace the old global refcounted pointer
    }

-- AlexRousskov

哦,不行。这会将配置启动/关闭过程置于主循环之外,并且无法通过异步操作访问。

已调整你的示例,使其更符合我的意思。

    int main() {
        addModule(new Module1);
            // calls init() and registers "no_cache" as Store::??::parse_cache_acl(...) etc.
        addModule(new Module2);
            // calls init() and registers "adaptation_enable" as Adaptation::??::parse_enable(...) etc.
        ...

        SquidConfig::startConfigure();
        scheduleAsync(..., SquidConfig::parse(), ...); // as next job with zero time delay
        scheduleAsync(..., SquidConfig::doneConfigure(), ...); // as next job with zero time delay after parse...

        mainLoop();

    // I like this loop, however for code simplicity I think it should be its own async event 'shutdown'
        while (registered module container is not empty) {
            delete popModule(); // calls shutdown() and deregisters popped m
        }
    }

    // note how parsing is unaware of individual module and configuration
    // types AND even of individual line content tokens!!
    void parse() {
        SquidDotConfTokenizer tokenizer(...);

        for each tag = SquidDotConfTokenizer.nextLine() {
           // find handler for that named config option...
           if ( opt = find(tag))
               opt->parseHandler(tokenizer);
           else
               handleUnclaimedLine(tag, tokenizer)
        }

        if (tokenizer.unusedLines())
            throw "unclaimed config options";
    }

    // only used to prep-state during a true reconfigure
    void startConfigure() {
        for each registered module m {
            // find module configuration; maybe even hide this inside parse()
            cfg.startingConfigure();
        }
    }

    // used after any config changes from any source...
    void doneConfigure() {
        for each registered module m {
            // find module configuration; maybe even hide this inside parse()
            cfg.reconfigureCompleted();
        }
    }

    using namespace Module; // ...

    Module::Config *current;
    Module::Config *future;

    // NP: legacy code here might call its own shutdown() instead of the hot-conf clone().
    startConfigure()
    {
       future = current->clone();
        // maybe followup with any removals needed to reset the future config to 'unused state'
    }

    parse(tokeniser)
    {
        // parse tokeniser line into *future ...
    }

    // NP: _this_ is the entirety of hot-swap.
    // legacy code which did shutdown earlier will be doing its own restart() stuff here instead of hot-swap.
    reconfigureComplete()
    {
       if (!changed) {
          delete future;
          return;
       }

       // anything else needed to finish up with ...
       Module::Config *tmp = current;
       current = future;
       delete tmp;
    }

这两个层级在调用意义上基本相同:Squid::startConfigure 调用 Module::startConfigure 等。但区别在于每个方法的范围从文件缩小到行,并在这些层级之间的标记化器交互量增加。

正如你所指出的,解析标记化器需要单独考虑。但那与此功能的重配置范围无关。

-- Amos Jeffries

尝试实现异步配置是值得的。

你添加了“甚至包括单个行内容标记”的评论。我的草图中也是如此。

我不认为限制模块配置 API 为“一次一个选项”并添加一个“模块选项注册”接口是个好主意。在我的草图中,模块知道自己的选项,并按自己意愿处理它们。当然,可能有一些通用/共享代码供模块重用以一次处理一个选项,但我不会将该细节暴露给上层或强加给下层。

可能有两个模块需要访问同一个选项,例如。我们也不知道模块应该按什么顺序处理选项(并且顺序可能是动态的,所以总是使用注册顺序是不够的)。总的来说,这只会增加复杂性,并使接口比必要时更僵化。

你草图中的最后一部分是关于实现重配置的一种可能方式,我想。同样,我不会强迫所有模块都这样做。我只会给它们新的或“未来”的 Module::Config 对象,让它们自己去解决如何处理过渡。争论点与上面“一次一个注册行”的异议非常相似。

我认为将解析与(重新)配置分开至关重要。如果解析失败,我们甚至不应该尝试(重新)配置。解析会生成 Module::Config 对象,除了内存占用外没有其他作用。大多数常见错误在此阶段被检测到。如果一切正常,所有模块都会被要求使用这些 Config 对象(重新)配置自己。我们甚至可以更进一步,进行三个重新配置步骤:

  1. 解析——为所有模块调用 Module::parse(tokenizer),生成 Module::Config 对象。在出错时中止重新配置。不对当前配置进行任何更改。

  2. 验证——为所有模块调用 Module::canReconfigure(cfg),生成成功/错误。在出错时中止重新配置。不对当前配置进行任何更改。

  3. 应用——为所有模块调用 Module::reconfigure(cfg)。不应失败。更改当前配置。

这里最大的挑战之一是处理模块之间的依赖关系,但让 Module::Config 对象不产生副作用将有助于此。

-- AlexRousskov

这部分让我非常困惑……

It is possible that two modules will need access to the same option, for example.

你是说字典意义上的“访问”,还是真的指的是在重新配置期间*设置*访问?

举一个同时加载两个使用相同配置行标签但选项格式不同的组件的例子,好吗?

如果你是指两个模块共享完全相同的选项,那么它们需要被创建,使得同时加载两者不会发生冲突,并且它们的状态保持同步。这对于 Squid 的配置或解析器来说不是问题。

或者,像 `asl bar src all` 这样的共享行是否应该单独解析并为每个需要引用它的组件单独存储?

 We also do not know the order in which the options should be handled by the module (and that order can be dynamic so always using the registration order is not good enough). Overall, it just adds complexity and makes the interface more rigid than necessary.

什么?我没有做这样的假设!注册顺序只与确保每个 squid.conf 选项/标签都有一个已知的对象接收它有关。

嗯,是的,我们在这次讨论中看到了不同的方面。例如,你的一些陈述让我认为你定义的“选项”与“acl”在“acl foo … bar”中的含义不同。

对于其他方面,我认为我看到了一点你的意思,但你似乎没有看到过渡的要求。让我们在这里阐述一下我正在做的假设/保证以及它们来自哪里……

I do not think it is a good idea to limit module configuration API to "one option at a time" and add an "module option registration" interface. In my sketch the module knows its options and handles them however it wants. There may be a common/shared code that a module can reuse to handle one option at a time, of course, but I would not expose that detail to the upper layer or force it on lower layers.

“一次一个选项”是过渡期间必需的,以防止许多组件(需要此类工作方式)的损坏。

 http_access allow foo
 acl bar src all
 http_access deny bar

当过渡完成时,这个限制可能会被移除。事实上,在我提出的模型中,预配置/配置/后配置的三步过程允许轻松地将上述操作异步或并行执行(如果需要),而无需我们现在就考虑这种用法的细节。

我们知道 squid.conf 是按顺序指定的。一些组件利用了这种保证,一些则不在乎。这是上层解析实现的一个细节。底层设计(并且只)需要保证行按照配置的顺序传递给组件,并且组件在配置完成后得到通知。参见上面关于同步/异步的再次讨论。在这种情况下,附加的好处是预配置调用允许创建和设置影子 TheConfig 等。这在 Squid 中还根本不存在。

我读到你的意思是:我们应该编写 Squid 来处理所有可能的选项,并禁止插件组件自己解析?在需要重新配置时,给它们一个预先填充的对象(Squid 甚至不知道字段格式!)?

我的意思是:

我看了将行分解成通用标记并传递给选项处理程序列表的方案,正如你和 Kinkie 所建议的那样。我们在那里遇到了一个主要问题。标记化的确切分隔符需要硬编码到 Squid 中。目前我们使用空格,但即使如此也有例外。有些行在带引号或不带引号时解析,有些则不。其他行则使用 `=` 和空格来分割,但仅限于行中的特定部分。像 ACL IP 列表这样的东西也按 `-` 和 `/` 分割,同样仅限于行的一部分。我们根本无法假设一个组件可能需要什么作为标记。

The last bit in your sketch is about a possible way to implement reconfiguration, I think. Again, I would not force that way on all modules. I would just give them the new or "future" Module::Config object and let them figure out how to handle the transition. The arguments are very similar to the "one registered line at a time only" objections above.

……所以我们反而强制 Squid 在加载任何第三方模块之前就预先知道其 TheConfig 类/结构的确切格式?创建一个新的,解析到其中,并在需要重新配置时将其传回给组件?以*节省*复杂性?我认为我不需要向你提及与此相关的任何问题。

IMO 这样做会带来*巨大*的复杂性和麻烦,比简单地将 squid.conf 缓冲区传递给组件要多。你也许可以采用让 pre-configure 方法/函数返回一个 void* 并将其传回的方式。

但是。由于我们只能在重新配置或不重新配置之间进行选择。 IMO 再次,我们应该将 TheConfig 的处理和细节留给组件,即使它需要它们。

考虑一个动态加载的第三方黑盒组件 Widget,它在最后的配置时间加载。为了解析 `widget_magic` 行,在什么信息和调用复杂性的最小传递下,上层(Squid)和下层(组件库)可以完成?

  squid:  'about to reconfigure'
  widget: '(void)
  squid:  'reconfigure your widget_magic with this line from squid.conf ...'
  squid:  'reconfigure your widget_magic2 with this line from squid.conf ...'
  widget:  'okay / error / abort'
  squid:  'done configuring.'
  widget:  'okay / error / abort'
I feel it is critical to separate parsing from [re]configuration. We should not even attempt to [re]configure Squid if parsing fails. Parsing produces Module::Config objects, that have no effect other than memory use. Most common errors are detected at this stage. If everything is OK, all modules are asked to [re]configure themselves using these Config objects. We can even go further and have three reconfiguration steps:

至少我们在这一点上达成了一致。你用一个额外的验证步骤重述了我的模型,但没有为组件预先设置来创建其“空”的 TheConfig 供我们使用 :smile:

IMO 验证应该在解析时进行。它要么是有效的解析,要么不是,并且产生的状态应该是可以工作的。如果你喜欢在所有内容都解析到新的 TheConfig 集之后进行单独的元验证来检查依赖关系等,没问题。我认为这会非常小,但可能有用。

-- Amos Jeffries

我很抱歉,但我不太明白你的大部分评论,因为我认为你正在攻击一个我并未提出的模型,同时讨论对我来说似乎不相关的低级细节。我怀疑只是我们的术语和表达方式存在基本差异,阻碍了我们相互理解并取得进展。我认为我们需要退一步。

我们应该迈出更小的步伐,或者在线上讨论。我先尝试前者。

在我的模型中,有两个最重要的设计决策:

  1. 每个模块都有自己的 Config 类(即 Module::Config),继承自某个通用的基类 ModuleConfig。只有模块 M 的用户才知道 M::Config 的细节。其他用户只知道它是一个基类 ModuleConfig 的实例,带有一些用于报告等通用方法。所有模块的 Config 被收集到一个 SquidConfig 类中,但这在大多数情况下并不重要。目前,该 SquidConfig 类的确切形状并不重要。
  2. 一个模块的 Config 对象除了在用作模块的“当前”配置时,没有运行时影响。对于每个模块,可能同时存在许多可能冲突的 Config 对象,但每个模块和 Squid 整体只有一个当前配置。

有三个最重要的配置和重新配置步骤:

  1. 在第一步中,每个模块通过解析该模块需要解析的任何 squid.conf 选项来创建其 Config 对象。让我们暂时忽略如何进行解析。让我们暂时忽略模块如何决定哪个 squid.conf 部分与该模块相关。让我们暂时忽略 squid.conf 如何呈现给模块。创建的 Config 对象被组装成一个 Squid Config 对象。让我们暂时忽略如何以及由谁来完成。这个没有运行时影响的 Squid Config 对象是第一步的最终结果。如果一个模块未能生成有效的 Config 对象,第一步就会中止。
  2. 在第二步中,每个模块在必要时会在其他 Config 对象的上下文中验证其 Config 对象。此步骤可以与第一步合并,但最好将其分开,原因有几个。一个原因是允许模块间检查,例如“我的配置选项 Foo 需要模块 M2 中的选项 Bar。是否指定了 M2::Bar?”。你不能在解析过程中进行此类检查,因为模块 M2 可能在我们创建自己的 Config 之后才创建它的 Config。另请注意,如果某些模块决定合并第一步和第二步,它是自由的。这样的模块将只在第二步中总是说“是的,这个配置是有效的”。
  3. 在第三步中,每个模块应用其 Config 对象(即,使提供的 Config 对象“当前”)。此步骤必须是单独的并且是最后的,因为我们不希望 Squid 处于部分配置状态。如果 squid.conf 无效,重新配置应无操作(除了错误消息等)。

忽略所有其他细节,你对上述内容有任何严重问题吗?

-- AlexRousskov

是的,我认为我们肯定在谈论不同的事情。我会努力在 IRC 上与你沟通处理的细节。

现在,我将专注于你的 Config 对象设计,并指出我不同意的地方,而不是进行比较。

 a. Each module has its own Config class (i.e., Module::Config), inherited from some common base ModuleConfig class. Only module M users know the details of M::Config. Others just know it is an instance of the base ModuleConfig class, with a few common methods for reporting and such. Configs from all modules are collected into one SquidConfig class, but that is not important in most cases. The exact shape of that SquidConfig class is not important for now.

你在上面做出了 3 个设计选择:

 b. A module Config object has no runtime effect on Squid except when it is used as the "current" config for the module. For each module, many, possibly conflicting, Config objects might exist at the same time, but there is only one current config for each module and for Squid as a whole.

我不同意*对于每个模块,可能同时存在多个(相互冲突的)Config 对象*。我认为对于任何给定模块,同时只需要存在 2 个 Config 对象。一个活动配置。一个正在被修改的影子配置。

超过 2 个配置对象,我们就进入了状态维护和合并复杂性的领域,我坚信 Squid 内部甚至不需要考虑允许这些。

我认为我们在重新配置过程上开始出现很大分歧。

我将第一步(1)分解,以展示我们在此处的观点差异以及我试图理解你在说什么……

1. 在第一步中,每个模块通过解析该模块需要解析的任何 squid.conf 选项来创建其 Config 对象。

  1. 让我们暂时忽略如何进行解析。

    • 我感到担心。但好吧。

    b. *让我们暂时忽略模块如何决定哪个 squid.conf 部分与该模块相关。*

    • 这里我们遇到了一个关键假设。我认为我们*不能*忽略它。

    • 发生这种情况如何决定了我们是在进行**填充 Module::Config** 还是**创建 Module::Config**(具有严格的存在状态含义)。

    • 如何发生决定了解析器的很大一部分(好的,我们忽略了这一点)。

    • 如何做出这些决定,决定了内部有多少小的 Module::Config 留在 Squid 中,以及我们如何引用它们。

    • 有多少 ModuleX::Config 实例,决定了我们如何将它们关联起来。

    c. *让我们暂时忽略 squid.conf 如何呈现给模块。*

    • 另一个重要的假设。我认为我们也不能忽略这一点。

    • 如何呈现定义了可以存在的配置状态。

    • 向后兼容性增加了许多限制。

    • 限制如何被 Module::Config 状态处理,定义了相关的 squid.conf 部分如何被检测到。(看到了循环吗?)

    d. *创建的 Config 对象被组装成一个 Squid Config 对象。让我们暂时忽略如何以及由谁来完成。*

    • 另一个我们不能就这样忽略的重要假设。

    • IMO 没有必要如此表述。

    • 我在这里看到的唯一用途是允许调用 Module::Config API 方法。

    • 我们需要澄清你试图通过这个达到什么目的。

    • 这决定了我们是否*能够*处理一个以上的 Module::Config 对象。

    • 还定义了被称为热配置的核心步骤,我认为我们正在为此设计。

    e. *那个没有运行时影响的 Squid Config 对象是第一步的最终结果。如果一个模块未能生成有效的 Config 对象,第一步就会中止。*

    • IMO 这个超级对象甚至不需要作为一个数据结构存在。

    • 它可以完美地作为信息状态存在。

    • 在配置系统的任何地方(向上或向下)的致命错误都是致命错误,而不仅仅是子系统末端当前持有的一些数据。

其他步骤(2)和(3)只有在上述步骤的基本假设下才有意义。在我们同意第一步设计之前,我认为我们应该跳过它们。

-- Amos Jeffries

我认为你的前两个担忧都不是问题。

"Module::Config being integrated into a Squid::Config" worries me. A _lot_ of the current dependency loops in Squid are directly caused by the existence of struct SquidConfig. I was under the impression that the cleanup work was dropping such dependency. We need to clarify this further.

集成并不意味着我们将有一个由各个模块配置成员组成的结构。我们可能有指向 ModuleConfig 的可搜索集合(每个模块一个)或类似的东西。最终的 SquidConfig 类将不会知道各个模块。我们很可能需要一个“单一配置”类,以便我们可以将全局配置传递给需要的代码。

例如,在验证步骤中,某些代码可能需要访问来自各个模块的 Module::Config 对象,而这些代码不能仅仅询问模块本身,因为那些 Module::Config 对象尚未应用/存储。

I disagree with ''For each module, many, possibly conflicting, Config objects might exist at the same time''. I see no cause for more than 2 to exist at the same time for any given module. One active config. One shadow config being altered.

我同意 Squid 可执行文件中可能不需要超过两个 Config 对象。一些未来的配置相关工具可能需要处理更多(例如,某种带有用户输入功能和撤销或历史操作的配置升级工具)。

但从设计角度来看,在现阶段 2 个或 22 个并没有显著差异。我说“多个”是为了强调 Config 对象不被假定为我们现在(最多)拥有的那种与模块状态绑定的、众所周知的全局唯一对象。它们将是“常规”对象,可以根据需要进行复制。

所以我想这些问题已经解决了,让我们称之为进展!

-- AlexRousskov

尽管你认为你不能忽略某些细节,但我认为我们在第一配置步骤的高层定义上达成了粗略的共识。只要你同意我的高层陈述*可能*是正确的(如果我们把细节处理好),我们就达成了一致。

我认为我们可以忽略你担心的绝大部分,直到高层步骤达成一致。一个人总可以忽略事情,直到没有什么可争论的(但有风险,在考虑细节时某些高层决策可能需要重做)!不过,让我们先试着解决其中两个担忧。

1. 我不太理解“创建与填充”问题。我认为我们将进行“创建”和“填充”。显然,模块会创建自己的 Config 对象:模块知道实际的 Module::Config 类型。Configurator(即执行高级配置步骤的 Squid 代码)不使用,也不应该知道该特定类型。然后,创建的对象将根据实际配置文件填充具体值。我们无法立即创建一个已填充的对象,因此这种“填充”过程似乎是合理且必要的。模块代码将填充 Config 还是 Config 会自我填充,目前并不重要。

2. 存在一个名为 SquidConfig 或类似的“累积配置”类。同样,我怀疑我们能否或应该避免它。C9 提供了其中一个动机:跨模块配置验证。如果我们无法在同一位置访问未来模块 M1 和未来模块 M2 的配置,那么我们就无法检查未来模块 M1 的配置是否与未来模块 M2 的配置一致。因此,在验证和应用所有未来配置之前,我们需要将它们存储在某处。该存储就是 SquidConfig。其他用途包括报告和持久化存储:两者都不应该知道所有模块(按名称)的列表,因此两者都需要某种“配置容器”。我希望我没有提出一个包含“Module1::Config c1; Module2::Config c2; …”成员的单一结构,这能解决您相关的担忧。

如果以上方案令人满意,让我们尝试继续讨论第二步,并在需要时回顾更低级别的细节。

-- AlexRousskov

分类: WantedFeature

导航:站点搜索站点页面类别🔼 向上