Spring Cloud简介
Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。
本文将介绍基于springBoot2.0正式版的springCloud的微服务搭建以及需要注意的细节.
1.服务注册与发现
在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud搭建服务注册与发现模块。
这里我们会用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。
所以,我们这里的核心内容就是服务发现模块:Eureka。下面我们动手来做一些尝试。
创建“服务注册中心”
1.1 创建springboot项目
1.2 修改pom文件,添加spring cloud 依赖如下
<dependencies><!-- springCloud 配置 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency> <!-- springCloud注测中心服务 --><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>
红色标记部分和2.0之前的版本有区别一定要注意:
2.0之前的版本为:
<dependencies> <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-server</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.RELEASE</version><type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
1.3 在启动类上加上注解 如下
通过@EnableEurekaServer
注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:
@SpringBootApplication @EnableEurekaServer public class Demo5Application {public static void main(String[] args) {SpringApplication.run(Demo5Application.class, args);} }
1.4 配置文件application.properties
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties
中问增加如下配置:
#注册中心服务ID spring.application.name=compute-server#端口号 server.port=1111 # eureka.client.registerWithEureka :表示是否将自己注册到Eureka Server,默认为true。 # 由于当前这个应用就是Eureka Server,故而设为false eureka.client.register-with-eureka=false # eureka.client.fetchRegistry :表示是否从Eureka Server获取注册信息,默认为true。因为这是一个单点的Eureka Server, # 不需要同步其他的Eureka Server节点的数据,故而设为false。 eureka.client.fetch-registry=false # eureka.client.serviceUrl.defaultZone :设置与Eureka Server交互的地址,查询服务和注册服务都需要依赖这个地址。默认是 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
1.5配置文件application.properties
为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port
属性设置为1111
。
启动工程后,访问:http://localhost:1111/
可以看到下面的页面,其中还没有发现任何服务:
2.搭建服务端
2.1 创建springboot项目同上
2.2 修改pom.xml文件,添加spring cloud 依赖如下(红色标记一定要添加)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></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>
2.3 在启动类上加上注解 如下
最后在主类中通过加上@EnableEurekaClient 注解,该注解能激活Eureka中的对注册中心注册服务实现,才能实现Controller中对服务信息的输出。
@SpringBootApplication @EnableEurekaClient public class Demo3Application {public static void main(String[] args) {SpringApplication.run(Demo3Application.class, args);} }
2.4 配置文件application.properties
#服务名称 spring.application.name=compute-service1 #端口号 server.port=2222 #在注册中心中进行注册 eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ #启动服务发现的功能,开启了才能调用其它服务 spring.cloud.config.discovery.enabled=true #发现的服务的名字--对应注测中心的服务名字 spring.cloud.config.discovery.serviceId=compute-server
通过spring.application.name
属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。
eureka.client.serviceUrl.defaultZone
属性对应服务注册中心的配置内容,指定服务注册中心的位置。
为了在本机上测试区分服务提供方和服务注册中心,使用server.port
属性设置不同的端口。
2.5 启动该项目
再次访问:http://localhost:1111/
可以看到,我们定义的服务被注册了。如下图所示:
3 spring cloud 路由网关服务---Zuul
在使用Zuul之前,我们先构建一个服务注册中心、以及两个简单的服务,比如:我构建了一个service-A,一个service-B。然后启动eureka-server和这两个服务。通过访问eureka-server,我们可以看到service-A和service-B已经注册到了服务中心。
服务service-A和service-B的配置都一样并实现在注册中心注册,只有端口号不一样而已如下所示:
service-A:
并在service-A:下构建一个controller如下:
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;@RestController public class MyController {@RequestMapping(value = "/info" ,method = RequestMethod.GET)public String info() { return "hello I am is spring-serviceA"; //测试代码直接返回一个字符串,不再调用service层等等。 } }
service-B:
并在service-B下构建一个controller如下:
package com.example.demo.controller;import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController;@RestController public class MyController {@RequestMapping(value = "/info" ,method = RequestMethod.GET)public String info() { return "hello I am is spring-service"; //测试代码直接返回一个字符串,不再调用service层等等。 } }
3.1 创建 spring boot项目 同上
3.2 修改pom.xml文件,添加spring cloud 依赖如下
<dependencies><!-- springBoot 核心 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-config</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-zuul</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
3.3 在启动类上加上注解 如下
- 应用主类使用
@EnableZuulProxy
注解开启Zuul - 这里用了@SpringCloudApplication注解,之前没有提过,通过源码我们看到,它整合了@SpringBootApplication、@EnableEurekaClient、@EnableCircuitBreaker,主要目的还是简化配置。这几个注解的具体作用这里就不做详细介绍了,之前的文章已经都介绍过。
@EnableZuulProxy @SpringCloudApplication public class Demo4Application {public static void main(String[] args) {SpringApplication.run(Demo4Application.class, args);} }
3.4 配置文件application.properties
application.properties
中配置eureka服务注册中心
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ server.port=3333 spring.application.name=service-zuul #表示只要访问以/api-a/开头的多层目录都可以路由到 id为compute-service的服务上 zuul.routes.compute-service=/api-a/**
#表示只要访问以/api-a/开头的多层目录都可以路由到 id为compute-service1的服务上
zuul.routes.compute-service1=/api-a/**
上面的一行等同于下面的两行
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=compute-service1
通配符 | 含义 | 举例 | 解释 |
---|---|---|---|
? | 匹配任意单个字符 | /feign-consumer/? | 匹配/feign-consumer/a,/feign-consumer/b,/feign-consumer/c等 |
* | 匹配任意数量的字符 | /feign-consumer/* | 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,无法匹配/feign-consumer/a/b/c |
** | 匹配任意数量的字符 | /feign-consumer/* | 匹配/feign-consumer/aaa,feign-consumer/bbb,/feign-consumer/ccc等,也可以匹配/feign-consumer/a/b/c |
3.4 启动项目 测试
输入地址:http://localhost:4444/api-a/info
每次刷新访问都会在下面结果轮询显示:
这就简单实现了springCloud的负载均衡.
3.5 服务过滤
在完成了服务路由之后,我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源。所以我们需要利用Zuul的过滤器来实现我们对外服务的安全控制。
在服务网关中定义过滤器只需要继承ZuulFilter
抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤。
比如下面的例子,定义了一个Zuul过滤器,实现了在请求被路由之前检查请求中是否有accessToken
参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized
错误。
package com.example.demo.filter;import javax.servlet.http.HttpServletRequest;import org.springframework.util.StringUtils;import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext;public class AccessFilter extends ZuulFilter {@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest();System.out.println(String.format("%s demoFilter request to %s", request.getMethod(), request.getRequestURL().toString()));String username = request.getParameter("username");// 获取请求的参数 if(!StringUtils.isEmpty(username)&&username.equals("lilei")){//通过ctx.setSendZuulResponse(true);// 对该请求进行路由 ctx.setResponseStatusCode(200); ctx.set("isSuccess", true);// 设值,让下一个Filter看到上一个Filter的状态 return null; }else{ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由 ctx.setResponseStatusCode(401);// 返回错误码 ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回错误内容 ctx.set("isSuccess", false); return null;}}@Overridepublic boolean shouldFilter() {return true;// 是否执行该过滤器,此处为true,说明需要过滤 }@Overridepublic int filterOrder() {return 0;// 优先级为0,数字越大,优先级越低 }/*pre:可以在请求被路由之前调用route:在路由请求时候被调用post:在route和error过滤器之后被调用error:处理请求时发生错误时被调用*/@Overridepublic String filterType() {return "pre";// 前置过滤器 }}
自定义过滤器的实现,需要继承ZuulFilter
,需要重写实现下面四个方法:
filterType
:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:pre
:可以在请求被路由之前调用routing
:在路由请求时候被调用post
:在routing和error过滤器之后被调用error
:处理请求时发生错误时被调用
filterOrder
:通过int值来定义过滤器的执行顺序shouldFilter
:返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。在上例中,我们直接返回true,所以该过滤器总是生效。run
:过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)
令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)
设置了其返回的错误码,当然我们也可以进一步优化我们的返回,比如,通过ctx.setResponseBody(body)
对返回body内容进行编辑等。
在实现了自定义过滤器之后,还需要实例化该过滤器才能生效,我们只需要在应用主类中增加如下内容:
再次访问链接:http://localhost:4444/api-a/info
提示如下错误:被过滤器给拦截了
访问新地址:http://localhost:4444/api-a/info?username=lilei
则可以正常访问
项目源码:https://download.csdn.net/download/guokezhongdeyuzhou/10303861
由于时间原因暂时更新到此,后面会陆续更新相关的其他特性.