0%

Java|SpringCloud + Netflix OSS Demo

image-20210920110949198

Netflix OSS已在新版的SpringCloud中移除了,SpringCloud Netflix已经落后版本了🤯,但是不妨通过其掌握微服务的思想,理解SpringCloud的核心思想,核心组件

分布式与集群

(1)微服务概念

微服务简单来说,一个springboot就是一个微服务,不同的是这个springboot只做一项单纯的任务

(2)服务注册

springcloud有个微服务注册中eureka server,通过它把微服务注册起来以供来调用

(3)服务访问

微服务直接可以通过注册中心的定位相互访问

(4)分布式概念

简单说,原来是在一个 springboot里就完成的事情,现在分布在多个 springboot里做,这就是初步具备分布式雏形了

  • 如果我要更新数据微服务,视图微服务是不受影响的
  • 可以让不同的团队开发不同的微服务,他们之间只要约定好接口,彼此之间是低耦合的。
  • 如果视图微服务挂了,数据微服务依然可以继续使用
    等等
(5)集群

提供相同功能,只是端口不一样的微服务称为集群

  • 比起一个 springboot, 两个springboot 可以分别部署在两个不同的机器上,那么理论上来说,能够承受的负载就是 x 2. 这样系统就具备通过横向扩展而提高性能的机制
  • 如果 8001 挂了,还有 8002 继续提供微服务,这就叫做高可用

SpringCloud介绍

参考资料:四种软件架构

(1)单体架构

image-20210912153357714

典型的三级架构,前端(Web/手机端)+中间业务逻辑层+数据库层

(2)分布式架构

img

将一个大的系统划分为多个业务模块,业务模块分别部署在不同的服务器上,各个业务模块之间通过接口进行数据交互。数据库也大量采用分布式数据库,通过LVS/Nginx代理应用,将用户请求均衡的负载到不同的服务器上

(3)微服务架构

img

将系统拆分成很多小应用(微服务),微服务可以部署在不同的服务器上,也可以部署在相同的服务器不同的容器上。当应用的故障不会影响到其他应用,单应用的负载也不会影响到其他应用

(4)SpringCloud组成

SpringCloud 就是一套工具,帮助我们很容易地搭建出这么一个 集群和分布式的架子出来,Spring Cloud 专注于为典型用例提供良好的开箱即用体验,并为其他用户提供可扩展性机制

img

  • Spring Cloud Netflix:cloud各项服务依赖与它,与各种Netflix OSS组件集成,组成微服务的核心,它的主要有组成有Eureka, Hystrix, Zuul
  • Eureka注册中心服务:SpringCloud服务中心,云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移
  • Microservice:微服务,在springcloud可以简单理解为专职做一项任务的springboot,微服务之间可以通过Ribbon和Feign两种方式进行微服务之间的访问(Feign是主流方式)
  • Zipkin链路跟踪:从属于Spring Cloud Sleuth(日志收集工具包),为SpringCloud应用实现了一种分布式追踪解决方案,可以查看微服务之间的复杂的调用关系
  • Config Server 配置服务器:俗称配置中心,配置管理工具包,让你可以把配置放到远程服务器(比如集中放在git),集中化管理集群配置
  • Bus 消息总线:事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Config Server 联合,再使用RabbitMQ实现热部署(所谓热部署即不需要重启微服务,对配置信息自动更新)
  • 断路器Hystrix:容错管理工具,旨在通过熔断机制控制服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力,简单来说就是提供异常或错误的处理微服务,比如说某个微服务寄了,断路器就可以调用其他微服务顶上(一般是错误处理的微服务)
  • Hystrix dashboard 断路器监控:通过turbine将集群中多个实例汇聚在一起,对微服务进行断路器监控
  • 网关Zuul:Zuul 是在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架,通过网关简化了对各个服务的访问:不再需要记录各个微服务的地址和端口,而是通过网关去访问他们

Springcloud 启动测试

(1)项目启动
  • 启动RabbitMQ(访问http://127.0.0.1:15672/# 即已开启)

  • 启动链路追踪服务器(这里开启的端口要与微服务中的配置一致)

    1
    java -jar zipkin-server-2.10.1-exec.jar  --server.port=8050 --zipkin.collector.rabbitmq.addresses=localhost
  • 访问配置仓库https://github.com/Autovy/SpringCloudConfig/ (也可以在config-server模块的配置文件修改成自己的仓库)

  • 运行EurekaServerApplication,启动注册中心服务(端口为8761)

  • 运行ConfigServerApplication,启动配置服务器(端口为8030)

  • 运行ProductDataServiceApplication,启动数据微服务(端口填写8001,8002形成集群)

  • 运行ProductViewServiceFeignApplication,启动视图微服务(端口可填写8012,8013)

  • 运行ProductServiceHystrixDashboardApplication,开启断路器监控,监控单个微服务(端口为8020)

  • 运行ProductServiceTurbineApplication,开启聚合断路器监控以监控集群(端口为8021)

  • 运行视图微服务里的 AccessViewService 来周期性地访问 http://127.0.0.1:8012/productshttp://127.0.0.1:8013/products,以提供监控数据

  • 运行ProductServiceZuulApplication,开启网关服务(端口为8060)

(2)测试服务注册中心

打开链接http://127.0.0.1:8761/ ,即可查看到注册服务中心Eureka

image-20210919135602819

(3)测试数据微服务

打开链接http://127.0.0.1:8001/productshttp://127.0.0.1:8002/products 都可以访问到返回的数据

image-20210919135818137

(4)测试视图微服务

打开链接http://127.0.0.1:8012/productshttp://127.0.0.1:8013/products 可以访问到视图页面,并且可以发现视图微服务会随机选择端口访问数据微服务实现负载均衡

image-20210919140106927

(5)测试服务链路追踪

打开链接http://127.0.0.1:8050/zipkin/dependency/ 即可访问到

Zipkin链路跟踪服务页面,即可看到微服务间的访问关系

image-20210919140414948

(6)测试Bus消息总线

可访问http://127.0.0.1:8030/version/dev 查看到配置服务器的信息

访问视图查看当前版本号

image-20210919140727703

修改配置服务器git上的版本号,这里我的仓库地址为https://github.com/Autovy/SpringCloudConfig/

image-20210919141137143

启动视图微服务中的FreshConfigUtil使用 post 的方式访问 http://localhost:8012/actuator/bus-refresh 地址,更新配置信息

image-20210919141416336

最后再查看视图上的更新

image-20210919141503558

可以访问RabbitMQ页面:http://127.0.0.1:15672/ ,查看队列,连接,还有交换机

(7)测试Hystrix断路器及其监控

打开链接http://localhost:8020/hystrix 即可进入Hystrix断路器的监控入口

image-20210919141754328

框内输入http://localhost:8012/actuator/hystrix.stream 即可对8012端口的视图微服务进行监控

image-20210919141940229

框内输入http://localhost:8021/turbine.stream 即可实现对整个集群的视图微服务进行监控

image-20210919142137829

停止数据微服务ProductDataServiceApplication集群,触发断路器:

访问视图可得

image-20210919142903819

访问单个微服务监控页面可得

image-20210919143250565

访问聚合集群微服务监控页面可得

image-20210919143120242

(8)网关测试

可以在zuul的配置文件中修改微服务的访问路由,我绑定的路由如下:

http://localhost:8060/api-data/products :访问数据微服务集群

image-20210919135818137

http://localhost:8060/api-view/products :访问视图微服务集群

image-20210919142625877

可以发现其访问集群的端口也是负载均衡的

服务注册中心

(1)需求

通过服务注册中心管理微服务,并且让微服务直接可以相互定位交流

(2)相关依赖
1
2
3
4
5
6
7
<dependencies>
<!-- 添加eureka服务端-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
(3)相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# eureka服务配置
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetch-registry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

# sring微服务模块命名
spring:
application:
name: eureka-server

  • hostname: localhost 表示主机名称
  • registerWithEureka:false 表示是否注册到服务器。 因为它本身就是服务器,所以就无需把自己注册到服务器
  • fetchRegistry: false 表示是否获取服务器的注册信息,和上面同理,这里也设置为 false
  • defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ 自己作为服务器,公布出来的地址。 比如后续某个微服务要把自己注册到 eureka server, 那么就要使用这个地址: http://localhost:8761/eureka/
(4)服务启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@SpringBootApplication
@EnableEurekaServer // 注解标注为Eureka服务端
public class EurekaServerApplication {

public static void main(String[] args) {

// 设置默认端口
int port = 8761;
// 启动EurekaServer管理页面
new SpringApplicationBuilder(EurekaServerApplication.class)
.properties("server.port=" + port)
.run(args);

}

}

配置服务器

(1)需求

微服务要做集群,这就意味着,会有多个微服务实例。 在业务上有时候需要修改一些配置信息,比如说 版本信息,倘若没有配置服务, 那么就需要挨个修改微服务,挨个重新部署微服务,这样就比较麻烦。

我们可以把这些配置信息放在一个公共的地方,比如git,然后通过配置服务器把它获取下来,然后微服务再从配置服务器上取下来

(2)git准备

在github上新建仓库,并创建respo目录,在目录下添加 product-view-service-feign-dev.properties文件并写入版本信息

如我创建的仓库:https://github.com/Autovy/SpringCloudConfig

(3)相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<!-- eureka客户端注册依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- springboot web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置服务支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
(4)相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 配置微服务名与配置服务器的配置信息(这里用git作为配置服务器)
spring:
application:
name: config-server
cloud:
config:
label: master
server:
git:
uri: https://github.com/Autovy/SpringCloudConfig/ #github仓库地址
search-paths: respo # 仓库下的目录
default-label: main # 分支名改为了main


# 设置注册中性的地址要与Eureka的配置对应
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
(5)服务启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@SpringBootApplication
@EnableEurekaClient // 注解标注为Eureka客户端,实现注册
@EnableDiscoveryClient
@EnableConfigServer // 使用该注解表明该springboot是个配置服务器
public class ConfigServerApplication {
public static void main(String[] args){
int port = 8030;

new SpringApplicationBuilder(ConfigServerApplication.class)
.properties("server.port="+port)
.run(args);

}

}

RabbitMQ

(1) RabbitMQ的介绍

通过RabbitMQ与消息总线Bus实现配置服务器热部署即自动更新配置信息

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。

AMQP(Advanced Message Queuing Protocol),顾名思义,它是一个消息协议,能够使得遵循该协议的客户端和消息中间件(Broker)进行通讯

(2)RabbitMQ安装

首先要安装erlang,并配置环境,才继续安装RabbitMQ

配置插件后重启RabbitMQ

image-20210916092150386

image-20210916092243391

(3)RabbitMQ页面无法访问问题

一般来说开启了RabbitMQ服务后,可以通过链接http://127.0.0.1:15672/进行访问

如果页面无法访问到,可以自行下列语句重新生成相关配置文件

1
net stop rabbitmq && rabbitmq-server -detached && net start rabbitmq

这样即可访问到RabbitMQ的页面(用户与密码默认为guest/guest)

image-20210916093536018

(4)消息路由过程

image-20210916094610081

消息(message)发布给交换机(Exchange)

Exchange相当于邮局或者信箱,它接收到消息后会根据不同的规则(称为Bindings)来确定要发给哪个队列(queue)

最后AMQP代理会将消息投递给订阅了此队列的消费者,或者是消费者依据需求自行获取

(5)模式分类

RabbitMQ提供了四种Exchange模式:fanout,direct,topic,header 。header在实际使用中很少用到,故我们只介绍前面三种

img

  • Direct 模式就是指定队列模式, 消息来了,只发给指定的 Queue, 其他Queue 都收不到
  • Topic 模式就是主题模式,Queue 按照某种主题分类接收消息
  • Fanout 模式就是广播模式,消息来了,会发给所有的队列

在本项目中,我们让config-server去git获取最新配置信息,并将该信息广播给集群中的所有视图微服务

断路器监控

(1)需求

前面我们了解了断路器, 当数据服务不可用的时候, 断路器就会发挥作用

而我们可以使用断路器监控来可视化断路器运行情况

(2)相关依赖
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
<dependencies>
<!-- 在eureka注册微服务依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<!-- 添加web支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 消息总线依赖,用于访问路径/actuator/bus-refresh -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- 增加断路器依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

<!-- 增加断路器依赖监控面板依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
(3)相关配置
1
2
3
4
# 配置微服务并命名
spring:
application:
name: hystrix-dashboard
(4)服务启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@SpringBootApplication
@EnableHystrixDashboard
public class ProductServiceHystrixDashboardApplication {

public static void main(String[] args) {

int port = 8020;
if(!NetUtil.isUsableLocalPort(port)) {
System.err.printf("端口%d被占用了,无法启动%n", port );
System.exit(1);
}

// 构造服务
new SpringApplicationBuilder(ProductServiceHystrixDashboardApplication.class)
.properties("server.port=" + port)
.run(args);

}

}

断路器聚合监控

(1)需求

上面的内容只能针对一个微服务进行断路器监控,但是一个微服务通常由多个实例组成,监控起来十分不方便;springcloud提供了turbine可以把一个集群里的多个实例汇聚在一个turbine里,这样就能够在集群层面进行监控了

(2)相关依赖
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
37
38
<dependencies>
<!-- 在eureka注册微服务依赖 -->
<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>

<!-- 消息总线依赖,用于访问路径/actuator/bus-refresh -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- 增加断路器依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

<!-- 增加断路器依赖监控面板依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>

<!-- 增加turbine -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>

</dependencies>

(3)相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: turbine

# 配置turbine
turbine:
aggregator:
cluster-config: default
# 配置Eureka中的serviceId列表,表明监控哪些服务
#(这样就会把微服务名称为product-view-service-feign的实例信息收集起来)
app-config: product-view-service-feign
cluster-name-expression: new String("default")


# 设置注册中性的地址要与Eureka的配置对应(注册)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
(4)服务启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 启动类
@SpringBootApplication
@EnableTurbine // 开启Turbine
public class ProductServiceTurbineApplication {

public static void main(String[] args) {

int port = 8021;
if(!NetUtil.isUsableLocalPort(port)){
System.err.printf("端口%d被占用,无法启动%n", port);
System.exit(1);

}

new SpringApplicationBuilder(ProductServiceTurbineApplication.class)
.properties("server.port="+ port)
.run(args);

}


}

网关Zuul

(1)需求

微服务有可能放在不同的 ip 地址上,有可能是不同的端口

为了访问他们,就需要记录这些地址和端口。 而地址和端口都可能会变化,这就增加了访问者的负担

这时候我们就可以通过网关简化对微服务的访问,仅需要一个地址一个端口就可以实现对一个微服务集群的访问

(2)相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<dependencies>
<!-- 微服务注册中心 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- web服务支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 增加zuul网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
(3)相关配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
application:
name: product-service-zuul

# 设置注册中性的地址要与Eureka的配置对应(注册)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/


# 对zuul进行路由映射
zuul:
routes:
api-a:
path: /api-data/**
serviceId: PRODUCT-DATA-SERVICE
api-b:
path: /api-view/**
serviceId: PRODUCT-VIEW-SERVICE-FEIGN
(4)服务启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 启动类
@SpringBootApplication
@EnableZuulProxy // 网关服务
@EnableEurekaClient // 注册服务客户端
@EnableDiscoveryClient
public class ProductServiceZuulApplication {

public static void main(String[] args) {

int port = 8060;
if(!NetUtil.isUsableLocalPort(port)){
System.err.printf("端口%d被占用了,无法启动%d", port);
System.exit(1);

}

new SpringApplicationBuilder(ProductServiceZuulApplication.class)
.properties("server.port=" + port)
.run(args);

}

}

数据微服务

(1)需求

访问数据库为视图微服务提供数据(在本项目中为了配置方便,不设dao层直接在service层提供假数据),真正意义上完整的springboot

(2)相关依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependencies>
<!-- eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- web服务用于提供控制层 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!--增加zipkin,使服务可以被追踪到-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
</dependencies>
(3)相关配置
1
2
3
4
5
6
7
8
9
10
11
12
# 配置微服务名与配置服务链路追踪
spring:
application:
name: product-data-service
zipkin:
base-url: http://localhost:8050

# 设置注册中性的地址要与Eureka的配置对应
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
(4)实体类
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
37
38
39
40
// 构造实体类
public class Product {

private int id;
private String name;
private int price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}


// 默认构造方法
public Product() {

}

// 利用该构造方法,可以声明一个由默认值的对象
public Product(int id, String name, int price) {
super();
this.id = id;
this.name = name;
this.price = price;
}

}
(5)服务层

不接入dao层,直接在服务层提供假数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 服务层(这里不接入dao层,而是提供假数据)
@Service
public class ProductService {

// 获得配置中的端口号
@Value("${server.port}")
String port;

public List<Product> list(){
List<Product> ps = new ArrayList<>();
// 提供假数据,list方法没有对应dao层相应的数据库操作方法
ps.add(new Product(1,"product a from port:"+port, 50));
ps.add(new Product(2,"product b from port:"+port, 150));
ps.add(new Product(3,"product c from port:"+port, 250));
return ps;

}

}
(6)控制层

接入服务层并映射路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@RestController
public class ProductController {
// 自动装配服务层
@Autowired
ProductService productService;

// 映射url路径
@RequestMapping("/products")
public Object products(){
// 调用服务层的方法
List<Product> ps = productService.list();
return ps;
}

}
(7)服务启动
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
@SpringBootApplication
@EnableEurekaClient // 服务注册中心客户端,微服务注册
public class ProductDataServiceApplication {

public static void main(String[] args) {

// 让用户输入端口号,开启多个服务形成集群
int port = 0;
System.out.println("请输入开启服务的端口号:");
Scanner strpost = new Scanner(System.in);
port = strpost.nextInt();


// 启动ProductDataService服务
new SpringApplicationBuilder(ProductDataServiceApplication.class)
.properties("server.port=" + port)
.run(args);


}

/* 配置zipkin:在启动类里配置 Sampler 抽样策略: ALWAYS_SAMPLE 表示持续抽样*/
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}

}

视图微服务

(1)需求

访问数据微服务(这里主要用feign的方式),将数据发送到视图层展示

(2)相关依赖
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<dependencies>
<!-- 服务注册依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--支持 Feign 方式的微服务访问-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- web支持依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 视图层依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!--增加zipkin,使服务可以被追踪到-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>


<!-- 增加一个 spring-cloud-starter-config 用于访问配置服务器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>

<!-- 消息总线依赖,用于访问路径/actuator/bus-refresh -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- 用于支持rabbitmq -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

<!-- 用于支持断路器 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

</dependencies>
(3)相关配置

这里的微服务由于要通过rabbitMQ访问到配置服务器,需要系统层面上的配置,故需要两个配置文件:bootstrap.yml 和 application.yml

参考资料:application.yml与bootstrap.yml的区别

  • bootstrap.yml 和 application.yml 都可以用来配置参数
  • bootstrap.yml 用来程序引导时执行,应用于更加早期配置信息读取。可以理解成系统级别的一些参数配置,这些参数一般是不会变动的。一旦bootStrap.yml 被加载,则内容不会被覆盖
  • application.yml 可以用来定义应用级别的, 应用程序特有配置信息,可以用来配置后续各个模块中需使用的公共参数等

bootstrap.yml文件

主要提供了 serviceId: config-server, 这个是配置服务器在 eureka server 里的服务名称,这样就可以定位 config-server了

在注册服务中心的注册也移到了bootstrap.yml

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
# 配置 config-server(服务端)的信息
spring :
cloud:
config:
label: main
profile: dev
discovery:
enabled: true
service-id: config-server
# 增加总线配置
bus:
enable: true
trace:
enabled: true
# 新增rabbitMQ配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

# 设置注册中心的地址要与Eureka的配置对应(注册)
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/

application.yml文件

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
# 配置微服务名与配置服务链路追踪
spring:
application:
name: product-view-service-feign
zipkin:
base-url: http://localhost:8050

# 配置thymeleaf
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
servlet:
content-type: text/html
mode: HTML5


# 开启断路器
feign.hystrix.enabled: true


# 新增路径访问允许,这样才能访问 /actuator/bus-refresh,用于访问配置服务器更新配置信息
management:
endpoints:
web:
exposure:
include: "*"
cors:
allowed-origins: "*"
allowed-methods: "*"

(4)实体类

与数据微服务的一致

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
37
// 构造实体类
public class Product {

private int id;
private String name;
private int price;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}

public Product() {

}
public Product(int id, String name, int price) {
super();
this.id = id;
this.name = name;
this.price = price;
}


}
(5)客户端

视图微服务作为客户端去访问数据微服务这个服务端,并利用断路器提供访问失败后的异常处理信息

Feign客户端

1
2
3
4
5
6
7
8
9
//Feign 客户端, 通过 注解方式访问PRODUCT-DATA-SERVICE服务的 products路径
// 如果访问的 PRODUCT-DATA-SERVICE 不可用的话,就调用 ProductClientFeignHystrix 来进行反馈信息
@FeignClient(value = "PRODUCT-DATA-SERVICE", fallback = ProductClientFeignHystrix.class)
public interface ProductClientFeign {

@GetMapping("products")
public List<Product> list();

}

Hystrix断路器处理

1
2
3
4
5
6
7
8
9
10
11
// 实现了 ProductClientFeign 接口,并提供了 list() 方法
@Component
public class ProductClientFeignHystrix implements ProductClientFeign {

public List<Product> list(){
List<Product> res = new ArrayList<>();
res.add(new Product(0, "产品数据微服务不可用", 0));
return res;
}

}
(6)服务层

接入客户端,自动装配客户端请求到的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service
public class ProductService {

@Autowired
ProductClientFeign productClientFeign;

// 服务类的数据从ProductClientRibbon(客户端)中获取
public List<Product> list(){
return productClientFeign.list();
}

}

(7)控制层

接入服务层,映射访问路径,发送数据到视图层并输出视图

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
@Controller
@RefreshScope
public class ProductController {

@Autowired
ProductService productService;

// 在配置服务器获得版本号
@Value("${version}")
String version;

// 控制器将数据放入product.html
@RequestMapping("/products")
public Object products(Model m){

List<Product> ps = productService.list();
// 发送到视图
m.addAttribute("version", version);
m.addAttribute("ps", ps);
return "products";

}



}
(8)视图层

使用thymeleaf可接入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
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>products</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style>

table {
border-collapse:collapse;
width:400px;
margin:20px auto;
}
td,th{
border:1px solid gray;
}

</style>
</head>
<body>

<div class="workingArea">
<table>
<thead>
<tr>
<th>id</th>
<th>产品名称</th>
<th>价格</th>
</tr>
</thead>
<tbody>
<tr th:each="p: ${ps}">
<td th:text="${p.id}"></td>
<td th:text="${p.name}"></td>
<td th:text="${p.price}"></td>
</tr>
<tr>
<td align="center" colspan="3">
<p th:text="${version}" >how2j springcloud version unknown</p>
</td>
</tr>
</tbody>
</table>
</div>

</body>

</html>
(9)网络访问

更新配置信息

使用 post 的方式访问配置服务器的 http://localhost:8012/actuator/bus-refresh 地址,用于更新配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//使用 post 的方式访问 http://localhost:8012/actuator/bus-refresh 地址
public class FreshConfigUntil {

public static void main(String[] args){

// 增加请求头
HashMap<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/json; charset=utf-8");

// 因为要去git获取,还要刷新config-server, 会比较卡,所以一般会要好几秒才能完成
System.out.println("请耐心等待");

// 发送post请求
String result = HttpUtil.createPost("http://localhost:8012/actuator/bus-refresh")
.addHeaders(headers).execute().body();

System.out.println("result" + result);
System.out.println("refresh 完成");

}


}

提供监控数据

不断对视图层进行访问,以提供断路器监控的数据

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
// 一个不断访问视图服务的类,以便在监控中观察到现象
// 访问集群的8012和8013端口
public class AccessViewService {

public static void main(String[] args) {

while (true){
ThreadUtil.sleep(1000);
access(8012);
access(8013);

}


}

public static void access(int port){

try{
String html = HttpUtil.get(String.format("http://127.0.0.1:%d/products", port));
System.out.println("html length:" + html.length());
}
catch (Exception e){
System.out.printf("%d地址的视图服务无法访问%n", port);
}

}


}

(10)服务启动
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
@SpringBootApplication
@EnableEurekaClient // 注册服务中信息注册客户端
@EnableDiscoveryClient
@EnableFeignClients //表明使用Feign方式
@EnableCircuitBreaker // 共享信息给断路监控器
public class ProductViewServiceFeignApplication {

public static void main(String[] args) {

// 增加rabbitmq是否启动判断
int rabbitMQPort = 5672;
if(NetUtil.isUsableLocalPort(rabbitMQPort)){
System.err.printf("未在端口%d发现rabbitMQ服务,请检查", rabbitMQPort);
System.exit(1);
}


// 让用户输入端口号,开启多个服务形成集群
int port = 0;
System.out.println("请输入开启服务的端口号:");
Scanner strpost = new Scanner(System.in);
port = strpost.nextInt();

new SpringApplicationBuilder(ProductViewServiceFeignApplication.class)
.properties("server.port=" + port)
.run(args);

}

/* 配置zipkon:在启动类里配置 Sampler 抽样策略: ALWAYS_SAMPLE 表示持续抽样*/
@Bean
public Sampler defaultSampler() {
return Sampler.ALWAYS_SAMPLE;
}

}

MPLE;
}

}