DCS实践干货:使用Redis实现分布式锁
场景介绍
很多互联网场景(如商品秒杀,论坛回帖盖楼等),需要用加锁的方式,以对某种资源进行顺序访问控制。如果应用服务集群部署,则涉及到对分布式应用加锁。当前分布式加锁主要有三种方式:(磁盘)数据库、缓存数据库、Zookeeper。接下里让我们一起看看加锁实践过程。
加锁实现
1 package dcsDemo01;
2
3 import java.util.UUID;
4
5 import redis.clients.jedis.Jedis;
6
7 public class DistributedLock {
8 private final String host = "192.168.0.220";
9 private final int port = 6379;
10
11 private static final String SUCCESS = "OK";
12 private static final String SET_IF_NOT_EXIST = "NX";
13 private static final String EXPIRE_TIME = "PX";
14
15 public DistributedLock(){}
16
17 /*
18 * @param lockName 锁名
19 * @param timeout 获取锁的超时时间
20 * @param lockTimeout 锁的有效时间
21 * @return 锁的标识
22 */
23 public String getLockWithTimeout(String lockName, long timeout, long lockTimeout) {
24 String ret = null;
25 Jedis jedisClient = new Jedis(host, port);
26
27 try {
28 String authMsg = jedisClient.auth("Demo@123");
29 if (!SUCCESS.equals(authMsg)) {
30 System.out.println("AUTH FAILED: " + authMsg);
31 }
32
33 String identifier = UUID.randomUUID().toString();
34 String lockKey = "DLock:" + lockName;
35 long end = System.currentTimeMillis() + timeout;
36
37 while(System.currentTimeMillis() < end) {
38 String result = jedisClient.set(lockKey, identifier, SET_IF_NOT_EXIST, EXPIRE_TIME, lockTimeout);
39 if(SUCCESS.equals(result)) {
40 ret = identifier;
41 break;
42 }
43
44 try {
45 Thread.sleep(2);
46 } catch (InterruptedException e) {
47 Thread.currentThread().interrupt();
48 }
49 }
50 }
51 catch (Exception e) {
52 e.printStackTrace();
53 }finally {
54 jedisClient.quit();
55 jedisClient.close();
56 }
57
58 return ret;
59 }
60
61 /*
62 * @param lockName 锁名
63 * @param identifier 锁的标识
64 */
65 public void releaseLock(String lockName, String identifier) {
66 Jedis jedisClient = new Jedis(host, port);
67
68 try {
69 String authMsg = jedisClient.auth("Demo@123");
70 if (!SUCCESS.equals(authMsg)) {
71 System.out.println("AUTH FAILED: " + authMsg);
72 }
73
74 String lockKey = "DLock:" + lockName;
75 if(identifier.equals(jedisClient.get(lockKey))) {
76 jedisClient.del(lockKey);
77 }
78 }
79 catch (Exception e) {
80 e.printStackTrace();
81 }finally {
82 jedisClient.quit();
83 jedisClient.close();
84 }
85 }
86 }
测试代码
假设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服务进行加锁访问的便捷性,具体技术实现需要考虑死锁、锁的检查等情况,欢迎点击分布式缓存服务DCS了解更多。
DCS实践干货:使用Redis实现分布式锁的更多相关文章
- 基于redis的分布式锁的分析与实践
前言:在分布式环境中,我们经常使用锁来进行并发控制,锁可分为乐观锁和悲观锁,基于数据库版本戳的实现是乐观锁,基于redis或zookeeper的实现可认为是悲观锁了.乐观锁和悲观锁最根本的区别在于 ...
- 不用找了,基于 Redis 的分布式锁实战来了!
Java技术栈 www.javastack.cn 优秀的Java技术公众号 作者:菜蚜 my.oschina.net/wnjustdoit/blog/1606215 前言:在分布式环境中,我们经常使用 ...
- Redis之分布式锁
目录 一.加锁原因 二.原子操作 三.分布式锁 四.分布式锁常见问题 一.加锁原因 在一些比较高并发的业务场景,经常听到通过加锁的方法实现线程安全. 下面简单介绍一下 1.1 加锁方式 数据库锁 数据 ...
- 基于redis的分布式锁实现
1.分布式锁介绍 在计算机系统中,锁作为一种控制并发的机制无处不在. 单机环境下,操作系统能够在进程或线程之间通过本地的锁来控制并发程序的行为.而在如今的大型复杂系统中,通常采用的是分布式架构提供服务 ...
- 基于redis 实现分布式锁的方案
在电商项目中,经常有秒杀这样的活动促销,在并发访问下,很容易出现上述问题.如果在库存操作上,加锁就可以避免库存卖超的问题.分布式锁使分布式系统之间同步访问共享资源的一种方式 基于redis实现分布式锁 ...
- 用Redis构建分布式锁-RedLock(真分布)
在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但是这些库实现的方式差别很大,而且很多简单的实现其实只需采用稍微增 ...
- 用Redis实现分布式锁 与 实现任务队列(转)
这一次总结和分享用Redis实现分布式锁 与 实现任务队列 这两大强大的功能.先扯点个人观点,之前我看了一篇博文说博客园的文章大部分都是分享代码,博文里强调说分享思路比分享代码更重要(貌似大概是这个意 ...
- 利用多写Redis实现分布式锁原理与实现分析(转)
利用多写Redis实现分布式锁原理与实现分析 一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...
- Redis实现分布式锁
http://redis.io/topics/distlock 在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段. 有很多三方库和文章描述如何用Redis实现一个分布式锁管理器,但 ...
- 基于redis的分布式锁
<?php /** * 基于redis的分布式锁 * * 参考开源代码: * http://nleach.com/post/31299575840/redis-mutex-in-php * * ...
随机推荐
- 【C++】智能指针简述(三):scoped_ptr
在介绍scoped_ptr之前,我们先回顾一下前两篇文章的内容. 首先,智能指针采用RAII机制,通过对象来管理指针,构造对象时,完成资源的初始化;析构对象时,对资源进行清理及汕尾. auto_ptr ...
- 调度kettle使用taskctl我该怎么部署
转载自: http://www.taskctl.com/forum/detail_133.html 最近在QQ群看到有小伙伴在问用taskctl调度kettle,都要安装些什么呢?都支持哪些平台上的k ...
- 如何利用CSS中的ime-mode用来控制页面上文本框中的全角/半角输入
css 之 ime-mode语法:ime-mode : auto | active | inactive | disabled取值:auto : 默认值.不影响ime的状态.与不指定 ime-mode ...
- jstree的基本使用例子
var menu = (function() { var _menu = {data:{}, initMenu : function() { $.jstree.defaults.core.themes ...
- 系统信号-signal.h
#define SIGSEGV 11 /* segmentation violation */ #define SIGSYS 12 /* bad argument to system call */ ...
- 梦想CAD控件关于标注的系统变量说明
主要用到函数说明: IMxDrawDimension::SetDimVarDouble 设置标注属性的实数类型变量值,详细说明如下: 参数 说明 [in] LONG iType 该属性的类形值 dVa ...
- HDU多校Round 4
Solved:3 rank:405................................. B. Harvest of Apples 知道了S(n,m) 可以o(1)的求S(n - 1, m ...
- nginx配置X-Forwarded-For 防止伪造ip
网上常见nginx配置ip请求头 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 风险: 用于可以通过自己设置请求头来伪造ip ...
- php第二十二节课
AJAX <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...
- UVA - 11212 Editing a Book (IDA*搜索)
题目: 给出n(1<n<10)个数字组成的序列,每次操作可以选取一段连续的区间将这个区间之中的数字放到其他任意位置.问最少经过多少次操作可将序列变为1,2,3……n. 思路: 利用IDA* ...