├── .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 | 
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
--------------------------------------------------------------------------------