首页 文章 精选 留言 我的

精选列表

搜索[VPU驱动],共10000篇文章
优秀的个人博客,低调大师

带着问题读 TiDB 源码:Power BI Desktop 以 MySQL 驱动连接 TiDB 报错

​常有人说,阅读源码是每个优秀开发工程师的必经之路,但是在面对像类似 TiDB 这样复杂的系统时,源码阅读是一个非常庞大的工程。而对一些 TiDB User 来说,从自己日常遇到的问题出发,反过来阅读源码就是一个不错的切入点,因此我们策划了《带着问题读源码》系列文章。 本文为该系列的第二篇,从一个 Power BI Desktop 在 TiDB 上表现异常的问题为例,介绍从问题的发现、定位,到通过开源社区提 issue、写 PR 解决问题的流程,从代码实现的角度来做 trouble shooting,希望能够帮助大家更好地了解 TiDB 源码。 首先我们重现一下失败的场景(TiDB 5.1.1 on MacOS),建一个简单的只有一个字段的表: CREATE TABLE test(name VARCHAR(1) PRIMARY KEY); MySQL 上可以 TiDB 上就不可以,报错 DataSource.Error: An error happened while reading data from the provider: 'Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.' Details: DataSourceKind=MySql DataSourcePath=localhost:4000;test 看 general log TiDB 上最后一条跑的 SQL 是: select COLUMN_NAME, ORDINAL_POSITION, IS_NULLABLE, DATA_TYPE, case when NUMERIC_PRECISION is null then null when DATA_TYPE in ('FLOAT', 'DOUBLE') then 2 else 10 end AS NUMERIC_PRECISION_RADIX, NUMERIC_PRECISION, NUMERIC_SCALE, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT, COLUMN_COMMENT AS DESCRIPTION, COLUMN_TYPE from INFORMATION_SCHEMA.COLUMNS where table_schema = 'test' and table_name = 'test'; 我们用 tiup 启动一个 TiDB 集群,使用 tiup client 执行该命令,tiup client 也会报错: error: mysql: sql: Scan error on column index 4, name "NUMERIC_PRECISION_RADIX": converting NULL to int64 is unsupported 那我们的注意力就集中在解决这条语句的问题,我们先看 tiup client 上报的这个错意味着什么。tiup client 使用的是 golang xo/usql 库,但是在 xo/usql 库中,我们并不能找到对应的报错信息,grep converting 关键字返回极有限且无关的内容。我们再看 xo/usql 的 mysql driver,其中又引用到了 go-sql-driver/mysql,下载它的代码并 grep converting,只返回了 changelog 中的一条信息,大概率报错的地方也不在这个库中。浏览一下 go-sql-driver/mysql 中的代码,发现它依赖于 database/sql,那我们看看 database/sql 的内容。database/sql 是 golang 的标准库,所以我们需要下载 golang 的源码。在 golang 的 database 目录中 grep converting,很快就找到了与报错信息相符的内容: go/src/database/sql/convert.go case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: if src == nil { return fmt.Errorf("converting NULL to %s is unsupported", dv.Kind()) } s := asString(src) i64, err := strconv.ParseInt(s, 10, dv.Type().Bits()) if err != nil { err = strconvErr(err) return fmt.Errorf("converting driver.Value type %T (%q) to a %s: %v", src, s, dv.Kind(), err) } dv.SetInt(i64) return nil 我们再追踪这个片段,看这里的类型是如何来的,最终我们会回到 go-sql-driver/mysql 中: mysql/fields.go case fieldTypeLongLong: if mf.flags&flagNotNULL != 0 { if mf.flags&flagUnsigned != 0 { return scanTypeUint64 } return scanTypeInt64 } return scanTypeNullInt 这部分的代码是在解析语句返回体中的 column definition,转换成 golang 中的类型。我们可以使用 mysql --host 127.0.0.1 --port 4000 -u root --column-type-info 连上后查看有问题的 SQL 返回的 column metadata: MySQL Field 5: `NUMERIC_PRECISION_RADIX` Catalog: `def` Database: `` Table: `` Org_table: `` Type: LONGLONG Collation: binary (63) Length: 3 Max_length: 0 Decimals: 0 Flags: BINARY NUM TiDB Field 5: `NUMERIC_PRECISION_RADIX` Catalog: `def` Database: `` Table: `` Org_table: `` Type: LONGLONG Collation: binary (63) Length: 2 Max_length: 0 Decimals: 0 Flags: NOT_NULL BINARY NUM 可以很明显的看到,tiup client 报错信息中的 NUMERIC_PRECISION_RADIX 字段的 column definition 在 TiDB 上有明显的问题,该字段在 TiDB 的返回体中被标记为了 NOT_NULL,很明显这是不合理的,因为该字段显然可以是 NULL,MySQL 的返回值也体现了这一点。所以 xo/usql 在处理返回体的时候报错了。到了这里,我们已经发现了 client 端为什么会报错,下面我们就需要去寻找 TiDB 为什么会返回一个错误的 column definition。 通过 TiDB Dev Guide 我们可以知道 TiDB 中一条 DQL 语句的大体执行过程,我们从入口的 server/conn.go#clientConn.Run 往下看去,一路经过 server/conn.go#clientConn.dispatch、server/conn.go#clientConn.handleQuery、server/conn.go#clientConn.handleStmt、server/driver_tidb.go#TiDBContext.ExecuteStmt、session/session.go#session.ExecuteStmt、executor/compiler.go#Compiler.Compile、planner/optimize.go#Optimize、planner/optimize.go#optimize、planner/core/planbuilder.go#PlanBuilder.Build、planner/core/logical_plan_builder.go#PlanBuilder.buildSelect,在 buildSelect 中,我们可以看到 TiDB planner 对查询语句进行的一系列处理,然后我们就可以走到 planner/core/expression_rewriter.go#PlanBuilder.rewriteWithPreprocess 和 planner/core/expression_rewriter.go#PlanBuilder.rewriteExprNode,在 rewriteExprNode 中,会把有问题的字段 NUMERIC_PRECISION_RADIX 进行解析,最终这条 CASE 表达式的解析会在 expression/builtin_control.go#caseWhenFunctionClass.getFunction 中,我们终于走到了计算 CASE 表达式返回的 column definition 的地方(这依赖于遍历 compiler 解析出的 AST): for i := 1; i < l; i += 2 { fieldTps = append(fieldTps, args[i].GetType()) decimal = mathutil.Max(decimal, args[i].GetType().Decimal) if args[i].GetType().Flen == -1 { flen = -1 } else if flen != -1 { flen = mathutil.Max(flen, args[i].GetType().Flen) } isBinaryStr = isBinaryStr || types.IsBinaryStr(args[i].GetType()) isBinaryFlag = isBinaryFlag || !types.IsNonBinaryStr(args[i].GetType()) } if l%2 == 1 { fieldTps = append(fieldTps, args[l-1].GetType()) decimal = mathutil.Max(decimal, args[l-1].GetType().Decimal) if args[l-1].GetType().Flen == -1 { flen = -1 } else if flen != -1 { flen = mathutil.Max(flen, args[l-1].GetType().Flen) } isBinaryStr = isBinaryStr || types.IsBinaryStr(args[l-1].GetType()) isBinaryFlag = isBinaryFlag || !types.IsNonBinaryStr(args[l-1].GetType()) } fieldTp := types.AggFieldType(fieldTps) // Here we turn off NotNullFlag. Because if all when-clauses are false, // the result of case-when expr is NULL. types.SetTypeFlag(&fieldTp.Flag, mysql.NotNullFlag, false) tp := fieldTp.EvalType() if tp == types.ETInt { decimal = 0 } fieldTp.Decimal, fieldTp.Flen = decimal, flen if fieldTp.EvalType().IsStringKind() && !isBinaryStr { fieldTp.Charset, fieldTp.Collate = DeriveCollationFromExprs(ctx, args...) if fieldTp.Charset == charset.CharsetBin && fieldTp.Collate == charset.CollationBin { // When args are Json and Numerical type(eg. Int), the fieldTp is String. // Both their charset/collation is binary, but the String need a default charset/collation. fieldTp.Charset, fieldTp.Collate = charset.GetDefaultCharsetAndCollate() } } else { fieldTp.Charset, fieldTp.Collate = charset.CharsetBin, charset.CollationBin } if isBinaryFlag { fieldTp.Flag |= mysql.BinaryFlag } // Set retType to BINARY(0) if all arguments are of type NULL. if fieldTp.Tp == mysql.TypeNull { fieldTp.Flen, fieldTp.Decimal = 0, types.UnspecifiedLength types.SetBinChsClnFlag(fieldTp) } 查看如上计算 column definition flag 的代码我们可以发现,无论 CASE 表达式的情况是怎么样的,NOT_NULL 标记位都一定会被设置成 false,所以问题不出现在这里!这个时候我们只能沿着上面的代码路径往回看,看看上面生成的 column definition 在后续有没有被修改。终于在 server/conn.go#clientConn.handleStmt 中,发现它调用了 server/conn.go#clientConn.writeResultSet,然后又陆续调用了server/conn.go#clientConn.writeChunks、server/conn.go#clientConn.writeColumnInfo、server/column.go#ColumnInfo.Dump 和 server/column.go#dumpFlag,在 dumpFlag 中,之前生成的 column definition flag 被修改了: func dumpFlag(tp byte, flag uint16) uint16 { switch tp { case mysql.TypeSet: return flag | uint16(mysql.SetFlag) case mysql.TypeEnum: return flag | uint16(mysql.EnumFlag) default: if mysql.HasBinaryFlag(uint(flag)) { return flag | uint16(mysql.NotNullFlag) } return flag } } 终于,我们找到了 TiDB 返回错误的 column definition 的原因!其实这个 bug 在 TiDB 最新版5.2.0中已经被修复了:*: fix some problems related to notNullFlag by wjhuang2016 · Pull Request #27697 · pingcap/tidb。 最后,在上述阅读代码的过程中,我们其实最好能够看到被 TiDB 解析后的 AST 是什么样子的,这样在最后遍历 AST 的过程中,才不至于摸瞎。TiDB dev guide 中有 parser 章节讲解如何调试 parser,parser/quickstart.md at master · pingcap/parser 中也有样例输出生成的 AST,但是简单地输出基本没有任何作用,我们可以使用 davecgh/go-spew 直接输出 parser 生成的 node,这样就能获得一个可被人理解的 tree: package main import ( "fmt" "github.com/pingcap/parser" "github.com/pingcap/parser/ast" _ "github.com/pingcap/parser/test_driver" "github.com/davecgh/go-spew/spew" ) func parse(sql string) (*ast.StmtNode, error) { p := parser.New() stmtNodes, _, err := p.Parse(sql, "", "") if err != nil { return nil, err } return &stmtNodes[0], nil } func main() { spew.Config.Indent = " " astNode, err := parse("SELECT a, b FROM t") if err != nil { fmt.Printf("parse error: %v\n", err.Error()) return } fmt.Printf("%s\n", spew.Sdump(*astNode)) } (*ast.SelectStmt)(0x140001dac30)({ dmlNode: (ast.dmlNode) { stmtNode: (ast.stmtNode) { node: (ast.node) { text: (string) (len=18) "SELECT a, b FROM t" } } }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, SelectStmtOpts: (*ast.SelectStmtOpts)(0x14000115bc0)({ Distinct: (bool) false, SQLBigResult: (bool) false, SQLBufferResult: (bool) false, SQLCache: (bool) true, SQLSmallResult: (bool) false, CalcFoundRows: (bool) false, StraightJoin: (bool) false, Priority: (mysql.PriorityEnum) 0, TableHints: ([]*ast.TableOptimizerHint) <nil> }), Distinct: (bool) false, From: (*ast.TableRefsClause)(0x140001223c0)({ node: (ast.node) { text: (string) "" }, TableRefs: (*ast.Join)(0x14000254100)({ node: (ast.node) { text: (string) "" }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, Left: (*ast.TableSource)(0x14000156480)({ node: (ast.node) { text: (string) "" }, Source: (*ast.TableName)(0x1400013a370)({ node: (ast.node) { text: (string) "" }, resultSetNode: (ast.resultSetNode) { resultFields: ([]*ast.ResultField) <nil> }, Schema: (model.CIStr) , Name: (model.CIStr) t, DBInfo: (*model.DBInfo)(<nil>), TableInfo: (*model.TableInfo)(<nil>), IndexHints: ([]*ast.IndexHint) <nil>, PartitionNames: ([]model.CIStr) { } }), AsName: (model.CIStr) }), Right: (ast.ResultSetNode) <nil>, Tp: (ast.JoinType) 0, On: (*ast.OnCondition)(<nil>), Using: ([]*ast.ColumnName) <nil>, NaturalJoin: (bool) false, StraightJoin: (bool) false }) }), Where: (ast.ExprNode) <nil>, Fields: (*ast.FieldList)(0x14000115bf0)({ node: (ast.node) { text: (string) "" }, Fields: ([]*ast.SelectField) (len=2 cap=2) { (*ast.SelectField)(0x140001367e0)({ node: (ast.node) { text: (string) (len=1) "a" }, Offset: (int) 7, WildCard: (*ast.WildCardField)(<nil>), Expr: (*ast.ColumnNameExpr)(0x14000254000)({ exprNode: (ast.exprNode) { node: (ast.node) { text: (string) "" }, Type: (types.FieldType) unspecified, flag: (uint64) 8 }, Name: (*ast.ColumnName)(0x1400017dc70)(a), Refer: (*ast.ResultField)(<nil>) }), AsName: (model.CIStr) , Auxiliary: (bool) false }), (*ast.SelectField)(0x14000136840)({ node: (ast.node) { text: (string) (len=1) "b" }, Offset: (int) 10, WildCard: (*ast.WildCardField)(<nil>), Expr: (*ast.ColumnNameExpr)(0x14000254080)({ exprNode: (ast.exprNode) { node: (ast.node) { text: (string) "" }, Type: (types.FieldType) unspecified, flag: (uint64) 8 }, Name: (*ast.ColumnName)(0x1400017dce0)(b), Refer: (*ast.ResultField)(<nil>) }), AsName: (model.CIStr) , Auxiliary: (bool) false }) } }), GroupBy: (*ast.GroupByClause)(<nil>), Having: (*ast.HavingClause)(<nil>), WindowSpecs: ([]ast.WindowSpec) <nil>, OrderBy: (*ast.OrderByClause)(<nil>), Limit: (*ast.Limit)(<nil>), LockTp: (ast.SelectLockType) none, TableHints: ([]*ast.TableOptimizerHint) <nil>, IsAfterUnionDistinct: (bool) false, IsInBraces: (bool) false, QueryBlockOffset: (int) 0, SelectIntoOpt: (*ast.SelectIntoOption)(<nil>) })

优秀的个人博客,低调大师

阿里集团业务驱动的升级 —— 聊一聊Dubbo 3.0 的演进思路

作者 | 远云 三位一体 2020年底,阿里云提出了“三位一体”的理念,目标是希望将“自研技术”、“开源项目”、“商业产品”形成统一的技术体系,令技术的价值可以达到最大化。 阿里集团内部的 HSF 框架在经历了多年双十一流量洪峰的考验后,锻炼出了高性能和高可用的核心竞争力。而对于 Dubbo,作为国内外最受欢迎的服务治理框架之一,它的开源亲和性就不用再多说了。 Dubbo 3.0 作为三位一体架构的首推方案,在集团内被寄予厚望。它完美融合了内部 HSF 的特性,天然拥有高性能、高可用的核心能力,我们期望用它来解决内部落地问题,做到技术栈统一。目前在考拉已经大规模落地,未来也会在众多核心场景进行落地,并承载 618、双十一等复杂的业务场景。 Dubbo 3.0 带来的好处 在具体说明 Dubbo 3.0 的变化细节之前,先从两个方面说一说升级到了 Dubbo 3.0 能带来什么好处。 首先是,Dubbo 3.0 会着力提升大规模集群实践中的性能与稳定性,通过优化数据存储方式来降低单机资源损耗,并基于此保证超大规模集群的水平扩容的情况下集群的稳定性。同时,Dubbo 3.0 提出了柔性集群的概念,能够在异构体系下有效保证和提高全链路总体的可靠性和资源的利用率。 第二点是 Dubbo 3.0 代表着 Dubbo 全面拥抱云原生的里程碑。当前 Dubbo 在国内外有着基数巨大的用户群体,而随着云原生时代的到来,这些用户上云的需求越来越强烈。Dubbo 3.0 将提供一整套的解决方案、迁移路径与最佳实践,来帮助企业实现云原生转型,从而享受云原生带来的红利。 1、业务收益 那么站在业务应⽤的视角来看,如果升级到 Dubbo 3.0,能获得哪些具体的收益呢? 首先,在性能与资源利用率⽅面,Dubbo 3.0 能有效降低框架带来的额外资源消耗,从而⼤幅提升资源利用率。 从单机视⻆,Dubbo 3.0 能节省约 50% 的内存占⽤;从集群视角,Dubbo 3.0 能⽀持的集群实例规模以百万计,为未来更大规模的业务扩容打下基础;而 Dubbo 3.0 对 Reactive Stream 通信模型的支持,在⼀些业务场景下能带来整体吞吐量的⼤幅提升。 其次,Dubbo 3.0 给业务架构升级带来了更多的可能性。最直观的就是通信协议的升级,给业务架构带来了更多选择。 Dubbo 原来的协议其实在⼀定程度上束缚了微服务接⼊⽅式。举个例子,移动端、前端业务要接入 Dubbo 的后端服务,需要经过网关层的协议转换;再比如,Dubbo 只⽀持 request-response 模式的通信,这使得⼀些需要流式传输或反向通信的场景⽆法得到很好的支持。 最后,Dubbo 3.0 给业务侧的云原生升级带来了整体的解决方案。不论是底层基础设施升级带来的被动变化,还是业务为解决痛点问题进行的主动升级,当业务升级到云原生,Dubbo 3.0 通过给出云原生解决方案,可以帮助业务产品快速接入云原生。 Dubbo 3.0 概览 在明确了升级到 Dubbo 3.0 能够带来的收益之后,来看看 Dubbo 3.0 有哪些具体的改动。 1、支持全新的服务发现模型。Dubbo 3.0 尝试从应用模型入手,对其云原生主流设计模型优化其存储结构,避免在模型上带来互通问题。新模型在数据组织上高度压缩,能有效提高性能和集群的可伸缩性。 2、提出了下一代 RPC 协议 —— Triple。这是一个基于 HTTP/2 设计的完全兼容 gRPC 协议的开放性新协议,由于是基于 HTTP/2 设计的,具有极高的网关友好型和穿透性,完全兼容 gRPC 协议使得其在多语言互通方面上天然具有优势。 3、提出了统一治理规则。这套规则面向云原生流量治理,能够覆盖传统 SDK 部署、Service Mesh 化部署、VM 虚拟机部署、Container 容器部署等一系列场景。一份规则治理全部场景可以大大降低流量治理成本,使得异构体系下的全局流量治理得到统一。 4、提供接入 Service Mesh 的解决方案。面向 Mesh 场景,Dubbo 3.0 提出了两种接入方式:一种是 Thin SDK 模式,部署模型和当前 Service Mesh 主流部署场景完全一样,而 Dubbo 将进行瘦身,屏蔽掉与 Mesh 相同的治理功能,仅保留核心的 RPC 能力;第二种是 Proxyless 模式,Dubbo 将接替 Sidecar 的工作职责,主动与控制面进行通信,基于 Dubbo 3.0 的统一治理规则,应用云原生流量治理功能。 应用级服务注册发现 应用级服务发现模型的原型其实最早在 Dubbo 2.7.6 版本中已经提出来了,经过一段时间的迭代,最终形成了 Dubbo 3.0 中一个较为稳定的模型。 在 Dubbo 2.7 及以前版本中,应用进行服务注册和发现时,都是以接口为粒度,每个接口都会对应在注册中心上的一条数据,不同的机器会注册上属于当前机器的元数据信息或者接口级别的配置信息,如序列化、机房、单元、超时配置等。 所有提供此服务的服务端在进行重启或者发布时,都会在接口粒度独立地变更。举个例子,一个网关类应用依赖了上游应用的 30 个接口,当上游应用在发布时,就有 30 个对应的地址列表在进行机器的上线和下线。 以接口作为注册发现的第一公民的方式是最早的 SOA 或微服务的拆分方式,提供了单一服务、单一节点的独立和动态变更能力。随着业务的发展,单一应用依赖的服务数在不断增多,每个服务提供方的机器数量也由于业务或容量原因不断增长,从客户端整体上看,依赖的总服务地址数迅速上涨。根据这种情况,可以考虑从注册发现的流程设计上优化。 这里注意有两个特征: • 随着单体应用拆分为多微服务应用的基本完成,大规模的服务拆分和重组已经不再是痛点,大部分接口都只被一个应用提供或者固定几个应用提供。 • 大量用于标志地址信息的 URL 都是存在极大冗余的,如超时时间、序列化等。这些配置变更频率极低,却在每个 URL 中都出现。 结合以上特征,最终应用级注册发现被提出了,即以应用作为注册发现的基本维度。和接口级的主要区别是,原来一个应用如果提供了 100 个接口,需要在注册中心上注册 100 个节点;如果这个应用有 100 台机器,那每次发布,对于它的客户端来说都是 10000 个虚拟节点的变更。而应用级注册发现则只需要 1 个节点, 每次发布只变更 100 个虚拟节点。对于依赖服务数多、机器多的应用而言,是几十到上百分之一数量级的规模下降,内存占用上也将至少下降一半。 然而,技术方案的设计不仅仅需要考虑功能正确,还需要考虑现有业务的升级。因此,升级到应用级注册发现的基础,是在其功能上需要对齐接口级注册发现的能力。而无论客户端是否升级或是否开启应用级注册发现,前提都是不影响正确的业务调用。 为了提供这个保证,我们设计了一个新的组件,元数据中心,用于管理两部分数据: • 接口应用映射关系:上报和查询接口到应用的映射,可以决定客户端是否启用应用级,避免业务代码变更; • 应用级元数据快照:当一个应用不同接口之间使用的配置不同就会出现数据的分化,因此在应用级方案里,提出了元数据快照的概念,即每个应用在每次发布时都会统一生成一份元数据快照,这个快照包含当前应用的元数据版本以及当前应用提供的所有接口的配置。这个快照 ID 会存储在 URL 中,这样既提供了动态变更能力, 又在量级上减少了数据存储对内存的压力。 最后,由于这个新的服务发现与 Spring Cloud、Service Mesh 等体系下的服务发现模型是高度相似的,因此 Dubbo 可以从注册中心层面与其他体系下的节点实现互发现。 Dubbo 3.0-云原生&阿里背书、易用 Dubbo 3.0 的定位是成为云原生时代的最佳微服务框架。目前可以看到的几个趋势有 K8s 成为资源调度的事实标准、Mesh 化成为主流以及规模上的急速增长等。这些趋势的存在都对 Dubbo 提出了更高的要求。 1、用户如何在 K8s 上更方便地部署和调用 Dubbo 服务是必须要解决的问题,要解决这个问题,统一的协议和数据交换格式是必须前提。2、Mesh 化的流行带来了多元化问题,即原生 Dubbo 和 Mesh 化 Dubbo 如何共存,多语言的场景如何支持。3、规模的增长会给整个 Dubbo 架构带来更大的挑战,无论是注册中心等组件,还是客户端,都会有更多的数据和调用量。 如何在保持稳定的前提下,提供更高效的服务是 Dubbo 演进的重中之重。 这些云原生时代带来的挑战,促成了 Dubbo 发展到下一代:新协议、K8s 基础架构支持、多语言支持和规模化。 1、下一代RPC协议 作为 RPC 框架最基础的能力是完成跨业务进程的服务调用,将服务组成链、组成网,这其中最核心的载体就是 RPC 协议。 同时,由于与业务数据的紧密耦合,RPC 协议的设计与实现,也在一些方面直接决定了业务架构,比如从终端设备到后端的交互、微服务架构中多语言的采用、服务间的数据传输模型等。 Dubbo 2 提供了 RPC 的核心语义,包括协议头、标志位、请求 ID 以及请求/响应数据。但在云原生时代,Dubbo 2 协议主要面临两个挑战:一是生态不互通,用户很难直接理解二进制的协议;二是对 Mesh 等网关型组件不够友好,需要完整的解析协议才能获取到所需要的调用元数据,如一些 RPC 上下文,从性能到易用性方面都会面临挑战。 Dubbo 作为服务框架,其最重要的还是提供远程通信能力。⽼版本 Dubbo 2 RPC 协议的设计与实现,已在实践中被证实在⼀些⽅面限制了业务架构的发展,⽐如从终端设备到后端服务的交互、微服务架构中多语言的采⽤用、服务间的数据传输模型等。 在支持已有的功能和解决存在的问题的前提下,下一代的协议需要有以下特性: • 协议需要解决跨语言互通的问题。传统的多语言多 SDK 模式和 Mesh 化跨语言模式都需要一种更通用易扩展的数据传输格式。 • 协议应该提供更完善的请求模型,除了 Request/Response 模型,还应该支持 Streaming 和 Bidirectional。 • 在性能上保留 request Id 机制,以避免队头阻塞带来的性能损耗。 • 易扩展,包括但不限于 Tracing/ Monitoring 等支持,也应该能被各层设备识别,降低用户理解难度。 基于这些需求,HTTP2/protobuf 的组合是最符合的。提到这两个的组合,可能很容易就会想到 gRPC 协议。新一代的协议和 gRPC 的关系如下: 1、Dubbo 新协议是基于 GRPC 扩展的协议,这也保证了在生态系统上新协议和 GRPC 是能够互通和共享的。 2、在第一点的基础上,Dubbo 新协议将更原生的支持 Dubbo 的服务治理,提供更大的灵活性。 3、在序列化方面,由于目前大多数应用方还没有使用 Protobuf ,所以新协议会在序列化方面给予足够的支持,平滑的适配现有序列化,方便迁移到 Protobuf。 4、在请求模型上,新协议将原生支持 Reactive,这也是 gRPC 协议所不具备的。 2、Service Mesh 为了使 Dubbo 在 Service Mesh 体系下落地,在参考了众多的方案之后,最终确定了最适合 Dubbo 3.0 的两种形态的 Mesh 方案。⼀种是经典的基于 Sidecar 的 Service Mesh,另⼀种是无 Sidecar 的 Proxyless Mesh。 对于 Sidecar Mesh 方案,其部署方式和当前主流 Service Mesh 部署方案一致。Dubbo 3.0 的重点是尽量给业务应用提供完全透明的升级体验,不止是编程视角的无感升级,还包括通过 Dubbo 3.0 轻量化、Triple 协议等,让整个调用链路上的损耗与运维成本也降低到最低。这个方案也被称为 Thin SDK 方案,而 Thin 的地方就是在于去除了所有不需要的组件。 Proxyless Mesh 部署方案则是 Dubbo 3.0 规划的另⼀种 Mesh 形态,目标是不需要启动 Sidecar,由传统 SDK 直接与控制面交互。 设想一下针对以下⼏种场景会⾮常适用 Proxyless Mesh 部署方案: • 业务方期望升级 Mesh 方案,但却无法接受由于 Sidecar 进行流量劫持所带来的性能损耗,这种情况常见于核心业务场景 • 期望降低 Sidecar 运维成本,降低系统复杂度 • 遗留系统升级缓慢,迁移过程漫长,多种部署架构⻓期共存 • 多种部署环境,这里的多种部署环境包括了如 VM 虚拟机、Container 容器等多种部署方式,也包括了多种类型应用混合部署,例如 Thin SDK 与 Proxyless 方案混合部署,对性能敏感应用部署 Proxyless 模式,对于周边应用采用 Thin SDK 部署方案,多种数据面共同由统一控制面进行调度。 将这两种形态统筹来看,在不同的业务场景、不同的迁移阶段、不同的基础设施保障情况下, Dubbo 都会有 Mesh ⽅案可供选择,⽽这进⼀步的都可以通过统⼀的控制⾯进行治理。 未来的部署 1、部署在K8s上 上图是 Dubbo 3.0 未来期望在 Kubernetes 上的部署方案。Dubbo 3.0 将在服务发现模型上对其 Kubernetes 的原生服务,支持不需要部署独立的注册中心就可以实现互相调用。 2、部署在Istio上 上图是 Dubbo 3.0 未来期望在 Istio 上的部署方案。这里采用的是 Thin SDK 与 Proxyless 混合部署模式,如图中的 Pod 1 和 Pod 3,数据流量由 Dubbo Service 直接发出,而 Pod 2 部署的是 Thin SDK 模式,流量由 Sidecar 进行拦截后流出。 柔性增强规划 云原生带来了技术标准化的重大变革。如何让应用在云上更简单地创建和运行,并具备可弹性扩展的能力,是所有云原生基础组件的核心目标。借助云原生技术带来的弹性能力,应用可以在极短时间内扩容出一大批机器以支撑业务需要。 比如为了应对零点秒杀场景或者突发事件,应用本身往往需要数千甚至数万的机器数来提升性能以满足用户的需要,但是在扩容的同时也带来了诸如集群节点极多导致的节点异常频发、服务容量受多种客观因素影响导致节点服务能力不均等一系列的问题,这些都是在云原生场景下集群大规模部署时会遇到的问题。 Dubbo 期待基于一种柔性的集群调度机制来解决这些问题。这种机制主要解决的问题有两个方面,一是在节点异常的情况下,分布式服务能够保持稳定,不出现雪崩等问题;二是对于大规模的应用,能够以最佳态运行,提供较高的吞吐量和性能。 • 从单一服务视角看,Dubbo 期望的目标是对外提供一种压不垮的服务,即是在请求数特别高的情况下,可以通过选择性地拒绝一些的请求来保证总体业务的正确性、时效性。 • 从分布式视角看,要尽可能降低因为复杂的拓扑、不同节点性能不一导致总体性能的下降,柔性调度机制能够以最优的方式动态分配流量,使异构系统能够根据运行时的准确服务容量合理分配请求,从而达到性能最优。 Dubbo 3.0 路线图 Apache Dubbo 3.0.0 作为捐给 Apache 后的一个里程碑版本已经在今年 6 月份正式发布了,这代表着 Apache Dubbo 全面拥抱云原生的一个节点。 在 2021 年 11 月我们会发布 Apache Dubbo 3.1 版本,届时我们会带来 Apache Dubbo 在 Mesh 场景下部署的实现与实践。 在 2022 年 3 月我们会发布 Apache Dubbo 3.2 版本,在这个版本中我们将带来全新的大规模应用部署下智能流量调度机制,提高系统稳定性与资源利用率。 最后,Apache Dubbo 3.0 已经和阿里巴巴集团内部的 RPC 框架实现了融合,期望用它来解决内部落地问题,做到技术栈统一。未来,Apache Dubbo 3.0 将大规模落地阿里集团,承载 618、双十一等复杂业务场景。 社区会尽可能保证一个较短的发版周期,及时对已有的问题进行修复。社区衷心地希望欢迎大家向社区提交 issue 和 PR,社区的同学会尽快进行 review、回复。感谢大家的支持! ﹀ ﹀ ﹀ 想要讨论更多与 Dubbo 相关的内容,可搜寻群号:21976540 加入

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册