Authored by LUOXC

refactor

package com.yohoufo.common.cache;
import com.google.common.collect.Lists;
import com.yoho.core.redis.cluster.annotation.Redis;
import com.yoho.core.redis.cluster.operations.nosync.YHRedisTemplate;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisLock {
private static final Long RELEASE_SUCCESS = 1L;
private static final String ACQUIRE_SUCCESS = "OK";
@Redis("gwNoSyncRedis")
private YHRedisTemplate redis;
public boolean acquire(RedisKeyBuilder keyBuilder, String value, final long timeout, final TimeUnit unit) {
try {
String script = "return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) ";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
String key = keyBuilder.getKey();
String result = redis.getStringRedisTemplate().execute(redisScript, Lists.newArrayList(key),
value, String.valueOf(unit.toMillis(timeout)));
return ACQUIRE_SUCCESS.equals(result);
} catch (Exception e) {
return false;
}
}
public void release(RedisKeyBuilder key, String value) {
try {
deleteKeyIfValueEquals(key, value);
} catch (Exception e) {
try {
deleteKeyIfValueEquals(key, value);
} catch (Exception e1) {
log.warn("release lock fail, key is {} value is {}", key, value);
}
}
}
private boolean deleteKeyIfValueEquals(RedisKeyBuilder keyBuilder, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript(script, Long.class);
String key = keyBuilder.getKey();
Long result = redis.getStringRedisTemplate().execute(redisScript, Lists.newArrayList(key), value);
if (RELEASE_SUCCESS.equals(result)) {
log.info("release lock ok, key is {} value is {}", key, value);
return true;
} else {
log.info("release lock ko, key is {} value is {}", key, value);
return false;
}
}
}
package com.yohoufo.common.lock;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* Redis分布式锁
* 使用 SET resource-name anystring NX EX max-lock-time 实现
* <p>
* 该方案在 Redis 官方 SET 命令页有详细介绍。
* http://doc.redisfans.com/string/set.html
* <p>
* 在介绍该分布式锁设计之前,我们先来看一下在从 Redis 2.6.12 开始 SET 提供的新特性,
* 命令 SET key value [EX seconds] [PX milliseconds] [NX|XX],其中:
* <p>
* EX seconds — 以秒为单位设置 key 的过期时间;
* PX milliseconds — 以毫秒为单位设置 key 的过期时间;
* NX — 将key 的值设为value ,当且仅当key 不存在,等效于 SETNX。
* XX — 将key 的值设为value ,当且仅当key 存在,等效于 SETEX。
* <p>
* 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
* <p>
* 客户端执行以上的命令:
* <p>
* 如果服务器返回 OK ,那么这个客户端获得锁。
* 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
*/
@Slf4j
class DefaultRedisLock implements RedisLock {
private static final String ACQUIRE_OK = "OK";
private static final Long RELEASE_OK = 1L;
private RedisTemplate<String, String> redisTemplate;
private String key;
/**
* 锁的有效时间(s)
*/
private long expireTimeMillis;
/**
* lua script
*/
private String lockValue;
/**
* 锁标记
*/
private boolean locked;
final Random random = new Random();
DefaultRedisLock(Builder builder) {
this.redisTemplate = builder.redisTemplate;
this.key = builder.key;
this.expireTimeMillis = builder.expireTimeMillis;
}
public boolean lock() {
updateLockValue();
while (true) {
if (acquire(lockValue, expireTimeMillis)) {
locked = true;
return true;
}
sleep(10, 50000);
}
}
public boolean lock(long timeout, TimeUnit timeUnit) {
updateLockValue();
// 请求锁超时时间,纳秒
long timeoutNanos = timeUnit.toNanos(timeout);
// 系统当前时间,纳秒
long nowNanoTime = System.nanoTime();
while ((System.nanoTime() - nowNanoTime) < timeoutNanos) {
if (acquire(lockValue, expireTimeMillis)) {
locked = true;
// 上锁成功结束请求
return true;
}
// 每次请求等待一段时间
sleep(10, 50000);
}
return locked;
}
public boolean tryLock() {
updateLockValue();
locked = acquireOr(lockValue, expireTimeMillis, false);
return locked;
}
public boolean unlock() {
if (locked) {
if (releaseOr(lockValue, false)) {
locked = false;
return true;
} else {
return false;
}
}
return true;
}
private boolean acquire(String value, long expireTimeMillis) {
String script = "return redis.call('SET', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) ";
RedisScript<String> redisScript = new DefaultRedisScript<>(script, String.class);
String result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value, String.valueOf(expireTimeMillis));
if (ACQUIRE_OK.equals(result)) {
log.info("acquire lock ok, key is {} value is {}", key, value);
return true;
} else {
log.info("acquire lock ko, key is {} value is {}", key, value);
return false;
}
}
private boolean acquireOr(String value, long expireTimeMillis, boolean defaultValue) {
try {
return acquire(value, expireTimeMillis);
} catch (Exception e) {
log.info("release lock ko, key is {} value is {}", key, value, e);
return defaultValue;
}
}
private boolean release(String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
RedisScript<Long> redisScript = new DefaultRedisScript(script, Long.class);
Long result = redisTemplate.execute(redisScript, Lists.newArrayList(key), value);
if (RELEASE_OK.equals(result)) {
log.info("release lock ok, key is {} value is {}", key, value);
return true;
} else {
log.info("release lock ko, key is {} value is {}", key, value);
return false;
}
}
private boolean releaseOr(String value, boolean defaultValue) {
try {
return release(value);
} catch (Exception e) {
// try once
try {
return release(value);
} catch (Exception e2) {
log.info("release lock ko, key is {} value is {}", key, value, e2);
return defaultValue;
}
}
}
private void updateLockValue() {
lockValue = UUID.randomUUID().toString();
}
/**
* 线程等待时间
*
* @param millis 毫秒
* @param nanos 纳秒
*/
private void sleep(long millis, int nanos) {
try {
Thread.sleep(millis, random.nextInt(nanos));
} catch (InterruptedException e) {
log.info("lock {} sleep interrupted ", key, e);
}
}
}
\ No newline at end of file
... ...
package com.yohoufo.common.lock;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
public interface RedisLock {
/**
* 阻塞方式的获取锁
*
* @return 是否成功获得锁
*/
boolean lock();
/**
* 获取锁 超时返回
*
* @return
*/
boolean lock(long timeout, TimeUnit timeUnit);
/**
* 尝试获取锁 立即返回
*
* @return 是否成功获得锁
*/
boolean tryLock();
/**
* 解锁
*/
boolean unlock();
static Builder builder() {
return new Builder();
}
static RedisLock create(RedisTemplate redisTemplate, String key, long expireTime, TimeUnit timeUnit) {
return builder()
.redisTemplate(redisTemplate)
.key(key)
.expireTime(expireTime, timeUnit)
.build();
}
class Builder {
RedisTemplate redisTemplate;
String key;
long expireTimeMillis;
private Builder() {
}
public Builder redisTemplate(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
return this;
}
public Builder key(String key) {
this.key = key;
return this;
}
public Builder expireTime(long expireTime, TimeUnit timeUnit) {
this.expireTimeMillis = timeUnit.toMillis(expireTime);
return this;
}
public RedisLock build() {
return new DefaultRedisLock(this);
}
}
}
... ...
package com.yohoufo.common.lock;
import com.yoho.core.redis.cluster.annotation.Redis;
import com.yoho.core.redis.cluster.operations.nosync.YHRedisTemplate;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
public class RedisLockFactory {
@Redis("gwNoSyncRedis")
private YHRedisTemplate redis;
public RedisLock newLock(RedisKeyBuilder keyBuilder, final long timeout, final TimeUnit unit) {
return RedisLock.create(redis.getStringRedisTemplate(), keyBuilder.getKey(), timeout, unit);
}
}
... ...
... ... @@ -5,11 +5,9 @@ import com.yohoufo.common.ApiResponse;
import com.yohoufo.common.annotation.IgnoreSession;
import com.yohoufo.common.annotation.IgnoreSignature;
import com.yohoufo.common.annotation.InnerApi;
import com.yohoufo.common.cache.RedisLock;
import com.yohoufo.common.exception.UfoServiceException;
import com.yohoufo.common.lock.RedisLock;
import com.yohoufo.common.lock.RedisLockFactory;
import com.yohoufo.common.utils.ExecutorServiceUtils;
import com.yohoufo.common.utils.RandomUtil;
import com.yohoufo.order.model.QuickDeliverOrderContext;
import com.yohoufo.order.model.request.TransferMoneyRequest;
import com.yohoufo.order.service.impl.TransferService;
import com.yohoufo.order.utils.NamedThreadFactory;
... ... @@ -36,7 +34,7 @@ public class OrderHelpController {
private TransferService transferService;
@Autowired
private RedisLock redisLock;
private RedisLockFactory redisLockFactory;
/**
* 转账
... ... @@ -64,7 +62,7 @@ public class OrderHelpController {
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000), NamedThreadFactory.newThreadFactory("test"));
IntStream.range(0, 10)
IntStream.range(0, 100)
.forEach(i -> executorService.submit(() -> lockTest(key)));
ExecutorServiceUtils.shutdownAndAwaitTermination(executorService);
return new ApiResponse.ApiResponseBuilder()
... ... @@ -77,18 +75,18 @@ public class OrderHelpController {
RedisKeyBuilder redisLockKey = RedisKeyBuilder.newInstance()
.appendFixed("ufo:order:lock:test:")
.appendVar(key);
String value = UUID.randomUUID().toString();
if (redisLock.acquire(redisLockKey, value, 5, TimeUnit.SECONDS)) {
RedisLock lock = redisLockFactory.newLock(redisLockKey,5, TimeUnit.SECONDS);
if (lock.tryLock()) {
try {
log.info("lock test {}, {} i got the lock", key, value);
Thread.sleep(RandomUtils.nextInt(4000, 5000));
log.info("lock test {}, i got the lock", key);
Thread.sleep(RandomUtils.nextInt(10, 50));
} catch (InterruptedException e) {
} finally {
redisLock.release(redisLockKey, value);
lock.unlock();
}
} else {
log.info("lock test {}, {} i not got the lock", key, value);
log.info("lock test {}, i not got the lock", key);
}
}
... ...
... ... @@ -16,9 +16,9 @@ import com.yohobuy.ufo.model.order.resp.PageResp;
import com.yohobuy.ufo.model.order.resp.SellerGoodsPageResp;
import com.yohoufo.common.alarm.EventBusPublisher;
import com.yohoufo.common.alarm.SmsAlarmEvent;
import com.yohoufo.common.cache.RedisLock;
import com.yohoufo.common.lock.RedisLock;
import com.yohoufo.common.lock.RedisLockFactory;
import com.yohoufo.common.exception.UfoServiceException;
import com.yohoufo.common.utils.StringUtil;
import com.yohoufo.dal.order.SellerOrderGoodsViewMapper;
import com.yohoufo.dal.order.model.SellerOrderGoods;
import com.yohoufo.order.model.QuickDeliverOrderContext;
... ... @@ -34,8 +34,6 @@ import com.yohoufo.order.service.seller.orderMeta.SellerOrderMetaService;
import com.yohoufo.order.service.support.codegenerator.OrderCodeGenerator;
import com.yohoufo.order.utils.LoggerUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
... ... @@ -85,19 +83,19 @@ public class QuickDeliverGoodsService {
private SellerNoticeFacade sellerNoticeFacade;
@Autowired
private RedisLock redisLock;
private RedisLockFactory redisLockFactory;
public DepositPublishResp publish(QuickDeliverOrderSubmitReq req) {
RedisKeyBuilder redisLockKey = RedisKeyBuilder.newInstance()
.appendFixed("ufo:order:lock:publishQuickDeliverGoods:")
.appendVar(req.getUid() + "-" + req.getStorageId());
String value = UUID.randomUUID().toString();
if (redisLock.acquire(redisLockKey, value,5, TimeUnit.SECONDS)) {
RedisLock lock = redisLockFactory.newLock(redisLockKey,5,TimeUnit.SECONDS);
if (lock.tryLock()) {
try {
QuickDeliverOrderContext qdoc = quickDeliverPublishProcessor.buildPublishCtx(req);
return doPublish(qdoc);
} finally {
redisLock.release(redisLockKey,value);
lock.unlock();
}
} else {
logger.warn("storage has publishing , {} ", redisLockKey);
... ...
... ... @@ -4,9 +4,9 @@ import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.yoho.core.config.ConfigReader;
import com.yoho.core.redis.cluster.annotation.Redis;
import com.yoho.core.redis.cluster.operations.nosync.YHValueOperations;
import com.yoho.core.redis.cluster.operations.serializer.RedisKeyBuilder;
import com.yohoufo.common.lock.RedisLock;
import com.yohoufo.common.lock.RedisLockFactory;
import com.yohoufo.common.utils.DateUtil;
import com.yohoufo.dal.order.BuyerOrderGoodsMapper;
import com.yohoufo.dal.order.SellerOrderMetaMapper;
... ... @@ -59,8 +59,8 @@ public class HkAccountSettlement {
@Autowired
private SellerOrderMetaMapper sellerOrderMetaMapper;
@Redis("gwNoSyncRedis")
private YHValueOperations valueOperations;
@Autowired
private RedisLockFactory redisLockFactory;
public void settle(Integer uid) {
... ... @@ -69,12 +69,11 @@ public class HkAccountSettlement {
RedisKeyBuilder redisLockKey = RedisKeyBuilder.newInstance()
.appendFixed("ufo:order:hkAccount:settle:")
.appendVar(uid + "-" + dealTime);
String redisLockValue = "Y";
if (redisLockValue.equals(valueOperations.get(redisLockKey))) {
RedisLock redisLock = redisLockFactory.newLock(redisLockKey,5, TimeUnit.SECONDS);
if (!redisLock.tryLock()) {
log.info("{} settle fail, it already in the process", uid);
return;
}
valueOperations.set(redisLockKey, redisLockValue, 5, TimeUnit.SECONDS);
// 没有要打款的账单
if (!tryLock(uid, dealTime)) {
log.info("{} settle fail, it already in the process", uid);
... ... @@ -113,6 +112,7 @@ public class HkAccountSettlement {
log.info("{} settle, sun income is {} but limit {}", uid, sumIncome, limitAmount);
releaseLockWithRetry(uid, dealTime);
}
redisLock.unlock();
}
private List<TradeBillResult> getTradeBillResults(Integer uid, List<TradeBills> tradeBills) {
... ...