大部分时候,需要通过shell脚本批量处理一些数据,在分布式环境下,数据库表的主键存储的都是分布id,通过Java代码生成。

shell脚本都是通过mysql命令生成insert语句,以前生成insert语句时,我都是先select MAX(id) from table赋值到MAX_ID,然后拼接,类似于

max_id_sql="select MAX(id) from table";
MAX_ID="$(query ${max_id_sql})";

echo "insert into table (id, col1,col2) values" > insert.sql

count=0;

while read -r line
do
    ((count++))
    col1="$(echo ${line} | cut -f1)";
    col2="$(echo ${line} | cut -f2)";
    echo "(${MAX_ID} + ${count}, ${col1}, ${col2})" >> insert.sql
done < datas;

这样的弊端是要自己计算,且id不符合规律,代码量还不小,同时写多张表难管理,抽空完成了一个shell版的Snowflake-id生成脚本

#!/bin/bash

# 定义起始时间戳(2024-01-01 00:00:00 UTC)时间越小,生成的id越大
readonly __BEGIN_EPOCH=$(date -d "2000-01-01 00:00:00" +%s%3N)

# 从MAC地址中提取MACHINE_ID
readonly __MACHINE_ID=$(ip link show | grep -Po 'link/ether \K[0-9a-f:]{17}' | head -n 1 | tr -d ':' | cut -c1-8 | xxd -r -p | od -An -t u4 | tr -d ' ' | awk '{print $1 % 1024}')

# 从IP地址中提取DATACENTER_ID
readonly __DATACENTER_ID=$(ip -4 addr show scope global | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -n 1 | cut -d'.' -f3 | awk '{print $1 % 1024}')

# 序列号初始化为0
_SN_SEQUENCE=0

# 上一次的时间戳
_SN_LAST_TIMESTAMP=0

# 🔒文件
readonly _SN_LOCKFILE="/tmp/snowflake-id.lock"

# 生成Snowflake ID的函数
next-id() {
    exec 200>> "${_SN_LOCKFILE}";
    flock -w 1 200

    # 获取当前时间戳(毫秒)
    local current_timestamp=$(date +%s%N | cut -b1-13)

    # 如果当前时间戳小于上次的时间戳,则报错
    if [ "${current_timestamp}" -lt "${_SN_LAST_TIMESTAMP}" ]; then
        echo "Clock error : offset : $((_SN_LAST_TIMESTAMP - current_timestamp)) ms" >&2
        flock -u 200;
        return 1
    fi

    # 如果时间戳相同,则递增序列号, 重复时使用
    # local timestamp_diff=$((current_timestamp - _SN_LAST_TIMESTAMP))
       # if [ "${timestamp_diff}" -le 10 ]; then
    if [ "${current_timestamp}" -eq "${_SN_LAST_TIMESTAMP}" ]; then
        _SN_SEQUENCE=$(( (_SN_SEQUENCE + 1) & 4095 ))  # 12位,最大4095
        if [ "$_SN_SEQUENCE" -eq 0 ]; then
            # 序列号溢出,等待下一毫秒
            sleep 0.001
            current_timestamp=$(date +%s%N | cut -b1-13)
        fi
    else
        _SN_SEQUENCE=0
    fi

    # 更新上次的时间戳
    _SN_LAST_TIMESTAMP=$current_timestamp

    # 计算偏移时间戳
    local time_diff=$((current_timestamp - __BEGIN_EPOCH))

    # 检查时间戳是否超过41位的限制
    local max_time_diff=$(( (1 << 41) - 1 ))
    if [ "${time_diff}" -gt "${max_time_diff}" ]; then
        echo "Time over flow : ${time_diff} > ${max_time_diff}" >&2
        flock -u 200;
        return 2
    fi

    local lastGenerateId=$(((time_diff << 22) | (__DATACENTER_ID << 17) | (__MACHINE_ID << 7) | _SN_SEQUENCE));
    flock -u 200;
    echo "${lastGenerateId}";
}


# # 测试生成多个Snowflake ID
# for i in {1..10}; do
#    # next-id 
#     # echo "${lastGenerateId}";
#     echo "$(next-id)"
# done

使用时直接调用即可

#!/bin/bash

. snowflake-id.sh

id="$(next-id)"

现在机器上测试,通过后再使用。

可能会出现重复的问题,重复时,将next-id函数的最后两行修改

lastGenerateId=$(((time_diff << 22) | (__DATACENTER_ID << 17) | (__MACHINE_ID << 7) | _SN_SEQUENCE));
flock -u 200;

在调用时先调用函数,再从全局变量lastGenerateId获取,这样的好处是不会重复,但只能在一个shell进程中使用

next-id;
echo "${lastGenerateId}";

如果仍然重复,可将前面的注释放开

# 如果时间戳相同,则递增序列号, 重复时使用
local timestamp_diff=$((current_timestamp - _SN_LAST_TIMESTAMP))
if [ "${timestamp_diff}" -le 10 ]; then
# if [ "${current_timestamp}" -eq "${_SN_LAST_TIMESTAMP}" ]; then
    _SN_SEQUENCE=$(( (_SN_SEQUENCE + 1) & 4095 ))  # 12位,最大4095
    if [ "$_SN_SEQUENCE" -eq 0 ]; then
        # 序列号溢出,等待下一毫秒
        sleep 0.001
        current_timestamp=$(date +%s%N | cut -b1-13)
    fi
else
    _SN_SEQUENCE=0
fi

witt
588 声望462 粉丝

一位爱好计算机运维,喜欢折腾软件,不爱写代码的准Java开发程序员。