背景
新建商品时,每个商品需要有商品编码,有以下要求
1.8位无序的编码(编码比较敏感,不能被外部猜测到平台的商品数量);
2.全局唯一,不可重复;
方案
由三部分组成,分别是“生成器”,“取号器”,"编码池"。"生成器"定时定量生成唯一编码放到"编码池",由“取号器”从"编码池"获取唯一编码,"编码池"的数量随着商品的创建会越来越少,
,我们采用每天一次检查,如果检查到当前"编码池"的可用编码低于阈值(10W)时,再重新申请新的号段生成100W编码。
8位 无序编码有两部分组成:号段+随机数。号段:2位,随机数:6位。也就是说一次性会生成100W的编码
"预生成的编码池"
"已使用的号段表"
这里涉及几个问题:
1.如何保证"编码池"的编码是全局唯一且无序的;
2."定时定量"如何评估;
3."取号器"需要保证高并发场景下的多线程竞争取号问题;
解释下第1个步骤:生成器首先要申请可用的号段。怎么申请?排除“已使用的号段”表中数据,随机生成一个两位数的号段。
步骤2:号段+随机数 保存到redis中,并且把“号段”持久化到Mysql中,记录哪些号段已被使用了。直到“编码池”的数据小于阈值,重复1,2步骤
这就解决了第一个问题,保证全局唯一,并且无序。
关于问题2,不是本文讨论的重点,这里一次性生成多少编码,阈值该定义多少,到时根据系统实际流量做调整。
“定时定量”也可以换种方式,由"取号"的时候来实时判断,当低于阈值时,新起一个线程生成新的一批编码丢到"编码池"。
问题3:每次取号成功后该号就从"编码池"删除。利用Redis单线程优势,可有效避免多线程并发取号问题,确保取到的号都是唯一的
如果对并发没有要求,可以把“编码池”放到Mysql中,利用乐观锁控制多线程并发问题
public String getCodFromPool(Integer type){
if(type==null){
throw new ServiceException( ResultCode.PARAM_IS_INVALID );
}
int loopCount = 0;
int maxLoopCount = 50;
while(true){
log.info( "循环开始:获取编码,次数:{}", loopCount);
loopCount++;
BasPreCodeInfo basPreCodeInfo = basPreCodeInfoMapper.selectCode(type);
if(basPreCodeInfo == null){
throw new ServiceException( ProductResultCode.OCR_MPU_CODE_EMPTY_ERROR );
}
int count = basPreCodeInfoMapper.deleteById(basPreCodeInfo.getId() );
//乐观锁,如果删除记录为0说明code已被使用了
if(count==1){
log.info( "循环结束:获取编码:{},次数:{}", basPreCodeInfo,loopCount);
return basPreCodeInfo.getPreCode();
}
try {
Thread.sleep( 100 );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
if(loopCount==maxLoopCount){
log.error( "========获取编码超过次数==========");
throw new ServiceException( ResultCode.SERVER_ERROR );
}
}
}