Spring Cloud Alibaba technology stack [in]

Keywords: Java ElasticSearch Spring Cloud search engine

Spring Cloud Alibaba Sentinel

1, Sentinel introduction

With the popularity of microservices, the stability between services and services becomes more and more important. Sentinel takes traffic as the starting point to protect the stability of services from multiple dimensions such as traffic control, fuse degradation and system load protection.

Sentinel has the following characteristics:

  • Rich application scenarios: Sentinel has undertaken the core scenarios of Alibaba's double 11 traffic promotion in recent 10 years, such as second kill (i.e. sudden traffic control within the range of system capacity), message peak cutting and valley filling, real-time fusing downstream unavailable applications, etc.
  • Complete real-time monitoring: Sentinel also provides real-time monitoring function. You can see the second level data of a single machine connected to the application in the console, and even the summary operation of clusters with a scale of less than 500.
  • Extensive open source Ecology: Sentinel provides out of the box integration modules with other open source frameworks / libraries, such as Spring Cloud, Dubbo and gRPC. You can quickly access Sentinel by introducing corresponding dependencies and simple configuration.
  • Perfect SPI extension points: Sentinel provides simple, easy-to-use and perfect SPI extension points. You can quickly customize logic by implementing extension points. For example, custom rule management, adapting data sources, etc.

2, Sentinel console installation

Sentinel provides a lightweight open source console, which provides machine discovery, health management, monitoring (single machine and cluster), rule management and push functions. This section details how to control the various behaviors of sentinel client through sentinel console. Sentinel console functions mainly include: flow control, degradation control, hotspot configuration, system rules and authorization rules.

2.1 download Sentinel

visit: https://github.com/alibaba/Sentinel/releases

Found: version 1.7.1:


Click sentinel-dashboard-1.7.1.jar to complete the download:

2.2 start sentinel dashboard

Copy the downloaded sentinel-dashboard-1.7.1.jar to the directory where the software is installed.

use:

java -jar sentinel-dashboard-1.7.1.jar

To start an instance of sentinel dashboard.
After successful startup:

We can access through the browser:
http://localhost:8080/

Where, user name: sentinel
Password: sentinel
More available startup parameter configurations:

java -D Parameter name=Parameter value -jar xx.jar

java -jar xx.jar --Parameter name=Parameter value



-Dsentinel.dashboard.auth.username=sentinel The login user name used to specify the console is sentinel;

-Dsentinel.dashboard.auth.password=123456 The login password used to specify the console is 123456. If these two parameters are omitted, the default user and password are both 123456 sentinel;

-Dserver.servlet.session.timeout=7200 Used to specify Spring Boot Server session The expiration time of, for example, 7200 indicates 7200 seconds; sixty m Indicates 60 minutes, and the default is 30 minutes;
 
-Dcsp.sentinel.dashboard.server=consoleIp:port Specify the console address and port

3, Build client


Just now we built sentinel dashboard. We also need to build a client to test various functions of sentinel.
We will build the Maven project structure as shown in the figure:

3.1 creating sub modules using IDEA


Select Maven project:

Click Next:

Parent: select spring cloud Alibaba examples
Name: named sentinel example client
Other items can keep the default value.
Click Finish to complete the creation.

3.2 adding dependencies

Modify the pom.xml file in sentinel example client:
Add the following:

<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-sentinel</artifactId>
	</dependency>
</dependencies>

There are 2 dependencies:

  • Spring cloud starter Alibaba sentinel this is an integration project of spring cloud and sentinel
  • Spring boot starter web is the most basic dependency for opening the web. Add the packaging plug of spring boot
<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

In this way, after our project is packaged, we can use Java jar to run it directly.

3.3 complete 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>spring-cloud-alibaba-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>sentinel-example-client</artifactId>
	<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-sentinel</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

3.4 add a profile


Named:

Modify the configuration file and add the following configuration:

server:
	port: 8085
spring:
	application:
		name: sentinel-client
	cloud:
		sentinel:
			transport:
				dashboard: localhost:8080
				port: 8719 #Sentinel client listens because sentinel dashboard will collect data from this port. If the port is occupied, + 1

Of which:

  • spring.cloud.sentinel.transport.dashboard specifies the ip and port address of sentinel console;
  • spring.cloud.sentinel.transport.port represents the communication port between sentinel client and console. The default is 8719. If this port has been occupied, sentinel will automatically scan + 1 from 8719 until an unoccupied port is found.

3.5 add a startup class


Name: com.bjsxt.SentinelClientApplication

Add the following code:

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

3.6 adding a Controller


Name: controller.TestController

Add the following interfaces to TestContrller:

@RestController
public class TestController {
	@GetMapping("/hello")
	public ResponseEntity<String> hello(){
		return ResponseEntity.ok("hello,sentinel") ;
	}
}

3.7 start up project


Access in browser:
http://localhost:8080/#/dashboard/home
appear:

Found no function.
At this point, let's access the hello interface we wrote:
http://localhost:8085/hello

Visit more times.
Visit again:
http://localhost:8080/#/dashboard/home

The console has been displayed normally.

In addition, we can see the request in the cluster link. We can flow control, downgrade, authorize and hotspot it

Configuration (the console is lazily loaded, and if there are no requests, the console will not have anything).

4, Flow control rules

Flow control rules.
In the cluster point link list, click the flow control button after / hello:

appear:

  • Resource Name: the unique name that identifies the resource. The default is the request path. You can also use @ SentinelResource configuration in the client;
  • For source: Sentinel can limit the flow for service callers, and fill in the micro service name, spring.application.name, which is default by default and does not distinguish between sources;
  • Threshold type, single machine threshold:
    • QPS (queries per second): limit the current when the QPS calling the api reaches the threshold;
    • Number of threads: limit the flow when the number of threads calling the api reaches the threshold.
    • Cluster: cluster is not selected by default;
  • Flow control mode:
    • Direct: when the api call reaches the current limit condition, the current is directly limited;
    • Association: limit the flow when the associated resource request reaches the threshold;
    • Link: only record the traffic on the specified link (the traffic of the specified resource coming in from the entrance resource. If the threshold is reached, limit the flow).
  • Flow control effect:
    • Quick failure: direct failure;
    • Warm Up: according to the value of codeFactor (cold loading factor, the default value is 3), the set QPS threshold can be reached only after preheating time from threshold / codeFactor;
    • Queuing: queue at a constant speed to let the request pass at a constant speed. The threshold type must be set to QPS, otherwise it is invalid.

4.1 QPS direct failure

Demonstrate the setting and effect of QPS direct failure. Click the flow control button behind the / hello request in the cluster point link list:

The effect of the above settings is to limit the current when the number of requests for / hello resources reaches more than 2 times in one second.
Click Add to finish setting the rule.

Now, in the browser, access:

http://localhost:8085/hello

When the hand speed is faster (more than twice a second), the page returns Blocked by Sentinel (flow limiting). And the response code is 429.

4.2 number of threads: direct failure

4.2.1 adding interfaces

Create a new interface in TestController.

/**
* Direct thread failure
* @return
* @throws InterruptedException
*/
@GetMapping("/thread")
public ResponseEntity<String> threadMode() throws InterruptedException {
	TimeUnit.SECONDS.sleep(1);
	return ResponseEntity.ok("hello,sentinel!") ;
}

Among them, we added:

TimeUnit.SECONDS.sleep(1);

Let the thread sleep for 1s, which makes it easier to trigger the rule.
Restart the project.
To access the thread interface we added:
http://localhost:8085/thread

It is found that the page needs to wait about 1s before responding, which is the effect of thread sleep.

4.2.2 add flow control rules



Click Add to complete the creation.

4.2.3 test the rule

Quick browser access:
http://localhost:8085/thread

4.3 Association

After accessing an interface and reaching certain flow control rules, start to restrict this interface.

4.3.1 add api interface in TestController

@GetMapping("/test1")
public ResponseEntity<String> test1(){
	return ResponseEntity.ok("hello,test1") ;
}
@GetMapping("/test2")
public ResponseEntity<String> test2(){
	return ResponseEntity.ok("hello,test2") ;
}

Restart the project and normally access test1 and test2 tests:

4.3.2 adding rules

We want test1 to be associated with test2, that is, after accessing the test2 interface to meet certain rules, start current limiting test1.

The above flow control rule indicates that when the number of accesses to / test2 within 1 second is greater than 2, the current limit is / test1.

4.3.3 test rules

We use to open two web pages and visit / test2 intensively, and then we manually request / test1 from the browser to see the effect.

Access test1:

It is found that current limiting has begun.

4.4 link

The call of the program can be regarded as a link. When a rule of a link is triggered, the link is current limited.

Starting from the API in the figure above, two links can be found, namely:

On the call of the two links, the current limiting rules complement each other.

4.4.1 add a Service


Name: (service.TestService)

The code is:

@Service
public class TestService {
	public String hello(){
		return "hello" ;
	}
}

4.4.2 adding interfaces

@Autowired
private TestService testService ;

@GetMapping("/link1")
public ResponseEntity<String> link1(){
	return ResponseEntity.ok(String.format("link1,call test,The result is%s",testService.hello())) ;
}

@GetMapping("/link2")
public ResponseEntity<String> lin2(){
	return ResponseEntity.ok(String.format("link2,call test,The result is%s",testService.hello())) ;
}

4.4.3 claim resources

We now turn the hello method in TestService into a resource:

be careful:
@Sentinel resource ("hello") identifies the method as a sentinel resource named hello.
Then restart the test:

4.4.4 adding link rules

Click cluster link:

At this point, we limit the resource flow to hello:


In the portal resource, we use link1. Click Add to complete the addition of rules.
The above configuration means that when accessing hello through / link1, if the QPS is greater than 2, the current is limited; The implication is that / link access to hello requests is not affected.

4.4.5 test the rule

Open link1 and link2 interfaces.

Quickly access link1 and then link2:

The test was accessed n times, and it was found that it was still unsuccessful. Uncomfortable!
Specific error:
https://github.com/alibaba/Sentinel/issues/1213

4.5 Warm Up

In addition to the direct failure of flow control effect, we can also choose to preheat Warm Up.

The default cold load factor coldFactor of sentinel client is 3, that is, the requested QPS starts from threshold / 3 and gradually rises to the set QPS threshold after preheating.
For example:
We set this rule for the hello resource.


The above configuration means that for / hello resources, the initial QPS threshold is 3. After 10 seconds, the QPS threshold reaches 10.
After adding:

Quick access / hello
http://localhost:8085/hello

see:

The process is similar to the following figure:

In the early stage, in the warm-up environment, the sudden high QPS will cause the system to directly refuse access. Slowly, a large number of new requests are introduced.


The fastest hand speed point is refreshed. You will often see the prompt of Blocked by Sentinel (flow limiting) at the beginning, and it will hardly appear after 10 seconds (because your hand speed is difficult to reach 10 times in 1 second).

4.6 queuing

Queuing does not reject requests, but strictly controls the interval between requests, that is, to let requests pass at a uniform speed.
Add interface in TestController:

private static Logger logger = LoggerFactory.getLogger(TestController.class) ;

@GetMapping("/queue")
public ResponseEntity<String> queue(){
	logger.info("Start processing request");
	return ResponseEntity.ok("ok") ;
}

Restart the project and access the queue interface:

Add a flow restriction rule to the queue:

Click Add to complete the creation.

Quickly access the queue interface and observe the background printing:
The meaning of the above configuration is that access / queue requests can only be accessed once per second at most. Requests exceeding can be queued and wait. If the waiting exceeds 2000 milliseconds, it will timeout. After adding this rule, you can quickly access localhost:8081/test1 for many times and sentinel client control
The bench log is printed as follows:

5, Degradation rule

In addition to flow control, fusing and degrading unstable resources in the call link is also one of the important measures to ensure high availability. Due to the complexity of the call relationship, if a resource in the call link is unstable, it will eventually lead to the accumulation of requests. Sentinel will restrict the call of a resource when it is in an unstable state in the call link (for example, the call timeout or the abnormal proportion increases), so as to make the request fail quickly and avoid affecting other resources and causing cascading errors. When a resource is downgraded, calls to the resource will be automatically fused within the next downgrade time window (the default behavior is to throw DegradeException).
When the access to the system fails for more than a certain number of times, fuse the interface.


We can find that there are three degradation strategies:

  • RT, average response time (grade_grade_rt): when five requests are continuously entered within 1s, and the average response time (in seconds) at the corresponding time exceeds the threshold (count, in ms), the call to this method will automatically fuse (throw DegradeException) within the next time window (timeWindow in DegradeRule, in s). Note that the RT upper limit of Sentinel's default statistics is 4900 ms, and those exceeding this threshold will be counted as 4900 ms. if you need to change this upper limit, you can configure it by starting the configuration item - DCSP. Sentinel. Statistical. Max.rt = XXX.
  • Grade_grade_exception_ratio: when the number of requests per second of the resource is > = 5 and the ratio of the total number of exceptions per second to the throughput exceeds the threshold (count in DegradeRule), the resource enters the degraded state, that is, within the next time window (timeWindow in DegradeRule, in s), calls to this method will be returned automatically. The threshold range of abnormal ratio is [0.0, 1.0], representing 0% - 100%.
  • Grade_grade_exception_count: when the number of exceptions of the resource in recent 1 minute exceeds the threshold, it will fuse. Note that since the statistical time window is of minute level, if the timeWindow is less than 60s, it may still enter the fusing state after the fusing state is ended.

5.1 RT

When five requests are continuously entered within 1s, and the average response time (in seconds) at the corresponding time exceeds the threshold (count, in ms), the call to this method will automatically melt (throw DegradeException) within the next time window (timeWindow in DegradeRule, in s).

5.1.1 add test interface

@GetMapping("/rt")
public ResponseEntity<String> rt() throws InterruptedException {
	TimeUnit.SECONDS.sleep(1);
	return ResponseEntity.ok("ok") ;
}

5.1.2 add downgrade rule


Five requests are entered within 1s. If the corresponding time exceeds 500ms, the fuse mechanism is executed in the next 3s.

5.1.3 testing

Open Apache jmeter.
Add a thread group:

1 s 10 threads send requests at the same time.

Add a sampler:

Add the interface we want to test:

Start test:

View results:



5.2 abnormal proportion

When the requests per second of resources are > = 5, and the ratio of the total number of exceptions per second to the throughput exceeds the threshold, the resources enter the degraded state, and the program will fail quickly in the next time window.

5.2.1 adding interfaces

@GetMapping("/exception")
public ResponseEntity<String> exception() throws InterruptedException {
	throw new RuntimeException("Just don't want to succeed!") ;
}

5.2.2 add degradation rule


The above configuration means that if the QPS of / exception is greater than 5 and the request exception ratio per second is greater than 0.5, the sentinel circuit breaker will open and the api interface will not be available in the next 3 seconds (time window).

In other words, if 10 requests come in within one second and more than 5 are wrong, the fuse will be triggered and the interface will not be available within one second.

5.2.3 testing

Open Jmeter and modify the requested address:

Start the test.
Open in browser:
http://localhost:8086/exception

Directly fused, stop jemter, wait for 3s, visit here:

5.3 number of exceptions

When the policy is the number of exceptions, it means that degradation will be triggered when the number of exceptions requested is greater than or equal to a certain value within the specified time window. Continue to use the interface test above.

5.3.1 adding rules


The above rule indicates that if the number of access / exception request exceptions is greater than or equal to 5 within 60 seconds, degradation will be triggered.

5.3.2 testing the rule

It can be seen that the downgrade was successfully triggered on the fifth visit.

6, Hotspot rule

Hot spots are frequently accessed data. Many times, we want to count the data with the highest access frequency in a hot data and restrict its access.

6.1 add an interface

@GetMapping("/buy")
@SentinelResource("buy")
public ResponseEntity<String> buy(String prodName,Integer prodCount){
	return ResponseEntity.ok("buy" + prodCount + "share" + prodName );
}

6.2 rules for adding hotspots

Add a hotspot rule to this resource:

The above configuration means: add a hotspot rule to the buy resource. When the value of the 0th parameter is Huawei, the QPS threshold is 3, otherwise it is 1. In addition, if the 0th parameter is not passed, the request is not limited by the hotspot rule.

6.3 test effect

Not Huawei:

After buying once, the current is limited inside.
Huawei: buy it three times at the same time to limit the current

7, System rules

System rules set flow restriction rules for the whole system, not for a resource. The setting page is as follows:

Threshold types include the following five types:

  • Load adaptation (only valid for Linux / Unix like machines): load1 of the system is used as the heuristic index for adaptive system protection. When system load1 exceeds the set heuristic value and the current number of concurrent threads exceeds the estimated system capacity, system protection will be triggered (BBR stage). The system capacity is estimated from the maxQps minRt of the system. The setting reference value is generally CPU cores 2.5.
  • CPU usage (version 1.5.0 +): when the system CPU utilization exceeds the threshold, the system protection is triggered (value range 0.0-1.0), which is sensitive.
  • Average RT: when the average RT of all inlet flows on a single machine reaches the threshold, the system protection is triggered, and the unit is milliseconds.
  • Number of concurrent threads: system protection is triggered when the number of concurrent threads of all inlet traffic on a single machine reaches the threshold.
  • Inlet QPS: when the QPS of all inlet flows on a single machine reaches the threshold, the system protection is triggered

8, Authorization rules

Authorization rules are used to configure the black and white list of resources:

The configuration indicates that only appA and appB can access test1 resources.

SentinelResource remote call test case construction

1, Introduction to @ SentinelResource

Sentinel provides @ SentinelResource annotation to define resources, and provides optional exception fallback and Block fallback.
Exception fallback refers to the fallback processing when Java exceptions occur in the method marked with @ SentinelResource annotation;
Block fallback refers to the fallback when the @ SentinelResource resource access does not comply with the rules defined by the Sentinel console (the default return is Blocked by Sentinel (flow limiting)). The usage of this annotation is briefly recorded here.

2, Construction of framework

We will build the test framework as shown in the figure:

2.1 setting up sentinel example

We will demonstrate all the functions of @ SentinelResource in sentinel example.

2.1.4 complete pom.xml file

<?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>spring-cloud-alibaba-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<packaging>pom</packaging>
	<artifactId>sentinel-examples</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
		</dependency>
	</dependencies>
</project>

2.1.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following contents:

Parent: select spring cloud Alibaba example
Name: sentinel-example
Click Finish to complete the creation

2.1.2 add dependency

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
	</dependency>
</dependencies>
  • Spring boot stat web is the most basic dependency for web development;
  • Spring cloud Alibaba Nacos discovery is the discovery component of services

2.1.3 modify the packaging method of the project

<packaging>pom</packaging>

2.2 setting up sentinel provider

Provide is an ordinary service provider.

2.2.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following contents:

Parent: select sentinel example
Name: sentinel-provider
Click Finish to complete the creation.

2.2.2 modify the packaging method of the project

We modify the packaging method of the project, and later we can use jar to publish the project.

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

2.2.3 complete pom.xml file

<?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>sentinel-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>sentinel-provider</artifactId>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

2.3 setting up sentinel consumer

Provide is an ordinary service consumer.

2.3.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following contents:

Parent: select sentinel example
Name: sentinel-consumer
Click Finish to complete the creation.

2.3.2 modify pom.xml file

We will demonstrate the function of @ SentinelResource in this project.

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

We modify the packaging method of the project, and later we can use jar to publish the project.

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

2.3.3 complete pom.xml file

<?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>sentinel-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>sentinel-consumer</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

3, Improve sentinel provider

We add an analog data interface to the provider.

3.1 add a data interface


The code is as follows:

@RestController
public class GoodsController {
	
	@GetMapping("/goods/buy/{name}/{count}")
	public ResponseEntity<String> buy(
		@PathVariable("name") String name,
		@PathVariable("count") Integer count) {
	return ResponseEntity.ok(String.format("purchase%d share%s", count, name));
	}
}

3.2 add profile

server:
	port: 8081
spring:
	application:
		name: sentinel-provider
	cloud:
		nacos:
			discovery:
				server-addr: localhost:8848

Just so that the service can register with the registry.

3.3 add a startup class


The code is as follows:

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelProviderApplication {
	public static void main(String[] args) {
		SpringApplication.run(SentinelProviderApplication.class ,args) ;
	}
}

3.4 start up test

Before starting, we must ensure that Nacos has been started successfully.

Test interface:
http://localhost:8081/goods/buy/huawei/1

4, Perfect sentinel consumer

4.1 calling service providers


The code is as follows:

@RestController
public class BuyController {

	@Autowired
	private RestTemplate restTemplate;
	
	@GetMapping("buy/{name}/{count}")
	@SentinelResource(value = "buy", fallback = "buyFallback", blockHandler =
	"buyBlock")
	public ResponseEntity<String> buy(@PathVariable String name, @PathVariable
	Integer count) {
		if (count >= 20) {
			throw new IllegalArgumentException("Too many purchases");
		}
		if ("miband".equalsIgnoreCase(name)) {
			throw new NullPointerException("Sold out");
		}
		Map<String, Object> params = new HashMap<>(2);
		params.put("name", name);
		params.put("count", count);
		return ResponseEntity.ok(
	this.restTemplate.getForEntity("http://sentinel-provider/goods/buy/{name}/{count}", String.class, params).getBody());
	}
	
	// Abnormal fallback
	public ResponseEntity<String> buyFallback(@PathVariable String name,
	@PathVariable Integer count, Throwable throwable) {
		return ResponseEntity.ok(
			String.format("[get into fallback [method] purchase%d share%s Failure,%s", count,name, throwable.getMessage()));
	}
	
	// sentinel fallback
	public ResponseEntity<String> buyBlock(@PathVariable String name,
	@PathVariable Integer count, BlockException e) {
		return ResponseEntity.ok(String.format("[get into blockHandler [method] purchase%d share%s Failed. There are too many buyers at present. Please try again later", count, name));
	}
}

4.2 add profile

New profile:

The contents are as follows:

server:
	port: 8083
spring:
	application:
		name: sentinel-consumer
	cloud:
		nacos:
			discovery:
				server-addr: localhost:8848
		sentinel:
			transport:
				dashboard: localhost:8080
				port: 8719

4.3 add startup class

@SpringBootApplication
@EnableDiscoveryClient
public class SentinelConsumerApplication {
	public static void main(String[] args) {
		SpringApplication.run(SentinelConsumerApplication.class ,args) ;
	}
	
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate(){
		return new RestTemplate() ;
	}
}

4.4 start up test

4.4.1 startup software

Before starting, you must ensure that these software have been started:

4.4.2 adding flow control rules



When the QPS exceeds 2 when accessing the resource, an exception is thrown.
Test:
http://192.168.1.11:8083/buy/huawei/1

5, Other usage

5.1 description

Writing a fallback method in the current class will make the code redundant and highly coupled. We can extract the fallback method into a specified class.

5.2 add BuyBlockHandler


The code is as follows:

public class BuyBlockHandler {
	// sentinel fallback
	public static String buyBlock(@PathVariable String name, @PathVariable
	Integer count, BlockException e) {
		return String.format("[get into blockHandler [method] purchase%d share%s Failed. There are too many buyers at present. Please try again later", count, name);
	}
}

5.3 add BuyFallBack


The code is as follows:

public class BuyBlockHandler {
	// sentinel fallback
	public static ResponseEntity<String> buyBlock(@PathVariable String name,
	@PathVariable Integer count, BlockException e) {
		return ResponseEntity.ok(String.format("[get into blockHandler [method] purchase%d share%s Failed. There are too many buyers at present. Please try again later", count, name));
	}
}

5.4 transformation of BuyController

@RestController
public class BuyController {
	
	@Autowired
	private RestTemplate restTemplate;
	
	@GetMapping("buy/{name}/{count}")
	@SentinelResource(
					value = "buy",
					fallback = "buyFallback",
					fallbackClass = BuyFallBack.class ,
					blockHandler = "buyBlock",
					blockHandlerClass = BuyBlockHandler.class ,
	)
	public ResponseEntity<String> buy(@PathVariable String name, @PathVariable
	Integer count) {
		if (count >= 20) {
		throw new IllegalArgumentException("Too many purchases");
		}
		if ("miband".equalsIgnoreCase(name)) {
		throw new NullPointerException("Sold out");
		}
		Map<String, Object> params = new HashMap<>(2);
		params.put("name", name);
		params.put("count", count);
		return ResponseEntity.ok(
	this.restTemplate.getForEntity("http://sentinel-provider/goods/buy/{name}/{
	count}", String.class, params).getBody());
	}
}

Spring Cloud Alibaba Seata

1, Introduction to Seata


Seata is an open source distributed transaction solution, which is committed to providing high-performance and easy-to-use distributed transaction services under the microservice architecture. Before the opening of Seata, the corresponding internal version of Seata has been playing the role of distributed consistency Middleware in Alibaba economy, helping the economy survive the double 11 over the years and providing strong support for BU businesses. After years of precipitation and accumulation, commercial products have been sold in Alibaba cloud and financial cloud. On January 2019, in order to create a more perfect technological ecology and inclusive technological achievements, Seata officially announced open source. In the future, Seata will help its technology more reliable and complete in the form of community co construction.

2, Installation of Seata server

Before using Seata, we first need to install the Seata server server.

2.1 download Seata

Since the version of spring cloud alibaba We use is 2.2.0.RELEASE, which controls the version of seata to 1.0.0, we download the version of seata of 1.0.0 here.
visit:
https://github.com/seata/seata/releases/tag/v1.0.0


Because I am using a windows computer, I choose the version of seata-server-1.0.0.zip.
Click this file to download.

2.2 Seata server directory analysis

Copy the Seata server to the directory of the software, and use the decompression tool to extract the file.

Bin: executable directory
Conf: profile directory
lib: dependent jar
LICENSE: authorization file

2.3 Seata startup

Enter the {seata}/bin directory and double-click:

Indicates that the Seata server has been started successfully.

3, Construction of framework


In this example, we simulate a scenario where a user purchases goods:

  • StorageService is responsible for deducting inventory quantity;
  • OrderService is responsible for saving orders;
  • AccountService is responsible for deducting user account balance;
  • Business is responsible for the whole process of placing orders by users;

3.1 construction of Seata examples project

seata examples is used to control the dependent version numbers of all projects and remove the public dependent seata.

3.1.1 create Maven project using IDEA


Select Maven project:

Click Next to add the following information:

Parent: select spring cloud Alibaba examples
Name: seata-examples
Other information can be left as default. Then click Finish to complete the creation.

3.1.2 add dependency

Open the pom.xml file of the project and add the following dependencies.

<dependencies>
	<!-- Service registration-->
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
	</dependency>
	<!-- seata-->
	<dependency>
		<groupId>com.alibaba.cloud</groupId>
		<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
	</dependency>
	<!-- web Basic dependencies of the project-->
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-web</artifactId>
	</dependency>
</dependencies>

3.1.3 complete 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>spring-cloud-alibaba-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>seata-examples</artifactId>
	<dependencies>
		<!-- Service registration-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
		</dependency>
		<!-- seata-->
		<dependency>
			<groupId>com.alibaba.cloud</groupId>
			<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
		</dependency>
		<!-- web Basic dependencies of the project-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>
</project>

Finally, the dependencies of our project are as follows:

As you can see, our seata version is 1.0.0.

3.2 build account service project

The account service project will be responsible for deducting the user account balance

3.2.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following information:

Parent: seata-examples
Name: account-service
Other values can remain the default.

3.2.2 add dependency

We need to use the ORM framework to complete the operation on the database. In this case, we need to use mybatis plus to operate the database.
Open pom.xml and add the following contents:

<dependencies>
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.3.0</version>
	</dependency>
	<!--MySQL rely on -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

In order to package and publish our project in the future, we add the package plug-in of boot Maven:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

In order to package and publish our project in the future, we add the package plug-in of boot Maven:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

3.2.3 the complete pom.xml file is as follows

<?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>seata-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>account-service</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>2.3</version>
		</dependency>
		<!--MySQL rely on -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

The dependencies of the project are as follows:

3.3 build business service project

business will mainly complete the order logic, including inventory deduction and order creation.

3.3.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following information:

Parent: seata-examples
Name: business-service
Other values can remain the default.

3.3.2 add dependency

In order to package and publish our project in the future, we add the package plug-in of boot Maven:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

3.2.3 the complete pom.xml file is as follows

<?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>seata-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>account-service</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>2.3</version>
		</dependency>
		<!--MySQL rely on -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

The dependencies of the project are as follows:

3.4 build order service project

The order service project will be responsible for saving user orders.

3.4.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following information:

Parent: seata-examples
Name: order-service
Other values can remain the default.

3.4.2 add dependency

We need to use the ORM framework to complete the operation on the database. In this case, we need to use mybatis plus to operate the database.
Open pom.xml and add the following contents:

<dependencies>
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.3.0</version>
	</dependency>
	<!--MySQL rely on -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

In order to package and publish our project in the future, we add the package plug-in of boot Maven:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

3.4.3 the complete pom.xml file is as follows

<?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>seata-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>order-service</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>2.3</version>
		</dependency>
		<!--MySQL rely on -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

The dependencies of the project are as follows:

3.5 building storage service project

Storage service will be responsible for deducting the inventory of goods.

3.5.1 create a Maven project using IDEA


Select Maven project:

Click Next and fill in the following information:

Parent: seata-examples
Name: storage-service
Other values can remain the default.

3.5.2 adding dependencies

We need to use the ORM framework to complete the operation on the database. In this case, we need to use mybatis plus to operate the database.

Open pom.xml and add the following contents:

<dependencies>
	<dependency>
		<groupId>com.baomidou</groupId>
		<artifactId>mybatis-plus-boot-starter</artifactId>
		<version>3.3.0</version>
	</dependency>
	<!--MySQL rely on -->
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
	</dependency>
</dependencies>

In order to package and publish our project in the future, we add the package plug-in of boot Maven:

<build>
	<plugins>
		<plugin>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-maven-plugin</artifactId>
		</plugin>
	</plugins>
</build>

3.5.3 the complete pom.xml file is as follows

<?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>seata-examples</artifactId>
		<groupId>com.bjsxt</groupId>
		<version>1.0</version>
	</parent>
	<modelVersion>4.0.0</modelVersion>
	<artifactId>order-service</artifactId>
	<dependencies>
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>2.3</version>
		</dependency>
		<!--MySQL rely on -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

The dependencies of the project are as follows:

3.6 the case of a complete project is

4, Code perfection

4.1 database table import

Before testing distributed transactions, we need to design the database and prepare the test data.
Create a new database named seata

Import Sql:

Import the sql:

Click start to import.
After success, the number of successful items found is:

The table is as follows:

Account: user's account table
Order: order table;
Stowage: inventory table of goods;
UNDO_LOG: rollback the transaction table. Undo is required for the SEATA AT mode_ Log table.




4.2 model object and Mapper object generation

Connect to the database using IDEA:


Set time zone:


Select the default and click ok

After success, as shown in the figure:

Execute code generation:

  • Account_tbl:

  • Order_tbl:

  • Storage_tbl:

    After code generation:

4.3 improvement of storage service code

4.3.1 interface design

The storage service is mainly used to deduct inventory.
Create a new interface:

It is named StorageService and the code is as follows:

The code is as follows:

public interface StorageService {
	/**
	* Deduct inventory of goods
	* @param commodityCode
	* Commodity code
	* @param count
	* Quantity of goods deducted
	*/
	void deduct(String commodityCode, int count);
}

4.3.2 implementation of the interface


The name is impl.StorageService, and the code implementation is as follows:

@Service
public class StorageServiceImpl implements StorageService {
	
	private static Logger logger = LoggerFactory.getLogger(StorageServiceImpl.class);

@Autowired
private StorageTblMapper storageTblMapper;
@Override
public void deduct(String commodityCode, int count) {
	logger.info("Start to deduct inventory, item code:{},number:{}", commodityCode, count);
	StorageTbl storageTbl = storageTblMapper.selectOne(
		new LambdaQueryWrapper<StorageTbl>()
			.eq(StorageTbl::getCommodityCode, commodityCode));
	int idleCount = storageTbl.getCount() - count;
	if (idleCount < 0) {
		throw new RuntimeException("Insufficient inventory");
	}
	storageTbl.setCount(idleCount);
	storageTblMapper.updateById(storageTbl);
	logger.info("Inventory deduction succeeded, item code:{},Remaining quantity:{}", commodityCode, idleCount);
	}
}

4.3.3 use Restful to expose this interface

Add a Controller

The code is as follows:

@RestController
public class StorageController {

	private static Logger logger = LoggerFactory.getLogger(StorageController.class) ;

	@Autowired
	private StorageService storageService ;

	/**
	* Deduct inventory of goods
	* @param commodityCode Commodity code
	* @param count Quantity of goods
	* @return
	*/
	@GetMapping("/deduct/{commodityCode}/{count}")
	public ResponseEntity<Void> deduct(
		@PathVariable("commodityCode") String commodityCode,
		@PathVariable("count") Integer count){
	logger.info("Account Service ... xid: " + RootContext.getXID());
	// Start deducting inventory
	storageService.deduct(commodityCode , count);
	return ResponseEntity.ok().build() ;
	}
}

4.3.4 add profile

Create a new configuration file in the resource directory:

The contents are as follows:

server:
	port: 18084
spring:
	application:
		name: storage-service
	cloud:
		alibaba:
			seata:
				tx-service-group: storage-service
		nacos:
			discovery:
				server-addr: localhost:8848
		datasource:
			name: storageDataSource
			type: com.alibaba.druid.pool.DruidDataSource
			username: root
			password: 123456
			driver-class-name: com.mysql.cj.jdbc.Driver
			url:
			jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
			druid:
				max-active: 20
				min-idle: 2
				initial-size: 2
seata:
	service:
		vgroup-mapping:
			account-service: default
		grouplist:
			default: 127.0.0.1:8091
		disable-global-transaction: false
		enabled: true
	mybatis-plus:
		mapper-locations: classpath:/mapper/*.xml

4.3.5 add startup class

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class StorageServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(StorageServiceApplication.class ,args) ;
	}
}

4.3.6 start project test

After starting the project, print the log, indicating that the connection to Seata server is successful.

4.4 improvement of account service code

4.4.1 interface design

Account service is mainly used to deduct the user's balance.
Create a new interface:

It is named AccountService and the code is as follows:

The code is as follows:

public interface AccountService {
	/**
	* Deduct amount from user's account
	* @param userId
	* User Id
	* @param money
	* amount of money
	*/
	void debit(String userId, int money);
}

4.4.2 implementation of the interface


The name is impl.StorageService, and the code implementation is as follows:

@Service
public class AccountServiceImpl implements AccountService {

	@Autowired
	private AccountTblMapper accountTblMapper;
	
	private static Logger logger = LoggerFactory.getLogger(AccountServiceImpl.class);
	
	@Override
	public void debit(String userId, int money) {
		logger.info("User ready to deduct:{} Balance, the amount deducted is:{}", userId, money);
		AccountTbl accountTbl = accountTblMapper.selectOne(
			new LambdaQueryWrapper<AccountTbl>()
				.eq(AccountTbl::getUserId, userId));
	int idleMoney = accountTbl.getMoney() - money;
	if (idleMoney < 0) {
	throw new RuntimeException("Insufficient user balance");
	}
	accountTbl.setMoney(idleMoney);
	accountTblMapper.updateById(accountTbl);
	logger.info("Deduction user{}Amount succeeded, remaining amount is{}", userId, money);
	}
}

4.4.3 use Restful to expose this interface

Add a Controller

The name is:
The code is as follows:

@RestController
public class AccountController {
	
	@Autowired
	private AccountService accountService ;
	
	private static Logger logger =
	LoggerFactory.getLogger(AccountController.class) ;
	
	@GetMapping("/debit/{userId}/{money}")
	public ResponseEntity<Void> debit(
		@PathVariable("userId") String userId,
		@PathVariable("money") Integer money){
	logger.info("Account Service ... xid: " + RootContext.getXID());
	// Start deducting balance
	accountService.debit(userId , money);
	return ResponseEntity.ok().build() ;
	}
}

4.4.4 add profile

Create a new configuration file in the resource directory:

The contents are as follows:

server:
	port: 18085
spring:
	application:
		name: account-service
	cloud:
		alibaba:
			seata:
				tx-service-group: account-service
		nacos:
			discovery:
				server-addr: localhost:8848
	datasource:
		type: com.alibaba.druid.pool.DruidDataSource
		username: root
		password: 123456
		driver-class-name: com.mysql.cj.jdbc.Driver
		url: jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
		druid:
			max-active: 20
			min-idle: 2
			initial-size: 2
seata:
	service:
		vgroup-mapping:
			account-service: default
		grouplist:
			default: 127.0.0.1:8091
		disable-global-transaction: false
	enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml

4.4.5 add startup class


It is named AccountServiceApplication and the code is as follows:

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class AccoutServiceApplication {
	
	public static void main(String[] args) {
	SpringApplication.run(AccoutServiceApplication.class ,args) ;
	}
}

4.4.6 start project test

After starting the project, print the log, indicating that the connection to Seata server is successful.

4.5 improvement of order service code

4.5.1 interface design

The order service is mainly used to save user orders.
Create a new interface:

It is named OrderService and the code is as follows:

The code is as follows:

public interface OrderService {
	/**
	* Create an order
	* @param userId User id
	* @param commodityCode Item number
	* @param orderCount Quantity of goods
	* @return OrderTbl
	*/
	OrderTbl create(String userId, String commodityCode, int orderCount) ;
}

4.5.4 Ribbon integration

Create a configuration class:

The code is as follows:

@Configuration
public class HttpUtilConfig {
	
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate(){
		return new RestTemplate() ;
	}
}

4.5.2 implementation of the interface


The name is impl.OrderService, and the code implementation is as follows:

@Service
public class OrderServiceImpl implements OrderService {

	@Autowired
	private OrderTblMapper orderTblMapper;
	
	@Autowired
	private AccountService accountService;
	private static Logger logger =
	LoggerFactory.getLogger(OrderServiceImpl.class);
	
	@Override
	public OrderTbl create(String userId, String commodityCode, int orderCount)
	{
		logger.info("Ready for{}Create an order,Item number is{},Quantity is{}", userId,
		commodityCode, orderCount);
		// 1. Calculate the total amount
		int orderMoney = calculate(commodityCode, orderCount);
		accountService.debit(userId, orderMoney);
		OrderTbl order = new OrderTbl();
		order.setUserId(userId);
		order.setCommodityCode(commodityCode);
		order.setCount(orderCount);
		order.setMoney(orderMoney);
		orderTblMapper.insert(order);
		// INSERT INTO orders ...
		return order;
	}
	private int calculate(String commodityCode, int orderCount) {
		// We don't have a list of goods now. Here we fix the price of goods
		int prodPrice = 0 ;
		if("HUAWEI_0001".equals(commodityCode)){ // Huawei time 100
			prodPrice = 100;
		}else if ("XIAOMI_002".equals(commodityCode)){ // Millet hour 200
			prodPrice = 200 ;
		}else {
			prodPrice = 1000 ; // 1000 for others
		}
		return orderCount * prodPrice ;
	}
}

4.5.3 implementation of remote call to account service

Create an AccountService class, which mainly completes the remote call to accout service.

The name is:

/**
* Realize the remote call to the account service
*/
@Service
public class AccountService {
	
	private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
	/**
	* 1 ribbon The way
	*/
	@Autowired
	private RestTemplate restTemplate ;
	
	/**
	* 2 feign The way
	*/
	public void debit(String userId, int orderMoney) {
		ResponseEntity<Void> entity = restTemplate.
			getForEntity(
				"http://accout-service/debit/{userId}/{orderMoney}",
				Void.class,
				userId,
				orderMoney
		);
		if(entity.getStatusCode()== HttpStatus.OK){
		logger.info("Deduction user{}The amount is successful. The amount deducted this time is{}",userId,orderMoney);
		return ;
		}
		logger.info("Deduction user{}Amount failed",userId);
		throw new RuntimeException("Deduction amount failed") ;
	}
}

When we use Ribbon here to make remote calls, we will also test Feign in the following chapters.

4.5.5 use Restful to expose this interface

Add a Controller

Named:

The code is as follows:

@RestController
public class OrderController {
	
	private static Logger logger = LoggerFactory.getLogger(OrderController.class) ;
	
	@Autowired
	private OrderService orderService ;
	
	/**
	* Create order
	* @param userId
	* User Id
	* @param commodityCode
	* Item number
	* @param orderCount
	* Quantity of goods
	* @return
	*/
	@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
	public ResponseEntity<Void> create(
		@PathVariable("userId") String userId,
		@PathVariable("commodityCode") String commodityCode,
		@PathVariable("orderCount") int orderCount){
	logger.info("Order Service ... xid: " + RootContext.getXID());
	orderService.create(userId, commodityCode, orderCount) ;
	return ResponseEntity.ok().build() ;
	}
}

4.5.6 adding configuration files

Create a new configuration file in the resource directory:

Named: application.yml
The contents are as follows:

server:
	port: 18086
spring:
	application:
		name: order-service
	cloud:
		alibaba:
			seata:
				tx-service-group: order-service
		nacos:
			discovery:
				server-addr: localhost:8848
	datasource:
		name: orderDataSource
		type: com.alibaba.druid.pool.DruidDataSource
		username: root
		password: 123456
		driver-class-name: com.mysql.cj.jdbc.Driver
		url:
		jdbc:mysql://localhost:3306/seata?useSSL=false&serverTimezone=UTC
		druid:
			max-active: 20
			min-idle: 2
			initial-size: 2
seata:
	service:
		vgroup-mapping:
			order-service: default
		grouplist:
			default: 127.0.0.1:8091
		disable-global-transaction: false
	enabled: true
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml

4.5.7 add startup class


Named: OrderServiceApplication
The code is as follows:

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.bjsxt.mapper")
public class OrderServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(OrderServiceApplication.class ,args) ;
	}
}

4.58 start project test

After starting the project, print the log, indicating that the connection to Seata server is successful.

4.6 improvement of business service code

4.6.1 interface design

In business service, it mainly completes the logic of placing an order, including two main steps, that is, remote calling of inventory service and order service.
Create a new interface:

Named: com.bjsxt.service.BusinessService

The code is as follows:

public interface BusinessService {
	/**
	* Procurement / order placing process
	* @param userId
	* User Id
	* @param commodityCode
	* Commodity code
	* @param orderCount
	* Quantity of goods
	*/
	void purchase(String userId, String commodityCode, int orderCount) ;
}

4.6.2 implementation of the interface


The name is impl.BusinessServiceImpl, and the code implementation is as follows:

@Service
public class BusinessServiceImpl implements BusinessService {
	
	private static Logger logger = LoggerFactory.getLogger(BusinessServiceImpl.class) ;
	
	@Autowired
	private StorageService storageService;
	
	@Autowired
	private OrderService orderService;
	
	@Override
	public void purchase(String userId, String commodityCode, int orderCount) {
		logger.info("Ready to place an order, user:{},Commodity:{},number:{}",userId,commodityCode,orderCount);
		storageService.deduct(commodityCode, orderCount);
		orderService.create(userId, commodityCode, orderCount) ;
		logger.info("Order completion");
	}
}

4.6.3 implementation of remote calling storage service

Create a StorageService class, which mainly completes the remote call to storage service.

The name is:

@Service
public class StorageService {
	
	private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
	/**
	* 1 In the form of Ribbon
	*/
	@Autowired
	private RestTemplate restTemplate ;
	/**
	* 2 In the form of Feign
	*/
	public void deduct(String commodityCode, int orderCount) {
		ResponseEntity<Void> entity = restTemplate.
			getForEntity(
				"http://storage-service/debut/{commodityCode}/{orderCount}",
				Void.class,
				commodityCode,
				orderCount
			);
		if (entity.getStatusCode()== HttpStatus.OK){
			logger.info("Inventory deduction succeeded, item No{},The amount deducted this time is{}",commodityCode,orderCount);
			return;
		}
		throw new RuntimeException("Inventory deduction failed") ;
	}
}

When we use Ribbon here to make remote calls, we will also test Feign in the following chapters.

4.6.4 implementation of remote calling order service

Create a new class:

The code is as follows:

@Service
public class OrderService {
	
	private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
	/**
	* 1 In the form of Ribbon
	*/
	@Autowired
	private RestTemplate restTemplate ;
	/**
	* 2 In the form of Feign
	*/
	public void create(String userId, String commodityCode, int orderCount) {
		ResponseEntity<Void> entity = restTemplate.
			getForEntity(
			"http://order-service/create/{userId}/{commodityCode}/{orderCount}",
			Void.class,
			userId ,
			commodityCode,
			orderCount
		);
		if (entity.getStatusCode()== HttpStatus.OK){
			logger.info("Order created successfully, user is{} ,Item number is{},The amount deducted this time is{}",userId ,
		commodityCode,orderCount);
			return;
		}
		throw new RuntimeException("Order creation failed") ;
	}
}

4.6.5 integrated Ribbon

Add a configuration class of HttpUtilConfig:

The code is as follows:

@Configuration
public class HttpUtilConfig {
	
	@LoadBalanced
	@Bean
	public RestTemplate restTemplate(){
		return new RestTemplate() ;
	}
}

4.6.6 adding configuration files

Create a new configuration file in the resource directory:

Named: application.yml
The contents are as follows:

server:
	port: 18087
spring:
	application:
		name: business-service
	cloud:
		alibaba:
			seata:
				tx-service-group: business-service
		nacos:
			discovery:
				server-addr: localhost:8848
seata:
	service:
		vgroup-mapping:
			business-service: default
		grouplist:
			default: 127.0.0.1:8091
		disable-global-transaction: false
	enabled: tru

4.6.7 add startup class


Named: BusinessServiceApplication
The code is as follows:

@SpringBootApplication
@EnableDiscoveryClient
public class BusinessServiceApplication {
	public static void main(String[] args) {
		SpringApplication.run(BusinessServiceApplication.class ,args) ;
	}
}

4.6.8 interface

Continue to transform the startup class:

@SpringBootApplication
@EnableDiscoveryClient
@RestController
public class BusinessServiceApplication {
	
	@Autowired
	private BusinessService businessService ;
	
	public static void main(String[] args) {
		SpringApplication.run(BusinessServiceApplication.class ,args) ;
	}
	/**
	* Start placing orders
	* @param userId
	* User Id
	* @param commodityCode
	* Item number
	* @param orderCount
	* Quantity of goods
	* @return
	*/
	@GetMapping("/purchase/{userId}/{commodityCode}/{orderCount}")
	public ResponseEntity<Void> purchase(
		@PathVariable("userId") String userId,
		@PathVariable("commodityCode")String commodityCode,
		@PathVariable("orderCount")Integer orderCount){
	businessService.purchase(userId,commodityCode,orderCount);
	return ResponseEntity.ok().build() ;
	}
}

4.6.9 start project test

After starting the project, print the log, indicating that the connection to Seata server is successful.

4.7 the overall calling process is as follows:

After startup:

Nacos-Server:

4.8 normal order test

Access in the browser:
http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1
For SXT_USER_1 purchase HUAWEI_0001 one product.
In the database:

  • Accout_ Inside TBL, SXT_USER_1. The user's amount is reduced by 100;
  • Storage_tbl inside, HUAWEI_0001 inventory decreased by 1;
  • Order_ In TBL, an order record is created;

    Note: remote call is normal at this time.

4.9 demonstration of distributed transaction

We demonstrate the exception shown in the figure:

We can find that there are three remote calls.

4.9.1 exception triggered by deduction of balance in accout service

4.9.2 restart the accout service

4.9.3 restore data in database

Account_Tbl:

Storage_Tbl:

4.9.4 reorder test

http://localhost:18087/purchase/SXT_USER_1/HUAWEI_0001/1

Database data:
Account_Tbl:

Storage_Tbl:

We found that the distributed transaction generated an exception inside the accout service, resulting in the accout_tbl table data rolled back.
However, in storage_tbl: the transaction at the stowage service is not rolled back.

4.10 solving distributed problems with Seata

4.10.1 transform AccountServiceImpl in accout service


When the user ID is: SXT_ USER_ At 2:00, we throw an exception. When it is for other users, we place orders normally.

4.10.2 transforming BusinessServiceImpl


Add an annotation to see if it can solve the problem of distributed transactions

4.10.3 restart test

Restart the accout service and the business service test
Using SXT_USER_1. Normal order test:

Stoage_tbl: normal inventory
Accout_Tbl: normal balance
Using SXT_USER_2 order test:

When an exception is found,
stoage_ The data in TBL has not changed, and the data is normal
Accout_ The data in TBL has not changed, and the data is normal
The distributed transaction test succeeded

5, Integration Feign test Seata

In the above chapter, we use the remote call in the form of Ribbon + RestTemplate. Now let's play
Shows how Feign is called.

5.1 transformation of business service

5.1.1 add dependency

Modify the pom.xml file in the business service project and add dependencies.

<dependencies>
	<dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-openfeign</artifactId>
	</dependency>
</dependencies>

5.1.2 add OrderServiceFeign


The code inside is as follows:

@FeignClient("order-service")
public interface OrderServiceFeign {

	@GetMapping("/create/{userId}/{commodityCode}/{orderCount}")
	ResponseEntity<Void> create(
		@PathVariable("userId") String userId,
		@PathVariable("commodityCode") String commodityCode,
		@PathVariable("orderCount") Integer orderCount);
}

5.1.3 adding StorageServiceFeign

@FeignClient("storage-service")
public interface StorageServiceFeign {

	@GetMapping("/deduct/{commodityCode}/{orderCount}")
	ResponseEntity<Void> deduct(
		@PathVariable("commodityCode") String commodityCode,
		@PathVariable("orderCount") Integer orderCount
	) ;
}

5.1.5 transformation of OrderService

@Service
public class OrderService {
	private static Logger logger = LoggerFactory.getLogger(StorageService.class) ;
	/**
	* 1 In the form of Ribbon
	*/
	@Autowired
	private RestTemplate restTemplate ;
	
	@Autowired
	private OrderServiceFeign orderServiceFeign ;
	/**
	* 2 In the form of Feign
	*/
	public void create(String userId, String commodityCode,  int orderCount){
	// Ribbon
	// ResponseEntity<Void> entity = restTemplate.
	// 		getForEntity(
	//		"http://order-service/create/{userId}/{commodityCode}/{orderCount}",
	// 		Void.class,
	// 		userId ,
	// 		commodityCode,
	// 		orderCount
	// );
	
	//Feign
	ResponseEntity<Void> entity = orderServiceFeign.create(userId, commodityCode,
	orderCount);
	if (entity.getStatusCode()== HttpStatus.OK){
		logger.info("Order created successfully, user is{} ,Item number is{},The amount deducted this time is{}",userId ,commodityCode,orderCount);
		return;
	}
	throw new RuntimeException("Order creation failed") ;
	}
}

5.1.7 transforming StorageService


The code is as follows:

@Service
public class StorageService {
	private static Logger logger = LoggerFactory.getLogger(StorageService.class);
	/**
	* 1 In the form of Ribbon
	*/
	@Autowired
	private RestTemplate restTemplate;
	
	@Autowired
	private StorageServiceFeign storageServiceFeign;
	
	/**
	* 2 In the form of Feign
	*/
	public void deduct(String commodityCode, int orderCount) {
	// 	Ribbon
	// 	ResponseEntity<Void> entity = restTemplate.
	// 		getForEntity(
	//		"http://storage-service/deduct/{commodityCode}/{orderCount}",
	// 		Void.class,
	// 		commodityCode,
	// 		orderCount
	// 	);
	
	//Feign
	ResponseEntity<Void> entity = storageServiceFeign.deduct(commodityCode,
	orderCount);
	if (entity.getStatusCode() == HttpStatus.OK) {
		logger.info("Inventory deduction succeeded, item No{},The amount deducted this time is{}", commodityCode,
	orderCount);
		return;
	}
	throw new RuntimeException("Inventory deduction failed");
	}
}

5.1.6 enable Feign support in the startup class

5.2 transformation of order service

5.2.1 add dependency

Add in dependencies:

<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

5.2.2 adding interfaces


The code inside is as follows:

@FeignClient("account-service")
public interface AccountServiceFeign {

	@GetMapping("/debit/{userId}/{orderMoney}")
	ResponseEntity<Void> debit(
		@PathVariable("userId") String userId,
		@PathVariable("orderMoney") Integer orderMoney
	) ;
}

5.2.3 modifying AccoutService

/**
* Realize the remote call to the account service
*/
@Service
public class AccountService {
	private static Logger logger = LoggerFactory.getLogger(AccountService.class) ;
	/**
	* 1 ribbon The way
	*/
	@Autowired
	private RestTemplate restTemplate ;
	
	@Autowired
	private AccountServiceFeign accountServiceFeign ;
	/**
	* 2 feign The way
	*/
	public void debit(String userId, int orderMoney) {
	//Ribbon
	// 	ResponseEntity<Void> entity = restTemplate.
	// 		getForEntity(
	// 		"http://accout-service/debit/{userId}/{orderMoney}",
	// 		Void.class,
	// 		userId,
	// 		orderMoney
	// 	);
		ResponseEntity<Void> entity = accountServiceFeign.debit(userId, orderMoney);
		if(entity.getStatusCode()== HttpStatus.OK){
			logger.info("Deduction user{}The amount is successful. The amount deducted this time is{}",userId,orderMoney);
			return ;
		}
		logger.info("Deduction user{}Amount failed",userId);
	throw new RuntimeException("Deduction amount failed") ;
	}
}

5.2.4 add Feign support in the startup class

5.3 restart test

Restart order service and business service
Restore the database data and start the test.
Normal order test:

Using SXT_USER_2. Order:

An error occurred, but all tables in the database are normal.
Seata test succeeded.

Posted by shane18 on Fri, 26 Nov 2021 18:03:51 -0800