SpringCloud Ribbon是基于NetfIixRibbon实现的一套客户端 负载均衡的工具。
简单的说,Ribbon是Neix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,就是在配置文件中列出LoadBalancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。我们很容易使用Ribbon实现自定义的负载均衡算法。
官网地址:https://github.com/Netflix/ribbon/wiki/Gettting-started(ribbon已进入维护)
LB负载均衡(Load Balance)是什么?
Ribbon本地负载均衡客户端 与 Nginx服务端负载均衡区别?
集中式LB与进程内LB的区别?
Ribbon其实就是一个软负载均衡的客户端组件,它可以和其他所需请求的客户端结合使用,和eureka结合
只是其中的一个实例
1、先选择Eureka Server,它优先选择在同一个区域内负载较少的server
2、再根据用户指定的策略(Ribbon提供了多种策略:比如轮询、随相和根据响应时间加权),再从server取到的服务注册列表中选择一个地址。
一句话:ribbon就是:负载均衡+RestTemplate调用
我们之前创建的eureka集群模式时已经使用了负载均衡,但是消费者服务pom文件中并没有引入关于Ribbon的依赖,why?
猜测spring-cloud-starter-netflix-eureka-client依赖中包含了ribbon依赖
查看消费者服务引入的依赖确实认证了这一点
当然也可以选择自行在pom文件加入ribbon依赖<dependency> <groupId>org.springframework.cloudgroupId> <artifactId>spring-cloud-starter-netflix-ribbonartifactId> dependency>
RestTemplate简单使用介绍
主要使用方法:getForObject()方法 / getForEntity()方法 / postForObject()方法 / postForEntity()方法
ForObject与ForEntity的区别:
public static final String PAYMENT_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> create(@RequestBody Payment payment) {
return restTemplate.postForObject(
PAYMENT_URL + "/payment/create", //url地址
payment, //请求体对象
CommonResult.class); //返回类型
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
return entity.getStatusCode().is2xxSuccessful() ? entity.getBody() : new CommonResult<>(444, "操作失败");
}
根据特定算法从服务列表中选择一个要访问的服务
Ribbon自带的负载均衡规则:
这个自定义配置类不能放在@ComponentScan 所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化定制的目的了。
什么意思呢?
主启动类中@SpringBootApplication注解包含@ComponentScan注解,该注解会扫描主启动类所在包及其子包,所以应该避免放到此包及其子包下
在cloud-consumer-order80模块下
新建package:com.mzr.myrule及mySelfRule类
mySelfRule类
@Configuration
public class mySelfRule {
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}
主启动类加注解@RibbonClient
@SpringBootApplication
@EnableEurekaClient
//在启动微服务时就能够加载我们自定义Ribbon配置类,从而使配置类生效
//name = "cloud-provider-payment"代表当前80微服务是要访问CLOUD-PAYMENT-SERVICE微服务
//configuration= MySelfRule.class代表不使用默认轮询规则,改用自定义随机规则
@RibbonClient(name = "cloud-provider-payment",configuration= MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class, args);
}
}
启动测试便可以发现server port是随机出现的
原理
根据我们之前创建的eureka集群来解释:
RoundRobinRule源码分析
IRuel接口(顶层)
public interface IRule {
Server choose(Object var1);
void setLoadBalancer(ILoadBalancer var1);
ILoadBalancer getLoadBalancer();
}
AbstractLoadBalancerRule抽象类实现了IRule接口
public abstract class AbstractLoadBalancerRule implements IRule, IClientConfigAware {
private ILoadBalancer lb;
public AbstractLoadBalancerRule() {
}
public void setLoadBalancer(ILoadBalancer lb) {
this.lb = lb;
}
public ILoadBalancer getLoadBalancer() {
return this.lb;
}
}
RoundRobinRule类继承AbstractLoadBalancerRule抽象类
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() {
//原子整数类,默认值为0
this.nextServerCyclicCounter = new AtomicInteger(0);
}
public RoundRobinRule(ILoadBalancer lb) {
this();
this.setLoadBalancer(lb);
}
public Server choose(ILoadBalancer lb, Object key) {
//判断是否存在负载均衡
if (lb == null) {
log.warn("no load balancer");
return null;
} else {
Server server = null;
int count = 0;
while(true) {
if (server == null && count++ < 10) {
//获取健康状态的所有微服务
List<Server> reachableServers = lb.getReachableServers();
//获取集群微服务列表
List<Server> allServers = lb.getAllServers();
int upCount = reachableServers.size();
int serverCount = allServers.size();
//判断健康状态的微服务是否为零,如果为0返回null
if (upCount != 0 && serverCount != 0) {
int nextServerIndex = this.incrementAndGetModulo(serverCount);
//根据下标获取服务
server = (Server)allServers.get(nextServerIndex);
if (server == null) {
Thread.yield();
} else {
//判断获得的服务是否正常,正常返回,否则返回null
if (server.isAlive() && server.isReadyToServe()) {
return server;
}
server = null;
}
continue;
}
log.warn("No up servers available from load balancer: " + lb);
return null;
}
if (count >= 10) {
log.warn("No available alive servers after 10 tries from load balancer: " + lb);
}
return server;
}
}
}
private int incrementAndGetModulo(int modulo) {
int current;
int next;
do {
//获取初始值0
current = this.nextServerCyclicCounter.get();
//取余
next = (current + 1) % modulo;
} while(!this.nextServerCyclicCounter.compareAndSet(current, next));
//this.nextServerCyclicCounter.compareAndSet(current, next):使用csa+自旋锁得到下标值
return next;
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
相关知识:JUC+自旋锁
服务提供者8001、8002controller层添加
@GetMapping(value = "/payment/lb")
public String getPaymentLB()
{
return serverPort;
}
ApplicationContextBean去掉注解@LoadBalanced
LoadBalancer接口
public interface LoadBalancer {
public ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
LoadBalancer接口实现类
@Component
public class MyLB implements LoadBalancer {
private AtomicInteger atomicInteger = new AtomicInteger(0);
public final int getAndInteger() {
int current;
int next;
do {
current = atomicInteger.get();
next = current == Integer.MAX_VALUE ? 0 : current + 1;
} while (!this.atomicInteger.compareAndSet(current, next));
System.out.println("************next:" + next);
return next;
}
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances) {
int i = getAndInteger() % serviceInstances.size();
return serviceInstances.get(i);
}
}
controller层
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://cloud-provider-payment";
@Resource
private RestTemplate restTemplate;
@Resource
private LoadBalancer loadBalancer;
@Resource
private DiscoveryClient discoveryClient;
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> create(@RequestBody Payment payment) {
return restTemplate.postForObject(
PAYMENT_URL + "/payment/create", //url地址
payment, //请求体对象
CommonResult.class); //返回类型
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id) {
return restTemplate.getForObject(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
}
@GetMapping("/consumer/payment/getEntity/{id}")
public CommonResult<Payment> getPayment2(@PathVariable("id") Long id) {
ResponseEntity<CommonResult> entity = restTemplate.getForEntity(PAYMENT_URL + "/payment/get/" + id, CommonResult.class);
return entity.getStatusCode().is2xxSuccessful() ? entity.getBody() : new CommonResult<>(444, "操作失败");
}
@GetMapping("/consumer/payment/lb")
public String getPaymentLB() {
List<ServiceInstance> instances = discoveryClient.getInstances("cloud-provider-payment");
ServiceInstance serviceInstance = loadBalancer.instances(instances);
if (instances == null || instances.size() <= 0) {
return null;
}
URI uri = serviceInstance.getUri();
return restTemplate.getForObject(uri + "/payment/lb", String.class);
}
}
测试