文章目录
服务治理
服务治理可以说是微服务架构中最核心和基础的模块,它主要用来实现各个微服务实例的自动化注册与发现。
在服务数量不多的时候通过静态配置就能很好的完成服务的调用配置。随着业务的发展,系统功能拓展,相应的微服务应用不断增加,静态配置的方式会变得越来越难以维护,并且面对业务的不断发展,集群规模、服务位置、服务命名等这些很难通过手工维护的方式去解决为,保障系统的正常运行,必然需要有一个中心化的组件完成对各个服务的整合,所以就产生了围绕服务注册与服务发现机制来完成对微服务应用实例的自动化管理的服务治理框架和产品。
- 服务注册:在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,将主机与端口号、版本号、通信协议等一些附加信息告知注册中心,注册中心按服务名分类组织服务清单。
- 服务发现:由于在服务治理框架下,调用方不再通过指定地址的方式而是通过服务名发起请求调用,调用方不知道具体的服务实例位置。因此调用方需要向服务中心获取所有服务的实例清单,以实现对具体服务实例的调用。
一、Eureka简介
Eureka包含两个组件:Eureka Server和Eureka Client。
- Eureka Server,即服务注册中心。支持高可用配置。提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
- Eureka Client,主要处理服务的注册与发现。客户端通过注解和参数配置的方式,嵌入在客户端应用程序的代码里,在应用程序运行时,Eureka客户端向服务中心注册自身提供的服务,并会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。同时,他也能从服务端查询当前注册的服务信息并把他们缓存到本地并周期性的刷新服务状态。
简单示例如下图:
二、简单Demo
1、父项目
新建一个Maven项目或者直接使用Spring Initializr创建一个SpringBoot项目。
用父项目只用来确定整体的jar包版本管理。
父项目pom大致如下:
<!-- 统一管理jar包版本 -->
<packaging>pom</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
......
<dependencyManagement>
2、公共模块
公共模块来定义通用的API和实体类
实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private long id;
private String name;
private String address;
}
通用反馈信息:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
private int code;
private String message;
private T data;
public CommonResult(int code, String message) {
this(code, message, null);
}
}
3、EurekaServer
服务注册中心:
服务注册中心的代码比较简单,主要为配置文件:
新建EurekaServer如下:
3.1 application.yml
下面是在本地测试的EurekaServer中的一个的配置文件。
server:
port: 7001
eureka:
instance:
hostname: eurekaserver7001 #Eureka服务端实例名称,需要做本地ip映射,修改host
client:
register-with-eureka: false #表示不向服务端注册自身
fetch-registry: false #false表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
#设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址
#注册到另一个Eureka注册中心,单机模式可以不需要此属性。
defaultZone: http://eurekaserver7002:7002/eureka/
3.2 pom.xml主要引入的jar包
<dependencies>
....
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
....
</dependencies>
启动类
3.3 Application.java
@SpringBootApplication
//说明此应用为EurekaServer端
@EnableEurekaServer
public class EurekaServerApplication7001 {
public static void main (String[] args) {
SpringApplication.run(EurekaServerApplication7001.class, args);
}
}
3.4 多注册中心配置
如果是在idea开发的话可以在resource目录下新建两个application-xxx.yml文件,文件中的Server.port不同,并且defaultZone也不相同,互相注册嘛(eclipse启动时加上–spring.profiles.active=xxx来指定生效的配置文件),并修改启动配置:
在打开界面此处输入后缀名即可启动不同配置的注册中心。
3.5 为注册中心添加认证
是不是感觉可以直接登录很不安全~~来了,加个简单的认证
- 首先 pom.xml引入spring-security 相关jar包
- 第二步,applicatiom.yml修改开启认证,defaultZone也要修改,要不注册不到第二个注册中心了
Tips: EurekaClient在注册到EurekaServer端的时候也需要加上认证。 - 添加Java配置WebSecurityConfig
默认情况下添加SpringSecurity依赖的应用每个请求都需要添加CSRF token才能访问,Eureka客户端注册时并不会添加,所以需要配置/eureka/**路径不需要CSRF token
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure (HttpSecurity http) throws Exception {
// http.csrf().disable();
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
4、EurekaClient
4.1、服务提供者
对外只提供一个查询User的服务,简单~~
- 客户端的application.yml
server:
port: 8001
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://alen:123456@eurekaserver7001:7001/eureka,http://alen:123456@eurekaserver7002:7002/eureka
spring:
application:
name: user-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: org.gjt.mm.mysql.Driver #MySql驱动包
url: jdbc:mysql://127.0.0.1:3306/alen?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.practice.entity #所有Entity别名类所在包
- pom.xml主要引入的jar包
<dependencies>
.....
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--自定义的通用模块-->
<dependency>
<groupId>com.practice</groupId>
<artifactId>common-api</artifactId>
<version>${project.version}</version>
</dependency>
.....
</dependencies>
- 启动类
@SpringBootApplication
@EnableEurekaClient
public class UserserviceApplication {
public static void main (String[] args) {
SpringApplication.run(UserserviceApplication.class, args);
}
}
其他的正常写。
4.2、服务消费者
此处消费者为Order服务,需要调用User查询服务
- application.yml
server:
port: 80
eureka:
client:
fetch-registry: true
register-with-eureka: true
#定义去eureka服务端获取服务列表的时间间隔
registry-fetch-interval-seconds: 30
service-url:
defaultZone: http://alen:123456@eurekaserver7001:7001/eureka,http://alen:123456@eurekaserver7002:7002/eureka
instance:
lease-renewal-interval-in-seconds: 30
#定义服务多久不去续约认为服务失效
lease-expiration-duration-in-seconds: 90
#所在区域
metadata-map.zone: SH
#服务主机名称
hostname: EurekaClient
#是否优先使用ip来作为主机名
prefer-ip-address: true
spring:
application:
name: OrderClient
#用户服务地址
service-url:
user-service: http://user-service
- 通过RestTemple访问
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private RestTemplate restTemplate;
@Value("${service-url.user-service}")
private String userServiceUrl;
@GetMapping("/user/get/{id}")
@ResponseBody
public CommonResult<com.practice.entity.User> getUser (@PathVariable Long id) {
return restTemplate.getForObject(userServiceUrl + "/user/get/{1}", CommonResult.class, id);
}
- 启动类
@SpringCloudApplication
@EnableEurekaClient
public class EurekaClientApplication {
public static void main (String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
简单测试,不写了–
三、服务治理机制
以下图为例,我们来看一下Eureka实现的服务治理体系是如何运作起来的:
- 服务注册中心-1和服务注册中心-2,互相注册组成了高可用集群。
- 服务提供者启动了两个实例分别注册到了两个注册中心上。
- 服务消费者可以从注册中心获取服务列表并调用服务。
1、服务提供者
- 服务注册
服务提供者在启动的时候会通过发送REST请求的方式将自己注册到EurekaServer上,同时带上自身服务的一些元数据信息。Eureka Server接收到这个REST请求之后将元数据信息存储起来。
在服务注册时,需要设置eureka.client.register-with-eureka=true(默认为true),若设置为false则不会启动注册操作 - 服务同步
在上图中,两个服务提供者分别被注册到了两个不同的服务注册中心上。此处由于服务注册中心之间互相注册为服务,当服务提供者发送注册请求到一个服务注册中心上市,它会将该请求转发给集群中相连的其他注册中心,从而实现注册中心之间的服务同步。 - 服务续约
在注册完服务之后,服务提供者会维护一个心跳用来持续高速Eureka Server可用状态,以防止EurekaServer的“剔除任务”将该服务实例从服务列表中排除出去,此项操作被称为“服务续约”。
2、服务消费者
- 获取服务
到服务消费者被启动时,会发送一个REST请求给服务注册中心,来获取注册中心维护的服务清单。为了性能考虑,EurekaServer会维护一份只读的服务清单来返回给客户端,同时该缓存清单会每隔30秒更新一次。 - 服务调用
服务消费者在获取服务清单后,通过服务名可以获得具体提供服务的实例名和该实例的元数据信息。 - 服务下线
当客户端正常关闭操作时,会触发一个服务下线的REST请求给EurekaServer,服务端在接受到该请求后将服务状态职位下线(DOWN),并把该下线事件传播出去。
3、服务注册中心
- 失效剔除
有些时候,服务并不会正常下线。为了从服务列表中将这些无法提供服务的实例剔除,EurekaServer在启动的时候会创建一个定时任务,默认每隔一段时间(60秒)将当前清单中超时(默认90秒)没有续约的服务剔除出去。 - 自我保护
服务注册到EurekaServer后,会维护一个心跳连接,告诉EurekaServer自己还活着。EurekaServer在运行期间会统计心跳失败的比例在15分钟内是否低于85%,如果处于低于的情况,EurekaServer会将当前的实力信息保护起来,让这些实例不会过期,尽可能保护这些注册信息。但是如果在保护期间实例出现问题,那么客户端很容易调用到有问题的服务实例,会出现调用失败的情况,所以客户端不需要有容错机制,比如可以使用请求重试、断路器等机制。