个人博客地址:
http://xiaohe-blog.top
文章目录
1. 什么是注册中心
Eureka是一个注册中心,那么注册中心是什么呢?
在微服务中,注册中心就像是一个卖房中介,房东们将自己的房子信息放在中介那里,客户在中介那里查看房子信息,看中了哪一套房,中介再牵线客户和房东签合同(因为房子不是中介的)。
服务提供者将自己能提供的服务放在注册中心,服务调用者在注册中心查看是否有自己想要的服务,如果有,直接调用服务。但是服务本身并不是注册中心的,所以调用者必须直接调用提供者的服务。
简而言之,注册中心就是收录服务的地方。
市面上的注册中心,大量使用的 :Eureka、Nacos、Consul、Zookeeper…
当然注册中心不仅仅是存服务这一个作用,他还兼顾着 服务注册后如何被发现、服务异常时如何解决、服务发现后如何路由、服务如何扩展等一系列功能。
2. Eureka注册中心
Eureka有两个角色 :
- Eureka Server(Eureka 服务端)。@EnableEurekaServer
- Eureka Client(Eureka 客户端)。@EurekaEurekaClient
其中 Eureka Client 又分为服务提供者和服务调用者,但是因为服务的提供者明天又会变成服务的调用者,所以这两者统称为客户端。(好比你家卖鱼,你是服务提供者,但是你想吃猪肉也要去买,你是服务消费者)
3. Eureka 案例
3.1准备工作
这里我们使用黑马的案例。
有两张表 :order、user,对应实体类如下 :
@Data
public class Order {
private Long id;
private Long price;
private String name;
private Integer num;
private Long userId; // 对应user表中的id
private User user;
}
@Data
public class User {
private Long id;
private String username;
private String address;
}
可以看到order表中包含user用户信息。
将order-service和user-service分为两个模块,给两张表提供最简单的查询功能。
order 和 user 的controller分别如下 :
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
// 根据id查询订单并返回
return orderService.queryOrderById(orderId);
}
}
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
在启动服务之后,使用RESTFul风格的url路径分别访问order、user服务先试试项目是否有问题
order-service和user-service分别为两个模块,那么我们如何借助Eureka实现“在order-service中调用user-service”这个功能呢?
3.2 RestTemplate
想要实现上述功能,如果我们能在order-service模块中发送http请求:http: //localhost:8081/user/{id} 就好了,那么我们如何在java代码中发送http请求呢?
java提供了RestTemplate这个类来发送http请求。将RestTemplate放到spring容器中,在order-service模块中使用RestTemplate来发送 “http: //localhost:8081/user/” + order.getUserId();就可以获得完整的订单信息。
@Bean
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
在OrderService中使用RestTemplate发送http请求。
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
这里并没有用到Eureka,但是我们已经能看到这种方式的缺点了 :哪有http路径写到java代码里的?这直接深度耦合难以复用了。
3.3 Eureka 注册中心
刚才只是使用RestTemplate发送了http请求,现在才是Eureka的应用。
既然用到了eureka,肯定要创建eureka注册中心。
我们先创建一个Eureka注册中心模块,将所有服务注册进去,再进行调用。
注册一个注册中心分为三部 :引入依赖,添加注解、添加配置
。
-
创建项目,添加依赖
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency>
-
编写启动类,加上 @EnableEurekaServer 注解
@SpringBootApplication @EnableEurekaServer public class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class, args); } }
-
配置注册中心
server: port: 10086 spring: application: name: eureka-server # eureka服务名称 # 注册eureka服务 eureka: client: service-url: # eureka地址信息 defaultZone: http://localhost:10086/eureka
将应用启动后就可以访问我们配置的Eureka注册中心了,这里是 :http://localhost:10086/
如下页面,红框内即为所有服务。当前我们只创建了注册中心,所以只有它一个服务。
这也可以看到 :注册中心本身也是一个服务。为什么呢?
当我们项目大的时候,又分为好几个注册中心,注册中心1想用注册中心2的一个服务,那么是不是就说明注册中心2本身也能提供服务。
3.4 Eureka 注册服务
现在我们可以将刚才两个order-service、user-service注册进注册中心。
注册服务也是三步 :引入依赖,添加注解、添加配置。(给两个服务都加上)
-
引入依赖
注册中心的依赖是 :eureka-server,而服务的依赖是 eureka-client
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
-
添加注解
注册中心的注解是@EnableEurekaServer,服务的是 @EnableEurekaClient
@SpringBootApplication @EnableEurekaClient public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }
-
配置文件
spring: application: name: userservice # eureka注册中心显示的名称就是应用名,所以尽量配置 # 因为要将这个服务注册进注册中心,所以将刚才注册中心的eureka复制过来就行 eureka: client: service-url: # eureka地址信息 defaultZone: http://localhost:10086/eureka
此时再打开Eureka,就会出现三个服务 :eureka-server、userservice、orderservice
当然,我们可以将userservice拷贝几份,将他们的端口改一下,在Eureka注册中心可以看到这些服务。
3.5 Eureka 远程调用
完成了注册中心和注册服务,就剩一个怎么在order-service中调用user-service了。
同样要借助RestTemplate,刚才的代码几乎不用动。
只有两处改动 :加一个注解@LoadBalanced
、将url路径替换为服务路径
。
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
String url = "http://userservice/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
注意在注入RestTemplate时加了一个注解 :@LoadBalanced,这个就是负载均衡注解,我们刚刚注册了三个userservice服务,到底用哪一个?由负载均衡实现,为什么要使用负载均衡呢?那也不能一亿次请求只分在一个服务身上吧,肯定是谁的压力小分到谁身上。
4. Eureka 架构原理
官网的Eureka工作流程图 :
Application service :服务提供者。
Application client :服务消费者。
- Register :将自己的IP端口注册到注册中心(Eureka)。
- Replicate :不同的注册中心同步服务,做到集群中的数据同步。
- Renew :服务续约,服务向注册中心发送心跳,每30s发送一次,告诉Eureka自己还活着。便于注册中心检查服务的健康状态。
- Cancel :服务下线,在注册中心删除此服务。
- Get Register :客户端(Client)从注册中心获取注册列表,第一次全量获取,后面为定时增量。
- Make Remote Call : 完成服务的远程调用
Eureka的数据存在map集合中,在内存里。消费者调用服务就是从注册中心拉取服务列表,在列表中找到需要的服务,接下来并不是借助注册中心调用服务,注册中心仅仅是注册中心,而是通过Make Remote Call 去远程调用需要的服务。
5. CAP: 分布式一致性定理
C : 一致性,在分布式系统中,是否立即达到数据同步效果。
A : 可用性,在分布式系统中,其中一些节点出现问题,整个整体是否还可用。
P : 分区容错性,在分布式系统中是否可以在有限的时间内达到数据一致的效果。
Eureka 只完成了A和P。
6. Eureka 自我保护
一般情况下,服务在Eureka上注册后,每30s会发送心跳包,Eureka通过心跳来判断服务是否健康,同时会定期清理超过90s没有发送心跳包的服务。
有两种情况会导致 Eureka Server 接收不到微服务的心跳
- 微服务自身问题。
- 微服务与Eureka之间的网络出现问题。
如果是网络出现问题,微服务怎么保护自己不被误删呢?
自我保护模式 :Eureka Server 在运行期间会去统计该服务的心跳失败比例,15分钟内心跳失败超过了85%,Eureka Server会将这个服务保护起来,同时提供一个警告,提示你去检查该服务的状态。
自我保护模式默认是开启的,同样也可以关闭。
eureka:
server:
enable-self-preservation: false # true为开启自我保护
eviction-interval-timer-in-ms: 60000 # 清理间隔,单位毫秒,默认为1min
有一个问题,在保护模式的前提下,只要一个服务消失,不管是我们主动关闭还是服务出现问题,Eureka都会将它保护起来,但是我们确实不再需要这个服务,如何不让Eureka保护它而是直接关闭该服务呢?这就涉及到Eureka的优雅停服了。
7. Eureka 优雅停服
有了优雅停服,我们就可以在不关闭保护模式的前提下直接将服务删除。我们通过actuator实现。
-
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
-
配置文件
management: endpoints: web: exposure: include: shutdown # 开启shutdown端点访问 endpoint: shutdown: enabled: true # 开启shutdown实现优雅停服
-
启动后发送post请求 :http://localhost:10086/actuator/shuwdown,发现注册中心中该服务已经被删除