redis实现延迟任务和分布式锁

1.什么是延迟任务

  • 定时任务:有固定周期的,有明确的触发时间

  • 延迟队列:没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件,任务可以立即执行,也可以延迟

redis实现延迟任务和分布式锁_第1张图片

应用场景:

场景一:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单;如果期间下单成功,任务取消

场景二:接口对接出现网络问题,1分钟后重试,如果失败,2分钟重试,直到出现阈值终止

2.redis实现

zset数据类型的去重有序(分数排序)特点进行延迟。例如:时间戳作为score进行排序

redis实现延迟任务和分布式锁_第2张图片

实现思路

redis实现延迟任务和分布式锁_第3张图片

问题思路

1.为什么任务需要存储在数据库中?

延迟任务是一个通用的服务,任何需要延迟得任务都可以调用该服务,需要考虑数据持久化的问题,存储数据库中是一种数据安全的考虑。

2.为什么redis中使用两种数据类型,list和zset?

效率问题,算法的时间复杂度

3.在添加zset数据的时候,为什么不需要预加载?

任务模块是一个通用的模块,项目中任何需要延迟队列的地方,都可以调用这个接口,要考虑到数据量的问题,如果数据量特别大,为了防止阻塞,只需要把未来几分钟要执行的数据存入缓存即可。

3.reids key值匹配

方案1:keys 模糊匹配

keys的模糊匹配功能很方便也很强大,但是在生产环境需要慎用!开发中使用keys的模糊匹配却发现redis的CPU使用率极高,所以公司的redis生产环境将keys命令禁用了!redis是单线程,会被堵塞

redis实现延迟任务和分布式锁_第4张图片

方案2:scan

SCAN 命令是一个基于游标的迭代器,SCAN命令每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为SCAN命令的游标参数, 以此来延续之前的迭代过程。

redis实现延迟任务和分布式锁_第5张图片

4.reids管道

普通redis客户端和服务器交互模式

redis实现延迟任务和分布式锁_第6张图片

Pipeline请求模型

redis实现延迟任务和分布式锁_第7张图片

官方测试结果数据对比

redis实现延迟任务和分布式锁_第8张图片

测试案例对比:

//耗时6151
@Test
public  void testPiple1(){
    long start =System.currentTimeMillis();
    for (int i = 0; i <10000 ; i++) {
        Task task = new Task();
        task.setTaskType(1001);
        task.setPriority(1);
        task.setExecuteTime(new Date().getTime());
        cacheService.lLeftPush("1001_1", JSON.toJSONString(task));
    }
    System.out.println("耗时"+(System.currentTimeMillis()- start));
}
​
​
@Test
public void testPiple2(){
    long start  = System.currentTimeMillis();
    //使用管道技术
    List objectList = cacheService.getstringRedisTemplate().executePipelined(new RedisCallback() {
        @Nullable
        @Override
        public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
            for (int i = 0; i <10000 ; i++) {
                Task task = new Task();
                task.setTaskType(1001);
                task.setPriority(1);
                task.setExecuteTime(new Date().getTime());
                redisConnection.lPush("1001_1".getBytes(), JSON.toJSONString(task).getBytes());
            }
            return null;
        }
    });
    System.out.println("使用管道技术执行10000次自增操作共耗时:"+(System.currentTimeMillis()-start)+"毫秒");
} 
   
  

5.分布式锁解决集群下的方法抢占执行

分布式锁:控制分布式系统有序的去对共享资源进行操作,通过互斥来保证数据的一致性。

解决方案:

redis实现延迟任务和分布式锁_第9张图片

redis分布式锁

sexnx (SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。

redis实现延迟任务和分布式锁_第10张图片

这种加锁的思路是,如果 key 不存在则为 key 设置 value,如果 key 已存在则 SETNX 命令不做任何操作

  • 客户端A请求服务器设置key的值,如果设置成功就表示加锁成功

  • 客户端B也去请求服务器设置key的值,如果返回失败,那么就代表加锁失败

  • 客户端A执行代码完成,删除锁

  • 客户端B在等待一段时间后再去请求设置key的值,设置成功

  • 客户端B执行代码完成,删除锁

示例代码:

/**
 * 加锁
 *
 * @param name
 * @param expire
 * @return
 */
public String tryLock(String name, long expire) {
    name = name + "_lock";
    String token = UUID.randomUUID().toString();
    RedisConnectionFactory factory = stringRedisTemplate.getConnectionFactory();
    RedisConnection conn = factory.getConnection();
    try {

        //参考redis命令:
        //set key value [EX seconds] [PX milliseconds] [NX|XX]
        Boolean result = conn.set(
                name.getBytes(),
                token.getBytes(),
                Expiration.from(expire, TimeUnit.MILLISECONDS),
                RedisStringCommands.SetOption.SET_IF_ABSENT //NX
        );
        if (result != null && result)
            return token;
    } finally {
        RedisConnectionUtils.releaseConnection(conn, factory,false);
    }
    return null;
}

这段代码是一个加锁的方法,用于在Redis中实现分布式锁。

  1. 首先,将传入的name参数加上"_lock"后缀,作为锁的key。

  2. 生成一个随机的token作为锁的value。

  3. 获取Redis连接工厂,并通过工厂获取一个Redis连接。

  4. 在try块中,使用Redis连接执行set命令来设置锁的key和value,并指定锁的过期时间和选项。其中,Expiry.from(expire, TimeUnit.MILLISECONDS)用于设置锁的过期时间,RedisStringCommands.SetOption.SET_IF_ABSENT表示只有当锁的key不存在时才进行设置(NX选项)。

  5. 如果set命令返回结果不为null且为true,表示成功获取到了锁,此时返回生成的token。

  6. 在finally块中,释放Redis连接。

  7. 如果没有成功获取到锁,则返回null。

这段代码的作用是通过Redis实现分布式锁,保证在多个线程或多个应用程序之间对同一资源的访问是互斥的。当一个线程成功获取到锁时,其他线程将无法获取到相同的锁,从而实现了对共享资源的安全访问。

你可能感兴趣的:(java,redis,数据库,缓存)