2024年3月

前言:

经过前面几大部分的学习,已经掌握了 Emit 的前因后果,今天来详细讲解 Emit 中 IL 的部分内容。

如前文所讲,通过 DynamicMethod(或 MethodBuilder)可获得 ILGenerator 这个用于编写 IL 指令的类,并用它来编写 IL 指令。

本篇主要讲解 ILGenerator 的介绍,以及主要的辅助方法,详细的指令方法,则拆分到下一篇介绍。

下面就开邕它的介绍吧:

1、ILGenerator 介绍

ILGenerator 是.NET 中的一个重要组件,用于动态生成 Intermediate Language(IL)代码。

通过ILGenerator,开发人员可以在运行时创建和修改方法体内的IL指令,实现动态方法的生成和优化。

ILGenerator 提供了一组方法,允许程序员发出各种IL指令,包括加载、存储、运算、流程控制等操作,从而实现对方法体逻辑的灵活控制。

在 .NET 开发中,ILGenerator 通常与 DynamicMethod 类结合使用。

通过 DynamicMethod 创建动态方法对象,然后使用 ILGenerator 在其中生成IL代码。

这种结合使开发人员能够在运行时动态生成高效的代码,应用于一些需要动态生成代码的场景,如动态代理、AOP等。

ILGenerator 的灵活性和强大功能为.NET开发提供了更多可能性和自定义性。

2、ILGenerator 简单示例:

先看一下之前文章提到的代码:

 DynamicMethod dynamicMethod = new DynamicMethod("MyMethod", typeof(void), null);
ILGenerator il
=dynamicMethod.GetILGenerator();
il.EmitWriteLine(
"hello world!");
il.Emit(OpCodes.Ret);

从示例代码使用了两类方法:

指令方法:il.Emit(OpCodes.Ret)
辅助方法:il.EmitWriteLine(
"hello world!")

所有的辅助方法,都是基于指令方法的封装,即用指令也可以实现该方法功能,

但用辅助方法,可以更简单的调用,下面开始介绍辅助方法。

3、ILGenerator 辅助方法:EmitWriteLine

该方法封装好的调用 WriteLine 输出控制台消息,使用它可以简单输出控制台方法,而不用编写 Emit 指令方法。

如果用 Emit 指令,编写是这样的:

            var il =methodBuilder.GetILGenerator();

il.Emit(OpCodes.Ldstr,
"这是一个示例消息");
il.Emit(OpCodes.Call,
typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }));

il.Emit(OpCodes.Ret);

实现效果对应代码:

4、ILGenerator 辅助方法:异常处理 try catch finally

辅助方法中,提供了关于常用的 try catch finally 的封装方法,可以帮助我们更简单的编写IL方法:

看示例代码:

 var il =methodBuilder.GetILGenerator();


il.BeginExceptionBlock();
//开始 try il.EmitWriteLine("hello world!");

il.BeginCatchBlock(
typeof(Exception));//开始 catch il.EmitWriteLine("hello world on error!");

il.BeginFinallyBlock();
//开始 finally il.EmitWriteLine("hello world on finally!");

il.EndExceptionBlock();
//结束 il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ret);

参照对应生成的代码:

5、ILGenerator 辅助方法:异常抛出

辅助方法中,也提供了一个抛出异常的方法,示例代码:

var il =methodBuilder.GetILGenerator();

il.ThrowException(
typeof(Exception));

il.Emit(OpCodes.Ret);

查看对应生成:

但是该辅助方法只能生成抛出异常,没有提供异常的参数。

如果需要更详细的异常抛出,则需要使用指令的方法:

指令方法如:

var il =methodBuilder.GetILGenerator();

il.Emit(OpCodes.Ldstr,
"这是一个示例异常消息");//创建一个新的 Exception 实例 ConstructorInfo ctor = typeof(Exception).GetConstructor(new Type[] { typeof(string) });
il.Emit(OpCodes.Newobj, ctor);
//使用 ThrowException 方法引发异常 il.Emit(OpCodes.Throw);


il.Emit(OpCodes.Ret);

生成的对应代码:

6、ILGenerator 辅助方法:定义变量

辅助方法中,有一个是用来辅助定义变量的。

但是它需要配置 Emit 指令使用,示例代码:

ILGenerator il =methodBuilder.GetILGenerator();

LocalBuilder local
= il.DeclareLocal(typeof(string)); // 定义变量

il.Emit(OpCodes.Ldstr,
"hello world"); // 加载字符串
il.Emit(OpCodes.Stloc, local); // 将字符串赋值给变量
il.Emit(OpCodes.Ldloc, local); //从变量中 加载值进栈
il.Emit(OpCodes.Ret);//返回(带值)

对应生成的代码:

在这个示例中(为了举例,多了中间的赋值取值的过程):

可以看出,定义的临时变量,都是没有名称的,只有类型。

它可以用来临时存值,需要用到的时候,再将值取出,对应两个 Emit 指令:

赋值:il.Emit(OpCodes.Stloc, local); //将字符串赋值给变量
取值:il.Emit(OpCodes.Ldloc, local); //从变量中 加载值进栈

7、ILGenerator 辅助方法:定义标签

标签的定义,可以理解为跳转,即现在不常用的 goto 语句所需的的标签。

标签的定义,在 if else 中, switch 中,for 循环中,都会常常使用到标签。

标签的使用分为3步,定义标签、设定标签、跳转到标签。

标签定义:

Label label = il.DefineLabel();

设定标签:

il.MarkLabel(label);

跳转标签:

在 IL(Intermediate Language)中,可以使用以下指令来跳转到标签(Label):

条件跳转指令:

  1. beq
    :如果两个值相等,则跳转到指定的标签。
  2. bge
    :如果第一个值大于或等于第二个值,则跳转到指定的标签。
  3. bgt
    :如果第一个值大于第二个值,则跳转到指定的标签。
  4. ble
    :如果第一个值小于或等于第二个值,则跳转到指定的标签。
  5. blt
    :如果第一个值小于第二个值,则跳转到指定的标签.
  6. bne.un
    :如果两个无符号整数值不相等,则跳转到指定的标签。
  7. brtrue
    :如果值为 true,则跳转到指定的标签。
  8. brfalse
    :如果值为 false,则跳转到指定的标签。
  9. brtrue.s
    :如果值为 true,则跳转到指定的标签(短格式)。
  10. brfalse.s
    :如果值为 false,则跳转到指定的标签(短格式).

无条件跳转指令:

  1. br
    :无条件跳转到指定的标签。
  2. br.s
    :短格式的无条件跳转到指定的标签。
  3. leave
    :无条件跳转到 try、filter 或 finally 块的末尾。
  4. leave.s
    :短格式的无条件跳转到 try、filter 或 finally 块的末尾.

比较跳转指令:

  1. bgt.un
    :如果第一个无符号整数值大于第二个值,则跳转到指定的标签。
  2. bge.un
    :如果第一个无符号整数值大于或等于第二个值,则跳转到指定的标签。
  3. blt.un
    :如果第一个无符号整数值小于第二个值,则跳转到指定的标签。
  4. ble.un
    :如果第一个无符号整数值小于或等于第二个值,则跳转到指定的标签.

其他跳转指令:

  1. switch
    :根据给定的索引值跳转到不同的标签。
  2. brnull
    :如果值为 null,则跳转到指定的标签。
  3. brinst
    :如果对象是类的实例,则跳转到指定的标签。

这些指令可以帮助控制流程,在特定条件下跳转到指定的标签位置执行相应的代码。

通过合理使用这些跳转指令,可以实现复杂的逻辑控制和条件判断。

总结:

这篇教程总结了.NET Emit 中关于 IL 指令的第六部分,着重介绍了 ILGenerator 辅助方法的详细内容。

ILGenerator 是在动态生成程序集时用来生成 Intermediate Language(IL)指令的一个重要工具。

读者通过本篇文章,可以迅速了解到该教程的主要内容和重点,更好地掌握 ILGenerator 辅助方法的使用及 IL 指令的生成过程。

下一篇,我们将重点讲解 IL 的指令内容。

现在园子的收入来源除了
会员

周边
,也在做一些高品质广告,今天这个来自阿里达摩院的广告就是我们很想做的高品质广告,上一个高品质广告是
通义灵码
,也是来自阿里达摩院。

自从被百度全面降权并几乎停止收录后,园子损失了巨大的百度搜索流量,虽然损失惨重,但也带来一个“副作用”,访问园子的用户质量更高了。

阿里达摩院找我们合作正是因为看重园子的用户群质量。以高质量用户群,与顶尖厂商合作,做高品质广告,这条路应该走的通。

这次的
2024阿里巴巴全球数学竞赛
广告,我们很想做好效果,不仅因为这将是园子4月份很重要的一份收入,而且这是我们第一次和阿里达摩院合作数学竞赛的推广,希望争取到后续的合作机会。

虽然我们已经投入全站广告资源推广这次数学竞赛,但由于推送时间短(3月27日晚才上线,4月11日就结束),考核要求高(考核的是参赛人次),仅靠广告位的推广,可能很难完成指标,所以我们在首页发布了这篇推广博文,请大家谅解。

今年的阿里巴巴全球数学竞赛是第六届,
报名
截止时间是北京时间2024年4月11日 23:59,预选赛时间是北京时间2024年4月13日 8:00 - 4月15日 8:00,决赛时间是北京时间2024年6月22日(周六) 8:00-24:00。

这届比赛有个与时俱进且很吸引人的地方是可以通过AI参赛,如果您不想做题,可以训练AI帮您代劳。

AI挑战赛:本次预选赛开放AI参赛资格,参赛者完成报名后可开始训练模型或设计Prompt,并需要在北京时间2024年04月12日0时前向组委会提交模型或Prompt,锁定做题方案;考试开始后,AI参赛者需在48小时内采用AI答题,并在考试系统中提交人类可阅读的答案,组委会将根据答题AI的理解能力、解题过程的逻辑性、创新性等因素综合评分。

AI代您做题,您代AI领奖,是不是很诱人?

  • 金奖5人,奖金 $30000/人
  • 银奖10人,奖金 $15000/人
  • 铜奖20人,奖金 $8000/人
  • 优秀奖50人,奖金 $2000/人

如果您有兴趣试试身手,欢迎通过园子的
专属链接
报名参赛。

要求统计所有分类下的数量,如果分类下没有对应的数据也要展示。这种问题在日常的开发中很常见,每次写每次忘,所以在此记录下。

这种统计往往不能直接group by,因为有些类别可能没有对应的数据

这里有两个思路(如果您有更好的方法,请一定要告诉我,求求了):

  1. 每种类型分别统计,用union 连接(比较适合类型已知、不多且确定的)


    过UNION操作符组合了三个独立的查询,每个查询都基于数据表计算了不同条件下的数量,简单粗暴,但不是很推荐


    SELECT `status` `key`,COUNT(id) amount FROM 数据表 WHERE `status` IS NULL
    UNION 
    SELECT 1 `key`,COUNT(id) amount FROM 数据表  WHERE `status` = 1
    UNION 
    SELECT 2 `key`,COUNT(id) amount FROM 数据表 WHERE `status` = 2
    UNION 
    SELECT 3 `key`,COUNT(id) amount FROM 数据表  WHERE `status` = 3
    

    优化(枚举类型,left join数据表):

    SELECT
    	a.`key`,
    	IFNULL( b.amount, 0 ) amount 
    FROM
    	( SELECT 1 AS `key` UNION SELECT 2 UNION SELECT 3 ) AS a
    LEFT JOIN ( SELECT `status` `key`, COUNT( id ) amount FROM 数据表 GROUP BY `status` ) AS b ON a.`key` = b.`key`
    

    性能分析:
    第一个SQL语句进行了多次全表扫描(假设索引未覆盖status字段)。这种做法可能导致更多的磁盘I/O操作,尤其是当数据量较大时,性能开销会较高。

    第二个SQL语句仅对device_patrol_task_record表进行了一次扫描,并利用了GROUP BY进行聚合操作,减少了磁盘I/O,理论上在大多数情况下比第一个SQL更高效。

    结论

    第二个SQL语句在性能和耗时上通常优于第一个SQL语句,因为它只需要遍历表一次,并对结果进行一次性聚合,避免了多次全表扫描带来的性能损失。

    若status字段上有索引,第二个SQL的优势会更加明显,因为可以利用索引加速查询过程。

  2. 先查类型表,left join数据表,最后对left join后的数据再进行分组,注意:要给右表(数据表)为空时判断


    和第一个的优化版思路是一样的


    SELECT
    	a.id `key`,
    	a.classify_name `name`,
    	COUNT(b.id) `count`
    FROM
    	分类表 a
    LEFT JOIN 数据表 b ON b.`level` = a.id
    GROUP BY a.id
    

本文深入探讨了Docker的发展历程、核心技术、在云服务中的应用以及其庞大生态系统。通过分析Docker如何革新容器化技术、加速云服务的发展,并构建一个多元化的生态系统,本文揭示了Docker在当代云计算领域中的重要角色及其对未来技术趋势的影响。

关注【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人。

file

一、引言

Docker,作为当今云计算领域的一颗璀璨明星,已经深刻改变了我们对软件开发、部署和运维的认识。从简单的容器化概念出发,Docker不仅仅是一种技术实现,它更是一种服务于云原生时代的哲学和实践。Docker的普及和成功,部分归功于它所提供的轻量级、可移植、自给自足的容器环境,这种环境为应用的快速迭代、灵活部署和高效运维提供了坚实基础。

在云服务架构的演变过程中,Docker的角色不可小观。以其独特的方式,Docker解决了传统虚拟化所面临的资源冗余和效率低下问题,实现了更加高效的资源利用和更快的部署速度。此外,Docker容器的隔离性和安全性,使得它成为多租户环境和微服务架构的理想选择。

从Docker的技术特性来看,它通过容器、镜像和仓库等核心组件,为开发者和运维人员提供了一套完整的解决方案。Docker容器的轻量级特性,意味着在同等硬件条件下,可以运行更多的实例。这不仅提高了资源的利用率,也使得应用的扩展变得更加灵活和迅速。

在实际应用案例中,例如在互联网公司中,Docker的应用可谓是革命性的。许多大型互联网公司,如Netflix、Alibaba和Google,都在其大规模的云服务中广泛采用了Docker容器技术。这些公司利用Docker来打包和部署应用,实现了服务的快速扩展和高效管理。例如,Netflix通过Docker容器实现了其全球服务的高可用性和弹性伸缩,极大地提高了用户体验。

此外,随着云原生技术的发展,Docker也在不断进化。新兴的技术如Kubernetes、Docker Swarm等,都是在Docker的基础上发展起来的,用于更高效地管理容器化应用。这些技术不仅加强了容器的编排和管理能力,也推动了云原生生态系统的成熟和发展。

综上所述,Docker不仅仅是技术层面的革新,更是一场思维和方法论的革命。它通过简化部署、提高效率、支持微服务等方式,极大地推动了云计算和云服务架构的发展。随着技术的不断成熟和应用的深入,Docker将继续在云服务领域扮演重要角色,引领未来技术的发展潮流。

二、Docker的诞生与发展

file
Docker的诞生和发展是一段充满创新和挑战的历程,它标志着云计算和容器化技术的一个重要转折点。从最初的轻量级容器引擎到成为当今云原生生态系统的核心组件,Docker的旅程反映了云计算领域的快速变化和技术创新的精神。

初创时期:轻量级容器的探索

Docker最初由Dotcloud公司(后更名为Docker Inc.)在2013年推出。它起源于Linux容器(LXC)技术,一种为Linux系统上的应用提供轻量级隔离环境的解决方案。Docker的出现,简化了容器的创建和管理过程,使得容器技术开始获得更广泛的关注和应用。

一个生动的例子是,当时的开发者面临“在我的电脑上可以运行,但是在服务器上不行”的常见问题。Docker通过提供一致的运行环境,有效地解决了这一问题,极大地提高了软件的可移植性和开发效率。

快速发展:构建云原生生态

随着Docker的普及,它很快成为云原生应用开发的重要工具。Docker容器的设计理念与微服务架构的需求高度契合,促进了微服务架构的普及。例如,Twitter和Spotify等公司通过采用Docker容器,实现了服务的快速迭代和高效运维,这在传统的大型单体应用架构中是难以想象的。

技术演进:从容器到编排

Docker的发展不仅停留在容器技术上,更向容器编排和管理领域扩展。2014年,Docker推出了Docker Compose,一个用于定义和运行多容器Docker应用程序的工具。随后,在2015年,Docker Swarm作为原生的集群管理工具问世,标志着Docker从单机容器管理向集群管理的转变。

这一阶段的转变,体现了Docker从单一技术到生态系统构建者的转变。它不仅仅提供了容器运行的环境,更是提供了一整套的解决方案,帮助企业在复杂的生产环境中有效管理和调度容器。

面临挑战:竞争与合作

Docker的发展并非一帆风顺,它也面临来自Kubernetes等其他容器编排工具的激烈竞争。Kubernetes,由Google开发并捐赠给Cloud Native Computing Foundation(CNCF),很快成为容器编排领域的领导者。这一阶段,Docker开始与Kubernetes等技术更紧密地集成,共同推动云原生生态系统的发展。

例如,Docker为Kubernetes提供了容器运行环境的支持,而Kubernetes则提供了更高级的容器编排和管理功能。这种竞争与合作的关系,加速了容器技术和云原生生态系统的成熟。

当前状态与未来展望

截至目前,Docker不仅是容器化技术的代名词,更是云原生时代不可或缺的一部分。它的发展受到企业、开发者社区以及云服务提供商的广泛支

三、Docker核心技术解析

file
Docker的核心技术不仅仅改变了软件部署和运维的方式,还为云计算和云原生应用的发展提供了强大动力。以下是对Docker核心技术的详细解析,结合具体案例,深入展示其工作原理和应用价值。

容器化技术原理

Docker的核心在于其容器化技术。与传统的虚拟机相比,Docker容器更为轻量,因为它们共享主机的操作系统而不是每个容器运行一个完整的操作系统实例。这意味着Docker容器在启动和运行时更快,资源占用更少。例如,一个典型的Docker容器可以在几秒钟内启动,而传统的虚拟机可能需要几分钟。

Docker架构和组件

Docker的架构包括Docker Daemon(守护进程)、Docker Client(客户端)、Docker Images(镜像)、Docker Containers(容器)、Docker Registries(仓库)等关键组件。

  • Docker Daemon
    :负责创建、运行和管理容器。
  • Docker Client
    :用户通过命令行或其他工具与Docker Daemon交互。
  • Docker Images
    :容器的蓝本,包含运行容器所需的所有内容。
  • Docker Containers
    :镜像的运行实例,可以被启动、停止、移动和删除。
  • Docker Registries
    :存储Docker镜像的地方,如Docker Hub。

以Docker Images为例,它们允许多次重复使用,确保了环境一致性和可复制性。在实际开发中,团队成员可以共享一个基础镜像,确保每个人的开发环境一致,从而减少了“在我的机器上能运行”的问题。

Docker与虚拟机的比较

虽然Docker容器和虚拟机都提供了隔离的执行环境,但它们在性能和资源效率上有显著差异。Docker容器直接运行在宿主机的操作系统之上,共享其内核,而不需要Hypervisor来模拟整个操作系统。这种方式使得Docker容器在启动速度和资源占用上远胜于虚拟机。

举个例子,一个云服务提供商可能在同一硬件上运行数百个容器,而如果使用虚拟机,这个数字可能减少到数十。这种高效率使Docker在云服务和微服务架构中成为更受欢迎的选择。

Docker的实际应用案例

在实际应用中,Docker展示了其在多种环境下的灵活性和效率。例如,PayPal使用Docker容器化其应用,实现了更快的开发周期和更高的资源利用率。通过容器化,PayPal能够快速部署和扩展服务,同时减少了对硬件资源的需求。

此外,Docker还广泛应用于持续集成和持续部署(CI/CD)流程中。通过使用Docker容器,开发者可以在一个一致的环境中构建、测试和部署应用,极大地简化了软件开发和交付过程。

综上所述,Docker的核心技术不仅在技术层面上提供了创新,而且还通过实际应用案例展示了其在现代软件开发和云服务中的广泛影响。随着技术的不断演

四、Docker在云服务中的应用

file
Docker在云服务领域的应用是其技术创新的一个重要体现。通过为云计算环境提供轻量级、高效、可移植的容器化解决方案,Docker极大地推动了云服务的发展和普及。以下是Docker在云服务中应用的详细分析,结合现实案例,展示其对云计算领域的深远影响。

云原生架构的推动

Docker容器化技术与云原生架构理念完美契合。云原生应用强调的是可扩展性、弹性、可靠性和自动化,而Docker正是通过其轻量级和高效性,为实现这些目标提供了基础。在Docker的帮助下,应用可以轻松地在云环境中部署、扩展和管理。

以Google Cloud Platform为例,其广泛采用Docker容器技术,为用户提供了灵活、可靠的云服务。通过容器化,Google能够实现资源的高效分配和管理,同时确保不同应用之间的隔离性和安全性。

Docker在云服务中的实际案例

亚马逊网络服务(AWS)和微软Azure都是Docker技术的重要应用案例。在AWS上,用户可以利用Amazon Elastic Container Service(ECS)轻松部署和管理Docker容器。微软Azure云服务也提供了对Docker容器的原生支持,允许开发者在Azure云上无缝部署和运行Docker应用。

例如,一个电子商务公司可能使用AWS来托管其基于Docker的微服务架构。通过在Docker容器中运行其服务,该公司能够快速响应市场变化,轻松实现服务的扩展和更新。

与其他云服务技术的协同

Docker不是一个孤立的技术,它与其他云服务技术如Kubernetes、Docker Swarm等紧密协作,形成一个完整的容器化生态系统。Kubernetes作为一个开源的容器编排平台,与Docker容器无缝集成,提供了应用部署、扩展和管理的高级功能。

以微软Azure Kubernetes Service(AKS)为例,它利用Docker和Kubernetes技术,为客户提供了一个可靠、高效的云原生应用部署和管理平台。用户可以在AKS上轻松部署和扩展基于Docker的容器应用,同时享受Kubernetes强大的编排和自动化能力。

结论

在云服务领域,Docker已经成为了一种不可或缺的技术。它不仅提高了云计算资源的利用效率,还极大地简化了云应用的开发、部署和管理过程。通过与其他云服务技术的协同,Docker进一步推动了云原生架构的发展,为企业和开发者提供了更加灵活、高效的云计算解决方案。随着技术的不断发展和成熟,Docker在云服务领域的作用将更加凸显,继续引领云计算技术的发展趋势。

五、Docker的生态系统

file
Docker的成功并非孤立存在,其背后是一个庞大且多元的生态系统,包括各种工具、平台和社区的共同努力。这个生态系统不仅支撑了Docker的技术发展,还促进了整个云计算领域的创新和增长。以下是对Docker生态系统的详细分析,结合实际案例,探讨其在云服务技术中的重要作用。

开源社区的推动力

Docker的成功很大程度上归功于其活跃的开源社区。开发者们不断贡献代码、分享经验,推动Docker技术的快速进步。社区成员不仅在Docker软件本身上做出了贡献,还开发了大量与Docker相关的工具和插件,进一步丰富了其生态系统。

例如,Portainer是一个受欢迎的开源工具,提供了一个用户友好的界面来管理Docker容器。Portainer简化了容器的部署、监控和维护过程,让Docker的使用变得更加易于接触和掌握。

与主流云服务的融合

Docker生态系统中的一个关键方面是与各大云服务平台的整合。亚马逊、Google和微软等主流云服务提供商都在其平台上深度集成了Docker,使得Docker成为了云计算的标准组成部分。

举例来说,Google Cloud的Cloud Run服务,允许用户直接在Google Cloud上运行基于Docker的应用。Cloud Run结合了Docker的灵活性和Google Cloud的强大计算能力,为用户提供了一种无需管理服务器的方式来运行应用。

相关技术的协同发展

Docker生态系统不仅包括Docker本身,还涉及到一系列与之协同工作的技术,如容器编排工具Kubernetes、持续集成和持续部署(CI/CD)工具Jenkins等。这些技术的结合为企业提供了全面的解决方案,帮助它们在现代化的云环境中高效地构建、部署和管理应用。

以Kubernetes为例,它与Docker容器的整合,为企业提供了一个强大的平台来自动化部署、扩展和管理容器化应用。Kubernetes不仅提高了资源利用率,还增强了系统的可靠性和弹性。

结论

Docker的生态系统是其成功的关键因素,它通过集成各种工具、平台和社区的力量,形成了一个强大且多样化的技术群体。这个生态系统不仅支撑了Docker的持续发展,还对整个云服务领域产生了深远的影响,推动了技术创新和应用多样性的发展。

六、总结

在探索Docker的历程、核心技术、云服务中的应用以及其庞大生态系统后,我们可以得出几个独特且深刻的洞见,这些洞见不仅对于理解Docker本身至关重要,也对整个云计算和云服务领域有着深远的影响。

Docker:容器化技术的革命者

Docker不仅仅是一种技术或工具,它更是容器化技术的革命者。Docker的出现,标志着从传统虚拟化向更高效、更灵活的容器化技术的转变。这一转变不仅提高了资源利用率和运维效率,还开启了微服务和云原生应用时代的大门。

云服务的加速器

Docker在云服务中的应用,展现了它作为云计算加速器的角色。通过简化部署、支持跨平台运行和加速开发过程,Docker大大加快了云服务的发展步伐,使得云原生应用不再是遥不可及的概念,而是成为现实中的常态。

生态系统的构建者

Docker成功的关键之一在于其强大的生态系统。通过整合多种技术、工具和社区资源,Docker构建了一个健康、多元化的生态环境。这个生态系统不仅促进了Docker本身的发展,也为整个云计算领域带来了新的创新和机遇。

展望未来

展望未来,Docker将继续在云计算和容器技术领域扮演重要角色。随着技术的不断进步和市场需求的变化,Docker可能会继续演化,以适应更加复杂和多样化的应用场景。同时,随着云原生技术的不断成熟,Docker及其生态系统也将面临新的挑战和机遇。

在总结Docker的影响和未来趋势时,我们应认识到,Docker不仅仅是技术的进步,更是一种思维方式的转变。它推动了从传统的软件开发和部署方法向更灵活、更高效的云原生模式的转变。这种转变已经并将继续影响着企业、开发者和整个IT行业的发展方向。因此,深入理解和有效利用Docker及其生态系统,对于任何希望在当今快速变化的技术环境中保持竞争力的组织和个人来说,都是至关重要的。

关注【TechLeadCloud】,分享互联网架构、云服务技术的全维度知识。作者拥有10+年互联网服务架构、AI产品研发经验、团队管理经验,同济本复旦硕,复旦机器人智能实验室成员,阿里云认证的资深架构师,项目管理专业人士,上亿营收AI产品研发负责人。
如有帮助,请多关注
TeahLead KrisChang,10+年的互联网和人工智能从业经验,10年+技术和业务团队管理经验,同济软件工程本科,复旦工程管理硕士,阿里云认证云服务资深架构师,上亿营收AI产品业务负责人。

作者:vivo 互联网中间件团队 - Liu Tao

在开源 RocketMQ 基础之上,关于【测试环境项目多版本隔离】业务诉求的落地与实践。

一、背景

在2022年8月份 vivo 互联网中间件团队完成了互联网在线业务的MQ引擎升级,从RabbitMQ 到 RocketMQ 的平滑升级替换。

在业务使用消息中间件的过程中,提出了开发测试环境项目多版本隔离的诉求。本文将介绍我们基于 RocketMQ 如何实现的多版本环境隔离。

二、消息中间件平台主体架构

在正式展开项目多版本实践之前,先大致介绍下我们消息中间件平台的主体架构。

图片

由上图可知,我们消息中间件平台的核心组件 mq-meta、RabbitMQ-SDK、mq-proxy,以及RocketMQ集群。

1. mq-meta

主要负责平台元数据管理,以及业务SDK启动时的鉴权寻址操作。

业务进行topic申请时,会自动分配创建到两个不同机房的broker上。

鉴权寻址时会根据业务接入Key找到所在 MQ 集群下的proxy节点列表,经过机房优先+分片选取+负载均衡等策略,下发业务对应的proxy节点列表。

2. RabbitMQ-SDK

目前业务使用的消息中间件SDK仍为原有自研的RabibitMQ SDK,通过AMQP协议收发消息。

与proxy之间的生产消费连接,遵循机房优先原则,同时亦可以人为指定优先机房策略。

3. mq-proxy

消息网关组件,负责AMQP协议与RocketMQ Remoting协议之间的相互转换,对于业务侧目前仅开放了AMQP协议。

具备读写分离能力,可配置只代理生产、只代理消费、代理生产消费这三种角色。

与broker之间的生产消费,遵循机房优先原则。

机房优先的实现:

  • 生产
    :proxy优先将消息发送到自己本机房的broker,只有在发送失败降级时,才会将消息发送到其他机房broker;通过扩展MQFaultStrategy+LatencyFaultTolerance,并结合快手负载均衡组件
    simple-failover-java
    实现机房优先+机房级别容灾的负载均衡策略。

  • 消费
    :在进行队列分配时,先轮询分配自己机房的队列;再将不存在任何消费的机房队列,进行轮询分配。通过扩展AllocateMessageQueueStrategy实现。

4. RocketMQ集群

每个MQ集群会由多个机房的broker组成。

每个topic则至少会分配到两个不同机房的broker上。实现业务消息
发送与消费的机房级别的容灾

每个broker部署两节点,采用主从架构部署,并基于zookeeper实现了一套自动主从切换的高可用机制。通过
异步刷盘+同步双写
来保证性能与消息的可靠性。

namesrv则为跨机房broker+mq-proxy之间的公共组件,为集群提供路由发现功能。

三、项目多版本实践

3.1 现状

后端服务通常采用微服务架构,各服务之间的通信,通常是同步与异步两种调用场景。其中同步是通过RPC调用完成,而异步则是通过MQ(RocketMQ)生产消费消息实现。

在多版本环境隔离中,同步调用场景,一些RPC框架都能有比较好的支持(如Dubbo的标签路由);但在异步调用场景,RocketMQ并不具备完整的版本隔离方案,需要通过组合一些功能自行实现。

最初消息中间件平台支持的多版本环境隔离大致如下:

  • 平台提供固定几个MQ逻辑集群(测试01、测试02、测试03...)来支持版本隔离。

  • 业务在进行多版本的并行测试时,需关注版本环境与MQ逻辑集群的对应关系,一个版本对应到一个MQ逻辑集群。

  • 不同MQ逻辑集群下用到的MQ资源(Topic、Group)自然就是不同的。

该方式主要存在如下
两个问题

1、使用成本较高

  • 业务需在消息中间件平台进行多套环境(集群)的资源申请。

  • 业务在部署多版本时,每个版本服务都需要配置一份不同的MQ资源接入Key,配置过程繁琐且容易出错。

2、环境维护成本较高

  • 在一个项目中,业务为了测试完整的业务流程,可能会涉及到多个生产方、消费方服务。尽管在某次版本中只改动了生产方服务,但仍需要在版本环境中一并部署业务流程所需的生产与消费方服务,增加了机器与人力资源成本。

为解决上述问题,提升多版本开发测试过程中的研发效率,中间件团队开始了RocketMQ多版本环境隔离方案的调研。

3.2 方案调研

图片

注释:

1、
物理隔离
:即机器层面的隔离,MQ的物理隔离,则意味着使用完全不同的MQ物理集群。

2、
资源逻辑隔离
:属于同一MQ物理集群,但采用不同的逻辑集群,业务侧需关注不同逻辑集群下相应的topic和group资源配置。

3、
基线版本
:通常为当前线上环境的版本或者是当前的主开发版本,为稳定版本。

4、
项目版本
:即项目并行开发中的多版本,非基线版本。

5、
消息回落
:针对消费而言,若消费方没有对应的项目版本,则会回落到基线版本来进行消费。

3.3 方案选择

基于我们需解决的问题,并对实现成本与业务使用成本的综合考量,我们仅考虑【基于消息维度的user-property】与【基于topic的messageQueue】这两种方案。

又因在全链路的多版本环境隔离的需求中,
业务使用的版本环境明确提出不做固定
,故而我们最终选择【基于消息维度的user-property】来作为我们多版本环境隔离的方案。

3.4 项目多版本的落地

基于消息维度的user-property来实现项目多版本的隔离。

1. 链路分析

在多版本环境中,真实的业务链路可能如下,服务调用可能走同步RPC或异步MQ。

图片

注释:

1、业务请求中带有流量标识,经过网关时,根据流量路由规则将流量染色为全链路染色标识v-traffic-lane。

2、流量标识为userId,流量路由规则为用户路由到指定版本,图中的链路情况:

图片

3、在后续的整个链路中,都需要将请求按照流量染色标识v-traffic-lane正确路由到对应版本环境。

2. 染色标识传递

为了正确识别当前服务所在版本,以及流量中的染色标识进行全链路传递,需要做如下事情:

(1)启动

图片

其中v-traffic-lane则是服务被拉起时所在的版本环境标识(由CICD提供),这样proxy就能知道这个客户端连接属于哪个版本。

(2)消息的发送与接收

图片

消息发送
:mq-proxy将AMQP消息转化为RocketMQ消息时,将染色标识添加到RocketMQ消息的user-property中。

消息接收
:mq-proxy将RocketMQ消息转化为AMQP消息时,将染色标识再添加到AMQP消息属性中。

注释:

上述红色点位,可通过改动SDK进行染色标识的传递,但这样就需要业务升级SDK了。这里我们是借助调用链agent来统一实现。

3.生产消费逻辑

图片

(1)生产

逻辑比较简单,对于存在版本tag的消息,只需要将版本标识作为一个消息属性,存储到当前topic中即可。

(2)消费

这里其实是有两个问题:
消费的多版本隔离、消息回落

我们先看下消费的多版本隔离应该如何实现?

通过使用不同的消费group,采用基于user-property的消息过滤机制来实现。

① 版本tag传递

  • 在RabbitMQ-SDK消费启动时,通过全链路Agent传递到proxy

② 项目环境消费【消费属于自己版本的消息】

  • proxy会根据版本tag在MQ集群自动创建
    带版本tag的group
    ,并通过消费订阅的消息属性过滤机制,
    只消费自己版本的消息

  • routingKey的过滤则依赖proxy侧的过滤来完成。相对基线版本,多版本的消息量应该会比较少,全量拉取到proxy来做过滤,影响可控。

  • 消费组group_版本tag无需业务申请,由客户端启动时proxy会自动创建

③ 基线消费【消费全部基线版本消息+不在线多版本的消息】

  • 启动时
    使用原始group
    ,订阅消费时,基于broker的routingKey过滤机制
    消费topic所有消息

  • 当消息被拉取到proxy后,再做一次消息属性过滤,将多版本进行选择性过滤,让基线消费到正确版本的消息。

图片

我们再来看下消息回落又该如何实现?

1、消息回落是基线消费需要根据多版本的在线情况,来决定是否需要消费多版本的消息。

2、上面已提到基线消费从broker是拉取所有消息进行消费。

3、我们通过在基线消费内部维护一个在线多版本tag的集合,然后进行多版本消息的选择性过滤来支持回落。

4、但这个在线多版本tag的集合,需要及时更新,才能更好的保证消息回落的准确性。

5、起初我们采用定时任务从broker拉取所有在线多版本tag的集合,每30s拉取一次,这样消息回落就需要30s才能生效,准确性差。

6、后面我们想到用广播通知机制,在多版本上下线时广播通知到所有的基线消费实例,保证了消息回落的实效性与准确性。

7、完整的基线消费实例在线多版本tag集合更新机制如下:

图片

(3)broker侧的调整

这里主要是为了配合消费多版本的实现,对broker进行了一些扩展。

1、提供在线多版本group集合的扩展接口。用以返回当前group所有在线的多版本group集合。

2、增加broker侧多版本消息过滤机制。因RocketMQ原生sql92过滤表达式,无法支持带点的属性字段过滤;而我们的版本标识(_vh_.v-traffic-lane)是存在的。

注释:

1、routingKey过滤机制:为基于broker的消息过滤机制的扩展,可实现RabbitMQ中的routingKey表达式相同的消息路由功能。

2、多版本生产消费逻辑,都在mq-proxy与RocketMQ-broker侧完成。业务也无需升级SDK。

4. 问题定位

在多版本隔离中,平台对用户屏蔽了复杂的实现细节,但用户使用时,也需要能观测到消息的生产消费情况,便于问题跟踪定位。

这里我们主要提供了如下功能:


消息查询
:可观测消息当前的版本标识,以及消息轨迹中的生产消费情况

图片

图片


消费group的在线节点
:可看到消费节点当前的版本标识

图片

四、总结与展望

本文概述了vivo互联网中间件团队,在开源RocketMQ基础之上,如何落地【测试环境项目多版本隔离】的业务诉求。其中涵盖了vivo消息中间件主体架构现状、业内较流行的几种方案对比,并对我们最终选择方案在实现层面进行了细节性的分析。希望可以给业界提供一种基于proxy来实现多版本隔离特性的案例参考。

在实现过程中遇到的问题点归结下来则是:

1. 流量染色标识在整个生产消费过程中如何传递?

  • 在客户端SDK使用全链路agent进行流量染色标识的添加、拆解、传递。

  • 在RocketMQ则存储到消息的user-property当中。

2. 消费客户端版本标识如何识别?

  • 客户端SDK使用全链路agent将版本标识添加到连接属性当中。

  • proxy则根据客户端版本标识自动创建多版本消费group。

3. 消费的多版本隔离如何实现?

  • 项目版本,通过不同的消费group,基于broker端消息属性的版本过滤来实现隔离。

  • 基线版本,则通过proxy侧消费过滤来忽略掉不需要消费的消息。

4. 消息回落如何实现?如何保证消息回落的实效性与准确性?

  • 基线版本内部会维护一个在线多版本消费group的集合,根据这个集合来决定消息是否需要回落到基线进行消费。

  • 消息回落的实效性与准确性则通过定时+广播消息的机制保证。

最后,我们实现的多版本隔离特性如下:

  • 多版本环境隔离。在proxy层面基于消息维度user-property来实现版本隔离,业务不需要升级SDK,业务使用层面仍然为同一套配置资源。

  • 支持消息回落。

  • 消费失败产生的重试消息也能被重投递到对应版本。

但仍存在如下
不足

多版本消费客户端全部下线场景:
若topic中仍存在一些已下线版本的消息没有消费,则这部分消息不保证一定能被基线版本全部消费到
。因基线版本与项目版本实际上采用的是不同的消费group,在broker的消费进度是不一致的,消息回落到基线消费之后,其消费位点可能已经超过项目版本消费group下线时的位点,中间存在偏差,会导致这部分消息再无法被基线版本消费到。

建议
用于开发测试环境,因其无法保证多版本消息至少会被消费一次

未来,消息中间件也会考虑线上环境全链路灰度场景的支持。

附录:

  1. RocketMQ 全链路灰度探索与实践
    +
    配置消息灰度

  2. 快手 RocketMQ 高性能实践
    +
    simple-failover-java

  3. 平安银行在开源技术选型上的思考和实践

  4. vivo 鲁班平台 RocketMQ 消息灰度方案

  5. OpenSergo

  6. 从RabbitMQ平滑迁移到RocketMQ技术实战