1. 分布式锁简介
在单体的应用开发场景中,在多线程的环境下,涉及并发同步的时候,为了保证一个代码块在同一时间只能由一个线程访问,我们一般可以使用synchronized语法和ReetrantLock去保证,这实际上是本地锁的方式。
也就是说,在同一个JVM内部,大家往往采用synchronized或者Lock的方式来解决多线程间的安全问题。但在分布式集群工作的开发场景中,在分布式架构下,在JVM之间,那么就需要一种更加高级的锁机制,来处理种跨JVM进程之间的线程安全问题,解决方案就是:使用分布式锁
分布式锁一般有如下特征
- 互斥性: 同一时刻只能有一个线程持有锁
- 重入性: 同一节点上的同一个线程如果获取了锁之后能够再次获取锁
- 锁超时:和J.U.C中的锁一样支持锁超时,防止死锁
- 能够及时从阻塞状态中被唤醒
2. Redisson分布式锁
作为 Java 开发人员,我们若想在程序中集成 Redis,必须使用 Redis 的第三方库。目前大家使用的最多的第三方库是jedis。
和SpringCloud gateway一样,Redisson也是基于Netty实现的,是更高性能的第三方库。 所以,这里推荐大家使用Redission替代 jedis。
2.1 Redisson简介
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还实现了可重入锁(Reentrant Lock)、公平锁(Fair Lock、联锁(MultiLock)、 红锁(RedLock)、 读写锁(ReadWriteLock)等,还提供了许多分布式服务。
Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
- 基本用法
@Slf4j
@SpringBootTest
public class RedissionTest {
@Resource
RedissonClient client;
@Test
public void testRBucketExamples() {
// RList 继承了 java.util.List 接口
RBucket<String> rstring = client.getBucket("redission:test:bucket:string");
rstring.set("this is a string");
RBucket<UserDTO> ruser = client.getBucket("redission:test:bucket:user");
UserDTO dto = new UserDTO();
dto.setToken(UUID.randomUUID().toString());
ruser.set(dto);
System.out.println("string is: " + rstring.get());
System.out.println("dto is: " + ruser.get());
client.shutdown();
}
@Test
public void testListExamples() {
// 默认连接上 127.0.0.1:6379
// RList 继承了 java.util.List 接口
RList<String> nameList = client.getList("redission:test:nameList");
nameList.clear();
nameList.add("张三");
nameList.add("李四");
nameList.add("王五");
nameList.remove(-1);
System.out.println("List size: " + nameList.size());
boolean contains = nameList.contains("李四");
System.out.println("Is list contains name '李四': " + contains);
nameList.forEach(System.out::println);
client.shutdown();
}
@Test
public void testMapExamples() {
// 默认连接上 127.0.0.1:6379
// RMap 继承了 java.util.concurrent.ConcurrentMap 接口
RMap<String, Object> map = client.getMap("redission:test:personalMap");
map.put("name", "张三");
map.put("address", "北京");
map.put("age", new Integer(50));
System.out.println("Map size: " + map.size());
boolean contains = map.containsKey("age");
System.out.println("Is map contains key 'age': " + contains);
String value = String.valueOf(map.get("name"));
System.out.println("Value mapped by key 'name': " + value);
client.shutdown();
}
@Test
public void testLuaExamples() {
// 默认连接上 127.0.0.1:6379
client.getBucket("redission:test:foo").set("bar");
String r = client.getScript().eval(RScript.Mode.READ_ONLY,
"return redis.call('get', 'redission:test:foo')", RScript.ReturnType.VALUE);
System.out.println("foo: " + r);
// 通过预存的脚本进行同样的操作
RScript s = client.getScript();
// 首先将脚本加载到Redis
String sha1 = s.scriptLoad("return redis.call('get', 'redission:test:foo')");
// 返回值 res == 282297a0228f48cd3fc6a55de6316f31422f5d17
System.out.println("sha1: " + sha1);
// 再通过SHA值调用脚本
Future<Object> r1 = client.getScript().evalShaAsync(RScript.Mode.READ_ONLY,
sha1,
RScript.ReturnType.VALUE,
Collections.emptyList());
try {
System.out.println("res: " + r1.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
client.shutdown();
}
@Test
public void testRAtomicLongExamples() {
// 默认连接上 127.0.0.1:6379
RAtomicLong atomicLong = client.getAtomicLong("redission:test:myLong");
// 线程数
final int threads = 10;
// 每条线程的执行轮数
final int turns = 1000;
ExecutorService pool = Executors.newFixedThreadPool(threads);
long start = System.currentTimeMillis();
for (int i = 0; i < threads; i++) {
pool.submit(() ->
{
try {
for (int j = 0; j < turns; j++) {
atomicLong.incrementAndGet();
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
ThreadUtil.sleepSeconds(5);
long sum = atomicLong.get();
System.out.println("atomicLong: " + sum);
//输出统计结果
float time = System.currentTimeMillis() - start;
System.out.println("运行的时长为:" + time);
System.out.println("每一次执行的时长为:" + time / sum);
client.shutdown();
}
@Test
public void testRLongAdderExamples() {
// 默认连接上 127.0.0.1:6379
RLongAdder longAdder = client.getLongAdder("redission:test:myLongAdder");
// 线程数
final int threads = 10;
// 每条线程的执行轮数
final int turns = 1000;
ExecutorService pool = Executors.newFixedThreadPool(threads);
long start = System.currentTimeMillis();
for (int i = 0; i < threads; i++) {
pool.submit(() ->
{
try {
for (int j = 0; j < turns; j++) {
longAdder.increment();
}
} catch (Exception e) {
e.printStackTrace();
}
});
}
ThreadUtil.sleepSeconds(5);
System.out.println("longAdder: " + longAdder.sum());
long sum = longAdder.sum();
//输出统计结果
float time = System.currentTimeMillis() - start;
System.out.println("运行的时长为:" + time);
System.out.println("每一次执行的时长为:" + time / sum);
client.shutdown();
}
}