通过Springcloud的Eureka或者Consul组件我们可以完成服务发现、服务注册等功能;但我们的微服务注册到注册中心后,其他的服务要如何调用注册中心里的服务呢?如果是平常的话,我们会使用
RestTeamplate
来远程调用服务,但如果我们要调用的服务搭建了集群,那我们要如何通过RestTeamplate
实现负载均衡调用呢?这时我们就可以使用Springcloud的 服务调用 来解决以上的问题。
SpringCloud提供了两个服务调用的组件,分别是
Ribbon
和OpenFeign
,但前者目前已经进入了维护阶段(不再更新),所以在此就做一个简单的了解即可,我们主要介绍OpenFeign
的使用。
Ribbon
简介
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
那其实说到底,Ribbon就等于 负载均衡+RestTeamplate调用
。
Ribbon使用
编写一个配置类,返回 RestTeamplate
对象,给该对象添加 @LoadBalanced
注解
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //赋予RestTemplate对象负载均衡的能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
当我们编写完配置类后,通过 Autowired
注解注入该对象即可使用;此时,我们再通过 RestTeamplate
调用别的服务时,会自动负载均衡调用。
Controller核心代码
private static final String PAYMENT_URL = "http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/consumer/payment/save")
public CommonResult<Payment> save(Payment payment) {
return restTemplate.postForObject(PAYMENT_URL + "/payment", payment, CommonResult.class);
}
IRule接口
简介
IRule接口定义了Ribbon负载均衡的各种策略,根据特定算法从中微服务中挑选出一个要访问的服务实例。
- com.netflix.loadbalancer.RoundRobinRule:轮询算法
- com.netflix.loadbalancer.RandomRule:随机算法
- com.netflix.loadbalancer.RetryRule:重试算法
- WeightedResponseTimeRule:对RoundRobinRule的扩展,响应速度越快的实例选择权重越大,越容易被选择
- BestAvailableRule:会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
- AvailabilityFilteringRule:先过滤掉故障实例,再选择并发较小的实例
- ZoneAvoidanceRule:默认规则,复合判断server所在区域的性能和server的可用性选择服务器
使用(替换选取算法)
-
新建规则类
@Configuration public class MySelfRule { @Bean public IRule myRule() { return new RandomRule(); //返回算法实例对象 } }
注意:这里新创建的配置类不能放在
@ComponentScan
注解所扫描的当前包下以及子包下 -
启动类添加
@RibbonClient
注解@SpringBootApplication @EnableEurekaClient @RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class) public class OrderMain80 { public static void main(String[] args) { SpringApplication.run(OrderMain80.class,args); } }
轮询算法原理
轮询算法:rest接口第几次请求次数 % 服务器集群总数量 = 实际调用服务的下标,每次重启rest接口计数从1开始。
代码实现
-
新建一个
LoadBalancer
接口public interface LoadBalancer{ ServiceInstance instances(List<ServiceInstance> serviceInstances); }
-
创建一个
LoadBalancer
接口的实现类@Component public class MyLB implements LoadBalancer{ private AtomicInteger atomicInteger = new AtomicInteger(0); //计算轮询算法所需要的当前下标 public final int getAndIncrement() { int current = 0; int next = 0; do { //使用自旋锁+CAS 操作完成获取下标的操作 current = atomicInteger.get(); //获取当前的下标值 next = current >= Integer.MAX_VALUE ? 0 : current + 1; } while(!this.atomicInteger.compareAndSet(current, next)); System.out.println("next => " + next); @Override //根据轮询算法返回要访问的服务实例 //serviceInstances参数为服务器集群的总数量 public ServiceInstance instances(List<ServiceInstance> serviceInstances) { // 得出当前是第几次访问 int index = getAndIncrement() % serviceInstances.size(); //从服务列表中取出下标对应的服务 ServiceInstance instance = serviceInstances.get(index); return instance; } }
-
Controller代码
//可以获取注册中心上的服务列表 @Resource private DiscoveryClient discoveryClient; @Resource private LoadBalancer loadBalancer; //注入自定义的算法接口 @GetMapping("/consumer/payment/lb") public String getPaymentLB(){ List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE"); if(instances == null || instances.size()<=0) { return null; } ServiceInstance serviceInstance = loadBalancer.instances(instances); URI uri = serviceInstance.getUri(); return restTemplate.getForObject(uri+"/payment/lb",String.class); }
注意:
之前不使用手写的轮询算法的时候,会在 restTemplate
对象上添加 @LoadBalancer
注解,这里手写玩轮询算法后,需 要将 @LoadBalancer
注解删除。
其实对Ribbon的了解到这里就可以了,毕竟Ribbon现在已经进入维护阶段了,个人更推荐使用 OpenFeign
来完成服务调用。
OpenFeign
简介
OpenFeign是什么?
Feign是一个声明式的Web服务客户端,让编写Web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可。
OpenFeign的作用是
Feign旨在使编写Java Http客户端变得更容易。前面在使用Ribbon+RestTemplate时,利用RestTemplate对http请求的封装处理,形成了一套模版化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。 OpenFeign就是通过
服务接口+注解
的方式来调用服务,而且OpenFeign底层也集成了Ribbon,通过轮询实现了客户端的负载均衡,这种方式可以让我们在编写客户端的Controller时,更优雅地实现服务的调用。
OpenFeign使用
因为OpenFeign是为了简化Web服务客户端的编写,所以所有操作都是在客户端上进行的。
-
导入依赖
<!--openfeign--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
-
yml配置
server: port: 80 eureka: client: register-with-eureka: false service-url: defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
-
启动类添加注解
@SpringBootApplication @EnableFeignClients public class OrderFeignMain80{ public static void main(String[] args) { SpringApplication.run(OrderFeignMain80.class,args); } }
-
在业务逻辑层(service)添加注解声明要调用哪个服务
package cn.pigman.springcloud.service; import cn.pigman.springcloud.common.CommonResult; import cn.pigman.springcloud.common.Payment; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @Service @FeignClient("CLOUD-PAYMENT-SERVICE") //设置要调用哪个服务 public interface PaymentFeignService { @GetMapping("/payment/{id}") //这里的请求方法和请求路径都要与所调用的服务中的Controller一致 CommonResult<Payment> getById(@PathVariable("id") Long id); @GetMapping("/payment/timeout") String timeout(); }
OpentFeign超时控制
在日常开发中,可能会出现一种情况,就是客户端调用服务端的服务,但有可能服务端的服务处理的是一个长时间的业务,这在服务端看来是正常的,但客户端并不清楚,只知道请求超时了,我就不等了;针对这种情况,我们可以通过超时控制来解决这个问题。
注意:OpentFeign调用服务默认等待时间为1秒,超时后报错。
**使用:**在yml文件中配置超时控制
# 配置Feign的超时控制相关信息
ribbon:
# 设置建立连接的超时时间,超过该时间feign就停止访问
ReadTimeout: 5000
# 设置读取资源的超时时间,超过该时间feign就停止访问
ConnectTime: 5000
日志
Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。
说白了就是对Feign接口的调用情况进行监控和输出。
日志级别
- NONE:默认的,不显示任何日志;
- BASIC:仅记录请求方法、URL、响应状态码及执行时间;
- HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息;
- FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。
使用
-
配置bean
@Configuration public class FeignConfig{ @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }
-
yml配置
# 配置Feign的日志信息 logging: level: # 配置Feign日志以什么级别监控哪个接口 cn.pigman.springcloud.service.PaymentFeignService: debug
-
后台日志查看