后端RPC框架【免费下载链接】finagleA fault tolerant, protocol-agnostic RPC system项目地址https://gitcode.com/gh_mirrors/fi/finagle点击查看免费下载Finagle 使用names名称来标识网络位置。当你通过ClientBuilder.dest或各类Client实现构造 Finagle 客户端时都必须提供一个名字。本文将以官方文档 doc/src/sphinx/Names.rst 为骨架结合 finagle-core 的源码实现完整讲解 Finagle 的命名体系Name数据类型、Resolver字符串解析、层级路径Path、委派表Dtab的语法与重写算法、Dtab.local/Dtab.limited的作用域语义以及Addr的动态地址状态机。读完本文你将掌握如何用inet!host:port、zk!ensemble!path等 scheme 字符串正确书写 Finagle 目标地址Name.Bound与Name.Path两种名称形态的区别与适用场景Dtab 的完整语法、|、、通配符*、注释、重写/回溯算法及挂载表式的命名空间思想Dtab.local与Dtab.limited的传播差异以及如何在跨服务请求图中做精准或粗粒度的流量重定向Addr的四态模型Pending / Neg / Failed / Bound如何表达动态的服务发现结果。1. 名称Name是什么1.1 Name 的两种变体Finagle 用Name数据类型表示你想要访问的东西。从源码看finagle-core/src/main/scala/com/twitter/finagle/Name.scala 中sealed trait Name只有两个实现case class Name.Bound(va: Var[Addr]): 标识一组网络位置。Var[Addr]表示一组可变化的Internet 地址host、port 对例如一个动态的 serverset 或 DNS 结果集。case class Name.Path(path: Path): 表示由层级路径Path即一串字节字符串序列所刻画的抽象名称。它必须先经过当前命名空间绑定才能得知具体的网络位置在哪里。源码注释对此做了更精确的补充Name.Bound携带三个字段 ——addr动态地址、id标识该地址的稳定标识用于相等性判断、path绑定过程中未被处理的残余路径分量而Name.Path则是未绑定路径必须由某种上下文通常是 Dtab来解析。实践中客户端通常借助Resolver把目标名称字符串解析成Name通过ClientBuilder.dest或协议对象的newClient如Http.newClient(/s/org/servicename)传入目标名底层都用Resolver完成解析。1.2 Name 的 Java 兼容 APIName对象还提供了 Java 兼容的入口Name.scala 中的object NamesNames.bound(addrs: Address*)—— 预绑定一组地址Names.bound(service)—— 直接把某个Service绑定为名称源码注释强调这在不经过网络测试 Finagle 客户端时极其有用示例中Http.client.newService(Name.bound(service), ...)直接返回 HelloNames.fromPath(path)—— 从路径构造基于路径的 Name。2. Resolver把字符串变成 Name2.1 scheme!arg 语法Resolver.eval负责把字符串解析成Name。形如scheme!arg的字符串使用给定scheme解释arg并且总是解析为Name.Bound实例。例如inet!twitter.com:80使用inetresolver 解释地址twitter.com:80——inet正是靠 DNS 完成这一工作的。再如zk!myzkhost.mycompany.com:2181!/my/zk/path读取 ZooKeeper 集群myzkhost.mycompany.com:2181上路径/my/zk/path处的 serversetServerSet 是 Twitter commons 中定义的、可动态变化的服务器集合抽象。当 scheme 缺失时默认使用inet因此twitter.com:8080与inet!twitter.com:8080完全等价。2.2 Resolver 的源码实现Resolver.scala 展示了完整的解析逻辑词法分析器lex把字符串切分成El元素、Bang!、Eq三类 tokeneval的语法为name : scheme ! arg以/开头的字符串被直接解释为逻辑名交给 Dtab 解释见下文否则按scheme!arg查表在已注册的 Resolver 中按scheme查找找不到抛出ResolverNotFoundException提示需要把包含该 resolver 的 jar 加入 classpath没有任何!时默认落到inetResolverResolver 通过com.twitter.finagle.util.LoadService机制发现实现Resolvertrait含val scheme: String与def bind(arg: String): Var[Addr]、提供 0 参构造并在META-INF/services/com.twitter.finagle.Resolver文件中登记即可注册新 resolver内置的基础 resolver 包括NegResolverneg恒为Addr.Neg、NilResolvernil恒为空Addr.Bound、FailResolverfail恒为Addr.Failed以及服务加载的inetresolver 和FixedInetResolverevalLabeled还支持labeladdr形式解析出(Name, label)二元组label 用于指标/日志打标。InetResolver的实现InetResolver.scala也值得关注它用AsyncSemaphore(100)并发控制 DNS 查询通过resolvePool线程池执行getAllByName并对localhost/空主机名做了免线程池的 Loopback 快速路径同时统计dns_lookups、dns_lookup_failures与queue_size指标。3. 层级路径Paths以/开头的名字是 Unix 传统意义上的层级路径。它们表示一个抽象位置——即你想要什么what。路径名必须被当前命名空间绑定才能确定网络位置在哪里where。例如/s/crawler可能就表示crawler这个服务。Finagle 用Path表示路径Path.scala它本质上是一串字节字符串Buf的序列。在 Namer.scala 中可以看到一个Namer就是把Path翻译成NameTree[Name]的上下文翻译结果用Activity表示因为 namer 可能代表外部过程如 DNS 查询或 ZooKeeper 查找bind递归地沿路径查找允许最大 100 层递归深度。4. 用委派表Dtab解释路径4.1 Dtab 是什么委派表delegation table简称dtab定义了 Finagle 事务的命名空间。一个 dtab 由有序的委派列表组成共同决定路径如何被解释。一条委派delegation是一条重写规则src dest其中src和dest都是路径。当某个名字以src为前缀时前缀被替换为dest否则规则不生效。例如/s /s#/foo/bar把路径/s/crawler重写为/s#/foo/bar/crawler注意前缀匹配的是路径分量components而不是字符。/s是/s/crawler的前缀但不是/s#/foo/bar/crawler的前缀。4.2 通配符前缀可以包含通配符*来匹配任意一个分量。例如/s#/*/bar /t/bah会把/s#/foo/bar/baz或/s#/boo/bar/baz都重写为/t/bah/baz从源码看DtabBase.scala 中Dentry.Prefix的元素分为Prefix.Label(buf)必须精确匹配与Prefix.AnyElem即*匹配任意分量两类matches(path)依次比较实现上述语义。4.3 系统路径 /$/namer/path...以/$/开头的路径称为系统路径system paths。它们被 Finagle 特殊解释作用类似 resolver scheme。形如/$/namer/path..使用给定的Namer解释剩余路径从而把路径翻译成地址。例如/$/inet/localhost/8080被 Finagle 绑定到 Internet 地址localhost:8080又如/$/com.twitter.serverset/zk.local.twitter.com:2181/foo/bar是描述 ZooKeeper 集群zk.local.twitter.com:2181上 serverset/foo/bar的路径。Namer.scala 中的全局 namer 正是处理/$/classname/path...形式的路径通过反射实例化类名对应的Namer要求 0 参构造把残余路径交给它同时内置处理/$/nil/...等特殊路径。4.4 注释与语法细节Dtab 可以包含以#开头的行注释。#前面必须有空白字符或分隔符如;、|、。例如下面这个带注释的 Dtab# delegation for /s /s /a # prefer /a | ( /b # or share traffic between /b and /c /c );等价于这个无注释版本/s /a | (/b /c);这里已经出现 Dtab 目标端的两种组合语法|表示备选fail-over表示并集union负载均衡。这背后是 NameTree.scala 中的NameTree数据结构Alt节点表示多个子树之间的故障切换选择第一个非负子树Union节点表示多个子树的并集对子树做负载均衡Leaf是叶子Neg表示否定位置此处不存在目标Empty表示空位置存在但暂无栖息。Dentry的dst字段类型正是NameTree[Path]所以一条委派的目标本身可以是一棵树。5. Dtab 重写与回溯一个完整示例我们用 Dtab 定义逻辑名如/s/crawler如何翻译成地址。因为重写被抽象出来我们可以通过操作 dtab 让同一个 Finagle 进程适应不同环境例如让/s/crawler在生产环境指向一组主机在开发/测试环境指向另一组主机。考虑如下 Dtab/zk# /$/com.twitter.serverset; /zk /zk#; /s## /zk/zk.local.twitter.com:2181; /s# /s##/prod; /s /s#;路径/s/crawler被逐步重写1. /s/crawler 2. /s#/crawler 3. /s##/prod/crawler 4. /zk/zk.local.twitter.com:2181/prod/crawler 5. /zk#/zk.local.twitter.com:2181/prod/crawler 6. /$/com.twitter.serverset/zk.local.twitter.com:2181/prod/crawler最终/s/crawler被转换为zk.local.twitter.com:2181上的 serverset/prod/crawler。这里用#字符表示处理器handler路径/s#处理/s依此类推。为什么需要这层间接考虑通过给/s加前缀来重新定义它——这是一种常见的命名空间操作。如果直接写/s /s/prefix会无限递归/s/crawler /s/prefix/crawler /s/prefix/prefix/crawler ...而有了/s#之后我们改为追加/s /s#/prefix即可得到想要的效果/s/crawler /s#/prefix/crawler ...Namer 绑定递归深度限制为 100正是为了防止此类无限递归在实际执行时失控Namer.scala 中bind注释明确写明了这一限制。5.1 优先级后追加的委派先尝试我们可以轻松操纵 Dtab 来影响解析的某一部分。例如若想使用 staging 环境的服务实例而非生产实例只需追加委派/s# /s##/staging得到/zk# /$/com.twitter.serverset; (a) /zk /zk#; (b) /s## /zk/zk.local.twitter.com:2181; (c) /s# /s##/prod; (d) /s /s#; (e) /s# /s##/staging; (f)/s/crawler的重写过程每步标注了命中的规则/s/crawler (e) /s#/crawler (f) /s##/staging/crawler (c) /zk/zk.local.twitter.com:2181/staging/crawler (b) /zk#/zk.local.twitter.com:2181/staging/crawler (a) /$/com.twitter.serverset/zk.local.twitter.com:2181/staging/crawler关键规则是后追加的委派先被尝试如果以某条委派为根的重写没能产生地址就从下一条匹配的委派继续重写。结合上述示例再看源码DtabBase.lookup实现中dentries dentries0.reverse即把追加序反转后按顺序收集匹配项只有一条匹配时直接返回该结果多条匹配时则构造NameTree.Alt(matches: _*)——Alt正是选择第一个非负子树的故障切换语义见 NameTree.scala。这就是后追加者优先 失败回溯的底层机制。其整体效果是一个回退机制fallback如果 staging 环境中存在crawler就使用它否则回退到生产定义。假设/staging/crawler在zk.local.twitter.com:2181上不存在搜索会从步骤 (a) 回溯产生如下重写序列/s/crawler (e) /s#/crawler (f) /s##/staging/crawler (c) /zk/zk.local.twitter.com:2181/staging/crawler (b) /zk#/zk.local.twitter.com:2181/staging/crawler (a) /$/com.twitter.serverset/zk.local.twitter.com:2181/staging/crawler (d) /s##/prod/crawler (c) /zk/zk.local.twitter.com:2181/prod/crawler (b) /zk#/zk.local.twitter.com:2181/prod/crawler (a) /$/com.twitter.serverset/zk.local.twitter.com:2181/prod/crawler由此可见委派提供了一种简单而灵活的定义命名空间的方式其效果类似 Unix 的挂载表mount table名字独立存在但绑定的细节由环境——即 dtab——负责。5.2 Dtab 沿请求图传播如果使用受支持的协议委派会在服务器之间传递。这样一台服务器就在**整个请求图entire request graph**的上下文中改变了名字的解释——也就是说命名空间的作用域是一个事务一台服务器可以影响当前事务的下游行为。例如开发者想把分布式系统中某个组件替换成该组件的开发版本只需编排发起方比如一个 HTTP 前端追加一条表达该覆盖的委派即可。Finagle 在以下协议中提供了委派传递支持TTwitterThrift、Mux、ThriftMuxMux 的变体以及 HTTP。使用这些协议时动态追加到请求上的委派可以在整个分布式请求图中生效。委派通过DtabAPI 动态追加。这是一个强大的能力应当谨慎使用。6. Dtab APIlocal 与 limited委派可以通过DtabAPI 动态追加或覆盖具体方式是两种作用域化的委派表Dtab.local和Dtab.limited。6.1 Dtab.local按请求、可传播的作用域local委派被定义为**按请求per-request、可传播的作用域。它非常适合应用于整个请求图的覆盖因为它对下游服务同样生效**。源码中 DtabBase.scala 的注释进一步说明local会在使用受支持协议Http、TTwitter、Mux、ThriftMux 等时被序列化进出站请求因此它对整个请求图都成立。6.2 Dtab.limited按请求、不传播的作用域limited委派是**按请求、不传播**的作用域。与Dtab.local不同Dtab.limited只作用于当前请求不影响调用图的其他部分。此外当Dtab.limited与Dtab.local冲突时只有Dtab.local被尊重。源码实现印证了这一点DtabCompanionBase用两个独立的Local[Dtab]分别保存_local与_limited并注明 Finagle 实际使用Dtab.base Dtab.limited Dtab.local来绑定Name.Path其中base是进程级、启动时设置、一般不再变更的系统/全局委派表。两者作用域都由com.twitter.util.Local决定local随请求传播limited不传播。6.3 粒度权衡与示例limited的更高粒度允许对回退和故障转移行为做更精细的控制非常适合大型请求图中只有部分端点需要重路由的场景而当更多端点开始故障时local这种粗线条broad strokes式的整体重路由就更有用了。考虑如下服务图ServiceA - ServiceB - ServiceC。在发往ServiceA的请求上设置Dtab.local它同样会体现在ServiceB和ServiceC中在发往ServiceA的请求上设置Dtab.limited它只存在于ServiceA内该请求的作用域内ServiceB和ServiceC看不到这个状态。再考察四个具体场景ServiceA为某请求对ServiceC设置Dtab.local将其重路由到ServiceD。由于ServiceB会传播Dtab.local请求同样被重路由到ServiceD。ServiceA为某请求对ServiceC设置Dtab.limited将其重路由到ServiceD。没有任何效果——因为ServiceA并不直接调用ServiceC且 limited 状态不会被传播到ServiceB。ServiceA为ServiceC设置Dtab.local重路由到ServiceD同时设置Dtab.limited重路由到ServiceE。行为与场景 1 相同——冲突时 local 优先。ServiceA为某请求对ServiceB设置Dtab.limited将其重路由到ServiceD。请求到ServiceB会被重路由但状态不会被传播。DtabCompanionBase还提供了unwind方法在某个函数作用域内临时设置 local/limited dtab退出时自动恢复原状态同时提供了隐式Flaggable[Dtab]让 Dtab 可以直接作为com.twitter.app.Flag使用如命令行-dtab启动参数还有Dtab.read用于按语法解析 dtab 字符串。7. Addr动态地址Name.Bound包含一个Var[Addr]表示动态变化的Addr。com.twitter.util.Var实现了一种自调整计算。Addr处于以下三种实际上四种状态之一Addr.Pending: 绑定仍在进行中——可能正在等待 DNS 应答或 ZooKeeper 操作完成。Addr.Neg: 绑定结果为否定即目标不存在。Addr.Failed(cause: Throwable): 绑定失败携带给定的cause。Addr.Bound(addrs: Set[Address]): 绑定成功携带一组具体的端点地址。从源码看Addr.scalaAddr.Bound还带metadatanamer 或 resolver 可附加任意键值元数据例如地理信息供客户端栈使用Addr.Pending与Addr.Neg是单例对象Java 兼容入口在object AddrsnewBoundAddr、newFailedAddr、pendingAddr、negAddr等。现在可以理解Var[Addr]有能力表示一个移动的目标moving target例如动态的 serverset。8. 小结名称、地址与绑定按文档脚注的精炼总结name 标识你想要什么whataddress 是位置标识对象在哪里wherebinding 是把名字变成地址的过程。客户端构造时通过Resolver解析scheme!arg字符串得到Name.Bound或以/开头的逻辑路径得到Name.Path路径由 Dtab 借助、|、、*、/$/系统路径等机制重写后追加的委派优先尝试、失败自动回溯最终由 namer如/$/com.twitter.serverset反射出的 serverset namer翻译成Var[Addr]Addr的四态Pending / Neg / Failed / Bound让地址集可以随 DNS、ZooKeeper 等外部事实动态演化Dtab.local随请求传播、覆盖整个请求图Dtab.limited仅限当前请求且不传播——两者配合即可实现从单端点精准重路由到全图粗粒度切换的任意粒度的流量控制。掌握了名称、路径、Dtab 与 Addr 这条完整链路你就理解了 Finagle 服务发现与路由的根基也就能熟练地在生产/测试环境之间、在不同命名空间之间灵活切换目标服务。赞分享后端RPC框架【免费下载链接】finagleA fault tolerant, protocol-agnostic RPC system项目地址https://gitcode.com/gh_mirrors/fi/finagle点击查看免费下载相关推荐gRPC 名称解析Name Resolution全指南URI 命名语法与 Resolver 插件机制gRPC 名称解析Name Resolution全指南URI 命名语法与 Resolver 插件机制 gRPC 客户端在创建 Channel 时需要把一后端RPC框架微服务通信PhpSpreadsheet 定义名称Defined Names完全指南命名区域与命名公式的创建、作用域与实战PhpSpreadsheet 定义名称Defined Names完全指南命名区域与命名公式的创建、作用域与实战 导读 在 Excel 电子表格中 A1后端PRQL 名称解析Name Resolution机制详解作用域、通配符匹配与 SQL 表前缀生成PRQL 名称解析Name Resolution机制详解作用域、通配符匹配与 SQL 表前缀生成 PRQLPipelined Relational Qu后端上一篇5分钟掌握authentik身份认证平台3大核心优势与完整文档系统解析下一篇终极指南如何用hkcam打造专属HomeKit智能监控系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考