High concurrency core technology - idempotency and Distributed Lock

The core technology of high concurrency - idempotency

1. What is Idempotence

Idempotent refers to: a idempotent affect any operation performed a plurality of times are generated with the same execution time impact.
With the concept of mathematical expression is this:. F (f (x) ) = f (x)
as the same nx1 = n, x1 is an idempotent operations. No matter how many times the result is multiplied by the same.

2. Common idempotency issues

Idempotence problem is often caused by network problems, and repeat the operation caused.

Scene One: thumbs up such functions, a user can only praise once on the same piece of paper point, has prompted repeated thumbs point had praised.

Sample code:

    public void like(Article article,User user) {
    //检查是否点过赞
    if (checkIsLike(article,user)) {
    //点过赞了
    throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
    saveLike(article,user);
}
}
</pre>

It looks like no problem, have checked before saving thumbs up thumbs up if, in theory, the same person will not be repeated thumbs up to the same article. But the reality is not the case. Because the network request is not queued coming in, but rushed onto the past.

In some cases, the user network is not good, within a very short period of time may click several times, due to network transmission problems, these requests may come at the same time our server.

  • The first request checkIsLike () returns false, saveLike being performed () operation, not come and commit the transaction
  • The second request came, checkIsLike () also returns false, and to perform the saveLike () operation

Like this, it creates a user at the same time an article like this for a number of operating points.

This is a typical power and other issues, an operating result and operating two are not the same, because you like a multi-point, in accordance with the principle of power, etc. No matter how many times you click on the result is the same, only one point of praise.

Many scenes are caused in this manner, such as user repeat orders, repeat comments, repeated submit the form etc.

Then how to solve it?
Assume that the network request is queued to come will not appear this problem.

So we can change it to this:

public synchronized void like(Article article,User user) {
    //检查是否点过赞
if (checkIsLike(article,user)) {
    //点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
saveLike(article,user);
}
}
</pre>

synchronized synchronous lock so that our request will obediently line up came.

PS: This is relatively low efficiency approach is not recommended way of example only child, synchronized scenes not suitable for distributed cluster.

Scene Two: Third-party correction

Our systems often need to deal with third-party systems, such as micro-channel recharge, what Alipay recharge, micro-channel and Alipay often callback interface to inform you pay your results. In order to ensure you receive a callback, the callback may often many times.

Sometimes we have to ensure the accuracy of the data will have a timer to query the result of payment of unknown water, and performs processing of the response.
If the timer callback happens to be in rotation and at the same time, this could turn out a BUG, has conducted two repeat.

So the question is :
If I were a recharge operation, the callback come back, will do business processing, succeeded to the user account to add money. This is necessary to ensure the idempotency, and assuming that micro letter to you the same transaction twice callback, if you give the user recharge twice, which is obviously unreasonable (I'm sure the boss deduct your salary), so to ensure that no matter micro-channel callback you how many times you can only charge to the user with a money transaction. This idempotent.

Solve problems such as power scheme

  • synchronized
    suitable for stand-alone applications, not the pursuit of performance, not the pursuit of concurrency.
  • Distributed Lock
    but often our application is distributed clusters, and very particular about performance, concurrency, so we need to use distributed lock to solve this problem.

Redis Distributed Lock:

/**
* setNx
*
*  @param key
*  @param value
*  @return
*/
public Boolean setNx(String key,Object value) {
    return redisTemplate.opsForValue().setIfAbsent(key,value);
}
/**
*  @param key 锁
*  @param waitTime 等待时间  毫秒
*  @param expireTime 超时时间  毫秒
*  @return
*/
public Boolean lock(String key,Long waitTime,Long expireTime) {
    String vlaue =  UUIDUtil.mongoObjectId();
    Boolean flag = setNx(key,vlaue);
    //尝试获取锁  成功返回
if (flag) {
    redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS);
    return flag;
}
else {
    //失败
//现在时间
long newTime =  System.currentTimeMillis();
    //等待过期时间
long loseTime = newTime + waitTime;
    //不断尝试获取锁成功返回
while (System.currentTimeMillis()  < loseTime) {
    Boolean testFlag = setNx(key,vlaue);
    if (testFlag) {
    redisTemplate.expire(key,expireTime,TimeUnit.MILLISECONDS);
    return testFlag;
}
//休眠100毫秒
try {
    Thread.sleep(100);
}
catch (InterruptedException e) {
    e.printStackTrace();
}
}}return false;}/**
*  @param key
*  @return
*/
public Boolean lock(String key) {
    return lock(key,1000L,60  *  1000L);
}
/**
*  @param key
*/
public void unLock(String key) {
    remove(key);
}
</pre>

We use Redis distributed lock code can be changed to this:

public void like(Article article,User user) {
    String key =  "key:like"  + article.getId()  +  ":"  + user.getUserId();
    //  等待锁的时间  0  ,  过期时间  一分钟防止死锁
boolean flag = redisService.lock(key,0,60  *  1000L);
    if(!flag) {
    //获取锁失败  说明前面的请求已经获取了锁
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
//检查是否点过赞
if (checkIsLike(article,user)) {
    //点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
    //保存点赞
saveLike(article,user);
}
//删除锁
redisService.unLock(key);
}
</pre>

key design is also very particular about:
data do not conflict with two business scenarios, key conflict is not, the key is not the same in different people, different articles Key are not the same.
Set according to the business scene.

A principle: narrow key as possible.  So as to enhance our concurrency.

First, let's get the lock, the lock acquire operation completed successfully executed, save the data, remove the lock. Less than a failure to obtain a lock to return. Set the expiration time is to prevent the 'deadlock', such as machine to obtain a lock, do not set an expiration time, but he crashed, did not remove the lock is released.

  • Version control
    CAS algorithm: CAS has three operands, memory value V, the expected value of the old A, to modify the new value of B. If and only if the expected value of the A and V are the same memory value, the memory value V revised to B, or do nothing. The more complicated, interested students can go and see.

Guess you like

Origin blog.51cto.com/14230003/2422709