服务发现组建,也是配置服务器
用来解决服务a如何找到服务b、并且管理微服务
下载应用程序,并打开,以启动Nacos Server
加依赖
org.springframework.cloud
spring-cloud-starter-alibaba-nacos-discovery
${latest.version}
没有注解。
写配置,制定Nacos Server的地址、服务名称
spring:
cloud:
nacos:
discovery:
#指定nacos server的地址
server-addr: localhost:8848
application:
#服务名称尽量用-,不要用_及特殊字符
name: user-center
namespace:主要用于隔离,例如隔离开发环境,生产环境,测试环境等。不能跨namespace调用实例。
group:把不同的微服务放到一个分组里,方便管理。(alibaba0.9.0中没有用到)
service:微服务
cluster:集群,对指定微服务的虚拟划分
instance:微服务实例
namespace和cluster:
namespace需要在控制台上先创建好
spring:
cloud:
nacos:
discovery:
#写控制台创建好的namespace的UUID,不能写名称!!
namespace: XXX
#集群名称自己起
cluster-name: xxx
Nacos的数据描述信息,如服务版本、权重、容灾策略、负载均衡策略、鉴权配置、各种自定义标签。
从作用范围看,分为服务级别元数据、集群级别元数据、实例级别元数据。
目前spring cloud alibaba0.9.0中,服务和集群级别元数据暂时用不上。
作用:
提供描述信息。
让微服务调用更加灵活。(微服务的版本控制,例如v1client只能调用v1server之类)
如何设置?1.控制台设置2.配置文件
spring:
cloud:
nacos:
discovery:
metadata:
#key: value的形式即可
a: b
haha: hehe
version: v1
实现方式:
服务器端负载均衡:
客户端负载均衡:
是开源的客户端负载均衡器。
ribbon会从nacos上获取要调用的服务地址列表,通过自有算法算出一个实例,交给restTemplate去请求。
不需要加依赖,因为nacos-discovery包中已经包含ribbon
写注解
//注解需要写在restTemplate上,为restTemplate整合ribbon
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
没有配置
当restTemplate请求时,ribbon会自动把server在nacos中的url的http地址转化成server自己的服务名称,例如
http://localhost:8080/users/{id}会变为http://user-center/users/{id}
默认是ZoneAvoidanceRule
java代码的方式:
建两个类,一个用于绑定用户服务与ribbon规则类,另一个用于写ribbon规则的实现类
@Configuration
@RibbonClient(name = "user-center" , configuration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration{
}
注意:RibbonConfiguration类所在的包要和启动类所在包平行,不能在一起!!否则会引发所有ribbon规则都会变成修改的,父子上下文相关问题。
因为@Configuration里面包含@Component,而启动类上的@SpringBootApplication中包含@ComponentScan,这个注解会扫描启动类所在的包及包下面的所有@Component、@Controller、@Repository、@Service等等,包括@Configuration,所以ribbon的配置类一定不能被启动类扫描到,否则会产生父子上下文扫描重叠的问题,导致事务不生效。在ribbon中会导致所有规则都变为改规则。
@Configuration
public class RibbonConfiguration{
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}
配置属性的方式:
user-center:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
所以对于代码配置基于代码,更加灵活,但更麻烦,有父子上下文的问题,线上修改还得重新打包发布。
属性配置的方式更简单也更直观,优先级更高,但极端场景下没有代码配置灵活。
所以微服务在能满足要求的前提下,尽量使用属性配置。在同一微服务中尽量保持单一性,不要一会属性一会代码。增加定位问题的复杂度。
方式一:让@ComeponentScan上下文重叠。(如上文所说,但完全不建议)
方式二:在RibbonClient注解类中,把@RibbonClient后面加s,configuration改为defaultConfiguration,且删掉name即可。
@Configuration
@RibbonClients(defaultConfiguration = RibbonConfiguration.class)
public class UserCenterRibbonConfiguration{
}
上面表格中ribbon组成的各项均可,用java代码方式,仿照rule规则,在实现类中写实现方法即可。
配置属性的话:
ribbon默认情况下是懒加载的,所以导致首次请求都过慢,可通过饥饿加载解决。
写配置:
ribbon:
eager-load:
#开启饥饿加载
enabled: true
#为哪些服务开启饥饿加载,也是细粒度配置,用逗号隔开
clients: user-center
权重取值在0-1之间,值越大代表这个实例被调用的几率越大。但在ribbon的规则中,并没有nacos权重相关,所以需自己写规则。
public class NacosWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
//读取配置文件,并初始化NacosWeightedRule
}
@Override
public Server choose(Object key) {
try {
//loadBalancer是ribbon的入口,想要的基本都有
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
//实现负载均衡算法
//可拿到服务发现相关api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//nacos client自动通过基于权重的负载均衡算法给我们选择一个实例。
Instance instance = namingService.selectOneHealthyInstance(name);
return new NacosServer(instance);
} catch (NacosException e) {
return null;
}
}
}
配置参照上面即可。
修改权重大小在nacos控制台,针对实例设置权重的数字即可。
server和client端配置
spring:
cloud:
discovery:
#设置集群名称,例如:HAOZI
cluster-name: HAOZI
写规则
public class NacosSameClusterWeightedRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
@Override
public Server choose(Object key) {
try {
//拿到配置文件中的集群名称 HAOZI
String clusterName = nacosDiscoveryProperties.getClusterName();
//loadBalancer是ribbon的入口,想要的基本都有
BaseLoadBalancer loadBalancer = (BaseLoadBalancer) this.getLoadBalancer();
//想要请求的微服务的名称
String name = loadBalancer.getName();
//可拿到服务发现相关api
NamingService namingService = nacosDiscoveryProperties.namingServiceInstance();
//1.找到指定服务的所有实例 A
//true代表只拿健康的实例
List instances = namingService.selectInstances(name , true);
//2.过滤出相同集群下的所有实例 B
List sameClusterInstances = instances.stream()
.filter(instance -> Objects.equals(instance.getClusterName(), clusterName))
.collect(Collectors.toList());
//3.如果B是空,就用A
List instancesToBeChosen = new ArrayList<>();
if(CollectionUtils.isEmpty(sameClusterInstances)){
instancesToBeChosen = instances;
}else{
instancesToBeChosen = sameClusterInstances;
}
//4.基于权重的负载均衡算法,返回一个实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(instancesToBeChosen);
return new NacosServer(instance);
} catch (NacosException e) {
log.error("异常",e);
return null;
}
}
}
//由于没有基于权重的负载均衡方法调用,所以通过源码找到,但源码的getHostByRandomWeight是protected的,
//所以写类继承该类并通过子类调用该方法并返回
class ExtendBalancer extends Balancer {
public static Instance getHostByRandomWeight2(List hosts){
return getHostByRandomWeight(hosts);
}
}
写配置
spring:
cloud:
nacos:
metadata:
# 自己这个实例的版本
version: v1
# 允许调用的提供者版本
target-version: v1
写规则
public class NacosFinalRule extends AbstractLoadBalancerRule {
@Autowired
private NacosDiscoveryProperties nacosDiscoveryProperties;
@Override
public Server choose(Object key) {
// 负载均衡规则:优先选择同集群下,符合metadata的实例
// 如果没有,就选择所有集群下,符合metadata的实例
try {
//通过配置文件拿到集群名称
String clusterName = this.nacosDiscoveryProperties.getClusterName();
//通过元数据拿到可以调用的版本信息
String targetVersion = this.nacosDiscoveryProperties.getMetadata().get("target-version");
DynamicServerListLoadBalancer loadBalancer = (DynamicServerListLoadBalancer) getLoadBalancer();
//微服务名称
String name = loadBalancer.getName();
//拿到服务相关api
NamingService namingService = this.nacosDiscoveryProperties.namingServiceInstance();
// 1. 查询所有实例 A
List instances = namingService.selectInstances(name, true);
List metadataMatchInstances = instances;
// 2. 筛选元数据匹配的实例 B
// 如果配置了版本映射,那么只调用元数据匹配的实例
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 clusterMetadataMatchInstances = metadataMatchInstances;
// 3. 筛选出同cluster下元数据匹配的实例 C
// 如果配置了集群名称,需筛选同集群下元数据匹配的实例
if (StringUtils.isNotBlank(clusterName)) {
clusterMetadataMatchInstances = metadataMatchInstances.stream()
.filter(instance -> Objects.equals(clusterName, instance.getClusterName()))
.collect(Collectors.toList());
// 4. 如果C为空,就用B
if (CollectionUtils.isEmpty(clusterMetadataMatchInstances)) {
clusterMetadataMatchInstances = metadataMatchInstances;
log.warn("发生跨集群调用。clusterName = {}, targetVersion = {}, clusterMetadataMatchInstances = {}", clusterName, targetVersion, clusterMetadataMatchInstances);
}
}
// 5. 随机选择实例
Instance instance = ExtendBalancer.getHostByRandomWeight2(clusterMetadataMatchInstances);
return new NacosServer(instance);
} catch (Exception e) {
log.warn("发生异常", e);
return null;
}
}
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
}
}
public class ExtendBalancer extends Balancer {
/**
* 根据权重,随机选择实例
*
* @param instances 实例列表
* @return 选择的实例
*/
public static Instance getHostByRandomWeight2(List instances) {
return getHostByRandomWeight(instances);
}
}