目前主流的负载方案分为以下两种:
Spring Cloud Ribbon是基于Netflix Ribbon 实现的一套客户端的负载均衡工具,Ribbon客户端组件提供一系列的完善的配置,如超时,重试等。通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。
<!--添加ribbon的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<dependency>
注意:如果引入了 nacos-discovery依赖,可以不用再引入ribbon依赖。因为 nacos-discovery依赖了ribbon
@Configuration
public class RestConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@Autowired
private RestTemplate restTemplate;
// version2
@GetMapping("/findOrderByUserId/{userId}")
public R findOrderByUserId(@PathVariable("userId") Integer userId) {
String url = "http://mall-order/order/findOrderByUserId/" + userId;
// import com.baomidou.mybatisplus.extension.api.R;
R result = restTemplate.getForObject(url, R.class);
return result;
}
此时的restTemplate已经具备了自动获取服务列表并根据负载均衡算法确定出要调用服务的能力。我们只需要根据服务名称调用服务即可,不再需要关心服务具体的url以及调用哪个服务。
其实关于请求调用,Spring提供有一个扩展点拦截器:ClientHttpRequestInterceptor。
Ribbon中使用LoadBalancerInterceptor实现了这个拦截器,会拦截请求,根据负载均衡算法,将原始URL中中的"mall-order"替换成服务实例ip:port,然后再进行最终调用。
@LoadBalanced其实就是一个修饰符,和@Qualifier一样,比如我们给user1打上@LoadBalanced:
@Bean("user1")
@LoadBalanced
User user1() {
return new User("1", "a");
}
@Bean("user2")
User user2() {
return new User("2", "b");
}
然后controller中给List users打上@LoadBalanced注解:
@Autowired(required = false)
@LoadBalanced
private List<User> users = Collections.emptyList();
和@Qualifier注解效果一样,只有user1被注入进了List,user2没有修饰符,没有被注入进去。
补充:
@Autowired可以将符合条件的bean注入到List和Map。
@Bean("user1")
User user1() {
return new User("1", "a");
}
@Bean("user2"))
User user2() {
return new User("2", "b");
}
@Autowired(required = false)
private List<User> users = Collections.emptyList();
@Autowired(required = false)
private Map<String,User> userMap = new HashMap<>();
此时就可以自动将两个bean注入进来,当注入map的时候,map的key必须是String类型,然后bean name将作为map的key,本例,map中将有两个key分别为user1和user2,value分别为对应的User Bean实例。
然后我们给user1和user2分别打上@Qualifier修饰符:
@Bean("user1")
@Qualifier("valid")
User user1() {
return new User("1", "a");
}
@Bean("user2")
@Qualifier("invalid")
User user2() {
return new User("2", "b");
}
然后将user list 和user map分别也打上@Qualifier修饰符:
@Autowired(required = false)
@Qualifier("valid")
private List<User> users = Collections.emptyList();
@Autowired(required = false)
@Qualifier("invalid")
private Map<String,User> userMap = new HashMap<>();
那么所有标注了@Qualifier(“valid”)的user bean都会自动注入到List users中去(本例是user1),所有标注了@Qualifier(“invalid”)的userbean都会自动注入到Map
另外当spring容器中有多个相同类型的bean的时候,可以通过@Qualifier来进行区分,以便在注入的时候明确表明你要注入具体的哪个bean,消除歧义。
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnMissingClass({"org.springframework.retry.support.RetryTemplate"})
static class LoadBalancerInterceptorConfig {
LoadBalancerInterceptorConfig() {
}
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
}
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {
return (restTemplate) -> {
List<ClientHttpRequestInterceptor> list = new ArrayList(restTemplate.getInterceptors());
// 给加了@LoadBalanced注解的restTemplate添加拦截器,使其对服务列表拉取和负载均衡有确定唯一服务的能力
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list);
};
}
}
@Configuration
public class RibbonConfig {
/**
* 全局配置
* 指定负载均衡策略
* @return
*/
@Bean
public IRule() {
// 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机权重)
return new NacosRule();
}
}
修改application.yml:
# 被调用的微服务名
mall-order:
ribbon:
# 指定使用Nacos提供的负载均衡策略(优先调用同一集群的实例,基于随机&权重)
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
可以利用@RibbonClient指定微服务及其负载均衡策略。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class, DruidDataSourceAutoConfigure.class})
//@RibbonClient(name = "mall-order", configuration = RibbonConfig.class)
//配置多个 RibbonConfig不能被@SpringbootApplication的@CompentScan扫描到,否则就是全局配置的效果
@RibbonClients(value = {
// 在SpringBoot主程序扫描的包外定义配置类
@RibbonClient(name = "mall-order", configuration = RibbonConfig.class),
@RibbonClient(name = "mall-account", configuration = RibbonConfig.class)
})
public class MallUserRibbonDemoApplication {
public static void main(String[] args) {
SpringApplication.run(MallUserRibbonDemoApplication.class, args);
}
}
注意:此处有坑。不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方,否则自定义的配置类就会被所有的RibbonClients共享。不建议这么使用,推荐yml方式。
通过实现IRule接口可以自定义负载策略,主要的选择服务逻辑在 choose 方法中。
@Slf4j
public class NacosRandomWithWeightRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
String serviceName = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
try {
//nacos基于权重的算法
Instance instance = namingService.selectOneHealthyInstance(serviceName);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("获取服务实例异常:{}", e.getMessage());
e.printStackTrace();
}
return null;
}
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
@Bean
public IRule ribbonRule() {
return new NacosRandomWithWeightRule();
}
修改application.yml:
# 被调用的微服务名
mall-order:
ribbon:
# 自定义的负载均衡策略(基于随机&权重)
NFLoadBalancerRuleClassName: com.tuling.mall.ribbondemo.rule.NacosRandomWithWeightRule
Ribbon默认懒加载,意味着只有在发起调用的时候才会创建客户端。在进行服务调用的时候,如果网络情况不好,第一次调用会超时。
开启饥饿加载,解决第一次调用慢的问题:
ribbon:
eager-load:
# 开启ribbon饥饿加载
enabled: true
# 配置mall-user使用ribbon饥饿加载,多个使用逗号分隔
clients: mall-order