摘要:为了能让其它系统能够使用生成的分布式ID,我用Netty框架写了一个服务器DistributedID,用于提供分布式ID的服务。
简介      
在《SnowFlake分布式ID算法实现(Java) 》文章中详细介绍了twitter的SnowFlake算法,即著名的雪花算法,用于解决在分布式环境产生唯一标识的问题。虽然已经知道怎样生成唯一的标识,但是怎么提供服务出来使用呢?就好比有了核弹,但没有导弹也没办法实现它的威力。
     我们暂且把通过雪花算法生成的唯一标识称为分布式ID吧,为了能让其它系统能够使用生成的分布式ID,我用Netty框架写了一个服务器DistributedID,用于提供分布式ID的服务,源码目前放在github上托管,源码地址如下:

在我本机上压测的几次数据如下:

invokeAsync test num is: 85000, cast time: 137 millsec, throughput: 620.4379562043796 send/millsec
invokeAsync test num is: 90000, cast time: 246 millsec, throughput: 365.8536585365854 send/millsec
invokeAsync test num is: 95000, cast time: 190 millsec, throughput: 500.0 send/millsec
invokeAsync test num is: 100000, cast time: 124 millsec, throughput: 806.4516129032259 send/millsec
invokeAsync test num is: 105000, cast time: 249 millsec, throughput: 361.4457831325301 send/millsec

压测的机器CPU4核,内存8G,客户端只是使用了一个Channel,压测的CPU也没有跑满100%,不过这并发能力已经足够高并发需求的系统使用了。


接入方式
目前分布式ID服务器DistributedID提供了了两种接入的方式:HTTP和SDK。
  • HTTP方式
DistributedID的HTTP服务器对外端口是16830,通过HTTP方式接入时,通过http://ip:16830/getid访问即可,也可以配置域名,通过http://host:16830/getid 的方式访问。向HTTP服务器发出请求后,如果HTTP服务器能够处理,那么会向客户端返回一个全局唯一的ID,客户端可以把响应的结果直接转发成Java的长整型;如果HTTP服务器负载过重或者因为异常没办法处理时,那么服务器不会返回任何内容,响应会出现超时,客户端可以自己捕获超时异常自行处理。

  • SDK方式
DistributedID的SDK服务器对外端口是16831,通过SDK方式接入时,需要了解SDK服务器的相关协议,编写客户端程序来跟SDK服务器通信。目前我自己写了一个DistributedID的SDK ——DistributedID-SDK,源码目前放在GitHub上托管,源码地址如下:


通信协议
HTTP服务器使用的是HTTP协议,如果需要了解HTTP协议的可以自行百度,在此处说的通信协议是对于DistributedID的SDK服务器来说的。一般来说,设计一个通信协议会有两个部分,请求的协议跟响应的协议,为了编码的方便,DistributedID在设计时把请求的协议跟响应的协议合并在一起使用,协议的格式如下:
|  rqid  |  did  |
   4byte   8byte

  • rqid: 请求id,4个字节 ,客户端请求时生成,响应时返回;
  • did: 分布式id,8个字节,服务器响应时生成,请求时传入0;

可能有的童鞋会有疑问,为什么要在协议里面增加一个rqid这个字段,除了麻烦,白白浪费带宽外,好像没什么用处。其实这个字段也是可以不要的,但是如果没有这个字段的话,在实现客户端SDK时,就不会使用异步请求的方式来跟服务器通信,因为客户端没办法标识响应的内容是哪一个请求。所以rqid是用来标识响应是属于哪一个请求的,rqid是客户端生成的一个Int类型的标识,在客户端中保持唯一。另外需要注意的是,客户端请求SDK服务器时,必须填写did这个字段,可以置为0,但不能不填。

实现
DistributedID生成分布式ID的核心是twitter snowflake算法,但是因为要提供http和sdk两种方式的接入,需要开启两个不同的通信线程对外服务。因为我希望获取分布式ID服务对于请求方式是透明的,即同一时刻无论哪一种方式获取到的分布式ID都是一样,所以设计时我把提供生成ID功能的SnowFlake包装成一个单实例,然后提供给HTTP服务线程和SDK服务线程使用。

由于协议的字节数是固定的,所以在解码时使用了Netty的FixedLengthFrameDecoder 解码器,固定包长为12个字节。编码时继承MessageToByteEncoder 编码器,编码逻辑也很简单,如下所示:
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, SdkProto sdkProto, ByteBuf out) throws Exception {
    out.writeInt(sdkProto.getRqid());
    out.writeLong(sdkProto.getDid());
}

为了保护服务器,DistributedID在设计时就考虑控制并发的处理数,防止并发请求过大时,服务器负载过重而崩溃。控制并发处理数的手段也不难,编码时使用了Java的Semapore信号量,初始化Semapore的数量为最大的并发数,每次调用SnowFlake算法生成分布式ID前,需要获取Semapore的许可,如果获取不到Semapore的许可,直接忽略此次请求,代码如下:
if (semaphore.tryAcquire(GlobalConfig.ACQUIRE_TIMEOUTMILLIS, TimeUnit.MILLISECONDS)) {
    try {
        sdkProto.setDid(snowFlake.nextId());
        ctx.channel().writeAndFlush(sdkProto).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                semaphore.release();
            }
        });
    } catch (Exception e) {
        semaphore.release();
        logger.error("SdkServerhandler error", e);
    }
} else {
    sdkProto.setDid(-1);
    ctx.channel().writeAndFlush(sdkProto);
    String info = String.format("SdkServerHandler tryAcquire semaphore timeout, %dms, waiting thread " +
                    "nums: %d availablePermit: %d",    //
            GlobalConfig.ACQUIRE_TIMEOUTMILLIS, //
            this.semaphore.getQueueLength(),    //
            this.semaphore.availablePermits()  //
    );
    logger.warn(info);
    throw new RemotingTooMuchRequestException(info);
}


部署
部署之前需要把项目源码打包成jar包,或者使用项目打包好的jar包,把jar包上传到服务器,执行如下命令:

java -jar distributedid.jar 1 2
 
执行上面命令指定了两个参数1和2,前面的1代表数据中心标识,后面的2代表的是机器或进程标识.

如果不指定这两个参数,那么会使用默认的值1。如果只考虑部署单机服务器,那么可以不考虑这两个参数,如果需要分布式集群来生成ID时,需要指定数据中心标识ID和机器进程标识ID,并且每一个服务器的数据中心标识ID和机器进程标识ID作为联合键全局唯一,这样才能保证集群生成的ID都是唯一的。

 
参考


如果想交流技术或者学习,可以加QQ群:136798125 或 399643539


版权说明:如无特殊说明,文章均为本站原创,如需转载请注明出处

本文标题:基于twitter雪花算法的分布式ID —— 服务器篇

本文地址:http://www.wolfbe.com/detail/201701/386.html

本文标签: distributedid netty snowflake

相关文章

感谢您的支持,朗度云将继续前行

扫码打赏,金额随意

温馨提醒:打赏一旦完成,金额无法退还,请谨慎操作!

扫二维码 我要反馈 回到顶部