5、Eureka服务注册与发现
5.1、什么是Eureka
- Eureka:怎么读?
- Netflix在设计Eureka时,遵循的就是AP原则
- Eureka是Netflix的一个子模块,也就是核心模块之一。Eureka是一个基于REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper;
5.2、原理讲解
- Eureka的基本结构
- SpringCloud封装了NetFlix公司开发的Eureka模块来实现服务注册和发现(对比Zookeeper)
- Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心
- 而系统中的其他微服务。使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑操作;
- 和Dubbo架构对比 Eureka包含两个组件:Eureka Server和Eureka Client。
- Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除掉(默认周期为90秒)
- 三大角色
- Eureka Server:提供服务的注册与发现。zookeeper
- Service Provider:将自身服务注册到Eureka中,从而使消费方能够找到。
- Service Consumer:服务消费方从Eureka中获取注册服务列表,从而找到消费服务。
自我保护机制:好死不如赖活着
- 当EurekaServer节点在短时间内丢失过多客户端时(可能发生了网络分区故障),那么这个节点就会进入自我保护模式。一旦进入该模式,EurekaServer就会保护服务注册表中的信息,不再删除服务注册表中的数据(也就是不会注销任何微服务)。当网络故障恢复后,EurekaServer节点会自动退出自我保护模式。
5.3、Eureka实现代码
- 新建子模块springcloud-eureka-7001,导入依赖
<!--EUREKA依赖--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善监控信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 新建配置文件application.yml
server: port: 7001 #Eureka配置 eureka: instance: hostname: localhost #Eureka服务端的实例名称 client: register-with-eureka: false #表示是否向eureka注册中心注册自己 fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心 service-url: #监控页面~ defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 新建启动类EurekaServer_7001.java,设置EurakeServer注解
//启动之后,访问http://localhost:7001/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服务端的启动类,可以接受别人注册进来~ public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class,args); } }
- 在父依赖中添加build代码,解决info无效的问题
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>build-info</goal> </goals> </execution> </executions> </plugin> </plugins> </build>
- 在服务者模块springcloud-provider-dept-8001中导入Eureka依赖
<!--EUREKA依赖--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善监控信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
- 新建配置文件application.yml
server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服务注册到哪里 eureka: client: service-url: defaultZone: http://localhost:7001/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的默认描述信息! hostname: localhost #修改主机名称 #解决url过滤问题 management: endpoints: web: exposure: include: "*" #info配置,无效,创建单独的build-info.properties解决,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
- 新建info配置文件META-INF/build-info.properties
build.app_name=kuangshen-springcloud build.company_name=blog.kuangstudy.com
- 在服务者的controller文件DeptController.java中配置新的方法获取注册的微服务的具体信息
//提供Restful服务! @RestController public class DeptController { @Autowired private DeptService deptService; //获取一些配置的信息,得到具体的微服务! @Autowired private DiscoveryClient client; @PostMapping("/dept/add") public boolean addDept(Dept dept){ return deptService.addDept(dept); } @GetMapping("/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return deptService.queryDeptById(id); } @GetMapping("/dept/list") public List<Dept> queryAll(){ return deptService.queryAll(); } //注册进来的微服务,获取一些消息 @GetMapping("/dept/discovery") public Object Discovery(){ //获取微服务列表的清单 List<String> services = client.getServices(); System.out.println("discovery=>services:"+services); //得到一个具体的微服务信息,通过具体的微服务id,applicationName List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT"); for (ServiceInstance instance : instances) { System.out.println( instance.getHost()+"\t"+ instance.getPort()+"\t"+ instance.getUri()+"\t"+ instance.getServiceId() ); } return this.client; } }
- 在服务者的启动类中添加EurekaClient及DiscoveryClient注解
//启动类 @SpringBootApplication @EnableEurekaClient //在服务启动后自动注册到Eureka中! @EnableDiscoveryClient //服务发现~ public class DeptProvider_8001 { public static void main(String[] args) { SpringApplication.run(DeptProvider_8001.class,args); } }
- 先运行springcloud-eureka-7001,再运行springcloud-provider-dept-8001,主界面如下,可点击查看服务者相关信息
- 访问http://localhost:7001/dept/discovery可查看微服务的详细信息,终端会输出详细信息
5.3、Eureka集群环境配置
结构示意图:
代码演示:
- 在C盘下修改接口配置,进入文件夹C:\Windows\System32\drivers\etc\hosts,在文件末尾添加不同url映射到localhost接口
- 新建两个子模块springcloud-eureka-7002,springcloud-eureka-7003,结构与springcloud-eureka-7001类似
启动类
//启动之后,访问http://localhost:7001/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服务端的启动类,可以接受别人注册进来~ public class EurekaServer_7001 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7001.class,args); } } ======================================= //启动之后,访问http://localhost:7002/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服务端的启动类,可以接受别人注册进来~ public class EurekaServer_7002 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7002.class,args); } } ======================================= //启动之后,访问http://localhost:7003/ @SpringBootApplication @EnableEurekaServer //EnableEurekaServer 服务端的启动类,可以接受别人注册进来~ public class EurekaServer_7003 { public static void main(String[] args) { SpringApplication.run(EurekaServer_7003.class,args); } }
配置文件
server: port: 7001 #Eureka配置 eureka: instance: hostname: eureka7001.com #Eureka服务端的实例名称 client: register-with-eureka: false #表示是否向eureka注册中心注册自己 fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心 service-url: #监控页面~ defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ ####################################### server: port: 7002 #Eureka配置 eureka: instance: hostname: eureka7002.com #Eureka服务端的实例名称 client: register-with-eureka: false #表示是否向eureka注册中心注册自己 fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心 service-url: #监控页面~ defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7003.com:7003/eureka/ ####################################### server: port: 7003 #Eureka配置 eureka: instance: hostname: eureka7003.com #Eureka服务端的实例名称 client: register-with-eureka: false #表示是否向eureka注册中心注册自己 fetch-registry: false #fetch-registry如果为false,则表示自己为注册中心 service-url: #监控页面~ defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
依赖与springcloud-eureka-7001相同
<!--导包--> <dependencies> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> <version>1.4.7.RELEASE</version> </dependency> </dependencies>
- 修改服务提供者的配置文件,使其向三个Eureka发布
server: port: 8001 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服务注册到哪里 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8001 #修改eureka上的默认描述信息! hostname: localhost #修改主机名称 #解决url过滤问题 management: endpoints: web: exposure: include: "*" #info配置,无效,创建单独的build-info.properties解决,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
- 运行三个Eureka及一个服务提供者,其中两个Eureka界面如下,可见每个Eureka都可见其他两个Eureka
5.4、对比Zookeeper
回顾CAP原则
RDBMS(Mysql、Oracle、sqlServer)===> ACID
NoSQL(redis、mongdb)===> CAP
ACID是什么?
- A(Atomicity)原子性
- C(Consistency)一致性
- I(Isolation)隔离性
- D(Durability)持久性
CAP是什么?
- C(Consistency)强一致性
- A(Availability)可用性
- P(Partition tolerance)分区容错性
CAP的三进二:CA、AP、CP(无法保证三个都满足)
CAP理论的核心
- 一个分布式系统不可能同时很好的满足一致性、可用性和分区容错性这三个需求
- 根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类:
- CA:单点集群,满足一致性,可用的系统,通常可扩展性较差(P分区容错性差)
- CP:满足一致性,分区容错性的系统,通常性能不是特别高(A可用性差)
- AP:满足可用性,分区容错性的系统,通常可能对一致性要求低一些(C一致性差)
作为服务注册中心,Eureka比Zookeeper好在哪里?
著名的CAP理论指出,一个分布式系统不可能同时满足C(一致性)、A(可用性)、P(容错性)。
由于分区容错性P在分布式系统中是必须要保证的,因此我们只能在A和C之间进行权衡。
- Zookeeper保证的是CP;
- Eureka保证的是AP;
Zookeeper保证的是CP
当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接受服务直接down掉不可用。也就是说,服务注册功能对可用性的要求是高于一致性的。但是zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30~120s,且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zk集群失去master节点是较大概率会发生的事件,虽然服务最终能够恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。
Eureka保证的是AP
Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保证注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
- Eureka不再从注册列表中移除因为长时间没收到心跳而应该过期的服务
- Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上(即保证当前节点依然可用)
- 当网络稳定时,当前实例新的注册信息会被同步到其他节点中
因此,Eureka可以很好地应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪。
6、Ribbon
spring-cloud-starter-netflix-eureka-client中已经内置了Ribbon,再导入Ribbon依赖会冲突。
6.1、Ribbon简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单地说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单地说,就是在配置文件中列出Load Balancer(简称LB)后面所有机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法
GitHub文档地址:https://github.com/Netflix/ribbon
注意:Ribbon现在已经进入了维护模式
Ribbon的职能:LB负载均衡,简单地说就是将用户的请求平均分摊到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡软件有Nginx,LVS,硬件F5等。
Ribbon本地负载均衡客户端和Nginx服务端负载均衡区别:
Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求。即负载均衡是由服务端实现的。
Ribbon是本地(客户端)负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务提供方
进程内LB
将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择一个合适的服务器。
Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon其实就是一个软件实现负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和Eureka结合只是其中的一个实例
示意图:
6.2、Ribbon初体验
- 在消费者模块springcloud-consumer-dept-80中导入Eureka依赖,不要再导入Ribbon依赖,否则会导致冲突
<!--Eureka--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency>
- 在该模块中增加Eureka的配置
server: port: 80 #Eureka配置 eureka: client: register-with-eureka: false #不向Eureka注册自己 service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 在配置类中,增加@LoadBalanced注解,以使用配置负载均衡实现RestTemplate
@Configuration public class ConfigBean { //@Configuration -- spring applicationContext.xml //配置负载均衡实现RestTemplate @Bean @LoadBalanced //Ribbon public RestTemplate getRestTemplate(){ return new RestTemplate(); } }
- 在controller层中DeptConsumerController.java中将访问地址改为服务名
@RestController public class DeptConsumerController { //理解:消费者,不应该有service层~ //RestFul RestTemplate... 供我们直接调用就可以了!注册到Spring中 //(url, 实体: Map, Class<T> responseType) @Autowired private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的restful服务模板~ //Ribbon 我们这里的地址,应该是一个变量,通过服务名来访问 //private static final String REST_URL_PREFIX = "http://localhost:8001"; private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT"; @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class); } @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class); } }
- 运行三个Eureka集群,服务提供者与消费者进行测试
6.3、使用Ribbon实现负载均衡
- 新建两个数据库,db02,db03,与db01相同
CREATE DATABASE /*!32312 IF NOT EXISTS*/`db02` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db02`; /*Table structure for table `dept` */ DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `deptno` bigint(20) NOT NULL AUTO_INCREMENT, `dname` varchar(60) DEFAULT NULL, `db_source` varchar(60) DEFAULT NULL, PRIMARY KEY (`deptno`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部门表'; /*Data for the table `dept` */ insert into `dept`(`deptno`,`dname`,`db_source`) values (1,'开发部','db02'),(2,'人事部','db02'),(3,'财务部','db02'),(4,'市场部','db02'),(5,'运维部','db02'); ####################################### CREATE DATABASE /*!32312 IF NOT EXISTS*/`db03` /*!40100 DEFAULT CHARACTER SET utf8 */; USE `db03`; /*Table structure for table `dept` */ DROP TABLE IF EXISTS `dept`; CREATE TABLE `dept` ( `deptno` bigint(20) NOT NULL AUTO_INCREMENT, `dname` varchar(60) DEFAULT NULL, `db_source` varchar(60) DEFAULT NULL, PRIMARY KEY (`deptno`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='部门表'; /*Data for the table `dept` */ insert into `dept`(`deptno`,`dname`,`db_source`) values (1,'开发部','db03'),(2,'人事部','db03'),(3,'财务部','db03'),(4,'市场部','db03'),(5,'运维部','db03');
- 新增两个服务提供者模块,springcloud-eureka-8002,springcloud-eureka-8003,与springcloud-eureka-8001相同,导入相同的依赖,并修改配置文件为对应的数据库及接口
pom.xml
<dependencies> <!--EUREKA依赖--> <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.4.7.RELEASE</version> </dependency> <!--actuator完善监控信息--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <!--我们需要拿到实体类,所以要配置api module--> <dependency> <groupId>com.kuang</groupId> <artifactId>springcloud-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--test--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--jetty--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jetty</artifactId> </dependency> <!--热部署工具--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> </dependency> </dependencies>
application.yml
server: port: 8002 #mybatis配置 mybatis: type-aliases-package: com.kuang.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml #spring的配置 spring: application: name: springcloud-provider-dept #三个服务名称一致 datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/db02?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT username: root password: 123456 #Eureka的配置,服务注册到哪里 eureka: client: service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ instance: instance-id: springcloud-provider-dept-8002 #修改eureka上的默认描述信息! hostname: localhost #修改主机名称 #解决url过滤问题 management: endpoints: web: exposure: include: "*" #info配置,无效,创建单独的build-info.properties解决,注意格式 info: app-name: kuangshen-springcloud company-name: blog.kuangstudy.com
DeptMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.kuang.springcloud.dao.DeptDao"> <insert id="addDept" parameterType="Dept"> insert into db02.dept (dname, db_source) values (#{dname},DATABASE()); </insert> <select id="queryDeptById" resultType="Dept" parameterType="Long"> select * from db02.dept where deptno = #{deptno}; </select> <select id="queryAll" resultType="Dept"> select * from db02.dept; </select> </mapper>
其余文件与springcloud-provider-dept-8001模块相同,名字不同
- 运行三个eureka集群及三个服务提供者模块及一个消费者模块,访问eureka7001主界面,发现有两个备用eureka页面及一个服务,服务后有三个服务提供者可供选择
- 运行服务消费者,多次运行后,发现服务消费者按轮询算法(默认在活跃的服务提供者中选择)依次访问三个服务提供者(这里使用三个不同的数据库进行模拟以便观察)
- 我们还可以使用不同的算法访问服务提供者,秩序在configuration类中添加@Bean重写相应的方法并返回即可;也可以在启动类的上一级目录中创建编写自定义的类,并在启动类中添加@RibbonClient注解,并在注解后指定对应的自定义类即可,这里不再演示。
7、Feign负载均衡
7.1、简介
feign是声明式的web service客户端,它让微服务之间的调用变得更简单了,类似controller调用service。SpringCloud继承了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端。
只需要创建一个接口,然后添加注解即可!
feign,主要是社区,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法
- 微服务名字 【ribbon】
- 接口和注解 【feign】
Feign能干什么?
- Feign旨在使编写Java Http客户端变得更容易
- 前面在使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(类似于以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可。)即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
- 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而且简单的实现了服务调用。
7.2、代码实现
- 在实体类模块导入feign依赖
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency>
- 在实体类模块springcloud-api新建一个service层接口DeptClientService,添加FeignClient注解,其值为服务提供者
@Component @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT") public interface DeptClientService { @GetMapping("/dept/get/{id}") public Dept queryById(@PathVariable("id") Long id); @GetMapping("/dept/list") public List<Dept> queryAll(); @PostMapping("/dept/add") public boolean addDept(Dept dept); }
- 新建feign消费者模块springcloud-consumer-dept-feign,与消费者模块springcloud-consumer-dept-80模块类似,仅改动controller层及启动类。
导入Feign依赖
<!--feign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> <version>1.4.7.RELEASE</version> </dependency>
创建controller文件DeptConsumerController.java
@RestController public class DeptConsumerController { @Autowired private DeptClientService deptClientService; @RequestMapping("/consumer/dept/add") public boolean add(Dept dept){ return this.deptClientService.addDept(dept); } @RequestMapping("/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id){ return this.deptClientService.queryById(id); } @RequestMapping("/consumer/dept/list") public List<Dept> list(){ return this.deptClientService.queryAll(); } }
启动类FeignDeptConsumer_80.java
@SpringBootApplication @EnableEurekaClient @EnableFeignClients(basePackages = {"com.kuang.springcloud"}) public class FeignDeptConsumer_80 { public static void main(String[] args) { SpringApplication.run(FeignDeptConsumer_80.class,args); } }
- 启动eureka集群,服务提供者及feign消费者进行测试,同样可以实现查询