Ribbon
1:什么是Ribbon
①:什么是客户端的负载均衡
进程内的LB(Load Balancer),他是一个类库集成到消费端,通过消费端进行获取提供者的地址
生活中:类似与你去火车站排队进站(有三条通道),只要是正常人,都会排队到人少的队伍中去.
程序中: 我们消费端 能获取到服务提供者地址列表,然后根据某种策略去获取一个地址进行调用.
②:什么是服务端的负载均衡
1.2) 什么是服务端的负载均衡?
生活中:类似与你去火车站排队进站的时候,有一个火车站的引导员告诉你说三号通道人少,你去三号通道排队。
程序中:就是你消费者调用的是ng的地址,由ng来选择 请求的转发(轮询 权重,ip_hash等)
2:如何快速整合ribbon
第一步:搭建二个eureka的集群
参考第四节Eureka
第二步:把ribbon集成到消费端
①:加入依赖
<!-- eureka 客户端的依赖(其实他集成了ribbon的依赖)-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 集成ribbon的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
②:写application.yml的配置文件
server:
port: 9004
servlet:
context-path: /
spring:
application:
#注册到eureka服务端的微服务名称
name: eureka-ribbon-client
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
eureka:
client:
service-url:
#注册到eureka服务端的地址
defaultZone: http://localhost:9000/eureka/
defaultZone=http://www:
eureka9000:
com:9000/eureka/,http://www:
eureka9001:
com:9001/eureka/:instance:
#点击具体的微服务,右下角是否显示ip
prefer-ip-address: true
#显示微服务的名称
instance-id: eureka-ribbon-client-9004
③:修改调用配置
package com.ribbon.client.conf;
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 MyConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
package com.ribbon.client.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class RibbonController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/ribbon")
public String ribbon() {
//通过微服务实例名称进行调用
return restTemplate.getForObject("http://EUREKA-CLIENT2/eurekaClient2Test", String.class);
}
}
第三步:搭建服务提供者
即其他客服端提供的服务,这里使用yinz-eureka-client的服务
测试地址:http://localhost:9004/ribbon
3:如何使用ribbon进行负载均衡测试
拷贝yinz-ribbon-client客服端两份,修改端口,定义相同的服务,打印不同的日志测试调用
4:测试Ribbon的负载均衡策略(默认的是轮询)#
关闭某台服务,测试宕机
5:Ribbon核心知识点
Ribbon所包含的负载均衡策略分类
**RandomRule:**随机选择一个Server
**RetryRule:**对选定的负载均衡策略机上重试机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的server
**RoundRobinRule:**轮询选择, 轮询index,选择index对应位置的Server
**AvailabilityFilteringRule:**过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个Server的运行状态
**BestAvailableRule:**选择一个最小的并发请求的Server,逐个考察Server,如果Server被tripped了,则跳过。
**WeightedResponseTimeRule:**根据响应时间加权,响应时间越长,权重越小,被选中的可能性越低;
**ZoneAvoidanceRule:**复合判断Server所在区域的性能和Server的可用性选择Server
如何修改默认负载均衡的策略
写一个配置类来修改ribbon的负载均衡的配置
@Configuration
public class MyConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
/**
* 随机算法的负载均衡
* @return
*/
@Bean
public IRule MyRule() {
//return new RandomRule();
return new RetryRule();
}
}
如何来自定义负载均衡策略
引用规则,使用@RibbonClient指定负载均衡策略
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "EUREKA-CLIENT2",configuration = MyRule.class)
public class RibbonClientApplication {
自定义的负载均衡策略不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方
自定义负载均衡策略
原生的RandomRule策略解析
public class RandomRule extends AbstractLoadBalancerRule {
Random rand;
public RandomRule() {
rand = new Random();
}
@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 = rand.nextInt(serverCount);
//获取一个服务列表
server = upList.get(index);
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;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
自定义Rule
package com.config;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 自定义的随机策略
* Created by smlz on 2019/4/8.
*/
public class MyRule extends AbstractLoadBalancerRule {
Random rand;
public MyRule () {
rand = new Random();
}
private int currentIndex = 0;
private List<Server> currentChooseList = new ArrayList<Server>();
/**
* Randomly choose from all living servers
*/
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) {
return null;
}
//第一次进来 随机选取一个下标
int index = rand.nextInt(serverCount);
//当前轮询的次数小于等于5
if(currentIndex<5) {
//保存当前选择的服务列表ip
if(currentChooseList.isEmpty()) {
currentChooseList.add(upList.get(index));
}
//当前的++
currentIndex++;
//返回保存的
return currentChooseList.get(0);
}else {
currentChooseList.clear();
currentChooseList.add(0,upList.get(index));
currentIndex=0;
}
if (server == null) {
Thread.yield();
continue;
}
if (server.isAlive()) {
return (server);
}
server = null;
Thread.yield();
}
return server;
}
@Override
public Server choose(Object key) {
return choose(getLoadBalancer(), key);
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
// TODO Auto-generated method stub
}
}
Feign
1:什么是Feign
Feign是Netflix开发的声明式、模板化的HTTP客户端,其灵感来自Retrofit、JAXRS-2.0以及WebSocket。Feign可帮助我们更加便捷、优雅地调用HTTP API。
在Spring Cloud中,使用Feign非常简单——只需创建接口,并在接口上添加注解即可。
Feign支持多种注解,例如Feign自带的注解或者JAX-RS注解等。Spring Cloud对Feign进行了增强,使其支持Spring MVC注解,另外还整合了Ribbon和Eureka,从而使得Feign的使用更加方便
eg: 以前我们调用dao的时候 ,我们是不是一个接口加 注解然后在service中就可以进行调用了
@Mapper
public interface OrderDao {
List queryOrdersByUserId(Integer userId);
}
2:Feign VS Ribbon
Ribbon+RestTemplate进行微服务调用缺点:
ResponseEntity responseEntity = restTemplate.getForEntity(“http://order-service/order/queryOrdersByUserId/”+userId,List.class);
①:我们不难发现,我们构建上诉的URL 是比较简单的,假如我们业务系统十分复杂,类似如下节点
https://www.baidu.com/s?wd=asf&rsv_spt=1&rsv_iqid=0xa25bbeba000047fd&issp=1&f=8&rsv_bp=0&rsv_idx=2&ie=utf-8&tn=baiduhome_pg&rsv_enter=1&rsv_sug3=3&rsv_sug1=2&rsv_sug7=100&rsv_sug2=0&inputT=328&rsv_sug4=328
那么我们构建这个请求的URL是不是很复杂,若我们请求的参数有可能变动,那么是否这个URL是不是很复杂
②:如果系统业务非常复杂,而你是一个新人,当你看到这行代码,恐怕很难一眼看出其用途是什么!此时,你很可能需要寻求老同事的帮助(往往是这行代码的作者,哈哈哈,可万一离职了呢?),或者查阅该目标地址对应的文档(文档常常还和代码不匹配),才能清晰了解这行代码背后的含义!否则,你只能陷入蛋疼的境地!
3:工程搭建
1、加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.1</version>
</dependency>
2、定义服务
// 正接口上的name指定微服务的名称, path是指定服务消费者的调用前缀
@FeignClient(name = "ms-provider-user",path = "/user")
public interface OrderApi {
@RequestMapping("/queryUserById/{userId}")
public String queryUserById(@PathVariable("userId") Integer userId);
}
将yinz-feign-client 打成jar
3、在消费端依赖jar
<dependency>
<groupId>com.tuling</groupId>
<artifactId>yinz-feign-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
4、在消费端主启动类商添加
@EnableFeignClients(basePackages = "com.yinz")
3、如何自定义Feign
1:自定义日志级别
日志级别:
NONE:【性能最佳,适用于生产】:不记录任何日志(默认值)
public class MyConfig{
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.NONE;
}
}
HEADERS:记录BASIC级别的基础上,记录请求和响应的header
public class MyConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.HEADERS;
}
}
FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据
public class MyConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
代码中如何自定义
public class MyConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.BASIC;
}
}
@FeignClient(name = "ms-provider-user",configuration=MyConfig.class,path = "/user")
public interface OrderApi {
@RequestMapping("/queryUserById/{userId}")
public String queryUserById(@PathVariable("userId") Integer userId);
}
yml中如何配置:
feign.client.config.ms-provider-order-feign-custom01.loggerLevel=full
如何支持feign的注解来替换springmvc的注解
代码配置
public class MyConfig {
// 修改协议契约
@Bean
public Contract feignContract() {
return new Contract.Default();
}
}
@FeignClient(name = "ms-provider-user",configuration=MyConfig.class,path = "/user")
public interface OrderApi {
@RequestMapping("/queryUserById/{userId}")
public String queryUserById(@PathVariable("userId") Integer userId);
}
配置文件配置
feign.client.config.ms-provider-order-feign-custom01.contract=feign.Contract.Default
创建拦截器设置公用参数实现:RequestInterceptor
编写拦截器:
public class MyInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
System.out.println("自定义拦截器");
template.header("token","123456");
}
}
加入拦截器配置
代码配置
public class MyCfg {
@Bean
public RequestInterceptor myInterceptor () {
return new MyInterceptor();
}
}
Yml配置
feign.client.config.ms-provider-order-feign-custom01.requestInterceptors[0]=com.yinz.client.MyInterceptor
服务提供端获取公共参数