├── .travis.yml ├── README.md ├── cckeyid ├── .cache └── IdCenterSender.php ├── explame └── explame.php └── pic └── bits_struct.png /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.0 5 | - 7.1 6 | 7 | script: php explame/explame.php 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

IdCenterSender ---PHP实现-64位分布式自增发号器

2 | 3 | ### C语言实现的PHP扩展的形式 4 | 5 | - [IdCenterSender.so - PHP扩展版本](https://github.com/whiteCcinn/IdCenterSender-so) 6 | 7 | PHP实现64位分布式ID发号器 8 | 9 | ## 原理 10 | 11 | 参考Snowflake算法,根据自身设计情况扩展了其中的细节。具体组成如下图: 12 | 13 | ![bits_struct.jpg](https://raw.githubusercontent.com/whiteCcinn/IdCenterSender/master/pic/bits_struct.png) 14 | 15 | > 如图所示,64bits 分成了4个部分。 16 | 17 | > 0. 最高位舍弃 18 | > 1. 毫秒级的时间戳,有41个bit.能够使用139年,当然这些是可以扩展的,可以通知指定起始时间来延长这个日期长度。也就是说服务启动开始之后就可以持续使用139年 19 | > 2. 自定义分布式机器节点id,占位12个bit,能够支持8191个节点。部署的时候可以配置好服务器id,也就是代码里面的node_id变量,每一台机器都需要用不同的node_id来标志,就像mysql的server_id一样 20 | > 3. 进程(毫秒)自增序号。占位10bit,一毫秒能产生2047个id。 21 | 22 | ## 总结特点: 23 | - 类snowflake算法 24 | - ID发号器有效期可以延续从发布开始的139年 25 | - 分布式支持8191台机器 26 | - 单进程调用的情况下,并发每秒支持200万个ID生成 27 | 28 | ## 唯一性保证 29 | > 同一毫秒内自增变量保证并发的唯一性(采用文件锁的方式对cache文件进行锁定)。 30 | 31 | ## 使用 32 | 33 | ``` 34 | include_once '../cckeyid/IdCenterSender.php'; 35 | 36 | echo \cckeyid\IdCenterSender::getInstance()->ck_get_new_id(1); 37 | 38 | echo PHP_EOL; 39 | 40 | print_r(\cckeyid\IdCenterSender::getInstance(true)->ck_get_new_id(4)); 41 | 42 | ``` 43 | -------------------------------------------------------------------------------- /cckeyid/.cache: -------------------------------------------------------------------------------- 1 | a:2:{s:14:"last_timestamp";i:1514185077554;s:8:"sequence";i:1;} -------------------------------------------------------------------------------- /cckeyid/IdCenterSender.php: -------------------------------------------------------------------------------- 1 | * 23 | * * 24 | \**********************************************************/ 25 | 26 | namespace cckeyid; 27 | 28 | class IdCenterSender 29 | { 30 | 31 | /** 32 | * 实例化对象 33 | * 34 | * @var null 35 | */ 36 | private static $instance = null; 37 | 38 | /** 39 | * 文件句柄 40 | * 41 | * @var null 42 | */ 43 | private $fileHandler = null; 44 | 45 | /** 46 | * 文件锁 47 | * 48 | * @var null 49 | */ 50 | private $lock = null; 51 | 52 | /** 53 | * 文件大小 54 | * 55 | * @var int 56 | */ 57 | private $fileSize = 1024; 58 | 59 | /** 60 | * 生成的id 61 | * 62 | * @var array 63 | */ 64 | private $ids = []; 65 | 66 | /** 67 | * 指定分布式ID发布器启动的时间 68 | * 69 | * @var int 70 | */ 71 | private $epoch = 0; 72 | 73 | /** 74 | * 指定分布式机器的节点ID 75 | * 76 | * @var int 77 | */ 78 | private $node_id = 1; 79 | 80 | /** 81 | * 日志位置 82 | * @var string 83 | */ 84 | private static $log_path = ''; 85 | 86 | /** 87 | * 本地缓存文件 88 | */ 89 | const DATAFILE = '.cache'; 90 | 91 | // 毫秒级时间戳42个bits 92 | const TIMESTAMP_BITS = 41; 93 | 94 | // 节点ID12个bits 95 | const NODE_ID_BITS = 12; 96 | 97 | // 毫秒内自增ID 98 | const SEQUENCE_BITS = 9; 99 | 100 | // 毫秒时间戳统计偏移量 101 | const TIMESTAMP_LEFT_SHIFT = 21; 102 | 103 | // 节点统计偏移量 104 | const NODE_ID_LEFT_SHIFT = 9; 105 | 106 | /** 107 | * 私有化构造方法 108 | */ 109 | private function __construct(){} 110 | 111 | /** 112 | * 私有化克隆方法 113 | */ 114 | private function __clone(){} 115 | 116 | /** 117 | * 饿汉单例设计模式获取对象 118 | * @param bool $new 是否生成一个新的实例 119 | * 120 | * @return IdCenterSender|null 121 | */ 122 | public static function getInstance($new = false) 123 | { 124 | if (!(self::$instance instanceof self) || $new) 125 | { 126 | self::$log_path = __DIR__ . DIRECTORY_SEPARATOR; 127 | self::$instance = new self(); 128 | } 129 | 130 | return self::$instance; 131 | } 132 | 133 | /** 134 | * 文件锁 135 | */ 136 | private function _lock_file() 137 | { 138 | 139 | if (!file_exists(self::$log_path . self::DATAFILE)) 140 | { 141 | touch(self::$log_path . self::DATAFILE); 142 | } 143 | 144 | $this->fileHandler = @fopen(self::$log_path . self::DATAFILE, 'r+'); 145 | 146 | // 非阻塞排它锁 147 | $this->lock = @flock($this->fileHandler, LOCK_EX | LOCK_NB); 148 | } 149 | 150 | private function _unlock_file() 151 | { 152 | @flock($this->fileHandler, LOCK_UN); 153 | 154 | @fclose($this->fileHandler); 155 | 156 | $this->lock = null; 157 | 158 | $this->fileHandler = null; 159 | } 160 | 161 | /** 162 | * 获取当前毫秒时间戳 163 | * 164 | * @return int 165 | */ 166 | public function get_curr_timestamp_ms() 167 | { 168 | return (int)(microtime(true) * 1000); 169 | } 170 | 171 | /** 172 | * 暂停一毫秒 173 | * 174 | * @return int 175 | */ 176 | private function wait_until_next_ms() 177 | { 178 | usleep(1000); 179 | 180 | return $this->get_curr_timestamp_ms(); 181 | } 182 | 183 | /** 184 | * 获取id 185 | * 186 | * @param int $num 187 | * 188 | * @return array|int 189 | */ 190 | public function ck_get_new_id($num = 1) 191 | { 192 | $now = $this->get_curr_timestamp_ms(); 193 | 194 | // 抢夺资源锁 195 | while (!$this->lock) 196 | { 197 | $this->_lock_file(); 198 | } 199 | 200 | $data = ['last_timestamp' => 0, 'sequence' => 0]; 201 | 202 | $serializeData = @fread($this->fileHandler, $this->fileSize); 203 | 204 | if (!empty($serializeData)) 205 | { 206 | $data = unserialize($serializeData); 207 | } 208 | 209 | if ($data["last_timestamp"] == 0 || $data["last_timestamp"] > $now) 210 | { 211 | $last_timestamp = $now; 212 | $sequence = rand(0, 10) % 2; 213 | } else 214 | { 215 | $last_timestamp = $data["last_timestamp"]; 216 | $sequence = $data["sequence"]; 217 | } 218 | if ($now == $last_timestamp) 219 | { 220 | $sequence = ($sequence + 1) & ((-1 ^ (-1 << self::SEQUENCE_BITS))); 221 | 222 | // 如果一毫秒内并发2047个ID都不够分配的话,那么阻塞等待下一秒再分配 223 | if ($sequence == 0) 224 | { 225 | $now = $this->wait_until_next_ms(); 226 | } 227 | } else 228 | { 229 | $sequence = 0; 230 | } 231 | 232 | @fseek($this->fileHandler, 0); 233 | $length = @fwrite($this->fileHandler, serialize(["last_timestamp" => $now, "sequence" => $sequence])); 234 | 235 | $id = (($now - ($this->epoch * 1000) & ((-1 ^ (-1 << self::TIMESTAMP_BITS)) ^ (1 << self::TIMESTAMP_BITS))) << self::TIMESTAMP_LEFT_SHIFT) 236 | | (($this->node_id & (-1 ^ (-1 >> self::NODE_ID_BITS))) << self::NODE_ID_LEFT_SHIFT) 237 | | ($sequence); 238 | 239 | $this->_unlock_file(); 240 | 241 | $this->ids[] = $id; 242 | 243 | if ($num > 1) 244 | { 245 | $this->ck_get_new_id($num - 1); 246 | } 247 | 248 | return count($this->ids) > 1 ? $this->ids : $id; 249 | } 250 | } 251 | 252 | 253 | 254 | -------------------------------------------------------------------------------- /explame/explame.php: -------------------------------------------------------------------------------- 1 | ck_get_new_id(1); 9 | 10 | echo PHP_EOL; 11 | 12 | print_r(\cckeyid\IdCenterSender::getInstance(true)->ck_get_new_id(4)); 13 | -------------------------------------------------------------------------------- /pic/bits_struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/whiteCcinn/IdCenterSender/04da12f5aade5bbd2d483c3658f082e5db42a54c/pic/bits_struct.png --------------------------------------------------------------------------------