• 关于

    允许外部中断是什么

    的搜索结果

问题

Web测试方法

在Web工程过程中,基于Web系统的测试、确认和验收是一项重要而富有挑战性的工作。基于Web的系统测试与传统的软件测试不同,它不但需要检查和验证是否按照设计的要求运行,而且还要测试系统在不同用户的浏...
技术小菜鸟 2019-12-01 21:41:32 7022 浏览量 回答数 1

问题

企业运营对DevOps的「傲慢与偏见」

摘要:出于各种原因,并非所有人都信任 DevOps 。有些人觉得 DevOps 只不过给开发者改善产品提供了一个途径而已,还有的人觉得 DevOps 是一堆悦耳的空头支票,甚至有人认为...
忆远0711 2019-12-01 21:32:29 9823 浏览量 回答数 0

回答

Go 的优势在于能够将简单的和经过验证的想法结合起来,同时避免了其他语言中出现的许多问题。本文概述了 Go 背后的一些设计原则和工程智慧,作者认为,Go 语言具备的所有这些优点,将共同推动其成为接替 Java 并主导下一代大型软件开发平台的最有力的编程语言候选。很多优秀的编程语言只是在个别领域比较强大,如果将所有因素都纳入考虑,没有其他语言能够像 Go 语言一样“全面开花”,在大型软件工程方面,尤为如此。 基于现实经验 Go 是由经验丰富的软件行业老手一手创建的,长期以来,他们对现有语言的各种缺点有过切身体会的痛苦经历。几十年前,Rob Pike 和 Ken Thompson 在 Unix、C 和 Unicode 的发明中起到了重要作用。Robert Griensemer 在为 JavaScript 和 Java 开发 V8 和 HotSpot 虚拟机之后,在编译器和垃圾收集方面拥有数十年的经验。有太多次,他们不得不等待 Google 规模的 C++/Java 代码库进行编译。于是,他们开始着手创建新的编程语言,将他们半个世纪以来的编写代码所学到的一切经验包含进去。 专注于大型工程 小型工程项目几乎可以用任何编程语言来成功构建。当成千上万的开发人员在数十年的持续时间压力下,在包含数千万行代码的大型代码库上进行协作时,就会发生真正令人痛苦的问题。这样会导致一些问题,如下: 较长的编译时间导致中断开发。代码库由几个人 / 团队 / 部门 / 公司所拥有,混合了不同的编程风格。公司雇佣了数千名工程师、架构师、测试人员、运营专家、审计员、实习生等,他们需要了解代码库,但也具备广泛的编码经验。依赖于许多外部库或运行时,其中一些不再以原始形式存在。在代码库的生命周期中,每行代码平均被重写 10 次,被弄得千疮百痍,而且还会发生技术偏差。文档不完整。 Go 注重减轻这些大型工程的难题,有时会以使小型工程变得更麻烦为代价,例如,代码中到处都需要几行额外的代码行。 注重可维护性 Go 强调尽可能多地将工作转给自动化的代码维护工具中。Go 工具链提供了最常用的功能,如格式化代码和导入、查找符号的定义和用法、简单的重构以及代码异味的识别。由于标准化的代码格式和单一的惯用方式,机器生成的代码更改看起来非常接近 Go 中人为生成的更改并使用类似的模式,从而允许人机之间更加无缝地协作。 保持简单明了 初级程序员为简单的问题创建简单的解决方案。高级程序员为复杂的问题创建复杂的解决方案。伟大的程序员找到复杂问题的简单解决方案。 ——Charles Connell 让很多人惊讶的一点是,Go 居然不包含他们喜欢的其他语言的概念。Go 确实是一种非常小巧而简单的语言,只包含正交和经过验证的概念的最小选择。这鼓励开发人员用最少的认知开销来编写尽可能简单的代码,以便许多其他人可以理解并使用它。 使事情清晰明了 良好的代码总是显而易见的,避免了那些小聪明、难以理解的语言特性、诡异的控制流和兜圈子。 许多语言都致力提高编写代码的效率。然而,在其生命周期中,人们阅读代码的时间却远远超过最初编写代码所需的时间(100 倍)。例如,审查、理解、调试、更改、重构或重用代码。在查看代码时,往往只能看到并理解其中的一小部分,通常不会有完整的代码库概述。为了解释这一点,Go 将所有内容都明确出来。 错误处理就是一个例子。让异常在各个点中断代码并在调用链上冒泡会更容易。Go 需要手动处理和返回每个错误。这使得它可以准确地显示代码可以被中断的位置以及如何处理或包装错误。总的来说,这使得错误处理编写起来更加繁琐,但是也更容易理解。 简单易学 Go 是如此的小巧而简单,以至于人们可以在短短几天内就能研究通整个语言及其基本概念。根据我们的经验,培训用不了一个星期(相比于掌握其他语言需要几个月),初学者就能够理解 Go 专家编写的代码,并为之做出贡献。为了方便吸引更多的用户,Go 网站提供了所有必要的教程和深入研究的文章。这些教程在浏览器中运行,允许人们在将 Go 安装到本地计算机上之前就能够学习和使用 Go。 解决之道 Go 强调的是团队之间的合作,而不是个人的自我表达。 在 Go(和 Python)中,所有的语言特性都是相互正交和互补的,通常有一种方法可以做一些事情。如果你想让 10 个 Python 或 Go 程序员来解决同一个问题,你将会得到 10 个相对类似的解决方案。不同的程序员在彼此的代码库中感觉更自在。在查看其他人的代码时,国骂会更少,而且人们的工作可以更好地融合在一起,从而形成了一致的整体,人人都为之感到自豪,并乐于工作。这还避免了大型工程的问题,如: 开发人员认为良好的工作代码很“混乱”,并要求在开始工作之前进行重写,因为他们的思维方式与原作者不同。 不同的团队成员使用不同的语言子集来编写相同代码库的部分内容。 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/e64418f1455d46aaacfdd03fa949f16d.png) 简单、内置的并发性 Go 专为现代多核硬件设计。 目前使用的大多数编程语言(Java、JavaScript、Python、Ruby、C、C++)都是 20 世纪 80 年代到 21 世纪初设计的,当时大多数 CPU 只有一个计算内核。这就是为什么它们本质上是单线程的,并将并行化视为边缘情况的马后炮。通过现成和同步点之类的附加组件来实现,而这些附加组件既麻烦又难以正确使用。第三方库虽然提供了更简单的并发形式,如 Actor 模型,但是总有多个可用选项,结果导致了语言生态系统的碎片化。今天的硬件拥有越来越多的计算内核,软件必须并行化才能高效运行。Go 是在多核处理器时代编写的,并且在语言中内置了简单、高级的 CSP 风格并发性。 面向计算的语言原语 就深层而言,计算机系统接收数据,对其进行处理(通常要经过几个步骤),然后输出结果数据。例如,Web 服务器从客户端接收 HTTP 请求,并将其转换为一系列数据库或后端调用。一旦这些调用返回,它就将接收到的数据转换成 HTML 或 JSON 并将其输出给调用者。Go 的内置语言原语直接支持这种范例: 结构表示数据 读和写代表流式 IO 函数过程数据 goroutines 提供(几乎无限的)并发性 在并行处理步骤之间传输管道数据 因为所有的计算原语都是由语言以直接形式提供的,因此 Go 源代码更直接地表达了服务器执行的操作。 OO — 好的部分 更改基类中的某些内容的副作用 面向对象非常有用。过去几十年来,面向对象的使用富有成效,并让我们了解了它的哪些部分比其他部分能够更好地扩展。Go 在面向对象方面采用了一种全新的方法,并记住了这些知识。它保留了好的部分,如封装、消息传递等。Go 还避免了继承,因为它现在被认为是有害的,并为组合提供了一流的支持。 现代标准库 目前使用的许多编程语言(Java、JavaScript、Python、Ruby)都是在互联网成为当今无处不在的计算平台之前设计的。因此,这些语言的标准库只提供了相对通用的网络支持,而这些网络并没有针对现代互联网进行优化。Go 是十年前创建的,当时互联网已全面发展。Go 的标准库允许在没有第三方库的情况下创建更复杂的网络服务。这就避免了第三方库的常见问题: 碎片化:总是有多个选项实现相同的功能。 膨胀:库常常实现的不仅仅是它们的用途。 依赖地狱:库通常依赖于特定版本的其他库。 未知质量:第三方代码的质量和安全性可能存在问题。 未知支持:第三方库的开发可能随时停止支持。 意外更改:第三方库通常不像标准库那样严格地进行版本控制。 关于这方面更多的信息请参考 Russ Cox 提供的资料 标准化格式 Gofmt 的风格没有人会去喜欢,但人人都会喜欢 gofmt。 ——Rob Pike Gofmt 是一种以标准化方式来格式化 Go 代码的程序。它不是最漂亮的格式化方式,但却是最简单、最不令人生厌的格式化方式。标准化的源代码格式具有惊人的积极影响: 集中讨论重要主题: 它消除了围绕制表符和空格、缩进深度、行长、空行、花括号的位置等一系列争论。 开发人员在彼此的代码库中感觉很自在, 因为其他代码看起来很像他们编写的代码。每个人都喜欢自由地按照自己喜欢的方式进行格式化代码,但如果其他人按照自己喜欢的方式格式化了代码,这么做很招人烦。 自动代码更改并不会打乱手写代码的格式,例如引入了意外的空白更改。 许多其他语言社区现在正在开发类似 gofmt 的东西。当作为第三方解决方案构建时,通常会有几个相互竞争的格式标准。例如,JavaScript 提供了 Prettier 和 StandardJS。这两者都可以用,也可以只使用其中的一个。但许多 JS 项目并没有采用它们,因为这是一个额外的决策。Go 的格式化程序内置于该语言的标准工具链中,因此只有一个标准,每个人都在使用它。 快速编译 ![image.png](https://ucc.alicdn.com/pic/developer-ecology/8a76f3f07f484266af42781d9e7b8692.png) 对于大型代码库来说,它们长时间的编译是促使 Go 诞生的原因。Google 主要使用的是 C++ 和 Java,与 Haskell、Scala 或 Rust 等更复杂的语言相比,它们的编译速度相对较快。尽管如此,当编译大型代码库时,即使是少量的缓慢也会加剧编译的延迟,从而激怒开发人员,并干扰流程。Go 的设计初衷是为了提高编译效率,因此它的编译器速度非常快,几乎没有编译延迟的现象。这给 Go 开发人员提供了与脚本类语言类似的即时反馈,还有静态类型检查的额外好处。 交叉编译 由于语言运行时非常简单,因此它被移植到许多平台,如 macOS、Linux、Windows、BSD、ARM 等。Go 可以开箱即用地为所有这些平台编译二进制文件。这使得从一台机器进行部署变得很容易。 快速执行 Go 的运行速度接近于 C。与 JITed 语言(Java、JavaScript、Python 等)不同,Go 二进制文件不需要启动或预热的时间,因为它们是作为编译和完全优化的本地代码的形式发布的。Go 的垃圾收集器仅引入微秒量级的可忽略的停顿。除了快速的单核性能外,Go 还可以轻松利用所有的 CPU 内核。 内存占用小 像 JVM、Python 或 Node 这样的运行时不仅仅在运行时加载程序代码,每次运行程序时,它们还会加载大型且高度复杂的基础架构,以进行编译和优化程序。如此一来,它们的启动时间就变慢了,并且还占用了大量内存(数百兆字节)。而 Go 进程的开销更小,因为它们已经完全编译和优化,只需运行即可。Go 还以非常节省内存的方式来存储数据。在内存有限且昂贵的云环境中,以及在开发过程中,这一点非常重要。我们希望在一台机器上能够快速启动整个堆栈,同时将内存留给其他软件。 部署规模小 Go 的二进制文件大小非常简洁。Go 应用程序的 Docker 镜像通常比用 Java 或 Node 编写的等效镜像要小 10 倍,这是因为它无需包含编译器、JIT,以及更少的运行时基础架构的原因。这些特点,在部署大型应用程序时很重要。想象一下,如果要将一个简单的应用程序部署到 100 个生产服务器上会怎么样?如果使用 Node/JVM 时,我们的 Docker 注册表就必须提供 100 个 docker 镜像,每个镜像 200MB,那么一共就需要 20GB。要完成这些部署就需要一些时间。想象一下,如果我们想每天部署 100 次的话,如果使用 Go 服务,那么 Docker 注册表只需提供 10 个 docker 镜像,每个镜像只有 20MB,共只需 2GB 即可。大型 Go 应用程序可以更快、更频繁地部署,从而使得重要更新能够更快地部署到生产环境中。 独立部署 Go 应用程序部署为一个包含所有依赖项的单个可执行文件,并无需安装特定版本的 JVM、Node 或 Python 运行时;也不必将库下载到生产服务器上,更无须对运行 Go 二进制文件的机器进行任何更改。甚至也不需要讲 Go 二进制文件包装到 Docker 来共享他们。你需要做的是,只是将 Go 二进制文件放到服务器上,它就会在那里运行,而不用关心服务器运行的是什么。前面所提到的那些,唯一的例外是使用net和os/user包时针对对glibc的动态链接。 供应依赖关系 Go 有意识避免使用第三方库的中央存储库。Go 应用程序直接链接到相应的 Git 存储库,并将所有相关代码下载(供应)到自己的代码库中。这样做有很多好处: 在使用第三方代码之前,我们可以对其进行审查、分析和测试。该代码就和我们自己的代码一样,是我们应用程序的一部分,应该遵循相同的质量、安全性和可靠性标准。 无需永久访问存储依赖项的各个位置。从任何地方(包括私有 Git repos)获取第三方库,你就能永久拥有它们。 经过验收后,编译代码库无需进一步下载依赖项。 若互联网某处的代码存储库突然提供不同的代码,这也并不足为奇。 即使软件包存储库速度变慢,或托管包不复存在,部署也不会因此中断。 兼容性保证 Go 团队承诺现有的程序将会继续适用于新一代语言。这使得将大型项目升级到最新版本的编译器会非常容易,并且可从它们带来的许多性能和安全性改进中获益。同时,由于 Go 二进制文件包含了它们需要的所有依赖项,因此可以在同一服务器上并行运行使用不同版本的 Go 编译器编译的二进制文件,而无需进行复杂的多个版本的运行时设置或虚拟化。 文档 在大型工程中,文档对于使软件可访问性和可维护性非常重要。与其他特性类似,Go 中的文档简单实用: 由于它是嵌入到源代码中的,因此两者可以同时维护。 它不需要特殊的语法,文档只是普通的源代码注释。 可运行单元测试通常是最好的文档形式。因此 Go 要求将它们嵌入到文档中。 所有的文档实用程序都内置在工具链中,因此每个人都使用它们。 Go linter 需要导出元素的文档,以防止“文档债务”的积累。 商业支持的开源 当商业实体在开放式环境下开发时,那么一些最流行的、经过彻底设计的软件就会出现。这种设置结合了商业软件开发的优势——一致性和精细化,使系统更为健壮、可靠、高效,并具有开放式开发的优势,如来自许多行业的广泛支持,多个大型实体和许多用户的支持,以及即使商业支持停止的长期支持。Go 就是这样发展起来的。 缺点 当然,Go 也并非完美无缺,每种技术选择都是有利有弊。在决定选择 Go 之前,有几个方面需要进行考虑考虑。 未成熟 虽然 Go 的标准库在支持许多新概念(如 HTTP 2 Server push 等)方面处于行业领先地位,但与 JVM 生态系统中的第三方库相比,用于外部 API 的第三方 Go 库可能不那么成熟。 即将到来的改进 由于清楚几乎不可能改变现有的语言元素,Go 团队非常谨慎,只在新特性完全开发出来后才添加新特性。在经历了 10 年的有意稳定阶段之后,Go 团队正在谋划对语言进行一系列更大的改进,作为 Go 2.0 之旅的一部分。 无硬实时 虽然 Go 的垃圾收集器只引入了非常短暂的停顿,但支持硬实时需要没有垃圾收集的技术,例如 Rust。 结语 本文详细介绍了 Go 语言的一些优秀的设计准则,虽然有的准则的好处平常看起来没有那么明显。但当代码库和团队规模增长几个数量级时,这些准则可能会使大型工程项目免于许多痛苦。总的来说,正是这些设计准则让 Go 语言成为了除 Java 之外的编程语言里,用于大型软件开发项目的绝佳选择。
有只黑白猫 2020-01-07 14:11:38 0 浏览量 回答数 0

回答

1.字符串转义序列转义字符 描述(在行尾时) 续行符\ 反斜杠符号' 单引号" 双引号a 响铃b 退格(Backspace)e 转义000 空n 换行v 纵向制表符t 横向制表符r 回车f 换页oyy 八进制数yy代表的字符,例如:o12代表换行xyy 十进制数yy代表的字符,例如:x0a代表换行other 其它的字符以普通格式输出 2.字符串格式化 3.操作符 一、算术运算符 注意: 双斜杠 // 除法总是向下取整。 从符点数到整数的转换可能会舍入也可能截断,建议使用math.floor()和math.ceil()明确定义的转换。 Python定义pow(0, 0)和0 ** 0等于1。 二、比较运算符 运算符 描述< 小于<= 小于或等于 大于= 大于或等于== 等于 != 不等于is 判断两个标识符是不是引用自一个对象is not 判断两个标识符是不是引用自不同对象注意: 八个比较运算符优先级相同。 Python允许x < y <= z这样的链式比较,它相当于x < y and y <= z。 复数不能进行大小比较,只能比较是否相等。 三、逻辑运算符 运算符 描述 备注x or y if x is false, then y, elsex x andy if x is false, then x, elsey not x if x is false, then True,elseFalse 注意: or是个短路运算符,它只有在第一个运算数为False时才会计算第二个运算数的值。 and也是个短路运算符,它只有在第一个运算数为True时才会计算第二个运算数的值。 not的优先级比其他类型的运算符低,所以not a == b相当于not (a == b),而 a == not b是错误的。 四、位运算符 运算符 描述 备注x | y 按位或运算符 x ^ y 按位异或运算符 x & y 按位与运算符 x << n 左移动运算符 x >> n 右移动运算符 ~x 按位取反运算符 五、赋值运算符 复合赋值运算符与算术运算符是一一对应的: 六、成员运算符 Python提供了成员运算符,测试一个元素是否在一个序列(Sequence)中。 运算符 描述in 如果在指定的序列中找到值返回True,否则返回False。not in 如果在指定的序列中没有找到值返回True,否则返回False。 4.关键字总结 Python中的关键字包括如下: and del from not while as elif global or with assert else if pass yield break except import print class exec in raise continue finally is return def for lambda try你想看看有哪些关键字?OK,打开一个终端,就像这样~ long@zhouyl:~$ pythonPython 2.7.3 (default, Jan 2 2013, 16:53:07) [GCC 4.7.2] on linux2Type "help", "copyright", "credits" or "license" for more information. import keywordkeyword.kwlist ['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'] ============================== 华丽的 正文分隔符 ======================================== 看到这些关键字你还能记得多少?你不妨自己一个一个对照想想它的用法,下面是我总结的,我根据前面的学习笔记将上述关键字分为以下几类: 1.判断、循环 对于Python的循环及判断主要包括这些关键字: if elif else for while break continue and or is not in 这几个关键字在前面介绍 if 语法、while语法、for语法以及and...or语法中已有介绍,下面再一笔带过: 1.1 if 语法 if语法与C语言、shell脚本之下的非常类似,最大的区别就是冒号以及严格的缩进,当然这两点也是Python区别于其他语言的地方: if condition1: do something elif condition2: do another thing else: also do something 1.2 while 语法 Python的while语法区别于C、shell下的while除了冒号及缩进之外,还有一点就是while可以携带一个可选的else语句: while condition: do something else: do something 注:else语句是可选的,但是使用while语句时一定要注意判断语句可以跳出! 1.3 for 语法 与while类似,Python的for循环也包括一个可选的else语句(跳出for循环时执行,但是如果是从break语句跳出则不执行else语句块中的代码!),而且for 加上 关键字in就组成了最常见的列表解析用法(以后会写个专门的博客)。 下面是for的一般用法: for i in range(1,10,2): do something if condition: break else: do something for的列表解析用法: for items in list: print items 1.4 and...or 语法 Python的and/or操作与其他语言不同的是它的返回值是参与判断的两个值之一,所以我们可以通过这个特性来实现Python下的 a ? b : c ! 有C语言基础的知道 “ a ? b : c ! ” 语法是判断 a,如果正确则执行b,否则执行 c! 而Python下我们可以这么用:“ a and b or c ”(此方法中必须保证b必须是True值),python自左向右执行此句,先判断a and b :如果a是True值,a and b语句仍需要执行b,而此时b是True值!所以a and b的值是b,而此时a and b or c就变成了b or c,因b是True值,所以b or c的结果也是b;如果a是False值,a and b语句的结果就是a,此时 a and b or c就转化为a or c,因为此时a是 False值,所以不管c是True 还是Flase,a or c的结果就是c!!!捋通逻辑的话,a and b or c 是不是就是Python下的a ? b : c ! 用法? 1.5 is ,not is 和 is not 是Python下判断同一性的关键字,通常用来判断 是 True 、False或者None(Python下的NULL)! 比如 if alue is True : ... (不记得本节的童鞋罚复习:python 学习笔记 2 -- 判断语句) 2.函数、模块、类 对于Python的函数及模块主要包括这些关键字: from import as def pass lambda return class 那么你还能记得它们么?下面简单介绍一下: 2.1 模块 Python的编程通常大量使用标准库中的模块,使用方法就是使用import 、from以及as 关键字。 比如: import sys # 导入sys模块 from sys import argv # 从sys模块中导入argv ,这个在前面介绍脚本传参数时使用到 import cPickle as p # 将cPickle模块导入并在此将它简单命名为p,此后直接可以使用p替代cPickle模块原名,这个在介绍文件输入输出时的存储器中使用到 2.2 函数 Python中定义函数时使用到def关键字,如果你当前不想写入真实的函数操作,可以使用pass关键字指代不做任何操作: def JustAFunction: pass 当然,在需要给函数返回值时就用到了return关键字,这里简单提一下Python下的函数返回值可以是多个(接收返回值时用相应数量的变量接收!)! 此外Python下有个神奇的Lambda函数,它允许你定义单行的最小函数,这是从Lisp中借用来的,可以用在任何需要函数的地方。比如: g = lambda x : x*2 # 定义一个Lambda函数用来计算参数的2倍并返回! print g(2) # 使用时使用lambda函数返回的变量作为这个函数的函数名,括号中带入相应参数即可! (不记得本节的童鞋罚复习:python 学习笔记 4 -- 函数篇) 3.异常 对于Python的异常主要包括这些关键字: try except finally raise 异常这一节还是比较简单的,将可能出现的异常放在 try: 后面的语句块中,使用except关键字捕获一定的异常并在接下来的语句块中做相应操作,而finally中接的是无论出现什么异常总在执行最后做finally: 后面的语句块(比如关闭文件等必要的操作!) raise关键字是在一定的情况下引发异常,通常结合自定义的异常类型使用。 (不记得本节的童鞋罚复习:python 学习笔记 6 -- 异常处理) 4.其他 上面的三类过后,还剩下这些关键字: print del global with assert yield exec 首先print 在前面的笔记或者任何地方你都能见到,所以还是比较熟悉的,此处就不多介绍了!del 关键字在前面的笔记中已有所涉及,比如删除列表中的某项,我们使用 “ del mylist[0] ” 可能这些剩下来的关键字你比较陌生,所以下面来介绍一下: 4.1.global 关键字 当你在函数定义内声明变量的时候,它们与函数外具有相同名称的其他变量没有任何关系,即变量名称对于函数来说是 局部 的。这称为变量的 作用域 。所有变量的作用域是它们被定义的块,从它们的名称被定义的那点开始。 eg. ? 1 2 3 4 5 6 7 8 9 10 11 !/usr/bin/python Filename: func_local.py def func(x): print'x is', x x = 2 print'Changed local x to', x x = 50 func(x) print'x is still', x 运行的结果是这样的:? 1 2 3 4 $ python func_local.py x is 50 # 运行func函数时,先打印x的值,此时带的值是作为参数带入的外部定义的50,所以能正常打印 x=50 Changed local x to 2 # 在func函数中将x赋2,并打印 x is still 50 # 运行完func函数,打印x的值,此时x的值仍然是之前赋给的50,而不是func函数中修改过的2,因为在函数中修改的只是函数内的局部变量 那么为什么我们要在这提到局部变量呢?bingo,聪明的你一下就猜到这个global就是用来定义全局变量的。也就是说如果你想要为一个在函数外定义的变量赋值,那么你就得告诉Python这个变量名不是局部的,而是 全局 的。我们使用global语句完成这一功能。没有global语句,是不可能为定义在函数外的变量赋值的。eg.? 1 2 3 4 5 6 7 8 9 10 11 12 !/usr/bin/python Filename: func_global.py def func(): global x print'x is', x x = 2 print'Changed local x to', x x = 50 func() print'Value of x is', x 运行的结果是这样的:? 1 2 3 4 $ python func_global.py x is 50 Changed global x to 2 Value of x is 2 # global语句被用来声明x是全局的——因此,当我们在函数内把值赋给x的时候,这个变化也反映在我们在主块中使用x的值的时候。 你可以使用同一个global语句指定多个全局变量。例如global x, y, z。 4.2.with 关键字 有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。如果不用with语句,打开一个文件并读文件的代码如下:? 1 2 3 file = open("/tmp/foo.txt") data = file.read() file.close() 当然这样直接打开有两个问题:一是可能忘记关闭文件句柄;二是文件读取数据发生异常,没有进行任何处理。下面是添加上异常处理的版本:? 1 2 3 4 5 file = open("/tmp/foo.txt") try: data = file.read() finally: file.close() 虽然这段代码运行良好,但是太冗余了。这时候就是with一展身手的时候了。除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。下面是with版本的代码:? 1 2 with open("/tmp/foo.txt") as file: data = file.read() 这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个__enter__()方法,一个__exit__()方法。with语句的执行逻辑如下:紧跟with后面的语句被求值后,返回对象的__enter__()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的__exit__()方法。 下面例子可以具体说明with如何工作:? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 !/usr/bin/python with_example01.py classSample: def __enter__(self): print"In __enter__()" return"Foo" def __exit__(self, type, value, trace): print"In __exit__()" def get_sample(): returnSample() with get_sample() as sample: print"sample:", sample 运行代码,输出如下? 1 2 3 4 $python with_example01.py In __enter__() # __enter__()方法被执行 sample: Foo # __enter__()方法返回的值 - 这个例子中是"Foo",赋值给变量'sample',执行代码块,打印变量"sample"的值为"Foo" In __exit__() # __exit__()方法被调用 4.3.assert 关键字 assert语句是一种插入调试断点到程序的一种便捷的方式。assert语句用来声明某个条件是真的,当assert语句失败的时候,会引发一AssertionError,所以结合try...except我们就可以处理这样的异常。 mylist # 此时mylist是有三个元素的列表['a', 'b', 'c']assert len(mylist) is not None # 用assert判断列表不为空,正确无返回assert len(mylist) is None # 用assert判断列表为空 Traceback (most recent call last): File "", line 1, in AssertionError # 引发AssertionError异常 4.4.yield 关键字 我们先看一个示例:? 1 2 3 4 5 6 7 8 def fab(max): n, a, b = 0,0,1 whilen < max: yield b # print b a, b = b, a + b n = n + 1 ''' 使用这个函数:? 1 2 3 4 5 6 7 8 forn in fab(5): ... print n ... 1 1 2 3 5 简单地讲,yield 的作用就是把一个函数变成一个 generator(生成器),带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(5) 不会执行 fab 函数,而是返回一个 iterable(可迭代的)对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。也可以手动调用 fab(5) 的 next() 方法(因为 fab(5) 是一个 generator 对象,该对象具有 next() 方法),这样我们就可以更清楚地看到 fab 的执行流程:? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 f = fab(5) f.next() 1 f.next() 1 f.next() 2 f.next() 3 f.next() 5 f.next() Traceback (most recent call last): File"", line 1, in StopIteration 当函数执行结束时,generator 自动抛出 StopIteration 异常,表示迭代完成。在 for 循环里,无需处理 StopIteration 异常,循环会正常结束。 我们可以得出以下结论:一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。 yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。 注:如果看完此段你还未明白yield,没问题,因为yield是初学者的一个难点,那么你下一步需要做的就是……看一看下面参考资料中给的关于yield的博文! 4.5.exec 关键字 官方文档对于exec的解释: "This statement supports dynamic execution of Python code."也就是说使用exec可以动态执行Python代码(也可以是文件)。? 1 2 3 4 5 6 7 8 9 10 11 12 13 longer = "print "Hello World ,my name is longer"" # 比如说我们定义了一个字符串 longer 'print "Hello World ,my name is longer"' exec(longer) # 使用exec 动态执行字符串中的代码 Hello World ,my name is longer exec(sayhi) # 使用exec直接打开文件名(指定sayhi,sayhi.py以及"sayhi.py"都会报一定的错,但是我觉得直接带sayhi报错非常典型) Traceback (most recent call last): File"", line 1, in TypeError: exec: arg 1must be a string, file, or code object # python IDE报错,提示exec的第一个参 数必须是一个字符串、文件或者一个代码对象 f = file("sayhi.py") # 使用file打开sayhi.py并创建f实例 exec(f) # 使用exec直接运行文件描述符f,运行正常!! Hi,thisis [''] script 上述给的例子比较简单,注意例子中exec语句的用法和eval_r(), execfile()是不一样的. exec是一个关键字(要不然我怎么会在这里介绍呢~~~), 而eval_r()和execfile()则是内建函数。更多关于exec的使用请详看引用资料或者Google之 在需要在字符中使用特殊字符时,python用反斜杠()转义字符。 原始字符串 有时我们并不想让转义字符生效,我们只想显示字符串原来的意思,这就要用r和R来定义原始字符串。如: print r’tr’ 实际输出为“tr”。 转义字符 描述 (在行尾时) 续行符 反斜杠符号 ’ 单引号 ” 双引号 a 响铃 b 退格(Backspace) e 转义 000 空 n 换行 v 纵向制表符 t 横向制表符 r 回车 f 换页 oyy 八进制数yy代表的字符,例如:o12代表换行 xyy 十进制数yy代表的字符,例如:x0a代表换行 other 其它的字符以普通格式输出
xuning715 2019-12-02 01:10:21 0 浏览量 回答数 0

回答

我们先从整体上看一下Kubernetes的一些理念和基本架构,然后从网络、资源管理、存储、服务发现、负载均衡、高可用、rollingupgrade、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性。  当然也会包括一些需要注意的问题。主要目的是帮助大家快速理解Kubernetes的主要功能,今后在研究和使用这个具的时候有所参考和帮助。  1.Kubernetes的一些理念:  用户不需要关心需要多少台机器,只需要关心软件(服务)运行所需的环境。以服务为中心,你需要关心的是api,如何把大服务拆分成小服务,如何使用api去整合它们。  保证系统总是按照用户指定的状态去运行。  不仅仅提给你供容器服务,同样提供一种软件系统升级的方式;在保持HA的前提下去升级系统是很多用户最想要的功能,也是最难实现的。  那些需要担心和不需要担心的事情。  更好的支持微服务理念,划分、细分服务之间的边界,比如lablel、pod等概念的引入。  对于Kubernetes的架构,可以参考官方文档。  大致由一些主要组件构成,包括Master节点上的kube-apiserver、kube-scheduler、kube-controller-manager、控制组件kubectl、状态存储etcd、Slave节点上的kubelet、kube-proxy,以及底层的网络支持(可以用Flannel、OpenVSwitch、Weave等)。  看上去也是微服务的架构设计,不过目前还不能很好支持单个服务的横向伸缩,但这个会在Kubernetes的未来版本中解决。  2.Kubernetes的主要特性  会从网络、服务发现、负载均衡、资源管理、高可用、存储、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性->由于时间有限,只能简单一些了。  另外,对于服务发现、高可用和监控的一些更详细的介绍,感兴趣的朋友可以通过这篇文章了解。  1)网络  Kubernetes的网络方式主要解决以下几个问题:  a.紧耦合的容器之间通信,通过Pod和localhost访问解决。  b.Pod之间通信,建立通信子网,比如隧道、路由,Flannel、OpenvSwitch、Weave。  c.Pod和Service,以及外部系统和Service的通信,引入Service解决。  Kubernetes的网络会给每个Pod分配一个IP地址,不需要在Pod之间建立链接,也基本不需要去处理容器和主机之间的端口映射。  注意:Pod重建后,IP会被重新分配,所以内网通信不要依赖PodIP;通过Service环境变量或者DNS解决。  2)服务发现及负载均衡  kube-proxy和DNS,在v1之前,Service含有字段portalip和publicIPs,分别指定了服务的虚拟ip和服务的出口机ip,publicIPs可任意指定成集群中任意包含kube-proxy的节点,可多个。portalIp通过NAT的方式跳转到container的内网地址。在v1版本中,publicIPS被约定废除,标记为deprecatedPublicIPs,仅用作向后兼容,portalIp也改为ClusterIp,而在serviceport定义列表里,增加了nodePort项,即对应node上映射的服务端口。  DNS服务以addon的方式,需要安装skydns和kube2dns。kube2dns会通过读取KubernetesAPI获取服务的clusterIP和port信息,同时以watch的方式检查service的变动,及时收集变动信息,并将对于的ip信息提交给etcd存档,而skydns通过etcd内的DNS记录信息,开启53端口对外提供服务。大概的DNS的域名记录是servicename.namespace.tenx.domain,“tenx.domain”是提前设置的主域名。  注意:kube-proxy在集群规模较大以后,可能会有访问的性能问题,可以考虑用其他方式替换,比如HAProxy,直接导流到Service的endpints或者Pods上。Kubernetes官方也在修复这个问题。  3)资源管理  有3个层次的资源限制方式,分别在Container、Pod、Namespace层次。Container层次主要利用容器本身的支持,比如Docker对CPU、内存、磁盘、网络等的支持;Pod方面可以限制系统内创建Pod的资源范围,比如最大或者最小的CPU、memory需求;Namespace层次就是对用户级别的资源限额了,包括CPU、内存,还可以限定Pod、rc、service的数量。  资源管理模型-》简单、通用、准确,并可扩展  目前的资源分配计算也相对简单,没有什么资源抢占之类的强大功能,通过每个节点上的资源总量、以及已经使用的各种资源加权和,来计算某个Pod优先非配到哪些节点,还没有加入对节点实际可用资源的评估,需要自己的schedulerplugin来支持。其实kubelet已经可以拿到节点的资源,只要进行收集计算即可,相信Kubernetes的后续版本会有支持。  4)高可用  主要是指Master节点的HA方式官方推荐利用etcd实现master选举,从多个Master中得到一个kube-apiserver保证至少有一个master可用,实现highavailability。对外以loadbalancer的方式提供入口。这种方式可以用作ha,但仍未成熟,据了解,未来会更新升级ha的功能。  一张图帮助大家理解:  也就是在etcd集群背景下,存在多个kube-apiserver,并用pod-master保证仅是主master可用。同时kube-sheduller和kube-controller-manager也存在多个,而且伴随着kube-apiserver同一时间只能有一套运行。  5)rollingupgrade  RC在开始的设计就是让rollingupgrade变的更容易,通过一个一个替换Pod来更新service,实现服务中断时间的最小化。基本思路是创建一个复本为1的新的rc,并逐步减少老的rc的复本、增加新的rc的复本,在老的rc数量为0时将其删除。  通过kubectl提供,可以指定更新的镜像、替换pod的时间间隔,也可以rollback当前正在执行的upgrade操作。  同样,Kuberntes也支持多版本同时部署,并通过lable来进行区分,在service不变的情况下,调整支撑服务的Pod,测试、监控新Pod的工作情况。  6)存储  大家都知道容器本身一般不会对数据进行持久化处理,在Kubernetes中,容器异常退出,kubelet也只是简单的基于原有镜像重启一个新的容器。另外,如果我们在同一个Pod中运行多个容器,经常会需要在这些容器之间进行共享一些数据。Kuberenetes的Volume就是主要来解决上面两个基础问题的。  Docker也有Volume的概念,但是相对简单,而且目前的支持很有限,Kubernetes对Volume则有着清晰定义和广泛的支持。其中最核心的理念:Volume只是一个目录,并可以被在同一个Pod中的所有容器访问。而这个目录会是什么样,后端用什么介质和里面的内容则由使用的特定Volume类型决定。  创建一个带Volume的Pod:  spec.volumes指定这个Pod需要的volume信息spec.containers.volumeMounts指定哪些container需要用到这个VolumeKubernetes对Volume的支持非常广泛,有很多贡献者为其添加不同的存储支持,也反映出Kubernetes社区的活跃程度。  emptyDir随Pod删除,适用于临时存储、灾难恢复、共享运行时数据,支持RAM-backedfilesystemhostPath类似于Docker的本地Volume用于访问一些本地资源(比如本地Docker)。  gcePersistentDiskGCEdisk-只有在GoogleCloudEngine平台上可用。  awsElasticBlockStore类似于GCEdisk节点必须是AWSEC2的实例nfs-支持网络文件系统。  rbd-RadosBlockDevice-Ceph  secret用来通过KubernetesAPI向Pod传递敏感信息,使用tmpfs(aRAM-backedfilesystem)  persistentVolumeClaim-从抽象的PV中申请资源,而无需关心存储的提供方  glusterfs  iscsi  gitRepo  根据自己的需求选择合适的存储类型,反正支持的够多,总用一款适合的:)  7)安全  一些主要原则:  基础设施模块应该通过APIserver交换数据、修改系统状态,而且只有APIserver可以访问后端存储(etcd)。  把用户分为不同的角色:Developers/ProjectAdmins/Administrators。  允许Developers定义secrets对象,并在pod启动时关联到相关容器。  以secret为例,如果kubelet要去pull私有镜像,那么Kubernetes支持以下方式:  通过dockerlogin生成.dockercfg文件,进行全局授权。  通过在每个namespace上创建用户的secret对象,在创建Pod时指定imagePullSecrets属性(也可以统一设置在serviceAcouunt上),进行授权。  认证(Authentication)  APIserver支持证书、token、和基本信息三种认证方式。  授权(Authorization)  通过apiserver的安全端口,authorization会应用到所有http的请求上  AlwaysDeny、AlwaysAllow、ABAC三种模式,其他需求可以自己实现Authorizer接口。  8)监控  比较老的版本Kubernetes需要外接cadvisor主要功能是将node主机的containermetrics抓取出来。在较新的版本里,cadvior功能被集成到了kubelet组件中,kubelet在与docker交互的同时,对外提供监控服务。  Kubernetes集群范围内的监控主要由kubelet、heapster和storagebackend(如influxdb)构建。Heapster可以在集群范围获取metrics和事件数据。它可以以pod的方式运行在k8s平台里,也可以单独运行以standalone的方式。  注意:heapster目前未到1.0版本,对于小规模的集群监控比较方便。但对于较大规模的集群,heapster目前的cache方式会吃掉大量内存。因为要定时获取整个集群的容器信息,信息在内存的临时存储成为问题,再加上heaspter要支持api获取临时metrics,如果将heapster以pod方式运行,很容易出现OOM。所以目前建议关掉cache并以standalone的方式独立出k8s平台。 答案来源网络,供您参考
问问小秘 2019-12-02 02:13:31 0 浏览量 回答数 0

回答

我们先从整体上看一下Kubernetes的一些理念和基本架构,然后从网络、资源管理、存储、服务发现、负载均衡、高可用、rollingupgrade、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性。  当然也会包括一些需要注意的问题。主要目的是帮助大家快速理解Kubernetes的主要功能,今后在研究和使用这个具的时候有所参考和帮助。  1.Kubernetes的一些理念:  用户不需要关心需要多少台机器,只需要关心软件(服务)运行所需的环境。以服务为中心,你需要关心的是api,如何把大服务拆分成小服务,如何使用api去整合它们。  保证系统总是按照用户指定的状态去运行。  不仅仅提给你供容器服务,同样提供一种软件系统升级的方式;在保持HA的前提下去升级系统是很多用户最想要的功能,也是最难实现的。  那些需要担心和不需要担心的事情。  更好的支持微服务理念,划分、细分服务之间的边界,比如lablel、pod等概念的引入。  对于Kubernetes的架构,可以参考官方文档。  大致由一些主要组件构成,包括Master节点上的kube-apiserver、kube-scheduler、kube-controller-manager、控制组件kubectl、状态存储etcd、Slave节点上的kubelet、kube-proxy,以及底层的网络支持(可以用Flannel、OpenVSwitch、Weave等)。  看上去也是微服务的架构设计,不过目前还不能很好支持单个服务的横向伸缩,但这个会在Kubernetes的未来版本中解决。  2.Kubernetes的主要特性  会从网络、服务发现、负载均衡、资源管理、高可用、存储、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性->由于时间有限,只能简单一些了。  另外,对于服务发现、高可用和监控的一些更详细的介绍,感兴趣的朋友可以通过这篇文章了解。  1)网络  Kubernetes的网络方式主要解决以下几个问题:  a.紧耦合的容器之间通信,通过Pod和localhost访问解决。  b.Pod之间通信,建立通信子网,比如隧道、路由,Flannel、OpenvSwitch、Weave。  c.Pod和Service,以及外部系统和Service的通信,引入Service解决。  Kubernetes的网络会给每个Pod分配一个IP地址,不需要在Pod之间建立链接,也基本不需要去处理容器和主机之间的端口映射。  注意:Pod重建后,IP会被重新分配,所以内网通信不要依赖PodIP;通过Service环境变量或者DNS解决。  2)服务发现及负载均衡  kube-proxy和DNS,在v1之前,Service含有字段portalip和publicIPs,分别指定了服务的虚拟ip和服务的出口机ip,publicIPs可任意指定成集群中任意包含kube-proxy的节点,可多个。portalIp通过NAT的方式跳转到container的内网地址。在v1版本中,publicIPS被约定废除,标记为deprecatedPublicIPs,仅用作向后兼容,portalIp也改为ClusterIp,而在serviceport定义列表里,增加了nodePort项,即对应node上映射的服务端口。  DNS服务以addon的方式,需要安装skydns和kube2dns。kube2dns会通过读取KubernetesAPI获取服务的clusterIP和port信息,同时以watch的方式检查service的变动,及时收集变动信息,并将对于的ip信息提交给etcd存档,而skydns通过etcd内的DNS记录信息,开启53端口对外提供服务。大概的DNS的域名记录是servicename.namespace.tenx.domain,“tenx.domain”是提前设置的主域名。  注意:kube-proxy在集群规模较大以后,可能会有访问的性能问题,可以考虑用其他方式替换,比如HAProxy,直接导流到Service的endpints或者Pods上。Kubernetes官方也在修复这个问题。  3)资源管理  有3个层次的资源限制方式,分别在Container、Pod、Namespace层次。Container层次主要利用容器本身的支持,比如Docker对CPU、内存、磁盘、网络等的支持;Pod方面可以限制系统内创建Pod的资源范围,比如最大或者最小的CPU、memory需求;Namespace层次就是对用户级别的资源限额了,包括CPU、内存,还可以限定Pod、rc、service的数量。  资源管理模型-》简单、通用、准确,并可扩展  目前的资源分配计算也相对简单,没有什么资源抢占之类的强大功能,通过每个节点上的资源总量、以及已经使用的各种资源加权和,来计算某个Pod优先非配到哪些节点,还没有加入对节点实际可用资源的评估,需要自己的schedulerplugin来支持。其实kubelet已经可以拿到节点的资源,只要进行收集计算即可,相信Kubernetes的后续版本会有支持。  4)高可用  主要是指Master节点的HA方式官方推荐利用etcd实现master选举,从多个Master中得到一个kube-apiserver保证至少有一个master可用,实现highavailability。对外以loadbalancer的方式提供入口。这种方式可以用作ha,但仍未成熟,据了解,未来会更新升级ha的功能。  一张图帮助大家理解:  也就是在etcd集群背景下,存在多个kube-apiserver,并用pod-master保证仅是主master可用。同时kube-sheduller和kube-controller-manager也存在多个,而且伴随着kube-apiserver同一时间只能有一套运行。  5)rollingupgrade  RC在开始的设计就是让rollingupgrade变的更容易,通过一个一个替换Pod来更新service,实现服务中断时间的最小化。基本思路是创建一个复本为1的新的rc,并逐步减少老的rc的复本、增加新的rc的复本,在老的rc数量为0时将其删除。  通过kubectl提供,可以指定更新的镜像、替换pod的时间间隔,也可以rollback当前正在执行的upgrade操作。  同样,Kuberntes也支持多版本同时部署,并通过lable来进行区分,在service不变的情况下,调整支撑服务的Pod,测试、监控新Pod的工作情况。  6)存储  大家都知道容器本身一般不会对数据进行持久化处理,在Kubernetes中,容器异常退出,kubelet也只是简单的基于原有镜像重启一个新的容器。另外,如果我们在同一个Pod中运行多个容器,经常会需要在这些容器之间进行共享一些数据。Kuberenetes的Volume就是主要来解决上面两个基础问题的。  Docker也有Volume的概念,但是相对简单,而且目前的支持很有限,Kubernetes对Volume则有着清晰定义和广泛的支持。其中最核心的理念:Volume只是一个目录,并可以被在同一个Pod中的所有容器访问。而这个目录会是什么样,后端用什么介质和里面的内容则由使用的特定Volume类型决定。  创建一个带Volume的Pod:  spec.volumes指定这个Pod需要的volume信息spec.containers.volumeMounts指定哪些container需要用到这个VolumeKubernetes对Volume的支持非常广泛,有很多贡献者为其添加不同的存储支持,也反映出Kubernetes社区的活跃程度。  emptyDir随Pod删除,适用于临时存储、灾难恢复、共享运行时数据,支持RAM-backedfilesystemhostPath类似于Docker的本地Volume用于访问一些本地资源(比如本地Docker)。  gcePersistentDiskGCEdisk-只有在GoogleCloudEngine平台上可用。  awsElasticBlockStore类似于GCEdisk节点必须是AWSEC2的实例nfs-支持网络文件系统。  rbd-RadosBlockDevice-Ceph  secret用来通过KubernetesAPI向Pod传递敏感信息,使用tmpfs(aRAM-backedfilesystem)  persistentVolumeClaim-从抽象的PV中申请资源,而无需关心存储的提供方  glusterfs  iscsi  gitRepo  根据自己的需求选择合适的存储类型,反正支持的够多,总用一款适合的:)  7)安全  一些主要原则:  基础设施模块应该通过APIserver交换数据、修改系统状态,而且只有APIserver可以访问后端存储(etcd)。  把用户分为不同的角色:Developers/ProjectAdmins/Administrators。  允许Developers定义secrets对象,并在pod启动时关联到相关容器。  以secret为例,如果kubelet要去pull私有镜像,那么Kubernetes支持以下方式:  通过dockerlogin生成.dockercfg文件,进行全局授权。  通过在每个namespace上创建用户的secret对象,在创建Pod时指定imagePullSecrets属性(也可以统一设置在serviceAcouunt上),进行授权。  认证(Authentication)  APIserver支持证书、token、和基本信息三种认证方式。  授权(Authorization)  通过apiserver的安全端口,authorization会应用到所有http的请求上  AlwaysDeny、AlwaysAllow、ABAC三种模式,其他需求可以自己实现Authorizer接口。  8)监控  比较老的版本Kubernetes需要外接cadvisor主要功能是将node主机的containermetrics抓取出来。在较新的版本里,cadvior功能被集成到了kubelet组件中,kubelet在与docker交互的同时,对外提供监控服务。  Kubernetes集群范围内的监控主要由kubelet、heapster和storagebackend(如influxdb)构建。Heapster可以在集群范围获取metrics和事件数据。它可以以pod的方式运行在k8s平台里,也可以单独运行以standalone的方式。  注意:heapster目前未到1.0版本,对于小规模的集群监控比较方便。但对于较大规模的集群,heapster目前的cache方式会吃掉大量内存。因为要定时获取整个集群的容器信息,信息在内存的临时存储成为问题,再加上heaspter要支持api获取临时metrics,如果将heapster以pod方式运行,很容易出现OOM。所以目前建议关掉cache并以standalone的方式独立出k8s平台。 “答案来源于网络,供您参考” 希望以上信息可以帮到您!
牧明 2019-12-02 02:16:53 0 浏览量 回答数 0

回答

我们先从整体上看一下Kubernetes的一些理念和基本架构, 然后从网络、 资源管理、存储、服务发现、负载均衡、高可用、rolling upgrade、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性。 当然也会包括一些需要注意的问题。主要目的是帮助大家快速理解 Kubernetes的主要功能,今后在研究和使用这个具的时候有所参考和帮助。 1.Kubernetes的一些理念: 用户不需要关心需要多少台机器,只需要关心软件(服务)运行所需的环境。以服务为中心,你需要关心的是api,如何把大服务拆分成小服务,如何使用api去整合它们。 保证系统总是按照用户指定的状态去运行。 不仅仅提给你供容器服务,同样提供一种软件系统升级的方式;在保持HA的前提下去升级系统是很多用户最想要的功能,也是最难实现的。 那些需要担心和不需要担心的事情。 更好的支持微服务理念,划分、细分服务之间的边界,比如lablel、pod等概念的引入。 对于Kubernetes的架构,可以参考官方文档。 大致由一些主要组件构成,包括Master节点上的kube-apiserver、kube-scheduler、kube-controller-manager、控制组件kubectl、状态存储etcd、Slave节点上的kubelet、kube-proxy,以及底层的网络支持(可以用Flannel、OpenVSwitch、Weave等)。 看上去也是微服务的架构设计,不过目前还不能很好支持单个服务的横向伸缩,但这个会在 Kubernetes 的未来版本中解决。 2.Kubernetes的主要特性 会从网络、服务发现、负载均衡、资源管理、高可用、存储、安全、监控等方面向大家简单介绍Kubernetes的这些主要特性 -> 由于时间有限,只能简单一些了。 另外,对于服务发现、高可用和监控的一些更详细的介绍,感兴趣的朋友可以通过这篇文章了解。 1)网络 Kubernetes的网络方式主要解决以下几个问题: a. 紧耦合的容器之间通信,通过 Pod 和 localhost 访问解决。 b. Pod之间通信,建立通信子网,比如隧道、路由,Flannel、Open vSwitch、Weave。 c. Pod和Service,以及外部系统和Service的通信,引入Service解决。 Kubernetes的网络会给每个Pod分配一个IP地址,不需要在Pod之间建立链接,也基本不需要去处理容器和主机之间的端口映射。 注意:Pod重建后,IP会被重新分配,所以内网通信不要依赖Pod IP;通过Service环境变量或者DNS解决。 2) 服务发现及负载均衡 kube-proxy和DNS, 在v1之前,Service含有字段portalip 和publicIPs, 分别指定了服务的虚拟ip和服务的出口机ip,publicIPs可任意指定成集群中任意包含kube-proxy的节点,可多个。portalIp 通过NAT的方式跳转到container的内网地址。在v1版本中,publicIPS被约定废除,标记为deprecatedPublicIPs,仅用作向后兼容,portalIp也改为ClusterIp, 而在service port 定义列表里,增加了nodePort项,即对应node上映射的服务端口。 DNS服务以addon的方式,需要安装skydns和kube2dns。kube2dns会通过读取Kubernetes API获取服务的clusterIP和port信息,同时以watch的方式检查service的变动,及时收集变动信息,并将对于的ip信息提交给etcd存档,而skydns通过etcd内的DNS记录信息,开启53端口对外提供服务。大概的DNS的域名记录是servicename.namespace.tenx.domain, "tenx.domain"是提前设置的主域名。 注意:kube-proxy 在集群规模较大以后,可能会有访问的性能问题,可以考虑用其他方式替换,比如HAProxy,直接导流到Service 的endpints 或者 Pods上。Kubernetes官方也在修复这个问题。 3)资源管理 有3 个层次的资源限制方式,分别在Container、Pod、Namespace 层次。Container层次主要利用容器本身的支持,比如Docker 对CPU、内存、磁盘、网络等的支持;Pod方面可以限制系统内创建Pod的资源范围,比如最大或者最小的CPU、memory需求;Namespace层次就是对用户级别的资源限额了,包括CPU、内存,还可以限定Pod、rc、service的数量。 资源管理模型 -》 简单、通用、准确,并可扩展 目前的资源分配计算也相对简单,没有什么资源抢占之类的强大功能,通过每个节点上的资源总量、以及已经使用的各种资源加权和,来计算某个Pod优先非配到哪些节点,还没有加入对节点实际可用资源的评估,需要自己的scheduler plugin来支持。其实kubelet已经可以拿到节点的资源,只要进行收集计算即可,相信Kubernetes的后续版本会有支持。 4)高可用 主要是指Master节点的 HA方式 官方推荐 利用etcd实现master 选举,从多个Master中得到一个kube-apiserver 保证至少有一个master可用,实现high availability。对外以loadbalancer的方式提供入口。这种方式可以用作ha,但仍未成熟,据了解,未来会更新升级ha的功能。 一张图帮助大家理解: 也就是在etcd集群背景下,存在多个kube-apiserver,并用pod-master保证仅是主master可用。同时kube-sheduller和kube-controller-manager也存在多个,而且伴随着kube-apiserver 同一时间只能有一套运行。 5) rolling upgrade RC 在开始的设计就是让rolling upgrade变的更容易,通过一个一个替换Pod来更新service,实现服务中断时间的最小化。基本思路是创建一个复本为1的新的rc,并逐步减少老的rc的复本、增加新的rc的复本,在老的rc数量为0时将其删除。 通过kubectl提供,可以指定更新的镜像、替换pod的时间间隔,也可以rollback 当前正在执行的upgrade操作。 同样, Kuberntes也支持多版本同时部署,并通过lable来进行区分,在service不变的情况下,调整支撑服务的Pod,测试、监控新Pod的工作情况。 6)存储 大家都知道容器本身一般不会对数据进行持久化处理,在Kubernetes中,容器异常退出,kubelet也只是简单的基于原有镜像重启一个新的容器。另外,如果我们在同一个Pod中运行多个容器,经常会需要在这些容器之间进行共享一些数据。Kuberenetes 的 Volume就是主要来解决上面两个基础问题的。 Docker 也有Volume的概念,但是相对简单,而且目前的支持很有限,Kubernetes对Volume则有着清晰定义和广泛的支持。其中最核心的理念:Volume只是一个目录,并可以被在同一个Pod中的所有容器访问。而这个目录会是什么样,后端用什么介质和里面的内容则由使用的特定Volume类型决定。 创建一个带Volume的Pod: spec.volumes 指定这个Pod需要的volume信息 spec.containers.volumeMounts 指定哪些container需要用到这个Volume Kubernetes对Volume的支持非常广泛,有很多贡献者为其添加不同的存储支持,也反映出Kubernetes社区的活跃程度。 emptyDir 随Pod删除,适用于临时存储、灾难恢复、共享运行时数据,支持 RAM-backed filesystemhostPath 类似于Docker的本地Volume 用于访问一些本地资源(比如本地Docker)。 gcePersistentDisk GCE disk - 只有在 Google Cloud Engine 平台上可用。 awsElasticBlockStore 类似于GCE disk 节点必须是 AWS EC2的实例 nfs - 支持网络文件系统。 rbd - Rados Block Device - Ceph secret 用来通过Kubernetes API 向Pod 传递敏感信息,使用 tmpfs (a RAM-backed filesystem) persistentVolumeClaim - 从抽象的PV中申请资源,而无需关心存储的提供方 glusterfs iscsi gitRepo 根据自己的需求选择合适的存储类型,反正支持的够多,总用一款适合的 :) 7)安全 一些主要原则: 基础设施模块应该通过API server交换数据、修改系统状态,而且只有API server可以访问后端存储(etcd)。 把用户分为不同的角色:Developers/Project Admins/Administrators。 允许Developers定义secrets 对象,并在pod启动时关联到相关容器。 以secret 为例,如果kubelet要去pull 私有镜像,那么Kubernetes支持以下方式: 通过docker login 生成 .dockercfg 文件,进行全局授权。 通过在每个namespace上创建用户的secret对象,在创建Pod时指定 imagePullSecrets 属性(也可以统一设置在serviceAcouunt 上),进行授权。 认证 (Authentication) API server 支持证书、token、和基本信息三种认证方式。 授权 (Authorization) 通过apiserver的安全端口,authorization会应用到所有http的请求上 AlwaysDeny、AlwaysAllow、ABAC三种模式,其他需求可以自己实现Authorizer接口。 8)监控 比较老的版本Kubernetes需要外接cadvisor主要功能是将node主机的container metrics抓取出来。在较新的版本里,cadvior功能被集成到了kubelet组件中,kubelet在与docker交互的同时,对外提供监控服务。 Kubernetes集群范围内的监控主要由kubelet、heapster和storage backend(如influxdb)构建。Heapster可以在集群范围获取metrics和事件数据。它可以以pod的方式运行在k8s平台里,也可以单独运行以standalone的方式。 注意: heapster目前未到1.0版本,对于小规模的集群监控比较方便。但对于较大规模的集群,heapster目前的cache方式会吃掉大量内存。因为要定时获取整个集群的容器信息,信息在内存的临时存储成为问题,再加上heaspter要支持api获取临时metrics,如果将heapster以pod方式运行,很容易出现OOM。所以目前建议关掉cache并以standalone的方式独立出k8s平台。 此答案来源于网络,希望对你有所帮助。
养狐狸的猫 2019-12-02 02:13:33 0 浏览量 回答数 0

回答

重试作用: 对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。 远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。 比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性。 优雅的重试机制要具备几点: 无侵入:这个好理解,不改动当前的业务逻辑,对于需要重试的地方,可以很简单的实现 可配置:包括重试次数,重试的间隔时间,是否使用异步方式等 通用性:最好是无改动(或者很小改动)的支持绝大部分的场景,拿过来直接可用 优雅重试共性和原理: 正常和重试优雅解耦,重试断言条件实例或逻辑异常实例是两者沟通的媒介。 约定重试间隔,差异性重试策略,设置重试超时时间,进一步保证重试有效性以及重试流程稳定性。 都使用了命令设计模式,通过委托重试对象完成相应的逻辑操作,同时内部封装实现重试逻辑。 Spring-tryer和guava-tryer工具都是线程安全的重试,能够支持并发业务场景的重试逻辑正确性。 优雅重试适用场景: 功能逻辑中存在不稳定依赖场景,需要使用重试获取预期结果或者尝试重新执行逻辑不立即结束。比如远程接口访问,数据加载访问,数据上传校验等等。 对于异常场景存在需要重试场景,同时希望把正常逻辑和重试逻辑解耦。 对于需要基于数据媒介交互,希望通过重试轮询检测执行逻辑场景也可以考虑重试方案。 优雅重试解决思路: 切面方式 这个思路比较清晰,在需要添加重试的方法上添加一个用于重试的自定义注解,然后在切面中实现重试的逻辑,主要的配置参数则根据注解中的选项来初始化 优点: 真正的无侵入 缺点: 某些方法无法被切面拦截的场景无法覆盖(如spring-aop无法切私有方法,final方法) 直接使用aspecj则有些小复杂;如果用spring-aop,则只能切被spring容器管理的bean 消息总线方式 这个也比较容易理解,在需要重试的方法中,发送一个消息,并将业务逻辑作为回调方法传入;由一个订阅了重试消息的consumer来执行重试的业务逻辑 优点: 重试机制不受任何限制,即在任何地方你都可以使用 利用EventBus框架,可以非常容易把框架搭起来 缺点: 业务侵入,需要在重试的业务处,主动发起一条重试消息 调试理解复杂(消息总线方式的最大优点和缺点,就是过于灵活了,你可能都不知道什么地方处理这个消息,特别是新的童鞋来维护这段代码时) 如果要获取返回结果,不太好处理, 上下文参数不好处理 模板方式 优点: 简单(依赖简单:引入一个类就可以了; 使用简单:实现抽象类,讲业务逻辑填充即可;) 灵活(这个是真正的灵活了,你想怎么干都可以,完全由你控制) 缺点: 强侵入 代码臃肿 把这个单独捞出来,主要是某些时候我就一两个地方要用到重试,简单的实现下就好了,也没有必用用到上面这么重的方式;而且我希望可以针对代码快进行重试 这个的设计还是非常简单的,基本上代码都可以直接贴出来,一目了然: 复制代码 public abstract class RetryTemplate { private static final int DEFAULT_RETRY_TIME = 1; private int retryTime = DEFAULT_RETRY_TIME; private int sleepTime = 0;// 重试的睡眠时间 public int getSleepTime() { return sleepTime; } public RetryTemplate setSleepTime(int sleepTime) { if(sleepTime < 0) { throw new IllegalArgumentException("sleepTime should equal or bigger than 0"); } this.sleepTime = sleepTime; return this; } public int getRetryTime() { return retryTime; } public RetryTemplate setRetryTime(int retryTime) { if (retryTime <= 0) { throw new IllegalArgumentException("retryTime should bigger than 0"); } this.retryTime = retryTime; return this; } /** * 重试的业务执行代码 * 失败时请抛出一个异常 * * todo 确定返回的封装类,根据返回结果的状态来判定是否需要重试 * * @return */ protected abstract Object doBiz() throws Exception; //预留一个doBiz方法由业务方来实现,在其中书写需要重试的业务代码,然后执行即可 public Object execute() throws InterruptedException { for (int i = 0; i < retryTime; i++) { try { return doBiz(); } catch (Exception e) { log.error("业务执行出现异常,e: {}", e); Thread.sleep(sleepTime); } } return null; } public Object submit(ExecutorService executorService) { if (executorService == null) { throw new IllegalArgumentException("please choose executorService!"); } return executorService.submit((Callable) () -> execute()); } } 复制代码 使用示例: 复制代码 public void retryDemo() throws InterruptedException { Object ans = new RetryTemplate() { @Override protected Object doBiz() throws Exception { int temp = (int) (Math.random() * 10); System.out.println(temp); if (temp > 3) { throw new Exception("generate value bigger then 3! need retry"); } return temp; } }.setRetryTime(10).setSleepTime(10).execute(); System.out.println(ans); } 复制代码 spring-retry Spring Retry 为 Spring 应用程序提供了声明性重试支持。 它用于Spring批处理、Spring集成、Apache Hadoop(等等)的Spring。 在分布式系统中,为了保证数据分布式事务的强一致性,在调用RPC接口或者发送MQ时,针对可能会出现网络抖动请求超时情况采取一下重试操作。 用的最多的重试方式就是MQ了,但是如果你的项目中没有引入MQ,就不方便了。 还有一种方式,是开发者自己编写重试机制,但是大多不够优雅。 缺陷 spring-retry 工具虽能优雅实现重试,但是存在两个不友好设计: 一个是重试实体限定为 Throwable 子类,说明重试针对的是可捕捉的功能异常为设计前提的,但是我们希望依赖某个数据对象实体作为重试实体, 但 sping-retry框架必须强制转换为Throwable子类。 另一个是重试根源的断言对象使用的是 doWithRetry 的 Exception 异常实例,不符合正常内部断言的返回设计。 Spring Retry 提倡以注解的方式对方法进行重试,重试逻辑是同步执行的,当抛出相关异常后执行重试, 如果你要以返回值的某个状态来判定是否需要重试,可能只能通过自己判断返回值然后显式抛出异常了。只读操作可以重试,幂等写操作可以重试,但是非幂等写操作不能重试,重试可能导致脏写,或产生重复数据。 @Recover 注解在使用时无法指定方法,如果一个类中多个重试方法,就会很麻烦。 spring-retry 结构 BackOff:补偿值,一般指失败后多久进行重试的延迟值。 Sleeper:暂停应用的工具,通常用来应用补偿值。 RetryState:重试状态,通常包含一个重试的键值。 RetryCallback:封装你需要重试的业务逻辑(上文中的doSth) RecoverCallback:封装了多次重试都失败后你需要执行的业务逻辑(上文中的doSthWhenStillFail) RetryContext:重试语境下的上下文,代表了能被重试动作使用的资源。可用于在多次Retry或者Retry 和Recover之间传递参数或状态(在多次doSth或者doSth与doSthWhenStillFail之间传递参数) RetryOperations: 定义了“重试”的模板(重试的API),要求传入RetryCallback,可选传入RecoveryCallback; RetryTemplate :RetryOperations的具体实现,组合了RetryListener[],BackOffPolicy,RetryPolicy。 RetryListener:用来监控Retry的执行情况,并生成统计信息。 RetryPolicy:重试的策略或条件,可以简单的进行多次重试,可以是指定超时时间进行重试(上文中的someCondition),决定失败能否重试。 BackOffPolicy: 重试的回退策略,在业务逻辑执行发生异常时。如果需要重试,我们可能需要等一段时间(可能服务器过于繁忙,如果一直不间隔重试可能拖垮服务器),当然这段时间可以是0,也可以是固定的,可以是随机的(参见tcp的拥塞控制算法中的回退策略)。回退策略在上文中体现为wait(); RetryPolicy提供了如下策略实现: NeverRetryPolicy:只允许调用RetryCallback一次,不允许重试; AlwaysRetryPolicy:允许无限重试,直到成功,此方式逻辑不当会导致死循环; SimpleRetryPolicy:固定次数重试策略,默认重试最大次数为3次,RetryTemplate默认使用的策略; TimeoutRetryPolicy:超时时间重试策略,默认超时时间为1秒,在指定的超时时间内允许重试; CircuitBreakerRetryPolicy:有熔断功能的重试策略,需设置3个参数openTimeout、resetTimeout和delegate delegate:是真正判断是否重试的策略,当重试失败时,则执行熔断策略;应该配置基于次数的SimpleRetryPolicy或者基于超时的TimeoutRetryPolicy策略,且策略都是全局模式,而非局部模式,所以要注意次数或超时的配置合理性。 openTimeout:openWindow,配置熔断器电路打开的超时时间,当超过openTimeout之后熔断器电路变成半打开状态(主要有一次重试成功,则闭合电路); resetTimeout:timeout,配置重置熔断器重新闭合的超时时间 CompositeRetryPolicy:组合重试策略,有两种组合方式,乐观组合重试策略是指只要有一个策略允许重试即可以,悲观组合重试策略是指只要有一个策略不允许重试即可以,但不管哪种组合方式,组合中的每一个策略都会执行。 BackOffPolicy 提供了如下策略实现: NoBackOffPolicy:无退避算法策略,即当重试时是立即重试; FixedBackOffPolicy:固定时间的退避策略,需设置参数sleeper(指定等待策略,默认是Thread.sleep,即线程休眠)、backOffPeriod(休眠时间,默认1秒); UniformRandomBackOffPolicy:随机时间退避策略,需设置sleeper、minBackOffPeriod、maxBackOffPeriod,该策略在[minBackOffPeriod,maxBackOffPeriod之间取一个随机休眠时间,minBackOffPeriod默认500毫秒,maxBackOffPeriod默认1500毫秒; ExponentialBackOffPolicy:指数退避策略,需设置参数sleeper、initialInterval、maxInterval和multiplier。initialInterval指定初始休眠时间,默认100毫秒,maxInterval指定最大休眠时间,默认30秒,multiplier指定乘数,即下一次休眠时间为当前休眠时间*multiplier; ExponentialRandomBackOffPolicy:随机指数退避策略,引入随机乘数,固定乘数可能会引起很多服务同时重试导致DDos,使用随机休眠时间来避免这种情况。 RetryTemplate主要流程实现: 复制代码 //示例一 public void upload(final Map<String, Object> map) throws Exception { // 构建重试模板实例 RetryTemplate retryTemplate = new RetryTemplate(); // 设置重试策略,主要设置重试次数 SimpleRetryPolicy policy =         new SimpleRetryPolicy(3, Collections.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)); // 设置重试回退操作策略,主要设置重试间隔时间 FixedBackOffPolicy fixedBackOffPolicy = new FixedBackOffPolicy(); fixedBackOffPolicy.setBackOffPeriod(100); retryTemplate.setRetryPolicy(policy); retryTemplate.setBackOffPolicy(fixedBackOffPolicy); // 通过RetryCallback 重试回调实例包装正常逻辑逻辑,第一次执行和重试执行执行的都是这段逻辑 final RetryCallback<Object, Exception> retryCallback = new RetryCallback<Object, Exception>() { //RetryContext 重试操作上下文约定,统一spring-try包装 public Object doWithRetry(RetryContext context) throws Exception { System.out.println("do some thing"); Exception e = uploadToOdps(map); System.out.println(context.getRetryCount()); throw e;//这个点特别注意,重试的根源通过Exception返回 } }; // 通过RecoveryCallback 重试流程正常结束或者达到重试上限后的退出恢复操作实例 final RecoveryCallback recoveryCallback = new RecoveryCallback() { public Object recover(RetryContext context) throws Exception { System.out.println("do recory operation"); return null; } }; try { // 由retryTemplate 执行execute方法开始逻辑执行 retryTemplate.execute(retryCallback, recoveryCallback); } catch (Exception e) { e.printStackTrace(); } } //示例二 protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,RecoveryCallback recoveryCallback,   RetryState state) throws E, ExhaustedRetryException { //重试策略 RetryPolicy retryPolicy = this.retryPolicy; //退避策略 BackOffPolicy backOffPolicy = this.backOffPolicy; //重试上下文,当前重试次数等都记录在上下文中 RetryContext context = open(retryPolicy, state); try { //拦截器模式,执行RetryListener#open boolean running = doOpenInterceptors(retryCallback, context); //判断是否可以重试执行 while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) { try {//执行RetryCallback回调 return retryCallback.doWithRetry(context); } catch (Throwable e) {//异常时,要进行下一次重试准备 //遇到异常后,注册该异常的失败次数 registerThrowable(retryPolicy, state, context, e); //执行RetryListener#onError doOnErrorInterceptors(retryCallback, context, e); //如果可以重试,执行退避算法,比如休眠一小段时间后再重试 if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) { backOffPolicy.backOff(backOffContext); } //state != null && state.rollbackFor(context.getLastThrowable()) //在有状态重试时,如果是需要执行回滚操作的异常,则立即抛出异常 if (shouldRethrow(retryPolicy, context, state)) { throw RetryTemplate. wrapIfNecessary(e); } } //如果是有状态重试,且有GLOBAL_STATE属性,则立即跳出重试终止;       //当抛出的异常是非需要执行回滚操作的异常时,才会执行到此处,CircuitBreakerRetryPolicy会在此跳出循环; if (state != null && context.hasAttribute(GLOBAL_STATE)) { break; } } //重试失败后,如果有RecoveryCallback,则执行此回调,否则抛出异常 return handleRetryExhausted(recoveryCallback, context, state); } catch (Throwable e) { throw RetryTemplate. wrapIfNecessary(e); } finally { //清理环境 close(retryPolicy, context, state, lastException == null || exhausted); //执行RetryListener#close,比如统计重试信息 doCloseInterceptors(retryCallback, context, lastException); } } 复制代码 有状态or无状态 无状态重试,是在一个循环中执行完重试策略,即重试上下文保持在一个线程上下文中,在一次调用中进行完整的重试策略判断。如远程调用某个查询方法时是最常见的无状态重试: 复制代码 RetryTemplate template = new RetryTemplate(); //重试策略:次数重试策略 RetryPolicy retryPolicy = new SimpleRetryPolicy(3); template.setRetryPolicy(retryPolicy); //退避策略:指数退避策略 ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); backOffPolicy.setInitialInterval(100); backOffPolicy.setMaxInterval(3000); backOffPolicy.setMultiplier(2); backOffPolicy.setSleeper(new ThreadWaitSleeper()); template.setBackOffPolicy(backOffPolicy); //当重试失败后,抛出异常 String result = template.execute(new RetryCallback<String, RuntimeException>() { @Override public String doWithRetry(RetryContext context) throws RuntimeException { throw new RuntimeException("timeout"); } }); //当重试失败后,执行RecoveryCallback String result = template.execute(new RetryCallback<String, RuntimeException>() { @Override public String doWithRetry(RetryContext context) throws RuntimeException { System.out.println("retry count:" + context.getRetryCount()); throw new RuntimeException("timeout"); } }, new RecoveryCallback () { @Override public String recover(RetryContext context) throws Exception { return "default"; } }); 复制代码 有状态重试,有两种情况需要使用有状态重试,事务操作需要回滚、熔断器模式。 事务操作需要回滚场景时,当整个操作中抛出的是数据库异常DataAccessException,则不能进行重试需要回滚,而抛出其他异常则可以进行重试,可以通过RetryState实现: 复制代码 //当前状态的名称,当把状态放入缓存时,通过该key查询获取 Object key = "mykey"; //是否每次都重新生成上下文还是从缓存中查询,即全局模式(如熔断器策略时从缓存中查询) boolean isForceRefresh = true; //对DataAccessException进行回滚 BinaryExceptionClassifier rollbackClassifier = new BinaryExceptionClassifier(Collections.<Class<? extends Throwable>>singleton(DataAccessException.class)); RetryState state = new DefaultRetryState(key, isForceRefresh, rollbackClassifier); String result = template.execute(new RetryCallback<String, RuntimeException>() { @Override public String doWithRetry(RetryContext context) throws RuntimeException { System.out.println("retry count:" + context.getRetryCount()); throw new TypeMismatchDataAccessException(""); } }, new RecoveryCallback () { @Override public String recover(RetryContext context) throws Exception { return "default"; } }, state); 复制代码 RetryTemplate中在有状态重试时,回滚场景时直接抛出异常处理代码: //state != null && state.rollbackFor(context.getLastThrowable()) //在有状态重试时,如果是需要执行回滚操作的异常,则立即抛出异常 if (shouldRethrow(retryPolicy,context, state)) { throw RetryTemplate. wrapIfNecessary(e); } 熔断器场景。在有状态重试时,且是全局模式,不在当前循环中处理重试,而是全局重试模式(不是线程上下文),如熔断器策略时测试代码如下所示。 复制代码 RetryTemplate template = new RetryTemplate(); CircuitBreakerRetryPolicy retryPolicy = new CircuitBreakerRetryPolicy(new SimpleRetryPolicy(3)); retryPolicy.setOpenTimeout(5000); retryPolicy.setResetTimeout(20000); template.setRetryPolicy(retryPolicy); for (int i = 0; i < 10; i++) { try { Object key = "circuit"; boolean isForceRefresh = false; RetryState state = new DefaultRetryState(key, isForceRefresh); String result = template.execute(new RetryCallback<String, RuntimeException>() { @Override public String doWithRetry(RetryContext context) throws RuntimeException { System.out.println("retry count:" + context.getRetryCount()); throw new RuntimeException("timeout"); } }, new RecoveryCallback () { @Override public String recover(RetryContext context) throws Exception { return "default"; } }, state); System.out.println(result); } catch (Exception e) { System.out.println(e); } } 复制代码 为什么说是全局模式呢?我们配置了isForceRefresh为false,则在获取上下文时是根据key “circuit”从缓存中获取,从而拿到同一个上下文。 Object key = "circuit"; boolean isForceRefresh = false; RetryState state = new DefaultRetryState(key,isForceRefresh); 如下RetryTemplate代码说明在有状态模式下,不会在循环中进行重试。 if (state != null && context.hasAttribute(GLOBAL_STATE)) { break; } 判断熔断器电路是否打开的代码: 复制代码 public boolean isOpen() { long time = System.currentTimeMillis() - this.start; boolean retryable = this.policy.canRetry(this.context); if (!retryable) {//重试失败 //在重置熔断器超时后,熔断器器电路闭合,重置上下文 if (time > this.timeout) { this.context = createDelegateContext(policy, getParent()); this.start = System.currentTimeMillis(); retryable = this.policy.canRetry(this.context); } else if (time < this.openWindow) { //当在熔断器打开状态时,熔断器电路打开,立即熔断 if ((Boolean) getAttribute(CIRCUIT_OPEN) == false) { setAttribute(CIRCUIT_OPEN, true); } this.start = System.currentTimeMillis(); return true; } } else {//重试成功 //在熔断器电路半打开状态时,断路器电路闭合,重置上下文 if (time > this.openWindow) { this.start = System.currentTimeMillis(); this.context = createDelegateContext(policy, getParent()); } } setAttribute(CIRCUIT_OPEN, !retryable); return !retryable; } 复制代码 从如上代码可看出spring-retry的熔断策略相对简单: 当重试失败,且在熔断器打开时间窗口[0,openWindow) 内,立即熔断; 当重试失败,且在指定超时时间后(>timeout),熔断器电路重新闭合; 在熔断器半打开状态[openWindow, timeout] 时,只要重试成功则重置上下文,断路器闭合。 注解介绍 @EnableRetry 表示是否开始重试。 序号 属性 类型 默认值 说明 1 proxyTargetClass boolean false 指示是否要创建基于子类的(CGLIB)代理,而不是创建标准的基于Java接口的代理。当proxyTargetClass属性为true时,使用CGLIB代理。默认使用标准JAVA注解 @Retryable 标注此注解的方法在发生异常时会进行重试 序号 属性 类型 默认值 说明 1 interceptor String ”” 将 interceptor 的 bean 名称应用到 retryable() 2 value class[] {} 可重试的异常类型 3 include class[] {} 和value一样,默认空,当exclude也为空时,所有异常都重试 4 exclude class[] {} 指定异常不重试,默认空,当include也为空时,所有异常都重试 5 label String ”” 统计报告的唯一标签。如果没有提供,调用者可以选择忽略它,或者提供默认值。 6 maxAttempts int 3 尝试的最大次数(包括第一次失败),默认为3次。 7 backoff @Backoff @Backoff() 重试补偿机制,指定用于重试此操作的backoff属性。默认为空 @Backoff 不设置参数时,默认使用FixedBackOffPolicy(指定等待时间),重试等待1000ms 序号 属性 类型 默认值 说明 1 delay long 0 指定延迟后重试 ,如果不设置则默认使用 1000 milliseconds 2 maxDelay long 0 最大重试等待时间 3 multiplier long 0 指定延迟的倍数,比如delay=5000l,multiplier=2时,第一次重试为5秒后,第二次为10秒,第三次为20秒(大于0生效) 4 random boolean false 随机重试等待时间 @Recover 用于恢复处理程序的方法调用的注释。返回类型必须与@retryable方法匹配。 可抛出的第一个参数是可选的(但是没有它的方法只会被调用)。 从失败方法的参数列表按顺序填充后续的参数。 用于@Retryable重试失败后处理方法,此注解注释的方法参数一定要是@Retryable抛出的异常,否则无法识别,可以在该方法中进行日志处理。 说明: 使用了@Retryable的方法不能在本类被调用,不然重试机制不会生效。也就是要标记为@Service,然后在其它类使用@Autowired注入或者@Bean去实例才能生效。 要触发@Recover方法,那么在@Retryable方法上不能有返回值,只能是void才能生效。 使用了@Retryable的方法里面不能使用try...catch包裹,要在发放上抛出异常,不然不会触发。 在重试期间这个方法是同步的,如果使用类似Spring Cloud这种框架的熔断机制时,可以结合重试机制来重试后返回结果。 Spring Retry不只能注入方式去实现,还可以通过API的方式实现,类似熔断处理的机制就基于API方式实现会比较宽松。 转载于:https://www.cnblogs.com/whatarewords/p/10656514.html
养狐狸的猫 2019-12-02 02:11:54 0 浏览量 回答数 0
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 企业信息查询 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 企业建站模板