前言
分布式锁是由于集群的存在而产生的问题,常见的有三种解决方案
- 基于数据库实现分布式锁
- 基于redis
- 基于zookeeper
基于数据库的性能太差了,这里就简单实现下后两种
redis 实现分布式锁
redis 实现分布式锁的原理是setnx,设置值如果成功就返回1,否则就是0
具体实现:
先定义个接口:
public interface RLock {
public String lock(Long acquireTimeout,Long timeout);
public void unLock(String value);
}
实现类:
package com.redisLock.lock;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.UUID;
public class LockRedis implements RLock{
// redis 线程池
private JedisPool jedisPool;
public LockRedis(JedisPool jedisPool) {
this.jedisPool = jedisPool;
}
// 相同的key 名称,对应的value 定义为锁的 Id
// redis 实现分布式锁。有两个超时时间问题,
/**
* 两个时间:
* 1. 获取锁的超时时间,规定时间内没有得到锁,直接放弃
* 2 , 获取锁之后,key 有对应的有效期,会失效
*/
private String redisLockKey = "redis_lock";
/**
*
* @param acquireTimeout 获取锁的超时时间
* @param timeout 锁失效的时间
*/
public String lock(Long acquireTimeout,Long timeout) {
// 1 建立redis 连接
Jedis con = jedisPool.getResource();
try{
// 2 定义Key 的value ,我们生成订单号,释放锁的时候起作用
String value = UUID.randomUUID().toString();
// 3 定义锁失效时间,redis 认得式 秒,一般传过来的式毫秒
int expireLock = (int) (timeout/1000);
// 4 定义获取锁超时时间
// 5 使用循环机制,不断的尝试获取锁
Long endTime = System.currentTimeMillis()+acquireTimeout;
while (System.currentTimeMillis()<endTime){
// 获取锁,
// 6 使用setNx 命令插入对应的redisLockKey,如果返回1 ,就得到了
if(con.setnx(redisLockKey,value)==1){
con.expire(redisLockKey,expireLock);
return value;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (con!=null)
con.close();
}
// 返回为空就是一直没有获取到,放弃了
return null;
}
// 释放锁
public void unLock(String value) {
Jedis con = jedisPool.getResource();
try{
if (con.get(redisLockKey).equals(value)){
con.del(redisLockKey);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (con!=null)
con.close();
}
}
}
工具类模拟订单生成
package com.IdUtil;
import java.text.SimpleDateFormat;
import java.util.Date;
public class IdUtil {
public static int count=0;
public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
public String getOrderId(){
return simpleDateFormat.format(new Date())+"-"+ count++;
}
}
业务类
package com.redisLock.service;
import com.IdUtil.IdUtil;
import com.redisLock.lock.LockRedis;
import com.redisLock.lock.RLock;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class OrderService implements Runnable{
private static JedisPool pool = null;
static {
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(200);
config.setMaxIdle(2);
config.setMaxWaitMillis(100*1000);
pool = new JedisPool(config,"127.0.0.1",6379,3000);
}
IdUtil idUtil = new IdUtil();
private RLock lockRedis = new LockRedis(pool);
public void secKill(){
String value = lockRedis.lock(30000L,30000L);
if(value==null){
System.out.println(Thread.currentThread().getName()+",获取锁失败");
return;
}
// 获取订单号
try {
Thread.sleep(300);
System.out.println(Thread.currentThread().getName()+"生产订单号"+idUtil.getOrderId());
} catch (Exception e) {
}
// 释放锁
lockRedis.unLock(value);
}
@Override
public void run() {
secKill();
}
}
模拟jvm 测试
package com.redisLock;
import com.redisLock.service.OrderService;
public class Test {
public static void main(String[] args) {
for (int i = 0; i <100 ; i++) {
OrderService thread = new OrderService();
new Thread(thread).start();
}
}
}
zookeeper 实现分布式锁
它的原理是 临时节点,zookeeper 的节点是唯一的,创建成功就代表得到了锁,断开连接,临时节点删除,就释放了锁,同时,基于每个节点发生变化具有监听机制,会通知等待的线程。
先定义接口
package com.zk.lock;
/**
* Created by Administrator on 2019/11/8.
*/
public interface ZLock {
public void lock();
public void unLock();
}
定义模板抽象类
package com.zk.lock;
import com.zk.lock.ZLock;
import org.I0Itec.zkclient.ZkClient;
/**
* Created by Administrator on 2019/11/8.
*/
public abstract class ZAbstractLock implements ZLock {
ZkClient zkClient = null;
String path = "";
public ZAbstractLock(ZkClient zkClient,String path) {
this.zkClient = zkClient;
this.path = path;
}
@Override
public void lock() {
if(!tryLock()){
waitLock(); // util others unlock
lock();
}
}
@Override
public void unLock() {
if (zkClient!=null) zkClient.close();
}
protected abstract void waitLock();
protected abstract boolean tryLock();
}
定义具体实现类
package com.zk.lock;
import com.zk.lock.ZAbstractLock;
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.concurrent.CountDownLatch;
/**
* Created by Administrator on 2019/11/8.
*/
public class ZLockClient extends ZAbstractLock {
private CountDownLatch countDownLatch = null;
public ZLockClient(ZkClient zkClient, String path) {
super(zkClient, path);
}
@Override
protected void waitLock() {
IZkDataListener listener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
countDownLatch.countDown();
}
};
zkClient.subscribeDataChanges(path,listener);
if(zkClient.exists(path)){
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();// 如果不为零,会阻滞
}catch (Exception e){
e.printStackTrace();
}
}
zkClient.unsubscribeDataChanges(path,listener);
}
@Override
protected boolean tryLock() {
try {
zkClient.createEphemeral(path);
return true;
}catch (Exception e){
return false;
}
}
}
id 工具类还用上面的
package com.IdUtil;
import java.text.SimpleDateFormat;
import java.util.Date;
public class IdUtil {
public static int count=0;
public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
public String getOrderId(){
return simpleDateFormat.format(new Date())+"-"+ count++;
}
}
服务类
package com.zk.service;
import com.IdUtil.IdUtil;
import com.zk.lock.ZLock;
import com.zk.lock.ZLockClient;
import org.I0Itec.zkclient.ZkClient;
/**
* Created by Administrator on 2019/11/8.
*/
public class OrderService implements Runnable{
IdUtil idUtil = new IdUtil();
ZkClient zkClient = new ZkClient("127.0.0.1");
ZLock zkeeperLock = new ZLockClient(zkClient,"/zlock");
// 生成订单
public void createOrder(){
zkeeperLock.lock();
// 获取订单号
String orderId = idUtil.getOrderId();
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+"生成订单编号:"+orderId);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
zkeeperLock.unLock();
}
}
@Override
public void run() {
createOrder();
}
}
测试
package com.zk;
import com.zk.service.OrderService;
/**
* Created by Administrator on 2019/11/8.
*/
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
OrderService service = new OrderService();
new Thread(service,"thread"+i).start();
}
}
}
以上都输出:
Thread-99生产订单号2019-11-09-17-55-28-0
Thread-63生产订单号2019-11-09-17-55-29-1
Thread-5生产订单号2019-11-09-17-55-29-2
Thread-70生产订单号2019-11-09-17-55-29-3
Thread-66生产订单号2019-11-09-17-55-30-4
Thread-15生产订单号2019-11-09-17-55-30-5
Thread-41生产订单号2019-11-09-17-55-30-6
Thread-53生产订单号2019-11-09-17-55-30-7
Thread-30生产订单号2019-11-09-17-55-31-8
Thread-85生产订单号2019-11-09-17-55-31-9
Thread-68生产订单号2019-11-09-17-55-31-10
Thread-1生产订单号2019-11-09-17-55-32-11
Thread-38生产订单号2019-11-09-17-55-32-12
Thread-19生产订单号2019-11-09-17-55-32-13
Thread-57生产订单号2019-11-09-17-55-33-14
。。。。。。。。。。。。。。。。。。。。
redis 和zookeeper 实现的不同
- 使用redis实现分布式锁
redis中的set nx 命令,当key不存在时,才能在redis中将key添加成功,利用该属性可以实现分布式锁,并且redis对于key有失效时间,可以控制当某个客户端加锁成功之后挂掉,导致阻塞的问题。
- 使用Zookeeper实现分布式锁
多个客户端在Zookeeper上创建一个相同的临时节点,因为临时节点只能允许一个客户端创建成功,那么只要任意一个客户端创建节点成功,谁就成功的获取到锁,当释放锁后,其他客户端同样道理在Zookeeper节点。