解密双十一电商优惠券批量下发设计与实现

前言

每年"双十一"这场全球最大的购物狂欢节中,电商平台需要在极短时间内向海量用户精准发放数亿张优惠券,除了系统硬件本身的性能需要提升之外,程序的代码也是十分重要的。本文将深入解密某电商平台双十一优惠券批量下发的核心技术架构,重点解析如何通过线程池与Java 8 Consumer接口构建高并发处理系统,实现秒级完成百万级优惠券发放的技术突破,同时也通用工具类实现其他场景批量发送。

程序结构概述

本文实践实践基于springboot实现,所以优惠券批量下发系统主要由以下几个核心部分组成:

  1. ​控制器(Controller)​:负责接收外部请求并触发批量任务。
  2. ​服务接口(CouponServiceV2)​:定义了批量任务处理的接口方法。
  3. ​服务实现类(CouponServiceImplV2)​:实现了批量任务处理逻辑,利用线程池并发执行任务。
  4. ​批量发送工具类(TaskBatchSendUtils)​:提供了通用的批量任务处理方法,支持自定义任务处理逻辑。

控制器层

首先控制器层是系统与外部交互的入口,负责接收请求并调用相应的服务方法。在本系统中,控制器通过一个简单的HTTP GET请求触发优惠券的批量下发任务。

typescript 复制代码
@GetMapping(value = "/coupon/sendv2")
public void sendv2() {
    couponServiceV2.batchTaskActionV2();
}

述代码中,当访问/coupon/sendv2接口时,控制器会调用couponServiceV2batchTaskActionV2方法,从而启动批量优惠券下发流程。

服务接口层

这里也就是Service层,Service层定义了系统所需的核心功能,便于后续的实现和扩展。在本系统中,定义了一个CouponServiceV2接口,其中包含一个batchTaskActionV2方法,用于执行批量任务。

csharp 复制代码
public interface CouponServiceV2 {
    public void batchTaskActionV2();
}

服务实现类

接下来就是,Service层实现类,主要是负责具体的业务逻辑实现。在本系统中,CouponServiceImplV2类实现了CouponServiceV2接口,并在batchTaskActionV2方法中完成了优惠券的批量下发任务, 模拟要下发的50条优惠卷,上游系统给我们的下发优惠卷源头,并且调用工具类批处理任务,这些优惠卷coupons,放入线程池threadPool,做什么业务disposeTask下发。

csharp 复制代码
@Override
public void batchTaskActionV2() {
    //1 模拟要下发的50条优惠卷,上游系统给我们的下发优惠卷源头
    List<String> coupons = getCoupons();

    long startTime = System.currentTimeMillis();

    //2 调用工具类批处理任务,这些优惠卷coupons,放入线程池threadPool,做什么业务disposeTask下发
    TaskBatchSendUtils.send(coupons, threadPool, TaskBatchSendUtils::disposeTaskV2);

    long endTime = System.currentTimeMillis();
    System.out.println("----costTime: "+(endTime - startTime) +" 毫秒");
}

批量发送工具类

接下来就是定义批量发送工具类TaskBatchSendUtils, TaskBatchSendUtils工具类封装了批量任务处理的核心逻辑,利用Java的并发工具类CountDownLatch确保所有任务都完成后再继续后续操作。同时,通过Java 8的Consumer接口,支持灵活的任务处理逻辑,TaskBatchSendUtils无需关心具体业务逻辑,只要对Consumer进行accept即可,这样后续也可以通过改工具类实现其他场景批量发送。

核心方法:send

typescript 复制代码
public class TaskBatchSendUtils {
    public static <T> void send(List<T> taskList, Executor threadPool, Consumer<? super T> consumer) throws InterruptedException {
        if (taskList == null || taskList.size() == 0) {
            return;
        }

        if(Objects.isNull(consumer)) {
            return;
        }

        CountDownLatch countDownLatch = new CountDownLatch(taskList.size());

        for (T couponOrShortMsg : taskList) {
            threadPool.execute(() -> {
                try {
                    consumer.accept(couponOrShortMsg);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
    }

    // 具体的任务处理方法
    public static void disposeTask(String task) {
        System.out.println(String.format("【%s】disposeTask下发优惠卷或短信成功", task));
    }

    public static void disposeTaskV2(String task) {
        System.out.println(String.format("【%s】disposeTask下发邮件成功", task));
    }

    public static void disposeTaskV3(String task) {
        System.out.println(String.format("【%s】disposeTask下发二维码序号成功", task));
    }
}

线程池配置

批量发送的关键,主要是利用线程池,合理的线程池配置可以显著提升系统的并发处理能力和稳定性,以下是线程池配置:

scss 复制代码
@Configuration
public class ThreadPoolConfig
{

    //线程池配置
    @Resource
    private ThreadPoolProperties threadPoolProperties;

    @Bean
    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
    {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();

        // 核心线程池大小
        threadPool.setCorePoolSize(threadPoolProperties.getCorePoolSize());
        // 最大可创建的线程数
        threadPool.setMaxPoolSize(threadPoolProperties.getMaxPoolSize());
        // 等待队列最大长度
        threadPool.setQueueCapacity(threadPoolProperties.getQueueCapacity());
        // 线程池维护线程所允许的空闲时间
        threadPool.setKeepAliveSeconds(threadPoolProperties.getKeepAliveSeconds());
        //异步方法内部线程名称
        threadPool.setThreadNamePrefix("spring默认线程池-");
        // 线程池对拒绝任务(无线程可用)的处理策略
        threadPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 任务都完成再关闭线程池
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        // 任务初始化
        threadPool.initialize();

        return threadPool;
    }

}

通过合理配置线程池,可以有效地管理并发任务,防止资源耗尽和系统崩溃。

Java 8的Consumer接口

这里简单介绍一下Java 8引入的Consumer接口,它是是一个函数式接口,表示接受一个输入参数并且不返回结果的操作。在本系统中,Consumer接口被用于定义任务的处理逻辑,使得任务处理逻辑可以灵活地传入和替换。

csharp 复制代码
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
  
}

在本文中,通过方法引用(如TaskBatchSendUtils::disposeTaskV2)将具体的任务处理逻辑传递给TaskBatchSendUtils.send方法,实现了任务处理逻辑的解耦和复用。

测试验证

通过上述设计和实现,系统能够高效地处理批量优惠券下发任务,利用线程池并发执行任务,显著提升了任务处理速度和系统吞吐量,接下来启动程序,查看最后耗时情况:

结语

本文详细介绍了一个基于线程池和Java 8的Consumer接口实现的优惠券批量下发系统。通过合理的架构设计和并发处理,系统能够高效地处理大量任务,提升系统性能和用户体验。在实际应用中,开发者可以根据具体业务需求,进一步优化和扩展系统功能,以满足更复杂的业务场景,欢迎大家到评论区进行留言。

相关推荐
bobz9657 小时前
virtio-networking 5: 介绍 vDPA kernel framework
后端
橙子家8 小时前
接口 IResultFilter、IAsyncResultFilter 的简介和用法示例(.net)
后端
bobz9658 小时前
Virtio-networking: 2019 总结 2020展望
后端
AntBlack8 小时前
每周学点 AI : 在 Modal 上面搭建一下大模型应用
后端
G探险者8 小时前
常见线程池的创建方式及应用场景
后端
bobz9659 小时前
virtio-networking 4: 介绍 vDPA 1
后端
柏油10 小时前
MySQL InnoDB 架构
数据库·后端·mysql
一个热爱生活的普通人10 小时前
Golang time 库深度解析:从入门到精通
后端·go
一只叫煤球的猫10 小时前
怎么这么多StringUtils——Apache、Spring、Hutool全面对比
java·后端·性能优化