SpringCloud-Ribbon&Feign负载均衡-03
负载均衡
1. Ribbon是什么
- 是一套基于NetFlix Ribbon实现的一套客户端负载均衡的工具
- 是Netflix发布的开源项目,主要提供功能为客户端的软件负载均衡算法,将Netflix的中间层服务连接到一起,Ribbonn提供一系列的完整的配置项,如连接超时,重试等等,换句话说,就是在配置文件中列出LoadBalancer后面所有的极其,Ribbon会自动帮助你基于某种规则去连接这些机器
2. Ribbon能干什么
- 负载均衡,LB,在微服务或是分布式集群中常用的一种应用
- 负载均衡简单说就是将用户的请求平均分到多个服务器上面,从而达到高用
- 常见的负载均衡有Nginx 和lvs 等
- Dubbo springcloud中均给我们提供了负载均衡
(1)负载均衡分类
- 集中式LB
- 就是在服务的提供者和调用者之间使用独立的LB设施,如Nginx,由该设施将访问的请求通过某种策略转发至服务的提供方
- 进程时LB
- 将lb的逻辑集成到了调用方,调用方从服务注册中心获知有哪些可以使用的地址,然后自己从这里面选择一个合适的地址
- Ribbon属于进程内的lb,他是一个类库,集成于调用方的进程,调用方通过他来获取到服务的提供方的地址
3. 集成Ribbon
- springcloud-consumer-dept-80的pom.xml添加ribbon和eureka的依赖
<!--Ribbon-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!--Eureka: Ribbon需要从Eureka服务中心获取要拿什么-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 在applicaion.yaml中配置Eureka
# Eureka配置
eureka:
client:
register-with-eureka: false # 不向 Eureka注册自己
service-url: # 从三个注册中心中随机取一个去访问
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
- 主启动类上面加上@EnableEurekaClient注解,开启Eureka
- 自定义Spring配置类:ConfigBean.java配置负载均衡(加注解)实现RestTemplate
@Configuration//是spring中的注解,相当于applicationContext.xml
public class ConfigBean {
//配置负载均衡实现restTemplate @LoadBalanced
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
- 修改controller.DeptConsumerController.java
//通过ribbon实现负载均衡的时候,这里的地址是一个变量,需要通过服务名来访问
// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "SPRINGCLOUD-PROVIDER-DEPT";
- 配置调用者,新建两个服务提供者springcloud-provider-dept-8002/8003并配置对应的数据库db02 db03
- 根据8001的配置配置剩余的两个
- 先开启服务集群,然后开启服务提供者最后开启80服务调用者,测试访问http://localhost/consumer/dept/list 看随机访问到的是哪个数据库的
- 测试结果为1-2-3是轮询算法
- 自定义规则在springcloud-provider-dept-80中的ConfigBean进行配置
@Configuration
public class ConfigBean {
//@Configuration -- spring applicationContext.xml
/**
* IRule:
* RoundRobinRule 轮询策略
* RandomRule 随机策略
* AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
* RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
*/
@Bean
public IRule myRule() {
return new RandomRule();//使用随机策略
//return new RoundRobinRule();//使用轮询策略
//return new AvailabilityFilteringRule();//使用轮询策略
//return new RetryRule();//使用轮询策略
}
}
- 也可以自定义规则,新建myRule.MyRule配置类需要在启动类的上一个包
/**
* @Auther: csp1999
* @Date: 2020/05/19/11:58
* @Description: 自定义规则
*/
@Configuration
public class MyRule {
@Bean
public IRule myRule(){
return new MyRandomRule();//默认是轮询RandomRule,现在自定义为自己的
}
}
- 主启动类开启负载均衡之后并指定自定义的MyRule配置类
//Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心IP地址和端口号
@SpringBootApplication
@EnableEurekaClient
//在微服务启动的时候就能加载自定义的Ribbon类(自定义的规则会覆盖原有默认的规则)
@RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = MyRule.class)//开启负载均衡,并指定自定义的规则
public class DeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer_80.class, args);
}
}
- MyRandomRule.java
public class MyRandomRule extends AbstractLoadBalancerRule {
/**
* 每个服务访问5次则换下一个服务(总共3个服务)
* <p>
* total=0,默认=0,如果=5,指向下一个服务节点
* index=0,默认=0,如果total=5,index+1
*/
private int total = 0;//被调用的次数
private int currentIndex = 0;//当前是谁在提供服务
//@edu.umd.cs.findbugs.annotations.SuppressWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE")
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;
}
server = upList.get(currentIndex);//从活着的服务中,获取指定的服务来进行操作
}
//======================================================
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 void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
- 开启测试自己的规则是否生效
4. Feign介绍(基于服务端)
- Feign简介:是一声明式的Web Service客户端,让微服务之间的调用变得更加简单,类似于Contoller调用Service.SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端
- 只需要创建接口,添加注解即可
- 面向接口编程
- Feign能干什么
- 将Java Http客户端变得更容易
- 使用Ribbon+RestTemplate的时候,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。
- Feign默认集成了Ribbon
5. 集成Feign
- 新建一个模块springcloud-consumer-dept-feign和原来的调用者80形成对比
- copy 80下的pom.xml和resource以及java代码去新模块中,并新增依赖
<!--Feign的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
- 原来的DeptConsumerController.java
@RestController
public class DeptConsumerController {
//理解:调用者没有service层
//如何调用提供者的service层呢
//RestTemplate:方法可以被我们直接调动--->需要注册到spring中
//(url , 实体:Map , Class<T> responseType)--->(url地址,需要传递一个参数的时候需要写,返回值)
@Autowired
private RestTemplate restTemplate;//提供多种便捷访问远程http服务的方法,简单的restful服务模板
//通过ribbon实现负载均衡的时候,这里的地址是一个变量,需要通过服务名来访问
// private static final String REST_URL_PREFIX = "http://localhost:8001";
private static final String REST_URL_PREFIX = "SPRINGCLOUD-PROVIDER-DEPT";
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id")Long id){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
}
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept){
return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add/",dept,Boolean.class);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
}
}
- 改造后的(新建接口service.DeptClientService里面那三个方法)
接口
@FeignClient(value = “SPRINGCLOUD-PROVIDER-DEPT”)
public interface DeptClientService {
@GetMapping("/dept/get/{id}")
public Dept queryById(@PathVariable("id") Long id);
@GetMapping("/dept/list")
public Dept queryAll();
@GetMapping("/dept/add")
public Dept addDept(Dept dept);
}
@RestController
public class DeptConsumerController {
@Autowired
private DeptClientService deptClientService;
@RequestMapping("/consumer/dept/add")
public boolean add(Dept dept) {
return deptClientService.addDept(dept);
}
@RequestMapping("/consumer/dept/get/{id}")
public Dept get(@PathVariable("id") Long id) {
return deptClientService.queryById(id);
}
@RequestMapping("/consumer/dept/list")
public List<Dept> list() {
return deptClientService.queryAll();
}
}
- 主配置类
@SpringBootApplication
@EnableEurekaClient
// feign客户端注解,并指定要扫描的包以及配置接口DeptClientService
@EnableFeignClients(basePackages = {
"com.sli.springcloud"})
// 切记不要加这个注解,不然会出现404访问不到
//@ComponentScan("com.sli.springcloud")
public class FeignDeptConsumer_80 {
public static void main(String[] args) {
SpringApplication.run(FeignDeptConsumer_80.class, args);
}
}