高い同時実行のコア技術 - 冪等
1.冪等とは何ですか
冪等を指す:冪等の任意の操作が同一の実行時間の影響で生成される複数回行う影響を及ぼす。
数学的な表現の概念とこの:. Fである(fは(X) )= f(x)が
同じNX1 = nと、x1は冪等の操作です。結果は同じで乗算された回数に関係なく。
2.一般的な冪等の問題
冪等性の問題は、多くの場合、ネットワークの問題によって引き起こされ、発生する操作を繰り返しています。
シーンワン:親指アップなどの機能は、ユーザーが唯一の紙ポイントの同じ部分に一度賞賛することができ、求めている繰り返し親指点は賞賛していました。
サンプルコード:
public void like(Article article,User user) {
//检查是否点过赞
if (checkIsLike(article,user)) {
//点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
//保存点赞
saveLike(article,user);
}
}
</pre>
それは理論的には、同じ人が同じアップ記事への親指を繰り返さない、場合親指を親指を保存する前にチェックして、問題はないように見えます。しかし、現実はそうではありません。ネットワーク要求が入ってくるキューに入れられますが、過去に急いでいませんので。
いくつかのケースでは、ユーザネットワークは、ネットワーク伝送の問題のために数回、クリックして、これらの要求が同時に当社のサーバーに来るかもしれない時間の非常に短い期間内に、良いではありません。
- 最初の要求checkIsLike()が来て、トランザクションをコミットしていない、偽、saveLikeが行われている()操作を返します。
- 第2の要求が来た、checkIsLike()は偽を返し、saveLike()操作を実行します
このように、それは同時に、ユーザの動作点の数については、このような記事を作成します。
これは典型的なパワーやその他の問題、動作結果であり、あなたが結果をクリックした回数に関係なく賞賛の同じ、一点のみであるあなたのマルチポイント、パワーの原則に従い、などのようなので2は、同じではありませんオペレーティング。
多くの場面では、そのようなユーザーのリピートオーダーとして、このように生じたコメントを繰り返し、フォームなどを提出繰り返され
次に、どのようにそれを解決するには?
ネットワーク要求が、この問題は表示されません来るようにキューイングされていることを前提としています。
だから我々はこれにそれを変更することができます:
public synchronized void like(Article article,User user) {
//检查是否点过赞
if (checkIsLike(article,user)) {
//点过赞了
throw new ApiException(CodeEnums.SYSTEM_ERR);
}
else {
//保存点赞
saveLike(article,user);
}
}
</pre>
素直に並ぶだろう私たちの要求が来たように、同期ロックを同期。
PS:これは、比較的低い効率的なアプローチは、分散クラスタのための唯一の子、同期されたシーンに適していない例として推奨されていないです。
シーン2:サードパーティ製の補正
当社のシステムは、多くの場合、あなたの結果を支払う通知するために、マイクロチャネルの再充電、どのようなAlipayの充電、マイクロチャネルとアリペイしばしばコールバックインタフェースなどのサードパーティ製のシステムに対処する必要があります。あなたがコールバックを受けられるようにするためには、コールバックは、多くの場合、何回してもよいです。
時には我々は、未知の水の支払いの結果を照会するタイマーを持つことになり、データの正確性を保証しなければならない、と応答の処理を実行します。
タイマーコールバックが回転すると同時に、これはバグを消す可能性が発生した場合は、2回の繰り返しを行ってきました。
そこで質問です:
私は再充電操作した場合、コールバックは、業務処理を行います、戻ってきて、お金を追加するユーザーアカウントに成功しました。これは冪等を確保する必要がある、とあなたが二回ユーザーの涵養を与える場合ので、どんなにを確保するために、(私は上司があなたの給与を差し引くと確信している)、明らかに不合理である、あなたのマイクロ手紙同じトランザクションが二回コールバックと仮定しますあなたはお金だけの取引でユーザーに充電できる回数マイクロチャネルコールバックします。この冪等。
そのような電源設定などの問題を解決
- 同期
スタンドアロンアプリケーションではなく、パフォーマンスの追求ではなく、同時実行の追求に適しています。 - ロック分散
しかし、多くの場合、我々のアプリケーションは、クラスタを分散され、かつ非常に特定のパフォーマンス、同時実行について、私たちは、この問題を解決するために、分散ロックを使用する必要があります。
Redisのは、ロックを分散しました:
/**
* 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>
私たちは、Redisのは、ロックコードがこれに変更することができ、分散使用します。
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>
キーデザインも非常に特定の程度である:
データは2つのビジネス・シナリオと競合しない、キーの競合はありませんが、鍵が異なる物品キーが同じではない、別の人には同じではありません。
ビジネスシーンに応じて設定してください。
原則:できるだけ狭いキー。 私たちの同時実行性を高めるように。
まずは、ロック、正常に実行完了したロック獲得操作を取得したデータを保存し、ロックを解除してみましょう。返すためにロックを取得するのに失敗未満。ロックを取得するために、このようなマシンとして、「デッドロック」を防ぐためである有効期限を設定し、有効期限を設定したが、彼はクラッシュし、ロックが解除され削除されませんでしたしません。
- バージョンコントロール
CASアルゴリズム:CASはBの新規の値を変更するための3つのオペランド、メモリ値V、旧Aの期待値を、持っています そしてAとVの期待値が同じメモリ値である場合にのみ場合は、メモリ値VはBに修正、または何もしません。より複雑な、興味のある学生が行くと見ることができます。