javaID⽣成策略个⼈总结
先研究⼀下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类的标识符属性获取,l⽂件中关于标识符属性的设置信息如下:
<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.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.Id());
}
}
这样是不是就能唯⼀的确定⼀个值了,?
我们项⽬中正好是⽤的是springboot,⾥⾯有workid,也有⾃⼰的zookeeper,是⽤起来应该还可以,模仿⼀个:构成为
1. 进程起始时间戳,或者进程启动ID(由指定⽂件加载并写⼊)
2. ⾃增序列
3. 节点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 urrent.atomic.AtomicLong;
/**
* Generate unique ID in a cluster
*/
public class IdGenerator {
/**
* Implementation of { 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 (ists()) {
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.String(id), file);
}
}
@Override
public long startId() {
return id;
}
}
/**
* Default start ID provider will try to use the { act.util.IdGenerator.StartIdProvider.FileBasedStartCounter}. In case          * File IO is not allowed (e.g. in GAE), then it will use { 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
hibernate要学多久*/
public static interface SequenceProvider {
long seqId();
public static class AtomicLongSeq implements SequenceProvider {
private final AtomicLong seq = new AtomicLong(0);

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。

发表评论