Spring Cloud服务调用
Ribbon负载均衡服务调用
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端 负载均衡的工具.
主要功能是提供客户端的软件负载均衡算法和服务调用,Ribbon客户端组件提供了一系列的完善的配置项如连接超时,重试等.
负载均衡:
- 集中式LB,Nginx 服务端的
- 进程内LB,Ribbon 客户端的
Ribbon一句话就是 负载均衡+RestTemplate的调用
Ribbon在工作时分为两步:
- 第一步先选择EurekaServer,它优先选择在同一区域中负载较少的server
- 第二步再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址.其中Ribbon提供了多种策略:比如轮询,随机和根据响应时间加权
Ribbon的依赖:

默认集成了Ribbon,Ribbon的依赖是spring-cloud-starter-netfix-ribbon;
RestTemplate的使用
常用的方法:getForObject方法/getForEntity方法;postForObject/postForEntity方法;
getForObject的方法返回对象为响应体中数据转发为的对象,基本可以理解为Json;
getForEntity的返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等
Get请求方法和Post请求方法
@GetMapping("/consumer/payment/getForEntity/{id}")
public CommonResult<Payment> getPaymentEntity(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_SRV + "/payment/get", CommonResult.class, id);
if (entity.getStatusCode().is2xxSuccessful()) {
return entity.getBody();
}else {
return new CommonResult<Payment>(444, "操作失败");
}
}
entity的forBody方法就是得到这个entity中的数据
Ribbon的负载均衡其他算法:
Ribbon的核心组件IRule
IRule:根据特定算法中从服务列表中选取一个要访问的服务

IRule是loadbalance中的一个接口.
IRule的实现类结构图

它的抽象实现中有7种自带的实现方式,7种负载均衡的实现.
替换负载均衡算法
如何替换,如何自己扩展:
默认是轮询
- 创建新的包
官网指出:这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下.
否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了
这个@ComponentScan就是主启动类上的注解@SpringBootApplication组合注解的一个注解,它的作用就是扫描当前包及其子包下.
所以需要新建包,在当前项目的启动类之外的包

2.自定义配置类
/**
* 自定义负载均衡算法配置类
*
* @author csq
* @date 2020/11/20 11:03:10
*/
@Configuration
public class MySelfRule {
@Bean
public IRule myRule() {
//定义为随机的
return new RandomRule();
}
}
3.自启动类配置
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon的配置类,从而使配置生效.
@RibbonClient(name = "cloud-payment-service", configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
添加注解,指定自定义配置.
Ribbon负载均衡算法原理 轮询

如何实现算法:在Ribbon的负载均衡接口中的IRule接口中choose选择方法.传入一个key,查看它的抽象实现类

比如它的一个具体实现子类
public class RoundRobinRule extends AbstractLoadBalancerRule {
//原子类
private AtomicInteger nextServerCyclicCounter;
private static final boolean AVAILABLE_ONLY_SERVERS = true;
private static final boolean ALL_SERVERS = false;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public RoundRobinRule() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
//choose选择 传入一个lb和key
public Server choose(ILoadBalancer lb, Object key) {
//没有负载均衡 报错
if (lb == null) {
log.warn("no load balancer");
return null;
}
//服务器
Server server = null;
//计数
int count = 0;
while (server == null && count++ < 10) {
//得到所有的服务实例 状态时活着的健康的 可达的
List<Server> reachableServers = lb.getReachableServers();
//做集群
List<Server> allServers = lb.getAllServers();
//可达的实例数
int upCount = reachableServers.size();
//服务器集群的总数量
int serverCount = allServers.size();
//健康的为0报错
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
//脚标 下标 这个方法 自旋锁和cas 得到当前下标值
int nextServerIndex = incrementAndGetModulo(serverCount);
//选择到一台机器
server = allServers.get(nextServerIndex);
if (server == null) {
/* Transient. */
Thread.yield();
continue;
}
if (server.isAlive() && (server.isReadyToServe())) {
return (server);
}
// Next.
server = null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: "
+ lb);
}
return server;
}
//自旋锁
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextServerCyclicCounter.get();
int next = (current + 1) % modulo;
// cas 比较并设置
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
手写轮询算法
/**
* 定义LoadBalance,获取服务列表
*
* @author csq
* @date 2020/11/20 11:39:45
*/
public interface LoadBalance {
//获取服务列表
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
@Component
public class MyLB implements LoadBalance {
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) {
//rest接口第几次请求数 % 服务器集群的总数量
int index = getAndIncrement() % serviceInstances.size();
return serviceInstances.get(index);
}
}
OpenFeign服务调用
Feign一个声明式WebService客户端,使用Feign能让编写Web Service客户端更加简单.
它的使用方法式定义一个服务接口然后在上面添加注解.Feign也支持可插拔式的编码器和解码器.Spring Cloud 对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters.Feign可以与Eureka和Ribbon组合使用以支持负载均衡.
既然已经有了Ribbon,那么还要Feign干嘛
OpenFeign给Feign添加了Spring MVC的支持,创建接口,在接口上添加注解.
使用步骤
接口+注解: 微服务调用接口+@FeignClient 消费者方法创建接口
Feign在消费端使用
1.依赖:
<!-- openfeign使用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
feign整合了ribbon
2.配置文件
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/
3.主启动类
//激活开启feign功能
@EnableFeignClients
@SpringBootApplication
public class OrderFeignMainApp80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMainApp80.class, args);
}
}
4.业务类
业务逻辑接口+@FeignClient配置调用provider服务
新建接口添加注解@FeignClient注解
//指定服务名称,告诉它是调用哪个服务名称下的服务
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
@Component
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
CommonResult<Payment> getPaymentById(@PathVariable("id")Long id);
}
controller层:
@RestController
@Slf4j
public class OrderFeignController {
@Autowired
private PaymentFeignService paymentFeignService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/consumer/order/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id")Long id) {
return paymentFeignService.getPaymentById(id);
}
}
调用流程:
Feign自带负载均衡配置项
feign自带了Ribbon,使用的默认负载均衡算法是轮询
OpenFeign超时控制
消费方调用提供者,一定会有超时的情况.
在服务提供方法提供一个超时方法
//提供一个超时的方法 ,查看feign的超时控制 @GetMapping("/payment/feign/timeout") public String paymentFeignTimeout() { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return serverPort; }
添加消费者feign调用接口方法
//提供一个超时的方法 ,查看feign的超时控制 @GetMapping("/payment/feign/timeout") public String paymentFeignTimeout();
添加消费者控制层方法
//提供一个超时的方法 ,查看feign的超时控制 @GetMapping("/consumer/order/timeout") public String paymentFeignTimeout(){ //opent feign -ribbon 客户端一般默认等待一秒钟 return paymentFeignService.paymentFeignTimeout(); }
消费端:
服务超时 Read timed out ,Open Feign默认等待1秒,超过后就报错了
OpenFeign默认支持Ribbon
在yml配置文件中开启OpenFeign客户端超时控制
# 设置feign客户端的超时时间 默认支持ribbon ribbon: # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间 ReadTimeout: 5000 # 指的是建立连接后从服务器读取到可用资源所用的时间 ConnectTimeout: 5000
OpenFeign日志打印功能
Feign提供了日志打印的功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节
说白了就是对Feign接口的调用情况进行监控和输出
@Configuration
public class FeignLogConfig {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
配置文件:
logging:
level:
cn.csq.springcloud.service.PaymentFeignService: debug
# feign日志以什么级别监控哪个接口