mall笔记

介绍

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)新建仓库

mall笔记

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)启动测试

mall笔记

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.gzSource 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配置中心。

mall笔记

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: 多环境配置

mall笔记

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则负责本地事务的运行。如下图所示:

mall笔记

下面是一个分布式事务在Seata中的执行流程:

  1. TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。

  2. XID 在微服务调用链路的上下文中传播。

  3. RM 向 TC 注册分支事务,接着执行这个分支事务并提交(重点:RM在第一阶段就已经执行了本地事务的提交/回滚),最后将执行结果汇报给TC。

  4. TM 根据 TC 中所有的分支事务的执行情况,发起全局提交或回滚决议。

  5. 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));
    }
}

mall笔记

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启动。

mall笔记

15、压力测试(Jmeter)

下载:http://jmeter.apache.org/download_jmeter.cgi

启动:bin目录下./jmeter.sh

mall笔记

16、性能优化

前端响应

mall笔记

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)访问测试

mall笔记

2、商品管理页面

mall笔记

3、分类管理页面

mall笔记

上一篇:Redis配置文件+springCache配置文件


下一篇:SpringCache