1. 微服务的注册中⼼
注册中⼼可以说是微服务架构中的”通讯录“ ,它记录了服务和服务地址的映射关系。在分布式架构中, 服务会注册到这⾥,当服务需要调⽤其它服务时,就这⾥找到服务的地址,进⾏调⽤。
1.1 注册中⼼的主要作⽤
服务注册中⼼(下称注册中⼼)是微服务架构⾮常重要的⼀个组件,在微服务架构⾥主要起到了协调者的⼀个作⽤。
注册中⼼⼀般包含如下⼏个功能:
1. 服务发现: 服务注册/反注册:保存服务提供者和服务调⽤者的信息 服务订阅/取消订阅:服务调⽤者订阅服务提供者的信息,最好有实时推送的功能 服务路由(可选):具有筛选整合服务提供者的能⼒。
2. 服务配置: 配置订阅:服务提供者和服务调⽤者订阅微服务相关的配置 配置下发:主动将配置推送给服务提供者和服务调⽤者
3. 服务健康检测 1. 微服务的注册中⼼ 1.1 注册中⼼的主要作⽤ ● ● ● ● ●3 检测服务提供者的健康情况
1.2 常⻅的注册中⼼
Zookeeper
zookeeper它是⼀个分布式服务框架,是Apache Hadoop 的⼀个⼦项⽬,它主要是⽤来解决分布式应 ⽤中经常遇到的⼀些数据管理问题,如:统⼀命名服务、状态同步服务、集群管理、分布式应⽤配置项 的管理等。简单来说zookeeper=⽂件系统+监听通知机制。
Eureka
Eureka是在Java语⾔上,基于Restful Api开发的服务注册与发现组件,Springcloud Netflix中的重要组 件。
Consul
Consul是由HashiCorp基于Go语⾔开发的⽀持多数据中⼼分布式⾼可⽤的服务发布和注册服务软件, 采 ⽤Raft算法保证服务的⼀致性,且⽀持健康检查。
Nacos
Nacos是⼀个更易于构建云原⽣应⽤的动态服务发现、配置管理和服务管理平台。简单来说 Nacos 就是 注册中⼼ + 配置中⼼的组合,提供简单易⽤的特性集,帮助我们解决微服务开发必会涉及到的服务注册 与发现,服务配置,服务管理等问题。 Nacos 还是 Spring Cloud Alibaba 组件之⼀,负责服务注册与 发现。
2. nacos简介
Nacos 致⼒于帮助您发现、配置和管理微服务。Nacos 提供了⼀组简单易⽤的特性集,帮助您快 速实现动态服务发现、服务配置、服务元数据及流量管理。 nacos的作⽤就是⼀个注册中⼼,⽤ 来管理注册上来的各个微服务
2.1 nacos实战⼊⻔
我们依然使用上篇文章的三层架构案例,来说明nacos的使用:
2.2.1 搭建nacos环境
第1步: 安装nacos
下载地址: https://github.com/alibaba/nac
os/releases
下载zip格式的安装包,然后进⾏解压缩操作,nacos解压缩即安装
第2步: 启动nacos
#切换⽬录
cd nacos/bin
#命令启动
startup.cmd -m standalone
或者直接双击startup.cmd运⾏
第3步: 访问nacos
打开浏览器输⼊http://localhost:8848/nacos,即可访问服务, 默认密码是nacos/nacos
2.2.2 将商品微服务注册到nacos
注册步骤:
①在pom.xml中添加nacos的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.apesource</groupId>
<artifactId>springcloud_alibaba_nacos_8_19</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shop_product</artifactId>
<dependencies>
<!-- springboot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- shop-common -->
<dependency>
<groupId>com.apesource</groupId>
<artifactId>shop_common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- nacos客户端 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
</project>
② 在主类上添加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableDiscoveryClient
public class ShopProductApplication {
public static void main(String[] args) {
SpringApplication.run(ShopProductApplication.class, args);
}
}
③在application.yml中添加nacos服务的地址
server:
port: 8081
spring:
application:
name: service-product #注册的服务名称与这里一致
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///testdb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
cloud: #添加nacos服务的地址
nacos:
discovery:
server-addr: 127.0.0.1:8848
④启动服务, 观察nacos的控制⾯板中是否有注册上来的商品微服务
在服务列表中,我们可以看到service-product成功注册至nacos:
2.2.3 将订单微服务注册到nacos
① 在pom.xml中添加nacos的依赖
<!-- nacos客户端 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
②在主类上添加@EnableDiscoveryClient注解
@SpringBootApplication
@EnableDiscoveryClient
public class ShopOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ShopOrderApplication.class, args);
}
}
③在application.yml中添加nacos服务的地址
server:
port: 8091
spring:
application:
name: service-order
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:///testdb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
username: root
password: 123456
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
④启动服务, 观察nacos的控制⾯板中是否有注册上来的订单微服务
⑤ 修改OrderController, 借助DiscoveryClient实现微服务调用
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private IOrderService orderService;
//获取nacos中服务的元数据
@Autowired
private DiscoveryClient discoveryClient;
/下单
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
//从nacos中获取服务地址
//可以直接通过服务名(service-product)来获取product的注册信息,实现调用
ServiceInstance serviceInstance=discoveryClient.getInstance("service-product").get(0);
String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
//通过restTemplate调用商品微服务
Product product = restTemplate.getForObject( "http://"+url+"/product/" + pid, Product.class);
//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.createOrder(order);
return order;
}
}
重新启动product和order的服务类,通过http:localhost:8091/order/prod/1进行下单操作:
3.服务调⽤Ribbon⼊⻔
经过以上的学习,已经实现了服务的注册和服务发现。当启动某个服务的时候,可以通过HTTP的形式 将信息注册到注册中⼼,并且可以通过SpringCloud提供的⼯具获取注册中⼼的服务列表。但是服务之间 的调⽤还存在很多的问题,如何更加⽅便的调⽤微服务,多个微服务的提供者如何选择,如何负载均衡 等。
3.1 Ribbon概述
3.1.1 什么是Ribbon
是 Netflixfa 发布的⼀个负载均衡器,有助于控制 HTTP 和 TCP客户端⾏为。在 SpringCloud 中, Nacos⼀般配合Ribbon进⾏使⽤,Ribbon提供了客户端负载均衡的功能,Ribbon利⽤从Nacos中读取到 的服务信息,在调⽤服务节点提供的服务时,会合理的进⾏负载。
在SpringCloud中可以将注册中⼼和Ribbon配合使⽤,Ribbon⾃动的从注册中⼼中获取服务提供者的列 表信息,并基于内置的负载均衡算法,请求服务。
3.1.2 Ribbon的主要作⽤
( 1)服务调⽤
基于Ribbon实现服务调⽤, 是通过拉取到的所有服务列表组成(服务名-请求路径的)映射关系。借助 RestTemplate 最终进⾏调⽤
( 2)负载均衡
当有多个服务提供者时,Ribbon可以根据负载均衡的算法⾃动的选择需要调⽤的服务地址
3.2 基于Ribbon实现订单调⽤商品服务
3.2.1 坐标依赖
在springcloud提供的服务发现的jar中以及包含了Ribbon的依赖。所以这⾥不需要导⼊任何额外的坐标。
3.2.2 ⼯程改造
(1)服务提供者:控制台打印查询商品的信息
(2)服务消费者:
①在创建RestTemplate⽅法 上添加 @LoadBalanced 注解
@SpringBootApplication
@EnableDiscoveryClient
public class ShopOrderApplication {
public static void main(String[] args) {
SpringApplication.run(ShopOrderApplication.class, args);
}
@Bean
@LoadBalanced//负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
②通过服务名称消费使⽤:restTemplate .getForObject("http://商品服务名称/product/1" , Product.class);
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private IOrderService orderService;
//获取nacos中服务的元数据
@Autowired
private DiscoveryClient discoveryClient;
//下单
@RequestMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid") Integer pid) {
//通过restTemplate调用商品微服务
String url = "service-product";
Product product = restTemplate.getForObject( "http://"+url+"/product/" + pid, Product.class);
//下单(创建订单)
Order order = new Order();
order.setUid(1);
order.setUsername("测试用户");
order.setPid(pid);
order.setPname(product.getPname());
order.setPprice(product.getPprice());
order.setNumber(1);
orderService.createOrder(order);
return order;
}
}
重新启动product和order的服务类,通过http:localhost:8091/order/prod/1进行下单操作:
4.服务调⽤Ribbon⾼级
4.1 负载均衡概述
4.1.1 什么是负载均衡
在搭建⽹站时,如果单节点的 web服务性能和可靠性都⽆法达到要求;或者是在使⽤外⽹服务时,经常 担⼼被⼈攻破,⼀不⼩⼼就会有打开外⽹端⼝的情况,通常这个时候加⼊负载均衡就能有效解决服务问 题。
负载均衡是⼀种基础的⽹络服务,其原理是通过运⾏在前⾯的负载均衡服务,按照指定的负载均衡算 法,将流量分配到后端服务集群上,从⽽为系统提供并⾏扩展的能⼒。
负载均衡的应⽤场景包括流量包、转发规则以及后端服务,由于该服务有内外⽹个例、健康检查等功 能,能够有效提供系统的安全性和可⽤性。
4.2 基于Ribbon实现负载均衡
4.2.1 搭建多服务实例
通过idea再启动⼀个 shop-product 微服务,设置其端⼝为8082
//1.
//2.
//3.分别启动两次服务器验证效果,并查看两个控制台发现已轮询的⽅式调⽤了商品服务 ShopProductApplication和ShopProductApplication2均被调用了3次(轮询)
4.2.2 负载均衡策略
Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接⼝为 com.netflix.loadbalancer.IRule ,实现⽅式如下:
①com.netflix.loadbalancer.RoundRobinRule :以轮询的⽅式进⾏负载均衡
②com.netflix.loadbalancer.RandomRule :随机策略
③com.netflix.loadbalancer.RetryRule :重试策略
④com.netflix.loadbalancer.WeightedResponseTimeRule :权重策略。会计算每个服务的权重,越 ⾼的被调⽤的可能性越⼤
⑤com.netflix.loadbalancer.BestAvailableRule :最佳策略。遍历所有的服务实例,过滤掉故障实 例,并返回请求数最⼩的实例返回
⑥com.netflix.loadbalancer.AvailabilityFilteringRule :可⽤过滤策略。过滤掉故障和请 求数超过阈 值的服务实例,再从剩下的实⼒中轮询调⽤
⑦ZoneAvoidanceRule:以区域可⽤的服务器为基础进⾏服务器的选择。使⽤Zone对服务器进 ⾏分类,这个Zone可以理解为⼀个机房、⼀个机架等。⽽后再对Zone内的多个服务做轮询
⾃定义负载均衡策略
⽅式1:定义⼀个新的IRule:(全局设置)
@Bean
public IRule randomRule(){
return new RandomRule();
}
⽅式2:在yml配置⽂件中配置:(局部设置)
service-product:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
5.Nacos配置管理
Nacos除了可以做注册中⼼,同样可以做配置管理来使⽤
5.1 统⼀配置管理
当微服务部署的实例越来越多,达到数⼗、数百时,逐个修改微服务配置就会让⼈抓狂,⽽且很容易出 错。我们需要⼀种统⼀配置管理⽅案,可以集中管理所有实例的配置。
Nacos⼀⽅⾯可以将配置集中管理,另⼀⽅可以在配置变更时,及时通知微服务,实现配置的热更新。
5.1.1 在nacos中添加配置⽂件
//以shop-product项目为例:
//1.在nacos配置列表添加配置文件
//2.填写配置信息
5.1.2 从微服务拉取配置
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项⽬启动。 但如果尚未读取application.yml,⼜如何得知nacos地址呢?
因此spring引⼊了⼀种新的配置⽂件: bootstrap.yaml⽂件,会在application.yml之前被读取,流程如 下:
配置实现步骤
1)引⼊nacos-config依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
2)添加bootstrap.yaml
不能使⽤原来的application.yml作为配置⽂件,⽽是新建⼀个bootstrap.yml作为配置⽂件(里面写的引用信息(引用nacos配置))
配置⽂件优先级(由⾼到低):
bootstrap.properties -> bootstrap.yml -> application.properties -> application.yml
spring:
application:
name: service-product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中⼼地址
file-extension: yaml # 配置⽂件格式
4) 在nacos中添加配置(把以前写在yml文件中的配置信息写在nacos的配置文件中)
5)启动ShopProductApplication服务类:
//发现查询功能依然可以使用
5.2配置热更新
我们最终的⽬的,是修改nacos中的配置后,微服务中⽆需重启即可让配置⽣效,也就是配置热更新。 要实现配置热更新,可以使⽤两种⽅式:
在nacos配置中⼼添加配置:
通过http://localhost:8081/test测试,成功获取到了nacos配置文件中的appName
5.2.1.⽅式⼀
在@Value注⼊的变量所在类上添加注解@RefreshScope,并在productController中通过@value逐个注入config.appName:
@RestController
@RefreshScope
public class ProductController {
@Autowired
private IProductService productService;
@Value("${config.appName}" )
private String appName;
@GetMapping("/test")
public String nacosConfingTest1() {
return appName;
}
//商品信息查询
@RequestMapping("/product/{pid}")
public Product product(@PathVariable("pid") Integer pid) {
Product product = productService.getById(pid);
System.out.println("商品:"+product);
return product;
}
}
添加了@RefreshScope注解后,我们在想改变appName时,可以直接在配置中心改动,而且不需要重启服务来更新数据,直接刷新测试页面即可更新数据。
5.2.2.⽅式⼆
硬编码⽅式
@RestController
public class NacosConfigController {
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping("/nacos-config-test2")
public String nacosConfingTest2() {
return applicationContext.getEnvironment().getProperty("config.app
Name");
}
}
5.3 配置共享
当配置越来越多的时候,我们就发现有很多配置是重复的,这时候就考虑可不可以将公共配置⽂件 提取出来
5.3.1 同服务内配置共享
实现步骤:
1. 新建⼀个以 spring.application.name 命名的配置⽂件,然后将其所有环境的公共配置放在⾥⾯
2. 新建⼀个名为service-product-test.yaml配置存放测试环境的配置
3. 新建⼀个名为service-product-dev.yaml配置存放开发环境的配置
4. 在两个环境⽂件中配置独有信息
5.更改bosotstrap.yml文件:
spring:
application:
name: service-product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yaml # 配置文件格式
profiles:
active: dev # 环境标识,开发环境
运行测试:成功获取到了service-product-dev.yml文件中的信息(devv)
此时,service-product-dev.yml和service-product-test.yml共享service-product.yml配置文件
5.3.2 不同微服务共享配置
不同服务之间实现配置共享的原理类似于⽂件引⼊,就是定义⼀个公共配置,然后在当前配置中引入。
1. 在nacos中定义⼀个DataID为all-service.yaml的配置,⽤于所有微服务共享:
2. 修改bootstrap.yaml
spring:
application:
name: service-product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yaml # 配置文件格式
shared-dataids: all-service.yaml # 配置要引⼊的配置
refreshable-dataids: all-service.yaml # 配置要实现动态配置
profiles:
active: test # 环境标识,开发环境
3. 启动商品微服务进⾏测试