springcloud部分组件使用

乱搭springcloud微服务demo!

首先,微服务不是一个具体的框架,它更应该理解为一个提升复杂多服务系统性能或扩展性的解决方案。但是,并不是所有项目都适合微服务的架构,一般来说,用nginx做服务端的负载均衡,已经可以满足大部分场景。而微服务,把复杂业务抽象成一个个单一职责的服务,达到系统解耦的目的,易维护易升级,由于服务间并不直接相连,因此某一服务失效也不会导致整个系统崩溃。然而,在运维层面上看,治理的难度和成本也会随之大幅度提升。

虽然,这种关乎到系统发展和公司愿景的架构选型都由大佬规划。但是,多了解一点总没错。以下学习macro大神的项目,结合自己的浅显理解,搭建一个demo练手。

附现有的微服务主流解决方案:

本文先以目前大众的Spring Cloud Netflix和官方提供的组件搭建微服务基础项目,下次将会把此项目的一些组件更换为现在很火的Spring Cloud Alibaba解决方案。

那么,开始乱搭之旅。


0.创建多模块项目

0-1.创建一个父工程

新项目创建过程不细说。创建完删除src目录,也可以直接创建maven空项目。

0-2.修改父工程pom文件

主要是添加modules

1
2
3
4
<modules>
<module>在这里写子模块名</module>
<module>在这里写子模块名</module>
</modules>

1.以Eureka作为注册中心

1-1.新建module

勾选Eureka服务:

修改子工程pom文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<!-- 注意修改父依赖 -->
<groupId>com.lizxing</groupId>
<artifactId>springcloud-demo</artifactId>
<version>1.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<artifactId>cloud-eureka</artifactId>
<version>1.0.0</version>
<name>cloud-eureka</name>
<description>Eureka注册中心</description>

父工程的modules加上:

1
<module>cloud-eureka</module>

此时整个工程的目录:

后续所有模块都以这种方式创建,不再细说。

1-2.启动注册中心

启动方法添加@EnableEurekaServer:

1
2
3
4
5
6
7
8
9
@EnableEurekaServer //启用Euerka注册中心功能
@SpringBootApplication
public class CloudEurekaApplication {

public static void main(String[] args) {
SpringApplication.run(CloudEurekaApplication.class, args);
}

}

配置必要信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8001
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost
client:
fetch-registry: false #指定是否要从注册中心获取服务(注册中心不需要开启)
register-with-eureka: false #指定是否要注册到注册中心(注册中心不需要开启)
server:
enable-self-preservation: false #关闭保护模式

启动,访问8001端口:

1-3.新建客户端

新建module,cloud-eureka-client:

启动方法添加@EnableDiscoveryClient:

1
2
3
4
5
6
7
8
9
@EnableDiscoveryClient //作为Eureka客户端
@SpringBootApplication
public class CloudEurekaClientApplication {

public static void main(String[] args) {
SpringApplication.run(CloudEurekaClientApplication.class, args);
}

}

配置文件:

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8002
spring:
application:
name: eureka-client
eureka:
client:
register-with-eureka: true #注册到Eureka的注册中心
fetch-registry: true #获取注册实例列表
service-url:
defaultZone: http://localhost:8001/eureka/ #配置注册中心地址

启动并刷新注册中心:

发现eureka-client已注册上。

实际上注册中心有可能会做集群,客户端可以同时注册到多个注册中心。

1-5.加上安全认证

注册中心添加依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置文件新增security用户名密码:

1
2
3
4
5
6
7
spring:
application:
name: eureka-server
security: #配置SpringSecurity登录用户名和密码
user:
name: root
password: 123456

新增配合类WebSecurityConfig关闭csrf:

1
2
3
4
5
6
7
8
9
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}

重新启动即可生效认证功能:

同时,客户端配置文件中的注册中心路径也要修改:

1
defaultZone: http://root:123456@localhost:8001/eureka/ #配置注册中心地址
1-6.其他常用配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
eureka:
client: #eureka客户端配置
register-with-eureka: true #是否将自己注册到eureka服务端上去
fetch-registry: true #是否获取eureka服务端上注册的服务列表
service-url:
defaultZone: http://localhost:8001/eureka/ # 指定注册中心地址
enabled: true # 启用eureka客户端
registry-fetch-interval-seconds: 30 #定义去eureka服务端获取服务列表的时间间隔
instance: #eureka客户端实例配置
lease-renewal-interval-in-seconds: 30 #定义服务多久去注册中心续约
lease-expiration-duration-in-seconds: 90 #定义服务多久不去续约认为服务失效
metadata-map:
zone: jiangsu #所在区域
hostname: localhost #服务主机名称
prefer-ip-address: false #是否优先使用ip来作为主机名
server: #eureka服务端配置
enable-self-preservation: false #关闭eureka服务端的保护机制

2.使用OpenFeign声明式服务调用

2-1.关于Ribbon和Hystrix

在使用OpenFeign前,先阐述一下两个组件:

  • Ribbon负载均衡

RestTemplate大家都用过,它相当于一个HTTP客户端,对于后端开发,可以使用它来请求其他项目的服务。那么,当同一个服务做了集群,为了提高系统的可用性,负载均衡就成为一个必须考虑的问题。而Ribbon可以很好地实现负载均衡。

如何使用?

只需创建一个配置类:

1
2
3
4
5
6
7
8
9
@Configuration
public class RibbonConfig {

@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

这样,一个@LoadBalanced就自动赋予RestTemplate负载均衡的功能,剩下的,还是一样的使用方法,例如请求:

1
2
3
4
@GetMapping("/say")
public Object sayHello() {
return restTemplate.getForObject(serviceUrl,Object.class);
}
  • Hystrix熔断器

若服务服务由于某些原因长时间不能返回响应,随着请求数的增加,服务器的性能会被损耗很大甚至导致崩溃。那么,当出现这种情况,需要在一定时间内返回错误响应而不是长时间等待以保护服务器。Hystrix提供了服务降级、服务熔断、线程隔离、请求缓存、请求合并及服务监控等强大功能。

服务降级示例:

1
2
3
4
5
6
7
8
9
10
// 服务调用方
@HystrixCommand(fallbackMethod = "sayDefault")
public Result saySomething(String content) {
return restTemplate.getForObject(serviceUrl + "/say/{1}", Result.class, content);
}

public Result sayDefault(@PathVariable String content) {
String s = "hello"
return new Result(s);
}

当远程服务长时间(配置指定)得不到响应时,自动调用fallbackMethod的本地指定方法,返回默认值,也可以是一个错误消息。

2-2.准备工作

OpenFeign集成了以上两种功能。

  • 新建两个module作为feign服务:

    记得修改pom文件和配置文件。新建完启动去注册中心检验有没有注册到。

2-3.开始使用OpenFeign

创建完项目,现在增加业务代码测试,在cloud-feign-business1-server下:

  • 新增User

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class User {

    private int id;
    private String name;
    private String sex;
    private int age;

    public User(int id, String name, String sex, int age) {
    this.id = id;
    this.name = name;
    this.sex = sex;
    this.age = age;
    }
    ...getset省略
    }
  • 新增UserService,UserSerciceImpl

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface UserService {

    User create();
    User getById(int id);
    }

    @Service
    public class UserServiceImpl implements UserService{

    @Override
    public User create() {
    return new User(1,"xiaoming", "男", 10);
    }

    @Override
    public User getById(int id) {
    return new User(id,"xiaoming", "男", 10);
    }
    }
  • 新增UserController

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    @RequestMapping("/user")
    public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/create")
    public User create() {
    return userService.create();
    }

    @GetMapping("/getById")
    public User getById(@RequestParam int id) {
    return userService.getById(id);
    }
    }

在cloud-feign-business2-server下:

  • 新增UserTestService:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Service
    @FeignClient(value = "feign-business1-server")
    public interface UserTestService {

    @GetMapping("/user/create")
    User create();

    @GetMapping("/user/getById")
    User getById(@RequestParam int id);
    }

    这里的意思是,当调用UserTestService.create方法时,会去注册中心寻找名为”feign-business1-server”的服务,找到后,向它发起”/user/create”请求并得到相应,实现远程调用的效果。

  • 新增UserTestController:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    @RequestMapping("/test/user")
    public class UserTestController {

    @Resource
    UserTestService userTestService;

    @GetMapping("/create")
    public User create() {
    return userTestService.create();
    }

    @GetMapping("/getById")
    public User getById(@RequestParam int id) {
    return userTestService.getById(id);
    }
    }
  • 调用方需要在启动方法上加上@EnableFeignClients:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @EnableFeignClients
    @EnableDiscoveryClient
    @SpringBootApplication
    public class CloudBusiness2ServerApplication {

    public static void main(String[] args) {
    SpringApplication.run(CloudBusiness2ServerApplication.class, args);
    }

    }

启动项目,注册中心:

访问localhost:8004/test/user/create:

成功调用cloud-feign-business1-server里面的接口。

负载均衡可以自行启动多个cloud-feign-business1-server然后多次访问”localhost:8004/test/user/create“测试效果,这里不再给出。

2-4.服务降级

在cloud-feign-business2-server(调用方)下:

  • 新增UserTestFallBack:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Service
    public class UserTestFallBack implements UserTestService {
    @Override
    public User create() {
    return new User(-1, "这是默认用户", "无", -1);
    }

    @Override
    public User getById(int id) {
    return new User(-1, "这是默认用户", "无", -1);
    }
    }
  • UserTestService指定fallback:

    1
    2
    @FeignClient(value = "feign-business1-server",fallback = UserTestFallBack.class)
    public interface UserTestService {
  • 配置文件加上:

    1
    2
    3
    feign:
    hystrix:
    enabled: true #在Feign中开启Hystrix

关闭cloud-feign-business1-server,访问localhost:8004/test/user/create:

在无法访问远程服务时,成功降级访问本地fallback方法。

3.使用Gateway网关服务

3-1.功能

Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/度量和弹性。

Spring Cloud Gateway功能:

  • 建立在Spring Framework 5,Project Reactor和Spring Boot 2.0之上
  • 能够匹配任何请求属性上的路由。
  • 谓词和过滤器特定于路由。
  • Hystrix断路器集成。
  • Spring Cloud DiscoveryClient集成
  • 易于编写的谓词和过滤器
  • 请求速率限制
  • 路径改写

具体介绍和示例可参考官网

3-2.初步尝试

创建cloud-gateway模块,选择gateway和eureka-client。

配置文件:

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
server:
port: 8005
service-url:
feign-business1-server: http://localhost:8003
spring:
application:
name: gateway
cloud:
gateway:
# 1.普通路由功能
routes:
- id: path_route #路由的ID
uri: ${service-url.feign-business1-server}/user/create #匹配后路由地址
predicates: # 断言,路径相匹配的进行路由
- Path=/user/create
# 2.注册中心寻找服务功能
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能
lower-case-service-id: true #使用小写服务名,默认是大写
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://root:123456@localhost:8001/eureka/

启动

  • 访问localhost:8005/user/create
  • 访问localhost:8005/feign-business1-server/user/create

发现都被路由到/localhost:8003/user/create,即feign-business1-server的接口上。

3-3.predicates的多种效果

predicates叫做谓语、断言。个人理解为匹配规则。

  • Path Route Predicate

    上面的例子就用的这种,- Path=/xxx/** 的意思就是当访问以”lalhost:8005/xxx/“开头的地址就会被匹配到指定的uri。

  • Before Route Predicate & After Route Predicate & Between Route Predicate

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    gateway:
    routes:
    - id: before_route
    uri: http://www.baidu.com
    predicates:
    - Before=2020-01-01T12:00:00.000+08:00

    意思在东8区2020-01-01 12:00:00前,访问此服务的请求都会跳转到百度。After类似。Between需要传两个时间,用逗号隔开。

  • Cookie Route Predicate

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    gateway:
    routes:
    - id: cookie_route
    uri: http://www.baidu.com
    predicates:
    - Cookie=cookiename, cookievalue

    匹配存在cookie名为cookiename,值为cookievalue的请求。

  • Header Route Predicate

    1
    2
    3
    4
    5
    6
    7
    8
    spring:
    cloud:
    gateway:
    routes:
    - id: header_route
    uri: http://www.baidu.com
    predicates:
    - Header=X-Request-Id, \d+

    匹配存在Header为X-Request-Id,内容为数字的请求。

更多Predicate参考官方文档路由谓词工厂

3-4.过滤器
  • AddRequestParameter GatewayFilter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    cloud:
    gateway:
    routes:
    - id: add_request_parameter_route
    uri: http://www.baidu.com
    filters:
    - AddRequestParameter=username, aaa
    predicates:
    - Method=GET

    对所有GET请求添加请求参数username,值为aaa。例如:xxx/xxx?username=aaa。

  • PrefixPath GatewayFilter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    spring:
    cloud:
    gateway:
    routes:
    - id: prefix_path_route
    uri: http://www.baidu.com
    predicates:
    - Method=GET
    filters:
    - PrefixPath=/user

    对所有GET请求加上/user前缀。

过滤器功能多样,还有Hystrix GatewayFilter(实现断路器模式),RequestRateLimiter GatewayFilter(实现限流)等多种过滤器,具体用法参考官网文档GatrwayFilter工厂

4.使用Spring Cloud Config配置中心

4-1.创建git仓库存放配置文件

image-20200925185436666

共创建2个分支,6份配置文件。

4-2.创建cloud-config模块

依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</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-security</artifactId>
</dependency>

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8006
spring:
application:
name: config-server
cloud:
config:
server:
git: #配置存储配置信息的Git仓库
uri: https://github.com/lizxing/springcloud-config.git
username: ****** #公有仓库的话可以不写用户名密码
password: ******
clone-on-start: true #开启启动时直接从git获取配置
security: #配置用户名和密码
user:
name: root
password: 123456
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/

启动方法j加上对应注解:

1
2
3
4
5
6
7
8
9
10
@EnableConfigServer
@EnableDiscoveryClient
@SpringBootApplication
public class CloudConfigApplication {

public static void main(String[] args) {
SpringApplication.run(CloudConfigApplication.class, args);
}

}

启动并访问localhost:8006/master/master-config-dev获取配置文件信息:

访问localhost:8006/master/master-config-dev.yml获取配置文件内容:

乱码问题先忽略:)

4-3.创建cloud-config-client模块

依赖:

1
2
3
4
5
6
7
8
9
10
11
12
<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-web</artifactId>
</dependency>

配置文件:(这里用bootstrap而不是application)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8007
spring:
application:
name: config-client
cloud:
config:
uri: http://localhost:8006 #配置中心地址
label: master #分支名称
name: master-config #配置文件名
profile: dev #后缀
# 组装起来就是 http://localhost:8006/master/master-config-dev
username: root
password: 123456
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:8001/eureka/

创建测试控制器:

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

@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo() {
return configInfo;
}
}

启动访问localhost:8007/configInfo:

也就是说访问这个接口相当于拿到localhost:8006/master/master-config-dev.yml配置文件内容。

4-4.刷新配置

当git仓库的配置刷新了,需要获取配置的客户端也要刷新。

cloud-config-client加入依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

配置文件加入:

1
2
3
4
5
management:
endpoints:
web:
exposure:
include: 'refresh'

ConfigClientController加上@RefreshScope注解:

1
2
3
@RefreshScope
@RestController
public class ConfigClientController {

修改git仓库的配置文件内容,先访问localhost:8007/actuator/refresh,然后再次访问localhost:8007/configInfo:

5.更多组件

Spring Cloud Bus消息总线、Spring Cloud Sleuth分布式请求链路跟踪等更多组件持续更新。。。

附本次demo地址