Mysql 分布式主键增长策略

主键增长策略

自动增长 AUTO INCREMENT

不设置主键的增长起点(默认1开始)

CREATE TABLE `t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB;

设置主键的增长起点(自己给定一个初始值)

CREATE TABLE `t` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `c` (`c`)
) ENGINE=InnoDB AUTO_INCREMENT=25000;

优点:
数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利; 数字型,占用空间小,易排序,在程序中传递也方便;
如果通过非系统增加记录时,可以不用指定该字段,不用担心主键重复问题。

缺点:
因为自动增长,在手动要插入指定ID的记录时会显得麻烦,尤其是当系统与其它系统集成时,需要数据导入时,很难保证原系统的ID不发生主键冲突(前提是老系统也是数字型的)。特别是在新系统上线时,新旧系统并行存在,并且是异库异构的数据库的情况下,需要双向同步时,自增主键将是你的噩梦;
在系统集成或割接时,如果新旧系统主键不同是数字型就会导致修改主键数据类型,这也会导致其它有外键关联的表的修改,后果同样很严重;
若系统也是数字型的,在导入时,为了区分新老数据,可能想在老数据主键前统一加一个字符标识(例如“o”,old)来表示这是老数据,那么自动增长的数字型又面临一个挑战

UUID 主键增长

每次生成随机唯一的值
排序不方便

优点 :
出现数据拆分、合并存储的时候,能达到全局的唯一性

缺点:
影响插入速度, 并且造成硬盘使用率低 uuid之间比较大小相对数字慢不少, 影响查询速度。 uuid占空间大,
如果你建的索引越多, 影响越严重


redis 主键增长

当使用数据库来生成ID性能不够要求时,我们可以尝试使用Redis来生成ID,主要依赖于Redis是单线程的,所以也可以生成全局唯一的ID,可以用Redis的原子操作INCR和INCRBY来实现

转载:一渣程序猿 https://blog.csdn.net/leiyong0326/article/details/52039200

扫描二维码关注公众号,回复: 12653740 查看本文章

先定义个主键生成策略接口,往后方便扩展

/**
 * 定义主键生成策略接口,以便修改扩展
 * @author LeiYong
 *
 */
public interface KeyGenerate {
    
    
	/**
	 * 生成String类型主键
	 * @param em
	 * @return
	 */
	public String generateStringKey(KeyGenerateEnum em);
	/**
	 * 生成long类型主键
	 * @param em
	 * @return
	 */
	public Long generateLongKey(KeyGenerateEnum em);
}

接下来提供一个替补队员,基础的主键生成方案,采用随机数(Long)或UUID(String)方式生成

 * /**基础主键生成策略,采用随机数或UUID+随机数
 * @author LeiYong
 *
 */
public class BaseKeyGenerate implements KeyGenerate{
    
    
	@Override
	public String generateStringKey(KeyGenerateEnum em) {
    
    
		return StringUtil.getUUID()+StringUtil.getNonceStr(6);
	}
	@Override
	public Long generateLongKey(KeyGenerateEnum em) {
    
    
		return System.currentTimeMillis()*1000000+NumberUtil.random(6);
	}
}
/**
 * 基础Service实现类
 * @author LeiYong
 *
 * @param <T>
 */
public  class BaseServiceImpl<T extends BaseModel> implements BaseService<T> {
    
    
 
	protected BaseMapper<T> baseMapper;
 
	private T baseData;
	@Autowired
	protected KeyGenerate keyGenerate;
	
	
	public BaseMapper<T> getBaseMapper() {
    
    
		return baseMapper;
	}
 
	public void setBaseMapper(BaseMapper<T> baseMapper) {
    
    
		this.baseMapper = baseMapper;
	}
 
	public Page<T> selectByEmail(String email){
    
    
		List<Model> conditions = MyBatisUtil.parseBase("email,=,"+email);
		return getBaseMapper().selectExtend(conditions, null, null);
	}
	@Override
	public T insertSelective(T data) {
    
    
		setPk(data);
		int result = baseMapper.insertSelective(data);
		if (result==1) {
    
    
			return data;
		}
		return null;
	}
	@Override
	public int insertBatch(List<T> list) {
    
    
		ArrayUtil.foreach(list,(T t,int i) -> setPk(t));
		return baseMapper.insertBatch(list);
	}
 
 
	@Override
	public int deleteByPK(String orgPk,String pk) {
    
    
		return baseMapper.deleteByPrimaryKey(orgPk,pk);
	}
	@Override
	public int deleteByBatch(String orgPk,String[] pks) {
    
    
		return  baseMapper.deleteByBatch(orgPk,pks);
	}
	@Override
	public int updateByPK(T data) {
    
    
		
		return baseMapper.updateByPrimaryKey(data);
	}
	@Override
	public int updateEnable(String orgPk, String status, String pk) {
    
    
		return baseMapper.updateState(orgPk, "enable", status, pk);
	}
 
	@Override
	public int updateEnables(String orgPk, String status, String... pks) {
    
    
		return baseMapper.updateStates(orgPk, "enable", status, pks);
	}
	/**
	 * 根据主键pk查询基本信息
	 */
	@Override
	public T findByPK(String orgPk,String pk) {
    
    
		return  baseMapper.selectByPrimaryKey(orgPk,pk);
	}
 
	@Override
	public Page<T> findByPage(T queryInfo, int pageNum, int pageSize,String orderBy) {
    
    
		PageHelper.startPage(pageNum, pageSize);
		return baseMapper.selectAllByCondition(queryInfo,orderBy);
	}
	
	@Override
	public Page<T> findAll(T queryInfo, String orderBy) {
    
    
		return baseMapper.selectAllByCondition(queryInfo,orderBy);
	}
	public Page<T> findByPage(List<Model> conditions, int pageNum, int pageSize, String order) {
    
    
		PageHelper.startPage(pageNum, pageSize);
		return getBaseMapper().selectExtend(conditions, null, order);
	}
 
	public Page<T> findAll(List<Model> conditions, String order) {
    
    
		return getBaseMapper().selectExtend(conditions, null, order);
	}
	/**
	 * 设置pk主键
	 * @param t
	 */
	public boolean setPk(T t){
    
    
		KeyGenerateEnum em = getKeyGenerateEnum();
		if (em != null) {
    
    
			Field idField = ReflectionUtil.findField(t.getClass(), "id", Long.class);
			//如果不存在id元素则采用String方式生成
			if (idField == null) {
    
    
				t.setPk(keyGenerate.generateStringKey(em));
			} else {
    
    
				//如果存在id元素则生成Long类型主键
				try {
    
    
					idField.setAccessible(true);
					idField.set(t, keyGenerate.generateLongKey(em));
				} catch (IllegalArgumentException | IllegalAccessException e) {
    
    
					throw new RuntimeException("生成主键遇到错误");
				}
			}
			return true;
		}
		return false;
	}
	/**
	 * 获取枚举类型
	 * @return
	 */
	public KeyGenerateEnum getKeyGenerateEnum(){
    
    
		Class<?> cls = ReflectionUtil.getGenericSuperclass(this.getClass());
		if (cls!=null) {
    
    
			KeyGenerateEnum em = KeyGenerateEnum.valueOf(cls.getSimpleName());
			return em;
		}
		return null;
	}	
	public T getBaseData() {
    
    
		return baseData;
	}
	public void setBaseData(T baseData) {
    
    
		this.baseData = baseData;
	}
}

Twitter的snowflake算法(雪花算法)
snowflake是Twitter开源分布式ID生成算法,结果是一个Long类型的ID

SnowFlake是一种介于自增长和UUID之间的一种主键(存储空间小、速度快、分布式、时间序列)它有如下优点
1.所有生成的id按时间趋势递增
2.整个分布式系统内不会产生ID碰撞(重复id,因为有datacenterId和workerId来做区分)
3.id生成的效率高
SnowFlake算法生成id的结果是一个64bit大小的整数,一般用户分布式环境的id生成下面我们来看一下SnowFlake生成的id的结构

由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的
在这里插入图片描述

附上java版源码

/**
 * SnowFlake
 */
public class SnowflakeIdWorker {
    
    
    /** 
     * 开始时间截 (1971-01-01 08:00:00) 
     * 时间戳是指格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数
     * */
    private final long twepoch = 31536000L;
    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;
    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;
    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;
    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;
    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    /** 工作机器ID(0~31) */
    private long workerId;
    /** 数据中心ID(0~31) */
    private long datacenterId;
    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;
   /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;
    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
    
    
        if (workerId > maxWorkerId || workerId < 0) {
    
    
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
    
    
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
    
    
        long timestamp = timeGen();
 
        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
    
    
            throw new RuntimeException(
                    String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        //如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp) {
    
    
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
    
    
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        //时间戳改变,毫秒内序列重置
        else {
    
    
            sequence = 0L;
        }
        //上次生成ID的时间截
        lastTimestamp = timestamp;
 
        //移位并通过或运算拼到一起组成64位的ID
        return ((timestamp - twepoch) << timestampLeftShift) //
                | (datacenterId << datacenterIdShift) //
                | (workerId << workerIdShift) //
                | sequence;
    }
    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
    
    
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
    
    
            timestamp = timeGen();
        }
        return timestamp;
    }
    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
    
    
        return System.currentTimeMillis();
    }
    /** 测试 */
    public static void main(String[] args) {
    
    
        SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0 , 0);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
    
    
            long id = idWorker.nextId();
            System.out.println(id);
        }
        long end = System.currentTimeMillis();
        System.out.println("生成1000000id用时:" + (end-start) + "ms" );        
    }    
}

猜你喜欢

转载自blog.csdn.net/weixin_44313584/article/details/109700643