微服务的概念是由Martin Fowler(马丁·福勒)在2014年提出的
微服务是由以单一应用程序构成的小服务,自己拥有自己的行程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用 HTTP API 通信。同时服务会使用最小的规模的集中管理能力,服务可以用不同的编程语言与数据库等组件实现。
简单来说,微服务就是将一个大型项目的各个业务模块拆分成多个互不相关的小项目,而这些小项目专心完成自己的功能,而且可以调用其他小项目的方法,从而完成整体功能
Spring Cloud
pringCloud是由Spring提供的一套能够快速搭建微服务架构程序的框架集
框架集表示SpringCloud不是一个框架,而是很多框架的统称
SpringCloud就是为了搭建微服务架构项目出现的
有人将SpringCloud称之为"Spring全家桶",广义上指代Spring的所有产品
Nacos
Nacos是Spring Cloud Alibaba提供的一个软件
微服务中所有项目都必须注册到注册中心才能成为微服务的一部分
下载地址
https://github.com/alibaba/nacos/releases/download/1.4.3/nacos-server-1.4.3.zip
Nacos启动命令
startup.cmd -m standalone
将项目注册到Nacos
首先要在当前模块pom文件中添加依赖
<!-- 支持项目注册到Nacos注册中心的依赖
discovery:发现
(当前项目注册后,微服务系统中,就能发现该项目)
-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
我们在创建好的application-dev.yml中编写对nacos注册的配置信息
spring:
application:
# 设置当前项目的名称,这个名字会提交给Nacos做当前微服务项目的名称
name: nacos-business
cloud:
nacos:
discovery:
# 配置Nacos的位置,用于提交当前项目信息
server-addr: localhost:8848
Nacos心跳机制
心跳:周期性的操作,来表示自己是健康可用的机制
注册到Nacos的微服务项目(模块)都是会遵循这个心跳机制的
心跳机制的目的
1.是表示当前微服务模块运行状态正常的手段
2.是表示当前微服务模块和Nacos保持沟通和交换信息的机制
默认情况下,服务启动开始每隔5秒会向Nacos发送一个"心跳包",这个心跳包中包含了当前服务的基本信息
Nacos接收到这个心跳包,首先检查当前服务在不在注册列表中,如果不在,按新服务的业务进行注册,如果在,表示当前这个服务是健康状态
如果一个服务连续3次心跳(默认15秒)没有和Nacos进行信息的交互,就会将当前服务标记为不健康的状态
如果一个服务连续6次心跳(默认30秒)没有和Nacos进行信息的交互,Nacos会将这个服务从注册列表中剔除
这些时间都是可以通过配置修改的
Dubbo概述
RPC是Remote Procedure Call的缩写 翻译为:远程过程调用
目标是为了实现两台(多台)计算机\服务器,相互调用方法\通信的解决方案
RPC只是实现远程调用的一套标准
该标准主要规定了两部分内容
1.通信协议
2.序列化协议
通信协议
通信协议指的就是远程调用的通信方式
实际上这个通知的方式可以有多种
例如:写信,飞鸽传书,发电报
在程序中,通信方法实际上也是有多种的,每种通信方式会有不同的优缺点
序列化协议
序列化协议指通信内容的格式,双方都要理解这个格式
上面的图片中,老婆给老公发信息,一定是双方都能理解的信息
发送信息是序列化过程,接收信息需要反序列化
程序中,序列化的方式也是多种的,每种序列化方式也会有不同的优缺点
什么是Dubbo
上面对RPC有基本认识之后,再学习Dubbo就简单了
Dubbo是一套RPC框架。既然是框架,我们可以在框架结构基础上,定义Dubbo中使用的通信协议,使用的序列化框架技术,而数据格式由Dubbo定义,我们负责配置之后直接通过客户端调用服务端代码。
可以说Dubbo就是RPC概念的实现
Dubbo是SpringCloudAlibaba提供的框架
能够实现微服务相互调用的功能!
Dubbo对协议的支持
RPC框架分通信协议和序列化协议
Dubbo框架支持多种通信协议和序列化协议,可以通过配置文件进行修改
Dubbo支持的通信协议
-
dubbo协议(默认)
-
rmi协议
-
hessian协议
-
http协议
-
webservice
-
.....
支持的序列化协议
-
hessian2(默认)
-
java序列化
-
compactedjava
-
nativejava
-
fastjson
-
dubbo
-
fst
-
kryo
Dubbo默认情况下,支持的协议有如下特征
-
采用NIO单一长链接
-
优秀的并发性能,但是处理大型文件的能力差
Dubbo方便支持高并发和高性能
Dubbo的发展历程
Dubbo历程
在dubbo2012年底停止更新后
国内很多公司在Dubbo的基础上进行修改,继续更新
比较知名的修改版本就是当当网的DubboX
2012年底dubbo停止更新后到2017年dubbo继续更新之前
2015SpringCloud开始兴起,当时没有阿里的框架
国内公司要从SpringCloud和Dubbo中抉择使用哪个微服务方案
学习的Dubbo指的都是2.7之后的版本
是能够和SpringCloudAlibaba配合使用的
Duboo的依赖
<!-- Dubbo在SpringBoot中使用的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
</dependency>
在当前项目yml中添加配置
dubbo:
protocol: # 协议
# port设置为-1 表示当前dubbo框架使用自动寻找可以端口的功能
# 默认寻找可用端口的规则是从20880开始,可用就使用,不可用就+1,直到找到可用的为止
port: -1
# 设置连接的名称,固定为dubbo
name: dubbo
registry: #注册
# 指定要注册到的注册中心
address: nacos://localhost:8848
consumer:
# 设置当前项目启动时,是否检查本项目需要的所有远程服务都已经在注册中心注册
# 设置它的值为false,表示不检查,以减少启动时因为需要的服务不可用导致的各种错误
check: false
将业务逻辑层实现类方法声明为Dubbo可调用的方法
还需要在SpringBoot启动类上添加@EnableDubbo的注解,才能真正让Dubbo功能生效
// @DubboService注解,标记的业务逻辑层实现类,其中所有的方法都会注册到Nacos
// 在其他服务启动"订阅发现"后,就可以调用这个类中的所有方法
@DubboService
@Service
@Slf4j
public class StockServiceImpl implements IStockService {
// .... 内容略
}
@SpringBootApplication
// 如果当前项目是dubbo调用中的生产者,必须添加@EnableDubbo注解
// 添加之后,在服务启动时,当前项目的提供的所有服务才能正确注册到Nacos
@EnableDubbo
public class CsmallStockWebapiApplication {
public static void main(String[] args) {
SpringApplication.run(CsmallStockWebapiApplication.class, args);
}
}
Dubbo生产者消费者配置小结
Dubbo生产者消费者相同的配置
pom文件添加dubbo依赖,yml文件配置dubbo信息
生产者
-
要有service接口项目
-
提供服务的业务逻辑层实现类要添加@DubboService注解
-
SpringBoot启动类要添加@EnableDubbo注解
消费者
-
pom文件添加消费模块的service依赖
-
业务逻辑层远程调用前,模块使用@DubboReference注解获取业务逻辑层实现类对象
Seata概述
下载地址
https://github.com/seata/seata/releases/download/v1.4.2/seata-server-1.4.2.zip
什么是Seata
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务
也是Spring Cloud Alibaba提供的组件
Seata构成部分包含
-
事务协调器TC
-
事务管理器TM
-
资源管理器RM
seata的启动
seata-server.bat -h 127.0.0.1 -m file
使用Seata
这模块pom添加依赖和配置
<!-- Seata整合SpringBoot的依赖 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</dependency>
<!-- Seata完成分布式事务过程中,需要额外的辅助依赖(如果不添加Seata无法正常运行) -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
在 application-dev.yml添加以下配置
seata:
# 定义一个事务的分组名称,同一个微服务项目的各模块名称应该一致,这个名称就是用来区分不同项目的
tx-service-group: csmall_group
service:
vgroup-mapping:
# 设置csmall_group分组使用的事务策略,default使用默认策略
csmall_group: default
grouplist:
# 设置seata的ip和端口号
default: localhost:8091
要想激活Seata功能非常简单,只要在起点业务的业务逻辑方法上添加专用的注解即可
@Service
@Slf4j
public class BusinessServiceImpl implements IBusinessService {
// Dubbo调用Order模块实现新增订单的功能
// 提交我们业务逻辑层方法中定义的orderAddDTO对象
// business模块是单纯的消费者,不需要在类上编写@DubboService
@DubboReference
private IOrderService dubboOrderService;
// Global:全局(全球) Transactional:事务
// 业务逻辑层方法上添加@GlobalTransactional这个注解
// 相当于设置了Seata分布式事务的起点,是AT模型中的TM(事务管理器)
// 效果是从这个方法开始,远程调用的所有方法中,对数据库的操作,设置在同一个事务中
// 根据事务的原子性特征,这些数据库操作要么都成功,出现异常时,所有的操作都会回滚
@GlobalTransactional
@Override
public void buy() {
// 代码略
}
}
Sentinel
下载地址
https://github.com/alibaba/Sentinel/releases
sentinel启动
在需要的模块中添加配置文件
<!-- Sentinel的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
在application-dev.yml文件添加配置
spring:
application:
# 设置当前项目的名称,这个名字会提交给Nacos做当前微服务项目的名称
name: nacos-stock
cloud:
sentinel:
transport:
# 配置sentinel提供的仪表台的位置
dashboard: localhost:8080
# 执行限流的端口号,每个项目需要设置不同的限流端口号
# 当前是stock模块,如果后续cart模块也需要限流,端口号可以使用8722
port: 8721
nacos:
discovery:
# 配置Nacos的位置,用于提交当前项目信息
server-addr: localhost:8848
在方法上添加限流的注解
@PostMapping("/reduce/count")
@ApiOperation("减少库存数的方法")
// @SentinelResource添加在控制层的方法上,在这个方法运行一次后
// 会被Sentinel仪表台识别,识别后可以设置这个方法的限流策略
// 如果方法不运行,仪表台中不会有这个方法
// "减少库存数",是仪表台识别这个方法时显示的名称
@SentinelResource("减少库存数")
public JsonResult reduceCount(StockReduceCountDTO stockReduceCountDTO){
// 调用减少库存数的业务逻辑层方法,如果发生异常,会由全局异常处理类处理
stockService.reduceStockCount(stockReduceCountDTO);
return JsonResult.ok("库存减少完成!");
}
自定义限流方法
SentinelResource注解中,可以自定义处理限流情况的方法
@PostMapping("/reduce/count")
@ApiOperation("减少库存数的方法")
// @SentinelResource添加在控制层的方法上,在这个方法运行一次后
// 会被Sentinel仪表台识别,识别后可以设置这个方法的限流策略
// 如果方法不运行,仪表台中不会有这个方法
// "减少库存数",是仪表台识别这个方法时显示的名称
// blockHandler是注解中的属性,它指定当方法被限流时,运行的自定义限流方法名称
@SentinelResource(value = "减少库存数",blockHandler = "blockError")
public JsonResult reduceCount(StockReduceCountDTO stockReduceCountDTO){
// 调用减少库存数的业务逻辑层方法,如果发生异常,会由全局异常处理类处理
stockService.reduceStockCount(stockReduceCountDTO);
return JsonResult.ok("库存减少完成!");
}
// Sentinel自定义限流方法规则
// 1.访问修饰符必须是public
// 2.返回值类型必须和被限流的控制器方法一致
// 3.方法名必须和@SentinelResource注解中设置的名称一致
// 4.参数列表必须和控制器方法一致,再添加一个BlockException类型参数在参数列表末尾
public JsonResult blockError(StockReduceCountDTO stockReduceCountDTO,
BlockException e){
// 这个方法会在请求被限流时运行,一般用于给出相应提示
return JsonResult.failed(
ResponseCode.INTERNAL_SERVER_ERROR,"服务器忙,请稍后再试");
}
SpringGateway网关
"网"指网络,"关"指关口或关卡
网关:就是指网络中的关口\关卡
网关就是当前微服务项目的"统一入口"
我们使用Spring Gateway作为当前项目的网关框架
Spring Gateway是Spring自己编写的,也是SpringCloud中的组件
Spring Gateway官网
添加相关配置
<dependencies>
<!-- web实例 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Nacos注册依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
</dependencies>
application.yml中替换配置
server:
port: 19000
spring:
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
# 开启动态路由
enabled: true
main:
# 防止SpringMVC和SpringGateway依赖冲突的配置
web-application-type: reactive
网关项目的knife4j配置
我们希望配置网关之后,在使用knife4j测试时
就不来回切换端口号了
我们需要在网关项目中配置Knife4j才能实现
而这个配置是固定的,
只要是网关项目配置各个子模块的knife4j功能,就直接复制这几个类即可
SwaggerProvider
@Component
public class SwaggerProvider implements SwaggerResourcesProvider {
/**
* 接口地址
*/
public static final String API_URI = "/v2/api-docs";
/**
* 路由加载器
*/
@Autowired
private RouteLocator routeLocator;
/**
* 网关应用名称
*/
@Value("${spring.application.name}")
private String applicationName;
@Override
public List<SwaggerResource> get() {
//接口资源列表
List<SwaggerResource> resources = new ArrayList<>();
//服务名称列表
List<String> routeHosts = new ArrayList<>();
// 获取所有可用的应用名称
routeLocator.getRoutes().filter(route -> route.getUri().getHost() != null)
.filter(route -> !applicationName.equals(route.getUri().getHost()))
.subscribe(route -> routeHosts.add(route.getUri().getHost()));
// 去重,多负载服务只添加一次
Set<String> existsServer = new HashSet<>();
routeHosts.forEach(host -> {
// 拼接url
String url = "/" + host + API_URI;
//不存在则添加
if (!existsServer.contains(url)) {
existsServer.add(url);
SwaggerResource swaggerResource = new SwaggerResource();
swaggerResource.setUrl(url);
swaggerResource.setName(host);
resources.add(swaggerResource);
}
});
return resources;
}
}
SwaggerController类
@RestController
@RequestMapping("/swagger-resources")
public class SwaggerController {
@Autowired(required = false)
private SecurityConfiguration securityConfiguration;
@Autowired(required = false)
private UiConfiguration uiConfiguration;
private final SwaggerResourcesProvider swaggerResources;
@Autowired
public SwaggerController(SwaggerResourcesProvider swaggerResources) {
this.swaggerResources = swaggerResources;
}
@GetMapping("/configuration/security")
public Mono<ResponseEntity<SecurityConfiguration>> securityConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(securityConfiguration).orElse(SecurityConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("/configuration/ui")
public Mono<ResponseEntity<UiConfiguration>> uiConfiguration() {
return Mono.just(new ResponseEntity<>(
Optional.ofNullable(uiConfiguration).orElse(UiConfigurationBuilder.builder().build()), HttpStatus.OK));
}
@GetMapping("")
public Mono<ResponseEntity> swaggerResources() {
return Mono.just((new ResponseEntity<>(swaggerResources.get(), HttpStatus.OK)));
}
}
SwaggerHeaderFilter类
@Component
public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory {
private static final String HEADER_NAME = "X-Forwarded-Prefix";
private static final String URI = "/v2/api-docs";
@Override
public GatewayFilter apply(Object config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
String basePath = path.substring(0, path.lastIndexOf(URI));
ServerHttpRequest newRequest = request.mutate().header(HEADER_NAME, basePath).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
return chain.filter(newExchange);
};
}
}