聊聊常见的分布式ID解决方案


highlight: xcode

theme: vuepress

为什么要使用分布式ID?

随着 Web 开发技术的不断发展,单体的系统逐步走向分布式系统。在分布式系统中,使用分布式 ID(Distributed IDs)主要是为了在没有单点故障的情况下生成唯一标识符。这些唯一标识符在很多场景中非常重要,例如数据库记录的主键、消息队列中的消息ID、日志系统中的唯一事件ID等。使用分布式 ID 有以下几个主要原因:

  1. 唯一性:分布式系统中,各个节点可能同时生成 ID,如果不加以控制,可能会出现 ID 冲突。分布式ID 生成方案确保在整个系统中生成的 ID 都是唯一的。
  2. 可扩展性:分布式 ID 生成方案能够在分布式系统的不同节点上生成 ID,不会成为系统扩展的瓶颈。相比于集中式的 ID 生成方案(如数据库自增 ID),分布式 ID 方案能够更好地适应系统的扩展需求。
  3. 高可用性:分布式 ID 生成方案通常设计为无单点故障的结构,即使某些节点故障,其他节点仍然可以正常生成 ID,保证系统的高可用性。
  4. 性能:分布式 ID 生成方案通常具有低延迟、高并发的特点,能够满足分布式系统中高频次的ID生成需求。相比于依赖集中式数据库生成 ID,分布式 ID 生成在性能上有显著优势。
  5. 时间排序:有些分布式 ID 生成方案(如 Twitter 的 Snowflake)生成的 ID 具有时间排序的特性,即 ID 的大致顺序反映了生成的时间。这对于某些需要按时间顺序处理的数据非常有用。

常见的分布式ID解决方案

下面介绍一下常见的分布式 ID 解决方案,有一些只是作为了解,在工作中基本上用不到。

UUID

UUID(Universally Unique Identifier),通用唯一识别码。UUID 是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的 MAC 地址)等数据计算生成的。

UUID 由以下几部分的组合:

  • 当前日期和时间,UUID 的第一个部分与时间有关,如果你在生成一个 UUID 之后,过几秒又生成一个 UUID,则第一个部分不同,其余相同。
  • 时钟序列。
  • 全局唯一的 IEEE 机器识别号,如果有网卡,从网卡 MAC 地址获得,没有网卡以其他方式获得。

UUID 是由一组 32 位数的 16 进制数字所构成,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36 个字符(即 32 个英数字母和 4 个连字号)。例如:

text aefbbd3a-9cc5-4655-8363-a2a43e6e6c80 xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

在 Java 中,可以使用 UUID 类来生成 UUID:

java @Test public void testUuid() { UUID uuid = UUID.randomUUID(); System.out.println(uuid); } // 4996554d-e196-446a-80e4-330037825695

如果需求是只保证唯一性,那么 UUID 也是可以使用的,但是按照上面的分布式 ID 的要求, UUID 其实是不能做成分布式 ID 的,原因如下:

  1. 首先分布式 ID 一般都会作为主键,但是安装 MySQL 官方推荐主键要尽量越短越好,UUID 每一个都很长,所以不是很推荐。
  2. 既然分布式 ID 是主键,然后主键是包含索引的,然后 MySQL 的索引是通过 B+ 树来实现的,每一次新的UUID 数据的插入,为了查询的优化,都会对索引底层的 B+ 树进行修改,因为 UUID 数据是无序的,所以每一次 UUID 数据的插入都会对主键生成的 B+ 树进行很大的修改,这一点很不好。
  3. 信息不安全:基于 MAC 地址生成 UUID 的算法可能会造成 MAC 地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。

自增ID

针对表结构的主键,我们常规的操作是在创建表结构的时候给对应的 ID 设置 auto_increment,也就是勾选自增选项。

但是这种方式我们清楚在单个数据库的场景中我们是可以这样做的,但如果是在分库分表的环境下,直接利用单个数据库的自增肯定会出现问题。因为 ID 要唯一,但是分表分库后只能保证一个表中的 ID 的唯一,而不能保证整体的 ID 唯一。

image.png

上面的情况我们可以通过单独创建主键维护表来处理。

image.png

简单举一个例子。创建一个表:

sql CREATE TABLE `t_order_id` ( `id` bigint NOT NULL AUTO_INCREMENT, `title` char(1) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `title` (`title`) ) ENGINE = InnoDB AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;

然后我们通过更新 ID 操作来获取 ID 信息:

```sql BEGIN;

REPLACE INTO torderid (title) values ('p') ; SELECT LASTINSERTID();

COMMIT; ```

但是这种方式还是很麻烦,不推荐。

数据库多主模式

单点数据库方式存在明显的性能问题,可以对数据库进行高可以优化,担心一个主节点挂掉没法使用,可以选择做双主模式集群,也就是两个 MySQL 实例都能单独生产自增的 ID。

查看主键自增的属性:

sql show variables like '%increment%'

image.png

我们可以设置主键自增的步长从 2 开始。

image.png

但是这种在并发量比较高的情况下,如何保证扩展性其实会是一个问题。在高并发情况下性能堪忧。

号段模式

号段模式是当下分布式 ID 生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增 ID,每次从数据库取出一个号段范围,例如 (1, 1000] 代表 1000 个ID,具体的业务服务将本号段,生成 1~1000 的自增 ID 并加载到内存。表结构如下:

sql CREATE TABLE id_generator ( id int(10) NOT NULL, max_id bigint(20) NOT NULL COMMENT '当前最大id', step int(20) NOT NULL COMMENT '号段的布长', biz_type int(20) NOT NULL COMMENT '业务类型', version int(20) NOT NULL COMMENT '版本号', PRIMARY KEY (`id`) )

  • max_id:当前最大的可用 id。
  • step:代表号段的长度。
  • biz_type:代表不同业务类型。
  • version:是一个乐观锁,每次都更新 version,保证并发时数据的正确性。

等这批号段 ID 用完,再次向数据库申请新号段,对 max_id 字段做一次 update 操作: update max_id = max_id + step,update 成功则说明新号段获取成功,新的号段范围是(max_id, max_id + step]

image.png

image.png

由于多业务端可能同时操作,所以采用版本号 version 乐观锁方式更新,这种分布式 ID 生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。但同样也会存在一些缺点比如:服务器重启,单点故障会造成 ID 不连续。

使用Redis生成

基于全局唯一 ID 的特性,我们可以通过 Redis 的 INCR 命令来生成全局唯一 ID。

同样使用 Redis 也有对应的缺点:

  • ID 生成的持久化问题,如果 Redis 宕机了怎么进行恢复。
  • 单个节点宕机问题。

当然针对故障问题我们可以通过 Redis 集群来处理,比如我们有三个 Redis 的 Master 节点。可以初始化每台 Redis 的值分别是 1,2,3。然后分别把分布式 ID 的 KEY 用 Hash Tags 固定每一个 Master 节点,步长就是 Master 节点的个数。各个 Redis 生成的 ID 为:

text A: 1, 4, 7 B: 2, 5, 8 C: 3, 6, 9

优点:

  • 不依赖于数据库,灵活方便,且性能优于数据库
  • 数字 ID 有序,对分页处理和排序都很友好
  • 防止了 Redis 的单机故障。

缺点:

  • 如果没有 Redis 中间件,需要安装配置,增加复杂度。
  • 集群节点确定是 3 个后,后面调整不是很方便。

Redis 分布式 ID 生成器实例代码,结合 Spring Boot 实现:

```java /** * Redis 分布式ID生成器 */ @Component public class RedisDistributedId {

@Autowired
private StringRedisTemplate redisTemplate;

private static final long BEGIN_TIMESTAMP = 1659312000l;

/**
 * 生成分布式ID
 * 符号位    时间戳[31位]  自增序号【32位】
 * @param item
 * @return
 */
public long nextId(String item) {
    // 1. 生成时间戳
    LocalDateTime now = LocalDateTime.now();
    // 格林威治时间差
    long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
    // 我们需要获取的 时间戳 信息
    long timestamp = nowSecond - BEGIN_TIMESTAMP;
    // 2. 生成序号 从Redis中获取
    // 当前当日期
    String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
    // 获取对应的自增的序号
    Long increment = redisTemplate.opsForValue().increment("id:" + item + ":" + date);
    return timestamp << 32 | increment;
}

} ```

雪花算法

Snowflake,雪花算法是由 Twitter 开源的分布式 ID 生成算法,以划分命名空间的方式将 64 bit 位分割成了多个部分,每个部分都有具体的不同含义,在 Java 中 64 Bit 位的整数是 long 类型,所以在 Java 中 Snowflake 算法生成的 ID 就是 long 来存储的。具体如下:

d8bd4b48def44f81a03cb399a6775733.png

  • 第一部分:占用 $1$ bit,第一位为符号位,不用。
  • 第二部分:$41$ 位的时间戳,41 bit 位可以表示 $2^{41}$ 个数,每个数代表的是毫秒,那么雪花算法的时间年限是 $2^{41} / (1000×60×60×24×365) = 69$ 年
  • 第三部分:$10$ bit 表示是机器数,即 $2^{10} = 1024$ 台机器,通常不会部署这么多机器
  • 第四部分:$12$ bit 位是自增序列,可以表示 $2^{12} = 4096$ 个数,一秒内可以生成 $4096$ 个 ID

示例代码:

```java package org.codeart.utils;

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

// ==============================Fields===========================================

/**
 * 开始时间截 (2020-11-03,一旦确定不可更改,否则时间被回调,或者改变,可能会造成id重复或冲突)
 */
private final long twepoch = 1604374294980L;

/**
 * 机器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=====================================

/**
 * 构造函数
 */
public SnowflakeIdWorker() {
    this.workerId = 0L;
    this.datacenterId = 0L;
}

/**
 * 构造函数
 * @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();
}

/**
 * 随机id生成,使用雪花算法
 * @return
 */
public static String getSnowId() {
    SnowflakeIdWorker sf = new SnowflakeIdWorker();
    String id = String.valueOf(sf.nextId());
    return id;
}

//=========================================Test=========================================

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

} ```

UidGenerator(百度)

UidGenerator 是百度开源的 Java 语言实现,基于 Snowflake 算法的唯一 ID 生成器。它是分布式的,并克服了雪花算法的并发限制。单个实例的 QPS 能超过 6000000。需要的环境:JDK 8+,MySQL(用于分配 WorkerId)。

百度的 Uidgenerator 对结构做了部分的调整,具体如下:

7e731bd302a24d60b3d1a2cb13273d21.png

UidGenerator 的时间部分只有 28 位,这就意味着 UidGenerator 默认只能承受 $2^{28} - 1/86400/365 = 8.5$ 年。你也可以根据你业务的需求,UidGenerator 可以适当调整 delta seconds、worker node id和 sequence 占用位数。

源码地址:https://github.com/baidu/uid-generator

中文文档地址:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

Leaf(美团)

Leaf 算法由美团开发,开源项目链接:https://github.com/Meituan-Dianping/Leaf。

Leaf 同时支持号段模式和 Snowflake 算法模式,可以切换使用。ID 号码是趋势递增的 8 byte的 64 位数字,满足上述数据库存储的主键要求。

Leaf 的 snowflake 模式依赖于 ZooKeeper,不同于原始 Snowflake 算法也主要是在 workId 的生成上,Leaf 中 workId 是基于 ZooKeeper 的顺序 Id 来生成的,每个应用在使用 Leaf-Snowflake 时,启动时都会都在 Zookeeper 中生成一个顺序 Id,相当于一台机器对应一个顺序节点,也就是一个 workId。

Leaf 的号段模式是对直接用数据库自增 ID 充当分布式 ID 的一种优化,减少对数据库的频率操作。相当于从数据库批量的获取自增 ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表 1000 个ID,业务服务将号段在本地生成 1~1000 的自增 ID 并加载到内存。

特性:

1)全局唯一,绝对不会出现重复的 ID,且 ID 整体趋势递增。 2)高可用,服务完全基于分布式架构,即使 MySQL 宕机,也能容忍一段时间的数据库不可用。 3)高并发低延时,在 CentOS 4C 8G 的虚拟机上,远程调用 QPS 可达 5W+,TP99 在 1ms 内。 4)接入简单,直接通过公司 RPC 服务或者 HTTP 调用即可接入。

Leaf 采用双 Buffer 的方式,它的服务内部有两个号段缓存区 Segment。当前号段已消耗 10% 时,还没能拿到下一个号段,则会另启一个更新线程去更新下一个号段。

简而言之就是 Leaf 保证了总是会多缓存两个号段,即便哪一时刻数据库挂了,也会保证发号服务可以正常工作一段时间。

TinyID(滴滴)

TinyID 算法由滴滴开发,开源项目链接:https://github.com/didi/tinyid。

TinyID 是在美团(Leaf)的 leaf-segment 算法基础上升级而来,不仅支持了数据库多主节点模式,还提供了 tinyid-client 客户端的接入方式,使用起来更加方便。但和美团(Leaf)不同的是,TinyID 只支持号段一种模式不支持雪花模式。TinyID 提供了两种调用方式,一种基于 Tinyid-server 提供的 http 调用方式,另一种 Tinyid-client 客户端方式。每个服务获取一个号段(1000, 2000]、(2000, 3000]、(3000, 4000]。

特性:

1)全局唯一的 long 型 ID。 2)趋势递增的 id。 3)提供 http 和 java-client 方式接入 4)支持批量获取 ID。 5)支持生成 1, 3, 5, 7, 9 有固定步长序列的 ID。 6)支持多个 db 的配置。

适用场景:只关心 ID 是数字,趋势递增的系统,可以容忍 ID 不连续,可以容忍 ID 的浪费。 不适用场景:像类似于订单 ID 的业务,因生成的 ID 大部分是连续的,容易被扫库、或者推算出订单量等信息。

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

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

相关文章

Python 读取esxi上所有主机的设备信息

&#xff08;主要是为了统计所有虚拟机的设备名称和所属主机&#xff09; 代码&#xff1a; from pyVim import connect from pyVmomi import vim import ssldef get_vm_devices(vm):devices []try:if vm.config is not None and hasattr(vm.config, hardware) and hasattr(v…

AI安全系列——[第五空间 2022]AI(持续更新)

最近很长时间没有更新&#xff0c;其实一直在学习AI安全&#xff0c;我原以为学完深度学习之后再学AI安全会更加简单些&#xff0c;但是事实证明理论转实践还是挺困难的&#xff0c;但是请你一定要坚持下去&#xff0c;因为“不是所有的坚持都有结果&#xff0c;但总有一些坚持…

Mac电脑清理软件有哪些 MacBooster和CleanMyMac哪个好用 苹果电脑清理垃圾软件推荐 cleanmymac和柠檬清理

对于苹果电脑用户来说&#xff0c;‌选择合适的清理软件可以帮助优化电脑性能&#xff0c;‌释放存储空间&#xff0c;‌并确保系统安全。一款好用的苹果电脑清理软件&#xff0c;能让Mac系统保持良好的运行状态&#xff0c;避免系统和应用程序卡顿的产生。有关Mac电脑清理软件…

CSS-1_0 CSS和文档流

文章目录 CSS和文档流如何证明这个流的存在呢&#xff1f;流和display番外&#xff1a;inline-block 碎碎念 CSS和文档流 首先什么叫流呢&#xff1f; 通常来说&#xff0c;我们最终看到的网页是HTML文档中定义的各个元素挨个输出的结果&#xff0c;这种一个接一个输出的方式…

14_Shell重定向输入输出

14_Shell重定向输入输出 输出重定向&#xff1a;一般情况&#xff0c;输出是在终端直接显示&#xff0c;改变输出位置&#xff0c;改变到文件中&#xff0c;这就是输出重定向 输入重定向&#xff1a;一般情况&#xff0c;输入是读取用户终端输入&#xff0c;改变输入位置&#…

Java二十三种设计模式-建造者模式(4/23)

建造者模式&#xff1a;构建复杂对象的专家 引言 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;用于创建一个复杂的对象&#xff0c;同时允许用户只通过指定复杂对象的类型和内容就能构建它们&#xff0c;它将对象的构建和表示分离&am…

Spring Boot 集成 RabbitMQ

依赖与配置 在 pom.xml 中引入 RabbitMQ 相关依赖 <!-- AMQP 依赖, RabbitMq --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId><version>3.2.7</version> &…

详解注意力机制上篇【RNN,Seq2Seq(Encoder-Decoder,编码器-解码器)等基础知识】

NLP-大语言模型学习系列目录 一、注意力机制基础——RNN,Seq2Seq等基础知识 二、注意力机制【Self-Attention,自注意力模型】 &#x1f525; 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;理解和生成自然语言的能力对于构建智能系统至关重要。从文本分类、机器翻…

昇思25天学习打卡营第22天|基于MindSpore的红酒分类实验

基于MindSpore的红酒分类实验 K近邻算法实现红酒聚类 1、实验目的 了解KNN的基本概念&#xff1b;了解如何使用MindSpore进行KNN实验。 2、K近邻算法原理介绍 K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;…

WSL-Ubuntu20.04环境使用YOLOv8 TensorRT推理加速

在阅读本章内容之前&#xff0c;需要把部署环境以及训练环境都安装好。 1.TensorRTX下载 这里使用Wang-xinyu大佬维护的TensorRTX库来对YOLOv8进行推理加速的演示&#xff0c;顺便也验证一下前面环境配置的成果。 github地址&#xff1a;GitHub - wang-xinyu/tensorrtx&#x…

SourceTree rebase(变基)的使用

参考资料 【Sourcetree】コミットを一つにまとめる【Sourcetree】リベースする 目录 前提0.1 merge与rebase0.2 merge合并分支0.3 rebase合并分支0.4 &#x1f4a5;超级注意事项&#x1f4a5; 一. 代码已提交&#xff0c;未推送&#xff0c;交互式变基1.1 通过SourceTree操作1…

Richtek立锜科技可用于智能门铃的电源管理解决方案

新型的智能门铃不仅能满足呼叫、提醒的需要&#xff0c;还能在线监控、远程操作、闯入通知、记录过程&#xff0c;系统构成相对复杂&#xff0c;与传统门铃相比有了很大的改变。 从电源管理的角度来观察&#xff0c;满足这样需求的系统构成也相对复杂&#xff1a; 处于外置状态…

ElementUIV12相关使用方法

今日内容 零、 复习昨日 零、 复习昨日 一、Element UI Element&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 官网&#xff1a; https://element.eleme.cn/#/zh-CN Element Plus,基于 Vue 3&#xff0c;面向设计师和开发者的组件库 官网: htt…

多样化数据可视化方法的全面示例:基于Python的多样化数据可视化

文章目录 前言代码效果展示 前言 本文演示了使用Python进行温度数据的多样化可视化方法。通过导入、处理和分析气象数据&#xff0c;我们生成了多种图表&#xff0c;包括直方图、核密度估计图、箱型图、小提琴图、条形图、山脊图、经验累积分布函数图和折线图。这些图表帮助我…

从产品手册用户心理学分析到程序可用性与易用性的重要区别

注&#xff1a;机翻&#xff0c;未校对。 Designing for People Who Have Better Things To Do With Their Lives 为那些生活中有更重要事情要做的人设计 When you design user interfaces, it’s a good idea to keep two principles in mind: 在设计用户界面时&#xff0c;…

微软Office PLUS办公插件下载安装指南

微软OfficePLUS插件下载安装指南 简介&#xff1a; OfficePLUS微软官方出品的Office插件 &#xff0c;OfficePLUS拥有30万高质量模板素材&#xff0c;能帮助Word、Excel、Powerpoint、PDF等多种办公软件提升效率&#xff0c;具有智能化、模板质量高、运行快、稳定性强等优点。…

探索Facebook:数字社交的魔力源泉

在当今信息爆炸和全球互联的时代&#xff0c;社交媒体平台成为了人们生活中不可或缺的一部分。而在这些平台中&#xff0c;Facebook无疑是最具影响力和创新性的代表之一。自2004年成立以来&#xff0c;Facebook不仅改变了人们的沟通方式&#xff0c;更通过不断的技术创新和用户…

Pycharm与Gitlab交互

环境准备 1、下载配置好本地Git 2、配置Pycharm上的Git 3、gitlab账号 Gitlab配置 Gitlab配置中文 账号》设置》偏好设置》简体中文 创建项目 命令行操作 打开项目会展示以下步骤 在pycharm克隆gitlab的项目 通过菜单栏 1、在PyCharm的顶部菜单栏中&#xff0c;选择“V…

探索智能合约在金融科技中的前沿应用与挑战

随着区块链技术的发展和普及&#xff0c;智能合约作为其核心应用之一&#xff0c;在金融科技&#xff08;FinTech&#xff09;领域中展现出了巨大的潜力和挑战。本文将深入探讨智能合约的基本概念、前沿应用案例&#xff0c;以及面临的技术挑战和发展趋势&#xff0c;旨在帮助读…

R语言进行集成学习算法:随机森林

# 10.4 集成学习及随机森林 # 导入car数据集 car <- read.table("data/car.data",sep ",") # 对变量重命名 colnames(car) <- c("buy","main","doors","capacity","lug_boot","safety"…