kafka消息消费有延迟_RabbitMQ与Kafka的技术差异以及使用注意点

206f92d92d10f1d2867235761c9a15c4.png

导言

c3b06193d6373a325796474808ef1ab3.png

作为一个有丰富经验的微服务系统架构师,经常有人问我,“应该选择RabbitMQ还是Kafka?”。基于某些原因, 许多开发者会把这两种技术当做等价的来看待。的确,在一些案例场景下选择RabbitMQ还是Kafka没什么差别,但是这两种技术在底层实现方面是有许多差异的。不同的场景需要不同的解决方案,选错一个方案能够严重的影响你对软件的设计,开发和维护的能力。第一篇文章介绍了RabbitMQ和Apache Kafka内部实现的相关概念。本篇文章会从两个方面探讨这两种技术之间的差异,一个是这两种技术之间的显著差异,另一个是对于软件架构师和开发者需要注意的差异。那么,我们先来说说架构模式,也就是我们尝试着利用这两种技术来实现的架构模式,并且评估什么时候该使用哪一个。注意 1如果你对RabbitMQ和Kafka的内部结构还不熟悉,我强烈推荐你阅读我之前的第一篇文章。如果你不确定,那么可以简要的看一下里面的标题和图表,至少对这些差异有个大概的了解。注意 2上一篇文章发表之后,有些读者问我对于Apache Pulsar的看法。Pulsar是另一种类型的消息系统,它旨在提供RabbitMQ和Kafka都有的一些优点。作为一个现代的消息系统,它看上去很有前途;但是像其他平台系统一样,都有各自的优缺点。这边文章主要是比较RabbitMQ和Kafka,之后我会尝试针对Apache Pulsar做一个比较。RabbitMQ和Kafka的显著差异

c3b06193d6373a325796474808ef1ab3.png

RabbitMQ是一个消息代理,但是Apache Kafka是一个分布式流式系统。好像从语义上就可以看出差异,但是它们内部的一些特性会影响到我们是否能够很好的设计各种用例。例如,Kafka最适用于数据的流式处理,但是RabbitMQ对流式中的消息就很难保持它们的顺序。另一方面,RabbitMQ内置重试逻辑和死信(dead-letter)交换器,但是Kafka只是把这些实现逻辑交给用户来处理。这部分主要强调在不同系统之间它们的主要差异。消息顺序对于发送到队列或者交换器上的消息,RabbitMQ不保证它们的顺序。尽管消费者按照顺序处理生产者发来的消息看上去很符合逻辑,但是这有很大误导性。RabbitMQ文档中有关于消息顺序保证的说明:“发布到一个通道(channel)上的消息,用一个交换器和一个队列以及一个出口通道来传递,那么最终会按照它们发送的顺序接收到。” ——RabbitMQ代理语义(Broker Semantics)换话句话说,只要我们是单个消费者,那么接收到的消息就是有序的。然而,一旦有多个消费者从同一个队列中读取消息,那么消息的处理顺序就没法保证了。由于消费者读取消息之后可能会把消息放回(或者重传)到队列中(例如,处理失败的情况),这样就会导致消息的顺序无法保证。一旦一个消息被重新放回队列,另一个消费者可以继续处理它,即使这个消费者已经处理到了放回消息之后的消息。因此,消费者组处理消息是无序的,如下表所示。

cf85f02d2d40f920c89cb556aa47befd.png

使用RabbitMQ丢失消息顺序的例子当然,我们可以通过限制消费者的并发数等于1来保证RabbitMQ中的消息有序性。更准确点说,限制单个消费者中的线程数为1,因为任何的并行消息处理都会导致无序问题。不过,随着系统规模增长,单线程消费者模式会严重影响消息处理能力。所以,我们不要轻易的选择这种方案。另一方面,对于Kafka来说,它在消息处理方面提供了可靠的顺序保证。Kafka能够保证发送到相同主题分区的所有消息都能够按照顺序处理。回顾第一篇文章介绍,默认情况下,Kafka会使用循环分区器(round-robin partitioner)把消息放到相应的分区上。不过,生产者可以给每个消息设置分区键(key)来创建数据逻辑流(比如来自同一个设备的消息,或者属于同一租户的消息)。所有来自相同流的消息都会被放到相同的分区中,这样消费者组就可以按照顺序处理它们。但是,我们也应该注意到,在同一个消费者组中,每个分区都是由一个消费者的一个线程来处理。结果就是我们没法伸缩(scale)单个分区的处理能力。不过,在Kafka中,我们可以伸缩一个主题中的分区数量,这样可以让每个分区分担更少的消息,然后增加更多的消费者来处理额外的分区。获胜者(Winner):显而易见,Kafka是获胜者,因为它可以保证按顺序处理消息。RabbitMQ在这块就相对比较弱。消息路由RabbitMQ可以基于定义的订阅者路由规则路由消息给一个消息交换器上的订阅者。一个主题交换器可以通过一个叫做routing_key的特定头来路由消息。或者,一个头部(headers)交换器可以基于任意的消息头来路由消息。这两种交换器都能够有效地让消费者设置他们感兴趣的消息类型,因此可以给解决方案架构师提供很好的灵活性。另一方面,Kafka在处理消息之前是不允许消费者过滤一个主题中的消息。一个订阅的消费者在没有异常情况下会接受一个分区中的所有消息。作为一个开发者,你可能使用Kafka流式作业(job),它会从主题中读取消息,然后过滤,最后再把过滤的消息推送到另一个消费者可以订阅的主题。但是,这需要更多的工作量和维护,并且还涉及到更多的移动操作。获胜者:在消息路由和过滤方面,RabbitMQ提供了更好的支持。消息时序(timing)在测定发送到一个队列的消息时间方面,RabbitMQ提供了多种能力:消息存活时间(TTL)发送到RabbitMQ的每条消息都可以关联一个TTL属性。发布者可以直接设置TTL或者根据队列的策略来设置。系统可以根据设置的TTL来限制消息的有效期。如果消费者在预期时间内没有处理该消息,那么这条消息会自动的从队列上被移除(并且会被移到死信交换器上,同时在这之后的消息都会这样处理)。TTL对于那些有时效性的命令特别有用,因为一段时间内没有处理的话,这些命令就没有什么意义了。延迟/预定的消息RabbitMQ可以通过插件的方式来支持延迟或者预定的消息。当这个插件在消息交换器上启用的时候,生产者可以发送消息到RabbitMQ上,然后这个生产者可以延迟RabbitMQ路由这个消息到消费者队列的时间。这个功能允许开发者调度将来(future)的命令,也就是在那之前不应该被处理的命令。例如,当生产者遇到限流规则时,我们可能会把这些特定的命令延迟到之后的一个时间执行。Kafka没有提供这些功能。它在消息到达的时候就把它们写入分区中,这样消费者就可以立即获取到消息去处理。Kafka也没用为消息提供TTL的机制,不过我们可以在应用层实现。不过,我们必须要记住的一点是Kafka分区是一种追加模式的事务日志。所以,它是不能处理消息时间(或者分区中的位置)。获胜者:毫无疑问,RabbitMQ是获胜者,因为这种实现天然的就限制Kafka。消息留存(retention)当消费者成功消费消息之后,RabbitMQ就会把对应的消息从存储中删除。这种行为没法修改。它几乎是所有消息代理设计的必备部分。相反,Kafka会给每个主题配置超时时间,只要没有达到超时时间的消息都会保留下来。在消息留存方面,Kafka仅仅把它当做消息日志来看待,并不关心消费者的消费状态。消费者可以不限次数的消费每条消息,并且他们可以操作分区偏移来“及时”往返的处理这些消息。Kafka会周期的检查分区中消息的留存时间,一旦消息超过设定保留的时长,就会被删除。Kafka的性能不依赖于存储大小。所以,理论上,它存储消息几乎不会影响性能(只要你的节点有足够多的空间保存这些分区)。获胜者:Kafka设计之初就是保存消息的,但是RabbitMQ并不是。所以这块没有可比性,Kafka是获胜者。容错处理当处理消息,队列和事件时,开发者常常认为消息处理总是成功的。毕竟,生产者把每条消息放入队列或者主题后,即使消费者处理消息失败了,它仅仅需要做的就是重新尝试,直到成功为止。尽管表面上看这种方法是没错的,但是我们应该对这种处理方式多思考一下。首先我们应该承认,在某些场景下,消息处理会失败。所以,即使在解决方案部分需要人为干预的情况下,我们也要妥善地处理这些情况。消息处理存在两种可能的故障:
  • 瞬时故障——故障产生是由于临时问题导致,比如网络连接,CPU负载,或者服务崩溃。我们可以通过一遍又一遍的尝试来减轻这种故障。

  • 持久故障——故障产生是由于永久的问题导致的,并且这种问题不能通过额外的重试来解决。比如常见的原因有软件bug或者无效的消息格式(例如,损坏(poison)的消息)

作为架构师和开发者,我们应该问问自己:“对于消息处理故障,我们应该重试多少次?每一次重试之间我们应该等多久?我们怎样区分瞬时和持久故障?”最重要的是:“所有重试都失败后或者遇到一个持久的故障,我们要做什么?”当然,不同业务领域有不同的回答,消息系统一般会给我们提供工具让我们自己实现解决方案。RabbitMQ会给我们提供诸如交付重试和死信交换器(DLX)来处理消息处理故障。DLX的主要思路是根据合适的配置信息自动地把路由失败的消息发送到DLX,并且在交换器上根据规则来进一步的处理,比如异常重试,重试计数以及发送到“人为干预”的队列。查看这篇文章[1],它在RabbitMQ处理重试上提供了额外的可能模式视角。在RabbitMQ中我们需要记住最重要的事情是当一个消费者正在处理或者重试某个消息时(即使是在把它返回队列之前),其他消费者都可以并发的处理这个消息之后的其他消息。当某个消费者在重试处理某条消息时,作为一个整体的消息处理逻辑不会被阻塞。所以,一个消费者可以同步地去重试处理一条消息,不管花费多长时间都不会影响整个系统的运行。

1d16c3e16bd5e44a666b41c047706dfb.png

消费者1持续的在重试处理消息1,同时其他消费者可以继续处理其他消息和RabbitMQ相反,Kafka没有提供这种开箱即用的机制。在Kafka中,需要我们自己在应用层提供和实现消息重试机制。另外,我们需要注意的是当一个消费者正在同步地处理一个特定的消息时,那么同在这个分区上的其他消息是没法被处理的。由于消费者不能改变消息的顺序,所以我们不能够拒绝和重试一个特定的消息以及提交一个在这个消息之后的消息。你只要记住,分区仅仅是一个追加模式的日志。一个应用层解决方案可以把失败的消息提交到一个“重试主题”,并且从那个主题中处理重试;但是这样的话我们就会丢失消息的顺序。我们可以在Uber.com上找到Uber工程师实现的一个例子。如果消息处理的时延不是关注点,那么对错误有足够监控的Kafka方案可能就足够了。如果消费者阻塞在重试一个消息上,那么底部分区的消息就不会被处理获胜者:RabbitMQ是获胜者,因为它提供了一个解决这个问题的开箱即用的机制。伸缩有多个基准测试,用于检查RabbitMQ和Kafka的性能。尽管通用的基准测试对一些特定的情况会有限制,但是Kafka通常被认为比RabbitMQ有更优越的性能。Kafka使用顺序磁盘I / O来提高性能。从Kafka使用分区的架构上看,它在横向扩展上会优于RabbitMQ,当然RabbitMQ在纵向扩展上会有更多的优势。Kafka的大规模部署通常每秒可以处理数十万条消息,甚至每秒百万级别的消息。过去,Pivotal记录了一个Kafka集群每秒处理一百万条消息[2]的例子;但是,它是在一个有着30个节点集群上做的,并且这些消息负载被优化分散到多个队列和交换器上。典型的RabbitMQ部署包含3到7个节点的集群,并且这些集群也不需要把负载分散到不同的队列上。这些典型的集群通常可以预期每秒处理几万条消息。获胜者:尽管这两个消息平台都可以处理大规模负载,但是Kafka在伸缩方面更优并且能够获得比RabbitMQ更高的吞吐量,因此这局Kafka获胜。但是,值得注意的是大部分系统都还没有达到这些极限!所以,除非你正在构建下一个非常受欢迎的百万级用户软件系统,否则你不需要太关心伸缩性问题,毕竟这两个消息平台都可以工作的很好。消费者复杂度RabbitMQ使用的是智能代理和傻瓜式消费者模式。消费者注册到消费者队列,然后RabbitMQ把传进来的消息推送给消费者。RabbitMQ也有拉取(pull)API;不过,一般很少被使用。RabbitMQ管理消息的分发以及队列上消息的移除(也可能转移到DLX)。消费者不需要考虑这块。根据RabbitMQ结构的设计,当负载增加的时候,一个队列上的消费者组可以有效的从仅仅一个消费者扩展到多个消费者,并且不需要对系统做任何的改变。

56e16592a40c8bc0866e2e58c63a0a90.png

RabbitMQ高效的伸缩相反,Kafka使用的是傻瓜式代理和智能消费者模式。消费者组中的消费者需要协调他们之间的主题分区租约(以便一个具体的分区只由消费者组中一个消费者监听)。消费者也需要去管理和存储他们分区偏移索引。幸运的是Kafka SDK已经为我们封装了,所以我们不需要自己管理。另外,当我们有一个低负载时,单个消费者需要处理并且并行的管理多个分区,这在消费者端会消耗更多的资源。当然,随着负载增加,我们只需要伸缩消费者组使其消费者的数量等于主题中分区的数量。这就需要我们配置Kafka增加额外的分区。但是,随着负载再次降低,我们不能移除我们之前增加的分区,这需要给消费者增加更多的工作量。尽管这样,但是正如我们上面提到过,Kafka SDK已经帮我们做了这个额外的工作。

f1c07390a1fa297a66973b51210fcd70.png

Kafka分区没法移除,向下伸缩后消费者会做更多的工作获胜者:根据设计,RabbitMQ就是为了傻瓜式消费者而构建的。所以这轮RabbitMQ获胜。如何选择?

c3b06193d6373a325796474808ef1ab3.png

现在我们就如面对百万美元问题一样:“什么时候使用RabbitMQ以及什么时候使用Kafka?”概括上面的差异,我们不难得出以下结论:优先选择RabbitMQ的条件:
  1. 高级灵活的路由规则

  2. 消息时序控制(控制消息过期或者消息延迟)

  3. 高级的容错处理能力,在消费者更有可能处理消息不成功的情景中(瞬时或者持久)

  4. 更简单的消费者实现

优先选择Kafka的条件:
  1. 严格的消息顺序

  2. 延长消息留存时间,包括过去消息重放的可能

  3. 传统解决方案无法满足的高伸缩能力

大部分情况下这两个消息平台都可以满足我们的要求。但是,它取决于我们的架构师,他们会选择最合适的工具。当做决策的时候,我们需要考虑上面着重强调的功能性差异和非功能性限制。这些限制如下:
  1. 当前开发者对这两个消息平台的了解

  2. 托管云解决方案的可用性(如果适用)

  3. 每种解决方案的运营成本

  4. 适用于我们目标栈的SDK的可用性

当开发复杂的软件系统时,我们可能被诱导使用同一个消息平台去实现所有必须的消息用例。但是,从我的经验看,通常同时使用这两个消息平台能够带来更多的好处。例如,在一个事件驱动的架构系统中,我们可以使用RabbitMQ在服务之间发送命令,并且使用Kafka实现业务事件通知。原因是事件通知常常用于事件溯源,批量操作(ETL风格),或者审计目的,因此Kafka的消息留存能力就显得很有价值。相反,命令一般需要在消费者端做额外处理,并且处理可以失败,所以需要高级的容错处理能力。这里,RabbitMQ在功能上有很多闪光点。以后我可能会写一篇详细的文章来介绍,但是你必须记住--你的里程(mileage)可能会变化,因为适合性取决于你的特定需求。总结思想

c3b06193d6373a325796474808ef1ab3.png

写这两篇文章是由于我观察到许多开发者把这RabbitMQ和Kafka作为等价来看待。我希望通过这两篇文章的帮助能够让你获得对这两种技术实现的深刻理解以及它们之间的技术差异。反过来通过它们之间的差异来影响这两个平台去给用例提供更好的服务。这两个消息平台都很棒,并且都能够给多个用例提供很好的服务。但是,作为解决方案架构师,取决于我们对每一个用例需求的理解,以及优化,然后选择最合适的解决方案。相关链接:
  1. https://engineering.nanit.com/rabbitmq-retries-the-full-story-ca4cc6c5b493

  2. https://content.pivotal.io/blog/rabbitmq-hits-one-million-messages-per-second-on-google-compute-engine

原文链接:https://medium.com/better-programming/rabbitmq-vs-kafka-1779b5b70c41

53f155bfd36ff401971ddb983109dfcb.png

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

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

相关文章

android 通知_Android 全局消息通知框架实现(类似EventBus)

Github项目地址 https://github.com/532268948/MessageDemo一、介绍随着页面的增多,页面之间的信息交流也会越来越多,比如一个社交类app,你在个人中心修改了自己的个人信息,在动态展示页面你就要通知该页面有关于自己的动态需要更新自己展示在…

2019运管取消了滴滴还查吗_劳务资质2019年取消吗?劳务资质新标准你真的了解吗?...

施工劳务资质2019新标准!劳务资质已经取消?不可能!虽然这样对于很多企业来说应该比较省事,劳务资质也是越来越受欢迎的,但是企业在办理资质之前一定要将资质标准了解清楚了,新版施工劳务资质仅仅是将专业进…

FFmpeg获取DirectShow设备数据(摄像头,录屏)

这两天研究了FFmpeg获取DirectShow设备数据的方法,在此简单记录一下以作备忘。本文所述的方法主要是对应Windows平台的。 1. 列设备 ffmpeg -list_devices true -f dshow -i dummy命令执行后输出的结果如下(注:中文的设备会出现乱码的情…

矩阵的对数运算公式_必修一——对数与对数运算

一、前言(废话)高中数学我们已经学习了二次函数,指数函数(如果不记得的读者可以往前面翻看一下),这次作者为读者们讲解的是对数与对数运算,对数是什么呢?读者们心里有自己的认知吗?二、对数对数函数是高中阶段学习的一…

分拣外观残缺的机器人_复合机器人AGV+协作机器人的应用领域

什么是复合机器人?复合型机器人是一种集成AGV移动机器人和通用工业机器人两项功能为一身的机器人。在工业领域,通用工业机器人被称为机械臂或者机械手,主要是替代人胳膊的抓取功能;而AGV移动机器人是替代人腿脚的行走功能。复合型机器人则是手脚并用&am…

前端之旅,做一点有回报的事情

本文是参加前端早读课【同说】做的分享 首先感谢情封对前端圈的辛苦付出,能坚持做好一件事真的很不容易,敬佩这份精神。2天前看到早读课的【同说】活动计划,我觉得想法真的很好,忽然情封微信邀请我参与,听到后我是心虚…

差异表达基因变化倍数_10.limma教程|差异表达统计相关理论

点击上方“蓝字”关注我们10.1 Top-Tables 简介limma包提供topTable和decideTests函数概括线性模型结果,进行假设检验,调整p值进行多重检验。结果包括(log2)倍数变化,标准误差,t统计和p值。用于显著性分析的基本统计量是修正t统计…

activemq中怎么知道推送消息是否成功_如何优雅的使用activeMQ 安装、应用、安全认证、持久化...

ActiveMQ安装1. 下载资源ActiveMQ官网:版本说明ActiveMQ5.10.x以上版本必须使用JDK1.8才能正常使用。ActiveMQ5.9.x及以下版本使用JDK1.7即可正常使用。上传至Linux服务器解压安装文件tar -zxf apache-activemq-5.9.0-bin.tar.gz检查权限ls -al apache-activemq-5.9…

DB2表结构DDL脚本导出

db2look是导出DDL语句脚本的命令&#xff0c;以下是对db2look的一个简单介绍。 语法&#xff1a;db2look -d <数据库名> -e -t <表名> -o <文件名>.dml -d&#xff1a;数据库名&#xff0c;这必须指定。 -e:抽取复制数据所需要的DDL文件&#xff0c;此项将生…

套装门安装_室内套装门-油漆工艺

上一期我们分享了套装门的分类和区别&#xff0c;主要从门芯和门皮的结构&#xff0c;主要分为纯实木门、实木复合门、模压门三种。也分享了三种类型的门的区别和基本定义&#xff0c;其中最为关键的是实木复合门&#xff0c;四层结构&#xff0c;其中门芯的填充料可操作空间很…

activiti 工作流_分享:springboot中关于工作流activiti的搭建

概念工作流产品使用activiti的算是比较多了&#xff0c;自带了一套UI界面&#xff0c;可以直接使用&#xff0c;用来设计流程&#xff0c;下面简单总结一下它的步骤&#xff1a;1 设计模型2 发布为流程&#xff0c;一个模型可以发布多个版本的流程3 建立一个流程的实例和实例任…

ae saber插件_AE激光插件Video Copilot Saber安装方法

AE激光Video Copilot Saber插件可以制作出能量光束&#xff0c;激光&#xff0c;传送门&#xff0c;闪电&#xff0c;电流&#xff0c;朦胧等特效&#xff0c;内含有40多种不同类型的特效预设&#xff0c; 下面说下AE激光插件saber安装方法。插件特色&#xff1a; 1.可以创建高…

继电器rc吸收电路取值_继电器的工作原理以及驱动电路解析

继电器是具有隔离功能的自动开关元件&#xff0c;广泛应用于遥控、遥测、通讯、自动控制、机电一体化及电力电了设备中&#xff0c;是最重要的控制元件之一。继电器实际上是用较小的电流去控制较大电流的一种“自动开关”。故在电路中起着自动调节、安全保护、转换电路等作用。…

口腔取模过程及注意事项_康贝齿科首家引进LAUNCA数字化口腔扫描,走进口腔数字化诊疗时代!...

口腔领域中的数字化技术包括数字化印模、数字化诊断与治疗、数字化生产、数字化手术导航、影像学以及相关材料的应用等。20世纪70年代Duret医生将工业数字化生产技术应用于口腔临床工作的大胆设想&#xff0c;使口腔数字化印模技术应运而生&#xff0c;走进现代医学的前沿。该设…

dataframe修改列名_python dataframe操作大全数据预处理过程(dataframe、md5)

hive表的特征选择&#xff0c;不同表之间的join训练数据、测试数据的分开保存使用pandas进行数据处理显示所有列&#xff1a;pd.set_option(display.max_columns, None)显示所有行&#xff1a;pd.set_option(display.max_rows, None)单列运算&#xff1a;df[col2] df[col1].ma…

android 获取控件高度_安卓开发入门教程UI控件_ProgressBar

什么是ProgressBarProgressBar是用于提示用户进行等待的UI控件,.基础样例1.loading图效果图代码布局文件代码<ProgressBarandroid:id"id/progressBar"style"?android:attr/progressBarStyle"android:layout_width"wrap_content"android:layo…

什么叫大地高_什么才叫睡得好?睡觉能满足这5个标准,说明睡眠质量高

每个人的睡觉习惯以及睡眠质量都是不一样的&#xff0c;有的人一晚甜睡到天亮&#xff0c;有的人则整晚辗转反侧难以入眠&#xff0c;有的人甚至会整晚都睡不着。睡眠质量的高低会影响一个人的精神状态以及工作的状态&#xff0c;而想要知道你的睡眠质量高不高&#xff0c;其实…

mysql function_Koa2 amp; mySQL amp; PM2 搭建后端服务

嘘&#xff01;啰嗦一句话。作为一名前端开发工程师&#xff0c;我们平常和后端接触最多的就是接口的对接。那么后端同事是怎么写一个接口来给到我们前端的同学去获取数据库数据并做一些花里胡哨的功能呢&#xff1f;今天我们就用Koa2 & mySql来搭建一个后端服务器&#xf…

datetime报错 sql脚本_《SQL必知必会》附录A样例表的获取和导入

拿到书的第二天就坐上了疫情期间的第一次飞机。。。飞机还延误了2个小时&#xff0c;有国外入境转机旅客搭乘了该趟航班&#xff0c;第一次觉得疫情离我这么近&#xff0c;不过还是坚持着到达了目的地&#xff0c;能做的只有带好口罩和手套。3个小时的飞行时间被拖到了5个小时&…

python不同版本共存_多个python版本共存时的pip配置

两种方法来配置pip Func1: 1.1 找到python环境的安装包&#xff0c;将python.exe文件重命名&#xff0c;如&#xff1a;将python2.7版本的python.exe重命名为Python2.exe,将python3.x版本的python.exe重命名为python3.x.exe。1.2 下面找到python安装包下的 Scripts文件夹下 pip…