Eureka注册中心
1.注册中心
服务注册中心是服务实现服务化管理的核心组件,类似于目录服务的作用。
主要用来存储服务信息,例如提供者url、路由信息等。
服务注册中心是的微服务架构中最基础的设施之一。
在微服务架构流行之前。注册中心就已经开始出现在分布式架构的系统中
Dubbo 是一个在国内比较流行的分布式框架,被大量的中小型互联网公司所采用,
它提供了比较完善的消息治理功能,而服务治理的实现主要依靠的就是注册中心。
注册中心可以说是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。
在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就到这里找到服务的地址,进行调用。
总结:服务注册中心的作用就是服务的注册和服务的发现。
2.类别
- Netflix Eureka
- Alibaba Nacos
- HashiCorp Consul
- Apache ZooKeeper
- CoreOS Etcd
- CNCF CoreDNS
3.作用
在分布式系统中,我们不仅仅是需要在注册中心找到服务和服务地址的映射关系这么简单,我们还需要考虑更多更复杂的问题:
- 服务注册后,如何被及时发现
- 服务宕机后,如何及时下线
- 服务如何有效的水平扩展
- 服务发现时,如何进行路由
- 服务异常时,如何进行降级
- 注册中心如何实现自身的高可用
这些问题的解决都依赖于注册中心。简单看,注册中心的功能有点类似于DNS服务器或者负载均衡器,
而实际上,注册中心作为微服务的基础组件,可能要更加复杂,也需要更多的灵活性和时效性。
解决问题: - 服务管理
- 服务的依赖关系管理
4.Eureka概念
Eureka是Netflix开发的服务发现组件,本身是一个基于REST的服务。Spring Cloud将它集成在其子项目Spring Cloud Netfix中
实现Spring Cloud的服务注册与发现,同时还提供了负载均衡、故障转移等能力。
读作 e rui ka
5.三种角色
- Eureka Server
通过Register、Get、 Renew 等接口提供服务的注册和发现。 - Application Service (Service Provider)
服务提供方,把自身的服务实例注册到Eureka Server中。 - Application Client (Service Consumer)
服务调用方,通过Eureka Server获取服务列表,消费服务。
6.单节点注册中心实现
- 父模块pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.4.RELEASE</version>
</parent>
<properties>
<spring-cloud.version>Hoxton.SR1</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 注册中心模块pom.xml
<!-- 继承父依赖-->
<parent>
<artifactId>cloud1</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<!-- <version>2.2.3.RELEASE</version>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
- application.yml
Eureka默认开启将自己注册至注册中心和从注册中心获取服务注册信息的配置
如果是单节点注册中心的话,要关闭两个配置项。否则报错
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
#是否将自己注册到注册中心
register-with-eureka: false
#是否从注册中心获取服务注册信息
fetch-registry: false
#注册中心对外路径
service-url:
defaultZone: http://${spring.cloud.client.ip-address}:${server.port}/eureka/
- 启动类
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
7.多节点注册中心实现
- 注册中心1
application.yml
server:
port: 8761
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka1
client:
#注册中心对外路径
service-url:
defaultZone: http://localhost:8762/eureka/
- 注册中心2
application.yml
对外路径引用对方的接口
server:
port: 8762
spring:
application:
name: eureka-server
eureka:
instance:
hostname: eureka2
client:
#注册中心对外路径
service-url:
defaultZone: http://localhost:8761/eureka/
显示Ip和端口
- application.yml加入
eureka:
instance:
hostname: eureka2
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
8.发布者
- Product
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product implements Serializable {
private Integer id;
private String name;
private Integer num;
private Double price;
}
- ProductService
public interface ProductService {
List<Product> listProducts();
}
- ProductServiceImpl
@Service
public class ProductServiceImpl implements ProductService{
@Override
public List<Product> listProducts() {
return Arrays.asList(
new Product(1,"lwx",2,1000D),
new Product(2,"xx",2,2000D)
);
}
}
- ProductController
@RestController
@RequestMapping("product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/list")
public List<Product> listProducts(){
return productService.listProducts();
}
}
- application.yml
server:
port: 7070
spring:
application:
name: provider
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
#注册中心对外路径
service-url:
defaultZone: http://localhost:8761/eureka/, http://localhost:8761/eureka/
9.消费者
- Order
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order implements Serializable {
private Integer id;
private String no;
private String address;
private Double price;
private List<Product> productList;
}
- OrderService
public interface OrderService {
Order selectOrderById(Integer id);
}
- OrderServiceImpl三种方法实现
先在启动类加入Bean
@SpringBootApplication
@EnableEurekaServer
public class App
{
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main( String[] args ) {
SpringApplication.run(App.class);
}
}
三种方法
- 1.DiscoveryClient: 通过元数据获取服务信息
private List<Product> selectOrderByDiscoveryClient(){
//拼接地址
StringBuffer sb = null;
//获取服务列表
List<String> serviceIds = discoveryClient.getServices();
if(CollectionUtils.isEmpty(serviceIds)){
return null;
}
//根据服务名称获取服务
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("provider");
if(CollectionUtils.isEmpty(serviceInstances)){
return null;
}
ServiceInstance serviceInstance = serviceInstances.get(0);
sb = new StringBuffer();
sb.append("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/list");
//ResponseEntity 封装返回数据
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {}
);
return response.getBody();
}
- 2.LoadBalancerClient: Ribbon的负载均衡器
private List<Product> selectOrderByLoadBalancerClient(){
//拼接地址
StringBuffer sb = null;
ServiceInstance serviceInstance = loadBalancerClient.choose("provider");
if(serviceInstance == null){
return null;
}
sb = new StringBuffer();
sb.append("http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/product/list");
//ResponseEntity 封装返回数据
ResponseEntity<List<Product>> response = restTemplate.exchange(
sb.toString(),
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {}
);
return response.getBody();
}
- 3.@LoadBalanced:通过注解开启Ribbon的负载均衡器
先在启动类加入注解
@Bean
@LoadBalanced //负载均衡注解
public RestTemplate restTemplate() {
return new RestTemplate();
}
private List<Product> selectOrderByLoadBalancerAnnoation(){
//ResponseEntity 封装返回数据
ResponseEntity<List<Product>> response = restTemplate.exchange(
"http://provider/product/list",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<Product>>() {}
);
return response.getBody();
}
- OrderController
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/{id}")
public Order selectOrderById(@PathVariable("id") Integer id){
return orderService.selectOrderById(id);
}
}
- application.yml
server:
port: 9090
spring:
application:
name: consumer
#不注册
eureka:
client:
#是否将自己注册到注册中心
register-with-eureka: false
#间隔多久从服务器拉取注册信息 默认30s
registry-fetch-interval-seconds: 10
#注册中心对外路径
service-url:
defaultZone: http://localhost:8761/eureka/, http://localhost:8761/eureka/
10.原理
- Register(服务注册): 把自己的IP和端口注册给Eureka.|
- Renew(服务续约):发送心跳包,每30秒发送一次,告诉Eureka自己还活着。如果90秒还未发送心跳,宕机。
- Cancel(服务下线):当Provider关闭时会向Eureka发送消息,把自己从服务列表中删除。防止Consumer调用到不存在的服务
- Get Registry(获取服务注册列表):获取其他服务列表。
- Replicate(集群中数据同步): Eureka 集群中的数据复制与同步。
- Make Remote Call(远程调用):完成服务的远程调用。
12.Eureka自我保护
一·般情况下,服务在Eureka上注册后,会每30秒发送心跳包,Eureka 通过心跳来判断服务是否健康,同时会定期删除超过90秒没有发送心跳的服务。
有两种情况会导致Eureka Server收不到微服务的心跳
- 微服务自身的原因
- 微服务与Eureka之间的网络故障
12.1 自我保护模式
自我保护模式
Eureka Server在运行期间会去统计心跳失败比例在15分钟之内是否低于85%,如果低于85%,Eureka Server会将这些实例保
护起来,让这些实例不会过期,同时提示一个警告。 这种算法叫做Eureka Server的自我保护模式。
12.2 为什么要启动自我保护
- 因为同时保留“好数据"与"坏数据"总比丢掉任何数据要更好,当网络故障恢复后,这个Eureka节点会退出"自我保护模式”。
- Eureka还有客户端缓存功能(也就是微服务的缓存功能):即使Eureka集群中所有节点都宕机失效,微服务的Provider和Consumer都能正常通信。
- 微服务的负载均衡策略会自动剔除死亡的微服务节点。
12.3.如何关闭自我保护
eureka:
server :
enable-self-preservation: false # true: 开启自我保护模式,false: 关闭自我保护模式
eviction-interval-timer-in-ms: 60000 #清理间隔(单位:毫秒,默认是60*1000)
13.Eureka优雅停服
配置了优雅停服以后,将不需要Eureka Server中配置关闭自我保护。使用actuator实现。
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- application.yml
#度量指标监控和健康检查
management:
#开启shutdown端点访问
endpoints:
web:
exposure:
include: shutdown
#开启shutdown实现优雅停服
endpoint:
shutdown:
enabled: true
便用POST请求访问: http/:localhost:7070/actuator/shutdown
14.Eureka安全认证
- pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- application.yml
spring:
application:
name: eureka-server
#安全认证
security:
user:
name: root
password: 1234
- 修改所有集群节点的url
defaultZone: http://root:1234@localhost:8761/eureka/
14.1 过滤CSRF
Eureka会自动化配置CSRF防御机制,Spring Security认为post, PUT, and DELETE http methods都是有风险的
如果这些method发送过程中没有带上CSRF token的话,会被直接拦截并返回403 forbidden,
多节点注册中心会无法注册
14.2 解决方法
- 1.CSRF忽略路径
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
//忽略该路径下请求
http.csrf().ignoringAntMatchers("/eureka/**");
}
}
- 2.保持密码验证的同时禁用CSRF防御机制
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}