10万条记录生成兑换码的实战方案

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【5月更文挑战第27天】项目实战,介绍一次生成10万条记录兑换码的功能,由于公司需要生成大批量的兑换码,单次生成的兑换码超过10条记录。本文用于介绍相关设计方案:

项目实战,介绍一次生成10万条记录兑换码的功能,由于公司需要生成大批量的兑换码,单次生成的兑换码超过10条记录。本文用于介绍相关设计方案:

1.接口超时的问题;

由于接口的功能是一个前后端交互的处理,生成10万条记录,对于数据库来说,大概需要几分钟的时间,如果使用常规的设计就会导致接口超时,业务同事使用接口会遇到界面死机的情况,比如说生成的界面如果是同步的话,通常情况下,界面是有一个超时时间的,大概是30秒,超过了 30秒之后,就会出现接口超时的提示,这样的结果肯定是不能接收的。设计的时候需要考虑,异步的方式进行处理。

在springboot中异步的方式有以下几种:

  1. 异步方法的异常处理:异步方法中的异常不会直接抛给调用者。如果你需要处理这些异常,需要使用Future、CompletableFuture或ListenableFuture的返回类型,并对这些对象进行适当的异常处理。
  2. 方法调用的自调用问题:直接在同一个类中调用另一个@Async注解的方法,Spring将无法拦截这次调用进行异步处理。解决方案通常包括将异步方法移至另一个Bean中,或使用AopContext.currentProxy()来调用。

目前我选择的技术:

CompletableFuture future = CompletableFuture.runAsync(() -> { // 模拟长时间运行的异步任务 System.out.println("Running in another thread."); });

在具体实现的时候,就能够把核心代码写到这个方法里面来实现了。前端调用的时候,直接返回处理成功或者已提交处理;就不会超时了;

2.并发的问题;

该问题主要是生成兑换码必须是唯一值的处理,在并发的时候,可能会出现重复的情况,目前使用数据库的分布式锁与redis的分布式锁进行处理;

在方法的开始位置,进行redis的setnx进行加锁的处理,并且只有加锁成功之后,才会进行相关逻辑的处理,否则的话也不会进行生成兑换码的逻辑;加锁失败的话,直接返回:“生成兑换码处理中,请稍后”;

并且会生成一笔记录,例如:

上图是数据生成中的处理,每次生成兑换码都会产生一笔生成的记录,为了让用户不用在等待界面了。

兑换码生成成功之后,操作的按钮会显示“下载”;如下图

具体的代码:

//随机生成批次号
        String batchNo = SerialNoGenerator.generateSerialNo("C", "K");
        req.setBatchNo(batchNo);
        boolean redisLock=false;
        resourceRights.setCreateBy(UserUtil.getJobNumber());
        try {
            redisLock= redisTemplate.setnx(redisKey, batchNo, RedisTemplate.DEFAULT_TIME);
            //添加并发锁
            if(redisLock)
            {
                //插入兑换记录
                saveInfo(req);
                CompletableFuture.runAsync(() -> {
                    saveCdkey(req,batchNo,txResourceRights);
                });
            }
            else
            {
                return Result.fail("生成兑换码防并发,请稍后再试");
            }
        } catch (Exception e) {
            SkyLog.error("Resource", "", "", e.getMessage(), e);
            return Result.fail("生成兑换码失败");
        }
        return Result.success("生成兑换码成功");
锁的删除是在核心逻辑saveCdkey里面进行删除的::
 } catch (Exception e) {
            SkyLog.error("Resource", "", "", e.getMessage(), e);
        } finally {
            redisTemplate.del(redisKey);
        }

3.导出的问题;

该问题是为了在导出大数据量问题的时候,需要把大数据量的图片进行加载出来,目前使用的是阿里巴巴的easyExcey的功能进行实现的。

添加依赖:

       com.alibaba

       easyexcel

       3.3.2


定义实体类:

public class ExportVO {
    @ExcelProperty("兑换码")
    private String stringCode;
    @ExcelProperty("兑换状态")
    private Double status;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}

导出代码的实现:

try {           
            //赋值的特殊处理
            cdkeyList.forEach(x -> {
                dataList.add(ExportVO.builder()
                        .cdkeyCode(x.getCdkeyCode())
                        .status(x.getStatus() == 1 ? "未兑换" : (x.getStatus() == 2 ? "已兑换" : "已作废"))
                        .build());
            });
            String fileName = URLEncoder.encode("兑换码" + DateUtil.format(new Date(), DatePattern.CHINESE_DATE_TIME_PATTERN), "UTF-8").replaceAll("\\+", "%20");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            EasyExcel.write(response.getOutputStream(), ExportVO.class)
                    .sheet("sheet1")
                    .doWrite(dataList);
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("导出失败");
        }

通过该组件的使用,可以实现10万条记录进行几秒内导出,具体的实现原理,我就不做一一介绍了;

4.提升效率的问题;

主要是在插入数据库表的时候,10万条记录的插入,还是需要花费不少时间的,目前我这边通过批量保存的方式进行处理的,通过把10万条记录进行拆分,分批次进行插入数据库的 处理,目前mybatis plu是支持批量导入的,具体实现的代码如下:

List keyList = new ArrayList<>();
            int splitSize = 500;
            int n = 0;
            for (int i = 0; i < req.getSize(); i++) {
                keyList.add(createCode(batchNo, rights));
                n++;
                if (n % splitSize == 0 || n == req.getSize()) {
                    resourceService.commonSaveBatch(cdkeyList);
                    keyList = new ArrayList<>();
                }
            }

实现的逻辑,就像这段代码写的一样,每次进行判断是否产生了500的整数倍数,如果是的话,就进行批量的插入处理;这个批量插入是一次进行执行的。这样的话可以大大缩减插入数据库的时间。

5.唯一值的设计问题;

唯一值的设计,目前是通过分布式锁的进行处理,每次生成的随机码都会查询一篇数据库,如果存在的话就重新 生成一个随机码进行实体的处理;这样的处理,可以避免出现重复兑换码的问题;

具体的代码如下:

import java.security.SecureRandom;
public class RandomCodeGenerator {
    public static void main(String[] args) {
        String charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        StringBuilder randomCode = new StringBuilder();
        SecureRandom random = new SecureRandom();
        for (int i = 0; i < 10; i++) {
            int index = random.nextInt(charSet.length());
            randomCode.append(charSet.charAt(index));
        }
        System.out.println("Random Code: " + randomCode.toString());
    }
}

这段代码的工作原理如下:

  1. 定义一个字符串charSet,其中包含了所有可能使用的字符:26个大写字母、26个小写字母和10个数字,共62个字符。
  2. 创建一个StringBuilder实例randomCode来构建最终的随机码。
  3. 创建一个SecureRandom实例来生成随机数。与Random相比,SecureRandom生成的随机数更适合用于安全敏感的应用。
  4. 在一个循环中,生成10次随机数。每次随机数的生成都是通过random.nextInt(charSet.length())来实现的,确保随机数的范围在0charSet.length()(不包含)之间。这个随机数被用作索引从charSet中选取一个字符,然后将这个字符附加到randomCode上。
  5. 循环结束后,randomCode将包含一个10位的由数字和大小写字母组成的随机码。
  6. 最后,打印生成的随机码。

这种方法生成的随机码具有良好的随机性和较高的安全性,适用于需要随机码作为识别码、密码或者其他安全要求较高的场景。


总结:

整个流程一共有六个问题点,但是每个问题点都是一个不小的坑。经过本次的小功能的开发,也学习了不少的知识点,希望通过本次的分享,能帮助到一些同学,也能给同学们提供下解决方案的思路。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
C# 索引
C# | 【完全开源】手机号码归属地查询,一秒内百万次查询
这个开源项目是一个.NET库,可以通过手机号码获取号码归属地信息,包括运营商、国家、省份、城市、邮政编码、区号等信息。 该库加载了一个包含46万条数据的“中国手机号归属地信息”数据集,并实现了高速查询。在我的7年老笔记本上执行一百万次查询耗时不足一秒。
307 0
淘宝批量复制宝贝提示“当前类目大于48小时发货的发货时间不能大于15天,请调整”怎么解决?
要复制这个宝贝上传到淘宝店铺,只需要重新复制一次,然后在大淘营淘宝宝贝复制专家下载配置的第二步,选择一个小于或等于15天的发货时间(见下图),这样就可以复制宝贝上传到淘宝店铺了。
|
XML JSON 缓存
Java实现商品ID获取淘宝商品历史价格信息数据方法
Java实现商品ID获取淘宝商品历史价格信息数据方法
|
XML JSON 缓存
Java实现商品ID获取京东商品历史价格数据方法
Java实现商品ID获取京东商品历史价格数据方法
|
SQL 大数据 开发者
电商项目之交易订单明细流水表执行测试|学习笔记
快速学习电商项目之交易订单明细流水表执行测试
电商项目之交易订单明细流水表执行测试|学习笔记
凑单后续,女朋友说商品购买不限数量……
凑单后续,女朋友说商品购买不限数量……
134 0
|
数据库
LeetCode(数据库)- 查询球队积分
LeetCode(数据库)- 查询球队积分
157 0
|
数据库
LeetCode(数据库)- 进店却未进行过交易的顾客
LeetCode(数据库)- 进店却未进行过交易的顾客
96 0
|
数据库
LeetCode(数据库)- 每位顾客最经常订购的商品
LeetCode(数据库)- 每位顾客最经常订购的商品
114 0
|
数据库
LeetCode(数据库)- 好友申请l:总体通过率
LeetCode(数据库)- 好友申请l:总体通过率
95 0