正文
雪花ID使用场景比较适合多服务器生成唯一ID,一般我们数据库中都是用自增ID,但如果分库就会导致ID可能重复, 所以需要服务器生成唯一ID,然后存入数据库。
snowflake的结构如下(每部分用-分开):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年), 然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点), 最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)一共加起来刚好64位, 为一个Long型。(转换成字符串后长度最多19)。
如:4912539910600137909
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞 (由datacenter和workerId作区分),并且效率较高。 经测试snowflake每秒能够产生26万个ID。
类一
/**
* 分布式 id 生成类 组成: <毫秒级时间戳+机器id+序列号>
* 默认情况下41bit的时间戳可以支持该算法使用到2082年,10bit的工作机器id可以支持1023台机器,序列号支持1毫秒产生4095个自增序列id
*/
class IdCreate
{
const EPOCH = 1514736000000; //开始时间,固定一个小于当前时间的毫秒数, 如 2018-01-01 00:00:00
const max12bit = 4095;
const max41bit = 1099511627775;
static $machineId = null; // 机器id
public static function init($mId = 0)
{
self::$machineId = $mId;
}
public static function createOnlyId($mId = 0)
{
if (!is_null($mId)) {
self::init($mId);
}
// 时间戳 42位
$time = floor(microtime(true) * 1000);
// 当前时间 与 开始时间 差值
$time -= self::EPOCH;
// 二进制的 毫秒级时间戳
$base = decbin(self::max41bit + $time);
// 机器id 10 位
if (!self::$machineId) {
$machine_id = self::$machineId;
} else {
$machine_id = str_pad(decbin(self::$machineId), 10, "0", STR_PAD_LEFT);
}
// 序列数 12位
$random = str_pad(decbin(mt_rand(0, self::max12bit)), 12, "0", STR_PAD_LEFT);
// 拼接
$base = $base .$machine_id .$random;
// 转化为 十进制 返回
return bindec($base);
}
}
$machineId = 1;
$cast_id = IdCreate::createOnlyId($machineId);
类二
看一个新例子:
class IdGenerator
{
const EPOCH = 1288834974657;//2010/11/4 9:42:54
const NODE_SHIFT = 7; //机器
const DC_SHIFT = 3;//data center
const SEQ_SHIFT = 12;
const TIME_SHIFT = 41; //时间戳精确到毫秒
public static $nodeId;
public static $dcId;
static $randCounter;
static $counter = 0;
static $lastTimestamp;
// public function __construct($nodeId = 8, $dcId = 1)
// {
// $this->init($nodeId, $dcId);
// }
public static function init($nodeId, $dcId)
{
//机器ID范围判断
$maxWorkerId = -1 ^ (-1 << self::NODE_SHIFT);
if($nodeId > $maxWorkerId || $nodeId < 0){
throw new \Exception("nodeId can't be greater than ".$maxWorkerId." or less than 0");
}
//数据中心ID范围判断
$maxDCId = -1 ^ (-1 << self::DC_SHIFT);
if ($dcId > $maxDCId || $dcId < 0) {
throw new \Exception("dcId can't be greater than ".$maxDCId." or less than 0");
}
self::$nodeId = $nodeId;
self::$dcId = $dcId;
}
//生成一个ID
public static function nextId($nodeId = 8, $dcId = 1){
self::init($nodeId, $dcId);
$timestamp = self::timeGen();
//时间毫秒/数据中心ID/机器ID,要左移的位数
$timestampLeftShift = self::SEQ_SHIFT + self::NODE_SHIFT + self::DC_SHIFT;
$dcIdShift = self::SEQ_SHIFT + self::NODE_SHIFT;
$nodeIdShift = self::SEQ_SHIFT;
if ($timestamp == static::$lastTimestamp) {
static::$randCounter++;
static::$randCounter %= pow(2, self::SEQ_SHIFT);
static::$counter++;
static::$counter %= pow(2, self::SEQ_SHIFT);
if (static::$counter == 0) {
usleep(1);
static::$randCounter = NULL;
$timestamp = self::timeGen();
}
} else {
static::$randCounter = NULL;
}
if (static::$randCounter === NULL) {
static::$randCounter = rand(0, pow(2, self::SEQ_SHIFT) -1);
}
static::$lastTimestamp = $timestamp;
//组合4段数据返回: 时间戳.数据标识.工作机器.序列
$nextId = (($timestamp - self::EPOCH) << $timestampLeftShift) |
(self::$dcId << $dcIdShift) |
(self::$nodeId << $nodeIdShift) | static::$randCounter;
if ($nextId <= 0) return time() . mt_rand(10000, 99999);
return $nextId;
}
//取当前时间毫秒
protected static function timeGen(){
$timestamp = (float)sprintf("%.0f", microtime(true) * 1000);
return $timestamp;
}
//精确到毫秒
public static function getTimeByIdGen($idGen)
{
//组合4段数据返回: 时间戳.数据标识.工作机器.序列
$timestamp = $idGen >> self::DC_SHIFT >> self::NODE_SHIFT >> self::SEQ_SHIFT;
return $timestamp + self::EPOCH;
}
public static function random_int($min = 0, $max)
{
$range = $max - $min;
if ($range < 1) return $min; // not so random...
$log = ceil(log($range, 2));
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd = $rnd & $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
}
类三
以及下面这个:
/**
* Twitter的分布式自增ID雪花算法snowflake
* @create 2018-08-23 10:21
**/
public class SnowFlake
{
/**
* 起始的时间戳
*/
private final static long START_STMP = 1480166465631L;
/**
* 每一部分占用的位数
*/
private final static long SEQUENCE_BIT = 12; //序列号占用的位数
private final static long MACHINE_BIT = 5; //机器标识占用的位数
private final static long DATACENTER_BIT = 5;//数据中心占用的位数
/**
* 每一部分的最大值
*/
private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
/**
* 每一部分向左的位移
*/
private final static long MACHINE_LEFT = SEQUENCE_BIT;
private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
private long datacenterId; //数据中心
private long machineId; //机器标识
private long sequence = 0L; //序列号
private long lastStmp = -1L;//上一次时间戳
public SnowFlake(long datacenterId, long machineId) {
if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 产生下一个ID
*
* @return
*/
public synchronized long nextId() {
long currStmp = getNewstmp();
if (currStmp < lastStmp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}
if (currStmp == lastStmp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currStmp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}
lastStmp = currStmp;
return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分
| datacenterId << DATACENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}
private long getNextMill() {
long mill = getNewstmp();
while (mill <= lastStmp) {
mill = getNewstmp();
}
return mill;
}
private long getNewstmp() {
return System.currentTimeMillis();
}
public static void main(String[] args) {
SnowFlake snowFlake = new SnowFlake(1, 1);
long start = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
System.out.println(snowFlake.nextId());
}
System.out.println(System.currentTimeMillis() - start);
}
}
PHP 批量生成兑换码实例
PHP 批量生成兑换码实例,可以参考 https://ibaiyang.github.io/blog/php/2022/10/17/PHP-批量生成兑换码实例.html
函数原型
decbin — 十进制转换为二进制
decbin ( int $number ) : string
返回一字符串,包含有给定 number 参数的二进制表示。所能转换的最大数值为十进制的 4294967295,其结果为 32 个 1 的字符串。
如:
echo decbin(12) . "\n";
echo decbin(26);
1100
11010
bindec — 二进制转换为十进制
bindec ( string $binary_string ) : number
返回 binary_string 参数所表示的二进制数的十进制等价值。
如:
echo bindec('110011') . "\n";
echo bindec('000110011') . "\n";
51
51
str_pad — 使用另一个字符串填充字符串为指定长度
str_pad ( string $input , int $pad_length , string $pad_string = " " , int $pad_type = STR_PAD_RIGHT ) : string
该函数返回 input 被从左端、右端或者同时两端被填充到制定长度后的结果。 如果可选的 pad_string 参数没有被指定,input 将被空格字符填充,否则它将被 pad_string 填充到指定长度。
参数
input
输入字符串。
pad_length
如果 pad_length 的值是负数,小于或者等于输入字符串的长度,不会发生任何填充,并会返回 input 。
pad_string
注意:
如果填充字符的长度不能被 pad_string 整除,那么 pad_string 可能会被缩短。
pad_type
可选的 pad_type 参数的可能值为 STR_PAD_RIGHT,STR_PAD_LEFT 或 STR_PAD_BOTH。如果没有指定 pad_type,则假定它是 STR_PAD_RIGHT。
$str = "Hello World";
echo str_pad($str,30,".");
// Hello World...................
base_convert — 各进制间相互转换
/**
* Convert a number between arbitrary bases
* @link http://php.net/manual/en/function.base-convert.php
* @param string $number <p>
* The number to convert
* </p>
* @param int $frombase <p>
* The base number is in
* </p>
* @param int $tobase <p>
* The base to convert number to
* </p>
* @return string number converted to base tobase
* @since 4.0
* @since 5.0
*/
function base_convert ($number, $frombase, $tobase) {}
参考资料
https://blog.csdn.net/cj2580/article/details/80980459
https://blog.csdn.net/jerryyang_2017/article/details/80334580