Práctico generador de identificación distribuido de desarrollo propio de Springcloud

1. Antecedentes

En el desarrollo diario, necesitamos usar ID para representar de forma única         varios datos en el sistema . Por ejemplo, la ID de usuario corresponde y solo corresponde a una persona, la ID de producto corresponde y solo corresponde a un producto y la ID de pedido corresponde a y sólo corresponde a un pedido. También tenemos varios ID en la vida real . Por ejemplo, un documento de identidad corresponde y solo corresponde a una persona. En pocas palabras, el ID es la identificación única de los datos. En circunstancias normales, la clave primaria de incremento automático de la base de datos se utilizará como ID de datos . Sin embargo, en el caso de una gran cantidad, a menudo introducimos métodos como distribución, subbase de datos y subbase de datos de tabla para manejarlo. Obviamente, después de que los datos son subbase de datos y subtabla, todavía necesitamos una identificación única para identificar un dato o mensaje, y la identificación de aumento automático de la base de datos ya no puede satisfacer la demanda. En este momento, es muy necesario un sistema que pueda generar una identificación global única. En resumen, ¿cuáles son los requisitos para los números de identificación en los sistemas empresariales?
        Unicidad global : No pueden aparecer números de identificación duplicados . Dado que es un identificador único, este es el requisito más básico.
        Tendencia creciente , aumento monótono : asegúrese de que el siguiente ID debe ser mayor que el ID anterior .
        Seguridad de la información : Si la identificación es continua, será muy fácil para usuarios malintencionados robar la información. Simplemente descargue la URL especificada en orden ; si es un número de pedido, será aún más peligroso. Los competidores pueden conocer directamente nuestro día a día. volumen de pedido. Por lo tanto, en algunos escenarios de aplicación, es posible que las identificaciones deban ser irregulares o irregulares.
        Al mismo tiempo, además de los requisitos para el número de identificación en sí, la empresa también tiene requisitos extremadamente altos para la disponibilidad del sistema de generación de números de identificación . Imagine que si el sistema de generación de identificación es inestable y depende en gran medida del sistema de generación de identificación . , no se pueden ejecutar acciones clave como la generación de órdenes. Por lo tanto, un sistema de generación de ID también necesita tener un retraso promedio y un retraso de TP999 lo más bajo posible; la disponibilidad es de 5 9 ; y el QPS es alto .

2: Introducción a los métodos comunes

        2.1 UUID

        La forma estándar de UUID (Identificador único universal) contiene 32 dígitos hexadecimales, divididos en cinco segmentos con guiones, 36 caracteres en forma de 8-4-4-4-12, ejemplo: 550e8400-e29b-41d4-a716 - 446655440000.

        2.1.1 Ventajas

                Muy alto rendimiento: generado localmente, sin consumo de red.

        2.1.2 Desventajas

                No es fácil de almacenar: el UUID es demasiado largo, 16 bytes y 128 bits, y generalmente está representado por una cadena de 36 bits, que no es aplicable a muchos escenarios.

                Inseguridad de la información: el algoritmo para generar UUID basado en la dirección MAC puede provocar la filtración de la dirección MAC. Esta vulnerabilidad se utilizó una vez para encontrar la ubicación del creador del virus Melissa.

                Cuando se utiliza ID como clave principal, habrá algunos problemas en entornos específicos. Por ejemplo, en el escenario de clave primaria de base de datos, UUID es muy inadecuado:

                ① MySQL recomienda oficialmente que la clave primaria sea lo más corta posible [4] El UUID de 36 caracteres no cumple con los requisitos.

                ② Desfavorable para el índice MySQL: si se usa como clave principal de la base de datos, en el motor InnoDB, el desorden del UUID puede causar cambios frecuentes en la ubicación de los datos, lo que afecta seriamente el rendimiento. Los índices agrupados se utilizan en el motor MySQL InnoDB. Dado que la mayoría de los RDBMS utilizan estructuras de datos de árbol B para almacenar datos de índice, debemos intentar utilizar claves primarias ordenadas para garantizar el rendimiento de escritura al seleccionar claves primarias.

                Puede usar directamente el UUID que viene con jdk. El original generado tiene un guión bajo. Si no lo necesita, puede eliminarlo usted mismo. Por ejemplo, el siguiente código:

public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            String rawUUID = UUID.randomUUID().toString();
            //去除“-”
            String uuid = rawUUID.replaceAll("-", "");
            System.out.println(uuid);
        }
    }
        2.2 Algoritmo de copo de nieve y sus derivados

                Esta solución es aproximadamente un algoritmo que genera ID dividiendo el espacio de nombres (los UUID también cuentan, porque son relativamente comunes, por lo que se analizan por separado). Snowflake es el algoritmo de generación de ID distribuida de código abierto de Twitter. Snowflake divide 64 bits en múltiples segmentos para marcar la máquina, la hora, etc. Por ejemplo, los 64 bits en Snowflake se representan como se muestra en la siguiente figura:

        Bit 0: bit de signo (marca positivo y negativo), siempre 0, inútil, no importa. Bits 1~41: 41 bits en total, utilizados para representar la marca de tiempo, la unidad es milisegundos, puede admitir 2^41 milisegundos (aproximadamente 69 años) Bits
        42~52: 10 bits en total, en términos generales, los primeros 5 bits representan la computadora ID de la sala, los últimos 5 dígitos representan la ID de la máquina (se puede ajustar de acuerdo con la situación real en el proyecto real), de modo que se puedan distinguir los nodos en diferentes grupos / salas de computadoras, de modo que se puedan representar 32 IDC y cada IDC Puede tener 32 máquinas.
        Bits 53~64: 12 bits en total, utilizados para representar el número de serie. El número de serie es un valor de incremento automático que representa el número máximo de ID que una sola máquina puede generar por milisegundo (2^12 = 4096), lo que significa que una sola máquina puede generar hasta 4096 ID únicas por milisegundo.

        En teoría, el QPS de la solución de copo de nieve es de aproximadamente 409,6 w/s. Este método de asignación puede garantizar que las ID generadas por cualquier máquina en cualquier IDC en cualquier milisegundo sean diferentes.

        Tres  microservicios de identificación distribuidos

        Del análisis anterior podemos ver que cada solución tiene sus propias ventajas y desventajas. Ahora nos referimos a la solución Meituan Leaf para implementar nuestra propia ID distribuida.

        3.1 Implementación de la solución Meituan Leaf

        La solución MySQL original tenía que leer y escribir en la base de datos cada vez para obtener la ID, lo que provocaba una gran presión sobre la base de datos. Cambie a la adquisición por lotes y obtenga el valor de un segmento numérico (el paso determina el tamaño) cada vez. Después de su uso, vaya a la base de datos para obtener un nuevo segmento numérico, lo que puede reducir en gran medida la presión sobre la base de datos.

       3.1.1 Ventajas

        Los servicios Leaf se pueden expandir linealmente fácilmente y su rendimiento puede soportar completamente la mayoría de los escenarios comerciales. El número de identificación es un número de 64 bits con una tendencia creciente de 8 bytes, que cumple con los requisitos de clave principal del almacenamiento de base de datos anterior.
        Alta tolerancia a desastres: el servicio Leaf tiene un caché de segmento de número interno. Incluso si la base de datos falla, Leaf aún puede brindar servicios al mundo exterior normalmente en un corto período de tiempo.
        El tamaño de max_id se puede personalizar, lo cual es muy conveniente para la migración empresarial desde el método de identificación original.


       3.1.2 Desventajas

        El número de identificación no es lo suficientemente aleatorio y puede filtrar información sobre la cantidad de números emitidos, lo que lo hace inseguro.
        Los datos de TP999 fluctúan mucho. Cuando el segmento numérico se agota, todavía habrá una espera para la E/S de actualización de la base de datos cuando se obtenga un nuevo segmento numérico. Los datos de tg999 ocasionalmente tendrán picos.
        La interrupción de la base de datos puede provocar que todo el sistema deje de estar disponible.

        3.1.3  Optimización

        El momento en que Leaf recupera el segmento numérico es cuando se consume el segmento numérico, lo que significa que el tiempo de emisión de ID en el punto crítico del segmento numérico depende de la próxima vez que se recupere el segmento numérico de la base de datos y de las solicitudes que entre durante este período. El hilo también puede estar bloqueado porque el segmento de número de base de datos no se recupera. Si la red que solicita la base de datos y el rendimiento de la base de datos son estables, esta situación tendrá poco impacto en el sistema, sin embargo, si la red tiembla al buscar la base de datos o se produce una consulta lenta en la base de datos, el tiempo de respuesta de la base de datos es estable. todo el sistema se ralentizará.
        Por esta razón, esperamos que el proceso de recuperación del segmento numérico por parte de la base de datos pueda ser sin bloqueo y que no sea necesario bloquear el hilo de solicitud cuando la base de datos está recuperando el segmento numérico, es decir, cuando el segmento numérico se consume para En cierto punto, el siguiente segmento numérico se cargará de forma asincrónica en la memoria. No es necesario esperar hasta que se agote el rango de números antes de actualizarlo. Hacer esto puede reducir en gran medida el indicador TP999 del sistema.
        Usando el método de doble búfer, hay dos segmentos de búfer de segmento numérico dentro del servicio Leaf. Cuando se ha emitido el 10% del segmento numérico actual, si el siguiente segmento numérico no se ha actualizado, se inicia otro hilo de actualización para actualizar el siguiente segmento numérico. Después de entregar todos los segmentos numéricos actuales, si el siguiente segmento numérico está listo, el siguiente segmento numérico se cambiará al segmento actual y luego se entregará, y el ciclo se repite. Generalmente se recomienda que la longitud del segmento se establezca en 600 veces el QPS de la llamada (10 minutos) durante los períodos pico de servicio, de modo que incluso si la base de datos no funciona, Leaf aún pueda continuar enviando llamadas durante 10 a 20 minutos sin verse afectado. Cada vez que llega una solicitud, se determinará el estado del siguiente segmento numérico y este segmento numérico se actualizará, por lo que las fluctuaciones ocasionales de la red no afectarán la actualización del siguiente segmento numérico.

Cuatro: combate real de identificación distribuida

        Configuración de base de datos

CREATE DATABASE qiyu_live_common CHARACTER set utf8mb3 
COLLATE=utf8_bin;


CREATE TABLE `t_id_generate_config` (
 `id` int NOT NULL AUTO_INCREMENT COMMENT '主键 id',
 `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
 `next_threshold` bigint DEFAULT NULL COMMENT '当前 id 所在阶段的阈
值',
 `init_num` bigint DEFAULT NULL COMMENT '初始化值',
 `current_start` bigint DEFAULT NULL COMMENT '当前 id 所在阶段的开始
值',
 `step` int DEFAULT NULL COMMENT 'id 递增区间',
 `is_seq` tinyint DEFAULT NULL COMMENT '是否有序(0 无序,1 有序)',
 `id_prefix` varchar(60) CHARACTER SET utf8mb4 COLLATE 
utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务前缀码,如果没有则返回
时不携带',
 `version` int NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
 `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时
间',
 `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE 
CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 
COLLATE=utf8mb4_unicode_ci;

        Insertar registro

INSERT INTO `t_id_generate_config` (`id`, `remark`, 
`next_threshold`, `init_num`, `current_start`, `step`, `is_seq`, 
`id_prefix`, `version`, `create_time`, `update_time`)
VALUES
 (1, '用户 id 生成策略', 10050, 10000, 10000, 50, 0, 
'user_id', 0, '2023-05-23 12:38:21', '2023-05-23 23:31:45');

        Construya el proyecto Springboot y los archivos de configuración.

        1. Cree dos mavens e importe las dependencias de maven

        Importar dependencias de maven

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>${dubbo.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <artifactId>log4j-to-slf4j</artifactId>
                    <groupId>org.apache.logging.log4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${qiyu-mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>org.idea</groupId>
            <artifactId>qiyu-live-id-generate-interface</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        Archivo de configuración

spring:
  application:
    name: qiyu-live-id-generate-provider
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    #    访问主库
    url: jdbc:mysql://192.168.1.128:8808/qiyu_live_common?useUnicode=true&characterEncoding=utf8
    username: root
    password: root

        En el siguiente módulo, se generan la enumeración de políticas de configuración básica y la interfaz externa.

       Crear una clase de enumeración de estrategia de generación de identificación

package org.qiyu.live.id.generate.enums;

/**
 * @Author idea
 * @Date: Created in 17:55 2023/6/13
 * @Description
 */
public enum IdTypeEnum {

    USER_ID(1,"用户id生成策略");

    int code;
    String desc;

    IdTypeEnum(int code, String desc) {
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }
}

        Generar método de interfaz externa

package org.qiyu.live.id.generate.interfaces;

/**
 * @Author idea
 * @Date: Created in 19:45 2023/5/25
 * @Description
 */
public interface IdGenerateRpc {
    /**
     * 获取有序id
     *
     * @param id
     * @return
     */
    Long getSeqId(Integer id);

    /**
     * 获取无序id
     *
     * @param id
     * @return
     */
    Long getUnSeqId(Integer id);

}

        A continuación, impleméntelo en el módulo de generación de identificación.

        Cree una clase po de base de datos (aquí está la tabla de políticas de configuración de identificación de la base de datos)

package com.laoyang.id.dao.po;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.Date;

/**
 * @Author idea
 * @Date: Created in 19:59 2023/5/23
 * @Description
 */
@TableName("t_id_gengrate_config")
public class IdGeneratePO {

    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * id备注描述
     */
    private String remark;

    /**
     * 初始化值
     */
    private long initNum;

    /**
     * 步长
     */
    private int step;

    /**
     * 是否是有序的id
     */
    private int isSeq;

    /**
     * 当前id所在阶段的开始值
     */
    private long currentStart;

    /**
     * 当前id所在阶段的阈值
     */
    private long nextThreshold;

    /**
     * 业务代码前缀
     */
    private String idPrefix;

    /**
     * 乐观锁版本号
     */
    private int version;

    private Date createTime;

    private Date updateTime;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getRemark() {
        return remark;
    }

    public void setRemark(String remark) {
        this.remark = remark;
    }

    public long getInitNum() {
        return initNum;
    }

    public void setInitNum(long initNum) {
        this.initNum = initNum;
    }

    public int getStep() {
        return step;
    }

    public void setStep(int step) {
        this.step = step;
    }

    public long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(long currentStart) {
        this.currentStart = currentStart;
    }

    public long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }

    public String getIdPrefix() {
        return idPrefix;
    }

    public void setIdPrefix(String idPrefix) {
        this.idPrefix = idPrefix;
    }

    public int getVersion() {
        return version;
    }

    public void setVersion(int version) {
        this.version = version;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public Date getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(Date updateTime) {
        this.updateTime = updateTime;
    }

    public int getIsSeq() {
        return isSeq;
    }

    public void setIsSeq(int isSeq) {
        this.isSeq = isSeq;
    }

    @Override
    public String toString() {
        return "IdGeneratePO{" +
                "id=" + id +
                ", remark='" + remark + '\'' +
                ", initNum=" + initNum +
                ", step=" + step +
                ", isSeq=" + isSeq +
                ", currentStart=" + currentStart +
                ", nextThreshold=" + nextThreshold +
                ", idPrefix='" + idPrefix + '\'' +
                ", version=" + version +
                ", createTime=" + createTime +
                ", updateTime=" + updateTime +
                '}';
    }
}

        Genere la clase de mapeo del asignador. Tenga en cuenta que se agrega un bloqueo optimista durante la inserción, preste atención a este sql

package com.laoyang.id.dao.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import com.laoyang.id.dao.po.IdGeneratePO;

import java.util.List;

/**
 * @Author idea
 * @Date: Created in 19:47 2023/5/25
 * @Description
 */
@Mapper
public interface IdGenerateMapper extends BaseMapper<IdGeneratePO> {

    @Update("update t_id_gengrate_config set next_threshold=next_threshold+step," +
            "current_start=current_start+step,version=version+1 where id =#{id} and version=#{version}")
    int updateNewIdCountAndVersion(@Param("id")int id,@Param("version")int version);

    @Select("select * from t_id_gengrate_config")
    List<IdGeneratePO> selectAll();
}

        Cree la clase bo bajo servicio para generar objetos de identificación ordenados y de identificación desordenados

        

package com.laoyang.id.service.bo;

import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author idea
 * @Date: Created in 20:00 2023/5/25
 * @Description 有序id的BO对象
 */
public class LocalSeqIdBO {

    private int id;
    /**
     * 在内存中记录的当前有序id的值
     */
    private AtomicLong currentNum;

    /**
     * 当前id段的开始值
     */
    private Long currentStart;
    /**
     * 当前id段的结束值
     */
    private Long nextThreshold;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public AtomicLong getCurrentNum() {
        return currentNum;
    }

    public void setCurrentNum(AtomicLong currentNum) {
        this.currentNum = currentNum;
    }

    public Long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(Long currentStart) {
        this.currentStart = currentStart;
    }

    public Long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(Long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }
}
package com.laoyang.id.service.bo;

import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * @Author idea
 * @Date: Created in 20:32 2023/5/26
 * @Description 无序id的BO对象
 */
public class LocalUnSeqIdBO {

    private int id;
    /**
     * 提前将无序的id存放在这条队列中
     */
    private ConcurrentLinkedQueue<Long> idQueue;
    /**
     * 当前id段的开始值
     */
    private Long currentStart;
    /**
     * 当前id段的结束值
     */
    private Long nextThreshold;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public ConcurrentLinkedQueue<Long> getIdQueue() {
        return idQueue;
    }

    public void setIdQueue(ConcurrentLinkedQueue<Long> idQueue) {
        this.idQueue = idQueue;
    }

    public Long getCurrentStart() {
        return currentStart;
    }

    public void setCurrentStart(Long currentStart) {
        this.currentStart = currentStart;
    }

    public Long getNextThreshold() {
        return nextThreshold;
    }

    public void setNextThreshold(Long nextThreshold) {
        this.nextThreshold = nextThreshold;
    }
}

        Generar clase de servicio para generar ID ordenado e ID desordenado

package com.laoyang.id.service;

/**
 * @Author idea
 * @Date: Created in 19:58 2023/5/25
 * @Description
 */
public interface IdGenerateService {

    /**
     * 获取有序id
     *
     * @param id
     * @return
     */
    Long getSeqId(Integer id);

    /**
     * 获取无序id
     *
     * @param id
     * @return
     */
    Long getUnSeqId(Integer id);
}

        Implemente métodos de identificación ordenados y de identificación desordenados (aquí está la clave, principalmente usando clases atómicas, algunas operaciones de sincronización, etc., grupo de subprocesos)

package com.laoyang.id.service.impl;

import jakarta.annotation.Resource;
import com.laoyang.id.dao.mapper.IdGenerateMapper;
import com.laoyang.id.dao.po.IdGeneratePO;
import com.laoyang.id.service.IdGenerateService;
import com.laoyang.id.service.bo.LocalSeqIdBO;
import com.laoyang.id.service.bo.LocalUnSeqIdBO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author idea
 * @Date: Created in 19:58 2023/5/25
 * @Description
 */
@Service
public class IdGenerateServiceImpl implements IdGenerateService, InitializingBean {

    @Resource
    private IdGenerateMapper idGenerateMapper;

    private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerateServiceImpl.class);
    private static Map<Integer, LocalSeqIdBO> localSeqIdBOMap = new ConcurrentHashMap<>();
    private static Map<Integer, LocalUnSeqIdBO> localUnSeqIdBOMap = new ConcurrentHashMap<>();
    private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(8, 16, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(1000),
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r);
                    thread.setName("id-generate-thread-" + ThreadLocalRandom.current().nextInt(1000));
                    return thread;
                }
            });
    private static final float UPDATE_RATE = 0.50f;
    private static final int SEQ_ID = 1;
    private static Map<Integer, Semaphore> semaphoreMap = new ConcurrentHashMap<>();

    @Override
    public Long getUnSeqId(Integer id) {
        if (id == null) {
            LOGGER.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalUnSeqIdBO localUnSeqIdBO = localUnSeqIdBOMap.get(id);
        if (localUnSeqIdBO == null) {
            LOGGER.error("[getUnSeqId] localUnSeqIdBO is null,id is {}", id);
            return null;
        }
        Long returnId = localUnSeqIdBO.getIdQueue().poll();
        if (returnId == null) {
            LOGGER.error("[getUnSeqId] returnId is null,id is {}", id);
            return null;
        }
        this.refreshLocalUnSeqId(localUnSeqIdBO);
        return returnId;
    }

    /**
     *
     * @param id 传的是对应的业务id
     * @return
     */
    @Override
    public Long getSeqId(Integer id) {
        if (id == null) {
            LOGGER.error("[getSeqId] id is error,id is {}", id);
            return null;
        }
        LocalSeqIdBO localSeqIdBO = localSeqIdBOMap.get(id);
        if (localSeqIdBO == null) {
            LOGGER.error("[getSeqId] localSeqIdBO is null,id is {}", id);
            return null;
        }
        this.refreshLocalSeqId(localSeqIdBO);
        long returnId = localSeqIdBO.getCurrentNum().incrementAndGet();
        if (returnId > localSeqIdBO.getNextThreshold()) {
            //同步去刷新 可能是高并发下还未更新本地数据
            LOGGER.error("[getSeqId] id is over limit,id is {}", id);
            return null;
        }
        return returnId;
    }

    /**
     * 刷新本地有序id段
     *
     * @param localSeqIdBO
     */
    private void refreshLocalSeqId(LocalSeqIdBO localSeqIdBO) {
        long step = localSeqIdBO.getNextThreshold() - localSeqIdBO.getCurrentStart();
        if (localSeqIdBO.getCurrentNum().get() - localSeqIdBO.getCurrentStart() > step * UPDATE_RATE) {
            Semaphore semaphore = semaphoreMap.get(localSeqIdBO.getId());
            if (semaphore == null) {
                LOGGER.error("semaphore is null,id is {}", localSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if (acquireStatus) {
                LOGGER.info("开始尝试进行本地id段的同步操作");
                //异步进行同步id段操作
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localSeqIdBO.getId());
                            tryUpdateMySQLRecord(idGeneratePO);
                        } catch (Exception e) {
                            LOGGER.error("[refreshLocalSeqId] error is ", e);
                        } finally {
                            semaphoreMap.get(localSeqIdBO.getId()).release();
                            LOGGER.info("本地有序id段同步完成,id is {}", localSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }

    /**
     * 刷新本地无序id段
     *
     * @param localUnSeqIdBO
     */
    private void refreshLocalUnSeqId(LocalUnSeqIdBO localUnSeqIdBO) {
        long begin = localUnSeqIdBO.getCurrentStart();
        long end = localUnSeqIdBO.getNextThreshold();
        long remainSize = localUnSeqIdBO.getIdQueue().size();
        //如果使用剩余空间不足25%,则进行刷新
        if ((end - begin) * 0.35 > remainSize) {
            LOGGER.info("本地无序id段同步开始,id is {}", localUnSeqIdBO.getId());
            Semaphore semaphore = semaphoreMap.get(localUnSeqIdBO.getId());
            if (semaphore == null) {
                LOGGER.error("semaphore is null,id is {}", localUnSeqIdBO.getId());
                return;
            }
            boolean acquireStatus = semaphore.tryAcquire();
            if (acquireStatus) {
                threadPoolExecutor.execute(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            IdGeneratePO idGeneratePO = idGenerateMapper.selectById(localUnSeqIdBO.getId());
                            tryUpdateMySQLRecord(idGeneratePO);
                        } catch (Exception e) {
                            LOGGER.error("[refreshLocalUnSeqId] error is ", e);
                        } finally {
                            semaphoreMap.get(localUnSeqIdBO.getId()).release();
                            LOGGER.info("本地无序id段同步完成,id is {}", localUnSeqIdBO.getId());
                        }
                    }
                });
            }
        }
    }

    //bean初始化的时候会回调到这里
    @Override
    public void afterPropertiesSet() throws Exception {
        List<IdGeneratePO> idGeneratePOList = idGenerateMapper.selectAll();
        for (IdGeneratePO idGeneratePO : idGeneratePOList) {
            LOGGER.info("服务刚启动,抢占新的id段");
            tryUpdateMySQLRecord(idGeneratePO);
            semaphoreMap.put(idGeneratePO.getId(), new Semaphore(1));
        }
    }

    /**
     * 更新mysql里面的分布式id的配置信息,占用相应的id段
     * 同步执行,很多的网络IO,性能较慢
     *
     * @param idGeneratePO
     */
    private void tryUpdateMySQLRecord(IdGeneratePO idGeneratePO) {
        int updateResult = idGenerateMapper.updateNewIdCountAndVersion(idGeneratePO.getId(), idGeneratePO.getVersion());
        if (updateResult > 0) {
            localIdBOHandler(idGeneratePO);
            return;
        }
        //重试进行更新
        for (int i = 0; i < 3; i++) {
            idGeneratePO = idGenerateMapper.selectById(idGeneratePO.getId());
            updateResult = idGenerateMapper.updateNewIdCountAndVersion(idGeneratePO.getId(), idGeneratePO.getVersion());
            if (updateResult > 0) {
                localIdBOHandler(idGeneratePO);
                return;
            }
        }
        throw new RuntimeException("表id段占用失败,竞争过于激烈,id is " + idGeneratePO.getId());
    }

    /**
     * 专门处理如何将本地ID对象放入到Map中,并且进行初始化的
     *
     * @param idGeneratePO
     */
    private void localIdBOHandler(IdGeneratePO idGeneratePO) {
        long currentStart = idGeneratePO.getCurrentStart();
        long nextThreshold = idGeneratePO.getNextThreshold();
        long currentNum = currentStart;
        if (idGeneratePO.getIsSeq() == SEQ_ID) {
            LocalSeqIdBO localSeqIdBO = new LocalSeqIdBO();
            AtomicLong atomicLong = new AtomicLong(currentNum);
            localSeqIdBO.setId(idGeneratePO.getId());
            localSeqIdBO.setCurrentNum(atomicLong);
            localSeqIdBO.setCurrentStart(currentStart);
            localSeqIdBO.setNextThreshold(nextThreshold);
            localSeqIdBOMap.put(localSeqIdBO.getId(), localSeqIdBO);
        } else {
            LocalUnSeqIdBO localUnSeqIdBO = new LocalUnSeqIdBO();
            localUnSeqIdBO.setCurrentStart(currentStart);
            localUnSeqIdBO.setNextThreshold(nextThreshold);
            localUnSeqIdBO.setId(idGeneratePO.getId());
            long begin = localUnSeqIdBO.getCurrentStart();
            long end = localUnSeqIdBO.getNextThreshold();
            List<Long> idList = new ArrayList<>();
            for (long i = begin; i < end; i++) {
                idList.add(i);
            }
            //将本地id段提前打乱,然后放入到队列中
            Collections.shuffle(idList);
            ConcurrentLinkedQueue<Long> idQueue = new ConcurrentLinkedQueue<>();
            idQueue.addAll(idList);
            localUnSeqIdBO.setIdQueue(idQueue);
            localUnSeqIdBOMap.put(localUnSeqIdBO.getId(), localUnSeqIdBO);
        }
    }
}

        Finalmente crea la clase de inicio.

package com.laoyang.id;

import jakarta.annotation.Resource;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import com.laoyang.id.service.IdGenerateService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import java.util.HashSet;

/**
 * @Author idea
 * @Date: Created in 19:45 2023/5/25
 * @Description
 */
@SpringBootApplication
public class IdGenerateApplication implements CommandLineRunner {

    private static final Logger LOGGER = LoggerFactory.getLogger(IdGenerateApplication.class);

    @Resource
    private IdGenerateService idGenerateService;

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(IdGenerateApplication.class);
        springApplication.setWebApplicationType(WebApplicationType.NONE);
        springApplication.run(args);
    }

    @Override
    public void run(String... args) throws Exception {
        HashSet<Long> idSet = new HashSet<>();
        for (int i = 0; i < 1500; i++) {
            Long id = idGenerateService.getSeqId(1);
            System.out.println(id);
            idSet.add(id);
        }
        System.out.println(idSet.size());
    }
}

        ¡Eventualmente el resultado se imprimirá en la consola!

Supongo que te gusta

Origin blog.csdn.net/qq_67801847/article/details/133254215
Recomendado
Clasificación