通常来说,不管使用什么数据库,表里都有一个名为 id 的主键,既然是主键,那么必然要满足唯一性,对于 MySQL 用户来说,它多半是一个 auto_increment 自增字段,也有一些别的用户喜欢使用 UUID 做主键,不过对 MySQL(特别是 InnoDB)来说,UUID 通常不是一个好选择,因为聚簇索引要求物理数据按照主键排序,而 UUID 本身是无序的,所以会带来很多不必要的 IO 消耗。于是乎我们得到一个结论:ID 最好是顺序的唯一值。

如此说来,就用 MySQL 的 auto_increment 自增字段不就好了?问题是这样无法满足高可用性,虽然可以通过多台服务器设置不同的 auto_increment 步长来提升可用性,但数据库本身始终就是那块最短的木板。至于解决方案,网上已经有很多类似的讨论:

  • 细聊分布式ID生成方法
  • 业务系统需要什么样的ID生成器
  • 分布式Unique ID的生成方法一览
  • 微信序列号生成器架构设计及演变

最流行的解决方案,当然是 twitter 的 snowflake,其大致含义是说:为了避免单点故障,在多个节点上运行 ID 生成器服务,每个节点都有自己独立的标识,ID 以时间因子为前缀,虽然不同的服务器时间可能存在差异,不能保证绝对的顺序,但是整体的趋势还是可以认为是顺序的,IO 负担可以忽略,同时以一个计数器为后缀,从而保证唯一性。

网上现有的开源 ID 生成器,比如 Chronos,都是运行为服务的形式,不过对我而言,这样有些太重了,于是我用 PHP 实现了一个非服务化的简版 ID 生成器,虽然它很简单,但是它并不简陋,实现了 snowflake 要求的功能:

<?phpclass Sequence
{const EPOCH = 1000000000;const TIME_BITS  = 30;const NODE_BITS  = 10;const COUNT_BITS = 20;private $node;public function __construct($node){$max = $this->max(self::NODE_BITS);if (is_int($node) === false || $node > $max || $node < 0) {throw new \InvalidArgumentException('node');}$this->node = $node;}public function generate($time = null){if ($time === null) {$time = time();}return ($this->time($time) << (self::NODE_BITS + self::COUNT_BITS)) |($this->node << self::COUNT_BITS) |($this->count($time));}public function restore($id){$binary = decbin($id);$position = -(self::NODE_BITS + self::COUNT_BITS);return array('time'  => bindec(substr($binary, 0, $position)) + self::EPOCH,'node'  => bindec(substr($binary, $position, - self::COUNT_BITS)),'count' => bindec(substr($binary, - self::COUNT_BITS)),);}private function time($time){$key = 'seq:time';if (apcu_fetch($key) === false && sleep(1) === 0) {apcu_add($key, $time);}$time -= self::EPOCH;$max = $this->max(self::TIME_BITS);if (is_int($time) === false || $time > $max || $time < 0) {throw new \InvalidArgumentException('time');}return $time;}private function count($time){$key = "seq:count:{$time}";while (!$count = apcu_inc($key)) {apcu_add($key, mt_rand(0, 9));}$max = $this->max(self::COUNT_BITS);if ($count > $max) {throw new \UnexpectedValueException('count');}return $count;}private function max($bits){return -1 ^ (-1 << $bits);}
}?>

本文中的实现利用 apcu 来保存数据,但是并不需要以服务的形式存在。其中我们自定义了一个时间的原点,这样时间的位数可以节省点儿,实际使用时,可以用项目立项的时间戳做为时间原点,这样更有意义些。以 30 位时间为例,如果时间原点是 1000000000 的话,那么理论上最大值可以保存到 2035-09-18,此外我们给节点留了 10 位,计数器留了 20 位,理论上可以容纳最多 1023 个节点,每个节点每秒最多 1048575 个 ID。这些阈值基本都足够了,多半还没到达上限,系统就已经挂了。

需要说明的是,如果使用秒级的时间,假设在一秒内重启 php-fpm,那么有可能会产生不唯一的值,所以我在代码里加上了 sleep(1) 的逻辑来规避此问题。另外,因为代码里并没有严格判断服务器可能出现的时间回退问题,所以还是有可能产生不唯一的值,但需要满足几个条件:首先,服务器时间发生了回退;其次,回退后生成 ID 时的时间恰好在以前使用过;最后,服务器因为 LRU 等原因清除了相关的缓存。要满足这些条件,基本是很难的。也就是说,对于绝大部分 PHP 项目而言,本文的代码可以认为是足够强壮的。

此外,生成的 ID 最好别直接用,不然别人可以反解出其中的数据,比如你有多少台服务器等等,解决办法是在应用层用 hashids 编码及解码,如此一来,数据库里保存的还是原始的 ID(Bigint),但是用户看到的却是 HASH ID,从而更好的保护了数据的安全。

转载于:https://www.cnblogs.com/dasn/articles/6048385.html

转:一个PHP实现的ID生成器相关推荐

  1. 雪花算法之唯一ID生成器理解

    雪花算法基本情况 雪花算法是一个分布式的唯一ID生成器. 它应该具有高并发,以及高性能优点. 基于时间戳,ID具有有序性,同时分布式下机器间时间差异过大(类似同一台机器时间回拨,一定会重复),会导致重 ...

  2. 5 大分布式 ID 生成器优缺点简单对比

    点击上方"业余草",选择"置顶公众号" 第一时间获取技术干货和业界资讯! 首选,不管是不是分布式系统,都有 ID 唯一的使用场景.而在分布式场景下,对 ID 的 ...

  3. 五大分布式ID生成器优缺点及对比

    首选,不管是不是分布式系统,都有 ID 唯一的使用场景.而在分布式场景下,对 ID 的唯一性要求更严格! 常见的,我们上淘宝买东西的订单 ID,就是一种分布式 ID.淘宝,前期的订单 id 好像是 1 ...

  4. 五大分布式ID生成器优缺点及对比(Java)

    首选,不管是不是分布式系统,都有 ID 唯一的使用场景.而在分布式场景下,对 ID 的唯一性要求更严格! 常见的,我们上淘宝买东西的订单 ID,就是一种分布式 ID.淘宝,前期的订单 id 好像是 1 ...

  5. 封装一个流水号ID生成器:id-spring-boot-starter

    文章转载自https://www.codesheep.cn/2019/09/04/id-springbt-starter/ 概述 ID号生成器(或:全局唯一ID生成器)是服务端系统的基础设施,而且ID ...

  6. 如何快速开发一个支持高效、高并发的分布式ID生成器(三)

    前面两个ID生成器只是简单的完成功能,如果实际应用到生产环境,则对ID生成器的要求更高,具体包括但不限于以下几点:(1) 产生全局唯一.且单调递增的ID: (2) 任何情况下ID不能重复或者回退: ( ...

  7. 如何快速开发一个支持高效、高并发的分布式ID生成器(二)

    前面介绍的是利用redis快速搭建一个ID生成器服务,这种方式搭建的ID生成器服务还存在一些缺陷: (1)    与应用耦合高,没有对外屏蔽掉内部实现细节,例如redis,用户完全不需要知道ID生成器 ...

  8. 如何快速开发一个支持高效、高并发的分布式ID生成器(一)

    ID生成器是指能产生不重复ID服务的程序,在后台开发过程中,尤其是分布式服务.微服务程序开发过程中,经常会用到,例如,为用户的每个请求产生一个唯一ID.为每个消息产生一个ID等等,ID生成器也是进行无 ...

  9. 来吧,自己动手撸一个分布式ID生成器组件

    在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处.但是又由于 ...

最新文章

  1. 【SRIO】5、Xilinx RapidIO核例子工程源码分析
  2. python三层装饰器-python 3层装饰器及应用场景
  3. 解答MPLS基础的路由问题—Vecloud微云
  4. Jupyter Notebook修改默认工作路径
  5. servlet学习--Cookie小应用
  6. QDU-GZS and String
  7. navigator对象介绍
  8. 反转字符串中的元音字符_C程序消除字符串中的所有元音
  9. vc6开发一个抓包软件_开发一个软件多少钱?3种软件开发公司报价
  10. 16.6 假新闻识别 Fake News Detection on Social Media A Data Mining Perspective
  11. Unity 脚本生成瓦片地图TileMap
  12. 踩坑内核参数tcp_tw_recycle
  13. Java是什么?Java到底能干嘛?
  14. 查询快递单号,自动识别快递公司
  15. iOS 屏幕左侧向右滑动返回
  16. mysql 1146 错误解决方法
  17. 网络安全关于Windows下BAT脚本使用
  18. 杭电OJ 1047(C++)
  19. C#知识点的总结,代码规范,json,ajax ,数据库,cookie,session等等
  20. 第三批入围企业公示!年度TOP100智能网联供应商评选

热门文章

  1. Spring JPA使用CriteriaBuilder动态构造查询
  2. mysql建立的一个自动更新组织树案案例
  3. 第一百九十九节,jQuery EasyUI,Panel(面板)组件
  4. linux驱动开发的经典书籍
  5. [asp常用代码]文件上传代码
  6. 苏宁物流研发中心运输产品线产品总监钟翼翔:IOT技术的应用分析
  7. 线下活动 | 揭秘大数据背后的京东虚拟平台(免费报名中)
  8. 你的微信朋友圈让你焦虑了吗?
  9. 2015版App推广全攻略:你所不知道的撕逼营销,事件营销和PR传播
  10. Android 引用第三方开源库,出现冲突 Manifest merger failed : Attribute application@icon value=...