Spring Cloud 是一个微服务框架,与 Spring Boot 结合,开发简单。将一个大工程项目,分成多个小 web 服务工程,可以分别独立扩展,又可以共同合作。

环境
  1. spring 官网的 sts 3.9.2,就是有spring 相关插件的eclipse;
  2. apache maven 3.5.4,配置阿里云镜像
  3. jdk1.8
  4. Spring Cloud Finchley版本
  5. Spring Boot 2.0.3
Spring Cloud 组件

服务注册中心、服务、断路器、配置中心

服务的注册与发现 - Eureka

使用 Eureka 来实现服务注册中心、服务提供者和服务消费者。

服务注册中心

一个服务注册中心,所有的服务都在注册中心注册,负载均衡也是通过在注册中心注册的服务来使用一定策略来实现。

1.新建一个 Spring Boot 工程,用来管理服务,elipse右键 -> new -> Spring Starter Project :


点击 next ,选择 Cloud Discovery 下的 Eureka Server 组件


生成的 pom 文件:


<?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>
<span ><<span >groupId</span>></span>com.beigege.cloud<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>eureka-server<span ></<span >artifactId</span>></span>
<span ><<span >version</span>></span>0.0.1-SNAPSHOT<span ></<span >version</span>></span>
<span ><<span >packaging</span>></span>jar<span ></<span >packaging</span>></span>

<span ><<span >name</span>></span>eureka-server<span ></<span >name</span>></span>
<span ><<span >description</span>></span>spring cloud学习<span ></<span >description</span>></span>

<span ><<span >parent</span>></span>
<span ><<span >groupId</span>></span>org.springframework.boot<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>spring-boot-starter-parent<span ></<span >artifactId</span>></span>
<span ><<span >version</span>></span>2.0.3.RELEASE<span ></<span >version</span>></span>
<span ><<span >relativePath</span> /></span> <span class="hljs-comment"><!-- lookup parent from repository --></span>
<span ></<span >parent</span>></span>

<span ><<span >properties</span>></span>
<span ><<span >project.build.sourceEncoding</span>></span>UTF-8<span ></<span >project.build.sourceEncoding</span>></span>
<span ><<span >project.reporting.outputEncoding</span>></span>UTF-8<span ></<span >project.reporting.outputEncoding</span>></span>
<span ><<span >java.version</span>></span>1.8<span ></<span >java.version</span>></span>
<span ><<span >spring-cloud.version</span>></span>Finchley.RELEASE<span ></<span >spring-cloud.version</span>></span>
<span ></<span >properties</span>></span>

<span ><<span >dependencies</span>></span>
<span ><<span >dependency</span>></span>
<span ><<span >groupId</span>></span>org.springframework.boot<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>spring-boot-starter-test<span ></<span >artifactId</span>></span>
<span ><<span >scope</span>></span>test<span ></<span >scope</span>></span>
<span ></<span >dependency</span>></span>
<span ><<span >dependency</span>></span>
<span ><<span >groupId</span>></span>org.springframework.cloud<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>spring-cloud-starter-netflix-eureka-server<span ></<span >artifactId</span>></span>
<span ></<span >dependency</span>></span>
<span ></<span >dependencies</span>></span>
<span ><<span >dependencyManagement</span>></span>
<span ><<span >dependencies</span>></span>
<span ><<span >dependency</span>></span>
<span ><<span >groupId</span>></span>org.springframework.cloud<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>spring-cloud-dependencies<span ></<span >artifactId</span>></span>
<span ><<span >version</span>></span>${spring-cloud.version}<span ></<span >version</span>></span>
<span ><<span >type</span>></span>pom<span ></<span >type</span>></span>
<span ><<span >scope</span>></span>import<span ></<span >scope</span>></span>
<span ></<span >dependency</span>></span>
<span ></<span >dependencies</span>></span>
<span ></<span >dependencyManagement</span>></span>
<span ><<span >build</span>></span>
<span ><<span >plugins</span>></span>
<span ><<span >plugin</span>></span>
<span ><<span >groupId</span>></span>org.springframework.boot<span ></<span >groupId</span>></span>
<span ><<span >artifactId</span>></span>spring-boot-maven-plugin<span ></<span >artifactId</span>></span>
<span ></<span >plugin</span>></span>
<span ></<span >plugins</span>></span>
<span ></<span >build</span>></span>

</project>


2.使用 @EnableEurekaServer 来说明项目是一个 Eureka:


​@SpringBootApplication
@EnableEurekaClient
@RestController
public class EurekaClientApplication {​

<span >public</span> static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.<span >class</span>, args);
}

<span >@Value(<span >"<span class="hljs-subst">${server.port}</span>"</span>)</span>
String port;

<span >@RequestMapping(<span >"/hi"</span>)</span>
<span >public</span> String home(<span >@RequestParam(value = <span >"name"</span>, defaultValue = <span >"beigege"</span>)</span> String name) {
<span >return</span> <span >"hi "</span> + name + <span >" ,i am from port:"</span> + port;
}

​}​

4.启动 Eureka Server 和 Eureka Client 项目,访问​​http://localhost​​:8761 ,即eureka server 的网址:


服务消费者 - Ribbon | Feign

Spring Cloud 两种调用服务的方式,ribbon + restTemplate,和 feign。

Ribbon

ribbon 在客户端实现了负载均衡。

启动 Eureka Server 和 Eureka Client,修改 Eureka Client 端口,再启动一个 Eureka Client 实例,相当于一个小的集群,访问localhost:8761


1.新建一个 spring boot 工程,名称为 service-ribbon,创建过程和 eureka-client 一样,组件多选一个 Cloud Routing 下的 Ribbon,创建完成之后的 pom 文件依赖:


​@SpringBootApplication
@EnableEurekaClient
public class ServiceRibbonApplication {​

<span class="hljs-function"><span >public</span> <span >static</span> <span >void</span> <span >main</span><span class="hljs-params">(String[] args)</span> </span>{
SpringApplication.run(ServiceRibbonApplication.class, args);
}

<span >@Bean</span>
<span >@LoadBalanced</span>
<span class="hljs-function">RestTemplate <span >restTemplate</span><span class="hljs-params">()</span> </span>{
<span >return</span> <span >new</span> RestTemplate();
}

​}​

3.写一个接口用来调用之前的 service-hi 服务的 /hi 接口:

新建 service 层,名叫 HelloService,调用 /hi 接口,这里用服务名 SERVICE-HI 代替具体的 URL:


​@RestController
public class HelloControler {​

<span >@Autowired</span>
HelloService helloService;
<span >@RequestMapping(value = <span >"/hi"</span>)</span>
<span >public</span> String hi(<span >@RequestParam</span> String name){
<span >return</span> helloService.hiService(name);
}

​}​

4.启动 service-ribbon 工程,多次访问该工程的 /hi 接口,即 localhost:8764/hi?name=test:

hi test ,i am from port:8762hi test ,i am from port:8763

上面交替显示,说明实现了负载均衡,ribbon 会在客户端发送请求时做一个负载均衡。

Feign

整合了 Ribbon,具有负载均衡的能力,整合了Hystrix,具有熔断的能力.

1.创建一个 spring boot工程,创建过程和 eureka-client 一样,多添加一个组件 Cloud Routing 下的 Feign,项目名叫 service-feign,创建完后的 pom 文件依赖:


​@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class SericeFeignApplication {​

<span >public</span> <span >static</span> <span >void</span> main(String[] args) {
SpringApplication.run(SericeFeignApplication.<span >class</span>, args);
}

​}​

2.写一个 SchedualServiceHi 接口,通过@ FeignClient(“服务名”),来指定调用哪个服务,@GetMapping("接口名"),来向接口发送 Get 请求,@RequestParam 是请求参数:


​@RestController
public class HiController {​

<span >@Autowired</span>
SchedualServiceHi schedualServiceHi;

<span >@GetMapping(value = <span >"/hi"</span>)</span>
<span >public</span> String sayHi(<span >@RequestParam</span> String name) {
<span >return</span> schedualServiceHi.sayHiFromClientOne( name );
}

​}​

3.启动 service-feign,访问localhost:8765/hi?name=test 测试,下面交替显示,说明实现了负载均衡:

hi test ,i am from port:8762 hi test ,i am from port:8763

断路器 - Hystrix

链式调用服务,其中一个服务宕机,其他服务可能会跟着异常,发生雪崩,当对一个服务的调用失败次数到达一定阈值,断路器会打开,执行服务调用失败时的处理,避免连锁故障,上面已经启动:eureka-server、两个 eureka-client 实例、service-ribbon 和 service-feign,下面继续修改,分别在 service-ribbon 和 service-feign 上实现断路器。

service-ribbon + Hystrix

1.在 pom 文件中加入 Hystrix 依赖:


<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>


2.在程序的启动类 ServiceRibbonApplication 加 @EnableHystrix 注解开启Hystrix:


​@Service
public class HelloService {​

<span >@Autowired</span>
RestTemplate restTemplate;

<span >@HystrixCommand</span>(fallbackMethod = <span >"hiError"</span>)
<span >public</span> <span class="hljs-built_in">String</span> hiService(<span class="hljs-built_in">String</span> name) {
<span >return</span> restTemplate.getForObject(<span >"http://SERVICE-HI/hi?name="</span>+name,<span class="hljs-built_in">String</span>.class);
}

<span >public</span> <span class="hljs-built_in">String</span> hiError(<span class="hljs-built_in">String</span> name) {
<span >return</span> <span >"hi,"</span>+name+<span >",sorry,error!"</span>;
}

​}​

重启 service-ribbon,访问 localhost:8764/hi?name=test

hi test ,i am from port:8763

关闭两个 eureka-client 实例,在访问 localhost:8764/hi?name=test,浏览器显示:

hi,test,sorry,error!

当服务不可用时,断路器会迅速执行熔断方法

service-feign + Hystrix

1.Feign 自带断路器,在D版本的Spring Cloud之后,它没有默认打开。需要在配置文件中配置打开:


feign: 
hystrix:
enabled: true


2.在请求接口指定熔断实现类,fallback 指定熔断实现类:


​@Component
public class SchedualServiceHiHystric implements SchedualServiceHi{​

<span >@Override</span>
<span class="hljs-function"><span >public</span> String <span >sayHiFromClientOne</span><span class="hljs-params">(String name)</span> </span>{
<span >return</span> <span >"sorry "</span>+name;
}

​}

3.访问测试,重启 service-feign,访问 localhost:8765/hi?name=test:

sorry test

打开 eureka-client,再次访问 localhost:8765/hi?name=test:

hi test ,i am from port:8762

证明断路器起到作用,注意浏览器缓存。

路由网关 - Zuul

1.创建一个 spring boot 工程,名称为 service-zuul,创建过程和 eureka-client 一样,组件要多添加一个 Cloud Routing 下的 Zuul,pom 文件的依赖如下:


        <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-web</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>


2.在入口 application 中添加 @EnableZuulProxy 注解,开启路由:


​@Component
public class MyFilter extends ZuulFilter {​

<span >private</span> <span >static</span> Logger log = LoggerFactory.getLogger(MyFilter.class);

<span class="hljs-comment">/*
* filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:
* pre:路由之前
* routing:路由之时
* post: 路由之后
* error:发送错误调用
*/</span>
<span >@Override</span>
<span class="hljs-function"><span >public</span> String <span >filterType</span><span class="hljs-params">()</span> </span>{
<span >return</span> <span >"pre"</span>;
}

<span class="hljs-comment">/*
* filterOrder:过滤的顺序
*/</span>
<span >@Override</span>
<span class="hljs-function"><span >public</span> <span >int</span> <span >filterOrder</span><span class="hljs-params">()</span> </span>{
<span >return</span> <span class="hljs-number">0</span>;
}

<span class="hljs-comment">/*
* shouldFilter:这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
*/</span>
<span >@Override</span>
<span class="hljs-function"><span >public</span> <span >boolean</span> <span >shouldFilter</span><span class="hljs-params">()</span> </span>{
<span >return</span> <span >true</span>;
}

<span class="hljs-comment">/*
* run:过滤器的具体逻辑。可以很复杂,包括查sql,nosql去判断该请求到底有没有权限访问,
* 这里是判断请求有没有 token 参数
*/</span>
<span >@Override</span>
<span class="hljs-function"><span >public</span> Object <span >run</span><span class="hljs-params">()</span> </span>{
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
log.info(String.format(<span >"%s >>> %s"</span>, request.getMethod(), request.getRequestURL().toString()));
Object accessToken = request.getParameter(<span >"token"</span>);
<span >if</span>(accessToken == <span >null</span>) {
log.warn(<span >"token is empty"</span>);
ctx.setSendZuulResponse(<span >false</span>);
ctx.setResponseStatusCode(<span class="hljs-number">401</span>);
<span >try</span> {
ctx.getResponse().getWriter().write(<span >"token is empty"</span>);
}<span >catch</span> (Exception e){}

<span >return</span> <span >null</span>;
}
log.info(<span >"ok"</span>);
<span >return</span> <span >null</span>;
}

​}​

重启 service-zuul 访问 localhost:8769/api-a/hi?name=test,浏览器显示

token is empty

加上 token 参数:localhost:8769/api-a/hi?name=test&token=666

hi test ,i am from port:8762

分布式配置中心

远程资源库

1.这里在 ​​码云​​ 注册一个账号,用来创建存储配置的资源库,在码云上创建一个名叫 SpringCloudConfig 项目:


2.新建一个配置文件 config-client-dev.properties,也可以是 yml 配置文件,内容为 test = version 1,然后提交:

2.默认是 master 分支,新建一个分支,名字叫 aa:


修改 aa 分支中的 config-client-dev.properties 配置文件的 test 属性为 version 2:


config-server

1.新建 spring boot 项目,组件选择 Cloud Config 中的 Config Server,pom 中的依赖:


​@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {​

<span >public</span> <span >static</span> <span >void</span> main(String[] args) {
SpringApplication.run(ConfigServerApplication.<span >class</span>, args);
}

​}

application.yml 配置文件,如果是公开的不需要填写用户名密码:


https://gitee.com/candy-boy/SpringCloudConfig #配置git仓库地址,后面的.git可有可无

username: #访问git仓库的用户名,码云登陆用户名

password: #访问git仓库的用户密码,码云登陆密码" title="" data-original-title="复制">


server:
port: 8888
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://gitee.com/candy-boy/SpringCloudConfig #配置git仓库地址,后面的.git可有可无
username: #访问git仓库的用户名,码云登陆用户名
password: #访问git仓库的用户密码,码云登陆密码


2.访问测试,启动程序,访问配置文件,可以如下访问:

  • {application}/{profile}[/{label}]如 localhost:8888/config-client/dev,即访问 config-client-dev.properties,其中 {application} 就是 最后一道横线前面的 config-client,{profile} 是最后一道横线后面到点,即 dev,{label} 指的是资源库的分支,不填则为默认分支,刚创建的资源库,默认分支是 master,访问结果如下:


其他访问方式如:

/{application}/{profile}[/{label}]/{application}-{profile}.yml/{label}/{application}-{profile}.yml/{application}-{profile}.properties/{label}/{application}-{profile}.properties

如果是下面这样,可能是用户名或密码错误:


3.测试其他分支,访问 aa 分支下的 config-client-dev.properties,localhost:8888/config-client/dev/aa


config-client

待续....

            </div>