订单id的设计问题探讨

如何设计一个订单id

设计一个订单ID系统需要考虑多个因素,包括唯一性、排序性(时间顺序)、可读性(可选)以及系统的扩展性和性能。结合这些因素,可以选择不同的方案来生成订单ID。以下是几种常见的订单ID设计方案:

1. 使用 UUID

UUID(Universally Unique Identifier)是一种标准的唯一标识符,确保了在分布式系统中的唯一性。UUID 有几种版本,其中最常用的是 UUID v4,它是基于随机数生成的。

优点:
  • 全局唯一性
  • 简单易用
缺点:
  • 不能按时间排序
  • 较长,不适合做主键(占用更多存储空间,影响索引性能)
示例:
import java.util.UUID;

public class OrderIdGenerator {
    public static String generateOrderId() {
        return UUID.randomUUID().toString();
    }

    public static void main(String[] args) {
        System.out.println(generateOrderId());
    }
}

2. 使用雪花算法(Snowflake)

雪花算法是 Twitter 开发的一种分布式 ID 生成算法,能够生成有序且唯一的 64 位整数 ID。雪花算法的 ID 结构如下:

  • 1 位符号位(始终为 0)
  • 41 位时间戳(自定义纪元后的毫秒数)
  • 10 位机器标识(5 位数据中心ID和5位机器ID)
  • 12 位序列号(同一毫秒内生成的不同ID)
优点:
  • 唯一且有序
  • 高性能,适合高并发场景
示例:
 
public class SnowflakeIdGenerator {
    private final long epoch = 1609459200000L; // 自定义纪元 (2021-01-01)
    private final long dataCenterIdBits = 5L;
    private final long workerIdBits = 5L;
    private final long sequenceBits = 12L;

    private final long maxDataCenterId = (1L << dataCenterIdBits) - 1;
    private final long maxWorkerId = (1L << workerIdBits) - 1;
    private final long sequenceMask = (1L << sequenceBits) - 1;

    private final long workerIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;

    private long dataCenterId;
    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long dataCenterId, long workerId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId must be between 0 and " + maxDataCenterId);
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId must be between 0 and " + maxWorkerId);
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampShift) |
               (dataCenterId << dataCenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        for (int i = 0; i < 10; i++) {
            System.out.println(idGenerator.nextId());
        }
    }
}

3. 使用自增ID和时间戳组合

将自增ID和时间戳组合起来,可以确保订单ID的唯一性和时间顺序性。

优点:
  • 简单易实现
  • 有序且可读性好
缺点:
  • 分布式环境中需解决自增ID的生成问题
示例:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;

public class OrderIdGenerator {
    private static final AtomicLong counter = new AtomicLong(0);

    public static String generateOrderId() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = sdf.format(new Date());
        long increment = counter.incrementAndGet();
        return timestamp + String.format("%04d", increment);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

4. 基于数据库的自增ID

使用数据库的自增列来生成订单ID,可以确保ID的唯一性和有序性。但需要注意在高并发和分布式环境中的性能和扩展性问题。

示例:
 
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id INT,
    status VARCHAR(20),
    total NUMERIC(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

在插入新订单时,数据库会自动生成唯一的自增ID:

INSERT INTO orders (user_id, status, total) VALUES (1, 'completed', 100.00);

总结

设计订单ID时,可以选择不同的方案,具体选择取决于系统的需求和环境:

  • UUID 适合需要全局唯一性的场景,但不能按时间排序。
  • 雪花算法 适合分布式环境,能生成有序且唯一的ID。
  • 自增ID和时间戳组合 简单易实现,适合单机或小规模分布式环境。
  • 数据库自增ID 适合不需要高度分布式的系统,简单直接。

根据具体需求,选择合适的方案并进行实现。

雪花算法生成的ID的组成部分

雪花算法(Snowflake Algorithm)是一种分布式唯一 ID 生成算法,由 Twitter 开发。它生成的 ID 是 64 位的整型数字,通过组合多个不同部分来确保生成的 ID 唯一且有序。雪花算法生成的 ID 通常由以下几个部分组成:

  1. 符号位(1位)

    • 固定为 0,因为生成的 ID 为正数。
  2. 时间戳(41位)

    • 表示自定义纪元(通常是某个固定时间点,如 Unix 纪元)以来的毫秒数。
    • 41 位可以表示的毫秒数约为 69 年(2^41 - 1 毫秒)。
  3. 数据中心ID(5位)

    • 表示数据中心或机房的 ID。
    • 5 位可以表示 32 个不同的数据中心。
  4. 机器ID(5位)

    • 表示工作机器的 ID。
    • 5 位可以表示 32 台不同的机器。
  5. 序列号(12位)

    • 表示同一毫秒内生成的不同 ID。
    • 12 位可以表示 4096 个不同的序列号。

雪花算法生成的 ID 由符号位、时间戳、数据中心ID、机器ID 和序列号组成。它通过时间戳保证全局有序性,通过数据中心ID和机器ID保证分布式环境中的唯一性,通过序列号保证同一毫秒内生成的多个 ID 的唯一性。

其他问题考虑

并发极高情况下,序列号溢出怎么办?

前面提到,序列号部分是12位,这意味着在同一毫秒内可以生成的ID数量为2^12 = 4096个。因此,在同一毫秒内,最多可以生成4096个不同的ID。如果在极高并发的情况下(超过4096个ID每毫秒,即每秒超过4096*1000个ID),标准的雪花算法会遇到序列号溢出的问题,进而导致生成重复的ID。在这种情况下,有几种策略可以解决这一问题:

1. 扩展序列号位数

一种简单的方法是扩展序列号部分的位数,但这会减少其他部分(如时间戳、数据中心ID、机器ID)的位数,从而减少它们的容量。

2. 时间回拨

另一种方法是在同一毫秒内生成的ID数量超过序列号限制时,等待到下一个毫秒。这种方法在极端高并发情况下可能会导致性能瓶颈。

3. 使用多实例

您可以部署多个生成器实例,每个实例有独立的机器ID和数据中心ID,通过负载均衡来分配请求,以减少单个实例的负载。

4. 分布式ID生成服务

使用分布式ID生成服务来分担负载,确保生成唯一ID的同时提升系统的扩展性和可靠性。

5. 混合策略

结合多种策略,如时间回拨和多实例,来应对极高并发的需求。

实现示例:扩展序列号位数

以下是一个扩展序列号位数的示例实现:

public class SnowflakeIdGeneratorExtended {
    private final long epoch = 1609459200000L; // 自定义纪元 (2021-01-01)
    private final long dataCenterIdBits = 5L;
    private final long workerIdBits = 5L;
    private final long sequenceBits = 14L; // 扩展序列号位数到14位

    private final long maxDataCenterId = (1L << dataCenterIdBits) - 1;
    private final long maxWorkerId = (1L << workerIdBits) - 1;
    private final long sequenceMask = (1L << sequenceBits) - 1;

    private final long workerIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;

    private long dataCenterId;
    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGeneratorExtended(long dataCenterId, long workerId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId must be between 0 and " + maxDataCenterId);
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId must be between 0 and " + maxWorkerId);
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampShift) |
               (dataCenterId << dataCenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeIdGeneratorExtended idGenerator = new SnowflakeIdGeneratorExtended(1, 1);
        for (int i = 0; i < 10000; i++) {
            System.out.println(idGenerator.nextId());
        }
    }
}

在这个示例中,序列号部分扩展到14位,这样每毫秒可以生成的ID数量增加到16384个(2^14)。这种扩展虽然增加了每毫秒可以生成的ID数量,但同时减少了其他部分的位数,可能不适用于所有场景。

使用多实例的方案

在分布式系统中,使用多实例来生成ID也是一个可行的方案。例如,将请求负载均衡到不同的实例,每个实例使用不同的数据中心ID和机器ID,这样可以大幅度减少单个实例的负载。

 
public class SnowflakeIdGeneratorMultiInstance {
    private static final int MAX_INSTANCES = 32;
    private static final SnowflakeIdGenerator[] GENERATORS = new SnowflakeIdGenerator[MAX_INSTANCES];

    static {
        for (int i = 0; i < MAX_INSTANCES; i++) {
            GENERATORS[i] = new SnowflakeIdGenerator(1, i);
        }
    }

    public static long generateId(int instanceId) {
        return GENERATORS[instanceId % MAX_INSTANCES].nextId();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            System.out.println(generateId(i));
        }
    }
}

在这个示例中,使用32个实例,每个实例使用不同的机器ID,通过简单的负载均衡分配请求。

总结

如果系统需要在极高并发(每毫秒超过4096个ID)下生成唯一ID,可以考虑以下解决方案:

  1. 扩展序列号位数:增加序列号部分的位数,增加每毫秒可生成的ID数量。
  2. 时间回拨:在同一毫秒内生成的ID数量超过限制时,等待下一毫秒。
  3. 使用多实例:通过负载均衡将请求分配到多个ID生成实例,每个实例使用不同的机器ID和数据中心ID。
  4. 分布式ID生成服务:使用专业的分布式ID生成服务,如 Twitter的Snowflake,MongoDB的ObjectId,或Apache的Kudu等。

如何做到订单号又短又不重复?

1. 基于时间的订单号生成

使用时间戳和自增序列的组合,可以生成唯一且有序的订单号。为了确保订单号的长度限制,需要进行一些特殊处理。

方案一:短时间戳 + 自增序列
  • 时间戳:使用自定义的短时间戳,如年月日时分秒。
  • 自增序列:在高并发的情况下,使用自增序列或分布式唯一ID生成器来确保同一秒内的唯一性。
  • 数据中心和机器ID:可以嵌入一些数据中心和机器ID信息。
示例代码:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class OrderIdGenerator {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    private static final AtomicInteger counter = new AtomicInteger(0);
    private static final int MAX_COUNTER = 999;

    public static synchronized String generateOrderId() {
        // 获取当前时间的短时间戳
        String timestamp = dateFormat.format(new Date());
        // 自增序列,达到最大值后重置
        int count = counter.getAndIncrement();
        if (count > MAX_COUNTER) {
            counter.set(0);
            count = counter.getAndIncrement();
        }
        // 格式化自增序列为三位
        return String.format("%s%03d", timestamp, count);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号的格式为 yyMMddHHmmssSSS + 自增序列的组合。生成的订单号长度为12位,格式如下:

  • 时间戳:6位(如 210615 表示 2021年6月15日)。
  • 自增序列:3位(如 001002,最多999)。

2. 基于随机数和哈希的订单号生成

使用随机数生成唯一订单号,并通过哈希函数确保唯一性。

方案二:短随机数 + 哈希校验
  • 随机数:生成一个较短的随机数。
  • 哈希校验:使用哈希函数来校验和生成唯一的订单号。
示例代码:
 
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class OrderIdGeneratorHash {
    private static final int ORDER_ID_LENGTH = 12;
    private static final Random random = new Random();
    private static final Set<String> existingOrderIds = new HashSet<>();

    public static synchronized String generateOrderId() {
        String orderId;
        do {
            orderId = generateRandomOrderId();
        } while (existingOrderIds.contains(orderId));
        existingOrderIds.add(orderId);
        return orderId;
    }

    private static String generateRandomOrderId() {
        StringBuilder orderId = new StringBuilder(ORDER_ID_LENGTH);
        for (int i = 0; i < ORDER_ID_LENGTH; i++) {
            orderId.append(random.nextInt(10)); // 生成0-9之间的随机数字
        }
        return orderId.toString();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号完全是随机生成的,并且使用一个Set来确保唯一性。实际生产环境中,需要有更高效的去重方法(如分布式缓存)。

3. 混合使用时间和随机数

结合时间戳和随机数,既保证有序性又保证唯一性。

方案三:时间戳 + 随机数
  • 时间戳:使用短时间戳。
  • 随机数:生成一定长度的随机数。
示例代码:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class OrderIdGeneratorMixed {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    private static final Random random = new Random();
    private static final int RANDOM_LENGTH = 4;

    public static synchronized String generateOrderId() {
        // 获取当前时间的短时间戳
        String timestamp = dateFormat.format(new Date());
        // 生成随机数
        String randomNumber = String.format("%04d", random.nextInt(10000)); // 生成4位随机数
        return timestamp + randomNumber;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号的格式为 yyMMddHHmmss + 4位随机数,生成的订单号长度为16位。例如:2106151200001234

总结

  • 方案一:基于时间戳和自增序列,适合有严格时间顺序要求的场景。
  • 方案二:基于随机数和哈希校验,适合没有严格时间顺序要求但需要高并发生成订单号的场景。
  • 方案三:结合时间戳和随机数,适合需要平衡有序性和唯一性的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/641311.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

论文阅读--GLIP

把detection和phrase ground(对于给定的sentence&#xff0c;要定位其中提到的全部物体)这两个任务合起来变成统一框架&#xff0c;从而扩展数据来源&#xff0c;因为文本图像对的数据还是很好收集的 目标检测的loss是分类loss定位loss&#xff0c;它与phrase ground的定位los…

内网穿透--Nps-自定义-上线

免责声明:本文仅做技术交流与学习... 目录 Nps项目: 一图通解: 1-下载nps/npc 2-服务端启动 访问web网页: 添加客户端&#xff0c;生成密匙. 3-kali客户端连接服务端 4-添加协议隧道. 5-kali生成后门&#xff1a; 6-kali创建监听: Nps项目: https://github.com/ehang…

《2024年中国机器人行业投融资报告》| 附下载

近年来&#xff0c;国内机器人行业取得了显著的技术进步&#xff0c;包括人工智能、感知技术、自主导航等技术方面的突破&#xff0c;使得机器人能够更好地适应复杂环境和任务需求&#xff0c;带动了机器人行业加快发展。 当然&#xff0c;技术的进步是外在驱动因素&#xff0…

【JAVA基础之网络编程】UDP和TCP协议以及三次握手和四次挥手的过程

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;mysql专栏&#xff1a;小林同学的专栏 目录 1. 网络编程 1.1 概述 1.2 网络编程的三要素 1.2.1 IP地址 1.2.2 InetAddress 1.2.3 端口和协议 1.3 UDP协议 1.3.1 UDP发送数据 1.3.2 UDP接收数据 1.4…

Terminal Web终端基础(Web IDE 技术探索 二)

Terminal是web终端技术&#xff0c;类似cmd命令窗口&#xff0c;Webcontainer 中推荐使用的是Xterm.js&#xff0c;这里就不细说Xterm.js 的使用了&#xff0c;我们使用第三方库来实现&#xff08;原生确实有点难用&#xff09;。 vue-web-terminal 一个由 Vue 构建的支持多内容…

基础5 探索JAVA图形编程桌面:字符操作组件详解

在繁华都市的一个角落&#xff0c;卧龙和凤雏相聚在他们常去的台球厅。灯光洒在绿色的台球桌上&#xff0c;彩色的台球整齐地排列着&#xff0c;仿佛在等待着一场激烈的角逐。 卧龙轻轻地拿起球杆&#xff0c;微微瞄准&#xff0c;然后用力一击&#xff0c;白球带着一股强大的力…

Vue.js - Vue 的安装 以及 常用的 Vue 指令 【0基础向 Vue 基础学习】

文章目录 Vue 快速上手1、Vue.js 官网 & Vue.js 的获取2、创建 Vue 实例&#xff0c;初始化渲染3、插值表达式 安装 Vue 开发者工具&#xff1a;装插件调试 Vue 应用Vue 指令1、v-show 指令2、v-if3、v-else & v-else-if4、v-onv-on 调用传参 5、v-bindv-bind 对于样式…

类和对象(下篇)(未完结)!

文章目录 在谈构造函数1.构造函数体赋值2.初始化列表尽量使用初始化列表&#xff1f;初始化列表的初始化顺序&#xff1f;成员变量声明处的缺省值构造函数支持类型转换3.explicit关键字 static成员 在谈构造函数 1.构造函数体赋值 class Date{public:Date(int year, int mont…

Python设计模式之适配器模式

目录 一、适配器模式 适配器模式的组成部分 适配器模式的种类 应用场景 实现步骤 二、测试例子 一、适配器模式 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过将一个现有接口转换为另一个期望的接口来让不兼容的接口能够合作…

香港服务器负载过高的原因和应对办法

保持网站正常运行看似简单&#xff0c;但事实上&#xff0c;有许多问题会影响网站和应用程序的性能&#xff0c;并可能导致停机。其中一个问题就是服务器过载。而香港服务器作为一种常见的服务器类型&#xff0c;有时会出现负载过高的情况。为了帮助您确保在香港服务器过载不会…

跨境电商投放Facebook广告推广攻略!

在出海浪潮中&#xff0c;跨境电商已经成为企业连接不同市场、拓展国际业务的重要途径。Facebook&#xff0c;作为全球最大的社交平台之一&#xff0c;拥有超过20亿的活跃用户&#xff0c;为跨境卖家提供了一个无与伦比的营销舞台。有效利用Facebook广告&#xff0c;不仅能帮助…

捕捉二氧化碳也能赚钱?深入探索CCUS技术与商业前景

引言 随着全球变暖和气候变化的加剧&#xff0c;如何有效减少二氧化碳&#xff08;CO2&#xff09;排放成为各国亟待解决的问题。近日&#xff0c;全球最大的二氧化碳捕集工厂在冰岛正式运营&#xff0c;这一消息引起了广泛关注。本文将深入探讨捕集二氧化碳技术&#xff08;C…

海康威视NVR通过ehome协议接入视频监控平台,视频浏览显示3011超时错误的问题解决,即:The request timeout! 【3011】

目录 一、问题描述 二、问题分析 2.1 初步分析 2.2 查看日志 2.3 问题验证 1、查看防火墙 2、查看安全组 3、问题原因 三、问题解决 3.1 防火墙开放相关端口 3.2 安全组增加规则 3.3 测试 1、TCP端口能够联通的情况 2、TCP端口不能够联通的情况 四、验证 五、云…

JWT使用方法

目录 基础概念 依赖 生成令牌 工具类 控制层 解析令牌 工具类 网关过滤器 效果 基础概念 Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7519).该token被设计为紧凑且安全的&#xff0c;特别适用于分布式站点…

Hsql每日一题 | day03

前言 就一直向前走吧&#xff0c;沿途的花终将绽放~ 题目&#xff1a;打折日期交叉问题 如下为平台商品促销数据&#xff1a;字段为品牌&#xff0c;打折开始日期&#xff0c;打折结束日期 brand stt edt oppo,2021-06-05,2021-06-09 oppo,2021-06-11,2021-06-21 vivo,…

共享经济中的创新演示:打造案例分析PPT,让想法流动起来

在当今这个看图说话的时代&#xff0c;无论是在职场打拼还是学术殿堂&#xff0c;一份牛气冲天的案例分析PPT无疑是你专业形象的加分项。 不管你是刚出道的小鲜肉&#xff0c;还是已经混迹江湖多年的老鸟&#xff0c;一份有深度、有力度的PPT都能帮你在人群中脱颖而出&#xf…

IT行业已经饱和?2024年报考计算机类专业还有出路吗?

&#x1f446;点击关注 获取更多编程干货&#x1f446; “高薪”光环加持&#xff0c;IT行业一直是不少人心仪的职业选择&#xff0c;计算机专业一度成为最热门的本科专业。 然而&#xff0c;正因报考计算机专业、想要入行IT行业的人越来越多&#xff0c;“行业饱和”、“人才…

Nodejs 第七十三章(网关层)

什么是网关层(getway)&#xff1f; 技术选型fastify 速度快适合网关层 fastify教程上一章有讲 网关层是位于客户端和后端服务之间的中间层&#xff0c;用于处理和转发请求。它充当了请求的入口点&#xff0c;并负责将请求路由到适当的后端服务&#xff0c;并将后端服务的响应…

免费的八字软件

无敌八字排盘软件完全免费使用&#xff0c;即使用不需要付费且无任何限制。同时推出手机版电脑版&#xff0c;两版本数据互通互用&#xff0c;即电脑版的数据可以备份到手机版上导入&#xff0c;手机版的数据也可以备份到电脑版上恢复导入&#xff0c;方便手机和电脑共用的朋友…

山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(十九)- JUC(5)

synchronized优化原理 轻量级锁 如果一个对象有多个线程访问&#xff0c;但多线程访问的时间是错开的&#xff08;没有竞争&#xff09;&#xff0c;可以用轻量级锁优化 Slf4j(topic "c.ExerciseTransfer")public class Test {​static final Object obj new Obj…