CAS+失敗重試方式實(shí)現(xiàn)數(shù)據(jù)庫的原子性更新
在前面一篇文章中我們講了Java底層基于Unsafe的CAS實(shí)現(xiàn)《什么是CAS?如果說不清楚,這篇文章要讀一讀!》,這篇文章來看看基于Spring和CAS理念來實(shí)現(xiàn)的樂觀鎖方案。
在數(shù)據(jù)庫修改單條數(shù)據(jù)時,常用的方式是select for update的悲觀鎖機(jī)制,如果鎖競爭比較大,沒有獲得鎖的操作會阻塞。使用CAS樂觀鎖的方式,可以大大提高并發(fā)性。例如,在分布式服務(wù)中,多個用戶并發(fā)下單操作前會先扣減庫存時,服務(wù)1、服務(wù)2和服務(wù)3為不同機(jī)器上的庫存服務(wù)。
庫存扣減操作流程如下:

使用CAS方式的樂觀鎖,當(dāng)庫存還剩3個,3個用戶同時下單,服務(wù)同時扣減庫存,可以并發(fā)地扣減成功,提高了并發(fā)性。如果庫存還剩1個,3個用戶同時下單,同時扣減庫存,這時只有1個用戶會操作成功,其余2個會失敗,避免了超賣。
在庫存的CAS操作中,先進(jìn)行庫存查詢操作,然后根據(jù)value+version方式修改庫存,如果操作失敗,則冪等地重復(fù)操作。通常會從操作次數(shù)和執(zhí)行時間兩個條件限制CAS的操作,如果兩個條件中有某個條件觸發(fā),可以拋出樂觀鎖異常。
在Java項(xiàng)目中,通過AOP+注解的方式實(shí)現(xiàn)數(shù)據(jù)庫操作的CAS冪等操作的統(tǒng)一處理。定義OptimisticRetry注解,標(biāo)識CAS重試Spring AOP的切點(diǎn),并且設(shè)置屬性value(最大重試次數(shù)條件),以及屬性maxExecuteTime(最大執(zhí)行時間條件)。OptimisticRetryAOP定義CAS重試的切面,樂觀鎖的實(shí)現(xiàn)。OptimisticRetry注解定義代碼如下:
/**
* 樂觀鎖的重試
**/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OptimisticRetry {
/**
* 最大重試次數(shù)
*/
int value() default 200;
/**
* 最大執(zhí)行時間
*/
int maxExecuteTime() default 2 * 60 * 60 * 1000;
}
OptimisticRetryAOP切面,定義標(biāo)注了OptimisticRetry注解的方法則進(jìn)行,CAS冪等重試,如果達(dá)到最大重試次數(shù)限制或者最大執(zhí)行時間限制,則拋出樂觀鎖異常OptimisticLockingFailureException。
代碼如下:
/**
* 樂觀鎖的重試
**/
@Aspect
@Component
@Order(10000)
@Slf4j
public class OptimisticRetryAOP {
@Pointcut("@annotation(com.smcx.winemall.common.lock.OptimisticRetry)|| @within(com.smcx.winemall.common.lock.OptimisticRetry)")
public void optimisticRetryPointcut() {
}
@Around("optimisticRetryPointcut()")
public Object doConcurrentOperation(ProceedingJoinPoint joinPoint) throws Throwable {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
// 代理目標(biāo)對象Class
Class targetClazz = joinPoint.getTarget().getClass();
Method method = targetClazz.getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
Optional<OptimisticRetry> optimisticRetryOption = getOptimisticRetry(method.getAnnotations());
if (!optimisticRetryOption.isPresent()) {
log.warn("no OptimisticRetry Annotation to execute!");
return joinPoint.proceed();
}
OptimisticRetryConfig optimisticRetryConfig = obtainOptimisticRetryConfig(optimisticRetryOption.get());
int numAttempts = 0;
OptimisticLockingFailureException lockFailureException;
long startTime = System.currentTimeMillis();
do {
numAttempts++;
try {
return joinPoint.proceed();
} catch (OptimisticLockingFailureException ex) {
lockFailureException = ex;
long executeTime = System.currentTimeMillis() - startTime;
long maxExecuteTime = optimisticRetryConfig.getMaxExecuteTime();
if (isLargerThanMaxExecuteTime(executeTime, maxExecuteTime)) {
log.warn("throw optimistic locking failure exception!num attempts [{}],start time [{}]," +
"actual execute time [{}] ms, max execute time [{}] ms",
numAttempts, startTime, executeTime, maxExecuteTime);
throw lockFailureException;
}
}
}
while (numAttempts <= optimisticRetryConfig.getMaxTryCount());
log.warn("throw optimistic locking failure exception!num attempts {} ", numAttempts);
throw lockFailureException;
}
/**
* 大于限制的重試執(zhí)行時間
*
* @param executeTime
* @return
*/
private boolean isLargerThanMaxExecuteTime(long executeTime, long maxExecuteTime) {
if (executeTime <= 0) {
return false;
}
if (maxExecuteTime > executeTime) {
return true;
}
return false;
}
private OptimisticRetryConfig obtainOptimisticRetryConfig(OptimisticRetry optimisticRetry) {
// 從注解中獲取配的值
OptimisticRetryConfig optimisticRetryConfig = new OptimisticRetryConfig();
optimisticRetryConfig.setMaxTryCount(optimisticRetry.value());
optimisticRetryConfig.setMaxExecuteTime(optimisticRetry.maxExecuteTime());
return optimisticRetryConfig;
}
private Optional<OptimisticRetry> getOptimisticRetry(Annotation[] annotations) {
if (ArrayUtils.isEmpty(annotations)) {
return Optional.empty();
}
for (Annotation anno : annotations) {
if (anno.annotationType().getName().equals(OptimisticRetry.class.getName())) {
return Optional.of((OptimisticRetry) anno);
}
}
return Optional.empty();
}
/**
* 重試配置
*/
@Data
private static class OptimisticRetryConfig implements Serializable {
private static final long serialVersionUID = -182211651320526367L;
/**
* 最大重試次數(shù)
*/
private int maxTryCount;
/**
* 最大執(zhí)行時間
*/
private long maxExecuteTime;
}
}
業(yè)務(wù)代碼中操作,代碼如下:
@Override
@Transactional(rollbackFor = Exception.class)
@OptimisticRetry
public void decreaseStockRemainingAmount(OperateStockDto operateStock) {
// 獲取存儲
WinemallStock existStock = winemallStockMapper.selectByOperateStock(operateStock);
// TODO 庫存是否充足判斷等條件判斷
// 修改庫存數(shù)據(jù)
if (0 == winemallStockMapper.updateStockRemainingAmount(existStock, 1)) {
throw new OptimisticLockingFailureException("decrease stock remaining amount optimistic locking failure!");
}
}
來源:blog.csdn.net/new_com/article/details/103834871
2022-07-26
2022-07-25
2022-07-24
2022-07-23
2022-07-21
如果你覺得這篇文章不錯,那么,下篇通常會更好。備注“公眾號”添加微信好友(微信號:zhuan2quan)。
