《Spring Cloud 微服务实战》 学习笔记
服务治理
服务治理可以说是微服务架构中最核心和基础的模块,它主要来实现各个微服务实力的自动化注册与发现。
服务注册
在服务治理框架中,通常会构建一个注册中心
,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按照服务名
分类组织服务清单。
比如: 有服务A和服务B,
服务A运行了两个节点,分别运行在server1:8000
和server2:8001
服务B运行了3个节点, 分别运行在server1:9000
,server2:9001
和server3:9002
当这些服务均向注册中心注册后,注册中心会有如下的一个服务清单:
服务名 | 位置 |
---|---|
服务A | server1:8000, server2:8001 |
服务B | server1:9000,server2:9001,server3:9002 |
服务发现
由于在服务治理框架下,服务间的调用不再通过
指定具体的实例地址来发现,而是通过服务名
发起请求调用实现。
所以, 服务调用方
在调用服务提供方
的接口时,并不知道服务的具体地址。因此,调用方需要向服务注册中心咨询服务
,并获取所有服务的实例清单
,以实现对具体服务实例的访问。
比如,现有服务C希望调用服务A,服务C就需要向服务注册中心发起
咨询服务
,服务中心会将服务A的位置清单返回给服务C,C会获取上述配置的两个可用位置:server1:8000
和server2:8001
,当服务C要发起调用的时候,便从该清单中以某种轮训策略去除一个位置来进行服务调用(这就是后续会分析客户端负载均衡
)。实际的框架为了性能等因素,不会采取每次请求都想注册中心获取服务的方式,而且不同的应用场景在缓存和服务剔除等机制上也有一些不同的实现策略。
Netflix Eureka
Spring Cloud Eureka使用 Netflix Eureka来实现服务注册与发现。
Eureka服务端
Eureka服务端
,我们也称之为注册中心,它通其他服务注册中心一样,支持高可用配置。
它依托强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。
如果Eureka以集群模式
部署,当集群中有分片出现故障时,那么Eureka就转入自我保护模式。它允许在分片故障期间继续提供服务的发现和注册,当故障分片恢复运行时,集群中的其他分片会把他们的状态再次童虎回来。
Eureka客户端
Eureka客户端
,主要处理服务的注册与发现。客户端服务通过注解和参数配置的方式,嵌入在客户端应用程序的代码中,在应用程序中,Eureka客户端向注册中心注册自身提供的服务并周期性的发送心跳来更新它的服务租约
。 同时,它也能从服务端查询注册的服务信息并把它们缓存到本地并周期性地刷新服务状态
搭建服务注册中心
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
启动类
在启动类上添加@EnableEurekaServer
注解启动一个服务注册中心。
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
配置文件
server:
port: 1111
eureka:
instance:
hostname: localhost
client:
#是否向注册中心注册自己
register-with-eureka: false
#是否从注册中心查询服务
fetch-registry: false
service-url:
# 通过改地址注册、发现服务
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
配置文件中添加了几个属性:
eureka.client.register-with-eureka
: 由于该应用时注册中心,所以设置为false,表示不向注册中心注册自己。eureka.client.fetch-registry
: 由于注册中心的职责是维护服务实例,它并不需要去检索调用服务,所以也设置为false.
访问
完成上述的配置后,通过http://localhost:1111/
便可看到Eureka信息面板:
其中Instances currently registered with Eureka
面板为空,表示当前注册中心没有任何注册服务。
注册服务
在完成注册中心的搭建之后,接下来我们尝试搭建一个spring-boot应用,并将它加入到Eureka的服务治理体系去。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
启动类
添加注解@EnableDiscoveryClient
用于向注册中心注册服务。
@SpringBootApplication
@EnableDiscoveryClient
public class Client1Application {
public static void main(String[] args) {
SpringApplication.run(Client1Application.class, args);
}
}
配置文件
server:
port: 8080
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
spring:
application:
#### 注册到注册中心的应用名。不能带有 下划线
name: cloud-eureka-client
启动
启动client后,会在Eureka注册中心看到注册信息。
编写HelloController
@RestController
public class HelloController {
//注意,引入的包不要搞错
@Autowired
private org.springframework.cloud.client.discovery.DiscoveryClient discoveryClient;
@Value("${spring.application.name}")
private String appName;
@RequestMapping("/hello")
public String index() {
List<ServiceInstance> instanceInfos = discoveryClient.getInstances(appName);
instanceInfos.forEach(instanceInfo -> {
System.out.println(instanceInfo.getInstanceId() +":"+instanceInfo.getServiceId());
});
return "hello world";
}
}
通过http://localhost:8080/hello
访问接口,后台打印如下日志:
高可用注册中心
在微服务架构中,需要充分考虑发生故障的情况,所以生产环境中必须对各个组件进行高可用部署,对微服务如此,对服务注册中心也一样。
Eureka Server的设计以爱是就考虑了高可用问题。在前面Eureka Server单节点的配置中,我们设置过下面两个参数,让注册中心不注册自己:
eureka:
client:
register-with-eureka: false
fetch-registry: false
Eureka Server高可用实际上就是讲自己作为服务向其他注册中心注册自己,这样就形成了一组互相注册的服务注册中心,达到高可用的效果。
如下:
application-peer1.yml
: 作为peer1服务中心的配置,并将serverUrl指向peer2
:
server:
port: 1111
eureka:
instance:
hostname: peer1
client:
service-url:
defaultZone: http://peer2:1112/eureka/
application-peer2.yml
: 作为peer2服务中心的配置,并将serverUrl指向peer1
:
server:
port: 1112
eureka:
instance:
hostname: peer2
client:
service-url:
defaultZone: http://peer1:1111/eureka/
修改hosts
文件:
127.0.0.1 peer1
127.0.0.1 peer2
分别使用如下命令,启动应用:
java -jar chap03-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=peer1 java -jar chap03-eureka-server-1.0-SNAPSHOT.jar --spring.profiles.active=peer2
访问peer1,显示如下:
访问peer2,显示如下:
客户端注册到集群
在服务注册中心集群化后,服务提供方(客户端)需要简单修改一下配置才能将服务注册到Eureka Server集群中:
eureka:
client:
service-url:
### 多个以逗号分隔开
defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
通过上面的操作,我们实现了服务注册中心(高可用模式)的搭建。
以及将服务注册到注册中心。
服务发现与消费
本文之前的内容,提供了 服务注册中心以及服务提供者(“cloud-eureka-client”),下面我们尝试构建一个服务消费者,它主要完成两个目标:发现服务以及消费服务
.
- 发现服务:由 Eureka的客户端完成
- 消费服务: 由
Ribbon
完成。
Ribbon
Ribbon是一个基于HTTP和TCP的客户端负载均衡器
.它可以通过客户端中配置的服务端列表(ribbonServerList)
去轮训访问以达到负载均衡的效果。
当Ribbon和Eureka联合使用时,ribbonServerList
会被扩展成从Eureka中心获取服务端列表。同时它也将职责委托给Eureka来确定服务端是否已经启动。后续会有专门的文章来介绍。
下面我们通过构建一个简单的实例,来看看Eureka的服务治理体系下,是如何实现服务的发现与消费的。
- 首先将Eureka Server 启动,并将hello-service 启动两个实例:
java -jar chap03-eureka-client-1.0-SNAPSHOT.jar --server.port=8080
java -jar chap03-eureka-client-1.0-SNAPSHOT.jar --server.port=8090
此时注册中心信息如下:
服务消费端(ribbo)
pom.xml
同client配置,需额外添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
配置文件
server:
port: 9000
eureka:
client:
service-url:
### 多个以逗号分隔开
defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
instance:
prefer-ip-address: true
spring:
application:
#### name 不能带有 下划线
name: ribbon-client
启动类
注册 RestTemplate
,并使用@LoadBalance
注解,标注使用负载均衡。
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonClient1Application {
public static void main(String[] args) {
SpringApplication.run(RibbonClient1Application.class, args);
}
@Bean
//负载均衡
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
ConsumerController
@RestController
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/ribbon-consumer")
public String consumer() {
//CLOUD-EUREKA-CLIENT 为服务提供方在Eureka中注册的应用名称。
return restTemplate.getForEntity("http://CLOUD-EUREKA-CLIENT/hello",String.class).getBody();
}
}
应用
通过http://localhost:9000/ribbon-consumer
请求接口,多次请求,并观察两个hello-service
的后台日志,可以发现两个节点都接收到了部分请求。