简介



Spring Cloud Ribbon是基于Netflix Ribbon实现的一套 客户端 负载均衡的工具 。(重点:客户端)

简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们也很容易使用Ribbon实现自定义的负载均衡算法。

LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。

负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。

常见的负载均衡有软件Nginx,LVS,硬件 F5等。

相应的在中间件,例如:dubbo和SpringCloud中均给我们提供了负载均衡, SpringCloud的负载均衡算法可以自定义。

集中式LB

即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;

就好像是左边一个消费方,右边一个服务方,中间是硬件LB设施,就是负载均衡是加在消费方和服务方中间的

进程内LB

将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。

就好像是进入麦当劳消费,有很多自助的窗口,我们每个人都是消费端,消费端(也就是我们自己)会根据看到的自动去判断去哪个窗口,也就是负载均衡施加在消费端的

Ribbon就属于进程内LB ,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。

官网:https://github.com/Netflix/ribbon/wiki/Getting-Started

Ribbon配置初步

修改microservicecloud-consumer-dept-80工程 (consumer client)

修改pom.xml文件

增加如下配置

   <!-- Ribbon相关 -->
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter- eureka </artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-ribbon</artifactId>
   </dependency>
   <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter- config </artifactId>
   </dependency>

说明:因为要实现ribbon是需要eureka配合的,所以这里也加入了eureka

修改application.yml 追加eureka的服务注册地址

server:
   port:  80 

eureka:
   client:
     register-with-eureka:   false
     service-url:
       defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/

对ConfigBean进行新注解@LoadBalanced 获得Rest时加入Ribbon的配置

package  com.atguigu.springcloud.cfgbeans; 

import  org.springframework.cloud.client.loadbalancer.LoadBalanced;
import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration;
import  org.springframework.web.client.RestTemplate; 

@Configuration
public   class  ConfigBean
{
   @Bean
  @LoadBalanced
   public  RestTemplate getRestTemplate()
  {
    return   new  RestTemplate();
  }
}

说明:

只是加入注解 @LoadBalanced

主启动类DeptConsumer80_App添加@EnableEurekaClient

package  com.atguigu.springcloud; 

import  org.springframework.boot.SpringApplication;
import  org.springframework.boot.autoconfigure.SpringBootApplication;
import  org.springframework.cloud.netflix.eureka.EnableEurekaClient; 

@SpringBootApplication
@EnableEurekaClient
public   class  DeptConsumer80_App
{
   public   static   void  main(String[]  args )
  {
   SpringApplication. run (DeptConsumer80_App. class ,  args );
  }
}

说明:

只是加入注解 @EnableEurekaClient

修改DeptController_Consumer客户端访问类

package  com.atguigu.springcloud.controller; 

import  java.util.List; 

import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.web.bind.annotation.PathVariable;
import  org.springframework.web.bind.annotation.RequestMapping;
import  org.springframework.web.bind.annotation.RestController;
import  org.springframework.web.client.RestTemplate; 

import  com.atguigu.springcloud.entities.Dept; 

@RestController
public   class  DeptController_Consumer
{
   //private static final String REST_URL_PREFIX = "http://localhost:8001";
   private   static   final  String  REST_URL_PREFIX  = "http://MICROSERVICECLOUD-DEPT"; 

   @Autowired
   private  RestTemplate  restTemplate ; 

   @RequestMapping (value= "/consumer/dept/add" )
   public   boolean  add(Dept  dept )
  {
    return   restTemplate .postForObject( REST_URL_PREFIX + "/dept/add" ,  dept , Boolean. class );
  } 

   @RequestMapping (value= "/consumer/dept/get/{id}" )
   public  Dept get( @PathVariable ( "id" ) Long  id )
  {
    return   restTemplate .getForObject( REST_URL_PREFIX + "/dept/get/" + id , Dept. class );
  } 

   @SuppressWarnings ( "unchecked" )
   @RequestMapping (value= "/consumer/dept/list" )
   public  List<Dept> list()
  {
    return   restTemplate .getForObject( REST_URL_PREFIX + "/dept/list" , List. class );
  }  

   //测试@EnableDiscoveryClient,消费端可以调用服务发现
   @RequestMapping (value= "/consumer/dept/discovery" )
   public  Object discovery()
  {
    return   restTemplate .getForObject( REST_URL_PREFIX + "/dept/discovery" , Object. class );
  }   

}

说明:

 //private static final String REST_URL_PREFIX = "http://localhost:8001";
  private   static   final  String  REST_URL_PREFIX  = "http://MICROSERVICECLOUD-DEPT";

说明:将原来的localhost:8001换成了从rureka上的applicaiotn名字,也就是8001yml文件的name配置,简而言之,就是舍弃了原来的域名加端口的形式,换成了应用名字的形式。

先启动3个eureka集群后,再启动microservicecloud-provider-dept-8001并注册进eureka

启动microservicecloud-consumer-dept-80

测试:

http://localhost/consumer/dept/get/1

http://localhost/consumer/dept/list

http://localhost/consumer/dept/add?dname=大数据部

小结

Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号

Ribbon负载均衡

架构说明



Ribbon在工作时分成两步

第一步先选择 EurekaServer ,它优先选择在同一个区域内负载较少的server.

第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。

其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。

参考microservicecloud-provider-dept-8001,新建两份,分别命名为8002,8003

新建8002/8003数据库,各自微服务分别连各自的数据库

8002SQL脚本


DROP  DATABASE IF  EXISTS  cloudDB02; 

CREATE  DATABASE cloudDB02  CHARACTER   SET  UTF8; 

USE cloudDB02; 

CREATE   TABLE  dept
(
  deptno BIGINT  NOT   NULL   PRIMARY   KEY  AUTO_INCREMENT,
  dname  VARCHAR (60),
  db_source    VARCHAR (60)
); 

INSERT   INTO  dept(dname,db_source)  VALUES ( '开发部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '人事部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '财务部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '市场部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '运维部' ,DATABASE()); 

SELECT  *  FROM  dept; 

8003SQL脚本

DROP  DATABASE IF  EXISTS  cloudDB03; 

CREATE  DATABASE cloudDB03  CHARACTER   SET  UTF8; 

USE cloudDB03; 

CREATE   TABLE  dept
(
  deptno BIGINT  NOT   NULL   PRIMARY   KEY  AUTO_INCREMENT,
  dname  VARCHAR (60),
  db_source    VARCHAR (60)
); 

INSERT   INTO  dept(dname,db_source)  VALUES ( '开发部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '人事部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '财务部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '市场部' ,DATABASE());
INSERT   INTO  dept(dname,db_source)  VALUES ( '运维部' ,DATABASE()); 

SELECT  *  FROM  dept;

修改8002/8003各自YML

8002YML

server:
   port:  8002 

mybatis:
   config-location:  classpath:mybatis/mybatis.cfg.xml   #mybatis所在路径
   type-aliases-package:  com.atguigu.springcloud.entities  #entity别名类
   mapper-locations:
  - classpath:mybatis/mapper/ ** / * .xml  #mapper映射文件 

spring:
    application:
     name:  microservicecloud-dept
    datasource:
     type:  com.alibaba.druid.pool.DruidDataSource
     driver-class-name:  org.gjt.mm.mysql.Driver
     url:  jdbc:mysql://localhost:3306/cloudDB02
     username:  root
     password:  123456
     dbcp2:
       min-idle:  5
       initial-size:  5
       max-total:  5
       max-wait-millis:  200 

eureka:
   client:   #客户端注册进eureka服务列表内
     service-url:
       defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
   instance:
     instance-id:  microservicecloud-dept8002    #自定义服务名称信息
     prefer-ip-address:   true       #访问路径可以显示IP地址 

info:
   app.name:  atguigu-microservicecloud
   company.name:  www.atguigu.com
   build.artifactId:  $project.artifactId$
   build.version:  $project.version$

8003YML

server:
   port:  8003 

mybatis:
   config-location:  classpath:mybatis/mybatis.cfg.xml   #mybatis所在路径
   type-aliases-package:  com.atguigu.springcloud.entities  #entity别名类
   mapper-locations:
  - classpath:mybatis/mapper/ ** / * .xml  #mapper映射文件 

spring:
    application:
     name:  microservicecloud-dept
    datasource:
     type:  com.alibaba.druid.pool.DruidDataSource
     driver-class-name:  org.gjt.mm.mysql.Driver
     url:  jdbc:mysql://localhost:3306/cloudDB03
     username:  root
     password:  123456
     dbcp2:
       min-idle:  5
       initial-size:  5
       max-total:  5
       max-wait-millis:  200 

eureka:
   client:   #客户端注册进eureka服务列表内
     service-url:
       defaultZone:  http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
   instance:
     instance-id:  microservicecloud-dept8003    #自定义服务名称信息
     prefer-ip-address:   true       #访问路径可以显示IP地址 

info:
   app.name:  atguigu-microservicecloud
   company.name:  www.atguigu.com
   build.artifactId:  $project.artifactId$
   build.version:  $project.version$

说明:



1 8002 8803 的端口server需要修改





2 8002 8003 的数据库连接需要修改







3 对外暴露的统一的服务实例名不要修改





尤其注意第三点:因为负载均衡,所以三个server服务虽然域名端口都是自己的但是三个服务的application 的名字需要是一个,这样client端进行调用的时候就能够采用相应的算法(轮询等)进行负载均衡的调用了。



启动3个eureka集群配置区

启动3个Dept微服务并各自测试通过

http://localhost:8001/dept/list

http://localhost:8002/dept/list

http://localhost:8003/dept/list

启动microservicecloud-consumer-dept-80 (client)

客户端通过Ribbo完成负载均衡并访问上一步的Dept微服务

http://localhost/consumer/dept/list

注意观察看到返回的数据库名字,各不相同,负载均衡实现



在实例中返回的json格式里面有调用的服务各自的数据库名字

测试结果

ribbon默认是采用的轮询的算法,所以当访问http://localhost/consumer/dept/list的时候,根据数据库的名字来判断调用的哪个服务,发现首先是随机的一个(这里假如是8002),再次访问发现是8003,再次访问发现是8001,以此类推。

总结:



Ribbon其实就是一个软负载均衡的客户端组件,

他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

Ribbon核心组件IRule

IRule:根据特定算法中从服务列表中选取一个要访问的服务

ribbon 默认支持的算法

RoundRobinRule

轮询

RandomRule

随机

AvailabilityFilteringRule

会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,

还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问

WeightedResponseTimeRule

根据平均响应时间计算所有服务的权重,响应时间越快服务权重越大被选中的概率越高。

刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,

会切换到WeightedResponseTimeRule

RetryRule

先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用的服务,如果尝试了多次之后还是失败,将会自动跳过该服务的访问继续轮询剩下的。比如8002出现问题会报error page,一开始一直是8001 8002 8003 的顺序,当多次发现8002 eoor的时候,会在8001 和8003 之间轮询,自动跳过8002

BestAvailableRule

会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务

ZoneAvoidanceRule

默认规则,复合判断server所在区域的性能和server的可用性选择服务器

代码实现:

将80(client端)的ConfigBean类进行重定义,加入如下配置

@Bean
public IRule myRule(){
//return new RoundRobinRule();原来默认的轮询算法
return new RandomRule();//换成官方的随机算法
}

Ribbon自定义

修改microservicecloud-consumer-dept-80

主启动类添加@RibbonClient

在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效,形如:

@RibbonClient (name= "MICROSERVICECLOUD-DEPT" ,configuration=MySelfRule. class )

说明:name是应用的名字即application,configuratioin是自定义的算法的类。

配置细节

官方文档明确给出了警告:

这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,

否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,也就是说

我们达不到特殊化定制的目的了。



意思就是,我们自定义算法的类不能够和主启动类在同一个包下或者相邻的子包下。

加入启动类在com.xxx.aaa下,我们自定义的类就不能在这里,可以是其他的包比如com.xxx.bbb

步骤

新建package com.atguigu.myrule

新建自定义Robbin规则类

package  com.atguigu.myrule; 

import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration; 

import  com.netflix.loadbalancer.IRule;
import  com.netflix.loadbalancer.RandomRule; 

@Configuration
public   class  MySelfRule
{
   @Bean
   public  IRule myRule()
  {
    return   new  RandomRule(); //Ribbon默认是轮询,我自定义为随机
  }
}

修改主启动类


package  com.atguigu.springcloud; 

import  org.springframework.boot.SpringApplication;
import  org.springframework.boot.autoconfigure.SpringBootApplication;
import  org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import  org.springframework.cloud.netflix.ribbon.RibbonClient; 

import  com.atguigu.myrule.MySelfRule; 

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="MICROSERVICECLOUD-DEPT",configuration=MySelfRule. class )
public   class  DeptConsumer80_App
{
   public   static   void  main(String[]  args )
  {
   SpringApplication. run (DeptConsumer80_App. class ,  args );
  }
}

测试

http://localhost/consumer/dept/list

看是否由原来的轮询变成了random

自定义规则深度解析

提出新需求:

问题:依旧轮询策略,但是加上新需求,每个服务器要求被调用5次。也即

以前是每台机器一次,现在是每台机器5次

解析源码:https://github.com/Netflix/ribbon/blob/master/ribbon-loadbalancer/src/main/java/com/netflix/loadbalancer/RandomRule.java

参考源码修改为我们需求要求的RandomRule_ZY.java(和上面的MySelfRule在同一个目录下)


package  com.atguigu.myrule; 

import  java.util.List;
import   java.util.Random ; 

import  com.netflix.client.config.IClientConfig;
import  com.netflix.loadbalancer.AbstractLoadBalancerRule;
import  com.netflix.loadbalancer.ILoadBalancer;
import  com.netflix.loadbalancer.Server; 

public   class  RandomRule_ZY  extends  AbstractLoadBalancerRule { 

   private   int  total = 0;    //总共被调用的次数,目前要求每台被调用5次
   private   int  currentIndex = 0;//当前提供服务的机器号 

     public  Server choose(ILoadBalancer  lb , Object  key ) {
         if  ( lb  ==  null ) {
             return   null ;
        }
        Server  server  =  null ; 

         while  ( server  ==  null ) {
             if  (Thread. interrupted ()) {
                 return   null ;
            }
            List<Server>  upList  =  lb .getReachableServers();
            List<Server>  allList  =  lb .getAllServers(); 

             int   serverCount  =  allList .size();
             if  ( serverCount  == 0) {
                 /*
                 * No servers. End regardless of pass, because subsequent passes
                 * only get more restrictive.
                 */
                 return   null ;
            } 

//             int  index = rand.nextInt(serverCount);
//            server = upList.get(index);
             if (total < 5)
            {
            server = upList.get(currentIndex);
            total++;
            } else  {
            total = 0;
            currentIndex++;
             if (currentIndex >= upList.size())
            {
              currentIndex = 0;
            } 

            } 

             if  ( server  ==  null ) {
                 /*
                 * The only time this should happen is if the server list were
                 * somehow trimmed. This is a transient condition. Retry after
                 * yielding.
                 */
                Thread. yield ();
                 continue ;
            } 

             if  ( server .isAlive()) {
                 return  ( server );
            } 

             // Shouldn't actually happen.. but must be transient or a bug.
             server  =  null ;
            Thread. yield ();
        } 

         return   server ; 

    } 

   @Override
   public  Server choose(Object  key ) {
    return  choose(getLoadBalancer(),  key );
  } 

   @Override
   public   void  initWithNiwsConfig(IClientConfig  clientConfig ) { 

  }
} 

说明:

这里主要修改了如下代码(核心算法)

            int  index = rand.nextInt(serverCount);
           server = upList.get(index);
            if (total < 5)
           {
           server = upList.get(currentIndex);
           total++;
           } else  {
           total = 0;
           currentIndex++;
            if (currentIndex >= upList.size())
           {
             currentIndex = 0;
           } 

           }

调用

MySelfRule.java

package  com.atguigu.myrule; 

import  org.springframework.context.annotation.Bean;
import  org.springframework.context.annotation.Configuration; 

import  com.netflix.loadbalancer.IRule;
import   com.netflix.loadbalancer.RandomRule ; 

@Configuration
public   class  MySelfRule
{
  @Bean
  public  IRule myRule()
 {
   //return new RandomRule();//Ribbon默认是轮询,我自定义为随机 

    return   new  RandomRule_ZY();//我自定义为每个机器被访问5次
 }
} 

目录结构

测试

http://localhost/consumer/dept/list

SpringCloud学习笔记(五):Ribbon负载均衡的更多相关文章

  1. spring cloud学习笔记二 ribbon负载均衡

    Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为.为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求.Ribb ...

  2. spring-cloud: eureka之:ribbon负载均衡自定义配置(二)

    spring-cloud: eureka之:ribbon负载均衡自定义配置(二) 有默认配置的话基本上就是轮询接口,现在我们改用自定义配置,同时支持:轮询,随机接口读取 准备工作: 1.eureka服 ...

  3. spring-cloud: eureka之:ribbon负载均衡配置(一)

    spring-cloud: eureka之:ribbon负载均衡配置(一) 比如我有: 一个eureka服务:8761 两个user用户服务: 7900/7901端口 一个movie服务:8010 1 ...

  4. SpringCloud与微服务Ⅵ --- Ribbon负载均衡

    一.Ribbon是什么 Sping Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具. 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户 ...

  5. SpringCloud学习笔记(2)——Ribbon

    参考SpringCloud官网第16.17章 16. Client Side Load Balancer: Ribbon Ribbon是一个客户端的负载均衡器,它提供对大量的HTTP和TCP客户端的访 ...

  6. SpringCloud微服务之Ribbon负载均衡(一)

    什么是微服务?什么是SpringCloud? 微服务是一种架构的模式,它提倡将一个应用程序划分成很多个微小的服务,服务与服务之间相互协调.相互配合.每个服务运行都是一个独立的进程,服务与服务之间采用轻 ...

  7. Spring Cloud微服务开发笔记5——Ribbon负载均衡策略规则定制

    上一篇文章单独介绍了Ribbon框架的使用,及其如何实现客户端对服务访问的负载均衡,但只是单独从Ribbon框架实现,没有涉及spring cloud.本文着力介绍Ribbon的负载均衡机制,下一篇文 ...

  8. Web负载均衡学习笔记之实现负载均衡的几种实现方式

    0x00 概要 负载均衡(Load Balance)是集群技术(Cluster)的一种应用.负载均衡可以将工作任务分摊到多个处理单元,从而提高并发处理能力.目前最常见的负载均衡应用是Web负载均衡.根 ...

  9. SpringCloud学习笔记(2):使用Ribbon负载均衡

    简介 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡工具,在注册中心对Ribbon客户端进行注册后,Ribbon可以基于某种负载均衡算法,如轮询(默认 ...

随机推荐

  1. USACO 2009 Open Cow Line /// 队列 oj26220

    题目大意: 输入n,n次操作 操作A:在L(左边)或R(右边)插入一个递增的数 操作D:在L(左边)或R(右边)删除m个数 Sample Input 10A LA LA RA LD R 2A RA R ...

  2. js 实现多选

    效果: html: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> < ...

  3. flutter 底部bottomNavigationBar凸起效果

    概要 最近在做flutter 的时候,之前看到想实现 底部导航栏中间按钮 凸起效果, 最近想做又突然找不到方案了,因此记录下这里的实现方式. 预览效果 代码 主要使用 BottomAppBar 组建, ...

  4. 利用msbuild白名单执行shellcode

    x64:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\msbuild.exe x32:C:\Windows\Microsoft.NET\Framewo ...

  5. lds 文件说明

    主要符号说明 OUTPUT_FORMAT(bfdname) 指定输出可执行文件格式. OUTPUT_ARCH(bfdname) 指定输出可执行文件所运行 CPU 平台 ENTRY(symbol) 指定 ...

  6. 数组那些事(slice,splice,forEach,map,filter等等)

    周五,再过会要下班了,刚才把<javascript高级程序设计>数组这块又看了下,加深下记忆.今天来继续练练笔,嘿嘿!(写下自己印象不深的东西) 一.数组的定义(数组定义分为两种) 方法一 ...

  7. vs数据库连接问题

    在swagger上测试时报错:数据库连接不上 原因:在项目中修改过connectionstring,但是每次编译时本地文件中并没有更新 修改: 修改配置文件属性:不复制 —> 始终复制

  8. TStringList常用操作

    TStringList常用操作 //TStringList 常用方法与属性: var List: TStringList; i: Integer; begin List := TStringList. ...

  9. 求教各路大神,Fillder的证书一直无法在手机上打开,请教怎么解决

    我跟足大神们的设置,软件是Fiddler4,手机是ios12.3.1. FD上该打勾的打勾了,该装证书的也装了,有帖子说重装证书和软件我也都试过,电脑也下了NET Framework 4.7_4.7. ...

  10. neo4j安装APOC插件

    1.APOC下载地址:https://github.com/neo4j-contrib/neo4j-apoc-procedures/releases/3.4.0.1 只要下载.jar这一个压缩文件就好 ...