About the program concurrency is the cliché of the topic, the work also often to meet, it is necessary to summarize, in fact, the associated solution is the lock, lock will consume the program's performance and some resources this is certain, of course, if you can use its own atomic operation (the full execution of instructions, It is best to implement lock-free programming without being interrupted by another thread during execution and without a context switch.
1. Prevent duplicate Requests
Recently, it has been found that the database has exactly the same data, and may be a continuous user click, causing the duplication of the database operation, we need to prevent such operations. There are many solutions, you can lock, or use the atomic nature of PHP directive, will be uri,userid,parms (to ensure that there is no random data), MD5 check to Seession, first get (), not a valid request, there is a description invalid request, it seems reasonable, but get (), set () This existence pit, for example a user repeatedly clicks, multiple requests are executed by multiple threads, at this time the order of execution is unpredictable, such as get () No value can be manipulated, and then there is set into the session, In the process of set-to-session, the other thread may also get () to have no results, so it is inserted twice, so we chose the Redis operation, Redis is atomic operation and is single-threaded. Setnx () method is good, if there is this key value return False, no write success, atomic operation, perfect!
/** * @return array Check for duplicate submissions*/ Publicfunction Checkrepeatsub () {$uri= App ('Request'),Getrequesturi (); $userid= \app\services\salecarcall\util\utils::getinstance ()getUserId (); $params= App ('Request'),All (); if(Empty ($params)) { return true; } $info= [ 'URI'=$uri,'userid'=$userid,'params'= = $params ]; $key='repeatsub.'. MD5 (Json_encode ($info)); $redis=Redishelper::getredishandle (); if(! $redis->setnx ($key,1)) { Throw NewDuplicateexception ('Duplicate Request'); } Else{$redis->expire ($key, -); return true; } }
2. Concurrent Application Resource Issues
Our system sometimes comes up with two customer service requests to the same resource problem, which is also a typical concurrency problem for several reasons,
2.1 Because the architecture of the database is the master-slave separation, read and write separation, a main multi-slave architecture, it is natural to read down from the library, write down on the main library, read the main library from the library IO thread Binlog, and then the SQL thread execution, and from the library execution is single-threaded, the main library is multithreaded concurrency, So anyway, master and slave will have a delay, which will cause a to apply to a clue, the processing state should be processed, write to the main library above, because of the delay, from the library this thread has not changed to have been processed, then this clue can be processed two times. So for high-timeliness, fast-changing data, it is recommended or read/write in the main library.
2.2 Our customer service workflow is typically read-and-write process, in fact, the principle of ++i is the same, involving two operations, ++i involves two times of memory operations, we are two times the operation of the database, the database of the natural locking mechanism for our convenience. So as far as possible the read-write changes encapsulated into an atomic operation. For the ++i operation, the individual is also accustomed to the redis operation, that is, repeating the set of submissions. The set of individuals for the database believes that Redis operations also have some pits.
For example: We lock Redis must have an expiration time, if the execution process encountered an error or throw an exception caused the last delete lock is not executed, then the resource will be locked, can not be applied to other people, but we use the expiration time is also a pit, The normal flow should be a application to a clue, request a Redis lock (SETNX), return false, indicating that the lock is occupied, re-apply for another clue, if you get the lock, do something, delete the lock, seemingly read and write to the mechanism of the lock is atomic operation, But the expiration of the setting is very error-prone, for example, the expiration time is 2s,a application to the id=1 clues, and then execute the processing logic, but the processing logic executes 5S (can occur), and then 3rd S, in fact, the lock has been invalidated, B can apply to id=1 this clue, Result b execution took 1S, then a executed, change the database, B's operation record is overwritten, or a after the completion of the execution of B, then a deleted lock is not to delete the lock you added, a delete is the lock of B, then C may apply to id=1 this clue, Resulting in the entire execution process is not atomic, there is confusion. So, finally, we chose a mechanism to use the database's row locks naturally (based on the index).
The principle is: in the InnoDB storage engine, based on multiple version of the control, read is not locked, (in the mechanism of read rewriting may appear deadlock), that is, ABC may have applied to the id=1 resources, and then all to be modified to their own name of the processed, based on InnoDB-line locking mechanism, the write operation is atomic , a modifies first, then BC blocks wait, a modifies, b modifies, then C modifies, and finally a B's update operation is overwritten by C, which is a typical update loss scenario. So, when we design the database, UpdateTime is the database level, when ABC performs the update operation, where Id=1 and updatetime= read out the time, so that after a update is complete, updatetime is changed, BC does not meet the conditions they read out and the database time consistent, update failed, re-apply for the next clue.
$retry =0; while($retry <3) {$commonclue= Salecluequeue::onwriteconnection ()Select('ID','Updated_at'),where('operator',0), >appointcallvars::next_call_type_moren)where('Is_double_appoint', self::D ouble_type)('Weight','ASC'),First (); if(!empty ($commonclue)) {$updateData= [ 'Appoint_status'=appointcallvars::appoint_status_calling,'operator'= Utils::getinstance ()getUserId (),]; $flag= Salecluequeue::where('ID', $commonclue->id)where('Updated_at', $commonclue->updated_at)Update ($UPDATEDATA); if($flag) {$log= self:: $logPress.'application to lead: Result:'. Json_encode ($nextcall); Logutils::getinstance ()-Loginfo ($log); Break; } Else{$retry++; } } Else{$retry++; } }
About program concurrency