分布式系列之ID生成器

news2024/12/23 18:07:21

背景

在分布式系统中,当数据库数据量达到一定量级后,需要进行数据拆分、分库分表操作,传统使用方式的数据库自有的自增特性产生的主键ID已不能满足拆分的需求,它只能保证在单个表中唯一,所以需要一个在分布式环境下都能使用的全局唯一ID。

应用场景

  • 用户ID、图片ID等各种业务场景
  • 分库分表情况下的订单号
  • 分布式链路追踪系统中的TraceId

需求分析:

  • 可靠性:全局唯一性,不能生成重复的ID,最基本的要求
  • 安全性:保证数据安全,防止恶意用户分析出ID生成规则,进而获取到系统业务信息
  • 递增性:下一个时间点产生的ID大于前一个时间点的ID
  • 时间有序:以时间为序,或ID里包含时间。表设计时可以不用考虑再添加一个时间字段,也方便冷热数据分离
  • 可读性:生成的ID应该有业务意义
  • 长度适中:不要太长,太长则数据表存储不便,可使用Long类型(String也行)
  • 分片支持:可以控制ShardingId。如某一个用户的文章要放在同一个分片内,这样查询效率高,修改也容易
  • 高可用:不能出现单点故障
  • 高性能:响应速度快,毫秒内生成的ID数量要满足海量用户请求
  • 扩展性:ID生成器服务集群发生节点宕机,加入新节点是否便捷
  • 可维护性:实现方案不能太复杂,方便后期维护

上面列举出11个需求点,已经足够多,当然也可以再增加。安全性和递增性之间存在一定的互斥,需要做取舍。递增性不强求绝对严格递增,即不需要满足+1递增。在做方案设计时,需要具体情况具体分析,前面几个是必须要满足。大体而言,首先确保满足前面几个需求点,即优先级更高,再考虑后面几个。能满足的需求点越多,则方案越复杂,需要加以权衡和取舍,不强求完全实现所有的需求点。

实现

实现方案有很多,不是每种方案都能完美实现上面提到的各个需求点:

  • 数据库
  • UUID
  • Snowflake
  • Redis
  • ZooKeeper
  • Snowflake-like

数据库

基于数据表auto increment规则来生成全局唯一递增ID。

优点:简单,可保证唯一性、递增性,步长固定

缺点:

  • 可用性:不高,数据库常见架构是一主多从+读写分离,生成自增ID是写请求,主库宕机,则服务不可用。
  • 扩展性:较差,性能有限,写入是单点,主库的写性能决定ID生成性能上限,且难以扩展
  • 兼容性:不同数据库语法和实现不同,数据库迁移时或多数据库版本支持时需要特殊处理

改进方法:冗余主库,避免写入单点;数据水平切分,保证各主库生成的ID不重复。

具体来说,比如可将1个写库变成N个写库,每个写库设置不同的auto increment初始值,和相同的步长,以保证每个数据库生成的ID是不同的。

改进后方案可提高可用性,但拓展性差的问题依旧存在。数据库写压力有所缓解,但写压力依旧存在;可考虑一次性从DB里取出多个ID放在Redis缓存里。

UUID

Universally Unique Identifier,标准型式包含32个16进制字符,以连字号分为五段,其形式为8-4-4-4-12,到目前为止业界一共有5种方式生成UUID,参考IETF发布的UUID规范:

  • 版本1 - 根据时间和节点 ID(通常是MAC地址)生成;
  • 版本2 - 根据标识符(通常是组或用户ID)、时间和节点ID生成;
  • 版本3、版本5 - 确定性UUID,通过散列名字空间标识符和名称生成;版本5和3的区别在于使用不同的散列算法;
  • 版本4 - 使用随机性或伪随机性生成。

v1

UUID-v1是通过使用主机MAC地址和当前日期和时间的组合生成的。之外还引入另一个随机组件,以确保其唯一性。但是如果使用同一台机器、同时时间生成UUID,会有很小的几率重复。

UUID-v1存在的问题是:

  • 存在重复几率
  • 根据ID能推算出创建时的相对时间
  • 根据ID能推算出创建的机器唯一标识

v2

UUID-v2和v1很类似,是根据标识符(通常是组或用户ID)、时间和节点ID生成,区别在于v2将v1中的部分时间信息换成主机名, 存在隐私风险,未大规模使用。

v3

UUID-v3通过MD5散列算法基于命名空间标识符和名称生成UUID。和v1、v2不同,v3不依赖与机器信息和时间信息,但v3要求输入命名空间+名称,命名空间本身也是一个UUID,用来标识应用环境,名称通常是用户账号、用户名之类的内容,通过命名空间+名称+三列算法算出UUID。

UUID-v5和v3类似,区别在于使用sha1散列算法。

v4

基于随机数的算法。用SecureRandom生成16个随机的Byte,用2个long来存储。记得加-Djava.security.egd=file:/dev/./urandom

JDK里UUID.randomUUID静态方法使用加密强度高的伪随机数生成器生成v4伪随机UUID:

public static UUID randomUUID() {
	SecureRandom ng = Holder.numberGenerator;
	byte[] randomBytes = new byte[16];
	ng.nextBytes(randomBytes);
	randomBytes[6]  &= 0x0f;  /* clear version        */
	randomBytes[6]  |= 0x40;  /* set to version 4     */
	randomBytes[8]  &= 0x3f;  /* clear variant        */
	randomBytes[8]  |= (byte) 0x80;  /* set to IETF variant  */
	return new UUID(randomBytes);
}

JDK提供UUID.randomUUID静态方法从字节数组生成基于名称的v3版本的UUID:

public static UUID nameUUIDFromBytes(byte[] name) {
	MessageDigest md;
	try {
	    md = MessageDigest.getInstance("MD5");
	} catch (NoSuchAlgorithmException nsae) {
	    throw new InternalError("MD5 not supported", nsae);
	}
	byte[] md5Bytes = md.digest(name);
	md5Bytes[6]  &= 0x0f;  /* clear version        */
	md5Bytes[6]  |= 0x30;  /* set to version 3     */
	md5Bytes[8]  &= 0x3f;  /* clear variant        */
	md5Bytes[8]  |= (byte) 0x80;  /* set to IETF variant  */
	return new UUID(md5Bytes);
}

v1变种

Hibernate

基于hibernate-core-6.4.4.Final版本的CustomVersionOneStrategy源码如下:

public class CustomVersionOneStrategy implements UUIDGenerationStrategy, UuidGenerator.ValueGenerator {
	private final long mostSignificantBits;
	
	public int getGeneratedVersion() {
		return 1;
	}
	
	public CustomVersionOneStrategy() {
		byte[] hiBits = new byte[8];
		System.arraycopy(Helper.getAddressBytes(), 0, hiBits, 0, 4);
		System.arraycopy(Helper.getJvmIdentifierBytes(), 0, hiBits, 4, 4);
		hiBits[6] = (byte)(hiBits[6] & 15);
		hiBits[6] = (byte)(hiBits[6] | 16);
		this.mostSignificantBits = BytesHelper.asLong(hiBits);
	}
	
	public UUID generateUuid(SharedSessionContractImplementor session) {
		long leastSignificantBits = generateLeastSignificantBits(System.currentTimeMillis());
		return new UUID(this.mostSignificantBits, leastSignificantBits);
	}
	
	public UUID generateUUID(SharedSessionContractImplementor session) {
		return this.generateUuid(session);
	}
	
	public long getMostSignificantBits() {
		return this.mostSignificantBits;
	}
	
	public static long generateLeastSignificantBits(long seed) {
		byte[] loBits = new byte[8];
		short hiTime = (short)((int)(seed >>> 32));
		int loTime = (int)seed;
		System.arraycopy(BytesHelper.fromShort(hiTime), 0, loBits, 0, 2);
		System.arraycopy(BytesHelper.fromInt(loTime), 0, loBits, 2, 4);
		System.arraycopy(Helper.getCountBytes(), 0, loBits, 6, 2);
		loBits[0] = (byte)(loBits[0] & 63);
		loBits[0] = (byte)(loBits[0] | 128);
		return BytesHelper.asLong(loBits);
	}
}

解读:

MongoDB

MongoDB的bson-4.11.1版本下ObjectId的源码:

public final class ObjectId implements Comparable<ObjectId>, Serializable {
    private static final AtomicInteger NEXT_COUNTER = new AtomicInteger((new SecureRandom()).nextInt());

	public ObjectId() {
		this(new Date());
	}
	
	public ObjectId(Date date) {
		this(dateToTimestampSeconds(date), NEXT_COUNTER.getAndIncrement() & 16777215, false);
	}
	
	private static int dateToTimestampSeconds(Date time) {
		return (int)(time.getTime() / 1000L);
	}
}

解读:

  • 16777215:即LOW_ORDER_THREE_BYTES,一个24位(3字节)的整数,其十六进制表示为0xFFFFFF。机器标识符是一个3字节的值,而16777215是3字节整数的最大值。这意味着机器标识符的范围是0到16777215,确保可以使用一个唯一的标识符来表示每台机器。
  • SecureRandom:和JDK UUID一样使用SecureRandom来生成强随机数。

ObjectId使用12字节的存储空间:

  • 前4个字节表示时间戳,秒级别
  • 随后3个字节是机器标识码
  • 随后2个字节由进程id组成,同一台机器上可能会运行多个mongod实例,因此也需要加入进程标识符PID
  • 最后3个字节是随机数

前9个字节保证同一秒钟不同机器不同进程产生的ObjectId的唯一性。后三个字节是一个自动增加的计数器(一个mongod进程需要一个全局的计数器),保证同一秒的ObjectId是唯一的。同一秒钟最多允许每个进程拥有(256^3=16777216)个不同的ObjectId。

ObjectId用于文档的主键_id字段,_id可在服务端、客户端生成,在客户端生成可以降低服务器端的压力。

总结

缺点:

  • 不易于存储:UUID太长,以36个字符串(加上4个连字符)表示;不适合作为数据表主键,不利于建索引,UUID的无序性可能会引起数据位置频繁变动,严重影响性能
  • 没有排序:无法保证趋势递增
  • 可读性不好
  • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置

优点:性能非常高,本地生成,没有远程调用等网络消耗,时延低。

标准的UUID算法使用场景不多,改进版如MongoDB的ObjectId,可用于生产实践中。

Snowflake

参考GitHub。Twitter在把存储系统从MySQL迁移到Cassandra的过程中,由于Cassandra没有顺序ID生成机制,于是自己开发一套全局唯一ID生成服务。

共64位二进制:

  • 第1位固定为0,没有业务含义,符号位
  • 第2~42位,共41位,为时间戳位,用于存入精确到毫秒数的时间
  • 第43~52位,共10位,为机器ID位,其中高位5bit是数据中心ID(dataCenterId),低位5bit是工作节点ID(workerId)
  • 第53~64位,共12位,代表1ms内可以产生的序列号,取值区间为[0,4095],也就是说在数据中心ID和机器ID相同的情况下,1ms最多可以生成4096个序列号

如果序列号超过最大值,则会将程序阻塞到下一毫秒,然后序列号归零,继续生成ID。要想Snowflake生成全局唯一的ID,则ID生成器必须也是全局单例。

Snowflake对ZooKeeper的依赖性:集群节点启动时,从一个ZooKeeper集群获取,保证所有节点不会有重复的机器号

代码:

public class SnowflakeIdWorker {
	/**
	 * 机器id所占的位数
	 */
	private final long workerIdBits = 5L;
	/**
	 * 数据标识id所占的位数
	 */
	private final long datacenterIdBits = 5L;
	/**
	 * 工作机器ID(0~31)
	 */
	private final long workerId;
	/**
	 * 数据中心ID(0~31)
	 */
	private final long datacenterId;
	/**
	 * 序列ID位数
	 */
	private static final long sequenceBits = 12L;
	/**
	 * 毫秒内序列(0~4095)
	 */
	private long sequence = 0L;
	
	/**
	 * 上次生成ID的时间截
	 */
	private long lastTimestamp = -1L;
	/**
	 * 构造函数
	 */
	public SnowflakeIdWorker(long workerId, long datacenterId) {
		// 支持的最大机器id,结果是31 (这个移位算法可以很快地计算出几位二进制数所能表示的最大十进制数)
		long maxWorkerId = ~(-1L << workerIdBits);
		if (workerId > maxWorkerId || workerId < 0) {
			throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
		}
		// 支持的最大数据标识id,结果是31
		long maxDatacenterId = ~(-1L << datacenterIdBits);
		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;
	}
	
	/**
	 * 获得下一个ID(线程安全)
	 */
	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) {
			// 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
			long sequenceMask = ~(-1L << sequenceBits);
			sequence = (sequence + 1) & sequenceMask;
			// 毫秒内序列溢出
			if (sequence == 0) {
				// 阻塞到下一个毫秒,获得新的时间戳
				timestamp = tilNextMillis(lastTimestamp);
			}
		} else {
			// 时间戳改变,毫秒内序列重置
			sequence = 0L;
		}
		// 上次生成ID的时间截
		lastTimestamp = timestamp;
		// 移位并通过或运算拼到一起组成64位的ID
		// 开始时间截(2024-01-01)
		long startEpoch = 1704038400000L;
		// 数据标识id向左移17位(12+5)
		long datacenterIdShift = sequenceBits + workerIdBits;
		// 时间截向左移22位(5+5+12)
		long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
		return ((timestamp - startEpoch) << timestampLeftShift)
	            | (datacenterId << datacenterIdShift)
	            | (workerId << sequenceBits)
	            | sequence;
	}
	
	/**
	 * 阻塞到下一个毫秒,直到获得新的时间戳
	 *
	 * @param lastTimestamp 上次生成ID的时间截
	 * @return 当前时间戳
	 */
	protected long tilNextMillis(long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}
	
	/**
	 * 返回以毫秒为单位的当前时间
	 */
	protected long timeGen() {
		return System.currentTimeMillis();
	}
}

如何分配datacenterId和workerId呢?

  • 写死 : 单机部署,然后写死两个值,不可取
  • 读配置文件 : 将值放在配置中心,应用启动时读取
  • 动态分配 :

存在的问题:

  • 时间戳只存在41位二进制,只能使用69年,69年后就可能产生重复ID
  • 如果机器性能足够好,每秒可以产生超过400万个ID,但是对于大部分企业来说,只需要每秒满足数万个ID即可。这种高性能浪费的主要是序号的二进制位,实际上,二进制位达到9位,就可以产生512个序号,如果机器性能足够,就可以每秒产生超过50万的ID,能满足绝大部分企业的需要
  • 从机器位来说,因为去中心化是分布式和微服务的趋势,所以在实现时,并未考虑受理机器编号,这样就会造成机器位数有10位二进制,可以表达区间[0, 1023]的整数。如果数据中心预估总共只有几十台机器,显然也会造成二进制位的浪费。

时钟回拨问题
获取到的当前Timestamp比前一个已生成ID的Timestamp还要小。做法是while循环,继续获取当前机器的时间,直到获取到更大的Timestamp才能继续工作,在这个等待过程中不能分配出新的ID。

解决方法
使用NTP(Network Time Protocol)确保系统时钟是准确的。最好把NTP配置成不会向后调整的模式。即NTP纠正时间时,不会向后回拨机器时钟。

Redis

基于Redis的原子操作INCR和INCRBY来实现,与数据库改进版类似,Redis采用集群化部署方案,每个节点设置一个不同的初始值,步长保持一致。且可以预生成一批ID,提高性能。

ZooKeeper

Snowflake改进

业界最常用的解决方案是基于Snowflake的改进版。

Boundary flake

GitHub:

Flake: A decentralized, k-ordered id generation service in Erlang

几点变化:

  • ID长度扩展到128位
  • 最高64位时间戳
  • 48位的Worker ID,和Mac地址一样长,启动时无需和ZooKeeper通讯获取Worker ID,做到完全去中心化
  • 16位的Seq Number
  • 基于Erlang

目的是用更多的位实现更小的冲突概率,且能支持更多的Worker同时工作,每毫秒能分配出更多的ID。

Instagram

Instagram的分布式存储方案:

  • 先把每个Table划分为多个逻辑分片(Logic Shard,简称LS)
  • 制定规则,每个LS被存储到哪个数据库实例上,数据库实例不需要很多。例如有2个PostgreSQL实例的系统,可将奇数逻辑分片存放到第一个数据库实例,偶数放到第二个
  • 每个Table指定一个字段作为分片字段,如用户表可指定uid作为分片字段
  • 插入一个新的数据时,先根据分片字段的值,决定数据被分配到哪个逻辑分片
  • 然后再根据逻辑分片和PostgreSQL实例的对应关系,确定这条数据应该被存放到哪台PostgreSQL实例上

Instagram unique ID组成:

  • 41位: 精确到毫秒的Timestamp,和Snowflake类似
  • 13位: 每个Logic Shard的代号,最大支持 2 13 2^{13} 213个LS
  • 10位: Sequence Number,简称SN,每个Shard每毫秒最多可以生成1024个ID

SN利用PostgreSQL表的自增序列(sequence)来生成:如果当前表上已经有5000条记录,则这个表的下一个自增序列就是5001(直接调用PG提供的方法可以获取到),然后把这个5001对1024取模就得到10位的SN。

优势在于:

  • 利用LS号来替换Snowflake使用的Worker号,就不需要到中心节点获取Worker号,做到完全去中心化
  • 通过ID可直接知道这条记录被存放在哪个LS上;数据迁移时,也是按LS为单位做数据迁移

开源

Leaf

美团-点评开源的Leaf。

UidGenerator

百度开源的UidGenerator。

Flyway

Flyway是一款开源的数据库版本管理工具,其源码里有对Snowflake的支持:
在这里插入图片描述
有待进一步研究。

其他

https://github.com/zhuzhong/idleaf

参考

  • 分布式系统Unique ID生成方法
  • UUID版本指南

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

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

相关文章

JavaScript进阶之作用域解构箭头函数

目录 一、作用域1.1 局部作用域1.2 全局作用域1.3 作用域链1.4 垃圾回收机制1.5 闭包1.6 变量提升 二、函数进阶2.1 函数提升2.2 函数参数2.3 箭头函数&#xff08;重要&#xff09; 三、解构赋值3.1 数组解构3.2 对象解构&#xff08;重要重要&#xff09; 一、作用域 1.1 局…

全自动蛋托清洗机介绍:

全自动蛋托清洗机&#xff0c;作为现代蛋品处理设备的杰出代表&#xff0c;凭借其高效、智能、环保的特性&#xff0c;正逐步成为蛋品加工行业的得力助手。 这款清洗机采用了先进的自动化设计理念&#xff0c;从进料、清洗到出料&#xff0c;全程无需人工干预&#xff0c;极大…

SpringCloud---服务注册(Eureka)

目录 前言 一.注册中心 二.CAP理论 三.常见的注册中心 四.Eureka 4.1搭建Eueka Server 4.2服务注册 4.3发现服务 4.4小结 学习专栏&#xff1a;http://t.csdnimg.cn/tntwg 前言 在SpringCloud里&#xff0c;我们可以发现一个巨大的问题&#xff0c;就是url是写死的&am…

如何在 Android 中删除和恢复照片

对于智能手机用户来说&#xff0c;相机几乎已经成为一种条件反射&#xff1a;你看到值得注意的东西&#xff0c;就拍下来&#xff0c;然后永远保留这段记忆。但如果那张照片不值得永远保留怎么办&#xff1f;众所周知&#xff0c;纸质快照拿在手里很难舍弃&#xff0c;而 Andro…

grafana大坑,es找不到时间戳 | No date field named timestamp found

grafana大坑&#xff0c;es找不到时间戳。最近我这边的es重新装了一遍&#xff0c;结果发现grafana连不上elasticsearch了&#xff08;以下简称es&#xff09;&#xff0c;排查问题查了好久一直以为是es没有装成功或者两边的版本不兼容&#xff0c;最后才发现是数值类型问题 一…

一天搞定React(3)——Hoots组件

Hello&#xff01;大家好&#xff0c;今天带来的是React前端JS库的学习&#xff0c;课程来自黑马的往期课程&#xff0c;具体连接地址我也没有找到&#xff0c;大家可以广搜巡查一下&#xff0c;但是总体来说&#xff0c;这套课程教学质量非常高&#xff0c;每个知识点都有一个…

【Node】npm i --legacy-peer-deps,解决依赖冲突问题

文章目录 &#x1f356; 前言&#x1f3b6; 一、问题描述✨二、代码展示&#x1f3c0;三、运行结果&#x1f3c6;四、知识点提示 &#x1f356; 前言 npm i --legacy-peer-deps&#xff0c;解决依赖冲突问题 &#x1f3b6; 一、问题描述 node执行安装指令时出现报错&#xff…

【QT】label适应图片(QImage)大小;图片适应label大小

目录 0.简介 1.详细代码 1&#xff09;label适应img大小 2&#xff09;img适应label大小 0.简介 一个小demo &#xff0c;想在QLabel中放一张QImage的图片&#xff0c;我有一张图片叫【bird.jpg】&#xff0c;是提前放在资源文件中的&#xff0c;直接显示在label上后&#…

【网络】网络聊天室udp

网络聊天室udp 一、低耦合度代码1、代码2、测试结果 二、高耦合度代码1、服务端小改&#xff08;1&#xff09;维护一个unordered_map用户列表&#xff08;2&#xff09;服务端代码&#xff08;3&#xff09;客户端不改的情况下结果展示 2、大改客户端&#xff08;udp全双工用多…

通过QT进行服务器和客户端之间的网络通信

客户端 client.pro #------------------------------------------------- # # Project created by QtCreator 2024-07-02T14:11:20 # #-------------------------------------------------QT core gui network #网络通信greaterThan(QT_MAJOR_VERSION, 4): QT widg…

饥荒dst联机服务器搭建基于Ubuntu

目录 一、服务器配置选择 二、项目 1、下载到服务器 2、解压 3、环境 4、启动面板 一、服务器配置选择 首先服务器配置需要2核心4G&#xff0c;4G内存森林加洞穴大概就占75% 之后进行服务器端口的开放&#xff1a; tcp:8082 tcp:8080 UDP:10888 UDP:10998 UDP:10999 共…

套接字编程一(简单的UDP网络程序)

文章目录 一、 理解源IP地址和目的IP地址二、 认识端口号1. 理解 "端口号" 和 "进程ID"2. 理解源端口号和目的端口号 三、 认识协议1. 认识TCP协议2. 认识UDP协议 四、 网络字节序五、 socket编程接口1. socket 常见API2. sockaddr结构&#xff08;1&#…

输入设备应用编程-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

输入设备应用编程 输入类设备编程介绍 什么是输入设备 输入设备&#xff08;input 设备&#xff09;&#xff0c;如鼠标、键盘、触摸屏等&#xff0c;允许用户与系统交互 input 子系统 Linux系统通过input子系统管理多种输入设备 Input子系统提供统一的框架和接口&#xff…

网络编程之LINUX信号

注意发送信号是给进程&#xff0c;不是线程&#xff0c;调用的是KILL函数&#xff0c;SIG是信号种类。pid0是本进程的其他的进程。 可以通过设置ERRNO来查看返回的错误&#xff0c;如下&#xff1a; 当目标进程收到信号后&#xff0c;要对信号进行一些执行操作&#xff1a; 定义…

[每周一更]-(第106期):DNS和SSL协作模式

文章目录 什么是DNS&#xff1f;DNS解析过程DNS解析的底层逻辑 什么是SSL&#xff1f;SSL证书SSL握手过程SSL的底层逻辑 DNS与SSL的协同工作过程 什么是DNS&#xff1f; DNS&#xff08;Domain Name System&#xff0c;域名系统&#xff09;是互联网的重要组成部分&#xff0c…

黑马程序员MySQL基础学习,精细点复习【持续更新】

文章目录 数据库Mysql基础一、数据库1.数据库2.数据库管理系统3.SQL4.Mysql目录结构5.关系型数据库6.SQL基础概念 mysql高级一、数据库备份和还原1.图形化界面备份与还原 二、约束1.分类&#xff1a;2.主键约束3.唯一约束4.非空约束5.默认值约束6.外键约束 三、表关系1.概述2.一…

《Windows API每日一练》13.1 打印基础

在Windows中使用打印机时&#xff0c;在调用一系列与打印相关的GDI绘图函数的背后&#xff0c;实际上启动了一系列模块之间复杂的交互过程&#xff0c;包括 GDI32库模块、打印机设备驱动程序库模块&#xff08;带.DRV后缀的文件&#xff09;、Windows后台打印处理程序&#xff…

5. harbor镜像仓库

harbor镜像仓库 一、镜像仓库1、类型2、构建私有仓库的方案 二、部署harbor仓库(单机版)1、安装docker(略)2、安装docker-compose工具3、安装harbor4、生成harbor需要的证书、密钥(V3版本证书)4.1 创建CA4.2 创建harbor仓库需要的证书 5、编辑harbor配置文件6、启动harbor 三、…

【C++】17.AVL树

一、AVL树的概念 二叉搜索树虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种…

Linux实用操作二

文章目录 Linux实用操作二日期、时区&#xff1a;date命令查看日期时间作用&#xff1a;语法&#xff1a;字段解释&#xff1a;操作&#xff1a; 修改Linux系统时区作用&#xff1a;操作&#xff1a; 使用ntp进行时间同步和校准作用&#xff1a;操作&#xff1a; IP地址、主机名…