往期分享:
微服务项目搭建
springCloud Alibaba之Nacos组件
由于部分代码放在上面两篇文章中,所以直接看这边文章的代码可能看不到什么东西。
负载均衡就是根据负载均衡策略,把负载分摊给多个操作单元去执行
Ribbon是Spring Cloud的一个组件, 它可以让我们使用一个注解就能轻松的搞定负载均衡
在RestTemplate 的生成方法上添加@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
修改OrderServiceImpl服务调用的方法
@Service
public class OrderServiceImpl implements IOrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Override
public Order createOrder(Long productId, Long userId) {
Order order = new Order();
order.setUid(userId);
order.setOid(1L);
order.setNumber(1);
order.setUsername("James");
Product product = restTemplate.getForObject("http://product-service/get?pid="
+productId,Product.class);
order.setPid(productId);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
return order;
}
}
为了更直观看到请求是进行负载均衡了,我们修改一下ProductController代码
@RestController
@Slf4j
public class ProductController {
@Autowired
private ProductService productService;
@Value("${server.port}")
private String port;
//商品信息查询
@RequestMapping("/product")
public Product findByPid(@RequestParam("pid") Long pid) {
log.info("接下来要进行{}号商品信息的查询", pid);
Product product = productService.findByPid(pid);
product.setPname(product.getPname()+" data from port:"+port);
log.info("商品信息查询成功,内容为{}", JSON.toJSONString(product));
return product;
}
}
4.当我重复访问地址:http://localhost:8082/save?pid=1&uid=1的时候,发现在交替访问端口为8081和8083的两个product-service服务,其实是因为默认的负载均衡策略就是(ZoneAvoidanceRule的策略)
com.netflix.loadbalancer.IRule , 具体的负载策略如下图所示:
策略名 | 策略描述 | 实现说明 |
---|---|---|
BestAvailableRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | 先过滤掉故障实例,再选择并发较小的实例; | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | 根据相应时间分配一个weight,相应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权 |
RetryRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | 轮询方式轮询选择server | 轮询index,选择index对应位置的server |
RandomRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ZoneAvoidanceRule(默认) | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |
我们可以通过修改配置来调整Ribbon的负载均衡策略,在order-server项目的application.yml中增加如下配置:
product-service: # 调用的提供者的名称
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
5.当我再次重复访问地址:http://localhost:8082/save?pid=1&uid=1的时候,发现在随机访问端口为8081和8083的两个product-service服务,证明上面配置的RandomRule策略是起作用了
ribbon实现的关键点是为ribbon定制的RestTemplate,ribbon利用了RestTemplate的拦截器机制,在拦截器中实现ribbon的负载均衡。负载均衡的基本实现就是利用applicationName从服务注册中心获取可用的服务地址列表,然后通过一定算法负载,决定使用哪一个服务地址来进行http调用。
大白话版:
1.通过反射获取请求传过来的参数,url=http://product-service/get?pid=1
2.按照规则去进行切割,得到product-service服务的名称
3.在启动项目时就从注册中心拉取了一份服务地址的清单,127.0.0.1:8081 127.0.0.1:8083
4.根据负载均衡策略选择一个节点的服务,再把product-service替换成该服务对应的 IP:端口,
如选择了8081,那么替换的到http://127.0.0.1:8081/get?pid=1
5.最后还是通过RestTemplate去发送请求
Feign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务
一样简单, 只需要创建一个接口并添加一个注解即可。
Nacos很好的兼容了Feign, Feign默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负
载均衡的效果。
在shop-order-server项目的pom文件加入Fegin的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在启动类OrderServer.java上添加Fegin的扫描注解,注意扫描路径(默认扫描当前包及其子包)
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderServer {
public static void main(String[] args) {
SpringApplication.run(OrderServer.class,args);
}
}
在shop-order-server项目中新增接口ProductFeignApi
package cn.test.feign;
//name的名称一定要和商品服务的服务名保持一致
@FeignClient(name = "product-service")
public interface ProductFeignApi {
@RequestMapping("/product")
public Product findByPid(@RequestParam("pid") Long pid);
}
修改OrderServiceImpl.java的远程调用方法
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderDao orderDao;
@Autowired
private ProductFeignApi productFeignApi;
@Override
public Order createOrder(Long productId,Long userId) {
//测试是否超时重试
System.out.println("测试是否超时重试");
Order order = new Order();
order.setUid(userId);
order.setOid(1L);
order.setNumber(1);
order.setUsername("James");
//通过Ribbon实现远程调用
//Product product = restTemplate.getForObject("http://product-service/product?pid="+productId,Product.class);
//通过Feign实现远程调用
Product product = productFeignApi.findByPid(productId);
System.out.println("查询成功:product " + JSON.toJSONString(product));
order.setPid(productId);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
return order;
}
}
重启订单服务,并验证.
超时配置
feign:
client:
config:
default:
connectTimeout: 3000
readTimeout: 3000
重试次数配置
https://github.com/Netflix/ribbon/wiki/Getting-Started#the-properties-file-sample-clientproperties
product-service: # 调用的提供者的名称
ribbon:
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 0
1.通过反射拿到代理类实现的接口 ProductFeignApi
2.通过反射拿到接口上注解@ Feignclient注解,井且把注解中的值取出来, product-service
3.通过反射拿到接口中方法井且拿到接口中方法上的注解@ Requestmapping,井且把值给取出来,/get
4.把方法中参数注解中的值也获取出来id
5.拼接路径http://product-service/get?pid=1
6.根据服务名上本地去找 product-service对应节点信息 localhost:8081 localhost:8083
7.根据你配置 ribbon的负载均衡策选择一个节点, localhost:8081
8.把服务名称替换成http://localhost::8081/get?pid=1
拿到接口中方法井且拿到接口中方法上的注解@ Requestmapping,井且把值给取出来,/get
9.使用 Resttemplate发送请求