redis实现 如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?

redis实现

在Spring Boot中使用Redis实现外卖配送单发布并保证只有一个骑手接单,可以通过以下步骤实现:

  1. 确保你的Spring Boot项目已经集成了Redis,并正确配置了Redis连接信息。

  2. 在发布外卖配送单时,生成一个唯一的标识符(比如订单ID或随机UUID),作为这个配送单的唯一标识。

  3. 在Redis中设置一个键,用来表示当前已经被接单的配送单。这个键可以是一个字符串类型的键,例如:“delivery_order_accepted”。

  4. 当骑手想要接单时,首先通过Redis的分布式锁机制尝试获取锁。只有一个骑手能够成功获取到锁,表示该骑手接到了单子。

    • 如果使用Jedis客户端,可以使用SETNX命令实现分布式锁。例如,使用setnx("delivery_order_accepted", orderId)来尝试获取锁,返回值为1表示成功获取到锁,0表示锁已经被其他骑手获取。

    • 如果使用Lettuce客户端,可以使用StatefulRedisConnectionsync()方法,然后使用setnx命令实现分布式锁。

  5. 如果骑手成功获取到锁,即成功接到单子,将配送单的信息存储在Redis中,例如使用Hash结构保存配送单的详细信息。

  6. 如果骑手没有成功获取到锁,表示已经有其他骑手接到了单子,可以给骑手返回一个提示或者重新获取其他的配送单。

需要注意的是,为了防止锁过期或异常情况下的数据不一致,可以设置合适的过期时间,并在骑手接单后更新或删除Redis中的配送单信息。

这样,通过使用Redis的分布式锁机制,你可以保证只有一个骑手能够接到配送单,避免多个骑手同时接单的情况发生。

以下是一个示例代码,演示如何使用Redis实现在Spring Boot中发布外卖配送单并保证只有一个骑手接单:

package com.tan.springboot2.p2;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
@RequestMapping("/delivery")
public class DeliveryController {
    private static final String DELIVERY_ORDER_ACCEPTED_KEY_PREFIX = "delivery_order_accepted:";
    private static final long LOCK_EXPIRATION_SECONDS = 300;
    private static final long LOCK_RENEWAL_SECONDS = 10;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @GetMapping("/publish")
    public String publishDeliveryOrder(String orderId) {
        String key = DELIVERY_ORDER_ACCEPTED_KEY_PREFIX + orderId;
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String value = UUID.randomUUID().toString();
        boolean lockAcquired=false;
        try {
            // 尝试获取锁
             lockAcquired = ops.setIfAbsent(key, value, LOCK_EXPIRATION_SECONDS, TimeUnit.SECONDS);
            if (lockAcquired) {
                // 成功获取到锁,表示骑手接到了单子
                // 这里可以保存配送单的详细信息到Redis中,使用Hash结构等
                log.info("cg");
                Thread.sleep(280*1000L);
                // 返回接单成功的消息
                return "骑手已接到单子:" + orderId;
            } else {
                log.info("sb");
                // 返回接单失败的消息
                return "该单子已被其他骑手接走" + orderId;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lockAcquired) {
                releaseLock(key, value);
            }
        }
        return key;
    }

    private void releaseLock(String key, String value) {
        RedisScript<Long> script = new DefaultRedisScript<>(
                "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end",
                Long.class
        );
        redisTemplate.execute(script, Collections.singletonList(key), value);
    }


}

boolean expired = redisTemplate.getExpire(key, TimeUnit.SECONDS) == -2;

在Redis中,使用getExpire方法可以获取指定键的剩余过期时间(TTL,Time to Live)。该方法返回一个长整型数值,表示键的剩余过期时间,单位为秒。有以下几种情况:

  • 如果键存在并且具有设置的过期时间,getExpire返回键的剩余过期时间的秒数。
  • 如果键存在但没有设置过期时间,getExpire返回-1。
  • 如果键不存在,或者已经过期,getExpire返回-2。

在示例代码中,我们通过redisTemplate.getExpire(key, TimeUnit.SECONDS)来获取指定键的剩余过期时间,然后与-2进行比较。如果返回值等于-2,说明键不存在或已经过期,表示锁已过期。在这种情况下,我们可以尝试重新获取锁,并进行续期操作。

需要注意的是,getExpire方法返回的剩余过期时间单位为秒,与我们在setIfAbsent方法中设置的过期时间单位保持一致。

请求测试 http://localhost:8081/delivery/publish?orderId=1
redis实现 如果一个外卖配送单子要发布,现在有200个骑手都想要接这一单,如何保证只有一个骑手接到单子?_第1张图片
https://github.com/tanwu001/springboot2.1
代码在p2包中

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