Spring Cloud Ribbon是基于Netflix Ribbon实现的一套 客户端 负载均衡的工具
提供客户端的软件负载均衡算法和服务调用(ribbon负载均衡+restTemplate)
LB负载均衡(LoadBalance):将用户的请求平摊的分配到多个服务器上,从而达到系统的HA(高可用)
常见的负载均衡软件:Nginx,LVS,硬件 F5等
Ribbon本地负载均衡客户端、Nginx服务端负载均衡区别
Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡由服务端实现的
Ribbon本地负载均衡,在调用微服务接口的时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方
进程内LB:将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。Ribbon就属于进程内LB,它是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon:为客户端提供服务端的地址,然后由客户端自行选择服务端
工作步骤分为两步:1、先选择EurekaServer(在注册中心查询可用服务列表并选择服务),优先选择在同一个区域内存负载较少的server。2、在根据用户指定的策略,再从server取到的服务注册列表中选择一个地址
不考虑eurekaserver集群的情况下,都为单机版,这时可以选择server,然后再从server的列表中选择服务payment
导入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-eureka-client</artifactId>
</dependency
为什么我们只配置了Eureka的依赖,而没有导入ribbon的依赖??
点击去发现,eureka集成了ribbon的依赖,所以即使我们没有单独的配置ribbon,它还是能实现负载均衡的
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-ribbon</artifactId>
<version>2.2.1.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
RestTemplate
可在服务之间发送HTTP请求进行调用
它提供了常见的REST请求方案的模板,例如GET请求、POST请求、PUT请求、DELETE请求以及一些通用的请求执行方法exchange以及execute。
RestTemplate继承自InterceptingHttpAccessor并实现了RestOperations接口,其中RestOperations接口定义了基本的RESTful操作,这些操作再RestTemplate中都得到了实现。
public class UserHelloController {
@Autowired
DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/hello")
public String hello(String name) {
// 获取服务集合
List<ServiceInstance> list = discoveryClient.getInstances("provider");
// 获取集合中第一个服务
ServiceInstance instance = list.get(0);
// 获取服务的主机名
String host = instance.getHost();
// 获取服务的端口号
int port = instance.getPort();
String url = "http://" + host + ":" + port + "/hello?name={1}";
// getForEntity第一个参数是url,第二个参数:接口返回的数据类型;第三个参数:可变长度的参数,用来给占位符填值的
ResponseEntity<String> responseEntity = restTemplate.getForEntity(url, String.class, name);
// 一般多线程用StringBuffer
StringBuffer sb = new StringBuffer();
// 获取响应状态码
HttpStatus statusCode = responseEntity.getStatusCode();
// 获取响应数据
String body = responseEntity.getBody();
sb.append("statusCode:")
.append(statusCode)
.append("</br>")
.append("body:")
.append(body)
.append("</br>");
// 获取响应头
HttpHeaders headers = responseEntity.getHeaders();
Set<String> keySet = headers.keySet();
for (String s : keySet) {
sb.append(s)
.append(":")
.append(headers.get(s))
.append("</br>");
}
return sb.toString();
}
}
}
getForObject的返回值就是服务提供者返回的数据,使用getForObject无法获取到响应头
postForEntity,参数的传递可以实key/value的形式,也可以是JSON数据
唯一的区别就是第二个参数的类型不同,这个参数如果是一个 MultiValueMap 的实例,则以 key/value 的形式发送,如果是一个普通对象,则会被转成 json 发送。
面试题:你除了用过轮询这样的负载均衡方法还用没用过其它的,如果你要是用ribbon的话,能不能换一种,负载算法或者是轮询算法,你有没有自己写过或换过,说说思路
IRule:根据特定算法中从服务列表中选取一个要访问的服务
再@ComponentScan不能扫描到的包中新建类并自定义负载规则
@Configuration
public class MySelfRule
{
@Bean
public IRule myRule()
{
return new RandomRule();//定义为随机
}
}
然后在启动类设置注解,配置为自定义的规则类
@RibbonClient(name="CLOUD-PAYMENT-SERVICE",configuration=MySelfRule.class)
Ribbon默认负载均衡轮询算法原理
负载均衡算法:rest接口第几次请求数%服务器集群总数量=实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
如:List[0] instances =127.0.0.1:8001
List[1] instances =127.0.0.1:8002
8001 + 8002 组合成为集群,它们共计2台机器,集群总数为2,按照轮询算法原理:
轮询算法:
public interface LoadBalancer{
ServiceInstance instances(List<ServiceInstance> serviceInstances);
}
然后实现上面的接口
@Component
public class MyLB implements LoadBalancer{
//AtomicInteger类,实现数据原子性更新
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
}
//负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启动后rest接口计数从1开始
@Override
public ServiceInstance instances(List<ServiceInstance> serviceInstances){
//获取调用的服务器下标
int index = getAndIncrement() % serviceInstances.size();
//返回获取调用的服务器实例
return serviceInstances.get(index);
}
}