> 文档中心 > 分布式ID之滴滴Tinyid源码分析

分布式ID之滴滴Tinyid源码分析

目录

  • 1、客户端
    • 1.1.实例化client对象
      • 1.1.1.TinyId
      • 1.1.2.IdGeneratorFactoryClient
    • 1.2.初始化ID信息
      • 1.2.1.TinyId
      • 1.2.2、AbstractIdGeneratorFactory
      • 1.2.3、IdGeneratorFactoryClient
      • 1.2.4、CachedIdGenerator
      • 1.2.5、HttpSegmentIdServiceImpl
    • 1.3.获取ID
      • 1.3.1.TinyId
      • 1.3.2、CachedIdGenerator
      • 1.3.3、SegmentId
    • 1.4.总结:
  • 2、服务端
    • 2.1.IdContronller
    • 2.2、DbSegmentIdServiceImpl
    • 2.3、定时加载token信息

1、客户端

1.1.实例化client对象

核心类及代码如下:

1.1.1.TinyId

private static IdGeneratorFactoryClient client = IdGeneratorFactoryClient.getInstance(null);

1.1.2.IdGeneratorFactoryClient

public class IdGeneratorFactoryClient extends AbstractIdGeneratorFactory {    //引用客户端项目中配置文件private static final String DEFAULT_PROP = "tinyid_client.properties";//请求服务端的url    private static String serverUrl = "http://{0}/tinyid/id/nextSegmentIdSimple?token={1}&bizType=";//初始化信息public static IdGeneratorFactoryClient getInstance(String location) { if (idGeneratorFactoryClient == null) {     synchronized (IdGeneratorFactoryClient.class) {  if (idGeneratorFactoryClient == null) {      if (location == null || "".equals(location)) {   init(DEFAULT_PROP);      } else {   init(location);      }  }     } } return idGeneratorFactoryClient;    }      //封装数据private static void init(String location) {     idGeneratorFactoryClient = new IdGeneratorFactoryClient();     Properties properties = PropertiesLoader.loadProperties(location);     String tinyIdToken = properties.getProperty("tinyid.token");     String tinyIdServer = properties.getProperty("tinyid.server");     String readTimeout = properties.getProperty("tinyid.readTimeout");     String connectTimeout = properties.getProperty("tinyid.connectTimeout");    }}

1.2.初始化ID信息

核心类及代码如下:

1.2.1.TinyId

public class TinyId {    public static Long nextId(String bizType) {     if (bizType == null) {  throw new IllegalArgumentException("type is null");     }     //最终目的就是初始化SegmentId对象,获取id相关信息     IdGenerator idGenerator = client.getIdGenerator(bizType);     ........    }}

1.2.2、AbstractIdGeneratorFactory

public abstract class AbstractIdGeneratorFactory implements IdGeneratorFactory {    private static ConcurrentHashMap<String, IdGenerator> generators = new ConcurrentHashMap<>();    //优先查询缓存中是否存在Id信息    @Override    public IdGenerator getIdGenerator(String bizType) { if (generators.containsKey(bizType)) {     return generators.get(bizType); } synchronized (this) {     if (generators.containsKey(bizType)) {  return generators.get(bizType);     }     //不存在则发起Http请求获取     IdGenerator idGenerator = createIdGenerator(bizType);     generators.put(bizType, idGenerator);     return idGenerator; }    }    /     * 根据bizType创建id生成器     *     * @param bizType     * @return     */    protected abstract IdGenerator createIdGenerator(String bizType);}

1.2.3、IdGeneratorFactoryClient

public class IdGeneratorFactoryClient extends AbstractIdGeneratorFactory {@Override    protected IdGenerator createIdGenerator(String bizType) { return new CachedIdGenerator(bizType, new HttpSegmentIdServiceImpl());    }}

1.2.4、CachedIdGenerator

public class CachedIdGenerator implements IdGenerator {    public CachedIdGenerator(String bizType, SegmentIdService segmentIdService) { this.bizType = bizType; this.segmentIdService = segmentIdService; loadCurrent();    }    //初始化数据,在每次启动项目时都会进行    public synchronized void loadCurrent() { if (current == null || !current.useful()) {     if (next == null) {   //从服务端获取id信息  SegmentId segmentId = querySegmentId();  this.current = segmentId;     } else {  current = next;  next = null;     } }    }    private SegmentId querySegmentId() { String message = null; try {     //调用HttpSegmentIdServiceImpl的getNextSegmentId方法,从服务端获取id信息     SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);     if (segmentId != null) {  return segmentId;     } } catch (Exception e) {     message = e.getMessage(); } throw new TinyIdSysException("error query segmentId: " + message);    }}

1.2.5、HttpSegmentIdServiceImpl

public class HttpSegmentIdServiceImpl implements SegmentIdService {    private static final Logger logger = Logger.getLogger(HttpSegmentIdServiceImpl.class.getName());    @Override    public SegmentId getNextSegmentId(String bizType) { String url = chooseService(bizType);  /* post请求服务端,获取数据,服务端返回各个如下:"currentId,loadingid,maxId,delta,remainder",比如:"1,20,100,1,1" currentId:当前ID loadingid:当该值小于当前ID时会请求一次服务请求,更新Id信息,当前Id+步长*0.2 maxId:当前能够获取的最大Id delta:每次id增量 remainder:余数量 */ String response = TinyIdHttpUtils.post(url, TinyIdClientConfig.getInstance().getReadTimeout(),  TinyIdClientConfig.getInstance().getConnectTimeout()); logger.info("tinyId client getNextSegmentId end, response:" + response); if (response == null || "".equals(response.trim())) {     return null; } SegmentId segmentId = new SegmentId(); String[] arr = response.split(","); segmentId.setCurrentId(new AtomicLong(Long.parseLong(arr[0]))); segmentId.setLoadingId(Long.parseLong(arr[1])); segmentId.setMaxId(Long.parseLong(arr[2])); segmentId.setDelta(Integer.parseInt(arr[3])); segmentId.setRemainder(Integer.parseInt(arr[4])); return segmentId;    }}

1.3.获取ID

1.3.1.TinyId

public class TinyId {    public static Long nextId(String bizType) { if (bizType == null) {     throw new IllegalArgumentException("type is null"); } ... ... //获取id return idGenerator.nextId();    }}

1.3.2、CachedIdGenerator

public class CachedIdGenerator implements IdGenerator {@Override    public Long nextId() { while (true) {     if (current == null) {  loadCurrent();  continue;     }     //获取下一次Id     Result result = current.nextId();     //大于最大Id,重新进行初始化     if (result.getCode() == ResultCode.OVER) {  loadCurrent();     } else {  if (result.getCode() == ResultCode.LOADING) {      //更新下一次数据Id信息,loadingId,只是让数据库提前更新Id信息,保证系统的健壮性      loadNext();  }  //无论是否大于loadingId都会返回当前Id  return result.getId();     } }    }}  //更新数据Id信息,loadingId,只是让数据库提前更新Id信息,保证系统的健壮性public void loadNext() { if (next == null && !isLoadingNext) {     synchronized (lock) {  if (next == null && !isLoadingNext) {      isLoadingNext = true;      executorService.submit(new Runnable() {   @Override   public void run() {try {    // 无论获取下个segmentId成功与否,都要将isLoadingNext赋值为false    next = querySegmentId();} finally {    isLoadingNext = false;}   }      });  }     } }    }

1.3.3、SegmentId

public class SegmentId {public Result nextId() { init(); //本地获取Id值,返回的Id为Id的增量 long id = currentId.addAndGet(delta); //大于最大Id if (id > maxId) {     return new Result(ResultCode.OVER, id); } //大于loadingId:当前ID+步长*0.2 if (id >= loadingId) {     return new Result(ResultCode.LOADING, id); } //正常 return new Result(ResultCode.NORMAL, id);    }}

1.4.总结:

具体流程如下:

  • 1、实例化client对象—>读取配置文件,请求服务端的封装URL信息。

  • 2、初始化ID信息—>判断缓存是否存在,存在则直接返回,—>不存在则请求服务端获取ID信息,存入本地。

  • 3、获取Id—>判断是否进行了初始化,没有则进行 ,—>获取Id时,判断是否大于最大id,判断是否大于loadingId,如果大于最大Id则重新初始化,—>如果loadingId则请求服务端更新Id信息,但是还是会返回当前Id,只是让数据库提前更新Id信息,保证系统的健壮性。

    客户端加载Id时,先判断是否存在本地缓存,如何没有则从服务端获取,并且存入本地。 客户端进行一次http会返回5个属性:currentId,loadingid,maxId,delta,remainder 当客户端存在缓存时,通过currentId.addAndGet(delta); 如何获取最新的Id值,大于loadingId(http返回的当前+步长*0.2)时,客户端会重新发起http请求,目的时让数据库更新信息,保证系统的健壮性。

2、服务端

2.1.IdContronller

客户端封装的URL,就是该请求:nextSegmentIdSimple

 @RequestMapping("nextSegmentIdSimple")    public String nextSegmentIdSimple(String bizType, String token) { logger.info("nextSegmentIdSimple ==== " + bizType); //对token进行验证 if (!tinyIdTokenService.canVisit(bizType, token)) {     return ""; } String response = ""; try {     SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);     response = segmentId.getCurrentId() + "," + segmentId.getLoadingId() + "," + segmentId.getMaxId()      + "," + segmentId.getDelta() + "," + segmentId.getRemainder(); } catch (Exception e) {     logger.error("nextSegmentIdSimple error", e); } return response;    }

2.2、DbSegmentIdServiceImpl

请求数据库,获取当前的ID信息,及更新ID信息

public class DbSegmentIdServiceImpl implements SegmentIdService {    public SegmentId getNextSegmentId(String bizType) { // 获取nextTinyId的时候,有可能存在version冲突,需要重试 for (int i = 0; i < Constants.RETRY; i++) {     //获取ID信息     TinyIdInfo tinyIdInfo = tinyIdInfoDAO.queryByBizType(bizType);     if (tinyIdInfo == null) {  throw new TinyIdSysException("can not find biztype:" + bizType);     }     Long newMaxId = tinyIdInfo.getMaxId() + tinyIdInfo.getStep();     Long oldMaxId = tinyIdInfo.getMaxId();     //更新ID最大值     int row = tinyIdInfoDAO.updateMaxId(tinyIdInfo.getId(), newMaxId, oldMaxId, tinyIdInfo.getVersion(),      tinyIdInfo.getBizType());     if (row == 1) {  tinyIdInfo.setMaxId(newMaxId);  //封装数据返回到客户端  SegmentId segmentId = convert(tinyIdInfo);  logger.info("getNextSegmentId success tinyIdInfo:{} current:{}", tinyIdInfo, segmentId);  return segmentId;     } else {  logger.info("getNextSegmentId conflict tinyIdInfo:{}", tinyIdInfo);     } } throw new TinyIdSysException("get next segmentId conflict");    }    //封装数据    public SegmentId convert(TinyIdInfo idInfo) { SegmentId segmentId = new SegmentId(); segmentId.setCurrentId(new AtomicLong(idInfo.getMaxId() - idInfo.getStep())); segmentId.setMaxId(idInfo.getMaxId()); segmentId.setRemainder(idInfo.getRemainder() == null ? 0 : idInfo.getRemainder()); segmentId.setDelta(idInfo.getDelta() == null ? 1 : idInfo.getDelta()); //设置LoadingId值,默认为达到步长的20%,客户端获取的当前Id>LoadingId时就会再次发起服务端请求 segmentId.setLoadingId(segmentId.getCurrentId().get() + idInfo.getStep() * Constants.LOADING_PERCENT / 100); return segmentId;    }}

2.3、定时加载token信息

具体时间可以自定义,并且也可以取消Token信息验证

public class TinyIdTokenServiceImpl implements TinyIdTokenService {     /     * 1分钟刷新一次token     */    @Scheduled(cron = "0 0/1 * * * ?")    public void refresh() { logger.info("refresh token begin"); init();    }    @PostConstruct    private synchronized void init() { logger.info("tinyId token init begin"); List<TinyIdToken> list = queryAll(); Map<String, Set<String>> map = converToMap(list); token2bizTypes = map; logger.info("tinyId token init success, token size:{}", list == null ? 0 : list.size());    }}