了解微服务架构
为什么出现微服务架构?
单体系统随着业务的需求变得越来越臃肿,暴露的问题越来越多,1.由于单体系统部署在一个进程内,虽然功能调用方便,但是调用的杂乱冗余,我们修改很小的功能部署上线就会影响其他功能的运行,还有系统中各个模块并发消耗的资源各有不同,很难有个准确的评估,单体系统的维护成本越来越大,且难以控制。
什么是微服务?
微服务是系统架构上的一种设计风格,将原本独立的系统拆分多个小型的服务,每个小型的服务都在各自独立的进程中运行,各个服务通过基于HTTP的Restful API 进行通信。
这些拆分的小型服务围绕着系统中某一项耦合度较高的业务功能进行构建,每个服务都维护自己的数据存储,业务开发测试以及独立部署,这些服务可以使用不同的语言编辑
微服务中组件的通信方式
在微服务的架构中,由于服务不在同一个进程中,组件间的通信模式发生了改变,通常会使用以下两种调用方式:
- 使用HTTP 的RESTful API 或轻量级的消息发送协议,实现信息的传递和服务调用的触发
- 通过轻量级额的消息中间件传递消息,如RabbitMQ
Spring Cloud的简介
Spring Cloud是一个基于Spring Boot 实现的微服务架构开发工具,为微服务架构中的涉及的配置管理、服务治理、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话、和集群状态管理等操作提供了简单的开发方式
Spring Cloud包含了多个子项目(针对分布式系统中设计的多个不同开源产品,如下:
1、Spring Cloud Config:
配置管理工具,支持使用Git存储配置内容,可以使用它实现应用配置的外部化存储,并支持
客户端配置信息刷新、加密/解密配置内容等。
2、Spring Cloud Netflix:核心组件,对多个Netflix OSS开源套件进行整合
Eureka: 服务治理组件,包含服务注册中心、服务注册与发现机制的实现。
Hystrix: 容错管理组件,实现断路器模式,帮助服务依赖中出现的延迟和为故障提供强大的容错能力。
Ribbon:客户端负载均衡的服务调用组件。
Feign:基于Ribbon和Hystrix的声明式服务调用组件。
Zuul:网关组件,提供智能路由、访问过滤等功能。
Archaiux:外部化配置组件
版本选择:
注意SpringBoot和Spring Cloud版本选择问题而报错
服务治理:Spring Cloud Eureka
Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,主要负责微服务架构中的服务治理,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。
服务治理主要用来实现各个微服务实例的自动化注册和发现,服务的注册和发现都在在服务的注册中心上去实现的,入门demo:如何使用Eureka 构建注册中心以及注册和发现服务
第一步:搭建服务注册中心,创建一个Spring Boot项目,引入Spring Cloud Eureka相关的依赖
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pingan.eureka.server</groupId>
<artifactId>eureka-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RC2</spring-cloud.version>
</properties>
<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-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能
@SpringBootApplication
@EnableEurekaServer
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它自己的客户端注册行为,在application.properties中问增加如下配置:
server.port=1234
#服务注册中心
eureka.instance.hostname=localhost
#不向注册中心注册(不注册自己)
eureka.client.register-with-eureka=false
#不需要检索服务(注册中心只需要维护服务的实例即可)
eureka.client.fetch-registry=false
#注册中心的访问地址
eureka.client.service-url.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/
访问:启动项目,通过locahost:1234既可以访问我们的注册中心,其中Instances currently registered with eureka是空的,因为我们没有注册任务服务。
注意:启动项目报错,检查相关依赖版本是否一致
第二步:注册服务
服务注册中心搭建之后,我们尝试将一个SpringBoot应用加入到Eureka的服务治理中,同样是新建一个springBoot项目,增加Spring Cloud Eureka模块的依赖,pom.xml文件和上面的注册中心的相同
在入口类中添加@EnableDiscoveryClient注解,激活Eureka中的DiscoverClient的实现(自动化配置,创建DiscoveryClient接口针对Eureka客户端的EurekaDiscoveyClient实例)
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@RestController
@EnableDiscoveryClient
public class SelfCoreApplication {
Logger logger = LoggerFactory.getLogger(SelfCoreApplication.class);
public static void main(String[] args) {
SpringApplication.run(SelfCoreApplication.class, args);
}
@Autowired
private DiscoveryClient discoveryClient;
@RequestMapping("/index")
public String index(){
logger.info("eureka server Host= "+discoveryClient.getLocalServiceInstance().getHost()+
"serviceId ="+discoveryClient.getLocalServiceInstance().getServiceId());
return "hello,self-core";
}
}
在application.properties文件中配置以下内容:
#服务注册中心中的服务名字
spring.application.name=self-core
#指定服务注册中心的地址,集群配置多个用逗号隔开
eureka.client.service-url.defaultZone=http://localhost:1234/eureka/
启动项目,发现服务已经注册到创建的注册中心上,多了一个self-core的服务,说明这个服务已经注册到注册中心上去了
第三步:服务发现和消费
我们已经构建了注册中心和服务提供者,下面尝试构建一个服务消费者:服务消费者主要有2个工作:其一是发现服务,其二就是消费服务,服务的发现由Eureka的客户端完成,而服务的消费由Ribbon完成
Ribbon 是一个基于HTTP 和TCP的客户端负载均衡器,可以通过在RibbonServerList的服务列表去轮询访问以达到负载均衡的作用,当Ribbon与Eureka联合使用的时候,Ribbon的服务实例清单RibbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心获取的服务列表。
同样新建一个Spring Boot项目,引入和之前相同的依赖,并且新增一个Ribbon模块的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
同样,在主类中添加@EnableDiscoveryClient将这个应用标记为Eureka的客户端应用,获取服务注册和发现的能力,在服务发现的这个应用中,我们添加一个RestTemplate的Bean,并且使用@LoadBalanced注解来开启客户端的负载均衡。
@SpringBootApplication
@EnableDiscoveryClient //注册改该应用为Eureka客户端应用,获取服务注册/发现的能力
public class RibbonDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonDemoApplication.class, args);
}
@Bean
@LoadBalanced //开启客户端得负载均衡
RestTemplate restTemplate(){
return new RestTemplate();
}
}
我们同样需要指明服务注册中心的地址,以及访问的端口,应用名称等
server.port=18889
spring.application.name=ribbon-consumer
#服务注册中心的地址,集群配置多个用逗号隔开
eureka.client.service-url.defaultZone=http://localhost:1234/eureka/
#为了安全考虑,服务注册中心的地址加入安全校验,username为安全校验的用户名,passworl为密码 eureka.client.service-url.defaultZone=http://<username>:<password>@localhost:1234/eureka/
测试:我们新建一个Controller,注入RestTemplate,通过RestTemplate这个对象来实现对self-core服务提供的/index接口进行调用,访问的地址是服务名,而不是一个具体的地址
@RestController
public class RibbonController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/ribbon",method = RequestMethod.GET)
public String helloRibbon(){
return restTemplate.getForEntity("http://self-core/index",String.class).getBody();
}
}
同样,启动项目后能看到服务注册中心多了一个ribbon-cosumer的服务,访问localhost:18889/ribbon,获取到服务提供者提供的接口数据
至此,入门的demo已经完成,关于RestTemplate的使用及相关API后面在介绍
搭建高可用的服务注册中心
在微服务的分布式环境中,我们需要充分考虑发生故障的情况,在生成环境中必须对各个组件进行高可用部署,在Eureka Server的设计的开始就有高可用的设计,在Eureka的服务治理设计中,所有的节点即是服务的提供方也是服务的消费方,注册中心也是这样的,我们在入门的demo添加了2个参数,让注册中心不注册自己;
实际上,Eureka Server的高可用即是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,从而显示服务清单的相互同步,达到高可用的效果!
我们使用2个SpringBoot 应用相互注册的服务注册中心,即可实现,具体如下:创建两个服务注册中心的SpringBoot应用,使用@EnableEurekaServer注解启动一个服务注册中心,在2个application.properties文件中配置如下,服务的注册中心互相注册
server.port=1235
spring.application.name=eureka-server
#服务注册中心
eureka.instance.hostname=127.0.0.1
#使用IP来配置注册中心的地址
eureka.instance.prefer-ip-address=true
#设置成false则不会启动注册服务,即不会将服务注册到service-url的地址中
#eureka.client.register-with-eureka=false
#eureka.client.fetch-registry=false
#访问的注册中心的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:1236/eureka/
server.port=1236
spring.application.name=eureka-server
#服务注册中心
eureka.instance.hostname=127.0.0.1
#使用IP来配置注册中心的地址
eureka.instance.prefer-ip-address=true
#eureka.client.register-with-eureka=false
#eureka.client.fetch-registry=false
#访问的注册中心的地址
eureka.client.service-url.defaultZone=http://127.0.0.1:1235/eureka/
分别访问127.0.0.1 : 1235 和127.0.0.1 : 1236查看注册中心中的服务,发现第一个注册中心中有第二个注册中心的服务,第二个注册中心中有第一个注册中心的服务,二者相互注册,互为服务和注册中心,这就是一个高可用的注册中心
Eureka 的总结和进阶:
Eureka的服务治理体系通的三个核心角色:
服务注册中心:提供服务的注册和发现的功能
服务提供者注册服务:提供服务的应用,可以是SpringBoot应用或者其他技术平台遵循Eureka通信机制的应用
服务消费者发现并消费服务:从服务注册中心获取服务列表,使用Ribbon来实现服务消费,后面还会有Feign的消费方式
pictrue
服务提供者的服务注册
服务提供者在启动的时候通过发送REST请求的方式将自己注册到Eureka Server上,同时带上自身服务的一些元数据信息,Rureka Server接收到这个REST请求之后将元数据信息存储在一个双层结构Map中,第一层的key为服务名,也就是InstanceInfo中的appName属性,第二层的key为实例名,InstanceInfo中的instanceId.
eureka.client.register-with-eureka =true ,设置成false将不会启动注册操作(默认就是true)
服务同步:
2个服务提供者分别注册到不同的注册服务中心,由于服务注册中心互相注册为服务,当服务提供者发送注册请求到一个服务注册中心时候,它会将该请求转发给集群中其他的注册中心,实现注册中心之间的服务同步
服务续约:
注册完服务,服务提供者会维护一个心跳给Eureka Server,放置剔除任务将服务实例和服务列表中排除出去
服务续约2个重要的属性
eureka.instance.lease-renewal-interval-in-seconds=30 服务续约任务的调用时间间隔 :默认30s
eureka.instance.lease-expiration-duration-in-seconds=90 服务时效的时间: 默认90s
服务消费者获取服务
假如在服务注册中心已经注册了一个服务,且该服务有2个实例,当我们启动服务消费者的时候,他会发送一个REST请求给服务注册中心,获取上面注册的服务清单,Eureka Server会维护一份制度的服务清单返回给客户端,同事该服务清单还会每30s更新一次
eureka.client.fetch-register = true ,设置成false将不会获取服务(默认就是true)
eureka.client.register-fetch-interval-seconds=30 ,设置缓存清单的更新时间(默认就是30)
服务调用
获取服务清单之后通过服务名可以获得具体服务的实例名和实例的元数据信息,有了服务实例的信息,客户端可以根据自己的需要决定调用那个实例,在Ribbon中会默认采用轮训的方式进行调用,从而实现负载均衡
其实访问实例的选择,Eureka中有Region和Zone的概念,一个Region中可以包含多个Zone,每个服务客户端需要被注册到一个zone中,所以一个客户端对应一个Region和zone,进行服务调用的时候,优先访问同一个zone的服务提供方
服务下线
当服务实例进行正常关闭重启的时候,它会出发一个服务下线的REST请求给Eureka Server,高速服务注册中心,服务端接受到请求后,将改服务状态设置成下线DOWN,并把下线事件传播出去
服务注册中心
失效提出服务
服务实例遇到下线,内存溢出,网络故障灯原因使得服务不能正常工作,而服务注册中心并未收到“服务下线”的请求,Eureka Server再启动的时候创建一个定时任务,每隔一段时间时间(默认60s)将当前清单中超时(默认90s)没有续约的服务剔除
自我保护
Eureka Server在运行期间,会统计心跳失败的比例在15分钟之内是否低于85%,如果出现低于的情况,Eureka Server就会将当前的实例注册信息保护起来,当这些实例不会过期,尽可能保护注册信息,在保护期间若出现问题就会导致客户端很容易拿到实际上已经不存在的服务实例,调用失败必须有容错机制
通过使用eureka.server.enable-self-preservation =false 关闭保护机制,确保注册中心将不可用的实例正确剔除
配置详解
Eureka客户端的配置主要分为以下2个方面
服务注册相关的配置信息,包括服务注册中心的地址,服务获取的时间间隔,可用区域呢,通常以eureka.client开头
服务实例相关配置信息:包括服务实例的名称,IP地址,端口号,健康检查路径等,通常以eureka.instance开头
服务实例的元数据:Eureka客户端在向服务注册中心发送注册请求时,用来描述自身服务信息的对象统称为元数据,包括一些标准化的元数据如:服务名称,实例名称,实例IP,实例端口等服务治理相关信息已经一些用于负载均衡策略和其他用途的数据
我们可以通过eureka.instance.<properties>=<value>的格式对标准化的元数据进行配置,其中properties就是EurekaInstanceConfigBeanz对象中的成员变量,对于自定义的元数据可以用过eureka.instance.metadataMap.<key>=<value>来配置
如:eureka.instance.metadataMap.zone =shanghai
实例名称的配置
实例名及时InstanceInfo中的instanceId参数,区分统一服务中不同实例的唯一标识,在spring cloud eureka默认采用如下的默认格式
${spring.cloud.clinet.hostname}:${spring.application.name}:${spring.application.instance_id}:${server.port}
对于实例名的命名规则,我们可以通过eureka.instance.instanceId参数来进行配置,,在本地客户端负载均衡调试中,我们不得不启动统一服务的多个实例,虽然我们可以通过指定不同的端口server.port来启动(实际可以通过设置server.port=0或者server.port=${random.int[10000,19999]}来让tomcat采用随机端口),但是这个时候我们发现注册到Eureka Server的实例名都是相同的,
eureka.instance.instanceId =${spring.application.name}:${random.int}来区分不同实例,实现同一个注解上不指定端口就能轻松启动多实例
端点配置
健康检查