服务注册中心Eureka
- Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
- Eureka包含两个组件:Eureka Server和Eureka Client。
- Eureka Server 提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到
- Eureka Client 是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
- 应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
- Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
开始创建Eureka 服务注册中心
首先我们先创建一个父级工程。 先定义一下SpringBoot和SpringCloud和Alibaba Cloud 的版本问题。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
通过dependecyManagement
来定义版本
创建服务注册中心模块 sgg-eureka-server7001
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
配置application.yml 文件
server:
port: 7001
spring:
application:
name: eureka7001
eureka:
instance:
hostname: eureka7001
client:
register-with-eureka: false # 关闭eureka 的自我注册
fetch-registry: false
创建eureka 的服务启动类
Eureka7001App.java
@SpringBootApplication
@EnableEurekaServer
public class Eureka7001App {
public static void main(String[] args) {
SpringApplication.run(Eureka7001App.class, args);
}
}
运行启动类。启动成功之后访问 eureka 的服务注册中心 http://localhost:7001
可以看到这个页面,说明我们的服务配置中心已经启动成功啦。 接下来我们需要配置一个 服务提供者和一个服务消费者
服务提供者 sgg-payment8001
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
配置 application.yml 文件
server:
port: 8001
spring:
application:
name: sgg-payment-service # 注册到服务注册中心的名称。消费者通过名称获取服务提供者的列表
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka # 服务注册中心的地址
fetch-registry: true
register-with-eureka: true
instance:
instance-id: sgg-payment-${server.port} # 配置服务提供者的单个实例的名称
prefer-ip-address: true # 配置服务提供者的 ip 地址显示
创建启动类 PayApp.java
@SpringBootApplication
@EnableEurekaClient
public class PayApp {
public static void main(String[] args) {
SpringApplication.run(PayApp.class, args);
}
}
创建一个实体类
@Data
public class Payment {
private Long id;
private String serial;
}
创建一个统一风格返回类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> implements Serializable {
private Integer code;
private String message;
private T data;
public CommonResult(Integer code, String message) {
this(code, message, null);
}
}
创建接口类提供给服务消费者使用
@RestController
@Slf4j
@RequestMapping(value = "payment")
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@PostMapping(value = "create")
public CommonResult create(@RequestBody Payment payment) {
log.info("=================支付流水插入成功: [ {} ], 插入条数 [ {} ]=====================", payment.getSerial(), result);
return new CommonResult(200, "支付流水插入成功", serverPort);
}
@GetMapping(value = "/{id}")
public CommonResult getPayment(@PathVariable(value = "id") Long id) {
Payment payment = new Payment(id, UUID.randomUUID().toString());
log.info("=================查询ID: [ {} ]查询结果: [ {} ]================", id, payment);
payment.setSerial(serverPort+payment.getSerial());
return new CommonResult(200, "查询成功", serverPort);
}
}
启动消费提供者。 访问接口。 然后再次访问 服务注册中心地址 http://localhost:7001
就会发现我们的服务已经注册到该 eureka服务注册中心了。 接下来我们继续尝试访问两个接口。
创建服务消费者 sgg-consumer80
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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>cn.fllday</groupId>
<artifactId>ssg-api-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
配置 application.yml
server:
port: 80
spring:
application:
name: ssg-consumer-service # 服务名称。
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka # 服务注册中心的地址
instance:
instance-id: sgg-consumer80 # 配置该服务名下实例的唯一名称
prefer-ip-address: true # 配置该服务是否显示 ip 地址
创建启动类
@SpringBootApplication
@EnableEurekaClient
public class OrderApp {
public static void main(String[] args) {
SpringApplication.run(OrderApp.class, args);
}
}
创建 接口。并且去访问 服务提供者开放的接口, 首先我们需要使用到 RestTemplate
, 是Spring用于同步client端的核心类,简化了与http服务的通信,并满足RestFul原则,程序代码可以给它提供URL,并提取结果。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具。当然你也可以 通过setRequestFactory属性切换到不同的HTTP源,比如Apache HttpComponents、Netty和OkHttp。
@Configuration
public class AppConfig {
@Bean
@LoadBalanced // 表明开启负载均衡, 就是比如说我们的服务名字叫 SGG-PAYMENT-SERVICE ,
// 但是这个服务名有两个服务实例都是这个名字,这个时候我们就需要使用
//这个注解,然后 RestTemplate 就会获取到实例列表。根据负载均衡策略去访问对应的服务实例。
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
@RestController
@Slf4j
@RequestMapping(value = "consumer/payment")
public class OrderController {
public static final String PAYMENT_URL = "http://SGG-PAYMENT-SERVICE/payment/";
@Autowired
RestTemplate restTemplate;
@GetMapping(value = "/{id}")
public CommonResult getOrderPayment(@PathVariable(value = "id")Long id){
ResponseEntity<CommonResult> forEntity = restTemplate.getForEntity(PAYMENT_URL + id, CommonResult.class);
return forEntity.getBody();
}
@GetMapping(value = "/create")
public CommonResult addPayment(Payment payment) {
// 注意这边传输的是一个实体类,所以我们需要在服务提供者处添加一个注解 @RequestBody
CommonResult body = restTemplate.postForEntity(PAYMENT_URL + "/create", payment, CommonResult.class).getBody();
return body;
}
}
启动启动类,访问 配置中心
可以发现,我们的 服务消费者也注册进来了。哈哈这个时候去访问接口。
访问创建的接口。
到这里有的同志就会问了,直接使用localhost:80/payment
也可以访问啊? 为什么要加一个注册中心多此一举呢? 没错,单机的状态下确实是可以,但是如果是集群的话,是不是就特别不方便了呢? 那么我们复制服务提供者重新改名字为。sgg-payment8002
修改 application.yml 文件中的端口号为 8002。
启动启动类。 这个时候再去查看 eureka 注册中心
就会发现我们的 名字叫 sgg-payment-service
服务名称下有两个实例。 如果这个时候,使用 ip地址加端口号的方式访问, 就没有办法负载均衡访问到我们的服务接口了。 我们继续访问消费者的接口。 多访问几次 看看返回的 serverPort 的不同。
可以看到每次访问。 端口号都有变化。 一次是 8001 一次是 8002 , 说明我们的 RestTemplate 使用的是 轮训的机制进行负载均衡配置的。
Eureka的高可用集群。
我们现在只有一个Eureka服务注册中心,当我们的Eureka中心挂掉了怎么办呢,我们来手动停止掉Eureka服务试试。
我这里已经停止掉了 Eureka 的服务注册中心。 我们继续访问之前的接口
发现我们的接口没有挂掉。还是可以访问的。 这是为啥呢?
原因就是当我们的eureka挂掉之后,虽然服务注册中心死了,但是我们的原来的服务提供者和消费者启动的时候,
注册中心是正常的,各个服务提供者和消费者已经保存了我们的实例之间的ip端口映射, 所以这个时候访问是没有问题的。
也就是说,虽然注册中心死了,但是每个服务都已经保存了一份缓存在各自的实例中。 各个实例时采用一段时间去注册中心更新这份缓存。
我们现在可以停掉一个服务试试。
可以发现。我们的消费者访问这个借口的时候。我们停掉的那个服务就会报错。 因为我们实例中的缓存无法去配置中心更新实例的信息。我们现在再重新启动服务注册中心。
再吃访问 这个接口发现还是报错。这是为啥呢? 我们刚刚不是说了。每个客户端会在一段时间内去更新这个缓存, 所以这段时间内没有去更新所以导致缓存中的实例列表不是最新的。 我们只需要等上一段时间。自然会更新成功,这个时间默认为 30S。
过了这个时间之后,我们再次访问接口
这里就不会出现报错的问题啦。
为了解决单机版的eureka挂掉的问题。 所以我们可以创建 eureka 的集群版。 保证 eureka 注册中心的高可用。
将 eureka 服务注册中心复制一份。 修改配置文件 。
原来的 7001 端口号的 eureka 配置如下
server:
port: 7001
spring:
application:
name: eureka7001
eureka:
instance:
hostname: eureka7001
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:7002/eureka/
server:
enable-self-preservation: false # 关闭自我保护模式
eviction-interval-timer-in-ms: 2000
复制的eureka 7002 端口的配置如下
server:
port: 7002
eureka:
instance:
hostname: eureka7002
client:
service-url:
defaultZone: http://localhost:7002/eureka/
fetch-registry: false
register-with-eureka: false
server:
enable-self-preservation: false # 关闭自我保护模式
eviction-interval-timer-in-ms: 2000
### eureka 的自我保护机制, 通常微服务自身的故障关闭只会导致个别服务出现故障,一般不会出现大面积故障,而网络故障则会导致eureka server在短时间内无法收到大批心跳
#考虑到这个问题,eureka设置了一个阀值,当挂掉的服务超过阀值时,server认为很大程度上出现了网络故障,将不在删除心跳过期的服务。
顺序启动 7001, 7002, 8001 , 8002, 80 服务
访问 http://localhost:7001 和 http://localhost:7002
页面都是一致的 , 7002 我就不放图了。
此时, 我们将 7001 和 8001 服务都给停掉。 然后看看 80 访问服务有没有及时的更新服务实例缓存。 先等个 30 秒啊。别着急 ,嘿嘿。
现在重新访问 7002 , 可以看到我们的实例少了一个, 通过访问 80 的接口,也可以发现我们的接口可以正常访问。 不过要注意的是记得关闭 eureka 的自我保护机制。
我在application.yml 的注释里面写了, 如果不关闭掉自我保护机制, 即使你的服务挂掉了, 但是eureka 可能还是会认为他还活着,想给他一个抢救的机会。 但是他可能已经永远的走了。
差不多就这些吧。 感谢 B站 尚硅谷的老师们 哈哈哈 ~~~~~