Ask questions: Solve form duplication

1. Pre-knowledge

1 HTTP is a stateless hypertext transfer protocol used to transfer hypertext from the World Wide Web server to the local browser. HTTP is an application layer protocol based on the TCP/IP protocol model. 2 Viewing HTTP request packets An HTTP request packet consists of three parts: a request line, a request header, and a request body

POST /user HTTP/1.1 // Request line Host: www.user.com Content-type: application/x-www-form-urlencoded Connection: Keep-alive user-agent: Mozilla/5.0. // This is the request header name=worldCopy the code

HTTP1.0 defines three request methods: GET, POST, and HEAD. HTTP1.1 adds five new request methods: OPTIONS, PUT, DELETE, TRACE, and CONNECT methods)

OPTIONS: Similar to HEAD, OPTIONS requests are used by clients to check server performance. This method will ask the server to return all HTTP request methods supported by the resource. This method will replace the resource name with ‘*’ and send an OPTIONS request to the server to test whether the server is functioning properly.

HEAD: The HEAD method, like the GET method, makes a request to the server for a specified resource. However, when the server responds to a HEAD request, it does not pass back the content portion of the resource: the response body. This way, we can get the response header information from the server without transferring the entire content. The HEAD method is often used by clients to view server performance.

GET: THE GET request displays the requested resource. In general, the GET method should only be used for reading data and should not be used for non-idempotent operations that have side effects. What it expects should be and should be safe and idempotent. By security, I mean that the request does not affect the state of the resource.

POST: A POST request submits data to a specified resource. The request server processes the data, such as form data submission and file upload, in the request body. The POST method is non-idempotent because the request may create a new resource or/and modify an existing resource.

PUT/PATCH: THE PUT request uploads its latest content to the specified resource location. The PUT method is idempotent. This method allows the client to send the latest data of the specified resource to the server instead of the content of the specified resource.

PATCH is a complement to the PUT method and is used to locally update known resources

The differences are as follows: 1.PATCH is generally used for partial resource update, while PUT is generally used for overall resource update. 2. When a resource does not exist, PATCH creates a new resource, and PUT only updates the existing resource. 3.PUT is idempotent, while PATCH is non-idempotent. 4.PATCH method appeared later, and was defined in RFC 5789 standard in 2010. \

DELETE: The requesting server deletes the resource identified by the request URI for deletion

TRACE: The request server displays the received request information. This method is used to test or diagnose HTTP requests

CONNECT: The CONNECT method is reserved for HTTP/1.1 and can change the connection to a proxy server in pipe mode. Typically used for communication between SSL encrypted server links and unencrypted HTTP proxy servers. \

Idempotent (idempotence) is a mathematical and computer concept that is often found in abstract algebra. The characteristic of an idempotent operation in programming is that any number of executions have the same effect as a single execution. An idempotent function, or idempotent method, is a function that can be executed repeatedly with the same parameters and achieve the same results. These functions do not affect system state, nor do they have to worry about system changes caused by repeated execution. So, for the request to edit the form, we use PUT, can not do any protection operation, that is, repeated submission will not cause any changes to the system, this time may be used to say: I use POST to request the background interface, and then use the SQL update database is not the same. According to the REST specification interface, each resource has a URI. Different HTTP methods have different operations on the resource, such as GET (reading resource information), POST (adding resource information), PUT (updating resource information), and DELETE (deleting resource information). Almost all computer languages can communicate with REST servers over the HTTP protocol. So POST requests are best used only to add resources and PUT requests are best used to update resource information.

Ii. Solutions

1 Ensure that the button can be clicked only once

If the user clicks to query or submit an order number, the button grays out or the page displays loding status (for example, displaying components such as masks) specifically to prevent the user from clicking repeatedly.

2 Store a unique identifier in the Session

Users to enter the page, the server generates a unique id value, endures session, at the same time to write it into the form of hidden field, the user input information, then click the submit on the server for the form of hidden domain field values compared with the session in a unique identifier value, equal specification is submitted for the first time, will handle the request, Then, delete the session unique identifier. If the session is unequal, repeat submission is identified and this processing is ignored.

3 Cache Queue

To receive the request quickly, put it in the buffer queue, and then use asynchronous tasks to process the queue data and filter out repeated requests, we can use LinkedList to implement the queue and a HashSet to implement de-duplication. The advantages of this method are asynchronous processing and high throughput, but the request results cannot be returned in time, requiring subsequent polling results.

4 token+redis

This approach is divided into two stages: acquiring token and business operation.

Take payments: In the first stage, before entering the order submission page, the order system shall initiate a token application request to the payment system according to the current user information, and the payment system shall save the token in Redis as the payment in the second stage. In the second stage, the front-end order system shall initiate a payment request with the token applied. If the token in REDis is deleted at the first time, the payment system will check whether the token exists in redis. If the token exists, it indicates that the payment is requested for the first time, and the payment logic will be processed. After the processing, the token in REDIS will be deleted. If the request does not exist, the request is repeated

5 based on optimistic lock to achieve

If you update existing data, you can do locked update, or you can design the table structure with version to do optimistic locking, which can ensure both efficiency and idempotent. The optimistic lock version field is incremented when business data is updated.

Update table set version = version + 1 WHERE id =1 and version =#{version}

Axios interceptor

Axios: Axios is a lightweight HTTP client

Perform HTTP requests based on XMLHttpRequest service, support rich configuration, support Promise, support browser side and Node.js side. As of Vue2.0, UVU announced that it was withdrawing its official recommendation for Vue-Resource in favor of Axios. Axios is now the first choice for most Vue developers

Features:

1 Create XMLHttpRequests from the browser 2 Create HTTP requests from Node.js 3 Support the Promise API 4 Intercept requests and responses 5 Transform request data and response data 6 Cancel requests 7 Automatically transform JSON data 8 The client supports XSRF defenseCopy the code

Note this feature 6 cancel request:

6.1 Basic Usage

// install NPM install axios --S // import axios from 'axios' If (process.env.node_env === 'development') {axios.defaults.baseURL = 'http://dev.xxx.com'} else if (process.env.NODE_ENV === 'production') { axios.defaults.baseURL = 'http://prod.xxx.com' }Copy the code

6.2 Create the following folders

6.3 Creating the axios.js file in the lib directory:

/* eslint-disable */ import axios from "axios"; import { baseURL } from "@/config"; import md5 from "js-md5"; // Let pending = {}; // let CancelToken = axios.canceltoken; class HttpRequest { constructor(baseUrl = baseURL) { this.baseUrl = baseUrl; this.queue = {}; } getInsideConfig(auth) { var config = { baseURL: this.baseUrl, headers: { Authorization: auth } }; return config; } distory(url) { delete this.queue[url]; if (! Object.keys(this.queue).length) { //Spin.hide() } } interceptors(instance, Url) {instance. Interceptors. Request. Use (config = > {/ / check whether in the json data contains repetitiveRequestLimit attribute, if included, then add idempotent calibration for this request if(config.data.hasOwnProperty("repetitiveRequestLimit")){ let key = md5(`${config.url}&${config.method}&${JSON.stringify(config.data)}`); CancelToken = new cancelToken (c => {if (pending[key]) {if (date.now () -pending [key] > 5000) { Delete the corresponding request record and re-initiate the delete Pending [key] request. } else {if a request is repeated to a user within 5s, cancel the request c("repeated"); }}}); Pending [key] = date.now (); }else{console.log(' I do not have requests for repetition limit ')} return config; }, error => { return Promise.reject(error); }); instance.interceptors.response.use( res => { this.distory(url); var { data } = res; return data; }, error => {// error request result processing, If (error && error. Response) {switch (error.response.status) {case 400: Error. Message = "error request "; break; Case 401: error. Message = "Unauthorized, please log in again "; break; Case 403: error. Message = "Access denied "; break; Case 404: error. Message = "error "; break; Case 405: error. Message = "request method not allowed "; break; Case 408: error. Message = "Request timed out "; break; Case 500: error. Message = "error "; break; Case 501: error. Message = "Network not implemented "; break; Case 502: error. Message = "error "; break; Case 503: error. Message = "Service unavailable "; break; Case 504: error. Message = "Network timeout "; break; Case 505: error. Message = "The HTTP version does not support this request "; break; Default: error.message = 'connection error ${error.response.status}'; }} else {error. Message = "Failed to connect to server "; } return Promise.reject(error.message); }); } request(options) { var instance = axios.create(); options = Object.assign(this.getInsideConfig(localStorage.getItem("Authorization")), options); this.interceptors(instance, options.url); return instance(options); } } export default HttpRequest;Copy the code

6.4 the config/index. Js

// You can set the background Url according to the node environment // you can use the node environment variable to determine, /* eslint-disable */ export var baseURL = process.env.node_env === 'development'? ' http://localhost:8080':' http://localhost:8081'Copy the code

6.5 API/baseIndex. Js

/* eslint-disable */
import HttpRequest from "@/lib/axios";
var axios = new HttpRequest();
export default axios;
Copy the code

6.6 api/requestdemo1

/* eslint-disable */ import axios from './baseIndex' // Implement a distributed lock test with native Redis export var getRedisLock = (object) => {return axios.request({ url: "/demo1/testRedisLock", method: "post", data:object }); }; Export var getRedissonLock = (object) => {return axios.request({url: "/demo1/testRedisson", method: "post", data:object }); };Copy the code

6.7 VUE page is imported

<template> <div class="home-content"> < button@click ="getUserData"> < button@click ="getUserData1"> Redisson distributed lock test </Button> </div> </div> </template> <script> /* eslint-disable */ import {getRedisLock,getRedissonLock} from '@/api/requestdemo1.js' export default { name: 'home', data() { return { } }, methods: {getUserData() {let person={requestName:' name ', salary:"10000", age:23, / / after add this parameter on the request of the axios idempotent operation, namely the repeated request in a short period of time can't send out repetitiveRequestLimit: true} getRedisLock (person). Then (res = > { console.log(res); }).catch((e)=>{ console.log(e); }).finally(() => { console.log('finish'); })}, getUserData1(){let person={requestName: name, salary:"10000", age:23, orderNumber:"ADW12314123", / / after add this parameter on the request of the axios idempotent operation, namely the repeated request in a short period of time can't send out / / repetitiveRequestLimit: true} getRedissonLock (person). Then (res = > { console.log(res); }).catch((e)=>{ console.log(e); }).finally(() => { console.log('finish'); }) } }, } </script> <style scoped> .home-container { padding: 10px; padding-top: 5px; } .home-content { padding: 10px; border-radius: 5px; background: #fff; } </style>Copy the code

6.8 test

7 Redis distributed lock

Lock, as we all know, is a synchronization tool in the program to ensure that shared resources can only be accessed by one thread at the same time. We are familiar with locks in Java, such as synchronized and Lock, which are often used by us. However, The Java Lock can only be effective when a single machine is used, and the distributed cluster environment is powerless. For a back-end solution to the problem of duplicate form submission, we can use distributed locking.

Distributed locks need to meet the following features:

1. Mutual exclusion: At any time, only one application can obtain distributed locks for the same piece of data

2. High availability: In distributed scenarios, the failure of a small number of servers does not affect normal use. In this case, services providing distributed locks need to be deployed in a cluster

3, prevent lock timeout: if the client does not actively release the lock, the server will automatically release the lock after a period of time, to prevent client downtime or network unreachable deadlock

4, exclusivity: lock unlocking must be carried out by the same server, that is, the holder of the lock can release the lock, can not appear you add the lock, others to unlock you

7.1 What is the nature of Redis distributed lock?

Redis acquires the lock and sets the expiration time as atomic operations:

1 SETNX :SET if Not exists, SETEX key seconds value

PSETEX :PSETEX key milliseconds value

(This command is similar to the SETEX command, but it sets the lifetime of the key in milliseconds rather than seconds, as the SETEX command does.)

The most common way to obtain a lock is by Redis:

Starting with Redis 2.6.12, the SET command uses parameters to achieve the same effect as SETNX, SETEX, and PSETEX

SET key value NX EX seconds: If NX and EX parameters are added, the effect is the same as SETEX

Example:

The name of the lock can be based on the id of the logged-in person and the uri of the request. When the value of key = lock is set to “Java”, setting it to any other value will fail, i.e. 1 if the lock is acquired, 0 if the lock is not acquired. Only one application can obtain a distributed lock, and setting the lock timeout period also prevents lock timeout

So the question is, can the value of the lock really be set arbitrarily as above?

7.2 How to Set the Value of Value?

Answer: It should be unique, thus achieving exclusivity of distributed locks

If value is not unique, the following request may occur?

1. Server 1 successfully obtains the lock. 2. The key expires, and the lock is automatically released. 4. Server 2 has obtained lock 5 for the same resource. Server 1 recovers from the block, and since the value is the same, the lock release operation releases the lock held by server 2, which causes problems

You can set value as follows: Method 1:UUID

String uuid = UUID.randomUUID().toString();
Copy the code

Method 2: id of the current thread

String id = Thread.currentThread().getId() + "";
Copy the code

Method 3: Distributed snowflake algorithm ID generator

Reference: distributed ids based on Snowflake algorithm cloud generator code: https://gitee.com/yu120/neuralCopy the code

7.3 How to Ensure high availability of Redis locks?

The general definition of High Availability is as follows: High Availability is used to describe a system that is specially designed to reduce downtime while maintaining High Availability of its services. That is, in a distributed scenario, the failure of a small number of servers does not affect normal use.

Not recommended: Redis single copy

This is not recommended for the following reasons: If Redis is in single-master mode, when the machine goes down, all clients will not be able to obtain the lock

Recommended :Redis Multi-copy (master/slave), Redis Sentinel (Sentinel), Redis Cluster

Reasons for recommendation: In order to improve availability, assume that a master and slave redis is deployed with one master and one slave. Because the master and slave synchronization of Redis is asynchronous, the master may hang up after client 1 sets the lock, and the data in the original master will be transferred to the original slave. The slave is then promoted to master so that the lock is not lost.

7.4 Demo practice

7.4.1 Introducing the Jedis client in the project

<! -- jedis-redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency>Copy the code

Redis distributed lock utility class:

/ * *

  • @description:
  • @author: geekAntony
  • @create: 2021-01-17 16:52

* * /

Public class RedisLockUtil {private long EXPIRE_TIME = 5; // Wait timeout,1s private long TIME_OUT = 1000; Private SetParams params = setparams.setparams ().nx().px(EXPIRE_TIME); private SetParams params = setparams.setparams ().nx(). JedisPool JedisPool = new JedisPool("127.0.0.1", 6379); /** * @param value * thread id, * @return */ public Boolean lock(String key,String value) {Long start = System.currentTimemillis (); Jedis jedis = jedisPool.getResource(); try { for (;;) String lock = jedis. SET (key, value, params); String lock = jedis. if ("OK".equals(lock)) { return true; Long l = system.currentTimemillis () -start; if (l >= TIME_OUT) { return false; } thread.sleep (100);} thread.sleep (100); } catch (InterruptedException e) { e.printStackTrace(); } } } finally { jedis.close(); }} /** * unlock ** @param value * thread ID, Public Boolean unlock(String key,String value) {Jedis Jedis = jedispool.getResource (); Jedis Jedis = jedispool.getResource (); String script = "if redis. Call ('get',KEYS[1]) == ARGV[1] then" + "return redis. Call ('del',KEYS[1])" + "else" + " return 0 " + "end"; try { String result = jedis.eval(script, Collections.singletonList(key), Collections.singletonList(value)).toString(); return "1".equals(result); } finally { jedis.close(); }}}Copy the code

Front-end controller:

/** * @program: structure * @description: * @author: geekAntony * @create: 2021-01-19 22:59 **/ @RestController @RequestMapping("/demo1") public class TestRedisLock { private static RedisLockUtil  demo = new RedisLockUtil(); @PostMapping(value = "/testRedisLock") public String add(@RequestBody Person person) { String id = Thread.currentThread().getId() + ""; boolean isLock = demo.lock("redislockName",id); Try {// Get the lock to execute the operation... Timeunit.seconds. Sleep (3); }else{return "Please do not repeat the form request "; } } catch (InterruptedException e) { e.printStackTrace(); } finally {// Unlock demo.unlock("redislockName",id); } return "complete business logic "; }}Copy the code

Testing:

8 Use Redisson distributed lock

Introducing Redisson dependencies:

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.13.4</version>
        </dependency>
Copy the code

application.yml:

Spring: Redis: host: 127.0.0.1 port: 6379 database: 1 password: timeout: 10000 jedis: pool: max-active: 8 max-idle: 8 min-idle: 0 max-wait: -1ms # Redisson Redisson: type Standalone # Redis server deployment type, standalone: standalone deployment, cluster: machine deployment. The default value is single-node deployment. Address: redis://127.0.0.1:6379 # For single-node deployment, the value must start with redis://Copy the code

Basic information configuration:

@data //lombok @configurationProperties (prefix = "redisson") public class RedssionProperties {/** * The redis server deployment type. */ private String type = "standalone "; */ private String type =" standalone "; /** * private String address; /** * private int database = 0; /** * Redis authentication password, if not required, should be null */ private String password; / * * * Redis minimum amount of free connection * / private int connectionMinimumIdleSize = 24; /** * private int connectionPoolSize = 64; /** * Redis server response timeout time, Redis command successfully sent start countdown (ms) */ private int timeout = 3000; */ private int connectTimeout = 10000; }Copy the code

Redisson configuration class

@Configuration @EnableConfigurationProperties(RedssionProperties.class) public class RedissonConfig { private final RedssionProperties redssionProperties; Public RedissonConfig(RedssionProperties RedssionProperties) {public RedissonConfig(RedssionProperties RedssionProperties) { this.redssionProperties = redssionProperties; } /** * RedissonClient instance is created on a single redis server and managed by the Spring container. * * @return */ @bean@conditionalonProperty (prefix = "redisson", name = "type", HavingValue = "SingleServerConfig ") public RedissonClient RedissonClient () {/** * Config: Single-node deployment configuration, MasterSlaveServersConfig: Master/slave replication configuration * SentinelServersConfig: Sentinel mode configuration, ClusterServersConfig: Cluster deployment configuration. * useSingleServer() : Initializes the Redis single-server configuration. SetAddress (String Address) : Sets the REDis server address. Format -- redis:// host: port, default redis://127.0.0.1:6379 * setDatabase(int database): Set the redis database to connect to, default is 0 * setPassword(String password) : * RedissonClient create(Config Config): */ Config Config = new Config(); */ Config Config = new Config(); config.useSingleServer() .setAddress(redssionProperties.getAddress()) .setDatabase(redssionProperties.getDatabase()) .setPassword(redssionProperties.getPassword()) .setConnectionPoolSize(redssionProperties.getConnectionPoolSize()) .setConnectionMinimumIdleSize(redssionProperties.getConnectionMinimumIdleSize()) .setTimeout(redssionProperties.getTimeout()) .setConnectTimeout(redssionProperties.getConnectTimeout()); RedissonClient redissonClient = Redisson.create(config); return redissonClient; }}Copy the code

Testing:

The detailed front-end code can be seen above:

<template> <div class="home-container"> <div class="home-content"> <Button @click="getUserData1">  </div> </div> </template> <script> /* eslint-disable */ import {getRedisLock,getRedissonLock} from '@/api/requestdemo1.js' export default { name: 'home', data() { return { userInfo: '', } }, methods: {getUserData1(){let person={requestName:' name ', salary:"10000", age:23, orderNumber:"ADW12314123", / / comment out this field so as to avoid axios interceptors to intercept / / repetitiveRequestLimit: true} getRedissonLock (person). Then (res = > {the console. The log (res); }).catch((e)=>{ console.log(e); }).finally(() => { console.log('finish'); }) } }, } </script> <style scoped> .home-container { padding: 10px; padding-top: 5px; } .home-content { padding: 10px; border-radius: 5px; background: #fff; } </style>Copy the code

Background controller:

@RestController @RequestMapping("/demo1") public class TestRedisLock { @Autowired private RedissonClient redissonClient;  private static Logger logger = LoggerFactory.getLogger(TestRedisLock.class); /** * redissonClient. getLock(String name) : reentrant lock * Boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) : Select * from waitTime where the lock is being acquired. This method returns false * from leaseTime. 3. If the lock is obtained successfully, return true; otherwise return false. */ @postMapping (value = "/testRedisson") public String addDemo1(@requestBody Person) {String result = "order [" + Person. GetOrderNumber () + "] "; String key = person.getorderNumber (); /** * getLock(String name) : Return the lock instance by name, implementing an unfair reentrant lock, so there is no guarantee that threads get the order * lock(): Get the lock, if the lock is not available, the current thread will sleep, */ RLock lock = redissonClient.getLock(key); boolean tryLock = false; TryLock = lock.tryLock(1, 60, timeUnit.seconds); } catch (InterruptedException e) { e.printStackTrace(); } // The lock fails. TryLock) {return "Order [" + person.getorderNumber () + "] is being paid, please wait!" ; } try {logger.info(" check payment status "); TimeUnit.SECONDS.sleep(1); Logger.info (" Order being paid [" + person.getorderNumber () + "]"); TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); Result = "order number XXX [" + person.getorderNumber () + "] :" + LLDB message (); } finally {/** * Boolean isLocked(): checks if the lock isLocked by any thread, returns true if it isLocked, false otherwise. * unlock() : Lock release, the implementation class of the Lock interface typically imposes restrictions on thread release of the Lock (usually only the Lock holder can release the Lock), and may throw (unchecked) exceptions if the restrictions are violated. If the lock has already been released, an exception will be thrown when the lock is released repeatedly. */ if (lock.isLocked()) { lock.unlock(); } } return result; }}Copy the code

Test results:

Three summary

The above package

Solution 1, which is simpler to implement, can be used early in the project or for interfaces that are not particularly important

Solution 2,3,4 are not recommended

Solution 5 is implemented based on optimistic lock, which occupies disk storage space but is simple and stable. Therefore, you are advised to use it

Solution 6 is novel and can be tried in the project

Solution 7 is a Demo of Redis implementing distributed locks, relying on highly available Redis

Solution 8 is a popular solution in production environments and relies on highly available Redis

Reference article:

Distributed lock implementation based on Redis

Juejin. Cn/post / 684490…

This is fine: a deep understanding of Redis distributed locks

Mp.weixin.qq.com/s?__biz=MzI…