分布式服务出现的问题
1.简单回顾一下,刚才我们写了什么:
**
**1.user-service:**对外提供了查询用户的接口
**2.consumer-demo:**通过RestTemplate访问 http://locahost:9091/user/{id} 接口,查询用户数据
2.存在什么问题?
在consumer中,我们把url地址硬编码到了代码中,不方便后期维护
consumer需要记忆user-service的地址,如果出现变更,可能得不到通知,地址将失效
consumer不清楚user-service的状态,服务宕机也不知道
user-service只有1台服务,不具备高可用性
即便user-service形成集群,consumer还需自己实现负载均衡
其实上面说的问题,概括一下就是分布式服务必然要面临的问题:
1.服务管理
2.如何自动注册和发现
3.如何实现状态监管
4.如何实现动态路由
5.服务如何实现负载均衡
6.服务如何解决容灾问题
7.服务如何实现统一配置
8.以上的问题,都将在SpringCloud中得到答案
以上问题都将在SprigCloud中得到答案
**
Eureka注册中心
问题及其对应解决方案
问题1服务的管理
首先我们来解决第一问题,服务的管理。
问题分析
在刚才的案例中,user-service对外提供服务,需要对外暴露自己的地址。而consumer-demo(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目可能会拆分出十几,甚至几十个微服务。此时如果还人为管理地址,不仅开发困难,将来
测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
解决方案
DevOps的思想是系统可以通过一组过程、方法或系统;提高应用发布和运维的效率,降低管理成本。
Eureka做什么?
案例解析
这就好比是 网约车出现以前,人们出门叫车只能叫出租车。一些私家车想做出租却没有资格,被称为黑车。而很多人想要约车,但是无奈出租车太少,不方便。私家车很多却不敢拦,而且满大街的车,谁知道哪个才是愿意载人的。一个想要,一个愿意给,就是缺少引子,缺乏管理啊。此时滴滴这样的网约车平台出现了,所有想载客的私家车全部到滴滴注册,记录你的车型(服务类型),身份信息联系方式)。这样提供服务的私家车,在滴滴那里都能找到,一目了然。此时要叫车的人,只需要打开APP,输入你的目的地,选择车型(服务类型),滴滴自动安排一个符合需求的车到你面前,为你服务,完美!
概述
Eureka就好比是滴滴,负责管理、记录服务提供者的信息。服务调用者无需自己寻找服务,而是把自己的需求告诉Eureka,然后Eureka会把符合你需求的服务告诉你。同时,服务提供方与Eureka之间通过 “心跳” 机制进行监控,当某个服务提供方出现问题Eureka自然会把它从服务列表中剔除。这就实现了服务的自动注册、发现、状态监控。
Eureka原理图
Eureka:就是一个服务注册中心(可以是一个集群),对外暴露自己的地址
提供者:启动后向Eureka注册自己的信息(地址,提供什么服务)
消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
Eureka入门案例
1.搭建eureka-server工程
1.1需要的jar坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudParent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
1.2编写启动类
package com.pjh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/*声明当前应用是eureka服务*/
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class,args);
}
}
1.3编写配置文件
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: http://127.0.0.1:10086/eureka
register-with-eureka: false # 不注册自己到eureka
fetch-registry: false #不拉取
server:
port: 10086
1.4测试
2.服务注册
概念
服务注册,就是在服务上添加Eureka的客户端依赖,客户端代码会自动把服务注册到EurekaServer中
服务注册的步骤
1.添加依赖
2.改造启动类,添加开启Eureka客户端发现的注解
3.修改配置文件,设置Eure服务地址
2.1.添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudParent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Eureka-Producer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
2.2.改造启动类,添加开启Eureka客户端发现的注解
package com.pjh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@EnableEurekaClient
@SpringBootApplication
public class EurekaProducerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaProducerApplication.class,args);
}
}
2.3.修改配置文件,设置Eure服务地址
#指定端口
server:
port: 8088
#指定datasource的相关信息
spring:
datasource:
url: jdbc:mysql://localhost:3309/test
password: 1234
username: root
driver-class-name: com.mysql.jdbc.Driver
application:
name: Eureka-Consumer #作为服务id使用
mybatis:
type-aliases-package: com.pjh.pojo
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
2.4测试
查看控制台显示,服务已经注册成功
3.服务的发现
概念
即从Eureka获取服务
服务发现步骤:
1.添加依赖
2.改造启动引导类,添加开启Eureka客户端发现的注解
3.修改配置文件,设置Eureka服务地址
4.改造处理器ConsumerController,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。
3.1添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringCloudParent</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Eureka-Consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
</project>
3.2改造启动引导类,添加开启Eureka客户端发现的注解
package com.pjh;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaConsumer2Application {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumer2Application.class,args);
}
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
3.3修改配置文件,设置Eureka服务地址
server:
port: 8089
spring:
application:
name: consumer2-eureka
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
3.4改造处理器ConsumerController,可以使用工具类DiscoveryClient根据服务名称获取对应服务地址列表。
package com.pjh.user.Controller;
import com.pjh.user.pojo.user;
import org.apache.catalina.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.List;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired(required = false)
private DiscoveryClient discoveryClient;
@GetMapping("/{id}")
public user QueryForId(@PathVariable String id){
String url = "http://localhost:8088/user/" + id;
/*获取eureka中注册的user-service实例列表*/
List<ServiceInstance> instancesList = discoveryClient.getInstances("producer-service");
ServiceInstance serviceInstance = instancesList.get(0);
url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort()
+ "/user/" + id;
return restTemplate.getForObject(url,user.class);
}
}
3.5测试
Eureka详解
基础架构
Eureka架构中的三个核心角色:
服务注册中心
Eureka的服务端应用,提供服务注册和发现功能,就是刚刚我们建立的eureka-server
服务提供者
提供服务的应用,可以是SpringBoot应用,也可以是其它任意技术实现,只要对外提供的是Rest风格服务即可。本例中就是我们实现的user-service
服务消费者
消费应用从注册中心获取服务列表,从而得知每个服务方的信息,知道去哪里调用服务方。本例中就是我们实
现的consumer-demo
高可用的Eureka Server简介
概述
Eureka Server即服务的注册中心,在刚才的案例中,我们只有一个EurekaServer,事实上EurekaServer也可以是一个集群,形成高可用的Eureka中心。
Eureka集群中的服务同步问题
多个EurekaServer之间也会注册相互注册为服务,当服务提供者注册到Eureka Server集群中的某个节点时,该节点会把服务的信息同步给集群中的每个节点,从而实现数据同步。因此,无论客户端访问到Eureka Server集群中的任意一个节点,都可以获取到完整的服务列表信息。 而作为客户端,需要把信息注册到每个Eureka中:
举例
如果有三个Eureka,则每一个EurekaServer都需要注册到其它几个Eureka服务中,例如:有三个分别为10086、10087、10088,则:
10086要注册到10087和10088上
10087要注册到10086和10088上
10088要注册到10086和10087上
搭建一个高可用Eureka集群:
案例介绍:
我们假设要搭建两台EurekaServer的集群,端口分别为:10086和10087
1.application配置文件配置
** p o r t : 8080 在 上 述 配 置 文 件 中 的 {port:8080}在上述配置文件中的 port:8080在上述配置文件中的{}表示在jvm启动时候若能找到对应port或者defaultZone参数则使用,若无则使用后面的默认值 **
**
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: ${defautlZone:http://127.0.0.1:10086/eureka} #${defautlZone:http://127.0.0.1:10086/eureka}
server:
port: ${port:10086} #${port:10086}
**所谓的高可用注册中心,其实就是把EurekaServer自己也作为一个服务,注册到其它EurekaServer上,这样多个EurekaServer之间就能互相发现对方,从而形成集群。 **
**把service-url的值改成了另外一台EurekaServer的地址,而不是自己 **
2.另外一台在启动的时候可以指定端口port和defaultZone配置:
**修改原来的启动配置组件;在如下界面中的 VM options 中
设置 -DdefaultZone=http:127.0.0.1:10087/eureka **
**复制一份并修改;在如下界面中的 VM options 中
设置 -Dport=10087 -DdefaultZone=http:127.0.0.1:10086/eureka **
**
3)启动测试;同时启动两台eureka server
4)客户端注册服务到集群
因为EurekaServer不止一个,因此 user-service 项目注册服务或者 consumer-demo 获取服务的时候,service-url参数需要修改为如下:
eureka:
client:
service-url: # EurekaServer地址,多个地址以','隔开
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eurek
Eureka客户端
服务提供者要向EurekaServer注册服务,并且完成服务续约等工作。
服务注册
服务提供者在启动时,会检测配置属性中的: eureka.client.register-with-erueka=true 参数是否正确,事实上默认就是true。如果值确实为true,则会向EurekaServer发起一个Rest请求,并携带自己的元数据信息,EurekaServer会把这些信息保存到一个双层Map结构中。
第一层Map的Key就是服务id,一般是配置中的 spring.application.name 属性
第二层Map的key是服务的实例id。一般host+ serviceId + port,例如: localhost:user-service:8081
值则是服务的实例对象,也就是说一个服务,可以同时启动多个不同实例,形成集群。
默认注册时使用的是主机名或者localhost,如果想用ip进行注册,可以在 user-service 中添加配置如下:
eureka:
instance:
ip-address: 127.0.0.1 # ip地址
prefer-ip-address: true # 更倾向于使用ip,而不是host名
服务续约
在注册服务完成以后,服务提供者会维持一个心跳(定时向EurekaServer发起Rest请求),告诉EurekaServer:“我还活着”。这个我们称为服务的续约(renew);
有两个重要参数可以修改服务续约的行为;可以在 user-service 中添加如下配置项:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
参数介绍:
**lease-renewal-interval-in-seconds:**服务续约(renew)的间隔,默认为30秒
**
** lease-expiration-duration-in-seconds:**服务失效时间,默认值90秒
也就是说,默认情况下每隔30秒服务会向注册中心发送一次心跳,证明自己还活着。如果超过90秒没有发送心跳,EurekaServer就会认为该服务宕机,会定时(eureka.server.eviction-interval-timer-in-ms设定的时间)从服务列表中移除,这两个值在生产环境不要修改,默认即可。
获取服务列表
当服务消费者启动时,会检测 eureka.client.fetch-registry=true 参数的值,如果为true,则会从EurekaServer服务的列表拉取只读备份,然后缓存在本地。并且 每隔30秒 会重新拉取并更新数据。可以在 consumer-demo项目中通过下面的参数来修改:
eureka:
client:
registry-fetch-interval-seconds: 30
失效剔除和自我保护
如下的配置都是在EurekaServer服务端进行
服务下线
当服务进行正常关闭操作时,它会触发一个服务下线的REST请求给Eureka Server,告诉服务注册中心:“我要下线了”。当服务注册中心收到请求之后。将该服务设置为下线状态
失效剔除
有时我们的服务可能由于内存溢出或网络故障等原因使得服务不能正常工作,而服务注册中心没有收到服务下线的请求。相对于服务提供者的“服务续约操作”,服务注册中心会在启动时创建一个定时任务,默认每隔一段时间(默认60秒),将清单中超时(默认为90秒),没有续约的服务剔除,这个操作称为失效剔除。
可以通过eureka.server.eviction-interval-timer-in-ms 参数对其进行修改,单位是毫秒。
自我关停
我们关停一个服务,很可能会在Eureka面板看到一条警告:
这是触发了Eureka自我保护机制。当服务未按时进行心跳续约时,Eureka会统计服务实例最近15分钟心跳续约的比例是否低于了85%。在生产环境下,因为网络延迟等原因,心跳失败实例的比例很有可能超标,但是此时就把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,直到网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务的失败容错
**可以通过下面的配置来关停自我保护: **
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)