redis分布式锁和简单秒杀限流的实现

分布式锁

 看门狗防止死锁

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处理分布式事务

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值