【luckydraw-ddd】领域开发07-ID生成领域开发
【luckydraw-ddd】领域开发07-ID生成领域开发
学习目的
【1】构建ID生成领域:根据不同场景分析不同ID策略的应用
【2】掌握ID生成策略:使用雪花算法、阿帕奇工具包 RandomStringUtils、日期拼接,三种方式生成ID,分别用在订单号、策略ID、活动号的生成上
参考分支 | 210911_xfg_IdGenerator |
开发分支 | dev_220119_02_IdGenerator |
1.需求分析
使用策略模式把三种生成ID的算法进行统一包装,由调用方决定使用哪种生成ID的策略。策略模式属于行为模式的一种,一个类的行为或算法可以在运行时进行更改
雪花算法本章节使用的是工具包 hutool 包装好的工具类,一般在实际使用雪花算法时需要做一些优化处理,比如支持时间回拨、支持手工插入、简短生成长度、提升生成速度等。
日期拼接和随机数工具包生成方式,都需要自己保证唯一性,一般使用此方式生成的ID,都用在单表中,本身可以在数据库配置唯一ID。(为什么不用自增ID,因为自增ID通常容易被外界知晓你的运营数据,以及后续需要做数据迁移到分库分表中都会有些麻烦)
开发说明
在 domain 领域包下新增支撑领域,ID 的生成服务就放到这个领域下实现。
关于 ID 的生成因为有三种不同 ID 用于在不同的场景下:构建不同长度ID供不同类型使用,避免同样长度ID使用造成混乱
- 订单号:唯一、大量、订单创建时使用、分库分表
- 活动号:唯一、少量、活动创建时使用、单库单表
- 策略号:唯一、少量、活动创建时使用、单库单表
2.项目结构设计
lottery-domain
└── src
└── main
└── java
└── cn.itedus.lottery.domain.support.ids
├── policy
│ ├── RandomNumeric.java
│ ├── ShortCode.java
│ └── SnowFlake.java
├── IdContext.java
└── IIdGenerator.java
IIdGenerator,定义生成ID的策略接口。RandomNumeric、ShortCode、SnowFlake,是三种生成ID的策略。
IdContext,ID生成上下文,也就是从这里提供策略配置服务
开发步骤
【1】定义IIdGenerator接口,提供nextId()生成id方法
【2】构建RandomNumeric、ShortCode、SnowFlake三种ID生成策略
【3】定义IdContext 生成策略对象(将策略装配到map)
【4】创建测试类SupportTest测试生成ID
ID生成领域开发
定义IIdGenerator接口,提供nextId()生成id方法
public interface IIdGenerator {
/**
* @return ID
*/
long nextId();
}
构建RandomNumeric、ShortCode、SnowFlake三种ID生成策略
【1】RandomNumeric
借助RandomStringUtils
生成随机ID
@Component
public class RandomNumeric implements IIdGenerator {
@Override
public long nextId() {
return Long.parseLong(RandomStringUtils.randomNumeric(11));
}
}
【2】ShortCode
采用日期拼接+随机位数的方式构建
@Component
public class ShortCode implements IIdGenerator {
@Override
public synchronized long nextId() {
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
int week = calendar.get(Calendar.WEEK_OF_YEAR);
int day = calendar.get(Calendar.DAY_OF_WEEK);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
// 打乱排序:2020年为准 + 小时 + 周期 + 日 + 三位随机数
StringBuilder idStr = new StringBuilder();
idStr.append(year - 2020);
idStr.append(hour);
idStr.append(String.format("%02d", week));
idStr.append(day);
idStr.append(String.format("%03d", new Random().nextInt(1000)));
return Long.parseLong(idStr.toString());
}
}
【3】SnowFlake
针对唯一性ID(订单号、批次号等),在并发场景下如果使用时间戳+随机数的组合并不可取,Java中UUID生成的序列号太长(不具备可读性),因此使用借助SnowFlake构建唯一序列(雪花算法)
@Component
public class SnowFlake implements IIdGenerator {
private Snowflake snowflake;
@PostConstruct
public void init() {
// 0 ~ 31 位,可以采用配置的方式使用
long workerId;
try {
workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
} catch (Exception e) {
workerId = NetUtil.getLocalhostStr().hashCode();
}
workerId = workerId >> 16 & 31;
long dataCenterId = 1L;
snowflake = IdUtil.createSnowflake(workerId, dataCenterId);
}
@Override
public synchronized long nextId() {
return snowflake.nextId();
}
}
定义IdContext 生成策略对象(将策略装配到map)
@Configuration
public class IdContext {
/**
* 创建 ID 生成策略对象,属于策略设计模式的使用方式
*
* @param snowFlake 雪花算法,长码,大量
* @param shortCode 日期算法,短码,少量,全局唯一需要自己保证
* @param randomNumeric 随机算法,短码,大量,全局唯一需要自己保证
* @return IIdGenerator 实现类
*/
@Bean
public Map<Constants.Ids, IIdGenerator> idGenerator(SnowFlake snowFlake, ShortCode shortCode, RandomNumeric randomNumeric) {
Map<Constants.Ids, IIdGenerator> idGeneratorMap = new HashMap<>(8);
idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);
idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);
idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);
return idGeneratorMap;
}
}
创建测试类SupportTest测试生成ID
@RunWith(SpringRunner.class)
@SpringBootTest
public class SupportTest {
private Logger logger = LoggerFactory.getLogger(SupportTest.class);
@Resource
private Map<Constants.Ids, IIdGenerator> idGeneratorMap;
@Test
public void test_ids() {
logger.info("雪花算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.SnowFlake).nextId());
logger.info("日期算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.ShortCode).nextId());
logger.info("随机算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.RandomNumeric).nextId());
}
}
3.问题思考
对比原有抽奖活动策略实现,ID生成策略实现借助@Configuration、@Bean注入
原有实现思路
@Component
public class IdsConfig {
@Resource
private SnowFlake snowFlake;
@Resource
private ShortCode shortCode;
@Resource
private RandomNumeric randomNumeric;
static Map<Constants.Ids, IIdGenerator> idGeneratorMap = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);
idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);
idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);
}
public static Map<Constants.Ids, IIdGenerator> getIds() {
return idGeneratorMap;
}
}
// 测试
@Resource
private IdsConfig idsConfig;
@Test
public void test_customIds() {
logger.info("雪花算法策略,生成ID:{}", idsConfig.getIds().get(Constants.Ids.SnowFlake).nextId());
logger.info("日期算法策略,生成ID:{}", idsConfig.getIds().get(Constants.Ids.ShortCode).nextId());
logger.info("随机算法策略,生成ID:{}", idsConfig.getIds().get(Constants.Ids.RandomNumeric).nextId());
}
借助@Configuration、@Bean注入
下述实现等价于"注入一个idGenerator对象,类型为Map<Constants.Ids, IIdGenerator>"
@Configuration
public class IdContext {
/**
* 创建 ID 生成策略对象,属于策略设计模式的使用方式
*
* @param snowFlake 雪花算法,长码,大量
* @param shortCode 日期算法,短码,少量,全局唯一需要自己保证
* @param randomNumeric 随机算法,短码,大量,全局唯一需要自己保证
* @return IIdGenerator 实现类
*/
@Bean
public Map<Constants.Ids, IIdGenerator> idGenerator(SnowFlake snowFlake, ShortCode shortCode, RandomNumeric randomNumeric) {
Map<Constants.Ids, IIdGenerator> idGeneratorMap = new HashMap<>(8);
idGeneratorMap.put(Constants.Ids.SnowFlake, snowFlake);
idGeneratorMap.put(Constants.Ids.ShortCode, shortCode);
idGeneratorMap.put(Constants.Ids.RandomNumeric, randomNumeric);
return idGeneratorMap;
}
}
// 测试使用
@Resource
private Map<Constants.Ids, IIdGenerator> idGeneratorMap;
@Test
public void test_ids() {
logger.info("雪花算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.SnowFlake).nextId());
logger.info("日期算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.ShortCode).nextId());
logger.info("随机算法策略,生成ID:{}", idGeneratorMap.get(Constants.Ids.RandomNumeric).nextId());
}
// TODO Springboot相关注解学习补充