• Nacos详解


    点赞再看,养成习惯,微信搜索【牧小农】关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友。
    项目源码地址:公众号回复 nacos,即可免费获取源码

    什么是微服务

    在了解Nacos之前,我们先来了解一下,什么是微服务?在2014年的时候,一位叫Martin Fowler(马丁·福勒 )的大神,提出了微服务的概念,定义:微服务是由单一应用程序构成的小小服务,拥有独立的进程和轻量化处理,服务依据业务功能设计,用全自动的方式进行部署,能够和其他服务使用HTTP API 进行通信,同时服务会使用最小的规模进行集中管理,服务可以用不同的编程语言与数据库等组件实现。

    架构的演变

    记得小农刚工作的时候,那时候的服务大多是单体应用,可能完善一点的,会部署集群,就是一个完整的项目部署多台机器,那么随着互联网的发展,网站应用的承载量和规模也在不断的扩大,从而导致我们的系统架构也在不断的进行变化,从互联网早期到如今,系统架构大体经历了(单体 ——> 垂直——>分布式——>SOA——>微服务),如下图所示:

    1、单体架构

    在最开始的时候,大多数服务都是单体架构,就是把所有的功能点集中在一个项目(应用)里面,统一开发部署,这种开发、部署以及维护成本都比较低。

    • 这种的好处,就是其项目架构简单,上手容易,适合用户量少的项目,开发成本比较低,因为都部署在一个节点上,维护也方便
    • 缺点就是,所有的功能都集中在一个项目工程里面,随着业务的发展,代码和功能会越来越多,难以支撑一个大的项目进行开发和维护,项目之间模块耦合度高,容错性低,对于不同的功能模块优化和扩展性低。
    2、垂直应用架构

    所谓的垂直应用架构,就是从单体应用变成多个应用,用来提升效率,比如供应链系统可以拆分为:商品系统、订单系统、履约系统、门户系统、采购系统等等。

    • 其优点就是,项目拆分后实现了流量的分担,一定程度上解决并发问题,针对不同的业务模块可以进行优化和水平拓展,同时不同系统之间不会互相影响,提高容错率。
    • 缺点:系统之间互相存在,无法进行相互调用,系统之间互相独立,会造成一部分功能的冗余。
    3、分布式系统

    随着业务的增加,在垂直应用架构中冗余的代码越来越多,需要将冗余的代码抽离出来,统一做成单独的业务层进行处理,变成一个单独的服务,控制层调用不用的业务层服务完成不同的业务功能。
    具体可以理解为一个项目拆分成表现层和服务层两个部分,服务层实现业务逻辑,表现层处理页面交互。

    • 优点:抽取公共的功能作为服务层,能够提高代码的复用率
    • 缺点: 系统之间的耦合度高,调用关系复杂
    4、SOA架构

    分布式系统中的缺点就是其调用复杂,当我们的服务越来越多或者当某一个服务压力过大需要进行水平拓展和负载均衡,对于资源的调度和治理就需要用到SOA(Service Oriented Architecture)为核心来解决,治理中心还可以帮助我们解决服务之间协议不同的问题。

    • 优点:使用治理中心(ESB\dubbo)能够解决服务之间调用管理的自动调用
    • 缺点:服务之间会有一定的依赖关系,如果说某个环节错误会影响较大,服务关系复杂,运维部署困难
    5、微服务架构

    微服务架构在某种程序上来说是SOA架构往下发展的下一步,它更加强服务的“彻底拆分”,目的就是提高效率,微服务架构中,每个服务必须独立部署同时互不影响,微服务架构更加轻巧、轻量级。

    微服务架构与SOA架构的不同之处

    1. 微服务架构处理更精细,某个模块只处理对应的功能,让专业的人做专业的事
    2. 每个服务都独立部署,服务之间互相不影响
    3. 在SOA架构中可能会数据库存储发生共享,而微服务强调每个服务都是独立数据库,保证每个服务之间的连接互不影响
    4. 微服务项目架构比SOA架构更适合互联网公司(迅捷开发、更效率的版本迭代、更细粒度)

    微服务在生活中的体验

    都说艺术来源于生活,那么技术也同样来自于生活!请举例说明
    例如:
    我们去撸串的时候,一般只有一两个人,集收银、招待、厨师于一体,而当我们去酒店的时候,会有专门的接待服务员、收银员、厨师、洗碗工等等,让专业的人做专业的事!

    微服务的好处在于,能够将服务进行原子化拆分,独立进行打包、部署和升级,能够保证微服务之间清晰的任务划分,有利于扩展,但是微服务分布式系统开发的技术成本比较高,比如要考虑容错、分布式锁、分布式事务等等,复杂性更高。

    Nacos 简介

    Nacos (Naming Configuration Service) 归属于 Spring Cloud Alibaba 技术栈下,集动态服务发现、配置和服务管理平台,用于构建元原生应用程序。其中 服务发现是微服务架构中的最关键的组件之一 ,Nacos提供了一组简单易用的特性,可以帮助我们更快速的实现动态服务发现、配置、元数据及流量管理。

    什么是Nacos

    注册中心 + 配置中心 = Nacos

    为啥使用Nacos?

    注册中心对比

    CAP模型

    在这里我们不得不提到一个很重要的理论,就是CAP模型,这也是分布式系统设计中考虑的三个核心要素:

    • 一致性(Consistency): 同一时刻的同一请求的实例返回结果相同,所有的数据要求具有强一致性
    • 可用性(Availability): 所有实例的读写请求在一定时间内可以得到正确的响应
    • 分区容错性(Partition tolerance): 在网络异常(光缆断裂、设备故障、宕机)的情况下,系统任然能够提供正常的服务

    以上就是CAP原则(又称CAP定理),但是以上三种情况不可能同时满足,分布式系统设计在考虑满足P(分区容错性)的前提下选择C(一致性)还是A(可用性),也就是我们熟悉的 AP 和 CP

    CP原则(分区容错性+一致性)

    CP原则属于强一致性原则,要求所有节点可以查询的数据随时都要保持一致,若干个节点形成一个逻辑的共享区域,某一个节点更新的数据都会立即同步到其他数据节点之中,当数据同步完成之后才能返回成功的结果,但是在实际的运行过程中网络的故障在所难免,如果这个时候若干个服务节点之间无法通讯时就会出现错误,牺牲可用性原则(A)

    AP原则(分区容错性+可用性)

    AP原则属于弱一致性原则,在集群中只要有存活的节点,那么所发来的所有请求都可以得到正确的详情,在进行数据同步处理操作中即便某些节点没有成功的实现数据同步也返回成功,这样就牺牲了一致性原则(C),可以应用在网络环境不是很好的场景当中。

    对于Nacos而言,支持CP和AP两种模式的动态切换,默认为AP

    切换命令:curl -X PUT '$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'

    Nacos 类似于我们酒店的前台,首先酒店里面的房间打扫干净以后并且符合入住条件的房间会注册到服务台,这样当有客人来就酒店住店,只需要通过前台去查找到可以入驻的房间即可,不需要进行等待,可以快速的入住到酒店

    【房间 ——> 服务】
    【服务台 ——> Nacos】
    【客人 ——> 用户】

    服务器提供的注册和发现机制可以帮助客人更快速的找到合适的房间。
    如果没有一个没有服务前台的酒店,客人入驻的时候需要自己寻找合适的房间,因为客人不知道每个房间的情况,无法确定哪个房间是服务入住条件的房间,客人只能一个一个去找,费时费力,同时这也是不合理的

    在微服务中,软件服务各自独立,但是最终是作为一个整体来服务于客户,软件之间也需要彼此通信和方法调用,微服务架构内部发起通讯调用方法的一方,我们称之为 “服务消费者”,提供远程方法的服务器称之为 “服务提供者”,往往为了提高系统性能,会提供多个服务器作为 服务提供者 ,这个时候服务消费者找服务提供者的过程,就类似于用户在找房间的过程,为了帮助 服务消费者 快速的发现 服务提供者,在微服务框架中都会引入注册中心,注册中心就类似于酒店的前台,提供在软件服务的注册和发现功能,服务提供者 会先在注册中心进行注册,声明可以进行对外提供服务,而服务消费者只需要通过注册中心找到可以使用的服务,就可以快速使用服务了,注册中心实现了服务提供和服务消费的快速配合的功能。

    Nacos 搭建

    官网地址:https://nacos.io/zh-cn/index.html
    文档地址:https://nacos.io/zh-cn/docs/quick-start.html
    下载地址:https://github.com/alibaba/nacos/releases

    一般推荐的是我们使用稳定版,官方也会在文档中说明

    下载之后解压后就可以使用

    执行命令:
    linux/Mac:

    sh startup.sh -m standalone

    Windows:

    startup.cmd -m standalone

    PS:注意nacos的文件路径一定要是英文,否则启动会报错

    nacos默认使用的数据库为内嵌的cmdb,我们也可以创建本地数据库并修改配置文件指向本地数据库即可(推荐)。

    启动成功以后,我们访问:http://localhost:8848/nacos
    用户名密码默认为:nacos/nacos
    如下图所示表示我们启动成功!

    Nacos 服务端的搭建

    Nacos注册中心

    服务发现是微服务架构中的关键组件之一。在这样的架构中,手动为每个客户端配置服务列表可能是一项艰巨的任务,并且使动态扩展变得极其困难。Nacos Discovery 帮助您将服务自动注册到 Nacos 服务器,Nacos 服务器会跟踪服务并动态刷新服务列表。此外,Nacos Discovery 将服务实例的一些元数据,如主机、端口、健康检查 URL、主页等注册到 Nacos

    官方API文档:https://spring.io/projects/spring-cloud-alibaba#learn

    有的同学会说,小农我english不太好,别担心,现在浏览器上不是有翻译吗,直接翻译!!!

    创建项目

    我们需要创建一个聚合工程,这样如果需要添加新的Nacos服务,直接添加子模块就可以
    新建父类项目:spring-cloud-alibaba
    新建子类项目:cloud-alibaba-nacos-9001

    POM

    父类项目

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2021.0.1.0</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在子项,引入 Nacos Discovery 进行服务注册/发现

    <dependency>
       <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    YML

    server:
      port: 9001
    spring:
      application:
        name: nacos-provider
      cloud:
        discovery:
          server-addr: localhost:8848
    
    management:
      endpoint:
        web:
          exposure:
            include: '*'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动类:

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
    
    @SpringBootApplication
    @EnableDiscoveryClient 
    public class CloudAlibabaNacos9001Application {
    
        public static void main(String[] args) {
            SpringApplication.run(CloudAlibabaNacos9001Application.class, args);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试类:

    
    @RestController
    public class DemoController {
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping(value = "/muxiaonong")
        public String getServerPort(){
                return "hello Nacos Discovery"+serverPort;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当我们启动服务以后,就可以看到在服务列表中,看到我们启动服务

    参考 cloud-alibaba-nacos-9001 新建一个的服务 cloud-alibaba-nacos-9002

    Nacos消费者负载均衡

    新建子类项目:cloud-alibab-consumer-8083
    Pom

    `
    
        4.0.0
        
            com.muxiaonong
            spring-cloud-alibaba
            0.0.1-SNAPSHOT
             
        
        com.muxiaonong
        cloud-alibab-consumer-8083
        0.0.1-SNAPSHOT
        cloud-alibab-consumer-8083
        Demo project for Spring Boot
        
            1.8
        
        
            
                org.springframework.boot
                spring-boot-starter-web
            
            
                com.alibaba.cloud
                spring-cloud-starter-alibaba-nacos-discovery
            
            
                org.springframework.boot
                spring-boot-starter-actuator
            
            
                org.springframework.boot
                spring-boot-devtools
                runtime
                true
            
            
                org.springframework.cloud
                spring-cloud-starter-loadbalancer
                3.1.0
            
    
        
    
    ` 
    
    ![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
    
    *   1
    *   2
    *   3
    *   4
    *   5
    *   6
    *   7
    *   8
    *   9
    *   10
    *   11
    *   12
    *   13
    *   14
    *   15
    *   16
    *   17
    *   18
    *   19
    *   20
    *   21
    *   22
    *   23
    *   24
    *   25
    *   26
    *   27
    *   28
    *   29
    *   30
    *   31
    *   32
    *   33
    *   34
    *   35
    *   36
    *   37
    *   38
    *   39
    *   40
    *   41
    *   42
    *   43
    *   44
    *   45
    *   46
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    同时我们也需要在父类项目中添加子类说明

    <modules>
        <module>cloudalibaba-nacos-9001</module>
        <module>cloudalibaba-nacos-9002</module>
        <module>cloudalibab-consumer-nacos-8083</module>
    </modules>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    yml

    server:
      port: 8083
    spring:
      application:
        name: nacos-consumer
      cloud:
        discovery:
          server-addr: localhost:8848
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    启动类

    @SpringBootApplication
    @EnableDiscoveryClient
    public class CloudAlibabConsumer8083Application {
    
        public static void main(String[] args) {
            SpringApplication.run(CloudAlibabConsumer8083Application.class, args);
        }
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    nacos-provider: 服务注册者
    nacos-consumer: 服务消费者

    服务间的调用

    如果我们想要我们创建服务的消费者去调用服务提供者,就需要通过Ribbon进行调用。

    什么是Ribbon

    Spring Cloud Ribbon 是一个基于HTTP和TCP客户端负载均衡器,基于Netflix Ribbon实现,通过Spring Cloud的封装,可以让我们很方便的将面向服务的Rest模板请求自动转换成客户端负载均衡服务调用,虽然它知识一个工具类库,不需要进行独立部署,它几乎存在于每一个Spring Cloud 构建的微服务和基础设施中,对于服务间的调用,AP网关的请求转发都需要经过Ribbon负载均衡来实现,而Ribbon的主要作用是:从注册服务器端拿到对应的服务列表后用负载均衡的方式访问对应的服务

    因为Nacos已经整合了Ribbon,所以我们如果想要使用,只需要导入Spring Cloud Alibaba Nacos的依赖就可以直接使用了。

    这个需要注意的是nacos自从2020版本之后不再整合的是Netflix,也就没有ribbon了,如果报错说找不到服务是因为,你使用了负载均衡算法,但是没有ribbon了,它不知道该使用哪个服务。
    所以这里只需要导入spring-cloud-starter-loadbalancer依赖即可

    <dependency>
       <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        <version>3.1.0</version>
    </dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Ribbon的基础使用

    对于Ribbon进行远程调用,我们通常使用 RestTemplate,其中getForObject是最常用方法,我们只需要在启动类中添加 RestTemplate就可以正常使用

     	@Bean
        @LoadBalanced 
        public RestTemplate getRestTemplate() {
            return new RestTemplate();
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    当body是一个对象时,可以直接这样实现:
    User result = restTemplate.getForObject(uri, User.class);

    不需要关注请求响应除body外的其他内容时,该函数就非常好用,可以少一个从Response中获取body的步骤。提供了三种不同的重载实现

    url: 表示被调用的目标Rest接口位置,多个服务提供者注册相同名称,Ribbon会自动寻找其中一个服务提供者,并且调用接口方法。这个就是负载均衡功能
    responseType: 返回值类型,JavaBean类型或者JavaBean数组类型
    urlVariables: 传递给url的动态参数,使用参数时候需要在url上需要使用{1}、{2}、{3}进行参数占位,这样传递的参数就会自动替换占位符

    getForObject(String url, Class responseType, Object … urlVariables)
    getForObject(String url, Class responseType, Map urlVariables)
    getForObject(URI url, Class responseType)

    Nacos负载均衡

    如果想让我们的消费者consuomer去调用服务提供者9001和9002,就是使用到我们的负载均衡了。
    yml

    server:
      port: 8083
    spring:
      application:
        name: nacos-consumer
      cloud:
        discovery:
          server-addr: localhost:8848
    
    # 服务提供者的名称
    service-url:
      nacos-user-service: http://nacos-provider
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动类上配置restTemplate

     @Bean
     @LoadBalanced
      public RestTemplate restTemplate(){
          return new RestTemplate();
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    测试类

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.client.RestTemplate;
    
    @RestController
    public class DemoController {
    
        @Autowired
        private RestTemplate restTemplate;
    
    	
        @Value("${service-url.nacos-user-service}")
        private String serverURL;
    
        @GetMapping(value = "/consumer/nacos")
        public String getDiscovery(){
            System.out.println(serverURL);
            return restTemplate.getForObject(serverURL+"/muxiaonong",String.class);
        }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    当我们输入:http://localhost:8083/consumer/nacos
    就可以看到9001和9002对应的切换显示了

    Nacos配置中心

    上面我们有讲 注册中心 + 配置中心 = Nacos ,前面我们将了注册中心,现在我们来讲一讲如何使用Nacos作为他的配置中心

    为什么使用Nacos配置中心

    1、配置属性的动态刷新
    2、配置文件的集中管理

    我们新建一个子项:cloud-alibaba-config-3377

    pom文件:

     <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    YML配置:
    在这里我们需要配置两个,一个是让当前项目注册到Nacos中,另外一个是去Nacos中读取指定后缀的配置文件
    bootstrap.yml

    # nacos配置
    server:
      port: 3377
    
    spring:
      application:
        name: nacos-config-client
      cloud:
        nacos:
          discovery:
            server-addr: localhost:8848 #Nacos服务注册中心地址
          config:
            server-addr: localhost:8848 #Nacos作为配置中心地址
            file-extension: yaml #指定yaml格式的配置
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    application.yml

    spring:
      profiles:
         active: dev
    
    
    • 1
    • 2
    • 3
    • 4

    启动类同样添加@EnableDiscoveryClient注解

    测试类:

    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.cloud.context.config.annotation.RefreshScope;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    
    @RestController
    @RefreshScope 
    public class DemoController {
    
        @Value("${config.info}")
        private String configInfo;
    
        @GetMapping("/config/info")
        public String getConfigInfo(){
            return configInfo;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    Nacos的配置规则

    官网:https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html

    在Nacos Spring Cloud 中,dataId的完整格式如下

    ${prefix}-${spring.profiles.active}.${file-extension}
    
    
    • 1
    • 2
    • prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
    • spring.profiles.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${file-extension}
    • file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。
     ${spring.application.name}-${spring.profiles.active}.${file-extension}
     nacos-config-client-dev.yaml
     微服务名称-当前环境-文件格式
    
    
    • 1
    • 2
    • 3
    • 4

    接下来而我们只需要在Nacos平台上去创建配置系项即可

    修改Nacos配置,不需要重启项目即可自动刷新

    通过链接访问即可

    小结

    创作不易,您的一键三连是我最大的动力!!!

    如果有疑问或者不懂的地方,欢迎在下面留言,小农看到了,会第一时间回复大家。

    怕什么真理无穷,进一步 有进一步的欢喜,大家加油~

  • 相关阅读:
    你们的优雅停机真的优雅吗?
    【python自动化】Playwright基础教程(七)Keyboard键盘
    Android 插件化技术应运而生出的 Apk 动态加载技术的开源框架
    SpringBoot整合Redis实现常用功能
    [机缘参悟-76]:深度思考-职场中注意事项与大忌-员工版
    1-(4-氨基苯)-1,2,2-三苯乙烯|AIE聚集诱导发光|1-(4-Aminophenyl)-1,2,2-triphenylethene
    CRM客户管理系统在市面上这么多?应该如何选型?各行业选型CRM必看!
    Spark---介绍及安装
    BIOTIN-PEG-COOH,生物素PEG羧基
    Docker容器内使用matplotlib.pyplot
  • 原文地址:https://blog.csdn.net/github_36665118/article/details/127818689