zoukankan      html  css  js  c++  java
  • 游戏服务器生成全局唯一ID的几种方法

    在服务器系统开发时,为了适应数据大并发的请求,我们往往需要对数据进行异步存储,特别是在做分布式系统时,这个时候就不能等待插入数据库返回了取自动id,而是需要在插入数据库之前生成一个全局的唯一id,使用全局的唯一id,在游戏服务器中,全局唯一的id可以用于将来合服方便,不会出现键冲突。也可以将来在业务增长的情况下,实现分库分表,比如某一个用户的物品要放在同一个分片内,而这个分片段可能是根据用户id的范围值来确定的,比如用户id大于1000小于100000的用户在一个分片内。目前常用的有以下几种:

    1Java 自带的UUID.

    UUID.randomUUID().toString(),可以通过服务程序本地产生,ID的生成不依赖数据库的实现。

    优势:

       本地生成ID,不需要进行远程调用。

       全局唯一不重复。

       水平扩展能力非常好。

     

    劣势:

       ID128 bits,占用的空间较大,需要存成字符串类型,索引效率极低。

       生成的ID中没有带Timestamp,无法保证趋势递增,数据库分库分表时不好依赖

    2,基于Redisincr方法

    Redis本身是单线程操作的,而incr更保证了一种原子递增的操作。而且支持设置递增步长。

    优势:

      部署方便,使用简单,只需要调用一个redisapi即可。

      可以多个服务器共享一个redis服务,减少共享数据的开发时间。

      Redis可以群集部署,解决单点故障的问题。

    劣势:

     如果系统太庞大的话,n多个服务同时向redis请求,会造成性能瓶颈。

    3,来自Flicker的解决方案

    这个解决方法是基于数据库自增id的,它使用一个单独的数据库专门用于生成id。详细的大家可以网上找找,个人觉得使用挺麻烦的,不建议使用。

    4Twitter Snowflake

    snowflaketwitter开源的分布式ID生成算法,其核心思想是:产生一个long型的ID,使用其中41bit作为毫秒数,10bit作为机器编号,12bit作为毫秒内序列号。这个算法单机每秒内理论上最多可以生成1000*(2^12)个,也就是大约400WID,完全能满足业务的需求。

    根据snowflake算法的思想,我们可以根据自己的业务场景,产生自己的全局唯一ID。因为Javalong类型的长度是64bits,所以我们设计的ID需要控制在64bits

    优点:高性能,低延迟;独立的应用;按时间有序。

    缺点:需要独立的开发和部署。

    比如我们设计的ID包含以下信息:

    | 41 bits: Timestamp | 3 bits: 区域 | 10 bits: 机器编号 | 10 bits: 序列号 |

    产生唯一IDJava代码:

    /**

    * 自定义 ID 生成器

    * ID 生成规则: ID长达 64 bits

    *

    * | 41 bits: Timestamp (毫秒) | 3 bits: 区域(机房) | 10 bits: 机器编号 | 10 bits: 序列号 |

    */

    public class GameUUID{

       // 基准时间

       private long twepoch = 1288834974657L; //Thu, 04 Nov 2010 01:42:54 GMT

       // 区域标志位数

       private final static long regionIdBits = 3L;

       // 机器标识位数

       private final static long workerIdBits = 10L;

       // 序列号识位数

       private final static long sequenceBits = 10L;

       // 区域标志ID最大值

       private final static long maxRegionId = -1L ^ (-1L << regionIdBits);

       // 机器ID最大值

       private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);

       // 序列号ID最大值

       private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

       // 机器ID偏左移10

       private final static long workerIdShift = sequenceBits;

       // 业务ID偏左移20

       private final static long regionIdShift = sequenceBits + workerIdBits;

       // 时间毫秒左移23

       private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;

       private static long lastTimestamp = -1L;

       private long sequence = 0L;

       private final long workerId;

       private final long regionId;

       public GameUUID(long workerId, long regionId) {

           // 如果超出范围就抛出异常

           if (workerId > maxWorkerId || workerId < 0) {

               throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");

           }

           if (regionId > maxRegionId || regionId < 0) {

               throw new IllegalArgumentException("datacenter Id can't be greater than %d or less than 0");

           }

           this.workerId = workerId;

           this.regionId = regionId;

       }

       public GameUUID(long workerId) {

           // 如果超出范围就抛出异常

           if (workerId > maxWorkerId || workerId < 0) {

               throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");

           }

           this.workerId = workerId;

           this.regionId = 0;

       }

       public long generate() {

           return this.nextId(false, 0);

       }

       /**

        * 实际产生代码的

        *

        * @param isPadding

        * @param busId

        * @return

        */

       private synchronized long nextId(boolean isPadding, long busId) {

           long timestamp = timeGen();

           long paddingnum = regionId;

           if (isPadding) {

               paddingnum = busId;

           }

           if (timestamp < lastTimestamp) {

               try {

                   throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");

               } catch (Exception e) {

                   e.printStackTrace();

               }

           }

           //如果上次生成时间和当前时间相同,在同一毫秒内

           if (lastTimestamp == timestamp) {

               //sequence自增,因为sequence只有10bit,所以和sequenceMask相与一下,去掉高位

               sequence = (sequence + 1) & sequenceMask;

               //判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0

               if (sequence == 0) {

                   //自旋等待到下一毫秒

                   timestamp = tailNextMillis(lastTimestamp);

               }

           } else {

               // 如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,

               // 为了保证尾数随机性更大一些,最后一位设置一个随机数

               sequence = new SecureRandom().nextInt(10);

           }

           lastTimestamp = timestamp;

           return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;

       }

       // 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势.

       private long tailNextMillis(final long lastTimestamp) {

           long timestamp = this.timeGen();

           while (timestamp <= lastTimestamp) {

               timestamp = this.timeGen();

           }

           return timestamp;

       }

       // 获取当前的时间戳

       protected long timeGen() {

           return System.currentTimeMillis();

       }

    }

    使用自定义的这种方法需要注意的几点:

    为了保持增长的趋势,要避免有些服务器的时间早,有些服务器的时间晚,需要控制好所有服务器的时间,而且要避免NTP时间服务器回拨服务器的时间;在跨毫秒时,序列号总是归0,会使得序列号为0ID比较多,导致生成的ID取模后不均匀,所以序列号不是每次都归0,而是归一个09的随机数。(本代码参考:http://www.jianshu.com/p/61817cf48cc3)

     

    上面说的这几种方式我们可以根据自己的需要去选择。在游戏服务器开发中,根据自己的游戏类型选择,比如手机游戏,可以使用简单的redis方式,简单不容易出错,由于这种游戏单服并发新建id量并不太大,完全可以满足需要。而对于大型的世界游戏服务器,它本身就是以分布式为主的,所以可以使用snowflake的方式,上面的snowflake代码只是一个例子,需要自己根据自己的需求去定制,所以有额外的开发量,而且要注意上述所说的注意事项。参考文章http://www.youxijishu.com/h-nd-147-2_323.html

  • 相关阅读:
    cin 与 getline
    ubuntu换源
    unbuntu 安装 bochs
    np.random.randint()的返回值
    vs2019 写入访问权限冲突
    44.Android之Shape设置虚线、圆角和渐变学习
    43.Android之ListView中BaseAdapter学习
    42.Android之ListView中ArrayAdapter简单学习
    Java编程思想学习(十六) 并发编程
    Java编程思想学习(十五) 注解
  • 原文地址:https://www.cnblogs.com/wgslucky/p/6006157.html
Copyright © 2011-2022 走看看