0%

SpringBoot笔记

@Mapper和@Repository注解的区别

@Mapper注解是由Mybatis框架中定义的一个描述数据层接口的注解,注解往往起到的都是一个描述性作用,用于告诉sprigng框架此接口的实现类由Mybatis负责创建,并将其实现类对象存储到spring容器中。

1、使用@mapper后,不需要在mapper配置中设置扫描地址,通过mapper.xml里面的namespace属性对应相关的mapper类,spring将动态的生成Bean后注入到ServiceImpl中。

2、@repository则需要在启动类中配置扫描包地址,然后生成mapper层的bean,之后被注入到ServiceImpl中(项目启动的时候没有去扫描使用@Repository注解的文件,所以使用@Repository需要配置扫描地址)

image-20210719165613109

@RequestParam

用于将请求参数区数据映射到功能处理方法的参数上。

image-20210719172247352

1
2
3
4
5
6
7
语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)

value:参数名

required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。

defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值

springboot(服务端接口)获取URL请求参数的几种方法

@PathVariable 映射 URL 绑定的占位符

@PathVariable无法接收对象,但是可以接收多个值

image-20210719172742087

image-20210719172754593

image-20210719172832439

image-20210721162647633

@RequestAttribute 获取HTTP的请求(request)对象属性值

image-20210728153424029

@RequestMapping来映射URL

@RestController注解相当于@ResponseBody + @Controller合在一起的作用。

@ResponseBody

作用:

该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。

使用时机:

返回的数据不是html标签的页面,而是其他某种格式的数据时(如json、xml等)使用;

转发与重定向的区别如下:

转发是服务器行为,重定向是客户端行为

1.转发在服务器端完成的;重定向是在客户端完成的

2.转发的速度快;重定向速度慢

3.转发的是同一次请求;重定向是两次不同请求

4.转发不会执行转发后的代码;重定向会执行重定向之后的代码

5.转发地址栏没有变化;重定向地址栏有变化

6.转发必须是在同一台服务器下完成;重定向可以在不同的服务器下完成

在servlet中调用转发、重定向的语句如下:

request.getRequestDispatcher(“new.jsp”).forward(request,
response);//转发到new.jsp

response.sendRedirect(“new.jsp”);//重定向到new.jsp

转发过程:客户浏览器发送http请求,web服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里,转发的路径必须是同一个web容器下的url,其不能转向到其他的web路径上去,中间传递的是自己的容器内的request。在客户浏览器路径栏显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

image-20210720105910177

重定向过程:客户浏览器发送http请求,web服务器接受后发送302状态码响应及对应新的location给客户浏览器,客户浏览器发现是302响应,则自动再发送一个新的http请求,请求url是新的location地址,服务器根据此请求寻找资源并发送给客户。在这里location可以重定向到任意URL,既然是浏览器重新发出了请求,则就没有什么request传递的概念了。在客户浏览器路径栏显示的是其重定向的路径,客户可以观察到地址的变化的。重定向行为是浏览器做了至少两次的访问请求的。

image-20210720105925499

重定向,其实是两次request

第一次,客户端request A,服务器响应,并response回来,告诉浏览器,你应该去B。这个时候IE可以看到地址变了,而且历史的回退按钮也亮了。重定向可以访问自己web应用以外的资源。在重定向的过程中,传输的信息会被丢失。

重定向与请求转发使用

前后两个页面 有数据传递 用请求转发,没有则用重定向。
比如servlet查询了数据需要在页面显示,就用请求转发。
比如servlet做了update操作跳转到其他页面,就用重定向。

**重定向第二次是客户端重新向浏览器发送请求(通过地址栏发送的请求都是 Get) **

转发取决于 发送转发这个请求的请求方式

image-20210806150751423

image-20210806150828903

GET和POST两种基本请求方法的区别

  • GET在浏览器回退时是无害的,而POST会再次提交请求。

  • GET产生的URL地址可以被Bookmark(书签),而POST不可以。

  • GET请求会被浏览器主动cache,而POST不会,除非手动设置。

  • GET请求只能进行url编码,而POST支持多种编码方式。

  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

  • GET请求在URL中传送的参数是有长度限制的,而POST没有。

  • 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。

  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

  • GET参数通过URL传递,POST放在Request body中

get post put delete区别

1、GET请求会向数据库发索取数据的请求,从而来获取信息,该请求就像数据库的select操作一样,只是用来查询一下数据,不会修改、增加数据,不会影响资源的内容,即该请求不会产生副作用。无论进行多少次操作,结果都是一样的。

2、与GET不同的是,PUT请求是向服务器端发送数据的,从而改变信息,该请求就像数据库的update操作一样,用来修改数据的内容,但是不会增加数据的种类等,也就是说无论进行多少次PUT操作,其结果并没有不同。

3、POST请求同PUT请求类似,都是向服务器端发送数据的,但是该请求会改变数据的种类等资源,就像数据库的insert操作一样,会创建新的内容。几乎目前所有的提交操作都是用POST请求的。

4、DELETE请求顾名思义,就是用来删除某一个资源的,该请求就像数据库的delete操作。

Rest风格

@PostMapping ,@GetMapping ,@PutMapping ,@DeleteMapping

image-20210723113317394

image-20210723102639758

image-20210723102804694

Rest原理(表单提交要使用REST的时候)

  • 表单提交会带上**_method=PUT**
  • 请求过来被HiddenHttpMethodFilter拦截
    • 请求是否正常,并且是POST
      • 获取到**_method**的值。
      • 兼容以下请求;PUT.DELETE.PATCH
      • 原生request(post),包装模式requesWrapper重写了getMethod方法,返回的是传入的值。
      • 过滤器链放行的时候用wrapper。以后的方法调用getMethod是调用requesWrapper的
WebMvcAutoConfiguration 启动器

image-20210723105736072

HiddenHttpMethodFilter 过滤器

image-20210723110202080

image-20210723110526694

image-20210723111537617

image-20210723112305966

Rest使用客户端工具,

  • 如PostMan直接发送Put、delete等方式请求,无需经过Filter过滤。

@Configuration

1
2
3
4
5
6
* 1、配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
* 2、配置类本身也是组件
* 3、proxyBeanMethods:代理bean的方法
* Full(proxyBeanMethods = true)、【保证每个@Bean方法被调用多少次返回的组件都是单实例的】
* Lite(proxyBeanMethods = false)【每个@Bean方法被调用多少次返回的组件都是新创建的】
* 组件依赖必须使用Full模式默认。其他默认是否Lite模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration(proxyBeanMethods = false) //告诉SpringBoot这是一个配置类 == 配置文件
public class MyConfig {

/**
* Full:外部无论对配置类中的这个组件注册方法调用多少次获取的都是之前注册容器中的单实例对象
* @return
*/
@Bean //给容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。返回的值,就是组件在容器中的实例
public User user01(){
User zhangsan = new User("zhangsan", 18);
//user组件依赖了Pet组件
zhangsan.setPet(tomcatPet());
return zhangsan;
}

@Bean("tom")
public Pet tomcatPet(){
return new Pet("tomcat");
}
}

@Conditional

条件装配:满足Conditional指定的条件,则进行组件注入

image-20210721110714491

配置绑定

@Component + @ConfigurationProperties

image-20210721100935458

@EnableConfigurationProperties +@ConfigurationProperties

1
2
3
4
5
6
7
@EnableConfigurationProperties(Car.class)

//1、开启Car配置绑定功能

//2、把这个Car这个组件自动注册到容器中

public class MyConfig { } //配置类

自动配置原理

自动配置类

1
2
3
4
5
6
7
8
9
10
@SpringBootApplication
@MapperScan("com.wang.mapper")
public class StudentApplication {

public static void main(String[] args) {
SpringApplication.run(StudentApplication.class, args);
}

}

@SpringBootApplication = @SpringBootConfiguration+@EnableAutoConfiguration +@ComponentScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@SpringBootConfiguration
@Configuration。代表当前是一个配置类

@ComponentScan
指定扫描哪些,Spring注解;

@EnableAutoConfiguration =@AutoConfigurationPackage + @Import

@AutoConfigurationPackage 自动配置包?指定了默认的包规则
@Import(AutoConfigurationPackages.Registrar.class) //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

//利用Registrar给容器中导入一系列组件
//将指定的一个包下的所有组件导入进来? 启动类所在包下。

@Import(AutoConfigurationImportSelector.class)
1、利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件
2、调用List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes)获取到所有需要导入到容器中的配置 类
3、利用工厂加载 Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader);得到所有的组件
4、从META-INF/spring.factories位置来加载一个文件。
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories

文件里面写死了spring-boot一启动就要给容器中加载的所有配置类

按需开启自动配置项

虽然我们127个场景的所有自动配置启动的时候默认全部加载。xxxxAutoConfiguration 按照条件装配规则(@Conditional),最终会按需配置。

修改默认配置

SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先

总结:

  • SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration

  • 每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定

  • 生效的配置类就会给容器中装配很多组件

  • 只要容器中有这些组件,相当于这些功能就有了

  • 定制化配置

  • 用户直接用自己@Bean替换底层的组件

  • 用户去看这个组件是获取的配置文件什么值就去修改。

xxxxxAutoConfiguration(导入所有自动配置类) —> 组件(按需加载所有组件) —> xxxxProperties里面拿值 —-> application.properties(配置文件里面设置值)

HttpServletRequest

HttpServletRequest对象代表客户端的请求,当客户端通过HTTP协议访问服务器时,HTTP请求头中的所有信息都封装在这个对象中,通过这个对象提供的方法,可以获得客户端请求的所有信息。

image-20210727115410977

image-20210727115426320

Session(会话控制)

Session对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的Web页之间跳转时,存储在Session对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。

Cookie,有时也用其复数形式 Cookies。类型为“小型文本文件”,是某些网站为了辨别用户身份,进行Session跟踪而储存在用户本地终端上的数据(通常经过加密),由用户客户端计算机暂时或永久保存的信息 。

Map、Model里面的数据会被放在request的请求域

常见的错误请求

400(请求错误)

400是一种HTTP状态码,告诉客户端它发送了一条异常请求。400页面是当用户在打开网页时,返回给用户界面带有400提示符的页面。其含义是你访问的页面域名不存在或者请求错误。主要分为两种。

1、语义有误,当前请求无法被服务器理解。除非进行修改,否则客户端不应该重复提交这个请求。

2、请求参数有误。

400的主要有两种形式:

1、bad request意思是“错误的请求”;

2、invalid hostname意思是”不存在的域名”。

403错误(没有权限)

​ 403错误是一种在网站访问过程中,常见的错误提示,表示资源不可用。服务器理解客户的请求,但拒绝处理它,通常由于服务器上文件或目录的权限设置导致的WEB访问错误。

404页面 (路径有问题)

404页面是客户端在浏览网页时,服务器无法正常提供信息,或是服务器无法回应,且不知道原因所返回的页面。

405错误(请求方式有问题)

405 不允许此方法
对于请求所标识的资源,不允许使用请求行中所指定的方法。

500错误(代码有问题)

500服务器内部错误

mybatis mapper.xml中#{} 与${}用法区别

https://blog.csdn.net/qq_27261867/article/details/70787849

mybatis-plus逆向工程

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.wang.config;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {

/**
* <p>
* 读取控制台内容
* </p>
*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}

public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();

// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
//设置输出路径
gc.setOutputDir(projectPath + "/***/src/main/java");
//设置作者
gc.setAuthor("王天一");
gc.setOpen(false);
// gc.setSwagger2(true); 实体属性 Swagger2 注解
mpg.setGlobalConfig(gc);

// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql:///springboot?serverTimezone=GMT");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);

// 包配置
PackageConfig pc = new PackageConfig();
// pc.setModuleName(scanner("模块名"));
pc.setParent("com.wang");
mpg.setPackageInfo(pc);

// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};

// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";

// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
//mapper.xml输出的位置
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/***/src/main/resources/mapper/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
/*
cfg.setFileCreate(new IFileCreate() {
@Override
public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) {
// 判断自定义文件夹是否需要创建
checkDir("调用默认方法创建的目录,自定义目录用");
if (fileType == FileType.MAPPER) {
// 已经生成 mapper 文件判断存在,不想重新生成返回 false
return !new File(filePath).exists();
}
// 允许生成模板文件
return true;
}
});
*/
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);

// 配置模板
TemplateConfig templateConfig = new TemplateConfig();

// 配置自定义输出模板
//指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
// templateConfig.setEntity("templates/entity2.java");
// templateConfig.setService();
// templateConfig.setController();

templateConfig.setXml(null);
mpg.setTemplate(templateConfig);

// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
strategy.setSuperControllerClass("");
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}

}

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
      <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>

<!-- 模板引擎 freemarker-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>


<!-- 模板引擎 如果模板引擎是 velocity-->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>


SpringCloud

Nacos :服务发现与注册,配置管理

nacos_map

服务注册与发现

主模块依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<springboot.version>2.3.2.RELEASE</springboot.version>
<springcloudalibaba.version>2.2.5.RELEASE</springcloudalibaba.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${springboot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${springcloudalibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>

==服务提供者==

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

</dependencies>

配置

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 8082

spring:
application:
name: nacos-stock
cloud:
nacos:
server-addr: 127.0.0.1:8848
discovery:
namespace: 793d1b0e-e601-4b41-bed1-7a8bc50768ab #指定命名空间
cluster-name: wang #集群名

主启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient //能够让注册中心发现
public class NacosStockApplication {

public static void main(String[] args) {
SpringApplication.run(NacosStockApplication.class,args);
}
}

Controller

1
2
3
4
5
6
7
8
@RestController
public class StockController {

@RequestMapping("/stock/test")
public String test(String info){
return "库存模块接收到的信息:"+info;
}
}

==服务消费者==

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.3</version>
</dependency>

</dependencies>

配置

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 8081


spring:
application:
name: nacos-order #配置项目名称
cloud:
nacos:
server-addr: 127.0.0.1:8848 #配置中心地址
discovery:
namespace: 793d1b0e-e601-4b41-bed1-7a8bc50768ab #指定命名空间 相互调用的服务要在同一命名空间下
cluster-name: wang #集群名

主启动类

1
2
3
4
5
6
7
8
9
@SpringBootApplication
@EnableDiscoveryClient //能够让注册中心发现
@EnableFeignClients(basePackages = "com.wang.feign") //开启Feign客户端支持
public class NacosOrderApplication {

public static void main(String[] args) {
SpringApplication.run(NacosOrderApplication.class,args);
}
}

通过openfeign调用

1
2
3
4
5
6
7
@FeignClient("nacos-stock")  //服务名
public interface StockFeignService {

@RequestMapping("/stock/test")
String test(@RequestParam("info") String info);

}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class OrderController {

@Autowired
private StockFeignService stockFeignService;

@RequestMapping("/order/test")
public String test(){
return stockFeignService.test("order");
}

}

配置管理

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

</dependencies>

配置

1
2
3
4
5
server:
port: 8086
spring:
profiles:
active: info
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#配置项目名称
spring.application.name=nacos-config-test

#配置服务注册中心地址
#spring.cloud.nacos.server-addr=127.0.0.1:8848
spring.cloud.nacos.server-addr=127.0.0.1:8848,127.0.0.1:8850,127.0.0.1:8852

#配置服务配置中心地址
spring.cloud.nacos.config.server-addr=127.0.0.1:8848,127.0.0.1:8850,127.0.0.1:8852

#指定properties格式的配置
spring.cloud.nacos.config.file-extension=properties
#指定分组
spring.cloud.nacos.config.group=TEST_GROUP

#配置命名空间
spring.cloud.nacos.config.namespace=793d1b0e-e601-4b41-bed1-7a8bc50768ab

#配置组 默认是DEFAULT_GROUP
#spring.cloud.nacos.config.group=DEFAULT_GROUP

#指定Data Id 命名规范:${spring.application.name 服务名}-${spring.profiles.active 环境}.${spring.cloud.nacos.config.file-extension 格式的配置}
#spring.cloud.nacos.config.name=nacos-config.properties


###################################多配置集
#data-id
spring.cloud.nacos.config.extension-configs[0].data-id=mysql-common.properties
#group
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
#自动刷新
spring.cloud.nacos.config.extension-configs[0].refresh=true

spring.cloud.nacos.config.extension-configs[1].data-id=redis-common.properties
spring.cloud.nacos.config.extension-configs[1].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[1].refresh=true

spring.cloud.nacos.config.extension-configs[2].data-id=crm.properties
spring.cloud.nacos.config.extension-configs[2].group=CRM_GROUP
spring.cloud.nacos.config.extension-configs[2].refresh=true

spring.cloud.nacos.config.extension-configs[3].data-id=oa-properties
spring.cloud.nacos.config.extension-configs[3].group=OA_GROUP
spring.cloud.nacos.config.extension-configs[3].refresh=true

spring.cloud.nacos.config.extension-configs[4].data-id=nacos-config.properties
spring.cloud.nacos.config.extension-configs[4].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[4].refresh=true

users.name=wang
users.age=10

主启动类

1
2
3
4
5
6
@SpringBootApplication
public class NacosConfigApplication {
public static void main(String[] args) {
SpringApplication.run(NacosConfigApplication.class,args);
}
}

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@RestController
@RefreshScope //支持nacos的动态刷新功能
public class NacosConfigController {

@Value("${users.name}")
private String name;

@Value("${users.age}")
private Integer age;


@Value("${users.mysql.common}")
private String mysql;

@Value("${users.redis.common}")
private String redis;

@Value("${users.crm.config}")
private String crm;

@Value("${users.oa.config}")
private String oa;

@Value("${student.name}")
private String sname;

@GetMapping("/getConfigInfo")
public String getConfigInfo(){
return name+":"+ age;
}


@GetMapping("/getCRMConfigInfo")
public String getCRMConfigInfo(){
return mysql+":"+ redis+":"+crm+":"+oa;
}

@GetMapping("/getCRMConfigInfo1")
public String getCRMConfigInfo1(){
return sname;
}

}

Nacos配置MySQL数据库

1.到Mysql中执行nacos-mysql.sql

image-20210823175714502

2.application.properties中添加以下代码

image-20210823175837434

1
2
3
4
5
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/config_info?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user=root
db.password=123456

image-20210823180038094

配置集群模式

默认是集群

image-20210823180233023

image-20210823180203778

单机 :standalone

配置端口号和本机ip地址

image-20210823180916565

设置三个关联Nacos的端口号和ip地址

image-20210823180351150

复制三个nacos,重复上面操作

image-20210823180555621

Sentinel分布式系统的流量防卫兵

Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

Sentinel-features-overview

导入依赖

1
2
3
4
5
<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

编写配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8401

spring:
application:
name: nacos-stock4-sentinel
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务注册中心地址
sentinel:
transport:
dashboard: 127.0.0.1:8080 #sentinel地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口

management:
endpoints:
web:
exposure:
include: '*'

主启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class,args);
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class FlowLimitController {
@RequestMapping("/testA")
public String testA(){
return "--------------testA";
}

@RequestMapping("/testB")
public String testB(){
return "--------------testB";
}
}

image-20210823144619297

实时监控

image-20210823144940329

流量控制

QPS:每秒的响应请求数

快速失败RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时

image-20210823145338165

QPS关联模式

如果A关联B,当B请求达到阈值,A请求会挂掉

image-20210823150324368

==Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)==方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过”冷启动”,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。初始阈值为最大阈值除以3. 经过预热时长到达最大阈值

image-20210823152957276

匀速排队

匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。

image

这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。

注意:匀速排队模式暂时不支持 QPS > 1000 的场景。

线程数

image-20210823145500651

服务降级—>服务端

降级是给服务提供者准备的

除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。

chain

现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。

Sentinel 提供以下几种熔断策略:

  • 慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
  • 异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
  • image-20210824150103255

image-20210824144936621

QQ图片20210824145135

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@RestController
public class FlowLimitController {
@RequestMapping("/testA")
public String testA() {
return "--------------testA";
}

@RequestMapping("/testB")
public String testB() {
return "--------------testB";
}


//热点规则限流 如果没有定义blockHandler方法,流量超出后,会进入异常页面
@RequestMapping("/testC")
@SentinelResource(value = "testC", blockHandler = "deal_testC")
//value:热点资源名 blockHandler:超出流量部分进入blockHandler兜底方法
public String testC(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2)
{
return "--------------testC";
}

//兜底方法
public String deal_testC(String p1, String p2, BlockException exception) {
return "testC-------热点限流";
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@RestController
public class RateLimitController {

@GetMapping("/byResource")
@SentinelResource(value = "byResource", blockHandler = "handleException") //按照资源限流,可自定义处理程序
public CommonResult byResource() {
return new CommonResult(200, "按照资源限流", new User("wang", 10));
}

public CommonResult handleException(BlockException exception) { //如果没有自定义处理程序,会报500(找不到FlowException)
return new CommonResult(444, exception.getClass().getCanonicalName());
}

@GetMapping("/byResourceUrl") //按照Url地址限流,会走系统默认处理程序
public CommonResult byResourceUrl() {
return new CommonResult(200, "按照Url地址限流", new User("wang", 10));
}

@GetMapping("/CustomerHandle") //
@SentinelResource(value = "CustomerHandle", blockHandlerClass = CustomerBlockHandle.class, blockHandler = "handleException1")
public CommonResult CustomerHandle() {
return new CommonResult(200, "按照资源地址限流", new User("wang", 10));
}

}

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class MainApp8401 {
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class,args);
}
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 8401

spring:
application:
name: nacos-stock4-sentinel
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务注册中心地址
sentinel:
transport:
dashboard: 127.0.0.1:8080 #sentinel地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口

management:
endpoints:
web:
exposure:
include: '*'

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>3.0.3</version>
</dependency>
</dependencies>

服务熔断—>客户端

熔断是给服务消费者准备的

服务提供者

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
public class PaymentController {

@Value("${server.port}")
private String serverPort;

public static HashMap<Integer, User> hashMap = new HashMap<>();
static {
hashMap.put(1,new User("王1",20));
hashMap.put(2,new User("王2",21));
hashMap.put(3,new User("王3",22));
}

@GetMapping("/paymentSQL/{id}")
public CommonResult<User> paymentSQL(@PathVariable("id") Integer id){
User user = hashMap.get(id);
CommonResult<User> result = new CommonResult(200 ,"端口"+ serverPort ,user);
return result;
}
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 9003

spring:
application:
name: nacos-provider
cloud:
nacos:
server-addr: 127.0.0.1:8848


management:
endpoints:
web:
exposure:
include: '*'

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

</dependencies>

服务消费者

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@RestController
public class ConsumerController {


@Resource
private ProviderFeign providerFeign;



@GetMapping("/ConsumerSQL/{id}")
// @SentinelResource(value = "fallback") //没有配置
// @SentinelResource(value = "fallback",fallback = "handleFallback") //fallback只负责业务异常
// @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台的配置违规
@SentinelResource(value = "fallback",blockHandler = "blockHandler",fallback = "handleFallback",
exceptionsToIgnore = {IllegalArgumentException.class}) //exceptionsToIgnore忽略某个异常,不走兜底方法
public CommonResult<User> fallback(@PathVariable("id") Integer id){
CommonResult<User> result = providerFeign.paymentSQL(id);
if(id == 4){
throw new IllegalArgumentException("非法参数异常........");
}else if(result.getData() == null){
throw new NullPointerException("空指针异常..........");
}
return result;
}

public CommonResult<User> handleFallback(Integer id,Throwable e){
User user = new User("null", null);
return new CommonResult<>(444,"handleFallback兜底异常"+e.getMessage(),user);
}

public CommonResult<User> blockHandler(Integer id, BlockException e){
User user = new User("null", null);
return new CommonResult<>(445,"blockHandler兜底异常"+e.getLocalizedMessage(),user);
}


@GetMapping("/ConsumerSQL1/{id}")
public CommonResult<User> fallback1(@PathVariable("id") Integer id){
return providerFeign.paymentSQL(id);
}

}

ProviderFeign

1
2
3
4
5
6
@FeignClient(value ="nacos-provider",fallback = ProviderFeignImpl.class)
public interface ProviderFeign {

@GetMapping("/paymentSQL/{id}")
CommonResult<User> paymentSQL(@PathVariable("id") Integer id);
}

ProviderFeignImpl

1
2
3
4
5
6
7
@Component
public class ProviderFeignImpl implements ProviderFeign {
@Override
public CommonResult<User> paymentSQL(Integer id) {
return new CommonResult<>(444444,"报错了.....",new User(null,null));
}
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
server:
port: 84

spring:
application:
name: nacos-consumer
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务注册中心地址
sentinel:
transport:
dashboard: 127.0.0.1:8080 #sentinel地址
port: 8719 #默认8719端口,假如被占用会自动从8719开始依次加1扫描,直到找到没有被占用的端口



feign:
sentinel:
enabled: true #激活Sentinel对feign的支持

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.5.2</version>
</dependency>


</dependencies>

Seata:分布式事务解决方案

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。

img

全局唯一的事务ID

TC (Transaction Coordinator) - 事务协调者

​ 维护全局和分支事务的状态,驱动全局事务提交或回滚。

TM (Transaction Manager) - 事务管理器

​ 定义全局事务的范围:开始全局事务、提交或回滚全局事务。

RM (Resource Manager) - 资源管理器

​ 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

![FEZ4$GV[OG]WWU2QE{FUX

img

QQ图片20210827192552

VT21LP}207$AM{QKV%NL{AF

QQ图片20210827192812

测试

下单–>减库存–>减账户–>下单成功

订单模块 seata-order-service2001

Controller

1
2
3
4
5
6
7
8
9
10
11
12
@RestController
public class T_orderController {

@Resource
T_orderService t_orderService;

@GetMapping("/order/create")
public CommonResult create(T_order t_order){
t_orderService.create(t_order);
return new CommonResult(200,"下单成功");
}
}

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Data
@AllArgsConstructor
@NoArgsConstructor
public class T_order {
private Long id;
private Long user_id;
private Long product_id;
private Integer count;
private BigDecimal money;
/**
* 订单状态 0为下单 1下单成功
*/
private Integer status;

}

Mapper

1
2
3
4
5
6
7
8
9
@Mapper
public interface T_orderMapper {
//创建订单
void create(T_order tOrder);

//修改订单状态
void update(@Param("user_id") Long user_id ,@Param("status") Integer status);

}

service

1
2
3
4
5
public interface T_orderService {
//创建订单
void create(T_order tOrder);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Service
public class T_orderServiceImpl implements T_orderService {

@Resource
T_orderMapper t_orderMapper;

@Resource
T_accountFeign t_accountService;

@Resource
T_storageFeign t_storageService;

@Override
@GlobalTransactional(name = "my_test_tx_group",rollbackFor = Exception.class) //name:唯一名 rollbackFor:发生异常就回滚
public void create(T_order tOrder) {
//创建新订单
t_orderMapper.create(tOrder);
//修改库存
t_storageService.decrease(tOrder.getProduct_id(),tOrder.getCount());
//修改余额
t_accountService.decrease(tOrder.getUser_id(),tOrder.getMoney());
//修改订单状态,下单成功
t_orderMapper.update(tOrder.getUser_id(),0);
}

}

OpenFeign

1
2
3
4
5
6
7
@Component
@FeignClient("seata-account-service2002")
public interface T_accountFeign {

@PostMapping(value = "/account/decrease")
public CommonResult decrease(@RequestParam("user_id") Long user_id, @RequestParam("money") BigDecimal money);
}
1
2
3
4
5
6
7
@Component
@FeignClient("seata-storage-service2003")
public interface T_storageFeign {

@PostMapping(value = "/storage/decrease")
public CommonResult decrease(@RequestParam("product_id") Long product_id, @RequestParam("count") Integer count);
}

启动类

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients(basePackages = "com.wang.service.openFeign")
@EnableDiscoveryClient
public class Order2001Application {
public static void main(String[] args) {
SpringApplication.run(Order2001Application.class,args);
}
}

xml

1
2
3
4
5
6
7
8
9
10
11
<mapper namespace="com.wang.mapper.T_orderMapper">
<insert id="create" parameterType="t_order">
insert into t_order(user_id,product_id,count,money,status)
values (#{user_id},#{product_id},#{count},#{money},0)
</insert>

<update id="update">
update t_order set status=1
where user_id=#{user_id} and status=#{status}
</update>
</mapper>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 2001

spring:
application:
name: seata-order-service2001
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group #配置事务分组名称
nacos:
discovery:
server-addr: localhost:8848
datasource: ##数据源配置
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/seata_order?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456


mybatis:
type-aliases-package: com.wang.entity #包取别名

# classpath就代表目录了
mapper-locations: classpath:mybatis/mapper/*.xml #配置xml文件路径

file.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
service {
#transaction service group mapping
vgroup_mapping.seata-server = "default"
#only support when registry.type=file, please don't set multiple addresses
default.grouplist = "127.0.0.1:8091"
#disable seata
disableGlobalTransaction = false
}

## transaction log store, only used in seata-server
store {
## store mode: file、db
mode = "db"

## file store property
file {
## store location dir
dir = "sessionStore"
}

## database store property
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://127.0.0.1:3306/seata"
user = "root"
password = "123456"
}
}

registry.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa
type = "nacos"

nacos {
serverAddr = "localhost:8848"
namespace = ""
cluster = "default"
}
eureka {
serviceUrl = "http://localhost:8761/eureka"
application = "default"
weight = "1"
}
redis {
serverAddr = "localhost:6379"
db = "0"
}
zk {
cluster = "default"
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
consul {
cluster = "default"
serverAddr = "127.0.0.1:8500"
}
etcd3 {
cluster = "default"
serverAddr = "http://localhost:2379"
}
sofa {
serverAddr = "127.0.0.1:9603"
application = "default"
region = "DEFAULT_ZONE"
datacenter = "DefaultDataCenter"
cluster = "default"
group = "SEATA_GROUP"
addressWaitTime = "3000"
}
file {
name = "file.conf"
}
}

config {
# file、nacos 、apollo、zk、consul、etcd3
type = "file"

nacos {
serverAddr = "localhost"
namespace = ""
}
consul {
serverAddr = "127.0.0.1:8500"
}
apollo {
app.id = "seata-server"
apollo.meta = "http://192.168.1.204:8801"
}
zk {
serverAddr = "127.0.0.1:2181"
session.timeout = 6000
connect.timeout = 2000
}
etcd3 {
serverAddr = "http://localhost:2379"
}
file {
name = "file.conf"
}
}

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- 官方的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

</dependencies>

账户模块seata-account-service2002

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
public class T_accountController {

T_accountService t_accountService;

public T_accountController(T_accountService t_accountService) {
this.t_accountService = t_accountService;
}

@PostMapping(value = "/account/decrease")
public CommonResult decrease(@RequestParam("user_id") Long user_id,
@RequestParam("money") BigDecimal money) {
t_accountService.decrease(user_id, money);
return new CommonResult(200,"账户余额扣减成功,哈哈哈");
}

}

实体类

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class T_account {
private Long id;
private Long user_id;
private BigDecimal total;
private BigDecimal used;
private BigDecimal residue;
}

Mapper

1
2
3
4
@Mapper
public interface T_accountMapper {
void decrease(@Param("user_id") Long user_id,@Param("money") BigDecimal money);
}

service

1
2
3
public interface T_accountService {
void decrease(@Param("user_id") Long user_id, @Param("money") BigDecimal money);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class T_accountServiceImpl implements T_accountService {

@Resource
T_accountMapper t_accountMapper;

@Override
public void decrease(Long user_id, BigDecimal money) {
// try {
// TimeUnit.SECONDS.sleep(20); //模拟请求超时
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
t_accountMapper.decrease(user_id,money);
}
}

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class T_account2002Application {
public static void main(String[] args) {
SpringApplication.run(T_account2002Application.class,args);
}
}

xml

1
2
3
4
5
6
7
<mapper namespace="com.wang.mapper.T_accountMapper">
<update id="decrease">
update t_account
set used = used + #{money},residue = residue - #{money}
where user_id=#{user_id}
</update>
</mapper>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 2002

spring:
application:
name: seata-account-service2002
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource: ##数据源配置
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/seata_account?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456


mybatis:
type-aliases-package: com.wang.entity #包取别名

# classpath就代表目录了
mapper-locations: classpath:mybatis/mapper/*.xml #配置xml文件路径

file.conf文件

registry.conf文件

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- 官方的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>


<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

</dependencies>

库存模块seata-storage-service2003

Controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
public class T_storageController {


T_storageService t_storageService;

public T_storageController(T_storageService t_storageService) {
this.t_storageService = t_storageService;
}

@PostMapping(value = "/storage/decrease")
public CommonResult decrease(@RequestParam("product_id") Long product_id,
@RequestParam("count") Integer count) {
t_storageService.decrease(product_id, count);
return new CommonResult(200,"库存扣减成功,哈哈哈哈");
}

}

实体类

1
2
3
4
5
6
7
8
9
10
@Data
@AllArgsConstructor
@NoArgsConstructor
public class T_storage {
private Long id;
private Long product_id;
private Integer total;
private Integer used;
private Integer residue;
}

Mapper

1
2
3
4
5
@Mapper
public interface T_storageMapper {

void decrease(@Param("product_id") Long product_id,@Param("count") Integer count);
}

Service

1
2
3
public interface T_storageService {
void decrease(@Param("product_id") Long product_id, @Param("count") Integer count);
}
1
2
3
4
5
6
7
8
9
10
11
@Service
public class T_storageServiceImpl implements T_storageService {

@Resource
T_storageMapper t_storageMapper;

@Override
public void decrease(Long product_id, Integer count) {
t_storageMapper.decrease(product_id,count);
}
}

启动类

1
2
3
4
5
6
7
@SpringBootApplication
@EnableDiscoveryClient
public class T_storage2003Application {
public static void main(String[] args) {
SpringApplication.run(T_storage2003Application.class,args);
}
}

xml

1
2
3
4
5
6
7
<mapper namespace="com.wang.mapper.T_storageMapper">
<update id="decrease">
update t_storage
set used=used+#{count},residue=residue-#{count}
where product_id=#{product_id}
</update>
</mapper>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 2003

spring:
application:
name: seata-storage-service2003
cloud:
alibaba:
seata:
tx-service-group: my_test_tx_group
nacos:
discovery:
server-addr: localhost:8848
datasource: ##数据源配置
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/seata_storage?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
username: root
password: 123456


mybatis:
type-aliases-package: com.wang.entity #包取别名

# classpath就代表目录了
mapper-locations: classpath:mybatis/mapper/*.xml #配置xml文件路径

file.conf文件

registry.conf文件

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<dependencies>

<dependency>
<groupId>com.wang</groupId>
<artifactId>nacos-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
<exclusions>
<exclusion>
<artifactId>seata-all</artifactId>
<groupId>io.seata</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-all</artifactId>
<version>1.2.0</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 配置中心 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

<!--服务注册与发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>

<!--spring-cloud-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- 官方的-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>


<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>

</dependencies>

Redis

五大基础数据类型

String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
127.0.0.1:6379> ping      //测试是否连接成功
PONG

127.0.0.1:6379> set name wang //设置值 set 键 值
OK

127.0.0.1:6379> get name //获取值 get 键
"wang"

127.0.0.1:6379> select 3 //切换数据库(redis中一个16个数据库) select 编号
OK

127.0.0.1:6379[3]> select 0
OK

127.0.0.1:6379> dbsize //查看当前数据库大小
(integer) 2

127.0.0.1:6379> keys * //查看所有的键
1) "name"
2) "\xac\xed\x00\x05t\x00\x05mykey"


127.0.0.1:6379> set key1 value1
OK
127.0.0.1:6379> set key2 value2
OK
127.0.0.1:6379> keys key* //模糊匹配
1) "key1"
2) "key2"


127.0.0.1:6379> flushdb //清空当前数据库
OK

127.0.0.1:6379> keys *
(empty list or set)

127.0.0.1:6379> FLUSHALL //清空全部数据库
OK

127.0.0.1:6379> exists name //是否存在某个键 exists 键
(integer) 1

127.0.0.1:6379> expire name 5 //设置某个键过期时间 expire 键 设置时间
(integer) 1

127.0.0.1:6379> ttl name //查看剩余时间 ttl 键
(integer) -2

127.0.0.1:6379> move name 1 //将当前数据库的 key 移动到给定的数据库当中。 move 键 库
(integer) 1

127.0.0.1:6379> append name hello //追加 append 键 值 (如果没有这个键,就会新建一个)
(integer) 9

127.0.0.1:6379> incr views
(integer) 7

127.0.0.1:6379> incr views //加一(自增) incr 键
(integer) 8

127.0.0.1:6379> decr views //减一(自减) decr 键
(integer) 7

127.0.0.1:6379> incrby views 10 //加10(相当于 i+=i ) incrby 键 要加的次数
(integer) 17

127.0.0.1:6379> decrby views 15 //减15(相当于 i-=i ) decrby 键 要减的次数
(integer) 2


127.0.0.1:6379> set name wangtianyi
OK
127.0.0.1:6379> get name
"wangtianyi"
127.0.0.1:6379> getrange name 0 5 //截取字符串 [0,5] getrange 键 起始索引 结束索引
"wangti"
127.0.0.1:6379> getrange name 0 -1 //截取全部字符串 getrange 键 0 -1
"wangtianyi"


127.0.0.1:6379> set key1 aaaaa
OK
127.0.0.1:6379> get key1
"aaaaa"
127.0.0.1:6379> setrange key1 1 bb //替换字符串 setrange 键 起始位置 替换的字符
(integer) 5
127.0.0.1:6379> get key1
"abbaa"



127.0.0.1:6379> setex mykey 30 "hello" //设置过期时间 设置mykey的值为“hello”,30秒后过期
OK
127.0.0.1:6379> ttl mykey //查看还剩多久过期
(integer) 23
127.0.0.1:6379> setnx id 11111 //不存在就创建(分布式锁中会用到)
(integer) 1
127.0.0.1:6379> keys *
1) "key1"
2) "id"
3) "name"
127.0.0.1:6379> ttl mykey
(integer) -2
127.0.0.1:6379> setnx id 22222 //如果存在,就创建失败
(integer) 0
127.0.0.1:6379> get id
"11111"



127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 //批量设置 mset 键1 值1 键2 值2 ......
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 //批量获取 mget 键1 键2 ....
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 //批量设置,如果都不存在就创建(原子性,要么都成功,要么都失败)
(integer) 0
127.0.0.1:6379> get k4
(nil)



key(user:id:filed) value
127.0.0.1:6379> mset user:1:name wang user:1:id 10 //设置user:1对象 name属性,值为wang
OK
127.0.0.1:6379> mget user:1:name user:1:id //获取user对象对应属性的值
1) "wang"
2) "10"





127.0.0.1:6379> getset db redis // 先get再set(先get,如果没有则返回null,然后再set) getset 键 值
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb // 如果存在值,则先返回存在的值,再去设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"





List 有序可重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
127.0.0.1:6379> lpush list one   //向list列表中插入值(插入到列表的头部 左进)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1 //先进后出(类似栈)
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lpush list foer
(integer) 4
127.0.0.1:6379> lrange list 0 2
1) "foer"
2) "three"
3) "two"
127.0.0.1:6379> rpush list five //从右边插入值(尾部)
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "foer"
2) "three"
3) "two"
4) "one"
5) "five"
127.0.0.1:6379> lpop list //移除list中第一个元素
"foer"
127.0.0.1:6379> rpop list //移除list中最后一个元素
"five"
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lindex list 0 //通过下标获得list中某一个的值
"three"
127.0.0.1:6379> llen list //获取list的长度
(integer) 3



127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3
127.0.0.1:6379> lpush list 3
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lrem list 1 1 // 移除list集合中指定元素 lrem list 个数 要移除的元素 (移除了list中1个1)
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "3"
3) "2"
127.0.0.1:6379> lrem list 2 3 // 移除list集合中指定元素 lrem list 个数 要移除的元素 (移除了list中2个3)
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "2"


127.0.0.1:6379> lrange mylist 0 -1
1) "wang1"
2) "wang2"
3) "wang3"
4) "wang4"
127.0.0.1:6379> ltrim mylist 1 2 //通过下标截取指定长度 ltrim 键 起始下标 长度
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "wang2"
2) "wang3"



127.0.0.1:6379> lpush user1 wang1
(integer) 1
127.0.0.1:6379> lpush user1 wang2
(integer) 2
127.0.0.1:6379> lpush user1 wang3
(integer) 3
127.0.0.1:6379> rpoplpush user1 user2 //将user1中最后一个元素移除,并添加到user2中
"wang1"
127.0.0.1:6379> lrange user2 0 -1
1) "wang1"




127.0.0.1:6379> exists list //判断当前数据库是否有list
(integer) 0
127.0.0.1:6379> lset list 0 wang // 如果没有键的话,替换时不会自动创建
(error) ERR no such key
127.0.0.1:6379> lpush list wang1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "wang1"
127.0.0.1:6379> lset list 0 wang2 //将list集合中,0下标的值替换成wang2
OK
127.0.0.1:6379> lrange list 0 0
1) "wang2"







127.0.0.1:6379> rpush list 1
(integer) 1
127.0.0.1:6379> rpush list 2
(integer) 2
127.0.0.1:6379> rpush list 3
(integer) 3
127.0.0.1:6379> rpush list 4
(integer) 4
127.0.0.1:6379> linsert list after 4 5 //在list集合中,在4后面插入一个5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
127.0.0.1:6379> linsert list before 1 0 //在list集合中,在1前面插入0
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
6) "5"

image-20210805122629875

Set 无序不可重复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
127.0.0.1:6379> sadd myset hello world   //向set中添加元素   sadd  键  值1  值2
(integer) 2
127.0.0.1:6379> smembers myset //查看set中的所有元素 smembers 键
1) "hello"
2) "world"
127.0.0.1:6379> sadd myset wang //向set中添加元素 sadd 键 值
(integer) 1
127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
3) "wang"
127.0.0.1:6379> sismember myset wang //查看set中是否存在某个值 sismember 键 值
(integer) 1
127.0.0.1:6379> sismember myset wang1
(integer) 0
127.0.0.1:6379> scard myset //获取set某个键的元素个数 scard 键
(integer) 3

127.0.0.1:6379> srem myset wang1 //移除set中指定元素 srem 键 值
(integer) 1


127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
3) "wang"
127.0.0.1:6379> srandmember myset // 随机抽取一个元素 srandmember 键
"world"
127.0.0.1:6379> srandmember myset
"wang"
127.0.0.1:6379> srandmember myset 2 // 随机抽取多个元素 srandmember 键 个数
1) "world"
2) "wang"



127.0.0.1:6379> smembers myset
1) "hello"
2) "world"
3) "wang"
127.0.0.1:6379> spop myset //随机移除一个元素 spop 键
"world"

127.0.0.1:6379> smembers myset
1) "2"
2) "1"
3) "4"
4) "hello"
5) "3"
6) "5"
127.0.0.1:6379> spop myset 2 //随机移除多个个元素 spop 键 个数
1) "1"
2) "5"



//将一个指定的值移动到另一个set集合
127.0.0.1:6379> smembers set1
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> smembers set2
1) "4"
2) "5"
3) "6"
127.0.0.1:6379> smove set1 set2 3 // smove 键1 键2 键1中的值 (将键一中的值移动到键2)
(integer) 1
127.0.0.1:6379> smembers set1
1) "1"
2) "2"
127.0.0.1:6379> smembers set2
1) "3"
2) "4"
3) "5"
4) "6"




127.0.0.1:6379> sadd set1 1 2 3 4 5
(integer) 5
127.0.0.1:6379> sadd set2 5 6 7 8 9
(integer) 5
127.0.0.1:6379> sdiff set1 set2 //差集
1) "1"
2) "2"
3) "3"
4) "4"
127.0.0.1:6379> sinter set1 set2 //交集(可以用来做共同好友)
1) "5"
127.0.0.1:6379> sunion set1 set2 //并集
1) "1"
2) "2"
3) "3"
4) "4"
5) "5"
6) "6"
7) "7"
8) "8"
9) "9"

Hash(Map集合 k-v)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
127.0.0.1:6379> hset hash1 k1 v1  //向hash中添加一个元素   hset  键  值(key value)  
(integer) 1
127.0.0.1:6379> hget hash1 k1 //获取hash中一个key的值
"v1"
127.0.0.1:6379> hmset hash1 k1 v1 k2 v2 //向hash中添加多个元素 hset 键 值1(key value) 值2(key value)
OK
127.0.0.1:6379> hmget hash1 k1 k2 //获取hash中多个key的值
1) "v1"
2) "v2"
127.0.0.1:6379> hgetall hash1 //获取hash中全部元素
1) "k1" //键1
2) "v1" //值1
3) "k2" //键2
4) "v2" //值2


127.0.0.1:6379> hdel hash1 k1 //删除hash中指定键对应的值 hdel 键 key名
(integer) 1
127.0.0.1:6379> hgetall hash1
1) "k2"
2) "v2"




127.0.0.1:6379> hgetall hash1
1) "k2"
2) "v2"
3) "k1"
4) "v1"
5) "k3"
6) "v3"
127.0.0.1:6379> hlen hash1 //获取hash表中 k-v键值对 数量
(integer) 3
127.0.0.1:6379> hexists hash1 k1 //判断hash表中,指定键值对是否存在 hexists 键 key
(integer) 1
127.0.0.1:6379> hexists hash1 k5
(integer) 0



127.0.0.1:6379> hkeys hash1 // 只获得hash中所有的key
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> hvals hash1 // 只获得hash中所有的value
1) "v2"
2) "v1"
3) "v3"



127.0.0.1:6379> hgetall hash1
1) "k2"
2) "v2"
3) "k1"
4) "v1"
5) "k3"
6) "v3"
7) "age"
8) "2"
127.0.0.1:6379> hincrby hash1 age i // 自增i hincrby 键 key i (i+=i)
(integer) 3
127.0.0.1:6379> hincrby hash1 age -i // 自减i hincrby 键 key -i (i-=i)
(integer) 2



127.0.0.1:6379> hsetnx hash1 k5 v5 //如果不存在就创建
(integer) 1
127.0.0.1:6379> hsetnx hash1 k5 v //如果存在就不创建
(integer) 0

Zset(有序集合)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
127.0.0.1:6379> zadd salary 5000 wang1  1000 wang2  1500 wang3   //向Zset中添加三个元素  zadd  键   分数1  值1
(integer) 3
127.0.0.1:6379> zrangebyscore salary -inf +inf //从小到大排序
1) "wang2"
2) "wang3"
3) "wang1"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores //从小到大排序,并显示分数
1) "wang2"
2) "1000"
3) "wang3"
4) "1500"
5) "wang1"
6) "5000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores //从大到小排序,并显示分数
1) "wang1"
2) "5000"
3) "wang2"
4) "2000"
5) "wang3"
6) "1500"
127.0.0.1:6379> zrangebyscore salary 1500 2000 //获取[1500,2000]间的值
1) "wang3"
127.0.0.1:6379> zrangebyscore salary 1500 5000 withscores //获取[1500,5000]间的值,并显示分数
1) "wang3"
2) "1500"
3) "wang1"
4) "5000"


127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "wang2"
2) "1000"
3) "wang3"
4) "1500"
5) "wang1"
6) "5000"
127.0.0.1:6379> zrem salary wang2 //移除Zset中指定元素
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "wang3"
2) "1500"
3) "wang1"
4) "5000"


127.0.0.1:6379> zcard salary //获取集合中元素个数
(integer) 2



127.0.0.1:6379> zrevrange salary 0 -1 withscores //从大到小排序,并显示分数
1) "wang1"
2) "5000"
3) "wang2"
4) "2000"
5) "wang3"
6) "1500"
127.0.0.1:6379> zcount salary -inf +inf //获取指定区间的成员数量 zcount 键 最小 最大
(integer) 3
127.0.0.1:6379> zcount salary 1500 2000
(integer) 2

三种特殊数据类型

Geospatial地理位置

有效的经度从-180度到180度。
有效的纬度从-85.05112878度到85.05112878度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing   //添加地理位置  geoadd  键  经度、纬度、名称
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing
(integer) 1
127.0.0.1:6379> geoadd china:city 114.05 22.52 shenzheng 120.16 30.24 hangzhou
(integer) 2



127.0.0.1:6379> geopos china:city beijing //获取指定城市的经度和纬度
1) 1) "116.39999896287918"
2) "39.900000091670925"
127.0.0.1:6379> geopos china:city shanghai
1) 1) "121.47000163793564"
2) "31.229999039757836"
127.0.0.1:6379> geopos china:city chongqing shenzheng //geopos 键 城市1 城市2
1) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "114.04999762773514"
2) "22.520000087950386"




127.0.0.1:6379> geodist china:city beijing shanghai //计算两个城市之间的距离 geodist 键 城市1 城市2
"1067378.7564"
127.0.0.1:6379> geodist china:city beijing shanghai km //计算两个城市之间的距离 geodist 键 城市1 城市2 单位
"1067.3788"



127.0.0.1:6379> georadius china:city 110 30 1000 km //以 110 30 这个经纬度为中心,寻找方圆1000km内的城市
1) "chongqing"
2) "shenzheng"
3) "hangzhou"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqing"



//以 110 30 这个经纬度为中心,寻找方圆1000km内的城市,并显示到中心的距离
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist
1) 1) "chongqing"
2) "341.9374"
2) 1) "shenzheng"
2) "924.6408"
3) 1) "hangzhou"
2) "977.5143"

//以 110 30 这个经纬度为中心,寻找方圆1000km内的城市,并显示各城市经纬度
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord
1) 1) "chongqing"
2) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "shenzheng"
2) 1) "114.04999762773514"
2) "22.520000087950386"
3) 1) "hangzhou"
2) 1) "120.16000002622604"
2) "30.240000322949022"


//以 110 30 这个经纬度为中心,寻找方圆1000km内的城市,并显示到中心的距离 ,各城市经纬度 限制查询出来的个数(count)
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 1
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885"
2) "29.529999579006592"
127.0.0.1:6379> georadius china:city 110 30 1000 km withdist withcoord count 3
1) 1) "chongqing"
2) "341.9374"
3) 1) "106.49999767541885"
2) "29.529999579006592"
2) 1) "shenzheng"
2) "924.6408"
3) 1) "114.04999762773514"
2) "22.520000087950386"
3) 1) "hangzhou"
2) "977.5143"
3) 1) "120.16000002622604"
2) "30.240000322949022"


127.0.0.1:6379> georadiusbymember china:city beijing 1000 km //以城市为中心,多少范围内的所有城市
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 2000 km
1) "chongqing"
2) "shenzheng"
3) "hangzhou"
4) "shanghai"
5) "beijing"



//将二维的经纬度转换为一维的字符串,如果两个字符串越接近,表明距离越近
127.0.0.1:6379> geohash china:city beijing shanghai
1) "wx4fbxxfke0"
2) "wtw3sj5zbj0"




127.0.0.1:6379> zrange china:city 0 -1 //查看所有城市
1) "chongqing"
2) "shenzheng"
3) "hangzhou"
4) "shanghai"
5) "beijing"
127.0.0.1:6379> zrem china:city beijing //删除指定城市
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqing"
2) "shenzheng"
3) "hangzhou"
4) "shanghai"

Hyperloglog 基数统计

网页的UV( UV:独立访客 一个人访问一个网站多次,但是还是算作一个人)

传统的方式,set保存用户的id,然后统计set中元素的数量

大数据量时可能会有误差

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
127.0.0.1:6379> pfadd pf1 a b c d e f g     //向Hyperloglog中添加元素  pfadd  键  值1.....  
(integer) 1
127.0.0.1:6379> pfcount pf1 //统计元素个数(去重) pfcount 键
(integer) 7
127.0.0.1:6379> pfadd pf2 a a c c b b d d
(integer) 1
127.0.0.1:6379> pfcount pf2
(integer) 4
127.0.0.1:6379> pfmerge pf3 pf1 pf2 //合并 pfmerge 新键 要合并的键
OK
127.0.0.1:6379> pfcount pf3
(integer) 7




Bitmap位图场景

统计用户信息,活跃,不活跃;登录,未登录;打卡,未打卡;两个状态的都可以用Bitmap

Bitmap位图,数据结构!操作二进制位来进行记录,就只有0,1两种状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> setbit sign 0 1   //设置    setbit 键  名  状态
(integer) 0
127.0.0.1:6379> setbit sign 1 1
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 4 0
(integer) 0
127.0.0.1:6379> setbit sign 5 1
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 1 //获取状态 getbit 键 名
(integer) 1
127.0.0.1:6379> getbit sign 2
(integer) 0
127.0.0.1:6379> bitcount sign //统计为1的所有状态 bitcount 键
(integer) 4

事务

Redis事务本质:一组命令的集合!一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行!

一次性(在一个队列中一次性执行)、顺序性(在队列中按照顺序执行)、排他性(事务在执行时不会被干扰)!执行一系列的命令

Redis事务没有隔离级别的概念

所有命令在事务中,并没有被执行!只有发起执行命令的时候才会执行! Exec(执行)

Redis单条命令保存原子性,但是事务不保证原子性

redis事务:

  • 开启事务(multi)
  • 命令入队()
  • 执行事务(exec)
1
2
3
4
5
6
7
8
9
10
11
12
127.0.0.1:6379> multi  //开启事务
OK
127.0.0.1:6379> set k1 v1 //命令入队
QUEUED
127.0.0.1:6379> set k2 v2 //命令入队
QUEUED
127.0.0.1:6379> get k2 //命令入队
QUEUED
127.0.0.1:6379> exec //执行事务
1) OK
2) OK
3) "v2"

事务执行完后就没了,如果还要用事务,就需要重新开启。

取消事务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
127.0.0.1:6379> multi  //开启事务
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> discard //取消事务
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI //报错(没有要执行的事务)
127.0.0.1:6379> get k3
(nil)

编译时异常(代码有问题!命令有错),在事务中所有的命令都不会被执行!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 //错误的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec //执行事务报错
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 //所有的命令都不会被执行
(nil)

运行时异常(比如1/0),如果事务队列中存在语法 错误,那么执行命令的时候,其他命令可以正常的执行,错误命令抛出异常!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> incr k1 //字符型不能自增(编译时不报错,执行时报错,不影响后面代码的执行)
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range //报错,但不影响后面代码的执行
3) OK
4) OK
5) "v1"
6) "v2"

监控 (Watch) 可以实现乐观锁

悲观锁:

  • ​ 很悲观,认为什么时候都会出问题,无论做什么都会加锁(效率低)

乐观锁:

  • 很乐观,认为什么时候都不会出问题,所以不会上锁!

  • 获取version(数据版本号 :表示数据被修改的次数)

  • 更新的时候比较version(在提交更新时,若刚才读取到的 version 值与当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功)

单线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money //监视 money 对象
OK
127.0.0.1:6379> multi //事务正常结束,数据期间没有发生变动,这个时候就正常执行成功!
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20

测试多线程修改值,使用watch可以当作redis的乐观锁操作!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//线程1
127.0.0.1:6379> watch money //监视 money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec //事务执行之前,线程2修改了money的值,这个时候,就会导致事务执行失败!
(nil)



//线程2
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK



//如果事务执行失败
127.0.0.1:6379> unwatch //如果发现事务执行失败,就先解锁
OK
127.0.0.1:6379> watch money //获取最新的值,再次监控,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec //比对监视的是否发生了变化,如果没有变化,那么可以执行成功,如果变了就执行失败!
1) (integer) 990
2) (integer) 30

Jedis

Jedis是Redis官方推荐的Java连接开发工具!