使用 vscode 开发 Rust
本文将介绍如果基于 vscode 搭建 Rust 开发环境。
Rust Struct 字段自引用问题
先来看一段 Java 代码,Application中有version和 logger。logger 依赖了 Version。
1 | public class Application |
那么问题来了,如何在 Rust 中实现相同的代码?
Rust-生命周期
生命周期,简而言之就是引用的有效作用域。在大多数时候,我们无需手动的声明生命周期,因为编译器可以自动进行推导。当多个生命周期存在,且编译器无法推导出某个引用的生命周期时,就需要我们手动标明生命周期。
悬垂指针和生命周期
生命周期的主要作用是避免悬垂引用,它会导致程序引用了本不该引用的数据:
1 | { |
此处 r 就是一个悬垂指针,它引用了提前被释放的变量 x。
借用检查
为了保证 Rust 的所有权和借用的正确性,Rust 使用了一个借用检查器(Borrow checker),来检查我们程序的借用正确性:
1 | { |
r 变量被赋予了生命周期 'a,x 被赋予了生命周期 'b,从图示上可以明显看出生命周期 'b 比 'a 小很多。在编译期,Rust 会比较两个变量的生命周期,结果发现 r 明明拥有生命周期 'a,但是却引用了一个小得多的生命周期 'b,在这种情况下,编译器会认为我们的程序存在风险,因此拒绝运行。如果想要编译通过,也很简单,只要 'b 比 'a 大就好。总之,x 变量只要比 r 活得久,那么 r 就能随意引用 x 且不会存在危险:
1 | { |
TCP-超时重传
TCP协议是一种面向连接的有状态网络协议。对于发送的每个数据包,一旦TCP堆栈收到特定数据包的ACK,它就认为它已成功传递。
TCP使用指数退避超时重传一个未确认的数据包,最多tcp_retries2时间(默认为15),每次重传超时在TCP_RTO_MIN(200毫秒)和TCP_RTO_MAX(120秒)之间。一旦第15次重试到期(默认情况下),TCP堆栈将通知上面的层(即应用程序)断开连接。
tcp_retries2可以通过修改文件
/proc/sys/net/ipv4/tcp_retries2或sysctl net.ipv4.tcp_retries2进行调优。
TCP_RTO_MIN和TCP_RTO_MAX的值在Linux内核中硬编码,并由以下常量定义:
1 | #define TCP_RTO_MAX ((unsigned)(120*HZ)) |
Linux2.6+使用1000毫秒的HZ,所以TCP_RTO_MIN是200毫秒,TCP_RTO_MAX是120秒。给定tcp_retries设置为15的默认值,这意味着在将断开的网络链路通知给上层(即应用程序)之前需要924.6秒,因为在最后一次(第15次)重试到期时检测到连接断开。
| Retransmission | RTO(ms) | Time before a timeout(sec) | Time before a timeout(min) |
|---|---|---|---|
| 1 | 200 | 0.2 secs | 0.0 mins |
| 2 | 400 | 0.6 secs | 0.0 mins |
| 3 | 800 | 1.4 secs | 0.0 mins |
| 4 | 1600 | 3.0secs | 0.1 mins |
| 5 | 3200 | 6.2 secs | 0.1 mins |
| 6 | 6400 | 12.6 secs | 0.2 mins |
| 7 | 12800 | 25.4secs | 0.4 mins |
| 8 | 25600 | 51.0 secs | 0.9 mins |
| 9 | 51200 | 102.2 secs | 1.7 mins |
| 10 | 102400 | 204.6 secs | 3.4 mins |
| 11 | 120000 | 324.6 secs | 5.4 mins |
| 12 | 120000 | 444.6 secs | 7.4 mins |
| 13 | 120000 | 564.6 secs | 9.4mins |
| 14 | 120000 | 684.6 secs | 11.4 mins |
| 15 | 120000 | 804.6 secs | 13.4 mins |
| 16 | 120000 | 924.6 secs | 15.4 mins |
真正起到限制重传次数的并不是真正的重传次数。而是以tcp_retries2为boundary,以rto_base(如TCP_RTO_MIN 200ms)为初始RTO,计算得到一个timeout值出来。如果重传间隔超过这个timeout,则认为超过了阈值。实际TCP 的 RTO 是动态计算[1]的,也就是说:
- 如果RTT比较小,那么RTO初始值就约等于下限200ms。由于timeout总时长是924600ms,表现出来的现象刚好就是重传了15次,超过了timeout值,从而放弃TCP流
- 如果RTT较大,比如RTO初始值计算得到的是1000ms。那么根本不需要重传15次,重传总间隔就会超过924600ms。例如一个RTT=400ms的情况,当tcp_retries2=10时,仅重传了3次就放弃了TCP流
由于tcp_retries2是个系统级参数,在实际使用中,可以针对应用修改TCP_USER_TIMEOUT[2]。
建议公式为(开启了 KeepAlive 的情况):
1 | TCP_USER_TIMEOUT >= TCP_KEEPIDLE + TCP_KEEPINTVL * TCP_KEEPCNT |
TCP_USER_TIMEOUT 是RFC 54288 规定的 TCP option,用来扩展 TCP RFC 7939 协议中本身的 “User Timeout” 参数(原协议不允许配置参数大小)。其用来控制已经发送,但是尚未被 ACK的数据包的存活时间,超过这个时间则会强制关闭连接。
系统性能分析-CPU耗时定位
上篇博客介绍了 CPU异常分析的工具和方法。本文将介绍如何定位 CPU 耗时。
Rust-特征
如果不同的类型具有相同的行为,那么我们就可以定义一个特征,然后为这些类型实现该特征。定义特征是把一些方法组合在一起,目的是定义一个实现某些目标所必需的行为的集合。
例如,我们现在有文章 Post 和微博 Weibo 两种内容载体,而我们想对相应的内容进行总结,也就是无论是文章内容,还是微博内容,都可以在某个时间点进行总结,那么总结这个行为就是共享的,因此可以用特征来定义:
1 | pub trait Summary { |
特征只定义行为看起来是什么样的,而不定义行为具体是怎么样的。因此我们需要为实现特征的类型,定义行为具体是怎么样的。
1 | pub struct Post { |
Rust-智能指针-Box
指针是一个包含了内存地址的变量,该内存地址引用或者指向了另外的数据。
在 Rust 中,最常见的指针类型是引用,引用通过 & 符号表示。不同于其它语言,引用在 Rust 中被赋予了更深层次的含义,那就是:借用其它变量的值。引用本身很简单,除了指向某个值外并没有其它的功能,也不会造成性能上的额外损耗,因此是 Rust 中使用最多的指针类型。
而智能指针则不然,它虽然也号称指针,但是它是一个复杂的家伙:通过比引用更复杂的数据结构,包含比引用更多的信息,例如元数据,当前长度,最大可用长度等。智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 Deref 和 Drop 特征:
- Deref 可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 *T
- Drop 允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
而 Box 指针是最简单的智能指针。本文将介绍 Box 指针以及 Deref 和 Drop 特征。
Java 操作HDFS小结
本文总结了如何使用 Java 客户端操作 HDFS,以及一些注意点。
Rust 所有权
Rust 为了解决内存安全问题,引入了所有权系统。
所有的程序都必须和计算机内存打交道,如何从内存中申请空间来存放程序的运行内容,如何在不需要的时候释放这些空间,成了重中之重,也是所有编程语言设计的难点之一。在计算机语言不断演变过程中,出现了三种流派:
- 垃圾回收机制(GC),在程序运行时不断寻找不再使用的内存,典型代表:Java、Go
- 手动管理内存的分配和释放, 在程序中,通过函数调用的方式来申请和释放内存,典型代表:C++
- 通过所有权来管理内存,编译器在编译时会根据一系列规则进行检查。
Rust 选择了第三种,最妙的是,这种检查只发生在编译期,因此对于程序运行期,不会有任何性能上的损失。