个人博客地址:
http://xiaohe-blog.top
Eureka是一个注册中心,那么注册中心是什么呢?
在微服务中,注册中心就像是一个卖房中介,房东们将自己的房子信息放在中介那里,客户在中介那里查看房子信息,看中了哪一套房,中介再牵线客户和房东签合同(因为房子不是中介的)。
服务提供者将自己能提供的服务放在注册中心,服务调用者在注册中心查看是否有自己想要的服务,如果有,直接调用服务。但是服务本身并不是注册中心的,所以调用者必须直接调用提供者的服务。
简而言之,注册中心就是收录服务的地方。
市面上的注册中心,大量使用的 :Eureka、Nacos、Consul、Zookeeper…
当然注册中心不仅仅是存服务这一个作用,他还兼顾着 服务注册后如何被发现、服务异常时如何解决、服务发现后如何路由、服务如何扩展等一系列功能。
Eureka有两个角色 :
其中 Eureka Client 又分为服务提供者和服务调用者,但是因为服务的提供者明天又会变成服务的调用者,所以这两者统称为客户端。(好比你家卖鱼,你是服务提供者,但是你想吃猪肉也要去买,你是服务消费者)
这里我们使用黑马的案例。
有两张表 :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”这个功能呢?
想要实现上述功能,如果我们能在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代码里的?这直接深度耦合难以复用了。
刚才只是使用RestTemplate发送了http请求,现在才是Eureka的应用。
既然用到了eureka,肯定要创建eureka注册中心。
我们先创建一个Eureka注册中心模块,将所有服务注册进去,再进行调用。
注册一个注册中心分为三部 :引入依赖,添加注解、添加配置
。
创建项目,添加依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
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本身也能提供服务。
现在我们可以将刚才两个order-service、user-service注册进注册中心。
注册服务也是三步 :引入依赖,添加注解、添加配置。(给两个服务都加上)
引入依赖
注册中心的依赖是 :eureka-server,而服务的依赖是 eureka-client
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
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注册中心可以看到这些服务。
完成了注册中心和注册服务,就剩一个怎么在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服务,到底用哪一个?由负载均衡实现,为什么要使用负载均衡呢?那也不能一亿次请求只分在一个服务身上吧,肯定是谁的压力小分到谁身上。
官网的Eureka工作流程图 :
Application service :服务提供者。
Application client :服务消费者。
Eureka的数据存在map集合中,在内存里。消费者调用服务就是从注册中心拉取服务列表,在列表中找到需要的服务,接下来并不是借助注册中心调用服务,注册中心仅仅是注册中心,而是通过Make Remote Call 去远程调用需要的服务。
C : 一致性,在分布式系统中,是否立即达到数据同步效果。
A : 可用性,在分布式系统中,其中一些节点出现问题,整个整体是否还可用。
P : 分区容错性,在分布式系统中是否可以在有限的时间内达到数据一致的效果。
Eureka 只完成了A和P。
一般情况下,服务在Eureka上注册后,每30s会发送心跳包,Eureka通过心跳来判断服务是否健康,同时会定期清理超过90s没有发送心跳包的服务。
有两种情况会导致 Eureka Server 接收不到微服务的心跳
如果是网络出现问题,微服务怎么保护自己不被误删呢?
自我保护模式 :Eureka Server 在运行期间会去统计该服务的心跳失败比例,15分钟内心跳失败超过了85%,Eureka Server会将这个服务保护起来,同时提供一个警告,提示你去检查该服务的状态。
自我保护模式默认是开启的,同样也可以关闭。
eureka:
server:
enable-self-preservation: false # true为开启自我保护
eviction-interval-timer-in-ms: 60000 # 清理间隔,单位毫秒,默认为1min
有一个问题,在保护模式的前提下,只要一个服务消失,不管是我们主动关闭还是服务出现问题,Eureka都会将它保护起来,但是我们确实不再需要这个服务,如何不让Eureka保护它而是直接关闭该服务呢?这就涉及到Eureka的优雅停服了。
有了优雅停服,我们就可以在不关闭保护模式的前提下直接将服务删除。我们通过actuator实现。
添加依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
配置文件
management:
endpoints:
web:
exposure:
include: shutdown # 开启shutdown端点访问
endpoint:
shutdown:
enabled: true # 开启shutdown实现优雅停服
启动后发送post请求 :http://localhost:10086/actuator/shuwdown,发现注册中心中该服务已经被删除