Go进阶笔记-微服务概览与治理

编程艺术 peanut 发表了文章 0 个评论 37 次浏览 2020-11-25 10:15 来自相关话题

基本上在产品的最开始阶段,为了快速构建产品,都是单体架构,尽快我们也会按照业务划分模块,但是这个样子始终最终部署的时候还是单体式应用。如我们早期可以使用Python 的Django快速迭代一个web应用,我们会在Django中划分不同的模块, ...查看全部

基本上在产品的最开始阶段,为了快速构建产品,都是单体架构,尽快我们也会按照业务划分模块,但是这个样子始终最终部署的时候还是单体式应用。
如我们早期可以使用Python 的Django快速迭代一个web应用,我们会在Django中划分不同的模块,也就是Django中的app。
而随着业务的迭代发展,项目越来越复杂,可能就会导致应用的扩展,可靠性越来越低,最终导致敏捷开发和自动化部署变得无法完成。

微服务定义

关于SOA



面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)进行拆分,并通过这些服务之间定义良好的接口和协议联系起来。接口是采用中立的方式进行定义的,它应该独立于实现服务的硬件平台、操作系统和编程语言。这使得构建在各种各样的系统中的服务可以以一种统一和通用的方式进行交互。



所以我们可以把微服务看做是SOA的一种实践:


  • 小即是美:小的服务代码少,bug也少,易于测试,易于维护,也更容易不断迭代完善。
  • 单一职责:一个服务只需要干好一件事情,专注才能做好。

什么是微服务?

围绕业务功能构建的,服务关注单一业务,服务间采用轻量级的通信机制,可以全自动独立部署,可以使用不同的编程语言和数据存储技术。微服务架构通过业务拆分实现服务组件化,通过组件组合快速开发系统,业务单一的服务组件又可以独立部署,使整个系统变得清晰灵活。


  • 原子服务
  • 独立进程
  • 隔离部署
  • 去中心化服务治理

注意:基础设施的建设,复杂度高。


自己的理解:


  • 简单说就是微小的服务或应用,比如linux上的各种工具:ls,cat,awk等
  • 微服务就是让每个小的服务专注的做好一件事
  • 每个服务单独开发和部署,服务之间是完全隔离的

微服务的优缺点

微服务也不是万金油,并不是所有的情况都需要做成微服务,同时微服务也有自己的缺点或者说微服务也会带来一些问题:


  • 微服务应用是分布式系统,因此系统必然会比单体应用的时候复杂:开发者不得不适用RPC或者消息传递来实现进程间通信;必须要写代码来处理消息传递中速度过慢或者服务不可用等局部失效问题。
  • 分区的数据库架构,同时更新多个业务主体的事务很普遍。这种事务对单体式应用来说很容易,因为只有一个数据库。在微服务架构中,需要更新不同服务使用的不同的数据库,从而对开发者提供了更高的要求和挑战。
  • 测试一个基于微服务的应用也变的很复杂。
  • 服务模块的依赖,应用的升级可能会涉及多个服务模块的修改。

优点:


  • 迭代周期短,极大的提升开发效率
  • 独立部署,独立开发
  • 可伸缩性好,能够针对指定的服务进行伸缩
  • 故障隔离,不会相互影响

缺点:


  • 复杂度增加,一个请求往往要经过多个服务,请求链路比较长
  • 监控和定位问题困难
  • 服务管理比较复杂

组件化服务

微服务的核心是组件化服务,通过将之前复杂的巨石机构,拆分成不同的服务,来实现组件化。即将应用拆散为一系列的服务运行在不同的进程中。单一的服务变化只需要重新部署对应的服务进程。


区中心化

  • 数据去中心化
  • 治理去中心化
  • 技术去中心化

注:治理区中心化,可以理解为消除架构中的热点,例如,我们通常在架构中使用的Nginx,所有的流量都会先经过Nginx,虽然也可以扩容,但是相对来说收益就比较低。


每个服务独享自身的数据存储设施(缓存,数据库等),而不是像传统应用共享一个缓存和数据库,这样有利于服务的独立性,隔离相关干扰。


基础设施自动化

无自动化不微服务。自动化包括测试和部署。
单一进程的传统应用被拆分为一系列的多进程服务后,意味着开发,调试,测试,监控和部署的复杂度会增加,必须要有合适的自动化基础设施来支持微服务架构,否则开发和运维的成本会大大增加。

  • CICD
  • Testing
  • K8s

落地微服务的关键因素


配套设施:


  • 微服务框架研发和维护
  • 打包,版本管理,上线平台支持
  • 硬件层支持,比如容易和容器调度
  • 服务治理平台支持,比如分布式链路追踪和监控
  • 测试自动化支持,比如上线前自动化case

组织架构


  • 微服务框架开发团队
  • 私有云研发团队
  • 测试平台研发团队

硬件层架构

JHhJAI.png

可用性 & 兼容性设计

微服务架构采用粗力度的进程间通信。关于可用性和兼容性主要包含以下方面:


  • 隔离
  • 超时控制
  • 负载保护
  • 限流
  • 降级
  • 重试
  • 负载均衡

注意:服务的提供者的变更可能引发服务消费者的兼容性破坏,时刻谨记服务契约的兼容性。
总结一句话:发送时要保守,接收时要开放。

微服务设计

API Gateway

常见的开源网关:Kong, APSix,


面向用户场景的API,而不是面向资源的API


BFF(Backend for Frontend) 可以认为是一种适配服务,将后端的微服务进行适配(主要包括聚合裁剪和适配逻辑),向无线端设备暴露友好和统一的API,方便无线设备介入访问后端服务。


BFF 可以理解为主要进行数据的组装,业务场景的聚合API


网关在微服务架构中承担着非常重要的角色,它是解偶拆分和后续升级的利器。在网关的配合下,单块BFF 实现解偶拆分,各业务团队可以独立开发和交付各自的微服务。
把跨横切面逻辑从BFF 剥离到网关上,BFF的开发可以更加专注于业务逻辑交付。实现架构上的关注分离。

Mircoservice划分

相对来说有两种不同不同的划分服务边界:通过业务职能(Business Capability)划分和DDD的限界上下文(Bounded Context)


Business Capability: 由公司内部不同部门提供的只能
Bounded Context:这里的业务边界的含义是“解决不同业务问题”的问题域和对应的解决方案域,为了解决某种类型的业务问题,贴近领域知识,也就是业务。

DDD 通过领域对象之间的交互实现业务逻辑与流程,并通过分层的方式将业务逻辑剥离出来,单独进行维护,从而控制业务本身的复杂度。


注意:微服务与微服务之间不是通过数据耦合的,所以微服与微服务之间都是通过接口调用,一定不是通过数据,服务与服务之间数据是隔离的。


什么是CQRS

CQRS — Command Query Responsibility Segregation,故名思义是将 command 与 query 分离的一种模式。


CQRS 将系统中的操作分为两类,即「命令」(Command) 与「查询」(Query)。命令则是对会引起数据发生变化操作的总称,即我们常说的新增,更新,删除这些操作,都是命令。而查询则和字面意思一样,即不会对数据产生变化的操作,只是按照某些条件查找数据。


CQRS 的核心思想是将这两类不同的操作进行分离,然后在两个独立的「服务」中实现。这里的「服务」一般是指两个独立部署的应用。在某些特殊情况下,也可以部署在同一个应用内的不同接口上。


Command 与 Query 对应的数据源也应该是互相独立的,即更新操作在一个数据源,而查询操作在另一个数据源上。


Mircoservice安全

关于外网的请求,通常在API Gateway进行统一的认证拦截,认证成功后,使用JWT方式通过RPC元数据传递的方式带到BFF层,BFF校验Token完整性后把身份信息注入到应用的Context中,BFF到其他下层的微服务,建议是直接在RPC Request中带入用户身份信息(UserID)请求服务


对于服务内部,一般要区分身份认证和授权


对于身份认证:如果是gRPC,可以很容易进行身份认证,如:证书…
对于授权:通过配置中心做一个RBAC的服务,下发到服务,服务加载的时候就可以很容易构建一个RBAC的认证,从而判断这个请求是否有权限。

gRPC && 服务发现

  • 多语言:语言中立,支持多种语言
  • 轻量级,高性能:序列化支持PB(Protocol Buffer) 和JSON, PB是一种语言无关的高性能序列化框架
  • 可插拔
  • IDL:基于文件定义服务,通过proto3工具生成指定语言的数据结构/服务端接口以及客户端Stub
  • 设计理念:如元数据的传递
  • 移动端:基于标准的HTTP2设计,支持双向流,消息头压缩,单TCP的多路复用/服务端推送等特性。
  • 服务而非对象,消息而非引用:促进微服务的系统间粗粒度消息交互设计理念
  • 负载无关的:不同的服务需要使用不同的消息类型和编码
  • 流:streaming API
  • 阻塞式和非阻塞式:支持异步和同步处理在客户端和服务端交互的消息序列
  • 元数据交换:常见的横切关注点,如认证或追踪,依赖数据交换。
  • 标准化状态码:客户端通常以有限的方式响应API调用返回的错误

Health Check

gRPC 有一个标准的健康监测协议,在gRPC的所有语言实现中基本都提供了生成代码合用于设置运行状态的功能。


主动健康检查可以在服务提供者服务不稳定时,被消费者所感知,临时从负载均衡中摘除,减少错误请求。当服务提供这重新稳定后,health check 成功,重新假如到消费者的负载均衡中,回复请求,health check 同样也被用于外挂方式的容器健康检测,或者流量检测


healthCheck 可以做什么 ?


  • 在我们的服务注册与发现中,假如服务的提供者Provider到Discoery 之间通信时正常的,但是我们的服务调用者Consumer到服务提供者Provider之间出现网络问题,这个时候如果没有健康检查,我们的服务调用这就会继续调用,但是这个时候其实是会调用失败的,而healthCheck 就可以避免这种情况的发生。它会对从Discoery中获取到的Provider进行健康检查,虽然Discoery中有这个Provider,但是如果健康检查有问题,那么就会把这个provider进行剔除。避免调用失败的问题。


  • 平滑发布


服务发现

CAP原理


  • C: consistency, 一致性,每次总是能够读到最近写入的数据或者失败
  • A: available, 每次请求都能读到数据
  • P: partition tolerance 分区容忍,不管任意个消息由于网络原因失败,系统都能能够继续工作

CAP原理中,P是必须满足的,C 和A 可以根据业务需要选择,要么是CP系统,要么是AP系统


客户端发现


一个服务实例启动时,它的网络地址会被注册到注册中心,当服务实例终止时,再从注册中心删除。这个服务实例的注册表通过心跳机制动态刷新;客户端使用一个负载均衡算法,去选择一个可用的服务实例,来响应这个请求。


服务端发现


客户端通过负载均衡器向一个服务发送请求,这个负载均衡器会查询服务注册表,并将请求路由到可用的服务实例上。服务实例在服务注册表上被注册和注销


DNhMgH.jpg


对比两种服务发现:


  • 客户端发现:直连,比服务端服务发现少一次网络跳转,Consumer需要内置特定的服务发现客户端和发现逻辑。
  • 服务端发现:Consumer无需关注服务发现具体细节,只需要知道服务的DNS域名即可,支持异构语言开发,需要基础设施支撑,多了一次网络跳转,可能有性能损失。

注意:微服务的和兴是去中心化,所以相对来说使用客户端服务发现模式比较好


推荐的服务发现:
https://nacos.io/zh-cn/docs/what-is-nacos.html
https://github.com/bilibili/discovery 学习一下代码


服务发现中的保护机制:


  • 如果发现短时间内大量服务提供这下线,会开启自我保护模式。这个时候不会剔除服务。
  • 如果服务消费者和服务注册中心通信故障,这个时候本身服务消费者会缓存配置,即使短时间内通信故障也不会有太大影响。

多集群 & 多租户

对于特别重要的服务通常是要考虑多级群。


  • 从单一集群考虑,多个节点保证可用性,我们通常使用N+2的方式来冗余节点。
  • 从单一集群故障带来的影响面角度考虑冗余多套集群。
  • 单个机房内的机房故障导致的问题。

多套冗余的集群对应多套独占的缓存,带来更好的性能和冗余能力
尽量避免业务隔离使用或者sharding带来的cache hit影响(按照业务划分集群资源)

但是这里会有一个问题需要考虑:
根据不同的业务划分集群后,如果其中一个业务的进群挂了之后,将流量切到正常集群的时候,这个时候因为独占缓存,所以就会导致产生到两的cache miss 透传到DB,这个时候DB的压力会瞬间变大。

解决办法:可以和所有集群建立连接,通过负载均衡的方式,这样请求就会均摊的打到不同的集群中
上,从而防止缓存击穿的情况。

注意这里还有一个问题:
对于服务中的个别服务可能会存在有大量的其他服务都会依赖这个服务的情况,如帐号服务,那么这个时候health check 的检查可能会占用一定的资源,并且随着规模的增加,光health check 就会占用非常高的资源,如何解决这个问题呢?

是否可以从全集群中选取一批节点(子集),利于划分子集限制连接池大小?


通常20-100个后端,部分场景需要大子集,比如批量读写操作。
后端平均分给客户端。
客户端重启,保持重新均衡,同时对后端重启保持透明,同时连接的变动最小。

需要思考这个算法的实现。


多租户


在一个微服务架构中允许系统共存是利用微服务稳定性及模块化最有效的方式之一。这种方式一般被称为多租户。租户卡一是测试,金丝雀发布,影子系统,甚至服务层或产品线,使用租户能够保证代码的隔离性并且能够基于流量租户做路由决策。



多租户就是解决RPC的路由或者叫做RPC染色


并行测试需要一个和生产环境一样的过渡(staging)环境,并且知识用来处理测试流量。在并行测试中,工程师团队首先完成生产服务的一次变动,然后将变动的代码部署到测试栈,这种方法可以在不影响生产环境的情况下让开发者稳定的测试服务,同时能够在发布前更容易的识别和控制bug,尽管并行测试是一种非常有效的集成测试方法,但是它也带来了一些可能影响服务架构成功的挑战:


  • 混用环境导致的不可靠测试
  • 多套环境带来的硬件成本
  • 难以做负载测试,仿真线上真实流量情况

使用这种方法(内部叫染色发布),我们可以把待测试的服务 B 在一个隔离的沙盒环境中启动,并且在沙盒环境下可以访问集成环境(UAT) C 和D。我们把测试流量路由到服务 B,同时保持生产流量正常流入到集成服务。服务 B 仅仅处理测试流量而不处理生产流量。另外要确保集成流量不要被测试流量影响。生产中的测试提出了两个基本要求,它们也构成了多租户体系结构的基础:


  • 流量路由:能够基于流入栈中的流量类型做路由。
  • 隔离性:能够可靠的隔离测试和生产中的资源,这样可以保证对于关键业务微服务没有副作用。

DUeeCq.png


这里可以理解为,对于不同的流量区别对待,对于测试的流量,也会在请求的时候带上对应的染色标记,这样到达系统的时候就会根据不同的染色标记走不同的路由,路由到具有相同染色的服务上。


小结

  • 对于微服整体有一认识
  • 对于公司现有系统架构的一些思考,可以跟着课程的深入学习,慢慢对公司现有架构整理出自己的意见和一些可行性的方案

需要关注的书籍与链接:


kafka利用命令查看consumer消费情况

智慧运维 空心菜 回复了问题 1 人关注 2 个回复 4513 次浏览 2019-06-05 07:51 来自相关话题

搜索引擎科学上网技能大放送

智慧运维 Nock 发表了文章 1 个评论 2309 次浏览 2017-01-13 00:06 来自相关话题

在今天,用户可以通过搜索引擎轻松找出自己想要的信息,但还是难以避免结果不尽如人意的情况。实际上,用户仅需掌握几个常用技巧即可轻松化解这种尴尬。   正常情况下我们搜索的关键是正确的关键词和搜搜引擎的选择,通过正确的搜索我们能得到答案的问 ...查看全部
在今天,用户可以通过搜索引擎轻松找出自己想要的信息,但还是难以避免结果不尽如人意的情况。实际上,用户仅需掌握几个常用技巧即可轻松化解这种尴尬。
 
正常情况下我们搜索的关键是正确的关键词和搜搜引擎的选择,通过正确的搜索我们能得到答案的问题可以到80%以上。


常用引擎推荐


No.1 谷歌https://google.com
google.png

No.2 百度https://www.baidu.com/
baidu.png

No.3 鸭鸭快跑 (https://duckduckgo.com/
duckduckgo.png

No.4 必应 (http://cn.bing.com/ )
bing.png

No.5 搜狗 (https://www.sogou.com/)
sogou.png


排错搜索过程


soerror.png


1、准确搜索


最简单、有效的准确搜索方式是在关键词上加上双引号,在这种情况下,搜索引擎只会反馈和关键词完全吻合的搜索结果, 把搜索词放在双引号中,代表完全匹配搜索,也就是说搜索结果返回的页面包含双引号中出现的所有的词,连顺序也必 须完全匹配.

比方说在搜索「zabbix mysql」的时候,在没有给关键词加上双引号的情况,搜索引擎会显示所有分别和「zabbix」以及「mysql」相关的信息,但这些显然并不是我们想要的结果。但在加上双引号后,搜索引擎则仅会在页面上反馈和「zabbix mysql」相吻合的信息。

准确搜索在排除常见但相近度偏低的信息时非常有用,可以为用户省去再度对结果进行筛选的麻烦。
yinhao.png


2、加号


在搜索引擎框里把多个关键字用加号(+)连接起来,搜索引擎就会自动去匹配互联网上与所有关键词相关的内容,默认与 空格等效,百度和Google都支持。
open-source-tech.png


3、减号-排除关键词


如果在进行准确搜索时没有找到自己想要的结果,用户可以对包含特定词汇的信息进行排除,仅需使用减号即可。

减号代表搜索不包含减号后面的词的页面。使用这个指令时减号前面必须是空格,减号后面没有空格,紧跟着需要排除的词 。
jianhao.png


4、OR或逻辑搜索


在默认搜索下,搜索引擎会反馈所有和查询词汇相关的结果,但通过使用「OR」逻辑,你可以得到和两个关键词分别相关的结果,而不仅仅是和两个关键词 都同时相关的结果。巧妙使用「OR」搜索可以让你在未能确定哪个关键词对于搜索结果起决定作用时依然可以确保搜索结果的准确性。
orlink.png


5、同义词搜索​


有时候对不太确切的关键词进行搜索反而会显得更加合适。在未能准确判断关键词的情况下,你可以通过同义词进行搜索。

如果你在搜索引擎输入「plumbing ~university」,你所得到的反馈结果会包含「plumbing universities」和「plumbing colleges」等相似条目。
daxue.png


6、善用星号


正如拼图游戏「Scrabble」的空白方块一样,在搜索引擎中,我们可以用星号填补关键词中的缺失部分,不论缺失的是一连串单词的其中一个还是一个单词的某一部分。此外,当你希望搜索一篇确定性偏低的文章时,也可以使用星号填补缺失部分。

例如,如果你在搜索引擎中输入「architect*」,你所得到的反馈结果将会是所有包含 architect、architectural、architecture、architected、architecting 以及其他所有以「architect」作为开头的词汇的条目。

常用的案例:搜索报错中的特定路径 , 有个词忘记了或者不会打:
xinhao.png


7、在两个数值之间进行搜索


在寻找问题的答案时,一个很好的方法是在一定范围内寻找和关键词相关的资讯。例如想要找出 1920 至 1950 年间的英国首相,直接在搜索引擎中输入「英国首相 1920.. 1950」即可得出想要的结果。

记住,数值之间的符号是两个英文句号加一个空格键。
kingdom.png


8、inurl  


该指令用于搜索查询词出现在url中的页面。BaiDu和Google都支持inurl指令。inurl指令支持中文和英文。 比如搜索:inurl:hadoop,返回的结果都是网址url中包含“hadoop”的页面。由于关键词出现在url 中对排名有一定影响,使用inurl:搜索可以更准确地找到与关键字相关的内容。
 
例如:inurl:openskill hadoop
inurl.png


9、intitle在网页标题、链接和主体中搜索关键词


有时你或许会遇上找出所有和关键词相关的所有网页标题、链接和网页主体的需求,在这个时候你需要使用的是限定词「inurl:」(供在 url 链接中搜索使用)、「intext:」(供在网页主体中搜索使用)以及「intitle:」(供在网页标题中搜索使用)。
 
使用intitle 指令找到的文件更为准确。出现在title中,说明页面内容跟关键字有很大关联。
intitle.png


10、allintitle


allintitle:搜索返回的是页面标题中包含多组关键词的文件。例如 :allintitle:zabbix docker,就相当于:intitle:zabbix intitle:docker,返回的是标题中中既包含“zabbix”,也包含“docker”的页面,显著提高搜索命中率。
allintitle.png


11、allinurl


与allintitle: 类似,allinurl:zabbix hadoop,就相当于 :inurl:zabbix inurl:hadoop 
allinurl.png


12、site站内搜索


绝大部分网站的搜索功能都有所欠缺,因此,更好的方法是通过 Google 等搜索引擎对站内的信息进行搜索。

你只需要在搜索引擎上输入「site:openskill.cn」加上关键词,搜索引擎就会反馈网站「openskill.cn」内和关键词相关的所有条目。如果再结合准确搜索功能,这项功能将会变得更加强大。
site.png


13、filetype


用于搜索特定文件格式。Google 和bd都支持filetype指令。 比如搜索filetype:pdf docker 返回的就是包含SEO 这个关键词的所有pdf 文件。 
filetype.png


14、搜索相关网站


查找与您已浏览过的网址类似的网站, 例如,你仅需在搜索引擎中输入「related:openskill.cn」即可得到所有和「openskill.cn」相关的网站反馈结果。
relate.png


15、搜索技能的组合使用


你可以对上述所有搜索技能进行组合运用,以便按照自己的意愿缩小或者扩展搜索范围。尽管有些技能或许并不常用,但准确搜索和站内搜索这些技能的使用范围还是相当广泛的。
union.png


其他技巧


other.png

随着 Google 等搜索引擎对于用户自然语言的理解程度与日俱增,这些搜索技能可以派上用场的情况或许将会变得越来越少,至少这是所有搜索引擎共同追求的目标。但是在当下,掌握这些搜索技能还是非常必要的。

参考:http://www.cnblogs.com/feiyuhuo/p/5398238.html http://blog.jobbole.com/72211/ 

CDH Hadoop + HBase HA 部署详解

大数据 空心菜 发表了文章 0 个评论 4613 次浏览 2016-11-07 21:07 来自相关话题

CDH 的部署和 Apache Hadoop 的部署是没有任何区别的。这里着重的是 HA的部署,需要特殊说明的是NameNode HA 需要依赖 Zookeeper   准备 Hosts文件配置: ...查看全部
hadoop.jpg

CDH 的部署和 Apache Hadoop 的部署是没有任何区别的。这里着重的是 HA的部署,需要特殊说明的是NameNode HA 需要依赖 Zookeeper
 


准备


Hosts文件配置:
cat > /etc/hosts << _HOSTS_
127.0.0.1 localhost
10.0.2.59 cdh-m1
10.0.2.60 cdh-m2
10.0.2.61 cdh-s1
_HOSTS_
各个节点服务情况
cdh-m1 Zookeeper JournalNode NameNode DFSZKFailoverController HMaster
cdh-m2 Zookeeper JournalNode NameNode DFSZKFailoverController HMaster
cdh-s1 Zookeeper JournalNode DataNode HRegionServer
对几个新服务说明下: 
  • JournalNode 用于同步 NameNode 元数据,和 Zookeeper 一样需要 2N+1个节点存活集群才可用。
  • DFSZKFailoverController(ZKFC) 用于主备切换,类似 Keepalived 所扮演的角色。

 
NTP 服务
设置时区
rm -f /etc/localtime
ln -s /usr/share/zoneinfo/UTC /etc/localtime
配置NTP Server
yum install -y ntp
cat > /etc/ntp.conf << _NTP_
driftfile /var/lib/ntp/drift

restrict default nomodify
restrict -6 default nomodify

server cn.ntp.org.cn prefer
server news.neu.edu.cn iburst
server dns.sjtu.edu.cn iburst
server 127.127.1.1 iburst

tinker dispersion 100
tinker step 1800
tinker stepout 3600
includefile /etc/ntp/crypto/pw

keys /etc/ntp/keys
_NTP_

# NTP启动时立即同步
cat >> /etc/ntp/step-tickers << _NTP_
server cn.ntp.org.cn prefer
server news.neu.edu.cn iburst
server dns.sjtu.edu.cn iburst
_NTP_

# 同步硬件时钟
cat >> /etc/sysconfig/ntpd << _NTPHW_
SYNC_HWCLOCK=yes
_NTPHW_
启动并设置开机自启动
/etc/init.d/ntpd start
chkconfig ntpd on
配置 NTP Client
yum install -y ntp
# 注意修改内网NTP Server地址
cat > /etc/ntp.conf << _NTP_
driftfile /var/lib/ntp/drift

restrict default nomodify
restrict -6 default nomodify

restrict 127.0.0.1
restrict -6 ::1

server 10.0.2.59 prefer

tinker dispersion 100
tinker step 1800
tinker stepout 3600
includefile /etc/ntp/crypto/pw

keys /etc/ntp/keys
_NTP_

# NTP启动时立即同步
cat >> /etc/ntp/step-tickers << _NTP_
server 10.0.2.59 prefer
_NTP_

# 同步硬件时钟
cat >> /etc/sysconfig/ntpd << _NTPHW_
SYNC_HWCLOCK=yes
_NTPHW_
启动并设置开机自启动
/etc/init.d/ntpd start
chkconfig ntpd on
检查 NTP 同步
ntpq -p

# 结果
remote refid st t when poll reach delay offset jitter
==============================================================================
*time7.aliyun.co 10.137.38.86 2 u 17 64 3 44.995 5.178 0.177
news.neu.edu.cn .INIT. 16 u - 64 0 0.000 0.000 0.000
202.120.2.90 .INIT. 16 u - 64 0 0.000 0.000 0.000
JDK
创建目录
mkdir -p /data/{install,app,logs,pid,appData}
mkdir /data/appData/tmp
cd /data/install
wget -c http://oracle.com/jdk-7u51-linux-x64.gz
tar xf jdk-7u51-linux-x64.gz -C /data/app
cd /data/app
ln -s jdk1.7.0_51 jdk1.7
cat >> /etc/profile << _PATH_
export JAVA_HOME=/data/app/jdk1.7
export CLASSPATH=.:\$JAVA_HOME/lib/dt.jar:\$JAVA_HOME/lib/tools.jar
export PATH=\$JAVA_HOME/bin:\$PATH
_PATH_
source /etc/profile

创建运行账户
useradd -u 600 run
安装包
http://archive.cloudera.com/cdh5/cdh/5/
cd /data/install
wget -c http://archive.cloudera.com/cdh5/cdh/5/hadoop-2.6.0-cdh5.4.5.tar.gz
wget -c http://archive.apache.org/dist/zookeeper/zookeeper-3.4.5/zookeeper-3.4.5.tar.gz
wget -c http://archive.cloudera.com/cdh5/cdh/5/hbase-1.0.0-cdh5.4.5.tar.gz


安装 Zookeeper


cd /data/install
tar xf zookeeper-3.4.5.tar.gz -C /data/app
cd /data/app
ln -s zookeeper-3.4.5 zookeeper
设置环境变量
sed -i '/^export PATH=/i\export ZOOKEEPER_HOME=/data/app/zookeeper' /etc/profile
sed -i 's#export PATH=#&\$ZOOKEEPER_HOME/bin:#' /etc/profile
source /etc/profile
删除无用文件
cd $ZOOKEEPER_HOME
rm -rf *xml *txt zookeeper-3.4.5.jar.* src recipes docs dist-maven contrib
rm -f $ZOOKEEPER_HOME/bin/*.cmd $ZOOKEEPER_HOME/bin/*.txt
rm -f $ZOOKEEPER_HOME/conf/zoo_sample.cfg
创建数据目录
mkdir -p /data/appData/zookeeper/{data,logs}
配置
cat > $ZOOKEEPER_HOME/conf/zoo.cfg << _ZOO_
tickTime=2000
initLimit=10
syncLimit=5
clientPort=2181
dataDir=/data/appData/zookeeper/data
dataLogDir=/data/appData/zookeeper/logs
server.1=cdh-m1:2888:3888
server.2=cdh-m2:2888:3888
server.3=cdh-s1:2888:3888
_ZOO_
修改Zookeeper的日志打印方式,与日志路径设置
编辑
$ZOOKEEPER_HOME/bin/zkEnv.sh
在27行后加入两个变量
ZOO_LOG_DIR=/data/logs/zookeeper
ZOO_LOG4J_PROP="INFO,ROLLINGFILE"
创建 myid文件
# 注意myid与配置文件保持一致
echo 1 >/data/appData/zookeeper/data/myid
设置目录权限
chown -R run.run /data/{app,appData,logs}
启动、停止
# 启动
runuser - run -c 'zkServer.sh start'
# 停止
runuser - run -c 'zkServer.sh stop'


安装 Hadoop


tar xf hadoop-2.6.0-cdh5.4.5.tar.gz -C /data/app
cd /data/app
ln -s hadoop-2.6.0-cdh5.4.5 hadoop
设置环境变量
sed -i '/^export PATH=/i\export HADOOP_HOME=/data/app/hadoop' /etc/profile
sed -i 's#export PATH=#&\$HADOOP_HOME/bin:\$HADOOP_HOME/sbin:#' /etc/profile
source /etc/profile
删除无用文件
cd $HADOOP_HOME
rm -rf *txt share/doc src examples* include bin-mapreduce1 cloudera
find . -name "*.cmd"|xargs rm -f
新建数据目录
mkdir -p /data/appData/hdfs/{name,edits,data,jn,tmp}
配置
切换到配置文件目录
cd $HADOOP_HOME/etc/hadoop
编辑 core-site.xml





fs.defaultFS
hdfs://hdfs-cdh




hadoop.tmp.dir
/data/appData/hdfs/tmp




fs.trash.interval
1440




io.file.buffer.size
131072




io.compression.codecs
org.apache.hadoop.io.compress.SnappyCodec

编辑 hdfs-site.xml





dfs.nameservices
hdfs-cdh




ha.zookeeper.quorum
cdh-m1:2181,cdh-m2:2181,cdh-s1:2181




dfs.ha.namenodes.hdfs-cdh
nn1,nn2




dfs.namenode.rpc-address.hdfs-cdh.nn1
cdh-m1:9000




dfs.namenode.http-address.hdfs-cdh.nn1
cdh-m1:50070




dfs.namenode.rpc-address.hdfs-cdh.nn2
cdh-m2:9000




dfs.namenode.http-address.hdfs-cdh.nn2
cdh-m2:50070




dfs.namenode.shared.edits.dir
qjournal://cdh-m1:8485;cdh-m2:8485;cdh-s1:8485;/hdfs-cdh




dfs.ha.automatic-failover.enabled
true




dfs.client.failover.proxy.provider.hdfs-cdh
org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider




dfs.ha.fencing.methods

sshfence
shell(/bin/true)





dfs.ha.fencing.ssh.private-key-files
/home/run/.ssh/id_rsa




dfs.ha.fencing.ssh.connect-timeout
50000




dfs.namenode.name.dir
/data/appData/hdfs/name




dfs.datanode.data.dir
/data/appData/hdfs/data




dfs.journalnode.edits.dir
/data/appData/hdfs/jn




dfs.namenode.edits.noeditlogchannelflush
true




dfs.namenode.edits.dir
/data/appData/hdfs/edits




dfs.datanode.hdfs-blocks-metadata.enabled
true




dfs.permissions.enabled
false




dfs.blocksize
64m

小于5个DataNode建议添加如下配置


dfs.replication
2




dfs.client.block.write.replace-datanode-on-failure.policy
NEVER
在 hadoop-env.sh 中添加如下变量
export JAVA_HOME=/data/app/jdk1.7
export HADOOP_LOG_DIR=/data/logs/hadoop
export HADOOP_PID_DIR=/data/pid
# SSH端口 可选
export HADOOP_SSH_OPTS="-p 36000"
Heap 设置,单位 MB
export HADOOP_HEAPSIZE=1024
权限设置
chown -R run.run /data/{app,appData,logs}
chmod 777 /data/pid
格式化
格式化只需要执行一次,格式化之前启动Zookeeper
 
切换用户
su - run
启动所有 JournalNode
hadoop-daemon.sh start journalnode
格式化 Zookeeper(为 ZKFC 创建znode)
hdfs zkfc -formatZK
NameNode 主节点格式化并启动
hdfs namenode -format
hadoop-daemon.sh start namenode
NameNode 备节点同步数据并启动
hdfs namenode -bootstrapStandby
hadoop-daemon.sh start namenode
启动 ZKFC
hadoop-daemon.sh start zkfc
启动 DataNode
hadoop-daemon.sh start datanode
启动与停止
切换用户
su - run
集群批量启动
需要配置运行用户ssh-key免密码登录,与$HADOOP_HOME/etc/hadoop/slaves
# 启动
start-dfs.sh
# 停止
stop-dfs.sh
单服务启动停止
启动HDFS
hadoop-daemon.sh start journalnode
hadoop-daemon.sh start namenode
hadoop-daemon.sh start zkfc
hadoop-daemon.sh start datanode
停止HDFS
hadoop-daemon.sh stop datanode
hadoop-daemon.sh stop namenode
hadoop-daemon.sh stop journalnode
hadoop-daemon.sh stop zkfc

测试
HDFS HA 测试
打开 NameNode 状态页:
http://cdh-m1:50010
http://cdh-m2:50010 

在 Overview 后面能看见 active 或 standby,active 为当前 Master,停止 active 上的 NameNode,检查 standby是否为 active。
 
HDFS 测试
hadoop fs -mkdir /test
hadoop fs -put /etc/hosts /test
hadoop fs -ls /test
结果:
-rw-r--r--   2 java supergroup         89 2016-06-15 10:30 /test/hosts
# 其中权限后面的列(这里的2)代表文件总数,即副本数量。
HDFS 管理命令
# 动态加载 hdfs-site.xml
hadoop dfsadmin -refreshNodes


HBase安装配置


cd /data/install
tar xf hbase-1.0.0-cdh5.4.5.tar.gz -C /data/app
cd /data/app
ln -s hbase-1.0.0-cdh5.4.5 hbase
设置环境变量
sed -i '/^export PATH=/i\export HBASE_HOME=/data/app/hbase' /etc/profile
sed -i 's#export PATH=#&\$HBASE_HOME/bin:#' /etc/profile
source /etc/profile
删除无用文件
cd $HBASE_HOME
rm -rf *.txt pom.xml src docs cloudera dev-support hbase-annotations hbase-assembly hbase-checkstyle hbase-client hbase-common hbase-examples hbase-hadoop2-compat hbase-hadoop-compat hbase-it hbase-prefix-tree hbase-protocol hbase-rest hbase-server hbase-shell hbase-testing-util hbase-thrift
find . -name "*.cmd"|xargs rm -f
配置
进入配置文件目录
cd $HBASE_HOME/conf
编辑 hbase-site.xml





hbase.rootdir
hdfs://hdfs-cdh/hbase




hbase.cluster.distributed
true




hbase.master
cdh-m1:60000,cdh-m2:60000




hbase.zookeeper.quorum
cdh-m1:2181,cdh-m2:2181,cdh-s1:2181




zookeeper.znode.parent
/hbase




hbase.coprocessor.user.region.classes
org.apache.hadoop.hbase.coprocessor.AggregateImplementation

在 hbase-env.sh 中添加如下变量
export JAVA_HOME=/data/app/jdk1.7
export HBASE_LOG_DIR=/data/logs/hbase
export HBASE_PID_DIR=/data/pid
export HBASE_MANAGES_ZK=false
# SSH 默认端口 可选
export HBASE_SSH_OPTS="-o ConnectTimeout=1 -p 36000"
Heap 设置,单位 MB
export HBASE_HEAPSIZE=1024
可选设置 regionservers 中添加所有RegionServer主机名,用于集群批量启动、停止
 
启动与停止
切换用户
su - run
集群批量启动
需要配置运行用户ssh-key免密码登录,与$HBASE_HOME/conf/regionservers
# 启动
start-hbase.sh
# 停止
stop-hbase.sh
单服务启动停止
HMaster
# 启动
hbase-daemon.sh start master
# 停止
hbase-daemon.sh stop master
HRegionServer
# 启动
hbase-daemon.sh start regionserver
# 停止
hbase-daemon.sh stop regionserver

测试
HBase HA 测试
浏览器打开两个HMaster状态页:
http://cdh-m1:60010
http://cdh-m2:60010 

可以在Master后面看见其中一个主机名,Backup Masters中看见另一个。
停止当前Master,刷新另一个HMaster状态页会发现Master后面已经切换,HA成功。
 
HBase 测试
进入hbase shell 执行:
create 'users','user_id','address','info'
list
put 'users','anton','info:age','24'
get 'users','anton'

# 最终结果
COLUMN CELL
info:age timestamp=1465972035945, value=24
1 row(s) in 0.0170 seconds
清除测试数据:
disable 'users'
drop 'users'
到这里安装就全部完成,不懂的地方可以留言交流!

Elasticsearch Recovery详解

大数据 OpenSkill 发表了文章 0 个评论 2652 次浏览 2016-09-08 23:56 来自相关话题

基础知识点 在Eleasticsearch中recovery指的就是一个索引的分片分配到另外一个节点的过程;一般在快照恢复、索引副本数变更、节点故障、节点重启时发生。由于master保存整个集群的状态信息,因此可以判断出哪些shard ...查看全部


基础知识点


在Eleasticsearch中recovery指的就是一个索引的分片分配到另外一个节点的过程;一般在快照恢复、索引副本数变更、节点故障、节点重启时发生。由于master保存整个集群的状态信息,因此可以判断出哪些shard需要做再分配,以及分配到哪个结点,例如:
  1. 如果某个shard主分片在,副分片所在结点挂了,那么选择另外一个可用结点,将副分片分配(allocate)上去,然后进行主从分片的复制。
  2. 如果某个shard的主分片所在结点挂了,副分片还在,那么将副分片升级为主分片,然后做主从分片复制。
  3. 如果某个shard的主副分片所在结点都挂了,则暂时无法恢复,等待持有相关数据的结点重新加入集群后,从该结点上恢复主分片,再选择另外的结点复制副分片。

 
正常情况下,我们可以通过ES的health的API接口,查看整个集群的健康状态和整个集群数据的完整性:
EsHealth.png

状态及含义如下:
  • green: 所有的shard主副分片都是正常的;
  • yellow: 所有shard的主分片都完好,部分副分片没有或者不完整,数据完整性依然完好;
  • red: 某些shard的主副分片都没有了,对应的索引数据不完整。
 recovery过程要消耗额外的资源,CPU、内存、结点之间的网络带宽等等。 这些额外的资源消耗,有可能会导致集群的服务性能下降,或者一部分功能暂时不可用。了解一些recovery的过程和相关的配置参数,对于减小recovery带来的资源消耗,加快集群恢复过程都是很有帮助的。 

减少集群Full Restart造成的数据来回拷贝

ES集群可能会有整体重启的情况,比如需要升级硬件、升级操作系统或者升级ES大版本。重启所有结点可能带来的一个问题: 某些结点可能先于其他结点加入集群, 先加入集群的结点可能已经可以选举好master,并立即启动了recovery的过程,由于这个时候整个集群数据还不完整,master会指示一些结点之间相互开始复制数据。 那些晚到的结点,一旦发现本地的数据已经被复制到其他结点,则直接删除掉本地“失效”的数据。 当整个集群恢复完毕后,数据分布不均衡,显然是不均衡的,master会触发rebalance过程,将数据在节点之间挪动。整个过程无谓消耗了大量的网络流量;合理设置recovery相关参数则可以防范这种问题的发生。
gateway.expected_nodesgateway.expected_master_nodesgateway.expected_data_nodes
以上三个参数是说集群里一旦有多少个节点就立即开始recovery过程。 不同之处在于,第一个参数指的是master或者data都算在内,而后面两个参数则分指master和data node。 在期待的节点数条件满足之前, recovery过程会等待gateway.recover_after_time (默认5分钟) 这么长时间,一旦等待超时,则会根据以下条件判断是否启动:
gateway.recover_after_nodesgateway.recover_after_master_nodesgateway.recover_after_data_nodes
 举例来说,对于一个有10个data node的集群,如果有以下的设置:
gateway.expected_data_nodes: 10gateway.recover_after_time: 5mgateway.recover_after_data_nodes: 8
那么集群5分钟以内10个data node都加入了,或者5分钟以后8个以上的data node加入了,都会立即启动recovery过程。 

减少主副本之间的数据复制

如果不是full restart,而是重启单个data node,仍然会造成数据在不同结点之间来回复制。为避免这个问题,可以在重启之前,先关闭集群的shard allocation:
EsNone.png
然后在节点重启完成加入集群后,再重新打开:
EsAll.png
这样在节点重启完成后,尽量多的从本地直接恢复数据。但是在ES1.6版本之前,即使做了以上措施,仍然会发现有大量主副本之间的数据拷贝。从表面去看,这点很让人不能理解。 主副本数据完全一致,ES应该直接从副本本地恢复数据就好了,为什么要重新从主片再复制一遍呢? 原因在于recovery是简单对比主副本的segment file来判断哪些数据一致可以本地恢复,哪些不一致需要远端拷贝的。而不同节点的segment merge是完全独立运行的,可能导致主副本merge的深度不完全一样,从而造成即使文档集完全一样,产生的segment file却不完全一样。 为了解决这个问题,ES1.6版本以后加入了synced flush的新特性。 对于5分钟没有更新过的shard,会自动synced flush一下,实质是为对应的shard加了一个synced flush ID。这样当重启节点的时候,先对比一下shard的synced flush ID,就可以知道两个shard是否完全相同,避免了不必要的segment file拷贝,极大加快了冷索引的恢复速度。 需要注意的是synced flush只对冷索引有效,对于热索引(5分钟内有更新的索引)没有作用。 如果重启的结点包含有热索引,那么还是免不了大量的文件拷贝。因此在重启一个结点之前,最好按照以下步骤执行,recovery几乎可以瞬间完成:[list=1]
  • 暂停数据写入程序
  • 关闭集群shard allocation
  • 手动执行POST /_flush/synced
  • 重启节点
  • 重新开启集群shard allocation 
  • 等待recovery完成,集群health status变成green
  • 重新开启数据写入程序
  •  

    特大热索引为何恢复慢

    对于冷索引,由于数据不再更新,利用synced flush特性,可以快速直接从本地恢复数据。 而对于热索引,特别是shard很大的热索引,除了synced flush派不上用场需要大量跨节点拷贝segment file以外,translog recovery是导致慢的更重要的原因。 从主片恢复数据到副片需要经历3个阶段:[list=1]
  • 对主片上的segment file做一个快照,然后拷贝到复制片分配到的结点。数据拷贝期间,不会阻塞索引请求,新增索引操作记录到translog里。
  • 对translog做一个快照,此快照包含第一阶段新增的索引请求,然后重放快照里的索引操作。此阶段仍然不阻塞索引请求,新增索引操作记录到translog里。
  • 为了能达到主副片完全同步,阻塞掉新索引请求,然后重放阶段二新增的translog操作。

  •  
    可见,在recovery完成之前,translog是不能够被清除掉的(禁用掉正常运作期间后台的flush操作)。如果shard比较大,第一阶段耗时很长,会导致此阶段产生的translog很大。重放translog比起简单的文件拷贝耗时要长得多,因此第二阶段的translog耗时也会显著增加。等到第三阶段,需要重放的translog可能会比第二阶段还要多。 而第三阶段是会阻塞新索引写入的,在对写入实时性要求很高的场合,就会非常影响用户体验。 因此,要加快大的热索引恢复速度,最好的方式是遵从上一节提到的方法: 暂停新数据写入,手动sync flush,等待数据恢复完成后,重新开启数据写入,这样可以将数据延迟影响可以降到最低。
     
    万一遇到Recovery慢,想知道进度怎么办呢? CAT Recovery API可以显示详细的recovery各个阶段的状态。 这个API怎么用就不在这里赘述了,参考: CAT Recovery
     


    其他Recovery相关的专家级设置


    还有其他一些专家级的设置(参见: recovery)可以影响recovery的速度,但提升速度的代价是更多的资源消耗,因此在生产集群上调整这些参数需要结合实际情况谨慎调整,一旦影响应用要立即调整回来。 对于搜索并发量要求高,延迟要求低的场合,默认设置一般就不要去动了。 对于日志实时分析类对于搜索延迟要求不高,但对于数据写入延迟期望比较低的场合,可以适当调大indices.recovery.max_bytes_per_sec,提升recovery速度,减少数据写入被阻塞的时长。
     
    最后要说的一点是ES的版本迭代很快,对于Recovery的机制也在不断的优化中。 其中有一些版本甚至引入了一些bug,比如在ES1.4.x有严重的translog recovery bug,导致大的索引trans log recovery几乎无法完成 (issue #9226)  。因此实际使用中如果遇到问题,最好在Github的issue list里搜索一下,看是否使用的版本有其他人反映同样的问题。
    分享阅读原文:http://elasticsearch.cn/article/38

    京东咚咚架构演进

    智慧运维 koyo 发表了文章 0 个评论 1636 次浏览 2016-08-24 22:26 来自相关话题

    咚咚是什么?咚咚之于京东相当于旺旺之于淘宝,它们都是服务于买家和卖家的沟通。 自从京东开始为第三方卖家提供入驻平台服务后,咚咚也就随之诞生了。 我们首先看看它诞生之初是什么样的。   1.0 诞生(2010 - 2011) ...查看全部
    dongdong.png

    咚咚是什么?咚咚之于京东相当于旺旺之于淘宝,它们都是服务于买家和卖家的沟通。 自从京东开始为第三方卖家提供入驻平台服务后,咚咚也就随之诞生了。 我们首先看看它诞生之初是什么样的。
     


    1.0 诞生(2010 - 2011)​


    为了业务的快速上线,1.0 版本的技术架构实现是非常直接且简单粗暴的。 如何简单粗暴法?请看架构图,如下:
    Arch1.png

    1.0 的功能十分简单,实现了一个 IM 的基本功能,接入、互通消息和状态。 另外还有客服功能,就是顾客接入咨询时的客服分配,按轮询方式把顾客分配给在线的客服接待。 用开源 Mina 框架实现了 TCP 的长连接接入,用 Tomcat Comet 机制实现了 HTTP 的长轮询服务。 而消息投递的实现是一端发送的消息临时存放在 Redis 中,另一端拉取的生产消费模型。

    这个模型的做法导致需要以一种高频率的方式来轮询 Redis 遍历属于自己连接的关联会话消息。 这个模型很简单,简单包括多个层面的意思:理解起来简单;开发起来简单;部署起来也简单。 只需要一个 Tomcat 应用依赖一个共享的 Redis,简单的实现核心业务功能,并支持业务快速上线。

    但这个简单的模型也有些严重的缺陷,主要是效率和扩展问题。 轮询的频率间隔大小基本决定了消息的延时,轮询越快延时越低,但轮询越快消耗也越高。 这个模型实际上是一个高功耗低效能的模型,因为不活跃的连接在那做高频率的无意义轮询。 高频有多高呢,基本在 100 ms 以内,你不能让轮询太慢,比如超过 2 秒轮一次,人就会在聊天过程中感受到明显的会话延迟。 随着在线人数增加,轮询的耗时也线性增长,因此这个模型导致了扩展能力和承载能力都不好,一定会随着在线人数的增长碰到性能瓶颈。

    1.0 的时代背景正是京东技术平台从 .NET 向 Java 转型的年代,我也正是在这期间加入京东并参与了京东主站技术转型架构升级的过程。 之后开始接手了京东咚咚,并持续完善这个产品,进行了三次技术架构演进。
     


    2.0 成长(2012)


    我们刚接手时 1.0 已在线上运行并支持京东 POP(开放平台)业务,之后京东打算组建自营在线客服团队并落地在成都。 不管是自营还是 POP 客服咨询业务当时都起步不久,1.0 架构中的性能和效率缺陷问题还没有达到引爆的业务量级。 而自营客服当时还处于起步阶段,客服人数不足,服务能力不够,顾客咨询量远远超过客服的服务能力。 超出服务能力的顾客咨询,当时我们的系统统一返回提示客服繁忙,请稍后咨询。 这种状况导致高峰期大量顾客无论怎么刷新请求,都很可能无法接入客服,体验很差。 所以 2.0 重点放在了业务功能体验的提升上,如下图所示:
    Arch2.png

    针对无法及时提供服务的顾客,可以排队或者留言。 针对纯文字沟通,提供了文件和图片等更丰富的表达方式。 另外支持了客服转接和快捷回复等方式来提升客服的接待效率。 总之,整个 2.0 就是围绕提升客服效率和用户体验。 而我们担心的效率问题在 2.0 高速发展业务的时期还没有出现,但业务量正在逐渐积累,我们知道它快要爆了。 到 2012 年末,度过双十一后开始了 3.0 的一次重大架构升级。
     


    3.0 爆发(2013 - 2014)


    经历了 2.0 时代一整年的业务高速发展,实际上代码规模膨胀的很快。 与代码一块膨胀的还有团队,从最初的 4 个人到近 30 人。 团队大了后,一个系统多人开发,开发人员层次不一,规范难统一,系统模块耦合重,改动沟通和依赖多,上线风险难以控制。 一个单独 tomcat 应用多实例部署模型终于走到头了,这个版本架构升级的主题就是服务化。

    服务化的第一个问题如何把一个大的应用系统切分成子服务系统。 当时的背景是京东的部署还在半自动化年代,自动部署系统刚起步,子服务系统若按业务划分太细太多,部署工作量很大且难管理。 所以当时我们不是按业务功能分区服务的,而是按业务重要性级别划分了 0、1、2 三个级别不同的子业务服务系统。 另外就是独立了一组接入服务,针对不同渠道和通信方式的接入端,见下图。
    Arch3.png

    更细化的应用服务和架构分层方式可见下图:
    Arch4.png

    这次大的架构升级,主要考虑了三个方面:稳定性、效率和容量。 做了下面这些事情:
    1. 业务分级、核心、非核心业务隔离
    2. 多机房部署,流量分流、容灾冗余、峰值应对冗余
    3. 读库多源,失败自动转移
    4. 写库主备,短暂有损服务容忍下的快速切换
    5. 外部接口,失败转移或快速断路
    6. Redis 主备,失败转移
    7. 大表迁移,MongoDB 取代 MySQL 存储消息记录
    8. 改进消息投递模型

     
    前 6 条基本属于考虑系统稳定性、可用性方面的改进升级。 这一块属于陆续迭代完成的,承载很多失败转移的配置和控制功能在上面图中是由管控中心提供的。 第 7 条主要是随着业务量的上升,单日消息量越来越大后,使用了 MongoDB 来单独存储量最大的聊天记录。 第 8 条是针对 1.0 版本消息轮询效率低的改进,改进后的投递方式如下图所示:
    Arch5.png

    不再是轮询了,而是让终端每次建立连接后注册接入点位置,消息投递前定位连接所在接入点位置再推送过去。 这样投递效率就是恒定的了,而且很容易扩展,在线人数越多则连接数越多,只需要扩展接入点即可。 其实,这个模型依然还有些小问题,主要出在离线消息的处理上,可以先思考下,我们最后再讲。

    3.0 经过了两年的迭代式升级,单纯从业务量上来说还可以继续支撑很长时间的增长。 但实际上到 2014 年底我们面对的不再是业务量的问题,而是业务模式的变化。 这直接导致了一个全新时代的到来。
     


    4.0 涅槃(2015 至今 )


    2014 年京东的组织架构发生了很大变化,从一个公司变成了一个集团,下设多个子公司。 原来的商城成为了其中一个子公司,新成立的子公司包括京东金融、京东智能、京东到家、拍拍、海外事业部等。 各自业务范围不同,业务模式也不同,但不管什么业务总是需要客服服务。 如何复用原来为商城量身订做的咚咚客服系统并支持其他子公司业务快速接入成为我们新的课题。

    最早要求接入的是拍拍网,它是从腾讯收购的,所以是完全不同的账户和订单交易体系。 由于时间紧迫,我们把为商城订做的部分剥离,基于 3.0 架构对接拍拍又单独订做了一套,并独立部署,像下面这样。
    Arch6.png

    虽然在业务要求的时间点前完成了上线,但这样做也带来了明显的问题:
    1. 复制工程,定制业务开发,多套源码维护成本高
    2. 独立部署,至少双机房主备外加一个灰度集群,资源浪费大

     
    以前我们都是面向业务去架构系统,如今新的业务变化形势下我们开始考虑面向平台去架构,在统一平台上跑多套业务,统一源码,统一部署,统一维护。 把业务服务继续拆分,剥离出最基础的 IM 服务,IM 通用服务,客服通用服务,而针对不同的业务特殊需求做最小化的定制服务开发。 部署方式则以平台形式部署,不同的业务方的服务跑在同一个平台上,但数据互相隔离。 服务继续被拆分的更微粒化,形成了一组服务矩阵(见下图)
    Arch7.png

    而部署方式,只需要在双机房建立两套对等集群,并另外建一个较小的灰度发布集群即可,所有不同业务都运行在统一平台集群上,如下图:
    Arch8.png

    更细粒度的服务意味着每个服务的开发更简单,代码量更小,依赖更少,隔离稳定性更高。 但更细粒度的服务也意味着更繁琐的运维监控管理,直到今年公司内部弹性私有云、缓存云、消息队列、部署、监控、日志等基础系统日趋完善, 使得实施这类细粒度划分的微服务架构成为可能,运维成本可控。 而从当初 1.0 的 1 种应用进程,到 3.0 的 6、7 种应用进程,再到 4.0 的 50+ 更细粒度的不同种应用进程。 每种进程再根据承载业务流量不同分配不同的实例数,真正的实例进程数会过千。 为了更好的监控和管理这些进程,为此专门定制了一套面向服务的运维管理系统,见下图:
    Arch9.png

    统一服务运维提供了实用的内部工具和库来帮助开发更健壮的微服务。 包括中心配置管理,流量埋点监控,数据库和缓存访问,运行时隔离,如下图所示是一个运行隔离的图示:
    Arch10.png

    细粒度的微服务做到了进程间隔离,严格的开发规范和工具库帮助实现了异步消息和异步 HTTP 来避免多个跨进程的同步长调用链。 进程内部通过切面方式引入了服务增强容器 Armor 来隔离线程, 并支持进程内的单独业务降级和同步转异步化执行。而所有这些工具和库服务都是为了两个目标:
    1. 让服务进程运行时状态可见
    2. 让服务进程运行时状态可被管理和改变


    最后我们回到前文留下的一个悬念,就是关于消息投递模型的缺陷。 一开始我们在接入层检测到终端连接断开后,消息无法投递,再将消息缓存下来,等终端重连接上来再拉取离线消息。 这个模型在移动时代表现的很不好,因为移动网络的不稳定性,导致经常断链后重连。 而准确的检测网络连接断开是依赖一个网络超时的,导致检测可能不准确,引发消息假投递成功。 新的模型如下图所示,它不再依赖准确的网络连接检测,投递前待确认消息 id 被缓存,而消息体被持久存储。 等到终端接收确认返回后,该消息才算投妥,未确认的消息 id 再重新登陆后或重连接后作为离线消息推送。 这个模型不会产生消息假投妥导致的丢失,但可能导致消息重复,只需由客户终端按消息 id 去重即可。
    Arch11.png

    京东咚咚诞生之初正是京东技术转型到 Java 之时,经历这些年的发展,取得了很大的进步。 从草根走向专业,从弱小走向规模,从分散走向统一,从杂乱走向规范。 本文主要重心放在了几年来咚咚架构演进的过程,技术架构单独拿出来看我认为没有绝对的好与不好, 技术架构总是要放在彼时的背景下来看,要考虑业务的时效价值、团队的规模和能力、环境基础设施等等方面。 架构演进的生命周期适时匹配好业务的生命周期,才可能发挥最好的效果。
    分享阅读原文:http://www.cnblogs.com/mindwind/p/5017591.html 

    图解Python 集合

    编程艺术 空心菜 发表了文章 2 个评论 2019 次浏览 2016-08-10 23:41 来自相关话题

    集合基本功能 集合是一个无序的,不重复的数据组合,用{}表示,它的主要作用如下: 去重,把一个列表变成集合,就会自动去重关系测试,测试两组数据之前的交集、差集、并集、子集等关系  集合创建: >>> ...查看全部


    集合基本功能


    集合是一个无序的,不重复的数据组合,用{}表示,它的主要作用如下:
    1. 去重,把一个列表变成集合,就会自动去重
    2. 关系测试,测试两组数据之前的交集、差集、并集、子集等关系

     集合创建:
    >>> set_job = set(['DEV', 'OPS', 'DBA', 'QA', 'Sales'])
    >>> set_man = set(('lucky', 'jack', 'andy', 'tom', 'andy', 'jim'))
    >>> print(set_job, type(set_job))
    {'DEV', 'OPS', 'Sales', 'QA', 'DBA'}
    >>> print(set_man, type(set_man)) # 天生去重,只有一个andy了
    {'andy', 'jack', 'lucky', 'tom', 'jim'}

     
    元素添加:
    >>> set_job = set(['DEV', 'OPS', 'DBA', 'QA', 'Sales'])
    >>> set_job.add('HR') # add方法只能添加一个
    >>> print(set_job)
    {'QA', 'HR', 'Sales', 'DEV', 'OPS', 'DBA'}
    >>> set_job.update(['FD', 'MD', 'MD'])
    >>> print(set_job)
    {'QA', 'HR', 'Sales', 'DEV', 'MD', 'OPS', 'FD', 'DBA'}
    >>> set_job.update(('AD', 'PD')) # update方法可以添加是列表或者元组,去重,如果添加的为一个单独字符串,则把字符串拆成字母添加到集合中
    >>> print(set_job)
    {'QA', 'HR', 'PD', 'Sales', 'DEV', 'MD', 'OPS', 'AD', 'FD', 'DBA'}

    元素删除:
    >>> set_job = {'QA', 'HR', 'PD', 'Sales', 'DEV', 'MD', 'OPS', 'AD', 'FD', 'DBA'}
    >>> set_job.remove('PD') # 删除指定元素
    >>> set_job.remove('xx') # 元素不存在则报错 KeyError
    Traceback (most recent call last):
    File "", line 1, in
    KeyError: 'xx'
    >>> print(set_job)
    {'QA', 'HR', 'MD', 'DEV', 'Sales', 'OPS', 'AD', 'FD', 'DBA'}
    >>> set_job.pop() # 随机删除一个元素
    'QA'
    >>> print(set_job)
    {'HR', 'MD', 'DEV', 'Sales', 'OPS', 'AD', 'FD', 'DBA'}
    >>> set_job.discard('OPS') # 指定删除
    >>> set_job.discard('xxx') # 不存在返回None,不会报KeyError
    >>> print(set_job)
    {'HR', 'MD', 'DEV', 'Sales', 'AD', 'FD', 'DBA'}

    其他:
    >>> set_job = {'QA', 'HR', 'PD', 'Sales', 'DEV', 'MD', 'OPS', 'AD', 'FD', 'DBA'}
    >>> len(set_job)  # 集合长度
    10
    >>> 'QA' in set_job  # 判断是否在集合中
    True
    >>> 'XXX' not in set_job # 不在集合中
    True
    >>> for i in set_job:   # 循环
    ...     print(i)


    集合关系测试


    交集:
    intercaiton.png
    >>> set_a = {5, 6, 7, 8, 9, 10}
    >>> set_b = {1, 2, 3, 4, 5, 6}
    >>> print(set_a.intersection(set_b)) # 常规方式
    {5, 6}
    >>> print(set_a & set_b) # 运算符(&)方式
    {5, 6}

    并集
    bingji.png
    >>> set_a = {5, 6, 7, 8, 9, 10}
    >>> set_b = {1, 2, 3, 4, 5, 6}
    >>> set_c = set_a.union(set_b)    # 关键字union做并集运算 先后顺序无关,谁并谁都可以
    >>> print(set_c)
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    >>> 
    >>> set_c = set_a | set_b     # 运算符关键符 | 做并集运算  先后顺序无关,谁并谁都可以
    >>> print(set_c)
    {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    差集
    chaji.png
    >>> set_a = {5, 6, 7, 8, 9, 10}
    >>> set_b = {1, 2, 3, 4, 5, 6}
    >>> set_c = set_a - set_b # a集合跟b集合做差集 关键符 -
    >>> print(set_c)
    {8, 9, 10, 7}
    >>> set_d = set_b - set_a # b集合跟a集合做差集 关键符 -
    >>> print(set_d)
    {1, 2, 3, 4}
    >>> set_c = set_a.difference(set_b) # a集合跟b集合做差集 关键字difference
    >>> print(set_c)
    {8, 9, 10, 7}
    >>> set_d = set_b.difference(set_a) # b集合跟a集合做差集 关键字difference
    >>> print(set_d)
    {1, 2, 3, 4}

     
    子集父集
    fuziji.png

    拿苹果来打比方就是,把苹果掰开,然后掰开的一小部分就是子集,然后整个苹果就是父集
    >>> set_a = {5, 6, 7, 8, 9, 10}
    >>> set_b = {1, 2, 3, 4, 5, 6}
    >>> set_c = {7, 8, 9, 10}
    >>> set_d = {1, 2, 3, 4}
    >>> set_e = {5, 6}
    >>> set_f = {11, 12, 13, 14, 15, 16}
    >>> set_c.issubset(set_a) # 测试集合c是否是集合a的子集 放回布尔值 关键字issubset
    True
    >>> set_d.issubset(set_b) # 测试集合d是否是集合b的子集 返回布尔值 issubset
    True
    >>> set_e.issubset(set_a)
    True
    >>> set_e.issubset(set_b)
    True
    >>> set_e <= set_a # 测试集合e是否是集合a的子集 关键符 <=
    True
    >>> set_e <= set_b
    True
    >>> set_f.issuperset(set_a) # 测试f集合是否是a集合的父集
    False
    >>> set_a.issuperset(set_e) # 测试a集合是否是集合e的父集 关键字issuperset
    True
    >>> set_b >= set_e # 测试集合b是否是集合e的父集 关键符 >=
    True
    >>> set_b >= set_d
    True

    对称差集
    对称差集就是两个集合去掉相同的部分,然后剩下的所有元素组成的集合
    duichengchaji.png
    >>> set_a = {5, 6, 7, 8, 9, 10}
    >>> set_b = {1, 2, 3, 4, 5, 6}
    >>> set_c = set_a.symmetric_difference(set_b) # 集合a和集合b做对称差集 关键字symmetric_difference
    >>> print(set_c)
    {1, 2, 3, 4, 7, 8, 9, 10}
    >>> set_c = set_a ^ set_b # 集合a和集合b做对称差集 关键符 ^
    >>> print(set_c)
    {1, 2, 3, 4, 7, 8, 9, 10}
    >>> set_c = set_b ^ set_a
    >>> print(set_c)
    {1, 2, 3, 4, 7, 8, 9, 10}

    所有方法:
    class set(object):
    """
    set() -> new empty set object
    set(iterable) -> new set object

    Build an unordered collection of unique elements.
    """
    def add(self, *args, **kwargs): # real signature unknown
    """
    Add an element to a set.

    This has no effect if the element is already present.
    """
    pass

    def clear(self, *args, **kwargs): # real signature unknown
    """ Remove all elements from this set. """
    pass

    def copy(self, *args, **kwargs): # real signature unknown
    """ Return a shallow copy of a set. """
    pass

    def difference(self, *args, **kwargs): # real signature unknown
    """
    Return the difference of two or more sets as a new set.

    (i.e. all elements that are in this set but not the others.)
    """
    pass

    def difference_update(self, *args, **kwargs): # real signature unknown
    """ Remove all elements of another set from this set. """
    pass

    def discard(self, *args, **kwargs): # real signature unknown
    """
    Remove an element from a set if it is a member.

    If the element is not a member, do nothing.
    """
    pass

    def intersection(self, *args, **kwargs): # real signature unknown
    """
    Return the intersection of two sets as a new set.

    (i.e. all elements that are in both sets.)
    """
    pass

    def intersection_update(self, *args, **kwargs): # real signature unknown
    """ Update a set with the intersection of itself and another. """
    pass

    def isdisjoint(self, *args, **kwargs): # real signature unknown
    """ Return True if two sets have a null intersection. """
    pass

    def issubset(self, *args, **kwargs): # real signature unknown
    """ Report whether another set contains this set. """
    pass

    def issuperset(self, *args, **kwargs): # real signature unknown
    """ Report whether this set contains another set. """
    pass

    def pop(self, *args, **kwargs): # real signature unknown
    """
    Remove and return an arbitrary set element.
    Raises KeyError if the set is empty.
    """
    pass

    def remove(self, *args, **kwargs): # real signature unknown
    """
    Remove an element from a set; it must be a member.

    If the element is not a member, raise a KeyError.
    """
    pass

    def symmetric_difference(self, *args, **kwargs): # real signature unknown
    """
    Return the symmetric difference of two sets as a new set.

    (i.e. all elements that are in exactly one of the sets.)
    """
    pass

    def symmetric_difference_update(self, *args, **kwargs): # real signature unknown
    """ Update a set with the symmetric difference of itself and another. """
    pass

    def union(self, *args, **kwargs): # real signature unknown
    """
    Return the union of sets as a new set.

    (i.e. all elements that are in either set.)
    """
    pass

    def update(self, *args, **kwargs): # real signature unknown
    """ Update a set with the union of itself and others. """
    pass

    Python菜鸟之路基础篇(一)

    编程艺术 空心菜 发表了文章 3 个评论 2548 次浏览 2016-07-26 00:55 来自相关话题

    Hello World 学习任何一门语言,我想大家写的以一句就是hello world吧,下面我们来看看Python的hello world 创建一个hello.py的文件:print ("Hello World")然后执 ...查看全部


    Hello World


    学习任何一门语言,我想大家写的以一句就是hello world吧,下面我们来看看Python的hello world
    创建一个hello.py的文件:
    print ("Hello World")
    然后执行命令:python hello.py ,输出
    crh:Python crh$ python3 hello.py 
    Hello World

     
    Python执行过程为: 把代码读到内存 ---->词法语法分析 ---->放到编译器 ----> 生成字节码 ---->执行字节码 ---->生成机器码---->CPU执行,图示如下:
    workfollow.png

    指定解释器
    在上面情况,我们指定Python3 来执行hello.py
    如果想要类似于执行shell脚本一样执行python脚本,例: ./hello.py ,那么就需要在 hello.py 文件的头部指定解释器,如下:
    crh:Python crh$ cat hello.py 
    #!/usr/bin/env python3

    print ("Hello World")
    像上面这样,然后给hello.py文件添加执行权限(chmod +x hello.py)就可以像执行shell脚本一样./hello.py 即可。
    ***像上面是利用Linux env命令通过环境变量去找到你想用的Python命令,如果你指定用某个Python版本的话一可以写绝对路径,比如:/usr/bin/python or /usr/local/bin/python3
     
    在交互器中执行
    除了把程序写在文件里,还可以直接调用python自带的交互器运行代码,进行调试和测试
    crh:Python crh$ python3
    Python 3.5.1 (default, Dec 26 2015, 18:08:53)
    [GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    [quote]>> print ("Hello World")
    Hello World
    >>>
     


    变量


    在一个计算机程序中引用变量是用来存储信息和操作的。他们还提供一种标签数据与一个描述性的名称,所以我们的程序可以被读者和我们自己更清楚地理解。它有助于认为变量保存信息的容器。他们的唯一目的是标签和数据存储在内存中。这些数据可以通过使用程序引用。
     
    声明变量
    #_[i]_coding:utf-8_[/i]_

    name = "Lucky chen"
    如上面所示声明了一个变量name,他的值是"Lucky chen"
     
    我们再来看一组连续赋值的过程:
    >>> name = "crh"
    >>> name1 = name
    >>> print (name,name1)
    crh crh
    >>> name = "Lucky"
    >>> print (name1,name)
    crh Lucky
    如上所示,为什么name1 = name 当后续把name变量的值改变了,为什么name1的值没有随着改变呢,如下看看你就明白了:
    >>> name = "crh"     #首先在内存中打开一块内存空间存储name变量值
    >>> id(name) #查看变量name的内存地址
    4412833952
    >>> name1 = name #把我们把name赋值给name1变量
    >>> id(name1) #然后查看name1变量的内存地址 (跟name变量的内存地址一样)
    4412833952
    >>> name = "lucky" #重新打开一个内存空间存储name的变量值
    >>> id(name) #查看新的name变量值的内存地址
    4412834176
    >>> id(name1) #查看name1变量内存地址
    4412833952
    如上所示,我们可以看出,nama1其实就是借助name变量做一个变量的赋值,通过name变量得到值所在内存中的内存地址后,从而变成了一个正常的赋值过程。而不换随着name变量的内存空间地址的改变而改变。示意图如下:
    var.png


    交互输入


    用户输入就是程序和用户的交互,程序等待用户输入一个参数然后重新继续进行:
    #!/usr/bin/env python3
    #Authe: Lucky.chen
    #_[i]_coding:utf-8_[/i]_

    name = input("Please enter your name:")
    print ("[size=16]#",name,"[/size]#")
    input.png

    如上图所示,默认Python3下的input函数用户输入的所有东西都当做字符串处理,所以你输入的年龄希望它是整数类型,还需要int()一下。
     
    在python2中input这个函数,用户默认输入的是什么格式的参数,Python就当作是什么类型处理,Python2中获取用户输入参数的函数还有一个叫做raw_input(),这个函数默认也是字符串处理。可以看出Python3为了简洁统一,在Python3中已经不存在了raw_input()函数,input()一个函数完全可以做到所有使用。
     
    平常如果我们用针对密码、密码串之类的交互的话,一般用户的输入时隐藏或者*****的,输入密码时,在Python下如果你需要你输入的东西不可见,可以利用getpass 模块中的 getpass方法来处理:
    #!/usr/bin/env python3
    #Authe: Lucky.chen
    #_[i]_coding:utf-8_[/i]_[/quote]

    #导入getpass模块
    import getpass

    # 将用户输入的内容赋值给 name 变量
    pwd = getpass.getpass("请输入密码:")

    # 打印输入的内容
    print("Password is [size=16]",pwd,"***print end")[/size]

    #比如我输入110119,执行过程和结果如下:
    请输入密码:
    Password is [size=16] 110119 ***print end
    [/size]


    注释和拼接


    注释:
    单行注释:# 开头就好,跟shell一样
    多行注释:成对的三个单引号 ''' 注释内容 ''' 或者 成对的三个双引号 """  注释内容  """
    >>> ''' My name is lucky ''' 
    ' My name is lucky '
    [quote]>> """ My age is 23 """
    ' My age is 23 '
    >>> # Good Idea
    ...
    >>> msg = """ My info is :
    ... age: 23
    ... name: lucky
    ... job: IT"""
    >>> print (msg)
    My info is :
    age: 23
    name: lucky
    job: IT
    >>>
     
    拼接:
    1、难受的 "+" 
    >>> name = "lucky"
    >>> age = 23
    >>> job = "IT"
    >>>
    >>> msg = """ Info of """ +name + """
    ... Name:""" +name + """
    ... Age:""" +age + """
    ... Job:""" +job
    Traceback (most recent call last):
    File "", line 4, in
    TypeError: Can't convert 'int' object to str implicitly
    >>>
    >>> name = "lucky"
    >>> age = "23"
    >>> job = "IT"
    >>> msg = """ Info of """ +name + """
    ... Name:""" +name + """
    ... Age:""" +age + """
    ... Job:""" +job
    >>> print (msg)
    Info of lucky
    Name:lucky
    Age:23
    Job:IT
    >>>
    如上所示 "+"拼接只能是字符串,如果是整型、或者是浮点都会报错。
     
    2、百分号(%)
     Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen[/quote]

    name = input("Please input your name: ")
    age = input("Please input your age:")
    job = input("Please input your job:")
    salary = input("Please input your salary:")

    msg = """------------ info of %s
    Name: %s
    Age: %s
    Job: %s
    Salary: %s
    """ % (name,name,age,job,salary)

    print (msg)
    Result:
    Please input your name: lucky
    Please input your age:23
    Please input your job:IT
    Please input your salary:40000
    ------------ info of lucky
    Name: lucky
    Age: 23
    Job: IT
    Salary: 40000

     3、使用format函数
    情况一:使用变量格式化
    Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    name = input("Please input your name: ")
    age = input("Please input your age:")
    job = input("Please input your job:")
    salary = input("Please input your salary:")

    msg = """------------ info of {_Name}
    Name: {_Name}
    Age: {_Age}
    Job: {_Job}
    Salary: {_Salary}
    """.format(_Name=name,_Age=age,_Job=job,_Salary=salary)

    print (msg)
    Result:
    Please input your name: crh
    Please input your age:23
    Please input your job:IT
    Please input your salary:45000
    ------------ info of crh
    Name: crh
    Age: 23
    Job: IT
    Salary: 45000
    情况二:使用下脚标
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    name = input('please input your name:')
    age = input('please input your age:')
    job = input('please input your job:')
    salary = input('please input your salary:')
    msg = '''
    ------------info of {0}-----------
    Name: {0}
    Age: {1}
    Job: {2}
    Salary: {3}
    '''.format(name,age,job,salary)
    print (msg)
    Result:
    please input your name:chenronghua
    please input your age:23
    please input your job:OPS
    please input your salary:50000

    ------------info of chenronghua-----------
    Name: chenronghua
    Age: 23
    Job: OPS
    Salary: 50000
    format是比较好的方式,有时候我们必须使用format方法,所以掌握了format就好。
     


    流程控制


    一、流程控制这里先介绍 if ...... else  and if ...... elif ..... else 
     
    1、if ..... else(用户认证登录)
    Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    name = input("Please input your name:")
    passwd = input("Please input your password:")

    if name == "crh" and passwd == "123456":
    print (" \033[32mWelcome login OPS Management platform\033[0m ")
    else:
    print ("\033[31mYour UserName or Password Error\033[0m")
    Result:
    Please input your name:crh
    Please input your password:34
    Your UserName or Password Error

     2、if ..... elfi ...... else
    Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    age = int(input("Pleast input your age:"))

    if age < 18:
    print (" \033[32m You're too young \033[0m ")
    elif age > 18 and age < 30:
    print ("\033[31m You still have many chance comes at a time when youth waiting for you \033[0m")
    else:
    print ("\033[33m Before you is too old to do \033[0m")
    Result:
    Pleast input your age:23
    You still have many chance comes at a time when youth waiting for you

     二、for 循环遍历
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    for num in range(5):
    print ("loop is:",num)

    # Result is:
    loop is: 0
    loop is: 1
    loop is: 2
    loop is: 3
    loop is: 4
    range函数也可以设置步长值,比如我们要打印出1-10中的所有偶数(默认步长为1)
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    for num in range(0,11,2):
    print ("loop is:",num)

    # Result
    loop is: 0
    loop is: 2
    loop is: 4
    loop is: 6
    loop is: 8
    loop is: 10
    除掉range函数我们还可以使用xrange函数,为什么要这里要介绍xrange呢,因为xrange相对于range来说性能比较优越,因为xrange不需要一上来就开辟一块很大的内存空间,具体可以参考我之前发布的文章:Python中xrange和range的异同 ,但是好像Python3.*中没有了xrange函数。
     
    for...else...循环介绍,不止if中有else,在for循环中也是可以用else的,在for循环中的else就是当前面的循环正常执行完后,没有跳出,后面的else代码将被执行。
    crh:Python crh$ cat for.py 
    #!/usr/bin/env python3
    #Auther: lucky.chen

    for num in range(4):
    print ("Loop is:",num)
    else:
    print ("normal")
    crh:Python crh$ ./for.py
    Loop is: 0
    Loop is: 1
    Loop is: 2
    Loop is: 3
    normal
    crh:Python crh$
    crh:Python crh$ cat for.py 
    #!/usr/bin/env python3
    #Auther: lucky.chen

    for num in range(4):
    if num > 2:
    break
    print ("Loop is:",num)
    else:
    print ("normal")
    crh:Python crh$ ./for.py
    Loop is: 0
    Loop is: 1
    Loop is: 2
    crh:Python crh$
    三、while循环遍历
     
    while 循环它的原理是:当条件为真的时候运行,当条件为假的时候停止!没有一个规定次数,不设置条件就永远循环下去。
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    import time
    count = 0
    while True:
    count +=1
    print ("loop",count)
    time.sleep(3)
    #这个循环3秒钟自+1后,无线循环只要这个条件为”真“,就无限循环下去
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    import time
    num = 0

    while num < 3:
    num +=1
    print ("Num is:",num)
    time.sleep(3)

    #这个循环每3秒循环一次,当条件num < 3的时候为真(自己设置的条件),当num不小于3的时候为假(false)循环停止.
    While ...... else
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    while 1:
    if num == 4:
    print ("I think stop")
    break
    print (num)
    num += 1
    else:
    print ("stop")

     四、break和continue介绍
    break在循环中的作用是跳出所在的循环体,不在进行循环,而continue是跳出所在循环体中的本次循环,后续没有完的循环继续。
    Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    num = 1

    print ("test break for loop start")
    for n in range(5):
    if n == num:
    break
    print (n)

    print ("\n")

    print ("test continue for loop start")
    for n in range(5):
    if n == num:
    continue
    print (n)
    Result:
    test break for loop start
    0


    test continue for loop start
    0
    2
    3
    4

    猜数字游戏:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    #load module (random)
    import random

    TryNum = 0
    RandNum = random.randrange(10)
    print (RandNum)


    while TryNum < 3:
    GuessNum = int(input("请猜测从0到9之间的一个中奖数字:"))

    if GuessNum >= 10:
    print ("你输入的数字不在中奖号码范围内,请重新输入!")
    continue

    if GuessNum == RandNum:
    print ("恭喜你猜对了,你将获得小米电视一台!")
    break

    elif GuessNum > RandNum:
    print ("你猜的数字太大了可以再往小了猜")

    else:
    print ("你猜的数字太小了可以往大了猜")

    TryNum += 1

    else:
    print ("不好意思你三次机会用完了,Game over!")

    #先随机到0-9中筛选出以为数字,然后用户三次机会猜测一个中奖号码,如果用户输入的数字不在范围内,则让用户再次输入.
    Result:
    4
    请猜测从0到9之间的一个中奖数字:11
    你输入的数字不在中奖号码范围内,请重新输入!
    请猜测从0到9之间的一个中奖数字:11
    你输入的数字不在中奖号码范围内,请重新输入!
    请猜测从0到9之间的一个中奖数字:11
    你输入的数字不在中奖号码范围内,请重新输入!
    请猜测从0到9之间的一个中奖数字:11
    你输入的数字不在中奖号码范围内,请重新输入!
    请猜测从0到9之间的一个中奖数字:2
    你猜的数字太小了可以往大了猜
    请猜测从0到9之间的一个中奖数字:5
    你猜的数字太大了可以再往小了猜
    请猜测从0到9之间的一个中奖数字:4
    恭喜你猜对了,你将获得小米电视一台!

     
    五、嵌套循环
    While for:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    count = 1

    while count < 4:
    print("count var lt 4")

    print ("#########################[size=16]#")[/size]

    for n in range(3):
    print ("for num is:",n)

    print ("****************************")
    count += 1
    Result:
    count var lt 4
    #########################[size=16]#[/size]
    for num is: 0
    for num is: 1
    for num is: 2
    ****************************
    count var lt 4
    #########################[size=16]#[/size]
    for num is: 0
    for num is: 1
    for num is: 2
    ****************************
    count var lt 4
    #########################[size=16]#[/size]
    for num is: 0
    for num is: 1
    for num is: 2
    ****************************

     死循环:
    #!/usr/bin/env python3.5
    #auther: lucky.chen
    import time

    while True:
    print ("One Loop")
    time.sleep(1)

    while True:
    print ("Two Loop")
    time.sleep(1)

    while True:
    print ("Three Loop")
    time.sleep(1)
    #这是一个死循环,第一次执行这段code的时候,依次往下执行,单到了第三个while的时候,就一直是true,所以一直在执行第三个while下的code.

    #结果如下:
    One Loop
    Two Loop
    Three Loop
    Three Loop
    Three Loop
    Three Loop
    ..........
    ..........
    如上我们给出了一个死循环的例子,但是如果如果我们需要跳出循环应该怎么做,如果用break可以做到吗?
    1、第一个while后加break分析
    loop1.png

     
    2、第二个while后加break分析
    loop2.png

     
    3、第三个while后加break分析
    loop3.png

     
    既然存在这种死循环的那我们有什么办法可以跳出呢?那就是打标志,标志位
    Code:
    #!/usr/bin/env python3.5
    #auther: lucky.chen

    count = 0
    while True:
    print ("我是第一层")
    jump_1_flag = False

    while True:
    print ("我是第二层")
    jump_2_flag = False

    while True:
    count += 1
    print ("我是第三层")

    if count > 3:
    jump_2_flag = True
    break

    if jump_2_flag:
    print ("第三层跳到我这里来了,我也要跳到第一层")
    jump_1_flag = True
    break

    if jump_1_flag:
    print ("第二层和第三层跳到第一层了,我也要跳")
    break
    分析和结果:
    tags.png

    强制清除Elasticsearch中已删除的文件

    大数据 空心菜 发表了文章 0 个评论 6280 次浏览 2016-06-05 01:14 来自相关话题

     Elasticsearch是建立在Apache Lucene 基础上的实时分布式搜索引擎,Lucene为了提高搜索的实时性,采用不可再修改(immutable)方式将文档存储在一个个segment中。也就是说,一个segment在写入到存储系统之后,将不可以 ...查看全部
     Elasticsearch是建立在Apache Lucene 基础上的实时分布式搜索引擎,Lucene为了提高搜索的实时性,采用不可再修改(immutable)方式将文档存储在一个个segment中。也就是说,一个segment在写入到存储系统之后,将不可以再修改。那么Lucene是如何从一个segment中删除一个被索引的文档呢?简单的讲,当用户发出命令删除一个被索引的文档#ABC时,该文档并不会被马上从相应的存储它的segment中删除掉,而是通过一个特殊的文件来标记该文档已被删除。当用户再次搜索到#ABC时,Elasticsearch在segment中仍能找到#ABC,但由于#ABC文档已经被标记为删除,所以Lucene会从发回给用户的搜索结果中剔除#ABC,所以给用户感觉的是#ABC已经被删除了。
     
     Elasticseach会有后台线程根据Lucene的合并规则定期进行segment merging 合并操作,一般不需要用户担心或者采取任何行动。被删除的文档在segment合并时,才会被真正删除掉。在此之前,它仍然会占用着JVM heap和操作系统的文件cache等资源。在某些情况下,我们需要强制Elasticsearch进行segment merging,已释放其占用的大量系统资源。
                           POST /{index}/_optimize?only_expunge_deletes=true&wait_for_completion=true
    _optimize命令可强制进行segment合并,并删除所有标记为删除的文档。Segment merging要消耗CPU,以及大量的I/O资源,所以一定要在你的ElasticSearch集群处于维护窗口期间,并且有足够的I/O空间的(如:SSD)的条件下进行;否则很可能造成集群崩溃和数据丢失。
     
    下图展示了我们在进行强制expunge时,所观察到的CPU和磁盘I/O的使用情况。该集群运行在微软的Azure云平台IaaS虚拟机上,所有的数据节点都采用 D13 虚拟机,数据存储在本地的SSD磁盘中。该集群是一个备份集群,为了保证合并顺利进行,在此期间暂停了所有对其进行的写操作,仅有少量的读操作。这里需要注意: expunge操作是一种不得已而为之的操作,即在Elasticsearch不能有效自动清除删除文件的情况下才执行该操作。同时建议在此操作期间,最好停止对集群的所有读/写操作,并暂停止shard的自动分配 (cluster.routing.allocation.enable= none),以防有节点被踢出后shard自动分配造成的数据丢失。
    Cpu.png

    sec.png

    下面两个设置可以用于控制清除时的处理速度,其中给出值是默认值,可以根据需求进行调整,具体请参见Merge。此外, 还可以临时将所有索引的replica设置为0,这样只用针对Primary进行expunge,以减小I/O压力。
    PUT /{index}/_settings
    {
    "settings": {
    "index.merge.policy.expunge_deletes_allowed": "10",
    "index.merge.policy.max_merge_at_once_explicit" : "30"
    }
    }
    参考资料:Lucene‘s Handling of Deleted Documents.
    分享阅读:http://blog.csdn.net/quicknet/article/details/46421505

    大型网站系统架构演化之路

    智慧运维 being 发表了文章 0 个评论 1849 次浏览 2016-01-10 16:26 来自相关话题

    前言 一个成熟的大型网站(如淘宝、天猫、腾讯等)的系统架构并不是一开始设计时就具备完整的高性能、高可用、高伸缩等特性的,它是随着用户量的增加,业务功能的扩展逐渐演变完善的,在这个过程中,开发模式、技术架构、设计思想也发生了很大的变化, ...查看全部


    前言


    一个成熟的大型网站(如淘宝、天猫、腾讯等)的系统架构并不是一开始设计时就具备完整的高性能、高可用、高伸缩等特性的,它是随着用户量的增加,业务功能的扩展逐渐演变完善的,在这个过程中,开发模式、技术架构、设计思想也发生了很大的变化,就连技术人员也从几个人发展到一个部门甚至一条产品线。所以成熟的系统架构是随着业务的扩展而逐步完善的,并不是一蹴而就;不同业务特征的系统,会有各自的侧重点,例如淘宝要解决海量的商品信息的搜索、下单、支付;例如腾讯要解决数亿用户的实时消息传输;百度它要处理海量的搜索请求;他们都有各自的业务特性,系统架构也有所不同。尽管如此我们也可以从这些不同的网站背景下,找出其中共用的技术,这些技术和手段广泛运用在大型网站系统的架构中,下面就通过介绍大型网站系统的演化过程,来认识这些技术和手段。


    一、最开始的网站架构


    最初的架构,应用程序、数据库、文件都部署在一台服务器上,如图:
    website_arch1.png


    二、应用、数据、文件分离


    随着业务的扩展,一台服务器已经不能满足性能需求,故将应用程序、数据库、文件各自部署在独立的服务器上,并且根据服务器的用途配置不同的硬件,达到最佳的性能效果。
    website_arch2.png


    三、利用缓存改善网站性能


    在硬件优化性能的同时,同时也通过软件进行性能优化,在大部分的网站系统中,都会利用缓存技术改善系统的性能,使用缓存主要源于热点数据的存在,大部分网站访问都遵循28原则(即80%的访问请求,最终落在20%的数据上),所以我们可以对热点数据进行缓存,减少这些数据的访问路径,提高用户体验。
    website_arch3.png

    缓存实现常见的方式是本地缓存、分布式缓存。当然还有CDN、反向代理等,这个后面再讲。本地缓存,顾名思义是将数据缓存在应用服务器本地,可以存在内存中,也可以存在文件,OSCache就是常用的本地缓存组件。本地缓存的特点是速度快,但因为本地空间有限所以缓存数据量也有限。分布式缓存的特点是,可以缓存海量的数据,并且扩展非常容易,在门户类网站中常常被使用,速度按理没有本地缓存快,常用的分布式缓存是Memcached、Redis。


    四、使用集群改善应用服务器性能


    应用服务器作为网站的入口,会承担大量的请求,我们往往通过应用服务器集群来分担请求数。应用服务器前面部署负载均衡服务器调度用户请求,根据分发策略将请求分发到多个应用服务器节点。
    website_arch4.png

    常用的负载均衡技术硬件的有F5,价格比较贵,软件的有LVS、Nginx、HAProxy。LVS是四层负载均衡,根据目标地址和端口选择内部服务器,Nginx和HAProxy是七层负载均衡,可以根据报文内容选择内部服务器,因此LVS分发路径优于Nginx和HAProxy,性能要高些,而Nginx和HAProxy则更具配置性,如可以用来做动静分离(根据请求报文特征,选择静态资源服务器还是应用服务器)。


    五、数据库读写分离和分库分表


    随着用户量的增加,数据库成为最大的瓶颈,改善数据库性能常用的手段是进行读写分离以及分库分表,读写分离顾名思义就是将数据库分为读库和写库,通过主备功能实现数据同步。分库分表则分为水平切分和垂直切分,水平切分则是对一个数据库特大的表进行拆分,例如用户表。垂直切分则是根据业务的不同来切分,如用户业务、商品业务相关的表放在不同的数据库中。
    website_arch5.png


    六、使用CDN和反向代理提高网站性能


    假如我们的服务器都部署在成都的机房,对于四川的用户来说访问是较快的,而对于北京的用户访问是较慢的,这是由于四川和北京分别属于电信和联通的不同发达地区,北京用户访问需要通过互联路由器经过较长的路径才能访问到成都的服务器,返回路径也一样,所以数据传输时间比较长。对于这种情况,常常使用CDN解决,CDN将数据内容缓存到运营商的机房,用户访问时先从最近的运营商获取数据,这样大大减少了网络访问的路径。比较专业的CDN运营商有蓝汛、网宿。
    website_arch6.png

    而反向代理,则是部署在网站的机房,当用户请求达到时首先访问反向代理服务器,反向代理服务器将缓存的数据返回给用户,如果没有缓存数据才会继续访问应用服务器获取,这样做减少了获取数据的成本。反向代理有Squid,Nginx。


    七、使用分布式文件系统


    用户一天天增加,业务量越来越大,产生的文件越来越多,单台的文件服务器已经不能满足需求,这时就需要分布式文件系统的支撑。常用的分布式文件系统有GFS、HDFS、TFS。
    website_arch7.png


    八、使用NoSql和搜索引擎


    对于海量数据的查询和分析,我们使用nosql数据库加上搜索引擎可以达到更好的性能。并不是所有的数据都要放在关系型数据中。常用的NOSQL有mongodb、hbase、redis,搜索引擎有lucene、solr、elasticsearch。
    website_arch8.png


    九、将应用服务器进行业务拆分


    随着业务进一步扩展,应用程序变得非常臃肿,这时我们需要将应用程序进行业务拆分,如百度分为新闻、网页、图片等业务。每个业务应用负责相对独立的业务运作。业务之间通过消息进行通信或者共享数据库来实现。
    website_arch9.png


    十、搭建分布式服务


    这时我们发现各个业务应用都会使用到一些基本的业务服务,例如用户服务、订单服务、支付服务、安全服务,这些服务是支撑各业务应用的基本要素。我们将这些服务抽取出来利用分部式服务框架搭建分布式服务。阿里的Dubbo是一个不错的选择。
    website_arch10.png


    总结


    大型网站的架构是根据业务需求不断完善的,根据不同的业务特征会做特定的设计和考虑,本文只是讲述一个常规大型网站会涉及的一些技术和手段。


    分享阅读整理原文:http://www.cnblogs.com/leefreeman/p/3993449.html
    参考书籍:《大型网站技术架构》《海量运维运营规划》


    更多话题 >>

    热门话题

    股票

    3 个问题, 1 人关注

    Go进阶笔记

    2 个问题, 1 人关注

    更多用户 >>

    热门用户

    peanut

    6 个问题, 3 次赞同

    chris

    9 个问题, 10 次赞同

    OS小编

    5 个问题, 3 次赞同

    空心菜

    148 个问题, 106 次赞同