├── 2015 └── 常用算法动态展示.md ├── 2016 ├── images │ ├── 1.png │ └── 2.png ├── 使用Go语言工作400天后的感受.md └── C10M高性能网络应用探索.md ├── 2017 ├── Q4 │ ├── Go语言之不服就干.md │ └── 听说你想让Go程序运行的更快.md ├── Q3 │ └── 冷门Go语言命令详解.md └── Q2 │ ├── 面向外网的Go语言网络调优详解.md │ └── Go语言之我的性能我做主.md ├── 2018 ├── .DS_Store ├── Q3 │ ├── .DS_Store │ ├── images │ │ ├── 1.jpg │ │ ├── 2.jpg │ │ ├── 3.jpg │ │ ├── 4.JPEG │ │ ├── 5.JPEG │ │ ├── 6.JPEG │ │ └── 7.JPEG │ ├── 如何从零开始设计一款好的技术开源产品.md │ ├── 从逻辑思维角度提升自己的表达技巧.md │ └── 为何k8s天然适合微服务.md ├── Q2 │ ├── 揭开以太坊交易费用Gas的神秘面纱.md │ ├── Linux下从零开始部署和使用Jaeger.md │ ├── 分布式数据库Cockroach初体验.md │ └── 使用Go语言编写区块链P2P网络.md └── Q1 │ ├── 在以太坊发行Token代币.md │ └── 使用Go语言从零开始编写PoS区块链.md ├── 2021 └── Q4 │ ├── rust-salary-anecdote.md │ ├── insert-after-every-T.md │ ├── rust-2021-2022.md │ ├── how-not-to-giveup-rust.md │ ├── deno-vs-node.md │ ├── deref.md │ ├── rust-tips.md │ ├── go-vs-rust.md │ ├── lifetime-too-long02.md │ ├── lifetime-too-long.md │ └── make-yes-1000-times-faster.md ├── 2022 └── Q1 │ ├── porting-tsc-to-go.md │ ├── rust-survey-2021.md │ ├── highlights-of-rust.md │ └── sustainability-with-rust.md ├── index ├── algorithm.md ├── architecture.md ├── manager.md ├── cloud-native.md ├── blockchain.md └── programing.md ├── README.md ├── 我的常用图表 └── compare-bar.js └── LICENSE /2018/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/.DS_Store -------------------------------------------------------------------------------- /2016/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2016/images/1.png -------------------------------------------------------------------------------- /2016/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2016/images/2.png -------------------------------------------------------------------------------- /2018/Q3/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/.DS_Store -------------------------------------------------------------------------------- /index/algorithm.md: -------------------------------------------------------------------------------- 1 | # 算法之美 2 | 3 | #### 2015 4 | - [常用算法动态展示](../2015/常用算法动态展示.md) -------------------------------------------------------------------------------- /2018/Q3/images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/1.jpg -------------------------------------------------------------------------------- /2018/Q3/images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/2.jpg -------------------------------------------------------------------------------- /2018/Q3/images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/3.jpg -------------------------------------------------------------------------------- /2018/Q3/images/4.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/4.JPEG -------------------------------------------------------------------------------- /2018/Q3/images/5.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/5.JPEG -------------------------------------------------------------------------------- /2018/Q3/images/6.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/6.JPEG -------------------------------------------------------------------------------- /2018/Q3/images/7.JPEG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunface/blogs/HEAD/2018/Q3/images/7.JPEG -------------------------------------------------------------------------------- /2021/Q4/rust-salary-anecdote.md: -------------------------------------------------------------------------------- 1 | @todo 2 | 3 | https://www.reddit.com/r/rust/comments/rvxzcw/would_people_with_rust_jobs_be_comfortable/ -------------------------------------------------------------------------------- /2021/Q4/insert-after-every-T.md: -------------------------------------------------------------------------------- 1 | # 某个Rust数组的非常规操作引发的深入思考 2 | 3 | https://www.reddit.com/r/rust/comments/rhq8ee/insert_value_into_vector_after_every_second/ -------------------------------------------------------------------------------- /index/architecture.md: -------------------------------------------------------------------------------- 1 | # 架构设计 2 | 3 | #### 2018Q2 4 | - [分布式数据库Cockroach初体验](../2018/Q2/分布式数据库Cockroach初体验.md) 5 | 6 | #### 2016 7 | - [C10M高性能网络应用探索](../2016/C10M高性能网络应用探索.md) -------------------------------------------------------------------------------- /index/manager.md: -------------------------------------------------------------------------------- 1 | # 技术管理 2 | 3 | #### 2018 年 Q3 4 | 5 | - [如何从零开始设计一款好的技术开源产品](../2018/Q3/如何从零开始设计一款好的技术开源产品.md) 6 | - [从逻辑思维角度提升自己的表达技巧](../2018/Q3/从逻辑思维角度提升自己的表达技巧.md) 7 | 8 | -------------------------------------------------------------------------------- /2021/Q4/rust-2021-2022.md: -------------------------------------------------------------------------------- 1 | # Rust语言2021年度总结:成为了更好的自己 2 | 3 | https://llogiq.github.io/2021/12/30/review.html 4 | 5 | ## 嵌入式 6 | https://blog.rust-embedded.org/this-year-in-embedded-rust-2021/ -------------------------------------------------------------------------------- /index/cloud-native.md: -------------------------------------------------------------------------------- 1 | # CloudNative微服务 2 | 3 | #### 2018年Q2 4 | - [Linux下从零开始部署和使用Jaeger](../2018/Q2/Linux下从零开始部署和使用Jaeger.md) 5 | 6 | #### 2018年Q3 7 | - [为何k8s天然适合微服务](../2018/Q3/为何k8s天然适合微服务.md) -------------------------------------------------------------------------------- /2021/Q4/how-not-to-giveup-rust.md: -------------------------------------------------------------------------------- 1 | # Rust从入门到放弃 2 | 3 | 我见过很多人在学习Rust后最终都放弃了,这里总结一些原因,避开他们可以让你的Rust之路好走很多! 4 | 5 | https://www.reddit.com/r/rust/comments/rgwcl7/how_not_to_learn_rust/ 6 | https://dystroy.org/blog/how-not-to-learn-rust/ -------------------------------------------------------------------------------- /index/blockchain.md: -------------------------------------------------------------------------------- 1 | # 区块链 2 | 3 | #### 2018年Q2 4 | - [揭开以太坊交易费用Gas的神秘面纱](../2018/Q2/揭开以太坊交易费用Gas的神秘面纱.md) 5 | - [使用Go语言编写区块链P2P网络](../2018/Q2/使用Go语言编写区块链P2P网络.md) 6 | 7 | #### 2018年Q1 8 | - [使用Go语言从零开始编写PoS区块链](../2018/Q1/使用Go语言从零开始编写PoS区块链.md) 9 | - [在以太坊发行Token代币](../2018/Q1/在以太坊发行Token代币.md) -------------------------------------------------------------------------------- /index/programing.md: -------------------------------------------------------------------------------- 1 | # 编程语言 2 | 3 | #### 2017年Q4 4 | - [Go语言之不服就干](../2017/Q4/Go语言之不服就干.md) 5 | - [听说你想让Go程序运行的更快](../2017/Q4/听说你想让Go程序运行的更快.md) 6 | 7 | #### 2017Q3 8 | - [冷门Go语言命令详解](../2017/Q3/冷门Go语言命令详解.md) 9 | 10 | 11 | #### 2017Q2 12 | - [Go语言之我的性能我做主](../2017/Q2/Go语言之我的性能我做主.md) 13 | - [面向外网的Go语言网络调优详解](../2017/Q2/面向外网的Go语言网络调优详解.md) 14 | 15 | #### 2016 16 | - [使用Go语言工作400天后的感受](../2016/使用Go语言工作400天后的感受.md) -------------------------------------------------------------------------------- /2021/Q4/deno-vs-node.md: -------------------------------------------------------------------------------- 1 | # Deno与Node的爱恨情仇 2 | 一门语言的强势崛起往往要靠几个特别明星的项目推动。`docker`和`k8s`成就了Go语言,那么`Rust`靠谁? 目前来看,`deno`有成为super star的潜力,因此本文会不那么肤浅的谈谈`deno`以及它的对手`node`。 3 | 4 | 5 | 6 | @todo 7 | 8 | 9 | ## 参考资料 10 | https://www.imaginarycloud.com/blog/deno-vs-node/ 11 | https://blog.bitsrc.io/is-deno-still-a-thing-a-look-at-the-status-of-the-node-killer-884d47981d09 12 | https://www.cloudsavvyit.com/14555/what-is-deno-and-how-does-it-differ-from-node-js/ 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sunface的博客 2 | 3 | - [知乎专栏](https://www.zhihu.com/people/iSunface/columns) 4 | - [CSDN](https://college.blog.csdn.net) 5 | 6 | 大道至简,技术如歌。本博客关注区块链、微服务、容器云、分布式架构、技术管理等方向,喜欢就给个 star,订阅就点 watch. 7 | 8 | 如果我写的任何文章曾在你的心里荡起涟漪,那至少说明在逝去的岁月里,我们在某一刻,共同经历着一样的技术探索之路。 9 | 有时候,虽然素未谋面,却已相识很久,很微妙也很知足。 10 | 11 | 12 | ## 最近文章 13 | #### 2021年12月 14 | - [实战 - Rust生命周期错误案例01](https://zhuanlan.zhihu.com/p/444395977) 15 | - [实战 - Rust生命周期错误案例02](https://zhuanlan.zhihu.com/p/445777626) 16 | 17 | #### 2018 年 7 月 18 | - [如何从零开始设计一款好的技术开源产品](2018/Q3/如何从零开始设计一款好的技术开源产品.md) 19 | - [为何k8s天然适合微服务](2018/Q3/为何k8s天然适合微服务.md) 20 | - [从逻辑思维角度提升自己的表达技巧](2018/Q3/从逻辑思维角度提升自己的表达技巧.md) 21 | 22 | ## 我的书籍 23 | - [Rust语言圣经](https://github.com/sunface/rust-course) 24 | -------------------------------------------------------------------------------- /我的常用图表/compare-bar.js: -------------------------------------------------------------------------------- 1 | option = { 2 | title: { 3 | text: 'Rust vs Go算法性能' 4 | }, 5 | tooltip: { 6 | trigger: 'axis', 7 | axisPointer: { 8 | type: 'shadow' 9 | } 10 | }, 11 | legend: {}, 12 | grid: { 13 | left: '3%', 14 | right: '4%', 15 | bottom: '3%', 16 | containLabel: true 17 | }, 18 | xAxis: { 19 | type: 'value', 20 | boundaryGap: [0, 0.01] 21 | }, 22 | yAxis: { 23 | type: 'category', 24 | data: ['binary-trees', 'regex-redux', 'mandelbrot', 'reverse-complment', 'k-nucleotide', 'spectral-norm','n-body','fasta','pidigits','fannkuch-redux'] 25 | }, 26 | series: [ 27 | { 28 | name: 'Rust', 29 | type: 'bar', 30 | data: [1.09, 0.77, 0.93, 0.45, 2.70, 0.72,3.29,0.77,0.71,7.54], 31 | label: { 32 | show: true, 33 | position: 'right' 34 | }, 35 | }, 36 | { 37 | name: 'Go', 38 | type: 'bar', 39 | data: [12.23, 3.85, 3.73, 1.35, 7.46, 1.43,6.38,1.26,1.00,8.31], 40 | label: { 41 | show: true, 42 | position: 'right' 43 | }, 44 | } 45 | ] 46 | }; -------------------------------------------------------------------------------- /2021/Q4/deref.md: -------------------------------------------------------------------------------- 1 | # 你所不知的Deref 2 | 3 | `Deref`在Rust中无处不在,很多你看不见的地方,它都在悄无声息的帮助我们完成各种类型转换。但是也有很多需要注意点,例如什么情况可以自动隐式的执行`Deref`,什么情况不行。 4 | 5 | ## 何时会自动Deref? 6 | 先来看一段代码 7 | ```rust 8 | use std::ops::Index; 9 | use std::rc::Rc; 10 | 11 | struct MyType { bytes: T } 12 | 13 | // Wow such clean much wow 14 | impl, I> Index for MyType { 15 | type Output = T::Output; 16 | 17 | fn index(&self, index: I) -> &Self::Output { 18 | &self.bytes[index] 19 | } 20 | } 21 | 22 | fn main() { 23 | let rc1 = Rc::new([1, 2, 3]); 24 | rc1[0]; // OK 25 | 26 | let rc2 = MyType { bytes: Rc::new([1, 2, 3]) }; 27 | rc2[0]; // error[E0608]: cannot index into a value of type `MyType>` 28 | // WHY???!?!!?!?!?!? 29 | } 30 | ``` 31 | 32 | 报错的原因是`Rc`自身没有实现`Index`,因此需要先通过`Deref`转为内部的数据类型,再进行索引。 33 | 34 | 首先`let rc1 = Rc::new([1, 2, 3]);`能行,是因为`Rc`能通过`Deref`转为`&[]`类型,因此`Deref`后,等同于直接对内部的数组切片取索引. 35 | 36 | 而` let rc2 = MyType { bytes: Rc::new([1, 2, 3]) };`不行,是因为`Rc::new([1,2,3])`实际上是泛型类型`T`,编译器并不知道T有没有实现`Deref`,因此也不能对`Rc::new([1,2,3])`进行索引。 37 | 38 | 简而言之,只有作为**表达式才能自动进行**`Deref`(`rc1`),作为类型时,不能自动进行`Deref`(`rc2`)。 39 | 40 | 41 | 你也可以为自己的类型实现`Deref`: 42 | ```rust 43 | use std::ops::Deref; 44 | use std::rc::Rc; 45 | 46 | struct MyType { bytes: T } 47 | 48 | impl Deref for MyType { 49 | type Target = T; 50 | 51 | fn deref(&self) -> &T { 52 | &self.bytes 53 | } 54 | } 55 | 56 | fn main() { 57 | let rc = Rc::new([1, 2, 3]); 58 | rc[0]; // OK 59 | 60 | let rc = MyType { bytes: Rc::new([1, 2, 3]) }; 61 | rc[0]; 62 | } 63 | ``` 64 | 但是这不符合官方推荐的使用方式,因为官方明确说明`Deref`只适用于智能指针,其它类型要小心使用。 -------------------------------------------------------------------------------- /2022/Q1/porting-tsc-to-go.md: -------------------------------------------------------------------------------- 1 | ## Rust大佬为何选择使用Go语言来移植tsc? 2 | > 原文: [I’m porting tsc to Go](https://kdy1.dev/posts/2022/1/tsc-go) 3 | 4 | 大家好,我是 `SWC` 的作者,目前正在将 TypeScript 的类型检查器 `tsc` 移植到 Go 语言上。众所周知,`SWC` 是用 Rust 语言实现,而我也是 Rust 的忠实粉丝,为何会有这种奇怪的选择呢? 5 | 6 | ## 为何移植 tsc? 7 | 随着 TypeScript 在全世界范围内的广泛采用,大型的 TS 项目都在面临一个困境:类型检查已经成为他们工作流程中最慢的环节之一,用静态类型交换开发效率显然不是开发者所希望看到的。 8 | 9 | 这背后的元凶就是 `tsc`,它会对类型进行全面的检查,然后将 TS 的代码编译为 Javascript。随着代码规模的快速增长,编译时间也会随之线性增加。对于中大型的项目而言,这个编译时间可能会变得非常慢,好在,有了 `SwC`,从 TS 到 JS 的转译时间被大大缩短了,但是类型检查依然是瓶颈之一。 10 | 11 | ## 什么是类型检查器? 12 | 类型检查器会在你的程序执行前对其进行正确性检查,这样可以确保在函数调用或变量赋值时使用了正确的值。同时为了避免开发者需要在所有地方都手动声明类型,tsc 同时还提供了强大的类型推导功能。 13 | 14 | 总之,类型检查可以帮助我们实现更可靠的代码,阻止可能的错误,特别是在代码重构时,类型检查更是可以发挥巨大的作用(重构过中大型动态语言项目的同学相信都深有感悟)。 15 | 16 | 可能读者大大们已经有些着急了,我是冲着你的题目来的,内容呢?别急,本文的重头戏来了。 17 | 18 | ## 为何不使用 Rust 19 | 首先,我并不是一开始就决定使用 GO 语言,而是试图用 Rust 去重写 tsc,简而言之,这次重写非常愉快也非常有趣。 20 | 21 | 同时,Rust 版本的重写相比原来的 tsc 提升了 **62 倍的性能**!为了测量编译时间的提升,我使用了 `conformance` 测试工具,该工具是由 TypeScript 官方编译器提供的,下面是时间对比: 22 | 23 | - tsc: 133.2秒 24 | - Rust 重写版: **2.13秒!** 25 | 26 | 既然提升这么大,为何最终却放弃了? 27 | 28 | 这里其实就涉及到**重写和移植这两者的区别**了。tsc 不可否认是一个非常大的项目,因此重写会是一件非常非常艰巨的事,但是这里的性能提升却非常诱人,而且我对于 Rust 的喜爱也加分不少。好在,还可以选择使用 Rust 移植,相比起重写,移植会轻松愉快的多! 29 | 30 | 但是,在深入阅读过 TypeScript 的源码后,我发现了几点至关重要的问题: 31 | 32 | - tsc 使用了大量共享可变性,例如两个可变引用指向同一个数据,并且很多实现非常依赖于垃圾回收(GC) 33 | - tsc 使用了大量的自引用结构,熟悉 Rust 的同学都知道,这种数据结构在 Rust 中有多么难 34 | 35 | 如果是重写,上面的都没有问题,可以重新选择其它解决方案实现,但是既然选择了移植,那么上面的问题就成了绕不过去的坎。 36 | 37 | 虽然我是 Rust 的拥护者和信徒,但是经过深思熟虑后,依然觉得 Rust 在这种场景下,并不是一个好的移植选择。如果使用 Rust,就必须大量依赖于 `unsafe` 的代码实现。 38 | 39 | 总之,语言的选择更多还是取决于任务的类型而不是喜好,虽然心有不甘,我最终还是选择了 Go 语言(实际还在 Go语言和 Zig 语言间做了一番调研)。 40 | 41 | ## 会开源吗? 42 | 目前该项目由 `Vercel` 赞助开发,我们会在未来某一个时间点将其开源,同时还会将 `tsc` 和 `swc` 桥接起来。 43 | 44 | 当支持了类型检查后, `swc` 就可以进一步提升大家的 `JS/TS` 工具链性能: 45 | 46 | - ✅ 转移 (替换 Babel) 47 | - 类型检查Type Checking (替换 tsc) 48 | - 体积缩小 (替换 Terser) 49 | - 打包 (替换 webpack) -------------------------------------------------------------------------------- /2021/Q4/rust-tips.md: -------------------------------------------------------------------------------- 1 | # Rust一些你可能不知道的事 2 | 3 | ## 就地取`&mut`进行函数调用 4 | ```rust 5 | use rand::Rng; 6 | 7 | fn main() { 8 | let mut rng = rand::thread_rng(); 9 | loop { 10 | let rand_string: String = (&mut rng) 11 | .sample_iter(&rand::distributions::Alphanumeric) 12 | .take(30) 13 | .map(char::from) 14 | .collect(); 15 | if rand_string.contains("test") { 16 | println!("Randomly generated {}", rand_string); 17 | return; 18 | } 19 | } 20 | } 21 | ``` 22 | 23 | ## &和dyn的区别 24 | 前文提到,`&`和`dyn`都可以用于特征对象,因此在功能上`&`和`dyn`几无区别,唯一的区别就是:`&`减少了一次指针调用。 25 | 26 | 因为`dyn`是一个宽指针(`fat pointer`), 它内部保存一个指针指向`vtable`,然后通过`vtable`查询到具体的函数指针,最后进行调用. 27 | 28 | 所以,如果你在乎性能,又想使用特征对象简化代码,可以优先考虑`&`。 29 | 30 | ## benchmark迷一般的性能结果 31 | 代码如下 32 | ```rust 33 | #![feature(test)] 34 | 35 | extern crate test; 36 | 37 | fn fibonacci_u64(number: u64) -> u64 { 38 | let mut last: u64 = 1; 39 | let mut current: u64 = 0; 40 | let mut buffer: u64; 41 | let mut position: u64 = 1; 42 | 43 | return loop { 44 | if position == number { 45 | break current; 46 | } 47 | 48 | buffer = last; 49 | last = current; 50 | current = buffer + current; 51 | position += 1; 52 | }; 53 | } 54 | #[cfg(test)] 55 | mod tests { 56 | use super::*; 57 | use test::Bencher; 58 | 59 | #[test] 60 | fn it_works() { 61 | assert_eq!(fibonacci_u64(1), 0); 62 | assert_eq!(fibonacci_u64(2), 1); 63 | assert_eq!(fibonacci_u64(12), 89); 64 | assert_eq!(fibonacci_u64(30), 514229); 65 | } 66 | 67 | #[bench] 68 | fn bench_u64(b: &mut Bencher) { 69 | b.iter(|| { 70 | for i in 100..200 { 71 | fibonacci_u64(i); 72 | } 73 | }); 74 | } 75 | } 76 | ``` 77 | 通过`cargo bench`运行后,得到一个难以置信的结果:`test tests::bench_u64 ... bench: 0 ns/iter (+/- 0)`, 难道Rust已经到达量子计算机级别了? 78 | 79 | 其实,原因藏在`LLVM`中: `LLVM`认为`fibonacci_u64`函数调用的结果没有使用,同时也认为该函数没有任何副作用(造成其它的影响,例如修改外部变量、访问网络等), 因此它有理由把这个函数调用优化掉! 80 | 81 | 解决很简单,使用Rust标准库中的[`black_box`](https://doc.rust-lang.org/std/hint/fn.black_box.html)函数: 82 | ```rust 83 | for i in 100..200 { 84 | black_box(fibonacci_u64(black_box(i))); 85 | } 86 | ``` 87 | 88 | 通过这个函数,告诉编译器,尽量少的做优化,此时LLVM就不会再自作主张了:) -------------------------------------------------------------------------------- /2017/Q4/Go语言之不服就干.md: -------------------------------------------------------------------------------- 1 | > 本文发表时间:2017年10月8号 2 | 3 | 不知不觉,我们团队选择go语言已经两年了,从最开始摸着石头过河到现在的驾轻就熟,感慨万千,总结来说:不服就干。 4 | 5 | 孙悟空不服天庭,所以大闹天空,那我们不服谁呢?可能不服某位年龄不低的老大哥对世界的绝对统治吧,想要翻出点浪花来。 6 | 7 | 是的,不服那就干!干之前当然得考虑过潜在的风险,最终确定在可控范围之内,同时因为Go的简单,团队转型也非常顺利,大概一周时间,就华丽丽的转型成功。 8 | 9 | 项目方面,先后做了消息推送、IM即时聊天、API网关、统一监控平台(系统监控、应用监控、全链路跟踪、服务可用性、业务监控等等),一路下来,最大的感受就是‘爽’,其次就是‘风一般的自由’。 10 | 11 | Go语言本身并不自由,较为强制的代码规范、泛型的缺失、略啰嗦的错误处理等,但是我们为什么感受到‘风一般的自由’呢?原因就在于Go实在是太他么的简单了: 简单上手、简单开发、简单测试、简单部署、简单维护,基本所有你能想到现代化软件研发过程中备受诟病的复杂性问题,在Go这里,都不存在。 12 | 13 | 可能有同学要说了,这个世界上不仅仅Go语言简单,比如python,能被科学界和学生普遍接受,也是因为简单啊,而且名气比Go大,生态比Go好,你们为什么不选Python,别急,且听我细细道来。 14 | 15 | 不知道大家有没有思考过,是什么让我们在用一门工具时能感受到:哇,牛逼,答案可能是:是否拥有简单、强大的生产力。 16 | 17 | Go的生产力体现在以下方面: 18 | 1.简单的语法 19 | - 新加入的员工很容易就转型为Go程序员,而且一上手就能有不俗的开发效率 20 | - 有过丰富开发经验的同学肯定都很痛恨去维护历史代码吧?各种神奇的magic代码,就算你喊破喉咙也没人能帮你解决这些,还好Go的语法足够简单,你想写出黑魔法代码估计也无能为力,因此Go的三方库代码可读性一般都很好 21 | 22 | 2.代码规范强制统一 23 | - 原因基本同上 24 | - Go的标准工具链提供了代码格式化 25 | 26 | 3.部署简单 27 | - 在本地交叉编译为目标平台的可执行文件,无需任何依赖,扔上去就可以运行 28 | - 容器亲和度极高 29 | 30 | 4.优秀全面的标准库 31 | - 基本上正常开发需要的所有功能,标准库都支持了,而且简单易用,稳定性和性能俱佳 32 | - 我们有express、tomcat 33 | go: 我们有标准库 34 | 35 | 我们有强大的fastjson 36 | go: 我们有标准库 37 | 38 | 我们有netty 39 | go: 我们有标准库 40 | 41 | 我们有强大的异步回调和纤程库 42 | go: 我们只需要在任何函数前加一个go关键字即可 43 | 44 | 我们有强大的三方性能分析工具 45 | go: 我们有自带的标准工具链 46 | 47 | 我们有强大的测试框架 48 | go: 我们有自带的标准工具链 49 | 50 | 51 | 5.写Go项目基本不用担心性能问题 52 | - 语言级并发和高执行性能决定了只要你用Go写出了项目,那么性能一般都是不错的(数据库性能和语言性能无关) 53 | - Go强大的标准工具链支持,能让项目的任何一段代码执行过慢、内存占用过高等问题纤毫毕现,我们团队解决过多个棘手的性能问题,在其它语言可能需要花费一两天的功夫,对go来说10分钟足矣 54 | 55 | 6.良好的生态 56 | - 是的,我承认,go的生态远远比不上java,但是该有的都有,而且三方库质量还都高,作者也普遍较为活跃 57 | - 现代化的框架、平台对go往往都支持的很好,甚至是官方原生大力支持,比如grpc、tensorflow等等 58 | 59 | 7.极快的编译速度 60 | - 我要编译了,请允许我喝杯咖咖咖咖啡啡啡啡放松下,shit,这就编译完了?咖啡包装还没拆 61 | 62 | 8.IDE亲和 63 | - go的ide插件都是go语言自己写的,因此跨平台支持的很好,对于ide的亲和度也很高,vscode、idea、vim都能很好的写Go代码 64 | 65 | 9.软实时级别的GC 66 | - 微妙级别的GC时间,决定了Go可以在部分实时领域大展身手,比如实时证券行情等 67 | - 我们就不必借助于C++、C去实现高性能代码了,大大提升了开发和维护效率 68 | 69 | 写了,这么多,前面的为什么在一些场景不选python,大家应该也明白了,说到底,还是想追求'风一般的自由‘。 70 | 71 | 快到尾声了,再简单提下Go适合的应用场景 72 | - Web服务 73 | - 中间件基础服务 74 | - DevOps服务 75 | - 云计算 76 | - 游戏、直播等实时性要求较高的领域 77 | - 跨平台可执行脚本 78 | 79 | 最后,再送大家一些福利,自从用了Go,团队加班显著减少,终于有时间陪伴女朋友了,想怎么happy就怎么happy。 80 | 81 | 我们都是平凡的,也都渴望着成功。一路千辛万苦,跨过山和大海,想要逆袭,可是你敢像孙大圣那样放弃所有吗? 82 | 83 | 不服就干。 84 | 85 | > 如果您喜欢这篇文章,请点击喜欢;如果想及时获得最新的咨询,请点击关注。您的支持是对作者都是最大的激励,万分感激!By 孙飞 -------------------------------------------------------------------------------- /2018/Q2/揭开以太坊交易费用Gas的神秘面纱.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2018年4月20号 2 | 分类于[区块链](../../index/blockchain.md) 3 | 4 | 大家都知道Gas有燃料的含义,那么以太坊的Gas是什么呢?估计能正确理解的人就不多了,甚至一些区块链老兵也无法说得很清楚。而且以太坊为什么要选择Gas这种设计,而不是更简单的方式,例如比特币。 5 | 6 | 首先来看看Gas到底是啥? 7 | 8 | 以太坊的货币是以太币(Ether),但是Gas其实并不是以太币延伸处的货币。Gas是以太坊虚拟机(EVM)内部的Token,当以太坊虚拟机执行一些指令操作时(例如智能合约、转账代码等),将扣除相应的Gas。 9 | 10 | 例如,不同类型合约的Gas消耗: 11 | - 仅执行一条指令: 1 Gas 12 | - 存储一个值: 100Gas 13 | - 调用另外一条合约: 20Gas 14 | 15 | 这样做是有很多好处的,最主要的是以太坊可以控制外部数据输入的边界,例如你如果想要写很长的合约或存储很多数据来达到恶意的目标,你就需要支付很多的费用来达成,天底下没有免费的午餐! 16 | 17 | 假如你想要向一条智能合约发送一次事务请求,首先被要求输入下面的内容: 18 | - Gas Price : 你需要为**每一单位的Gas**支付的以太币数额 19 | - Gas Limit: 为了执行这次事务,你愿意支付的**最大Gas单位数** 20 | 21 | 看到这里,各位同学可能还是很迷惑。到底这些Gas XXX 合在一起是什么鬼?这里我们用一个类比的例子来诠释一下。 22 | 23 | ## 女佣服务 24 | 假设你加入了一个新的女佣服务中介,这个中介决定给旗下的女佣更多的特权—例如她们拥有更灵活的选择服务目标的权利,甚至如果市场允许的情况下,可以让女佣们赚取更高的佣金。 25 | 26 | 27 | 这里,中介引入了一种新的中间货币Token,来计算女佣的酬劳,最终再将中间货币兑换为美元发放。这种Token可以通过以下方式获取: 28 | - 卧室保洁 : 1 Token 29 | - 客厅保洁: 3 Tokens 30 | - 浴室保洁: 10 Tokens 31 | 32 | 目前来说,这些价格是由中介制定的,不会改变。所以无论如何,对于女佣来说,她的收入将是固定的。 33 | 34 | ##### 服务灵活性 35 | 之前提到过,引入中间货币Token后,一些服务和佣金形式将更佳灵活,那应该怎么实现呢?毕竟单次服务的Token数是固定的。 36 | 37 | 灵活性就体现在对于每个Token,顾客愿意为之支付的金额,这个金额是可以协商的。 38 | 39 | **女佣可能提出:我为每个Token收取的费用是25美元,你可能会提出不同的意见:我最多只能为你的每个Token付出15美元** 40 | 41 | 这个时候,意见出现了分歧,就只有两种办法:女佣接受你的价格然后开始保洁或者她拒绝你的价格去寻找能提供更好价格的用户。 42 | 43 | > 以上的过程和Gas Price是非常相像的 44 | 你会说:我愿意为每个Gas支付0.00002以太币,那矿工可以选择接受你的出价,或者拒绝你后去选择出价更高的人。 45 | 46 | (当然,对于以太坊来说,还略有不同,为了实现自动化,矿工会设置一个最小可接受的Gas Price,如果你的出价高于这个最小值,那矿工就可以开始为你服务) 47 | 48 | 截止目前,通过女佣服务这个例子,我们更好的理解了何为Gas Price,那么再来看看Gas Limit,在真实世界中,该是怎么样的例子? 49 | 50 | ## 女佣服务中间货币的限制 51 | 现在假设你租用了一栋房子准备用来举办一次大型聚会party,聚会当然很high,但是结果也很残酷:起来后,你发现屋里子到处都是乱糟糟的,我擦,好头疼,该怎么办?别急,这个时候就可以使用咱们的女佣服务中介了,最终我们跟中介谈妥了**每个Token的价格:20美金**。 52 | 53 | >“你这栋房子有多少间房间?” 54 | “额,不是很清楚” 55 | “那你希望我们怎么报价呢” 56 | 你假装深沉(其实就是钱包羞涩):"这样吧,我最多给你支付100个Token,如果房间太多用完了,那你停止打扫即可;如果没有用完,需要退还给我剩余的Token" 57 | 58 | 最终,大家达成了一致的协议: 59 | - 总计需要支付Token数: 100 60 | - 每个Token需要支付的价格:20美元 61 | 62 | 以上过程跟Gas Limit也是极其相似的,假如你要发起一次交易,那你提供以下报价: 63 | - Gas Price : 20 Gwei (单位Gas的价格) 64 | - Gas Limit : 10000 Gas 65 | 66 | **Gwei是以太币的子单位,1以太币 = 1,000,000,000 Gwei** 67 | 68 | 对于矿工来说,就意味着你提供了这个报价,如果他愿意执行,那就消耗Gas Limit,如果任务成功完成,则退给你余额;反之则,停止任务,并收取所有的Gas Limit 69 | 70 | ## 不要心存侥幸 71 | 你发起请求之前,肯定会觉得10000Gas足够执行智能合约的任务了。但是一旦执行费用超过了这个10000Gas,那你的任务就会执行失败,而且钱概不退还。 72 | 73 | 同样的,如果你之前支付的100个Token不够用,那女佣也将立刻停止打扫。 74 | 75 | 不过还是稍有不同的:女佣虽然没有彻底打扫完,但至少你收获了大部分的干净房间;但是智能合约不同,如果你的Gas Limit用完,智能合约还没有执行完,那执行过程将全部回滚,简而言之—你赔了夫人又折兵。 76 | 77 | > 小秘诀 78 | 如果你想要节省交易费用,应该降低Gas Price,保持或者提升Gas Limit,一般用户恰恰都是进行相反的选择。 79 | 80 | ## 总结 81 | Gas是EVM用来执行你的智能合约的收费,合理的理解和设置Gas Limit和Gas Price是很重要的,**在下一节我们将讨论以太坊为什么选择Gas的方式而不是比特币的方式**。 82 | 83 | -------------------------------------------------------------------------------- /2022/Q1/rust-survey-2021.md: -------------------------------------------------------------------------------- 1 | # Rust语言2021年调查报告 2 | Rust 官方于昨日凌晨发布了2021年的调查报告,涵盖了社区发展、Rust使用情况、工作应用等方面,快来一起看看吧。 3 | 4 | > 原文 [Rust Survey 2021 Results](https://blog.rust-lang.org/2022/02/15/Rust-Survey-2021.html) 5 | 6 | Rustaceans 大家好!时光荏苒,匆匆又是一年,值此辞旧迎新之际, Rust 2021 年度调查报告发布了,在此我们感谢所有参与其中的人。 7 | 8 | ## 全球化社区 9 | 在过去一年,Rust 社区一直在快速发展,例如你所看到的这份报告汇聚了 9354 人的回复,这个数字相比 2020 年增加了约 1500 人。 10 | 11 | 其中 90% 的参与者说他们愿意在任何场景中使用 Rust,至于投反对票的也有:5% 的人说曾经用过 Rust,但未来不会再用,还有 4% 的人表示尚未用过 Rust 语言。 12 | 13 | 调查报告被翻译成 10 种语言,对于收到的有效结果中,英文占比最高 78%,紧跟其后的就是简体中文 6%,德文 4%,法文 3%。尽管英文的占比最高,但是受访者实际上来自于世界各地:美国 24%,德国 12%,中国 7% 以及英国 6%。 总之有 113 个不同的国家和地区参与了此次调查! 14 | 15 | 有 7% 的用户明确表示了不希望使用英文来作为技术交流的主要语言,还有 23% 希望能在英文之外增加更多的语言。根据调查,除了英文之外,最受欢迎的语言依次是:简单中文、德文和法文,而最希望使用其它语言作为技术交流则来自简体中文、日文和俄文使用者。 16 | 17 | ## 谁在使用 Rust 18 | 使用 Rust 的用户比例一直在持续上升,例如,有 81% 的人表示每周至少会参与一次 Rust 代码的开发,而去年的数据是 72%。 19 | 20 | 有 75% 的参与者认为他们已经有能力写生产级的项目,虽然 27% 觉得他们依然需要时间去解决跟 Rust 编译器斗争的问题。 21 | 22 | 23 | 24 | 总之,Rustaceans 对于使用 Rust 的体验还是非常满意的,虽然有 1% 的人认为相当的难用,还有 25% 的人认为:相比其它编程语言,使用 Rust 并没有让他们获得额外的好处。 25 | 26 | ## 在工作中使用 Rust 27 | 现在,我们已经可以非常有底气的说:在职业工作中使用 Rust 是非常安全的。在使用 Rust 的用户中,有 23% 将 Rust 作为他们的主要语言,还有 59% 会选择性的使用,相比去年的 42%,这个提升是相当大的。 28 | 29 | 30 | 31 | 在工作中采用 Rust 不是一件容易的事,但是最终往往会被认为是相当值得的投入。首先,对于那些在工作中使用 Rust 的调用者而言,有 83% 认为在工作中使用 Rust 是有一定挑战的,至于这种挑战到底是来源于 Rust 本身还是在工作中采用新语言本身就较难,我们目前并不清楚。 32 | 33 | 而在工作效率上,只有 13% 觉得 Rust 会拖慢团队的开发进度,而 82% 的用户认为 Rust 会帮助他们的团队更好的达成既定目标。 34 | 35 | 对于在工作中采用 Rust 的成本,仅 1% 的用户觉得回报跟付出和遇到的挑战不匹配,而 79% 的用户认为为了使用 Rust 而遇到的挑战是非常值得的。当问到他们的团队是否会在未来继续使用 Rust 时,90% 的用户给出了肯定的回答。最后,有 89% 的调查者说他们的团队在使用 Rust 过程中收获了愉快和享受。 36 | 37 | 调查的其中一项是关于为何在工作中选择 Rust 语言,最多的回答是: Rust 语言允许用户构建安全和正确的软件,有 96% 的用户对该回答表示赞同。除了正确性,性能( 92% )也得到了非常多的赞同。 38 | 39 | 40 | 41 | 总之,Rust 已经有能力应对来自生产级别的挑战:只有 3% 的调查者说 Rust 对于生产来说是一个有风险的选择。 42 | 43 | ## 前路漫漫,挑战仍在 44 | 年度报告给出的结果说明 Rust 的社区正在健康有序的快速发展,但是这并不意味着我们就可以高枕无忧了。 45 | 46 | 编译时间,Rust 一直在改进的历史问题,但是一直也没有达到预期,61% 的人认为需要继续改善。不过值得称赞的是,有 61% 认为在过去一年中编译团队确实提升了一些编译速度。 47 | 48 | 其它需要改进的领域包括: 硬盘空间占用(45%),debugging(40%),GUI开发(56),等等。 49 | 50 | IDE 体验(rust-analyzer, Itellij Rust 等)获得了进步最大奖:56% 认为在过去的一年中,它们的提升很大。 51 | 52 | 编译器错误信息获得了最多的褒奖:90% 的人对此表示非常满意。 53 | 54 | 当问到对 Rust 未来怎么看时,最大的担忧就是:在产业中无法得到大范围的使用(38%),考虑到 Rust 社区的快速发展和在工作中的使用率大幅提升,我们对此持有乐观的态度。 55 | 56 | 第二大担忧是:语言会否变得过度复杂(33%),但是有趣的是,与此相反,依然有少数用户希望增加更多的功能特性,特别是那些特定使用场景的用户,例如嵌入式。 57 | 58 | 还有一个担忧就是那些使用 Rust 工作的人在需要进一步提升水平时,无法得到更多支持(30%)。在 Rust 基金会的支持下,我们相信这种支持会越来越多,越来越好,但是毫无疑问,目前来说,依然有大量的工作在等待完成(偏个题,强烈推荐通过 [course.rs](https://course.rs) 来学习 Rust,这里有非常全面、深入且系统化的讲解)。 59 | 60 | ## 未来可期 61 | 对于 Rust 的历史来说,2021 年是非常重要的一年:Rust 基金会的成立,发布了Rust 2021 大版本,社区的快速发展,种种迹象都表明 Rust 的未来非常值得期待。 62 | 63 | 虽然任务依然艰巨,但是让我们期待和拥抱一个更好的 2022 吧! -------------------------------------------------------------------------------- /2017/Q3/冷门Go语言命令详解.md: -------------------------------------------------------------------------------- 1 | 2 | > 本文发表时间:2017年8月15号 3 | 4 | ### $ go build -x 5 | -x会列出来go build调用到的所有命令。 6 | 如果你对Go的工具链好奇,或者使用了一个跨C编译器,并且想知道调用外部编译器用到的具体参数,或者怀疑链接器有bug;使用**-x**来查看所有调用。 7 | 8 | ```bash 9 | $ go build -x 10 | WORK=/var/folders/00/1b8h8000h01000cxqpysvccm005d21/T/go-build600909754 11 | mkdir -p $WORK/hello/perf/_obj/ 12 | mkdir -p $WORK/hello/perf/_obj/exe/ 13 | cd /Users/jbd/src/hello/perf 14 | /Users/jbd/go/pkg/tool/darwin_amd64/compile -o $WORK/hello/perf.a -trimpath $WORK -p main -complete -buildid bbf8e880e7dd4114f42a7f57717f9ea5cc1dd18d -D _/Users/jbd/src/hello/perf -I $WORK -pack ./perf.go 15 | cd . 16 | /Users/jbd/go/pkg/tool/darwin_amd64/link -o $WORK/hello/perf/_obj/exe/a.out -L $WORK -extld=clang -buildmode=exe -buildid=bbf8e880e7dd4114f42a7f57717f9ea5cc1dd18d $WORK/hello/perf.a 17 | mv $WORK/hello/perf/_obj/exe/a.out perf 18 | ``` 19 | 20 | ### $go build -gcflags 21 | 这个参数将会传递给编译器。*go tool compile -help*列出来了所有我们可以传递给编译器的参数。 22 | 23 | 例如,禁用编译器优化和内联优化,你可以使用下面的参数: 24 | ```bash 25 | $ go build -gcflags="-N -I" 26 | ``` 27 | 28 | ### $go test -v 29 | 如果不使用**-v**参数来测试,输出很少很多,我经常使用-v参数来打开详细测试日志。例子: 30 | 31 | ```bash 32 | $ go test -v context 33 | === RUN TestBackground 34 | --- PASS: TestBackground (0.00s) 35 | === RUN TestTODO 36 | --- PASS: TestTODO (0.00s) 37 | === RUN TestWithCancel 38 | --- PASS: TestWithCancel (0.10s) 39 | === RUN TestParentFinishesChild 40 | --- PASS: TestParentFinishesChild (0.00s) 41 | === RUN TestChildFinishesFirst 42 | --- PASS: TestChildFinishesFirst (0.00s) 43 | === RUN TestDeadline 44 | --- PASS: TestDeadline (0.16s) 45 | === RUN TestTimeout 46 | --- PASS: TestTimeout (0.16s) 47 | === RUN TestCanceledTimeout 48 | --- PASS: TestCanceledTimeout (0.10s) 49 | ... 50 | PASS 51 | ok context 2.426s 52 | ``` 53 | 54 | ### $ go test -race 55 | 现在可以使用Go工具提供的**-race**参数进行竞争检测。它会检测并报告竞争。开发的过程中用这个命令来检测一下。 56 | 57 | 注:完整的命令是: 58 | ```bash 59 | $ go test -race mypkg // to test the package 60 | $ go run -race mysrc.go // to run the source file 61 | $ go build -race mycmd // to build the command 62 | ``` 63 | 64 | ### $ go test -run 65 | 你可以在测试的时候通过**-run**参数来正则匹配过滤需要测试的代码。下面的命令只会运行[test examples](https://blog.golang.org/examples): 66 | ```bash 67 | $ go test -run=Example 68 | ``` 69 | 70 | ### $ go test -coverprofile 71 | 当测试一个包的时候,可以输出一个测试覆盖率,然后使用命令**go tool**来在浏览器里面可视化: 72 | ```bash 73 | go test -coverprofile=c.out && go tool cover -html=c.out 74 | ``` 75 | 上面的命令将会创建一个测试覆盖率文件在浏览器打开结果. 76 | 77 | ### $ go test -exec 78 | 一般很少有人知道Go的这个功能,你可以通过**-exec**插入另一个程序。这个参数允许通过Go工具完成一些外部工作。 79 | 80 | 一个常见的需求场景是你需要在一些宿主机上面执行一些测试。我们可以通过**-exec**命令调用**adb**命令来把二进制文件导入安卓设备并且可以收集到结果信息。参考[这个](https://github.com/golang/go/blob/master/misc/android/go_android_exec.go)来在安卓设备上面执行。 81 | 82 | ### $ go get -u 83 | 如果你通过*go get*命令获取Go包,而这个包已经存在于本地的**GOPATH**,那么这个命令并不会帮你更新包。**-u**可以强制更新到最新版。 84 | 85 | 如果你是一个库作者,你最好在你的安装说明上加上**-u**参数,例如,[golint](https://github.com/golang/lint#installation)是这么做的: 86 | ```bash 87 | go get -u github.com/golang/lint/golint 88 | ``` 89 | 90 | ### $ go get -d 91 | 如果你想clone一个代码仓库到**GOPATH**里面,跳过编译和安装环节,使用**-d**参数。这样它只会下载包并且在编译和安装之前停止。 92 | 93 | 当需要clone虚拟网址代码仓库的时候,我经常使用这个命令来代替*git clone*,因为这样可以把Go代码自动放入合适的目录下面。例如: 94 | ```bash 95 | $ go get -d golang.org/x/oauth2/... 96 | ``` 97 | 98 | 这样可以克隆到*$GOPATH/src/golang.org/x/ouath2*目录下面。假设*golang.org/x/oauth2*是一个虚拟网址,通过*go get*获取这个代码仓库要比找出仓库的真实地址(*go.googlesource.com/oauth2*)更简单。 99 | 100 | ### $ go get -t 101 | 如果你的测试包的有附加的依赖包,**-t**可以一并下载测试包的依赖包。如果没有加这个参数,*go get*只会下载非测试包的依赖包。 102 | 103 | ### $ go list -f 104 | 这个命令可以列出来Go的所有包,并且可以指定格式。这个写脚本的时候很有用。 105 | 106 | 下面这个命令将会打印所有依赖的**runtime**包: 107 | ```bash 108 | go list -f ‘’ runtime [runtime/internal/atomic runtime/internal/sys unsafe] 109 | ``` 110 | 111 | > 如果您喜欢这篇文章,请点击喜欢;如果想及时获得最新的咨询,请点击关注。您的支持是对作者都是最大的激励,万分感激!By 孙飞 -------------------------------------------------------------------------------- /2021/Q4/go-vs-rust.md: -------------------------------------------------------------------------------- 1 | # 决战紫禁之巅 - Rust Vs Go 2 | 3 | 在过去10年中,码江湖持续数十年的风平浪静,突然被两位异军突起的侠客所打破,他们神秘但不低调,嚣张却不滥杀,威名可止小儿啼。他们就是西门吹Go和叶孤Rust。 4 | 5 | 众人皆知,文无第一,武无第二,双方虽表面兄弟嘻嘻哈哈,但是异心早就埋下,遂相约在月圆之日决战紫禁之巅!而我Sunface,将作为自封的双方见证人,隔着山头(安全第一)为大家带来现场直播(其实是我想拜师,先来偷偷看看)。 6 | 7 | ## 剑道PK 8 | 只见二人掏出佩剑,贯以深厚的内力,在空中以剑代笔,挥斥方遒,竟靠一己之力营造出虚拟投影的效果。 9 | 10 | 以剑画字,剑快者自然占尽优势,但见叶孤Rust不仅人长得玉树临风,御剑速度也当得起当世顶尖的称号, 在我还在喊牛逼666之时,就已经洋洋洒洒写完,持剑玉立于旁,甚是孤傲,等待观众的评判,可惜他不知观众就我一人,否则定生尴尬。 11 | 12 | "吾名叶孤Rust,于2006年拜师于Mozilla研究院,并于2015年学武有成,正式入江湖历练,因帅气和实力并存,蒙江湖人厚爱,连续6年成为江湖榜单Stackoverflow中最受欢迎之侠客。吾之剑道追求安全、极速和高效,但是所学甚难,汝等若想学,必先自宫,哦不是,必先潜心修炼,方能入吾之门。" 从文字可见此人的孤傲及不羁,真是实力与逼格兼具,我不禁感叹道。 13 | 14 | 西门吹Go人狠话不多,额头虽已见汗渍,但是也以相当快的速度完成,然后低调的站于一旁,只见他写道: 15 | "我的剑道追求朴实无华,重剑无锋,于2007年拜入谷歌门下,继承了实用性的优点,并于2012年光速出师,并于2016年获评江湖正榜TIOBE最佳实用性语言,花里胡哨于我没用,若你们想学,只要跟着我练几遍,即可入门“ 原来是个实用至上粉,嗯,听起来我这等驽钝之辈也可入门,不错,不错,我的心有些痒痒的。 16 | 17 | ## 发挥场景PK 18 | 既然是招生,哦比武,那肯定要谈谈自己的剑道适用面,二位大侠亦不能免俗,继续写道。 19 | 20 | "吾乃全能,上画星辰宇宙,下掘大地海洋。。。",突然一个烂番茄横空出现,砸在他面前,伴随着一声大吼:"说人话!!!", 叶孤Rust旋即改口道:"嗯,我的剑法 21 | 22 | - 非常适合写UI,无论是桌面UI或者是JS,都非常适合,目前相关的生态也在飞速发展中:[yew](https://yew.rs), [swc](https://swc.rs), [deno](https://deno.land),未来甚至一统前端江湖,也未尝不可,极致的安全和快就是我的剑道优势 23 | 24 | - 可以写Web层开发, 虽然难入门,但是安全性可以让你的应用运行的更稳定,性能可以帮助你节省大量服务器成本 25 | 26 | - 非常适合写中间件和基础服务, 安全和高性能,同时又拥有强大的抽象和表达能力,如果我说不适合写这些高性能组件,估计你自己都不相信 27 | 28 | - 非常适合系统开发,君不见这么挑剔毒嘴的linus,都把我纳入了linux内核之中,这份荣誉仅次于少林扫地僧C大师。同时也非常适合写系统工具、嵌入式,安全、高效、快速,简直就是完美的代名词 29 | 30 | - 不太适合企业级应用,目前Java大哥还是绝对的王者,在他的领域中,我打不过他 31 | 32 | 也不知扔番茄的是哪位高人,竟恐怖如斯,让如此高傲的叶孤Rust都不得不暂时屈服! 33 | 34 | 另一边的西门吹Go就利索的多,非常符合实用派(工具人)定位: 35 | - 写Web层,目前我是新生代绝对的王者,因为我的剑法简单、实用、速度也不错,你Rust拿头跟我比? (旁边的叶孤Rust只是哼哼两声,并没有过多反驳,但是眼中那不服之意也很明显,或许他觉得未来还是有一战之力?真是期待啊) 36 | 37 | - 我已经占领了容器和容器编排领域,运维工具上,谁与争锋?(没想到,他看上去这么低调,但是到了要表达自我的时候,竟然如此张狂,可怕可敬!) 38 | 39 | - 额,其它领域嘛,我可以试试,但是不能给你们保证效果 40 | 41 | 月光下,二位大侠长身玉立,或翩翩君子或沉稳如山,都是如此的迷人,一时让我竟不知道如何抉择,要不再等等看? 42 | 43 | ## 武功PK 44 | 论道结束,自然要手下见真章,但见二人打成一团,不分彼此,鸡飞狗跳,尘土飞扬,看来积怨真的已久,必须要大爆发一次方解恨。此情此景倒颇为符合静如处子,动若野狗的至高武功描述,看来二位对于大道均有所悟。 45 | 46 | 毕竟武功这块儿隔行如隔山,为了读者能看明白,从后面开始我们用大白话来说明(装逼太累了,还是正常写吧)。 47 | 48 | #### 叶孤Rust的武功特点 49 | 总结来说,花里胡哨,异常全面,总之你想要的啥都有: 50 | - 通过所有权系统实现无GC的同时,还不需要手动内存管理,同时避免各种野指针、数据竞争等头疼已久的内存安全问题 51 | - 各种零损耗的抽象,既有现代化语言的高级抽象,又有系统语言的高性能 52 | - 支持原生多线程并发,也支持`async/await`并发 53 | - 跨平台 54 | - 闪电般的性能,要不是我300hz的氪金咪咪眼,根本就看不清他的动作! 55 | - 代码可维护性很高,文档、安全、完善的工具链等 56 | - 难(个人猜测,难的原因就是啥都想要、啥都想追求完美,莫非叶孤Rust此人是一个既贪心又追求完美之人?我暗搓搓的想道) 57 | 58 | #### 西门吹go的武功特点 59 | 江湖惯例,重要的事情说三遍, 实用,实用,还是实用: 60 | - 有GC和运行时,因此不用操心内存管理 61 | - 简单易上手,3天上手不是闹着玩的(西门吹go的剑谱目测还没有叶孤Rust的一半厚) 62 | - 代码可维护性高,文档、完善的工具链等 63 | - 跨平台和交叉编译 64 | - 闪电般的编译速度 65 | - 还可以的性能(看看了西门吹go的额头汗水,可能他在重度舞剑时,还是有些吃力,还是叶孤Rust潇洒啊) 66 | 67 | 再谈谈一些详细的感受(大侠名字太长了,我先用最后一个字代替-_-): 68 | - Rust有设计非常完善的泛型,Go目前刚准备发布初步的泛型支持 69 | - Rust有设计非常完善的错误处理,Go目前还为人诟病:`if err != nil` 70 | - Go的异步编程极其简单,Rust的`async/awati`大概复杂10倍 71 | - 得益于各种高级特性,Rust可以写出函数式语言的简洁,Go嘛,比较啰嗦,但是可读性好 - 因为简单 72 | - 宏和编译器插件,使得Rust扩展性很好 73 | - Rust是显式接口,Go是隐式接口,前者写代码麻烦,后者读代码麻烦 74 | - Rust无GC也无需手动管理内存, Go有GC 75 | - Rust的包管理工具极其强大,Go的嘛,mu....至少有,要求那么多干嘛? 76 | - Rust的模式匹配极其强大,可以写出很多dreaming code 77 | - Rust的各种条件编译和条件依赖爽翻天 78 | - Rust可以做几乎能想到的所有性能优化场景,Go的性能优化局限性较大 79 | 80 | 不仅感叹道,二位大侠在各自的道上,钻研都极深,导致了两位存在巨大的分歧,最终站上了紫禁之巅。 81 | 82 | ## 速度PK 83 | 又到了喜闻乐见的速度PK环节,在码江湖,流行最广的一句话就是:不服?来比比速度,速度鄙视链存在于你能想到的一切地方,对就连速度鄙视链本身都有鄙视链,比如那个“那个速度野榜凭什么跟我比?垃圾”。 84 | 85 | #### 算法性能 86 | 因此,本次比拼的重头戏终于来临,先看一份江湖算法榜单对二位的速度评定: 87 | 88 | 89 | 从图中可以看出叶孤Rust的速度远快于西门吹Go(这个比拼的是两者摆好姿势全力以赴测试的结果,实际性能差距没有这么大). 90 | 91 | #### web框架性能 92 | 再来看看,web框架的对比: 93 | 94 | 上图分别是双方最常用的web框架,差距一目了然(公允的说,gin不是go最快的,但是是go框架中最常用的里面最快的,因此这个对比并不意味着极致性能的差距有这么大,例如fasthttp就很快,但是我们更倾向于它是一个http服务而非web框架) 95 | 96 | #### 搜索引擎性能 97 | Rust和Go都有自己的搜索引擎,它们都有一个共同的梦想:在码江湖中击败如日中天的lucene(ES使用的搜索引擎),那我们来看看三者的不严谨性能对比(因为搜索引擎性能对比项非常多,因此我们计算总体性能,然后以最慢的那个作为基数1,方便对比): 98 | 99 | 100 | 101 | 总之,从速度能来看,叶孤Rust的优势是全面领先的. 102 | 103 | ## 真正的王者 104 | 105 | 巅峰对决依然在继续,但我决定撤了,因为之前的烂番茄是我扔的。。。 106 | 107 | 而我想要收集的信息已最终收集完成,并且也选定了未来的方向,那你呢?谁是你心中真正的王者? -------------------------------------------------------------------------------- /2016/使用Go语言工作400天后的感受.md: -------------------------------------------------------------------------------- 1 | > 本文发表时间:2016年3月28号 2 | 2016-07-07最新修改,添加GC演变说明。 3 | 4 | 5 | 我在2011年就听说了Go并学习了一段时间,坦白的说,那时候对Go是比较无感的,因为并 6 | 没有看到Go特别亮眼的地方,可能和我使用C、Erlang、Java有关,这三种语言可以写高性能、高 7 | 并发、高可用的服务;包含了面向过程、面向并发、面向对象的思想,我觉得我并不需要再学习 8 | Go,何况那个时候好像也没宣传的那么优秀。 9 | 10 | 一切都发生在418天前,因为工作的需要,我开始写Go了,本来预期是一段压抑、蛋疼的 11 | 旅程(被迫使用其它语言的同学可能都会有此感受),结果收获了非一般的惊喜、非一般的效率、 12 | 非一般的开发体验。 13 | 14 | 15 | 16 | 先用几个词来总结一下我的感受:简洁、标准、组合、创造力、生产力! 17 | 特别是生产力,由于Go优秀的标准库、完美的代码安全、全面的三方库、完善的测试机 18 | 制、完善的标准管理工具,使用Go给我们小组带来了极大的生产力。 19 | 20 | 总之,在使用过的语言中,除了Erlang外,我还没在其它语言上感受过如此之高的生产 21 | 力(可能笔者的见识比较狭隘,请轻喷;) )。 最近思考了很多,也横向对比了一些,Go 22 | 具有高生产力的原因如下: 23 | 24 | ### 不一定需要IDE 25 | 我主要使用的开发工具一个是liteIDE,另外一个就是Vim,特别是在自己实验一些好 26 | 的想法时,Vim用的更多。Go开发时的简洁和对其它第三方组件无依赖性,决定了Go可以在任 27 | 何地方写,任何地方运行。曾经我尝试过用Vim去写Java,结果无奈的放弃了这个想法。对于很 28 | 多环境下来说,不依赖IDE是很重要的。 29 | 30 | 31 | ### 极快的编译构建速度 32 | 以前编译C++,Erlang程序,离开座位去喝杯茶,吹吹牛是很正常的事。但是自从用 33 | 了Go,这种闲逛行为貌似变得更像是在打酱油,因为一个完整的项目最多仅仅需要数秒到数十秒!! 34 | 这个对于初接触Go的同学来说,很神奇;这个对于提高程序员和项目组的生产力来说,很重要! 35 | 36 | 37 | ### 极其优秀的标准库 38 | 使用Go的标准库也是非常享受的。从标准库的设计就能看得出,创建Go语言的那几位图 39 | 灵奖大神,不仅仅有异常深厚的理论储备,还有着非常丰富、非常广泛的生产实践经验,因为Go 40 | 语言解决了很多真实系统设计中,常常会遇到的问题。例如,可以非常简单的使用http服务、为 41 | socket设置读写超时、统计服务器信息、单元测试压力测试、多样化的性能测试等等,所有的这些 42 | 让写出高可用、高性能的服务简单了数倍! 43 | 44 | ###多样化的内置数据结构 45 | Map是我使用非常多的数据结构-大概10-20%的代码都使用了Map。因此作为一个静态 46 | 语言,Go把Map内置为标准的数据类型,是非常棒的,大大简化了使用步骤,提升了性能。 47 | Go语言中的动态数组-slice,对于实际使用中也是非常关键的,配合内置函数append 48 | 和range,slice的使用灵活度令人难以置信。 49 | 50 | 通过这些内置数据结构和内置函数,Go程序员在处理各种数据结构时就掌握了既标准 51 | 又强大的武器,也是Go语言之所以简洁的原因之一。 52 | 53 | 54 | ### 统一的代码格式规范 55 | 不知道大家有没有做过项目总结,根据我多年的经验,项目中有一些最浪费时间的地方, 56 | 其中一个地方就是代码格式问题。在各大论坛、各大语言板块都充斥着括号位置、空格、缩进的 57 | 讨论,这些讨论在Go语言中都可以打住了。go fmt可以自动帮我们处理好这些问题,让我们的每 58 | 个源码文件的代码格式都是统一规范的。在实际应用中,这个特性可以大大提高团队的开发效率: 59 | 60 | 最简单的,写代码时,你再也不用在=两边,函数参数的后面等地方加上空格了,只要输入 61 | go fmt myProject,一切问题都烟消云散。 62 | 63 | 64 | ### 只有一个的二进制可执行文件 65 | 使用过Go语言的同学,应该都知道程序编译后仅仅生成一个可执行的二进制文件,非常 66 | 方便,但是我仍然认为我们大大低估了这个语言特性的好处,特别是在项目的后续开发、维护过程 67 | 中。现在社会,网络带宽和存储设备都不再昂贵,但是部署、配置、管理、升级各种软件服务的 68 | 代价还是较为高昂的。 69 | 70 | 如果大家自己管理过多个系统、集群,就会知道:升级或安装依赖库、JDK等 71 | 组件时,总会碰到特别痛苦的时候,几天的加班加点搞定一个问题都有可能!而纯Go写的程序则不 72 | 一样,不管项目处于在哪个阶段,我们需要的也仅仅是一个binary文件,对于提高生产力来说,这个 73 | 无疑是非常巨大的。 74 | 75 | 76 | ### 完善的标准测试框架 77 | Go的测试框架是非常棒的,和其它语言不同的是:我们仅仅使用Go语言自带的标准化测试 78 | 框架,就可以很好实现各种测试功能:单元,黑盒,白盒,压力测试等等。除了标准测试外,还有很 79 | 多第三方的优秀测试框架,但是要记住的是:最好只使用一套测试框架,这样能保持项目的完整性和 80 | 一致性。就笔者的经验来看,目前的标准测试框架足够用了,如果需要单步断点可以看这里。 81 | 82 | 83 | 84 | ### 性能分析 85 | Go语言自带标准的性能分析工具,包括CPU、内存、阻塞操作(http请求,数据库请求, 86 | time.Sleep等)在内的都可以测量,influxdb就是利用这些工具实现了数据库的常量时间访问。 87 | 88 | 89 | ### 那么对于网上常常被喷的那些所谓的Go的‘问题’呢? 90 | 91 | ##### GC 92 | Go语言从1.3开始,GC的改进就在持续进行中,1.5中,GC改成三色mark and 93 | sweep后,性能得到了极大的提高,在1.6中,对于内存占用很高、对象很多的系统又进行了一 94 | 次大优化,可以说,现在的Go程序一般不存在GC问题,除非:分布式缓存、数据库、消息推送 95 | 这样海量对象的场景,这时候需要自己做好可复用对象管理、合并部分小对象、将一些小型的struct 96 | 结构定义为值类型而不是指针类型、还可以从业务层面和架构层面进行优化设计等等。 97 | 98 | 不过,虽然说不太需要担心GC问题,但是大家还是最好学习一下Go的GC原理和 99 | 内存管理,理解底层原理对于很多时候快速的定位解决问题是大有裨益的。 100 | 101 | PS: 2016年7月7日补充,昨天看到一个使用GO作为核心语言的国外大型视频直播 102 | 网站分享了关于GC的演变史,直接说结果吧:1.2版本的时候,GC STW时间是2秒左右,到了1.6版本 103 | 再结合一些GC参数调优,达到了30-50ms的级别,目前的1.7版本由于将一些STW工作放到了并发去 104 | 执行,因此GC时间到了1m级别,整整上千倍的提升,基本上延时已经不是问题了。后续版本将进一步 105 | 改进STW问题,同时大大改善GC的吞吐率。 106 | 107 | ##### 泛型 108 | 其实刚从其它语言过来时,我或多或少会在写Go代码时用到模版的思想,但是后 109 | 面发现,这样会让代码更难维护,因此,写Go代码就要用Go的哲学和思想。 110 | 111 | 写过C++代码的都知道:抽象是一把双刃剑,过度的抽象就可能存在潜在的问题。 112 | 在我看来,Go所具有的OO思想,是简洁的,是纯粹的,是组合的核心思想。 113 | 114 | 编程现在就是化繁为简,充分利用组合的思想(也是Unix程序设计提倡的思想),可 115 | 以让我们的开发模型和项目代码大大简化、返璞归真 。Go的OO思想充分体现了三位大神作者的 116 | 编程和设计功底,估计也只有Unix的作者才能写出这么完美的OO实现了! 117 | 118 | ##### GOPATH 119 | 对于新手来说,GOPATH很难理解清楚,我当时刚接触时,也是迷糊了一段时间。 120 | 现在则是混合了两种方式,第一种,在默认的GOPATH下的src中存放项目;第二种,自定义一个 121 | 临时的GOPATH,然后存放项目。后者的独立性更好,但是全局使用go build ,go test等命令时就 122 | 会有问题,比如手动导出GOPATH才行: 123 | 124 | ```bash 125 | mkdir sunface 126 | cd sunface-bench/ 127 | export GOPATH=$PWD 128 | go get -v github.com/otoolep/bleve-bench 129 | go install github.com/otoolep/bleve-bench/cmd/bench/. 130 | $GOPATH/bin/bench -h 131 | ``` 132 | 133 | ### Go语言的总结 134 | Go从出身来看更像学院派语言,但是实际上它是彻头彻尾的工程语言,特别是它很适合 135 | 我们团队,很适合基础架构、中间件、云计算平台、PasS平台的开发,因为说它是云时代的未来第 136 | 一语言,丝毫不为过。 137 | 138 | 139 | 就我这边的项目而言,分布式日志平台、搜索系统、消息推送等项目中,避免了JVM或者 140 | EVM的麻烦,实在是太美好了,所以,如果条件允许,我会完全选择纯Go来设计实现一个系统,也 141 | 许这种美妙只有真正深入用过的人才能体会 ;) 142 | 143 | 144 | 145 | ### 广告时间 146 | 欢迎大家加入Golang隐修会,QQ群894864,欢迎加入这个大 147 | 家庭,这里有所有你想要的,而且热心大神很多哦! 148 | 149 | -------------------------------------------------------------------------------- /2021/Q4/lifetime-too-long02.md: -------------------------------------------------------------------------------- 1 | # 生命周期过大-02 2 | 3 | 继上篇文章后,我们再来看一段**可能**涉及生命周期过大导致的无法编译问题: 4 | ```rust 5 | fn bar(writer: &mut Writer) { 6 | baz(writer.indent()); 7 | writer.write("world"); 8 | } 9 | 10 | fn baz(writer: &mut Writer) { 11 | writer.write("hello"); 12 | } 13 | 14 | pub struct Writer<'a> { 15 | target: &'a mut String, 16 | indent: usize, 17 | } 18 | 19 | impl<'a> Writer<'a> { 20 | fn indent(&'a mut self) -> &'a mut Self { 21 | &mut Self { 22 | target: self.target, 23 | indent: self.indent + 1, 24 | } 25 | } 26 | 27 | fn write(&mut self, s: &str) { 28 | for _ in 0..self.indent { 29 | self.target.push(' '); 30 | } 31 | self.target.push_str(s); 32 | self.target.push('\n'); 33 | } 34 | } 35 | 36 | fn main() {} 37 | ``` 38 | 39 | 报错如下: 40 | ```console 41 | error[E0623]: lifetime mismatch 42 | --> src/main.rs:2:16 43 | | 44 | 1 | fn bar(writer: &mut Writer) { 45 | | ----------- 46 | | | 47 | | these two types are declared with different lifetimes... 48 | 2 | baz(writer.indent()); 49 | | ^^^^^^ ...but data from `writer` flows into `writer` here 50 | ``` 51 | 52 | WTF,这什么报错,之前都没有见过,而且很难理解,什么叫`writer`滑入了另一个`writer`? 53 | 54 | 别急,我们先来仔细看下代码,注意这一段: 55 | ```rust 56 | impl<'a> Writer<'a> { 57 | fn indent(&'a mut self) -> &'a mut Self { 58 | &mut Self { 59 | target: self.target, 60 | indent: self.indent + 1, 61 | } 62 | } 63 | ``` 64 | 这里的生命周期定义说明`indent`方法使用的。。。等等!你的代码错了,你怎么能在一个函数中返回一个新创建实例的引用?!!最重要的是,编译器不提示这个错误,竟然提示一个莫名其妙看不懂的东东。 65 | 66 | 行,那我们先解决这个问题,将该方法修改为: 67 | ```rust 68 | fn indent(&'a mut self) -> Writer<'a> { 69 | Writer { 70 | target: self.target, 71 | indent: self.indent + 1, 72 | } 73 | } 74 | ``` 75 | 76 | 怀着惴惴这心,再一次运行程序,果不其然,编译器又朝我们扔了一坨错误: 77 | ```console 78 | error[E0308]: mismatched types 79 | --> src/main.rs:2:9 80 | | 81 | 2 | baz(writer.indent()); 82 | | ^^^^^^^^^^^^^^^ 83 | | | 84 | | expected `&mut Writer<'_>`, found struct `Writer` 85 | | help: consider mutably borrowing here: `&mut writer.indent()` 86 | ``` 87 | 88 | 哦,这次错误很明显,因为`baz`需要`&mut Writer`,但是咱们`writer.indent`返回了一个`Writer`,因此修改下即可: 89 | ```rust 90 | fn bar(writer: &mut Writer) { 91 | baz(&mut writer.indent()); 92 | writer.write("world"); 93 | } 94 | ``` 95 | 96 | 这次总该成功了吧?再次心慌慌的运行编译器,哐: 97 | ```console 98 | error[E0623]: lifetime mismatch 99 | --> src/main.rs:2:21 100 | | 101 | 1 | fn bar(writer: &mut Writer) { 102 | | ----------- 103 | | | 104 | | these two types are declared with different lifetimes... 105 | 2 | baz(&mut writer.indent()); 106 | | ^^^^^^ ...but data from `writer` flows into `writer` here 107 | ``` 108 | 109 | 可恶,还是这个看不懂的错误,仔细检查了下代码,这次真的没有其他错误了,只能硬着头皮上。 110 | 111 | 大概的意思可以分析,生命周期范围不匹配,说明一个大一个小,然后一个`writer`中流入到另一个`writer`说明,两个`writer`的生命周期定义错了,既然这里提到了`indent`方法调用,那么我们再去仔细看一眼: 112 | ```rust 113 | impl<'a> Writer<'a> { 114 | fn indent(&'a mut self) -> Writer<'a> { 115 | Writer { 116 | target: self.target, 117 | indent: self.indent + 1, 118 | } 119 | } 120 | ... 121 | } 122 | ``` 123 | 好像有点问题,`indent`返回的`Writer`的生命周期和外面的`Writer`的生命周期一模一样,这很不合理,一眼就能看出前者远小于后者,那我们尝试着修改下`indent`: 124 | ```rust 125 | fn indent<'b>(&'b mut self) -> Writer<'b> { 126 | Writer { 127 | target: self.target, 128 | indent: self.indent + 1, 129 | } 130 | } 131 | ``` 132 | 133 | Bang! 编译成功,不过稍等,回想下生命周期消除的规则,我们还可以实现的更优雅: 134 | ```rust 135 | fn bar(writer: &mut Writer) { 136 | baz(&mut writer.indent()); 137 | writer.write("world"); 138 | } 139 | 140 | fn baz(writer: &mut Writer) { 141 | writer.write("hello"); 142 | } 143 | 144 | pub struct Writer<'a> { 145 | target: &'a mut String, 146 | indent: usize, 147 | } 148 | 149 | impl<'a> Writer<'a> { 150 | fn indent(&mut self) -> Writer { 151 | Writer { 152 | target: self.target, 153 | indent: self.indent + 1, 154 | } 155 | } 156 | 157 | fn write(&mut self, s: &str) { 158 | for _ in 0..self.indent { 159 | self.target.push(' '); 160 | } 161 | self.target.push_str(s); 162 | self.target.push('\n'); 163 | } 164 | } 165 | 166 | fn main() {} 167 | ``` 168 | 169 | 至此,问题彻底解决,太好了,我感觉我又变强了。可是默默看了眼自己的头发,只能以`哎~`一声叹息结束本章内容。 -------------------------------------------------------------------------------- /2021/Q4/lifetime-too-long.md: -------------------------------------------------------------------------------- 1 | # 生命周期声明的范围过大 2 | 3 | 在大多时候,Rust的生命周期你只要标识了,即可以通过编译,但是总是存在一些情况,会导致编译无法通过,本文就讲述这样一种情况:因为生命周期声明的范围过大,导致了编译无法通过,希望大家喜欢 4 | 5 | ## 例子1 6 | ```rust 7 | struct Interface<'a> { 8 | manager: &'a mut Manager<'a> 9 | } 10 | 11 | impl<'a> Interface<'a> { 12 | pub fn noop(self) { 13 | println!("interface consumed"); 14 | } 15 | } 16 | 17 | struct Manager<'a> { 18 | text: &'a str 19 | } 20 | 21 | struct List<'a> { 22 | manager: Manager<'a>, 23 | } 24 | 25 | impl<'a> List<'a> { 26 | pub fn get_interface(&'a mut self) -> Interface { 27 | Interface { 28 | manager: &mut self.manager 29 | } 30 | } 31 | } 32 | 33 | fn main() { 34 | let mut list = List { 35 | manager: Manager { 36 | text: "hello" 37 | } 38 | }; 39 | 40 | list.get_interface().noop(); 41 | 42 | println!("Interface should be dropped here and the borrow released"); 43 | 44 | // this fails because inmutable/mutable borrow 45 | // but Interface should be already dropped here and the borrow released 46 | use_list(&list); 47 | } 48 | 49 | fn use_list(list: &List) { 50 | println!("{}", list.manager.text); 51 | } 52 | ``` 53 | 运行后报错: 54 | ```console 55 | error[E0502]: cannot borrow `list` as immutable because it is also borrowed as mutable // `list`无法被借用,因为已经被可变借用 56 | --> src/main.rs:40:14 57 | | 58 | 34 | list.get_interface().noop(); 59 | | ---- mutable borrow occurs here // 可变借用发生在这里 60 | ... 61 | 40 | use_list(&list); 62 | | ^^^^^ 63 | | | 64 | | immutable borrow occurs here // 新的不可变借用发生在这 65 | | mutable borrow later used here // 可变借用在这里结束 66 | ``` 67 | 68 | 这段代码看上去并不复杂,实际上难度挺高的,首先在直觉上,`list.get_interface()`借用的可变引用,按理来说应该在这行代码结束后,就归还了,为何能持续到`use_list(&list)`后面呢? 69 | 70 | 这是因为我们在`get_interface`方法中声明的`lifetime`有问题,该方法的参数的生明周期是`'a`,而`List`的生命周期也是`'a`,说明该方法至少活得跟`List`一样久,再回到`main`函数中,`list`可以活到`main`函数的结束,因此`list.get_interface()`借用的可变引用也会活到`main`函数的结束,在此期间,自然无法再进行借用了。 71 | 72 | 要解决这个问题,我们需要为`get_interface`方法的参数给予一个不同于`List<'a>`的生命周期`'b`,最终代码如下: 73 | ```rust 74 | struct Interface<'b, 'a: 'b> { 75 | manager: &'b mut Manager<'a> 76 | } 77 | 78 | impl<'b, 'a: 'b> Interface<'b, 'a> { 79 | pub fn noop(self) { 80 | println!("interface consumed"); 81 | } 82 | } 83 | 84 | struct Manager<'a> { 85 | text: &'a str 86 | } 87 | 88 | struct List<'a> { 89 | manager: Manager<'a>, 90 | } 91 | 92 | impl<'a> List<'a> { 93 | pub fn get_interface<'b>(&'b mut self) -> Interface<'b, 'a> 94 | where 'a: 'b { 95 | Interface { 96 | manager: &mut self.manager 97 | } 98 | } 99 | } 100 | 101 | fn main() { 102 | 103 | let mut list = List { 104 | manager: Manager { 105 | text: "hello" 106 | } 107 | }; 108 | 109 | list.get_interface().noop(); 110 | 111 | println!("Interface should be dropped here and the borrow released"); 112 | 113 | // this fails because inmutable/mutable borrow 114 | // but Interface should be already dropped here and the borrow released 115 | use_list(&list); 116 | } 117 | 118 | fn use_list(list: &List) { 119 | println!("{}", list.manager.text); 120 | } 121 | ``` 122 | 123 | 当然,咱还可以给生命周期给予更有意义的名称: 124 | ```rust 125 | struct Interface<'text, 'manager> { 126 | manager: &'manager mut Manager<'text> 127 | } 128 | 129 | impl<'text, 'manager> Interface<'text, 'manager> { 130 | pub fn noop(self) { 131 | println!("interface consumed"); 132 | } 133 | } 134 | 135 | struct Manager<'text> { 136 | text: &'text str 137 | } 138 | 139 | struct List<'text> { 140 | manager: Manager<'text>, 141 | } 142 | 143 | impl<'text> List<'text> { 144 | pub fn get_interface<'manager>(&'manager mut self) -> Interface<'text, 'manager> { 145 | Interface { 146 | manager: &mut self.manager 147 | } 148 | } 149 | } 150 | 151 | fn main() { 152 | let mut list = List { 153 | manager: Manager { 154 | text: "hello" 155 | } 156 | }; 157 | 158 | list.get_interface().noop(); 159 | 160 | println!("Interface should be dropped here and the borrow released"); 161 | 162 | // this fails because inmutable/mutable borrow 163 | // but Interface should be already dropped here and the borrow released 164 | use_list(&list); 165 | } 166 | 167 | fn use_list(list: &List) { 168 | println!("{}", list.manager.text); 169 | } 170 | ``` -------------------------------------------------------------------------------- /2018/Q3/如何从零开始设计一款好的技术开源产品.md: -------------------------------------------------------------------------------- 1 | # 如何从零开始设计一款好的技术开源产品 2 | 3 | > 本文发表时间:2018 年 7 月 13 号 4 | > 分类于[技术管理](../../index/manager.md) 5 | 6 | ## 前言 7 | 技术男擅于想象也擅于幻想,类如在全球最大同性交友平台上,打造你的最强兵器,出尽风头,博得更多的同性友谊。那么问题来了,那么大的用户群体,你怎么才能脱颖而出,笔者自己也思考了很久,总结出一套可行的方案。 8 | 9 | ## 七种兵器 10 | > 剑之灵动,刀之雄厚,七种兵器如果选择就够侠者喝一壶了。同时用千年寒铁和普通铁矿打造出的兵器肯定是云泥之别的,但是千年寒铁肯定是极其稀有的。 11 | 12 | ### 各种XXX攻略 13 | 君不见最近Github上Star上涨很快的大部分都是XXX攻略,例如架构师知识图谱,面试知识图谱等等,简直是开挂般的存在,但是本文的标题是‘技术开源产品’,攻略显然不是产品,这里就排除在外了。 14 | 15 | ### 什么是具有可行性的产品 16 | 拥有特定的目标和用户群体,能产生出相对独一无二的价值,就可以认为是一款具有可行性的产品了,这里有三个对象:产品目标、用户群体、产品创新,下面我们来一一分析,该怎么选材 17 | 18 | ### 用户群体 19 | 因为咱们已经是技术开源产品了,用户群体无非就是开发或者测试,但是考虑到github上绝大部分都是开发,所以首先排除测试用户,至于开发,可以进一步细分为前端、后端、运维,产品对应的用户群体越大,自然收获的star会更多。 20 | 21 | 众所周知,前端的开发群体是最多的,因为作为开发或多或少都要会一些前端。在前端领域,javascript的开源产品,获取star的速度明显是鹤立鸡群,藐视一切英雄好汉的,如果你具有较好的js和nodejs水准,就可以倾向于打造面向js用户群体的产品了。 22 | 23 | 再来谈谈后端,后端又分为面向特定领域:云计算、区块链等和面向特定语言:python、go、java、rust等,那该怎么选择呢? 24 | 25 | 首先是,一定站在风口上,猪都能起飞,何况咱们这些高智商人群,因此特定领域的风口目前来看就是k8s+docker生态,区块链生态,机器学习生态等,至于特定语言,go和rust是有很大优势的,因为这两门语言目前在开源社区非常受欢迎,附带着相关的库也会变得容易获得青睐。 26 | 27 | **总结:尽量选择处于技术风口的技术领域和编程语言,让你的目标群体放得更大** 28 | 29 | 30 | ### 产品目标 31 | 产品的目标首先来说就是产品的类型 32 | - 一站式平台 33 | - 工具类服务 34 | - 编程框架库 35 | 36 | 其次就是产品难易度,这个也很重要,你要充分预估你的时间能完成的项目是什么样的?相对简单的产品,同时还更容易让社区中的同性伙伴们参与进来,但是换来的代价就是你面对的可能是一片红海,这个很自然,简单的东西做得人自然更多。 37 | 38 | **总结:首先根据自身的时间/技术能力选择一个最适合的产品类型,同时要考虑到两点: 未来社区的参与度和用户群体的潜在大小** 39 | 40 | ### 产品创新 41 | 要知道,任何一个产品都不太可能是完全创新,因此和你的产品重叠的产品可能已经存在很多,那么你的产品凭什么突围而出呢?是时候祭出我们的尚方宝剑了:微创新。 42 | 43 | 微创新的概念这里就不谈了,我谈谈几个点: 44 | - 功能上的创新:完善、优化等 45 | - 使用体验方面的创新:举个例子,区块链火了后,P2P网络也迅速火了,这个时候ipfs把自己的p2p技术栈抽象了出来,形成了一个库:libp2p,但是!实在是太tmd不好用了,谁用谁熬夜! 这个时候,另外一个主打产品体验的p2p库横空出世,简直完爆libp2p,尽管功能上还不完善,稳定性上也达不到生产级别,但是在可以预期的未来,这款产品在市场上肯定要大火的 46 | - 生态的创新:有些产品自成生态,啥都自己来;有些产品选择三方生态,设定好基本的产品形势,让大家都能参与进来 47 | - 文档和官网的创新:这个很重要!有好的官网和文档,对比不好的,完全两码事,对了你还需要一个和产品名字相同的域名,看上去更专业 48 | - 可维护性的创新:有些产品天生就难以维护,让后来者欲生欲死 49 | 50 | **总结:一定要考虑清楚产品的微创新是什么,通过创新给用户带来的价值是什么?千万不要自我觉得良好** 51 | 52 | 53 | #### 用户思维 54 | 如果你是XX用户,你需要什么功能;如果你是一个小白用户,该怎么使用这款产品。简而言之,要换位思考,换位思考这个词大家都知道,但是又有几个人真正知道,你做的产品是为你自己虚拟的用户打造的还是为真是用户打造的?值得深思。 55 | 56 | 57 | 58 | ## 独孤九剑 59 | > 武器在手,天下我有,哈哈!等等,大侠,你这是?野球拳??! 60 | 61 | 对于绝大多数程序员来说,想到了做什么后,紧接着就是简单思考一番,就是直接开撸了,但是大侠们,你们是不是略过了很重要的一步?你还没有学剑谱呢!因此产品设计是极其重要的一步,否则做到后面,重构或者目标偏移都是很正常的事情。 62 | 63 | ### 设定产品价值观(产品哲学,英文Philosophy) 64 | 大家应该都听说过阿里的六脉神剑价值观:客户第一、团队合作、拥抱变化、诚信、激情、敬业。 65 | 66 | 从中可以看出,价值观对于公司行为和员工行为的导向有着极其重要的作用。同样的,产品价值观也是如此,我们做产品时,任何需求的变动,任何体验的优化都应该在产品价值观的范畴之内。 67 | 68 | 例如一个新出的小众语言pony,它的价值观就很简单: 69 | > 让事情能够简单、高效的完成 70 | 71 | 根据这个价值观,它分解了几个子价值观,一起来看看: 72 | - 正确性 73 | 事情能完成的前提条件是它是正确的, 一切功能特性的添加和修改首先要保证的就是正确性 74 | - 性能 75 | 除了正确性之外,最重要的就是性能, 有损性能的语言特性和功能均不予以添加 76 | - 简洁 77 | 因为简洁只排到了第三名,为了正确性和性能是可以牺牲简洁性的,与此相比,另一门很流行的语言go,就把简洁性排在了性能前面,因此为了简洁可以稍微牺牲性能 78 | 79 | 从上面看得出,产品价值观会在方方面面指导我们的产品特性和功能该怎么添加、修改,一旦了有边界,产品就会按照既定的轨道前行,最终很好的到达终点。 80 | 81 | ### 在价值观基础上分解产品总的目标,形成几个子目标 82 | 例如,要开发一款消息平台,目标用户是外部的用户,包括了浏览器、移动客户端、物联网客户端等,那么这就是我们的产品总目标,该怎么分解为子目标呢? 83 | 84 | **注意!这里是产品目标,尽量不要带上技术色彩** 85 | 1. 我们需要提供外部用户连接产品并发送和消费消息的能力 86 | 2. 消息应该是有序的,可持久化的,可从任意位置开始遍历 87 | 3. 消息应该尽可能快的送到用户手中 88 | 4. 要支持单播、广播等方式,让消息推送的策略更加灵活 89 | 5. 消息要能被追踪,通过消息ID可以对消息进行可视化 90 | 6. 完善的数据统计 91 | 7. 通过管理后台进行管理操作 92 | 8. 需要做业务隔离,业务之间彼此互不影响 93 | 94 | 这些产品目标加起来,就能形成一个可用的消息平台了,但是到了这一步我们无法进行开发,同时这些需求也太大了,下一步,我们需要根据这些目标对产品做一下技术架构。 95 | 96 | ### 技术架构 97 | 整体架构可以大概分为三层,客户端层,服务器逻辑层,数据存储层。 98 | 99 | 外部用户要连接产品并进行操作,就需要提供通信协议和客户端SDK 100 | 101 | #### 通信协议 102 | 然后客户端和服务器通信肯定要有一套协议,按照目前的标准,选择MQTT是合理的选择。MQTT是应用协议,那网络协议用什么承载呢?TCP、Websocket、Http?按照我们的使用场景,可能都需要支持。 103 | 104 | #### 客户端SDK 105 | 再来看客户端,我们是否需要SDK?因为MQTT协议本身表达能力有限,如果要做复杂的逻辑,可能需要基于MQTT封装一层自定义协议,那这个时候就需要客户端的SDK,常用的有安卓、IOS、Javascript、Go、python等。 106 | 107 | 消息需要有序、持久和遍历,那么就需要提供数据存储层,同时我们还是一个开源项目,在产品价值观中,性能肯定是很靠前的,所以我们要重视性能。 108 | 109 | ### 数据存储层 110 | 这个时候选择mysql等关系数据库就不慎可靠了(注意!如果是给公司开发的内部项目,用户较少,是可以考虑Mysql的,更加方便);hbase很有名,性能也还可以,还能水平扩展,但是维护成本高,遇到问题未必hold住。因此Nosql成为比较自然的选择,例如foundationDB和cassandraDB。同时,为了测试方便,而且有些用户不需要持久化消息,那需要提供一种基于内存的存储模式。 111 | 112 | 113 | 剩下的功能,包括后台管理都应该在服务器逻辑层了,具体得就不再细讲了,大家有兴趣可以自己私下尝试,或者看看[MeQ消息平台](http://meq.io) 114 | 115 | 116 | **对于产品价值观和目标、技术架构,我们可以写到产品的首页Readme.md中,让所有用户看到。** 117 | 118 | ### 设定里程碑 119 | 有了产品目标、技术架构还不够,还需要为开发过程设定里程碑,在某个阶段我们都应该提供一个可交付的产品,要有详细的产品交付目标和日期,因此这些就是一个一个里程碑,对应敏捷项目管理里,类似一个一个迭代。 120 | 121 | 然后把这些里程碑输入到项目issues中的milestone里,设定好截止日期。 122 | 123 | ### 需求分解 124 | 最后,就是需求分解了,基于产品目标和技术架构,分解出一个一个详细的需求,每个需求都不要太大,以1-5天可以完成为准,然后把这些需求录入到issues中,再指定到具体的里程碑。 125 | 126 | 至此,我们的产品就完成了设计的过程,剩余的就是开发了,相信这个也是大家最熟悉的部分,就不赘述了。 127 | 128 | 129 | 130 | ## 总结 131 | 我相信,任何事务都有方法论可以依循,就看我们能不能根据实际情况,正确的去发现和运用这些方法了,而且有了方法后,一定要落成文档,让更多的人可以复制你的经验,更快的走向成功。 -------------------------------------------------------------------------------- /2022/Q1/highlights-of-rust.md: -------------------------------------------------------------------------------- 1 | # 为何又来了一门新语言? 2 | 为何需要一个新语言?简而言之,因为还缺一门无GC、**性能高**、**工程性强**、语言级安全性以及能同时得到工程派和学院派认可的语言,而Rust算是这样的语言。你也可以回忆下熟悉的语言,看是不是有另外一门语言可以同时满足这些需求: ) 3 | 4 | 而Rust最为人诟病的点,也就一个:学习曲线陡峭,其实严格来说,当语言生态起来后,这个不算问题。 5 | 6 | ## 缓解内卷 7 | 说Rust作为新语言会增加内卷,其实恰恰相反,Rust可以缓解内卷。为何不说C++内卷说Java、Python、JS内卷?不就是后几个相对简单、上手容易嘛?而Rust怎么看也是C++级别的上手难度O,O 8 | 9 | 其实从我内心不可告人的角度出发,并不希望Rust大众化,因为这样可以保饭碗、保薪资,还能更有行业内地位。但是从对Rust的喜爱角度出发,我还是希望能卷一些,但是。。。目前来看真的卷不动,现在全世界范围Rust的需求都大于供给,特别是优秀的Rust程序员更是难寻。 10 | 11 | 与Go语言相比,一个优秀的Rust程序员所需的门槛高得多,例如融汇贯通Rust语言各种中高级特性、闭着眼睛躺过各种坑、不用回忆无需查找就能立刻写出最合适的包/模块/方法、性能/安全/工程性的权衡选择信手拈来、深层性能优化易如反掌、异步编程小菜一碟,更别说Rust之外的操作系统、网络、算法等等相关知识。 12 | 13 | 所以,Rust可以缓解内卷,而不是增加内卷,可以说是程序员的福音,不再是被随意替换的螺丝钉。 14 | 15 | ## 效率 16 | 下面从三个角度来谈谈Rust的效率:学习、运行、开发。 17 | 18 | #### 学习效率 19 | 众所周知,Rust学习曲线陡峭,最初我对此说法还嗤之以鼻,随着不断的深入,我现在也很认可这个说法。Rust之难,不在于语言特性,这些都可以很容易学到,而在于: 20 | 21 | - 实践中如何融会贯通的运用 22 | - 遇到了坑时(生命周期、借用错误,自引用等)如何迅速、正确的解决 23 | - 大量的标准库方法记忆及熟练使用,这些是保证开发效率的关键 24 | - 心智负担较重,特别是初中级阶段时 25 | 26 | 好在,针对这些,目前国内有了一门非常全面的[Rust学习教程](https://github.com/sunface/rust-course)(非官方那本书),可以有效降低Rust的学习和使用门槛。 27 | 28 | #### 运行效率 29 | 得益于各种零抽象开销、深入到底层的优化潜力、优质的标准库和三方库实现,Rust具备非常优秀的性能,和C语言、C++是[一个级别](https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/rust.html)。 30 | 31 | 同时Rust有一个极大的好处:只要按照正确的方式使用Rust,无需性能优化,就能实现非常优秀的表现,不可谓不惊艳, 32 | 33 | 现在有不少用Rust重写的工具、平台都超过了原来用C、C++实现的版本,将老前辈拍死在沙滩上,俨然成为一种潮流~~ 34 | 35 | #### 开发效率 36 | Rust的开发效率可以先抑后扬来形容,在刚开始上手写项目时,你的开发速度将显著慢于go、java等语言,一旦开始熟悉标准库常用的方法,开发效率将大幅提升,甚至当形成肌肉记忆后,开发效率将不会慢于这些语言,而且原生就能写出高质量、安全、高效的代码,可以说中高级级Rust程序员就是高效程序员的代名词。 37 | 38 | ## 个人的好处 39 | 学习Rust对个人也有极大的好处。 40 | 41 | #### 成为更好的程序员 42 | 要学好Rust,你需要深入理解内存、堆栈、引用、变量作用域这些其它高级语言往往不会深入接触的内容,同时Rust会通过语法、编译器和clippy这些静态检查工具半帮助半强迫的让你成为更优秀的程序员,写出更好的代码。 43 | 44 | 同时,当你掌握了Rust,你自发性的就会想要去做一些更偏底层的事情,这些都可以帮助你更加了解操作系统、网络、性能优化等底层知识,也会间接或者直接的接触到各种算法、数据结构实现。 45 | 46 | 慢慢的,你就在成为那个更好的程序员,也是更优秀的自己。 47 | 48 | #### 增加不可替代性 49 | 语言难学,也有好处,一旦掌握,你将具备较强的不可替代性,不再是一个简单的工具人角色。看看现在内卷严重的Java,工具人有多少!一个人离职,另外一个人很快就能替补上。 50 | 51 | 当然,我不是说Rust会给公司带来这种隐形的维护成本,毕竟这其实是一种双赢,公司收获了更优秀的程序员(不可否认的是Rust程序员普遍确实水平更高,毕竟都是有很好的其它语言基础、也很有追求的自驱性人才),而你也收获了更稳定的工作环境甚至是更高的收入。 52 | 53 | ## 团队的好处 54 | 先不说安全、可靠性等对公司团队非常友好的特性,就说Rust程序只要能跑起来,那代码质量其实就是相当不错的,因为Rust编译器、clippy啥的实在是严师厉友,甚至有些鸡毛。 55 | 56 | 正因为这较高的质量下限,我们在review时并不用担心潜在的各种坑,因此可以实现快速的开发、review、merge流程。 57 | 58 | 而且由于Rust语言拥有异常强大的编译器和语言特性,因此Rust的代码天然就会比其它语言有更少的Bug,同时Rust拥有非常完善的工具链、最好的包管理工具,这些叠加在一起,决定了Rust非常适合大型开发者团队的协作开发。 59 | 60 | 也许Rust在开发速度上不是最快的,但是从开发 + 维护的角度来看,这个成本绝对是各个语言中最小的之一,当然如果你的公司就追求做出来能用就行,那Rust确实有些灰姑娘的感觉。 61 | 62 | 还有一点很重要,现在的Rust程序员往往拥有超出更出众的能力和学习自驱性,因此团队招到的人天然就保持了较高的底线,如果你有幸招到一个优秀的Rust程序员,那真是捡到宝了,他也会同时带动周围的人一起慢慢优秀(优秀的Rust程序员较好辨别,门槛低的语言就并没有那么好辨别)。总之,一个这样的程序员会给团队带来远超他薪资的潜在回报和长远收益。 63 | 64 | ## 开源 65 | 目前Rust的主战场是在开源上,Go的成功也证明了农村包围城市的可行性。 66 | 67 | - UI层开发:Rust的WASM发展的如火如荼, 隐隐有王者风范,在JS的基础设施领域Rust也是如鱼得水, 例如`swc`、`deno`等,同时`nextjs`也是押宝Rust,可以说Rust在前端的成功完全是无心插柳柳成荫。 68 | - 基础设施层, 数据库、搜索引擎、网络设施、云原生等都在出现Rust的身影,而且还不少 69 | - 系统开发,目前linux已经将Rust列为即将支持的内核开发语言,是即C语言后第二门支持内核开发的语言,不过刚开始将主要支持驱动开发 70 | - 系统工具,现在最流行的就是用Rust重写之前C、C++写的一票系统工具,还都获得了挺高的关注和很好的效果, 例如 sd, exa, ripgrep, fd, bat等 71 | - 操作系统, Rust在开发的操作系统现在有好几个,其中最有名的可能就是谷歌的fushia, Rust在其中扮演非常重要的角色 72 | - 区块链,目前Rust和Go可以说各领风骚,未来Rust可能会一统江湖 73 | 74 | 类似的还有很多,我们就不一一列举, 总之,现在有大量的项目在被用Rust重写,同时还有海量的项目在等待被重写,这些都是赚取star和认可的好机会,在其它语言杀成一片红海时,Rust还留了一大片蓝海等待大家的探索! 75 | 76 | 77 | 78 | ## 相比其他语言Rust的优势 79 | 由于篇幅有限,我们这里不会讲述详细的对比,就是简单介绍下Rust的优势。因此并不是说Rust就优于这些语言,大家轻喷:) 80 | 81 | #### Go 82 | Rust语言表达能力更强,性能更高,同时线程安全方面Rust也更强,不容易写出错误的代码,包管理Rust也更好,Go虽然在1.10版本后提供了包管理,但是目前还比不上Rust的。 83 | 84 | #### C++ 85 | 与C++相比,Rust的性能相差无几,但是在安全性方面会更优,特别是使用第三方库时,Rust的严格要求会让第三方库的质量明显高很多。 86 | 87 | 语言本身的学习,Rust的前中期学习曲线会更陡峭,但是对于未来使用场景和生态的学习,C++会更难、更复杂。 88 | 89 | #### Java 90 | 除了极少部分纯粹的数字计算性能,Rust的性能是全面领先于Java的,同时Rust占用内存小的多,因此实现同等规模的服务,Rust所需的硬件成本会显著降低。 91 | 92 | #### Python 93 | 性能自然是Rust完胜,同时Rust对运行环境要求较低,这两点差不多就足够抉择了,因为python和rust的彼此适用面其实不太冲突。 94 | 95 | ## 使用现状 96 | 97 | - AWS从2017年开始就用Rust实现了无服务器计算平台: AWS Lambda 和 AWS Fargate, 并且用Rust重写了Bottlerocket OS和AWS Nitro系统,这两个是弹性计算云(EC2)的重要服务 98 | - Cloudflare是Rust的重度用户,DNS、无服务计算、网络包监控等基础设施都都与Rust密不可分 99 | - Dropbox的底层存储服务完全由Rust重写,达到了数万PB的规模 100 | - Google除了在安卓系统的部分模块中使用Rust外,还在它最新的操作系统fuchsia中重度使用Rust 101 | - Facebook使用Rust来增强自己的网页端、移动端和API服务的性能,同时还写了Hack编程语言的虚拟机 102 | - Microsoft使用Rust为Azure平台提供一些组件,其中包括IoT的核心服务 103 | - Github和npmjs.com,使用Rust提供高达每天13亿次的npm包下载 104 | - Rust目前已经成为全世界区块链平台的首选开发语言 105 | - Tidb,国内最有名的开源分布式数据库 106 | 107 | 尤其值得一提的是,AWS实际上在押宝Rust,未来对Rust的使用可能很快会上升到**first-class**的地位。 108 | 109 | 110 | ## 总结 111 | 连续6年最受欢迎的语言当然不是浪得虚名。 无GC、**效率高**、**工程性强**、强安全性以及能同时得到工程派和学院派认可, 这些令Rust拥有了自己的特色和生存空间,社区的友善,生态的快速发展,大公司的重仓跟进,一切的一切都在说明Rust的未来。 112 | 113 | 当然,语言毕竟还是工具,我们不能神话它,但是可以给它一个机会,也许你最终能收获自己的真爱:) 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /2022/Q1/sustainability-with-rust.md: -------------------------------------------------------------------------------- 1 | # AWS 眼中的 Rust 语言 2 | 夸 Rust 语言的方式至少有 3000 种,但是从环保和可持续发展角度去夸的大家见过嘛?这不,AWS 就给我们带来了一篇非常精彩的文章,一起来看看。 3 | 4 | > 原文链接: https://aws.amazon.com/cn/blogs/opensource/sustainability-with-rust/ ,由于原文过长,译文进行了适当的精简(例如夸 AWS 的部分 - , -) 5 | 6 | Rust 是一门完全开源的语言,在 2015 年发布了 1.0 版本,但是在 2020 年 才迎来了真正的大发展契机,这是由于 Rust 的开源支持由 Molzilla 移交给了 [Rust基金会](https://foundation.rust-lang.org/),后者是由 亚马逊云AWS、谷歌、华为、微软和 Mozilla 共同创建的非营利性组织。 7 | 8 | 在 AWS,Rust 已经成为构建大规模基础设施的关键,其中一部分: 9 | 10 | - [Firecraker](https://firecracker-microvm.github.io/) 是开源的虚拟化技术,用于支持 AWS Lambda 和其它无服务器计算 11 | - 为 Amazon S3 、Amazon EC2、Amazon CloudFront 等提供关键性服务 12 | - 在 2020 年发布了 [Bottlerocket](https://aws.amazon.com/bottlerocket/) 一个基于 Linux 的容器化操作系统 13 | 14 | ## 云计算的能源效率 15 | 16 | 17 | 18 | *来源: IEA(2021),全球数据中心能源需求,2010-2022,https://www.iea.org/data-and-statistics/charts/global-data-centre-energy-demand-by-data-centre-type-2010-2022* 19 | 20 | 在全世界范围内,数据中心每年大概消耗 [200 太瓦时](https://www.iea.org/data-and-statistics/charts/global-data-centre-energy-demand-by-data-centre-type-2010-2022) 的能源,大概占全球能源消耗总量的 1%。 21 | 22 | 图中有几个有趣的点值得关注。首先,数据中心消耗的能源总量在过去 12 年间并没有显著的变化,这个其实蛮反直觉的,因为在这些年间,大数据、机器学习、边缘计算、区块链等等耗电大户发展速度都非常快。其次,虽然总量没有变,但是三种数据中心的能源占比分布发生了巨大的变化:超大规模(hyperscale)、传统服务、云计算。 23 | 24 | 总量反直觉变化的关键,其实就在于这些年能源效率得到了大幅提升,以及很多传统服务都迁移到了云计算上,而后者通过多租户、智能硬件资源利用、优化驱动和存储、更高效的冷却系统等等一系列措施大幅降低了能源的消耗。 25 | 26 | 尽管能源效率提升巨大,但是,依然存在两个问题。首先,当前的现状已经足够好了吗?例如让能源占比保持在 1% 的水准。其次,能源效率能否像过去一样继续快速提升?考虑到即将爆发的无人驾驶、机器人、机器学习领域,我们对此保持不乐观的态度,因为这个领域需要处理异常巨大的数据集。 27 | 28 | 29 | 30 | 当然,还能从能源本身入手来改善,例如 AWS 计划在 2025 年之前实现所有数据中心都使用可再生能源,但可再生能源并不意味着它没有环境影响,它依然需要 50 万英亩的太阳能板来生成 200 太瓦时的数据中心能源需求。总之,可再生能源不是一个设计上的概念,也不能替代设计上的优化,我们还需要其它方式。 31 | 32 | 这些其它方式包括:为非关键服务放松 `SLA` 的要求和资源供给优先级,利用虚拟化实现更长的设备升级周期,更多地利用缓存、设置更长的 TTL ,对数据进行分类并通过自动化的策略来实现尽可能及时的数据删除,为加密和压缩选择更高效的算法等等,最后但也最重要的是:**我们可以选择使用一门能源效率高的编程语言来实现基础服务和用户端的软件**。 33 | 34 | ## 编程语言的能源效率 35 | 对于开发者来说,估计没几个人能搞清楚自己服务的能源效率,那么该如何对比编程语言之间的能源效率呢?好在国外有专家做了相关的[学术研究](https://greenlab.di.uminho.pt/wp-content/uploads/2017/10/sleFinal.pdf)。 36 | 37 | 他精心设计了 10 个测试场景,然后衡量了 27 种不同的语言的执行时间、能源消耗、最大内存使用,最终得出了一个结论:C 和 Rust 在能源效率方面无可争议的击败了其它语言,事实上,它们比 Java 的能源效率高 50% , 比 python 高 98%。 38 | 39 | 40 | 41 | 其实,C 和 Rust 能效高很正常,但是比其它语言高出这么多就相当出乎意料了:根据上图的数据,采用 C 和 Rust,你将减少大概 50% 的能耗,这还是保守估计。 42 | 43 | 那么问题来了,既然这两个都可以,为何不选择历史更悠久的 C 语言呢?它的生态和社区都比 Rust 要更好。 好在,linux 创始人 Linus Torvalds 在 2021 年度的开源峰会上给出了答案:他承认,[使用 C 语言就像是拿着一把链锯在玩耍](https://thenewstack.io/linus-torvalds-on-community-rust-and-linuxs-longevity/),同时还说道:"C 语言的类型互动并不总是合乎逻辑的,以至于对于绝大多数人来说,这种互动都可能存在陷阱"。 44 | 45 | 作为侧面的证明,Rust 在去年下半年被纳入了 Linux 的官方开发语言,如果大家想知道其它的官方语言有哪些?我可以很轻松的列出一个列表:C 语言。。。这叫列表?这就结束了?其它的呢?别急,事实上,之前仅有 C 语言是官方支持的,可想而知 Rust 是多么的优秀才能从这么多竞争者中脱颖而出! 46 | 47 | 总之,Linus Torvalds 亲口说过 Rust 是他见过的第一门可以称之为**能很好的解决问题**的编程语言,Rust 在比肩 C 的效率的同时,还能避免各种不安全的风险,对于能耗来说,我们就能在节省一半能耗的同时还不用担心安全性。 48 | 49 | 多个分析报告也指出:七成以上的 C/C++ 的高风险 CVE 可以在 Rust 中得到有效的规避,且使用的是同样的解决方法!事实上,ISRG(非盈利组织,Let's Encrypt 项目发起者) 就有一个目标,希望能将所有对网络安全敏感的基础设施转移到 Rust 上。正在进行的项目包括: Linux 内核对 Rust 的进一步支持,以及将 curl 迁移到基于 TLS 和 HTTP 的 Rust 版本上。 50 | 51 | 52 | 53 | 不仅仅是能耗,上图中的中间列还提供了执行时间的测量指标,可以看出 Rust 和 C 在性能上也相当接近,而且两者都比其它语言也更快(其实对于 C++ 的结果,我个人不太理解,如果有大神阅读过之前提到的学术研究报告,欢迎给出答案 :D )。这意味着,当为了能效和安全选择了 Rust 后,我们依然可以获得类似 C 语言级别的性能,还是优化过的。 54 | 55 | ## Rust 成功案例 56 | 下面一起来看几个关于使用 Rust 后获得性能和成本双丰收的案例。 57 | 58 | #### Tenable 59 | 60 | 61 | *https://medium.com/tenable-techblog/optimizing-700-cpus-away-with-rust-dc7a000dbdb2* 62 | 63 | Tenable 是一家网络安全解决方案提供商,它提供了一套可视化工具,并通过一个 `sidecar agent` 来过滤采集到的指标数据。最开始,该公司使用 Javascript 作为主要语言,当业务开始快速增长时,性能降级的问题就不可避免的发生了。 64 | 65 | 因此,在经过一系列调研后,`Tenable` 最终决定使用 Rust 来重写该服务,以获取更好的性能和安全性。最终结果也没有让他们失望,在延迟方面获得了 50% 的提升,如上图所示。 66 | 67 | 除了用户体验的提升之外,在 CPU 和内存占用方面也提升巨大,这可以帮助他们节省大量的硬件和能耗成本: 68 | 69 | 70 | 71 | 72 | 73 | #### Discord 74 | 75 | 76 | 77 | *https://discord.com/blog/why-discord-is-switching-from-go-to-rust* 78 | 79 | Discord 最初使用 Python、Go、Elixir 来实现,但是随即他们发现其中一个关键的 Go 服务存在一些问题。该服务很简单,但是会出现较慢的尾延迟现象,原因是 Go 语言拥有 GC 垃圾回收,当对象在快速创建和释放时,GC 会较为频繁的运行,然后导致整个程序的暂停,因此当 GC 发生时,程序是无法响应用户的请求的,从图中的不断起伏的峰值表现,你可以看出问题所在。 80 | 81 | 为了解决问题,Discord 选择使用 Rust 来重写该服务,然后下图是结果对比,Go 语言是左边一列,Rust 是右边一列: 82 | 83 | 84 | 85 | 从图中可以看出几点: 86 | 87 | - 由于 GC 导致的峰值起伏没有了 88 | - Rust 版本比 Go 的版本响应时间降低了 10 倍以上,注意看,图中的时间单位是不一样的 89 | 90 | 在重写后,由于性能的大幅提升,还帮助 Discord 降低了服务器资源的需求,变相节省了大笔金钱。 91 | 92 | 从上面两个例子中,我们看到两个公司都是为了性能才去使用 Rust ,但是在性能之外他们还收获了能效上的提升和硬件成本上的降低,这不得不说是一种意外之喜了。 93 | 94 | 95 | -------------------------------------------------------------------------------- /2021/Q4/make-yes-1000-times-faster.md: -------------------------------------------------------------------------------- 1 | # 深度优化让Rust yes命令提速1000倍 2 | 3 | 最近翻出了一篇收藏挺久的文章,是关于如何深度优化`yes`程序的故事,我在查询了相关资料后,突然觉得可以分享给国内的Rustacean,希望大家能喜欢。 4 | 5 | 最简单的`Unix`命令是什么?相信99%的人可能都会选择`echo`,它做的事情确实很简单,将一个字符串输出到`stcout`,然后返回`true`。但是除了这个`echo`,还有一个简单到令人发指的命令`yes`,可能大多数同学都没听说过,它做的事情很简单,往`stdout`中不停的输出`y`,通过换行符进行分割: 6 | ```console 7 | y 8 | y 9 | y 10 | ... 11 | ``` 12 | 13 | 这个命令看上去很像是猴子派来的救兵,实际上它在某些场景有奇用,比如安装过程中需要输入好几次`y`时: 14 | ```shell 15 | yes | sh boring_installation.sh 16 | ``` 17 | 18 | ## 实现yes 19 | 背景知识介绍完毕,让我们来实现下这个`yes`程序,先来一个使用`Python`实现的版本: 20 | ```python 21 | while True: 22 | print("y") 23 | ``` 24 | 25 | 很简单,对吗? 不过性能和代码一样也非常"简单": 26 | ```console 27 | python yes.py | pv -r > /dev/null 28 | [4.17MiB/s] 29 | ``` 30 | 31 | `pv`是一个`Unix`命令,用来统计标准输出的数据量的(都是命令,真是命比命,气死人,咱的`yes`咋就这么弱). 32 | 33 | 再来看看`Mac`系统自带的: 34 | ```console 35 | yes | pv -r > /dev/null 36 | [34.2MiB/s] 37 | ``` 38 | 39 | 嗯,马马虎虎,10个`Python`吧,那再来看看咱们的主角`Rust`,众所周知,`Rust`简直就是高性能的代名词,那性能刷刷的: 40 | ```rust 41 | use std::env; 42 | 43 | fn main() { 44 | let expletive = env::args().nth(1).unwrap_or("y".into()); 45 | loop { 46 | println!("{}", expletive); 47 | } 48 | } 49 | ``` 50 | 先不说性能,代码嘛,却是比`python`啰嗦点,不过问题不大,性能才是王道: 51 | ```console 52 | cargo run --release | pv -r > /dev/null 53 | Compiling yes v0.1.0 54 | Finished release [optimized] target(s) in 1.0 secs 55 | Running `target/release/yes` 56 | [2.35MiB/s] 57 | ``` 58 | 59 | W...T...F? 这性能,狗不理啊,吓得我赶紧检查了下是不是使用了`release`模式,结果是,Rust确实这么慢! 说好的Rust重写Python程序,性能提升50倍呢?原来童话世界都是骗人的。 60 | 61 | 为了调用问题所在,我先去看了`C`语言实现的版本,发现了这句话: 62 | ```C 63 | /* Repeatedly output the buffer until there is a write error; then fail. */ 64 | while (full_write (STDOUT_FILENO, buf, bufused) == bufused) 65 | continue; 66 | ``` 67 | 68 | 嗯,先写入`Buffer`,然后等积累一批数据后,一起输出是个相当好的选择,我瞬间又信心满满,来修改下Rust代码: 69 | ```rust 70 | use std::env; 71 | use std::io::{self, BufWriter, Write}; 72 | 73 | const BUFSIZE: usize = 8192; 74 | 75 | fn main() { 76 | let expletive = env::args().nth(1).unwrap_or("y".into()); 77 | let mut writer = BufWriter::with_capacity(BUFSIZE, io::stdout()); 78 | loop { 79 | writeln!(writer, "{}", expletive).unwrap(); 80 | } 81 | } 82 | ``` 83 | 这里有一点要注意,`Buffer`大小需要是4的倍数,以满足内存对齐的需求,可以提升性能。连这个细节都考虑到,我更是对上面的代码充满了信心。 84 | 85 | 额,事实证明,信心这玩意,和实力真的一点关系都没有,`53MB/s`,嗯,已经把`python`甩到尾灯都看不见了,可是。。。大家知道世界最快的速度是多少吗? 在我的机器上,C语言的最快实现是`3GB/s`,嗯大概60倍于我们的速度。。。 86 | 87 | ## 问题剖析 88 | 这个世界不乏奇迹,在代码上也是,奇迹总是存在的,但是奇迹不是天降的,需要人的努力才能实现,那咱们来分析下,到底这段程序还慢在哪里。 89 | 90 | #### 优化热点路径的性能 91 | 首先,Buf的引入肯定是正确的,不然几个字节几个字节的输出到控制台上,效率肯定非常低,大部分时间都耗费在io和系统调用等待上。 92 | 93 | 其次,查询文档发现,每次输出到控制台,都会涉及一次`lock`操作,当执行次数非常多时,锁的存在会大幅影响性能。 94 | 95 | 最后,寻找一个合适的`Buffer`最大长度。 96 | 97 | #### 优化程序启动性能 98 | 为何要优化启动性能?因为该性能测试实际上分为两项:只运行一秒的短期测试和持续运行的长期测试,对于短期测试而言,程序启动性能提升一点,也会有结果上的影响: 99 | - 引入`Cow`来减少字符串复制 100 | - 为不同的平台实现`to_bytes`,将字符串转为字节数组 101 | - 使用倍增`memcp`的方式将`buffer`填满 102 | - 使用`std::ffi::OsString`来减少内存分配 103 | 104 | 最终,优化后的代码如下: 105 | ```rust 106 | use std::borrow::Cow; 107 | use std::env; 108 | use std::io::{self, Write}; 109 | use std::process; 110 | 111 | #[cfg(not(unix))] 112 | mod platform { 113 | use std::ffi::OsString; 114 | pub const BUFFER_CAPACITY: usize = 16 * 1024; 115 | 116 | pub fn to_bytes(os_str: OsString) -> Vec { 117 | os_str 118 | .into_string() 119 | .expect("non utf-8 argument only supported on unix") 120 | .into() 121 | } 122 | } 123 | 124 | #[cfg(unix)] 125 | mod platform { 126 | use std::ffi::OsString; 127 | pub const BUFFER_CAPACITY: usize = 64 * 1024; 128 | 129 | pub fn to_bytes(os_str: OsString) -> Vec { 130 | use std::os::unix::ffi::OsStringExt; 131 | os_str.into_vec() 132 | } 133 | } 134 | 135 | use platform::*; 136 | 137 | fn fill_up_buffer<'a>(buffer: &'a mut [u8], output: &'a [u8]) -> &'a [u8] { 138 | if output.len() > buffer.len() / 2 { 139 | return output; 140 | } 141 | 142 | let mut buffer_size = output.len(); 143 | buffer[..buffer_size].clone_from_slice(output); 144 | 145 | while buffer_size < buffer.len() / 2 { 146 | let (left, right) = buffer.split_at_mut(buffer_size); 147 | // 因为下一行代码同时借用了&mut和&,因此我们不能通过类似slice[..2].clone_from_slice(&slice[3..])的方式来实现,会造成编译错误 148 | // 使用`buffer.split_at_mut`可以解决该问题,最终返回两个切片,并不会引起所有权的问题 149 | right[..buffer_size].clone_from_slice(left); 150 | buffer_size *= 2; 151 | } 152 | 153 | &buffer[..buffer_size] 154 | } 155 | 156 | fn write(output: &[u8]) { 157 | let stdout = io::stdout(); 158 | let mut locked = stdout.lock(); 159 | let mut buffer = [0u8; BUFFER_CAPACITY]; 160 | 161 | let filled = fill_up_buffer(&mut buffer, output); 162 | while locked.write_all(filled).is_ok() {} 163 | } 164 | 165 | fn main() { 166 | write( 167 | &env::args_os() 168 | .nth(1) 169 | .map(to_bytes) 170 | .map_or(Cow::Borrowed(&b"y\n"[..]), |mut arg| { 171 | arg.push(b'\n'); 172 | Cow::Owned(arg) 173 | }), 174 | ); 175 | process::exit(1); 176 | } 177 | ``` 178 | 179 | 经过这些努力后,最终性能激增到`3.1GB/s`,嗯,略超过`C`语言的最佳实现。 180 | 181 | ## 总结 182 | 虽然`yes`程序不起眼,用处也不大,但是在此过程中,我们证明了Rust只要优化得当,就可以成为C语言那样风一般的男子。 183 | 184 | **一时的差不可怕,一世的差才可怕**。 -------------------------------------------------------------------------------- /2018/Q2/Linux下从零开始部署和使用Jaeger.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2018年5月10号 2 | 分类于[CloundNative微服务](../../index/cloud-native.md) 3 | 4 | 感谢原作者给予的授权! 5 | >原文链接:https://imscc.io/posts/trace/install_jaeger_on_linux/ 6 | 原文作者: 聪少 7 | 8 | 9 | 最近在折腾Jaeger,Jaeger官网都是介绍如何通过Docker部署,二进制部署文档基本没有(已咨询过作者,作者说没文档!你参考Docker自己部署好了!!!),所以打算写一篇Linux部署。 10 | ### Jaeger 11 | Jaeger是Uber推出的一款调用链追踪系统,类似于Zipkin和Dapper,为微服务调用追踪而生。 其主要用于多个服务调用过程追踪分析,图形化服务调用轨迹,便于快速准确定位问题。 12 | 13 | #### Jaeger组成 14 | - 前端界面展示UI 15 | - 数据存储Cassandra 16 | - 数据查询Query 17 | - 数据收集处理Collector 18 | - 客户端代理Agent 19 | - 客户端库jaeger-client-* 20 | #### Jaeger服务之间关系 21 | ![Jaeger服务之间关系](http://upload-images.jianshu.io/upload_images/8245841-393a21a4d9865b5a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 22 | 23 | ### 打造所需原材料 24 | 25 | - [Docker](https://www.docker.com) 26 | - [Cassandra](https://cassandra.apache.org) 27 | - [Jaeger二进制安装包](https://github.com/jaegertracing/jaeger/releases) 28 | - [Jaeger源码](https://github.com/jaegertracing/jaeger) 29 | - [Nginx](http://nginx.org) 30 | 31 | ### Docker部署 32 | #### CenterOS 7 安装Docker 33 | 关于Docker部署网上到处都是,我使用的是CentOS 7,其他版本自行查找,这里就用最简单粗暴的方式安装。 34 | ``` shell 35 | yum update -y 36 | yum -y install docker 37 | systemctl start docker 38 | ``` 39 | #### 替换Docker镜像源 40 | 由于国内下载镜像比较慢,这里我将Docker镜像源替换成阿里云。 41 | 注册一个阿里云用户,访问 [https://cr.console.aliyun.com/#/accelerator](https://cr.console.aliyun.com/#/accelerator) 获取专属Docker加速器地址。 42 | 如图: 43 | ![ali_docker_mirror](http://upload-images.jianshu.io/upload_images/8245841-50f2e725cc676c90.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 44 | 45 | 46 | 针对Docker客户端版本大于1.10.0的用户可以通过修改daemon配置文件/etc/docker/daemon.json来使用加速器: 47 | ``` shell 48 | sudo mkdir -p /etc/docker 49 | sudo tee /etc/docker/daemon.json <<-'EOF' 50 | { 51 | "registry-mirrors": ["骚年您老人家的阿里镜像加速器地址"] 52 | } 53 | EOF 54 | sudo systemctl daemon-reload 55 | sudo systemctl restart docker 56 | ``` 57 | OK!Docker部署到此为止,有人会问我Jaeger用二进制部署了,为啥还需要部署Docker。这里是一个悲伤的故事,Docker是用来部署Cassandra的。因为Cassandra是Java实现的,部署要配置一大堆我不太了解的Java环境,比较麻烦,既然有Docker这种神奇何不好好利用一下呢!部署Cassandra的时候我会把存储挂在到本地硬盘,这样就避免了Docker服务异常关闭导致数据丢失了,所以使用Docker部署无伤大雅,这里又有人会问为何不用Docker部署Jaeger,这又是一件悲伤的故事!!!因为Jaeger本身只有链路跟踪,并没有更高级的业务功能(类似异常告警),所以我们可能会针对Jaeger进行二次开发。So不如自己部署一套,好用来做研究。好了废话不多说让我们继续Jaeger部署之旅吧! 58 | 59 | ### Cassandra部署 60 | Cassandra是一套开源分布式NoSQL数据库系统。由Facebook开发,用于储存收件箱等简单格式数据,集GoogleBigTable的数据模型与Amazon Dynamo的完全分布式的架构于一身. 61 | Cassandra是一个混合型的非关系的数据库,类似于Google的BigTable。P2P去中心化的存储。很多方面都可以称之为Dynamo 2.0。 62 | 63 | #### Docker下载Cassandra 64 | 前面已经啰嗦过为什么试用Docker部署Cassandra了,这里就简单介绍如何部署. 65 | ``` shell 66 | docker search cassandra 67 | docker pull docker.io/cassandra 68 | ``` 69 | 70 | #### Docker部署Cassandra集群 71 | 我准备了3台服务器 72 | ``` shell 73 | 10.100.7.46 74 | 10.100.7.47 75 | 10.100.7.48 76 | ``` 77 | 10.100.7.46这台服务器作为种子点 78 | 79 | ``` shell 80 | docker run --name some-cassandra -v /httx/cassandra/data:/var/lib/cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.100.7.47 -p 7000:7000 -p 9042:9042 -e CASSANDRA_SEEDS=10.100.7.46 cassandra:latest 81 | docker run --name some-cassandra -v /httx/cassandra/data:/var/lib/cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.100.7.48 -p 7000:7000 -p 9042:9042 -e CASSANDRA_SEEDS=10.100.7.46 cassandra:latest 82 | docker run --name some-cassandra -v /httx/cassandra/data:/var/lib/cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.100.7.48 -p 7000:7000 -p 9042:9042 -e CASSANDRA_SEEDS=10.100.7.46 cassandra:latest 83 | ``` 84 | /httx/cassandra/data这个是我的数据本地保存目录,9042:9042是将客户端链接地址暴露出来。简单吧! 85 | 86 | #### 导入Jaeger表结构 87 | 导入Jaeger表结构,这里不得不吐槽一下Jaeger!Jaeger二进制安装包里根本没有数据sql文件,而且没有任何文档告诉你SQL文件在哪里找,没安装文档!我表示是崩溃的,最终在源码目录的一个角落中找到SQL文件,路径展示一下: 88 | 89 | $GOPATH/src/github.com/jaegertracing/jaeger/plugin/storage/cassandra/schema/v001.cql.tmpl 90 | 91 | ``` shell 92 | 格式: 93 | cqlsh -h HOST -p PORT -f fileName 94 | cqlsh 10.100.7.46 -f $GOPATH/src/github.com/jaegertracing/jaeger/plugin/storage/cassandra/schema/v001.cql.tmpl 95 | ``` 96 | 上面的命令是我搜索来的,因为在导入之前我已经手动一条一条加进去了(>﹏<)!如果不好用的话,读者可以直接cqlsh一条一条黏上去!!!! 97 | 98 | 99 | ### Jaeger部署 100 | 101 | ok! 现在正式进入主题:Jaeger安装。- [Jaeger二进制安装包](https://github.com/jaegertracing/jaeger/releases) 102 | ``` shell 103 | tar -zxvf jaeger-1.4.1-linux-amd64.tar.gz 104 | mv jaeger-1.4.1-linux-amd64 jaeger 105 | ``` 106 | 107 | #### Collector部署 108 | ``` shell 109 | mkdir collector 110 | mv jaeger-collector collector/collector 111 | nohup ./collector --cassandra.keyspace=jaeger_v1_datacenter1 --cassandra.servers=10.100.7.46,10.100.7.47,10.100.7.48 --collector.zipkin.http-port=9411 1>1.log 2>2.log & 112 | ``` 113 | #### Query部署 114 | ``` shell 115 | mkdir query 116 | mv jaeger-query query/query 117 | mv jaeger-ui-build/build query/ 118 | cd query 119 | nohup ./query --cassandra.keyspace jaeger_v1_datacenter1 --cassandra.servers 10.100.7.46,10.100.7.47,10.100.7.48 --query.static-files=./build 1>1.log 2>2.log & 120 | ``` 121 | 122 | ok 访问你Query的地址 htpp://queryIp:16686 就可以看到久违的jaeger的页面了! 123 | ![image](http://upload-images.jianshu.io/upload_images/8245841-c6a01c06f66d08c9.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 124 | 125 | #### Agent部署 126 | Agent 部署就比较简单了,指定collector地址就OK了! 127 | 128 | ``` shell 129 | nohup ./jaeger-agent --collector.host-port=10.100.7.46:14267 1>1.log 2>2.log & 130 | ``` 131 | 132 | See you! 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /2017/Q2/面向外网的Go语言网络调优详解.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2017年5月17号 2 | 3 | ### 前言 4 | 很早以前crypto/tls(TLS长连接库)和net/http的性能不敢恭维,因此我们都使用Nginx做反向代理,但是Go1.8将要来了,这种格局即将被打破了! 5 | 6 | 我们最近尝试性的将Go1.8编译的服务暴漏到了外网,结果发现crypto/tls 和net/http都得到了极大的提升:稳定性、性能以及服务的可伸缩性! 7 | 8 | ### crypto/tls 9 | 现在已经是2016年了,我们不可能再去裸奔在互联网了,因此基于TLS是必然的选择,所以我们需要crypto/tls这个库。好消息就是在1.8下,该库的性能得到了很大的提升,性能表现堪称十分优秀,而且安全性也非常出色。 10 | 11 | 默认推荐的配置类似 12 | [Mozilla标准] (https://wiki.mozilla.org/Security/Server_Side_TLS),然而我们应该要设置PreferServerCipherSuits为true,这样可以使用更安全更快速的密文族;设置CurvePreferences避免未优化的Curve;选择CurveP256而不是CurveP384,因为后者可能会为每个客户端消耗将近1秒的cpu时间!! 13 | 14 | ```php 15 | &tls.Config{ 16 | PreferServerCipherSuites: true, 17 | // 仅仅使用拥有汇编实现的Curve 18 | CurvePreferences: []tls.CurveID{ 19 | tls.CurveP256, 20 | tls.X25519, // Go 1.8 only 21 | }, 22 | } 23 | ``` 24 | 25 | 如果可以接受TLS兼容性上可能存在的问题(例如版本问题,下面的配置建议更现代化,因此对老版本可能不够兼容),还可以设置MinVersion和CipherSuites 26 | 27 | ```java 28 | MinVersion: tls.VersionTLS12, 29 | CipherSuites: []uint16{ 30 | tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 31 | tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32 | tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, // Go 1.8 only 33 | tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, // Go 1.8 only 34 | tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 35 | tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 36 | 37 | // 最好禁用下面的参数,因为没有提供向前的安全性,但是对于部分客户端可能需要开启 38 | // tls.TLS_RSA_WITH_AES_256_GCM_SHA384, 39 | // tls.TLS_RSA_WITH_AES_128_GCM_SHA256, 40 | } 41 | ``` 42 | 43 | Go的CBC密码族实现在Lucky13攻击下,不够稳定,因此我们在上面的配置中禁用了CBC密码族,虽然go1.8已经进行了改善。 44 | 45 | 注意!上述的优化只针对amd64架构,在此架构下,我们甚至可以考虑cloudflare公司的开源的性能极高的加密版本(AES-GCM,Chacha20-Poly2305,P256)。 46 | 47 | 当然,我们还需要证书,这里我们可以使用golang.org/x/crypto/acme/autocert和Letss Encrypt,同时别忘了将http请求重定向到https,如果你的客户端是浏览器,还可以考虑[HSTS](https://www.owasp.org/index.php/HTTP_Strict_Transport_Security_Cheat_Sheet). 48 | 49 | ```java 50 | srv := &http.Server{ 51 | ReadTimeout: 5 * time.Second, 52 | WriteTimeout: 5 * time.Second, 53 | Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 54 | w.Header().Set("Connection", "close") 55 | url := "https://" + req.Host + req.URL.String() 56 | http.Redirect(w, req, url, http.StatusMovedPermanently) 57 | }), 58 | } 59 | go func() { log.Fatal(srv.ListenAndServe()) }() 60 | ``` 61 | **配置完后,我们可以使用[SSL labs tes](https://www.ssllabs.com/ssltest/)t来检查我们的TLS是否正确** 62 | 63 | 64 | ### net/http 65 | net/http是一个成熟的HTTP1.1和HTTP2协议栈,具体怎么用,这里就不赘述了,我们来讲讲服务器端背后的故事。 66 | 67 | ###### timeouts 68 | 在外网环境中,这个参数是最重要的也是最容易被忽视的之一!你的后端服务如果不设置超时,在内网环境可能还Ok,但是到了外网环境,那就是灾难,特别是在遇到攻击时。 69 | 70 | Timeouts的应用是一种资源控制,就算goroutine很廉价,但是文件描述符fd很昂贵的,一个不再工作或者长闲置的连接是不该去占用宝贵的fd的。 71 | 72 | 当服务器的fd不够用时,在accept新连接时就会失败,报错如下: 73 | 74 | http: Accept error: accept tcp [::]:80: accept: too many open files; retrying in 1s 75 | 76 | 默认的net/http的http.Server,可以通过http.ListenAndServe和http.ListenAndServeTLS创建,是没有timeouts的,这完全不是我们想要的。 77 | 78 | 79 | ![](http://upload-images.jianshu.io/upload_images/8245841-e510b699ed79a261.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 80 | 81 | 如上图所示,http.Server主要有三种timeouts,ReadTimeout,WriteTimeout,IdleTimeout,我们可以这样设置: 82 | ```java 83 | srv := &http.Server{ 84 | ReadTimeout: 5 * time.Second, 85 | WriteTimeout: 10 * time.Second, 86 | IdleTimeout: 120 * time.Second, 87 | TLSConfig: tlsConfig, 88 | Handler: serveMux, 89 | } 90 | log.Println(srv.ListenAndServeTLS("", "")) 91 | ``` 92 | 93 | ReadTimeout是从连接accept一直到所有请求的body被完全读取(如果不读body,那就是所有header被读取)。该超时是net/http包在连接accept之后直接设置SetReadDeadline的。 94 | 95 | ReadTimeout存在一个问题,服务器没有给更多的时间来流式处理来自客户端的数据。因此Go1.8引入了ReadHeaderTimeout,这里的超时仅仅针对Header的读取超时,当然这个没有解决根本问题,因此新的解决方案在[issue#16100](https://github.com/golang/go/issues/16100)有进一步的讨论,关于怎么在Handler中处理ReadTimeout。 96 | 97 | WriteTimeout是从包头读取成功开始,一直到回复(response)的写入,是在[readRequest](https://github.com/golang/go/blob/3ba31558d1bca8ae6d2f03209b4cae55381175b3/src/net/http/server.go#L753-L755)的末尾调用SetWriteDeadline函数实现的。 98 | 99 | 当连接是HTTPS时,SetWriteDeadline会在连接accept后立刻调用一次,这里是处理TLS的握手超时。因此,这次超时是在HTTP包头读取或者等待第一个字节传输之前结束。 100 | 101 | 和ReadTimeout一样,WriteTimeout也无法从Handler中进行相对控制:[issue#16100](https://github.com/golang/go/issues/16100) 102 | 103 | 最后是IdleTimeout,这个是在Go1.8引入的一个很有用的参数,用来控制服务器端KeepAlive的连接允许空闲的最大时间。在go1.8之前,ReadTimeout有一个很大的问题,对于Keepalive的连接是不友好的(尽管可以在应用层来解决Idle的超时问题):因为在上一个请求的读取完毕后,下一个请求的ReadTimeout会立即开始重新计时,这样连接空闲的时间也算在ReadTimeout内,造成了连接的过早断开。 104 | 105 | 综上所述,当我们在Go1.8中处理外部不受信任的连接时,我们要设置上这三个超时,这样客户端就不会因为各种过慢的写或者读,一直霸占连接了。 106 | 107 | ### http2 108 | 在Go1.6版本及之后,HTTP2会自动开启,当且仅当: 109 | - 请求是基于TLS/HTTPS的 110 | - Server.TLSNextProto设置为nil(注意,如果设置为空map,那会禁用HTTP2) 111 | - Server.TLSConfig被设置并且ListenAndServerTLS被使用;或者,使用Serve,同时tls.Config.NextProtos包含了"h2",例如[]string{"h2","http/1.1"} 112 | 113 | 同时在Go1.8版本修复了一个关于HTTP2的ReadTimeout的Bug,再结合1.8的其它特性,我的建议是尽快升级1.8。 114 | 115 | ### tcp keepalive 116 | 如果你在用ListenAndServe(与此相对的是给Serve传一个net.Listener参数,但是这种方式没有做任何防护),那么三分钟长的TCP keepalive时间将自动被设置 117 | 118 | 如果你用的是TCP长连接服务,那么你该使用net.ListenTCP,同时设置keepalive时间,根据我的经验,如果不设置这个,那么长连接存在泄漏的风险,后面我会详细写一篇文章分析TCP连接泄漏的问题。 119 | 120 | ### metrics 121 | 我们可以用Server.ConnState来获取连接的状态,注意,我们要自己维护map[net.Conn]ConnState。 122 | 123 | ### 总结 124 | 以后再也不用在Go的Web服务前再前置一个Nginx了,节省了服务器同时也降低了请求的延迟,前提是,我们使用了Go1.8。 125 | -------------------------------------------------------------------------------- /2015/常用算法动态展示.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2015年11月30号 2 | 3 | ### 1. 快速排序 4 | --- 5 | 快速排序是由东尼·霍尔所发展的一种排序算法。在平均状况下,排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较,但这种状况并不常见。事实上,快速排序通常明显比其他Ο(n log n) 算法更快,因为它的内部循环(inner loop)可以在大部分的架构上很有效率地被实现出来。 6 | 7 | 快速排序使用分治法(Divide and conquer)策略来把一个串行(list)分为两个子串行(sub-lists)。 8 | 9 | ###### 算法步骤 10 | 1. 从数列中挑出一个元素,称为 “基准”(pivot) 11 | 2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作 12 | 3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序 13 | 14 | 递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 15 | 16 | 17 | ![](http://upload-images.jianshu.io/upload_images/8245841-d5a60cc824f5abfe.gif?imageMogr2/auto-orient/strip) 18 | 19 | ### 2. 堆排序 20 | --- 21 | 堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。 22 | 23 | 堆排序的平均时间复杂度为Ο(nlogn) 。 24 | 25 | ###### 算法步骤 26 | 1. 创建一个堆H[0..n-1] 27 | 2. 把堆首(最大值)和堆尾互换 28 | 3. 把堆的尺寸缩小1,并调用shift_down(0),目的是把新的数组顶端数据调整到相应位置 29 | 4. 重复步骤2,直到堆的尺寸为1 30 | 31 | 32 | ![](http://upload-images.jianshu.io/upload_images/8245841-2158eee865b7ab46.gif?imageMogr2/auto-orient/strip) 33 | 34 | ### 3. 归并排序 35 | --- 36 | 归并排序(Merge sort,台湾译作:合并排序)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。 37 | 38 | ###### 算法步骤 39 | 1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列 40 | 2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置 41 | 3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置 42 | 4. 重复步骤3直到某一指针达到序列尾 43 | 5. 将另一序列剩下的所有元素直接复制到合并序列尾 44 | 45 | 46 | ![](http://upload-images.jianshu.io/upload_images/8245841-0c281218680fd358.gif?imageMogr2/auto-orient/strip) 47 | 48 | ### 4. 二分查找算法 49 | --- 50 | 51 | 二分查找算法是一种在有序数组中查找某一特定元素的搜索算法。 52 | ###### 算法步骤 53 | 1. 搜素过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜 素过程结束 54 | 2. 如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。 55 | 3. 如果在某一步骤数组 为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。 56 | 57 | 折半搜索每次把搜索区域减少一半,时间复杂度为Ο(logn) 。 58 | 59 | ### 5. BFPRT(线性查找算法) 60 | --- 61 | BFPRT算法解决的问题十分经典,即从某n个元素的序列中选出第k大(第k小)的元素,通过巧妙的分 析,BFPRT可以保证在最坏情况下仍为线性时间复杂度。该算法的思想与快速排序思想相似,当然,为使得算法在最坏情况下,依然能达到o(n)的时间复杂 度,五位算法作者做了精妙的处理。 62 | 63 | ###### 算法步骤 64 | 1. 将n个元素每5个一组,分成n/5(上界)组。 65 | 2. 取出每一组的中位数,任意排序方法,比如插入排序。 66 | 3. 递归的调用selection算法查找上一步中所有中位数的中位数,设为x,偶数个中位数的情况下设定为选取中间小的一个。 67 | 4. 用x来分割数组,设小于等于x的个数为k,大于x的个数即为n-k。 68 | 5. 若i==k,返回x;若ik,在大于x的元素中递归查找第i-k小的元素。 69 | 终止条件:n=1时,返回的即是i小元素。 70 | 71 | ### 6. BFPRT(线性查找算法) 72 | --- 73 | 深度优先搜索算法(Depth-First-Search),是搜索算法的一种。它沿着树的深度遍历树的节点,尽可能深的搜索树的分 支。当节点v 的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发 现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。DFS属于盲目搜索。 74 | 75 | 深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。一般用堆数据结构来辅助实现DFS算法。 76 | 77 | ###### 算法步骤 78 | 1. 访问顶点v; 79 | 2. 依次从v的未被访问的邻接点出发,对图进行深度优先遍历;直至图中和v有路径相通的顶点都被访问; 80 | 3. 若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。 81 | 82 | 上述描述可能比较抽象,举个实例: 83 | > DFS 在访问图中某一起始顶点 v 后,由 v 出发,访问它的任一邻接顶点 w1;再从 w1 出发,访问与 w1邻 接但还没有访问过的顶点 w2;然后再从 w2 出发,进行类似的访问,… 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止。 84 | 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点。如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问;如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止。 85 | 86 | ### 7. BFS(广度优先搜索) 87 | --- 88 | 广度优先搜索算法(Breadth-First-Search),是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树(图)的宽度遍历树(图)的节点。如果所有节点均被访问,则算法中止。BFS同样属于盲目搜索。一般用队列数据结构来辅助实现BFS算法。 89 | 90 | ###### 算法步骤 91 | 1. 首先将根节点放入队列中。 92 | 2. 从队列中取出第一个节点,并检验它是否为目标。 93 | 如果找到目标,则结束搜寻并回传结果。 94 | 否则将它所有尚未检验过的直接子节点加入队列中。 95 | 3. 若队列为空,表示整张图都检查过了——亦即图中没有欲搜寻的目标。结束搜寻并回传“找不到目标”。 96 | 4. 重复步骤2。 97 | 98 | ![](http://upload-images.jianshu.io/upload_images/8245841-df7ef2c4137c2220.gif?imageMogr2/auto-orient/strip) 99 | 100 | ### 8. Dijkstra算法 101 | 戴克斯特拉算法(Dijkstra’s algorithm)是由荷兰计算机科学家艾兹赫尔·戴克斯特拉提出。迪科斯彻算法使用了广度优先搜索解决非负权有向图的单源最短路径问题,算法最终得到一个最短路径树。该算法常用于路由算法或者作为其他图算法的一个子模块。 102 | 103 | 该算法的输入包含了一个有权重的有向图 G,以及G中的一个来源顶点 S。我们以 V 表示 G 中所有顶点的集合。每一个图中的边,都是两个顶点所形成的有序元素对。(u, v) 表示从顶点 u 到 v 有路径相连。我们以 E 表示G中所有边的集合,而边的权重则由权重函数 w: E → [0, ∞] 定义。因此,w(u, v) 就是从顶点 u 到顶点 v 的非负权重(weight)。边的权重可以想像成两个顶点之间的距离。任两点间路径的权重,就是该路径上所有边的权重总和。已知有 V 中有顶点 s 及 t,Dijkstra 算法可以找到 s 到 t的最低权重路径(例如,最短路径)。这个算法也可以在一个图中,找到从一个顶点 s 到任何其他顶点的最短路径。对于不含负权的有向图,Dijkstra算法是目前已知的最快的单源最短路径算法。 104 | 105 | ###### 算法步骤 106 | 1. 初始时令 S={V0},T={其余顶点},T中顶点对应的距离值 107 | 若存在,d(V0,Vi)为弧上的权值 108 | 若不存在,d(V0,Vi)为∞ 109 | 2. 从T中选取一个其距离值为最小的顶点W且不在S中,加入S 110 | 3. 对其余T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值缩短,则修改此距离值 111 | 4. 重复上述步骤2、3,直到S中包含所有顶点,即W=Vi为止 112 | 113 | 114 | ![](http://upload-images.jianshu.io/upload_images/8245841-13fddba5f4b26fb1.gif?imageMogr2/auto-orient/strip) 115 | 116 | ### 9. 动态规划算法 117 | --- 118 | 动态规划(Dynamic programming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。 动态规划常常适用于有重叠子问题和最优子结构性质的问题,动态规划方法所耗时间往往远少于朴素解法。 119 | 120 | 动态规划背后的基本思想非常简单。大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多 子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个 子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。 121 | 122 | 关于动态规划最经典的问题当属背包问题。 123 | 124 | ###### 算法步骤 125 | 1. 最优子结构性质。如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。最优子结构性质为动态规划算法解决问题提供了重要线索。 126 | 2. 子问题重叠性质。子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。 动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是 在表格中简单地查看一下结果,从而获得较高的效率。 127 | 128 | ### 10. 朴素贝叶斯分类算法 129 | --- 130 | 朴素贝叶斯分类算法是一种基于贝叶斯定理的简单概率分类算法。贝叶斯分类的基础是概率推理,就是在各种条件的存在不确定,仅知其出现概率的情况下, 如何完成推理和决策任务。概率推理是与确定性推理相对应的。而朴素贝叶斯分类器是基于独立假设的,即假设样本每个特征与其他特征都不相关。 131 | 132 | 朴素贝叶斯分类器依靠精确的自然概率模型,在有监督学习的样本集中能获取得非常好的分类效果。在许多实际应用中,朴素贝叶斯模型参数估计使用最大似然估计方法,换言之朴素贝叶斯模型能工作并没有用到贝叶斯概率或者任何贝叶斯模型。 133 | -------------------------------------------------------------------------------- /2018/Q3/从逻辑思维角度提升自己的表达技巧.md: -------------------------------------------------------------------------------- 1 | # 从逻辑思维角度提升自己的表达技巧 2 | 3 | > 本文发表时间:2018 年 7 月 6 号 4 | > 分类于[技术管理](../../index/manager.md) 5 | 6 | ## 逻辑性 7 | 8 | 从事软件开发行业的同学们或多或少都具有相当不错的逻辑性,毕竟编程开发本身就是逻辑性较强的任务。但是大家是否考虑过这种逻辑性应该怎么应用到社交技巧上?下面就跟着笔者一起来详细分析下吧。 9 | 10 | ## 逻辑思维 11 | 12 | 当逻辑性上升到软技能(社交技巧)层面,就成了逻辑思维。 13 | 14 | 逻辑思维一直是职场社交和个人职业发展中最重要的软技能之一。它的本质就是在遇到问题时,给你提供一种梳理问题、分析问题及解决问题的方法论。例如,当你需要向领导陈述问题时,你可以运用逻辑思维梳理即将表达的内容,把你的问题背景、论点及结论划分清晰,从而将你的结论层次清晰的传递过对方。 15 | 16 | 因此,你的逻辑思维能力越强,相对应的解决问题的能力及沟通技巧就越强。下面的内容,我们将从**问题描述、思考过程和沟通表达**三个角度对逻辑思维进行展开,问题描述不清楚甚至错误,那么思考过程就失去了意义;同时没有思考过程就没有合理的结论,没有合理的结论也就无法更好的进行沟通表达,因此这两点是逻辑思维的重中之重。 17 | 18 | ## 问题描述 19 | 20 | 描述问题的技巧主要为了解决以下的难点: 21 | 22 | - 不能准确的界定问题。 23 | - 对于问题不能进行较好的拆分 24 | - 看待问题不全面 25 | 26 | 这里,首先我们引入**5W2H**的方法,该方法在管理学中会经常被提及,但是实际上,我们可以运用到个人决策方面,用来提升自身的问题分析和描述能力。 27 | 28 | 提出疑问对于发现问题和解决问题是极其重要的。创造力高的人,都具有擅于提问题的能力,众所周知,提出一个好的问题,就意味着问题解决了一半。提问题的技巧高,可以发挥人的想象力。相反,有些问题提出来,反而挫伤我们的想象力。 29 | 30 | 发明者在设计新产品时,常常提出:为什么(Why);做什么(What);何人做(Who);何时(When);何地(Where);如何(How );多少(How much)。这就构成了 5W2H 法的总框架。如果提问题中常有"假如……"、“如果……”、“是否……”这样的虚构,就是一种设问,设问需要更高的想象力。 31 | 32 | ![5W2H](images/1.jpg) 33 | 34 | ### 一个简单的例子 35 | 36 | 其实缺乏 5W2H 导致了问题的场景在生活中是比比皆是的 37 | 38 | > A: 老同学,今天遇到你很高兴啊,下次咱们一起去吃饭,聊一聊 39 | B: 好啊 40 | A: 88 41 | B: (黑人问号)心理状态:什么时候去啊?去哪里?AA? 42 | B: 得出结论: 看来是随便说说的,并不是真的想去 43 | 44 | 因此回答这一小节开头的难点,我们来简单分析下。 45 | 46 | ### 不能准确的界定问题 47 | 48 | 如果是这一条的话,以后就用这个方法来思考问题。人的首要思维都是靠大脑在动,其实殊不知,很多事情用手和脑同时动,智商会被拉高的,而且思维会很活跃。所以请记下这个方法,在以后思考问题界定问题的时候,就用笔画下这个 5W2H,然后一一的去对入。时间久后,自然而然的锻炼除了大脑和手共同 “ 思考 ”,从而拉升智商的提高。 49 | 50 | ![](images/2.jpg) 51 | 52 | ### 看待问题不全面 53 | 54 | ![](images/3.jpg) 55 | 56 | ## 思考过程 57 | 58 | 有了良好的问题描述,我们就可以开始更加深入的思考了,其实在描述问题的过程中,本身就进行了初步的思考了,不是吗?(回答不是的同学,请继续看上一节 ;( ) 59 | 60 | ### 对问题进行更进一步的明确 61 | 62 | 明确问题主要有两个方法: 63 | 64 | 1. 设定想要的状态,即设定目标或设定参照物。当问题很明确,那么设定“理所当然”的目标即可,如:公司连续两年赤字,那么设定目标为公司盈利即可;当问题不明确,那么需要设定理想目标,如:公司连续 10 年保持全国第四,那么设定目标可考虑 5 年内成为全国第一。 65 | 2. 把问题具体化到能够思考原因的大小,列举具体事例,从事例中归纳问题。如,年轻员工没有朝气,那么我们可以通过列举具体事例:打招呼有气无力、写资料错误率高、辞职率高等,通过这样的细化,我们就知道具体的问题是什么了。 66 | 67 | ### 深挖原因 68 | 69 | 深挖原因的关键在于,不停地询问为什么,逐步深入挖掘。如何有逻辑地深入挖掘原因?这里建议使用**MECE 分析法**。下面会介绍 MECE,但是**大家要注意**: 不是所有问题都是严格的按照 MECE 的方法来执行,大家可以在学习后,自己总结一套简化版本,适用于日常情况的,生搬硬套总是会落入下乘的。 70 | 71 | #### MECE 分析法 72 | 73 | 该方法是麦肯锡的黄金法则:四步看透问题的本质,精准解决问题! 74 | 75 | MECE,是 Mutually Exclusive Collectively Exhaustive 缩写,中文意思是"相互独立,完全穷尽"。 也就是对于一个重大的议题,能够做到不重叠、不遗漏的分类,而且能够借此有效把握问题的核心,并解决问题的方法。 76 | 77 | 所谓的不遗漏、不重叠指在将某个整体(不论是客观存在的还是概念性的整体)划分为不同的部分时,必须保证划分后的各部分符合以下要求: 78 | 79 | - 完整性(无遗漏),指分解工作的过程中不要漏掉某项,意味着问题的细分是在同一维度上并有明确区分、不可重迭的 80 | - 独立性(无重复),强调每项工作之间要独立,无交叉重叠,意味着问题的分析要全面、周密 81 | 82 | #### 一个例子 83 | 84 | 工厂里搞 5S 管理时候培训师经常使用的一个例子,“怎么整理三个抽屉里的杂乱东西”: 85 | 86 | ##### 方法一 87 | 88 | 1. 抽屉一的东西分类,整理 89 | 2. 抽屉二的东西分类,整理 90 | 3. 抽屉三的东西分类,整理 91 | 92 | ###### 方法二 93 | 94 | 1. 定义三个类别,三个抽屉分别规定一个类别 95 | 2. 整理抽屉一到一二三 96 | 3. 整理抽屉二到一二三 97 | 4. 整理抽屉三到一二三 98 | 99 | ###### 方法三: 100 | 101 | 1. 所有东西都拿出来 102 | 2. 规定三个抽屉内物品的用途 103 | 3. 所有的物品按照类别放进去 104 | 105 | > 我们来评价一下三种方法: 106 | > 方法一,程序最复杂,需要至少九个步骤,且结果最差,整理后东西仍然是混乱的,按照 5S 的定义只涉及到整理没涉及到整顿; 107 | > 方法二,程序复杂,九个步骤,结果是清晰的; 108 | > 方法三,步骤简单,结果清晰 109 | 110 | ### MECE 的四个步骤 111 | 112 | #### 确定范围。 113 | 114 | 也就是要明确当下讨论的问题到底是什么,以及我们想要达到的目的是什么。这个范围决定了问题的边界。这也让”完全穷尽“成为一种可能。换句话说,MECE 中的”完全穷尽“是指有边界的穷尽。 115 | 116 | #### 寻找符合 MECE 的切入点。 117 | 118 | 所谓的切入点是指,你准备按什么来分,或者说大家共同的属性是什么。比如,是按颜色分、按大小分、按时间序列分还是按重要性分?这一步是最难的,但也是最关键的。在找切入点的时候,一定要记得以终为始!这个时候一定要反复思考,你当初要解决的【问题】或当初分析的【目的】是什么。换句话说,你希望分类后解决什么问题,得出什么结论。 119 | 120 | #### 找出大的分类后考虑是否可以用 MECE 继续细分。 121 | 122 | 当你觉得这些内容已经确定以后,仔细琢磨它们。是不是每一项内容都是独立的、可以清楚区分的事情?如果是,那么你的内容清单就是"相互独立的"。如果不是,对它们进行分类和归纳。 123 | 124 | #### 确认有没有遗漏或重复。 125 | 126 | 分完类之后必须重新检视一遍,看看有没有明显的遗漏或重复。建议画出一个金字塔结构图,用可视化的方式比较容易发现是否有重叠项。 127 | 128 | #### 注意事项 129 | 130 | 1. 在确立问题的时候,通过类似鱼刺图的方法,在确立主要问题的基础上,再逐个往下层层分解,直至所有的疑问都找到,通过问题的层层分解,可以分析出关键问题和初步的解决问题的思路; 131 | 2. 结合头脑风暴法找到主要问题,然后在不考虑现有资源的限制基础上,考虑解决该问题的所有可能方法,在这个过程中,要特别注意多种方法的结合有可能是个新的解决方法,然后再往下分析,每种解决方法所需要的各种资源,并通过分析比较,从上述多种方案中找到目前状况下最现实最令人满意的答案。 132 | 133 | ### 一个案例: 如何组织一场培训 134 | 135 | #### 确立核心问题 136 | 137 | ![](images/4.JPEG) 138 | 139 | #### 列出关键点,并且完全穷尽 140 | 141 | ![](images/5.JPEG) 142 | 143 | #### 检查每一项是否完全独立,如果不是,对它们进行分类和归纳 144 | 145 | ![](images/6.JPEG) 146 | 147 | #### 再检查是否每一层是否完全独立,而且穷尽 148 | 149 | ![](images/7.JPEG) 150 | 151 | ### MECE 总结 152 | 153 | 我们会发现这种呈现的结构变成了金字塔样式,每一层都是下一层内容的总结概括,而第一层是要阐述的核心问题(或观点),这就是麦肯锡推崇的金字塔思维结构。使用金字塔结构图可以比较容易地发现是否有重叠项。 154 | 155 | MECE 原则最大好处就在于,对于影响问题产生的所有因素进行层层分解,通过分解得出关键问题所在,以及解决问题的初步思路。无论绩效问题还是业绩问题,都可以通过 MECE 不断归纳总结,梳理思路寻找达到目标的关键点。 156 | 157 | ## 沟通表达 158 | 159 | 在问题被描述、思考清楚后,就该沟通表达了,可以遵循**论点** -> **结论** -> **理由** -> **行动**的框架。 160 | 161 | ### 论点/背景 162 | 163 | 论点,一般指接下来谈话的中心内容。论点阐述时经常包含背景介绍,它们往往不可分割,阐述论点,应尽量从对方了解的信息开始阐述。 164 | 165 | 我们可以使用上本提到的 5W2H 法来细化论点,同时将来各事件元素(时间、地点、人物、事件、原因、如何进展、进展如何等)梳理清楚。 166 | 167 | 举一个简单例子,你想找领导聊聊加班的事情,你不能说 “Boss,关于加班我想找你聊一聊“ 。这样显然没有将事件元素陈述清晰,Boss 会无法判断你谈话的内容,他只能去猜测你将要表达内容是关于加班的哪个方面。正确的论点阐述应该是 “Boss,最近,年轻员工加班时间增加过多了,导致了大家怨言较多,人心浮动,我们是不是应该做一些调整?”, 这样论点就描述清楚了。 168 | 169 | ### 结论 170 | 171 | 1. 不要答非所问 172 | 结论和论点要紧密相关,这样易于被听众快速理解接。如果问题是“是/否”类型,那就以是否作为结论开始;如果问题是怎么做,那就回答该怎么做,尽量避免答案过于弯绕。 173 | 174 | 例如,领导问你最近你的工作完成的如何?你回答说“还在进行中”,其实就不太合理,领导既然这么问,也许是抱着以下目的的: 175 | 176 | - 阶段性的结果是什么 177 | - 是否存在一些困难 178 | - 也许你平时跟领导汇报太少了,所以他对你的状态是不清楚的 179 | - 对你的关心 180 | 181 | 结果,你仅仅是一句“还在进行中”,基本没有给领导提供任何可用的信息。 182 | 183 | 2. 遵循金字塔原则:结论先行 184 | 否则倾听者容易产生疲倦。当希望给倾听者以准备时间或者倾听者自行得出结论的时候,我们才将结论放后面。 185 | 186 | 3. 理由 187 | 理由的陈述关键点在于前文提到的**MECE 分析法**, 筛选“合格”理由作为结论的支撑,避免将一个相似的理由分成多个理由来说。那么如何做到符合 MECE 原则?如何筛选“正确合格”理由? 188 | 189 | - 换位思考 190 | 从对方角度设想,选择能让对方信服的理由,所以面对不同的听众,要有不同的侧重 191 | - 理由的理由 192 | 对于认同感不强的理由,采用“理由的理由”去支持它。“理由的理由”可以是:数据证明、一般常识/规律、事例的累积、已决断的策略、公司规定/制度等 193 | - 理由推导结论 194 | 有大量事实,但没有结论的场景,我们可以先陈列大量理由,从理由中推导出结论 195 | - 理由整理分类 196 | 已经有结论,但理由支撑没有思路的时候,我们同样可以先陈列大量理由,再整理分类理由 197 | 198 | **在目前互联网行业,有一个很好的结论阐述法:基于数据的结论。因此大家在给予结论时,首先给出基于理由推导出的结论,其次应该尽量用数据来佐证你的结论,提高说服力。** 199 | 200 | 最后我们再介绍一种科学的由理由推导结论的方法: 201 | 1. 归纳并举:列举理由,通过理由的共通点推测结论,这种推理方式的缺点是显得主观,所以要格外注意考虑例外的情况 202 | 2. 演绎推理型:三段式,"大前提 + 小前提" -> 结论。如果大前提、小前提是错误的,那么演绎推理的结论也是无意义的,所以要注意确保大、小前提是正确的 203 | 204 | ## 最后 205 | 206 | 给大家推荐一本书<<零秒思考>>,推荐一篇文章[<<使用 A4 纸笔记法 100 天增强逻辑思维>>](https://mp.weixin.qq.com/mp/appmsg/show?__biz=MjM5NjA3OTM0MA%3D%3D&appmsgid=203182672&itemidx=1&sign=595c3d2fe1233a441011b9d8e696a083#rd&uin=MjU4MjUyNzM2MA%3D%3D&key=79cf83ea5128c3e59a05178691cc0d51487d45a3b3704ccaa2340ff568d898b089e6e31cf75c6a261042289e859b66c0&devicetype=android-10&version=25000338&lang=zh_CN&pass_ticket=1V0MbhKBMhjP%2BE4eiEonppyltegkgcZRy%2F%2FCxeKJlauehqow4jLPOnbhMpyI0xCB#wechat_webview_type=1#wechat_redirect&From=test) 207 | 208 | 理论懂得再多,如果不实践,那就是纸上谈兵,希望大家最终都能通过实践形成强大的逻辑思维,为未来的成功之路打下坚实的基础。 209 | -------------------------------------------------------------------------------- /2017/Q2/Go语言之我的性能我做主.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2017年6月8号 2 | 3 | 对于一些服务来说,性能是极其重要的一环,事关系统的吞吐、访问的延迟,进而影响用户的体验。 4 | 5 | 写性能测试在Go语言中是很便捷的,go自带的标准工具链就有完善的支持,下面我们来从Go的内部和系统调用方面来详细剖析一下Benchmark这块儿。 6 | 7 | ### Benchmark 8 | --- 9 | Go做Benchmar只要在目录下创建一个_test.go后缀的文件,然后添加下面函数: 10 | ```java 11 | func BenchmarkStringJoin1(b *testing.B) { 12 | b.ReportAllocs() 13 | input := []string{"Hello", "World"} 14 | for i := 0; i < b.N; i++ { 15 | result := strings.Join(input, " ") 16 | if result != "Hello World" { 17 | b.Error("Unexpected result: " + result) 18 | } 19 | } 20 | } 21 | ``` 22 | 调用以下命令: 23 | ```bash 24 | # go test -run=xxx -bench=. -benchtime="3s" -cpuprofile profile_cpu.out 25 | ``` 26 | 该命令会跳过单元测试,执行所有benchmark,同时生成一个cpu性能描述文件. 27 | 28 | 这里有两个注意点: 29 | - -benchtime 可以控制benchmark的运行时间 30 | - b.ReportAllocs() ,在report中包含内存分配信息,例如结果是: 31 | ```bash 32 | BenchmarkStringJoin1-4 300000 4351 ns/op 32 B/op 2 allocs/op 33 | ``` 34 | 35 | -4表示4个CPU线程执行;300000表示总共执行了30万次;4531ns/op,表示每次执行耗时4531纳秒;32B/op表示每次执行分配了32字节内存;2 allocs/op表示每次执行分配了2次对象。 36 | 37 | 根据上面的信息,我们就能对热点路径进行内存对象分配的优化,例如针对上面的程序我们可以进行小小的优化: 38 | ```java 39 | func BenchmarkStringJoin2(b *testing.B) { 40 | b.ReportAllocs() 41 | input := []string{"Hello", "World"} 42 | join := func(strs []string, delim string) string { 43 | if len(strs) == 2 { 44 | return strs[0] + delim + strs[1]; 45 | } 46 | return ""; 47 | }; 48 | for i := 0; i < b.N; i++ { 49 | result := join(input, " ") 50 | if result != "Hello World" { 51 | b.Error("Unexpected result: " + result) 52 | } 53 | } 54 | } 55 | ``` 56 | 新的Benchmark结果是: 57 | ```bash 58 | BenchmarkStringJoin2-4 500000 2440 ns/op 16 B/op 1 allocs/op 59 | ``` 60 | 可以看出来,在减少了内存分配后,性能提升了**60%**以上! 61 | 62 | ### Cpu Profile 63 | --- 64 | 上一节的benchmark结果,我们只能看到函数的整体性能,但是如果该函数较为复杂呢?然后我们又想知道函数内部的耗时,这时就该Cpu Profile登场了。 65 | 66 | Cpu profile是Go语言工具链中最闪耀的部分之一,掌握了它以及memory、block profile,那基本上就没有你发现不了的性能瓶颈了。 67 | 68 | 之前的benchmark同时还生成了一个profile_cpu.out文件,这里我们执行下面的命令: 69 | ```bash 70 | # go tool pprof app.test profile_cpu.out 71 | Entering interactive mode (type "help" for commands) 72 | (pprof) top10 73 | 8220ms of 10360ms total (79.34%) 74 | Dropped 63 nodes (cum <= 51.80ms) 75 | Showing top 10 nodes out of 54 (cum >= 160ms) 76 | flat flat% sum% cum cum% 77 | 2410ms 23.26% 23.26% 4960ms 47.88% runtime.concatstrings 78 | 2180ms 21.04% 44.31% 2680ms 25.87% runtime.mallocgc 79 | 1200ms 11.58% 55.89% 1200ms 11.58% runtime.memmove 80 | 530ms 5.12% 61.00% 530ms 5.12% runtime.memeqbody 81 | 530ms 5.12% 66.12% 2540ms 24.52% runtime.rawstringtmp 82 | 470ms 4.54% 70.66% 2420ms 23.36% strings.Join 83 | 390ms 3.76% 74.42% 2330ms 22.49% app.BenchmarkStringJoin3B 84 | 180ms 1.74% 76.16% 1970ms 19.02% runtime.rawstring 85 | 170ms 1.64% 77.80% 5130ms 49.52% runtime.concatstring3 86 | 160ms 1.54% 79.34% 160ms 1.54% runtime.eqstring 87 | ``` 88 | 上面仅仅展示部分函数的信息,并没有调用链路的性能分析,因此如果需要完整信息,我们要生成svg或者pdf图。 89 | 90 | ```bash 91 | # go tool pprof -svg profile_cpu.out > profile_cpu.svg 92 | # go tool pprof -pdf profile_cpu.out > profile_cpu.pdf 93 | ``` 94 | 下面是profile_cpu.pdf的图: 95 | 96 | ![](http://upload-images.jianshu.io/upload_images/8245841-d253daa7671a216d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 97 | 可以看到图里包含了多个benchmark的合集(之前的两段benmark函数都在同一个文件中),但是我们只关心性能最差的那个benchmark,因此需要过滤: 98 | ```bash 99 | go test -run=xxx -bench=BenchmarkStringJoin2B$ -cpuprofile profile_2b.out 100 | go test -run=xxx -bench=BenchmarkStringJoin2$ -cpuprofile profile_2.out 101 | go tool pprof -svg profile_2b.out > profile_2b.svg 102 | go tool pprof -svg profile_2.out > profile_2.svg 103 | ``` 104 | 105 | ![](http://upload-images.jianshu.io/upload_images/8245841-7c9a5c1698fa57db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 106 | 107 | 根据图片展示,benchmark自身的函数(循环之外的函数)runtime.concatstrings触发了内存对象的分配,造成了耗时,但是跟踪到这里,我们已经无法继续下去了,因此下面就需要flame graphs 了。 108 | > “A flame graph is a good way to drill down your benchmarks, finding your bottlenecks #golang” via @TitPetric 109 | 110 | 111 | ![](http://upload-images.jianshu.io/upload_images/8245841-286cb460d24460a4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 112 | 113 | 如果想详细查看,你只要点击这些矩形块就好。 114 | 115 | ![](http://upload-images.jianshu.io/upload_images/8245841-8569d4ce1449c047.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 116 | 117 | 生成这些图,我们需要 [uber/go-torch](https://github.com/uber/go-torch)这个库,这个库使用了[https://github.com/brendangregg/FlameGraph](https://github.com/brendangregg/FlameGraph),下面是一个自动下载依赖,然后生成frame graph的脚本,读者可以根据需要,自己实现。 118 | 119 | ```bash 120 | #!/bin/bash 121 | # install flamegraph scripts 122 | if [ ! -d "/opt/flamegraph" ]; then 123 | echo "Installing flamegraph (git clone)" 124 | git clone --depth=1 https://github.com/brendangregg/FlameGraph.git /opt/flamegraph 125 | fi 126 | 127 | # install go-torch using docker 128 | if [ ! -f "bin/go-torch" ]; then 129 | echo "Installing go-torch via docker" 130 | docker run --net=party --rm=true -it -v $(pwd)/bin:/go/bin golang go get github.com/uber/go-torch 131 | # or if you have go installed locally: go get github.com/uber/go-torch 132 | fi 133 | 134 | PATH="$PATH:/opt/flamegraph" 135 | bin/go-torch -b profile_cpu.out -f profile_cpu.torch.svg 136 | ``` 137 | 138 | 至此,我们的benchmark之路就告一段落,但是上面所述的cpu profile不仅仅能用在benchmark中,还能直接在线debug生产环境的应用性能,具体的就不详细展开,该系列后续文章会专门讲解。 139 | 140 | ### 完整源码 141 | --- 142 | 143 | ```java 144 | package main 145 | 146 | import "testing" 147 | import "strings" 148 | 149 | func BenchmarkStringJoin1(b *testing.B) { 150 | b.ReportAllocs() 151 | input := []string{"Hello", "World"} 152 | for i := 0; i < b.N; i++ { 153 | result := strings.Join(input, " ") 154 | if result != "Hello World" { 155 | b.Error("Unexpected result: " + result) 156 | } 157 | } 158 | } 159 | 160 | func BenchmarkStringJoin1B(b *testing.B) { 161 | b.ReportAllocs() 162 | for i := 0; i < b.N; i++ { 163 | input := []string{"Hello", "World"} 164 | result := strings.Join(input, " ") 165 | if result != "Hello World" { 166 | b.Error("Unexpected result: " + result) 167 | } 168 | } 169 | } 170 | 171 | func BenchmarkStringJoin2(b *testing.B) { 172 | b.ReportAllocs() 173 | input := []string{"Hello", "World"} 174 | join := func(strs []string, delim string) string { 175 | if len(strs) == 2 { 176 | return strs[0] + delim + strs[1]; 177 | } 178 | return ""; 179 | }; 180 | for i := 0; i < b.N; i++ { 181 | result := join(input, " ") 182 | if result != "Hello World" { 183 | b.Error("Unexpected result: " + result) 184 | } 185 | } 186 | } 187 | 188 | func BenchmarkStringJoin2B(b *testing.B) { 189 | b.ReportAllocs() 190 | join := func(strs []string, delim string) string { 191 | if len(strs) == 2 { 192 | return strs[0] + delim + strs[1]; 193 | } 194 | return ""; 195 | }; 196 | for i := 0; i < b.N; i++ { 197 | input := []string{"Hello", "World"} 198 | result := join(input, " ") 199 | if result != "Hello World" { 200 | b.Error("Unexpected result: " + result) 201 | } 202 | } 203 | } 204 | 205 | func BenchmarkStringJoin3(b *testing.B) { 206 | b.ReportAllocs() 207 | input := []string{"Hello", "World"} 208 | for i := 0; i < b.N; i++ { 209 | result := input[0] + " " + input[1]; 210 | if result != "Hello World" { 211 | b.Error("Unexpected result: " + result) 212 | } 213 | } 214 | } 215 | 216 | func BenchmarkStringJoin3B(b *testing.B) { 217 | b.ReportAllocs() 218 | for i := 0; i < b.N; i++ { 219 | input := []string{"Hello", "World"} 220 | result := input[0] + " " + input[1]; 221 | if result != "Hello World" { 222 | b.Error("Unexpected result: " + result) 223 | } 224 | } 225 | } 226 | ``` 227 | -------------------------------------------------------------------------------- /2018/Q2/分布式数据库Cockroach初体验.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2018年5月7号 2 | 分类于[架构设计](../../index/architecture.md) 3 | 4 | ### 结论前置 5 | 1. CR(笔者对cockroach的简称)数据库的产品体验比TIDB要好,例如官网、文档、部署维护、后台管理、监控,当然这里不是说tidb不好,tidb兼容mysql协议就非常棒,而且中文文档和中文的客服支持也是极棒的。 6 | 7 | 2.CR对标准sql的支持比较完善,详情见[sql特性支持](https://www.cockroachlabs.com/docs/stable/sql-feature-support.html) [sql语句](https://www.cockroachlabs.com/docs/stable/sql-statements.html) 8 | 9 | 3.单节点性能大概是postgre数据库的60%,延迟抖动控制的较好 10 | 11 | 4.整体上手速度很快,从看文档、搭建环境到修改一个应用为postgre连接并成功的运行起来,大概1个小时左右 12 | 13 | 5.吐槽一下简书,代码高亮很不完善,估计是hilight.js中只引入了少数所谓主流语言的插件 14 | ### 下载 15 | > [下载地址](https://www.cockroachlabs.com/docs/stable/install-cockroachdb.html) 16 | 17 | 下载极其简单,只要下载获得cockroach的binary文件(Go语言的可执行文件)即可 18 | 19 | ### 安装 20 | > [安装文档](https://www.cockroachlabs.com/docs/stable/start-a-local-cluster.html) 21 | 22 | **本文使用以下三个节点(host为虚构)** 23 | |节点|Host| 24 | |-|-| 25 | |1|10.100.1.1| 26 | |2|10.100.1.2| 27 | |3|10.100.1.3| 28 | 29 | |启动常用启动参数|描述| 30 | |-|-| 31 | |--insecure|不启用TLS加密模式,建议非生产环境使用| 32 | |--host|数据库监听地址,默认为本机的外网IP| 33 | |--port|数据库监听端口,默认为26257| 34 | |--http-port|HTTP请求的端口,比如后台管理服务,默认为8089| 35 | |查看详细|cockroach start -h| 36 | ###### 启动节点1 37 | ```bash 38 | cockroach start --insecure 39 | ``` 40 | 41 | 启动成功后能看到以下命令行提示(请忽略里面的主机信息): 42 | ```bash 43 | * WARNING: RUNNING IN INSECURE MODE! 44 | * 45 | * - Your cluster is open for any client that can access . 46 | * - Any user, even root, can log in without providing a password. 47 | * - Any user, connecting as root, can read or write any data in your cluster. 48 | * - There is no network encryption nor authentication, and thus no confidentiality. 49 | * 50 | * Check out how to secure your cluster: https://www.cockroachlabs.com/docs/stable/secure-a-cluster.html 51 | * 52 | 53 | sf:cockroach-v1.1.2.darwin-10.9-amd64 sf$ CockroachDB node starting at 2017-11-04 03:57:55.322402865 +0000 UTC (took 1.0s) 54 | build: CCL v1.1.2 @ 2017/11/02 19:30:00 (go1.8.3) 55 | admin: http://sf.local:9090 56 | sql: postgresql://root@sf.local:26257?application_name=cockroach&sslmode=disable 57 | logs: /Users/sf/Downloads/cockroach-v1.1.2.darwin-10.9-amd64/cockroach-data/logs 58 | store[0]: path=/Users/sf/Downloads/cockroach-v1.1.2.darwin-10.9-amd64/cockroach-data 59 | status: restarted pre-existing node 60 | clusterID: 514ebcce-7320-4d72-b2db-b618c0b404bf 61 | nodeID: 1 62 | ``` 63 | 64 | 这里有三点需要注意 65 | - admin: 后台管理地址 66 | - logs: 日志位置 67 | - store: 数据存储位置 68 | 69 | 其中logs和store位置都可以在启动参数中指定 70 | 71 | ###### 依次启动节点2和3 72 | ```bash 73 | cockroach start --insecure --join=10.100.1.1:26257 74 | ``` 75 | 如果三个节点是部署在同一台机器 76 | ```bash 77 | cockroach start \ 78 | --insecure \ 79 | --store=node2 \ 80 | --host=localhost \ 81 | --port=26258 \ 82 | --http-port=8081 \ 83 | --join=localhost:26257 84 | ``` 85 | 86 | ######结果查验 87 | 88 | 至此所有节点都启动成功,我们来看看集群是否搭建成功: 89 | 访问任意节点的后台管理http://host:8080例如http://10.100.1.2:8080 90 | 91 | 可以看到以下界面(以下所有图里的节点地址和我们的三个虚拟节点地址不一致,因为笔者周末在家临时搭建了一套集群): 92 | 93 | ![后台管理首页](http://upload-images.jianshu.io/upload_images/8245841-b718e156b1ddb756.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 94 | 95 | - 正中间区域是总体监控图,可以在Dashboard中选择更多的监控维度,具体的监控项解释可以把鼠标移动到叹号上查看 96 | - 右边是集群状态和集群重要事件通知 97 | 98 | 下面我们选择**View nodes list**,进入以下界面: 99 | 100 | ![集群节点状态](http://upload-images.jianshu.io/upload_images/8245841-6f56e1cca20934d3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 101 | 102 | - 这里能看到每个节点的状态 103 | - Bytes和Replicas代表了数据的分布,因为CR每份数据默认是存三份,我们恰好三个节点,因此每个节点存一份,最终三个节点存储的bytes和replicas会同步一致,实际上tidb也差不多是这个原理,都是基于谷歌的f1和spanner论文产生的 104 | - 点击logs可以查看每个节点的详细日志 105 | 106 | 至此所有的安装就结束了,下面开始简单的使用之旅 107 | 108 | ### CR客户端 109 | CR的客户端其实就是CR本身,是不是很爽?服务器程序和客户端程序都是同一个执行文件。 110 | 111 | ###### 连接数据库 112 | 用户可以选择连接到三台服务器上去使用CR客户端,也可以在开发机(本地电脑)安装一个CR客户端(适合当前系统的cockroach可执行程序),这里我们选择在本地电脑上远程连接,可以连接三台服务器中的任何一台: 113 | ```bash 114 | cockroach sql --insecure --host=10.100.1.2 --port=26257 115 | ``` 116 | 117 | 连接成功后,在命令行输入 118 | ```bash 119 | show databases; 120 | ``` 121 | 查看默认存在的数据库 122 | 123 | CR客户端[命令文档](https://www.cockroachlabs.com/docs/stable/use-the-built-in-sql-client.html) 124 | 125 | #### 一个简单的例子 126 | 我们以Go语言为例,来看看怎么连接数据库并进行简单的操作 127 | 128 | ###### 下载go驱动 129 | ```bash 130 | go get -u -v github.com/lib/pq 131 | ``` 132 | 133 | ###### 创建用户maxroach 134 | ```bash 135 | cockroach user set maxroach --insecure --host=10.100.1.2 --port=26257 136 | ``` 137 | ###### 创建数据库 138 | ```bash 139 | cockroach sql --insecure -e 'CREATE DATABASE bank' --host=10.100.1.2 --port=26257 140 | ``` 141 | > 这里我们直接使用客户端的外部命令,用户也可以进入客户端命令行,使用sql命令创建 142 | 143 | ###### 给用户授权 144 | ```bash 145 | cockroach sql --insecure -e 'GRANT ALL ON DATABASE bank TO maxroach' 146 | ``` 147 | 148 | ###### 代码连接数据库 149 | ```java 150 | package main 151 | 152 | import ( 153 | "database/sql" 154 | "fmt" 155 | "log" 156 | 157 | _ "github.com/lib/pq" 158 | ) 159 | 160 | func main() { 161 | // Connect to the "bank" database. 162 | db, err := sql.Open("postgres", "postgresql://maxroach@10.100.1.3:26257/bank?sslmode=disable") 163 | if err != nil { 164 | log.Fatal("error connecting to the database: ", err) 165 | } 166 | 167 | // Create the "accounts" table. 168 | if _, err := db.Exec( 169 | "CREATE TABLE IF NOT EXISTS accounts (id INT PRIMARY KEY, balance INT)"); err != nil { 170 | log.Fatal(err) 171 | } 172 | 173 | // Insert two rows into the "accounts" table. 174 | if _, err := db.Exec( 175 | "INSERT INTO accounts (id, balance) VALUES (1, 1000), (2, 250)"); err != nil { 176 | log.Fatal(err) 177 | } 178 | 179 | // Print out the balances. 180 | rows, err := db.Query("SELECT id, balance FROM accounts") 181 | if err != nil { 182 | log.Fatal(err) 183 | } 184 | defer rows.Close() 185 | fmt.Println("Initial balances:") 186 | for rows.Next() { 187 | var id, balance int 188 | if err := rows.Scan(&id, &balance); err != nil { 189 | log.Fatal(err) 190 | } 191 | fmt.Printf("%d %d\n", id, balance) 192 | } 193 | } 194 | ``` 195 | 196 | 运行代码 197 | ```bash 198 | go run basic-sample.go 199 | ``` 200 | 201 | 结果 202 | ``` 203 | Initial balances: 204 | 1 1000 205 | 2 250 206 | ``` 207 | 208 | ###### 使用事务(包含重试逻辑) 209 | ```java 210 | package main 211 | 212 | import ( 213 | "context" 214 | "database/sql" 215 | "fmt" 216 | "log" 217 | 218 | "github.com/cockroachdb/cockroach-go/crdb" 219 | ) 220 | 221 | func transferFunds(tx *sql.Tx, from int, to int, amount int) error { 222 | // Read the balance. 223 | var fromBalance int 224 | if err := tx.QueryRow( 225 | "SELECT balance FROM accounts WHERE id = $1", from).Scan(&fromBalance); err != nil { 226 | return err 227 | } 228 | 229 | if fromBalance < amount { 230 | return fmt.Errorf("insufficient funds") 231 | } 232 | 233 | // Perform the transfer. 234 | if _, err := tx.Exec( 235 | "UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, from); err != nil { 236 | return err 237 | } 238 | if _, err := tx.Exec( 239 | "UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, to); err != nil { 240 | return err 241 | } 242 | return nil 243 | } 244 | 245 | func main() { 246 | db, err := sql.Open("postgres", "postgresql://maxroach@localhost:26257/bank?sslmode=disable") 247 | if err != nil { 248 | log.Fatal("error connecting to the database: ", err) 249 | } 250 | 251 | // Run a transfer in a transaction. 252 | err = crdb.ExecuteTx(context.Background(), db, nil, func(tx *sql.Tx) error { 253 | return transferFunds(tx, 1 /* from acct# */, 2 /* to acct# */, 100 /* amount */) 254 | }) 255 | if err == nil { 256 | fmt.Println("Success") 257 | } else { 258 | log.Fatal("error: ", err) 259 | } 260 | } 261 | ``` 262 | 263 | 这里我们引用了"github.com/cockroachdb/cockroach-go/crdb",因为CR的事务重试逻辑封装在里面。 264 | 265 | 执行结果: 266 | ```bash 267 | Success 268 | ``` 269 | 270 | 查询结果 271 | ``` 272 | cockroach sql --insecure -e 'SELECT id, balance FROM accounts' --database=bank --host=10.100.1.1 --port=26257 273 | 274 | +----+---------+ 275 | | id | balance | 276 | +----+---------+ 277 | | 1 | 900 | 278 | | 2 | 350 | 279 | +----+---------+ 280 | (2 rows) 281 | ``` 282 | 283 | ###### 写在最后 284 | 至此,我们初体验该结束了,相信大家也学会了怎么搭建集群和写代码去访问数据库了,后面有更多的感悟,我会继续完善这个系列。 285 | 286 | 总之,Cockroach真的是一个很不错也很有潜力的数据库,同时衷心的祝愿国产的Tidb越来越好,扬中华开源之威。 -------------------------------------------------------------------------------- /2016/C10M高性能网络应用探索.md: -------------------------------------------------------------------------------- 1 | > 本文发表时间:2016年3月28号 2 | 3 | ### C10K时代 4 | 在高性能网络的场景下,C10K是一个具有里程碑意义的场景,15年前它给互联网领域带来了很大的挑战。发展至今,我们已经进入C10M的场景进行网络性能优化。这期间有怎样的发展和趋势?围绕着各类指标分别有哪些探索和实践? 5 | 6 | 7 | 8 | ### C10K时代的问题与优化手段 9 | 首先带大家回顾一下当年C10K场景中遇到的问题以及为了解决我们单机下高并发的承载能力所做的改进。在当时的年代,国内互联网的普及程度相对较低,C10K并没有给当时中国的互联网环境带来太大冲击,但是在全球互联网环境下大家开始意识到这个问题。为了解决该问题,首先的研究方向就是IO模型的优化,逐渐解决了C10K的问题。 10 | 11 | epoll、kqueue、iocp就是IO模型优化的一些最佳实践,这几种技术实现分别对应于不同的系统平台。以epoll为例,在它的基础上抽象了一些开发框架和库,为广大软件开发者在软件开发带来了便利,比如libevent、libev等。随着当年在IO模型上的革命,衍生出了很多至今为止我们都在大量使用的优秀开源软件,比如nginx、haproxy、squid等,通过大量的创新、实践和优化,使我们在今天能够很轻易地解决一个大并发压力场景下的技术问题。 12 | 13 | 这里简单列了几点,较为常用的优化技术手段。 14 | 15 | 16 | ### CPU亲和性&内存局域性 17 | 18 | 目前我们使用的服务器主要是多路、多核心的x86平台。用于运行我们的软件代码,在很多场景的业务需求下,都会涉及一定并发任务,无论是多进程模型还是多线程模型,都要把所有的调度任务交给操作系统,让操作系统帮我们分配硬件资源。我们常用的服务器操作系统都属于分时操作系统,调度模型都尽可能的追求公平,并没有为某一类任务做特别的优化,如果当前系统仅仅运行某一特定任务的时候,默认的调度策略可能会导致一定程度上的性能损失。我运行一个A任务,第一个调度周期在0号核心上运行,第二个调度周期可能就跑到1号核心上去了,这样频繁的调度可能会造成大量的上下文切换,从而影响到一定的性能。 19 | 20 | 数据局域性是同样类似的问题。当前x86服务器以NUMA架构为主,这种平台架构下,每个CPU有属于自己的内存,如果当前CPU需要的数据需要到另外一颗CPU管理的内存获取,必然增加一些延时。所以我们尽可能的尝试让我们的任务和数据在始终在相同的CPU核心和相同的内存节点上,Linux提供了sched_set_affinity函数,我们可以在代码中,将我们的任务绑定在指定的CPU核心上。一些Linux发行版也在用户态中提供了numactl和taskset工具,通过它们也很容易让我们的程序运行在指定的节点上。 21 | 22 | 23 | 24 | ### RSS、RPS、RFS、XPS 25 | 26 | 这些技术都是近些年来为了优化Linux网络方面的性能而添加的特性,RPS、RFS、XPS都是Google贡献给社区,RSS需要硬件的支持,目前主流的网卡都已支持,即俗称的多队列网卡,充分利用多个CPU核心,让数据处理的压力分布到多个CPU核心上去。RPS和RFS在linux2.6.35的版本被加入,一般是成对使用的,在不支持RSS特性的网卡上,用软件来模拟类似的功能,并且将相同的数据流绑定到指定的核心上,尽可能提升网络方面处理的性能。XPS特性在linux2.6.38的版本中被加入,主要针对多队列网卡在发送数据时的优化,当你发送数据包时,可以根据CPU MAP来选择对应的网卡队列,低于指定的kernel版本可能无法使用相关的特性,但是发行版已经backport这些特性。 27 | 28 | 29 | 30 | ### IRQ优化 31 | 32 | 关于IRQ的优化,这里主要有两点,第一点是关于中断合并。在比较早期的时候,网卡每收到一个数据包就会触发一个中断,如果小包的数据量特别大的时候,中断被触发的数量也变的十分可怕。大部分的计算资源都被用于处理中断,导致性能下降。后来引入了NAPI和Newernewer NAPI特性,在系统较为繁忙的时候,一次中断触发后,接下来用轮循的方式读取后续的数据包,以降低中断产生的数量,进而也提升了处理的效率。第二点是IRQ亲和性,和我们前面提到了CPU亲和性较为类似,是将不同的网卡队列中断处理绑定到指定的CPU核心上去,适用于拥有RSS特性的网卡。 33 | 34 | 这里再说说关于网络卸载的优化,目前主要有TSO、GSO、LRO、GRO这几个特性,先说说TSO,以太网MTU一般为1500,减掉TCP/IP的包头,TCP的MaxSegment Size为1460,通常情况下协议栈会对超过1460的TCP Payload进行分段,保证最后生成的IP包不超过MTU的大小,对于支持TSO/GSO的网卡来说,协议栈就不再需要这样了,可以将更大的TCPPayload发送给网卡驱动,然后由网卡进行封包操作。通过这个手段,将需要在CPU上的计算offload到网卡上,进一步提升整体的性能。GSO为TSO的升级版,不在局限于TCP协议。LRO和TSO的工作路径正好相反,在频繁收到小包时,每次一个小包都要向协议栈传递,对多个TCPPayload包进行合并,然后再传递给协议栈,以此来提升协议栈处理的效率。GRO为LRO的升级版本,解决了LRO存在的一些问题。这些特性都是在一定的场景下才可以发挥其性能效率,在不明确自己的需求的时候,开启这些特性反而可能造成性能下降。 35 | 36 | 37 | 38 | ### Kernel优化 39 | 40 | 关于Kernel的网络相关优化我们就不过多的介绍了,主要的内核网络参数的调整在以下两处:net.ipv4.*参数和net.core.*参数。主要用于调节一些超时控制及缓存等,通过搜索引擎我们能很容易找到关于这些参数调优的文章,但是修改这些参数是否能带来性能的提升,或者会有什么弊端,建议详细的阅读kernel文档,并且多做一些测试来验证。 41 | 42 | 43 | 44 | ### 更深入的探索和实践 45 | 46 | 接下来,我们着重了解如何去更进一步提升我们单机网络吞吐以及网络处理性能的技术和手段。 47 | 48 | 计算机硬件做为当前IT发展的重要组成部分。作为软件开发者,我们更应该掌握这部分的内容,学习了解我们的软件如何在操作系统中运行,操作系统又怎样分配我们的硬件资源。 49 | 50 | 51 | 52 | ### 硬件 53 | 54 | #### CPU 55 | 56 | CPU是计算机系统中最核心、最关键的部件。在当前的x86服务器领域我们接触到主要还是Intel的芯片。索性我们就以IntelXeon 2600系列举例。 57 | 58 | Intel Xeon 2600系列的CPU已经发布了3代,第4代产品2016年Q1也即将面市,图例中均选取了4代产品最高端的型号。图一为该系列CPU的核心数量统计,从第一代的8核心发展到即将上市的22核心,若干年前,这是很可怕的事情。装配该型号CPU的双路服务器,再开启超线程,轻而易举达到80多个核心。就多核处理器的发展历程来讲,核心数量逐年提升,主频基本稳定在一定的范围内,不是说单核主频不再重要,而是说在当前的需求场景下,多核心才是更符合我们需求的处理器。 59 | 60 | ![](http://7j1ynv.com5.z0.glb.qiniucdn.com/wp-content/uploads/2015/12/112.png) 61 | 62 | 63 | 不仅仅是核心数量,像LLC缓存的容量、内存带宽都有很大的提升,分别达到了55MB和76.8GB/s。 64 | 65 | #### 内存 66 | 67 | 关于内存,可能它的发展历程并没有像CPU或者其他硬件这样耀眼夺目。可能大家更关心的就是价格吧。目前在服务器领域,DDR3内存仍是主流,DDR4内存因为成本等问题并没有大面积普及。这里列举了IDF15的一些数据,从Intel的销售市场调研报告来看,在明年Q2左右会看到更多的服务器CPU支持DDR4,但是PC机的普及可能还需要一段过渡时间。 68 | 69 | #### 网络 70 | 71 | 当年我们可能仅仅使用一台服务器就能满足我们的业务需求,但是随着业务规模的扩大,单台服务器的能力已经远不能支撑现在的业务,所谓的分布式扩展,便被大家推了上现,所以现在的业务对网络的依赖越来越高。关于网络硬件,我们也以Inter系列的网卡来举例,总结一下目前比较成熟的特性,像RSS特性,前面也提到了,这个特性是需要硬件支持的,目前大部分中小企业可能还是千兆网络为主,像82559这类的网卡,都已经支持了比较多的队列。今年新出的X710芯片也是正对应着云计算或者虚拟化的需求,提供更多相关的特性,如virtualfunction,SR-IOV,tunnel protocol offload等等。随着云计算的发展,未来包含这些特性的网卡将会成为主流。 72 | 73 | #### 如何更好的利用硬件特性 74 | 75 | 现在主流的硬件性能已经很强大了,但是我们的应用软件,真的能够充分利用这些硬件吗?如何更进一步把这些硬件特性利用起来,我们摸索出一系列的技术的段,先聊聊比较关键的三点:数据包处理、任务调度和数据存储。 76 | 77 | 首先就是如何处理我们的数据包,使其速度更快,效率更高。其次让我们的任务按业务逻辑进行调度,而不仅仅是我起个并发模型,让操作系统帮我们调度。第三是关于数据访问,也就是和内存交互的一些技巧。 78 | 79 | 在2013年在Shmoocon会议上,Robert提出”kernel不是一个万能的解决方案,它正是一个问题所在。”这样一个命题。随着互联网的发展,Linux已经成为服务器领域上不可或缺的角色。但是它的初衷并不是针对某一类应用场景进行特殊的优化和适配,本质上来说还是一个分时系统,希望更公平地服务众多的用户以和任务。但正是因为它的设计目标是如此,所以在过去的发展中也主要是解决这些问题。进而所造成的一个问题就是系统越来越庞大,功能越来越多,逻辑越来越臃肿,虽然通过一些手段有的优化,但是整体的架构对于我们上层的软件开发者来说还是比较复杂的。 80 | 81 | 图2是从linux foundation的一篇文章kernel_flow中截取的,我们可以看到在用户态中,从调用write()等API到将数据发送到网卡上经过了相当多的逻辑处理,在很多需求场景中,可以省略一定的逻辑上整个的东西,复杂的实现,一定程度上对软件开发者的理解造成了障碍。 82 | 83 | ![](images/1.png) 84 | 85 | 这里重点讲两个瓶颈点,第一个就是全局的队列,在我们在写用户态网络程序中,对同一个网络端口,仅允许一个监听实例,接收的数据包由一个队列来维护,并发的短连接请求较大时,会对这个队列造成较大的竞争压力,成为一个很大瓶颈点,至少在linuxkernel 3.9版本之前是这样,在3.9的版本合并了一个很关键的特性SO_REUSEPORT,支持多个进程或线程监听相同的端口,每个实例分配一个独立的队列,一定程度上缓解这个问题。用更容易理解的角度来描述,就是支持了我们在用户态上对一个网络端口,可以有多个进程或线程去监听它。正是因为有这样一个特性,我们可以根据CPU的核心数量来进行端口监听实例的选择,进一步优化网络连接处理的性能。第二点也是一个比较大的问题,在linuxkernel中有一个全局的连接表,用于维护TCP连接状态,这个表在维护大量的TCP连接时,会造成相当严重的资源竞争。总的来说,有锁的地方,有资源占用的地方都可能会成为瓶颈点。 86 | 87 | 对于前面提到的问题,是我们为了实现高性能网络优先要解决的。目前,在业界来说解决这些问题主要有以下两套方案。第一套方案就是说更进一步在linuxkernel中优化网络协议栈处理的业务逻辑和结构,但是这里会有一个问题,前提你要有一个相对有一定kerne经验的团队做这件事情,而且对它足够了解,但是对于大部分企业来说是很难具备这样的一个团队的。另外一个解决方案,让你的数据包接近用户态,尽可能的和kernel少做交互,目前我们选择的是第二种方式,尽可能不让kernel做太多事情,至少在网络数据包处理这块。 88 | 89 | 关于网络数据包处理这块,如何让它更接近用户态,主要有两点。第一点,在收到数据包之后不进协议栈,把数据包的内存直接映射到用户态,让我们的程序在用户态直接可以看到这些数据。这样就绕过了kernel的处理。第二个其这间利用了linuxUIO,这个特性叫UIO,通过这个模块框架我们可以在驱动程序收到数据包之后,直接放到用户态的内存空间中,也同样达到了绕过协议栈的目的。 90 | 91 | 大家也可能想到一个问题,我虽然绕过了Linux协议栈,数据包直接达到用户态,对于大部分比较业务应用来说,没有太大的意义,为什么?因为此时应用程序看到的数据包仅仅是内存中的一段二进制,获取IP等信息,不知如何获取,和原有的socket编程方式完全不兼容。在一些特殊专用领域的,它们可能会有一些特有的流程来处理这些数据,而且不需要协议栈的支持,例如IDS这类功能。所以我们需要这样一个协议栈,并且支持传统的Socket框架,避免带来应用程序修改成本。我们知道协议栈从BSD等系统最初的实现,发展到现在,经历了几十个年头,功能越来越复杂,规模越来越庞大。像前面提到采用绕过kernel默认协议栈的方案的话,协议栈是永远不可避免的。无论是购买一个商业的协议栈,移植开源协议栈,或者自己重新实现。 92 | 93 | 现在一些大公司都已经进行了相关的尝试,并且带到了生产环境中。对于协议栈,还是有一些优化的空间,就TCP协议来说,它的场景其实是针对不可控的互联网而产生的,它所面临的问题也主要是延时不同,距离不同等等广域网的场景,比如滑动窗口,TCP拥塞算法等等。对于现在的互联网业务而言,每套业务系统都有很多的业务层次,除了接入层需要直接面向互联网和用户打交道,其余大量的数据交互都发生在机房内部或机房之间,就算跨两个异地的机房,之间的网络延时也不过几毫秒,并不像互联网的环境那样的复杂,也正因如此,很多特性在这些场景中看来有些“多余”。也正是因为这些特性,对于网络性能而言也是有一定的损耗,也给我们更多的空间,根据我们自己的业务对其进行定制和优化。 94 | 95 | 扩展性,这个也是大家都面临的一个问题。例如,数据流量进来,需要对它进行采样统计分析,或者做一些更精细化的流量调度,如此众多的业务需求,如果像前面提到的在linuxkernel内部做优化,做扩展的话,挑战还是蛮大的。正是因为我们有如此多的业务需求,所以我们选择了在kernel之外做这样的事情。 96 | 97 | 我们的服务器经常会有一种现象,CPU0 使用率很高,但大部分的CPU核心很空闲,我们在编程的时候,多进程模型或多线程模型,只是描述程序的并发模型,和系统的多核调度并没有太多直接的联系。这些调度模型是否能够充分利用多核,就像前面提到的CPU亲和性,将某个进程或线程绑定在指定的CPU核心,充分利用CPU核心的缓存,减少在上下文切换等。这里产生了另外一个问题,虽然这种方式是能解决对于CPU多核利用率不佳的问题,但这种硬性关联无法解决我们业务上的需求,也就是说结合业务,我们更了解应该怎样调度我们计算资源和数据。 98 | 99 | 刚刚提到了多进程和多线程,有些同学说用协程的模型也可以来解决这个问题吧?但协程只是让开发者更容易使用的一种封装,在进程内模拟并发,并自行管理上下文,比系统提供的要轻一些,但最终还是落在现在的调度模型上。这里有一个比较有争议的建议,不见得在任何场景中都适用。 100 | 101 | 前面已经提到过,对于在一条网络连接上的数据处理,尽可能保持在一个CPU核心上,以此换取CPUCache的利用率。网络连接流保持在一个核心上,可以降低在网络连接流上处理的优化成本,如果背道而驰的话,可能我接收数据包的是CPU0核心,发送数据包的是CPU1核心,这样可能会引入业务处理上的复杂度。 102 | 103 | 无锁数据结构,其是更多的都是编程上的一些技巧,虽然我们用的是多核平台,尽可能让每个核心做自己的事情,但是不可避免在业务的需求上还是有跨CPU核心或实例的通信过程,这类通信是无法避免的,我们只有尽可能保证不要有锁的出现,不是用锁解决资源问题不好,而是这种上下文中,解决数据一致性可能成本会颇高,得不偿失,特别是在本文提到的需求场景下。 104 | 105 | 保证你的数据结构尽可能在相同的CPU核心上进行处理,对于一个数据包相关的数据结构或者哈希表,在相同的类型实例上都保存一份,虽然增加了一些内存占用,但降低了资源冲突的概率。 106 | 107 | 图3是一些常见的延时,其中一部分的数据是在我的laptop上测试得出的,另外一部分是引用JeffDean。我们可以看到对于一个CPU寄存器或缓冲的访问,基本都在1个时钟周期内就可以完成了,延时是很低的,对于L1缓存来说至少需要4个时钟周期,LLC至少要26个时钟周期,到我们内存访问的话就是更是拉开相当大的数量级。也是说明前面为什么多次提到尽可能充分利用CPU的缓存,而不是说尽可能把数据放在内存里,因为内存访问的代价,在这里看来有些昂贵的,的确是这样的。但是说对于IO设备,之间的差距则变得更大了。 108 | 109 | ![](images/2.png) 110 | 111 | 前面提到的都是关于任务调度以及关于对数据包处理上的优化的关键点,最后这一点主要关于内存的技巧。第一个是尽可能的不要使用多级的指针嵌套,怎么理解呢?在我们传统中实现,都会抽象很多结构体,通过我们的业务抽象出很多层次,然后通过指针一级级关联下去,从这个角度看没有什么问题,通过指针可以很方便对数据进行索引和处理,避免数据处理的拷贝。但最大的问题也是发生在这里,多级的指针检索,有很大的机率触发你的CacheMiss。对于网络数据处理,尽可能将你的数据结构以及数据业务层面尽可能抽象更加扁平化一些,这是一个取舍的问题,也是在高性能面前寻求一个平衡点。 112 | 113 | Hugepage主要解决的问题就是TLB Miss的问题。TLB是需要靠硬件实现的,因为成本很高,所以它的容量没有像其他的存储呈指数级的增长。但是我们的内存几十G,上百G都已经是常态了。这种不对称的存在,造成了大内存在4k页面的时候产生了大量的TLB Miss。目前解决的手段就是使用更大的内存页,也就是hugepage,虽然可以换来性能提升,但是也引入了另外一个问题,就是内存利用率的问题,同时要求你对大页内存的使用进行数据结构上的优化。 114 | 115 | 最后一个内存预分配的问题,其实在一些软件中可以看到它的影子,像一些数据库的实现,我们可以看到预分配内存的场景,它的引入解决的问题是什么呢?如果需要内存,进行数据存储的时候,频繁去申请释放内存,在内存管理就会把整体的性能拉下来,提前进行内存预分配,可以一定程度上降低这方面的开销。还是说要对内存进行更精细化的管理,避免在内存分配上引入一些性能损失或容量损失等等。 116 | 117 | 前面提到了这些都是我们在做高性能网络框架这个项目中遇到的一些问题,在业内交流汲取的经验,以及我们在实践上所带来的一些积累。就目前整体的IT产业的发展,无论软件还是硬件来说,它的发展在向一个目标前进,软件开发人员需要了解硬件的工作原理,硬件接触较多的同学比如运维也要更了解你的业务是怎样的实现,软硬结合会是未来持续方向之一。虽然说在当前这个云计算大潮下,很多软件人员只需要把代码写好,直接发布出去了,这样对你的开发是带来一定便利性。但也逃避不了这样的一个角色,帮助你让你的软件更好的运行在底层平台上。 118 | 119 | 8086刚问世的那个时代,主CPU是没有浮点运算的,要用携处理器来支持。随着历史发展,所有功能都在向CPU内迁移,就像FSB,逐渐取消了,内存管理功能移到了CPU内部,这是CPU整个处理器产业的发展。当然对我们来说是很好的,我们可以使用更大的带宽在CPU和内存之间进行交互。我国近几年对于网络安全的发展尤其看重,比如全站HTTPS,我们知道这种场景的话需要很大计算资源进行加密解密,但是这部分的运算让通用CPU来做的话,性能并不是特别理想,所以又引入了的协处理器的概念,当然已经不是当年的那个协处理器,而用一块辅助的硬件加速卡帮我们做SSL Offload的事情。使用GPU进行加速处理,这个也是我们调研的方向,就是我们希望用GPU这样一个比较优秀的并行架构能够帮助我们在网络处理上,可能达到更好的效果,另外提到关于英特尔的phi卡,天河2号,连续几年拿到超算排名,不可否认有一部分是由phi卡带来的。作为软件开发人员不仅仅代码写的漂亮,质量高,一定要对硬件有一定的了解,做运维的同学不仅需要对硬件,对中间件,对服务器了解的同时,也同时多看看业务方有怎样的需求,怎么样把资源和业务结合的更好,更好的为我们的用户提供更好的服务。 -------------------------------------------------------------------------------- /2018/Q1/在以太坊发行Token代币.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2018年3月12号 2 | 3 | 虽然国家目前严令禁止ICO,但是我觉得基于区块链平台的虚拟币本身是很有价值的,大家只要不去碰基于发币的ICO就好,因此这里简单谈谈怎么在以太坊发行自己的代币(Test网络)。 4 | 5 | ### 翻墙 6 | 目前来说,相关的网站很多都是被墙的,因此没有梯子建议就放弃吧,推荐自己购买香港或者海外的云服务器,用shadowsocks搭建代理,注意如果用阿里云的话,不要用视频等大流量服务,可能会被封。 7 | 8 | ### 下载安装 9 | 下载并安装最新版本以太坊钱包 10 | 11 | ### 选择网络 12 | 下载好以太坊钱包后选择`testnet`,别选成`mainnet`了,创建代币合约和转账代币是要收费的。选好testnet后,钱包会去同步区块信息,目前都是快速同步区块的header,而且是点对点的方式,所以很快。 13 | 14 | ![选择Rinkeby测试网络](https://upload-images.jianshu.io/upload_images/8245841-c05c399623f6d74b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 15 | 16 | ### 创建账户 17 | 在钱包界面选择Wallet,然后选择ADD ACCOUNT 18 | ![创建账户](http://upload-images.jianshu.io/upload_images/8245841-10961b09583445cd?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 19 | 20 | ### 领取测试环境的以太币ether 21 | 新建完账户,余额是0.00ether 22 | ![账号余额](https://upload-images.jianshu.io/upload_images/8245841-34e1d6fade8ef1b0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 23 | 24 | 因此我们需要领取一些rinkeby测试环境的ether,进入[链接](https://faucet.rinkeby.io),可以看到有三种方法获取,我们就用第一种发推特的方式,点击下面圈出来的tweet的链接: 25 | ![发推领取测试ether](http://upload-images.jianshu.io/upload_images/8245841-62605da78bd19636?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 26 | 27 | 接着会弹出以下推文,把0x0000...换成你的账户地址0x...,然后发布推文即可: 28 | ![发推文,替换账户地址](http://upload-images.jianshu.io/upload_images/8245841-ba7a253727582b8b?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 29 | ![账户地址](https://upload-images.jianshu.io/upload_images/8245841-4d733dc3f8b33f3f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 30 | 31 | 32 | 接着进入twitter,找到发表的推文,拷贝链接: 33 | ![拷贝推文链接](https://upload-images.jianshu.io/upload_images/8245841-ae7ba4afdf85c17f.jpeg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 34 | 35 | 最后回到领取ether的[链接](https://faucet.rinkeby.io),把推文链接粘贴进去 36 | ![获取ether](https://upload-images.jianshu.io/upload_images/8245841-73dd07f01eda3588.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 37 | 38 | 过大概3-10分钟,就能在你的钱包看到ether币了! 39 | ![以太币到位](https://upload-images.jianshu.io/upload_images/8245841-f884fba20a78f5db.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 40 | 41 | ### 创建智能合约 42 | ![创建合约](http://upload-images.jianshu.io/upload_images/8245841-ee622444c5f33b79?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 43 | 44 | ![选择合约](http://upload-images.jianshu.io/upload_images/8245841-9db857bc302b2799?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 45 | 46 | 47 | 拷贝以下合约代码(这段代码是从官方的[token例子]([https://www.ethereum.org/token#the-code)中拷贝的,但是官方的例子有Bug,这里予以修复) 48 | ```solidity 49 | pragma solidity ^0.4.16; 50 | 51 | interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) external; } 52 | 53 | contract TokenERC20 { 54 | // Public variables of the token 55 | string public name; 56 | string public symbol; 57 | uint8 public decimals = 18; 58 | // 18 decimals is the strongly suggested default, avoid changing it 59 | uint256 public totalSupply; 60 | 61 | // This creates an array with all balances 62 | mapping (address => uint256) public balanceOf; 63 | mapping (address => mapping (address => uint256)) public allowance; 64 | 65 | // This generates a public event on the blockchain that will notify clients 66 | event Transfer(address indexed from, address indexed to, uint256 value); 67 | 68 | // This notifies clients about the amount burnt 69 | event Burn(address indexed from, uint256 value); 70 | 71 | /** 72 | * Constructor function 73 | * 74 | * Initializes contract with initial supply tokens to the creator of the contract 75 | */ 76 | function TokenERC20( 77 | uint256 initialSupply, 78 | string tokenName, 79 | string tokenSymbol 80 | ) public { 81 | totalSupply = initialSupply * 10 ** uint256(decimals); // Update total supply with the decimal amount 82 | balanceOf[msg.sender] = totalSupply; // Give the creator all initial tokens 83 | name = tokenName; // Set the name for display purposes 84 | symbol = tokenSymbol; // Set the symbol for display purposes 85 | } 86 | 87 | /** 88 | * Internal transfer, only can be called by this contract 89 | */ 90 | function _transfer(address _from, address _to, uint _value) internal { 91 | // Prevent transfer to 0x0 address. Use burn() instead 92 | require(_to != 0x0); 93 | // Check if the sender has enough 94 | require(balanceOf[_from] >= _value); 95 | // Check for overflows 96 | require(balanceOf[_to] + _value > balanceOf[_to]); 97 | // Save this for an assertion in the future 98 | uint previousBalances = balanceOf[_from] + balanceOf[_to]; 99 | // Subtract from the sender 100 | balanceOf[_from] -= _value; 101 | // Add the same to the recipient 102 | balanceOf[_to] += _value; 103 | emit Transfer(_from, _to, _value); 104 | // Asserts are used to use static analysis to find bugs in your code. They should never fail 105 | assert(balanceOf[_from] + balanceOf[_to] == previousBalances); 106 | } 107 | 108 | /** 109 | * Transfer tokens 110 | * 111 | * Send `_value` tokens to `_to` from your account 112 | * 113 | * @param _to The address of the recipient 114 | * @param _value the amount to send 115 | */ 116 | function transfer(address _to, uint256 _value) public { 117 | _transfer(msg.sender, _to, _value); 118 | } 119 | 120 | /** 121 | * Transfer tokens from other address 122 | * 123 | * Send `_value` tokens to `_to` on behalf of `_from` 124 | * 125 | * @param _from The address of the sender 126 | * @param _to The address of the recipient 127 | * @param _value the amount to send 128 | */ 129 | function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { 130 | require(_value <= allowance[_from][msg.sender]); // Check allowance 131 | allowance[_from][msg.sender] -= _value; 132 | _transfer(_from, _to, _value); 133 | return true; 134 | } 135 | 136 | /** 137 | * Set allowance for other address 138 | * 139 | * Allows `_spender` to spend no more than `_value` tokens on your behalf 140 | * 141 | * @param _spender The address authorized to spend 142 | * @param _value the max amount they can spend 143 | */ 144 | function approve(address _spender, uint256 _value) public 145 | returns (bool success) { 146 | allowance[msg.sender][_spender] = _value; 147 | return true; 148 | } 149 | 150 | /** 151 | * Set allowance for other address and notify 152 | * 153 | * Allows `_spender` to spend no more than `_value` tokens on your behalf, and then ping the contract about it 154 | * 155 | * @param _spender The address authorized to spend 156 | * @param _value the max amount they can spend 157 | * @param _extraData some extra information to send to the approved contract 158 | */ 159 | function approveAndCall(address _spender, uint256 _value, bytes _extraData) 160 | public 161 | returns (bool success) { 162 | tokenRecipient spender = tokenRecipient(_spender); 163 | if (approve(_spender, _value)) { 164 | spender.receiveApproval(msg.sender, _value, this, _extraData); 165 | return true; 166 | } 167 | } 168 | 169 | /** 170 | * Destroy tokens 171 | * 172 | * Remove `_value` tokens from the system irreversibly 173 | * 174 | * @param _value the amount of money to burn 175 | */ 176 | function burn(uint256 _value) public returns (bool success) { 177 | require(balanceOf[msg.sender] >= _value); // Check if the sender has enough 178 | balanceOf[msg.sender] -= _value; // Subtract from the sender 179 | totalSupply -= _value; // Updates totalSupply 180 | emit Burn(msg.sender, _value); 181 | return true; 182 | } 183 | 184 | /** 185 | * Destroy tokens from other account 186 | * 187 | * Remove `_value` tokens from the system irreversibly on behalf of `_from`. 188 | * 189 | * @param _from the address of the sender 190 | * @param _value the amount of money to burn 191 | */ 192 | function burnFrom(address _from, uint256 _value) public returns (bool success) { 193 | require(balanceOf[_from] >= _value); // Check if the targeted balance is enough 194 | require(_value <= allowance[_from][msg.sender]); // Check allowance 195 | balanceOf[_from] -= _value; // Subtract from the targeted balance 196 | allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance 197 | totalSupply -= _value; // Update totalSupply 198 | emit Burn(_from, _value); 199 | return true; 200 | } 201 | } 202 | 203 | ``` 204 | 205 | 206 | ![合约信息](http://upload-images.jianshu.io/upload_images/8245841-e865be844057a411?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 207 | 208 | 209 | token name ,和 token symbol自己可以随便命名,然后把费用Fee拉到最大(Faster,为了更快的让矿工记录你的合约),最后点击发布就ok了。 210 | 211 | 212 | ### 发起转账 213 | 目标账户地址可以填写`[0x8DF451466Ee0e75F73eafB36a8C0833F3022a687](/send/0x8DF451466Ee0e75F73eafB36a8C0833F3022a687 "0x8DF451466Ee0e75F73eafB36a8C0833F3022a687") 214 | ` 215 | 216 | ![转账界面](https://upload-images.jianshu.io/upload_images/8245841-0c9e9c5181b57bc4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 217 | 218 | 219 | 查看转账信息 220 | ![image.png](https://upload-images.jianshu.io/upload_images/8245841-62448637f92af9e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 221 | 222 | 点击其中一笔转账 223 | ![image.png](https://upload-images.jianshu.io/upload_images/8245841-cc6ef77deb932384.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 224 | 225 | 226 | 点击上图的Transaction下的蓝色地址,可以前往rinkeby.io查看详细信息 227 | 228 | 229 | ### 小结 230 | 发代币的基本方法已经介绍完了,在后续章节,会继续介绍发行代币的高级技巧,欢迎大家订阅。 231 | 这里要额外提一下,发代币的关键就是智能合约,而智能合约一旦上传是不可变的,因此请务必小心谨慎: 232 | - 确保智能合约没有Bug,一旦存在Bug,你就别想去修复了 233 | - 代码要尽量简洁,代码越长,执行费用越高(每次转账都要执行一次) 234 | 235 | 同时,大家也可以在[这里](https://coinmarketcap.com/tokens/)查看代币的Coin 236 | -------------------------------------------------------------------------------- /2017/Q4/听说你想让Go程序运行的更快.md: -------------------------------------------------------------------------------- 1 | 2 | 到现在为止,我已经忘记了我在写什么,但我确定这篇文章是关于Go语言的。这主要是一篇,关于运行速度,而不是开发速度的文章——这两种速度是有区别的。 3 | 4 | 我曾经和很多聪明的人一起工作。我们很多人都对性能问题很痴迷,我们之前所做的是尝试逼近能够预期的(性能)的极限。应用引擎有一些非常严格的性能要求,所以我们才会做出改变。自从使用了Go语言之后,我们已经学习到了很多提升性能以及让Go在系统编程中正常运转的方法。 5 | 6 | Go的简单和原生并发使其成为一门非常有吸引力的后端开发语言,但更大的问题是它如何应对延迟敏感的应用场景?是否值得牺牲语言的简洁性使其速度更快?让我们来一起看一下Go语言性能优化的几个方面:语言特性、内存管理、并发,并根据这些做出合适的优化决策。所有这里介绍的测试代码都在[这里](https://github.com/tylertreat/go-benchmarks). 7 | 8 | #### Channels 9 | Channel在Go语言中受到了很多的关注,因为它是一个方便的并发工具,但是了解它对性能的影响也很重要。在大多数场景下它的性能已经“足够好”了,但是在某些延时敏感的场景中,它可能会成为瓶颈。Channel并不是什么黑魔法。在Channel的底层实现中,使用的还是锁。在没有锁竞争的单线程应用中,它能工作的很好,但是在多线程场景下,性能会急剧下降。我们可以很容易的使用[无锁队列ring buffer](https://github.com/Workiva/go-datastructures/blob/master/queue/ring.go)来替代channel的功能。 10 | 11 | 第一个性能测试对比了单线程buffer channel和ring buffer(一个生产者和一个消费者)。先看看单核心的情况(GOMAXPROCS = 1) 12 | 13 | ```bash 14 | BenchmarkChannel 3000000 512 ns/op 15 | BenchmarkRingBuffer 20000000 80.9 ns/op 16 | ``` 17 | 正如你所看到的,ring buffer大约能快6倍(如果你不熟悉Go的性能测试工具,中间的数字表示执行次数,最后一个数组表示每次执行花费的时间)。接下来,我们再看下调整GOMAXPROCS = 8的情况。 18 | 19 | ```bash 20 | BenchmarkChannel-8 3000000 542 ns/op 21 | BenchmarkRingBuffer-8 10000000 182 ns/op 22 | ``` 23 | **ring buffer快了近三倍** 24 | 25 | Channel通常用于给worker分配任务。在下面的测试中,我们对比一下多个reader读取同一个channel或者ring buffer的情况。设置GOMAXPROCS = 1 测试结果表明channel在单核程应用中性能表现尤其的好。 26 | 27 | ```bash 28 | BenchmarkChannelReadContention 10000000 148 ns/op 29 | BenchmarkRingBufferReadContention 10000 390195 ns/op 30 | ``` 31 | 然而,ring buffer在多核心的情况下速度更快些: 32 | ```bash 33 | BenchmarkChannelReadContention-8 1000000 3105 ns/op 34 | BenchmarkRingBufferReadContention-8 3000000 411 ns/op 35 | ``` 36 | 最后,我们来看看多个reader和多个writer的场景。从下面的对比同样能够看到ring buffer在多核心时更好些。 37 | ```bash 38 | BenchmarkChannelContention 10000 160892 ns/op 39 | BenchmarkRingBufferContention 28068 34344 ns/op 40 | BenchmarkChannelContention-8 5000 314428 ns/op 41 | BenchmarkRingBufferContention-8 10000 182557 ns/op 42 | ``` 43 | ring buffer只使用CAS操作达到线程安全。我们可以看到,在决定选择channel还是ring buffer时很大程度上取决于系统的核数。对于大多数系统, GOMAXPROCS> 1,所以无锁的ring buffer往往是一个更好的选择。Channel在多核心系统中则是一个比较糟糕的选择。 44 | 45 | #### defer 46 | defer是提高可读性和避免资源未释放的非常有用的关键字。例如,当我们打开一个文件进行读取时,我们需要在结束读取时关闭它。如果没有defer关键字,我们必须确保在函数的每个返回点之前关闭文件。 47 | 48 | ```go 49 | func findHelloWorld(filename string) error { 50 | file, err := os.Open(filename) 51 | if err != nil { 52 | return err 53 | } 54 | 55 | scanner := bufio.NewScanner(file) 56 | for scanner.Scan() { 57 | if scanner.Text() == "hello, world!" { 58 | file.Close() 59 | return nil 60 | } 61 | } 62 | 63 | file.Close() 64 | if err := scanner.Err(); err != nil { 65 | return err 66 | } 67 | 68 | return errors.New("Didn't find hello world") 69 | } 70 | ``` 71 | 这样很容易出错,因为很容易在任何一个return语句前忘记关闭文件。defer则通过一行代码解决了这个问题。 72 | 73 | ```go 74 | func findHelloWorld(filename string) error { 75 | file, err := os.Open(filename) 76 | if err != nil { 77 | return err 78 | } 79 | defer file.Close() 80 | 81 | scanner := bufio.NewScanner(file) 82 | for scanner.Scan() { 83 | if scanner.Text() == "hello, world!" { 84 | return nil 85 | } 86 | } 87 | 88 | if err := scanner.Err(); err != nil { 89 | return err 90 | } 91 | 92 | return errors.New("Didn't find hello world") 93 | } 94 | ``` 95 | 乍一看,人们会认为defer明可能会被编译器完全优化掉。如果我只是在函数的开头使用了defer语句,编译器确实可以通过在每一个return语句之前插入defer内容来实现。但是实际情况往往更复杂。比如,我们可以在条件语句或者循环中添加defer。第一种情况可能需要编译器找到应用defer语句的条件分支. 编译器还需要检查panic的情况,因为这也是函数退出执行的一种情况。通过静态编译提供这个功能(defer)似乎(至少从表面上看)是不太可能的。 96 | 97 | derfer并不是一个零成本的关键字,我们可以通过性能测试来看一下。在下面的测试中,我们对比了一个互斥锁在循环体中加锁后,直接解锁以及使用defer语句解锁的情况。 98 | ```bash 99 | BenchmarkMutexDeferUnlock-8 20000000 96.6 ns/op 100 | BenchmarkMutexUnlock-8 100000000 19.5 ns/op 101 | ``` 102 | 使用defer几乎慢了5倍。平心而论,77ns也许并不那么重要,但是在一个循环中它确实对性能产生了影响。通常要由开发者在性能和代码的易读性上做权衡。优化从来都是需要成本的(*译者注:在Go1.9种defer性能大概提升为50-60ns/op*)。 103 | 104 | #### Json与反射 105 | Reflection通常是缓慢的,应当避免在延迟敏感的服务中使用。JSON是一种常用的数据交换格式,但Go的encoding/json库依赖于反射来对json进行序列化和反序列化。使用[ffjson](https://github.com/pquerna/ffjson/)(译者注:easyjson会更快),我们可以通过使用代码生成的方式来避免反射的使用,下面是性能对比。 106 | 107 | ```bash 108 | BenchmarkJSONReflectionMarshal-8 200000 7063 ns/op 109 | BenchmarkJSONMarshal-8 500000 3981 ns/op 110 | 111 | BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op 112 | BenchmarkJSONUnmarshal-8 300000 5839 ns/op 113 | ``` 114 | (ffjson)生成的JSON序列化和反序列化比基于反射的标准库速度快38%左右。当然,如果我们对编解码的性能要求真的很高,我们应该避免使用JSON。**[MessagePack](http://msgpack.org/index.html)是序列化代码一个更好的选择**。在这次测试中我们使用[msgp](https://github.com/tinylib/msgp)库跟JSON的做了对比。 115 | ```bash 116 | BenchmarkMsgpackMarshal-8 3000000 555 ns/op 117 | BenchmarkJSONReflectionMarshal-8 200000 7063 ns/op 118 | BenchmarkJSONMarshal-8 500000 3981 ns/op 119 | 120 | BenchmarkMsgpackUnmarshal-8 20000000 94.6 ns/op 121 | BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op 122 | BenchmarkJSONUnmarshal-8 300000 5839 ns/op 123 | ``` 124 | 125 | 这里的差异是显着的。即使是跟(ffjson)生成的代码相比,MessagePack仍然快很多。 126 | 127 | 如果我们真的*很在意微小的优化,我们还应该避免使用interface类型*, 它需要在序列化和反序列化时做一些额外的处理。在一些动态调用的场景中,运行时调用也会增加一些额外 开销。编译器无法将这些调用替换为内联调用。 128 | 129 | ```bash 130 | BenchmarkJSONReflectionUnmarshal-8 200000 9362 ns/op 131 | BenchmarkJSONReflectionUnmarshalIface-8 200000 10099 ns/op 132 | ``` 133 | 134 | 我们再看看调用查找,即把一个interface变量转换为它真实的类型。这个测试调用了同一个struct的同一个方法。区别在于第二个变量是一个指向结构体的一个指针。 135 | 136 | ```bash 137 | BenchmarkStructMethodCall-8 2000000000 0.44 ns/op 138 | BenchmarkIfaceMethodCall-8 1000000000 2.97 ns/op 139 | ``` 140 | 排序是一个更加实际的例子,很好的显示了性能差异。在这个测试中,我们比较排序1,000,000个结构体和1,000,000个指向相同结构体的interface。对结构体进行排序比对interface进行排序快63%。 141 | 142 | ```bash 143 | BenchmarkSortStruct-8 10 105276994 ns/op 144 | BenchmarkSortIface-8 5 286123558 ns/op 145 | ``` 146 | 147 | 总之,如果可能的话避免使用JSON。如果确实需要用JSON,生成序列化和反序列化代码。一般来说,最好避免依靠反射和interface,而是编写使用的具体类型。不幸的是,这往往导致很多重复的代码,所以最好以抽象的这个代码生成。再次,权衡得失。 148 | 149 | #### 内存管理 150 | Go实际上不暴露堆或直接堆栈分配给用户。事实上,“heap”和“stack”这两个词没有出现在[Go语言规范](https://golang.org/ref/spec)的任何地方。这意味着有关栈和堆东西只在技术上实现相关。实际上,每个goroutine确实有着自己堆和栈。编译器确实难逃分析,以确定对象是在栈上还是在堆中分配。 151 | 152 | 不出所料的,避免堆分配可以成为优化的主要方向。通过在栈中分配空间(即多使用A{}的方式创建对象,而不是使用new(A)的方式),我们避免了昂贵的malloc调用,如下面所示的测试。 153 | 154 | ```bash 155 | BenchmarkAllocateHeap-8 20000000 62.3 ns/op 96 B/op 1 allocs/op 156 | BenchmarkAllocateStack-8 100000000 11.6 ns/op 0 B/op 0 allocs/op 157 | ``` 158 | 自然,通过指针传值比通过对象传世要快,因为前者需要复制唯一的一个指针,而后者需要复制整个对象。下面测试结果中的差异几乎是可以忽略的,因为这个差异很大程度上取决于被拷贝的对象的类型。注意,可能有一些编译器对对这个测试进行一些编译优化。 159 | 160 | ```bash 161 | BenchmarkPassByReference-8 1000000000 2.35 ns/op 162 | BenchmarkPassByValue-8 200000000 6.36 ns/op 163 | ``` 164 | 165 | 然而,heap空间分配的最大的问题在于GC(垃圾回收)。如果我们生成了很多生命周期很短的对象,我们会触发GC工作。在这种场景中对象池就派上用场了。在下面的测试用,我们比较了使用堆分配与使用[sync.Pool](https://golang.org/pkg/sync/#Pool)的情况。对象池提升了5倍的性能。 166 | 167 | ```bash 168 | BenchmarkConcurrentStructAllocate-8 5000000 337 ns/op 169 | BenchmarkConcurrentStructPool-8 20000000 65.5 ns/op 170 | ``` 171 | 需要指出的是,Go的sysc.Pool在垃圾回收过程中也会被回收。使用[sync.Pool的作用](http://dominik.honnef.co/go-tip/2014-01-10/#syncpool)是复用垃圾回收操作之间的内存。我们也可以维护自己的空闲对象列表使对象不被回收,但这样可能就让垃圾回收失去了应有的作用。Go的[pprof](http://blog.golang.org/profiling-go-programs)工具在分析内存使用情况时非常有用的。在盲目做内存优化之前一定要使用它来进行分析。 172 | 173 | #### 亲和缓存 174 | 当性能真的很重要时,你必须开始硬件层次的思考。著名的一级方程式车手杰基·斯图尔特曾经说过,“要成为一个赛车手你不必成为一名工程师,但你必须有机械知识。”深刻理解一辆汽车的内部工作原理可以让你成为一个更好的驾驶员。同样,理解计算机如何工作可以使你成为一个更好的程序员。例如,内存如何布局的?CPU缓存如何工作的?硬盘如何工作的? 175 | 176 | 内存带宽仍然是现代CPU的有限资源,因此缓存就显得极为重要,以防止性能瓶颈。现在的多核处理器把数据缓存在cache line中,大小通常为64个字节,以减少开销较大的主存访问。为了保证cache的一致性,对内存的一个小小的写入都会让cache line被淘汰。对相邻地址的读操作就无法命中对应的cache line。这种现象叫做[false sharing。](https://en.wikipedia.org/wiki/False_sharing) 当多个线程访问同一个cache line中的不同数据时这个问题就会变得很明显。 177 | 178 | 想象一下,Go语言中的一个struct是如何在内存中存储的,我们用之前的[ring buffer ](https://github.com/Workiva/go-datastructures/blob/master/queue/ring.go)作为一个示例,结构体可能是下面这样: 179 | 180 | ```go 181 | type RingBuffer struct { 182 | queue uint64 183 | dequeue uint64 184 | mask, disposed uint64 185 | nodes nodes 186 | } 187 | ``` 188 | queue和dequeue字段分别用于确定生产者和消费者的位置。这些字段的大小都是8byte,同时被多个线程并发访问和修改来实现队列的插入和删除操作,因为这些字段在内存中是连续存放的,它们仅仅使用了16byte的内存,它们很可能被存放在同一个cache line中。因此修改其中的任何一个字段都会导致其它字段缓存被淘汰,也就意味着接下来的读取操作将会变慢。也就是说,在ring buffer中添加和删除元素会导致很多的CPU缓存失效。 189 | 190 | 我们可以给结构体的字段直接增加padding.每一个padding都跟一个CPU cache line一样大,这样就能确保ring buffer的字段被缓存在不同的cache line中。下面是修改后的结构体: 191 | 192 | ```go 193 | type RingBuffer struct { 194 | _padding0 [8]uint64 195 | queue uint64 196 | _padding1 [8]uint64 197 | dequeue uint64 198 | _padding2 [8]uint64 199 | mask, disposed uint64 200 | _padding3 [8]uint64 201 | nodes nodes 202 | } 203 | ``` 204 | 205 | 实际运行时会有多少区别呢?跟其他的优化一样,优化效果取决于实际场景。它跟CPU的核数、资源竞争的数量、内存的布局有关。虽然有很多的因素要考虑,我们还是要用数据来说话。我们可以用添加过padding和没有padding的ring buffer来做一个对比。 206 | 207 | 首先,我们测试一个生产者和一个消费者的情况,它们分别运行在一个gorouting中.在这个测试中,两者的差别非常小,只有不到15%的性能提升: 208 | ```bash 209 | BenchmarkRingBufferSPSC-8 10000000 156 ns/op 210 | BenchmarkRingBufferPaddedSPSC-8 10000000 132 ns/op 211 | ``` 212 | 但是,当我们有多个生产者和多个消费者时,比如各100个,区别就会得更加明显。在这种情况下,填充的版本快了约36%。 213 | ```bash 214 | BenchmarkRingBufferMPMC-8 100000 27763 ns/op 215 | BenchmarkRingBufferPaddedMPMC-8 100000 17860 ns/op 216 | ``` 217 | False sharing是一个非常现实的问题。根据并发和内存争的情况,添加Padding以减轻其影响。这些数字可能看起来微不足道的,但它已经起到优化作用了,特别是在考虑到在时钟周期的情况下。 218 | 219 | #### 无锁共享 220 | 无锁的数据结构对充分利用多核心是非常重要的。考虑到Go致力于高并发的使用场景,它不鼓励使用锁。它鼓励更多的使用channel而不是互斥锁。 221 | 222 | 这就是说,标准库确实提供了常用的内存级别的原子操作, 如[atomic包](htthttps://golang.org/pkg/sync/atomic/p://)。它提供了原子比较并交换,原子指针访问。然而,使用原子包在很大程度上是[不被鼓励](https://groups.google.com/forum/#!msg/golang-nuts/AoO3aivfA_E/zFjhu8XvngMJ)的: 223 | 224 | > We generally don’t want sync/atomic to be used at all…Experience has shown us again and again that very very few people are capable of writing correct code that uses atomic operations…If we had thought of internal packages when we added the sync/atomic package, perhaps we would have used that. Now we can’t remove the package because of the Go 1 guarantee. 225 | 226 | 实现无锁有多困难?是不是只要用一些CAS实现就可以了?在了解了足够多的知识后,我认识到这绝对是一把双刃剑。无锁的代码实现起来可能会非常复杂。atomic和[unsafe](https://golang.org/pkg/unsafe/)包并不易用。而且,编写线程安全的无锁代码非常有技巧性并且很容易出错。像ring buffer这样简单的无锁的数据结构维护起来还相对简单,但是其它场景下就很容易出问题了。 227 | 228 | [Ctrie](http://lampwww.epfl.ch/~prokopec/ctries-snapshot.pdf)是我写的一篇无锁的数据结构实现,[这里](http://bravenewgeek.com/breaking-and-entering-lose-the-lock-while-embracing-concurrency/)有详细介绍尽管理论上很容易理解,但事实上[实现](https://github.com/Workiva/go-datastructures/blob/master/trie/ctrie/ctrie.go)起来非常复杂。复杂实现最主要的原因就是缺乏[双重CAS](https://en.wikipedia.org/wiki/Double_compare-and-swap),它可以帮助我们自动比较节点(去检测tree上的节点突变),也可以帮助我们生成节点快照。因为没有硬件提供这样的操作,需要我们自己去[模拟](https://timharris.uk/papers/2002-disc.pdf)。 229 | 230 | 第一个版本的Ctrie实现是[非常失败](http://https//github.com/Workiva/go-datastructures/issues/122)的,不是因为我错误的使用了Go的同步机制,而是因为对Go语言做了错误的假设。Ctrie中的每个节点都有一个和它相关联的同伴节点,当进行快照时,root节点都会被拷贝到一个新的节点,当树中的节点被访问时,也会被惰性拷贝到新的节点(持久化数据结构),这样的快照操作是常数耗时的。为了避免整数溢出,我们使用了在堆上分配对象来区分新老节点。在Go语言中,我们使用了空的struct。在Java中,两个新生成的空object是不同的,因为它们的内存地址不同,所以我假定了Go的规则也是一样的。但是,结果是残酷的,可以参考下面的[文档](http://https//golang.org/ref/spec#Size_and_alignment_guarantees): 231 | > A struct or array type has size zero if it contains no fields (or elements, respectively) that have a size greater than zero. Two distinct zero-size variables may have the same address in memory. 232 | 233 | 所以悲剧的事情发生了,两个新生成的节点在比较的时候是相等的,所以双重CAS总是成功的。这个BUG很有趣,但是在高并发、无锁环境下跟踪这个bug简直就是地狱。如果在使用这些方法的时候,第一次就没有正确的使用,那么后面会需要大量的时间去解决隐藏的问题,而且也不是你第一次做对了,后面就一直是对的。 234 | 235 | 但是显而易见,编写复杂的无锁算法是有意义的,否则为什么还会有人这么做呢?Ctrie跟同步map或者跳跃表比起来,插入操作更耗时一些,因为寻址操作变多了。Ctrie真正的优势是内存消耗,跟大多的Hash表不同,它总是一系列在tree中的keys。另一个性能优势就是它可以在常量时间内完成线性快照。我们对比了在100个并发的条件下对synchronized map 和Ctrie进行快照: 236 | 237 | ```bash 238 | BenchmarkConcurrentSnapshotMap-8 1000 9941784 ns/op 239 | BenchmarkConcurrentSnapshotCtrie-8 20000 90412 ns/op 240 | ``` 241 | 在特定的访问模式下,无锁数据结构可以在多线程系统中提供更好的性能。例如,[NATS](http://nats.io/)消息队列使用基于synchronized map的数据结构来完成订阅匹配。如果使用无锁的Ctrie,吞吐量会提升很多。下图中的耗时中,蓝线表示使用基于锁的数据结构的实现,红线表示无锁的数据结构的实现 242 | 243 | 244 | ![](http://upload-images.jianshu.io/upload_images/8245841-9b9bddc0a4fdfd94.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 245 | 246 | 在特定的场景中避免使用锁可以带来很好的性能提升。从ring buffer和channel的对比中就可以看出无锁结构的明显优势。然而,我们需要在编码的复杂程度以及获得的好处之间进行权衡。事实上,有时候无锁结构并不能提供任何实质的好处。 247 | 248 | #### 优化的注意事项 249 | 正如我们从上面的讨论所看到的,性能优化总是有成本的。认识和理解优化方法仅仅是第一步。更重要的是理解应该在何时何处取使用它们。引用 C. A. R. Hoare的一句名言,它已经成为了适用所有编程人员的经典格言: 250 | > The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming. 251 | 252 | 但这句话的[观点](http://ubiquity.acm.org/article.cfm?id=1513451)不是反对优化,而是让我们学会在速度之间进行权衡——算法的速度、响应速度、维护速度以及系统速度。这是一个很主观的话题,而且没有一个简单的标准。过早进行优化是错误的根源吗?我是不是应该先实现功能,然后再优化?或者是不是根本就不需要优化?没有标准答案。有时候先实现功能再提升速度也是可以的。 253 | 254 | 不过,我的建议是只对关键的路径进行优化。你在关键路径上走的越远,你优化的回报就会越低以至于近乎是在浪费时间。能够对性能是否达标做出正确的判断是很重要的。不要在此之外浪费时间。要用数据驱动——用经验说话,而不是出于一时兴起。还有就是要注重实际。给一段时间不是很敏感的代码优化掉几十纳秒是没有意义的。比起这个还有更多需要优化的地方。 255 | 256 | #### 总结 257 | 如果你已经读到了这里,恭喜你,但你可能还有一些问题搞错了。我们已经了解到在软件中我们实际上有两种速度——响应速度和执行速度。用户想要的是第一种,而开发者追求的是第二种,CTO则两者都要。目前第一种速度是最重要的,只要你想让用户用你的产品。第二种速度则是需要你排期和迭代来实现的。它们经常彼此冲突。 258 | 259 | 也许更耐人寻味的是,我们讨论了一些可以让Go提升一些性能并让它在低延时系统中更可用的方法。Go语言是为简洁而生的,但是这种简洁有时候是有代价的。就跟前面两种速度的权衡一样,在代码的可维护性以及代码性能上也需要权衡。速度往往意味着牺牲代码的简洁性、更多的开发时间和后期维护成本。要明智的做出选择。 -------------------------------------------------------------------------------- /2018/Q3/为何k8s天然适合微服务.md: -------------------------------------------------------------------------------- 1 | # 为何k8s天然适合微服务 2 | 3 | > 本文发表时间:2018 年 7 月 6 号 4 | > 分类于[CloudNative微服务](../../index/cloud-native.md) 5 | 6 | ## 一、从企业上云的三大架构看容器平台的三种视角 7 | ![](https://pic4.zhimg.com/80/v2-73c3d9efd91312ce671baad877ce1e75_hd.jpg) 8 | 9 | 如图所示,企业上云的三大架构为 IT 架构、应用架构和数据架构,在不同的公司,不同的人、不同的角色,关注的重点不同。 10 | 11 | 对大部分的企业来讲,上云的诉求是从 IT 部门发起的,发起人往往是运维部门,他们关注计算、网络、存储,试图通过云计算服务来减轻 CAPEX 和 OPEX。 12 | 13 | 有的公司有 ToC 的业务,因而累积了大量的用户数据,公司的运营需要通过这部分数据进行大数据分析和数字化运营,因而在这些企业里面往往还需要关注数据架构。 14 | 15 | 从事互联网应用的企业,往往首先关注的是应用架构,是否能够满足终端客户的需求,带给客户良好的用户体验。业务量上往往会有短期内出现爆炸式增长的现象,因而关注高并发应用架构,并希望这个架构可以快速迭代,从而抢占风口。 16 | 17 | 在容器出现之前,这三种架构往往通过虚拟机云平台的方式解决。当容器出现之后,容器的各种良好的特性让人眼前一亮,它的轻量级、封装、标准、易迁移、易交付的特性,使得容器技术迅速被广泛使用。 18 | 19 | ![](https://pic1.zhimg.com/80/v2-187eba16b5d56f0681d2fddfee4d88dd_hd.jpg) 20 | 然而一千个人心中有一千个哈姆雷特,由于原来工作的关系,三类角色分别从自身的角度看到了容器的优势给自己带来的便捷。 21 | 22 | **对于原来在机房里管计算、网络、存储的 IT 运维工程师来讲**,容器更像是一种轻量级的运维模式,在他们看来,容器和虚拟机的最大的区别就是轻量级,启动速度快,他们往往更愿意推出虚拟机模式的容器。 23 | 24 | **对于数据架构来讲**,他们每天都在执行各种各样的数据计算任务,容器相对于原来的 JVM,是一种隔离性较好,资源利用率高的任务执行模式。 25 | 26 | **从应用架构的角度出发**,容器是微服务的交付形式,容器不仅仅是做部署的,而且是做交付的,CI/CD 中的 D 的。 27 | 28 | 所以这三种视角的人,在使用容器和选择容器平台时方法会不一样。 29 | 30 | ## 二、Kubernetes 才是微服务和 DevOps 的桥梁 31 | 32 | #### Swarm:IT 运维工程师 33 | 34 | 35 | ![](https://pic2.zhimg.com/80/v2-e09b2fc513cc51ed2652c4ea2410f5e4_hd.jpg) 36 | 37 | 38 | 39 | 从 IT 运维工程师的角度来看:容器主要是轻量级、启动快,并且自动重启,自动关联,弹性伸缩的技术,使得 IT 运维工程师似乎不用再加班。 40 | 41 | Swarm 的设计显然更加符合传统 IT 工程师的管理模式。 42 | 43 | 他们希望能够清晰地看到容器在不同机器的分布和状态,可以根据需要很方便地 SSH 到一个容器里面去查看情况。 44 | 45 | 容器最好能够原地重启,而非随机调度一个新的容器,这样原来在容器里面安装的一切都是有的。 46 | 47 | 可以很方便地将某个运行的容器打一个镜像,而非从 Dockerfile 开始,这样以后启动就可以复用在这个容器里面手动做的 100 项工作。 48 | 49 | 容器平台的集成性要好,用这个平台本来是为了简化运维的,如果容器平台本身就很复杂,像 Kubernetes 这种本身就这么多进程,还需要考虑它的高可用和运维成本,这个不划算,一点都没有比原来省事,而且成本还提高了。 50 | 51 | 最好薄薄的一层,像一个云管理平台一样,只不过更加方便做跨云管理,毕竟容器镜像很容易跨云迁移。 52 | 53 | Swarm 的使用方式比较让 IT 工程师有熟悉的味道,其实 OpenStack 所做的事情它都能做,速度还快。 54 | 55 | ![](https://pic4.zhimg.com/80/v2-e281d95bfe2e3c712f099b2f0578a73c_hd.jpg) 56 | 57 | ### Swarm 的问题 58 | 59 | 然而容器作为轻量级虚拟机,暴露出去给客户使用,无论是外部客户,还是公司内的开发,而非 IT 人员自己使用的时候,他们以为和虚拟机一样,但是发现了不一样的部分,就会有很多的抱怨。 60 | 61 | 例如自修复功能,重启之后,原来 SSH 进去手动安装的软件不见了,甚至放在硬盘上的文件也不见了,而且应用没有放在 Entrypoint 里面自动启动,自修复之后进程没有跑起来,还需要手动进去启动进程,客户会抱怨你这个自修复功能有啥用? 62 | 63 | 例如有的用户会 ps 一下,发现有个进程他不认识,于是直接 kill 掉了,结果是 Entrypoint 的进程,整个容器直接就挂了,客户抱怨你们的容器太不稳定,老是挂。 64 | 65 | 容器自动调度的时候,IP 是不保持的,所以往往重启后原来的 IP 就没了,很多用户会提需求,这个能不能保持啊,原来配置文件里面都配置的这个 IP ,挂了重启就变了,这个怎么用啊,还不如用虚拟机,至少没那么容易挂。 66 | 67 | 容器的系统盘,也即操作系统的那个盘往往大小是固定的,虽然前期可以配置,后期很难改变,而且没办法每个用户可以选择系统盘的大小。有的用户会抱怨,我们原来本来就很多东西直接放在系统盘的,这个都不能调整,叫什么云计算的弹性啊。 68 | 69 | 如果给客户说容器挂载数据盘,容器都启动起来了,有的客户想像云主机一样,再挂载一个盘,容器比较难做到,也会被客户骂。 70 | 71 | 如果容器的使用者不知道他们在用容器,当虚拟机来用,他们会觉得很难用,这个平台一点都不好。 72 | 73 | Swarm 上手虽然相对比较容易,但是当出现问题的时候,作为运维容器平台的人,会发现问题比较难解决。 74 | 75 | Swarm 内置的功能太多,都耦合在了一起,一旦出现错误,不容易 debug。如果当前的功能不能满足需求,很难定制化。很多功能都是耦合在 Manager 里面的,对 Manager 的操作和重启影响面太大。 76 | 77 | ### Mesos:数据运维工程师 78 | 79 | ![](https://pic4.zhimg.com/80/v2-d3e4775c5dcdf92eb7219d8d72998045_hd.jpg) 80 | 从大数据平台运维的角度来讲,如何更快地调度大数据处理任务,在有限的时间和空间里面,更快地跑更多的任务,是一个非常重要的要素。 81 | 82 | 所以当我们评估大数据平台牛不牛的时候,往往以单位时间内跑的任务数目以及能够处理的数据量来衡量。 83 | 84 | 从数据运维的角度来讲,Mesos 是一个很好的调度器。既然能够跑任务,也就能够跑容器,Spark 和 Mesos 天然的集成,有了容器之后,可以用更加细粒度的任务执行方式。 85 | 86 | 在没有细粒度的任务调度之前,任务的执行过程是这样的。任务的执行需要 Master 的节点来管理整个任务的执行过程,需要 Worker 节点来执行一个个子任务。在整个总任务的一开始,就分配好 Master 和所有的 Work 所占用的资源,将环境配置好,等在那里执行子任务,没有子任务执行的时候,这个环境的资源都是预留在那里的,显然不是每个 Work 总是全部跑满的,存在很多的资源浪费。 87 | 88 | 在细粒度的模式下,在整个总任务开始的时候,只会为 Master 分配好资源,不给 Worker 分配任何的资源,当需要执行一个子任务的时候,Master 才临时向 Mesos 申请资源,环境没有准备好怎么办?好在有 Docker,启动一个 Docker,环境就都有了,在里面跑子任务。在没有任务的时候,所有节点上的资源都是可被其他任务使用的,大大提升了资源利用效率。 89 | 90 | 这就是 Mesos 最大的优势,在 Mesos 的论文中,最重要阐述的就是资源利用率的提升,而 Mesos 的双层调度算法是核心。 91 | 92 | 原来大数据运维工程师出身的,会比较容易选择 Mesos 作为容器管理平台。不过原来是跑短任务,加上 marathon 就能跑长任务。但是后来 Spark 将细粒度的模式 deprecated 掉了,因为效率还是比较差。 93 | 94 | ### Mesos 的问题 95 | 96 | ![](https://pic4.zhimg.com/80/v2-f3983b70c19ae7a2ed4089a737803d4c_hd.jpg) 97 | 调度在大数据领域是核心中的核心,在容器平台中是重要的,但不是全部。所以容器还需要编排,需要各种外围组件,让容器跑起来运行长任务,并且相互访问。Marathon 只是万里长征的第一步。 98 | 99 | 所以早期用 Marathon + Mesos 的厂商,多是裸用 Marathon 和 Mesos 的,由于周边不全,因而要做各种的封装,各家不同。大家有兴趣可以到社区上去看裸用 Marathon 和 Mesos 的厂商,各有各的负载均衡方案,各有各的服务发现方案。 100 | 101 | 所以后来有了 DCOS,也就是在 Marathon 和 Mesos 之外,加了大量的周边组件,补充一个容器平台应有的功能,但是很可惜,很多厂商都自己定制过了,还是裸用 Marathon 和 Mesos 的比较多。 102 | 103 | 而且 Mesos 虽然调度牛,但是只解决一部分调度,另一部分靠用户自己写 framework 以及里面的调度,有时候还需要开发 Executor,这个开发起来还是很复杂的,学习成本也比较高。 104 | 105 | 虽说后来的 DCOS 功能也比较全了,但是感觉没有如 Kubernetes 一样使用统一的语言,而是采取大杂烩的方式。在 DCOS 的整个生态中,Marathon 是 Scala 写的,Mesos 是 C++ 写的,Admin Router 是 Nginx+lua,Mesos-DNS 是Go,Marathon-lb 是 Python,Minuteman 是 Erlang,这样太复杂了吧,林林总总,出现了 Bug 的话,比较难自己修复。 106 | 107 | ### Kubernetes 108 | 109 | ![](https://pic1.zhimg.com/80/v2-b1533bf9aeb79e620041d5309abd4bbe_hd.jpg) 110 | 而 Kubernetes 不同,初看 Kubernetes 的人觉得他是个奇葩所在,容器还没创建出来,概念先来一大堆,文档先读一大把,编排文件也复杂,组件也多,让很多人望而却步。我就想创建一个容器,怎么这么多的前置条件。如果你将 Kubernetes 的概念放在界面上,让客户去创建容器,一定会被客户骂。 111 | 112 | 在开发人员角度,使用 Kubernetes 绝对不是像使用虚拟机一样,开发除了写代码,做构建,做测试,还需要知道自己的应用是跑在容器上的,而不是当甩手掌柜。开发人员需要知道,容器是和原来的部署方式不一样的存在,你需要区分有状态和无状态,容器挂了起来,就会按照镜像还原了。开发人员需要写 Dockerfile,需要关心环境的交付,需要了解太多原来不了解的东西。实话实说,一点都不方便。 113 | 114 | 在运维人员角度,使用 Kubernetes 也绝对不是像运维虚拟机一样,我交付出来了环境,应用之间互相怎么调用,我才不管,我就管网络通不通。在运维眼中他做了过多不该关心的事情,例如服务的发现,配置中心,熔断降级,这都应该是代码层面关心的事情,应该是 SpringCloud 和 Dubbo 关心的事情,为什么要到容器平台层来关心这个。 115 | 116 | ### Kubernetes + Docker,却是 Dev 和 Ops 融合的一个桥梁。 117 | 118 | Docker 是微服务的交付工具,微服务之后,服务太多了,单靠运维根本管不过来,而且很容易出错,这就需要研发开始关心环境交付这件事情。例如配置改了什么,创建了哪些目录,如何配置权限,只有开发最清楚,这些信息很难通过文档的方式又及时又准确地同步到运维部门来,就算是同步过来了,运维部门的维护量也非常的大。 119 | 120 | 所以,有了容器,最大的改变是环境交付的提前,是每个开发多花 5% 的时间,去换取运维 200% 的劳动,并且提高稳定性。 121 | 122 | 而另一方面,本来运维只管交付资源,给你个虚拟机,虚拟机里面的应用如何相互访问我不管,你们爱咋地咋地,有了 Kubernetes 以后,运维层要关注服务发现,配置中心,熔断降级。 123 | 124 | 两者融合在了一起。在微服务化的研发的角度来讲,Kubernetes 虽然复杂,但是设计的都是有道理的,符合微服务的思想。 125 | 126 | ## 三、微服务化的十个设计要点 127 | 128 | 微服务有哪些要点呢?第一张图是 SpringCloud 的整个生态。 129 | 130 | ![](https://pic3.zhimg.com/80/v2-2940295fbe535853f2209c939bf72b6a_hd.jpg) 131 | 第二张图是微服务的 12 要素以及在网易云的实践。 132 | ![](https://pic1.zhimg.com/80/v2-11e7c3c6f94cb4b61262f21f0522389b_hd.jpg) 133 | 134 | 第三张图是构建一个高并发的微服务,需要考虑的所有的点。(打个广告,这是一门课程,即将上线。) 135 | ![](https://pic1.zhimg.com/80/v2-b7b51e63f96b905134a21a3bdf82452d_hd.jpg) 136 | 137 | 接下来细说微服务的设计要点。 138 | 139 | ### 设计要点一:API 网关。 140 | ![](https://pic4.zhimg.com/80/v2-2049df4a0aa21772ba52a52cc4fe7a0c_hd.jpg) 141 | 142 | 在实施微服务的过程中,不免要面临服务的聚合与拆分,当后端服务的拆分相对比较频繁的时候,作为手机 App 来讲,往往需要一个统一的入口,将不同的请求路由到不同的服务,无论后面如何拆分与聚合,对于手机端来讲都是透明的。 143 | 144 | 有了 API 网关以后,简单的数据聚合可以在网关层完成,这样就不用在手机 App 端完成,从而手机 App 耗电量较小,用户体验较好。 145 | 146 | 有了统一的 API 网关,还可以进行统一的认证和鉴权,尽管服务之间的相互调用比较复杂,接口也会比较多,API 网关往往只暴露必须的对外接口,并且对接口进行统一的认证和鉴权,使得内部的服务相互访问的时候,不用再进行认证和鉴权,效率会比较高。 147 | 148 | 有了统一的 API 网关,可以在这一层设定一定的策略,进行 A/B 测试,蓝绿发布,预发环境导流等等。API 网关往往是无状态的,可以横向扩展,从而不会成为性能瓶颈。 149 | 150 | ### 设计要点二:无状态化,区分有状态的和无状态的应用。 151 | ![](https://pic1.zhimg.com/80/v2-19eebf97bbc7f21af452a6663cfec38d_hd.jpg) 152 | 153 | 影响应用迁移和横向扩展的重要因素就是应用的状态,无状态服务,是要把这个状态往外移,将 Session 数据,文件数据,结构化数据保存在后端统一的存储中,从而应用仅仅包含商务逻辑。 154 | 155 | 状态是不可避免的,例如 ZooKeeper, DB,Cache 等,把这些所有有状态的东西收敛在一个非常集中的集群里面。 156 | 157 | 整个业务就分两部分,一个是无状态的部分,一个是有状态的部分。 158 | 159 | 无状态的部分能实现两点,一是跨机房随意地部署,也即迁移性,一是弹性伸缩,很容易地进行扩容。 160 | 161 | 有状态的部分,如 DB,Cache,ZooKeeper 有自己的高可用机制,要利用到他们自己高可用的机制来实现这个状态的集群。 162 | 163 | 虽说无状态化,但是当前处理的数据,还是会在内存里面的,当前的进程挂掉数据,肯定也是有一部分丢失的,为了实现这一点,服务要有重试的机制,接口要有幂等的机制,通过服务发现机制,重新调用一次后端服务的另一个实例就可以了。 164 | 165 | ### 设计要点三:数据库的横向扩展。 166 | ![](https://pic2.zhimg.com/80/v2-91d4ca417ea4bc57fc5f43f73c4c8457_hd.jpg) 167 | 168 | 数据库是保存状态,是最重要的也是最容易出现瓶颈的。有了分布式数据库可以使数据库的性能可以随着节点增加线性地增加。 169 | 170 | 分布式数据库最最下面是 RDS,是主备的,通过 MySql 的内核开发能力,我们能够实现主备切换数据零丢失,所以数据落在这个 RDS 里面,是非常放心的,哪怕是挂了一个节点,切换完了以后,你的数据也是不会丢的。 171 | 172 | 再往上就是横向怎么承载大的吞吐量的问题,上面有一个负载均衡 NLB,用 LVS,HAProxy, Keepalived,下面接了一层 Query Server。Query Server 是可以根据监控数据进行横向扩展的,如果出现了故障,可以随时进行替换的修复,对于业务层是没有任何感知的。 173 | 174 | 另外一个就是双机房的部署,DDB 开发了一个数据运河 NDC 的组件,可以使得不同的 DDB 之间在不同的机房里面进行同步,这时候不但在一个数据中心里面是分布式的,在多个数据中心里面也会有一个类似双活的一个备份,高可用性有非常好的保证。 175 | 176 | ### 设计要点四:缓存 177 | ![](https://pic3.zhimg.com/80/v2-0f8f843c8d833ad122475e0dd56e5eab_hd.jpg) 178 | 179 | 在高并发场景下缓存是非常重要的。要有层次的缓存,使得数据尽量靠近用户。数据越靠近用户能承载的并发量也越大,响应时间越短。 180 | 181 | 在手机客户端 App 上就应该有一层缓存,不是所有的数据都每时每刻从后端拿,而是只拿重要的,关键的,时常变化的数据。 182 | 183 | 尤其对于静态数据,可以过一段时间去取一次,而且也没必要到数据中心去取,可以通过 CDN,将数据缓存在距离客户端最近的节点上,进行就近下载。 184 | 185 | 有时候 CDN 里面没有,还是要回到数据中心去下载,称为回源,在数据中心的最外层,我们称为接入层,可以设置一层缓存,将大部分的请求拦截,从而不会对后台的数据库造成压力。 186 | 187 | 如果是动态数据,还是需要访问应用,通过应用中的商务逻辑生成,或者去数据库读取,为了减轻数据库的压力,应用可以使用本地的缓存,也可以使用分布式缓存,如 Memcached 或者 Redis,使得大部分请求读取缓存即可,不必访问数据库。 188 | 189 | 当然动态数据还可以做一定的静态化,也即降级成静态数据,从而减少后端的压力。 190 | 191 | ### 设计要点五:服务拆分和服务发现 192 | ![](https://pic4.zhimg.com/80/v2-8c582f0e98d0ac592918beb012757559_hd.jpg) 193 | 194 | 当系统扛不住,应用变化快的时候,往往要考虑将比较大的服务拆分为一系列小的服务。 195 | 196 | 这样第一个好处就是开发比较独立,当非常多的人在维护同一个代码仓库的时候,往往对代码的修改就会相互影响,常常会出现我没改什么测试就不通过了,而且代码提交的时候,经常会出现冲突,需要进行代码合并,大大降低了开发的效率。 197 | 198 | 另一个好处就是上线独立,物流模块对接了一家新的快递公司,需要连同下单一起上线,这是非常不合理的行为,我没改还要我重启,我没改还让我发布,我没改还要我开会,都是应该拆分的时机。 199 | 200 | 另外再就是高并发时段的扩容,往往只有最关键的下单和支付流程是核心,只要将关键的交易链路进行扩容即可,如果这时候附带很多其他的服务,扩容即是不经济的,也是很有风险的。 201 | 202 | 再就是容灾和降级,在大促的时候,可能需要牺牲一部分的边角功能,但是如果所有的代码耦合在一起,很难将边角的部分功能进行降级。 203 | 204 | 当然拆分完毕以后,应用之间的关系就更加复杂了,因而需要服务发现的机制,来管理应用相互的关系,实现自动的修复,自动的关联,自动的负载均衡,自动的容错切换。 205 | 206 | ### 设计要点六:服务编排与弹性伸缩 207 | ![](https://pic3.zhimg.com/80/v2-716f5d35a790b5eb0cf3c378cafb9e70_hd.jpg) 208 | 209 | 当服务拆分了,进程就会非常的多,因而需要服务编排来管理服务之间的依赖关系,以及将服务的部署代码化,也就是我们常说的基础设施即代码。这样对于服务的发布,更新,回滚,扩容,缩容,都可以通过修改编排文件来实现,从而增加了可追溯性,易管理性,和自动化的能力。 210 | 211 | 既然编排文件也可以用代码仓库进行管理,就可以实现一百个服务中,更新其中五个服务,只要修改编排文件中的五个服务的配置就可以,当编排文件提交的时候,代码仓库自动触发自动部署升级脚本,从而更新线上的环境,当发现新的环境有问题时,当然希望将这五个服务原子性地回滚,如果没有编排文件,需要人工记录这次升级了哪五个服务。有了编排文件,只要在代码仓库里面 revert,就回滚到上一个版本了。所有的操作在代码仓库里都是可以看到的。 212 | 213 | ### 设计要点七:统一配置中心 214 | ![](https://pic1.zhimg.com/80/v2-ee65797a5f4293d78775f250f37d2cb7_hd.jpg) 215 | 216 | 服务拆分以后,服务的数量非常多,如果所有的配置都以配置文件的方式放在应用本地的话,非常难以管理,可以想象当有几百上千个进程中有一个配置出现了问题,是很难将它找出来的,因而需要有统一的配置中心,来管理所有的配置,进行统一的配置下发。 217 | 218 | 在微服务中,配置往往分为几类,一类是几乎不变的配置,这种配置可以直接打在容器镜像里面,第二类是启动时就会确定的配置,这种配置往往通过环境变量,在容器启动的时候传进去,第三类就是统一的配置,需要通过配置中心进行下发,例如在大促的情况下,有些功能需要降级,哪些功能可以降级,哪些功能不能降级,都可以在配置文件中统一配置。 219 | 220 | ### 设计要点八:统一的日志中心 221 | ![](https://pic1.zhimg.com/80/v2-88ef3b80e824cc64df8f2c40188b1a31_hd.jpg) 222 | 223 | 同样是进程数目非常多的时候,很难对成千上百个容器,一个一个登录进去查看日志,所以需要统一的日志中心来收集日志,为了使收集到的日志容易分析,对于日志的规范,需要有一定的要求,当所有的服务都遵守统一的日志规范的时候,在日志中心就可以对一个交易流程进行统一的追溯。例如在最后的日志搜索引擎中,搜索交易号,就能够看到在哪个过程出现了错误或者异常。 224 | 225 | ### 设计要点九:熔断,限流,降级 226 | ![](https://pic1.zhimg.com/80/v2-74fecf0238c7d15d8d1597bc263e425e_hd.jpg) 227 | 228 | 服务要有熔断,限流,降级的能力,当一个服务调用另一个服务,出现超时的时候,应及时返回,而非阻塞在那个地方,从而影响其他用户的交易,可以返回默认的托底数据。 229 | 230 | 当一个服务发现被调用的服务,因为过于繁忙,线程池满,连接池满,或者总是出错,则应该及时熔断,防止因为下一个服务的错误或繁忙,导致本服务的不正常,从而逐渐往前传导,导致整个应用的雪崩。 231 | 232 | 当发现整个系统的确负载过高的时候,可以选择降级某些功能或某些调用,保证最重要的交易流程的通过,以及最重要的资源全部用于保证最核心的流程。 233 | 234 | 还有一种手段就是限流,当既设置了熔断策略,又设置了降级策略,通过全链路的压力测试,应该能够知道整个系统的支撑能力,因而就需要制定限流策略,保证系统在测试过的支撑能力范围内进行服务,超出支撑能力范围的,可拒绝服务。当你下单的时候,系统弹出对话框说 “系统忙,请重试”,并不代表系统挂了,而是说明系统是正常工作的,只不过限流策略起到了作用。 235 | 236 | ### 设计要点十:全方位的监控 237 | ![](https://pic1.zhimg.com/80/v2-b367fd48938230d8fb2955b84cd93926_hd.jpg) 238 | 239 | 当系统非常复杂的时候,要有统一的监控,主要有两个方面,一个是是否健康,一个是性能瓶颈在哪里。当系统出现异常的时候,监控系统可以配合告警系统,及时地发现,通知,干预,从而保障系统的顺利运行。 240 | 241 | 当压力测试的时候,往往会遭遇瓶颈,也需要有全方位的监控来找出瓶颈点,同时能够保留现场,从而可以追溯和分析,进行全方位的优化。 242 | 243 | ## 四、Kubernetes 本身就是微服务架构 244 | 245 | 基于上面这十个设计要点,我们再回来看 Kubernetes,会发现越看越顺眼。 246 | 247 | 首先 Kubernetes 本身就是微服务的架构,虽然看起来复杂,但是容易定制化,容易横向扩展。 248 | ![](https://pic3.zhimg.com/80/v2-41add76a0b1cd02074ff99d26402892b_hd.jpg) 249 | 250 | 如图黑色的部分是 Kubernetes 原生的部分,而蓝色的部分是网易云为了支撑大规模高并发应用而做的定制化部分。 251 | ![](https://pic1.zhimg.com/80/v2-22b05baed189fb9bdaba5e4039e80fba_hd.jpg) 252 | 253 | Kubernetes 的 API Server 更像网关,提供统一的鉴权和访问接口。 254 | 255 | 众所周知,Kubernetes 的租户管理相对比较弱,尤其是对于公有云场景,复杂的租户关系的管理,我们只要定制化 API Server,对接 Keystone,就可以管理复杂的租户关系,而不用管其他的组件。 256 | ![](https://pic1.zhimg.com/80/v2-8beb43c3363a53274e9c0994ce8eb1e9_hd.jpg) 257 | 258 | 在 Kubernetes 中几乎所有的组件都是无状态化的,状态都保存在统一的 etcd 里面,这使得扩展性非常好,组件之间异步完成自己的任务,将结果放在 etcd 里面,互相不耦合。 259 | 260 | 例如图中 pod 的创建过程,客户端的创建仅仅是在 etcd 中生成一个记录,而其他的组件监听到这个事件后,也相应异步的做自己的事情,并将处理的结果同样放在 etcd 中,同样并不是哪一个组件远程调用 kubelet,命令它进行容器的创建,而是发现 etcd 中,pod 被绑定到了自己这里,方才拉起。 261 | 262 | 为了在公有云中实现租户的隔离性,我们的策略是不同的租户,不共享节点,这就需要 Kubernetes 对于 IaaS 层有所感知,因而需要实现自己的 Controller,Kubernetes 的设计使得我们可以独立创建自己的 Controller,而不是直接改代码。 263 | ![](https://pic4.zhimg.com/80/v2-91aec2ae7048c9be4c5d14c2846dddd5_hd.jpg) 264 | 265 | API-Server 作为接入层,是有自己的缓存机制的,防止所有的请求压力直接到后端数据库上。但是当仍然无法承载高并发请求时,瓶颈依然在后端的 etcd 存储上,这和电商应用一摸一样。当然能够想到的方式也是对 etcd 进行分库分表,不同的租户保存在不同的 etcd 集群中。 266 | 267 | 有了 API Server 做 API 网关,后端的服务进行定制化,对于 client 和 kubelet 是透明的。 268 | ![](https://pic1.zhimg.com/80/v2-25f7e24db4445ba739d9a30a49479beb_hd.jpg) 269 | 270 | 如图是定制化的容器创建流程,由于大促和非大促期间,节点的数目相差比较大,因而不能采用事先全部创建好节点的方式,这样会造成资源的浪费,因而中间添加了网易云自己的模块 Controller 和 IaaS 的管理层,使得当创建容器资源不足的时候,动态调用 IaaS 的接口,动态的创建资源。这一切对于客户端和 kubelet 无感知。 271 | ![](https://pic4.zhimg.com/80/v2-40320ad412f120448bdf8b5f160c62eb_hd.jpg) 272 | 273 | 为了解决超过 3 万个节点的规模问题,网易云需要对各个模块进行优化,由于每个子模块仅仅完成自己的功能,Scheduler 只管调度,Proxy 只管转发,而非耦合在一起,因而每个组件都可以进行独立的优化,这符合微服务中的独立功能,独立优化,互不影响。而且 Kubernetes 的所有组件都是 Go 开发的,更加容易一些。所以 Kubernetes 上手慢,但是一旦需要定制化,会发现更加容易。 274 | 275 | ## 五、Kubernetes 更加适合微服务和 DevOps 的设计 276 | 277 | 好了,说了 K8S 本身,接下来说说 K8S 的理念设计,为什么这么适合微服务。 278 | ![](https://pic2.zhimg.com/80/v2-2f2d7777c23693b5a8ffaa60bc024afb_hd.jpg) 279 | 280 | 前面微服务设计的十大模式,其中一个就是区分无状态和有状态,在 K8S 中,无状态对应 deployment,有状态对应 StatefulSet。 281 | 282 | deployment 主要通过副本数,解决横向扩展的问题。 283 | 284 | 而 StatefulSet 通过一致的网络 ID,一致的存储,顺序的升级,扩展,回滚等机制,保证有状态应用,很好地利用自己的高可用机制。因为大多数集群的高可用机制,都是可以容忍一个节点暂时挂掉的,但是不能容忍大多数节点同时挂掉。而且高可用机制虽然可以保证一个节点挂掉后回来,有一定的修复机制,但是需要知道刚才挂掉的到底是哪个节点,StatefulSet 的机制可以让容器里面的脚本有足够的信息,处理这些情况,实现哪怕是有状态,也能尽快修复。 285 | ![](https://pic4.zhimg.com/80/v2-7f9799aeeb0cb83b3f97d7833bd670c0_hd.jpg) 286 | 287 | 在微服务中,比较推荐使用云平台的 PaaS,例如数据库,消息总线,缓存等。但是配置也是非常复杂的,因为不同的环境需要连接不同的 PaaS 服务。 288 | 289 | K8S 里面的 headless service 是可以很好地解决这个问题的,只要给外部服务创建一个 headless service,指向相应的 PaaS 服务,并且将服务名配置到应用中。由于生产和测试环境分成 Namespace,虽然配置了相同的服务名,但是不会错误访问,简化了配置。 290 | ![](https://pic4.zhimg.com/80/v2-7f9799aeeb0cb83b3f97d7833bd670c0_hd.jpg) 291 | 292 | 微服务少不了服务发现,除了应用层可以使用 SpringCloud 或者 Dubbo 进行服务发现,在容器平台层当然是用 Service了,可以实现负载均衡,自修复,自动关联。 293 | ![](https://pic2.zhimg.com/80/v2-e06cf0afb975aebac6ae9303381bc517_hd.jpg) 294 | 295 | 服务编排,本来 K8S 就是编排的标准,可以将 yml 文件放到代码仓库中进行管理,而通过 deployment 的副本数,可以实现弹性伸缩。 296 | ![](https://pic2.zhimg.com/80/v2-b6ab5f1572abe7c857cae91503045f53_hd.jpg) 297 | 298 | 对于配置中心,K8S 提供了 configMap,可以在容器启动的时候,将配置注入到环境变量或者 Volume 里面。但是唯一的缺点是,注入到环境变量中的配置不能动态改变了,好在 Volume 里面的可以,只要容器中的进程有 reload 机制,就可以实现配置的动态下发了。 299 | ![](https://pic3.zhimg.com/80/v2-80bb4f5a52e3efea59519071e0276dcc_hd.jpg) 300 | 301 | 统一日志和监控往往需要在 Node 上部署 Agent,来对日志和指标进行收集,当然每个 Node 上都有,daemonset 的设计,使得更容易实现。 302 | ![](https://pic3.zhimg.com/80/v2-1d0829c125a8274f741050563dbdfb05_hd.jpg) 303 | 304 | 当然目前最最火的 Service Mesh,可以实现更加精细化的服务治理,进行熔断,路由,降级等策略。Service Mesh 的实现往往通过 sidecar 的方式,拦截服务的流量,进行治理。这也得力于 Pod 的理念,一个 Pod 可以有多个容器,如果当初的设计没有 Pod,直接启动的就是容器,会非常的不方便。 305 | 306 | 所以 K8S 的各种设计,看起来非常冗余和复杂,入门门槛比较高,但是一旦想实现真正的微服务,K8S 可以给你各种可能的组合方式。实践过微服务的人,往往会对这一点深有体会。 307 | 308 | ## 六、Kubernetes 常见的使用方式 309 | 310 | 关于微服务化的不同阶段,Kubernetes 的使用方式,详见这篇文章:[微服务化的不同阶段 Kubernetes 的不同玩法](https://zhuanlan.zhihu.com/p/34852026) 311 | 312 | 313 | 314 | 原文地址:[为什么 kubernetes 天然适合微服务](https://mp.weixin.qq.com/s/L5imIU7pW6RN0VB2XgQ_kw) -------------------------------------------------------------------------------- /2018/Q1/使用Go语言从零开始编写PoS区块链.md: -------------------------------------------------------------------------------- 1 | >本文发表时间:2018年3月16号 2 | 3 | ## PoS简介 4 | 在[上一篇文章]((https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f))中,我们讨论了工作量证明(Proof of Work),并向您展示了如何编写自己的工作量证明区块链。当前最流行的两个区块链平台,比特币和Ethereum都是基于工作量证明的。 5 | 6 | 但是工作证明的缺点是什么呢?其中一个主要的问题是电力能源的消耗。为了挖掘更多的比特币,就需要建立更多的挖矿硬件池,现在在世界各地,挖矿池都在不断建立中,而且呈现出规模越来越大的趋势。例如以下这张照片(仅仅是矿池的一角): 7 | ![挖矿场的一角.png](https://upload-images.jianshu.io/upload_images/8245841-999aef0e41d1b4e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 8 | 9 | 挖矿工作需要耗费大量的电力,仅比特币开采耗费的能源就超过了159个国家的电力能源消耗总和!!这种能源消耗是非常非常不合理的,而且,从技术的角度来看,工作量证明还有其他不足之处:随着越来越多的人参与到挖矿工作中,共识算法的难度就需要提高,难度的提高意味着需要更多、更长时间的挖矿,也意味着区块和交易需要更长的时间才能得到处理,因此能源的消耗就会越发的高。总之,工作量证明的方式就是一场竞赛,你需要更多的计算能力才能有更大的概率赢得比赛。 10 | 11 | 有很多区块链学者都试图找到工作量证明的替代品,到目前为止最有希望的就是PoS(权益证明或者股权证明,Proof of Stake)。目前在生产环境,已经有数个区块链平台使用了PoS,例如[Nxt](https://nxtplatform.org/what-is-nxt/) 和[Neo](https://neo.org/)。以太坊Ethereum在不远的未来也很可能会使用PoS——他们的Casper项目已经在测试网络上运行和测试了。 12 | 13 | ##### 那么,到底什么才是股权证明PoS呢? 14 | 在PoW中,节点之间通过hash的计算力来竞赛以获取下一个区块的记账权,而在PoS中,块是已经铸造好的(这里没有“挖矿”的概念,所以我们不用这个词来证明股份),铸造的过程是基于每个节点(Node)愿意作为抵押的令牌(Token)数量。这些参与抵押的节点被称为验证者(Validator),**注意在本文后续内容中,验证者和节点的概念是等同的!**令牌的含义对于不同的区块链平台是不同的,例如,在以太坊中,每个验证者都将Ether作为抵押品。 15 | 16 | 如果验证者愿意提供更多的令牌作为抵押品,他们就有更大的机会记账下一个区块并获得奖励。你可以把奖励的区块看作是存款利息,你在银行存的钱越多,你每月的利息就会越高。 17 | 18 | 因此,这种共识机制被称为股权证明PoS。 19 | ##### PoS的缺陷是什么? 20 | 您可能已经猜到,一个拥有大量令牌的验证者会在创建新块时根据持有的令牌数量获得更高的概率。然而,这与我们在工作量证明中看到的并没有什么不同:比特币矿场变得越来越强大,普通人在自己的电脑上开采多年也未必能获得一个区块。因此,许多人认为,使用了PoS后,区块的分配将更加民主化,因为任何人都可以在自己的笔记本上参与,而不需要建立一个巨大的采矿平台,他们不需要昂贵的硬件,只需要一定的筹码,就算筹码不多,也有一定概率能获得区块的记账权,希望总是有的,你说呢? 21 | 22 | 从技术和经济的角度来看,还有其他不利因素。我们不会一一介绍,但[这里](https://bitcoin.stackexchange.com/questions/25743/what-are-the-downsides-of-proof-of-stake)有一个很好的介绍。在实际应用中,PoS和PoW都有自己的优点和缺点,因此以太坊的Casper具有两者混合的特征。 23 | 24 | 像往常一样,了解PoS的方法是编写自己的代码,那么,我们开始吧! 25 | 26 | ## 编写PoS代码 27 | 我们建议在继续之前看一下[200行Go代码编写区块链Part2](https://medium.com/@mycoralhealth/part-2-networking-code-your-own-blockchain-in-less-than-200-lines-of-go-17fe1dad46e1),因为在接下来的文章中,一些基础知识不再会介绍,因此这篇文章能帮助你回顾一下。 28 | 29 | ##### 注意 30 | 我们将实现PoS的核心概念,然后因为文章长度有限,因此一些不必要的代码奖省去! 31 | - P2P网络的实现。文中的网络是模拟的,区块链状态只在其中一个中心化节点持有,而不是每个节点,同时状态通过该持有节点广播到其它节点 32 | 33 | - 钱包和余额变动。本文没有实现一个钱包,持有的令牌数量是通过stdin(标准输入)输入的,你可以输入你想要的任何数量。一个完整的实现会为每个节点分配一个hash地址,并在节点中跟踪余额的变动 34 | 35 | ##### 架构图 36 | ![架构.png](https://upload-images.jianshu.io/upload_images/8245841-5372d326394c5a58.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 37 | 38 | - 我们将有一个中心化的TCP服务节点,其他节点可以连接该服务器 39 | - 最新的区块链状态将定期广播到每个节点 40 | - 每个节点都能提议建立新的区块 41 | - 基于每个节点的令牌数量,其中一个节点将随机地(以令牌数作为加权值)作为获胜者,并且将该区块添加到区块链中 42 | 43 | 44 | ##### 设置和导入 45 | 在开始写代码之前,我们需要一个环境变量来设置TCP服务器的端口,首先在工作文件夹中创建.env文件,写入一行配置: 46 | `ADDR=9000` 47 | 我们的Go程序将读取该文件,并且暴露出9000端口。同时在工作目录下,再创建一个main.go文件。 48 | ```go 49 | package main 50 | 51 | import ( 52 | "bufio" 53 | "crypto/sha256" 54 | "encoding/hex" 55 | "encoding/json" 56 | "fmt" 57 | "io" 58 | "log" 59 | "math/rand" 60 | "net" 61 | "os" 62 | "strconv" 63 | "sync" 64 | "time" 65 | 66 | "github.com/davecgh/go-spew/spew" 67 | "github.com/joho/godotenv" 68 | ) 69 | ``` 70 | 71 | - `spew` 可以把我们的区块链用漂亮的格式打印到终端terminal中 72 | - `godotenv` 允许我们从之前创建的.evn文件读取配置 73 | 74 | ##### 快速脉搏检查 75 | 如果你读过我们的其他教程,就会知道我们是一家医疗保健公司,目前要去收集人体脉搏信息,同时添加到我们的区块上。把两个手指放在你的手腕上,数一下你一分钟能感觉到多少次脉搏,这将是您的BPM整数,我们将在接下来的文章中使用。 76 | 77 | ##### 全局变量 78 | 现在,让我们声明我们需要的所有全局变量(main.go中)。 79 | ```go 80 | // Block represents each 'item' in the blockchain 81 | type Block struct { 82 | Index int 83 | Timestamp string 84 | BPM int 85 | Hash string 86 | PrevHash string 87 | Validator string 88 | } 89 | 90 | // Blockchain is a series of validated Blocks 91 | var Blockchain []Block 92 | var tempBlocks []Block 93 | 94 | // candidateBlocks handles incoming blocks for validation 95 | var candidateBlocks = make(chan Block) 96 | 97 | // announcements broadcasts winning validator to all nodes 98 | var announcements = make(chan string) 99 | 100 | var mutex = &sync.Mutex{} 101 | 102 | // validators keeps track of open validators and balances 103 | var validators = make(map[string]int 104 | ``` 105 | - Block是每个区块的内容 106 | - Blockchain是我们的官方区块链,它只是一串经过验证的区块集合。每个区块中的PrevHash与前面块的Hash相比较,以确保我们的链是正确的。tempBlocks是临时存储单元,在区块被选出来并添加到BlockChain之前,临时存储在这里 107 | - candidateBlocks是Block的通道,任何一个节点在提出一个新块时都将它发送到这个通道 108 | - announcements也是一个通道,我们的主Go TCP服务器将向所有节点广播最新的区块链 109 | - mutex是一个标准变量,允许我们控制读/写和防止数据竞争 110 | - validators是节点的存储map,同时也会保存每个节点持有的令牌数 111 | 112 | ##### 基本的区块链函数 113 | 在继续PoS算法之前,我们先来实现标准的区块链函数。如果你之前看过[200行Go代码编写区块链](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc?source=user_profile---------13----------------),那接下来应该更加熟悉。 114 | `main.go` 115 | ```go 116 | // SHA256 hasing 117 | // calculateHash is a simple SHA256 hashing function 118 | func calculateHash(s string) string { 119 | h := sha256.New() 120 | h.Write([]byte(s)) 121 | hashed := h.Sum(nil) 122 | return hex.EncodeToString(hashed) 123 | } 124 | 125 | //calculateBlockHash returns the hash of all block information 126 | func calculateBlockHash(block Block) string { 127 | record := string(block.Index) + block.Timestamp + string(block.BPM) + block.PrevHash 128 | return calculateHash(record) 129 | } 130 | ``` 131 | 132 | 这里先从hash函数开始,calculateHash函数会接受一个string,并且返回一个SHA256 hash。calculateBlockHash是对一个block进行hash,将一个block的所有字段连接到一起后,再进行hash。 133 | `main.go` 134 | ```go 135 | func generateBlock(oldBlock Block, BPM int, address string) (Block, error) { 136 | 137 | var newBlock Block 138 | 139 | t := time.Now() 140 | 141 | newBlock.Index = oldBlock.Index + 1 142 | newBlock.Timestamp = t.String() 143 | newBlock.BPM = BPM 144 | newBlock.PrevHash = oldBlock.Hash 145 | newBlock.Hash = calculateBlockHash(newBlock) 146 | newBlock.Validator = address 147 | 148 | return newBlock, nil 149 | } 150 | ``` 151 | generateBlock是用来创建新块的。每个新块都有的一个重要字段是它的hash签名(通过calculateBlockHash计算的)和上一个连接块的PrevHash(因此我们可以保持链的完整性)。我们还添加了一个Validator字段,这样我们就知道了该构建块的获胜节点。 152 | 153 | `main.go` 154 | ```go 155 | // isBlockValid makes sure block is valid by checking index 156 | // and comparing the hash of the previous block 157 | func isBlockValid(newBlock, oldBlock Block) bool { 158 | if oldBlock.Index+1 != newBlock.Index { 159 | return false 160 | } 161 | 162 | if oldBlock.Hash != newBlock.PrevHash { 163 | return false 164 | } 165 | 166 | if calculateBlockHash(newBlock) != newBlock.Hash { 167 | return false 168 | } 169 | 170 | return true 171 | } 172 | ``` 173 | 174 | isBlockValid会验证Block的当前hash和PrevHash,来确保我们的区块链不会被污染。 175 | 176 | ##### 节点(验证者) 177 | 当一个验证者连接到我们的TCP服务,我们需要提供一些函数达到以下目标: 178 | - 输入令牌的余额(之前提到过,我们不做钱包等逻辑) 179 | - 接收区块链的最新广播 180 | - 接收验证者赢得区块的广播信息 181 | - 将自身节点添加到全局的验证者列表中(validators) 182 | - 输入Block的BPM数据- BPM是每个验证者的人体脉搏值 183 | - 提议创建一个新的区块 184 | 185 | 这些目标,我们用`handleConn`函数来实现 186 | `main.go` 187 | ```go 188 | func handleConn(conn net.Conn) { 189 | defer conn.Close() 190 | 191 | go func() { 192 | for { 193 | msg := <-announcements 194 | io.WriteString(conn, msg) 195 | } 196 | }() 197 | // validator address 198 | var address string 199 | 200 | // allow user to allocate number of tokens to stake 201 | // the greater the number of tokens, the greater chance to forging a new block 202 | io.WriteString(conn, "Enter token balance:") 203 | scanBalance := bufio.NewScanner(conn) 204 | for scanBalance.Scan() { 205 | balance, err := strconv.Atoi(scanBalance.Text()) 206 | if err != nil { 207 | log.Printf("%v not a number: %v", scanBalance.Text(), err) 208 | return 209 | } 210 | t := time.Now() 211 | address = calculateHash(t.String()) 212 | validators[address] = balance 213 | fmt.Println(validators) 214 | break 215 | } 216 | 217 | io.WriteString(conn, "\nEnter a new BPM:") 218 | 219 | scanBPM := bufio.NewScanner(conn) 220 | 221 | go func() { 222 | for { 223 | // take in BPM from stdin and add it to blockchain after conducting necessary validation 224 | for scanBPM.Scan() { 225 | bpm, err := strconv.Atoi(scanBPM.Text()) 226 | // if malicious party tries to mutate the chain with a bad input, delete them as a validator and they lose their staked tokens 227 | if err != nil { 228 | log.Printf("%v not a number: %v", scanBPM.Text(), err) 229 | delete(validators, address) 230 | conn.Close() 231 | } 232 | 233 | mutex.Lock() 234 | oldLastIndex := Blockchain[len(Blockchain)-1] 235 | mutex.Unlock() 236 | 237 | // create newBlock for consideration to be forged 238 | newBlock, err := generateBlock(oldLastIndex, bpm, address) 239 | if err != nil { 240 | log.Println(err) 241 | continue 242 | } 243 | if isBlockValid(newBlock, oldLastIndex) { 244 | candidateBlocks <- newBlock 245 | } 246 | io.WriteString(conn, "\nEnter a new BPM:") 247 | } 248 | } 249 | }() 250 | 251 | // simulate receiving broadcast 252 | for { 253 | time.Sleep(time.Minute) 254 | mutex.Lock() 255 | output, err := json.Marshal(Blockchain) 256 | mutex.Unlock() 257 | if err != nil { 258 | log.Fatal(err) 259 | } 260 | io.WriteString(conn, string(output)+"\n") 261 | } 262 | 263 | } 264 | ``` 265 | 第一个Go协程接收并打印出来自TCP服务器的任何通知,这些通知包含了获胜验证者的通知。 266 | 267 | io.WriteString(conn, “Enter token balance:”)允许验证者输入他持有的令牌数量,然后,该验证者被分配一个SHA256地址,随后该验证者地址和验证者的令牌数被添加到验证者列表validators中。 268 | 269 | 接着我们输入BPM,验证者的脉搏值,并创建一个单独的Go协程来处理这块儿逻辑,下面这一行代码很重要: 270 | `delete(validators, address)` 271 | 272 | 如果验证者试图提议一个被污染(例如伪造)的block,例如包含一个不是整数的BPM,那么程序会抛出一个错误,我们会立即从我们的验证器列表validators中删除该验证者,他们将不再有资格参与到新块的铸造过程同时丢失相应的抵押令牌。 273 | 274 | 正式因为这种抵押令牌的机制,使得PoS协议是一种更加可靠的机制。如果一个人试图伪造和破坏,那么他将被抓住,并且失去所有抵押和未来的权益,因此对于恶意者来说,是非常大的威慑。 275 | 276 | 接着,我们用generateBlock函数创建一个新的block,然后将其发送到candidateBlocks通道进行进一步处理。将Block发送到通道使用的语法: 277 | `candidateBlocks <- newBlock` 278 | 279 | 上面代码中最后一段的循环会周期性的打印出最新的区块链,这样每个验证者都能获知最新的状态 280 | 281 | ##### 选择获胜者 282 | 283 | 这里是PoS的主题逻辑。我们需要编写代码以实现获胜验证者的选择;他们所持有的令牌数量越高,他们就越有可能被选为胜利者。 284 | 285 | 为了简化代码,我们只会让提出新块儿的验证者参与竞争。在传统的PoS,一个验证者即使没有提出一个新的区块,也可以被选为胜利者。切记,PoS不是一种确定的定义(算法),而是一种概念,因此对于不同的平台来说,可以有不同的PoS实现。 286 | 287 | 下面来看看pickWinner函数: 288 | `main.go` 289 | ```go 290 | // pickWinner creates a lottery pool of validators and chooses the validator who gets to forge a block to the blockchain 291 | // by random selecting from the pool, weighted by amount of tokens staked 292 | func pickWinner() { 293 | time.Sleep(30 * time.Second) 294 | mutex.Lock() 295 | temp := tempBlocks 296 | mutex.Unlock() 297 | 298 | lotteryPool := []string{} 299 | if len(temp) > 0 { 300 | 301 | // slightly modified traditional proof of stake algorithm 302 | // from all validators who submitted a block, weight them by the number of staked tokens 303 | // in traditional proof of stake, validators can participate without submitting a block to be forged 304 | OUTER: 305 | for _, block := range temp { 306 | // if already in lottery pool, skip 307 | for _, node := range lotteryPool { 308 | if block.Validator == node { 309 | continue OUTER 310 | } 311 | } 312 | 313 | // lock list of validators to prevent data race 314 | mutex.Lock() 315 | setValidators := validators 316 | mutex.Unlock() 317 | 318 | k, ok := setValidators[block.Validator] 319 | if ok { 320 | for i := 0; i < k; i++ { 321 | lotteryPool = append(lotteryPool, block.Validator) 322 | } 323 | } 324 | } 325 | 326 | // randomly pick winner from lottery pool 327 | s := rand.NewSource(time.Now().Unix()) 328 | r := rand.New(s) 329 | lotteryWinner := lotteryPool[r.Intn(len(lotteryPool))] 330 | 331 | // add block of winner to blockchain and let all the other nodes know 332 | for _, block := range temp { 333 | if block.Validator == lotteryWinner { 334 | mutex.Lock() 335 | Blockchain = append(Blockchain, block) 336 | mutex.Unlock() 337 | for _ = range validators { 338 | announcements <- "\nwinning validator: " + lotteryWinner + "\n" 339 | } 340 | break 341 | } 342 | } 343 | } 344 | 345 | mutex.Lock() 346 | tempBlocks = []Block{} 347 | mutex.Unlock() 348 | } 349 | ``` 350 | 351 | 352 | 每隔30秒,我们选出一个胜利者,这样对于每个验证者来说,都有时间提议新的区块,参与到竞争中来。接着创建一个`lotteryPool`,它会持有所有验证者的地址,这些验证者都有机会成为一个胜利者。然后,对于提议块的暂存区域,我们会通过`if len(temp) > 0`来判断是否已经有了被提议的区块。 353 | 354 | 在`OUTER FOR`循环中,要检查暂存区域是否和lotteryPool中存在同样的验证者,如果存在,则跳过。 355 | 356 | 在以` k, ok := setValidators[block.Validator] `开始的代码块中,我们确保了从`temp`中取出来的验证者都是合法的,即这些验证者在验证者列表`validators`已存在。若合法,则把该验证者加入到`lotteryPool`中。 357 | 358 | 那么我们怎么根据这些验证者持有的令牌数来给予他们合适的随机权重呢? 359 | 360 | 首先,用验证者的令牌填充`lotteryPool`数组,例如一个验证者有100个令牌,那么在`lotteryPool`中就将有100个元素填充;如果有1个令牌,那么将仅填充1个元素。 361 | 362 | 然后,从`lotteryPool`中随机选择一个元素,元素所属的验证者即是胜利者,把胜利验证者的地址赋值给`lotteryWinner`。这里能够看出来,如果验证者持有的令牌越多,那么他在数组中的元素也越多,他获胜的概率就越大;同时,持有令牌很少的验证者,也是有概率获胜的。 363 | 364 | 接着我们把获胜者的区块添加到整条区块链上,然后通知所有节点关于胜利者的消息:`announcements <- “\nwinning validator: “ + lotteryWinner + “\n”`。 365 | 366 | 最后,清空`tempBlocks`,以便下次提议的进行。 367 | 368 | 以上便是PoS一致性算法的核心内容,该算法简单、明了、公正,所以很酷! 369 | 370 | ##### 收尾 371 | 下面我们把之前的内容通过代码都串联起来 372 | `main.go` 373 | ```go 374 | func main() { 375 | err := godotenv.Load() 376 | if err != nil { 377 | log.Fatal(err) 378 | } 379 | 380 | // create genesis block 381 | t := time.Now() 382 | genesisBlock := Block{} 383 | genesisBlock = Block{0, t.String(), 0, calculateBlockHash(genesisBlock), "", ""} 384 | spew.Dump(genesisBlock) 385 | Blockchain = append(Blockchain, genesisBlock) 386 | 387 | // start TCP and serve TCP server 388 | server, err := net.Listen("tcp", ":"+os.Getenv("ADDR")) 389 | if err != nil { 390 | log.Fatal(err) 391 | } 392 | defer server.Close() 393 | 394 | go func() { 395 | for candidate := range candidateBlocks { 396 | mutex.Lock() 397 | tempBlocks = append(tempBlocks, candidate) 398 | mutex.Unlock() 399 | } 400 | }() 401 | 402 | go func() { 403 | for { 404 | pickWinner() 405 | } 406 | }() 407 | 408 | for { 409 | conn, err := server.Accept() 410 | if err != nil { 411 | log.Fatal(err) 412 | } 413 | go handleConn(conn) 414 | } 415 | } 416 | ``` 417 | 418 | 这里从.env文件开始,然后创建一个创世区块`genesisBlock`,形成了区块链。接着启动了Tcp服务,等待所有验证者的连接。 419 | 420 | 启动了一个Go协程从`candidateBlocks`通道中获取提议的区块,然后填充到临时缓冲区`tempBlocks`中,最后启动了另外一个Go协程来完成`pickWinner`函数。 421 | 422 | 最后面的for循环,用来接收验证者节点的连接。 423 | 424 | 这里是所有的源代码:[mycoralhealth/blockchain-tutorial](https://github.com/mycoralhealth/blockchain-tutorial/blob/master/proof-stake/main.go) 425 | 426 | ##### 结果 427 | 下面来运行程序,打开一个终端窗口,通过`go run main.go`来启动整个TCP程序,如我们所料,首先创建了创始区块`genesisBlock`。 428 | ![](https://upload-images.jianshu.io/upload_images/8245841-8c50062793235088.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 429 | 430 | 431 | 接着,我们启动并连接一个验证者。打开一个新的终端窗口,通过linux命令`nc localhost 9000`来连接到之前的TCP服务。然后在命令提示符后输入一个持有的令牌数额,最后再输入一个验证者的脉搏速率BPM。 432 | 433 | ![](https://upload-images.jianshu.io/upload_images/8245841-5f2fb3d2391299fe.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 434 | 435 | 436 | 然后观察第一个窗口(主程序),可以看到验证者被分配了地址,而且每次有新的验证者加入时,都会打印所有的验证者列表 437 | ![](https://upload-images.jianshu.io/upload_images/8245841-72e4a6141fabd29a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 438 | 439 | 稍等片刻,检查下你的新窗口(验证者),可以看到正在发生的事:我们的程序在花费时间选择胜利者,然后Boom一声,一个胜利者就诞生了! 440 | 441 | ![](https://upload-images.jianshu.io/upload_images/8245841-b8253ace6ad57356.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 442 | 443 | 再稍等一下,boom! 我们看到新的区块链被广播给所有的验证者窗口,包含了胜利者的区块和他的BPM信息。很酷吧! 444 | ![](https://upload-images.jianshu.io/upload_images/8245841-9cbc8479f7457c78.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 445 | 446 | ## 下一步做什么 447 | 448 | 449 | 你应该为能通过本教程感到骄傲。大多数区块链的发烧友和许多程序员都听说过PoS的证明,但他们很多都无法解释它到底是什么。你已经做得更深入了,而且实际上已经从头开始实现了一遍,你离成为下一代区块链技术的专家又近了一步! 450 | 451 | 因为这是一个教程,我们可以做更多的事情来让它成为区块链,例如: 452 | - 阅读我们的PoW,然后结合PoS,看看你是否可以创建一个混合区块链 453 | - 添加时间机制,验证者根据时间块来获得提议新区快的概率。我们这个版本的代码让验证者可以在任何时候提议新的区块。 454 | - 添加完整的点对点的能力。这基本上意味着每个验证者将运行自己的TCP服务器,并连接到其他的验证者节点。这里需要添加逻辑,这样每个节点都可以找到彼此,[这里](https://bitcoin.stackexchange.com/questions/3536/how-do-bitcoin-clients-find-each-other)有更多的内容。 455 | 456 | 或者你可以学习一下我们其它的教程: 457 | - [200行Go代码编写区块链](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc) 458 | - [使用IPFS在区块链上存储文件](https://medium.com/@mycoralhealth/learn-to-securely-share-files-on-the-blockchain-with-ipfs-219ee47df54c) 459 | - [区块链网络](https://medium.com/@mycoralhealth/part-2-networking-code-your-own-blockchain-in-less-than-200-lines-of-go-17fe1dad46e1) 460 | - [从零编写PoW](https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f) 461 | 462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /2018/Q2/使用Go语言编写区块链P2P网络.md: -------------------------------------------------------------------------------- 1 | > 外文发表日期: 2018-04-14 2 | 分类于[区块链](../../index/blockchain.md) 3 | 本文发表时间:2018-04-15 4 | 外文链接:https://medium.com/coinmonks/code-a-simple-p2p-blockchain-in-go-46662601f417 5 | 6 | ![](https://upload-images.jianshu.io/upload_images/8245841-7ed8a7dcaa5c8fe4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 7 | 8 | 在之前的文章中,我们已经知道了怎么编写[PoW](https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f)也知道了[IPFS](https://medium.com/@mycoralhealth/learn-to-securely-share-files-on-the-blockchain-with-ipfs-219ee47df54c)怎么工作, 但是有一个致命的缺点,我们的服务都是中心化的,这篇文章会教你怎么实现一个简单的完全去中心化的P2P网络。 9 | 10 | ## 背景知识 11 | #### 什么是P2P网络 12 | 在真正的P2P架构中,不需要中心化的服务来维护区块链的状态。例如,当你给朋友发送比特币时,比特币区块链的“状态”应该更新,这样你朋友的余额就会增加,你的余额就会减少。 13 | 14 | 在这个网络中,不存在一个权力高度中心化的机构来维护状态(银行就是这样的中心化机构)。对于比特币网络来说,每个节点都会维护一份完整的区块链状态,当交易发生时,每个节点的区块链状态都会得到更新。这样,只要网络中51%的节点对区块链的状态达成一致,那么区块链网络就是安全可靠的,具体可以阅读这篇一致性协议[文章](https://medium.com/@mycoralhealth/code-your-own-blockchain-mining-algorithm-in-go-82c6a71aba1f)。 15 | 16 | 本文将继续之前的工作,[200行Go代码实现区块链](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc), 并加入P2P网络架构。在继续之前,强烈建议你先阅读该篇文章,它会帮助你理解接下来的代码。 17 | 18 | ## 开始实现 19 | 编写P2P网络可不是开开玩笑就能简单视线的,有很多边边角角的情况都要覆盖到,而且需要你拥有很多工程学的知识,这样的P2P网络才是可扩展、高可靠的。有句谚语说得好:站在巨人肩膀上做事,那么我们先看看巨人们提供了哪些[工具](https://en.wikipedia.org/wiki/Standing_on_the_shoulders_of_giants)吧。 20 | 21 | 喔,看看,我们发现了什么!一个用Go语言实现的P2P库[go-libp2p](https://github.com/libp2p/go-libp2p)!如果你对新技术足够敏锐,就会发现这个库的作者和IPFS的作者是同一个团队。如果你还没看过我们的IPFS教程,可以看看[这里](https://medium.com/@mycoralhealth/learn-to-securely-share-files-on-the-blockchain-with-ipfs-219ee47df54c), 你可以选择跳过IPFS教程,因为对于本文这不是必须的。 22 | 23 | #### 警告 24 | 目前来说,`go-libp2p`主要有两个缺点: 25 | 1. 安装设置比较痛苦,它使用gx作为包管理工具,怎么说呢,不咋好用,但是凑活用吧 26 | 2. 目前项目还没有成熟,正在紧密锣鼓的开发中,当使用这个库时,可能会遇到一些数据竞争(data race) 27 | 28 | 对于第一点,不必担心,有我们呢。第二点是比较大的问题,但是不会影响我们的代码。假如你在使用过程中发现了数据竞争问题,记得给项目提一个issue,帮助它更好的成长! 29 | 30 | 总之,目前开源世界中,现代化的P2P库是非常非常少的,因为我们要多给`go-libp2p`一些耐心和包容,而且就目前来说,它已经能很好的满足我们的目标了。 31 | 32 | #### 安装设置 33 | 最好的环境设置方式是直接clone `libp2p`库,然后在这个库的代码中直接开发。你也可以在自己的库中,调用这个库开发,但是这样就需要用到`gx`了。这里我们使用简单的方式,假设你已经安装了Go: 34 | - `go get -d github.com/libp2p/go-libp2p/…` 35 | - 进入`go-libp2p`文件夹 36 | - `make` 37 | - `make deps` 38 | 39 | 40 | 这里会通过gx包管理工具下载所有需要的包和依赖,再次申明,我们不喜欢gx,因为它打破了Go语言的很多惯例,但是为了这个很棒的库,认怂吧。 41 | 42 | 这里,我们在`examples`子目录下进行开发,因此在`go-libp2p`的examples下创建一个你自己的目录 43 | - `mkdir ./examples/p2p` 44 | 45 | 然后进入到p2p文件夹下,创建`main.go`文件,后面所有的代码都会在该文件中。 46 | 47 | 你的目录结构是这样的: 48 | ![](https://upload-images.jianshu.io/upload_images/8245841-64bb1e5d2f53687e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 49 | 50 | 好了,勇士们,拔出你们的剑,哦不,拔出你们的`main.go`,开始我们的征途吧! 51 | 52 | #### 导入相关库 53 | 这里申明我们需要用的库,大部分库是来自于`go-libp2p`本身的,在教程中,你会学到怎么去使用它们。 54 | ```go 55 | package main 56 | 57 | import ( 58 | "bufio" 59 | "context" 60 | "crypto/rand" 61 | "crypto/sha256" 62 | "encoding/hex" 63 | "encoding/json" 64 | "flag" 65 | "fmt" 66 | "io" 67 | "log" 68 | mrand "math/rand" 69 | "os" 70 | "strconv" 71 | "strings" 72 | "sync" 73 | "time" 74 | 75 | "github.com/davecgh/go-spew/spew" 76 | golog "github.com/ipfs/go-log" 77 | libp2p "github.com/libp2p/go-libp2p" 78 | crypto "github.com/libp2p/go-libp2p-crypto" 79 | host "github.com/libp2p/go-libp2p-host" 80 | net "github.com/libp2p/go-libp2p-net" 81 | peer "github.com/libp2p/go-libp2p-peer" 82 | pstore "github.com/libp2p/go-libp2p-peerstore" 83 | ma "github.com/multiformats/go-multiaddr" 84 | gologging "github.com/whyrusleeping/go-logging" 85 | ) 86 | ``` 87 | 88 | `spew`包可以很方便、优美的打印出我们的区块链,因此记得安装它: 89 | - `go get github.com/davecgh/go-spew/spew` 90 | 91 | #### 区块链结构 92 | 记住,请先阅读[200行Go代码实现区块链](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc), 这样,下面的部分就会简单很多。 93 | 94 | 先来申明全局变量: 95 | ```go 96 | // Block represents each 'item' in the blockchain 97 | type Block struct { 98 | Index int 99 | Timestamp string 100 | BPM int 101 | Hash string 102 | PrevHash string 103 | } 104 | 105 | // Blockchain is a series of validated Blocks 106 | var Blockchain []Block 107 | 108 | var mutex = &sync.Mutex{} 109 | ``` 110 | 111 | 112 | - 我们是一家健康看护公司,因此Block中存着的是用户的脉搏速率BPM 113 | - Blockchain是我们的"状态",或者严格的说:最新的Blockchain,它其实就是Block的切片(slice) 114 | - mutex是为了防止资源竞争出现 115 | 116 | 下面是Blockchain相关的特定函数: 117 | ```go 118 | // make sure block is valid by checking index, and comparing the hash of the previous block 119 | func isBlockValid(newBlock, oldBlock Block) bool { 120 | if oldBlock.Index+1 != newBlock.Index { 121 | return false 122 | } 123 | 124 | if oldBlock.Hash != newBlock.PrevHash { 125 | return false 126 | } 127 | 128 | if calculateHash(newBlock) != newBlock.Hash { 129 | return false 130 | } 131 | 132 | return true 133 | } 134 | 135 | // SHA256 hashing 136 | func calculateHash(block Block) string { 137 | record := strconv.Itoa(block.Index) + block.Timestamp + strconv.Itoa(block.BPM) + block.PrevHash 138 | h := sha256.New() 139 | h.Write([]byte(record)) 140 | hashed := h.Sum(nil) 141 | return hex.EncodeToString(hashed) 142 | } 143 | 144 | // create a new block using previous block's hash 145 | func generateBlock(oldBlock Block, BPM int) Block { 146 | 147 | var newBlock Block 148 | 149 | t := time.Now() 150 | 151 | newBlock.Index = oldBlock.Index + 1 152 | newBlock.Timestamp = t.String() 153 | newBlock.BPM = BPM 154 | newBlock.PrevHash = oldBlock.Hash 155 | newBlock.Hash = calculateHash(newBlock) 156 | 157 | return newBlock 158 | } 159 | ``` 160 | 161 | - `isBlockValid`检查Block的hash是否合法 162 | - `calculateHash`使用`sha256`来对原始数据做hash 163 | - `generateBlock`创建一个新的Block区块,然后添加到区块链Blockchain上,同时会包含所需的事务 164 | 165 | #### P2P结构 166 | 167 | 下面我们快接近核心部分了,首先我们要写出创建主机的逻辑。当一个节点运行我们的程序时,它可以作为一个主机,被其它节点连接。下面一起看看代码:-) 168 | ```go 169 | // makeBasicHost creates a LibP2P host with a random peer ID listening on the 170 | // given multiaddress. It will use secio if secio is true. 171 | func makeBasicHost(listenPort int, secio bool, randseed int64) (host.Host, error) { 172 | 173 | // If the seed is zero, use real cryptographic randomness. Otherwise, use a 174 | // deterministic randomness source to make generated keys stay the same 175 | // across multiple runs 176 | var r io.Reader 177 | if randseed == 0 { 178 | r = rand.Reader 179 | } else { 180 | r = mrand.New(mrand.NewSource(randseed)) 181 | } 182 | 183 | // Generate a key pair for this host. We will use it 184 | // to obtain a valid host ID. 185 | priv, _, err := crypto.GenerateKeyPairWithReader(crypto.RSA, 2048, r) 186 | if err != nil { 187 | return nil, err 188 | } 189 | 190 | opts := []libp2p.Option{ 191 | libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", listenPort)), 192 | libp2p.Identity(priv), 193 | } 194 | 195 | if !secio { 196 | opts = append(opts, libp2p.NoEncryption()) 197 | } 198 | 199 | basicHost, err := libp2p.New(context.Background(), opts...) 200 | if err != nil { 201 | return nil, err 202 | } 203 | 204 | // Build host multiaddress 205 | hostAddr, _ := ma.NewMultiaddr(fmt.Sprintf("/ipfs/%s", basicHost.ID().Pretty())) 206 | 207 | // Now we can build a full multiaddress to reach this host 208 | // by encapsulating both addresses: 209 | addr := basicHost.Addrs()[0] 210 | fullAddr := addr.Encapsulate(hostAddr) 211 | log.Printf("I am %s\n", fullAddr) 212 | if secio { 213 | log.Printf("Now run \"go run main.go -l %d -d %s -secio\" on a different terminal\n", listenPort+1, fullAddr) 214 | } else { 215 | log.Printf("Now run \"go run main.go -l %d -d %s\" on a different terminal\n", listenPort+1, fullAddr) 216 | } 217 | 218 | return basicHost, nil 219 | } 220 | ``` 221 | `makeBasicHost`函数有3个参数,同时返回一个host结构体 222 | - `listenPort`是主机监听的端口,其它节点会连接该端口 223 | - `secio`表明是否开启数据流的安全选项,最好开启,因此它代表了"安全输入/输出" 224 | - `randSeed`是一个可选的命令行标识,可以允许我们提供一个随机数种子来为我们的主机生成随机的地址。这里我们不会使用 225 | 226 | 函数的第一个`if`语句针对随机种子生成随机key,接着我们生成公钥和私钥,这样能保证主机是安全的。`opts`部分开始构建网络地址部分,这样其它节点就可以连接进来。 227 | 228 | `!secio`部分可以绕过加密,但是我们准备使用加密,因此这段代码不会被触发。 229 | 230 | 接着,创建了主机地址,这样其他节点就可以连接进来。` log.Printf `可以用来在控制台打印出其它节点的连接信息。最后我们返回生成的主机地址给调用方函数。 231 | 232 | #### 流处理 233 | 之前的主机需要能处理进入的数据流。当另外一个节点连接到主机时,它会想要提出一个新的区块链,来覆盖主机上的区块链,因此我们需要逻辑来判定是否要接受新的区块链。 234 | 235 | 同时,当我们往本地的区块链添加区块后,也要把相关信息广播给其它节点,这里也需要实现相关逻辑。 236 | 237 | 先来创建流处理的基本框架吧: 238 | ```go 239 | func handleStream(s net.Stream) { 240 | 241 | log.Println("Got a new stream!") 242 | 243 | // Create a buffer stream for non blocking read and write. 244 | rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) 245 | 246 | go readData(rw) 247 | go writeData(rw) 248 | 249 | // stream 's' will stay open until you close it (or the other side closes it). 250 | } 251 | ``` 252 | 253 | 这里创建一个新的`ReadWriter`,为了能支持数据读取和写入,同时我们启动了一个单独的Go协程来处理相关读写逻辑。 254 | 255 | #### 读取数据 256 | 首先创建`readData`函数: 257 | ```go 258 | func readData(rw *bufio.ReadWriter) { 259 | 260 | for { 261 | str, err := rw.ReadString('\n') 262 | if err != nil { 263 | log.Fatal(err) 264 | } 265 | 266 | if str == "" { 267 | return 268 | } 269 | if str != "\n" { 270 | 271 | chain := make([]Block, 0) 272 | if err := json.Unmarshal([]byte(str), &chain); err != nil { 273 | log.Fatal(err) 274 | } 275 | 276 | mutex.Lock() 277 | if len(chain) > len(Blockchain) { 278 | Blockchain = chain 279 | bytes, err := json.MarshalIndent(Blockchain, "", " ") 280 | if err != nil { 281 | 282 | log.Fatal(err) 283 | } 284 | // Green console color: \x1b[32m 285 | // Reset console color: \x1b[0m 286 | fmt.Printf("\x1b[32m%s\x1b[0m> ", string(bytes)) 287 | } 288 | mutex.Unlock() 289 | } 290 | } 291 | } 292 | ``` 293 | 294 | 该函数是一个无限循环,因为它需要永不停歇的去读取外面进来的数据。首先,我们使用`ReadString`解析从其它节点发送过来的新的区块链(JSON字符串)。 295 | 296 | 然后检查进来的区块链的长度是否比我们本地的要长,如果进来的链更长,那么我们就接受新的链为最新的网络状态(最新的区块链)。 297 | 298 | 同时,把最新的区块链在控制台使用一种特殊的颜色打印出来,这样我们就知道有新链接受了。 299 | 300 | 如果在我们主机的本地添加了新的区块到区块链上,那就需要把本地最新的区块链广播给其它相连的节点知道,这样这些节点机会接受并更新到我们的区块链版本。这里使用`writeData`函数: 301 | ```go 302 | func writeData(rw *bufio.ReadWriter) { 303 | 304 | go func() { 305 | for { 306 | time.Sleep(5 * time.Second) 307 | mutex.Lock() 308 | bytes, err := json.Marshal(Blockchain) 309 | if err != nil { 310 | log.Println(err) 311 | } 312 | mutex.Unlock() 313 | 314 | mutex.Lock() 315 | rw.WriteString(fmt.Sprintf("%s\n", string(bytes))) 316 | rw.Flush() 317 | mutex.Unlock() 318 | 319 | } 320 | }() 321 | 322 | stdReader := bufio.NewReader(os.Stdin) 323 | 324 | for { 325 | fmt.Print("> ") 326 | sendData, err := stdReader.ReadString('\n') 327 | if err != nil { 328 | log.Fatal(err) 329 | } 330 | 331 | sendData = strings.Replace(sendData, "\n", "", -1) 332 | bpm, err := strconv.Atoi(sendData) 333 | if err != nil { 334 | log.Fatal(err) 335 | } 336 | newBlock := generateBlock(Blockchain[len(Blockchain)-1], bpm) 337 | 338 | if isBlockValid(newBlock, Blockchain[len(Blockchain)-1]) { 339 | mutex.Lock() 340 | Blockchain = append(Blockchain, newBlock) 341 | mutex.Unlock() 342 | } 343 | 344 | bytes, err := json.Marshal(Blockchain) 345 | if err != nil { 346 | log.Println(err) 347 | } 348 | 349 | spew.Dump(Blockchain) 350 | 351 | mutex.Lock() 352 | rw.WriteString(fmt.Sprintf("%s\n", string(bytes))) 353 | rw.Flush() 354 | mutex.Unlock() 355 | } 356 | 357 | } 358 | ``` 359 | 360 | 361 | 首先是一个单独协程中的函数,每5秒钟会将我们的最新的区块链状态广播给其它相连的节点。它们收到后,如果发现我们的区块链比它们的要短,就会直接把我们发送的区块链信息丢弃,继续使用它们的区块链,反之则使用我们的区块链。总之,无论哪种方法,所有的节点都会定期的同步本地的区块链到最新状态。 362 | 363 | 这里我们需要一个方法来创建一个新的Block区块,包含之前提到过的脉搏速率(BPM)。为了简化实现,我们不会真的去通过物联网设备读取脉搏,而是直接在终端控制台上输入一个脉搏速率数字。 364 | 365 | 首先要验证输入的BPM是一个整数类型,然后使用之前的`generateBlock`来生成区块,接着使用`spew.Dump`输入到终端控制台,最后我们使用`rw.WriteString`把最新的区块链广播给相连的其它节点。 366 | 367 | 牛逼了我的哥,现在我们完成了区块链相关的函数以及大多数P2P相关的函数。在前面,我们创建了流处理,因此可以读取和写入最新的区块链状态;创建了状态同步函数,这样节点之间可以互相同步最新状态。 368 | 369 | 剩下的就是实现我们的`main`函数了: 370 | ```go 371 | func main() { 372 | t := time.Now() 373 | genesisBlock := Block{} 374 | genesisBlock = Block{0, t.String(), 0, calculateHash(genesisBlock), ""} 375 | 376 | Blockchain = append(Blockchain, genesisBlock) 377 | 378 | // LibP2P code uses golog to log messages. They log with different 379 | // string IDs (i.e. "swarm"). We can control the verbosity level for 380 | // all loggers with: 381 | golog.SetAllLoggers(gologging.INFO) // Change to DEBUG for extra info 382 | 383 | // Parse options from the command line 384 | listenF := flag.Int("l", 0, "wait for incoming connections") 385 | target := flag.String("d", "", "target peer to dial") 386 | secio := flag.Bool("secio", false, "enable secio") 387 | seed := flag.Int64("seed", 0, "set random seed for id generation") 388 | flag.Parse() 389 | 390 | if *listenF == 0 { 391 | log.Fatal("Please provide a port to bind on with -l") 392 | } 393 | 394 | // Make a host that listens on the given multiaddress 395 | ha, err := makeBasicHost(*listenF, *secio, *seed) 396 | if err != nil { 397 | log.Fatal(err) 398 | } 399 | 400 | if *target == "" { 401 | log.Println("listening for connections") 402 | // Set a stream handler on host A. /p2p/1.0.0 is 403 | // a user-defined protocol name. 404 | ha.SetStreamHandler("/p2p/1.0.0", handleStream) 405 | 406 | select {} // hang forever 407 | /**** This is where the listener code ends ****/ 408 | } else { 409 | ha.SetStreamHandler("/p2p/1.0.0", handleStream) 410 | 411 | // The following code extracts target's peer ID from the 412 | // given multiaddress 413 | ipfsaddr, err := ma.NewMultiaddr(*target) 414 | if err != nil { 415 | log.Fatalln(err) 416 | } 417 | 418 | pid, err := ipfsaddr.ValueForProtocol(ma.P_IPFS) 419 | if err != nil { 420 | log.Fatalln(err) 421 | } 422 | 423 | peerid, err := peer.IDB58Decode(pid) 424 | if err != nil { 425 | log.Fatalln(err) 426 | } 427 | 428 | // Decapsulate the /ipfs/ part from the target 429 | // /ip4//ipfs/ becomes /ip4/ 430 | targetPeerAddr, _ := ma.NewMultiaddr( 431 | fmt.Sprintf("/ipfs/%s", peer.IDB58Encode(peerid))) 432 | targetAddr := ipfsaddr.Decapsulate(targetPeerAddr) 433 | 434 | // We have a peer ID and a targetAddr so we add it to the peerstore 435 | // so LibP2P knows how to contact it 436 | ha.Peerstore().AddAddr(peerid, targetAddr, pstore.PermanentAddrTTL) 437 | 438 | log.Println("opening stream") 439 | // make a new stream from host B to host A 440 | // it should be handled on host A by the handler we set above because 441 | // we use the same /p2p/1.0.0 protocol 442 | s, err := ha.NewStream(context.Background(), peerid, "/p2p/1.0.0") 443 | if err != nil { 444 | log.Fatalln(err) 445 | } 446 | // Create a buffered stream so that read and writes are non blocking. 447 | rw := bufio.NewReadWriter(bufio.NewReader(s), bufio.NewWriter(s)) 448 | 449 | // Create a thread to read and write data. 450 | go writeData(rw) 451 | go readData(rw) 452 | 453 | select {} // hang forever 454 | 455 | } 456 | } 457 | ``` 458 | 459 | 首先是创建一个创世区块(如果你读了[200行Go代码实现你的区块链](https://medium.com/@mycoralhealth/code-your-own-blockchain-in-less-than-200-lines-of-go-e296282bcffc),这里就不会陌生)。 460 | 461 | 其次我们使用`go-libp2p`的`SetAllLoggers`日志函数来记录日志。 462 | 463 | 接着,设置了所有的命令行标识: 464 | 465 | - `secio`之前有提到,是用来加密数据流的。在我们的程序中,一定要打开该标识 466 | - `target`指明当前节点要连接到的主机地址 467 | - `listenF`是当前节点的监听主机地址,这样其它节点就可以连接进来,记住,每个节点都有两个身份:主机和客户端, 毕竟P2P不是白叫的 468 | - `seed`是随机数种子,用来创建主机地址时使用 469 | 470 | 然后,使用`makeBasicHost`函数来创建一个新的主机地址,如果我们只想做主机不想做客户端(连接其它的主机),就使用`if *target == “”`。 471 | 472 | 接下来的几行,会从`target`解析出我们要连接到的主机地址。然后把`peerID`和主机目标地址`targetAddr`添加到"store"中,这样就可以持续跟踪我们跟其它主机的连接信息,这里使用的是`ha.Peerstore().AddAddr`函数。 473 | 474 | 接着我们使用` ha.NewStream`连接到想要连接的节点上,同时为了能接收和发送最新的区块链信息,创建了`ReadWriter`,同时使用一个Go协程来进行`readData`和`writeData`。 475 | 476 | #### 哇哦 477 | 终于完成了,写文章远比写代码累!我知道之前的内容有点难,但是相比P2P的复杂性来说,你能通过一个库来完成P2P网络,已经很牛逼了,所以继续加油! 478 | 479 | ## 完整代码 480 | [mycoralhealth/blockchain-tutorial](https://github.com/mycoralhealth/blockchain-tutorial/blob/master/p2p/main.go) 481 | 482 | ## 运行结果 483 | 现在让我们来试验一下,首先打开3个独立的终端窗口做为独立节点。 484 | 485 | 开始之前,请再次进入`go-libp2p`的根目录运行一下`make deps`,确保所有依赖都正常安装。 486 | 487 | 回到你的工作目录`examples/p2p`,打开第一个终端窗口,输入 488 | `go run main.go -l 10000 -secio` 489 | ![终端1](https://upload-images.jianshu.io/upload_images/8245841-a949bc72e0b744c2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 490 | 491 | 492 | 细心的读者会发现有一段话"Now run…",那还等啥,继续跟着做吧,打开第二个终端窗口运行:`go run main.go -l 10001 -d -secio` 493 | ![终端2](https://upload-images.jianshu.io/upload_images/8245841-d2fd73a1a2a997ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 494 | 495 | 这是你会发现第一个终端窗口检测到了新连接!![终端1](https://upload-images.jianshu.io/upload_images/8245841-37394c2d6052a2dd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 496 | 497 | 接着打开第三个终端窗口,运行:`go run main.go -l 10002 -d -secio` 498 | 499 | ![终端3](https://upload-images.jianshu.io/upload_images/8245841-0c24d728db8898b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 500 | 501 | 检查第二终端,又发现了新连接 502 | ![终端2](https://upload-images.jianshu.io/upload_images/8245841-e01d9181e48123cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 503 | 504 | 接着,该我们输入BPM数据了,在第一个终端窗口中输入"70",等几秒中,观察各个窗口的打印输出。 505 | ![终端1](https://upload-images.jianshu.io/upload_images/8245841-772afd9f729e40f0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 506 | 507 | ![终端2](https://upload-images.jianshu.io/upload_images/8245841-4ff201eddaed3390.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 508 | 509 | ![终端3](https://upload-images.jianshu.io/upload_images/8245841-0571659c2ecdbdd0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 510 | 511 | 来看看发生了什么: 512 | - 终端1向本地的区块链添加了一个新的区块Block 513 | - 终端1向终端2广播该信息 514 | - 终端2将新的区块链跟本地的对比,发现终端1的更长,因此使用新的区块链替代了本地的区块链,然后将新的区块链广播给终端3 515 | - 同上,终端3也进行更新 516 | 517 | 所有的3个终端节点都把区块链更新到了最新版本,同时没有使用任何外部的中心化服务,这就是P2P网络的力量! 518 | 519 | 我们再往终端2的区块链中添加一个区块试试看,在终端2中输入"80" 520 | 521 | ![终端2](https://upload-images.jianshu.io/upload_images/8245841-89dcdce816d8120d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 522 | 523 | ![终端1](https://upload-images.jianshu.io/upload_images/8245841-62633aa803feed03.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 524 | 525 | ![终端3](https://upload-images.jianshu.io/upload_images/8245841-d2795256e23f1a09.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 526 | 527 | 528 | 结果忠诚的记录了我们的正确性,再一次欢呼吧! 529 | 530 | ## 下一步 531 | 先享受一下自己的工作,你刚用了区区几百行代码就实现了一个全功能的P2P网络!这不是开玩笑,P2P编程时非常复杂的,为什么之前没有相关的教程,就是因为太难了。 532 | 533 | 但是,这里也有几个可以改进的地方,你可以挑战一下自己: 534 | 535 | - 之前提到过,`go-libp2p`是存在数据竞争的Bug的,因此如果你要在生产环境使用,需要格外小心。一旦发现Bug,请反馈给作者团队知道 536 | - 尝试将本文的P2P网络跟之前的共识协议结合,例如之前的文章[PoW](https://medium.com/@mycoralhealth/code-your-own-proof-of-stake-blockchain-in-go-610cd99aa658) 和[PoS](https://mp.weixin.qq.com/s?__biz=MzA3NDIzMDU5OA==&mid=2247483653&idx=1&sn=7fd3e3c9838e9fb6e83d24d1616daf6c&chksm=9f03bf2ca874363ac4cecc0a45f209f5783a53ff078bf8d7294db224d137d17350d589a23df1#rd) (PoS是中文译文) 537 | - 添加持久化存储。截止目前,为了简化实现,我们没有实现持久化存储,因此节点关闭,数据就丢失了 538 | - 本文的代码没有在大量节点的环境下测试过,试着写一个脚本运行大量节点,看看性能会怎么变化。如果发现Bug记得给我们[提交]((https://github.com/mycoralhealth/blockchain-tutorial/tree/master/p2p) 539 | - 学习一下节点发现技术。新节点是怎么发现已经存在的节点的?这篇文章是一个[很好的起点](https://bitcoin.stackexchange.com/questions/3536/how-do-bitcoin-clients-find-each-other) 540 | 541 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------