SpringCloud_learn03_Ribbon&Feign
SpringCloud_Ribbon
Ribbon是什么
Ribbon配置
Ribbon负载均衡
Ribbon核心组件IRule
Ribbon自定义
SpringCloud_Feign
Feign是什么
Feign工程
一、SpringCloud_Ribbon
1.Ribbon是什么
|- Spring Cloud Ribbon是基于Netflix Ribbon实现的一套【客户端】【软件负载均衡】的工具。
|- 主要功能是提供客户端的软件负载均衡算法,将Netflix的中间层服务连接在一起。
|- Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。
|- 简单的,在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
附:LB,即负载均衡(Load Balance),在微服务或分布式集群中经常用的一种应用。负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA。
常见的负载均衡有软件Nginx,LVS,硬件 F5等。dubbo和SpringCloud中均给我们提供了负载均衡,SpringCloud的负载均衡算法可以自定义。
集中式LB:在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5, 也可以是软件,如nginx), 由该设施负责把访问请求通过某种策略转发至服务的提供方;
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
注!=>Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
2.Ribbon初步配置(实现步骤)
|- 添加pom.xml配置(将客户端注册成为一个eureka的客户端)
<!-- Ribbon相关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
|- 修改application.yml追加eureka的服务注册地址
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
|- 在请求的configBean添加新注解
package com.atguigu.springcloud.cfgbeans;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced // spring-cloud-ribbon是一套客户端的负载均衡工具
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
|- 主启动类上添加@EnableEurekaClient
|- 修改DeptController_Consumer客户端访问类
//private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "http://MICROSERVICECLOUD-DEPT";
|- 测试
启动eureka集群、启动 microservicecloud-provider-dept-8001 并注册进eureka集群、启动客户端 microservicecloud-consumer-dept-80应用
访问:http://localhost/consumer/dept/get/1
3.Ribbon负载均衡
《Ribbon架构》
实现步骤:
|- 复制 microservicecloud-provider-dept-8001 新建 microservicecloud-provider-dept-8002、microservicecloud-provider-dept-8003工程
|- 微服务可以具有自己独立的数据库,建库建表脚本:
db2:
DROP DATABASE IF EXISTS cloudDB02;
CREATE DATABASE cloudDB02 CHARACTER SET UTF8;
USE cloudDB02;
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;
db3:
DROP DATABASE IF EXISTS cloudDB03;
CREATE DATABASE cloudDB03 CHARACTER SET UTF8;
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
端口、数据库连接、对外暴露的统一服务实例名
|- 测试
启动eureka集群、启动dept8001~8003服务,访问http://localhost:8001/dept/list、http://localhost:8002/dept/list、http://localhost:8003/dept/list、
启动 microservicecloud-consumer-dept-80 应用,多访问 http://localhost/consumer/list 几次,发现默认轮询负载
总结:Ribbon其实是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例
4.Ribbon核心组件IRule
IRule: 根据特定算法中从服务列表中选取一个要访问的服务,默认出厂自带以下算法:
RoundRobinRule轮询、RandomRule随机
AvailabilityFilteringRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,还有并发的连接数量超过阈值的服务,然后对剩下的服务列表按照轮询策略访问
WeightedResponseTimeRule:根据响应时间计算所有服务的权重,响应时间越快的服务,权重越大,被选中的概率越高。刚启动时如果统计信息不足,会先使用轮询策略,统计一段时间
RetryRule:先按照轮询策略获取服务,如果服务失败,则在指定的时间内会进行重试
BestAvailableRule:先过滤掉多次访问故障的而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
ZoneAvoidanceRule:复合判断server所在区域的性能和server的可用选择服务器
配置示例:
@Configuration
public class ConfigBean
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
@Bean
public IRule myRule() {
return new RandomRule(); //这里修改了默认的轮询算法为随机算法生效
}
}
5.Ribbon自定义(以后要学会从github上拉下来源码,改造)
|- 客户端的主启动类上添加@RibbonClient(name="MICROSERVICECLOUD-DEPT", configuration=MyselfRule.class),表示访问 MICROSERVICECLOUD-DEPT 不再使用Ribbon默认提供的算法
|- 注!!!自定义的负载均衡算法类不能存放在@ComponentScan所扫描的包及子包下
|- 新建包及类:com.wx.myrule.MyselfRule.java、com.wx.myrule.MyRoundRobin.java
package com.wx.myrule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.netflix.loadbalancer.IRule;
@Configuration
public class MyselfRule
{
@Bean
public IRule myRule() {
return new MyRoundRobin(); //这里修改了默认的轮询算法为随机算法生效
}
}
package com.wx.myrule;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;
/**
* @author Juwenzhe
* @description 轮询规则:每台机器访问n次,才会继续访问下一台机器
*/
public class MyRoundRobin extends AbstractLoadBalancerRule {
private AtomicInteger nextServerCyclicCounter;
private int countIndex = 0;
private int n = 1;
private static Logger log = LoggerFactory.getLogger(RoundRobinRule.class);
public MyRoundRobin() {
nextServerCyclicCounter = new AtomicInteger(0);
}
public MyRoundRobin(ILoadBalancer lb) {
this();
setLoadBalancer(lb);
}
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();
if ((upCount == 0) || (serverCount == 0)) {
log.warn("No up servers available from load balancer: " + lb);
return null;
}
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();
countIndex++;
if(countIndex % n == 0) {
countIndex = 0;
}
int next = (current + (countIndex+1) / n) % modulo;
System.out.println(next);
if (nextServerCyclicCounter.compareAndSet(current, next))
return next;
}
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
二、SpringCloud_Feign
1.Feign是什么
|- Feign是一个声明式的WebService客户端
|- Feign旨在简化Java http客户端编程,开发了一套以面向接口编程为目标的依赖包
|- 核心:
·)通过微服务名称获取调用地址
·)通过接口+注解,调用服务
Feign的使用方法是定义一个接口,然后在上面添加注解,同时也支持JAX-RS标准的注解。Feign也支持可拔插式的编码器和解码器。Spring Cloud对Feign进行了封装,使其支持了Spring MVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign集成了Ribbon
与Ribbon不同的是,通过feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用,近似等于 Ribbon + RestTemplate
2.Feign工程(实现实践)
|- 构建新的客户端工程 microservicecloud-consumer-dept-feign (参照 microservicecloud-consumer-dept-80 工程)
|- 在 microservicecloud-consumer-dept-feign 中添加对 feign 的支持
|- 在 microservicecloud-api 新增包service,并抽取服务端controller中(provider)的接口!!! 并新增注解@FeignClient
@FeignClient(value = "MICROSERVICECLOUD-DEPT")
public interface DeptClientService {
@PostMapping(value="/dept/add")
public boolean add(Dept dept);
@GetMapping(value="/dept/get/{id}")
public Dept get(@PathVariable("id") Long id);
@GetMapping(value="/dept/list")
public List<Dept> list();
}
|- microservicecloud-api 公共依赖包进行 mvn clean install
|- 修改 microservicecloud-consumer-dept-feign 工程的controller
package com.wx.springcloud.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.wx.springcloud.entities.Dept;
import com.wx.springcloud.service.DeptClientService;
@RestController
public class DeptController_Consumer
{
@Autowired
private DeptClientService deptService = null;
@RequestMapping(value="/consumer/dept/add")
public boolean add(Dept dept)
{
return deptService.add(dept);
}
@RequestMapping(value="/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id)
{
return deptService.get(id);
}
@GetMapping(value="/consumer/dept/list")
public List<Dept> list()
{
return deptService.list();
}
}
|- 修改 microservicecloud-consumer-dept-feign 工程的注启动类
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients(basePackages= {"com.wx.springcloud"})
@ComponentScan("com.wx.springcloud")
public class DeptConsumer80_Feign
{
public static void main(String[] args)
{
SpringApplication.run(DeptConsumer80_Feign.class, args);
}
}
|- 测试:依次启动eureka、provider、feign工程,并访问接口:http://localhost/consumer/dept/list
3.Feign工程实践中的报错:
feign.FeignException: status 404 reading DeptClientService#list(); content:
{"timestamp":1578313467118,"status":404,"error":"Not Found","message":"Not Found","path":"/consumer/dept/list"}
at feign.FeignException.errorStatus(FeignException.java:62) ~[feign-core-9.5.0.jar:na]
at feign.codec.ErrorDecoder$Default.decode(ErrorDecoder.java:91) ~[feign-core-9.5.0.jar:na]
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:138) ~[feign-core-9.5.0.jar:na]
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76) ~[feign-core-9.5.0.jar:na]
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103) ~[feign-core-9.5.0.jar:na]
at com.sun.proxy.$Proxy79.list(Unknown Source) ~[na:na]
at com.wx.springcloud.controller.DeptController_Consumer.list(DeptController_Consumer.java:35) ~[classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_91]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_91]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_91]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_91]
...
解决:
在抽取的接口中,应该与服务提供方,即provider的controller中的接口一致!而不是客户端的接口!