手写负载均衡
private final DiscoveryClient discoveryClient;
@GetMapping("/{id}")
public ShareDto findById(@PathVariable Integer id){
Share share = shareService.findById(id);
List<ServiceInstance> instances = discoveryClient.getInstances("user-center");
// String url = instances.stream()
// .map(instance->instance.getUri()+"/user/{id}")
// .findFirst()
// .orElseThrow(()->new IllegalArgumentException("当前没有实例!"));
List<String> targetUrls = instances.stream()
.map(instance -> instance.getUri() + "/user/{id}")
.collect(toList());
//随机获取指定一个,达到负载均衡的目的
int i = ThreadLocalRandom.current().nextInt(targetUrls.size());
String url = targetUrls.get(i);
UserDto userDto = restTemplate.getForObject(
url,
UserDto.class,
share.getId());
log.info("请求的目标地址{}",url);
ShareDto shareDto = new ShareDto();
BeanUtils.copyProperties(share,shareDto);
shareDto.setWxNickname(userDto.getWxNickname());
return shareDto;
}
使用Allow parallel run 启动多个应用,注意改server port
使用Ribbon实现负载均衡
RestTemplate 添加负载均衡注解
@MapperScan("com.fly")
@SpringBootApplication
public class ContentApplication {
public static void main(String[] args) {
SpringApplication.run(ContentApplication.class, args);
}
@LoadBalanced //负载均衡
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
使用,不需要discoveryClient了
/**
* 使用Ribbon 实现负载均衡
* @param id
* @return
*/
@GetMapping("/{id}")
public ShareDto findById(@PathVariable Integer id){
Share share = shareService.findById(id);
UserDto userDto = restTemplate.getForObject(
"http://user-center/user/{userId}",
UserDto.class,
share.getId());
ShareDto shareDto = new ShareDto();
BeanUtils.copyProperties(share,shareDto);
shareDto.setWxNickname(userDto.getWxNickname());
return shareDto;
}
Ribbon组成
Ribbon内置的负载均衡规则
默认为轮询RoundRobinRule
自定义颗粒度配置java
com.fly.contentcenter.configuration.UserCenterConfiguration
@Configuration
@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
public class UserCenterConfiguration {
}
com.fly.ribbonconfigration.RibbonConfiguration
@Configuration
public class RibbonConfiguration {
@Bean
public IRule randomRule(){
//随机规则
return new RandomRule();
}
}
RibbonConfiguration 要放到SpringBootApplication启动类之外,防止被扫描到,出现父子上下文重叠问题,如果父子上下文扫描重叠就会导致事务不生效。
一般我们在Spring的配置文件application.xml中对Service层代码配置事务管理,可以对Service的方法进行AOP增强或事务处理如事务回滚,但是遇到一个问题,在Controller类中调用Service层方法,配置的事务管理会失效,查询相关资料发现原因。其实Spring和SpringMVC俩个容器为父子关系,Spring为父容器,而SpringMVC为子容器。也就是说application.xml中应该负责扫描除@Controller的注解如@Service,而SpringMVC的配置文件应该只负责扫描@Controller,否则会产生重复扫描导致Spring容器中配置的事务失效。
在Spring的applicationContext.xml文件中配置配上下面这段代码:
<context:component-scan base-package="com.chuxin.platform,com.chuxin.core">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
而在SpringMVC的dispatcher-servlet.xml配置文件中配置下面这段代码:
<context:component-scan base-package="com.chuxin.platform.controller" use-default-filters="false">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
自定义颗粒度配置 配置属性方式,推荐使用这种
# <clientName>
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
两种方式对比
全局配置
方式一:将UserCenterConfiguration放到springboot可以扫描的地方,不推荐
方式二:
@Configuration
//@RibbonClient(name = "user-center",configuration = RibbonConfiguration.class)
@RibbonClients(defaultConfiguration = RibbonConfiguration.class) //全局配置
public class UserCenterConfiguration {
}
其它配置
### 开启饥饿加载,因为懒加载第一次请求很慢 ``` # 默认是懒加载,开启饥饿加载 ribbon: eager-load: enabled: true clients: user-center # 多个用,分割 ``` ### 扩展Ribbon-支持Nacos权重 ``` /** * @see com.alibaba.nacos.api.naming.NamingService#selectOneHealthyInstance * * 扩展Ribbon支持Nacos权重 */ @Slf4j public class NacosWeightRandomRule extends AbstractLoadBalancerRule { @Autowired private NacosDiscoveryProperties discoveryProperties; @Override public void initWithNiwsConfig(IClientConfig iClientConfig) {}
@Override
public Server choose(Object o) {
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) this.getLoadBalancer();
String name = loadBalancer.getName();//想要请求的微服务的名称
try {
Instance instance = discoveryProperties.namingServiceInstance().selectOneHealthyInstance(name);
log.info("选中的instance = {}", instance);
return new NacosServer(instance);
}catch (NacosException e) {
log.error("发生异常", e);
return null;
}
}
}
@Configuration
public class RibbonConfiguration {
@Bean
public IRule randomRule(){
//随机规则
// return new RandomRule();
return new NacosWeightRandomRule();
}
}
### 扩展Ribbon-同一集群优先调用
/**
-
扩展Ribbon-同一集群优先调用
*/
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}@Override
public Server choose(Object o) {
try {
// 配置文件的集群名称
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();NamingService namingService = nacosDiscoveryProperties.namingServiceInstance(); // 找到指定服务的所有实例 List<Instance> instances = namingService.selectInstances(name, true); //过滤出相同集群下的所有实例 List<Instance> sameClusterInstances = instances.stream() .filter(instance -> Objects.equals(instance.getClusterName(), clusterName)) .collect(Collectors.toList()); List<Instance> instancesToBeChosen = new ArrayList<>(); if (CollectionUtils.isEmpty(sameClusterInstances)){ instancesToBeChosen = instances; log.warn("发生跨集群的调用, name = {}, clusterName = {}, instances = {}", name, clusterName, instances ); }else { instancesToBeChosen = sameClusterInstances; } // 4. 基于权重的负载均衡算法,返回1个实例 Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen); log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance); } catch (NacosException e) { e.printStackTrace(); } return null;
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(Listhosts) {
return getHostByRandomWeight(hosts);
}
}
### 扩展Ribbon支持基于元数据的版本管理
/**
- 扩展Ribbon支持基于元数据的版本管理
- spring:
- cloud:
-
nacos:
-
metadata:
-
# 自己这个实例的版本
-
version: v1
-
# 允许调用的提供者版本
-
target-version: v1
*/
@Slf4j
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
@Override
public Server choose(Object o) {
try {
// 配置文件的集群名称
String clusterName = nacosDiscoveryProperties.getClusterName();
String targetVersion = nacosDiscoveryProperties.getMetadata().get("target-version");
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 找到指定服务的所有实例
List<Instance> instances = namingService.selectInstances(name, true);
List<Instance> metadataMatchInstances = instances;
// 如果配置了版本映射,那么只调用元数据匹配的实例
if (StringUtils.isNotBlank(targetVersion)){
metadataMatchInstances = instances.stream()
.filter(instance -> Objects.equals(targetVersion, instance.getMetadata().get("version")))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(metadataMatchInstances)) {
log.warn("未找到元数据匹配的目标实例!请检查配置。targetVersion = {}, instance = {}", targetVersion, instances);
return null;
}
}
List<Instance> clusterMetadataMatchInstances = metadataMatchInstances;
// 如果配置了集群名称,需筛选同集群下元数据匹配的实例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
}
}
// 基于权重的负载均衡算法,返回1个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
log.info("选择的实例是 port = {}, instance = {}", instance.getPort(), instance);
} catch (NacosException e) {
e.printStackTrace();
}
return null;
}
static class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
}
### 不能跨namespace调用