Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智能路由,微代理,控制总线)。分布式系统的协调导致了样板模式, 使用Spring Cloud开发人员可以快速地支持实现这些模式的服务和应用程序。他们将在任何分布式环境中运行良好,包括开发人员自己的笔记本电脑,裸机数据中心,以及Cloud Foundry等托管平台。
Spring Cloud专注于提供良好的开箱即用经验的典型用例和可扩展性机制覆盖。
• 分布式/版本化配置
• 服务注册和发现
• 路由
• service - to - service调用
• 负载均衡
• 断路器
• 分布式消息传递
微服务注册与发现
服务发现是基于微服务架构的关键原则之一。
服务提供者,服务消费者,服务发现组件这三者之间的关系大概如下:
各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件会存储这些信息。
服务消费者可从服务发现组件查询服务提供者的网络地址,并使用该地址调用服务提供者的接口。
各个微服务与服务发现组件使用一定机制(心跳)通信。服务发现组件如果长时间无法与某微服务实例通信,就会注销该实例。
Spring Cloud提供了多种服务发现组件的支持,例如Eureka,Consul,Zookeeper等。
Eureka是Netflix开源的服务发现组件,本身是一个基于REST的服务。它包含Server和Client两部分。Spring将它集成在子项目Spring Cloud Netflix中。
先来看看Eureka的官方架构图
Application Service:服务提供者
Application Client: 服务消费者
Eureka Server提供服务发现的能力,各个微服务启动时,会向Eurekaserver 注册自己的信息(比如IP,端口,微服务名等),Eureka Server会存储这些信息。
Eureka Client是一个Java客户端,用于简化与EurekaServer的交互。
微服务启动后,会周期性(默认30秒)地向Eureka Server发送心跳。
如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳,Eureka Server将会注销该实例(默认90秒)
默认情况下,Eureka Server同时也是Eureka Client。多个Eureka Server实例,互相之间通过复制的方式,来实现服务注册表中数据的同步。
Eureka Client会缓存服务注册表中的信息。微服务无须每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次,即使Eureka Server所有节点都宕机,服务消费者依然可以使用缓存中的信息找到服务提供者并完成调用。
编写Eureka Server
先创建一个Spring Boot项目:
项目pom.xml (这块之前写错了,重新更新一下)
<?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> <groupId>com.learn</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <modules> <module>eureka-server</module> <module>demo-provider-user</module> </modules> <packaging>pom</packaging> <name>demo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> <spring-cloud.version>Edgware.RELEASE</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> </project>
创建一个eureka-server组件, pom.xml
<?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>com.learn</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>eureka-sever</artifactId> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka-server</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
application.yml
server: #Eureka Server的端口 port: 8761 eureka: instance: hostname: localhost client: #是否将自己注册到Eureka Server register-with-eureka: false #是否从Eureka Server获取注册信息 fetch-registry: false #设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址 service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
logback.xml:
<?xml version="1.0" encoding="UTF-8"?> <configuration scan="true" scanPeriod="10 seconds"> <!--定义日志文件的存储地址和前缀名--> <property name="LOG_HOME" value="F://learn//logs" /> <property name="LOG_PREFIX" value="demo-eureka-server" /> <!-- 一般信息按照每天生成日志文件 --> <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <File>${LOG_HOME}/${LOG_PREFIX}-info.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 每天一归档 --> <fileNamePattern>${LOG_HOME}/${LOG_PREFIX}-info-%d{yyyyMMdd}.log.%i</fileNamePattern> <!-- 单个日志文件最多500MB, 30天的日志周期,最大不能超过20GB --> <maxFileSize>100MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern> </encoder> </appender> <!--错误信息按照每天生成日志文件--> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>ERROR</level> </filter> <File>${LOG_HOME}/${LOG_PREFIX}-error.log</File> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> <!-- 每天一归档 --> <fileNamePattern>${LOG_HOME}/${LOG_PREFIX}-error-%d{yyyyMMdd}.log.%i</fileNamePattern> <!-- 单个日志文件最多500MB, 30天的日志周期,最大不能超过20GB --> <maxFileSize>100MB</maxFileSize> <maxHistory>30</maxHistory> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%msg%n</Pattern> </encoder> </appender> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <!-- 日志输出级别 --> <root level="INFO"> <!-- <appender-ref ref="STDOUT" /> --> <appender-ref ref="INFO_FILE" /> <appender-ref ref="ERROR_FILE" /> </root> </configuration>
EurekaServerApplication.java
package com.learn.eurekasever; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; //声明是一个Eureka Server @EnableEurekaServer @SpringBootApplication public class EurekaSeverApplication { public static void main(String[] args) { SpringApplication.run(EurekaSeverApplication.class, args); } }
启动一下,并访问http://localhost:8761
新建一个微服务,并将其注册到Eureka Server上
pom.xml:
<?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>com.learn</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <artifactId>demo-provider-user</artifactId> <packaging>jar</packaging> <name>demo-provider-user</name> <description>Demo project for Spring Boot</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <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> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> </dependencies> </project>
application.yml
spring: application: #注册到Eureka Server上的应用名称 name: demo-provider-user eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: #将自己的IP注册到Eureka Server prefer-ip-address: true
DemoProviderUserApplication.java
package com.learn.demoprovideruser; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; //声明这是一个Eureka Client @EnableEurekaClient @SpringBootApplication public class DemoProviderUserApplication { public static void main(String[] args) { SpringApplication.run(DemoProviderUserApplication.class, args); } }
先运行Eureka-server节点,然后运行DemoProviderUser节点,重新进入http://localhost:8761,看看新启动的微服务是否已经注册到eureka上了
当然,在实际的生产环境上,Eureka Client会定时连接Eureka Server,获取服务注册表中的信息并缓存在本地。即便Eureka Server发生宕机,也不能影响到服务之间的调用。如果是单节点Eureka Server发生宕机的话,某些微服务一旦出现了不可用的情况,Eureka Client中的缓存不被及时更新,就可能会影响到微服务的调用,甚至影响到整个应用系统的高可用。因此在实际生产环境中,通常会部署一个高可用的Eureka Server集群。
在这边,我们把eureka server组件进行一下更新
pom.xml修改成
<artifactId>eureka-sever-ha</artifactId>
application.yml修改成
spring: application: name: eureka-server-ha --- spring: profiles: peer1 server: #Eureka Server的端口 port: 8761 eureka: instance: hostname: peer1 client: service-url: defaultZone: http://peer2:8762/eureka/ --- spring: profiles: peer2 server: #Eureka Server的端口 port: 8762 eureka: instance: hostname: peer2 client: service-url: defaultZone: http://peer1:8761/eureka/
将Eureka-server组件打包成jar包,然后用命令行到jar 包所在位置
用java –jar xxxx.jar –spring-profiles-active=peer1, java –jar xxxx.jar–spring-profiles-active=peer2分别启动两个节点,我们的eureka-server集群环境就搭建好了