微服务架构以及Spring Cloud近年来越来越流行。而Spring Cloud包含内容十分庞杂,要想完全掌握需要花费大量的时间精力。学习一门技术最好的办法,是通过简单实践,搞清楚如何使用该技术的核心功能,同时把主要组件的关系搞明白。随后应用于自己的项目实践中,在实践过程中根据需要,引入各种特性,同时在需要深入研究的部分做深入研究,在遇到问题时刨根问底。真正使用至少1年半载才能谈真正掌握。而面对spring cloud全家桶式的框架,更是如此。不要被各种没听说过的名词吓到,其实spring cloud里每个框架都对应解决一个微服务中的问题,我们学习时也应该先抓住其中几个最核心的问题入手,通过实践,逐步上手,培养信心。如果一上来就通盘学习,难免会陷入贪多嚼不烂,自信受挫的窘境。
本教程紧抓住Spring Cloud中最核心的几个概念,结合实践讲解。让你能够在很短的时间内,自己搭建起一个微服务架构的系统,体验微服务中的核心组件。当然这只是个敲门砖,接下来你可以基于本教程搭建起来的微服务系统,继续学习更多的spring cloud知识。从0到1已经完成,从1到10000当然也没有那么难了。
一般来说,大家对微服务第一感觉,就是把以往一个大的系统拆解为若干个业务相对独立的小系统,小系统对外提供服务,通过相互调用完成完整的业务。这个理解是没错的(但肯定是不够的),这也是微服务系统要解决的核心问题---服务的注册和发现。此外,每个微服务都需要做到负载均衡,并且由于相互间调用频繁,一旦出现问题要能够容错。
本文第一部分所要讲解的也就是如下几个知识点:
接下来直接进入实践部分,假定读者已经具备spring boot的基础知识,并且也已经了解微服务的基本概念。本文代码采用相关版本如下:
Spring Boot | 2.0.2.RELEASE |
Spring Cloud | Finchley.SR2 |
Spring Cloud Netflix | 2.0.1.RELEASE |
Spring Cloud最新版本现在是Greenwich,对应的Spring Boot版本是2.1.x,你也可以尝试使用此版本。
我例子中构建工具使用gradle。当然你也可以使用Maven。
本文的实践中,我们以一个音乐网站举例,搭建三个应用,都采用集群的方式,分别为:
song和lyric会注到服务注册中心上,song应用则会调用lyric应用的服务接口,完成自己的业务。
本文内容预计会耗费你不超过8个小时的时间去学习和实践。
服务注册中心是微服务的核心,所有的服务只有注册到服务注册中心上,才能成为微服务中的一员,彼此调用。本节,我们将使用Spring Cloud Eureka来搭建服务注册中心。
1.1 创建一个Spring Boot的web项目。
1.2 修改构建文件build.gradle,添加对Spring Cloud的支持以及Eureka的依赖。
1.2.1添加依赖管理插件
apply plugin: 'io.spring.dependency-management'
1.2.2添加版本号变量
ext {
springCloudVersion = 'Finchley.SR2'
springCloudNetflixVersion = '2.0.1.RELEASE'
}
1.2.3添加依赖管理
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
imports {
mavenBom "org.springframework.cloud:spring-cloud-netflix:${springCloudNetflixVersion}"
}
}
1.2.4添加Eureka server端依赖
dependencies {
compile(
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
)
}
build.gradle完成代码
buildscript {
ext {
springBootVersion = '2.0.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath("org.springframework:springloaded:1.2.7.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse-wtp'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.liyiming.beyond'
version = '1.0'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
configurations {
providedRuntime
}
ext {
springCloudVersion = 'Finchley.SR2'
springCloudNetflixVersion = '2.0.1.RELEASE'
junitVersion = '4.12'
lombokVersion = '1.18.4'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
imports {
mavenBom "org.springframework.cloud:spring-cloud-netflix:${springCloudNetflixVersion}"
}
}
dependencies {
compile(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server',
)
providedRuntime('org.springframework.boot:spring-boot-starter-tomcat')
testCompile('org.springframework.boot:spring-boot-starter-test')
}
1.3 代码修改
boot主类添加@EnableEurekaServer
@EnableEurekaServer
@SpringBootApplication
public class BeyondApplication {
public static void main(String[] args) {
SpringApplication.run(BeyondApplication.class, args);
}
}
1.4、配置文件
为了测试高可用的配置中心,我们将启动两个eureka server服务,单机模拟集群环境。为了方便测试,我们创建两个配置文件,启动时分别使用。
先创建application-peer1.yml,内容如下:
server:
port: 1111
tomcat:
uri-encoding: UTF-8
eureka:
client:
serviceUrl:
defaultZone: http://peer2:1112/eureka/
instance:
hostname: peer1
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
application:
name: eureka-server
在创建一个application-peer2.yml,内容如下
server:
port: 1112
tomcat:
uri-encoding: UTF-8
eureka:
client:
serviceUrl:
defaultZone: http://peer1:1111/eureka/
instance:
hostname: peer2
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
application:
name: eureka-server
说明:
1、peer1端口使用1111,peer2使用端口1112.
2、设置hostname分别为peer1,peer2
3、defaultZone。peer1指向peer2,peer2指向peer1。互相注册到对方。
4、spring.application.name。为服务命名。在eureka上同名的服务属于一个服务集群。也可以用eureka.instance.appname。但spring.application.name 优先级更高。
5、spring.freemarker。配置好freemarker后,才可以正确显示eureka server的控制台页面。
1.5修改hosts文件
我是mac环境,打开/etc/hosts,配置peer1和peer2,如下:
127.0.0.1 peer1
127.0.0.1 peer2
1.6启动调试
为了实验集群的效果,我们启动两个Eureka的节点。因此在1.4中编写了两个配置文件,我们通过在IDEA中的配置,分别以这两个配置文件启动项目。IDEA中新建一个Run/Debug Configurations,选择Spring Boot,然后按照下图填好,注意红框,手动填入pee1。保存后,以同样的方式再创建一个Run/Debug Configuration,唯一不同是填入peer2。然后分别启动peer1和peer2. 如果能够正常启动,并且能看到启动log中有一行:The following profiles are active: peer1(或者是peer2)。说明已经按照不同配置文件启动成功了。
接下来我们打开浏览器,输入http://peer1:1111或者http://peer2:1112/,看到如下管理界面,说明Eureka已经启动成功。
图为我访问peer2看到的界面。可以看到Eureka-Server注册上来两个实例。另外可以看到注册上来的副本有peer1,并且是avaliable副本
至此我们已经搭建起Eureka服务注册中心集群,下一节我们来学习如何把服务注册上来。
本节我们新建一个工程,并且把他注册到刚刚搭建好的服务注册中心上。好吧,既然是实践,为了更真实,我们需要假想一个项目。我爱好音乐,假设我们建设一个音乐网站,其中一个微服务是歌曲(song),另外一个微服务是歌词(lyric)。我们先创建一个名为song的Spring Boot项目。创建完成后,按如下步骤操作。
2.1修改构建文件build.gradle
首先按照1.2.1到1.2.3进行修改。然后添加Eureka客户端的依赖:
dependencies {
compile(
'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
)
}
2.2配置文件
为了演示负载均衡,我们将会以集群的形式启动song服务,所以此处我们同样创建两个配置文件。
application-song1.yml
server:
port: 9081
tomcat:
uri-encoding: UTF-8
eureka:
client:
service-url:
defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
spring:
application:
name: song
application-song2.yml
server:
port: 9082
tomcat:
uri-encoding: UTF-8
eureka:
client:
service-url:
defaultZone: http://peer1:1111/eureka/,http://peer2:1112/eureka/
spring:
application:
name: song
两份配置文件,除了server.port不同,其他全部一样。
注意配置项eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/。这个配置项告诉了应用Eureka的地址,我们把之前创建的Eureka集群两台机器的地址都配上,以让应用注册到服务注册中心上。另外spring.application.name需要一样,这样Eureka才会认为两个song的应用属于同一个集群。
2.3编写测试代码
创建一个controller
添加一个接口:
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(){
return "song application say hello";
}
2.4启动调试
按照上节启动注册中心集群的方式创建两个Run/Debug Configurations,分别使用两个不同的配置文件,然后启动,此时我们再次访问Eureka服务,看到如下界面:
可以看到song应用启动成功,有两个实例。
我们访问任意一个,比如localhost:9081/hello, 可以看到正确输出song application say hello。
至此song微服务已经成功注册到Eureka上,并且自己也是可以被成功访问到的。接下来我们将会进入最核心的服务发现与消费。
Spring Cloud中,服务发现由Eureka实现,而消费则是由Ribbon完成。Ribbon通过Eureka获取服务清单,轮询访问实现负载均衡。不过本文不展开讲解Ribbon的使用,而是讲解Ribbon更高层的封装Feign。Feign整合了Ribbon和Hystrix(容错处理),并且它还提供了声明式的web服务客户端定义。使用Feigin,只需要按照Spring MVC的注解方式,在调用方service层按照服务提供方的接口定义,再做一次接口声明即可。调用时,只需要调用service接口中声明的方法即可。http请求发送、负载均衡无需关注,feign都帮你处理好了。所以在实践中一般都是直接使用Feign。关于Feign的特点和简单用法可以参照下图:
使用Feign也很简单,上图已经给出了简单用法,下面我将按步骤讲解。
3.1 创建服务提供方lyric应用
我们再创建一个lyric应用,提供歌词服务,而song应用会调用lyric的服务接口。创建lyric应用的步骤同第二章讲解的创建song应用一样。只需要修改配置文件端口以及修改application.name为lyric。
同样也为lyric创建一个controller,添加一个接口,用以测试:
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello(){
return "lyric application say hello";
}
我们也同样启动起来两个实例,此时打开Eureka的管理界面,可以看到多了lyric的应用:
接下来我们对song应用做一些修改,通过Feign调用lyric应用的hello接口。
3.2 为song添加Feign依赖
spring cloud的相关依赖添加请参考上面章节,这里默认已经添加。使用Feign只需要再添加如下依赖:
dependencies {
implementation('org.springframework.cloud:spring-cloud-starter-openfeign')
}
3.3 接口声明代码
3.3.1 song的boot主类增加注解
声明本应用是一个Feign的客户端
@EnableFeignClients
3.3.2 声明被调用的接口
增加service接口类,根据被调用方的接口定义,进行声明,代码示例如下:
@Service
@FeignClient("lyric")
public interface LyricServiceFeign {
@RequestMapping("/hello")
String hello();
}
@FeignClient("lyric"),lyric是服务方注册到Eureka的application 名称,即微服务的名称。
@RequestMapping("/hello"),声明了被调用方的接口地址。
3.4 接口调用
在需要调用服务方接口的类中,注入上步的接口类LyricServiceFeign:
@Autowired
LyricServiceFeign lyricServiceFeign;
直接调用接口的方法即可
lyricServiceFeign.hello()
此时feign会向服务方(lyric应用)发起http请求,而且负载均衡。
完全代码如下:
@Autowired
LyricServiceFeign lyricServiceFeign;
@RequestMapping(value = "/LyricHello", method = RequestMethod.GET)
public String knowledgeHello(){
return "song invoke lyric service , "+lyricServiceFeign.hello();
}
3.5 测试
重启song应用,访问song应用的接口 http://localhost:9081/LyricHello,可以看到页面显示song invoke lyric service , lyric application say hello。并且通过多次访问,可以看到lyric不同节点均匀打印日志,说明通过feign对服务的调用是负载均衡的
学习到这里,我们掌握了如何通过Eureka搭建服务注册中心;应用如何注册到服务注册中心;微服务之间如何通过Feign进行调用。至此,Spring Cloud最核心的内容已经通过实践,正常运转起来了。不过Spring Cloud远不止这些内容,接下来我们还会讲解网关以及分布式服务跟踪的相关实践。此外关于Eureka和Feign还需要看更多的资料,学习的更为深入。另外Feign封装的Ribbon和Hystrix,如果有时间,也是很有必要单独学习的。
相信本文的实践在8小时内是可以完成的,甚至于都用不上8个小时,但是我们的收获还是很大的,微服务的核心架子已经搭建了起来,组件间如何配合工作也很清楚。后一部分的学习是基于本文搭建起来的环境继续实践的,所以我们一定要一步步夯实,从核心内容扩散开来,去接触更多的知识。