优大网

分类存档: 开发经验 ( 1 / 2)

单点登录怎么实现?

一、单点登录简介

假设一个场景:公司内部有财务、OA、订单服务等各类相互独立的应用系统,员工张三对这些系统有操作权限,如果张三想要登录某个系统进行业务操作,那么他需要输入相应的账号与密码。想象一下,当公司内部有 100 个应用系统,张三是不是要输入 100 次用户名和密码进行登录,然后分别才能进行业务操作呢?显然这是很不好的体验,因此我们需要引入一个这样的机制:张三只要输入一次用户名和密码登录,成功登录后,他就可以访问财务系统、OA 系统、订单服务等系统。这就是单点登录。

1

单点登录的英文全称是 Single Sign On,简称是 SSO。它的意思是说用户只需要登录一次,就可以在个人权限范围内,访问所有相互信任应用的功能模块,不管整个应用群的内部有多么复杂,对用户而言,都是一个统一的整体。用户访问 Web 系统的整个应用群与访问单个系统一样,登录和注销分别只要一次就够了。举个简单的例子,你登录了百度网页之后,点击跳转到百度贴吧,这时可以发现你已经自动登录了百度贴吧。

二、我们的技术实现
2.5

SSO 的技术实现要想做好并不容易,我们认为需求优先级应该先是单点登录和单点注销功能,然后是应用接入的门槛,最后是数据安全性,安全性对于 SSO 也非常重要。SSO 的核心是认证中心,但要实现用户一次登录,到处访问的效果,技术实现需要建立在用户系统、认证中心、权限系统、企业门户的基础上,各职责如下:

  • 用户系统:负责用户名、密码等帐户信息管理,包括增加、修改、启用、停用用户帐号,同时为认证中心提供对用户名和密码的校验。
  • 认证中心:负责凭证 token 的生成、加密、颁发、验证、销毁、登入 Login、登出 Logout。用户只有拥有凭证并验证通过才能访问企业门户。
  • 权限系统:负责角色管理、资源设置、授权设置、鉴定权限,具体实现可参考 RBAC。权限系统可为企业门户提供用户权限范围内的导航。
  • 企业门户:作为应用系统的集成门户 (Portal),集成了多个应用系统的功能,为用户提供链接导航、用户信息和登出功能等。
 2.1、服务端功能实现
  • 登录认证:接收登录帐号信息,让用户系统验证用户的登录信息。
  • 凭证生成:创建授权凭证 token,生成的凭证一般包含用户帐号信息、过期时间等信息,它是一串加密的字符串,加密算法如 AES{凭证明文 +MD5 加信息},可采用 JWT 标准。
  • 凭证颁发:与 SSO 客户端通信,发送凭证给 SSO 客户端。
  • 凭证验证:接收并校验来自 SSO 客户端的凭证有效性,凭证验证包括算法验证和数据验证。
  • 凭证销毁与登出:接收来自 SSO 客户端的登出请求,记录并销毁凭证,跳转至登录页面。
 2.2、客户端功能实现
  • 1、请求拦截:拦截应用未登录请求,跳转至登录页面。
  • 2、获取凭证:接收并存储由 SSO 服务端发来的凭证,凭证存储的方式有 Cookie、Session、网址传参、Header 等。
  • 3、提交凭证验证:与 SSO 服务端通信,发出校验凭证有效性的请求。
  • 4、获取用户权限:获取该凭证的用户权限,并返回受保护资源给用户。
  • 5、凭证销毁与登出:销毁本地会话,然后跳转至登出页面。
 2.3、用户单点登录流程
3
  • 登录:将用户输入的用户名和密码发送至认证中心,然后认证中心调用用户系统来验证登录信息。
  • 生成并颁发凭证:通过登录信息的验证后,认证中心创建授权凭证 token,然后把这个授权凭证 token 返回给 SSO 客户端。SSO 客户端拿到这个 token,进行存储。在后续请求中,在 HTTP 请求数据中都得加上这个 token。
  • 凭证验证:SSO 客户端发送凭证 token 给认证中心,认证中心校验这个 token 的有效性。凭证验证有算法验证和数据验证,算法验证可在 SSO 客户端完成。
 2.4、用户访问流程和单点注销

4

以上是用户的访问流程,如果用户没有有效的凭证,认证中心将强制用户进入登录流程。对于单点注销,用户如果注销了应用群内的其中一个应用,那么全局 token 也会被销毁,应用群内的所有应用将不能再被访问。

 2.5、具体接入与集成
2.5

我们的应用接入与集成具体如下:

  • 1、用户系统:接入国内机票平台的用户系统,负责登录认证。
  • 2、权限系统:接入国内机票平台的权限系统。
  • 3、认证中心:负责生成并颁发凭证、销毁凭证,改造国内机票平台的登入、登出。
  • 4、凭证验证:在国内机票、国际机票应用系统中调用 SSO 客户端组件实现凭证的验证。
  • 5、企业门户:由国内机票平台、国际机票平台承担。
三、JWT 标准
3.0

JSON Web Token (JWT) 是目前应用最为广泛的 token 格式,是为了在网络应用环境间传递声明而执行的一种基于 JSON 的开放标准(RFC 7519)。该 token 设计紧凑且安全,特别适用于分布式站点的单点登录、API 网关等场景。JWT 的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息。该 token 也可直接被用于认证,也可被加密。JWT 信息体由 3 部分构成:头 Header+ 载荷 Payload+ 签名 Signature,具体优点如下:

  • JWT 支持多种语言,C#、Java、JavaScript、Node.js、PHP 等很多语言都可以使用。
  • JWT 可以自身存储一些和业务逻辑有关的所必要的非敏感信息,因为有了 Payload 部分。
  • 利于传输,因为 JWT 的构成非常简单,字节占用很小。
  • 不需要在服务端保存会话信息,不仅省去服务端资源开销,而且使得应用易于扩展。
作者介绍

杨丽,拥有多年互联网应用系统研发经验,曾就职于古大集团,现任职中青易游的系统架构师,主要负责公司研发中心业务系统的架构设计以及新技术积累和培训。现阶段主要关注开源软件、软件架构、微服务以及大数据。

张辉清,10 多年的 IT 老兵,先后担任携程架构师、古大集团首席架构、中青易游 CTO 等职务,主导过两家公司的技术架构升级改造工作。现关注架构与工程效率,技术与业务的匹配与融合,技术价值与创新。

https://mp.weixin.qq.com/s?__biz=MzIwMzg1ODcwMw==&mid=2247487340&idx=1&sn=4063fdba487a860b8a576332c1ea3eb8&chksm=96c9b90ca1be301a985edbc1c2e340bdaf748364231636a33857233263ccdb847204451aae6b#rd

一文总结学习 Python 的 14 张思维导图

https://blog.csdn.net/csdnnews/article/details/78248699

本文主要涵盖了 Python 编程的核心知识(暂不包括标准库及第三方库)。

按顺序依次展示了以下内容的一系列思维导图:基础知识,数据类型(数字,字符串,列表,元组,字典,集合),条件&循环,文件对象,错误&异常,函数,模块,面向对象编程。

14 张思维导图

基础知识

数据类型

序列

字符串

列表 & 元组

字典 & 集合

条件 & 循环

文件对象

错误 & 异常

函数

模块

面向对象编程

作者:ZY,来源:ZOE | 数林觅风 ,解释权归原作者所有,侵删。

淘宝大秒系统设计详解

导读:最初的秒杀系统的原型是淘宝详情上的定时上架功能,由于有些卖家为了吸引眼球,把价格压得很低。但这给的详情系统带来了很大压力,为了将这种突发流量隔离,才设计了秒杀系统,文章主要介绍大秒系统以及这种典型读数据的热点问题的解决思路和实践经验。

一些数据

大家还记得2013年的小米秒杀吗?三款小米手机各11万台开卖,走的都是大秒系统,3分钟后成为双十一第一家也是最快破亿的旗舰店。经过日志统计,前端系统双11峰值有效请求约60w以上的QPS ,而后端cache的集群峰值近2000w/s、单机也近30w/s,但到真正的写时流量要小很多了,当时最高下单减库存tps是红米创造,达到1500/s。

热点隔离

秒杀系统设计的第一个原则就是将这种热点数据隔离出来,不要让1%的请求影响到另外的99%,隔离出来后也更方便对这1%的请求做针对性优化。针对秒杀我们做了多个层次的隔离:

  • 业务隔离。把秒杀做成一种营销活动,卖家要参加秒杀这种营销活动需要单独报名,从技术上来说,卖家报名后对我们来说就是已知热点,当真正开始时我们可以提前做好预热。
  • 系统隔离。系统隔离更多是运行时的隔离,可以通过分组部署的方式和另外99%分开。秒杀还申请了单独的域名,目的也是让请求落到不同的集群中。
  • 数据隔离。秒杀所调用的数据大部分都是热数据,比如会启用单独cache集群或MySQL数据库来放热点数据,目前也是不想0.01%的数据影响另外99.99%。

当然实现隔离很有多办法,如可以按照用户来区分,给不同用户分配不同cookie,在接入层路由到不同服务接口中;还有在接入层可以对URL的不同Path来设置限流策略等。服务层通过调用不同的服务接口;数据层可以给数据打上特殊的标来区分。目的都是把已经识别出来的热点和普通请求区分开来。

动静分离

前面介绍在系统层面上的原则是要做隔离,接下去就是要把热点数据进行动静分离,这也是解决大流量系统的一个重要原则。如何给系统做动静分离的静态化改造我以前写过一篇《高访问量系统的静态化架构设计》详细介绍了淘宝商品系统的静态化设计思路,感兴趣的可以在《程序员》杂志上找一下。我们的大秒系统是从商品详情系统发展而来,所以本身已经实现了动静分离,如图1。

图片描述

图1 大秒系统动静分离
除此之外还有如下特点:

  • 把整个页面Cache在用户浏览器
  • 如果强制刷新整个页面,也会请求到CDN
  • 实际有效请求只是“刷新抢宝”按钮

这样把90%的静态数据缓存在用户端或者CDN上,当真正秒杀时用户只需要点击特殊的按钮“刷新抢宝”即可,而不需要刷新整个页面,这样只向服务端请求很少的有效数据,而不需要重复请求大量静态数据。秒杀的动态数据和普通的详情页面的动态数据相比更少,性能也比普通的详情提升3倍以上。所以“刷新抢宝”这种设计思路很好地解决了不刷新页面就能请求到服务端最新的动态数据。

基于时间分片削峰

熟悉淘宝秒杀的都知道,第一版的秒杀系统本身并没有答题功能,后面才增加了秒杀答题,当然秒杀答题一个很重要的目的是为了防止秒杀器,2011年秒杀非常火的时候,秒杀器也比较猖獗,而没有达到全民参与和营销的目的,所以增加的答题来限制秒杀器。增加答题后,下单的时间基本控制在2s后,秒杀器的下单比例也下降到5%以下。新的答题页面如图2。

图片描述

图2 秒答题页面
其实增加答题还有一个重要的功能,就是把峰值的下单请求给拉长了,从以前的1s之内延长到2~10s左右,请求峰值基于时间分片了,这个时间的分片对服务端处理并发非常重要,会减轻很大压力,另外由于请求的先后,靠后的请求自然也没有库存了,也根本到不了最后的下单步骤,所以真正的并发写就非常有限了。其实这种设计思路目前也非常普遍,如支付宝的“咻一咻”已及微信的摇一摇。

除了在前端通过答题在用户端进行流量削峰外,在服务端一般通过锁或者队列来控制瞬间请求。

数据分层校验

图片描述

图3 分层校验
对大流量系统的数据做分层校验也是最重要的设计原则,所谓分层校验就是对大量的请求做成“漏斗”式设计,如图3所示:在不同层次尽可能把无效的请求过滤,“漏斗”的最末端才是有效的请求,要达到这个效果必须对数据做分层的校验,下面是一些原则:

  • 先做数据的动静分离
  • 将90%的数据缓存在客户端浏览器
  • 将动态请求的读数据Cache在Web端
  • 对读数据不做强一致性校验
  • 对写数据进行基于时间的合理分片
  • 对写请求做限流保护
  • 对写数据进行强一致性校验

秒杀系统正是按照这个原则设计的系统架构,如图4所示。

图片描述

图4 秒杀系统分层架构
把大量静态不需要检验的数据放在离用户最近的地方;在前端读系统中检验一些基本信息,如用户是否具有秒杀资格、商品状态是否正常、用户答题是否正确、秒杀是否已经结束等;在写数据系统中再校验一些如是否是非法请求,营销等价物是否充足(淘金币等),写的数据一致性如检查库存是否还有等;最后在数据库层保证数据最终准确性,如库存不能减为负数。

实时热点发现

其实秒杀系统本质是还是一个数据读的热点问题,而且是最简单一种,因为在文提到通过业务隔离,我们已能提前识别出这些热点数据,我们可以提前做一些保护,提前识别的热点数据处理起来还相对简单,比如分析历史成交记录发现哪些商品比较热门,分析用户的购物车记录也可以发现那些商品可能会比较好卖,这些都是可以提前分析出来的热点。比较困难的是那种我们提前发现不了突然成为热点的商品成为热点,这种就要通过实时热点数据分析了,目前我们设计可以在3s内发现交易链路上的实时热点数据,然后根据实时发现的热点数据每个系统做实时保护。 具体实现如下:

  • 构建一个异步的可以收集交易链路上各个中间件产品如Tengine、Tair缓存、HSF等本身的统计的热点key(Tengine和Tair缓存等中间件产品本身已经有热点统计模块)。
  • 建立一个热点上报和可以按照需求订阅的热点服务的下发规范,主要目的是通过交易链路上各个系统(详情、购物车、交易、优惠、库存、物流)访问的时间差,把上游已经发现的热点能够透传给下游系统,提前做好保护。比如大促高峰期详情系统是最早知道的,在统计接入层上Tengine模块统计的热点URL。
  • 将上游的系统收集到热点数据发送到热点服务台上,然后下游系统如交易系统就会知道哪些商品被频繁调用,然后做热点保护。如图5所示。

图片描述

图5 实时热点数据后台
重要的几个:其中关键部分包括:

  • 这个热点服务后台抓取热点数据日志最好是异步的,一方面便于做到通用性,另一方面不影响业务系统和中间件产品的主流程。
  • 热点服务后台、现有各个中间件和应用在做的没有取代关系,每个中间件和应用还需要保护自己,热点服务后台提供一个收集热点数据提供热点订阅服务的统一规范和工具,便于把各个系统热点数据透明出来。
  • 热点发现要做到实时(3s内)。

关键技术优化点

前面介绍了一些如何设计大流量读系统中用到的原则,但是当这些手段都用了,还是有大流量涌入该如何处理呢?秒杀系统要解决几个关键问题。

Java处理大并发动态请求优化

其实Java和通用的Web服务器相比(Nginx或Apache)在处理大并发HTTP请求时要弱一点,所以一般我们都会对大流量的Web系统做静态化改造,让大部分请求和数据直接在Nginx服务器或者Web代理服务器(Varnish、Squid等)上直接返回(可以减少数据的序列化与反序列化),不要将请求落到Java层上,让Java层只处理很少数据量的动态请求,当然针对这些请求也有一些优化手段可以使用:

  • 直接使用Servlet处理请求。避免使用传统的MVC框架也许能绕过一大堆复杂且用处不大的处理逻辑,节省个1ms时间,当然这个取决于你对MVC框架的依赖程度。
  • 直接输出流数据。使用resp.getOutputStream()而不是resp.getWriter()可以省掉一些不变字符数据编码,也能提升性能;还有数据输出时也推荐使用JSON而不是模板引擎(一般都是解释执行)输出页面。

同一商品大并发读问题

你会说这个问题很容易解决,无非放到Tair缓存里面就行,集中式Tair缓存为了保证命中率,一般都会采用一致性Hash,所以同一个key会落到一台机器上,虽然我们的Tair缓存机器单台也能支撑30w/s的请求,但是像大秒这种级别的热点商品还远不够,那如何彻底解决这种单点瓶颈?答案是采用应用层的Localcache,即在秒杀系统的单机上缓存商品相关的数据,如何cache数据?也分动态和静态:

  • 像商品中的标题和描述这些本身不变的会在秒杀开始之前全量推送到秒杀机器上并一直缓存直到秒杀结束。
  • 像库存这种动态数据会采用被动失效的方式缓存一定时间(一般是数秒),失效后再去Tair缓存拉取最新的数据。

你可能会有疑问,像库存这种频繁更新数据一旦数据不一致会不会导致超卖?其实这就要用到我们前面介绍的读数据分层校验原则了,读的场景可以允许一定的脏数据,因为这里的误判只会导致少量一些原本已经没有库存的下单请求误认为还有库存而已,等到真正写数据时再保证最终的一致性。这样在数据的高可用性和一致性做平衡来解决这种高并发的数据读取问题。

同一数据大并发更新问题

解决大并发读问题采用Localcache和数据的分层校验的方式,但是无论如何像减库存这种大并发写还是避免不了,这也是秒杀这个场景下最核心的技术难题。

同一数据在数据库里肯定是一行存储(MySQL),所以会有大量的线程来竞争InnoDB行锁,当并发度越高时等待的线程也会越多,TPS会下降RT会上升,数据库的吞吐量会严重受到影响。说到这里会出现一个问题,就是单个热点商品会影响整个数据库的性能,就会出现我们不愿意看到的0.01%商品影响99.99%的商品,所以一个思路也是要遵循前面介绍第一个原则进行隔离,把热点商品放到单独的热点库中。但是无疑也会带来维护的麻烦(要做热点数据的动态迁移以及单独的数据库等)。

分离热点商品到单独的数据库还是没有解决并发锁的问题,要解决并发锁有两层办法。

  • 应用层做排队。按照商品维度设置队列顺序执行,这样能减少同一台机器对数据库同一行记录操作的并发度,同时也能控制单个商品占用数据库连接的数量,防止热点商品占用太多数据库连接。
  • 数据库层做排队。应用层只能做到单机排队,但应用机器数本身很多,这种排队方式控制并发仍然有限,所以如果能在数据库层做全局排队是最理想的,淘宝的数据库团队开发了针对这种MySQL的InnoDB层上的patch,可以做到数据库层上对单行记录做到并发排队,如图6所示。

图片描述

图6 数据库层对单行记录并发排队
你可能会问排队和锁竞争不要等待吗?有啥区别?如果熟悉MySQL会知道,InnoDB内部的死锁检测以及MySQL Server和InnoDB的切换会比较耗性能,淘宝的MySQL核心团队还做了很多其他方面的优化,如COMMIT_ON_SUCCESS和ROLLBACK_ON_FAIL的patch,配合在SQL里面加hint,在事务里不需要等待应用层提交COMMIT而在数据执行完最后一条SQL后直接根据TARGET_AFFECT_ROW结果提交或回滚,可以减少网络的等待时间(平均约0.7ms)。据我所知,目前阿里MySQL团队已将这些patch及提交给MySQL官方评审。

大促热点问题思考

以秒杀这个典型系统为代表的热点问题根据多年经验我总结了些通用原则:隔离、动态分离、分层校验,必须从整个全链路来考虑和优化每个环节,除了优化系统提升性能,做好限流和保护也是必备的功课。

除去前面介绍的这些热点问题外,淘系还有多种其他数据热点问题:

  • 数据访问热点,比如Detail中对某些热点商品的访问度非常高,即使是Tair缓存这种Cache本身也有瓶颈问题,一旦请求量达到单机极限也会存在热点保护问题。有时看起来好像很容易解决,比如说做好限流就行,但你想想一旦某个热点触发了一台机器的限流阀值,那么这台机器Cache的数据都将无效,进而间接导致Cache被击穿,请求落地应用层数据库出现雪崩现象。这类问题需要与具体Cache产品结合才能有比较好的解决方案,这里提供一个通用的解决思路,就是在Cache的client端做本地Localcache,当发现热点数据时直接Cache在client里,而不要请求到Cache的Server。
  • 数据更新热点,更新问题除了前面介绍的热点隔离和排队处理之外,还有些场景,如对商品的lastmodifytime字段更新会非常频繁,在某些场景下这些多条SQL是可以合并的,一定时间内只执行最后一条SQL就行了,可以减少对数据库的update操作。另外热点商品的自动迁移,理论上也可以在数据路由层来完成,利用前面介绍的热点实时发现自动将热点从普通库里迁移出来放到单独的热点库中。

按照某种维度建的索引产生热点数据,比如实时搜索中按照商品维度关联评价数据,有些热点商品的评价非常多,导致搜索系统按照商品ID建评价数据的索引时内存已经放不下,交易维度关联订单信息也同样有这些问题。这类热点数据需要做数据散列,再增加一个维度,把数据重新组织。

作者简介:许令波,2009年加入淘宝,目前负责商品详情业务和稳定性相关工作,长期关注性能优化领域,参与了淘宝高访问量Web系统主要的优化项目,著有《深入分析Java Web技术内幕》一书。个人网站http://xulingbo.net

如何看待自己写的烂代码

如果你不是入行不久的新程序员,你很可能会遇到一些你曾经写过的老代码,看到它们,你可能会有这样的反应:

哦,shit!这是什么?当时我脑袋进水了?

我就这样过。我的朋友和同事们都经历过。你很可能也发生过这样的事情。

最近我的一些前同事联系到我,问我是否能帮助他们做一些前端开发工作。我想着挣一些外快也不错,而且,这个公司里我曾经工作过两年半,他们都是优秀的程序员。

昨晚,我遇到了一些之前在那个公司里任职时自己写的JavaScript老代码,下面是我的反应:

  1. 怀旧追思: 哇塞!这是当年我写的啊!
  2. 羞耻: 我靠,怎么写成这样。应该使用更好的方法。真惭愧,当时怎么会把这样的代码放到产品中。
  3. 困:不早了,我该睡觉了。
  4. 骄傲:这段代码运行的良好——虽然不是最佳算法。更妙的是,我是5年前写的它,计算一下,它至少被成功的执行了70亿次!

早上醒来时我感觉好多了,不再为我那不是很完美的代码而忧愁。写代码不是为了美,是为了价值。所以,下次,当你再看到自己的一年前、5年前、甚至更多年前写的让人难为情的“烂代码”时,如果它们还在产品库中,还在生产着价值,你应该为它们骄傲!

.NET技术+25台服务器怎样支撑世界第54大网站

http://www.csdn.net/article/2014-07-22/2820774-stackoverflow-update-560m-pageviews-a-month-25-servers

摘要:同时使用Linux和Windows平台产品,大量使用静态的方法和类,Stack Overflow是个重度性能控。同时,取代横向扩展,他们坚持着纵向扩展思路,因为“硬件永远比程序员便宜”。

【编者按】StackOverflow是一个IT技术问答网站,用户可以在网站上提交和回答问题。当下的StackOverflow已拥有400万个用户,4000万个回答,月PV5.6亿,世界排行第54。然而值得关注的是,支撑他们网站的全部服务器只有25台,并且都保持着非常低的资源使用率,这是一场高有效性、负载均衡、缓存、数据库、搜索及高效代码上的较量。近日,High Scalability创始人Todd Hoff根据Marco Cecconi的演讲视频“ The architecture of StackOverflow”以及Nick Craver的博文“ What it takes to run Stack Overflow”总结了StackOverflow的成功原因。


免费订阅“CSDN大数据”微信公众号,实时了解最新的大数据进展!

CSDN大数据,专注大数据资讯、技术和经验的分享和讨论,提供Hadoop、Spark、Imapala、Storm、HBase、MongoDB、Solr、机器学习、智能算法等相关大数据观点,大数据技术,大数据平台,大数据实践,大数据产业资讯等服务。


以下为译文

意料之中,也是意料之外,Stack Overflow仍然重度使用着微软的产品。他们认为既然微软的基础设施可以满足需求,又足够便宜,那么没有什么理由去做根本上的改变。而在需要的地方,他们同样使用了Linux。究其根本,一切都是为了性能。

另一个值得关注的地方是,Stack Overflow仍然使用着纵向扩展策略,没有使用云。他们使用了384GB的内存和2TB的SSD来支撑SQL Servers,如果使用AWS的话,花费可想而知。没有使用云的另一个原因是Stack Overflow认为云会一定程度上的降低性能,同时也会给优化和排查系统问题增加难度。此外,他们的架构也并不需要横向扩展。峰值期间是横向扩展的杀手级应用场景,然而他们有着丰富的系统调整经验去应对。该公司仍然坚持着Jeff Atwood的名言——硬件永远比程序员便宜。

Marco Ceccon曾提到,在谈及系统时,有一件事情必须首先弄明白——需要解决问题的类型。首先,从简单方面着手,StackExchange究竟是用来做什么的——首先是一些主题,然后围绕这些主题建立社区,最后就形成了这个令人敬佩的问答网站。

其次则是规模相关。StackExchange在飞速增长,需要处理大量的数据传输,那么这些都是如何完成的,特别是只使用了25台服务器,下面一起追根揭底:

状态

 

  • StackExchange拥有110个站点,以每个月3到4个的速度增长。
  • 400万用户
  • 800万问题
  • 4000万答案
  • 世界排名54位
  • 每年增长100%
  • 月PV 5.6亿万
  • 大多数工作日期间峰值为2600到3000请求每秒,作为一个编程相关网站,一般情况下工作日的请求都会高于周末
  • 25台服务器
  • SSD中储存了2TB的SQL数据
  • 每个web server都配置了2个320G的SSD,使用RAID 1
  • 每个ElasticSearch主机都配备了300GB的机械硬盘,同时也使用了SSD
  • Stack Overflow的读写比是40:60
  • DB Server的平均CPU利用率是10%
  • 11个web server,使用IIS
  • 2个负载均衡器,1个活跃,使用HAProxy
  • 4个活跃的数据库节点,使用MS SQL
  • 3台实现了tag engine的应用程序服务器,所有搜索都通过tag
  • 3台服务器通过ElasticSearch做搜索
  • 2台使用了Redis的服务器支撑分布式缓存和消息
  • 2台Networks(Nexus 5596 + Fabric Extenders)
  • 2 Cisco 5525-X ASAs
  • 2 Cisco 3945 Routers
  • 主要服务Stack Exchange API的2个只读SQL Servers
  • VM用于部署、域控制器、监控、运维数据库等场合

 

平台

 

  • ElasticSearch
  • Redis
  • HAProxy
  • MS SQL
  • Opserver
  • TeamCity
  • Jil——Fast .NET JSON Serializer,建立在Sigil之上
  • Dapper——微型的ORM

 

UI

 

  • UI拥有一个信息收件箱,用于新徽章获得、用户发送信息、重大事件发生时的信息收取,使用WebSockets实现,并通过Redis支撑。
  • 搜索箱通过 ElasticSearch 实现,使用了一个REST接口。
  • 因为用户提出问题的频率很高,因此很难显示最新问题,每秒都会有新的问题产生,从而这里需要开发一个关注用户行为模式的算法,只给用户显示感兴趣的问题。它使用了基于Tag的复杂查询,这也是开发独立Tag Engine的原因。
  • 服务器端模板用于生成页面。

 

服务器

 

  • 25台服务器并没有满载,CPU使用率并不高,单计算SO(Stack Overflow)只需要5台服务器。
  • 数据库服务器资源利用率在10%左右,除下执行备份时。
  • 为什么会这么低?因为数据库服务器足足拥有384GB内存,同时web server的CPU利用率也只有10%-15%。
  • 纵向扩展还没有遇到瓶颈。通常情况下,如此流量使用横向扩展大约需要100到300台服务器。
  • 简单的系统。基于.Net,只用了9个项目,其他系统可能需要100个。之所以使用这么少系统是为了追求极限的编译速度,这点需要从系统开始时就进行规划,每台服务器的编译时间大约是10秒。
  • 11万行代码,对比流量来说非常少。
  • 使用这种极简的方式主要基于几个原因。首先,不需要太多测试,因为Meta.stackoverflow本来就是一个问题和bug讨论社区。其次,Meta.stackoverflow还是一个软件的测试网站,如果用户发现问题的话,往往会提出并给予解决方案。
  • 纽约数据中心使用的是Windows 2012,已经向2012 R2升级(Oregon已经完成了升级),Linux系统使用的是Centos 6.4。

 

SSD

 

  • 默认使用的是Intel 330(Web层等)
  • Intel 520用于中间层写入,比如Elastic Search
  • 数据层使用Intel 710和S3700
  • 系统同时使用了RAID 1和RAID 10(任何4+以上的磁盘都使用RAID 10)。不畏惧故障发生,即使生产环境中使用了上千块2.5英寸SSD,还没碰到过一块失败的情景。每个模型都使用了1个以上的备件,多个磁盘发生故障的情景不在考虑之中。
  • ElasticSearch在SSD上表现的异常出色,因为SO writes/re-indexes的操作非常频繁。
  • SSD改变了搜索的使用方式。因为锁的问题,Luncene.net并不能支撑SO的并发负载,因此他们转向了ElasticSearch。在全SSD环境下,并不需要围绕Binary Reader建立锁。

 

高可用性

 

  • 异地备份——主数据中心位于纽约,备份数据中心在Oregon。
  • Redis有两个从节点,SQL有2个备份,Tag Engine有3个节点,elastic有3个节点,冗余一切,并在两个数据中心同时存在。
  • Nginx是用于SSL,终止SSL时转换使用HAProxy。
  • 并不是主从所有,一些临时的数据只会放到缓存中
  • 所有HTTP流量发送只占总流量的77%,还存在Oregon数据中心的备份及一些其他的VPN流量。这些流量主要由SQL和Redis备份产生。

 

数据库

 

  • MS SQL Server
  • Stack Exchange为每个网站都设置了数据库,因此Stack Overflow有一个、Server Fault有一个,以此类推。
  • 在纽约的主数据中心,每个集群通常都使用1主和1只读备份的配置,同时还会在Oregon数据中心也设置一个备份。如果是运行的是Oregon集群,那么两个在纽约数据中心的备份都会是只读和同步的。
  • 为其他内容准备的数据库。这里还存在一个“网络范围”的数据库,用于储存登陆凭证和聚合数据(大部分是stackexchange.com用户文件或者API)。
  • Careers Stack Overflow、stackexchange.com和Area 51等都拥有自己独立的数据库模式。
  • 模式的变化需要同时提供给所有站点的数据库,它们需要向下兼容,举个例子,如果需要重命名一个列,那么将非常麻烦,这里需要进行多个操作:增加一个新列,添加作用在两个列上的代码,给新列写数据,改变代码让新列有效,移除旧列。
  • 并不需要分片,所有事情通过索引来解决,而且数据体积也没那么大。如果有filtered indexes需求,那么为什么不更高效的进行?常见模式只在DeletionDate = Null上做索引,其他则通过为枚举指定类型。每项votes都设置了1个表,比如一张表给post votes,1张表给comment votes。大部分的页面都可以实时渲染,只为匿名用户缓存,因此,不存在缓存更新,只有重查询。
  • Scores是非规范化的,因此需要经常查询。它只包含IDs和dates,post votes表格当下大约有56454478行,使用索引,大部分的查询都可以在数毫秒内完成。
  • Tag Engine是完全独立的,这就意味着核心功能并不依赖任何外部应用程序。它是一个巨大的内存结构数组结构,专为SO用例优化,并为重负载组合进行预计算。Tag Engine是个简单的windows服务,冗余的运行在多个主机上。CPU使用率基本上保持在2-5%,3个主机专门用于冗余,不负责任何负载。如果所有主机同时发生故障,网络服务器将把Tag Engine加载到内存中持续运行。
  • 关于Dapper无编译器校验查询与传统ORM的对比。使用编译器有很多好处,但在运行时仍然会存在fundamental disconnect问题。同时更重要的是,由于生成nasty SQL,通常情况还需要去寻找原始代码,而Query Hint和parameterization控制等能力的缺乏更让查询优化变得复杂。

 

编码

 

  • 流程

 

 

  • 大部分程序员都是远程工作,自己选择编码地点
  • 编译非常快
  • 然后运行少量的测试
  • 一旦编译成功,代码即转移至开发交付准备服务器
  • 通过功能开关隐藏新功能
  • 在相同硬件上作为其他站点测试运行
  • 然后转移至Meta.stackoverflow测试,每天有上千个程序员在使用,一个很好的测试环境
  • 如果通过则上线,在更广大的社区进行测试

 

 

  • 大量使用静态类和方法,为了更简单及更好的性能
  • 编码过程非常简单,因为复杂的部分被打包到库里,这些库被开源和维护。.Net 项目数量很低,因为使用了社区共享的部分代码。
  • 开发者同时使用2到3个显示器,多个屏幕可以显著提高生产效率。

 

缓存

 

  • 缓存一切
  • 5个等级的缓存
  • 1级是网络级缓存,缓存在浏览器、CDN以及代理服务器中。
  • 2级由.Net框架 HttpRuntime.Cache完成,在每台服务器的内存中。
  • 3级Redis,分布式内存键值存储,在多个支撑同一个站点的服务器上共享缓存项。
  • 4级SQL Server Cache,整个数据库,所有数据都被放到内存中。
  • 5级SSD。通常只在SQL Server预热后才生效。
  • 举个例子,每个帮助页面都进行了缓存,访问一个页面的代码非常简单:

 

 

  • 使用了静态的方法和类。从OOP角度来看确实很糟,但是非常快并有利于简洁编码。
  • 缓存由Redis和Dapper支撑,一个微型ORM

 

 

  • 为了解决垃圾收集问题,模板中1个类只使用1个副本,被建立和保存在缓存中。监测一切,包括GC操。据统计显示,间接层增加GC压力达到了某个程度时会显著的降低性能。
  • CDN Hit 。鉴于查询字符串基于文件内容进行哈希,只在有新建立时才会被再次取出。每天3000万到5000万Hit,带宽大约为300GB到600GB。
  • CDN不是用来应对CPU或I/O负载,而是帮助用户更快的获得答案

 

部署

 

  • 每天5次部署,不去建立过大的应用。主要因为

 

 

  • 可以直接的监视性能
  • 尽可能最小化建立,可以工作才是重点

 

 

  • 产品建立后再通过强大的脚本拷贝到各个网页层,每个服务器的步骤是:

 

 

  • 通过POST通知HAProxy下架某台服务器
  • 延迟IIS结束现有请求(大约5秒)
  • 停止网站(通过同一个PSSession结束所有下游)
  • Robocopy文件
  • 开启网站
  • 通过另一个POST做HAProxy Re-enable

 

 

  • 几乎所有部署都是通过puppet或DSC,升级通常只是大幅度调整RAID阵列并通过PXE boot安装,这样做非常快速。

 

协作

 

  • 团队

 

 

  • SRE (System Reliability Engineering):5人
  • Core Dev(Q&A site)6-7人
  • Core Dev Mobile:6人
  • Careers团队专门负责SO Careers产品开发:7人

 

 

  • Devops和开发者结合的非常紧密
  • 团队间变化很大
  • 大部分员工远程工作
  • 办公室主要用于销售,Denver和London除外
  • 一切平等,些许偏向纽约工作者,因为面对面有助于工作交流,但是在线工作影响也并不大
  • 对比可以在同一个办公室办公,他们更偏向热爱产品及有才华的工程师,他们可以很好的衡量利弊
  • 许多人因为家庭而选择远程工作,纽约是不错,但是生活并不宽松
  • 办公室设立在曼哈顿,那是个人才的诞生地。数据中心不能太偏,因为经常会涉及升级
  • 打造一个强大团队,偏爱极客。早期的微软就聚集了大量极客,因此他们征服了整个世界
  • Stack Overflow社区也是个招聘的地点,他们在那寻找热爱编码、乐于助人及热爱交流的人才。

 

编制预算

 

  • 预算是项目的基础。钱只花在为新项目建立基础设施上,如此低利用率的 web server还是3年前数据中心建立时购入。

 

测试

 

  • 快速迭代和遗弃
  • 许多测试都是发布队伍完成的。开发拥有一个同样的SQL服务器,并且运行在相同的Web层,因此性能测试并不会糟糕。
  • 非常少的测试。Stack Overflow并没有进行太多的单元测试,因为他们使用了大量的静态代码,还有一个非常活跃的社区。
  • 基础设施改变。鉴于所有东西都有双份,所以每个旧配置都有备份,并使用了一个快速故障恢复机制。比如,keepalived可以在负载均衡器中快速回退。
  • 对比定期维护,他们更愿意依赖冗余系统。SQL备份用一个专门的服务器进行测试,只为了可以重存储。计划做每两个月一次的全数据中心故障恢复,或者使用完全只读的第二数据中心。
  • 每次新功能发布都做单元测试、集成测试盒UI测试,这就意味着可以预知输入的产品功能测试后就会推送到孵化网站,即meta.stackexchange(原meta.stackoverflow)。

 

监视/日志

 

  • 当下正在考虑使用http://logstash.net/做日志管理,目前使用了一个专门的服务将syslog UDP传输到SQL数据库中。网页中为计时添加header,这样就可以通过HAProxy来捕获并且融合到syslog传输中。
  • Opserver和Realog用于显示测量结果。Realog是一个日志展示系统,由Kyle Brandt和Matt Jibson使用Go建立。
  • 日志通过HAProxy负载均衡器借助syslog完成,而不是IIS,因为其功能比IIS更丰富。

 

关于云

 

  • 还是老生常谈,硬件永远比开发者和有效率的代码便宜。基于木桶效应,速度肯定受限于某个短板,现有的云服务基本上都存在容量和性能限制。
  • 如果从开始就使用云来建设SO说不定也会达到现在的水准。但毫无疑问的是,如果达到同样的性能,使用云的成本将远远高于自建数据中心。

 

性能至上

 

  • StackOverflow是个重度的性能控,主页加载的时间永远控制在50毫秒内,当下的响应时间是28毫秒。
  • 程序员热衷于降低页面加载时间以及提高用户体验。
  • 每个独立的网络提交都予以计时和记录,这种计量可以弄清楚提升性能需要修改的地方。
  • 如此低资源利用率的主要原因就是高效的代码。web server的CPU平均利用率在5%到15%之间,内存使用为15.5 GB,网络传输在20 Mb/s到40 Mb/s。SQL服务器的CPU使用率在5%到10%之间,内存使用是365GB,网络传输为100 Mb/s到200 Mb/s。这可以带来3个好处:给升级留下很大的空间;在严重错误发生时可以保持服务可用;在需要时可以快速回档。

 

学到的知识

1. 为什么使用MS产品的同时还使用Redis?什么好用用什么,不要做无必要的系统之争,比如C#在Windows机器上运行最好,我们使用IIS;Redis在*nix机器上可以得到充分发挥,我们使用*nix。

2. Overkill即策略。平常的利用率并不能代表什么,当某些特定的事情发生时,比如备份、重建等完全可以将资源使用拉满。

3. 坚固的SSD。所有数据库都建立在SSD之上,这样可以获得0延时。

4. 了解你的读写负载。

5. 高效的代码意味着更少的主机。只有新项目上线时才会因为特殊需求增加硬件,通常情况下是添加内存,但在此之外,高效的代码就意味着0硬件添加。所以经常只讨论两个问题:为存储增加新的SSD;为新项目增加硬件。

6. 不要害怕定制化。SO在Tag上使用复杂查询,因此专门开发了所需的Tag Engine。

7. 只做必须做的事情。之所以不需要测试是因为有一个活跃的社区支撑,比如,开发者不用担心出现“Square Wheel”效应,如果开发者可以制作一个更更轻量级的组件,那就替代吧。

8. 注重硬件知识,比如IL。一些代码使用IL而不是C#。聚焦SQL查询计划。使用web server的内存转储究竟做了些什么。探索,比如为什么一个split会产生2GB的垃圾。

9. 切勿官僚作风。总有一些新的工具是你需要的,比如,一个编辑器,新版本的Visual Studio,降低提升过程中的一切阻力。

10. 垃圾回收驱动编程。SO在减少垃圾回收成本上做了很多努力,跳过类似TDD的实践,避免抽象层,使用静态方法。虽然极端,但是确实打造出非常高效的代码。

11. 高效代码的价值远远超出你想象,它可以让硬件跑的更快,降低资源使用,切记让代码更容易被程序员理解。

原文链接: StackOverflow Update: 560M Pageviews A Month, 25 Servers, And It’s All About Performance(编译/仲浩 审校/魏伟)

我从其他Shell脚本中学到了什么?

摘要:作者Fizer Khan是一位Shell脚本迷,他对有关Shell脚本新奇有趣的东西是如此的痴迷。本文他分享了八大Shell脚本技巧,希望你会喜欢,这些脚本可以直接拿来使用!

作者Fizer Khan是一位Shell脚本迷,他对有关Shell脚本新奇有趣的东西是如此的痴迷。最近他遇到了authy-ssh脚本,为了缓解ssh服务器双重认证问题,他学到了许多有用且很酷的东西。对此,他想分享给大家。

一、Colors your echo 

大多数情况下,你希望输出echo Color,比如绿色代表成功,红色代表失败,黄色代表警告。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
NORMAL=$(tput sgr0)
GREEN=$(tput setaf 2; tput bold)
YELLOW=$(tput setaf 3)
RED=$(tput setaf 1)
function red() {
    echo -e "$RED$*$NORMAL"
}
function green() {
    echo -e "$GREEN$*$NORMAL"
}
function yellow() {
    echo -e "$YELLOW$*$NORMAL"
}
# To print success
green "Task has been completed"
# To print error
red "The configuration file does not exist"
# To print warning
yellow "You have to use higher version."

这里使用tput来设置颜色、文本设置并重置到正常颜色。想更多了解tput,请参阅prompt-color-using-tput
 

 

二、To print debug information (打印调试信息)

打印调试信息只需调试设置flag。

 

1
2
3
4
5
6
7
8
9
function debug() {
    if [[ $DEBUG ]]
    then
        echo ">>> $*"
    fi
}
# For any debug message
debug "Trying to find config file"

 

某些极客还会提供在线调试功能:
 

 

1
2
3
# From cool geeks at hacker news
function debug() { ((DEBUG)) && echo ">>> $*"; }
function debug() { [ "$DEBUG" ] && echo ">>> $*"; }

三、To check specific executable exists or not (检查特定可执行的文件是否存在) 

1
2
3
4
5
6
7
8
9
10
11
12
OK=0
FAIL=1
function require_curl() {
    which curl &>/dev/null
    if [ $? -eq 0 ]
    then
      return $OK
    fi
    return $FAIL
}

这里使用which来命令查找可执行的curl 路径。如果成功,那么可执行的文件存在,反之则不存在。将&>/dev/null设置在输出流中,错误流会显示to /dev/null (这就意味着在控制板上没有任何东西可打印)。
 

 

 

有些极客会建议直接通过返回which来返回代码。

 

1
2
3
# From cool geeks at hacker news
function require_curl() { which "curl" &>/dev/null; }
function require_curl() { which -s "curl"; }

四、To print usage of scripts  (打印使用的脚本) 

当我开始编写shell 脚本,我会用echo来命令打印已使用的脚本。当有大量的文本在使用时, echo命令会变得凌乱,那么可以利用cat来设置命令。

 

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat << EOF
Usage: myscript <command> <arguments>
VERSION: 1.0
Available Commands
    install - Install package
    uninstall - Uninstall package
    update - Update package
    list - List packages
EOF

这里的<<被称为<<here document,字符串在两个EOF中。 

五、User configured value vs Default value (用户配置值VS 默认值)

有时,如果用户没有设置值,那么会使用默认值。

 

1
URL=${URL:-http://localhost:8080}

检查URL环境变量。如果不存在,可指定为localhost。
 

六、To check the length of the string 检查字符串长度

 

1
2
3
4
5
if [ ${#authy_api_key} != 32 ]
then
  red "you have entered a wrong API key"
  return $FAIL
fi

利用 ${#VARIABLE_NAME} 定义变量值的长度。

七、To read inputs with timeout (读取输入超时)
 

 

1
2
3
4
5
6
7
8
READ_TIMEOUT=60
read -t "$READ_TIMEOUT" input
# if you do not want quotes, then escape it
input=$(sed "s/[;\`\"\$\' ]//g" <<< $input)
# For reading number, then you can escape other characters
input=$(sed 's/[^0-9]*//g' <<< $input)

八、To get directory name and file name  (获取目录名和文件名) 

 

1
2
3
4
5
6
7
8
# To find base directory
APP_ROOT=`dirname "$0"`
# To find the file name
filename=`basename "$filepath"`
# To find the file name without extension
filename=`basename "$filepath" .html`

英文出自:FizerKhan

趣文:为什么软件开发周期通常是预期的两三倍?

【感谢@袁欣_Jason 的热心翻译。如果其他朋友也有不错的原创或译文,可以尝试推荐给伯乐在线。】

导读:有国外开发者在 Quora 提了这个问题:“为什么软件开发周期通常是预期的两三倍?” 并补充问:“这是开发人员的错误? 是管理失误? 是因为做事方法不对, 或者说缺乏好的方法?还是说这就是软件开发流程的特点?” Michael Wolfe 在2012年1月28日给的回复,非常经典,截至我们发布时已有8016个赞。以下是译文。

让我们先沿着海岸线,从旧金山(SF)走路去洛杉矶(LA),去拜访我们住在Newport Beach的朋友,我拿出地图在上面画出了行进路线。

全程大约有400英里,如果我们每天走10小时每小时4公里的话,只用10天就可以到达目的地。立刻打电话给我们的朋友预定下周六的晚餐,告诉他们下周六晚上六点我们一定会准时出现,朋友们已经等不及了!

第二天清晨,我们带着准备冒险的兴奋起床,背起行囊,拿出地图,准备计划我们冒险的第一天,看一眼地图,噢,不!

哇,海岸线上有这么多迂回曲折的线路。每天行进40英里的话,10天后只能勉强到达Half Moon Bay(半月湾?)。这趟旅行至少有500英里,而不是400。赶紧打电话给我们的朋友,将晚餐顺延至下下周周二。人还是应该现实一点。朋友们有点失望,不过仍然盼望见到我们,况且花 12 天从 SF 到 LA 也不赖。

把不开心的事丢到一边,准备出发。两小时过后,我们才刚刚走出动物园。出了什么事?我们低头看了一下脚下的路:

天哪,这样走路也太慢了!有沙子、海水、阶梯、溪流,还有海边愤怒的海狮!这样我们只能按每小时2公里的速度前进,只有我们预估一般的速度。要么我们现在每天走20小时,要么再把晚餐推迟一个星期。好吧,让我们各退一步:每天走12个小时,把晚餐安排到下下周的周末。只好再打电话给朋友告诉他们这个情况。朋友们有些不高兴,但还是表示可以,到时候见。

在辛苦走了12小时后,我们准备在 Moss Beach 扎营休息。靠,要把帐篷在风中立起来根本不可能。直到半夜才开始休息。不过没什么大不了:明天在加快点速度就可以了。

第二天早上睡过头了,早上10点才醒,起来浑身酸痛精疲力尽。艹,今天没法走12个小时了,先走10个小时,明天可以走14个小时。收拾东西出发。

再缓慢行进了几个小时之后,我发现伙伴脚有点跛。妈的,是水泡。必须现在解决它,在这些问题开始减慢我们的速度前,必须将它们扼杀在萌芽状态。我慢跑了45分钟到达内陆3英里远的Pescadero,买了一些创可贴再快速跑回去给朋友包扎了一下。我快累坏了,太阳也快下山了,又浪费了一天的时间。到我们准备休息前今天只走了6英里。但是我们确实需要补充一下供给。一切都很好,明天我们就能赶上。

第二天醒来,扎紧脚上的绷带准备出发。转角之后突然发现,靠!这是个啥?

你妹的地图上怎么没标出它!现在我们只能往内陆走3英里,绕过这些被联邦政府用栅栏保护起来的区域,中途迷路了两次,在中午前才好不容易又回到了海岸线。今天的时间过了一大半,而我们才前进了差不多1英里。好吧,不过我们不会再打电话给朋友推迟了,今天我们会一直走到午夜试着赶上进度。

晚上在大雾里断断续续地睡了一夜。一大早被我的伙伴叫醒,他一阵阵的头疼,还有点发烧,我问他能不能坚持一下。“你在想什么呢,混蛋,我已经连续三天在这么冷的雾中赶路,没有休息过了。“好吧,今天看来只能黄了,只能在原地好好恢复了。现在我们已经有经验了,今天好好休息明天再走14个小时,还有几天的时间,我们一定能够做到!

第二天我们昏昏沉沉地起来了。我看了一眼随身的地图:

天啊!我们已经走了10天旅程里的第5天还没有离开海湾区域!太荒唐了!我们要重新估计一下准确的时间再打给朋友,搞不好会被骂,但至少得找一个现实一点的目标。

同伴说,我们在四天里走了40英里,这趟旅程至少又600英里,那就至少要60天,安全一点的说法说不定要70天,“没门…是,以前我是没走路从SF去过LA,但肯定不会要70天的时间,如果告诉他们我们要到复活节才能到,那要被他们笑死的“,我说。

我接着说,“如果你能保证每天走16个小时,我们就能把落下的时间补回来!我知道很困难,但现在是最关键的时刻,别抱怨了!”伙伴对我吼道 “一开始又不是我告诉别人下周日我们就能到的!因为你犯的这个错差点要我的命!”

两个人就这样不说话了。我还是没打出电话,等我的伙伴明天冷静一点我再决定,我也愿意做一些更合理的承诺。

第二天上午,我们一直待在各自的帐篷中直到一场暴风雨袭来。我们赶紧收拾好东西直到10点才摆脱危险。浑身酸痛,又长了好多新水泡。之前发生的事谁也没提,直到发现我那愚蠢的伙伴把水壶落下了,又被我指责了一顿,我们不得不再花30分钟回去取它。

我心里记得我们的厕纸已经快用完了,下次到一个小镇的时候应该囤一点。在我们又转个弯后,才发现一条湍急的河流挡住了去路,这时我突然感到肚子一阵难受……

http://blog.jobbole.com/45756/

分布式系统事务原子性的非阻塞实现

摘要:分布式系统中,常见的多对象更新操作实现却比较困难,如果将这些操作看作一个事务,实现事务的原子性就成了关键所在。通常情况下原子性都是通过锁实现的,作者提供了一种非锁的方式实现——NBTA。

本文作者Peter Bailis是美国Berkeley的研究生,主要研究方向是分布式系统与数据库。作者目前主要的研究内容是分布式数据的一致性,尤其是如何调和ACID特性和分布式一致性模型,以及如何在理论和实际中更好的理解最终一致性。

作者将分布式系统中的事务定义为针对多个服务器的同时操作,本文主要讨论了分布式系统事务的原子性的一种实现算法。通常情况下原子性都是通过锁实现的,这个算法并没有使用锁,原理很简单,采用了简单的多版本控制和存储一些额外的元数据,虽然作者只是在实验环境中实现了这个算法,并没有投入到实际生产中,但是作者思考问题的方式值得参考。

分布式系统事务原子性

在现实的分布式系统中,多对象更新的操作很常见,但是实现起来却并不简单。同时更新两个或多个对象时,对于这些对象的其他读取者,原子性很重要:你的更新要么全部可见,要么全部不可见。

这里所说的原子性和线性一致并不是一个概念,数据一致性在Gilbert和Lynch证明CAP原理时被提到过,后来通常被称为原子一致性。而线性一致化关注实时的顺序操作,是一个单对象的问题。这里的“原子性”源于数据库环境(ACID中的“A”),涉及对多个对象的执行和查询操作。为了避免混淆,我们称这个原子性为“事务原子性”。

许多场景中都会遇到这种问题,从社交网络图(例如Facebook的TAO系统,双向的朋友关系被保存在两个单向的指针中)到类似计数器(例如Twitter的Rainbird分层聚合器)和二级索引的分布式数据结构。本文中,我将假设我们的工作都是高可用的事务,原子性的多对象更新,或事务的原子性,是其首要特性。

现有的技术

多对象更新的事务操作通常采用以下三种策略之一:

 

 

使用锁来同时更新多个项目。执行更新操作时加写锁,执行读操作时加读锁,就可以保证事务的原子性。但是在分布式环境中,局部故障和网络延迟都意味着锁操作可能会导致Bad Time。

具体来讲,锁操作有可能会导致一些怪异的结果。如果客户端在持有锁时宕机,服务器本应该最终撤销这个锁。这需要某种形式的故障检测或超时(在异步网络中会导致一些尴尬的情况)以及在撤销锁前同时撤销以前的操作。但是在执行更新操作时阻塞读操作显然是不合理的,反之亦然。如果我们追求高可用性,锁不是一个值得考虑的方案。

 

实体组

 

将想要同时更新的对象放在一起。这种策略通常称为“实体组”,可以让事务性原子更简单:在一台机器上加锁很快,而且不会遇到分布式锁的局部故障和网络延迟的问题。不幸的是,这种解决方案会影响数据布局和分布,而且不适用于难于分割的数据。

 

Fuck-it模式

使用“fuck-it”模式,不进行任何并发控制的情况下更新所有的对象,并保持事务的原子性。这个策略是很常见的:扩展性良好,适用于任何系统,但是直到系统达到稳定状态后,才会提供原子性保证(例如聚合,或者说最终一致性)。

NBTA

 

在这篇文章中,作者会介绍一种简单的替代方案,作者称其为事务原子性的非阻塞实现,简称为NBTA(Non-blocking transactional atomicity),使用多版本和一些额外的元数据在不使用锁的情况下,保证事务的原子性。具体来说,这种方案不会由于过程错误而阻塞读取和写入操作。关键的想法是避免执行局部更新,并且利用额外的元数据代替副本间的同步。

NBTA示例

可以用这个简单的场景来说明NBTA:有两个服务器,server for x上存储x,server for y上存储y,初值都是0。假设有两个客户端,Client1要执行写入操作,使x=1,y=1,Client2要同时读取x和y,关于副本的问题稍后会讨论。作者将Client1要执行的写入操作称为一个事务,而这个事务的操作对象server for x和server for y被称为事务兄弟。

good和pending

将每台服务器的存储分为两中状态:good和pending。要保证同属于一个事务的写入操作,如果其中一个操作被存储为good状态,这个事务的其它写入操作要么被存储为good,要么被存储为pending。比如在上面所说的场景中,如果x=1在server for x上被存储为good,那么必须保证y=1在server for y被存储为good或pending。

首先,各服务器会收到到写操作请求保存为pending状态,然后一旦服务器知晓(可能是异步的)某个写入操作相关的事务兄弟都已经将操作请求保存为pending状态,这个服务器就会更新这个操作为good状态。客户端进行两轮通信,就可以使服务器得到写操作已经稳定的信息:第一轮通信中,server for x和server for y会将从Client1收到请求保存为pending状态,并将确认回复给Client1,Client1收到确认后会进行第二轮通信,通知server for x和server for y写操作已达到稳定状态。

竞争危害和指针

理想的状态是,只读取good状态的数据,就可以保证事务的原子性。但是存在一种竞争条件的情况:比如server for x已经更新x=1,并保存为good状态,但在其事务兄弟server for y中相关操作y=1依旧是pending状态,Client2如果只读取good状态的数据,得到的结果将是x=1,y=0,破坏了事务的原子性。我们希望这种情况下,第二个服务器能够自动调用pending状态的数据以供读取。

为了解决这个问题,可以在每个写入操作中加入一些额外的信息:事务兄弟的列表以及一个时间戳。这个时间戳是客户端进行多值更新前,为每个写操作唯一生成的,比如,可以是客户端ID+本地时间或一个随机数。这样的话,当一个客户端读取good状态的数据时,还会读到时间戳和具有相同时间戳的事务兄弟的列表。客户端也会在发送读取请求附带一个时间戳,服务器会根据时间戳从pending或good中取出数据交付给客户端。如果客户端的请求中没有附带时间戳,服务器会将good中时间戳最高的值交付给客户端。

优化

以下是NBTA算法的一些优化:

pending和good的规模

如果用在good中只保存最近的写入操作,那么一个写入操作的兄弟事务可能会被覆盖,为了避免这种情况的发生,服务器会在good中将历史数据保留一定的时间。

更快的写操作

有一种方案可以替代客户端的第二轮通信操作。服务器一旦将写操作存入pending中,就直接互相通信,可使用类似于PAXOS的算法实现。此外,客户端也可以异步发起第二轮通信。然而,为了确保客户端在这些情况下读取写操作,它们要保留元数据直到每个写操作都被存为good状态。

副本

目前为止的讨论都基于每个数据项只存储在一个服务器上。算法实现的前提条件是每个服务器的强一致性。服务器间的副本有两种情况:如果所有的客户端都只能访问一部分服务器,那么客户端只需要对这些对应的服务器集合进行更新,这组服务器都存有数据的副本。如果客户端可以访问任何服务器,那么需要花费较长的时间去同步数据。

读/写事务

以上讨论的算法同样适用于读/写操作。对于ANSI标准的可重复读模型,主要的问题是保证从一个事务的原子组中读取。可以在事务执行前,事先声明所有的读取操作或者通过类似向量时间的元数据实现。

元数据的规模

最谨慎的做法是将元数据一直保存,但是也可以在写操作在所有服务器中都达到good状态时,将元数据删除。

算法的实现

作者采用LevelDB数据库实现了NBTA算法及其改进。在Yahoo!的云平台上,8个操作的NBTA事务可以达到最终一致性的33%(所有都是写操作)至95.2%(所有都是读操作)峰值吞吐量。并且这种实现是线性扩展的,运行50个EC2实例,对于长度为8的事务(50%的读操作,50%的写操作),可以达到每秒执行250000次操作。

实验结果表明NBTA的性能大大优于基于锁的操作,因为不会发生阻塞。主要的花销来自于元数据以及将写入操作从pending更新为good。基于这些结果,作者已经开始将NBTA应用于其它数据存储和二级索引上。

结论

这篇文章展现了如何在不使用锁的情况下,实现在任意数据分片的原子性多对象更新。数据库中有很多类似于NBTA的算法。例如客户端第二轮通信的优化是通过PAXOS的算法实现的,使用额外的元数据保持并发更新类似于B树或其它非锁的数据结构。当然,多版本并发控制和基于时间戳的并发控制在数据库系统中也都有悠久的历史。但是NBTA的关键是实现事务的原子性,同时避免中央集权的时间戳或并发控制机制。具体来说要在数据读取操作前达到一个稳定状态,主要的挑战是解决竞争条件。在实际中,相比其它基于锁的技术,这个算法表现得很好。(编译/周小璐 审校/仲浩)

原文链接:Non-blocking transactional atomicity

http://www.csdn.net/article/2013-08-08/2816504-non-blocking-transactional-atomicity

 

优化技巧分享:把内存消耗降低至原来的1/20

摘要:Plumbr是一家Java存泄露检测器开发公司,在最近的客户报告中,他们发现了一个内存耗尽相关的问题。在检测后他们查出了问题所在,通过优化最终降低了21.5倍的堆内存。

这是最近发生的又一起内存相关的事件了。这个案例是从一个最近的客户报告中提取出来,一个异常运行的应用在其产品中反复报告内存耗尽。

这个症状是由我们的一个实验性功能发现,它主要用来监测某一类数据结构的使用情况。它提供了一个信号探针,结果会指向问题源代码的某一位置。为了保护客户的隐私,我们人为重建了该例子并保持它同原真实场景在技术层面的一致性。你可以免费在此处 下载到源码

故事开始于一组从外界源加载进来的对象。同外部的信息交互是基于XML的接口,这本身并没什么大不了的,但事实上“基于XML的格式进行通讯”的实现细节被分散到了系统的每一个角落。 传入系统的文档是首先被转换成XMLBean实例,然后在整个系统范围内被使用,这中做法听起来有点傻。

整个问题中最核心的部分是一个延迟加载的缓冲方案。缓存的对象是“Person”的实例:

 

1
2
3
4
5
6
7
// Imports and methods removed to improve readability
public class Person {
    private String id;
    private Date dateOfBirth;
    private String forename;
    private String surname;
}

 

 

你也许会说这才能消耗多少内存呢。但当我们揭开进一步的细节时,发现事情就变了味了。表面上根据设计,声称实现只用到的诸如上文提到的那样一些简单的类,但真实的情形是使用了基于模型生成的数据结构。使用的模型是诸如下面的这个简化的XSD片段。

 

1
2
3
4
5
6
<xs:schema targetNamespace="http://plumbr.eu" xmlns:xs="http://www.w3.org/2001/XMLSchema"
        elementFormDefault="qualified"> <xs:element name="person"> <xs:complexType>
        <xs:sequence> <xs:element name="id" type="xs:string"/> <xs:element
        name="dateOfBirth" type="xs:dateTime"/> <xs:element name="forename"
        type="xs:string"/> <xs:element name="surname" type="xs:string"/>
        </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

 

 

使用 XMLBeans,开发者生成了该模型,并在真实的场景中使用。现在我们回到开始的这个缓存的方案上来,假设它设计初衷是为了支持最多1.3M Person类的实例,而我们实际却要塞进去同等数量的大家伙,这从根上就注定了失败。

跑一组测试用例后,发现1.3M个基于XMLBean的生成的实例需要消耗大概1.5GB的堆空间。我们当时想这肯定可以做的更好。

第一个改进是显而易见的,外部同系统内部集成的实现细节是不应该把影响传递给系统的每一个角落的。所以我们把缓存改成了使用简单的  java.util.HashMap<Long, Person>。ID是键,Person是值。我们发现内存的消耗立即降低到了214MB。但这还不能令我们满意。

由于Map中的键是一个数,我们有十足的理由使用 Trove Collections来进一步降低它的内存消耗。这在实现上的改动很快,我们只需把  HashMap 改成  TLongObjectHashMap<Person> ,堆的消耗进一步降低到了143MB。

活干到这个程度我们已经可以收工了,但是工程师的好奇心驱使我们要更进一步。不由自主的我们发现了系统的数据存在着大量的重复信息。例如Date Of Birth其实已经在ID中编码了,所以Date Of Birth可以直接从ID中得到,而不必使用额外的空间去它。

经过改良,Person类现在变成了这个样子:

 

1
2
3
4
5
6
// Imports and methods removed to improve readability
public class Person {
    private String id;
    private String forename;
    private String surname;
}

 

 

重新跑一边测试证实我们的改进的确有效,堆消耗降低到了93MB。但是我们还未满足。

该应用在64位的机器上使用老的JDK6。默认情况下,这么做不能 压缩普通对象的指针的。通过参数”-XX:UseCompressedOops“切换到压缩模式使我们获得了额外的收获,现在我们的内存消耗降低到了73MB。

 

当然,我们还能走的更远。比如基于键值建立B-tree,但这已经开始影响到了代码的可读性,所以我们决定到此为止。降低21.5倍的堆内存应该已经是一个足够好的结果了。

让我们再重复一下学到了什么

如果你对这个实验很好奇,请在此处 下载相关的代码。使用到的的测量工具和其具体描述可以在这篇博文找到。

 

  • 别把同外部模块的整合影响到系统的每一个角落
  • 冗余的数据可能带来开销。在可能的情况下尽量消除它
  • 基本数据类型是你最经常打交道的朋友,务必知道些关于它们的工具,如果还没玩过 Trove请立刻开始吧
  • JVM自带的优化技术不可忽视

原文链接:  Nikita Salnikov-Tarnovski ,编译:感谢@ NULL_文龙 的热心翻译

译文链接: http://blog.jobbole.com/40666/

代码审查——提高代码质量的终极武器

摘要:SmartBear公司发起一项调查研究2013年代码审查的使用情况并从超过650名专业开发人士那收集了“实践经验”,结果非常有趣。90%的受访者表示代码审查提高软件质量;74%表示代码审查还有助于跨团队之间知识共享。

如果糟糕的软件是我们的克星,那么优秀的代码就是解药。

软件无法工作是件非常恼人的事!而这种情况往往是由于糟糕的代码所致。在一个项目中,如果开发者孤军奋战,这种情况出现的几率就会增大。

幸运的是,团队中的一些成员愿意贡献自己的空闲时间来改善软件质量。通常,这些人就是我们常说的QA测试者——他们坚持不懈地寻找bug。这里有一个最佳实践方式能够更有效地识别软件代码中的缺陷——同行代码审查(peer code review)。

研究表明,同行代码审查是寻找代码缺陷最高效的方法。有分析称超过12000软件代码使用同行代码审查可以完成60%的效率,而使用单元测试只有25%。每隔一小时使用同行代码审查,可以为开发团队的QA测试节省20个小时,这个听起来不错吧。

近日,SmartBear公司发起一项调查研究2013年代码审查的使用情况。超过650名专业开发人士回答了如何使用代码审查以及对性能影响的相关问题并从中收集了“实践经验”,结果非常有趣。

在此次调查中超过70%的受访者参与了不同程度的协同审查,结果发现,那些做代码审查的比不做代码审查的软件整体质量满意度高达2倍。

我们从中抽取了一个问题,一起来看下代码审查能带来哪些好处。

Q:你认为代码审查最大的好处是什么?

如图所示,显而易见,代码审查最大好处莫过于提高软件质量;74%的受访者表示,代码审查还有助于跨团队之间知识共享;有60%的受访者还发现,代码审查有助于团队之间相互指导及提升协作。

由此可以看出:

 

  1. 代码审查是提高开发团队技能以及保持团队迭代更新最有效的最佳实践方法。
  2. 代码审查工具辅助文档过程本身允许大家互相学习和更正评论。
  3. 假如做代码审查的开发者碰巧没坐在你边上或者开发团队遍布全球或者某个疯狂的同事从凌晨1点工作到5点,那么代码审查工具可追踪任何一条评论以方便你在空闲的时候查看。

 

代码审查不仅能帮助你确保评审、改进代码,还能为开发人员节省大量的时间,帮助你拯救因糟糕软件无法正常运行而带来的压力。

英文出自:Smartbear

http://www.csdn.net/article/2013-06-03/2815518-code-review

较早的文章

Copyright © 2024 优大网 浙ICP备13002865号

回到顶部 ↑