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/
新建数据库,数据库名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;
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>
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
文档: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)添加父工程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) 启动测试
官网: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)启动测试
准备好服务调用的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; }
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)访问测试
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()); } }
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)访问测试
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中角色:
其中,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中关于本事务的记录。
缓存商品分类信息。
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。
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
1)负载均衡
2)反向代理
控制台输入jvisualvm启动。
下载:http://jmeter.apache.org/download_jmeter.cgi
启动:bin目录下./jmeter.sh
前端响应
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集群
码云: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)访问测试