服务注册与负载均衡
SpringCloud Alibaba
服务发现者与服务消费者是成对存在的
服务发现
服务中心会定时的向注册在自己的微服务发送心跳检测,防止该微服务突然崩溃导致的雪崩问题。
Nacos
区别于Eureka的另外一种服务发现组件,Nacos是阿里巴巴团队出品的一款开源服务发现组件,安装可以直接百度Nacos下载。
安装完成后可以在控制台中启动,启动完成之后访问8848端口。
<!--xxxx-spring-boot-starter--> 其他项目对spring的支持
<!--spring-cloud-starter-{spring cloud子项目的名称}-[{模块名称}]--> springcloud自己模块下的
<!--feign spring-cloud-starter-openfeign-->
<!--sentinel spring-cloud-starter-alibaba-sentinel-->
<!-- Nacos依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<!--整合spring cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--整合spring cloud alibaba-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>0.9.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
不需要加注解了(之前Eureka需要加Client注解),在配置文件中写如下配置。
spring:
cloud:
nacos:
discovery:
# 指定nacos server的地址
server-addr: localhost:8848
application:
# 服务名称尽量用-,不要用_,不要用特殊字符
name: user-center
这里要注意,微服务是注册到了我们从控制台启动的Nacos,所以不需要在我们的微服务中启动一个Eureka一样的服务。
DiscoverClient
使用该对象可以查询到所有当前注册在服务中心的所有服务,只需要直到该服务的名称,适用于Eureka/Zookeeper/consul等服务发现端。
new DiscoverClient.getInstances("");//这里填写ServiceId,也就是应用名称,注册到服务端的那个
使用DiscoverClient可以获取对应服务的URL地址,所以可以配合RestTemplate来调用其他微服务。
String target = discoverClient.getInstances("xxxx")
.stream().map( c -> c.getUri().toString() + "/users/{id}" )
.findFirst()
.orElseThrow( () -> new IllegalArgumentException("xxxx异常") );
经典的Lambda表达式,超级炫酷。
服务发现的领域模型
namespace : 用以区别环境,是一种隔离的状态,如生产环境,开发环境,测试环境等。
cluster :集群,一个集群下有多个微服务运行。
ps : 需要在yml中配置,在nacos节点下配置。
元数据
nacos概念,可以提供描述信息,可以让微服务调用更加灵活。
Nacos数据(如配置和服务)描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签 (label),从作用范围来看,分为服务级别的元信息、集群的元信息及实例的元信息。
两种配置方式
- 控制台指定 (在Nacos控制台中指定)
- 配置文件指定
metadata:
x: y
负载均衡
- 服务器端负载均衡 Nginx ==> Tomcat
- 客户端负载均衡 类似于Eureka和Nacos的负载均衡
nginx是一个HTTP Server ,侧重关心HTTP协议层面的传输和访问控制
Tomcat是一个应用服务器,比如:将应用部署到tomcat服务器
手动(低配版)实现负载均衡
使用随机数在获取的微服务列表中随机选择URL来实现负载均衡、
使用Ribbon实现负载均衡
Ribbon是Netflix开源的客户端负载均衡器,为我们提供了负载均衡的算法。
在spring-cloud-starter-alibaba-nacos-discovery
依赖中已经包含了Ribbon的依赖。
添加注解@LoadBances
@Bean
@LoadBances//使用Ribbon扩展RestTemplate
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
再次使用restTemplate的getForObject()
,在其中将URL的地址换成对应微服务的serviceId即可调用相关的微服务(会自动实现负载均衡)。
相关URL为:http://微服务名称/接口名称
Ribbon组成
Ribbon在没有Zone的环境下是默认使用轮询的算法实现负载均衡的。
Ribbon自定义配置一(实现细粒度配置Ribbon负载均衡)
Java代码实现
父子上下文不可以重叠,重叠后子上下文就会覆盖掉父上下文,导致所有的使用到Ribbon的地方都会采用自己所定义的Ribbon规则,无法实现微服务A使用单独的Ribbon规则。
@Configuration //这个包要配置在SpringBoot主类之外
public class ribbonConfiguration(){
@Bean
public IRule getRule(){
//设置随机的ribbon规则
return new RandomRule();
}
}
@Configuration //配置在SpringBoot主类之下。并且设置对应的serviceId
@RibbonClient(name = "user-center" , confiuration=ribbonConfiguration.class)
public class UserCenterRibbonConfiguration{
}
配置文件实现
user-center:
ribbon:
NFLoadBalanceRuleClassName: com.netflix.loadbalancer.RandomRule
Ribbon自定义配置二(实现全局配置Ribbon负载均衡)
修改注解@RibbonClient(name = "user-center" , confiuration=ribbonConfiguration.class)
为@RibbonClients( defaultConfiuration=ribbonConfiguration.class)
commond + option + b 查看当前IDEA接口所有的实现类
Ribbon支持的配置项
上图中所有的配置项都可以去自定义,都可以通过Java以及配置文件的方式去实现。
Java代码实现
按照代码实现,就按照IRule去new一个新的实现类即可。
配置文件实现
Ribbon的懒汉与饿汉模式
Ribbon在默认时是按照懒汉模式加载的,也就是第一次访问的时候才进行配置与初始化,只要在配置文件中开启饥饿模式就可以实现饿汉模式。
ribbon:
eager-load:
enabled: true
Ribbon配置权重
Ribbon可以为实例配置权重,从而可以根据不同实例所在环境不同分配不同的优先级。
如何自定义实现Ribbon配置权重 ====> Ribbon结合Nacos配置权重
Ribbon实现同集群优先级调用
如上问一样,使用自定义的Ribbon实现对应算法,继承AbstractLoadBalanceRule,实现对应的方法。
使用NacosDiscoveryProperties可以获取配置文件中规定的属性,例如规定了什么集群。
- 找到指定服务的所有实例 A
- 过滤出相同集群下的所有实例 B
- 如果 B 是空 ,就用 A
- 基于权重的负载均衡算法,返回一个实例
package com.itmuch.contentcenter.configuration;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.client.naming.core.Balancer;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.Server;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.alibaba.nacos.NacosDiscoveryProperties;
import org.springframework.cloud.alibaba.nacos.ribbon.NacosServer;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
// 拿到配置文件中的集群名称 BJ
String clusterName = nacosDiscoveryProperties.getClusterName();
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
// 想要请求的微服务的名称
String name = loadBalancer.getName();
// 拿到服务发现的相关API
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
// 1. 找到指定服务的所有实例 A
List<Instance> instances = namingService.selectInstances(name, true);
// 2. 过滤出相同集群下的所有实例 B
List<Instance> sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
// 3. 如果B是空,就用A
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);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("发生异常了", e);
return null;
}
}
}
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List<Instance> hosts) {
return getHostByRandomWeight(hosts);
}
}
Ribbon支持基于元数据的版本管理
如何自定义实现Ribbon配置权重 ====> Ribbon支持基于元数据的版本管理
Nacos中的namespace作用
namespace实现了一个隔离的效果,相当于在一个容器上实现了几种不同的服务注册,不同namespace上的项目都发现不了其他的namespace。