多少英雄好汉都倒在了生成ID这条路上(activiti6)
又有好多天没有写博客了,刚换工作比较忙,总感觉不写点博客就没有进步。这篇标题主要是最近工作用到了activiti,使用过程中发现了一个问题,本着发现问题首先百度,百度讲不清楚就自己研究,自己研究出来了就博客分享,让后人少走弯路的理念,所以写了这篇博客!
首先从activiti6.0的默认ID生成说起:activiti6有个表如下
由百度知道,activiti中的ID生成是由DbIdGenerator这个类完成的,如下
点进入GetNextIdBlockCmd这个类中
这里有点头大了,每次取一块ID,结果没有看到啥时候更新那个next.dbid,傻子都不知道只知道取,不更新绝对会有问题,所以必然有地方更新,思路如下
1、想到这东西是用mybatis,然后我们找一下那个sql的xml:
在scope下全局搜索updateProperty
真的是怀疑人生了,根本找不到哪里调用了这个更新的sql
2、这个Cmd里先查出数据,然后setValue方法改一下值,仿佛等着后面有逻辑帮他调用一下那个更新方法一样,根据经验拦截器,AOP这些是可以做到这一点的,如果用到了这种通用处理方式,确实不大可能直接写死调用updateProperty方法,所以updateProperty这个sql的Id没法直接查到,这时候最先想到的就是拼接,update + Property这个类名,所以可以单独全局搜索”update”,如下
使用快捷键查找哪里用到这里了
每个都点进去看一下就知道这操作有多骚了,
再次回到ID生成那个类中
先描述一下这个ID生成作者的思想,他是想每次获取id时一次获取一整块ID,并且通过一个拦截器取更新最大ID值到next.dbid的数据库表的字段中,每次取一个id然后nextId自增1,当取到超过整块ID的最大值时lastId < nextId,然后再次通过查数据库获取一整块ID,一直重复这个过程。当然每次重启其实都会重新获取一块ID的,因为lastId的默认值时-1,nextId默认值时0,重启必然就会满足这个条件。 在如此之长的骚操作面前,这两个小小的synchronized有用吗?下面以两个线程的访问时序图来揭示答案
然后所以网上都说activiti的默认ID策略并发会有问题,但是没有说清楚原因。在我看来这种代码要是放在我们自己的程序中就是bug,放在开源的软件中,就被美化成了默认ID策略不支持高并发,何止是不支持高并发啊,感觉稍微一点并发就可能莫名其妙中招啊。然后就有人建议用UUID了,通过查看实现类可以看到activiti就两种ID策略
说到这,该点题了,不然感觉成零分作文了,以我这么多年开发经验来看接触到的ID生成策略如下
1、数据库自增
很明显的弊端就是,不支持高并发了
2、ID批量预取
这里的activiti6的默认ID生成规则就是ID批量预取了,其实这个是没有处理好,要是取得一个ID块后立马在同一个synchronized块里更新一下那个next.dbid也不会死的这么难看了,不过就算是按照这种方式实现了,也难免出现一些问题,浪费ID、那一块ID在使用完得那一刻多个现场都会同时去访问数据库,这时候得并发其实也不会很理想
3、雪花算法
很多人推崇得方法,用起来简单,出问题之后想死,典型场景就是服务器因为各种原因,时间倒退了一点点(专业点:时钟回拨),很大可能出现ID冲突,毕竟算法里强依赖当前时间
4、各种花里胡哨的ID策略
大多都是ID批量预取的变种,比如搞个线程在后台一直生成一批批得ID,再比如按照业务分块,每个业务单独使用一块块ID,其实都有自己的问题,最主要是实现得很复杂
4、UUID
这是一种已经放弃抵抗,认命的最终解决方案,缺点也很明显,懂一点索引的应该知道,要想索引效率高,少走IO,要尽量保证ID短小和有顺序,一个UUID就32位,而且还没有顺序,你甚至可以感觉到那索引树又要长高不少,那IO次数又要多多少了。
ID生成真心是一个老大难问题,就像1+1=2都很难证明一样,往往看似简单的问题,其实很复杂,要不然怎么activiti这么牛B的代码都会栽倒这里了。再说一下,我看网上好多人问怎么实现自定义的activiti的ID生成器,难道看了上面的实现类,都不知道怎么自定义吗,你自己实现一下IdGenerator,然后重写唯一的方法String getNextId()都做不到吗?还是打个样吧
package com.rd.activiti.gen; import org.activiti.engine.impl.cfg.IdGenerator; import java.util.concurrent.atomic.AtomicLong; /** * @author: rongdi * @date: 2020-12-14 22:49 */ public class MyGen implements IdGenerator { private AtomicLong aLong = new AtomicLong(100); @Override public synchronized String getNextId() { return String.valueOf(aLong.incrementAndGet()); } }
<property name="idGenerator"> <bean class="com.rd.activiti.gen.MyGen" /> </property>
最后总结下:ID这个老大难问题,只能是具体业务具体分析了,往往最终的实现是和自己业务特性强绑定或者是在性能和满足业务之间做一定的妥协之后的产物。