跳至主要內容

luckydraw-ddd 07-ID生成领域开发

holic-x...大约 5 分钟项目luckydraw-ddd

领域开发-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

image-20220119210514617

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相关注解学习补充

评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v3.1.3