分布式锁场景和介绍:
分布式锁演进-基本原理
分布式锁演进-阶段一
分布式锁演进-阶段二
分布式锁演进-阶段三
分布式锁演进-阶段四
分布式锁演进-阶段五
分布式锁一般有如下的特点:
互斥性: 同一时刻只能有一个线程持有锁
可重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
高性能和高可用: 加锁和解锁需要高效,同时也需要保证高可用,防止分布式锁失效
具备阻塞和非阻塞性:能够及时从阻塞状态中被唤醒
分布式锁模板类:
/**
* 分布式锁模板类
* Created by sunyujia@aliyun.com on 2016/2/23.
*/
public interface DistributedLockTemplate {
/**
*
* @param lockId 锁id(对应业务唯一ID)
* @param timeout 单位毫秒
* @param callback 回调函数
* @return
*/
public Object execute(String lockId,int timeout,Callback callback);
}
public interface DistributedReentrantLock {
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException;
public void unlock();
}
回调接口:
/**
* Created by sunyujia@aliyun.com on 2016/2/23.
*/
public interface Callback {
public Object onGetLock() throws InterruptedException;
public Object onTimeout() throws InterruptedException;
}
锁实现类
/**
* Created by sunyujia@aliyun.com on 2016/2/26.
*/
public class RedisDistributedLockTemplate implements DistributedLockTemplate {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(RedisDistributedLockTemplate.class);
private JedisPool jedisPool;
public RedisDistributedLockTemplate(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
@Override
public Object execute(String lockId, int timeout, Callback callback) {
RedisReentrantLock distributedReentrantLock = null;
boolean getLock=false;
try {
distributedReentrantLock = new RedisReentrantLock(jedisPool,lockId);
if(distributedReentrantLock.tryLock(new Long(timeout), TimeUnit.MILLISECONDS)){
getLock=true;
return callback.onGetLock();
}else{
return callback.onTimeout();
}
}catch(InterruptedException ex){
log.error(ex.getMessage(), ex);
Thread.currentThread().interrupt();
}catch (Exception e) {
log.error(e.getMessage(), e);
}finally {
if(getLock) {
distributedReentrantLock.unlock();
}
}
return null;
}
}
/**
* Created by sunyujia@aliyun.com on 2016/2/26.
*/
public class HHRedisDistributedLockTemplate implements DistributedLockTemplate {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(HHRedisDistributedLockTemplate.class);
private JedisPool jedisPool;
private Jedis jedis ;
public HHRedisDistributedLockTemplate(JedisPool jedisPool) {
this.jedisPool = jedisPool;
this.jedis = jedisPool.getResource();
}
public boolean tryLock(String key, String ran, int timout){
System.out.println("tryLock key:"+key+"ran:"+ran);
Long val = jedis.setnx(key, ran);
System.out.println("tryLock key:"+key+"ran:"+ran+"val:"+val);
jedis.pexpire(key,timout);
return jedis.get(key).equals(ran);
}
public boolean unLock(String key, String value){
if (value.equals(jedis.get(key))){
jedis.del(key);
System.out.println("unLock key:"+key+"val:"+value);
}else{
jedis.close();
System.out.println("unlockERROR:"+"key:"+key+"expectVal:"+value+"val:"+jedis.get(key));
return false;
}
jedis.close();
return true;
}
@Override
public Object execute(String lockId, int timeout, Callback callback) {
String ran = Thread.currentThread().getName();
boolean getLock=false;
try {
if(tryLock(lockId,ran, timeout)){
getLock=true;
return callback.onGetLock();
}else{
return callback.onTimeout();
}
}catch(InterruptedException ex){
log.error(ex.getMessage(), ex);
Thread.currentThread().interrupt();
}catch (Exception e) {
log.error(e.getMessage(), e);
}finally {
if(getLock) {
unLock(lockId,ran);
}else{
jedis.close();
}
}
return null;
}
}
lua脚本加锁和解锁
class RedisLockInternals {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(RedisLockInternals.class);
private JedisPool jedisPool;
/**
* 重试等待时间
*/
private int retryAwait=300;
private int lockTimeout=2000;
RedisLockInternals(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
String tryRedisLock(String lockId,long time, TimeUnit unit) {
final long startMillis = System.currentTimeMillis();
final Long millisToWait = (unit != null) ? unit.toMillis(time) : null;
String lockValue=null;
while (lockValue==null){
lockValue=createRedisKey(lockId);
if(lockValue!=null){
break;
}
if(System.currentTimeMillis()-startMillis-retryAwait>millisToWait){
break;
}
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(retryAwait));
}
return lockValue;
}
private String createRedisKey(String lockId) {
Jedis jedis = null;
boolean broken = false;
try {
String value=lockId+randomId(1);
jedis = jedisPool.getResource();
即:
// - 获取锁(unique_value可以是UUID等)
// SET resource_name unique_value NX PX 30000
//
// - 释放锁(lua脚本中,一定要比较value,防止误解锁)
// if redis.call("get",KEYS[1]) == ARGV[1] then
// return redis.call("del",KEYS[1])
//else
// return 0
// end
String luaScript = ""
+ "\nlocal r = tonumber(redis.call('SETNX', KEYS[1],ARGV[1]));"
+ "\nredis.call('PEXPIRE',KEYS[1],ARGV[2]);"
+ "\nreturn r";
List<String> keys = new ArrayList<String>();
keys.add(lockId);
List<String> args = new ArrayList<String>();
args.add(value);
args.add(lockTimeout+"");
Long ret = (Long) jedis.eval(luaScript, keys, args);
if( new Long(1).equals(ret)){
return value;
}
}finally {
if(jedis!=null) jedis.close();
}
return null;
}
void unlockRedisLock(String key,String value) {
Jedis jedis = null;
boolean broken = false;
try {
//String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return
//redis.call('del', KEYS[1]) else return 0 end";
jedis = jedisPool.getResource();
String luaScript=""
+"\nlocal v = redis.call('GET', KEYS[1]);"
+"\nlocal r= 0;"
+"\nif v == ARGV[1] then"
+"\nr =redis.call('DEL',KEYS[1]);"
+"\nend"
+"\nreturn r";
List<String> keys = new ArrayList<String>();
keys.add(key);
List<String> args = new ArrayList<String>();
args.add(value);
Object r=jedis.eval(luaScript, keys, args);
} finally {
if(jedis!=null) jedis.close();
}
}
private final static char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y',
'Z'};
private String randomId(int size) {
char[] cs = new char[size];
for (int i = 0; i < cs.length; i++) {
cs[i] = digits[ThreadLocalRandom.current().nextInt(digits.length)];
}
return new String(cs);
}
public static void main(String[] args){
System.out.println(System.currentTimeMillis());
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(300));
System.out.println(System.currentTimeMillis());
}
}
分布式速度限制
/**
* 分布式速率限制 例如:限制n秒钟请求x次
*/
public class AccessSpeedLimit {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(AccessSpeedLimit.class);
private JedisPool jedisPool;
public AccessSpeedLimit(){
}
public AccessSpeedLimit(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
public JedisPool getJedisPool() {
return jedisPool;
}
public void setJedisPool(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
/**
* 针对资源key,每seconds秒最多访问maxCount次,超过maxCount次返回false
*
* @param key
* @param seconds
* @param limitCount
* @return
*/
public boolean tryAccess(String key,int seconds,int limitCount){
LimitRule limitRule=new LimitRule();
limitRule.setLimitCount(limitCount);
limitRule.setSeconds(seconds);
return tryAccess(key,limitRule);
}
/**
* 针对资源key,每limitRule.seconds秒最多访问limitRule.limitCount,超过limitCount次返回false
* 超过lockCount 锁定lockTime
* @param key
* @param limitRule
* @return
*/
public boolean tryAccess(String key,LimitRule limitRule){
String newKey="Limit:"+key;
Jedis jedis = null;
boolean broken = false;
long count=-1;
try {
jedis = jedisPool.getResource();
List<String> keys = new ArrayList<String>();
keys.add(newKey);
List<String> args = new ArrayList<String>();
args.add(Math.max(limitRule.getLimitCount(), limitRule.getLockCount())+"");
args.add(limitRule.getSeconds()+"");
args.add(limitRule.getLockCount()+"");
args.add(limitRule.getLockTime()+"");
count=Long.parseLong(jedis.eval(buildLuaScript(limitRule),keys,args)+"");
return count<=limitRule.getLimitCount();
} finally {
if(jedis!=null)jedis.close();
}
}
private String buildLuaScript(LimitRule limitRule){
StringBuilder lua=new StringBuilder();
lua.append("\nlocal c");
lua.append("\nc = redis.call('get',KEYS[1])");
lua.append("\nif c and tonumber(c) > tonumber(ARGV[1]) then");
lua.append("\nreturn c;");
lua.append("\nend");
lua.append("\nc = redis.call('incr',KEYS[1])");
lua.append("\nif tonumber(c) == 1 then");
lua.append("\nredis.call('expire',KEYS[1],ARGV[2])");
lua.append("\nend");
if(limitRule.enableLimitLock()){
lua.append("\nif tonumber(c) > tonumber(ARGV[3]) then");
lua.append("\nredis.call('expire',KEYS[1],ARGV[4])");
lua.append("\nend");
}
lua.append("\nreturn c;");
return lua.toString();
}
}
测试分布式锁
public class AccessSpeedLimitTest {
@Test
public void test1() throws InterruptedException {
JedisPool jp=new JedisPool("localhost",6379);
AccessSpeedLimit accessSpeedLimit=new AccessSpeedLimit(jp);
SimpleDateFormat sdf=new SimpleDateFormat(" mm:ss");
while(true){
//10.0.0.1这个ip每1秒钟最多访问5次if块内代码.
if(accessSpeedLimit.tryAccess("10.0.0.1", 1,5)){
System.out.println("yes"+sdf.format(new Date()));
}else{
System.out.println("no"+sdf.format(new Date()));
}
Thread.sleep(100);
}
}
@Test
public void test2() throws InterruptedException {
JedisPool jp=new JedisPool("127.0.0.1",6379);
final RedisDistributedLockTemplate template=new RedisDistributedLockTemplate(jp);
LimitRule limitRule=new LimitRule();
limitRule.setSeconds(1);
limitRule.setLimitCount(5);
limitRule.setLockCount(7);
limitRule.setLockTime(2);
AccessSpeedLimit accessSpeedLimit=new AccessSpeedLimit(jp);
SimpleDateFormat sdf=new SimpleDateFormat(" mm:ss");
while(true){
//10.0.0.1这个ip每1秒钟最多访问5次if块内代码.1秒超过10次后,锁定2秒,2秒内无法访问.
if(accessSpeedLimit.tryAccess("10.0.0.1",limitRule)){
System.out.println("yes"+sdf.format(new Date()));
}else{
System.out.println("no"+sdf.format(new Date()));
}
Thread.sleep(100);
}
}
}