当前位置: 首页 > 工具软件 > tinyid > 使用案例 >

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

钱经赋
2023-12-01

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());
    }
}
 类似资料: