先研究一下hibernate的几个主键生成策略
1、uuid生成策略
uuid生成策略采用128位的UUID算法来生成一个字符串类型的主键值,这个算法使用IP地址、JVM的启动时间(精确到1/4秒)、系统时间 和一个计数器值(在当前的JVM中唯一)经过计算来产生标识符属性值,可以用于分布式的Hibernate应用中。产生的标识符属性是一个32位长度的字 符串。使用这种生成策略,对应持久化类中标识符属性的类型应该设置为String类型,其示例配置信息如下所示。
<id name="id" type="java.lang.String" column="ID"> <generator class="uuid"> </generator> </id>
这种标识符属性生成策略生成的数值可以保证多个数据库之间的唯一性,由于该值是32位长的字符串,所以占用的数据库空间较大。推荐在实际开发中使用这种生成策略。
2、guid生成策略
这种标识符属性生成策略借助MS SQL Server或者MySQL数据库中的GUID字符串产生标识符属性值。如果使用MS SQL Server数据库,表中需要把标识符属性对应的字段类型设置为"uniqueidentifier"。其示例配置信息如下:
<id name="id" type="java.lang.String" column="ID"> <generator class="guid"> </generator> </id>
说明:mysql5.6以上出现了GUID,但是GUID很大,而且如果需要建索引需要拿性能会比较差。这样对于某些查询只需要索引
或者需要利用索引来满足高并发下的性能的话,GUID会是一个性能瓶颈。
一致性哈希能够来解决GUID和分片问题,在多写少读下比较好,但是mysql确实用来优化为快速的随机读。
怎么避免单点故障问题还没有有效的方案,只是通过这两台机器做主备和负载均衡。
3、native生成策略
由Hibernate根据所使用的数据库支持能力从identity、sequence或者hilo生成策略中选择一种,其示例配置信息如下:
<id name="id" type="java.lang.Integer" column="ID"> <generator class="native"> </generator> </id>
使用这种标识符属性生成策略可以根据不同的数据库采用不同的生成策略,如Oracle中使用sequence,在MySQL中使用identity便于Hibernate应用在不同的数据库之间移植。
这个做的挺好,可以根据数据库的不同,选择不同的生成策略,但是核心的生成不在这里,在数据库的生成策略,到分布式的时候依旧会出现问题。
4、assigned生成策略
assigned生成策略由Hibernate应用自定义标识符属性的数值,即在调用Session对象的save()方法持久化对象时,需要首先为持久化对象的标识符属性赋值。
如果<generator>元素没有设置主键生成策略,则默认为assigned生成策略,其示例配置信息如下:
<id name="id" type="java.lang.Integer" column="ID"> <generator class="assigned"></generator> </id>
这里我们可以根据这种设置,设置不同的标识符属性,以此来匹配分布式环境和多数据库的需求。
5、foreign生成策略
foreign生成策略通过关联的持久化对象为当前持久化对象设置标识符属性值,当处理持久化类一对一关联时,一个持久化类的标识符属性值可以参考关联持久化类的标识符属性值获取。
例如,User类和Profile类标识符属性都是id,二者是一对一的关联关系。可以设置Profile类的标识符属性值通过User类的标识符属性获取,Profile.hbm.xml文件中关于标识符属性的设置信息如下:
<id name="id" type="java.lang.Integer" column="ID" > <generator class="foreign"> <param name="property">user</param> </generator> </id>
这里其实就是两个关联,太长且没有意义。
hibernate还有其它的生成策略。比如:increment生成策略(最大加1)、identity生成策略(根据特定值自动增加)、sequence生成策略(数据库创建序列)、hilo生成策略(高地位算法,需要建表)、seqhilo生成策略(高地位算法,但是它使用指定的sequence获取高位值)。这个组合基本满足分布式、单机需求,但是核心要么依赖数据库,高并发下会存在一定的问题。要么就是sequence,可用数据库,也可利用接口自己设置初始值。感觉灵活度还是不高。
再来看看其它的策略
根据特定算法生成唯一id:
可重现的id生成方案:使用用户提供的特定的数据源(登录凭证),通过某种算法生成id,这个过程可重现的,只要用户提供的数据源是唯一的,那么生成的id也是唯一的。
例如通过用户注册的email+salt,使用摘要算法(md5/sha)生成128bit的数据,然后通过混合因子转变为一个long类型的数据是64bit,有264 个可用数据,理论上冲突几率极低,优点:可用保证id固定的,每次通过email登录,直接能得到id,不需要访问数据库查询id。
不可重现的方案:
使用每个服务器环境的如下参数:
1. 服务器网卡MAC地址/IP地址(确保服务器之间不冲突)
2. 每个生成ID的程序的唯一编号(确保同一服务器上的不同服务之间不冲突)
3. 程序每次启动的唯一编号(确保程序的每次启停之间不冲突)
4. 启动后内存里的序列号/系统当前时间(确保程序的一次运行期内不冲突)
以及其他的参数,混合生成id,保证多台服务器、多个线程生成的id不冲突。
例如:
UUID.randomUUID().toString() 生成的是length=32的16进制格式的字符串,如果回退为byte数组共16个byte元素,即UUID是一个128bit长的数字,一般用16进制表示。算法的核心思想是结合机器的网卡、当地时间、一个随即数来生成UUID。从理论上讲,如果一台机器每秒产生10000000个GUID,则可以保证(概率意义上)3240年不重复
市面上流行的方式个人觉得比较好的就是利用zookeeper了,比如Twitter的Snowflake,
是由(时间+应用的workId+应用的内存的sequence)生成
来段代码看看吧,废话不说:
package com.idworker; public class IdWorker { private final long workerId; private final static long twepoch = 1288834974657L; private long sequence = 0L; private final static long workerIdBits = 4L; public final static long maxWorkerId = -1L ^ -1L << workerIdBits; private final static long sequenceBits = 10L; private final static long workerIdShift = sequenceBits; private final static long timestampLeftShift = sequenceBits + workerIdBits; public final static long sequenceMask = -1L ^ -1L << sequenceBits; private long lastTimestamp = -1L; public IdWorker(final long workerId) { super(); if (workerId > this.maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format( "worker Id can't be greater than %d or less than 0", this.maxWorkerId)); } this.workerId = workerId; } public synchronized long nextId() { long timestamp = this.timeGen(); if (this.lastTimestamp == timestamp) { this.sequence = (this.sequence + 1) & this.sequenceMask; if (this.sequence == 0) { System.out.println("###########" + sequenceMask); timestamp = this.tilNextMillis(this.lastTimestamp); } } else { this.sequence = 0; } if (timestamp < this.lastTimestamp) { try { throw new Exception( String.format( "Clock moved backwards. Refusing to generate id for %d milliseconds", this.lastTimestamp - timestamp)); } catch (Exception e) { e.printStackTrace(); } } this.lastTimestamp = timestamp; long nextId = ((timestamp - twepoch << timestampLeftShift)) | (this.workerId << this.workerIdShift) | (this.sequence); System.out.println("timestamp:" + timestamp + ",timestampLeftShift:" + timestampLeftShift + ",nextId:" + nextId + ",workerId:" + workerId + ",sequence:" + sequence); return nextId; } private long tilNextMillis(final long lastTimestamp) { long timestamp = this.timeGen(); while (timestamp <= lastTimestamp) { timestamp = this.timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } public static void main(String[] args){ IdWorker worker2 = new IdWorker(2); System.out.println(worker2.nextId()); } } 这样是不是就能唯一的确定一个值了,? 我们项目中正好是用的是springboot,里面有workid,也有自己的zookeeper,是用起来应该还可以,
模仿一个:构成为
- 进程起始时间戳,或者进程启动ID(由指定文件加载并写入)
- 自增序列
- 节点ID
package act.util;
import org.joda.time.DateTime;
import org.osgl.$;
import org.osgl.util.E;
import org.osgl.util.IO;
import org.osgl.util.S;
import java.io.File;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicLong;
/**
* Generate unique ID in a cluster
*/
public class IdGenerator {
/**
* Implementation of {@code StartIdProvider} shall return a
* unique id per each system start
*/
public static interface StartIdProvider {
/**
* Returns the system start ID. The start ID shall be different
* between System starts, but it shall remaining the same value
* within one system life time
*/
long startId();
/**
* Generate system start ID based on timestamp
*/
public static class Timestamp implements StartIdProvider {
private final long id;
public Timestamp() {
long origin = DateTime.parse("2016-05-10").getMillis();
// let's assume the system cannot be restart within 10 seconds
long l = ($.ms() - origin) / 1000 / 10;
id = l;
}
@Override
public long startId() {
return id;
}
}
/**
* Generate system start ID based on incremental sequence. The newly generated ID
* will be write to a File
*/
public static class FileBasedStartCounter implements StartIdProvider {
private final long id;
public FileBasedStartCounter() {
this(".global.id.do-not-delete");
}
public FileBasedStartCounter(String path) {
File file = new File(path);
if (file.exists()) {
String s = IO.readContentAsString(file);
long seq = Long.parseLong(s);
seq = seq + 1;
IO.writeContent(S.str(seq), file);
id = (seq);
} else {
id = 0;
IO.writeContent(Long.toString(id), file);
}
}
@Override
public long startId() {
return id;
}
}
/**
* Default start ID provider will try to use the {@link act.util.IdGenerator.StartIdProvider.FileBasedStartCounter}. In case
* File IO is not allowed (e.g. in GAE), then it will use {@link act.util.IdGenerator.StartIdProvider.Timestamp}
*/
public static class DefaultStartIdProvider implements StartIdProvider {
private StartIdProvider delegate;
public DefaultStartIdProvider() {
this(".global.id.do-not-delete");
}
public DefaultStartIdProvider(String path) {
try {
delegate = new FileBasedStartCounter(path);
delegate.startId();
} catch (Exception e) {
delegate = new Timestamp();
}
}
@Override
public long startId() {
return delegate.startId();
}
}
}
/**
* {@code SequenceProvider} shall generate unique ID within
* one JVM per each call
*/
public static interface SequenceProvider {
long seqId();
public static class AtomicLongSeq implements SequenceProvider {
private final AtomicLong seq = new AtomicLong(0);
@Override
public long seqId() {
return (seq.incrementAndGet());
}
}
}
public static interface NodeIdProvider {
long nodeId();
public static class IpProvider implements NodeIdProvider {
private static enum EffectiveBytes {
ONE(1), TWO(2), THREE(3), FOUR(4);
private int value;
private EffectiveBytes(int value) {
this.value = value;
}
public static EffectiveBytes valueOf(int n) {
switch (n) {
case 1: return ONE;
case 2: return TWO;
case 3: return THREE;
case 4: return FOUR;
default :
throw E.unexpected("Invalid EffectiveByte value: %s", n);
}
}
}
private final EffectiveBytes effectiveBytes;
private final long id;
public IpProvider() {
this(4);
}
public IpProvider(int effectBytes) {
this.effectiveBytes = EffectiveBytes.valueOf(effectBytes);
String ip = LocalIpAddressUtil.ip();
String[] sa = ip.split("\\.");
int n = effectiveBytes.value;
long l = 0;
for (int i = 0; i < n; ++i) {
String b = sa[3 - i];
long factor = 1;
for (int j = 0; j < i; ++j) {
factor = factor * 256;
}
l += Long.valueOf(b) * factor;
}
id = (l);
}
public long nodeId() {
return id;
}
}
}
public static interface LongEncoder {
String longToStr(long l);
public abstract static class LongEncoderBase implements LongEncoder {
private final char[] digits;
private final int MAX_RADIX;
public LongEncoderBase(char[] digits) {
this.digits = digits;
this.MAX_RADIX = digits.length;
}
/**
* Code copied from JDK Long.toString(long, String)
*/
public String longToStr(long l) {
int radix = MAX_RADIX;
char[] buf = new char[65];
int charPos = 64;
boolean negative = (l < 0);
if (!negative) {
l = -l;
}
while (l <= -radix) {
buf[charPos--] = digits[(int)(-(l % radix))];
l = l / radix;
}
buf[charPos] = digits[(int)(-l)];
if (negative) {
buf[--charPos] = '-';
}
return new String(buf, charPos, (65 - charPos));
}
}
}
public static class UnsafeLongEncoder extends LongEncoder.LongEncoderBase {
/**
* Extended char table for representing a number as a String
*/
private final static char[] digits = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '!', '$', '%', '&',
'.', ',', ';', ':', '=', '?',
'+', '-', '*', '/', '<', '>',
'_', '~', '#', '^', '@', '|',
'(', ')', '[', ']', '{', '}'
};
public UnsafeLongEncoder() {
super(digits);
}
}
public static class SafeLongEncoder extends LongEncoder.LongEncoderBase {
/**
* Extended char table for representing a number as a String
*/
private final static char[] digits = {
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'a', 'b',
'c', 'd', 'e', 'f', 'g', 'h',
'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F',
'G', 'H', 'I', 'J', 'K', 'L',
'M', 'N', 'O', 'P', 'Q', 'R',
'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', '.', '-', '_', '~',
};
public SafeLongEncoder() {
super(digits);
}
public static void main(String[] args) {
System.out.println(new SafeLongEncoder().longToStr(Long.MAX_VALUE));
System.out.println(new UnsafeLongEncoder().longToStr(Long.MAX_VALUE));
System.out.println(Long.MAX_VALUE);
}
}
public static final LongEncoder SAFE_ENCODER = new SafeLongEncoder();
public static final LongEncoder UNSAFE_ENCODER = new UnsafeLongEncoder();
private final NodeIdProvider nodeIdProvider;
private final StartIdProvider startIdProvider;
private final SequenceProvider sequenceProvider;
private LongEncoder longEncoder;
/**
* Create a default IdGenerator with following configuration:
* <ul>
* <li>Node ID provider: four byte IP address</li>
* <li>Start ID provider: stored in <code>.global.id.do-not-delete</code> file</li>
* <li>Sequence ID provider: Atomic Long sequence</li>
* <li>Long Encoder: {@link SafeLongEncoder}</li>
* </ul>
*/
public IdGenerator() {
this(new NodeIdProvider.IpProvider(), new StartIdProvider.DefaultStartIdProvider(),
new SequenceProvider.AtomicLongSeq(), SAFE_ENCODER);
}
/**
* Create a default IdGenerator with following configuration:
* <ul>
* <li>Node ID provider: four byte IP address</li>
* <li>Start ID provider: stored in <code>.global.id.do-not-delete</code> file</li>
* <li>Sequence ID provider: Atomic Long sequence</li>
* <li>
* Long Encoder: {@link UnsafeLongEncoder} when `useUnsafeLongEncoder` is set to
* `true` or {@link SafeLongEncoder} otherwise
* </li>
* </ul>
* @param useUnsafeLongEncoder indicate use safe or unsafe long encoder
*/
public IdGenerator(boolean useUnsafeLongEncoder) {
this(new NodeIdProvider.IpProvider(), new StartIdProvider.DefaultStartIdProvider(),
new SequenceProvider.AtomicLongSeq(), useUnsafeLongEncoder ? UNSAFE_ENCODER : SAFE_ENCODER);
}
/**
* Create a default IdGenerator with specified node id provider, start id provider and sequence provider:
*/
public IdGenerator(NodeIdProvider nodeIdProvider, StartIdProvider startIdProvider, SequenceProvider sequenceProvider, LongEncoder longEncoder) {
this.nodeIdProvider = $.notNull(nodeIdProvider);
this.startIdProvider = $.notNull(startIdProvider);
this.sequenceProvider = $.notNull(sequenceProvider);
this.longEncoder = $.notNull(longEncoder);
}
/**
* Create a default IdGenerator with following configuration:
* <ul>
* <li>Node ID provider: N byte IP address, where N is specified by effectiveIpBytes argument</li>
* <li>Start ID provider: stored in <code>.global.id.do-not-delete</code> file</li>
* <li>Sequnce ID provider: Atomic Long sequence</li>
* </ul>
*/
public IdGenerator(int effectiveIpBytes) {
this.nodeIdProvider = new NodeIdProvider.IpProvider(effectiveIpBytes);
this.startIdProvider = new StartIdProvider.DefaultStartIdProvider();
this.sequenceProvider = new SequenceProvider.AtomicLongSeq();
this.longEncoder = SAFE_ENCODER;
}
/**
* Create a default IdGenerator with following configuration:
* <ul>
* <li>Node ID provider: N byte IP address, where N is specified by effectiveIpBytes argument</li>
* <li>Start ID provider: use start ID file specified by startIdFile argument</li>
* <li>Sequnce ID provider: Atomic Long sequence</li>
* </ul>
*/
public IdGenerator(int effectiveIpBytes, String startIdFile) {
this.nodeIdProvider = new NodeIdProvider.IpProvider(effectiveIpBytes);
this.startIdProvider = new StartIdProvider.DefaultStartIdProvider(startIdFile);
this.sequenceProvider = new SequenceProvider.AtomicLongSeq();
this.longEncoder = SAFE_ENCODER;
}
/**
* Create a default IdGenerator with following configuration:
* <ul>
* <li>Node ID provider: 4 byte IP address</li>
* <li>Start ID provider: use start ID file specified by startIdFile argument</li>
* <li>Sequnce ID provider: Atomic Long sequence</li>
* </ul>
*/
public IdGenerator(String startIdFile) {
this.nodeIdProvider = new NodeIdProvider.IpProvider();
this.startIdProvider = new StartIdProvider.DefaultStartIdProvider(startIdFile);
this.sequenceProvider = new SequenceProvider.AtomicLongSeq();
this.longEncoder = SAFE_ENCODER;
}
/**
* Generate a unique ID across the cluster
* @return
*/
public String genId() {
StringBuilder sb = S.builder();
sb.append(longEncoder.longToStr(nodeIdProvider.nodeId()))
.append(longEncoder.longToStr(startIdProvider.startId()))
.append(longEncoder.longToStr(sequenceProvider.seqId()));
return sb.toString();
}
public static void main(String[] args) {
IdGenerator idGen = new IdGenerator(1);
for (int i = 0; i < 10; ++i) {
System.out.println(idGen.genId());
}
idGen = new IdGenerator(4);
for (int i = 0; i < 10; ++i) {
System.out.println(idGen.genId());
}
}
}
再说一下集中存放(分布式环境), 集中存放不一定影响性能,最重要的是看你的需求和设计了,如果说你的项目并发量不是很高的时候,单独启动一个服务不同步读取即可,你可以当这些信息存入数据库中,或者直接初始化一个序列也行。如果说你的量级高了,你也只需要稍加修改i即可,使用批量分配的机制,每次请求分配很多个值,使用双缓冲,当1个 用完可以使用另一个,同时将那个已经使用的刷新。这样就可以解决这种问题,中等级别的并发使用这种方式完全能满足。如果说你的i/o超过了一台电脑的i/o速度,那这种方法就不太实用了。
个人认为集中存放还有另外一种方法,就是说给每个用户去分片,每个人相关的id从从一个片中去读区,这个量要满足他多久的时间需求就需要你设计为多大的区域,当然可以再次跟这个用户分配另一个区域(即下一跳),结合前面说的,你也可以一次多读取一些,读2个,还可以增加缓存,这样能将并发提供更多。