SpringCloud

SpringCloud是什么

SpringCloud是一个微服务框架,Spring Cloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。
何为微服务?简而言之,微服务架构风格这种开发方法,是以开发一组小型服务的方式来开发一个独立的应用系统的。其中每个小型服务都运行在自己的进程中,并经常采用HTTP资源API这样轻量的机制来相互通信。这些服务围绕业务功能进行构建,并能通过全自动的部署机制来进行独立部署。这些微服务可以使用不同的语言来编写,并且可以使用不同的数据存储技术。对这些微服务我们仅做最低限度的集中管理。
一个微服务一般完成某个特定的功能,比如下单管理、客户管理等等。每一个微服务都是微型六角形应用,都有自己的业务逻辑和适配器。一些微服务还会发布API给其它微服务和应用客户端使用。

服务的注册与发现:eureka

Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。

Eureka包含两个组件:Eureka Server和Eureka Client。

Eureka Server提供服务注册服务,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。

Eureka Client是一个java客户端,用于简化与Eureka Server的交互,客户端同时也就别一个内置的、使用轮询(round-robin)负载算法的负载均衡器。

在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即使所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。

在yml文件中配置eureka的有关参数有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
eureka:
instance:
# hostname: T231
# 使用IP注册,Spring就会自动为我们获取第一个非回环IP地址
prefer-ip-address: true
# 心跳间隔
lease-renewal-interval-in-seconds: 3
# 服务失效时间: 如果多久没有收到请求,则可以删除服务
lease-expiration-duration-in-seconds: 7
client:
# 关闭eureka client
# enabled: false
# 注册自身到eureka服务器
register-with-eureka: true
# 表示是否从eureka服务器获取注册信息
fetch-registry: false
# 客户端从Eureka Server集群里更新Eureka Server信息的频率
eureka-service-url-poll-interval-seconds: 60
# 定义从注册中心获取注册服务的信息
registry-fetch-interval-seconds: 5
# 设置eureka服务器所在的地址,查询服务和注册服务都需要依赖这个地址
serviceUrl:
# 设置eureka服务器所在的地址,可以同时向多个服务注册服务
defaultZone: http://127.0.0.1:8000/eureka/
server:
# renewal-percent-threshold: 0.1
# 关闭自我保护模式
enable-self-preservation: false
# Eureka Server 自我保护系数,当enable-self-preservation=true时,启作用
# renewal-percent-threshold:
# 设置清理间隔,单位为毫秒,默认为0
eviction-interval-timer-in-ms: 3000
# 设置如果Eureka Server启动时无法从临近Eureka Server节点获取注册信息,它多久不对外提供注册服务
wait-time-in-ms-when-sync-empty: 6000000
# 集群之间相互更新节点信息的时间频率
peer-eureka-nodes-update-interval-ms: 60000

创建一个Maven父工程

因为我们在使用注册中心时,会创建出很多个工程里面用到的依赖很相似,所以这里我们先创建一个父工程出来,主要用来管理依赖版本。这里我们选择Spring Boot的版本为2.0.4.RELEASE,Spring Cloud的版本Finchley.SR1。创建一个名字叫做spring-cloud-parent的父工程,pom.xml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!-- 继承spring boot的项目 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath />
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<!-- 管理spring cloud依赖包 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<!-- 导入spring webmvc -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>

服务注册中心(Eureka Server)

创建子工程eureka-server作为服务注册中心,pom.xml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
<parent>
<groupId>com.aowin</groupId>
<artifactId>spring-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>

在src/main/resources目录下添加配置文件application.properties,配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 修改服务的端口号
server:
port: 8000
# 给服务取一个名字
spring:
application:
name: eureka-server
eureka:
client:
# 表示当前服务是一个eureka的服务端
register-with-eureka: false
fetch-registry: false
service-url:
# 设置eureka服务器所在的地址,可以同时向多个服务注册服务,这里是自己注册自己
defaultZone: http://127.0.0.1:8000/eureka
instance:
# 设置eureka的服务的域名或者ip
hostname: 127.0.0.1

我们这里配置的服务器名字为eureka-server,ip地址为127.0.0.0,端口为8000。

启动一个Eureka服务注册中心程序
只需要在SpringBoot的启动程序类上添加注解@EnableEurekaServer即可。

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}

创建服务提供者(Eureka Client)

创建子工程(order-service),作为服务的提供者,并且将其注册到Eureka的Server中。pom.xml文件配置jar包:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>

在src/main/resources目录下添加配置文件application.properties,配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 修改服务的端口号
server:
port: 8100
# 给服务取一个名字
spring:
application:
name: order-service
eureka:
instance:
# 设置eureka的服务的域名或者ip
hostname: 127.0.0.1
client:
service-url:
# 设置eureka服务器所在的地址,可以同时向多个服务注册服务
defaultZone: http://127.0.0.1:8000/eureka

创建服务程序,包结构如下:

在OrderServerController.java中

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class OrderServerController {

@Value("${server.port}")
private String port;

@RequestMapping("/order")
public String execute(@RequestParam String goodsId, @RequestParam String userId) {
return port + "-->处理订单,商品id:" + goodsId + ",用户id:" + userId;
}
}

在OrderServerApplication.java中

1
2
3
4
5
6
7
@SpringBootApplication
@EnableEurekaClient
public class OrderServerApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServerApplication.class, args);
}
}

启动注册服务和客户端测试:

服务的调用方式

在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的。Spring cloud有两种服务调用方式,一种是ribbon+restTemplate(负载均衡),另一种是feign(集合了负载均衡和断路器)。

负载均衡客户端Spring Cloud Ribbon

Spring Cloud Ribbon是一个基于HTTP和TCP的负载均衡客户端,基于Netflix Ribbon实现。Feign默认集成了ribbon

创建子工程order-service-ribbon,pom.xml文件导入jar包依赖:

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
</dependencies>

在src/main/resources目录下添加配置文件application.yml,配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8300
spring:
application:
name: ribbon-service
eureka:
instance:
# 设置服务注册中心的域名
hostname: 127.0.0.1
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka

建服务程序,包结构如下:

RibbonOrderServiceApplication.java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
@EnableEurekaClient
//开启断路功能@EnableHystrix
public class RibbonOrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonOrderServiceApplication.class, args);
}

@Bean//会在ioc容器中注入一个对象 对象的名字和方法名字相同
//将方法返回的对象注入到了ioc容器中
@LoadBalanced //负载均衡的
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

OrderController.java代码如下:

1
2
3
4
5
6
7
8
9
10
@RestController
public class OrderController {
@Autowired
private OrderService orderService;

@RequestMapping("/order")
public String orderService(@RequestParam String goodsId, @RequestParam String userId,HttpServletRequest request) {
return orderService.execute(goodsId, userId);
}
}

OrderService.java代码如下:

1
2
3
4
5
6
7
8
@Service
public class OrderService {
@Autowired
private RestTemplate restTemplate;
public String execute(String goodsId,String userId) {
return restTemplate.getForObject("http://order-service/order?goodsId="+goodsId+"&userId="+userId, String.class);
}
}

将order-service服务的server.port改为8200再次启动,相当于给order-service服务开启了集群
同时启动当前服务。打开浏览器访问:

http://localhost:8300/order?goodsId=10&userId=100
连续访问两次看到如下页面:

断路器保护Spring Cloud Hystrix

在微服务的架构中,一个应用系统被拆分为了很多个服务单元,各单元间通过服务注册和订阅的方式互相依赖,由于每个服务单元都在不同的服务器上运行,依赖通过远程调用的方式执行的,这样就可能会因为网络或者自身服务的问题出现调用故障或者是延迟,而这些也会导致服务的调用者的对外服务也出现延迟,若此时调用方请求不断增加,最后就会因为等待出现故障的依赖方响应形成任务挤压,最终导致整个应用系统的瘫痪。为解决这样的问题,产生了断路器等一系列的服务保护机制。

Spring Cloud Hystrix即提供了断路保护的功能,程序添加断路器方法如下:在order-service-ribbon的工程中添加以下依赖

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

RibbonOrderServiceApplication类上添加注解@EnableHystrix表示启用断路器功能。

修改OrderService代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class OrderService {

@Autowired
private RestTemplate restTemplate;

@HystrixCommand(fallbackMethod="error")//配合断路回调方法
public String execute(String goodsId,String userId) {
return restTemplate.getForObject("http://order-service/order?goodsId="+goodsId+"&userId="+userId, String.class);
}

//断路器回调方法
public String error(String goodsId,String userId) {
return "订单goodsId:"+goodsId+",userId:"+userId+"处理失败";
}
}

关闭掉所有的order-service,使集群中的所有服务都关闭,再次访问:

声明式服务调用Spring Cloud Feign

Spring Cloud Feign基于Netflix Feign实现,整合了Spring Cloud Ribbon和Spring Cloud Hystrix,除了提供这两者的强大功能外,还提供了一种声明式的Web服务客户端定义方式。它具有可插拔的注解特性,可使用Feign 注解和JAX-RS注解。创建Spring Cloud Feign服务调用过程如下:
新建子工程feign-order-service,pom.xml文件中配置依赖jar包如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 导入服务的调用依赖 包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 导入熔断器依赖包 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>

在src/main/resources目录下添加配置文件application.yml,配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
server:
port: 8400
spring:
application:
name: feign-service
eureka:
instance:
# 设置服务注册中心的域名
hostname: 127.0.0.1
client:
service-url:
defaultZone: http://127.0.0.1:8000/eureka
feign:
hystrix:
enabled: true

包结构如下:

FeignOrderServiceApplication.java中:

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class FeignOrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(FeignOrderServiceApplication.class, args);
}
}

OrderController.java中:

1
2
3
4
5
6
7
8
9
10
@RestController
public class OrderController {
@Autowired
private OrderClient orderClient;

@RequestMapping("/order")
public String execute(@RequestParam String goodsId, @RequestParam String userId) {
return orderClient.execute(goodsId, userId);
}
}

OrderClient.java中:

1
2
3
4
5
6
@FeignClient(value = "order-service", fallback = HystricOrderClient.class)
public interface OrderClient {
@RequestMapping("/order")
public String execute(@RequestParam(value = "goodsId") String goodsId,
@RequestParam(value = "userId") String userId);
}

HystricOrderClient.java中:

1
2
3
4
5
6
7
@Component
public class HystricOrderClient implements OrderClient {
@Override
public String execute(String goodsId, String userId) {
return "订单" + goodsId + ",userId:" + userId + "处理失败!";
}
}

启动feign服务,并且启动两个服务集群,一个服务端口为8100另一个8200。在浏览器中访问两次:

1
http://localhost:8400/order?goodsId=20&userId=10


API路由网关服务Spring Cloud Zuul

在微服务架构中,需要几个基础的服务治理组件,包括服务注册与发现(Eureka)、服务消费(Feign或者Ribbon)、负载均衡、断路器(Hystrix)、智能路由(Zuul)、配置管理(Config)等,由这几个基础组件相互协作,共同组建了一个简单的微服务系统。一个微服务的简单架构如下图:

Spring Cloud Zuul的主要作用就是路由转发和过滤器的功能,路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。
现在我们就创建一个Zuul的工程,并且将其注册在Eureka中并且实现对之前创建好的order-service-ribbon和feign-order-service服务的路由分发功能。
创建zuul-service工程,pom文件配置依赖的jar包:

1
2
3
4
5
6
7
8
9
10
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>

在src/main/resources目录下添加配置文件application.pyml,配置文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server.port: 8500
eureka.instance.hostname: 127.0.0.1
eureka.client.service-url.defaultZone: http://127.0.0.1:8000/eureka/
spring.application.name: zuul-order-service
# 配置路由的路径 zuul.routes.自定义的名字
# path 定义拦截的路径
# service-id 服务的名字
zuul.routes.ribbon.path: /ribbon/**
zuul.routes.ribbon.service-id: ribbon-service

zuul.routes.feign.path: /feign/**
zuul.routes.feign.service-id: feign-service

zuul.host.socket-timeout-millis: 60000
zuul.host.connect-timeout-millis: 60000
ribbon.read-timeout: 60000
ribbon.connection-timeout: 60000
hystrix.command.default.execution.isolation.thread.timeoutInMillisends: 60000

配置一个拦截器。
包结构如下:

在AccessFilter.java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class AccessFilter extends ZuulFilter {

@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object user = request.getParameter("username");
//如果没有username的值 或者值不等于Mr.Zhang
if (user == null || !user.equals("Mr.Zhang")) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
return null;
}

@Override
public boolean shouldFilter() {
// TODO Auto-generated method stub
return true;
}

@Override
public int filterOrder() {
// TODO Auto-generated method stub
return 0;
}

@Override
public String filterType() {
// TODO Auto-generated method stub
return "pre";
}

}

filterType():返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下:

  1. pre:路由之前
  2. routing:路由之时
  3. post: 路由之后
  4. error:发送错误调用

filterOrder():过滤的顺序
shouldFilter():这里可以写逻辑判断,是否要过滤,本文true,永远过滤。
run():过滤器的具体逻辑代码。

ZuulOrderServiceApplication.java中:

1
2
3
4
5
6
7
8
9
10
11
12
13
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulOrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulOrderServiceApplication.class, args);
}

@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}

启动服务,并通过浏览器访问:

1
http://localhost:8500/feign/order?goodsId=20&userId=1

结果为:

分布式配置中心Spring Cloud Config

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。
分布式配置的具体配置,在这片不再过多的讲述,想了解的朋友请自助下载文档:SpringCloud的基本使用

参考:

1
2
3
4
https://blog.csdn.net/Sky786905664/article/
https://blog.csdn.net/hry2015/article/details/78577695
分布式配置:https://blog.csdn.net/yelllowcong/article/details/79593809
eureka:https://blog.csdn.net/u012702547/article/details/77877914

-------------本文结束感谢您的阅读-------------