分布式锁
?看门狗防止死锁
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使用案例_毕业即失业吗的博客-CSDN博客_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();
}
}
}
来源
Redission 分布式锁框架 - 简书
Redis秒杀案例(基于springboot)_芝士汉堡 ????的博客-CSDN博客_redis springboot 秒杀
Redis 秒杀案例_bladejsW的博客-CSDN博客_redis 秒杀
springboot2.x整合Redission_保护我方胖虎的博客-CSDN博客_springboot 整合redission
【分布式】Redis分布式之事务(Transaction)操作_王思勤(勤思)的博客-CSDN博客_redis处理分布式事务
|