笔记-HTTPS握手流程

HTTPS(超文本传输安全协议)的连接流程结合了传输层的TCP三次握手和TLS(传输层安全性协议)的握手过程来提供一个安全的连接。这个过程可以大致分为以下几个步骤:

1. TCP三次握手

这是任何HTTP或HTTPS连接开始的地方。客户端和服务器之间建立一个TCP连接,这是一个可靠的连接,确保了数据包正确无误地到达。

2. TLS握手

在TCP连接成功建立之后,TLS握手过程开始,目的是安全地交换密钥,验证服务器,并且建立加密通道。

a) 客户端Hello(Client Hello)

客户端发送一个“Client Hello”消息到服务器,包含:

  • 客户端支持的TLS版本。
  • 可接受的加密算法(密码套件)列表。
  • 一个客户端随机数(Client Random),之后用于生成加密密钥。

b) 服务器Hello(Server Hello)

服务器响应一个“Server Hello”消息,包含:

  • 选择的TLS版本和密码套件。
  • 一个服务器随机数(Server Random)。
  • 服务器证书(通常是X.509格式)。

c) 服务器证书验证

客户端验证服务器证书的有效性,它可能包括:

  • 确认证书由可信的证书颁发机构(CA)签名。
  • 检查证书的有效期。
  • 确认证书中的域名与正在连接的服务器域名一致。

d) 密钥交换

客户端使用从服务器证书中提取的公钥,发送预主密钥(Pre-Master Secret),这通常是加密的。

e) 会话密钥生成

客户端和服务器都使用客户端随机数、服务器随机数和预主密钥生成相同的会话密钥,这将用于此后的对话加密。

f) 客户端完成(Client Finished)

客户端发送一个“Finished”消息,这通常是之前所有消息的加密和散列值,用于服务器验证。

g) 服务器完成(Server Finished)

服务器同样发送一个“Finished”消息,客户端验证这个消息以确保没有中间人攻击。

3. 加密通信

HTTPS使用的是一种称为公钥基础设施(PKI)的体系结构,在这种体系结构中,确实使用了公钥和私钥机制,但它结合了对称加密和非对称加密的优点来提供安全通信。

  1. 非对称加密
    • 在初始的握手阶段,服务器会向客户端发送其公钥作为数字证书的一部分。
    • 客户端将使用此公钥加密信息(如预主密钥),然后发送回服务器。
    • 只有持有对应私钥的服务器才能解密这些信息,这确保了即使这些信息被截获,没有私钥也无法解密。
    • 这个过程是非对称的,因为加密(公钥)和解密(私钥)使用不同的密钥。
  2. 对称加密
    • 一旦非对称步骤完成,客户端和服务器就会使用交换的信息来生成对称的会话密钥。
    • 后续的通信将使用这个对称密钥进行加密和解密,这意味着客户端和服务器都使用同一个密钥。
    • 对称加密比非对称加密更快,适合大量数据的加密通信。

笔记-TCP的三次握手四次挥手

TCP三次握手例子

假设你的电脑(客户端A)尝试通过TCP连接到一个网站的服务器(服务器B)。服务器的IP地址是192.168.1.1,客户端的IP地址是192.168.1.2,而你想要访问的服务(比如一个网页)是通过标准的HTTP服务端口80提供的。

  1. SYN步骤:
    • 客户端A(192.168.1.2:12345)生成一个随机的初始序列号,比如1000,并向服务器B的80端口发送一个TCP段,其SYN位设置为1。
    • 此时的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.2:12345 目的地址:192.168.1.1:80 Seq=1000 ACK=0 Flags=[SYN]
    • 服务器B接收到这个请求。
  2. SYN-ACK步骤:
    • 服务器B(192.168.1.1:80)收到请求后,会从它自己的端口回应客户端A。它也会生成一个随机的初始序列号,比如5000,并且把确认号设置为客户端A的序列号加1(也就是1001)。
    • 服务器B发送的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.1:80 目的地址:192.168.1.2:12345 Seq=5000 ACK=1001 Flags=[SYN, ACK]
    • 客户端A接收到这个响应。
  3. ACK步骤:
    • 客户端A(192.168.1.2:12345)再次发送一个TCP段给服务器B,ACK位设置为1,序列号为1001(表示客户端A准备好接收服务器B的下一个字节),确认号为5001(表示服务器B发送的数据已被确认,服务器可以发送下一个字节了)。
    • 此时客户端A发送的TCP包头部信息大致如下:makefileCopy code源地址:192.168.1.2:12345 目的地址:192.168.1.1:80 Seq=1001 ACK=5001 Flags=[ACK]
    • 服务器B接收到这个确认。

TCP三次握手作用包括:

  1. 同步序列号:由于TCP是基于字节流的,各个数据包的顺序非常重要。三次握手可以同步连接双方的初始序列号,从而保证数据传输的顺序和完整性。
  2. 确认双方的接收和发送能力:三次握手确保了双方都能够接收和发送数据。首次握手确认了服务器的接收能力,第二次握手确认了服务器的发送能力以及客户端的接收能力,最后一次握手则确认了客户端的发送能力。
  3. 防止旧的连接请求造成混乱:网络中可能存在旧的连接请求数据包(因为延迟或重发),这些过时的数据包如果被接收,可能会造成错误的连接建立。三次握手通过确认和序列号机制,使得旧的连接请求不会被错误地接受。
  4. 资源分配:在三次握手过程中,TCP堆栈会分配缓冲区和变量以跟踪连接状态。通过这样的过程,只有在双方都确认连接请求之后,才会完成资源分配,避免了因为未完成连接而造成的资源浪费。

三次握手是对建立TCP连接过程的最小化要求,它可以有效地启动一个双向通信会话,同时避免了一些常见的网络通信问题。如果减少到两次握手,那么就无法解决上述的第三个问题,也就是说,如果网络中存在延迟的连接请求,那么这样的请求可能会导致不必要的连接建立,从而造成混乱。三次握手是在效率和可靠性之间的一个妥协。

四次挥手断开TCP连接的例子:

假设客户端A与服务器B之间建立了一个TCP连接,并且在此期间它们已经交换了一些数据。现在,客户端A决定关闭这个连接。以下是关闭过程的详细步骤和假设的序列号及确认号。

  1. 第一次挥手
    • 客户端A发送一个FIN标志的段给服务器B,指示A已经完成了数据的发送。
    • 假设客户端A之前发送的最后一个数据段的序列号是10000。
    • 客户端A的FIN段的序列号将会是10001(因为TCP协议规定FIN标志的段也消耗一个序列号)。
    • 如果服务器B之前发送给客户端A的最后一个数据段的序列号是5000,那么客户端A发送的ACK确认号将是5001,确认它已经收到了直到序列号5000的所有数据。
    • 段信息如下:cssCopy code源地址:客户端A 目的地址:服务器B Seq=10001 Ack=5001 Flags=[FIN, ACK]
  2. 第二次挥手
    • 服务器B收到客户端A的FIN段,发送一个ACK段回应,确认收到A的结束请求。
    • 服务器B的ACK段的确认号将是客户端A的序列号加1,也就是10002,因为它需要确认收到了包含FIN标志的那个序列号。
    • 此时,服务器B还没有准备好关闭连接或者可能仍有数据需要发送给客户端A。
    • 段信息如下:cssCopy code源地址:服务器B 目的地址:客户端A Seq=服务器B当前的序列号 Ack=10002 Flags=[ACK]
  3. 第三次挥手
    • 服务器B已完成数据的发送,并且准备关闭连接,发送一个FIN标志的段给客户端A。
    • 假设服务器B之前发送的最后一个数据段的序列号是8000。
    • 服务器B发送的FIN段的序列号将会是8001。
    • 服务器B在发送FIN之前也已经收到了客户端A之前的所有数据,所以ACK确认号将是客户端A之前的确认号,也就是10002。
    • 段信息如下:cssCopy code源地址:服务器B 目的地址:客户端A Seq=8001 Ack=10002 Flags=[FIN, ACK]
  4. 第四次挥手
    • 客户端A收到服务器B的FIN段,发送一个ACK段回应,确认收到B的结束请求。
    • 客户端A的ACK段的确认号将是服务器B的序列号加1,也就是8002。
    • 此时客户端A会等待足够的时间确保服务器B收到了这个ACK段,这个时间通常是该连接的往返时延的两倍。
    • 殅信息如下:cssCopy code源地址:客户端A 目的地址:服务器B Seq=10002 Ack=8002 Flags=[ACK]

TCP的四次挥手(Four-way Handshake)断开连接的过程比建立连接的三次握手(Three-way Handshake)要复杂,这主要是因为TCP连接是全双工的,意味着每个方向的通信是独立的。这里是为什么需要四次挥手的一些原因:

  1. 全双工通信:在TCP连接中,数据传输是全双工的,即数据可以在两个方向上同时进行。因此,每个方向都必须单独关闭,这就需要两个FIN标志和两个ACK标志。
  2. 确保数据完全传输:TCP连接的一方(比如客户端)发送FIN标志仅表示它没有更多的数据要发送,但是它仍然可以接收数据。服务器在收到这个FIN之后,可能仍然有数据要发送给客户端。服务器必须确保所有传出的数据都被客户端接收到,然后才能安全地发送它自己的FIN标志来关闭连接的另一个方向。
  3. 防止数据丢失:四次挥手允许每一方确认它们已经接收到对方发送的最后一个包。这意味着如果任何FIN或ACK在传输过程中丢失,连接不会立即关闭,从而允许重传丢失的包,确保连接的可靠终止。
  4. TIME_WAIT状态:客户端发送最后一个ACK后,它还需要等待足够的时间(称为2MSL,Maximum Segment Lifetime)以确保服务器收到了这个ACK。如果服务器没有收到ACK,它将重新发送FIN包。TIME_WAIT状态确保了旧的连接残留的数据包不会在新的连接中出现,从而避免可能的混淆。

总结来说,四次挥手的过程确保了连接的双方都能够完全完成数据传输,并且优雅地关闭连接,没有任何一方突然中断连接导致数据丢失。这也是为什么不能简化为三次或更少次数的原因,因为TCP设计的优先考虑是确保数据传输的可靠性和完整性。

笔记-Java针对字节码的优化

编译器生成字节码时的优化

编译器在生成字节码时可以进行一些优化,以提高代码的执行效率和性能。以下是一些常见的优化技术:

  1. 常量折叠:编译器可以在编译时将常量表达式计算出来,将其结果作为字节码中的常量。这样可以避免在运行时重复计算相同的表达式。
  2. 循环展开:编译器可以将循环展开,将循环体中的代码复制多次以减少循环的迭代次数。这样可以减少循环控制的开销,提高程序的执行速度。
  3. 变量内联:编译器可以将某些函数调用替换为直接使用函数体中的代码。这样可以避免函数调用的开销,减少程序的运行时间。
  4. 公共子表达式消除:编译器可以识别出相同的子表达式,并将其计算结果存储在临时变量中。这样可以避免重复计算相同的表达式,提高程序的执行效率。
  5. 死代码消除:编译器可以识别出不会被执行的代码,将其从生成的字节码中删除。这样可以减少字节码的大小,提高程序的执行效率。
  6. 消除冗余检查:消除不必要的安全检查、空指针检查等。

这些优化技术可以帮助编译器生成更高效的字节码,从而提高代码的执行效率和性能。编译器根据代码的特性和优化策略的选择,可以进行不同程度的优化。

JVM在运行时执行的一些关键优化:

即时编译(JIT)和适应性优化

  • 方法热点探测:JVM监控方法和循环的执行频率,将经常执行的代码(热点代码)识别出来进行优化。
  • 适应性优化:JVM能够根据程序的实际运行情况动态调整优化策略。
  • 多级优化:JVM可能会采用多级别的优化,对不同的代码应用不同程度的优化。

编译器优化

  • 方法内联:将方法调用替换为方法体本身,减少方法调用的开销。
  • 循环优化:包括循环展开、循环不变式外提等技术来减少循环开销。
  • 逃逸分析:分析对象是否在方法或线程外部被引用,如果没有“逃逸”,可能在栈上分配内存,避免堆分配。
  • 消除冗余检查:消除不必要的安全检查、空指针检查等。
  • 公共子表达式消除:如果同一个表达式被多次计算,并且每次计算的结果都相同,JIT编译器会尝试只计算一次。

垃圾回收(GC)优化

  • 分代收集:根据对象的生命周期将堆内存分成几块,如年轻代、老年代等,以不同方式进行垃圾回收。
  • 垃圾回收算法:采用多种算法,如标记-清除、标记-整理、复制算法等,优化GC性能和响应时间。
  • 并发和并行垃圾回收:在多核处理器上并行进行垃圾回收,以及在应用线程运行的同时并发执行垃圾回收。

锁和同步优化

  • 轻量级锁:当锁未竞争时,使用更高效的算法来减少线程阻塞。
  • 锁消除:编译器识别并去除不需要的同步。
  • 锁粗化:如果在一系列操作中多次获取和释放同一个锁,则可能将锁的范围扩展到整个操作序列,减少锁操作的开销。
  • 偏向锁:在单线程访问的情况下降低锁的竞争开销。

运行时性能监控与反馈

  • 动态编译器反馈:JIT编译器可以根据代码的执行情况收集信息,并利用这些信息进行更好的优化。
  • 反优化:在一些情况下,之前做出的优化可能不再有效,JVM可以将已经编译的代码恢复到未优化的状态。

JVM的优化是一个持续的过程,随着程序的运行,JVM会逐渐学习并应用越来越多的优化策略,以期达到最优性能。

笔记-JAVA 基本数据类型和对应封装类思想


Java中的基本数据类型和它们的封装类(有时被称作包装类)之间存在一些关键的不同点,这些设计主要是为了解决基本数据类型在面向对象编程中的局限性。

基本数据类型:

  1. 性能:基本数据类型(如int, double, char等)通常会有更好的性能,因为它们存储的是值,且存储在栈上。
  2. 存储:基本类型直接存储数据,它们不是对象,不具备对象的特性。
  3. 默认值:基本类型有默认值,例如int的默认值为0,boolean的默认值为false,无需显式初始化。
  4. 栈内存:基本类型的变量通常存储在栈内存上,这使得访问速度非常快。

封装类:

  1. 对象特性:封装类(如Integer, Double, Character等)是类,它们包装了基本类型的值并提供了对象的特性。
  2. 堆内存:封装类的实例存储在堆内存中,它们是引用类型。
  3. null值:封装类可以赋值为null,表示这个引用不指向任何对象,而基本数据类型不能赋值为null
  4. 工具方法:封装类提供了一些有用的工具方法,例如转换、比较、解析字符串为相应的类型等。
  5. 集合框架:Java的集合框架(如List, Set, Map等)只能存储对象,不能存储基本类型。这就是封装类存在的主要原因之一。
  6. 泛型:Java的泛型只支持引用类型,不支持基本数据类型。因此,需要使用封装类来使用泛型。
  7. 类型系统的一致性:将基本类型封装为类,让Java的类型系统更加统一,可以将基本类型当做对象处理。

设计原因:

  1. 性能与对象抽象:基本数据类型提供了性能优势,封装类提供了面向对象的抽象。这样的设计允许Java在保持较高性能的同时,又能利用面向对象的特性。
  2. 兼容集合框架:为了能在集合框架中使用基本类型的值,需要一个可以作为Object子类的类型,这就是封装类的作用。
  3. 泛型支持:泛型的引入使得类型安全的集合成为可能,封装类使得基本类型可以在这样的集合中使用。
  4. 自动装箱与拆箱:Java 5引入了自动装箱和拆箱,这使得基本类型和封装类之间的转换自动化,简化了编程。

封装类和基本数据类型之间的转换是自动的,这个特性被称为自动装箱和拆箱。例如,当你把一个int赋值给一个Integer对象时,自动装箱就发生了;反过来,当你把一个Integer赋值给一个int时,就会发生自动拆箱。这种设计让开发者能够在面向对象编程和性能之间找到平衡点。

使用基本数据类型场景:

  1. 性能敏感:如果你正在编写性能敏感的代码,比如在大规模数据处理、科学计算或者实时系统中,基本类型更快,因为它们避免了对象封装的开销。
  2. 默认值:当你需要确保类型总是有一个默认值,比如int默认是0,而不是null时。
  3. 数组处理:处理大量的数值数据时使用基本类型数组(如int[]),这样做可以减少内存消耗并提高处理速度。

使用封装类场景:

  1. 需要对象:当你需要将数字作为对象传递,例如在使用集合框架类ArrayListHashMap时,只能存储对象。
  2. 允许null:如果你需要表示一个变量可能没有值(即null),或者需要在某些业务逻辑中区分0null
  3. 泛型代码:Java的泛型不支持基本数据类型,所以在泛型代码中,例如ArrayList<Integer>,必须使用封装类。
  4. 使用对象方法:封装类提供了多种方法,比如将数字转换为字符串,或者与其他数字类型进行转换等。
  5. 系统编程或API调用:有些系统编程接口或API方法需要对象类型的参数,这时必须使用封装类。
  6. 序列化:只有对象才能被序列化,这样才能通过网络发送或写入文件等。

笔记-UTF-8、UTF-16编码

UTF-8编码方式:

UTF-8使用一至四个字节为每个字符编码,编码规则如下:

  1. 单字节的字符:对于标准的ASCII字符(U+0000至U+007F),UTF-8和ASCII编码是相同的,使用单个字节,其最高位始终为0。
  2. 多字节的字符:对于其他范围内的字符,则使用两个到四个字节表示:
    • 两字节字符:第一个字节的前三位为110,后面五位用于数据;第二个字节的前两位为10,后面六位用于数据。
    • 三字节字符:第一个字节的前四位为1110,后面四位用于数据;随后的字节的前两位为10,后面六位用于数据。
    • 四字节字符:第一个字节的前五位为11110,后面三位用于数据;随后的字节的前两位为10,后面六位用于数据。

每个字节中,以10开头的字节为后续字节,它们不会独立出现,只会跟随一个高位字节。这种编码方法的一个优势是通过查看任意字节,都能够知道它是单独的字符还是某个字符的一部分,因为UTF-8字节序列是顺序无关的,所以不像UTF-16和UTF-32,UTF-8不需要字节顺序标记(BOM)来指示字节的顺序

UTF-16编码方式:

UTF-16(16-bit Unicode Transformation Format)使用单个16位的代码单元来编码U+0000至U+FFFF之间的字符,这部分包括了基本的多语言平面(BMP)。对于更高位的字符,则使用一对16位的代码单元,即32位来编码,这种对称称为代理对(Surrogate Pair)。

  • BMP字符:使用一个16位的代码单元,直接对应于字符的Unicode码点。
  • 代理对:对于超出BMP的字符,使用两个16位的代码单元。第一个代码单元(高代理项)的范围是U+D800至U+DBFF,第二个代码单元(低代理项)的范围是U+DC00至U+DFFF。

UTF-8与UTF-16的区别:

  1. 字节顺序:UTF-8是字节顺序无关的,而UTF-16需要区分字节顺序,因此有UTF-16LE和UTF-16BE两种格式,分别代表小端序和大端序,有时还有带字节顺序标记(BOM)的格式。
  2. 空间效率:对于ASCII字符,UTF-8更为高效,只需要一个字节。而UTF-16不论什么字符至少需要两个字节。但对于很多亚洲语言,UTF-16可能更加空间高效,因为这些语言中的字符在UTF-8中可能需要三个或四个字节。
  3. 编码复杂性:UTF-8是可变长度的,从1到4个字节不等;UTF-16也是可变长度的,但主要是两个长度,要么是2个字节,要么是4个字节(代理对)。
  4. 兼容性:UTF-8编码兼容ASCII,因此被广泛用于网络和文件存储,特别是在主要交流用的是ASCII字符的系统中。UTF-16常用于需要处理大量非BMP字符的应用中,比如某些文本处理软件。

笔记-字节顺序标记

字节顺序标记(Byte Order Mark,简称 BOM)是一个用于指示字节序(Byte Order)的特殊字符,具体是 Unicode 字符 U+FEFF。字节序问题主要存在于那些使用多于一个字节表示每个字符的编码方案中,比如 UTF-16 和 UTF-32。字节序是指在这些字节中哪一个字节先存储,这在不同架构的系统中可能不同,通常分为大端序(Big-Endian)和小端序(Little-Endian):

  • 大端序(Big-Endian):高位字节(more significant byte)在前,低位字节(less significant byte)在后。例如,Unicode 字符 U+1234 在大端序中存储为 12 34
  • 小端序(Little-Endian):低位字节在前,高位字节在后。同样的 U+1234 字符在小端序中存储为 34 12

由于大端序和小端序的存在,使用如 UTF-16 和 UTF-32 等编码的文本文件可能需要一个 BOM 来明确字节序。例如,如果一个文本文件以 FE FF 开头,则表明该文件采用大端序的 UTF-16 编码;如果以 FF FE 开头,则表明采用小端序。

然而,UTF-8 不受字节序问题的影响,因为它使用单个字节序列来表示每个字符,不涉及字节间的顺序问题。UTF-8 编码中的每个字节是明确的,不会根据存储顺序而改变含义。因此,UTF-8 编码不需要 BOM 来指示字节序。

在实践中,虽然 UTF-8 本身不需要 BOM,但有时会在 UTF-8 编码的文件开始处看到一个 BOM(即 EF BB BF)。这是为了与那些需要 BOM 的编码方式兼容,或者用来明确指明文件是以 UTF-8 编码的。但在很多情况下,UTF-8 BOM 是不必要的,甚至可能导致问题,比如在不期望 BOM 的上下文中解析文本时。因此,是否使用 UTF-8 BOM 往往取决于具体的应用需求和上下文。


UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode字符集的可变长度字符编码系统。它可以用来表示Unicode标准中的任何字符,并且其设计旨在兼容传统的ASCII编码。

网络中会存在大端系统和小端系统?

大端(Big-Endian)系统和小端(Little-Endian)系统的存在主要是由于不同的硬件架构和设计哲学所导致的。当计算机科学还处于起步阶段时,不同的系统设计者对于如何在内存中存储数据有不同的看法。这些看法转化成了两种主要的数据表示方法,即大端序和小端序,它们定义了多字节数据在内存中字节的顺序。

大端序(Big-Endian)

在大端序中,最高有效字节(MSB)存储在最低的内存地址上,这也意味着数字的第一个字节是最重要的字节。如果我们读取一个地址,我们首先得到的是数字的最高有效位。这种方式直观地反映了我们通常书写数字的方式,例如数值0x12345678在内存中的存储顺序将是 12 34 56 78

大端序的优点:

  • 读取数据的高位非常直接,特别是对于网络协议而言,因为协议字段经常是从高位开始定义的。
  • 更符合人类阅读数字的习惯。

小端序(Little-Endian)

而在小端序中,最低有效字节(LSB)存储在最低的内存地址上。数值0x12345678在小端序的系统中将被存储为 78 56 34 12。这种方式在内存地址增长的方向上按照数字的权重递增。

小端序的优点:

  • 在进行数值计算时,低位在低地址,有时可以简化数值增长和扩展的处理。
  • 对于可以变长的数据表示,添加新的字节不会改变现有字节的地址。

为什么同时存在

不同的处理器架构在设计时会选择适合自己的一种模式。例如,Intel的x86架构就是小端序,而许多网络协议和早期的RISC架构如SPARC则是大端序。这些选择往往是基于硬件设计时的考虑和历史遗留问题。

随着时间的推移,两种系统都已经被广泛应用,而且各有其优势和特定的使用场景。在多数现代架构中,如x86-64和许多ARM架构,支持在两种模式之间切换,给操作系统和应用程序提供了更大的灵活性。然而,由于这两种不同的存储方式在互操作性上可能导致问题,因此在网络协议设计中通常会选择使用网络字节顺序(大端序),以确保不同系统之间数据传输的一致性。

最低有效字节是什么意思?

最低有效字节(Least Significant Byte,简称 LSB)指的是在多字节数字中数值最小的那个字节。在数字表示中,最低有效字节代表着数值中的最低位(通常是最右边的数字在十进制表示中,或是最低的二进制位)。换句话说,对于数字的修改,最低有效字节中的任何改变将对整个数值产生最小的影响。

例如,对于一个32位的整数0x12345678(在十六进制表示中),其字节从最低位到最高位依次为:

  • 0x78(十六进制的120,二进制的01111000)是最低有效字节。
  • 0x56(十六进制的86)是第二字节。
  • 0x34(十六进制的52)是第三字节。
  • 0x12(十六进制的18)是最高有效字节(Most Significant Byte,简称 MSB)。

在小端序存储中,0x78作为最低有效字节会被放置在最低的地址。这意味着如果我们在小端序系统中查看地址从低到高的数据,我们会首先看到78

在计算机内存和数据处理中,了解哪个字节是最低有效字节非常重要,特别是在执行二进制算术运算时。对于小端序系统,最低有效字节的处理通常是首先进行的,尤其在涉及数值增长或者运算进位时。

SQL-CTE递归查询子ID

场景1: 查询某个节点的所有子孙id

idparent_idname
1NULLRoot
21Child_A
32Child_B
父子id结构
WITH RECURSIVE descendants AS (
    -- 初始条件
    SELECT id, parent_id, name
    FROM my_table
    WHERE id = 1
    UNION ALL
    -- 递归条件
    SELECT t.id, t.parent_id, t.name
    FROM my_table t
    JOIN descendants d ON t.parent_id = d.id
)
SELECT id, name FROM descendants WHERE id <> 1;

场景2: 查询某个节点的所有子孙id,构建children数组结构JSON

用sql来说似乎没法实现

Postgres 分布式拓展 citus 初探

citus是什么

citus是postgres的分布式拓展,它能够以非常简单的配置实现分布式的postgres数据库并实扩容,还能够将SQL进行并行化处理,充分利用多台机器的资源,使用于大量数据的实时查询场景。

citus的基本概念

节点:Coordinator Node(协调节点)、Worker Node(工作节点)

表:Distributed Table(分布式表)、 Reference Table(引用表) 、Local Tables (本地表)

citus的节点分为工作节点和协调节点。

协调节点是我们连接使用的主节点,它的主要职责是连接所有的工作节点分发和协调数据,不会保存分布式表的数据。

工作节点是实际保存数据和执行SQL的主体,协调节点会将SQL处理后分发到对应的节点执行任务。

citus的表有三种类型:分布式表、引用表、本地表。

分布式表,顾名思义就是实现分布式存储的表,它的数据是根据实现创建分布式表时指定的字段作为key进行哈希,水平分布在所有的数据节点中,是citus最常用的分布式表。

引用表也是分布式表的一种,它的数据完整会被保存所有的工作节点中,所有工作节点都拥有该表的全部数据。

本地表不是分布式表,它是存储在协调节点上面的表,和普通postgres数据库的表一样。

实战体验

准备一台主节点、若干工作节点

我这边正好有四台Hosthatch虚拟机,都重装成debian11用来搭建实战。

协调节点 192.168.10.202

工作节点1 192.168.10.101

工作节点2 192.168.10.102

工作节点3 192.168.10.103

先安装postgres和citus拓展(所有节点都要执行下面的步骤)

apt update &amp;&amp; apt install -y sudo gnupg2

#添加citus源
curl https://install.citusdata.com/community/deb.sh | sudo bash
#添加postgresql源,我们要安装最新的postgresql-15
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add -

#更新源
sudo apt-get update

#安装postgresql-15、以及对应版本的citus拓展
sudo apt-get -y install postgresql-15 postgresql-15-citus-11.1

修改postgresql监听IP、添加四台节点互相信任(所有节点都要执行)

# 预加载citus扩展
sudo pg_conftool 15 main set shared_preload_libraries citus
# 修改监听IP,这里我直接改了 *,如果是暴露在公网的建议只监听内网ip
sudo pg_conftool 15 main set listen_addresses '*'
# 修改添加四台节点互信
sudo vim /etc/postgresql/15/main/pg_hba.conf
#在最底下追加下面内容
host    all             all              192.168.10.202/24                trust
host    all             all              192.168.10.101/24                trust
host    all             all              192.168.10.102/24                trust
host    all             all              192.168.10.103/24                trust

#重启 
systemctl restart postgresql

完成了上面的步骤后,我们开始选择一个数据库添加citus拓展(所有节点都要执行)

# 我这边直接选择的postgres,如果要使用不同的库,所有节点都需要有该库存在
sudo -i -u postgres psql -c "CREATE EXTENSION citus;"

接下来在协调节点中执行相关函数,设置协调节点和添加工作节点(只在协调节点运行)

#设置协调节点 IP端口根据实际情况来
sudo -i -u postgres psql -c "SELECT citus_set_coordinator_host('192.168.10.202', 5432);"
#添加工作节点
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.101', 5432);"
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.102', 5432);"
sudo -i -u postgres psql -c "SELECT * from citus_add_node('192.168.10.103', 5432);"

#查看活跃的工作节点
sudo -i -u postgres psql -c "SELECT * FROM citus_get_active_worker_nodes();"

到此为止,我们前置配置相关的工作已经完成了,接下来就是创建分布式表并感受分布式的快乐了,比如下面的流程

#后续都是在协调节点执行
#创建表结构
CREATE TABLE IF NOT EXISTS key (
                                   id uuid,
                                   word varchar NULL,
                                   url varchar NULL,
                                   description varchar null,
                                   PRIMARY KEY (id)
);
#调用citus提供的函数创建分布式表 第一个参数是表名 第二个参数是依据该字段进行分片,将数据分配到所有的工作节点
SELECT create_distributed_table('key', 'id');

#这样就建立好了一个分布式表,使用它也是让程序直接连接协调节点,就像平常连接一个数据库那样直接使用即可

Linux AMD APU 省电降温指南

安装软件

RyzenAdj 命令行控制AMD APU工具

Ryzen Controller 封装RyzenAdj的GUI工具(简单好用,而且支持中文)

同类型的封装RyzenAdj的GUI工具 还有RyzenAdjCtl它可以做到更加细粒度的控制这类工具主要是针对移动端的AMDAPU,对于桌面端的支持不是太好。

博主使用的是桌面端的5600G,测试时发现RyzenAdj只能控制CPU功耗,设置GPU频率不生效。

针对iGPU,可以使用这个工具Radeon Profile,它是Linux下AMD显卡的专用超频工具,虽然它没法对我们的核显超频,但是可以在首页控制核显运行频率来大幅度减少功耗。

RyzenAdj效果

在默认设置时,随便播放一个1080P视频,功耗在50W上下

手动设置功耗墙到10W

播放同样的1080P视频,功耗和温度显著降低,这时候打开任务管理器可以发现CPU占用提高很多,但丝毫不影响视频播放的性能。

Radeon Profile效果

功耗计实测

在没置功耗墙之前,整机待机功耗73W左右(7块硬盘+pcie网卡+pcie转sata卡+4根16G 3200内存),突然负载时功耗能飙到140W,满载功耗也在130W。设置功耗墙为10W后,整机功耗最多也就84W,这时候性能确实拉了,但是日常使用并用作NAS已经是绰绰有余了,非常满意!