MySQLによる分散ロック
5719 ワード
紹介する
分散システムでは、分散ロックは最も基礎的なツールクラスです.例えば、2つの支払機能を有するマイクロサービスが配備されている場合、ユーザは1つの注文に対して2回の支払操作を開始する可能性があり、この2回の要求は2つのサービスに送信される可能性があるため、重複コミットを分散ロックで防止し、ロックを取得したサービスは正常に支払操作を行い、ロックを取得できないサービスプロンプトの重複操作を取得しなければならない.
当社は大量の基礎ツール類をパッケージしており、分散ロックを使用したい場合は3つのことをします.
1.データベースにgloballocktableテーブルを作成する.対応するjarパッケージ3を導入する.コードに@Autowired GlobalLockComponent globalLockComponentと書いてこのコンポーネントを使用します
この文章を読んでspringboot-starterで同じ機能を実現することもできます.しかし、私たちはこの方法で実現したのではなく、別の文章を開いて、私たちがどのように実現したのかを分析します.
この文章はまずMySQLの分布式の実現を分析します
表を作る
他人に使わせるコンポーネント
ロックオブジェクトの定義は次のとおりです.
具体的なロックとロック解除ロジック
このツール類には特に興味深い点が2つありますが、まず注意点を見てください2(上のコードに表示されています)
1.ロックが長時間解放されないように、Redisで実現するとロックタイムアウト時間を設定し、タイムアウト自動解放(後にRedisで分散ロックを実現すると書きます)をMySQLで実現すると先に削除して追加することができます.削除するときにidで削除したものを見ることができますが、nameで削除したものではありません.なぜですか?まず自分で考えてみましょう
nameで削除すると、他の人がこのロックを削除した後、nameでロックを追加した可能性があるので、まだタイムアウト時間になっていないのに、結局あなたはnameに基づいて削除しました.idで削除すると、返されたid=0の場合、他の人がロックを再ロックしたことを説明し、再取得する必要があります.
2.GlobalLockTableオブジェクトdaoレイヤの他の方法はすべて名を知っていて、この方法を見てみましょう.すなわち,コード中の注意点1は,ロックを試みるたびにselectではなく直接insertSelectiveWithTestであり,クエリ時間が少なく効率が向上することを示している.
InsertSelectiveWithTestの役割は、lockKeyが存在する場合に挿入操作を行わず、0を返すことです.ロックキーが存在しない場合は挿入操作を行い、1を返します.
使用
私たちが使いたいときは、ビジネスロジックだけ書けばいいので、とても便利です.
まとめ
以上述べたように、MySQLを使用して分散ロックを実現し、皆さんの役に立つことを願っています.
分散システムでは、分散ロックは最も基礎的なツールクラスです.例えば、2つの支払機能を有するマイクロサービスが配備されている場合、ユーザは1つの注文に対して2回の支払操作を開始する可能性があり、この2回の要求は2つのサービスに送信される可能性があるため、重複コミットを分散ロックで防止し、ロックを取得したサービスは正常に支払操作を行い、ロックを取得できないサービスプロンプトの重複操作を取得しなければならない.
当社は大量の基礎ツール類をパッケージしており、分散ロックを使用したい場合は3つのことをします.
1.データベースにgloballocktableテーブルを作成する.対応するjarパッケージ3を導入する.コードに@Autowired GlobalLockComponent globalLockComponentと書いてこのコンポーネントを使用します
この文章を読んでspringboot-starterで同じ機能を実現することもできます.しかし、私たちはこの方法で実現したのではなく、別の文章を開いて、私たちがどのように実現したのかを分析します.
この文章はまずMySQLの分布式の実現を分析します
表を作る
CREATE TABLE `globallocktable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`lockKey` varchar(60) NOT NULL COMMENT ' ',
`createTime` datetime NOT NULL COMMENT ' ',
PRIMARY KEY (`id`),
UNIQUE KEY `lockKey` (`lockKey`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT=' ';
他人に使わせるコンポーネント
@Component
public class GlobalLockComponent {
@Resource
GlobalLockTableDAO globalLockDAO;
/**
* , true, false
*/
public boolean tryLock(String key) {
return GlobalLockUtil.tryLock(this.globalLockDAO, key);
}
/**
* , timeoutMs( ) ,
* key ,
*/
public boolean tryLockWithClear(String key, Long timeoutMs) {
return GlobalLockUtil.tryLockWithClear(this.globalLockDAO, key, timeoutMs);
}
/**
* , key
*/
public void releasLock(String key) {
GlobalLockUtil.releasLock(this.globalLockDAO, key);
}
}
ロックオブジェクトの定義は次のとおりです.
public class GlobalLockTable {
private Integer id;
private String lockKey;
private Date createTime;
// get set
}
GlobalLockTableDAO
public interface GlobalLockTableDAO {
int deleteByPrimaryKey(Integer id);
int deleteByLockKey(String lockKey);
GlobalLockTable selectByLockKey(String key);
int insertSelectiveWithTest(GlobalLockTable record);
}
具体的なロックとロック解除ロジック
public class GlobalLockUtil {
private static Logger logger = LoggerFactory.getLogger(GlobalLockUtil.class);
private static GlobalLockTable tryLockInternal(GlobalLockTableDAO lockDAO, String key) {
GlobalLockTable insert = new GlobalLockTable();
insert.setCreateTime(new Date());
insert.setLockKey(key);
// 1
int count = lockDAO.insertSelectiveWithTest(insert);
if (count == 0) {
GlobalLockTable ready = lockDAO.selectByLockKey(key);
logger.warn("can not lock the key: {}, {}, {}", insert.getLockKey(), ready.getCreateTime(),
ready.getId());
return ready;
}
logger.info("yes got the lock by key: {}", insert.getId(), insert.getLockKey());
return null;
}
/** , **/
public static boolean tryLockWithClear(GlobalLockTableDAO lockDAO, String key, Long timeoutMs) {
GlobalLockTable lock = tryLockInternal(lockDAO, key);
if (lock == null) return true;
if (System.currentTimeMillis() - lock.getCreateTime().getTime() <= timeoutMs) {
logger.warn("sorry, can not get the key. : {}, {}, {}", key, lock.getId(), lock.getCreateTime());
return false;
}
logger.warn("the key already timeout wthin : {}, {}, will clear", key, timeoutMs);
// 2
int count = lockDAO.deleteByPrimaryKey(lock.getId());
if (count == 0) {
logger.warn("sorry, the key already preemptived by others: {}, {}", lock.getId(), lock.getLockKey());
return false;
}
lock = tryLockInternal(lockDAO, key);
return lock != null ? false : true;
}
/** **/
public static boolean tryLock(GlobalLockTableDAO lockDAO, String key) {
return tryLockInternal(lockDAO, key) == null ? true : false;
}
/** **/
public static void releasLock(GlobalLockTableDAO lockDAO, String key) {
lockDAO.deleteByLockKey(key);
}
}
このツール類には特に興味深い点が2つありますが、まず注意点を見てください2(上のコードに表示されています)
1.ロックが長時間解放されないように、Redisで実現するとロックタイムアウト時間を設定し、タイムアウト自動解放(後にRedisで分散ロックを実現すると書きます)をMySQLで実現すると先に削除して追加することができます.削除するときにidで削除したものを見ることができますが、nameで削除したものではありません.なぜですか?まず自分で考えてみましょう
nameで削除すると、他の人がこのロックを削除した後、nameでロックを追加した可能性があるので、まだタイムアウト時間になっていないのに、結局あなたはnameに基づいて削除しました.idで削除すると、返されたid=0の場合、他の人がロックを再ロックしたことを説明し、再取得する必要があります.
2.GlobalLockTableオブジェクトdaoレイヤの他の方法はすべて名を知っていて、この方法を見てみましょう.すなわち,コード中の注意点1は,ロックを試みるたびにselectではなく直接insertSelectiveWithTestであり,クエリ時間が少なく効率が向上することを示している.
InsertSelectiveWithTestの役割は、lockKeyが存在する場合に挿入操作を行わず、0を返すことです.ロックキーが存在しない場合は挿入操作を行い、1を返します.
insert into `globallocktable` (`id`,
`lockKey`, `createTime` )
select #{id,jdbcType=INTEGER}, #{lockKey,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}
from dual where not exists
(select 1 from globallocktable where lockKey = #{lockKey,jdbcType=VARCHAR})
使用
私たちが使いたいときは、ビジネスロジックだけ書けばいいので、とても便利です.
if (!globalLockComponent.tryLock(name)) {
//
return;
}
try {
//
} catch (Exception e) {
} finally {
globalLockComponent.releasLock(name)
まとめ
以上述べたように、MySQLを使用して分散ロックを実現し、皆さんの役に立つことを願っています.