im即时通讯消息id的设计
消息id的特性
在im通讯中,消息的id在项目的功能实现中具有非常重要的作用。例如:消息接受顺序的保证,客户端消息重传时的去重,离线消息拉取的定位,以及消息幂等操作的判断等。因此消息id设计中,需要具备 递增
,唯一
消息id生成策略
对于递增、唯一有许多的设计策略,例如有:
-
主键自增:数据插入后不能获取消息id,需要再次查询获得插入的消息id
-
时间戳+UUID:长度太长,索引性能差。分布式下对时钟一致要求
-
雪花算法:使得高并发。但长度过长,可能出现丢失精度
-
使用redis自增
良好的消息id可使项目功能实现可为简洁,对此我参考的IM融云的消息id设计并对此进行的实现。
融云消息id的设计
融云消息id为16字符的字符串,字串中每个字符由5bit的数据编码而来,即消息id是由80bit的数据转换而来:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4VVp4qh-1660895848977)(C:\Users\86139\AppData\Roaming\Typora\typora-user-images\image-20220819121831978.png)]
- 时间戳:最高位的时间戳保证了id的唯一性。相比于雪花算法41位时间戳,42位的时间戳延长使用到2019年保证了使用。
- 自旋ID:在高并发场景下,同一时间戳可能有多个消息id生成。自旋id对这一时刻的消息进行自增编号,避免id的重复。12bit最多可表示4096条消息
- 会话类型:即表示会话类型:单聊、群聊、客户、聊天室等
- 会话id:如群聊id,聊天室id等
消息id的实现
这里使用Java进行实现,由于Java中64bit的long并不能存储80bit的数据,所以要对80bit的数据进行分段:
- 64bit : 时间戳 42bit | 自旋ID 12bit | 会话类型4bit | 会话ID高位6bit
- 16bit:会话ID低位
前64bit获取
/**
* @param type 会话类型
* @param highCid 会话id高位6bit
* @return 高位64bit数据
*/
private static long createHighBits(int type, int highCid) {
long highBits = System.currentTimeMillis(); // 获取时间戳
int spinId = createSpinId(); // 获取自旋id
// 依次移位
highBits = highBits<<12 | spinId;
highBits = highBits<<4 | (type&0xF);
highBits = highBits<<6 | (highCid&0x3F);
return highBits;
}
后16bit获取
long lowCid = (int) (cid&0xFFFF);
自旋ID获取
并发条件下,自旋ID获取要具有唯一性。这里采用AtomicInteger的cas机制获取
/**
* 获取自旋id : 0 ~ 0xFFF(4095)
*/
private final static int SPIN_ID_LIMIT = 0xFFF; // 最高值限制
private final static AtomicInteger INCR = new AtomicInteger(0); // 递增值
private static int createSpinId() {
int ret = INCR.getAndIncrement();
if(INCR.get() >= SPIN_ID_LIMIT) {
INCR.set(0);
}
return ret;
}
编码
对两段数据进行32位编码(5bit > 1char
// 编码转为字符串:5 bit > 1 char
private final static char[] CHS = {'2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
private static String createStrId(long highBit, long lowCid) {
char[] arr = new char[16];
int pos = 15;
while(pos >= 0) {
int tmp = 0;
if(pos>=13) {
tmp = (int) (lowCid&0x1F);
lowCid>>=5;
} else if(pos == 12) {
tmp = (int) ((highBit&0xF)<<1 | lowCid);
highBit >>= 5;
} else {
tmp = (int) (highBit&0x1F);
highBit >>= 5;
}
arr[pos--] = CHS[tmp];
}
return new String(arr);
}
测试
多个线程去获取消息id。判断是否具有递增,唯一,以及同一时刻获取的id是否唯一?
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i=0; i<10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for(int i=0; i<10; i++)
log.debug("生成id : {}", createId(0xF,0x3FFFFF));
}
});
}
}
测试结果
16:14:46.557 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4i22zzzzz
16:14:46.558 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4n29zzzzz
16:14:46.562 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6a2czzzzz
16:14:46.562 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6a2dzzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2ezzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2fzzzzz
16:14:46.558 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4n28zzzzz
16:14:46.558 [pool-1-thread-2] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4i23zzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2gzzzzz
16:14:46.558 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4n26zzzzz
16:14:46.557 [pool-1-thread-8] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz522bzzzzz
16:14:46.558 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4n2azzzzz
16:14:46.557 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4n27zzzzz
16:14:46.557 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4i24zzzzz
16:14:46.557 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz4i25zzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2izzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2hzzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2szzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2tzzzzz
16:14:46.563 [pool-1-thread-2] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2jzzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2uzzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2wzzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2vzzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2kzzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2yzzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2zzzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e32zzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e35zzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e34zzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e36zzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e37zzzzz
16:14:46.563 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2mzzzzz
16:14:46.563 [pool-1-thread-8] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2nzzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e39zzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2pzzzzz
16:14:46.563 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3bzzzzz
16:14:46.563 [pool-1-thread-8] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3czzzzz
16:14:46.563 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2qzzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3ezzzzz
16:14:46.563 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3fzzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2rzzzzz
16:14:46.563 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3hzzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3izzzzz
16:14:46.563 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3jzzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3kzzzzz
16:14:46.563 [pool-1-thread-2] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e2xzzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3nzzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e33zzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3qzzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3szzzzz
16:14:46.563 [pool-1-thread-9] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e38zzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3tzzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3uzzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3azzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3wzzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3xzzzzz
16:14:46.563 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3yzzzzz
16:14:46.563 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3zzzzzz
16:14:46.563 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e42zzzzz
16:14:46.563 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3dzzzzz
16:14:46.563 [pool-1-thread-8] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3gzzzzz
16:14:46.564 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i44zzzzz
16:14:46.563 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3mzzzzz
16:14:46.563 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3pzzzzz
16:14:46.563 [pool-1-thread-2] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3rzzzzz
16:14:46.563 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e3vzzzzz
16:14:46.564 [pool-1-thread-5] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4azzzzz
16:14:46.564 [pool-1-thread-7] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6e43zzzzz
16:14:46.564 [pool-1-thread-10] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4czzzzz
16:14:46.564 [pool-1-thread-1] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i45zzzzz
16:14:46.564 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i46zzzzz
16:14:46.564 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i48zzzzz
16:14:46.564 [pool-1-thread-8] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i47zzzzz
16:14:46.564 [pool-1-thread-4] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4gzzzzz
16:14:46.564 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i49zzzzz
16:14:46.564 [pool-1-thread-3] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4hzzzzz
16:14:46.564 [pool-1-thread-6] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4kzzzzz
16:14:46.564 [pool-1-thread-2] INFO com.wcw.lowchat.util.MyIdGenerator - 生成id : 83dcctz6i4bzzzzz
...
参考文章
http://www.52im.net/thread-1998-1-1.html
http://www.52im.net/thread-2747-1-1.html
https://www.cnblogs.com/jiangxinlingdu/p/8440413.html