扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
rust是可以开发分布式系统的。
成都创新互联服务项目包括顺庆网站建设、顺庆网站制作、顺庆网页制作以及顺庆网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,顺庆网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到顺庆省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!
引子
构建一个分布式系统 并不是一件容易的事情,我们需要考虑很多的问题,首先就是我们的系统到底需要提供什么样的功能,譬如:
一致性:我们是否需要保证整个系统的线性一致性,还是能容忍短时间的数据不一致,只支持最终一致性。
稳定性:我们能否保证系统 7 x 24 小时稳定运行。系统的可用性是 4 个 9,还有 5 个 9?如果出现了机器损坏等灾难情况,系统能否做的自动恢复。
扩展性:当数据持续增多,能否通过添加机器就自动做到数据再次平衡,并且不影响外部服务。
分布式事务:是否需要提供分布式事务支持,事务隔离等级需要支持到什么程度。
上面的问题在系统设计之初,就需要考虑好,作为整个系统的设计目标。为了实现这些特性,我们就需要考虑到底采用哪一种实现方案,取舍各个方面的利弊等。
后面,我将以我们开发的分布式 Key-Value TiKV 作为实际例子,来说明下我们是如何取舍并实现的。
TiKV
TiKV 是一个分布式 Key-Value store,它使用 Rust 开发,采用 Raft 一致性协议保证数据的强一致性,以及稳定性,同时通过 Raft 的 Configuration Change 机制实现了系统的可扩展性。
TiKV 提供了基本的 KV API 支持,也就是通常的 Get,Set,Delete,Scan 这样的 API。TiKV 也提供了支持 ACID 事务的 Transaction API,我们可以使用 Begin 开启一个事务,在事务里面对 Key 进行操作,最后再用 Commit 提交一个事务,TiKV 支持 SI 以及 SSI 事务隔离级别,用来满足用户的不同业务场景。
Rust
在规划好 TiKV 的特性之后,我们就要开始进行 TiKV 的开发。这时候,我们面临的第一个问题就是采用什么样的语言进行开发。当时,摆在我们眼前的有几个选择:
Go,Go 是我们团队最擅长的一门语言,而且 Go 提供的 goroutine,channel 这些机制,天生的适合大规模分布式系统的开发,但灵活方便的同时也有一些甜蜜的负担,首先就是 GC,虽然现在 Go 的 GC 越来越完善,但总归会有短暂的卡顿,另外 goroutine 的调度也会有切换开销,这些都可能会造成请求的延迟增高。
Java,现在世面上面有太多基于 Java 做的分布式系统了,但 Java 一样有 GC 等开销问题,同时我们团队在 Java 上面没有任何开发经验,所以没有采用。
C++,C++ 可以认为是开发高性能系统的代名词,但我们团队没有特别多的同学能熟练掌握 C++,所以开发大型 C++ 项目并不是一件非常容易的事情。虽然使用现代 C++ 的编程方式能大量减少 data race,dangling pointer 等风险,我们仍然可能犯错。
当我们排除了上面几种主流语言之后,我们发现,为了开发 TiKV,我们需要这门语言具有如下特性:
静态语言,这样才能最大限度的保证运行性能。
无 GC,完全手动控制内存。
Memory safe,尽量避免 dangling pointer,memory leak 等问题。
Thread safe,不会遇到 data race 等问题。
包管理,我们可以非常方便的使用第三方库。
高效的 C 绑定,因为我们还可能使用一些 C library,所以跟 C 交互不能有开销。
综上,我们决定使用 Rust,Rust 是一门系统编程语言,它提供了我们上面想要的语言特性,但选择 Rust 对我们来说也是很有风险的,主要有两点:
我们团队没有任何 Rust 开发经验,全部都需要花时间学习 Rust,而偏偏 Rust 有一个非常陡峭的学习曲线。
基础网络库的缺失,虽然那个时候 Rust 已经出了 1.0,但我们发现很多基础库都没有,譬如在网络库上面只有 mio,没有好用的 RPC 框架,HTTP 也不成熟。
但我们还是决定使用 Rust,对于第一点,我们团队花了将近一个月的时间来学习 Rust,跟 Rust 编译器作斗争,而对于第二点,我们就完全开始自己写。
幸运的,当我们越过 Rust 那段阵痛期之后,发现用 Rust 开发 TiKV 异常的高效,这也就是为啥我们能在短时间开发出 TiKV 并在生产环境中上线的原因。
一致性协议
对于分布式系统来说,CAP 是一个不得不考虑的问题,因为 P 也就是 Partition Tolerance 是一定存在的,所以我们就要考虑到底是选择 C - Consistency 还是 A - Availability。
我们在设计 TiKV 的时候就决定 - 完全保证数据安全性,所以自然就会选择 C,但其实我们并没有完全放弃 A,因为多数时候,毕竟断网,机器停电不会特别频繁,我们只需要保证 HA - High Availability,也就是 4 个 9 或者 5 个 9 的可用性就可以了。
既然选择了 C,我们下一个就考虑的是选用哪一种分布式一致性算法,现在流行的无非就是 Paxos 或者 Raft,而 Raft 因为简单,容易理解,以及有很多现成的开源库可以参考,自然就成了我们的首要选择。
在 Raft 的实现上,我们直接参考的 etcd 的 Raft。etcd 已经被大量的公司在生产环境中使用,所以它的 Raft 库质量是很有保障的。虽然 etcd 是用 Go 实现的,但它的 Raft library 是类似 C 的实现,所以非常便于我们用 Rust 直接翻译。在翻译的过程中,我们也给 etcd 的 Raft fix 了一些 bug,添加了一些功能,让其变得更加健壮和易用。
现在 Raft 的代码仍然在 TiKV 工程里面,但我们很快会将独立出去,变成独立的 library,这样大家就能在自己的 Rust 项目中使用 Raft 了。
使用 Raft 不光能保证数据的一致性,也可以借助 Raft 的 Configuration Change 机制实现系统的水平扩展,这个我们会在后面的文章中详细的说明。
存储引擎
选择了分布式一致性协议,下一个就要考虑数据存储的问题了。在 TiKV 里面,我们会存储 Raft log,然后也会将 Raft log 里面实际的客户请求应用到状态机里面。
首先来看状态机,因为它会存放用户的实际数据,而这些数据完全可能是随机的 key - value,为了高效的处理随机的数据插入,自然我们就考虑使用现在通用的 LSM Tree 模型。而在这种模型下,RocksDB 可以认为是现阶段最优的一个选择。
RocksDB 是 Facebook 团队在 LevelDB 的基础上面做的高性能 Key-Value Storage,它提供了很多配置选项,能让大家根据不同的硬件环境去调优。这里有一个梗,说的是因为 RocksDB 配置太多,以至于连 RocksDB team 的同学都不清楚所有配置的意义。
关于我们在 TiKV 中如何使用,优化 RocksDB,以及给 RocksDB 添加功能,fix bug 这些,我们会在后面文章中详细说明。
而对于 Raft Log,因为任意 Log 的 index 是完全单调递增的,譬如 Log 1,那么下一个 Log 一定是 Log 2,所以 Log 的插入可以认为是顺序插入。这种的,最通常的做法就是自己写一个 Segment File,但现在我们仍然使用的是 RocksDB,因为 RocksDB 对于顺序写入也有非常高的性能,也能满足我们的需求。但我们不排除后面使用自己的引擎。
因为 RocksDB 提供了 C API,所以可以直接在 Rust 里面使用,大家也可以在自己的 Rust 项目里面通过 rust-rocksdb 这个库来使用 RocksDB。
分布式事务
要支持分布式事务,首先要解决的就是分布式系统时间的问题,也就是我们用什么来标识不同事务的顺序。通常有几种做法:
TrueTime,TrueTime 是 Google Spanner 使用的方式,不过它需要硬件 GPS + 原子钟支持,而且 Spanner 并没有在论文里面详细说明硬件环境是如何搭建的,外面要自己实现难度比较大。
HLC,HLC 是一种混合逻辑时钟,它使用 Physical Time 和 Logical Clock 来确定事件的先后顺序,HLC 已经在一些应用中使用,但 HLC 依赖 NTP,如果 NTP 精度误差比较大,很可能会影响 commit wait time。
TSO,TSO 是一个全局授时器,它直接使用一个单点服务来分配时间。TSO 的方式很简单,但会有单点故障问题,单点也可能会有性能问题。
TiKV 采用了 TSO 的方式进行全局授时,主要是为了简单。至于单点故障问题,我们通过 Raft 做到了自动 fallover 处理。而对于单点性能问题,TiKV 主要针对的是 PB 以及 PB 以下级别的中小规模集群,所以在性能上面只要能保证每秒百万级别的时间分配就可以了,而网络延迟上面,TiKV 并没有全球跨 IDC 的需求,在单 IDC 或者同城 IDC 情况下,网络速度都很快,即使是异地 IDC,也因为有专线不会有太大的延迟。
解决了时间问题,下一个问题就是我们采用何种的分布式事务算法,最通常的就是使用 2 PC,但通常的 2 PC 算法在一些极端情况下面会有问题,所以业界要不通过 Paxos,要不就是使用 3 PC 等算法。在这里,TiKV 参考 Percolator,使用了另一种增强版的 2 PC 算法。
这里先简单介绍下 Percolator 的分布式事务算法,Percolator 使用了乐观锁,也就是会先缓存事务要修改的数据,然后在 Commit 提交的时候,对要更改的数据进行加锁处理,然后再更新。采用乐观锁的好处在于对于很多场景能提高整个系统的并发处理能力,但在冲突严重的情况下反而没有悲观锁高效。
对于要修改的一行数据,Percolator 会有三个字段与之对应,Lock,Write 和 Data:
Lock,就是要修改数据的实际 lock,在一个 Percolator 事务里面,有一个 primary key,还有其它 secondary keys, 只有 primary key 先加锁成功,我们才会再去尝试加锁后续的 secondary keys。
Write,保存的是数据实际提交写入的 commit timestamp,当一个事务提交成功之后,我们就会将对应的修改行的 commit timestamp 写入到 Write 上面。
Data,保存实际行的数据。
当事务开始的时候,我们会首先得到一个 start timestamp,然后再去获取要修改行的数据,在 Get 的时候,如果这行数据上面已经有 Lock 了,那么就可能终止当前事务,或者尝试清理 Lock。
当我们要提交事务的时候,先得到 commit timestamp,会有两个阶段:
Prewrite:先尝试给 primary key 加锁,然后尝试给 second keys 加锁。如果对应 key 上面已经有 Lock,或者在 start timestamp 之后,Write 上面已经有新的写入,Prewrite 就会失败,我们就会终止这次事务。在加锁的时候,我们也会顺带将数据写入到 Data 上面。
Commit:当所有涉及的数据都加锁成功之后,我们就可以提交 primay key,这时候会先判断之前加的 Lock 是否还在,如果还在,则删掉 Lock,将 commit timestamp 写入到 Write。当 primary key 提交成功之后,我们就可以异步提交 second keys,我们不用在乎 primary keys 是否能提交成功,即使失败了,也有机制能保证数据被正常提交。
大数据成为主流业务,而R语言显然是处理大数据的最佳工具。
R语言三个优势原因
支持使用缺省值作为核心数据值,因而可以很容易地处理不完整的数据源,而不完整的数据源才是现实中最常见的。
可视化软件包ggplot2将数据与绘图分离,实现图层叠加,现在以成为使用最广泛的R语言拓展包。
顶层语言外壳是可定制的,程序员利用这个功能搭建了集成开发环境比如RStudio,这使得R语言更易于学,主要用于商业方面。
你好,很高兴为你解答。
专访资深程序员庄晓立:我为什么要选择Rust?
Rust是由Mozilla开发的注重安全、性能和并发性的编程语言。这门语言自推出以来就得到了国内外程序员的大力推崇。Rust声称解决了传统C语言和C++语言几十年来饱受责难的内存安全问题,同时还保持了极高的运行效率、极深的底层控制、极广的应用范围。但在国内有关Rust的学习文档并不多见,不久前,笔者联系上了Rust1.0版本代码贡献者庄晓立(精彩博文:为什么我说Rust是靠谱的编程语言),请他分享Rust语言特性以及学习经验。
CSDN:你是从什么时候开始接触Rust语言的?是什么地方吸引了你?
庄晓立:我大概从2013年后半年开始深入接触Rust语言。它居然声称解决了传统C语言和C++语言几十年来饱受责难的内存安全问题,同时还保持了极高的运行效率、极深的底层控制、极广的应用范围。
其ownership机制令人眼前一亮,无虚拟机(VM)、无垃圾收集器(GC)、无运行时(Runtime)、无空指针/野指针/内存越界/缓冲区溢出/段错误、无数据竞争(Data Race)……所有这些,都深深地吸引了我——这个十多年以来深受C语言折磨的痛并快乐着的程序员。
CSDN:在你看来,Rust是怎样的一门语言?它适合开发什么类型的项目?为何你会说Rust不惧怕任何竞争对手,它既能取代C语言地位;又可挑战C++市场,还可向Java、Python分一杯羹?与这些语言相比,Rust有哪些优越的特性?
庄晓立:Rust是一门系统编程语言,特别适合开发对CPU和内存占用十分敏感的系统软件,例如虚拟机(VM)、容器(Container)、数据库/游戏/网络服务器、浏览器引擎、模拟器等,而这些向来主要都是C/C++的传统领地。
此外,Rust在系统底层开发领域,如裸金属(bare metal)、操作系统(OS)、内核(kernel)、内核模块(mod)等,也有强劲的实力,足以挑战此领域的传统老大C语言。Rust丰富的语言特性、先进的设计理念、便捷的项目管理,令它在上层应用开发中也能大展拳脚,至少在运行性能上比带VM和GC的语言要更胜一筹。无GC实现内存安全机制、无数据竞争的并发机制、无运行时开销的抽象机制,是Rust独特的优越特性。
其他语言很难同时实现这些目标,例如传统C/C++无法保证内存安全,Java/Python等无法消除运行时开销。但Rust毕竟还是很年轻的项目,它释放影响力需要时间,被世人广泛接受需要时间;它的潜力能否爆发出来,需要时间去检验。我们只需耐心等待。
CSDN:Rust在国内有没有具体的实际使用案例?
庄晓立:因为Rust1.0正式版刚刚发布不足一月,在国内影响力还不大,我们不能苛求它在国内有实际应用案例。但是在国外,一两年前就已经有OpenDNS和Skylight把Rust应用在生产环境。还有浏览器引擎Servo、Rust编译器和标准库、项目管理器Cargo等“两个半大型应用案例”。这些足够说明Rust语言的成熟和实用。
CSDN:你参与了Rust1.0版本代码贡献,目前该版本正式版已经发布,对此你感觉如何?这门语言是否已经达到比较成熟的阶段?
庄晓立:我积极参与了Rust语言开源项目,多次贡献源代码,曾连续三次出现在Rust官方博客公布的Rust 1.0 alpha、Rust 1.0 beta和Rust 1.0正式版的贡献者名单中。在Rust 1.0正式版出台的过程中及此前的很长一段时间,开发者付出了极大的努力,确保Rust 1.0正式版在Semver 2.0规范下,务必保持向后兼容性,除非遇到重大Bug不得不修复。
我认为,在1.0正式发布之后,Rust就已经进入了比较成熟的阶段。而且,Rust还在快速迭代发展过程中,1.0发布6周后将发布1.1,再6周后将发布1.2,必然会一步一个台阶,越来越成熟稳定。
CSDN:除了功能优先级以外,在你看来,Rust正在朝什么方向发展?未来的Rust可以期待什么样的特性?
庄晓立:Rust一定会沿着“确保内存安全、无运行开销、高效实用”的既定方向持续发展。在短期内值得期待的语言特性有:动态Drop、偏特化、继承、改进borrow checker、改进宏和语法扩展。短期内值得期待的其他特性有:增强文件系统API、提供内存申请释放API、更好地支持Windows和ARM、更快的编译速度、更方便的二进制分发机制(MUSL)、更实用的工具等等。
CSDN:据我了解,你之前也比较推崇Go语言,为何想到放弃Go转向Rust?
庄晓立:推崇Go语言还谈不上,不过我曾经尝试努力接受Go语言,2011底年开始我曾经花费将近半年时间深度关注Go开发进程,提了很多具体的改进意见和建议,也曾经多次尝试贡献源代码。后来考虑到Go语言的设计理念跟我偏差太大,其社区也不太友好,慢慢地疏远了它。我曾经写过一篇博客《我为什么放弃Go语言》,谈到了很多具体的原因。
CSDN:国内,参与Rust代码贡献的开发者多吗?有核心的人员吗?有哪些社区在维护Rust?
庄晓立:国内参与Rust代码贡献的开发者并不多,但也不少,官方的贡献者名单中也偶见几个貌似国人的名字。Rust的核心开发人员基本上都是Mozilla公司的员工,他们专职负责开发维护Rust语言和相关的项目,Rust社区也主要是他们参与组织和管理的。社区人员讨论主要集中在GitHub项目主页RFC/PR/Issue官方、Discuss论坛/IRC、Reddit、HN、StackOverflow等。
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流