雪花算法 — 集群高并发情况下如何保证分布式唯一全局ID生成?

news2025/4/26 18:52:25

雪花算法

问题

为什么需要分布式全局唯一ID以及分布式ID的业务需求

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识:

  • 如在美团点评的金融、支付、餐饮、酒店
  • 猫眼电影等产品的系统中数据逐渐增长,对数据库分库分表后需要有一个唯一ID来标识一条数据或信息;
  • 特别一点的订单、骑手、优惠券都需要有唯一ID做标识

此时一个能够生成全局唯一ID的系统是非常必要的。

image-20230712162743217

ID生成规则部分硬性要求

  1. 全局唯一:不能出现重复的ID号,既然是唯一标识,这是最基本的要求。
  2. 趋势递增:在MySQL的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引,在主键的选择上面我们应该尽量使用有序的主键保证写入性能。
  3. 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
  4. 信息安全:如果ID是连续,恶意用户的爬取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就危险了,竞争对手可以直接知道我们一天的单量。所以在一些应用场景下,需要ID无规则/不规则,让竞争对手不好猜。
  5. 含时间戳:这样能够快速在开发中了解这个分布式ID生成的时间。

ID号生成系统的可用性要求

  1. 高可用:发布一个获取分布式ID请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式ID。
  2. 低延迟:发一个获取分布式ID的请求,服务器就要快,极速。
  3. 高QPS:例如并发一口气10万个创建分布式ID请求同时杀过来,服务器要顶得住且一下子成功创建10万个分布式ID。

一般通用方案

UUID

UUID.randomUUID(),UUID(Universally Unique Identifier)的标准形式包含 32 个 16 进制数字,以连字号分为 5 段,形式为 8-4-4-4-12 的 36 个字符,示例:550e8400-e29b-41d4-a716-446655440000

性能非常高:本地生成(java.util 包下),没有网络消耗。

问题:如果只考虑唯一性,没问题,但是UUID入数据库的性能非常差,因为它是无序的。

  1. 无序,无法预测他的生成顺序,不能生成递增有序的数字

    首先分布式id一般都会作为主键,但是按照MySQL官方推荐主键尽量越短越好,UUID每一个都很长,所以不是很推荐。

  2. 主键,ID作为主键时,在特定的环境下会存在一些问题

    比如做DB主键的场景下,UUID就非常不适用,MySQL官方有明确的建议主键尽量越短越好,36个字符长度的UUID不符合要求。
    image-20230712183255432

  3. 索引,B+树索引的分裂

    既然分布式ID是主键,然后主键是包含索引的,而MySQL的索引是通过B+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的B+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的B+树进行很大的修改,这一点很不好,插入完全无序,不但会导致一些中间节点产生分裂,也会白白创造出很多不饱和的节点,这样大大降低了数据库插入的性能。

UUID只能保证全局唯一性,不满足后面的趋势递增、单调递增,所以不推荐。

数据库自增主键

单机✅

在分布式里面,数据库的自增ID机制的主要原理是:数据库自增ID和MySQL数据库的 replace into 实现的。

这里的 replace into 跟 insert 功能类似,不同点在于:replace into 首先尝试插入数据列表中,如果发现表中已经有此行数据(根据主键或唯一索引判断)则先删除,再插入,否则直接插入新数据。

REPLACE INTO 的含义是插入一条记录,如果表中唯一索引的值遇到冲突,则替换老数据。

CREATE TABLE t_test(
	id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
	stub CHAR(1) NOT NULL DEFAULT '',
	UNIQUE KEY stub (stub)
)

SELECT * FROM t_test;

REPLACE INTO t_test (stub) VALUES('b');

创建好表,先执行第一次 REPLACE INTO,影响的行数为一行:

image-20230712190309092

进行查询:

image-20230712190131703

再执行第二次 REPLACE INTO,此时它影响的行数为两行:

image-20230712190219088

进行查询:

image-20230712190418175

原来的数据被删除了,新插入了主键为2的数据。

此时该方案满足了:唯一性递增性单调性

在分布式情况下,且并发量不多,可以使用这种方案来解决,获得一个全局的唯一ID。

集群分布式❌

那数据库自增ID机制适合做分布式ID吗?答案是不太适合。

  • 系统水平扩展比较困难,比如定义好步长和机器台数之后,如果要添加机器该怎么做?

    假设现在有一台机器发号是:1,2,3,4,5(步长是1),这个时候需要扩容机器一台。可以这样做:把第二台机器的初始值设置得比第一台超过很多,貌似还好,但是假设线上如果有100台机器,这个时候扩容要怎么做,简直是噩梦。所以系统水平扩展方案复杂难以实现。

  • 数据库压力还是很大,每次获取ID都得读写一次数据库,非常影响性能,不符合分布式ID里面的延迟低和高QPS的规则(在高并发下,如果都去数据库里面获取ID,那是非常影响性能的)。

  • 在分库分表场景下使用数据库自增 ID 作为主键可能会遇到以下问题:

    1. 唯一性冲突:在分库分表的环境中,不同的数据库实例或表可能有自己的自增 ID 序列,这可能导致生成的 ID 存在冲突。比如,如果有两个数据库实例都有一个 ID 自增列从 1 开始递增,可能会导致生成相同的 ID,引发唯一性冲突。
    2. 数据一致性难以保证:当数据分散在不同的数据库和表中时,需要跨多个数据库进行查询和事务操作。这样带来了一些挑战,例如维护数据一致性和事务的隔离性。自增 ID 不再是全局唯一的,而是在每个分片(shard)内唯一,因此需要特殊的设计和处理来确保数据的一致性。
    3. 数据迁移和扩展困难:当需要增加分片或迁移数据时,自增 ID 可能会成为一个限制,因为它们被用作数据的标识符。重新分配 ID 范围、解决冲突和维护正确的关联关系可能会变得复杂和耗时。

基于Redis生成全局ID策略

单机✅

因为Redis是单线程,天然保证原子性,可以使用原子操作 INCRINCRBY 来实现。

SET count 10
INCR count    	// 返回 11
INCRBY count 5  // 返回 16

集群分布式❌

注意:在Redis集群情况下,同样和MySQL一样需要设置不同的增长步长,同时key一定要设置有效期。

可以使用Redis集群来获取更高的吞吐量。

假设一个集群中有5台Redis,可以初始化每台Redis的值分别是 1,2,3,4,5, 然后设置步长都是5。

各个Redis生成的ID为:

A:1 6 11 16 21
B:2 7 12 17 22
C:3 8 13 18 23
D:4 9 14 19 24
E:5 10 15 20 25

但是存在的问题是,就是Redis集群的维护和配置比较麻烦,因为要设置避免单点故障,哨兵机制等。

如果我们为了一个ID,却需要引入整个Redis集群,有种杀鸡焉用牛刀的感觉,不推荐。

Snowflake - 雪花算法

上面的三个方案:UUID、数据库自增主键、基于Redis生成全局ID策略,都可以落地实现,但各有优缺点。

接下来我们来了解一下雪花算法。

概述

Twitter的分布式自增ID算法——Snowflake。

最初Twitter把存储系统从MySQL迁移到Cassandra(由Facebook开发的一套开源分布式NoSQL数据库系统),因为Cassandra没有顺序ID生成机制,所有开发了这样一套全局唯一ID生成服务。

Twitter的分布式雪花算法SnowFlake,经测试SnowFlake每秒可以产生26万个自增可排序的ID。

  1. Twitter的SnowFlake生成ID能够按照时间有序生成
  2. SnowFlake算法生成ID的结果是一个64Bit大小的整数,为一个Long型(转换成字符串后长度最多19)
  3. 分布式系统内不会产生ID碰撞(由 datacenter数据中心 和 workerID机器码做区分)并且效率较高

分布式系统中,有一些需要全局唯一ID的场景,生成ID的基本要求:

  1. 在分布式环境下,必须全局唯一性
  2. 一般都需要单调递增,因为一般唯一ID都会存在数据库,而InnoDB的特性就是将内容存储在主键索引上的叶子节点,而且是从左往右递增的,所有考虑到数据库性能,一般生成ID也最好是单调递增的。为了防止ID冲突可以使用36位UUID,但是UUID有一些缺点,首先是它相对比较长,另外UUID一般是无序的
  3. 可能还会需要无规则,因为如果使用唯一ID作为订单号这种,为了不让别人知道一天的订单量多少,就需要这种规则

结构

雪花算法的几个核心组成部分:

image-20230712193219410

号段解析:

  1. 1bit - 符号位

    不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的ID一般都是用正数,所以最高位固定为0。

  2. 41bit - 时间戳位,用来记录时间戳,毫秒级:

    41位可以表示 2^{41}-1 个数字

    如果只用来表示正整数(计算机中正数包含0),可以表示的范围是:02^{41}-1,减1是因为可以表示的数值范围是从0开始计算的,而不是从1。

    也就是说41位可以表示 2^{41}-1 个毫秒的值,转换成单位年则是 (2^{41}-1)/(1000*60*60*24*365) = 69

  3. 10Bit - 工作机器ID

    可以部署在 2^{10} = 1024 个节点,包括5位 datacenterId(数据中心,机房) 和 5位 workerID(机器码)

    5位可以表示的最大正整数是 2^{5}-1 = 31 个数字,即可以用 [0, 1, 2, 3, …, 31] 这32个数字来表示不同的数据中心 和 机器码

  4. 12位bit - 序列号位

    可以用来表示的正整数是 2^{12}-1 = 4095,即可以用 [0, 1, 2, …, 4094] 这4095个数字来表示同一个机器同一个时间戳(毫秒)内产生的4095个ID序号。

SnowFlake可以保证:

  • 所有生成的ID按时间趋势递增
  • 整个分布式系统内不会产生重复ID,因为有 datacenterId 和 workerId 来做区分

源码

不过Twitter的雪花算法是由 Scala 语言编写的,但已经有前辈用 Java 实现了:GitHub地址

package com.example.demo.util;

/**
 * @author: yuanyuan.jing
 * @date: 2018/11/30 11:09
 * @description: 雪花算法生成全局唯一id
 */
public class SnowflakeIdWorker {
    /**
     * Twitter_Snowflake<br>
     * SnowFlake的结构如下(每部分用-分开):<br>
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000 <br>
     * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
     * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截)
     * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
     * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
     * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
     * 加起来刚好64位,为一个Long型。<br>
     * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万个ID左右。
     */


        // ==============================Fields==============================
        /** 开始时间截 (2015-01-01) */
        private final long twepoch = 1420041600000L;

        /** 机器id所占的位数 */
        private final long workerIdBits = 5L;

        /** 数据标识id所占的位数 */
        private final long datacenterIdBits = 5L;

        /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
        private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

        /** 支持的最大数据标识id,结果是31 */
        private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

        /** 序列在id中占的位数 */
        private final long sequenceBits = 12L;

        /** 机器ID向左移12位 */
        private final long workerIdShift = sequenceBits;

        /** 数据标识id向左移17位(12+5) */
        private final long datacenterIdShift = sequenceBits + workerIdBits;

        /** 时间截向左移22位(5+5+12) */
        private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

        /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
        private final long sequenceMask = -1L ^ (-1L << sequenceBits);

        /** 工作机器ID(0~31) */
        private long workerId;

        /** 数据中心ID(0~31) */
        private long datacenterId;

        /** 毫秒内序列(0~4095) */
        private long sequence = 0L;

        /** 上次生成ID的时间截 */
        private long lastTimestamp = -1L;

        // ==============================Constructors==============================
        /**
         * 构造函数
         * @param workerId 工作ID (0~31)
         * @param datacenterId 数据中心ID (0~31)
         */
        public SnowflakeIdWorker(long workerId, long datacenterId) {
            if (workerId > maxWorkerId || workerId < 0) {
                throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
            }
            if (datacenterId > maxDatacenterId || datacenterId < 0) {
                throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
            }
            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }

        // ==============================Methods==============================
        /**
         * 获得下一个ID (该方法是线程安全的)
         * @return SnowflakeId
         */
        public synchronized long nextId() {
            long timestamp = timeGen();

            // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
            if (timestamp < lastTimestamp) {
                throw new RuntimeException(
                        String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }

            // 如果是同一时间生成的,则进行毫秒内序列
            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                // 毫秒内序列溢出
                if (sequence == 0) {
                    // 阻塞到下一个毫秒,获得新的时间戳
                    timestamp = tilNextMillis(lastTimestamp);
                }
            }
            // 时间戳改变,毫秒内序列重置
            else {
                sequence = 0L;
            }

            // 上次生成ID的时间截
            lastTimestamp = timestamp;

            // 移位并通过或运算拼到一起组成64位的ID
            return ((timestamp - twepoch) << timestampLeftShift)
                    | (datacenterId << datacenterIdShift)
                    | (workerId << workerIdShift)
                    | sequence;
        }

        /**
         * 阻塞到下一个毫秒,直到获得新的时间戳
         * @param lastTimestamp 上次生成ID的时间截
         * @return 当前时间戳
         */
        protected long tilNextMillis(long lastTimestamp) {
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = timeGen();
            }
            return timestamp;
        }

        /**
         * 返回以毫秒为单位的当前时间
         * @return 当前时间(毫秒)
         */
        protected long timeGen() {
            return System.currentTimeMillis();
        }

    	// ==============================Test==============================
        public static void main(String[] args) {
            SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);
            for (int i = 0; i < 100; i++) {
                long id = idWorker.nextId();
                //System.out.println(Long.toBinaryString(id));
                System.out.println(id);
            }
        }

}

但是我们实际工作中是不会用这个代码的,仅仅作为学习使用,我们在工程中会使用 Hutool 工具包。

工程落地经验

Hutool 工具包:https://github.com/looly/hutool

SpringBoot整合雪花算法

  • pom.xml

    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-captcha</artifactId>
        <version>4.6.8</version>
    </dependency>
    
  • 新建工具类

    package com.atguigu.boot.util;
    
    package cn.itedus.lottery.domain.support.ids;
    
    import cn.hutool.core.lang.Snowflake;
    import cn.hutool.core.net.NetUtil;
    import cn.hutool.core.util.IdUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;
    import javax.annotation.PostConstruct;
    
    @Slf4j
    @Component
    public class IdGeneratorSnowflake {
    
        private long workerId = 0;
        private long datacenterId = 1;
        private Snowflake snowflake = IdUtil.createSnowflake(workerId, datacenterId);
    
        @PostConstruct
        public void init() {
            try {
                workerId = NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());
                log.info("当前机器的workerId:{}", workerId);
            } catch(Exception e) {
                e.printStackTrace();
                log.warn("当前机器的workerId获取失败", e);
                workerId = NetUtil.getLocalhostStr().hashCode();
            }
        }
    
        public synchronized long snowflakeId() {
            return snowflake.nextId();
        }
    
        public synchronized long snowflakeId(long workerId, long datacenterId) {
            Snowflake snowflake = IdUtil.createSnowflake(workerId,datacenterId);
            return snowflake.nextId();
        }
    
        // 测试
        public static void main(String[] args) {
            System.out.println(new IdGeneratorSnowflake().snowflakeId());
        }
    }
    
    

    image-20230713111923617

  • 在业务类中使用

    @Service
    public class OrderService {
        
    	@Autowired
        private IdGeneratorSnowflake idGeneratorSnowflake;
    
        public String getIDBySnowFlake() {
            // 利用线程池 同时创建多个id
            ExecutorService threadPool = Executors.newFixedThreadPool(5);
            for (int i = 1; i <= 20; i++) {
                threadPool.submit(() -> {
                    log.info("{}", idGeneratorSnowflake.snowflakeId());
                });
            }
            threadPool.shutdown();
            return "hello snowflake";
        }
    }
    

    image-20230713124911348

    image-20230713124853468

    生成的ID都是唯一的,只不过在多线程场景下我们打印的顺序不同。

优缺点

优点

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的
  • 可以根据自身业务特性分配bit位,非常灵活

缺点

  • 依赖机器时钟,如果机器时钟回拨,会导致重复ID生成
  • 在单机上是递增的,但由于涉及到分布式环境,每台机器上的时钟不可能完全同步,有时候会出现不是全局递增的情况,(此缺点可以认为无所谓,一般分布式ID只要求趋势递增,并不会严格要求递增,90%的需求只要求趋势递增)

其它补充

如果想彻底解决时钟问题,那么这两家大厂已经开源了成熟的解决方案,并且优化了雪花算法:

  • 百度开源的分布式唯一ID生成器 —— UidGenerator
  • 美团点评分布式ID生成系统 —— Leaf
    • 美团技术文章 —— Leaf——美团点评分布式ID生成系统

参考b站:尚硅谷

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

接口测试辅助,Fiddler抓取安卓手机https请求(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 Fiddler 是一款免…

Java设计模式之行为型-迭代器模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色设计 四、案例分析 五、总结 一、基础概念 迭代器模式是一种常用的设计模式&#xff0c;它主要用于遍历集合对象&#xff0c;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不暴露该对象的内部表示。 举个简单的…

第二章:Cyber RT通信机制解析与实践

Cyber RT解析与实践 第二章&#xff1a;Cyber RT通信机制解析与实践 Cyber RT解析与实践 Cyber RT解析与实践一、Cyber RT 通讯机制简介1. 话题2. 服务3. 参数 二、数据通信基础Protobuf1. Protobuf 简介2. Protobuf 创建3. Protobuf 编译4. Protobuf 案例实战 三、Cyber RT 话…

CPU性能指标简览

作为计算机的运算核心和控制核心&#xff0c;CPU&#xff08;Central Processing Unit&#xff09;由运算器、控制器、寄存器和实现其之间联系的数据、控制及状态的总线构成&#xff0c;决定着计算机运算性能强弱。作为信息技术产业的核心基础元器件&#xff0c;CPU的运作可分为…

3-40V输入,2.7V启动,20A电流,PWM\模拟信号调光

应用说明&#xff1a; Hi600X 是一系列外围电路简洁的宽调光比升压恒流驱动器&#xff0c;适用于 3-40V 输入电压范围的 LED 照明领域。 Hi600X 系列芯片&#xff0c;2.7V 启动电压&#xff0c;工作电压范围 5-40V&#xff0c;VIFB反馈电压 0.2V&#xff0c;提高整体转换效率。…

JVM的类加载机制和垃圾回收机制

目录 类加载机制类加载机制的步骤加载验证准备解析初始化 双亲委派模型工作原理双亲委派模型的优点 垃圾回收机制死亡对象的判断可达性分析算法可达性分析算法的缺点引用计数算法循环引用问题 垃圾回收算法标记-清除算法复制算法标记-整理算法分代算法 类加载机制 对于任意一个…

使用 vector + 递归 解决问题

class Solution {const char* To[10] { "","", "abc", "def", "ghi","jkl", "mno", "pqrs","tuv", "wxyz" };//常量字符串初始化string 注意此非定义(缺省值)--实例化…

收好这5个SVG编辑器,轻松实现高效创作

随着设计工作的不断发展&#xff0c;对SVG图形的需求也逐渐增加&#xff0c;SVG编辑器就随之诞生。可市面上的SVG编辑质量参差不齐&#xff0c;设计师无法第一时间找到适合自己的&#xff0c;于是本文就收集整理了5款相对来说比较好用的SVG编辑器为设计师们推荐&#xff0c;一起…

java项目之个人网站(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的个人网站。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&#xf…

golang macaron静态资源访问配置

1、本地文件目录如下&#xff1a; 2、macaron配置 package mainimport ("log""net/http""gopkg.in/macaron.v1" )func myHandler(ctx *macaron.Context) string {return "the request path is: " ctx.Req.RequestURI }func main() …

基于linux下的高并发服务器开发(第一章)- Makefile(2)1.11

03 / 工作原理 ◼ 命令在执行之前&#xff0c;需要先检查规则中的依赖是否存在  如果存在&#xff0c;执行命令  如果不存在&#xff0c;向下检查其它的规则&#xff0c;检查有没有一个规则是用来生成这个依赖的&#xff0c;如 果找到了&#xff0c;则执行该规则中…

PowerShell 报错:因为在此系统上禁止运行脚本。有关详细信息

PowerShell 报错&#xff1a;因为在此系统上禁止运行脚本。有关详细信息 Import-Module : 无法加载文件 D:\\Documents\WindowsPowerShell\Modules\posh-git\1.1.0\posh-git.psm1&#xff0c;因为在此系统上禁止运行脚本。有关详细信息&#xff0c;请参阅 https:/go.microsoft.…

k8s 查看程序日志输出

总是和k8s大交道&#xff0c;把相关的命令也学习一下 查看程序日志输出&#xff1a; kubectl logs -f ce202307130100024-gy-decision-xkygl-64f795ff7b-mtr67 --tail1000 不重定向的情况下&#xff0c;可以把多进程下的日志都输出出来。挺好用的 kubectl logs -f pod --tailxx…

稳定币成关键指标,DeFi蓝筹拥抱RWA!下轮牛市会由什么叙事掀起?

从上海升级、BRC20、Meme 乃至老调重弹的减半叙事&#xff0c;每一次热炒都会激起新的市场关注。上半年&#xff0c;稳定币市场经历了一波格局调整&#xff0c;不少 DeFi 项目也出现了一些引人注目的叙事。下轮牛市&#xff0c;会是新一轮“DeFi盛夏”吗&#xff1f; 作为一个半…

如何编写测试用例

用例的五个构成元素&#xff1a; 下面从这五个元素的角度&#xff0c;去剖析如何编写测试用例 用例标题 用例标题就是测试点名称。用例标题是用来说明这个用例的测试目的的&#xff0c;好的用例标题是别人看完你这个用例标题后就知道你这个用例是测什么的。但并不是标题越详…

(自动机)+(卡常)+(前缀和)

edu151 C. Strong Password 样例&#xff1a; s123412341234 m2 s11234 s24321nt[i][j]:有了j以后找下一个数的位置的坐标。void solve() {cin>>s;ll lens.size();vector<array<ll,10>>nt(len1);nt[len].fill(len1);for(int ilen-1;i>0;i--){nt[i]nt[i1]…

js每隔两条或者几条数据,则就插入(新增)一条数据

js每隔两条(自定义条数)数据&#xff0c;则添加一个空的 item&#xff08;这个也可以新增你指定的数据&#xff09;,但是最后一条则不需要新增条数 let temp [] // 你需要处理的原数据 // 新的数组 const newData []for (let i 0; i < temp.length; i) {// 将原始数据添…

Vue3 使用存储库Pinia(state)

一、创建state import { defineStore } from "pinia"; export const useStore defineStore(main, {state() {return {name: 张三,age:26 }} })二、引用store.js import {useStore} from "../store/store" // 引入store const store useStore()三、html…

Flutter底部导航BottomNavigationBar

Flutter底部导航BottomNavigationBar 主要代码&#xff1a; bottomNavigationBar: BottomNavigationBar(//选中菜单颜色fixedColor: Colors.red,//图标大小&#xff0c;默认24.0iconSize: 30,//第几个菜单选中currentIndex: currentIndex,//当item数量大于3个时需要设置type属…

线性回归模型(Linear regression)

- 回归问题的标记如下 【假设可以写成h&#xff0c;也可以写成函数f】 - 工作方式 这就是一个监督学习算法的工作方式&#xff0c;我们可以看到这里有我们的训练集&#xff08;房屋平米数x&#xff0c;房屋价格y&#xff09; &#xff0c;我们把它喂给我们的学习算法&#xf…