Spring CloudRibbon是基于NetflixRibbon实现的一套客户端 负载均衡工具。 简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将Netflix的中间服务连接在一起,Ribbon客户端组件提供一系列完善的配置添加连接超时,重试等,简单的说,就是在配置文件中列出Load Balance(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我也很容易使用Ribbon实现自定义的负载均衡算法。
负载均衡算法:(HA:高可用) LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用,负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。 常见的负载均衡有软件Nginx、LVS、硬件F5等。 相应的在中间件,例如:dubbo和springcloud均给我们提供了负载均衡,springcloud的负载均衡算法可以自定义。 LB(负载均衡)可以分为集中式LB和进程内LB 集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发到服务提供方。 进程内LB:将LB逻辑集成到消费方,消费方从服务中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器,==Ribbon就属于进程内LB,==它只是一个类库,集成与消费方进程,消费方通过它来后去到服务提供方的地址。
在Config.java文件里的 public RestTemplate geRestTemplate()方法上添加注解==@LoadBalanced== // 开启负载均衡 以上的配置目的在于:完成真正的通过微服务名字从Eureka上找到并访问。 3. 在服务消费者主程序application上添加注解==@EnableEurekaClient==//开启Eureka的客户端功能 4. 修改服务消费者的客户端访问类,主要是修改获取服务的前缀路径(TEST_URL_PREFIX),改为了集群配置下的访问前缀,修改后的代码如下:
package com.lyj.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.lyj.springcloud.entities.Dept; @RestController public class DeptController_consumer { /** * RestTemplate提供了多种便捷访问远程Http服务的方法, 是一种简单便捷的访问restful服务模板类, 是spring提供的用于访问Rest服务的客户端模板工具集 用法: * 使用restTemplate访问restful接口非常的简单粗暴无脑。 (url,requestMap,ResponseBean.class)这三个参数分别代表 REST请求地址、请求参数、HTTP响应转换被转换成的对象类型。 * */ @Autowired private RestTemplate restTemplate; //下面的单机版的 // private static final String TEST_URL_PREFIX = "http://localhost:8001"; //下面这个是集群配置的。 private static final String TEST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT"; @RequestMapping(value = "/consumer/dept/add") public boolean add(Dept dept) { return restTemplate.postForObject(TEST_URL_PREFIX + "/dept/add", dept, boolean.class); // return restTemplate.getForObject(TEST_URL_PREFIX + "/dept/add", boolean.class, dept); } @RequestMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return restTemplate.getForObject(TEST_URL_PREFIX + "/dept/get/" + id, Dept.class); } @SuppressWarnings("unchecked") @RequestMapping(value = "/consumer/dept/list") public List < Dept > list(Dept dept) { return restTemplate.getForObject(TEST_URL_PREFIX + "/dept/list", List.class); } // 测试@EnableDiscoveryClient,消费者可以调用服务发现,消费者的application主程序不用添加@EnableDiscoveryClient @RequestMapping(value = "/consumer/dept/discovery") public Object discovery() { return restTemplate.getForObject(TEST_URL_PREFIX + "/dept/discovery", Object.class); } } 一次启动7001、7002、7003、8001、80工程进行检测。测试数据: 测试结果:总结:Ribbon和Eureka整合后Consumer可以直接调用服务而不用再关心地址和端口号
架构说明图: Ribbon在工作时分成两步: 第一步先选择EurekaServer,它优先选择在同一个区域内负载较少的server。 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略:比如轮询、随机和根据响应时间加权。 新建工程8002、8003服务提供者工程。并且把8001工程的pom.xml文件和/microservicecloud-provider-dept-8001/src/main/java路径下的com.lyj.springcloud中的所有文件和文件夹copy到8002、8003工程的相应位置,然后修改主程序名字(也可以不用修改,建议修改),下面是8002修改的模板:
package com.lyj.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @EnableDiscoveryClient // 开启服务发现功能,控制器里的服务发现方法能正常运行。 @EnableEurekaClient // 本服务启动后会自动将服务注册进Eureka服务中心中 @SpringBootApplication public class DeptProvider8002_App { /** * 使用服务发现功能步骤: 1、先要启动EurekaServer 2、再启动DeptProvider8001_App主启动类,需要 稍等一会 3、访问http://localhost:8001/dept/discovery * * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub SpringApplication.run(DeptProvider8002_App.class, args); } }一个服务多个实例的结果即这些服务提供者的数据库是各不相同的,但功能是一样的,所以建立cloudDB02数据库和cloudDB03数据库(表示一样的),下面 SQL语句模板:
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服务提供者工程的application.yml配置文件 只需要修改如下的配置:
#修改服务器端口号 server: #指定服务器端口号8001 port: 8002 #数据库连接地址 url: jdbc:mysql://localhost:3306/cloudDB02 #由于是一个服务多个实例,对应着多个独立的数据库,所以application.name名一定要一样,不能改 spring: application: # 当前应用(服务)起的名字,;配置ribbon,实现一个服务多个实例时,所有服务提供者的下面这个名字不能修改,要一样。 name: microservicecloud-dept # 修改实例的名 #自定义实例的statusIP地址名字(主机名字:服务名称修改,不采用默认的名字) instance: instance-id: microservicecloud-dept8002 prefer-ip-address: true #访问路径可以显示IP地址修改模板:
server: #指定服务器端口号8001 port: 8003 mybatis: #mybatis配置文件所在路径 config-location: classpath:mybatis/mybatis.cfg.xml #所有Entity别名类所在包 type-aliases-package: com.lyj.springcloud.entities #mapper映射文件所在路径 mapper-locations: - classpath:mybatis/mapper/**/*.xml spring: application: # 当前应用(服务)起的名字 name: microservicecloud-dept datasource: #当前数据源操作类型 type: com.alibaba.druid.pool.DruidDataSource #mysql驱动包 driver-class-name: com.mysql.jdbc.Driver #数据库名称 url: jdbc:mysql://localhost:3306/cloudDB03 username: mysql password: 15827318595 dbcp2: min-idle: 5 #数据库连接池的最小维持连接数 initial-size: 5 #初始化连接数5个 max-total: 5 #设置最大连接数 max-wait-millis: 200 #s等待连接获取的最大超时时间 #配置Eureka eureka: #客户端注册进Eureka服务列表内 client: service-url: # defaultZone: http://localhost:7001/eureka #(单机版服务器) defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 集群配置(多台服务器共同使用) # defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # eureka-server-connect-timeout-seconds: 60000 # eureka-connection-idle-timeout-seconds: 60000 # eureka-server-read-timeout-seconds: 60000 # eureka-service-url-poll-interval-seconds: 60000 #自定义实例的statusIP地址名字(主机名字:服务名称修改,不采用默认的名字) instance: instance-id: microservicecloud-dept8003 prefer-ip-address: true #访问路径可以显示IP地址 #配置Eurekaweb界面访问各个服务的的详细信息的info界面 info: app.name: lyj-microservicecloud company.name: www.lyj.com build.artifactId: $project.artifactId$ build.version: $project.version$ #debug: true测试步骤: 依次启动服务器7001、7002、7003工程,再依次启动服务提供者8001、8002、8003工程、然后测试以下链接数据: 测试结果(8001工程): 如果三个都通过了,则启动80工程(服务消费者工程),访问(服务消费者链接):http://localhost/consumer/dept/list 默认的负载均衡算法是轮询算法,也就是一个服务多个实例按顺序轮着交替给服务消费者提供服务。 总结:Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结结合使用,和eureka结合只是其中的一个实例。 Ribbon的核心组件IRule: ribbon自带有以上7种算法:
RoundRobinRule:轮询算法 RandomRule:随机算法。 AvailabilityFilteringRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。 WeightResponseTimeRule:根据平均响应时间计算所有服务的权重,响应时间越快权重越大被选中的概率越高,刚启动时如果统计信息不足,则使用RoundRobinRule策略,等统计信息足够,会切换到WeightResponseTimeRule。 RetryRule:先按照RoundRobinRule的策略获取服务,如果获取服务失败则在指定时间内会进行重试,获取可用服务。 BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。 ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可选性选择服务器。
使用Ribbon定义的7种方法用法:
@Configuration public class ConfigBean { /** * @LoadBalanced//开启负载均衡 springcloud ribbon是基于Netflix Ribbon实现的一套客户端, 负载均衡的工具 * * @return */ @Bean @LoadBalanced // 开启负载均衡 public RestTemplate geRestTemplate() { return new RestTemplate(); } /** * Ribbon有七种算法,如果要切换算法, 只需要在这边new一个算法就行了 * * @return */ @Bean public IRule myRule() { /** * 达到目的,用我们重新选择的随机算法替代默认的轮询算法。并添加到容器里 */ // return new RandomRule();// 随机算法 // return new RoundRobinRule();//轮询算法 /** * 先按照RoundRobinRule的策略获取, 如果获取服务失败则在指定时间内会再次进行尝试,获取可用服务, 如果多次访问一个宕机的服务提供者的实例,它会在多次尝试连接以后 * 如果还是不成功,会自动放弃后面负载均衡的时候访问这个这个服务实例。 */ return new RetryRule(); } }Ribbon自定义算法的用法: 具体的我都写在代码里了: 主启动类配置:
package com.lyj.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.lyj.myrule.MySelfRule; @SpringBootApplication @EnableEurekaClient // 开启Eureka的客户端功能 /** * @RibbonClient(name="" configuration="") 在启动该微服务的时候就能去加载我们自定义的Ribbon配置类,从而使配置类生效 name指定自定义的Ribbon算法给哪个服务使用,值为服务的名字(大写) * configuration指定自定义算法类名.class,这里有个细节,就是我们自定义的Ribbon算法, 不能被ComponentScan()所扫描,自定义算法类也就是不能在主启动类所在包及其子包下, * 因为主启动类上标注的@SpringBootApplication里面含有@ComponentScan()这个注解 这个注解会把主启动类所在包及其子包下所有定义的标注有@Configuration注解的配置类都会被扫进来, * 所以不能把Ribbon算法自定义类放主启动类包及其子包下。 * * * */ @RibbonClient(name = "MICROSERVICECLOUD-DEPT", configuration = MySelfRule.class) public class DeptConsumer80_App { public static void main(String[] args) { // TODO Auto-generated method stub SpringApplication.run(DeptConsumer80_App.class, args); } }架构图: 自定义算法类:
package com.lyj.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() { // ribbon默认的是轮询算法,这里自定义为随机算法 return new RandomRule(); } }自定义规则深度解析: 自定义负载均衡算法实现:
还是在主启动类上引入@RibbonClient(name = “MICROSERVICECLOUD-DEPT”, configuration = MySelfRule.class)注解,在MySelfRule.class文件里方法里 return new +自定义算法类;定义自定义算法类及自定义算法方法的实现,自定义算法类都是要继承AbstractLoadBalancerRule,然后实现里面的方法,在里面修改代码,实现自己的算法方法。 自定义算法类: . package com.lyj.myrule; import java.util.List; import java.util.concurrent.ThreadLocalRandom; 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 { /** * total=0 //当total==5以后,我就执指针才能往下走, index=0 //当前对外提供服务的服务器地址控制路径, 当total==5时要重新置为零,但是已经达到过一个5次,我都index=1 * 分析:我们5次,但是微服务只有8001、8002、8003三台 OK?? * * @param lb * @param key * @return */ 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 = chooseRandomInt(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; } protected int chooseRandomInt(int serverCount) { return ThreadLocalRandom.current().nextInt(serverCount); } @Override public Server choose(Object key) { return choose(getLoadBalancer(), key); } // @Override // public Server choose(Object key) { // // TODO Auto-generated method stub // return null; // } @Override public void initWithNiwsConfig(IClientConfig clientConfig) { // TODO Auto-generated method stub } }测试: 依次启动7001、7002、7003、8001、8002、8003、80工程即可,访问:http://localhost/consumer/dept/list 结果:(这里自定义的方法是访问每个服务实例的都是访问5次然后按轮询算法到下一个访问服务实例) 负载均衡算法架构图: ribbon自定义负载均衡算法可以参考GitHub网站:参考网站
Feign是一个声明式WebService客户端,使用Feign能让编写Web Service客户端更贱简单,它的使用方法是定义一个接口,然后在上面添加注解,同时支持JAX-RS标准的注解,Feign也支持可拔插式的编码器和解码器,SpringCloud对Feign进行了封装,使其支持了SpringMVC标准注解和HttpMessageconverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。 官网解释:官方解释
其实就是:Feign是一个声明式的web服务客户端,使的编写Web服务端变得非常容易,只需要创建一个接口,然后在上面添加注解即可。 参考官网: 参考链接
Feign能干什么? Feign旨在使编写javaHttp客户端变得更加容易。 前面在使用Ribbon+RestTemplate时,利用RestTemplate请求的封装处理,形成了一套模板haul的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常对每个微服务自行封装一些客户端类来包装这些依赖服务的调用,所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用SpringCloud Ribbon时,自动封装调用客户端的开发量。
首先看下流程图:
创建Feign的模块,将dept-80工程的src/main/java下的包文件全部copy到Feign工程里的相应目录下,然后把主程序上Ribbon的注解删除,Feign工程里pom文件要引入以下依赖: <!-- 引入feign依赖 ===start --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <!-- 引入feign依赖 ===end --> 在api公共模块工程的pom文件也引入以上的依赖。在api工程下创建service接口包,再新建DeptClientService接口并在接口上添加注解@FeignClient注解,即面向接口编程。具体代码: package com.lyj.springcloud.service; import java.util.List; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.lyj.springcloud.entities.Dept; /** * @FeignClient(value = "MICROSERVICECLOUD-DEPT") * 指该接口中的方法用在哪个服务上,并且针对接口编程, * 其实REST与Feign都是Client。 * * @author 96971 * */ @FeignClient(value = "MICROSERVICECLOUD-DEPT") public interface DeptClientService { @RequestMapping(value = "/dept/add", method = RequestMethod.POST) public boolean add(@RequestBody Dept dept); @RequestMapping(value = "/dept/get/{id}", method = RequestMethod.GET) public Dept get(@PathVariable("id") Long id); @RequestMapping(value = "/dept/list", method = RequestMethod.GET) public List < Dept > list(); } 对api工程 执行mvn clean和mvn install,重新编译,保证项目是最新的。在Feign工里修改Controller文件,注入DeptClientService,即现在的Controller面向的是DeptClientService接口编程 代码如下: package com.lyj.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 com.lyj.springcloud.entities.Dept; import com.lyj.springcloud.service.DeptClientService; @RestController public class DeptController_consumer { /** * 注入公共模块api工程里的DeptClientService接口; */ @Autowired private DeptClientService service; @RequestMapping(value = "/consumer/dept/add") public Object add(Dept dept) { return this.service.add(dept); } @RequestMapping(value = "/consumer/dept/get/{id}") public Dept get(@PathVariable("id") Long id) { return this.service.get(id); } @RequestMapping(value = "/consumer/dept/list") public List < Dept > list(Dept dept) { return this.service.list(); } } 在Feign工程的主程序类上天机以下注解(即@EnableFeignClients(basePackages = { “com.lyj.springcloud” }) @ComponentScan(“com.lyj.springcloud”)): package com.lyj.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @SpringBootApplication @EnableEurekaClient // 开启Eureka的客户端功能 /** * @EnableFeignClients(basePackages= {"com.lyj.springcloud"}) 开启Feign功能, 调用api工程里的带有@FeignClient注解的service接口 * 这里的basePackages指定添加@FeignClient所在工程包。即api工程包名 * * @author 96971 @ComponentScan("com.lyj.springcloud") 这个是扫描本工程下的工程包名。 */ @EnableFeignClients(basePackages = { "com.lyj.springcloud" }) @ComponentScan("com.lyj.springcloud") public class DeptConsumer80__feign_App { public static void main(String[] args) { // TODO Auto-generated method stub SpringApplication.run(DeptConsumer80__feign_App.class, args); } }测试: Feign集成了Ribbon 利用Ribbon维护了Microservicecloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,==通过肺功能只需要定义服务绑定接口且以声明式的方法,==优雅而简单的实现了服务调用。
总结:Feign通过接口的方法调用Rest服务(之前是Ribbon+RestTemplage),该请求发送给Eureka服务器(http://MICROSERVICECLOUD-DEPT/dept/list),通过Feign直到找到服务接口,由于在进行服务调用的时候融合了Ribbon的技术,所以也支持负载均衡的作用。默认的是轮询算法。 测试结果: