introduction
We know that in order to solve the problem of resource sharing without high concurrency in the same process, we can solve it through high concurrency programming, and realize the visibility of variables between threads by adding volatile keyword to variables; Modify code blocks, objects, or methods with the synchronized keyword and call Java util. The API under the current package explicitly locks and releases locks to achieve synchronization in multi-threaded scenarios.
However, when multiple servers are deployed, the high concurrency access to control multiple threads under different JVM processes will fail. Whether by adding the volatile keyword to a variable, or by adding the synchronized keyword to an object lock in a code block that controls concurrent access, or by calling Java util. The API under the current package explicitly locking and releasing locks can not solve the problem of multi-threaded concurrent access synchronization in different JVM processes in distributed scenarios. Typically, such as second kill, order placement and inventory reduction in e-commerce scenarios, order service and inventory service belong to different micro services, and each micro service will have multiple instances.
At this time, it is necessary to introduce a distributed transaction lock scheme to solve the problem. There are three common implementation methods of distributed transaction lock: redis, zookeeper and database version lock (also known as optimistic lock). Among them, redis is the simplest and most efficient way to implement distributed transaction locks. Redis implements distributed transaction locks mainly through its setnx command and executing Lua script to implement atomic operations. In addition, redis client also provides higher-level usage of redission to implement distributed transaction locks. However, the underlying implementation of distributed transaction locks by reission is also based on the execution of lua scripts.
In order to control the length of the article and make the standard have dry content worthy of careful reading by readers, this article only involves the implementation of the spring boot microservice project by executing setnx command and lua script through redis client. In the other two ways, when I have time, I will write an article to explain it through actual combat.
1. Principle of redis implementing distributed transaction lock
Redis can implement distributed transaction locks because it is a global database and a NO-SQL database in the form of key value. When multiple threads in different jvm processes execute the same piece of code, they can implement global lock adding and lock releasing operations. The setnx command is used to determine whether the key exists in the redis cache. If it does not, the set succeeds. If the set succeeds, it means that the distributed lock is obtained and the subsequent logic to control concurrent access can be carried out. In order to prevent the deadlock caused by the downtime of the locked machine, redis can set the expiration time for the cache key to solve the problem; Executing lua script is an atomic operation, and only one client can execute it at a time, which is very necessary to ensure the atomicity and consistency of transactions in distributed high concurrency scenarios. Therefore, implementing distributed transaction locking by executing lua script has become a very good solution.
2. Build a micro service project integrating Redis
Spring Redis requires Redis version 2.6 or above. Spring Data integrates with Redis through two Java open source class libraries, jedis and lattice. No matter which client you use, you need to use org.org in the Spring Data redisjar package springframework. data. Redis. The two abstract interfaces RedisConnection and RedisConnectionFactory under the connection package are used to obtain the working connection for Redis interaction. Jedis and lattice class libraries provide implementation classes of RedisConnectionFactory interface, letticconnectionfactory and JedisConnectionFactory.
Spring boot starter data redis startup relies on the Lettuce client by default. However, many people are used to using the jedis client to operate redis, because using the jedis client to operate the redis command is closer to the native redis command usage.
2.1 introduction to redis automatic configuration
The redis auto configuration class in the spring boot project is located at org springframework. boot. autoconfigure. data. RedisAutoConfiguration class under redis package. The source code of this autoconfiguration class is as follows:
@Configuration( proxyBeanMethods = false ) @ConditionalOnClass({RedisOperations.class}) @EnableConfigurationProperties({RedisProperties.class}) @Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class}) public class RedisAutoConfiguration { public RedisAutoConfiguration() { } @Bean @ConditionalOnMissingBean( name = {"redisTemplate"} ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; } }
It instantiates the redis connection object according to the configuration information in the RedisProperties property property configuration class, and automatically imports two configuration classes: lettueconnectionconfiguration and JedisConnectionConfiguration. At the same time, when two beans are missing in the project, instantiate and inject RedisTemplate and StringRedisTemplate beans into the Spring IOC container.
2.1 framework construction of micro service project
In my last article on microservice practices Remember the detailed process of stepping on and filling the pit of building a micro service registry and client using Nacos version 2.0.3 Based on the project, Alibaba demos, a micro service aggregation project, is built. Add three sub module projects: Alibaba commons, Alibaba service provider and Alibaba service consumer.
The Alibaba service provider project simulates e-commerce inventory service, and the Alibaba service consumer project simulates e-commerce order service. Both microservices provide web services.
2.2 project maven dependency
- Alibaba demos project POM xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.spring.cloud</groupId> <artifactId>alibaba-demos</artifactId> <version>1.0-SNAPSHOT</version> <modules> <module>alibaba-commons</module> <module>alibaba-service-provider</module> <module>alibaba-service-consumer</module> </modules> <name>alibaba-demos</name> <description>spring cloud alibaba demos</description> <packaging>pom</packaging> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.2.7.RELEASE</spring-boot.version> <spring-cloud.version>Hoxton.RELEASE</spring-cloud.version> <spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version> </properties> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.7.RELEASE</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-test</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> <version>2.0.3</version> </dependency> <dependency> <groupId>com.alibaba.spring</groupId> <artifactId>spring-context-support</artifactId> <version>1.0.2</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> <version>2.2.0.RELEASE</version> <exclusions> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-consul-discovery</artifactId> </exclusion> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> </exclusion> <exclusion> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> <version>0.9.10</version> </dependency> <dependency> <groupId>io.prometheus</groupId> <artifactId>simpleclient</artifactId> <version>0.0.9</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.20</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.16.18</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <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>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> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
- Alibaba commons module project POM xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>alibaba-demos</artifactId> <groupId>com.spring.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>alibaba-commons</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.4.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.5</version> </dependency> </dependencies> </project>
- Alibaba service provider module project POM xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>alibaba-demos</artifactId> <groupId>com.spring.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>alibaba-service-provider</artifactId> <dependencies> <dependency> <groupId>com.spring.cloud</groupId> <artifactId>alibaba-commons</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.spring</groupId> <artifactId>spring-context-support</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </exclusion> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </exclusion> <exclusion> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> </exclusion> <exclusion> <groupId>io.prometheus</groupId> <artifactId>simpleclient</artifactId> </exclusion> <exclusion> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </exclusion> <exclusion> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.3.2.RELEASE</version> <configuration> <mainClass></mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
- Alibaba service consumer module project POM xml
<?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.spring.cloud</groupId> <artifactId>alibaba-consumer</artifactId> <version>1.0.0-SNAPSHOT</version> <name>alibaba-consumer</name> <description>Demo project for Spring Boot</description> <parent> <artifactId>alibaba-demos</artifactId> <groupId>com.spring.cloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </exclusion> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> </exclusion> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-keyvalue</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>com.spring.cloud</groupId> <artifactId>alibaba-commons</artifactId> <version>1.0-SNAPSHOT</version> <exclusions> <exclusion> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.spring</groupId> <artifactId>spring-context-support</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.nacos</groupId> <artifactId>nacos-client</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </exclusion> <exclusion> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </exclusion> <exclusion> <groupId>org.reflections</groupId> <artifactId>reflections</artifactId> </exclusion> <exclusion> <groupId>io.prometheus</groupId> <artifactId>simpleclient</artifactId> </exclusion> <exclusion> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> </exclusion> <exclusion> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </exclusion> <exclusion> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </exclusion> <exclusion> <groupId>org.xmlunit</groupId> <artifactId>xmlunit-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-ribbon</artifactId> <version>${spring-cloud-alibaba.version}</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.xmlunit</groupId> <artifactId>xmlunit-core</artifactId> <version>2.6.2</version> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.0.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <version>2.2.7.RELEASE</version> <configuration> <mainClass>com.spring.cloud.alibabaconsumer.AlibabaConsumerApplication</mainClass> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
2.3 project configuration
1 Alibaba service provider project application properties
server.port=9000 server.servlet.context-path=/services spring.profiles.active=dev spring.jackson.time-zone=GMT+8 spring.devtools.add-properties=false mybatis-plus.mapper-locations=classpath:com/spring/cloud/alibaba/service/provider/mapper/*Mapper.xml mybatis-plus.configuration.map-underscore-to-camel-case=true mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
2 Alibaba service provider project application-dev.properties
spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=heshengfu2018 logging.level.root.com.apache.ibatis=trace logging.level.root.java.sql.Connection=debug logging.level.java.sql.Statement=info logging.level.java.sql.PreparedStatement=info
3 Alibaba service provider project bootstrap Properties to register the inventory service with the registry
spring.cloud.nacos.discovery.username=nacos spring.cloud.nacos.discovery.password=nacos spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 spring.cloud.nacos.discovery.namespace=public spring.cloud.nacos.config.server-addr=127.0.0.1:8848 spring.application.name=stock-service
4 Alibaba service consumer project application Properties file
# Application service WEB access port server.port=9002 server.servlet.context-path=/order-service spring.devtools.add-properties=false spring.profiles.active=dev spring.jackson.time-zone=GMT+8 mybatis-plus.mapper-locations=classpath:com/spring/cloud/alibabaconsumer/mapper/*Mapper.xml mybatis-plus.configuration.map-underscore-to-camel-case=true mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl # redis configuration spring.redis.client-name=redis-client spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.database=0 spring.redis.jedis.pool.max-active=8 spring.redis.jedis.pool.max-wait=5000ms spring.redis.jedis.pool.min-idle=1 spring.redis.jedis.pool.time-between-eviction-runs=30000ms #Microservice url stock.service.query-stock-url=http://stock-service/services/stock/findStockByCode stock.service.update-count-url=http://stock-service/services/stock/updateStockCountById
5 Alibaba service consumer project application-dev.properties
spring.datasource.type=com.zaxxer.hikari.HikariDataSource spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/vueblog2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai spring.datasource.username=vueblog spring.datasource.password=vueblog2021# logging.level.root.com.apache.ibatis=trace logging.level.root.java.sql.Connection=debug logging.level.java.sql.Statement=info logging.level.java.sql.PreparedStatement=info
For the relationship between inventory service and order service and the process of creating orders, the author draws a simple flow chart as shown below

2.4 database table creation and entity class creation
- Create a new inventory table and add data
Open the navicat client, create a new connection, use the root account and login password to connect to the local MySQL service test database, and then execute the following sql script in the console
DROP TABLE IF EXISTS `stock_info`; CREATE TABLE `stock_info` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `good_code` varchar(30) NOT NULL COMMENT 'Commodity code', `good_name` varchar(100) DEFAULT NULL COMMENT 'Trade name', `count` int(11) DEFAULT '0' COMMENT 'Quantity of goods', `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', `created_by` varchar(30) NOT NULL DEFAULT 'system' COMMENT 'Creator', `last_updated_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last update time', `last_updated_by` varchar(30) NOT NULL DEFAULT 'system' COMMENT 'Last updated by', `unit_price` int(11) DEFAULT '0' COMMENT 'Unit price', PRIMARY KEY (`id`), UNIQUE KEY `uk_good_code` (`good_code`) ) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8mb4; -- ---------------------------- -- Records of stock_info -- ---------------------------- INSERT INTO `stock_info` VALUES ('1', 'huawei_mate3', 'Huawei mobile phone mate3', '1000', '2021-11-08 23:42:02', 'heshengfu', '2021-11-21 21:11:08', 'heshengfu', '200000'); INSERT INTO `stock_info` VALUES ('2', 'huawei_mate5', 'Huawei mobile phone mate5', '1000', '2021-11-08 23:42:02', 'heshengfu', '2021-11-21 21:11:08', 'heshengfu', '300000'); INSERT INTO `stock_info` VALUES ('3', 'iphone_plus8', 'iPhone plus8', '1000', '2021-11-08 23:42:02', 'heshengfu', '2021-11-21 21:11:08', 'heshengfu', '500000'); INSERT INTO `stock_info` VALUES ('4', 'iphone_11', 'Apple Mobile 11', '860', '2021-11-08 23:42:02', 'heshengfu', '2022-01-03 14:26:58', 'system', '650000'); INSERT INTO `stock_info` VALUES ('5', 'iphone_12', 'Apple Mobile 12', '1000', '2021-11-08 23:42:02', 'heshengfu', '2021-11-21 21:11:08', 'heshengfu', '700000'); INSERT INTO `stock_info` VALUES ('6', 'iphone_13', 'Apple Mobile 13', '1000', '2021-11-08 23:42:02', 'heshengfu', '2021-11-21 21:11:08', 'heshengfu', '800000'); INSERT INTO `stock_info` VALUES ('7', 'xiaomi_note3', 'Mi phones note3', '500', '2021-11-28 20:21:23', 'system', '2021-11-28 20:21:23', 'system', '200000'); INSERT INTO `stock_info` VALUES ('8', 'xiaomi_note4', 'Mi phones note4', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '280000'); INSERT INTO `stock_info` VALUES ('9', 'xioami_note5', 'Mi phones note5', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '300000'); INSERT INTO `stock_info` VALUES ('10', 'xiaomi_note6', 'Mi phones note6', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '330000'); INSERT INTO `stock_info` VALUES ('11', 'xiaomi_note7', 'Mi phones note7', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '350000'); INSERT INTO `stock_info` VALUES ('12', 'xiaomi_note8', 'Mi phones note8', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '380000'); INSERT INTO `stock_info` VALUES ('13', 'honor50', 'Glory 50', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '219900'); INSERT INTO `stock_info` VALUES ('14', 'honor50_SE', 'Glory 50 SE', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '219900'); INSERT INTO `stock_info` VALUES ('15', 'honor50Pro', 'Glory 50 Pro', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '349900'); INSERT INTO `stock_info` VALUES ('16', 'honorX10', 'glory X10', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '179900'); INSERT INTO `stock_info` VALUES ('17', 'honorX30_Max', 'glory X30_Max', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '239900'); INSERT INTO `stock_info` VALUES ('18', 'honorX30_Magic3_drag888', 'glory X30_Magic3_Xiaolong 888', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '469900'); INSERT INTO `stock_info` VALUES ('19', 'honorX30_Magic3_Pro', 'glory X30_Magic3_Pro', '500', '2021-11-28 20:42:19', 'system', '2021-11-28 20:42:19', 'system', '469900'); INSERT INTO `stock_info` VALUES ('20', 'meizu', 'Meizu mobile phone', '500', '2021-11-30 02:05:15', 'system', '2021-11-30 02:05:15', 'system', '200000'); INSERT INTO `stock_info` VALUES ('21', 'meizu3', 'Meizu mobile phone', '500', '2021-11-30 02:07:46', 'system', '2021-11-30 02:07:46', 'system', '200000'); INSERT INTO `stock_info` VALUES ('22', 'GalaxyNote20', 'Samsung Noto20', '500', '2021-12-04 16:22:32', 'system', '2021-12-04 16:22:32', 'system', '589900'); INSERT INTO `stock_info` VALUES ('23', 'GalaxyNote3', 'Samsung Note3', '500', '2021-12-04 16:36:50', 'system', '2021-12-04 16:36:50', 'system', '280000'); INSERT INTO `stock_info` VALUES ('24', 'GalaxyNote4', 'Samsung Note4', '500', '2021-12-04 16:36:50', 'system', '2021-12-04 16:36:50', 'system', '300000'); INSERT INTO `stock_info` VALUES ('25', 'GalaxyNote5', 'Samsung Note4', '500', '2021-12-04 16:36:50', 'system', '2021-12-04 16:36:50', 'system', '330000'); INSERT INTO `stock_info` VALUES ('26', 'GalaxyNote6', 'Samsung Note6', '500', '2021-12-04 16:36:50', 'system', '2021-12-04 16:36:50', 'system', '350000'); INSERT INTO `stock_info` VALUES ('27', 'GalaxyNote7', 'Samsung Note7', '500', '2021-12-04 16:36:50', 'system', '2021-12-04 16:36:50', 'system', '380000');
- Similarly, open the navicat client to create a new connection, use the vueblog user and login password to connect to the MySQL service vueblog2 database, and then execute the creation script of the order table
DROP TABLE IF EXISTS `orders`; CREATE TABLE `orders` ( `order_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary key', `user_id` bigint(20) NOT NULL, `order_no` varchar(50) NOT NULL COMMENT 'Order No', `good_code` varchar(30) NOT NULL COMMENT 'Commodity code', `good_count` int(11) NOT NULL DEFAULT '1' COMMENT 'Order quantity', `order_money` bigint(20) NOT NULL DEFAULT '0' COMMENT 'Order amount, unit: Min', `created_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time', `created_by` varchar(30) NOT NULL DEFAULT 'system' COMMENT 'Creator', `last_updated_date` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Last modified', `last_updated_by` varchar(30) DEFAULT 'system' COMMENT 'Last modified by', PRIMARY KEY (`order_id`) ) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8mb4;
- Com.com under Alibaba commons module spring. cloud. alibaba. commons. Create new entity classes for the above two databases under the POJO package
StockInfo.java
@Data @TableName("stock_info") public class StockInfo extends BaseEntity { /** * Primary key ID */ @TableId(type = IdType.AUTO) private Long id; /** * Commodity code */ @TableField(value = "good_code") private String goodCode; /** * Trade name */ @TableField(value = "good_name") private String goodName; /** * Inventory quantity */ @TableField(value = "count") private Integer count; /** * Unit price of goods, unit: minute */ @TableField(value = "unit_price") private Long unitPrice; }
OrderInfo.java
@Data @TableName("orders") public class OrderInfo extends BaseEntity { @TableId(type=IdType.AUTO) private Long orderId; @TableField(value="user_id") private Long userId; @TableField(value = "order_no") private String orderNo; @TableField(value = "good_code") private String goodCode; @TableField(value = "good_count") private int goodCount; @TableField(value = "order_money") private Long orderMoney; }
BaseEntity.java
@Data public class BaseEntity implements Serializable { /** * Creator */ @TableField(value = "created_by", fill = FieldFill.INSERT) private String createdBy; /** * Creation date (with time) */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(value = "created_date", fill = FieldFill.INSERT) private Date createdDate; /** * Modified by user ID */ @TableField(value = "last_updated_by", fill = FieldFill.INSERT_UPDATE) private String lastUpdatedBy; /** * Modification date (with time) */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @TableField(value = "last_updated_date", fill = FieldFill.INSERT_UPDATE) private Date lastUpdatedDate; }
2.5 inventory microservice code
- Start class serviceproviderapplication java
@SpringBootApplication(scanBasePackages = {"com.spring.cloud.alibaba.commons", "com.spring.cloud.alibaba.service.provider"}) @MapperScan(basePackages = "com.spring.cloud.alibaba.service.provider.mapper") @EnableDiscoveryClient public class ServiceProviderApplication { public static void main(String[] args) { SpringApplication.run(ServiceProviderApplication.class, args); } }
@The EnableDiscoveryClient annotation is used to enable the automatic discovery of microservices
- MybatisPlus paging configuration class
@Configuration public class MybatisPlusConfig { @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setOverflow(true); paginationInterceptor.setDialectClazz("com.baomidou.mybatisplus.extension.plugins.pagination.dialects.MySqlDialect"); paginationInterceptor.setSqlParser(new JsqlParserCountOptimize()); return paginationInterceptor; } }
3) Mapper layer encoding
Here, we choose MybatisPlus as the persistence layer framework. By inheriting BaseMapper, we can directly obtain the basic database CRUD method.
@Repository public interface StockMapper extends BaseMapper<StockInfo> { }
- Service layer code
public interface IStockService extends IService<StockInfo> { /** * Find inventory by item code */ ResponseVo findStockByGoodCode(String goodCode); /** * Modify inventory */ ResponseVo updateStockById(StockInfo stockInfo); }
- Controller layer coding
@RestController @RequestMapping("/stock") @RefreshScope public class StockController { @Resource private IStockService stockService; /** * Find inventory by item code */ @GetMapping(value = "/findStockByCode") public ResponseVo findStockByGoodCode(@RequestParam("goodCode") String goodCode){ if(StringUtils.isEmpty(goodCode)) { throw new IllegalArgumentException("parameter goodCode cannot be null"); } return stockService.findStockByGoodCode(goodCode); } /** * Modify inventory */ @PostMapping("/updateStockCountById") public ResponseVo updateStockById(@RequestBody StockInfo stockInfo){ if(stockInfo.getId()==null || stockInfo.getId()<=0){ throw new IllegalArgumentException("parameter id cannot small than 0"); } if(stockInfo.getCount() < 0) { throw new IllegalArgumentException("parameter count cannot small than 0"); } return stockService.updateStockById(stockInfo); } }
2.6 order service code
1) Start class alibabaconsumerapplication java
@SpringBootApplication @EnableDiscoveryClient @MapperScan(basePackages = "com.spring.cloud.alibabaconsumer.mapper") public class AlibabaConsumerApplication { public static void main(String[] args) { SpringApplication.run(AlibabaConsumerApplication.class, args); } }
- Configuration class
The RestTemplateConfig class is used to construct the RestTemplate template tool class bean for remote service calls that implement the http or https protocol.
@Configuration public class RestTemplateConfig { @Bean @LoadBalanced public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){ return restTemplateBuilder.build(); } }
The TaskPoolConfig class is used to construct a user-defined thread pool. After a user places an order successfully, it will be displayed asynchronously
package com.spring.cloud.alibabaconsumer.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Configuration public class TaskPoolConfig { /** * Custom thread pool * @return ThreadPoolExecutor */ @Bean(name = "customTaskWorkPoolExecutor") public ThreadPoolExecutor customTaskWorkPoolExecutor() { ArrayBlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue(25); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 50, 30000, TimeUnit.MILLISECONDS, taskQueue); return threadPoolExecutor; } }
- Persistent layer coding
package com.spring.cloud.alibabaconsumer.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.spring.cloud.alibaba.commons.pojo.OrderInfo; import org.springframework.stereotype.Repository; @Repository public interface OrderMapper extends BaseMapper<OrderInfo> { }
- Service layer code
The service layer mainly implements the method of creating orders
public interface OrderService extends IService<OrderInfo> { ResponseVo createOrder(OrderInfo orderEntity, Integer flag); }
package com.spring.cloud.alibabaconsumer.service.impl; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.spring.cloud.alibaba.commons.pojo.OrderInfo; import com.spring.cloud.alibaba.commons.pojo.ResponseVo; import com.spring.cloud.alibaba.commons.pojo.StockInfo; import com.spring.cloud.alibabaconsumer.mapper.OrderMapper; import com.spring.cloud.alibabaconsumer.service.OrderService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.script.RedisScript; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import redis.clients.jedis.Jedis; import javax.annotation.Resource; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @Service public class OrderServiceImpl extends ServiceImpl<OrderMapper, OrderInfo> implements OrderService { private final static Logger logger = LoggerFactory.getLogger(OrderServiceImpl.class); private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); @Resource private RestTemplate restTemplate; @Resource private StringRedisTemplate stringRedisTemplate; @Resource private RedisConnectionFactory redisConnectionFactory; @Resource(name="customTaskWorkPoolExecutor") private ThreadPoolExecutor threadPoolExecutor; @Value("${stock.service.query-stock-url}") private String queryGoodStockServiceUrl; @Value("${stock.service.update-count-url}") private String updateStockCountUrl; /** * The mode of releasing the lock is controlled by the flag parameter * @param orderEntity Order entity class * @param flag Lock release method ID: 1-RedisTemplate#del(key) to release the lock; 2-Jedis#eval method executes lua script to release lock; 3-RedisTemplate#execute method executes lua script to release lock */ @Override public ResponseVo createOrder(OrderInfo orderEntity, Integer flag) { ResponseVo responseVo; if (flag == 1 || flag == 2) { responseVo = setNxLock(orderEntity, flag); } else { responseVo = redisTemplateLock(orderEntity); } return responseVo; } private void completeOrderInfo(OrderInfo orderInfo) { if (orderInfo.getUserId() == null) { orderInfo.setUserId(1L); } String orderNo = sdf.format(new Date(System.currentTimeMillis())); logger.info("orderNo={}", orderNo); orderInfo.setOrderNo(orderNo); Date now = new Date(System.currentTimeMillis()); orderInfo.setCreatedBy("system"); orderInfo.setCreatedDate(now); orderInfo.setLastUpdatedBy("system"); orderInfo.setLastUpdatedDate(now); } private ResponseVo setNxLock(OrderInfo orderEntity, Integer flag) { String goodCode = orderEntity.getGoodCode(); Jedis jedis = (Jedis) redisConnectionFactory.getConnection().getNativeConnection(); // Add distributed lock when checking inventory String lockKey = "lock_" + goodCode; long currentTime = System.currentTimeMillis(); Long lockResult = jedis.setnx(lockKey, String.valueOf(currentTime)); if (lockResult == 1) { // Set the lock failure time to 5s try { jedis.expire(lockKey, 5); logger.info("get distribute lock success, lockKey={}", lockKey); return queryStockAndInsertOrder(orderEntity); } catch (Exception e) { logger.error("", e); return ResponseVo.error(e.getMessage()); } finally { delLockByExecuteJedisCommand(jedis, lockKey, currentTime, flag); } } else { logger.warn("get redis lock failed, stop to order"); return ResponseVo.error("Please place an order later. Other customers are placing an order for the same product"); } } /** * Query inventory and save order * @param orderEntity * @return */ private ResponseVo queryStockAndInsertOrder(OrderInfo orderEntity) { String goodCode = orderEntity.getGoodCode(); String requestUrl = queryGoodStockServiceUrl + "?goodCode={goodCode}"; Map<String, Object> paramMap = new HashMap<>(1); paramMap.put("goodCode", goodCode); // Call remote inventory service through RestTemplate JSONObject jsonResponse = restTemplate.getForObject(requestUrl, JSONObject.class, paramMap); logger.info("queryResponse={}", JSONUtil.toJsonStr(jsonResponse)); if (jsonResponse == null) { return ResponseVo.error("Remote call to inventory service failed"); } int status = jsonResponse.getInt("status"); if (status != 200) { return ResponseVo.error(status, jsonResponse.getStr("message")); } StockInfo stockInfo = jsonResponse.get("data", StockInfo.class); if (stockInfo.getCount() <= orderEntity.getGoodCount()) { return ResponseVo.error("Insufficient inventory of goods"); } completeOrderInfo(orderEntity); int insertCount = this.baseMapper.insert(orderEntity); logger.info("insertCount={}", insertCount); // Asynchronous inventory reduction asyncDecreaseStock(stockInfo, orderEntity.getGoodCount()); return ResponseVo.success(orderEntity); } private ResponseVo redisTemplateLock(OrderInfo orderEntity) { String goodCode = orderEntity.getGoodCode(); String lockKey = "lock_" + goodCode; Long value = System.currentTimeMillis(); // ValueOperation#setIfAbsent(key, value) is equivalent to jedis Setnx (key, value) method can be used to add cache when there is no key value in redis Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, String.valueOf(value)); if (flag) { // Lock successfully. Perform inventory query try { stringRedisTemplate.expire(lockKey, 5, TimeUnit.SECONDS); logger.info("get distribute lock success, lockKey={}", lockKey); return queryStockAndInsertOrder(orderEntity); } catch (Exception e) { logger.error("", e); return ResponseVo.error(e.getMessage()); } finally { // Lua script, pay attention to the syntax of lua script language. Lua Xiaobai readers can jump here to learn: https://www.runoob.com/lua/lua-tutorial.html String script = "local value = redis.call('GET', KEYS[1])\n" + "if value == ARGV[1] then \n" + " redis.call('DEL', KEYS[1])" + "return 1 \n" + "end " + "return 0 \n" ; // Construct RedisScript instance RedisScript<Long> redisScript = RedisScript.of(script, Long.class); List<String> keys = new ArrayList<>(1); keys.add(lockKey); Long count = stringRedisTemplate.execute(redisScript, keys, String.valueOf(value)); if (count == 1) { logger.info("release redis lock success"); } else { logger.warn("release redis lock failed"); } } } else { logger.warn("get redis lock failed, stop to order"); return ResponseVo.error("Please place an order later. Other customers are placing an order for the same product"); } } private void delLockByExecuteJedisCommand(Jedis jedis, String lockKey, Long currentTime, Integer flag) { if (flag==1) { String value = jedis.get(lockKey); if (value !=null && Long.parseLong(value) == currentTime) { jedis.del(lockKey); logger.info("release redis lock, lockKey={}",lockKey); } } else if (flag == 2) { delLockByJedisExecuteLuaScript(jedis, lockKey, currentTime); } } private void delLockByJedisExecuteLuaScript(Jedis jedis, String lockKey, Long currentTime) { String script = "local value = redis.call('GET', KEYS[1])\n" + "if value == ARGV[1] then \n" + " redis.call('DEL', KEYS[1])" + "return 1 \n" + "end " + "return 0 \n" ; List<String> keys = new ArrayList<>(1); keys.add(lockKey); List<String> args = new ArrayList<>(1); args.add(String.valueOf(currentTime)); // Note that the return type here must use Long. If Integer is used, an error will be reported Long count = (Long) jedis.eval(script, keys, args); if (count == 1) { logger.info("release redis lock success"); } else { logger.warn("release redis lock failed"); } } /** * Asynchronous inventory reduction in order to simplify the steps, the thread pool is used to simulate inventory reduction. The real e-commerce environment will use RabbitMq or RocketMq message queue to realize the logic of inventory reduction * @param stockInfo * @param orderCount */ private void asyncDecreaseStock(StockInfo stockInfo, int orderCount) { threadPoolExecutor.execute(() -> { // Inventory reduction int remainCount = stockInfo.getCount() - orderCount; stockInfo.setCount(remainCount); stockInfo.setLastUpdatedBy("system"); stockInfo.setLastUpdatedDate(new Date(System.currentTimeMillis())); ResponseVo updateResponse = restTemplate.postForObject(updateStockCountUrl, stockInfo, ResponseVo.class); logger.info("updateResponse={}", JSONUtil.toJsonStr(updateResponse)); if (updateResponse.getStatus() == 200) { logger.info("update stock count success"); } else { logger.warn("update stock count failed, stockInfo={}, remainCount={}", stockInfo, remainCount); } }); } }
In order to prevent a client from releasing a lock held by another client, it is necessary to verify whether the lock to be deleted is a lock added by itself before releasing the lock, which is also called signature verification. The redis client directly executes get(key) to determine whether the value value is equal to the expected value, and then deletes the key to release the lock. This method cannot guarantee the atomicity of the operation. Because there is a sudden service outage after redis signature verification and before deleting the key, the way redis executes lua atomic script just ensures the atomicity of the operation.
There are two ways to execute lua script through redis client: one is through Jedis#eval method, and the other is through RedisTemplate#execute method. By tracing the method execution chain, we will find that their bottom layers actually execute the eval command line lua script through redisconnection.
5) Controller layer coding
The controller layer pays attention to the receiving of the interface parameters for creating orders and the calling of the service layer
package com.spring.cloud.alibabaconsumer.controller; import com.spring.cloud.alibaba.commons.pojo.OrderInfo; import com.spring.cloud.alibaba.commons.pojo.ResponseVo; import com.spring.cloud.alibabaconsumer.service.OrderService; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; @RestController @RequestMapping("/order") public class OrderController { @Resource private OrderService orderService; @PostMapping("/create") public ResponseVo createOrder(@RequestBody OrderInfo orderEntity, @RequestParam("flag") Integer flag) { return orderService.createOrder(orderEntity, flag); } }
3 Effect experience
After coding, it's time to run the project to see the effect!
3.1 service startup
Start Mysql and Redis services locally, and start Nacos service in stand-alone mode on Linux server
Mysql and Redis services on this machine can be found through "my computer" - > right click "management" - > "services and Applications" - > "services". Follow the local Mysql and Redis services, and then click "start" in the upper left corner to complete the startup of Mysql and Redis services.
For the startup of the Nacos service installed on the Linux server, you can connect to the Linux server through the remote ssh client and enter the Nacos bin to execute the stand-alone mode startup command (the Nacos cluster mode cannot be started by using different ports instead of different instances on my 1-core 2G server, so you have to use the stand-alone mode)
ssh startup.sh -m standalaone
If you start the nacos service on your own windows system computer, enter cmd in the bin directory of nacos through the dos command, and then enter the following command in the open console
startup.cmd -m standalone
Then start Alibaba service provider and Alibaba service consumer in IDEA
After the two microservices are started successfully, we enter the following URL in the browser to enter the Nacos UI interface. You can see that both stock service and order service are registered in the Nacos registry

3.2 create order interface test
After the two micro service starts successfully, the order interface is called in the postman (the different locking and releasing redis lock modes can be seen by modifying the flag parameter value).
POST http://localhost:9002/order-service/order/create?flag=2 { "userId": 1, "goodCode": "iphone_11", "goodCount": 10, "orderMoney": 6500000 }
Click Send to see the interface response information as follows:
{ "uuid": "c6f638f1-a1d8-4b98-9be7-2508a27f0a3b", "status": 200, "message": "OK", "data": { "createdBy": "system", "createdDate": "2022-01-03 23:20:43", "lastUpdatedBy": "system", "lastUpdatedDate": "2022-01-03 23:20:43", "orderId": 12, "userId": 1, "orderNo": "20220103232043.752", "goodCode": "iphone_11", "goodCount": 10, "orderMoney": 6500000 } }
The following log information can be seen on the console of Alibaba service consumer service:
2022-01-03 23:20:43.679 INFO 3884 --- [nio-9002-exec-5] c.s.c.a.service.impl.OrderServiceImpl : get distribute lock success, lockKey=lock_iphone_11 2022-01-03 23:20:43.749 INFO 3884 --- [nio-9002-exec-5] c.s.c.a.service.impl.OrderServiceImpl : queryResponse={"data":{"unitPrice":650000,"lastUpdatedBy":"system","count":860,"lastUpdatedDate":"2022-01-03 14:26:58","createdDate":"2021-11-08 23:42:02","goodName":"Apple Mobile 11","createdBy":"heshengfu","id":4,"goodCode":"iphone_11"},"message":"OK","uuid":"3c3c4016-ec9f-40c0-928f-c4375c52ea14","status":200} 2022-01-03 23:20:43.753 INFO 3884 --- [nio-9002-exec-5] c.s.c.a.service.impl.OrderServiceImpl : orderNo=20220103232043.752 SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12906d4] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@21355453 wrapping com.mysql.cj.jdbc.ConnectionImpl@1f928ab] will not be managed by Spring ==> Preparing: INSERT INTO orders ( user_id, order_no, good_code, good_count, order_money, created_by, created_date, last_updated_by, last_updated_date ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? ) ==> Parameters: 1(Long), 20220103232043.752(String), iphone_11(String), 10(Integer), 6500000(Long), system(String), 2022-01-03 23:20:43.753(Timestamp), system(String), 2022-01-03 23:20:43.753(Timestamp) <== Updates: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@12906d4] 2022-01-03 23:20:43.771 INFO 3884 --- [nio-9002-exec-5] c.s.c.a.service.impl.OrderServiceImpl : insertCount=1 2022-01-03 23:20:43.794 INFO 3884 --- [pool-4-thread-2] c.s.c.a.service.impl.OrderServiceImpl : updateResponse={"data":1,"message":"OK","uuid":"288b8ca8-a9f2-4dec-9f04-3fb1de1adb6b","status":200} 2022-01-03 23:20:43.794 INFO 3884 --- [pool-4-thread-2] c.s.c.a.service.impl.OrderServiceImpl : update stock count success 2022-01-03 23:20:43.942 INFO 3884 --- [nio-9002-exec-5] c.s.c.a.service.impl.OrderServiceImpl : release redis lock success
In the above logs, we can clearly see the sql execution logs of obtaining and releasing redis locks and inserting data into the order table.
In the Alibaba service consumer service console, you can see the log information of inventory query and inventory reduction
2022-01-03 23:20:43.695 INFO 19532 --- [nio-9000-exec-4] c.s.c.a.s.p.service.impl.StockService : goodCode=iphone_11 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a91626] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@7270531 wrapping com.mysql.cj.jdbc.ConnectionImpl@517d56] will not be managed by Spring ==> Preparing: SELECT id,good_code,good_name,count,unit_price,created_by,created_date,last_updated_by,last_updated_date FROM stock_info WHERE good_code = ? ==> Parameters: iphone_11(String) <== Columns: id, good_code, good_name, count, unit_price, created_by, created_date, last_updated_by, last_updated_date <== Row: 4, iphone_11, Apple Mobile 11, 860, 650000, heshengfu, 2021-11-08 23:42:02, system, 2022-01-03 14:26:58 <== Total: 1 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@a91626] 2022-01-03 23:20:43.779 INFO 19532 --- [nio-9000-exec-5] c.s.c.a.s.p.service.impl.StockService : id=4, count=850 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@909c37] was not registered for synchronization because synchronization is not active JDBC Connection [HikariProxyConnection@2391458 wrapping com.mysql.cj.jdbc.ConnectionImpl@517d56] will not be managed by Spring ==> Preparing: UPDATE stock_info SET good_code=?, good_name=?, count=?, unit_price=?, created_by=?, created_date=?, last_updated_by=?, last_updated_date=? WHERE id=? ==> Parameters: iphone_11(String), Apple Mobile 11(String), 850(Integer), 650000(Long), heshengfu(String), 2021-11-08 23:42:02.0(Timestamp), system(String), 2022-01-03 23:20:43.0(Timestamp), 4(Long) <== Updates: 1
The inventory service console also prints out the details of inventory query and inventory reduction
Then we query stock in both databases_ Both info table and orders table can see the change of inventory data and the increase of order data


5 Summary
- Taking nacos as the registration center, this paper builds two micro services to simulate the inventory service and order service in e-commerce projects, and mainly demonstrates the implementation of distributed transaction lock using redis in distributed scenarios.
- Two common redis java oriented clients are lettuce and Jedis;
- Redis executes Lua script to ensure the atomicity of database collapse operation transactions. There are two main ways for redis to execute Lua script: jedis #eval (string script, list < string > keys, list < string > args) and redistemplate #execute (redisscript < T > script, list < k > keys, object... Args)
- Disadvantages: multiple Alibaba service provider service instances are not started, and Jemter pressure test tool is not used for high concurrency scenario testing. The next article will conduct supplementary testing for multiple instances and high concurrency scenarios
This project has been uploaded to gitee personal code warehouse. Interested friends can clone it for reference. Gitee code warehouse address https://gitee.com/heshengfu1211/alibaba-demos.git
---END--- It's not easy to be original. Welcome to see that all friends here can light up [Looking]. Thank you!