分布式锁
看门狗防止死锁
redission初始化
spring环境
<!--整合redission框架start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.5</version>
</dependency>
<!--整合redission框架enc-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
yml配置
spring:
#redisson配置,默认连接库0,无密码只配置连接地址即可
redis:
host: 127.0.0.1
database: 0 #如果没有设置密码,下面这个需要删除
password:
非spring环境
redission使用案例_毕业即失业吗的博客
依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>version</version>
</dependency>
初始化代码
//单机
RedissonClient redisson = Redisson.create();
Config config = new Config();
config.useSingleServer().setAddress("myredisserver:6379");
RedissonClient redisson = Redisson.create(config); //主从
Config config = new Config();
config.useMasterSlaveServers()
.setMasterAddress("127.0.0.1:6379")
.addSlaveAddress("127.0.0.1:6389", "127.0.0.1:6332", "127.0.0.1:6419")
.addSlaveAddress("127.0.0.1:6399");
RedissonClient redisson = Redisson.create(config); //哨兵
Config config = new Config();
config.useSentinelServers()
.setMasterName("mymaster")
.addSentinelAddress("127.0.0.1:26389", "127.0.0.1:26379")
.addSentinelAddress("127.0.0.1:26319");
RedissonClient redisson = Redisson.create(config); //集群
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // cluster state scan interval in milliseconds
.addNodeAddress("127.0.0.1:7000", "127.0.0.1:7001")
.addNodeAddress("127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
普通锁
@Controller
public class TestRedissonClient {
@Autowired
RedissonClient redisson;
@ResponseBody
@GetMapping("/hello")
public String hello(){
// 1、获取一把锁,只要锁的名字一样,既是同一把锁
RLock lock = redisson.getLock ("my-lock");
// 2、加锁
lock.lock ();// 阻塞式等待
try {
System.out.println ("加锁成功,执行业务..."+Thread.currentThread ().getId () );
// 模拟超长等待
Thread.sleep (20000);
} catch (Exception e) {
e.printStackTrace ( );
}finally {
// 3、解锁
System.out.println ("释放锁..."+Thread.currentThread ().getId () );
lock.unlock ();
}
return "hello";
}
}
//1. 普通的可重入锁
RLock lock = redissonClient.getLock("generalLock");
// 拿锁失败时会不停的重试
// 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
lock.lock();
// 尝试拿锁10s后停止重试,返回false
// 具有Watch Dog 自动延期机制 默认续30s
boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
// 拿锁失败时会不停的重试
// 没有Watch Dog ,10s后自动释放
lock.lock(10, TimeUnit.SECONDS);
// 尝试拿锁100s后停止重试,返回false
// 没有Watch Dog ,10s后自动释放
boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
公平锁
//2. 公平锁 保证 Redisson 客户端线程将以其请求的顺序获得锁
RLock fairLock = redissonClient.getFairLock("fairLock");
读写锁
//3. 读写锁 没错与JDK中ReentrantLock的读写锁效果一样
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("readWriteLock");
readWriteLock.readLock().lock();
readWriteLock.writeLock().lock();
结论:
保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁)。读锁是一个共享锁
(1)读+读:相当于无锁,并发读情况下,只会在 redis 中记录好,所有当前的读锁,他们都会加锁成功。
(2)写+读:等待写锁释放
(3)写+写:阻塞方式
(4)读+写:有读锁,写也需要等待
只要有写的存在,都必须等待
信号量:Semaphore
/**
* 车库停车:
* 信号量也可以做分布式限流
* @return
*/
@GetMapping("/park")
public String park()throws Exception{
RSemaphore park = redisson.getSemaphore ("park");
// 获取一个信号,获取一个值,占一个车位
//park.acquire ();
//park.acquire (23);// 占用23个
// 如果有数量则占用,没有则失败
boolean b = park.tryAcquire ( );
if (b){
// 执行业务
}else {
return "没有可以占用的,失败";
}
return "占用成功,当前剩余车可占用量:"+park.availablePermits ();
}
@GetMapping("/go")
public String go(){
RSemaphore park = redisson.getSemaphore ("park");
//park.release ();// 开发一个车位
park.release (30);// 开发 30个车位
return "增加车位成功";
}
闭锁:CountDownLatch
/**
* 模拟 5 个班级人走完,学校放假(闭校)
* @return
* @throws Exception
*/
@GetMapping("lockDoor")
public String lockDoor()throws Exception{
RCountDownLatch door = redisson.getCountDownLatch ("door");
door.trySetCount (5);
door.await ();// 等待闭锁都完成
return "放假了";
}
@GetMapping("/gogo/{id}")
public String gogo(@PathVariable("id")String id){
RCountDownLatch door = redisson.getCountDownLatch ("door");
door.countDown ();// 计数-1
long count = door.getCount ( );
return id+"班走完,剩余:"+count;
}
RedissonRedLock
分布式锁缺点
RedLock加锁机制
由于分布式锁在哨兵模式下存在的缺点,因此在多redis实例的情况下,引入RedLock,保证锁的可靠性。
Redisson提供了RedissonRedLock锁实现了RedLock,需要同时使用多个独立的Redis实例分别进行加锁,只有超过一半的锁加锁成功,则认为是成功加锁。
使用场景:
多个服务间保证同一时刻同一时间段内同一用户只能有一个请求(防止关键业务出现并发攻击);
这个锁的算法实现了多redis实例的情况,相对于单redis节点来说,优点在于 防止了 单节点故障造成整个服务停止运行的情况;并且在多节点中锁的设计,及多节点同时崩溃等各种意外情况有自己独特的设计方法;
代码示例
RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");
RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();
秒杀
秒杀最主要是要解决超卖和超时问题。
核心示例
@RestController
public class indexController {
@Autowired
private Redisson redisson;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/deduct_stock")
public String deductStock(){
String lockKey = "lockKey";//简单的不完善的分布式状态锁
RLock reLock = redisson.getLock(lockKey);
try {
reLock.lock();//相当于setIfAbsent("lockKey", "zhujian",10, TimeUnit.SECONDS);
int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
if(stock > 0){
int realStock = stock - 1;
stringRedisTemplate.opsForValue().set("stock",realStock + " ");
System.out.println("扣减成功,剩余库存:"+realStock);
}else {
System.out.println("扣减失败,库存不足");
}
} finally {
reLock.unlock();
}
return "end";
}
}
限流
我们是有面临高并发下需要对接口或者业务逻辑限流的问题,我们可以采用Guaua依赖下的RateLimiter 实现,实际上,Redisssion也有类似的限流功能。
RateLimiter 被称为令牌桶限流,此类限流是首先定义好一个令牌桶,指明在一定时间内生成多少个令牌,每次访问时从令牌桶获取指定数量令牌,如果获取成功,则设为有效访问。
1.获取限流实例
2.设置令牌桶规则
设置令牌桶规则,例如 1分钟秒内,生成6个有效令牌
3.对限流的业务进行令牌获取尝试
// 尝试获取令牌 底层默认是获取一个令牌
boolean tryAcquire();// 尝试获取指定令牌
boolean tryAcquire(long permits);// 一定时间内尝试获取1个令牌
boolean tryAcquire(long timeout, TimeUnit unit);
4.限流实战
RateType.OVERALL 表示针对所有客户端
RRateLimiter rateLimiter;
@PostConstruct
public void initRateLimiter(){
RRateLimiter ra = redissonClient.getRateLimiter("rate-limiter");
ra.setRate(RateType.OVERALL, 6, 1, RateIntervalUnit.MINUTES);
rateLimiter = ra;
}
---------
@GetMapping("/rate/limiter")
public String testRateLimiter() {
return lockService.testRateLimiter();
}
---------
public String testRateLimiter() {
boolean b = rateLimiter.tryAcquire();
if (b) {
return "ok";
}
return "fail";
}
5.规则设置注意事项
// setRate 我们项目服务重启,就会强制重置之前的限流配置与状态,以当前为准
ra.setRate(RateType.OVERALL, 6, 1, RateIntervalUnit.MINUTES);
// trySetRate 我们项目服务重启,不会更新限流配置与限流状态,但参数更改后亦不会生效!比如之前是十分钟内颁布令牌100个,更改为5分钟内颁布令牌30个并不会生效
ra.trySetRate(RateType.OVERALL, 7, 2, RateIntervalUnit.MINUTES);
事务
为RMap、RMapCache、RLocalCachedMap、RSet、RSetCache和RBucket这样的对象提供了具有ACID属性的事务功能Redisson事务通过分布式锁保证了连续写入的原子性,同时在内部通过操作指令队列实现了Redis原本没有的提交与滚回功能当提交与滚回遇到问题的时候,将通过org.redisson.transaction.TransactionException告知用户
示例
package com.demo.redis.transaction;
import org.redisson.api.RBucket;
import org.redisson.api.RTransaction;
import org.redisson.api.RedissonClient;
import org.redisson.api.TransactionOptions;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import javax.annotation.Resource;
/**
* RedisTransaction
*
* @author wangmingcong
*/
@Component
public class RedisTransaction {
@Resource
private RedissonClient redissonClient;
/**
* 获取 RTransaction
*
* @return 返回 值
*/
public RTransaction getTransaction() {
RTransaction transaction = redissonClient.createTransaction(TransactionOptions.defaults());
Assert.notNull(transaction, "transaction is null");
return transaction;
}
/**
* 获取事务
*
* @param name 名称
* @return 返回 RBucket
*/
public RBucket<String> getTransactionBucket(String name) {
RBucket<String> bucket = this.getTransaction().getBucket(name, StringCodec.INSTANCE);
Assert.notNull(bucket, "bucket is null");
return bucket;
}
/**
* 事务提交
*
* @param transaction 事务
*/
public void commit(RTransaction transaction) {
transaction.commit();
}
/**
* 事务提交 (异步)
*
* @param transaction 事务
*/
public void commitAsync(RTransaction transaction) {
transaction.commitAsync();
}
/**
* 事务 回滚
*
* @param transaction 事务
*/
public void rollback(RTransaction transaction) {
transaction.rollback();
}
/**
* 事务 回滚 (异步)
*
* @param transaction 事务
*/
public void rollbackAsync(RTransaction transaction) {
transaction.rollbackAsync();
}
}
package com.demo.redis.transaction;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.redisson.api.RBucket;
import org.redisson.api.RTransaction;
import org.redisson.client.codec.StringCodec;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@Slf4j
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisTransactionTest {
private static final String NAME = "demo:redisTransaction";
@Resource
private RedisTransaction redisTransaction;
@Test
public void test() {
// 创建事务
RTransaction transaction = redisTransaction.getTransaction();
try {
// 执行事务的命令
RBucket<String> bucket = transaction.getBucket(NAME);
bucket.set("value");
// 或者
transaction.getBucket(NAME, StringCodec.INSTANCE).set("value");
transaction.getMap(NAME).put("key", "value");
// 提交事务
transaction.commit();
} catch (Exception ex) {
log.error("", ex);
// 异常回滚
transaction.rollback();
}
}
}