文章目录
概念
基本概念
微服务就是将一个大型单体应用拆分成多个独立的、负责特定业务的小服务。一个单体应用改造成微服务应用会让应用的性能得到巨大提升,但也带来了微服务架构繁琐的治理工作。而微服务组件是为了更好的治理微服务架构项目,让程序员将更多的精力放在业务开发上。目前主流的微服务项目是使用Spring Cloud组件 + Spring Cloud Alibaba组件共同搭建的。
微服务七大组件
- 注册中心
国内常用的注册中心组件是Spring Cloud Alibaba的nacos - 远程调用与负载均衡
国内常用的远程调用与负载均衡组件是Spring Cloud的openfeigh + loadbalancer - 网关
国内常用的网关组件是Spring Cloud的gateway - 配置中心
国内常用的配置中心组件是Spring Cloud Alibaba的nacos - 分布式事务
国内常用的分布式事务组件是Spring Cloud Alibaba的seata - 熔断降级
国内常用的熔断降级组件是Spring Cloud Alibaba的sentinel - 链路追踪
国内常用的链路追踪组件是Spring Cloud的micrometer tracing + zipkin
初始化Maven父工程
- 创建一个简单的maven工程
- 规整目录
删除src目录,只保留pom.xml文件和.gitignore文件 - 将maven工程打包方式配置为pom
<packaging>pom</packaging>
- 规定编码方式以及依赖版本号
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
<spring-cloud-alibaba.version>2022.0.0.0</spring-cloud-alibaba.version>
<spring-cloud.version>2022.0.0</spring-cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- Spring Boot版本管理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud版本管理 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Cloud Alibaba版本管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 添加Spring Boot依赖
<dependencies>
<!-- Spring Boot 启动依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
整合注册中心组件
- 下载解压nacos
下载地址:nacos下载 - 启动nacos(Windows环境)
startup.cmd -m standalone - 用maven创建一个订单子模块order
- 规定编译版本和编码方式
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
- 父工程引入注册中心依赖
<!-- 注册中心组件 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
- order模块引入web启动依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 创建启动类
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- 配置nacos注册中心地址
server:
port: 20100
spring:
application:
name: order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
- 启动order模块
- 访问nacos
可以看到order服务已注册到nacos中了
整合远程调用与负载均衡组件组件
我们模拟订单服务创建订单后远程调用库存服务扣减库存的场景
- 用之前的方式创建一个storage模块并整合注册中心组件
- 编写storage服务扣减库存接口
@RestController
@RequestMapping("/storage")
public class StorageController {
@GetMapping("/reduceStorage/{productId}")
public String reduceStorage(@PathVariable Integer productId) {
return "扣减库存成功;";
}
}
- order服务引入远程调用与负载均衡组件依赖
<!-- 远程调用组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- 负载均衡组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
- order服务启动类开启远程调用注解
@EnableFeignClients
- order服务编写Feign接口
@FeignClient("storage")
public interface StorageClient {
@GetMapping("/storage/reduceStorage/{productId}")
String reduceStorage(@PathVariable("productId") Integer productId);
}
- order服务编写Controller接口
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/createOrder")
public String createOrder() {
return orderService.createOrder();
}
}
- order服务编写Service接口
public interface OrderService {
String createOrder();
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private StorageClient storageClient;
@Override
public String createOrder() {
return "创建订单成功;" + storageClient.reduceStorage(1);
}
}
- 重启order服务和storage服务并访问测试
整合网关组件
- 用之前的方式创建一个gateway模块并整合注册中心组件
- 引入网关和负载均衡组件依赖
<dependencies>
<!-- 无需引入web启动依赖 -->
<!-- 网关启动依赖 -->
<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>
</dependencies>
- 配置网关路由
spring:
cloud:
gateway:
routes:
- id: order_route # 订单服务路由id
uri: lb://order # 转发到订单服务并实现负载均衡
predicates:
- Path=/api-order/** # 匹配 /api-order 开头的请求
filters:
- RewritePath=/api-order/?(?<segment>.*), /$\{segment} # 路径重写,如 /api-order/order 重写为 /order
- id: storage_route # 库存服务路由id
uri: lb://storage # 转发到库存服务并实现负载均衡
predicates:
- Path=/api-storage/**
filters:
- RewritePath=/api-storage/?(?<segment>.*), /$\{segment}
- 启动网关
网关已注册到注册中心了
- 使用网关ip和接口前缀访问order服务和storage服务
整合配置中心组件
以gateway模块为例
- 下载并启动nacos
这一步前面已经做了,可以跳过 - 引入配置中心相关依赖
<!-- 配置中心组件 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- bootstrap启动依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
- 创建并配置bootstrap.yml
在Spring Boot中,bootstrap.yml会比application.yml优先加载,所以可以将application.yml的部分配置迁移到bootstrap.yml,帮助程序加载配置中心的配置
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
prefix: ${spring.application.name} #通过 ${prefix}-${spring.profiles.active}.${file-extension} 可以找到配置中心对应的配置文件
file-extension: yml # nacos配置文件扩展名,目前只支持 properties 和 yaml 类型
namespace: public # 默认就是 public
group: DEFAULT_GROUP # 默认就是 DEFAULT_GROUP
Spring Boot默认会将prefix-spring.profiles.active.file-extension作为DataID找到配置中心对应的配置文件,如果profiles.active.file不配置,则会将prefix.file-extension作为DataID找到配置中心对应的配置文件
默认情况下,配置文件的命名空间为public,分组名为DEFAULT_GROUP,我们可以为每个微服务创建不同的命名空间来区分不同服务,创建不同分组来区分不同环境,这里就不做演示了
-
在配置中心创建配置文件
-
将网关的配置迁移到配置中心
-
重启gateway服务并测试
整合分布式事务组件
操作数据库模拟创建订单和扣减库存
前面我们调用创建订单接口只是返回字符串信息,接下来我们创建订单数据库、订单表、库存数据库和库存表,实现真实的数据库操作
- 创建订单数据库spring_cloud_order
- 创建订单表
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_id` int(11) NOT NULL COMMENT '商品id',
`user_id` int(11) NOT NULL COMMENT '用户id',
`order_number` varchar(100) NOT NULL COMMENT '订单编号',
`order_amount` decimal(10,2) NOT NULL COMMENT '订单金额',
`flag` tinyint(4) NOT NULL DEFAULT '1' COMMENT '删除标志:0-删除,1-不删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COMMENT='订单表';
- 创建库存数据库spring_cloud_storage
- 创建库存表
CREATE TABLE `t_storage` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`product_id` int(11) NOT NULL COMMENT '商品id',
`quantity` int(11) NOT NULL COMMENT '商品数量',
`flag` tinyint(4) NOT NULL DEFAULT '1' COMMENT '删除标志:0-删除,1-不删除',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='库存表';
- 新增产品id为1的库存数据
INSERT INTO `spring_cloud_storage`.`t_storage` (`id`, `product_id`, `quantity`, `flag`, `create_time`, `update_time`) VALUES (1, 1, 100, 1, '2024-06-01 09:22:56', '2024-06-08 16:32:13');
-
order服务整合Mybatis-Plus
- 引入maven依赖
<!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <!-- mybatis-plus启动依赖。注意依赖版本要匹配 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
- 配置yml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/spring_cloud_order?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&useSSL=false username: root password: root mybatis-plus: global-config: db-config: logic-delete-field: flag logic-not-delete-value: 1 logic-delete-value: 0 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # mybatis-plus Sql日志打印 mapper-locations: /mapper/**/*.xml # mapper对应xml扫描路径
- 启动类配置Mapper接口扫描
@MapperScan("com.xiaolin.order.mapper")
- 编写代码
@Data @TableName("t_order") public class Order implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; private Integer productId; private Integer userId; private String orderNumber; private BigDecimal orderAmount; private Integer flag; private LocalDateTime createTime; private LocalDateTime updateTime; }
public interface OrderMapper extends BaseMapper<Order> { }
-
storage服务整合Mybatis-Plus
同理,请自行整合 -
优化创建订单接口
@GetMapping("/createOrder")
public String createOrder(Order order) {
return orderService.createOrder(order);
}
String createOrder(Order order);
@Override
public String createOrder(Order order) {
// 创建订单
order.setOrderNumber(UUID.randomUUID().toString());
orderMapper.insert(order);
// 扣减库存
String reduceRes = storageClient.reduceStorage(order.getProductId());
return "创建订单成功 == " + reduceRes;
}
- 优化扣减库存接口
@Autowired
private StorageMapper storageMapper;
@GetMapping("/reduceStorage/{productId}")
public String reduceStorage(@PathVariable Integer productId) {
Storage storage = storageMapper.selectOne(new LambdaUpdateWrapper<Storage>().eq(Storage::getProductId, productId));
storage.setQuantity(storage.getQuantity() - 1);
storageMapper.updateById(storage);
return "库存扣减成功";
}
-
重启order和storage服务
-
测试正常情况
-
测试异常情况
- 库存接口新增一行异常代码,模拟扣减库存失败
int a=10/0;
- 重启库存服务并测试
我们发现订单创建成功但库存却扣减失败了,这明显不符合数据一致性原理,所以接下来我们引入分布式事务组件Seata,解决分布式环境数据库事务问题
整合Seata
- 下载并解压Seata
下载地址 Seata - 在seata配置文件conf/application.yml配置注册中心地址
seata:
registry:
# support: nacos, eureka, redis, zk, consul, etcd3, sofa
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
group: SEATA_GROUP
namespace:
cluster: default
username:
password:
-
启动seata server
双击 bin/seata-server.bat
可以看到seata server已注册到注册中心
-
在订单数据库和库存数据库新建表undo_log
CREATE TABLE IF NOT EXISTS `undo_log`
(
`branch_id` BIGINT NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(128) NOT NULL COMMENT 'global transaction id',
`context` VARCHAR(128) NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` LONGBLOB NOT NULL COMMENT 'rollback info',
`log_status` INT(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` DATETIME(6) NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME(6) NOT NULL COMMENT 'modify datetime',
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8mb4 COMMENT ='AT transaction mode undo table';
ALTER TABLE `undo_log` ADD INDEX `ix_log_created` (`log_created`);
- order服务和storage服务配置seata server
seata:
registry:
type: nacos
nacos:
application: seata-server # seate 服务端服务名
server-addr: 127.0.0.1:8848
group : SEATA_GROUP # seata 服务端分组名,请确保seata服务端与客户端的namespace和group一致
namespace: # seata 服务端命名空间
username:
password:
context-path:
tx-service-group: default_tx_group
service:
vgroup-mapping:
default_tx_group: default
-
order服务service接口添加全局事务注解
-
重启order和storage服务测试
测试后我们可以发现,即使库存服务扣减库存失败了,订单服务也会回滚订单数据,这样就保证了数据一致性了
整合熔断降级组件
以order服务为例
- 下载sentinel控制台
下载地址:sentinel控制台 - 启动sentinel控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar
- 引入maven依赖
<!-- 熔断降级组件sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 配置sentinel
spring:
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080 # sentinel 控制台地址
- 编写获取订单列表接口
@GetMapping("/getOrders")
public String getOrders() {
return orderService.getOrderList();
}
String getOrderList();
@SentinelResource(value = "getOrderList") // 指定资源名并标识资源是否被限流、降级
@Override
public String getOrderList() {
return "返回订单列表";
}
- 重启order服务
- 访问getOrders接口
- 访问sentinel控制台
- 访问地址:http://localhost:8080/#/login
- 用户名:sentinel
- 密码:sentinel
可以看到order服务
-
对getOrders接口添加流控规则
这样我们就新增了一个流控规则
- 测试
每秒请求数超过2就会出现限流报错
- 优化报错提示
service接口中编写熔断降级提示方法
/** * 编写熔断降级提示 * @param be * @return */ public String getOrderListBlockHandler(BlockException be) { be.printStackTrace(); return "触发熔断降级处理规则"; }
@SentinelResource注解指定getOrderListBlockHandler
@SentinelResource(value = "getOrderList",blockHandler ="getOrderListBlockHandler")
- 重启order服务
限流报错提示友好了
-
优化异常提示
如果接口运行时抛出异常了,这时候不会触发流控规则,而是会提示500错误,如下图我们模拟接口异常
-
service接口中编写异常提示方法
public String getOrderListFallback(Throwable t) { t.printStackTrace(); return "触发异常处理规则"; }
- @SentinelResource注解指定getOrderListFallback
@SentinelResource(value = "getOrderList",blockHandler = "getOrderListBlockHandler",fallback = "getOrderListFallback")
- 重启order服务
异常提示友好了
-
对getOrders接口添加熔断规则
添加熔断规则跟添加限流规则类似,我这里就不展示啦
整合链路追踪组件
- 下载zipkin
下载地址:zipkin - 启动zipkin
java -jar zipkin.jar
- order和storage服务引入maven依赖
<!-- 健康监控配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 链路追踪依赖 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing</artifactId>
</dependency>
<!-- micrometer-tracing-bridge-brave适配zipkin的桥接包 -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<!-- OpenZipkin Zipkin with Brave -->
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!-- micrometer-observation -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-observation</artifactId>
</dependency>
<!-- feign-micrometer -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-micrometer</artifactId>
</dependency>
- order和storage服务配置micrometer-tracing和zipkin
management:
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans
tracing:
sampling:
probability: 1.0 #采样率默认为0.1(0.1就是10次只能有一次被记录下来),值越大收集越及时。
- 重启order和storage服务
- 访问创建订单接口
- 访问zipkin
- 访问地址:http://127.0.0.1:9411
点击找到一个痕迹页签,可以看到,创建订单经过了order服务和storage这两个服务,点击show还可以查看调用详情
点击依赖页面,可以看到order服务依赖于storage服务
首先,恭喜各位朋友坚持看完了教程。相信你们看完这个教程并动手实践一遍,以后就能轻松搭建一个Spring Cloud服务了。也希望朋友们可以多多关注、多多点赞收藏哦。谢谢大家啦