服务调用
Ribbon负载均衡
Ribbon是什么?
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用,Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助的基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
负载均衡(LB)是什么
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。
常见的负载均衡有软件Nginx,LVS,硬件F5等
Ribbon本地负载均衡客户端VS Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有的请求都会交给nginx,然后又nginx实现转发请求。即负载均衡是由服务器实现的。(集中式LB)。集中式LB即在服务的消费方和提供方之间福利的LB设施(可以实硬件,如F5,也可以是软件,如Nginx),由该设施负责把请求通过某种策略转发至服务的提供方。
Ribbon本地负载均衡,在调用微服务接口时候,会在注册中心上获得注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。(进程内LB)。进程内LB将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。Ribbon就是属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon负载均衡使用
Ribbon在工作时分两步:①选择EurekaServer,它优先选择在同一个区域负载较少的server;②根据用户指定的策略,在从server取到服务注册列表中选择一个地址。其中Ribbon提供了很多策略,比如轮询、随机和根据响应时间加权。
在前面的实验中已经完成了负载均衡,详见,但是在前面的实验中,并没有引入spring-cloud-starter-netflix-ribbon也可以使用ribbon,那是因为在eureka中自带了spring-cloud-starter-netflix-ribbon引用。
Ribbon核心组件IRule
IRule根据特定算法中从服务列表中选取一个要访问的服务。
IRule.java源码
public interface IRule{
/*
* choose one alive server from lb.allServers or在所有提供服务的server中选取出活跃的server
* lb.upServers according to key 或者根据某种算法来确定key
*
* @return choosen Server object. NULL is returned if none
* server is available
*/
public Server choose(Object key);
public void setLoadBalancer(ILoadBalancer lb);
public ILoadBalancer getLoadBalancer();
}
IRule的实现类有下面几个
Ribbon负载规则替换
配置细节
官方文档文档明确给出了警告:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的。
1、修改配置类
在程序中,主程序类上有@SpringBootApplication注解,改注解中包含了@ComponentScan,因此自定义Ribbon配置类不能放在主程序类所在的包以及其子包下。如下图所示:
MySelfRule.java
@Configuration
public class MySelfRule {
@Bean
public IRule muRule(){
return new RandomRule(); //定义为随机,可根据上图中的实现类切换其他的规则
}
}
2、在主启动类上添加@RibbonClient(name = "CLOUD-PAYMENT_SERVICE",configuration = MySelfRule.class)
来切换为自定义的规则。
3、启动消费端,再次访问http://localhost/consumer/payment/forEntity/1接口,发现由8001和8002随机提供服务。
Ribbon IRule负载均衡算法
轮询的负载均衡算法:rest接口第几次请求数%服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始。
例如eureka中有两个CLOUD-PAYMENT_SERVICE服务器,第一次访问时,rest接口计数为1,那么实际调用服务器位置下标为:1%2 = 2;第二次访问时,rest接口计数为2,2%2 = 0,一次类推。服务器重启后,重新从1开始计数。
底层原理
Unsafe:是CAS的核心类,由于java不能直接访问底层系统,需要通过本地方法来访问,Unsafe相当于是一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样,直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法。
注意,Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务
CAS全称为Compare-And-Swap。它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为期望值,如果是则更改为新的值,这个过程是原子的。
CAS并发原语体现在java语言中就是sun.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
CAS的缺点:①循环时间长,开销大。如果CAS失败,会一直进行尝试,那么若CAS长时间一直步成功,可能会给CPU带来很大的开销;②只能保证一个共享变量的原子操作;③ABA问题
手动编写IRule实现类
创建负载均衡接口LoadBalancer.java
public interface LoadBalancer {
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
LoadBalancer实现类MyLB.java
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndIncrement(){
int current;
int next;
do{
current = this.atomicInteger.get();
next = current >= 2147483647 ? 0 : current+1;
}while (!this.atomicInteger.compareAndSet(current,next));
System.out.println("********next:"+next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
在80消费端的controller中,引入自定义的LoadBalancer,并使用自定义的LoadBalancer实现负载均衡
@GetMapping(value = "/consumer/payment/lb")
public String getPaymentLB(){
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVCIE");
if(instances == null || instances.size() <= 0){
return "错误";
}
ServiceInstance serviceInstance = loadBalancer.instances(instances);
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri+"/payment/lb",String.class);
}
OpenFeign
Feign是一个声明式的Web服务客户端,让编写Web服务客户端编得非常容易,只需创建一个接口并在接口上添加注解即可。
Feign能干什么?
Feign旨在使编写Java Http客户端变得更容易,前面在使用Ribbon——RestTemplate时,利用RestTemplate怼Http请求的封装处理,形成一套模板化的调用方法。但实际开发中,由于对服务的调用可能不止一处,往往一个接口会被多处调用,所以通常会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Geign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义,在Fiegn的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Respository注解,现在是一个微服务接口上标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
Feign和OpenFeign的对比
OpenFeign使用
1、建moudle
新建名为cloud-consumer-feign-order80的模块
2、改POM
修改POM文件,添加openFeign的依赖
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
3、写YML
server:
port: 80
#Eureka
eureka:
client:
#表示是否将自己注册进EurekaServer,默认为true
register-with-eureka: true
service-url:
#注册地址
# defaultZone: http://localhost:7001/eureka #单机版
defaultZone: http://eureka7002.com:7001/eureka,http://eureka7001.com:7002/eureka #集群版
4、主启动
在主启动类上添加@EnableFeignClients注解来开启FeignClients
@SpringBootApplication
//开启FeignClient
@EnableFeignClients
public class OrderOpenFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderOpenFeignMain80.class,args);
}
}
5、业务类
新建PaymentFeignService接口并新增注解@FeignClient,这个注解的作用是将当前service标记为一个Feign组件
@Component
//该注解表明将当前service作为一个Feign组件
//value指明要访问的服务名称
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/getPaymentById/{id}")
public CommonResult<Payment> getPaymentById(@Param("id") Long id);
}
OrderFeignController.java
@RestController
@Slf4j
public class OrderFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
6、启动,测试
启动7001,7002,8001,8002,Feign80
地址访问http://localhost/consumer/payment/get/1,在OpenFeign中已集成Ribbon,因此此访问由8001和8002轮流提供服务。
OpenFeign超时控制
问题:OpenFeign默认等待一秒钟,当服务端的访问需要处理的时间较长时,超出了OpenFeign的默认等待时间,会报出下列错误
当服务端的处理值得消费端等待,为了避免消费端出现服务超时的情况,我们需要设置Feign客户端的超时控制。
yml文件
server:
port: 80
#Eureka
eureka:
client:
#表示是否将自己注册进EurekaServer,默认为true
register-with-eureka: true
service-url:
#注册地址
# defaultZone: http://localhost:7001/eureka #单机版
defaultZone: http://eureka7002.com:7001/eureka,http://eureka7001.com:7002/eureka #集群版
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
#指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间,单位是毫秒
ReadTimeout: 5000
#指的是建立连接后从服务器读取到可用资源所用的时间
ConnectTimeout: 5000
在yml中添加feign客户端超时时间,这个时间要超过服务端的响应时间
OpenFeign日志打印功能
OpenFeign的日志级别:
NONE:默认的,不显示任何日志;
BASIC:仅记录请求方法、URL、响应状态码以及执行时间;
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息;
FULL:除了HEADERSzhong定义的信息之外,还有请求和响应的正文及元数据
配置
配置类FeignConfig.java
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
消费端yml中添加下列配置
logging:
level:
#feign日志以什么级别监控哪个接口
cn.yz.springcloud.service.PaymentFeignService: debug
重启消费端,访问地址,会详细的打印出访问的情况
配置
配置类FeignConfig.java
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
消费端yml中添加下列配置
logging:
level:
#feign日志以什么级别监控哪个接口
cn.yz.springcloud.service.PaymentFeignService: debug
重启消费端,访问地址,会详细的打印出访问的情况