基于Redis实现微信抢红包功能

安装Lua(可选)

  1. 参考http://www.lua.org/ftp/.教程,下载5.3.5_1版本,本地安装,如果你使用的是Mac,那建议用brew工具直接执行brew install lua就可以顺利安装,

  2. 有关brew工具的安装可以参考https://brew.sh/.网站,建议翻墙否则会很慢。

  3. 安装IDEA插件,在IDEA->Preferences面板,Plugins,里面Browse repositories,在里面搜索lua,然后就选择同名插件lua。安装好后重启IDEA

  4. 配置Lua SDK的位置: IDEA->File->Project Structure,选择添加Lua,路径指向Lua SDK的文件夹

编写lua脚本

lua脚本学习可以参考 https://www.runoob.com/lua/lua-basic-syntax.html.

--缓存抢红包列表信息列表key
local listKey = 'red_packet_list_'..KEYS[1]
--当前被抢红包key
local redPacket = 'red_packet_'..KEYS[1]
--获取当前红包库存
local stock = tonumber(redis.call('hget', redPacket, 'stock'))
--没有库存,返回为0
if stock <= 0 then return 0 end
--库存减1
stock = stock -1
--保存当前库存
redis.call('hset',redPacket,'stock', tostring(stock))
--往链表中加入当前红包信息
redis.call('rpush', listKey, ARGV[1])
--如果是最后一个红包,则返回2,表示抢红包已经结束,需要将列表中的数据保存到数据库中
if stock == 0 then return 2 end
--如果并非最后一个红包,则返回1,表示抢红包成功
return 1

使用 Redis 实现抢红包

加载lua脚本

@Configuration
public class RedisConfiguration {
    @Bean(name = "redPacket")
    public DefaultRedisScript<Long> loadRedPackRedisScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setLocation(new ClassPathResource("redPacket.lua"));
        redisScript.setResultType(java.lang.Long.class);
        return redisScript;
    }
}

**UserRedPacketService **

public interface UserRedPacketService {
    /**
     * 通过Redis实现抢红包
     * @param redPacketId 红包编号
     * @param userId 用户编号
     *@return 0-没有库存,失败
     * 1-成功,且不是最后一个红包
     * 2-成功,且是最后一个红包
     */
    Long grapRedPacketByRedis(Long redPacketId, Long userId);
}

**UserRedPacketServiceImpl **

@Slf4j
@Service
public class UserRedPacketServiceImpl implements UserRedPacketService {
    private final RedisScript<Long> ratePacket;
    private final StringRedisTemplate stringRedisTemplate;

    public UserRedPacketServiceImpl(@Qualifier("redPacket") RedisScript<Long> ratePacket, StringRedisTemplate stringRedisTemplate) {
        this.ratePacket = ratePacket;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public Long grapRedPacketByRedis(Long redPacketId, Long userId) {
        // 当前抢红包用户和日期信息
        String args = userId + "_" + System.currentTimeMillis();
        Long result = stringRedisTemplate.execute(ratePacket, Lists.newArrayList(redPacketId + ""), args);
        log.info("返回结果:{}", result);
        return result;
    }
}

UserRedPacketController

  @Autowired
    private UserRedPacketService userRedPacketService;
    @GetMapping("/grapRedPacketByRedis")
    public boolean grapRedPacketByRedis(Long redPacketId, Long userId) {
        Long result = userRedPacketService.grapRedPacketByRedis(redPacketId, userId);
        Map<String, Object> resultMap = Maps.newHashMap();
        boolean flag = result > 0;
        return flag;
    }

测试

红包编号为1和红包数量数量为8个以及每个红包金额为5
hset red_packet_1 stock 8
hset red_packet_1 unit_amount 10

public class RedisPackThread {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor redPacketExecutor = new ThreadPoolExecutor(100, 1000, 60L,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000), r -> {
            //            t.setName("redPacket");
            return new Thread(r);
        }, (r, executor) -> {
            System.out.println("async sender is error rejected, runnable: " + r + ", executor: {}" + executor);
        });
        CountDownLatch cdl = new CountDownLatch(100);
        CyclicBarrier cyclicBarrier = new CyclicBarrier(100);
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            redPacketExecutor.submit(() -> {
                try {
                    cyclicBarrier.await();
                    RestTemplate restTemplate = new RestTemplate();
                    Boolean result = restTemplate.getForObject("http://localhost:8888/grapRedPacketByRedis?redPacketId=1&userId=" + finalI, Boolean.class);
                    if(Objects.requireNonNull(result))
                    {
                        System.out.println("我是线程:" + finalI + "  我抢到红包了");
                    }
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                } finally {
                    cdl.countDown();
                }
            });
        }
        cdl.await();
        redPacketExecutor.shutdown();
    }
}

运行结果
在这里插入图片描述

总结

redis执行lua脚本的时候,会将它作为一个整体执行,要么全部执行成功,如果出现异常则执行结果不会更新到redis中,很好的解决了高并发的问题。

最后

关注我公众号,专注分享Java技术干货,包括多线程、JVM、Spring Boot、Spring CloudRedis、架构设计、微服务、消息队列、Linux、面试题、程序员攻略、最新动态等。
Java移动技术栈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值