介绍
SpringBoot、SpringCloud、SpringCloudAlibaba、Nacos、Sentinel、Seata整合demo。
软件架构
JDK 1.8
Spring Boot 2.1.10.RELEASE
Spring Cloud Greenwich.SR6
Spring Cloud Alibaba 2.1.2.RELEASE
Nacos 1.2.1
Seata 1.2.0
系统架构图
参考文档
SpringCloud文档:https://spring.io/projects/spring-cloud
SpringCloudAlibaba文档:https://github.com/alibaba/spring-cloud-alibaba/wiki
Nacos文档:https://nacos.io/zh-cn/docs/what-is-nacos.html
Seata文档:https://seata.io/zh-cn/docs/overview/what-is-seata.html
Sentinel文档:https://sentinelguard.io/zh-cn/docs/introduction.html
Mybatis-plus文档:https://mp.baomidou.com/guide/
环境准备
1、数据库
新建数据库,数据库名mall
,字符集utf8mb4
,排序规则utf8mb4_unicode_ci
。
-- 订单
DROP TABLE IF EXISTS `t_order`;
CREATE TABLE `t_order`(
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
`code` varchar(64) DEFAULT NULL COMMENT '订单编号',
`total` double(14, 2) DEFAULT '0.00' COMMENT '总计',
`address` varchar(64) DEFAULT NULL COMMENT '收货地址',
`telephone` varchar(32) DEFAULT NULL COMMENT '电话',
`user_id` bigint(11) DEFAULT NULL,
`status` char(1) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 订单项
DROP TABLE IF EXISTS `t_order_item`;
CREATE TABLE `t_order_item`
(
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`product_count` int(11) DEFAULT 0 COMMENT '商品数量',
`subtotal` double(14, 2) DEFAULT '0.00' COMMENT '小计',
`product_id` bigint(11) DEFAULT NULL,
`order_id` bigint(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 商品
DROP TABLE IF EXISTS `t_product`;
CREATE TABLE `t_product`
(
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL COMMENT '商品名称',
`code` varchar(64) DEFAULT NULL COMMENT '商品编号',
`price` double(14, 2) DEFAULT '0.00' COMMENT '商品价格',
`description` varchar(100) DEFAULT NULL COMMENT '商品描述',
`image` varchar(100) DEFAULT NULL COMMENT '商品图片',
`count` int(11) DEFAULT 0 COMMENT '商品数量',
`category_id` bigint(11) DEFAULT NULL,
`status` char(1) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 分类
DROP TABLE IF EXISTS `t_category`;
CREATE TABLE `t_category`
(
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- 用户
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`
(
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(64) NOT NULL COMMENT '用户名',
`password` varchar(128) NOT NULL COMMENT '密码',
`nick_name` varchar(64) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
`telephone` varchar(32) DEFAULT NULL,
`birthday` datetime DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`status` char(1) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2、构建工具(maven)
settings.xml配置
<!-- 设置aliyun镜像 -->
<mirrors>
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<!-- 设置jdk1.8版本编译 -->
<profiles>
<profile>
<id>jdk8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>jdk8</activeProfile>
</activeProfiles>
3、代码仓库(gitee)
1)新建仓库
2)克隆
https://gitee.com/zhanglei-code/mall.git
3)修改.gitignore文件
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
.mvn/wrapper/maven-wrapper.jar
**/.mvn
**/mvnw
**/mvnw.cmd
**/*.iml
**/target/
**/node_modules/
.idea
4、代码生成(mybatis-plus-generator)
文档:https://mp.baomidou.com/guide/
1)创建mybatis-plus-generator工程
2)添加依赖
<dependencies>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- mp -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- mp代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 模版引擎 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
</dependencies>
3)复制官网代码生成例子,稍作修改
4)测试代码生成
后端工程
1、项目初始化
1)添加父工程pom,加入maven管理
<?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>
<groupId>com.zl</groupId>
<artifactId>mall</artifactId>
<version>1.0-SNAPSHOT</version>
<name>mall</name>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.2.RELEASE</spring-cloud-alibaba.version>
</properties>
<!-- Spring Boot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.10.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<!-- 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>
</project>
2)依次创建maven子模块
3)子模块添加依赖
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
4)添加启动类
@SpringBootApplication
public class MallProductApplication {
public static void main(String[] args) {
SpringApplication.run(MallProductApplication.class, args);
}
}
5)添加配置文件application.properties
server.port=8081
spring.application.name=mall-product
6)指定启动参数(可跳过)
# 指定服务使用端口
-Dserver.port=8001
# 设置堆最大空间
-Xmx256m
# 设置堆最小空间
-Xms256m
7) 启动测试
2、服务注册(nacos)
官网:https://nacos.io/zh-cn/index.html
1)下载安装
# 1.下载
- https://github.com/alibaba/nacos/releases
# 2.解压
- linux/unix/mac系统
tar -xvf nacos-server-$version.tar.gz
# 3.启动
- linux/unix/mac系统
进入到nacos的bin目录:sh startup.sh -m standalone
# 4.访问
- http://localhost:8848/nacos/
用户名/密码:nacos/nacos
# 5.关闭
- linux/unix/mac系统
进入到nacos的bin目录:sh shutdown.sh
2)添加依赖
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
3)添加配置
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
4)启动测试
3、服务调用(RestTemplate、Ribbon、OpenFeign)
准备好服务调用的Controller。
1)RestTemplate服务调用
@RestController
public class TestController {
@GetMapping("test")
public String test() {
// 使用ip调用
RestTemplate restTemplate = new RestTemplate();
String result = restTemplate.getForObject("http://127.0.0.1:8082/order/1", String.class);
return result;
}
}
2)RestTemplate+Ribbon负载均衡调用
启动类添加代码
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
测试代码
@Autowired
private RestTemplate restTemplate;
@GetMapping("test")
public String test() {
// 使用服务名负载均衡调用
String result = restTemplate.getForObject("http://mall-order/order/1", String.class);
return result;
}
3)OpenFeign服务调用
添加依赖
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类添加注解
@EnableFeignClients
编写调用接口
@FeignClient("mall-order")
public interface OrderService {
@GetMapping("order/{id}")
public Map<String, Object> getOrder(@PathVariable("id") Long id);
}
测试代码
@Autowired
private OrderService orderService;
@GetMapping("test")
public Map<String, Object> test() {
// 使用OpenFeign调用
Map<String, Object> result = orderService.getOrder(1L);
return result;
}
4、网关(gateway)
1)添加依赖
<!-- gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2)添加启动类
@SpringBootApplication
@EnableDiscoveryClient
public class MallGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(MallGatewayApplication.class, args);
}
}
3)添加配置文件application.yml
server:
port: 8088
spring:
application:
name: mall-gateway
cloud:
nacos:
server-addr: 127.0.0.1:8848
gateway:
routes:
# lb(loadbalance)代表负载均衡转发路由
- id: product_route
uri: lb://mall-product
predicates:
- Path=/product/**
- id: order_route
uri: lb://mall-order
predicates:
- Path=/order/**
- id: user_route
uri: lb://mall-user
predicates:
- Path=/user/**
#开启根据服务名动态获取路由
discovery:
locator:
enabled: true
4)跨域配置
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
// 配置跨域
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.setAllowCredentials(true);
source.registerCorsConfiguration("/**",corsConfiguration);
return new CorsWebFilter(source);
}
}
5)访问测试
5、引入MP(mybatis-plus)
1)依赖
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mp -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2)配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mall?characterEncoding=utf-8&useSSL=false
spring.datasource.username=root
spring.datasource.password=1234
mybatis-plus.mapper-locations=classpath:/mapper/**Mapper.xml
3)配置类
@Configuration
@MapperScan("com.zl.product.mapper")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4)测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MallProductApplicationTest {
@Autowired
private IProductService productService;
@Test
public void save() {
Product product = new Product();
product.setCode("C1001001");
product.setName("小米手机");
product.setPrice(2499.00);
product.setCount(1000);
product.setCreateTime(LocalDateTime.now());
productService.save(product);
}
@Test
public void updateById() {
Product product = new Product();
product.setId(1L);
product.setName("华为手机");
productService.updateById(product);
}
@Test
public void removeById() {
productService.removeById(1L);
}
@Test
public void list() {
List<Product> products = productService.list();
products.forEach(System.out::println);
}
@Test
public void page() {
Page<Product> page = new Page<>(1, 10);
productService.page(page);
page.getRecords().forEach(System.out::println);
System.out.println("当前页数" + page.getCurrent());
System.out.println("总页数" + page.getPages());
System.out.println("每页个数" + page.getSize());
System.out.println("总条数" + page.getTotal());
System.out.println("是否有下一页" + page.hasNext());
System.out.println("是否有上一页" + page.hasPrevious());
}
}
6、模板引擎(freemarker)
1)依赖
<!-- freemarker -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- webjars-jquery -->
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
<!-- webjars-bootstrap -->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.6.0</version>
</dependency>
2)配置
# 默认配置
spring.freemarker.template-loader-path=classpath:/templates/
spring.freemarker.suffix=.ftl
spring.freemarker.charset=UTF-8
spring.freemarker.cache=false
3)添加文件夹
resources下添加static与templates文件夹。
4)编写页面
templates下新建test.ftl文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>商城首页</title>
<link rel="stylesheet" href="/webjars/bootstrap/4.6.0/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="alert alert-success">
<a href="#" class="close" data-dismiss="alert" aria-label="close">×</a>
你好, ${name}
</div>
</div>
<script src="/webjars/jquery/3.5.1/dist/jquery.min.js"></script>
<script src="/webjars/bootstrap/4.6.0/js/bootstrap.min.js"></script>
</body>
</html>
5)编写Controller
@Controller
public class IndexController {
@GetMapping("test")
public String test(Model model) {
model.addAttribute("name", "小明");
return "test";
}
}
6)访问测试
7、分布式事务(seata)
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
官网地址:https://seata.io/zh-cn/index.html。
资源下载
下载地址:https://github.com/seata/seata/releases
下载文件:seata-server-1.2.tar.gz
、Source code(zip)
Source code(zip)
中script文件夹资源目录介绍(下面简称资源文件):
client
存放client端sql脚本 (包含 undo_log表) ,参数配置。
config-center
各个配置中心参数导入脚本,config.txt(包含server和client,原名nacos-config.txt)为通用参数文件。
server
server端数据库脚本(包含lock_table、branch_table、global_table)及各个容器配置。
快速开始
Seata分TC、TM和RM三个角色,TC(Server端)为单独服务端部署,TM和RM(Client端)由业务系统集成。
我们需要搭建Server端(下载的seata-server-1.2.tar.gz软件包)与Client端(我们的微服务),另外还有一个配置端(Nacos配置中心),需要导入相关配置供Server端和Client端使用。
配置端
1)启动Nacos
2)推送配置
修改资源文件script/config-center/config.txt中store.mode,附上修改部分。
……
service.vgroupMapping.my_test_tx_group=default
service.default.grouplist=127.0.0.1:8091
……
store.mode=db
……
store.db.datasource=druid
store.db.dbType=mysql
store.db.driverClassName=com.mysql.jdbc.Driver
store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true
store.db.user=root
store.db.password=1234
store.db.minConn=5
store.db.maxConn=30
store.db.globalTable=global_table
store.db.branchTable=branch_table
store.db.queryLimit=100
store.db.lockTable=lock_table
store.db.maxWait=5000
……
进入到资源文件script/config-center/nacos,使用终端执行./nacos-config.sh
,将配置推送到nacos配置中心。
Server端
1)创建seata-server高可用的db
Server端存储模式(store.mode)现有file、db、redis三种(后续将引入raft,mongodb)。
file模式为单机模式,全局事务会话信息内存中读写并持久化本地文件root.data,性能较高;
db模式为高可用模式,全局事务会话信息通过db共享,相应性能差些;
redis模式Seata-Server 1.3及以上版本支持,性能较高,存在事务信息丢失风险,请提前配置合适当前场景的redis持久化配置。
我们选择db模式,创建名为seata的数据库,脚本见资源文件script/server/db/mysql.sql。
-- -------------------------------- The script used when storeMode is 'db' --------------------------------
-- the table to store GlobalSession data
CREATE TABLE IF NOT EXISTS `global_table`
(
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`status` TINYINT NOT NULL,
`application_id` VARCHAR(32),
`transaction_service_group` VARCHAR(32),
`transaction_name` VARCHAR(128),
`timeout` INT,
`begin_time` BIGINT,
`application_data` VARCHAR(2000),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`xid`),
KEY `idx_gmt_modified_status` (`gmt_modified`, `status`),
KEY `idx_transaction_id` (`transaction_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store BranchSession data
CREATE TABLE IF NOT EXISTS `branch_table`
(
`branch_id` BIGINT NOT NULL,
`xid` VARCHAR(128) NOT NULL,
`transaction_id` BIGINT,
`resource_group_id` VARCHAR(32),
`resource_id` VARCHAR(256),
`branch_type` VARCHAR(8),
`status` TINYINT,
`client_id` VARCHAR(64),
`application_data` VARCHAR(2000),
`gmt_create` DATETIME(6),
`gmt_modified` DATETIME(6),
PRIMARY KEY (`branch_id`),
KEY `idx_xid` (`xid`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
-- the table to store lock data
CREATE TABLE IF NOT EXISTS `lock_table`
(
`row_key` VARCHAR(128) NOT NULL,
`xid` VARCHAR(96),
`transaction_id` BIGINT,
`branch_id` BIGINT NOT NULL,
`resource_id` VARCHAR(256),
`table_name` VARCHAR(32),
`pk` VARCHAR(36),
`gmt_create` DATETIME,
`gmt_modified` DATETIME,
PRIMARY KEY (`row_key`),
KEY `idx_branch_id` (`branch_id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
2)指定注册中心与配置中心
修改seata-server-1.2.0/conf/registry.conf。
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
# 指定注册中心
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
namespace = ""
cluster = "default"
username = "nacos"
password = "nacos"
}
}
config {
# file、nacos 、apollo、zk、consul、etcd3
# 指定配置中心
type = "nacos"
nacos {
serverAddr = "127.0.0.1:8848"
namespace = ""
group = "SEATA_GROUP"
username = "nacos"
password = "nacos"
}
}
3)启动seata-server
sh seata-server.sh -h 127.0.0.1 -p 8091 -m db
# 参数说明:
# -h: 注册到注册中心的ip
# -p: Server rpc 监听端口,默认8091
# -m: 全局事务会话信息存储模式,file、db、redis,优先读取启动参数 (Seata-Server 1.3及以上版本支持redis)
# -n: Server node,多个Server时,需区分各自节点,用于生成不同区间的transactionId,以免冲突
# -e: 多环境配置
Client端
1)创建AT模式所需的undo_log表
参与全局事务的数据库中都需要创建undo_log表,脚本见资源文件script/client/at/db/mysql.sql。
-- for AT mode you must to init this sql for you business database. the seata server not need it.
CREATE TABLE IF NOT EXISTS `undo_log`
(
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'increment id',
`branch_id` BIGINT(20) NOT NULL COMMENT 'branch transaction id',
`xid` VARCHAR(100) 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 NOT NULL COMMENT 'create datetime',
`log_modified` DATETIME NOT NULL COMMENT 'modify datetime',
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE = InnoDB
AUTO_INCREMENT = 1
DEFAULT CHARSET = utf8 COMMENT ='AT transaction mode undo table';
2)添加seata依赖
<!-- seata-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<!-- 指定版本 -->
<version>1.2.0</version>
</dependency>
3)添加配置文件
tx-service-group与资源文件script/config-center/config.txt中service.vgroupMapping保持一致。
seata:
enabled: true
application-id: applicationName
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: true
config:
type: nacos
nacos:
namespace:
serverAddr: 127.0.0.1:8848
group: SEATA_GROUP
userName: "nacos"
password: "nacos"
registry:
type: nacos
nacos:
application: seata-server
server-addr: 127.0.0.1:8848
namespace:
userName: "nacos"
password: "nacos"
4)加入全局事务注解
在全局事务发起的入口添加@GlobalTransactional,其它服务不需要添加。
/**
* 模拟用户购买商品
*/
@GlobalTransactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
@Override
public void buy() {
// 1.订单服务保存数据(模拟保存订单)
orderService.create();
// 2.商品服务修改数据(模拟扣减库存)
Product product = productMapper.selectById(1L);
product.setCount(product.getCount() - 1);
productMapper.updateById(product);
int i = 1 / 0;
}
开始愉快的测试吧!
Seata的分布式事务解决方案是业务层面的解决方案,只依赖于单台数据库的事务能力。Seata框架中一个分布式事务包含3中角色:
- Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。
- Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。
- Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。
其中,TM是一个分布式事务的发起者和终结者,TC负责维护分布式事务的运行状态,而RM则负责本地事务的运行。如下图所示:
下面是一个分布式事务在Seata中的执行流程:
-
TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。
-
XID 在微服务调用链路的上下文中传播。
-
RM 向 TC 注册分支事务,接着执行这个分支事务并提交(重点:RM在第一阶段就已经执行了本地事务的提交/回滚),最后将执行结果汇报给TC。
-
TM 根据 TC 中所有的分支事务的执行情况,发起全局提交或回滚决议。
-
TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。
为什么Seata在第一阶段就直接提交了分支事务?
Seata能够在第一阶段直接提交事务,是因为Seata框架为每一个RM维护了一张UNDO_LOG表(这张表需要客户端自行创建),其中保存了每一次本地事务的回滚数据。因此,二阶段的回滚并不依赖于本地数据库事务的回滚,而是RM直接读取这张UNDO_LOG表,并将数据库中的数据更新为UNDO_LOG中存储的历史数据。
如果第二阶段是提交命令,那么RM事实上并不会对数据进行提交(因为一阶段已经提交了),而实发起一个异步请求删除UNDO_LOG中关于本事务的记录。
8、分布式缓存(redis)
缓存商品分类信息。
1)依赖
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2)配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=20000ms
#最大连接数,默认8
spring.redis.jedis.pool.max-active=100
#最大连接阻塞等待时间,默认1ms
spring.redis.jedis.pool.max-wait=20000ms
#最大空闲连接,默认8
spring.redis.jedis.pool.max-idle=20
#最小空闲连接,默认0
spring.redis.jedis.pool.min-idle=10
3)测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MallProductApplicationTest {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private ICategoryService categoryService;
@Test
public void redis() {
List<Category> list = categoryService.list();
redisTemplate.opsForValue().set("category", JSON.toJSONString(list));
String json = redisTemplate.opsForValue().get("category");
System.out.println(JSON.parseArray(json, Category.class));
}
}
2)高并发下缓存失效问题
# 缓存穿透
- 描述:指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到DB去查询,失去了缓存的意义。
- 风险:利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃。
- 解决:null结果缓存,并加入短暂过期时间。
# 缓存雪崩
- 描述:缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
- 解决:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这 样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
# 缓存击穿
- 描述:对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到DB,我们称为缓存击穿。
- 解决:加锁。大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去查DB。
9、分布式锁(redisson)
10、消息队列(rabbitMQ)
11、全文检索(elasticsearch)
12、熔断降级(sentinel)
1)下载安装
# 下载
- https://github.com/alibaba/Sentinel/releases
# 启动
- 仪表盘是个jar包可以直接通过java命令启动 如: java -jar 方式运行 默认端口为 8080
- java -Dserver.port=9191 -jar sentinel-dashboard-1.7.2.jar
# 访问web页面
- http://localhost:9191/#/login
- 用户名&密码都是sentinel
2)引入依赖
<!--引入nacos client依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
3)配置文件
spring.cloud.sentinel.enabled=true
spring.cloud.sentinel.transport.dashboard=localhost:9191
spring.cloud.sentinel.transport.port=8719
13、Nginx(负载均衡、反向代理)
1)负载均衡
2)反向代理
14、性能监控( Java VisualVM + Visual GC)
控制台输入jvisualvm启动。
15、压力测试(Jmeter)
下载:http://jmeter.apache.org/download_jmeter.cgi
启动:bin目录下./jmeter.sh
16、性能优化
前端响应
Vue前端首次加载慢的问题解决:
# 1.路由懒加载
{
path: '/Message',
name: 'Message',
component: resolve => require(['@/views/Message.vue'], resolve)
}
# 2.cdn加速
- index.html中引入cdn
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.2/theme-chalk/index.css">
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/element-ui/2.15.2/index.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vue-router/3.2.0/vue-router.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/vuex/3.6.0/vuex.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
- 根目录创建vue.config.js,内容如下:
module.exports = {
configureWebpack: {
externals:{
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'axios': 'axios',
'core-js': 'core-js'
}
}
}
- main.js、router/index.js、store/index.js中注释掉原有Vue、Vuex、VueRouter、ELEMENT、axios、core-js的导入
# 3.gzip压缩
- 安装compression-webpack-plugin插件
npm install compression-webpack-plugin --save-dev
- 修改nginx.conf配置文件,在http节点内添加如下内容:
gzip on;
gzip_static on;
gzip_buffers 4 16k;
gzip_comp_level 5;
gzip_types text/plain application/javascript text/css application/xml text/javascript application/x- httpd-php image/jpeg image/gif image/png;
- nginx报unknown directive “gzip_static“解决
[root@VM-16-13-centos nginx-1.18.0]# ./configure --prefix=/usr/local/nginx --with-http_gzip_static_module
[root@VM-16-13-centos nginx-1.18.0]# make
[root@VM-16-13-centos nginx-1.18.0]# make install
16、集群
1)MySQL集群
2)Redis集群
3)RabbitMQ集群
4)ElasticSearch集群
前端工程
1、项目初始化
码云:https://gitee.com/panjiachen/vue-admin-template/
1)安装启动
# 克隆项目
git clone https://gitee.com/panjiachen/vue-admin-template.git
# 进入项目目录
cd vue-admin-template
# 安装依赖
npm install
# 建议不要直接使用 cnpm 安装以来,会有各种诡异的 bug,可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npm.taobao.org
# 启动服务
npm run dev
2)访问测试