场景介绍
很多互联网场景 (如商品秒杀, 论坛回帖盖楼等), 需要用加锁的方式, 以对某种资源进行顺序访问控制. 如果应用服务集群部署, 则涉及到对分布式应用加锁. 当前分布式加锁主要有三种方式:(磁盘) 数据库, 缓存数据库, Zookeeper. 接下里让我们一起看看加锁实践过程.
加锁实现
- package dcsDemo01;
- import java.util.UUID;
- import redis.clients.jedis.Jedis;
- public class DistributedLock {
- private final String host = "192.168.0.220";
- private final int port = 6379;
- private static final String SUCCESS = "OK";
- private static final String SET_IF_NOT_EXIST = "NX";
- private static final String EXPIRE_TIME = "PX";
- public DistributedLock(){}
- /*
- * @param lockName 锁名
- * @param timeout 获取锁的超时时间
- * @param lockTimeout 锁的有效时间
- * @return 锁的标识
- */
- public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) {
- String ret = null;
- Jedis jedisClient = new Jedis(host, port);
- try {
- String authMsg = jedisClient.auth("Demo@123");
- if (!SUCCESS.equals(authMsg)) {
- System.out.println("AUTH FAILED:" + authMsg);
- }
- String identifier = UUID.randomUUID().toString();
- String lockKey = "DLock:" + lockName;
- long end = System.currentTimeMillis() + timeout;
- while(System.currentTimeMillis() <end) {
- String result = jedisClient.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_TIME, lockTimeout);
- if(SUCCESS.equals(result)) {
- ret = identifier;
- break;
- }
- try {
- Thread.sleep(2);
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- }finally {
- jedisClient.quit();
- jedisClient.close();
- }
- return ret;
- }
- /*
- * @param lockName 锁名
- * @param identifier 锁的标识
- */
- public void releaseLock(String lockName, String identifier) {
- Jedis jedisClient = new Jedis(host, port);
- try {
- String authMsg = jedisClient.auth("Demo@123");
- if (!SUCCESS.equals(authMsg)) {
- System.out.println("AUTH FAILED:" + authMsg);
- }
- String lockKey = "DLock:" + lockName;
- if(identifier.equals(jedisClient.get(lockKey))) {
- jedisClient.del(lockKey);
- }
- }
- catch (Exception e) {
- e.printStackTrace();
- }finally {
- jedisClient.quit();
- jedisClient.close();
- }
- }
- }
测试代码
假设 20 个线程对 10 台 mate10 手机进行抢购:
- package dcsDemo01;
- import java.util.UUID;
- public class CaseTest {
- public static void main(String[] args) {
- ServiceOrder service = new ServiceOrder();
- for (int i = 0; i < 20; i++) {
- ThreadBuy client = new ThreadBuy(service);
- client.start();
- }
- }
- }
- class ServiceOrder {
- private final int MAX = 10;
- DistributedLock DLock = new DistributedLock();
- int n = 10;
- public void handleOder() {
- String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();
- String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);
- System.out.println("正在为用户:" + userName + "处理订单");
- if(n> 0) {
- int num = MAX - n + 1;
- System.out.println("用户:"+ userName + "购买第" + num + "台, 剩余" + (--n) + "台");
- }else {
- System.out.println("用户:"+ userName + "无法购买, 已售罄!");
- }
- DLock.releaseLock("Huawei Mate 10", identifier);
- }
- }
- class ThreadBuy extends Thread {
- private ServiceOrder service;
- public ThreadBuy(ServiceOrder service) {
- this.service = service;
- }
- @Override
- public void run() {
- service.handleOder();
- }
- }
运行结果
配置好实际的缓存实例连接地址, 端口与连接密码, 运行代码, 得到以下结果:
正在为用户: eee56fb7Thread-16 处理订单
用户: eee56fb7Thread-16 购买第 1 台, 剩余 9 台
正在为用户: d6521816Thread-2 处理订单
用户: d6521816Thread-2 购买第 2 台, 剩余 8 台
正在为用户: d7b3b983Thread-19 处理订单
用户: d7b3b983Thread-19 购买第 3 台, 剩余 7 台
正在为用户: 36a6b97aThread-15 处理订单
用户: 36a6b97aThread-15 购买第 4 台, 剩余 6 台
正在为用户: 9a973456Thread-1 处理订单
用户: 9a973456Thread-1 购买第 5 台, 剩余 5 台
正在为用户: 03f1de9aThread-14 处理订单
用户: 03f1de9aThread-14 购买第 6 台, 剩余 4 台
正在为用户: 2c315ee6Thread-11 处理订单
用户: 2c315ee6Thread-11 购买第 7 台, 剩余 3 台
正在为用户: 2b03b7c0Thread-12 处理订单
用户: 2b03b7c0Thread-12 购买第 8 台, 剩余 2 台
正在为用户: 75f25749Thread-0 处理订单
用户: 75f25749Thread-0 购买第 9 台, 剩余 1 台
正在为用户: 26c71db5Thread-18 处理订单
用户: 26c71db5Thread-18 购买第 10 台, 剩余 0 台
正在为用户: c32654dbThread-17 处理订单
用户: c32654dbThread-17 无法购买, 已售罄!
正在为用户: df94370aThread-7 处理订单
用户: df94370aThread-7 无法购买, 已售罄!
正在为用户: 0af94cddThread-5 处理订单
用户: 0af94cddThread-5 无法购买, 已售罄!
正在为用户: e52428a4Thread-13 处理订单
用户: e52428a4Thread-13 无法购买, 已售罄!
正在为用户: 46f91208Thread-10 处理订单
用户: 46f91208Thread-10 无法购买, 已售罄!
正在为用户: e0ca87bbThread-9 处理订单
用户: e0ca87bbThread-9 无法购买, 已售罄!
正在为用户: f385af9aThread-8 处理订单
用户: f385af9aThread-8 无法购买, 已售罄!
正在为用户: 46c5f498Thread-6 处理订单
用户: 46c5f498Thread-6 无法购买, 已售罄!
正在为用户: 935e0f50Thread-3 处理订单
用户: 935e0f50Thread-3 无法购买, 已售罄!
正在为用户: d3eaae29Thread-4 处理订单
用户: d3eaae29Thread-4 无法购买, 已售罄!
不加锁场景
如果注释掉加锁代码, 变成无锁情况, 则抢购无序.
- // 测试类中注释两行用于加锁的代码:
- public void handleOder() {
- String userName = UUID.randomUUID().toString().substring(0,8) + Thread.currentThread().getName();
- // 加锁代码
- //String identifier = DLock.getLockWithTimeout("Huawei Mate 10", 10000, 2000);
- System.out.println("正在为用户:" + userName + "处理订单");
- if(n> 0) {
- int num = MAX - n + 1;
- System.out.println("用户:"+ userName + "够买第" + num + "台, 剩余" + (--n) + "台");
- }else {
- System.out.println("用户:"+ userName + "无法够买, 已售罄!");
- }
- // 加锁代码
- //DLock.releaseLock("Huawei Mate 10", identifier);
- }
注释加锁代码后的运行结果, 可以看出处理过程是无序的:
正在为用户: e04934ddThread-5 处理订单
正在为用户: a4554180Thread-0 处理订单
用户: a4554180Thread-0 购买第 2 台, 剩余 8 台
正在为用户: b58eb811Thread-10 处理订单
用户: b58eb811Thread-10 购买第 3 台, 剩余 7 台
正在为用户: e8391c0eThread-19 处理订单
正在为用户: 21fd133aThread-13 处理订单
正在为用户: 1dd04ff4Thread-6 处理订单
用户: 1dd04ff4Thread-6 购买第 6 台, 剩余 4 台
正在为用户: e5977112Thread-3 处理订单
正在为用户: 4d7a8a2bThread-4 处理订单
用户: e5977112Thread-3 购买第 7 台, 剩余 3 台
正在为用户: 18967410Thread-15 处理订单
用户: 18967410Thread-15 购买第 9 台, 剩余 1 台
正在为用户: e4f51568Thread-14 处理订单
用户: 21fd133aThread-13 购买第 5 台, 剩余 5 台
用户: e8391c0eThread-19 购买第 4 台, 剩余 6 台
正在为用户: d895d3f1Thread-12 处理订单
用户: d895d3f1Thread-12 无法购买, 已售罄!
正在为用户: 7b8d2526Thread-11 处理订单
用户: 7b8d2526Thread-11 无法购买, 已售罄!
正在为用户: d7ca1779Thread-8 处理订单
用户: d7ca1779Thread-8 无法购买, 已售罄!
正在为用户: 74fca0ecThread-1 处理订单
用户: 74fca0ecThread-1 无法购买, 已售罄!
用户: e04934ddThread-5 购买第 1 台, 剩余 9 台
用户: e4f51568Thread-14 购买第 10 台, 剩余 0 台
正在为用户: aae76a83Thread-7 处理订单
用户: aae76a83Thread-7 无法购买, 已售罄!
正在为用户: c638d2cfThread-2 处理订单
用户: c638d2cfThread-2 无法购买, 已售罄!
正在为用户: 2de29a4eThread-17 处理订单
用户: 2de29a4eThread-17 无法购买, 已售罄!
正在为用户: 40a46ba0Thread-18 处理订单
用户: 40a46ba0Thread-18 无法购买, 已售罄!
正在为用户: 211fd9c7Thread-9 处理订单
用户: 211fd9c7Thread-9 无法购买, 已售罄!
正在为用户: 911b83fcThread-16 处理订单
用户: 911b83fcThread-16 无法购买, 已售罄!
用户: 4d7a8a2bThread-4 购买第 8 台, 剩余 2 台
总的来说, 使用 DCS 服务中 Redis 类型的缓存实例实现分布式加锁, 有几大优势:
1, 加锁操作简单, 使用 SET,GET,DEL 等几条简单命令即可实现锁的获取和释放.
2, 性能优越, 缓存数据的读写优于磁盘数据库与 Zookeeper.
3, 可靠性强, DCS 有主备和集群实例类型, 避免单点故障.
以上代码实现仅展示使用 DCS 服务进行加锁访问的便捷性, 具体技术实现需要考虑死锁, 锁的检查等情况, 欢迎点击分布式缓存服务 https://www.huaweicloud.com/product/dcs.html DCS https://www.huaweicloud.com/product/dcs.html 了解更多.
来源: http://www.bubuko.com/infodetail-2662246.html