月度归档:2019年04月

C语言推荐书籍从入门到进阶

极客侠网站导航(全部书单资源导航页):https://pymlovelyq.github.io/archives/

前言:技术书阅读方法论总结

一.速读一遍(最好在1~2天内完成)

人的大脑记忆力有限,在一天内快速看完一本书会在大脑里留下深刻印象,对于之后复习以及总结都会有特别好的作用。
对于每一章的知识,先阅读标题,弄懂大概讲的是什么主题,再去快速看一遍,不懂也没有关系,但是一定要在不懂的地方做个记号,什么记号无所谓,但是要让自己后面再看的时候有个提醒的作用,看看第二次看有没有懂了些。

二.精读一遍(在2周内看完)

有了前面速读的感觉,第二次看会有慢慢深刻了思想和意识的作用,具体为什么不要问我,去问30年后的神经大脑专家,现在人类可能还没有总结出为什么大脑对记忆的完全方法论,但是,就像我们专业程序员,打代码都是先实践,然后就渐渐懂了过程,慢慢懂了原理,所以第二遍读的时候稍微慢下来,2周内搞定。记住一句话:没看完一个章节后,总结一下这个章节讲了啥。很关键。

三.实践(在整个过程中都要)

实践的时候,要注意不用都去实践,最好看着书,敲下代码,把重点的内容敲一遍有个肌肉记忆就很不错了。
以及到自己做过的项目中去把每个有涉及的原理的代码,研究一遍,就可以了

一共四个系列整整32本电子书,找了好久终于齐了,如果都能看完看懂且科学的总结外加合理的实践,相信未来你的技术路会更好走,当然阿里巴巴,腾讯,阿里这些的Offer不将是梦,除了多看点技术书,你觉得还有什么能拿出来和985,211的朋友比呢?
最后 附上这32本书的电子版链接:

所有资源百度网盘链接:https://pan.baidu.com/s/1C0RDhilrwlmEIZ811oAZoA
提取码:s9si
复制这段内容后打开百度网盘手机App,操作更方便哦
备注:里面已经顺便整理压缩好,需要下载后才可以打开,网盘直接打开会显示损坏。

资源图示(下载链接如上):
0.png

以下是每本书的推荐语,清楚自己缺的是什么,就下定决心去补吧,一个好工作意味着高收入,投资自己的时间换来更宝贵的东西。

一.C语言入门,初学,编程基础系列

1.《C语言程序设计:现代方法》(第2版)

1.jpg

推荐理由:时至今日, C语言仍然是计算机领域的通用语言之一,但今天的 C语言已经和最初的时候大不相同了。本书最主要的一个目的就是通过一种“现代方法”来介绍 C语言,书中强调标准 C,强调软件工程,不再强调“手工优化”。这一版中紧密结合了 C99标准,并与 C89标准进行对照,补充了 C99中的最新特性。本书分为 C语言的基础特性、 C语言的高级特性、 C语言标准库和参考资料 4个部分。每章末尾都有一个“问与答”小节给出一系列与该章内容相关的问题及答案,此外还包含适量的习题。
本书是为大学本科阶段的 C语言课程编写的教材,同时也非常适合作为其他课程的辅助用书。

2.《C语言程序设计》(第2版)谭浩强版本

2.jpg

这本书堪称经典之作初学者学习可以看看,这个就是零基础入门学习C语言的,上手快。但也要坚持上机,要是只看书,不在电脑上运行一下看看,是永远学不会的。关键在实践!坚持!

不过这本书被诟病的地方也不少,可以看完上面那本再看这本,很多东西就懂了。

3.《程序员修炼之道》

3.png

《程序员修炼之道》由一系列的独立的部分组成,涵盖的主题从个人责任、职业发展,直到用于使代码保持灵活、并且易于改编和复用的各种架构技术。利用许多富有娱乐性的奇闻轶事、有思想性的例子以及有趣的类比,全面阐释了软件开发的许多不同方面的最佳实践和重大陷阱。无论你是初学者,是有经验的程序员,还是软件项目经理,本书都适合你阅3读。

4.《C和指针》

4.jpg

看到书名很让人担心翻译的英语水平。实际不然,翻译的很好。只能说标题党了。看封面不难理解作者用pointers的意思吧,再说了书又不是只讲指针。书名用《C语言指导》更好些,
这是一本全面的C语言入门书。当然入门的深度和高度都比国内的教材高太多了。所以,如果你能直接从这本书开始学的话,起点会比较高,当然能学懂的话,说明你很有才。
一般情况下,本书的部分内容更适合有C基础的人看。如ADT、递归、指针和数组的部分,书中所述的思想是国内教材所缺乏的。看完本书,能得到一个正确的C语言观。

5.《C primer plus》(入门首选)

5.jpg

C prime Plus这本书看了两遍,练习题基本上都自己独立做完了。题目没怎么主动算法能力(毕竟不是算法的书),但是每个细节说的很清楚。初学者很容易找到信心的。学完c primer plus之后可以来看上面谭大爷的书找错误。

6.《高质量程序设计指南》(一定要看)

6.jpg

大一上学期的时候,一个偶然的机会接触了本书的第一版,引发了对软件工程的思考,让我很早就意识到代码规范的重要性,为今后学习打下了坚实基础,真的很感激这本书,虽然其内容都很简单,但是在我迷茫的时候真的给了我很多启迪。

7.《C/C++深层探索》

7.jpg

很早读过的书,很不错,姚的另外一本c标准:标准和实现也非常好。原创佳作~~语言的扩充成为C++,我们知道C语言是一种程式语言,而C++则为对象化语言,因此C++比C更加接近人类的语言,因此第四代语言就是人类语言,这就是说人类也是按照程式来行动的,也是一种程式动物或者程式生物。人类根据一定的世界的部分而创造的语言本不与世界一致或者总一致,因此才有扩充,但是基本词汇只有这么多,因此没办法表述所有的事物,因此不得不将词汇表扩充至一切声响,这就是音乐的美丽,音乐就是现代语言的最终发展。可见音乐的重要性。

8.《从问题到程序》(最佳高校教材)

8.jpg

既适合初入门到的小子,也适合相见恨晚的匹夫.这里不得不赞一下老裘借鉴得好,而且里面又简略提到不少CS里面的概念:
讲单词计数的时候顺带介绍了有限状态机;程序设计语言里的副作用,前条件,后条件,短路求值.习题也是很不错的,高斯消元,3n+1问题,约瑟夫环,实现一个简单”虚拟机”和”汇编器”还要弄单步执行功能不少open problem.
风格严谨,十分强调程序的强健和安全,测试.越界访问从头到尾都在强调,后面还自己实现了个通用整数输入检查函数,还有通用错误信息处理函数,错误处理讨论得很详细.代码简练,命名规范.老早就讲了函数,”强调通过函数抽象建立清晰结构的重要性”.提供大量的模式,实例和建议,教会初学者设计、权衡.内容不依赖任何具体C实现,讨论IDE好处和坏处.
作为第一本C语言是很合适的.

二.内核/驱动系列

1.《Linux C编程一站式学习》

9.jpg

此书内容涵盖极广:C的基本语法,简单的数据结构,C与汇编的联系,计算机系统结构,操作系统,正则表达式,TCP/IP,无所不包。如此一来似乎样样通而样样不精。其实不是这么回事。作者内容穿插得非常好,用十分简单的方式把每个方面最重要的东西阐明了。所以,其实这是本入门书,当然也适合各个方面都了解之后总结用。看完这本书可能觉得什么都懂一点但什么都不完全会,不要紧,后面的参考文献多数都是经典。入门书嘛,但求上手快。这本书上手就非常快。里面几乎一事一例,不多不少,恰到好处。而且例子基本都简单小巧可爱,不会的地方复制代码调试即可。
“我本来就是菜鸟一个,怎么了?国内这破环境,真正的大家才不稀罕写书,都捞钱去了。”其实中文书水平普遍低下,主要就是缺少宋劲杉老师这样的“菜鸟”。本书适合做零基础的初学者学习C语言的第一本教材,帮助读者打下牢固的基础。有一定的编程经验但知识体系不够完整的读者也可以对照本书查缺补漏,从而更深入地理解程序的工作原理。本书最初是为北京亚嵌教育研究中心的嵌入式Linux系统工程师就业班课程量身定做的教材之一,也适合作为高等院校程序设计基础课程的教材。本书对于C语言的语法介绍得非常全面,对C99标准做了很多解读,因此也可以作为一本精简的C语言语法参考书。

2.《Linux内核设计与实现》(第3版)

10.png

这书估计慕名而来的人都会在第一时间略感失望,首先书很薄,而且讲解不求深入。如果一个人在第一次翻阅此书的时候有这样的印象,那应该好好反省下自己是否太浮躁了。
其实这部书的定位有点不高不低,但也正因如此,它是最适合过渡阶段的内核学习者阅读的一部书。正确的阅读顺序或许应是这样的:恐龙书or现代OS->LDK->情景分析之类的详解书。
LDK很适合在你系统地学习了OS理论之后,直接看代码详解又觉得暂且还不够功力的学习者,它可以带你由理论学习阶段逐渐过渡到实践阶段。对于这样一部书,要是太厚就有点骗钱的嫌疑,要是太深入又会让人觉得作者故意显摆自己的学识。LDK算得上是恰到好处。
另外,本书后面的参考文献十分值得一读,要是您读完本书之后觉得不错,建议把它推荐的参考文献也找来读一读,或许会让您有更惊艳的感受。

3.《Linux设备驱动程序》(第3版)

11.jpg

适合中低水平的人。Linux 设备驱动模型真心复杂!对于写Linux驱动的人来说, 这本书应该是教科书级别的吧, 必读.

4.《深入Linux内核架构》

12.jpg

觉得是linux内核的一大作,坊间关于《深入理解linux内核》的传说,本人用自己的拙学是这么理解的。对于可以有较好的英文阅读能力的人,可以不用看毛德操的老师的书,后者已经完全可以替代了。注意现在比较的逻辑,并没有拿这本书去调戏《深入理解》,毕竟本人认为本书阅读时间该是有操作系统概念,然后还没有深入代码研究的阶段。所以同样还在摸索的你我,不要被本书的页数给吓到了,这本书我每天晚上花了3个小时,差不多花了45天阅读完,建议一口气看完,不然就打不到效果了,当然如果你是在校学生,我建议花一个学期对着源码研究。现在这本书也已经被我成功推荐到我们的team了…

三.应用系列

1.《UNIX环境高级编程》

13.png

好书的妙处之一,就是能给你与作者交流的感觉。技术书籍常犯两个毛病,一个是着眼点太低,堆砌细节(比如谭浩强的《C程序设计》),读起来好像听和尚念经,无法交流。再一个就是着眼点太高,兜售哲学(比如ESR的《The Art of UNIX Programming》),读起来好像听于丹老师讲论语,不敢交流。此书的经典性就在于不高不低不多不少,把UNIX系统编程的来龙去脉向你娓娓道来。很多地方都可以让你感觉到,你的疑惑作者在写书的时候已经了如指掌。从疑惑到顿悟的那一瞬间的畅快感是学习最大的快乐。所以,我们的口号就是:有问题,找APUE。

2.《UNIX网络编程》

14.png

还是在大二就买了这本书,但一直没拿起来看,各种拖延。了解 linux 下的网络编程,这本很赞。其中讲到了较为底层的网络编程系统调用和几种网络通信模式,譬如阻塞式,非阻塞式,I/O 多路复用等。但离实践还是由于一定的距离,网络编程中重点不在于系统调用,而是对具体的项目想要设计与之适应的网络模式。W.Richard Stevens 爹爹的书,每本都可以是经典。荐!

四.高能来袭,C语言进阶系列(学完就等着封神吧王者归来BAT等你)

1.《C陷阱与缺陷》

15.png

这是一本小册子,有让人继续读下去的欲望,倒不是因为页数少好欺负,是因为书中所说的几乎所有需要注意的地方作为一个程序员都有可能遇到,作者叙述起来很有意思,丝毫没有说教的感觉,举的例子很简单却一针见血。
此书作为一本常备读物是非常合适的,没事经常翻翻加深印象。

2.《C专家编程》

16.png

一年前我翻了翻这本书就觉得很棒,但那是我并不“主修”C,也没好好看,最近在认真读这本书,真是赞叹不已。
它使你对C的使用有深入了解,最后还介绍了一些C++,如果你以前没太多接触过C++,只知道C,通过这本书打开通往C++之门也不错。书中还提到了一些当年那些传说中Hacker的的故事,挺风趣的。
但是看这本书还是要有些背景的。
你要学过编译原理,虽然不需要学的太深太好,但至少对里面的一些概念要有所了解,否则对里面内存分配的部分(事实上很多是针对编译器的),你会感到吃力。还有,你要有些Unix/Linux的文化背景,比如Unix的C编程风格,还有Unix里的一些命令,工具。

3.《C语言程序设计》K&R版

17.jpg

拿到这本薄薄的书,很多人开始怀疑,C语言是这么几百页能讲清楚的么。看完这本书,我想答案已经很明了,却真的让人感到震憾。什么是好书?无法删减的书才是真正的好书。K&R的书一如C语言的设计理念:简单而高效
里面的习题建议都认真做一遍,而且是在linux下用vi来做,用makefile来编译,用shell脚本来进行测试,本来第八章的题就是和linux相关的
计算机的大学生们不应只会在WINDOWS下用VC来编程,而都应该在linux环境下进行程序设计,因为linux本身就是为开发者准备的操作系统

4.《C语言解惑》

18.jpg

本书脱胎于作者在C语言的摇篮——贝尔实验室教授C语言的讲稿,几乎涵盖了C语言各个方面的难点,并包含了一些其他书籍很少分析到的问题。在每个谜题后面都有详尽的解题分析,使读者能够清晰地把握C语言的构造与含义,学会处理许多常见的限制和陷阱,是一本绝佳的C语言练习册。

5.《你必须知道的495个C语言问题》

19.jpg

但比教材经典,最好手边一本教材,一边翻,一边看本书。建议集中时间看,然后再重新复习!很实用的书,比c语言陷阱,c语言解惑要深刻!!!广度还行,深度不足,适合查缺补漏。

6.《C语言参考手册(原书第5版)》

20.jpg

这是C99确定发布后出版的参考手册。相比K&R要更加接近现在。K&R适合入门,而这本书不读,恐怕不算”学过C语言“。

7.《C语言接口与实现》

21pg.jpg

另外,就我个人感觉而言,这本书的语言属于那种简单准确的风格,与原文的语义一致性很高,基本上没有因炫耀文字而牺牲准确性之处。新手当做兴趣书看或者老手老复习下也可以。可以加深对ADT的理解。

8.《深入理解计算机系统》(修订版或第3版)

这本书是引导你如何练内功的,但是要是我来说的话,我个人认为这本书是在你学完数据结构和导论之间去看,因为这本书只是让你去深入理解计算机导论里面的一些概念,算是高配版本的计算机导论,目的是为了引出来操作系统、组成原理这些专业核心的课程。你要是指望看完这本书你就能左手写个App右手写个Web动态网站的话就错了,这本书的意义正在于他的这个英文版的名字,Computer System — A Programmer’s Perspective,一个程序员的修养,如何利用计算机的工作流程去优化自己写的东西这个才是这本书的目的。

10.《C语言的科学和艺术》

22.jpg

本书的作者因为对本科生的教育做出了杰出的贡献而获得了Bing Award奖,而这本书也正体现了他作为一名教师,深入浅出的教学方法和易于理解又引人入胜的行文风格。
文中所用的例子也都非常符合本节所要讲述的内容,并且把不得以而用到的以后章节的知识以一种genlib库的形式封装了起来,隐藏了C的复杂性,从而避免了初学者的困惑。这样,在读完本书后,会发现,我们不仅仅学到了C的知识,而且把库的编写方法、习惯都潜移默化的留在了心中。在书中很多地方都会有作者关于软件工程和优秀程序设计风格的见解,如接口的编写等等,都对我们打下扎实基础起到了积极的作用。
特别需要指出的一点是:这本书对于C语言中比较困难的部分:如指针、C风格字符串、数组和指针的关系、数组和字符串的关系,都有“一针见血”式的透彻分析,使初学者能够容易的明白其中的知识,也使有经验的读者能够抓住重点理解更加深入。对于这些比较精髓的知识,特别是指针和数组名的区别,会在文中多次被提醒:分配内存、左值!
初学者在编程中,很少接触文件的操作,但是文件操作非常重要,无论初学者还是有一定经验的读者都应该对C标准库中的文件函数熟练的掌握,这本书对文件的介绍会让你有系统理解,而且对使用这些函数时常会犯的错误有先知一般的预见,从而避免了初学者遇到问题调试时的辛苦周折。
如果非要说说这本书的缺点,我想就是,没有把genlib库的代码刻成cd附在书里,这多少会给初学者上机调试造成了不便,好在网上有这本书中的源代码和其他资源,而且书后也有完整的代码。其实换种思路想,这也可以算是一件好事,国内学生的动手能力差,那就应该在敲代码的同时把她理解了吧,呵呵,有点自虐倾向-_-b
最后,无论如何,如果你想学习C语言,那么看看这本书吧,她很好的!(而且不必在乎什么“C语言已死”这样的胡说八道)作为一种应用最广的面向过程的语言,她会让你对计算机程序设计形成一种必要的经典的思考模式!

11.《数据结构与算法分析C语言系列》

23.jpg

因为最近需要复习数据结构与算法,所以网上搜索了下这方面的经典书籍。这本书的C语言版本高居榜首,获得一致好评,正好该书又有Java语言的版本,就买来拜读一下。前后大概花了1个月的时间将该书看了两遍,书中的主要数据结构都敲代码实现了一遍,现在算是将以前的数据结构课程都回忆了起来,对比当时上学用的谭浩强的那本数据结构教程,真是天壤之别。有时间的话可以在这本书的基础上看一下<<算法导论>>。

这本书确实是很好的数据结构与算法分析的最佳入门教程,不过看这本书还是要有点数据结构的基础。通过Java语言描述,讨论了主要的数据结构:表、栈、队列、树、散列、优先队列、不相交集合和图;同时讨论了经典的排序算法:插入排序、希尔排序、堆排序、归并排序、快速排序;介绍了5种常用算法:贪婪算法、分治算法、动态规划、随机化算法、回溯算法;并讨论了Java Collection中相关数据结构的实现:ArrayList、LinkedList、TreeSet、TreeMap、HashSet、HashMap、PriorityQueue。

12.《Linux程序设计》

24.jpg

《Linux程序设计》是我的Linux编程入门书籍,也是做为教材使用了一整个学期,在阅读和学习这本书的时候产生了很多的疑问,书里也没有对应的解答,直到……直到我看了APUE,带着这些问题去学习APUE,产生了巨大的能量。总之,推荐这本书,但是这本书也只是入门书籍,站在《Linux程序设计》的肩膀上,学习APUE,在Linux的世界里遨游吧!

13.《现代编译原理》

25.jpg

翻了这么多本书,这是我看过的唯一一本讲具体怎么构建一个编译器的书。同时这本书所构建的编译器就像作者说的那样,简单但是并不平庸,拥有很多挺先进的特性。也能算是一个优化编译器。

但是要跟着这本书做下来还是有一定难度的,需要扎实的C语言功底。

14.《重构-改善既有代码的设计》

26.jpg

大师Martin Fowler的经验之谈,看后有种醍醐灌顶、欲罢不能的感觉。重构也是当今敏捷开发一项不可或缺的技艺,建议所有有设计和项目开发经验的开发者都应读一下。

15.《老码识途-从机器码到框架的系统观逆向修炼之路》

27.jpg

我们《软件开发环境》老师写的书,先教你通过反汇编来分析、修改、自己写底层机器码,后面着重探讨面向对象特性在底层的实现和体现。
知识点都是底层的干货,对理解高层封装出来的一些概念的本质灰常有帮助。比如指针本质上就是个4字节的地址,指针类型只是由编译器识别,然后体现在控制访问多少个字节的CPU指令上;
比如函数是怎么实现调用、传参、返回的,传参又有寄存器传值、压栈传值、压栈传地址等方式,跨语言调用函数时调用惯例的协调。
总之弄懂了这些底层的机制,对高层语言的理解会透彻很多。
不过最好有一点汇编基础再读,否则略艰涩。
另一个特点是全书一直贯彻一种”猜测——实证”的思想,跟作者交流过这本书好几次,感觉这种思想是他最想传达的东西。

16.《C语言进阶》

28.jpg

这本书应该适用于学过C,但是想温习一下的人。里面有一部分基础语法,但是也有很多高级的东西。函数指针与指针函数,指针数组与数组指针,预定义,预编译,调试之类。但是感觉最后一章的常用算法有种多余的感觉。如果想应付面试,看这本书应该也没有错,里面有很多笔试喜欢考的sizeof的东西。

17.《实用C语言编程》

29.jpg

很老的一本C语言书,可以说是我的C语言启蒙书,里面的资料,尤其是附录是我现在还经常翻阅的原因,书写的很朴实,也如书名,确实实用,易懂.把这本书吃透了,找个工作,那是再容易不过了,所以说一本好书需要时间来检验它,在岁月中沉淀下来…岁月检验过的好书,不解释。

总结:天下没有不劳而获的果实,望各位年轻的朋友,想学技术的朋友,在决心扎入技术道路的路上披荆斩棘,把书弄懂了,再去敲代码,把原理弄懂了,再去实践,将会带给你的人生,你的工作,你的未来一个美梦。

所有资源百度网盘链接:https://pan.baidu.com/s/1C0RDhilrwlmEIZ811oAZoA
提取码:s9si
复制这段内容后打开百度网盘手机App,操作更方便哦
备注:里面已经顺便整理压缩好,需要下载后才可以打开,网盘直接打开会显示损坏。

 

!!!  好像, 里面资料的 是加密的, 需要付费后才能解密的!!

 

来源: https://pymlovelyq.github.io/posts/33303225/

C语言的编译链接过程的介绍

  从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。

编译过程

编译过程又可以分成两个阶段:编译和会汇编。

编译

编译是读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,源文件的编译过程包含两个主要阶段:

第一个阶段是预处理阶段,在正式的编译阶段之前进行。预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。如#include指令就是一个预处理指令,它把头文件的内容添加到.cpp文件中。这个在编译之前修改源文件的方式提供了很大的灵活性,以适应不同的计算机和操作系统环境的限制。一个环境需要的代码跟另一个环境所需的代码可能有所不同,因为可用的硬件或操作系统是不同的。在许多情况下,可以把用于不同环境的代码放在同一个文件中,再在预处理阶段修改代码,使之适应当前的环境。

主要是以下几方面的处理:

(1)宏定义指令,如 #define a b

对于这种伪指令,预编译所要做的是将程序中的所有a用b替换,但作为字符串常量的 a则不被替换。还有 #undef,则将取消对某个宏的定义,使以后该串的出现不再被替换。

(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif等。

这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

(3) 头文件包含指令,如#include "FileName"或者#include 等。

在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在 /usr/include目录下。在程序中#include它们要使用尖括号(< >)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。

(4)特殊符号,预编译程序可以识别一些特殊的符号。

例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。

预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。

第二个阶段编译、优化阶段,经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。

编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。

优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。

对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除,等等。

后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑是如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流水线、RISC、CISC、VLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。

汇编

汇编实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:

代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。

数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

UNIX环境下主要有三种类型的目标文件:

(1)可重定位文件

其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

(2)共享的目标文件

这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个 目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

(3)可执行文件

它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

链接过程

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。

例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

根据开发人员指定的同库函数的链接方式的不同,链接处理可分为两种:

(1)静态链接

在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。

(2) 动态链接

在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。

我们在linux使用的gcc编译器便是把以上的几个过程进行捆绑,使用户只使用一次命令就把编译工作完成,这的确方便了编译工作,但对于初学者了解编译过程就很不利了,下图便是gcc代理的编译过程:

从上图可以看到:

预编译

将.c 文件转化成 .i文件

使用的gcc命令是:gcc –E

对应于预处理命令cpp

编译

将.c/.h文件转换成.s文件

使用的gcc命令是:gcc –S

对应于编译命令 cc –S

汇编

将.s 文件转化成 .o文件

使用的gcc 命令是:gcc –c

对应于汇编命令是 as

链接

将.o文件转化成可执行程序

使用的gcc 命令是: gcc

对应于链接命令是 ld

总结起来编译过程就上面的四个过程:预编译、编译、汇编、链接。Lia了解这四个过程中所做的工作,对我们理解头文件、库等的工作过程是有帮助的,而且清楚的了解编译链接过程还对我们在编程时定位错误,以及编程时尽量调动编译器的检测错误会有很大的帮助的。

在Ignite上运行微服务

在Ignite上运行微服务

从本文开始,会通过一个系列的篇幅来介绍使用Apache Ignite内存数据组织平台来构建容错、可扩展的基于微服务的解决方案。

详细信息参考:https://liyuj.gitee.io/doc/java/ServiceGrid.html

介绍

当前,很多公司都会将自己的应用或者解决方案构建于微服务架构之上,这样做的主要好处是,可以将一个解决方案拆分为一组松耦合的软件组件(微服务)。这些软件组件可能有自己的版本以及生命周期,甚至有自己的开发团队。此外,这些软件还可能使用不同的语言和技术来开发和维护。但是因为所有的微服务都会是更大的构件(软件或者解决方案)的一部分,所以它们至少需要一个机制来进行彼此的交互和数据交换。

同时,基于微服务的解决方案也会用于高负载或者需要处理快速增长的数据的场景,因此和不是基于微服务的应用和解决方案一样,它也会面临同样的问题和困难。

  • 面向磁盘的数据库已经无法跟上快速增长的数据量,这些数据需要以并行的方式进行存储和处理,数据库正在成为整个应用或者解决方案的性能瓶颈;
  • 软件的高可用性保证作为一个好功能已经成为过去式,当前,应用的高可用性已经成为事实上的标配。

从本文开始,会通过一个系列的篇幅来一步步地介绍使用Apache Ignite内存数据组织平台来构建容错、可扩展的基于微服务的解决方案。

使用Apache Ignite的基于微服务的解决方案

下图会描述整个解决方案的主要构成,之后会深入,一个一个定义它们的角色。

1

Ignite集群层

集群有两个作用:

首先,作为主要的数据存储,它直接在内存中保存数据集。因为数据离CPU更近,一个微服务不需要从磁盘上获取数据,这显著地提升了整体的性能。从上图来看,我们指定了一个特定的集群组(数据节点)来专门处理这个问题。

一个数据节点是一个Ignite服务端节点,它持有数据集的一部分,并且可以执行查询和计算。另外,有赖于基于对象序列化的二进制格式化技术,一个数据节点不需要部署模型对象和计算的支撑类,这个叫做对等类加载机制,它可以管理从应用逻辑节点(服务节点)预加载的计算类。

其次,集群管理微服务的生命周期,并且为微服务配备所有必要的API,比如与其他服务或者数据节点进行通信的API。要达到这一点,一个基于Ignite集群的解决方案需要包含前述的服务节点,这些节点部署有微服务,并且应用逻辑也在这里执行。一个服务节点可以部署一个或者多个微服务,这个取决于具体的应用以及负载情况。每个微服务都需要实现Ignite的Service接口,它直接就有了容错和访问其他微服务的能力。

Ignite会处理在服务节点范围内,一个微服务的一个或者多个副本的部署,并且会自动地进行容错和负载平衡。在上述的图1中,这类微服务被命名为MS<N>(MS1,MS2等)。在多个服务和数据节点间传播负载的好处是,如果MS1微服务改变,不需要重启整个集群,所有需要做的就是在部署有MS1微服务的服务节点上更新MS1的相关类,因此只有所有节点的一个子集需要重启。

所有的节点(服务和数据节点)都是相互连接的,这使得部署在一个服务节点上的MS1可以与部署在任何其他的或者自身服务节点上的微服务进行通信,也可以向任何数据节点获取和发送数据以及计算。

持久化存储层

这一层是可选的,可以用于如下的场景:

  • 在内存中持有所有的数据是不必要或者不可行的;
  • 需要从基于磁盘的副本中恢复数据集的能力,这时整个集群需要停机或者需要重启。

要启用持久化存储层,只需要简单地提供一个实现了CacheStore接口的Ignite数据缓存就可以了。在默认支持的实现中,有RDBMS,MongoDB以及Cassandra。

外部应用层

这是微服务架构应用的"用户",基本上来说,这是一个触发调用一个一个微服务的各种执行流程的层次。

这个层可以使用具体到每个微服务的外部协议来与微服务进行通信(而在内部,微服务间相互通信可以使用Ignite服务,或者使用Ignite客户端连接进行连接),这方面都很大的灵活性以及多样化的选择。

----

作为一个系统,一个可能的架构由如下层次组成:

  • Ignite集群层
  • 持久化存储层
  • 外部应用层

1

本文中会关注第一层(Ignite集群层),可以参考一个GitHub项目,他包含了日常中实现拟议的微服务架构所必须的构建块,尤其是要覆盖如下部分:

  • 配置和启动数据节点;
  • 使用Ignite的服务API的典型服务实现;
  • 配置和启动驻有Ignite服务的服务节点;
  • 一个连接到集群并且触发服务执行的虚拟应用。

微服务位于内存数据网格架构之上时的数据节点

正如第一篇中提到的,数据节点是持有数据集一部分数据的服务端节点,应用逻辑端会在这个数据集上执行查询和计算。通常来说,这种类型的节点对应用逻辑是透明的,因为这些节点只是简单地存储数据集,然后当应用访问数据时高效地进行处理就可以了。

下面会看一下在实现层面如何定义一个数据节点。

可以下载这个GitHub项目然后找到 data-node-config.xml,它会用于创建一个新的数据节点,这个配置包含了一组与数据节点有关的段落和参数。

首先,需要为每一个要部署到集群中的Ignite缓存配置一个特定的节点过滤器。这个过滤器会在缓存启动时被调用,它会定义一个要存储缓存数据的集群节点的子集--数据节点。同样的过滤器在网络拓扑发生变化时也会被调用,比如新节点加入集群或者旧节点离开集群。过滤器的实现需要加入每个节点的类路径中,不管该节点是否会成为数据节点。

<bean class="org.apache.ignite.configuration.CacheConfiguration">
...
  <property name="nodeFilter">
    <bean class="common.filters.DataNodeFilter"/>
  </property>
</bean>

第二,实现过滤器,在本例中,使用了一个非常明确的实现,DataNodeFilter,它通过检查data.node参数来确定一个节点是否会被视为数据节点。如果一个节点在属性映射中配置了这个参数,那么他会成为一个数据节点然后数据会驻留于此,否则该节点会被忽略。

 public boolean apply(ClusterNode node) {
  Boolean dataNode = node.attribute("data.node");
  return dataNode != null && dataNode;
}

第三,data-node-config.xml为每个使用这个配置启动的节点的属性映射添加了data.node属性,就像下面这样:

<property name="userAttributes">
  <map key-type="java.lang.String" value-type="java.lang.Boolean">
    <entry key="data.node" value="true"/>
  </map>
</property>

最后,通过使用示例中的DataNodeStartup文件,或者将data-node-config.xml传递给Ignite的ignite.sh/bat脚本来启动一个数据节点的实例。如果选择了后者,那么一定要将java/app/common目录中的所有类文件构建成一个jar包,然后还要将这个jar文件加入到每个数据节点的类路径中。

微服务位于内存数据网格架构之上时的服务节点

在实现层次上服务节点的定义与前述数据节点的用法没有什么大的不同。基本上,需要建立一个方式,即指定一个特定的微服务将要部署在哪些节点上,它们会是整个集群的一个子集。

最初,需要使用服务网格API实现一个微服务,为后文起见,可以回顾一下那个GitHub示例中的已有服务实现,即Maintenance Service。

这个服务可以调度一个车辆维护的服务,并且可以查看已做保养的清单,它实现了所有服务网格的必要方法,包括init(...)execute(...)以及cancel(...),并且在这个接口中增加了新的方法:

 public interface MaintenanceService extends Service {
  public Date scheduleVehicleMaintenance(int vehicleId);
  public List<Maintenance> getMaintenanceRecords(int vehicleId);
}

将这个维护服务配置并且部署到特定的Ignite节点(服务节点)上有几种方式。在本例中,通过maintenance-service-node-config.xml启动的每个节点,都可以考虑进行维护服务的部署,下面可以看一下配置。

首先,确保维护服务的实例只会被部署到指定了节点过滤器的节点上:

<bean class="org.apache.ignite.services.ServiceConfiguration">
  <property name="nodeFilter">
    <bean class="common.filters.MaintenanceServiceFilter"/>
  </property>
</bean>

第二,维护服务使用的过滤器,只会被部署到在属性映射中配置了maintenance.service.node的节点上:

 public boolean apply(ClusterNode node) {
  Boolean dataNode = node.attribute("maintenance.service.node");
  return dataNode != null && dataNode;
}

最后,通过如下的XML片段,使用maintenance-service-node-config.xml启动的每个节点在映射中都会包含这个属性:

<property name="userAttributes">
  <map key-type="java.lang.String" value-type="java.lang.Boolean">
    <entry key="maintenance.service.node" value="true"/>
  </map>
</property>

就这些了,使用MaintenanceServiceNodeStartup文件,或者将maintenance-service-node-config.xml传递给Ignite的ignite.sh/bat脚本,就可以启动维护服务节点的一个或者多个实例,如果选择了后者,一定要确保将java/app/common和java/services/maintenance目录中的所有文件打包成一个jar文件,然后将这个jar文件添加到每个服务将要被部署的节点的类路径上。

示例中包含了另一个与车辆管理有关的Ignite服务,使用VehicleServiceNodeStartup文件或者使用经过vehicle-service-node-config.xml配置的ignite.sh/bat,可以启动至少一个部署有该服务的服务节点,如果选择了ignite.sh/bat方式,不要忘了组装一个jar文件然后将其加入相关节点的类路径上。

在内存数据网格之上运行微服务的示例应用

一旦准备好了数据节点,维护服务和车辆服务节点也都启动运行了,那么就可以运行第一个示例应用来访问这个分布式微服务了。

在示例中找到并且启动TestAppStartup,这个应用会接入集群,往预定义的缓存中注入虚拟数据,然后与服务进行交互。

MaintenanceService maintenanceService = ignite.services().serviceProxy(MaintenanceService.SERVICE_NAME,
MaintenanceService.class, false);
int vehicleId = rand.nextInt(maxVehicles);
Date date = maintenanceService.scheduleVehicleMaintenance(vehicleId);

如果注意了,应用会使用服务代理来与服务进行交互,代理的好处就是,启动应用的节点不需要在本地类路径中持有服务的实现,也不需要在本地部署一个服务。

-----------

描述的是集群如何与持久化存储集成以及外部应用如何发请求给微服务 -- 应用与集群无关也不会依赖Ignite的API。

这里还会提到第二部分中介绍的GitHub工程,因此,要确保将其检出到本机并且更新到最新版。

数据节点的持久化存储

Ignite是一个内存数据平台,默认将数据保持在内存中。然而,也可以将其持久化到磁盘上。比如希望确保即使集群重启数据也不会丢失。 要开启持久化,只需要解决三个小事情:

  1. 决定使用什么技术作为持久化存储(关系数据库、MongoDB、Cassandra、Hadoop等等);
  2. 找到一个已有的CacheStore接口实现,或者如果有必要也可以自己开发一个;
  3. 将实现加入缓存配置中。

就这么多了!做完之后,第一部分中描述的数据节点,就会与持久化存储进行交互,如下图所示:

1

要强调的是,如果内存中的数据发生变更,数据会被自动地传播到磁盘上,或者如果内存中没有对应该主键的值,会即时从持久化中进行数据的预加载。 下面看一下基于这个GitHub工程,如何为微服务架构实现以及插入一个自定义的持久化存储。

为了演示方便,创建了一个虚拟持久化存储实现,它实际上将数据存储在一个ConcurrentHashMap中,这个演示只是为了说明,如果需要创建一个自定义的持久化存储实现,Ignite基本上只需要实现三个方法:

 /** {@inheritDoc} */
public BinaryObject load(Long key) throws CacheLoaderException {
System.out.println(" >>> Getting Value From Cache Store: " + key);
return storeImpl.get(key);
}

/** {@inheritDoc} */
public void write(Cache.Entry entry) throws CacheWriterException {
System.out.println(" >>> Writing Value To Cache Store: " + entry);
storeImpl.put(entry.getKey(), entry.getValue());
}

/** {@inheritDoc} */
public void delete(Object key) throws CacheWriterException {
System.out.println(" >>> Removing Key From Cache Store: " + key);
storeImpl.remove(key);
}

下一步,在数据节点的配置中,通过在名为maintenance的缓存配置中添加一行代码,就可以开启这个自定义存储。

<property name="cacheStoreFactory">
  <bean class="javax.cache.configuration.FactoryBuilder" factory-method="factoryOf">
    <constructor-arg value="common.cachestore.SimpleCacheStore"/>
  </bean>
</property>

最后,要检查一下Ignite集群与持久化存储的通信,怎么做呢,在开发环境中打开GitHub工程然后启动一个数据节点的实例(DataNodeStartup文件),一个维护服务节点的实例(MaintenanceServiceNodeStartup文件)和一个车辆服务节点的实例(VehicleServiceNodeStartup文件)。所有节点互联之后,启动TestAppStartup,它会接入集群,注入数据然后调用服务。TestAppStartup执行完毕后,打开 DataNodeStartup的日志窗口,就可以看到类似下面这样的一个字符串:

 >>> Writing Value To Cache Store: Entry [key=1, val=services.maintenance.common.Maintenance [idHash=88832938, hash=1791054845, date=Tue Apr 18 14:55:52 PDT 2017, vehicleId=6]]

之所以显示这个字符串,是因为TestAppStartup触发了一个maintenance缓存的更新,它会自动地给前述虚拟持久化存储发送一个更新。

从外部应用接入

TestAppStartup是一个与部署在Ignite集群中的微服务进行交互的应用样例,某种意义上来说它是一个内部应用,因为它直接接入集群并且调用了服务网格的API。

但是对于外部应用来说,它不可能也不应该知道集群及其整体的部署,那么它怎么与微服务进行交互呢?一个简单的方案就是,Ignite服务以不同的方式监听来自外部应用的请求然后做出响应。

比如,当MaintenanceService的一个实例部署进集群后,它通过一个预定义的端口开启一个服务套接字来接收远程的连接(查看MaintenanceServiceImpl可以了解更多细节)。那么使用ExternalTestApp启动一个外部应用之后,它就会使用服务套接字与服务连接,然后获得每个车辆的维护调度,ExternalTestApp的输出大致如下:

>>> Getting maintenance schedule for vehicle:0
>>> Getting maintenance schedule for vehicle:1
>>> Getting maintenance schedule for vehicle:2
>>> Getting maintenance schedule for vehicle:3
>>> Getting maintenance schedule for vehicle:4
>>> Getting maintenance schedule for vehicle:5
>>> Getting maintenance schedule for vehicle:6
  >>> Maintenance{vehicleId=6, date=Tue Apr 18 14:55:52 PDT 2017}
>>> Getting maintenance schedule for vehicle:7
>>> Getting maintenance schedule for vehicle:8
>>> Getting maintenance schedule for vehicle:9
>>> Shutting down the application.

在这个系列中,展示了在Ignite集群中如何部署和维护一个基于微服务的解决方案。这个方案不需要关注微服务生命周期以及高可用性等事情,交给Ignite就行了,只需要关注实际业务逻辑即可。再者,所有的数据以及微服务都是在整个集群中分布的,这就意味着不用再担心性能和容错性-Ignite都已经解决了。

 

来源:https://my.oschina.net/liyuj/blog/892755

ignite-架构概述

为了更好地理解Apache Ignite和用例的功能,理解它的体系结构和拓扑结构非常重要。通过更好地理解Ignite的体系结构,可以决定拓扑或缓存模式,以解决企业体系结构场景中的不同问题,并从内存计算中获得最大的益处。与主从设计不同,Ignite使用了一个完全对等的架构。ignite集群中的每个节点都可以接受读写,无论数据在哪里写入。本章主要内容:

  • 功能概述
  • 不同的集群拓扑
  • 集群拓扑结构
  • 缓存策略
  • 集群划分
  • 数据模型
  • 多数据中心复制
  • 异步支持
  • SQL查询如何在Ignite中工作
  • 弹性

功能概述

Ignite体系结构具有足够的灵活性和高级特性,可用于大量不同的体系结构模式和风格。您可以将Ignite看作是一组独立的、集成良好的内存中组件的集合,以提高应用程序的性能和可伸缩性。下面的示意图表示Apache Ignite的基本功能:

Ignite是以模块化的方式进行组织,并为每个功能提供一个jar(库)。您只需将所需的库应用到项目中,就可以使用Ignite。

集群拓扑

Ignite的设计表明整个系统本身就具有固有的可用性和可扩展性。ignite节点间通信允许所有节点接收更新,无需一个快速的主协调器。节点可以不受干扰地添加或删除,以增加可用RAM的数量。
Ignite数据结构具有完全的弹性,允许对单个服务器或多个服务器进行无干扰的自动检测和恢复。

客户端和服务端

Apache Ignite具有一个可选的服务概念,并提供了两种类型的节点:客户端和服务器节点。

  • Server 包含数据、缓存、计算和流,并且可以是内存中的Map-Reduce任务的一部分。
  • Client 提供远程连接服务器以将元素放入/获取到缓存的能力。它还可以存储部分数据(近缓存),这是一个较小的本地缓存,存储最近和最频繁访问的数据。

    还可以将服务器节点分组到一个集群中执行工作。在集群中,可以限制作业执行、服务部署、流和其他任务只在集群组中运行。
    你可以基于任何谓词创建一个集群组。例如,您可以从一组节点创建一个集群组,其中所有节点负责为testCache缓存数据。默认情况下,所有节点都作为服务节点启动。客户端模式需要显式启用。注意,您不能物理地将数据节点与计算节点分离。在Apache Ignite中,包含数据的服务器也用于执行计算。Apache Ignite客户机节点也参与作业执行。乍一看,这个概念似乎很复杂,但让我们试着澄清这个概念。服务器节点总是存储数据,默认情况下可以参与任何计算任务。另一方面,客户端节点可以操作服务器缓存、存储本地数据并参与计算任务。通常,客户端节点用于从缓存中放置或检索数据。这种混合客户端节点在开发具有多个节点的大型Ignite网格时提供了灵活性。客户端和服务器节点都位于一个网格中,在某些情况下(例如,数据节点中的大容量acid事务),您不希望在数据节点上执行任何计算。在这种情况下,您可以通过创建相应的集群组来选择只在客户端节点上执行作业。通过这种方式,您可以在一个网格中将数据节点与计算节点分开。可以使用以下伪代码对客户端进行计算:

ClusterGroup clientGroup = ignite.cluster().forClients(); IgniteCompute clientCompute = ignite.compute(clientGroup);
// Execute computation on the client nodes. clientCompute.broadcast(() -> System.out.println("sum of: " + (2+2)));

这种方法有一个缺点。使用这种方法,数据将被分配给独立的节点,并且为了计算这些数据,所有的客户端节点都需要从服务器节点检索数据。它可以产生大量的网络连接并产生延迟。但是,您可以在一个主机上单独的JVM上运行客户机和服务器节点,以减少网络延迟。从部署的角度来看,Apache Ignite服务器可以分为以下几个组:

  • 内置应用程序
  • 服务器在单独的JVM

内置应用程序

使用这种方法,Apache Ignite节点与应用程序在同一个JVM上运行。它可以是运行在应用服务器上的任何web应用程序,也可以是独立的Java应用程序。例如,我们的独立HelloIgnite应用程序是来自第一章—是一个嵌入式的Ignite。Ignite服务器与应用程序一起在相同的JVM中运行,并与网格的其他节点连接。如果应用程序宕机或被关闭,则Ignite server也将关闭。拓扑如下图所示:

服务器在单独的JVM

在这种方法中,服务器节点将在独立的JVM中运行,客户机节点将远程连接到服务器。服务器节点参与缓存、计算执行、流等等。客户端还可以使用REST API连接到任何单独的节点。默认情况下,所有的Ignite节点都作为服务器节点启动;客户端节点需要显式启用。

这是最常见的方法,因为它在集群机制方面提供了更大的灵活性。Ignite服务器可以被离线并重新启动,不会对整个应用程序或集群产生任何影响。

客户机和服务器分别位于单个主机上的JVM中

当您的数据节点上有大量事务并计划在该节点上执行一些计算时,您可以考虑这种方法。您可以在一个容器(例如Docker或OpenVZ)中单独的JVM中执行客户机和服务器。容器可以位于单个主机中。容器将隔离资源(cpu、ram、网络接口等),JVM只使用分配给这个容器的独立资源。

这种方法也有它自己的缺点。在执行过程中,客户端(compute)节点可以从驻留在其他主机上的任何其他数据节点检索数据,并且可以增加网络延迟。

缓存拓扑

Ignite提供了三种不同的缓存拓扑的方法::分区,复制和本地。缓存模式分别为每个缓存配置。每个缓存拓扑都有其优缺点。默认缓存拓扑是分区的,没有任何备份选项。

分区缓存拓扑

这种拓扑的目标是获得极好的可扩展性。在这种模式下,Ignite集群透明地对缓存的数据进行分区,以便在整个集群中均匀地分配负载。通过均匀地划分数据,缓存的大小和处理能力随集群的大小呈线性增长。管理数据的职责在整个集群中自动共享。集群中的每个节点或服务器都包含其主数据,如果定义了备份副本,则包含备份副本。

对于分区缓存拓扑,缓存上的DML操作非常快,因为每个键只需要更新一个主节点(可选为一个或多个备份节点)。对于高可用性,应该配置缓存条目的备份副本。备份副本是一个或多个主副本的冗余副本,该副本将驻留在另一个节点中。有一个简单的公式可以计算,为了实现集群的高可用性,需要多少备份。

备份拷贝数= N-1,其中N为集群中节点的总数

假设集群中总共有3个节点。如果您总是希望从集群获得响应(当某些节点不可用时),备份副本的数量应该不少于2。在这种情况下,存在3个缓存条目副本,2个备份副本和1个主副本。当处理大型数据集时或更新非常频繁,分区是适合的方式。备份过程可以是同步的,也可以是异步的。在同步模式下,客户端应该在完成提交或写入之前等待远程节点的响应。

缓存复制拓扑

这种方法的目标是获得极高的性能。使用这种方法,缓存数据被复制到集群的所有成员。由于数据被复制到每个集群节点,因此可以使用它而无需等待。这为读取提供了可能的最高速度。每个成员都从自己的内存访问数据。缺点是频繁的写操作非常昂贵。更新复制缓存需要将新版本推给所有其他集群成员。如果更新频率很高,这将限制可伸缩性。

在上图中,相同的数据存储在所有集群节点中;复制缓存的大小受每个节点上可用内存大小的限制。这种模式适用于缓存读取比缓存写入频繁得多,而且数据集很小的场景。复制的可伸缩性与成员数量、每个成员更新的频率和更新的大小成反比。

本地模式

这是缓存模式的一个非常原始的版本;使用这种方法,没有数据分布到集群中的其他节点。就本地缓存而言,没有任何复制或分区过程,数据获取非常便捷和快速。它为最近和经常使用的数据提供零延迟访问。本地缓存主要用于只读操作。它对于读/写传递行为也非常有效,在这种行为中,数据是在缓存丢失时从数据源中加载的。与分布式缓存不同,本地缓存仍然具有分布式缓存的所有特性;它提供查询缓存、自动数据删除等功能。

缓存策略

随着高交易量的web应用程序和移动应用程序的激增,数据存储已成为性能的主要瓶颈。在大多数情况下,持久性存储(如关系数据库)不能通过添加更多的服务器来完美地扩展。在这种情况下,内存中的分布式缓存为解决数据存储瓶颈提供了一个很好的解决方案。
它扩展了多个服务器(称为网格),将它们的内存集中在一起,并在所有服务器上保持缓存的同步。在分布式内存缓存中有两种主要策略:

Cache-aside

在这种方法中,应用程序负责从持久性存储区进行读写。缓存根本不与数据库交互。这叫做cache-aside。缓存的行为就像一个快速扩展的内存数据存储。应用程序在查询数据存储之前检查缓存中的数据。此外,应用程序在对持久性存储进行任何更改后更新缓存。

然而,尽管cache-aside的速度非常快,但是这种策略也有一些缺点。如果多个应用程序处理相同的数据存储,应用程序代码可能变得复杂,并可能导致代码重复。当缓存数据丢失时,应用程序将查询数据存储、更新缓存并继续处理。如果不同的应用程序线程同时执行此处理,则可能导致多个数据存储访问。

Read-through and Write-through

这就是应用程序对内存缓存作为主要的数据存储,并读取数据并将数据写入。内存内缓存负责在缓存丢失时将查询传播到数据存储。此外,数据在缓存中更新时将自动更新。所有通读和写操作都将参与整个缓存事务,并作为一个整体提交或回滚。

Read-through and Write-through比cache-aside有许多优势。首先,它简化了应用程序代码。读入允许缓存在自动过期时从数据库重新加载对象。这意味着您的应用程序不必在高峰时间访问数据库,因为最新的数据总是在缓存中。

Write behind

也可以使用write-behind来获得更好的写性能。Write-behind允许应用程序快速更新缓存并返回。然后,它聚合更新并将其作为批量操作异步刷新到持久性存储中。同样,对于Write-behind,您可以指定节流限制,因此数据库写入速度不如缓存更新快,因此对数据库的压力更小。此外,您可以安排数据库写操作在非高峰时间发生,这可以最小化对数据库的压力。

Apache Ignite通过实现Java JCache特性提供了上述所有缓存策略。此外,Ignite提供了Ignite Cassandra模块,它通过使用Cassandra作为过期缓存条目的持久存储来实现Ignite缓存的持久性存储。

数据模型

Apache Ignite实现了键值数据模型,特别是JCache (JSR 107)规范。JCache为Java应用程序与缓存交互提供了一种常见的方式。
从设计的角度来看,JCache提供了一个非常简单的键值存储。键值存储是一个简单的Hashtable或Map,主要用于通过主键访问数据库表。您可以将键值作为传统RDBMS中的一个简单表,其中包含两个列,如键和值。值列的数据类型可以是任何原始数据类型,如字符串、整数或任何复杂的Java对象(或Blob - in Oracle术语)。应用程序可以提供一个键和值并将它们持久化。如果键已经存在,将覆盖该值,否则将创建一个新值。为了清晰起见,我们可以将键值存储与Oracle术语进行比较:

 

它有非常原始的操作,比如从存储中放置、获取或删除值。因为它总是使用主键访问,所以它们通常有很好的性能和可扩展性。Java缓存API定义了五个核心接口:CachingProvider、CacheManager、Cache、Entry和Expire。

  • CachingProvider定义了建立、配置、获取、管理和控制零个或多个cachemanager的机制
  • CacheManager定义了在CacheManager上下文中建立、配置、获取、管理和控制唯一命名缓存的机制。
  • 缓存是类似于数据结构的哈希表,允许临时存储基于键的值。缓存属于单个CacheManager。
  • 条目是存储在缓存中的一个键-值对。
    键值对可以在图中很容易地进行说明。考虑以下缓存中的条目图:

通过键值存储,Java缓存API还提供了以下附加特性:

  • 基本的缓存操作
  • 原子操作,类似于java.util.ConcurrentMap
  • 通读缓存
  • 直写式高速缓存
  • 条目处理器
  • 缓存事件监听器
  • 统计数据
  • 完整的泛型API用于编译时的安全性
  • 按引用存储(仅适用于堆缓存)和按值存储
    除了JCache之外,Apache Ignite还提供了ACID事务、SQL查询功能、数据加载、异步模式和各种内存模型。Apache Ignite提供了IgniteCache接口,它扩展了使用缓存的Java缓存接口。

 

CAP定理

当第一次开始使用Apache Ignite时,想知道Ignite一方面支持ACID事务,另一方面,Ignite也是一个高度可用的分布式系统。在任何NoSQL数据存储中,支持ACID事务并同时提供高可用性都是一个具有挑战性的特性。要横向扩展,需要强大的网络分区容忍度,这需要放弃一致性或可用性。NoSQL系统通常通过放松关系可用性或事务语义来实现这一点。许多流行的NoSQL数据存储(如Cassandra和Riak)仍然没有事务支持,并被归类为AP系统。AP一词来源于著名的CAP定理10,意为可用性和分区容忍性,在NoSQL系统中,这比一致性更重要。

如上图所见,分布式系统只能具有以下三个属性中的两个:

  • 分区容忍性:也就是说,如果在两个节点之间断开网络,系统仍然可以工作。
  • 一致性:在集群中的每个节点有相同的数据。
  • 可用性:如果可能的话总有一个节点可进行响应查询。
    因此,让我们看看三种选择中有两种会如何影响系统行为,如下所示:
  • CA系统:在这种方法中,为了获得一致性和可用性,牺牲了分区容忍性。数据库系统提供了事务,系统是高度可用的。大多数关系数据库被归类为CA系统。该系统存在严重的扩展问题。
  • CP系统:在CP系统中,为了一致性和容错而牺牲了可用性。在节点失败的情况下,将丢失一些数据。
  • AP系统:这个系统总是可用的和可分区的。此外,通过向集群添加节点,该系统可以轻松地扩展。Cassandra是这类系统的一个很好的例子。
    现在,我们回到我们的问题,Ignite在CAP定理中的哪个位置?乍一看,Ignite可以被分到CP类,因为它是完全兼容ACID的分布式事务,具有分区容错性。但这只是一部分。Apache Ignite也可以看作是AP系统。为什么Ignite有两种不同的分类?因为它有两种不同的用于缓存操作、事务和原子的事务模式。
    在事务模式中,您可以在一个事务中对多个DML操作进行分组,并在缓存中提交。在此场景中,Ignitet将通过悲观锁锁定一个访问的数据。如果您为缓存配置备份副本,则Ignite将使用2p提交协议来处理它的事务。

另一方面,在原子模式下,Ignite支持多个原子操作,一次一个。在原子模式下,每个DML操作要么成功要么失败,读和写操作都不会锁定数据。这种模式提供了比事务模式更高的性能。在Ignite cache中进行写入时,对于每一块数据,在主节点中将有一个主副本和一个备份副本(如果定义的话)。当您从Ignite grid读取数据时,总是从主节点读取数据,除非主节点关闭,这时数据将从备份中读取。从这个角度看,您获得了系统可用性和整个系统作为AP系统的分区容忍性。在原子模式中,Ignite与Apache Cassandra非常相似。然而,现实世界的系统很少完全属于上述所有类别,因此将CAP视为一个连续体会更有帮助。大多数系统将努力保持一致性、可用性和分区容错性,许多系统可以根据最重要的部分进行调优。

Clustering

Apache Ignite的设计目标是在集群中跨多个节点处理高工作负载。集群被设计为一组节点。客户端可以向集群中的任何节点发送读/写请求。Ignite节点可以自动发现彼此,并且数据分布在集群中的所有节点上。这有助于在需要时扩展集群,而不需要一次重新启动整个集群。Ignite提供了一种简单的方法来在网格中创建集群节点的逻辑组,并将相关数据配置到类似的节点中,以提高应用程序的性能和可伸缩性。

Cluster group

Ignite ClusterGroup提供了在集群中创建一组逻辑节点的简单方法。按照设计,Ignite集群中的所有节点都是相同的。然而,Ignite允许出于特定目的对任何应用程序的节点进行逻辑分组。例如,您可以将所有节点集中在一起,使用名称myCache服务缓存,或者所有访问缓存myCache的客户端节点。此外,你可能希望仅仅在远程节点上部署服务。你可以限制作业执行、服务部署、消息传递、事件和其他任务只在一些集群组中运行。

Ignite提供了以下三种方法来在Ignite Grid中创建逻辑集群:

  • Predefined cluster group(预定义集群组)。Ignite提供了接口的ClusterGroup的预定义实现,以基于任何谓词创建集群组。谓词可以是远程节点、缓存节点、具有指定属性的节点等等。以下代码是一个示例集群组,其中包含缓存myCache的所有节点缓存数据:
IgniteCluster cluster = ignite.cluster();
//名称为“myCache”缓存数据的所有数据节点。
ClusterGroup dataGroup = cluster.forDataNodes("myCache");
  • Cluster group with Node Attributes(具有节点属性的群集组)。尽管集群中的每个节点都是相同的,但用户可以配置节点为主节点或worker节点和数据节点。启动时,所有集群节点自动将所有环境和系统属性注册成为节点的属性。但是,用户可以通过配置来选择分配自己的节点属性:
IgniteConfiguration cfg = new IgniteConfiguration();
Map<String, String> attrs = Collections.singletonMap("ROLE", "master");
cfg.setUserAttributes(attrs);
Ignite ignite = Ignition.start(cfg);

在声明节点之后,可以使用属性master对节点进行分组,如下所示:

IgniteCluster cluster = ignite.cluster();
ClusterGroup workerGroup = cluster.forAttribute("ROLE", "master");
Collection<GridNode> workerNodes = workerGroup.nodes();
  • Custom cluster group(自定义集群组)。有时它也称为动态集群组。可以根据某些谓词定义动态集群组。谓词可以基于任何指标,如CPU利用率或空闲堆空间。这样的集群组将始终只包含传递谓词的节点。下面是使用了小于256 MB堆内存的节点上的集群组的示例。注意,该组中的节点将根据使用的堆内存随时间而变化:
IgniteCluster cluster = ignite.cluster();
//使用的堆内存小于256MB的节点
ClusterGroup readyNodes = cluster.forPredicate((node) -> node.metrics().getHeapMemoryUsed(\ ) < 256);

Data collocation数据配置

数据配置术语是指将相同的相关数据分配到相同的节点。例如,如果我们有一个缓存为客户属性相关数据和另一个缓存为客户的事务类型的数据。我们可以将他们相关联的数据分配到相同的节点上。在此方法中,相关数据的网络往返次数减少,客户端应用程序可以从单个节点获取数据。在这种情况下,具有相同字段集的多个缓存被分配给相同的节点。

image.png

例如,客户信息及其帐户信息位于同一个Ignite主机上。为了实现这一点,用于缓存客户端对象的缓存键应该有一个带有@AffinityKeyMapped注释的字段或方法,这样为帐户对象的key配置提供value。为了方便,您还可以选择使用AffinityKey类,如下所示:

Object clientKey1 = new AffinityKey("Client1", "accId"); 
Object clientKey2 = new AffinityKey("Client2", "accId ");
Client c1 = new Client (clientKey1, ...); 
Client c2 = new Client (clientKey2, ...);
cache.put("accId ", new Account(“credit card”));  
cache.put(clientKey1, c1); 
cache.put(clientKey2, c2);

要计算关联函数,可以使用任何一组字段,不需要使用任何类型的唯一键。例如,要计算客户端帐户关联函数,可以使用拥有account ID的 client ID。

Compute collocation with Data 数据计算配置

Apache Ignite还提供了将数据计算单元路由到所需数据缓存的节点的能力。这个概念被称为计算和数据的配置。它允许将整个工作单元路由到某个节点。要将计算与数据配置在一起,应该使用IgniteCompute.affinityrun(…)IgniteCompute.affinitycall(…)方法。

下面是如何在客户信息及其帐户信息分配的同一集群节点上配置计算。

String accId = "acountId";
ignite.compute().affinityRun("myCache", accId, () -> { Account account = cache.get(accId);
Client c1 = cache.get(clientKey1);
Client c2 = cache.get(clientKey2);
... });

计算单元以本地方式访问客户数据,这种方法大大降低了数据在集群中的网络往返,提高了数据处理的性能。
Apache Ignite通过两种关联函数实现:

  • RendezvousAffinityFunction-这个函数允许在部分到节点的映射中有一点差异(例如,某些节点可能负责比其他节点多一点的分区)。但是,它保证当拓扑发生变化时,分区只能迁移到已连接的节点。集群中的现有节点之间不会发生数据交换。这是Apache Ignite使用的默认关联函数。
  • FairAffinityFunction-这个函数试图确保集群节点之间的分区分布是均匀的。这是以集群中现有节点之间可能的分区迁移为代价的。

Zero SPOF 零单点故障

在任何分布式系统中,节点故障应该能够被预判的,特别是当集群的规模增大时。零单点故障(SPOF)设计模式确保系统的单个节点或部分的失效不能阻碍整个集群或系统工作。使用主-从复制或混合主-主系统的系统设计属于这一类。在Hadoop 2.0.0之前,Hadoop NameNode是HDFS集群中的一个SPOF。大多数企业不希望单点失败,原因显而易见。
Apache Ignite作为一个水平可伸缩的分布式系统,其设计方式是使集群中的所有节点都相等,你可以从集群中的任何节点进行读写。在Ignite集群中没有主-从通信。

数据在集群中备份或复制,因此任何节点的失败都不会导致整个集群或应用程序崩溃。通过这种方式,Ignite提供了一种动态的高可用性。这种方法的另一个好处是可以轻松地添加新节点。当新节点加入集群时,它们可以从现有节点上接管一部分数据。因为所有节点都是相同的,所以这种通信可以在运行的集群中无缝地进行。

在Ignite集群中使用SQL

在Ignite中处理SQL查询有两种主要方法:

  • In-memory Map-Reduce:如果您正在对分区缓存执行任何SQL查询,Ignite将查询分解为内存中的映射查询和一个单一的reduce查询。map查询的数量取决于分区的大小和集群中的分区数量。然后,在参与缓存的所有数据节点上执行所有的map查询,将结果提供给reduce节点,从而在这些中间结果上运行reduce查询。如果您不熟悉Map-Reduce模式,可以将它想象成一个Java Fork-join过程。
  • H2 SQL engine:如果您正在对复制的或本地缓存执行SQL查询,那么Ignite就知道所有数据在本地可用,并在H2数据库引擎中运行一个简单的本地SQL查询。需要注意的是,在复制缓存中,每个节点都包含其他节点的副本数据。H2数据库是一个用Java编写的免费数据库,可以在嵌入式模式下工作。根据配置的不同,每个Ignite节点都可以使用嵌入式H2 SQL引擎。

多数据中心复制

当前,多数据中心复制是任何数据库的主要需求之一,包括RDBMS和NoSQL。简单地说,多数据中心复制意味着在不同的数据中心之间复制数据。多个数据中心复制可以有几个场景:

  • 地理位置的场景:在这个场景中,数据应该托管在不同的数据中心中,这取决于用户的位置,以便提供响应性的交换。在这种情况下,数据中心可以位于不同的地理位置,例如在不同的地区或在不同的国家。数据同步在数据中心中是完全透明的,是双向的。在应用程序逻辑代码中可以定义用户将连接到哪个数据中心。
  • 实时备份的场景: 在这个场景中,大多数用户使用不同的数据中心作为实时备份,可以快速地用作后备集群。这个用例与灾难恢复非常相似。有时也叫被动复制。在被动复制中,复制发生在一个方向。从主复制到副本。客户端可以连接到一个数据中心的主数据库,并在数据库上执行所有CRUD操作。

    为了确保两个数据库之间的一致性,副本作为只读数据库启动,只有从主复制的事务才能修改数据库内容。
    Apache Ignite不支持多个数据中心复制。但是可以跨越不同数据中心的Ignite节点(例如,一个数据中心有10个节点,另一个数据中心有10个节点)。

    跨越Ignite网格到多个数据中心可以引出一些问题:

  • Latency延迟:这将在服务端影响性能。如果来自不同数据中心的任何节点都有主数据的备份副本,则事务时间可能非常高。从长远来看,也可能会遇到数据一致性方面的问题。
  • 连接到不同数据中心的客户端将面临不同的客户端操作延迟。

异步支持

通常,任何普通的(同步的)put/get或执行调用都会阻塞应用程序的执行,直到从服务器获得结果为止。之后,可以根据客户的需要对结果进行迭代和使用。这可能是处理任何持久性存储的最常见方式。但是,异步方法执行可以提供显著的好处,并且是现代系统的要求。Apache Ignite提供了在所有分布式api上使用异步和同步方法灵活调用的范例。它可以从存储中put/get任何条目,也可以在计算网格中执行作业。Ignite异步方法执行是一个非阻塞操作,并返回一个IgniteFuture对象而不是实际结果。可以通过调用IgniteFuture.get()方法获得结果。以下是一个使用异步调用从Ignite缓存中获取条目的非常简单的例子:

// 获得或者创建缓存
IgniteCache<Integer, String> cache = ignite.getOrCreateCache("testCache"); 
// 得到一个异步的缓存
IgniteCache<Integer, String> asynCache = cache.withAsync();
// 放一些数据进去
for(int i = 1; i <= 100; i++){ 
  cache.put(i, Integer.toString(i));
}
//异步获取第一条记录
asynCache.withAsync().get(1);
// 获得 future promise
IgniteFuture<String> igniteFuture = asynCache.future();
// java 8 lamda expression
igniteFuture.listen(f-> System.out.println("Cache Value:" + f.get()));

在上面的代码中,我们将100个条目同步插入到Ignite缓存testCache中。然后,异步请求第一个条目并获得调用的future。接下来,异步地侦听要完成的操作。注意,Java main()方法的主线程并不等待任务完成,而是将任务交给另一个线程,然后继续。

  • 弹性
    Ignite客户端节点在本质上是完全弹性的。弹性这个词指的是服务器或系统在出现故障时快速恢复并继续运行的能力。在某些情况下,Ignite客户端节点可以从集群断开连接:
  • 主机宕机或重新启动;
  • 缓慢的客户端可以被服务器断开连接。
    当客户端确定一个节点与集群断开连接时,它尝试重新建立与服务器的连接。这一次,它将分配给一个新的节点ID,并尝试重新连接到集群。

安全

Apache Ignite是一个开源项目,它不提供任何安全特性。

结论

我们介绍了内存数据网格的基本概念。我们首先简要地描述了Ignite函数概述和具有不同拓扑结构的各种缓存策略。我们还介绍了一些基于标准数据访问模式的技术,如缓存、通读和写入。我们介绍了Ignite数据配置技术,并简要介绍了Ignite键值数据存储。

小礼物走一走,来简书关注我

来源: https://www.jianshu.com/p/4f6d00548363

Ignite的集群部署

Ignite的集群部署

Ignite具有非常先进的集群能力,本文针对和集群有关的技术点做一个简短的介绍,然后针对实际应用的可能部署形式做了说明和对比,从中我们可以发现,Ignite平台在部署的灵活性上,具有很大的优势。

Ignite的设计表明整个系统本身就具有固有的可用性和可扩展性。ignite节点间通信允许所有节点接收更新,无需一个快速的主协调器。节点可以不受干扰地添加或删除,以增加可用RAM的数量。
Ignite数据结构具有完全的弹性,允许对单个服务器或多个服务器进行无干扰的自动检测和恢复。

客户端和服务端

Apache Ignite具有一个可选的服务概念,并提供了两种类型的节点:客户端和服务器节点。

  • Server 包含数据、缓存、计算和流,并且可以是内存中的Map-Reduce任务的一部分。
  • Client 提供远程连接服务器以将元素放入/获取到缓存的能力。它还可以存储部分数据(近缓存),这是一个较小的本地缓存,存储最近和最频繁访问的数据。

    还可以将服务器节点分组到一个集群中执行工作。在集群中,可以限制作业执行、服务部署、流和其他任务只在集群组中运行。
    你可以基于任何谓词创建一个集群组。例如,您可以从一组节点创建一个集群组,其中所有节点负责为testCache缓存数据。默认情况下,所有节点都作为服务节点启动。客户端模式需要显式启用。注意,您不能物理地将数据节点与计算节点分离。在Apache Ignite中,包含数据的服务器也用于执行计算。Apache Ignite客户机节点也参与作业执行。乍一看,这个概念似乎很复杂,但让我们试着澄清这个概念。服务器节点总是存储数据,默认情况下可以参与任何计算任务。另一方面,客户端节点可以操作服务器缓存、存储本地数据并参与计算任务。通常,客户端节点用于从缓存中放置或检索数据。这种混合客户端节点在开发具有多个节点的大型Ignite网格时提供了灵活性。客户端和服务器节点都位于一个网格中,在某些情况下(例如,数据节点中的大容量acid事务),您不希望在数据节点上执行任何计算。在这种情况下,您可以通过创建相应的集群组来选择只在客户端节点上执行作业。通过这种方式,您可以在一个网格中将数据节点与计算节点分开。可以使用以下伪代码对客户端进行计算:

ClusterGroup clientGroup = ignite.cluster().forClients(); IgniteCompute clientCompute = ignite.compute(clientGroup);
// Execute computation on the client nodes. clientCompute.broadcast(() -> System.out.println("sum of: " + (2+2)));

这种方法有一个缺点。使用这种方法,数据将被分配给独立的节点,并且为了计算这些数据,所有的客户端节点都需要从服务器节点检索数据。它可以产生大量的网络连接并产生延迟。但是,您可以在一个主机上单独的JVM上运行客户机和服务器节点,以减少网络延迟。从部署的角度来看,Apache Ignite服务器可以分为以下几个组:

  • 内置应用程序
  • 服务器在单独的JVM

1.相关概念

1.1.节点平等

Ignite没有master节点或者server节点,也没有worker节点或者client节点,按照Ignite的观点所有节点都是平等的。但是开发者可以将节点配置成master,worker或者client以及data节点。

1.2.发现机制

Ignite节点之间会自动感知,集群可扩展性强,不需要重启集群,简单地启动新加入的节点然后他们就会自动地加入集群。这是通过一个发现机制实现的,他使节点可以彼此发现对方,Ignite默认使用TcpDiscoverySpi通过TCP/IP协议来作为节点发现的实现,也可以配置成基于多播的或者基于静态IP的,这些方式适用于不同的场景。

1.3.部署模式

Ignite可以独立运行,也可以在集群内运行,也可以将几个jar包嵌入应用内部以嵌入式的模式运行,也可以运行在Docker容器以及Mesos和Yarn等环境中,可以在物理机中运行,也可以在虚拟机中运行,这个广泛的适应性是他的一个很大的优势。

1.4.配置方式

Ignite的大部分配置选项,都同时支持通过基于Spring的XML配置方式以及通过Java代码的编程方式进行配置,这个也是个重要的优点。

1.5.客户端和服务端

Ignite中各个节点是平等的,但是可以根据需要将节点配置成客户端或者服务端,服务端节点参与缓存,计算,流式处理等等,而原生的客户端节点提供了远程连接服务端的能力。Ignite原生客户端可以使用完整的Ignite API,包括近缓存,事务,计算,流,服务等等。 所有的Ignite节点默认都是以服务端模式启动的,客户端模式需要显式地启用,如下:

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="clientMode" value="true"/>
</bean>

2.创建集群

一个Ignite节点可以从命令行启动,可以用默认的配置也可以传递一个配置文件。可以启动很多的节点然后他们会自动地发现对方。 要启动一个基于默认配置的网格节点,打开命令行然后切换到IGNITE_HOME(安装文件夹),然后输入如下命令:

$ bin/ignite.sh

然后会看到大体如下的输出:

1.[02:49:12] Ignite node started OK (id=ab5d18a6)
2.[02:49:12] Topology snapshot [ver=1, nodes=1, CPUs=8, heap=1.0GB]

在嵌入式模式中,通过如下的代码同样可以启动一个节点:

Ignite ignite = Ignition.start();

3.集群组

从设计上讲,所有集群节点都是平等的,所以没有必要以一个特定的顺序启动任何节点,或者给他们赋予特定的规则。然而,Ignite可以因为一些应用的特殊需求而创建集群节点的逻辑组,比如,可能希望只在远程节点上部署一个服务,或者给部分worker节点赋予一个叫做worker的规则来做作业的执行。比如,下面这个例子只把作业广播到远程节点(除了本地节点):

final Ignite ignite = Ignition.ignite();
IgniteCluster cluster = ignite.cluster();
IgniteCompute compute = ignite.compute(cluster.forRemotes());
compute.broadcast(() -> System.out.println("节点Id: " + ignite.cluster().localNode().id()));

Ignite内置了很多预定义的集群组,同时还支持集群组的自定义。可以基于一些谓词定义动态集群组,这个集群组只会包含符合该谓词的节点。下面这个例子,一个集群组只会包括CPU利用率小于50%的节点,注意这个组里面的节点会随着CPU负载的变化而改变:

IgniteCluster cluster = ignite.cluster();
ClusterGroup readyNodes = cluster.forPredicate((node) -> node.metrics().getCurrentCpuLoad() < 0.5);

4.集群配置

Ignite中,通过DiscoverySpi节点可以彼此发现对方,可以配置成基于多播的或者基于静态IP的。Ignite提供了TcpDiscoverySpi作为DiscoverySpi的默认实现,它使用TCP/IP来作为节点发现的实现。 对于多播被禁用的情况,TcpDiscoveryVmIpFinder会使用预配置的IP地址列表,只需要提供至少一个远程节点的IP地址即可,但是为了保证冗余一个比较好的做法是提供2-3个网格节点的IP地址。如果建立了与任何一个已提供的IP地址的连接,Ignite就会自动地发现其他的所有节点。 也可以同时使用基于多播和静态IP的发现,这种情况下,除了通过多播接受地址以外,TcpDiscoveryMulticastIpFinder也可以使用预配置的静态IP地址列表。 下面的例子,显示的是如何通过预定义的IP地址列表建立集群:

TcpDiscoverySpi spi = new TcpDiscoverySpi();
TcpDiscoveryVmIpFinder ipFinder = new TcpDiscoveryVmIpFinder();
// 设置预定义IP地址,注意端口或者端口范围是可选的。
ipFinder.setAddresses(Arrays.asList("1.2.3.4", "1.2.3.5:47500..47509"));
spi.setIpFinder(ipFinder);
IgniteConfiguration cfg = new IgniteConfiguration();
cfg.setDiscoverySpi(spi);
// 启动集群
Ignition.start(cfg);

5.零部署

和计算等有关的代码可能是任意自定义的类,在Ignite中, 远程节点会自动感知这些类,不需要显式地将任何jar文件部署或者移动到任何远程节点上。这个行为是通过对等类加载(P2P类加载)实现的,他是Ignite中的一个特别的分布式类加载器,实现了节点间的字节码交换。当对等类加载启用时,不需要在集群内的每个节点上手工地部署代码,也不需要每次在发生变化时重新部署。 可以通过如下方法启用对等类加载;

<bean class="org.apache.ignite.configuration.IgniteConfiguration">
    <property name="peerClassLoadingEnabled" value="true"/>
</bean>

6.云部署

对于很多的云环境,通常有多播被禁用以及IP地址不固定的限制,对于这种情况,Ignite提供了发现的扩展机制解决了该问题,并且内置了对于常见的云服务(比如AWS)的支持,本文不赘述,开发者可以参照相关的文档

7.Docker等其他环境的部署

对于Docker、Mesos、Yarn等环境,Ignite同样支持,本文不赘述,开发者可以参照相关的文档

8.部署实践

Ignite的部署模式非常的灵活,在实际的场景中可以针对实际需要采用不同的部署方式,下面做简单的总结和对比:

8.1.独立式Ignite集群

这种情况下,集群的部署完全独立于应用,这个集群可以用于分布式计算,分布式缓存,分布式服务等,这时应用以客户端模式接入集群进行相关的操作,大体是如下的部署模式:

1

优点 对已有的应用运行环境影响小,并且这个集群可以共享,为多个应用提供服务,对整个应用来说,额外增加了很多的计算和负载能力。 缺点 需要单独的一组机器,相对成本要高些,如果缓存操作并发不高或者计算不饱和,存在资源利用率低的情况。整体架构也变得复杂,维护成本也要高些。

8.2.嵌入式Ignite集群

这种情况下,可以将必要的jar包嵌入已有应用的内部,利用Ignite的发现机制,自动建立集群,大体是如下的部署模式:

2

优点 无需额外增加机器,成本最低,Ignite可以和应用无缝集成,所有节点都为服务端节点,可以充分利用Ignite的丰富功能。这个模式可扩展性最好,简单增加节点即可快速扩充整个系统的计算和负载能力。 缺点 Ignite占用了服务器的部分资源,对应用整体性能有影响,可能需要进行有针对性的优化,应用更新时,集群可能需要重启,这时如果Ignite需要加载大量的数据,重启的时间可能变长,甚至无法忍受。

8.3.混合式Ignite集群

这种情况下,将上述2种模式混合在一起,即同时增加机器部署独立集群,同时又将Ignite嵌入应用内部以服务端模式运行,通过逻辑集群组进行资源的分配,整体上形成更大的集群,大体是如下的部署模式:

3

这种模式更为灵活,调优后能做到成本、功能、性能的平衡,综合效果最佳。这时可以将缓存的数据通过集群组部署到应用外部的节点上,这样可以避免频繁的冷启动导致缓存数据频繁的长时间加载,对于计算,也能够动态地充分利用所有计算节点的资源。

 

来源: https://my.oschina.net/liyuj/blog/651036?p=1