• DevOps ≠ Chef/Puppet at 2014年02月18日

    (三)仅靠 Chef/Puppet 本身无法实现 Full-Stack 部署自动化

    如果要实现 Full-stack Automation,那么就必须实现环境创建自动化,软件安装和配置自动化,应用部署和配置自动化,监控和告警自动化,故障检测和恢复自动化,扩展自动化,如下图所示(注 6)。

    [attach] 2264[/attach] 环境创建:创建 VMs、网络、存储、负载均衡,协调不同角色 VMs 的创建过程和配置。 软件安装和配置:操作系统配置,比如创建用户、组,设置 ulimit 参数等;基础软件安装和配置,比如 mysql/nginx。这些软件的特点是变动不频繁。对于 Chef/Puppet 来说,这个步骤的自动化是其最擅长的。它们都提供大量现成的 Recipes,并抽象各种异构系统之间的差异。 应用部署和配置:部署应用代码,比如 war 包、db 脚本、php/rails 代码等。这部分的变动是频繁的。对于 Chef/Puppet 来说,其是可以做这个工作的,但是很多人认为用 Fabric/Glu 等更为合适。另外,对于复杂的系统来说,如果保证升级期间系统的可用性,系统的各个应用组件需尽量是无状态和松耦合的。如果系统的组件之间是有依赖的,那么升级期间必须动态地协调 (Orchestration)、控制好相关组件的行为。 监控和告警:包括 OS 级别和应用级别的可用性和性能监控。如果发现异常,需要能够自动发出告警信息。 健康检测和恢复:为了应付云基础设施故障,系统需要 Design By Failure. 在异常发生时,系统可以发现并进行自动处理。 自动伸缩:一般情况下,业务存在高峰期和低估期。为了节省成本,系统应该是可以自动伸缩的。 对于上述的每一个步骤,看似都存在现成的工具可以用来实现自动化。但是,实现 Full-Stack 部署自动化从来就不是一件容易的事情,绝非简单通过选择、拼凑相关工具即可实现。Autodesk 中国研发中心最近在 InfoQ 上分享了他们基于 AWS 的自动化部署实践。文章详细阐述了业务目标,设计目标和限制,和最终的实现方案,是一个非常好的案例。在这里,我们再来分析一下两种差异较大的实现方式。

    (1) 基于 PaaS 的实现方式 (以 Cloud Foundry 为例)。 环境创建

    用户不需要创建物理资源环境,Cloud Foundry 会自动创建并分配资源给各个租户,用户无法控制底层 OS 等

    软件安装和配置

    用户不需要软件安装。Cloud Foundry 已经安装好相关软件,只是支持的类型和版本有限

    应用部署和配置

    Cloud Foundry 提供接口,用户调用接口进行应用部署和配置。应用类型必须是 Cloud Foundry 支持的,只能进行有限的配置,比如 Tomcat 的配置参数等

    监控和告警

    Cloud Foundry 提供监控服务,但是 Metric 类型有限,无法支持自定义 Metric

    健康检测和恢复

    Cloud Foundry 提供 Container 级别的健康检测和恢复

    自动伸缩

    基于 Cloud Foundry 提供的 monitoring 接口和应用控制接口,可以实现 instance 级别的自动伸缩

    貌似这种方式是最完美的方案,Cloud Foundry 基本替你实现了上述的所有自动化步骤,应用开发人员只需专注于应用逻辑本身的开发。然而,Cloud Foundry 也限制了用户的选择权和控制权,特别是大型的、复杂的、分布式系统,开发人员需要的是 Full-Stack 可控制性。

    (2) Netflix 的实现方式。 环境创建

    通过自己研发的 Asgard 管理和部署工具实现

    软件安装和配置

    基础软件和配置都打包成 AMI,基于 Netflix 自己的打包工具 Aminator

    应用部署和配置

    同上,应用代码和配置也打包进 AMI(注 7)

    监控和告警

    Leverage AWS Cloudwatch, 同时也将通过自己开发的 Servo

    Lib 将 custom metric 发送至 Cloudwatch.

    健康检测和恢复

    Leverage Atoscaling group,健壮性测试有 Netflix 自己开发的 Chaos Monkey 工具

    自动伸缩

    Leverage AWS AutoScaling Group

    Netflix 除了 Leverage AWS 的 CloudWatch/Auto Scaling Group/ELB 等服务外,自己也开发了一序列工具完成了 Full-Stack 部署自动化。这些工具通过 Asgard 这个可视化云管理和部署控制台集成起来。另外,Deployable image 这种部署模式虽可简化部署并确保一致性,但是一部分复杂性转移到了应用层面(注 7)。系统的各个组件是无状态和松耦合,同时还需要 Eureka 这种服务来实现中间层的负载和 failover。

    在上述两个案例中,我们都看不到 Chef/Puppet 的影子。即便是 Cloud Foundry 自身的部署工具 Bosh,但也不是基于 Chef/Puppet。 所以,尽管 Chef/Puppet 非常流行,并且有很多成功案例,但是我们决不能把 DevOps = Chef/Puppet。它们只是建设 DevOps 所需工具链中的可选工具,仅此而已。

    参考文档

    注 1: What’s DevOps?

    注 2: 10+ Deploys Per Day: Dev and Ops Cooperation at Flickr

    注 3: Ops, DevOps and PaaS (NoOps) at Netflix

    注 4: 对国内云计算三个现象的思考

    注 5: Agile, ITITL, Cloud How DevOps brings it all together

    注 6: “It’s the App, Stupid!” on Orchestration, DevOps Automation and What’s in Between

    注 7: Netflix deployable image

    关于作者

    阮志敏,AWS 认证架构师,目前在三星中国研究院从事 PaaS 相关工作。

  • DevOps ≠ Chef/Puppet at 2014年02月18日

    DevOps 不仅仅是工具,即便是工具,其也是建设 DevOps 所需工具链中的可选工具。

    (二)Chef/Puppet 只是 DevOps 工具链中的可选工具

    DevOps 目的是打造标准化的、可重复的、完全自动化的 Delivery Pipeline, 其范围涵盖需求,设计,开发,构建,部署,测试和发布。除了需求、设计和开发外,其他的四个步骤都是可以自动化的。自动化是提高可测试性,一致性,稳定性和交付频率的核心。

    下图来自 IBM Agile, ITITL, Cloud How DevOps brings it all together 。该图非常清晰地解释 DevOps 如何实现交付的自动化(注 5)。

    [attach] 2263[/attach]

    图中 DevOps 流程所需要用到的工具和环境有:

    源代码版本控制工具: 比如 SVN, Git 等。 持续集成工具:比如 Jenkins, Bambo 等。 Artifact 存储仓库:持续集成构建后的 artifact 都统一放在一个仓库中,比如 Nexus/Artifactory, 当然也可以是 FTP, S3 等。 配置和部署工具:Chef/Puppet/CFEngine, Fabric/ControlTier,也包括新兴的 Docker 等。 Cloud Provision 工具:在云环境下,由于任何 IT Infra 资源都以编程接口提供,意味着 Full-Stack Automation (from “bare-metal to running business services”) 成为了可能。Cloud provision 工具可以自己通过 API 构建 (如 Netflix Asgard),也可以直接使用 IaaS 服务商提供的扩展服务如 AWS CloudFormation & Opsworks,也可以使用第三方工具如 Ringscale/Scalr 等。相当一部分 Cloud Provision 本身也集成了 Chef/Puppet 来实现后续的部署和配置。 测试工具:除了传统的测试工具外,还需要模拟 Infra 灾难、验证系统健壮性的工具,如 Netflix 的 Chao Monkey。 发布工具:一般情况下,人们需要拥有 DTAP 四个环境,即开发环境、测试环境、Staging 环境和生产环境。每种环境的作用,部署方式和代码版本等是不一样。比如开发环境是持续部署的,测试环境是定期如每天晚上自动部署,Staging 和生产环境是手动触发的。 云基础设施:包括 AWS/Azure 等公有云,Cloudstack/Openstack 等私有云。 因此,我们看到,Chef/Puppet 只是实现 DevOps 工具链的可选工具,可以用来实现配置和部署自动化。但是仅靠 Chef/Puppet 本身无法实现 Full-Stack 部署自动化。

  • 及时把大家谈论的东西总结一下,省得忘记。

  • 阿里巴巴? 三年前就这个 JD, 天天招聘,有点诚意啊

  • [i=s] 本帖最后由 scmroad 于 2014-2-17 17:06 编辑

    除了时间和触发原因的不同,最主要的是构建内容的不同,目的也不同。

    如果构建时间很长,很难在短时间内构建完成,那么就可以分成两种 1)每次提交代码,svn 自动触发的构建。这里仅包括必须要构建的内容。甚至可以是一个增量构建 2)daily build 首先是一个完全构建,其次应包含了构建后的测试,各种检查等。

  • Scaling Mercurial at Facebook

    POSTED ABOUT A MONTH AGO OPEN SOURCE · INFRA · PERFORMANCE · OPTIMIZATION With thousands of commits a week across hundreds of thousands of files, Facebook's main source repository is enormous--many times larger than even the Linux kernel, which checked in at 17 million lines of code and 44,000 files in 2013. Given our size and complexity—and Facebook's practice of shipping code twice a day--improving our source control is one way we help our engineers move fast.

    Choosing a source control system

    Two years ago, as we saw our repository continue to grow at a staggering rate, we sat down and extrapolated our growth forward a few years. Based on those projections, it appeared likely that our then-current technology, a Subversion server with a Git mirror, would become a productivity bottleneck very soon. We looked at the available options and found none that were both fast and easy to use at scale.

    Our code base has grown organically and its internal dependencies are very complex. We could have spent a lot of time making it more modular in a way that would be friendly to a source control tool, but there are a number of benefits to using a single repository. Even at our current scale, we often make large changes throughout our code base, and having a single repository is useful for continuous modernization. Splitting it up would make large, atomic refactorings more difficult. On top of that, the idea that the scaling constraints of our source control system should dictate our code structure just doesn't sit well with us.

    We realized that we'd have to solve this ourselves. But instead of building a new system from scratch, we decided to take an existing one and make it scale. Our engineers were comfortable with Git and we preferred to stay with a familiar tool, so we took a long, hard look at improving it to work at scale. After much deliberation, we concluded that Git's internals would be difficult to work with for an ambitious scaling project.

    Instead, we chose to improve Mercurial. Mercurial is a distributed source control system similar to Git, with many equivalent features. Importantly, it's written mostly in clean, modular Python (with some native code for hot paths), making it deeply extensible. Just as importantly, the Mercurial developer community is actively helping us address our scaling problems by reviewing our patches and keeping our scale in mind when designing new features.

    When we first started working on Mercurial, we found that it was slower than Git in several notable areas. To narrow this performance gap, we've contributed over 500 patches to Mercurial over the last year and a half. These range from new graph algorithms to rewrites of tight loops in native code. These helped, but we also wanted to make more fundamental changes to address the problem of scale.

    Speeding up file status operations

    For a repository as large as ours, a major bottleneck is simply finding out what files have changed. Git examines every file and naturally becomes slower and slower as the number of files increases, while Perforce "cheats" by forcing users to tell it which files they are going to edit. The Git approach doesn't scale, and the Perforce approach isn't friendly.

    We solved this by monitoring the file system for changes. This has been tried before, even for Mercurial, but making it work reliably is surprisingly challenging. We decided to query our build system's file monitor, Watchman, to see which files have changed. Mercurial's design made integrating with Watchman straightforward, but we expected Watchman to have bugs, so we developed a strategy to address them safely.

    Through heavy stress testing and internal dogfooding, we identified and fixed many of the issues and race conditions that are common in file system monitoring. In particular, we ran a beta test on all our engineers' machines, comparing Watchman's answers for real user queries with the actual file system results and logging any differences. After a couple months of monitoring and fixing discrepancies in usage, we got the rate low enough that we were comfortable enabling Watchman by default for our engineers.

    For our repository, enabling Watchman integration has made Mercurial's status command more than 5x faster than Git's status command. Other commands that look for changed files--like diff, update, and commit—also became faster.

  • 洪强宁介绍 Douban App Engine 的架构与特性

    豆瓣网首席架构师洪强宁最近在 PyCon China 分享了他们研发两年的成果:Douban App Engine(DAE)。InfoQ 中文站编辑在现场对洪强宁的分享(PPT 下载)进行了记录,提取重点内容如下:DAE 是专门针对 Python 做的私有 PaaS 平台,目前已经支撑了 427 个应用,其中 126 个是对外应用。126 个对外应用包括: 豆瓣电台的 Bubbler 豆瓣移动版网站 豆瓣 FM 豆瓣电影 豆瓣小组 对内应用则包括 Douban Code 平台、豆瓣上线系统 Up 平台、用于人事管理和活动组织的豆瓣花名册、验证深度学习算法有效性的豆瓣电影评论数据分析系统精灵宝钻等等。DAE 目前每天处理 2.4 亿动态请求,峰值可达到 5K qps,运行在 32 台服务器上(豆瓣称之为 32 个节点)。 开发 DAE 的原因

    洪强宁介绍开发 DAE 的原因,包括几个方面: 用 Git 替换 SVN。此前,豆瓣所有的代码都在一个 SVN repo 上,commit 数量将近 17 万。PyCon 北京之前,豆瓣正式停用 SVN,将所有代码转移到 4.6k 个 git repo 上,目前有 2.8K 个 fork。项目数量以每一两天一个新项目的速度增长 基础设施公用,不用给每一个应用都配一套MySQL、BeansDB、Memcache、MQ 什么的了 大大简化了新项目启动的操作:你只需要 dae create 即可创建你的项目,dae serve 即可测试,dae deploy 即可上线 最佳实践可以自动实施到所有的项目上,这包括每次提交都进入持续集成系统进行自动化测试,分级上线,状态收集和故障报告的标准化等 基于上述好处,DAE 的存在还带来了另一个极大的好处:运维工作的可扩展性。如果没有 DAE,那么 SA 的工作量跟服务器数量、应用种类的数量是呈平方增长关系的。有了 DAE,只需要 4 个 SA 就能完成所有的豆瓣运维。 DAE 的架构设计

    DAE 的架构是这样设计的:每一个应用都有一个 app.yaml 定义这个应用的版本、是 sync 模式还是异步模式、出了问题该通知谁等信息。应用依赖采用类 pip 的方案解决,只需执行 dae install package 即可安装该 package 以及相关的所有依赖。豆瓣提供了一个 pypi 的镜像供大家使用。实例分为三种:web、service 和 daemon。一个 web 实例就是一个 gunicorn。使用 gunicorn 的原因是因为它是用 Python 写的,适合豆瓣对它做二次开发,而且 gunicorn 还有一些很好的特性,比如 graceful restart。service 是用于在应用和应用之间传递信息的,用 gunicorn 配合 thrift 实现。daemon 是那种长期运行、不会死掉的守护进程,采用 ZooKeeper 做全局管理。有了这个就可以实现一些其他的应用,可以保证 Message Queue 里有固定数量的消费者,通过定时产生一些消息,让队列的消费者执行特定程序来实现定时任务。路由系统采用两级 Nginx 结构,请求过来之后判断它是找哪个应用的哪个实例,给它做一个 HTTP proxy 过去。基本上,长期没人访问的应用消耗是很小的,仅占用硬盘空间。这里使用了 unix socket 而避免使用 tcp,是看中了 unix socket 的文件属性,实现更简单。所有的基础服务,如 MySQL、memcache、doubandb、cdn、mail、irc 等,都是通过 API 的方式提供。业务访问不同的服务会有不同的前缀,通过这个实现隔离和监控。 DAE 的特点

    根据洪强宁的介绍,DAE 本身的资源占用是很低的,因为是用进程来做资源分配,用 Unix 账号做资源隔离,监控则交给外部的监控系统。另外,DAE 是一个自己实现自己的系统:通过一个 DAE 应用部署应用,通过一个 DAE 应用管理应用,一个 DAE 应用 scale 所有的应用,而 DAE 的代码也是托管在一个 DAE 应用上,会有循环依赖。DAE 自身的升级是通过开发集群->beta 集群->stable 集群逐级上线的,目的是在发布到外部应用之前 DAE 系统已经经过了内部系统的测试。DAE 在部署应用时,会分批逐步在各节点部署,部署后会自动测试,测试不通过就会自动回滚,这样来保证应用部署过程中和部署后的可用性。另外,DAE 的环境是不受限的,比如 C 扩展,比如 Python 的 fork、subprocess、multiprocessing 等在其他 PaaS 上禁止使用的特性,在 DAE 上都是允许的。这样当然会造成一些潜在的问题,比如你的应用 fork 出来一堆线程没有处理,就会在那里占用很多资源。所以我们做了一个曹娥来解决这个问题,在父进程死掉的时候杀死所有 fork 出来的子进程。接下来希望做的一些事情: 让 DAE 成为豆瓣所有应用的平台 采用 cgroups 实施资源限制,以免外部监控跟不上的情况 开发新的服务系统,解决在应用 thrift 中发现的一些问题,和 thrift 互为补充 自动实现跨 IDC 实现 QoS,让任何应用的问题不会影响其他应用的表现 DAE 并没有计划做公有云服务,因为原本的设计采用的 Unix 账号模式,设计目标是数千个 app 的容量,很明显是无法支持公有云的用量的。开源方面,其实一直有这个计划,会在基础库逐步开源之后再开放出来。

  • 作为一个面向内部设计的系统,CODE 的源代码中混杂了大量豆瓣专有系统的依赖,如果要开源出来需耗费不少的工作量,之前清风在 C2D2 的分享和段念在 QCon 上海 2013 大会的分享中都提到这一点:

    CODE 在豆瓣内部是运行到 DAE 上面的,一些基础设施的维护都靠豆瓣平台组的工程师,如果要开源的话需要把豆瓣内部的依赖移除。另外豆瓣内部也有一套 Web 框架,CODE 用的就是这套内部框架,如果 CODE 开源,这套框架也需要一并开源。

    虽然有种种麻烦,但豆瓣的工程师们仍然将大量工作之外的时间热情的投入到将 CODE 开源的工作当中。一开始,CODE 团队零星的将一些底层的基础库开源,包括 Python 版本的 git HTTP 实现 GPack,git SSH 实现 Maria,Pygit2 的 wrapper Ellen 等;2014年2月14日,豆瓣 CODE 宣布将框架代码和周边项目代码全部公开!此时的 CODE 已经聚集了 85 位 committer 为其贡献代码,并且已经托管了 1916 个项目,其中大部分都是类似 CODE 这样的、由工程师自主发起的非官方项目。

    技术说明

    CODE 在豆瓣内部对应一个 DAE 应用,其实就是一个 Web 项目。CODE 目前最核心的工作是对 git 仓库的读写,另外就是 code review。

    CODE 发展两年来一直在扩展自己的定位,从单纯的代码管理系统发展为可以将设计、产品都拉进来一同协作的平台,周边衍生出大量的附加项目,从 image diff 到类 Dropbox 的文件同步工具,可谓五花八门。所有这些功能都围绕一个核心:对 git 仓库的读写。

    XTao:目前 CODE 支持 http 和 ssh 协议进行 clone 和 push 操作,http 目前我们使用的是 git_http_backend 这个开源项目,我们自己也用 Python 实现了一个 Grack(原项目基于 Ruby),即 GPack,目前还没有大规模的使用。ssh 我们用的是 CMGS 同学写的 Maria。

    CODE 的 Web 版 git 操作的变化很大,一开始直接使用 subprocess 调用 shell 的 git 命令,然后解析命令返回的文本,python 对象化,然后供 Web 使用,后来因为性能问题开始使用 Pygit2,从此就是 git 命令和 Pygit2 混用,一直到现在。我们的这个库也开源了:Ellen。

    Pygit2 是 libgit2 一个 Python 接口,在使用 Pygit2 和 libgit2 的过程中,也经常当小白鼠,也给 Pygit2 和 libgit2 提过一些 Pull Request。对 Pygit2 我们改动比较大,因为 CODE 使用模式问题,我们有一个自己的 fork 版本,跟上游的主要区别有:

    把 libgit2 内嵌到 Pygit2 里面,做了静态编译,不再依赖系统的 libgit2 版本,方便升级 Pygit2。 一直采用最新版本的 libgit2,Pygit2 本身比较稳定,不能及时的使用 libgit2 最新的方法。 为了性能写了一些特殊的方法。 总体来说 libgit2 实现了核心的 git 接口,并不是很成熟,但是对于 Web 应用来说已经够用了。

    以上的部分是与 git 操作紧密结合的部分。我们在 Web 界面也做了一些核心功能,主要包括 Pull Request 的实现,代码 diff 和 review 功能的实现,以及 Issue 和 Gist 系统。这里说一下代码 diff 和 review 功能,在实现的时候,一方面是用了 git 操作接口的 API,一方面是对于代码片段做了处理,在前端页面上支持 diff 的按行展开评论等一些功能。

    同时,CODE 团队也在尝试分布式 git 方案,目前也已经有个原型了。

    规则的建立

    豆瓣曾经历过大桌子开会、强制大家做 review 的岁月,而 CODE 颠覆了这一情况。CODE 团队将 code review 视为 CODE 的第二个核心功能,认为促进工程师之间的沟通是 CODE 最大的成就之一。

    CODE 为每个项目设置了三个角色,分为 owner(有全部权限)、committer(有 push 和 merge 权限)、member。review 机制根据项目的不同设置了不同的规则,如产品线级别的、需要对外发布的项目,基础库等项目都需要经过严格的 review,如东西团队对 review 设置了如下规则:

    尊重他人,就事论事,对事不对人,毕竟每个人都写过烂代码; PR 中的每一个 commit log 都应该可以和代码对应,方便 review; 尽量不要发太大的 PR,以免引起 reviewer 的恐慌; 建议保证一个 PR 的粒度和专注,最好不要出现一个 PR 里又有重构又加新 feature 的情况,同样容易引起 reviewer 的恐慌; 提 PR 之前请确保在本地或测试环境一切正常; reviewee 如果接受 reviewer 提出的修改意见,需要在修改提交以后知晓 reviewer,常见的做法可以是在 review comment 处回复(并带 commit 链接); 评论中至少出现一个 lgtm 且保证 ci 通过之后 PR 才可以被合并;(注:lgtm 即 looks good to me 的缩写) PR 合并者与提交者不能是同一个人; PR 需从一个特定分支(分支的名字尽量能表达代码的功能)发往上游的 master 分支; Model 的部分,如不紧急需要 unittest; Web 的部分,如不紧急需要 webtest; PR 合并后如引起 bug 或功能异常,并经查确为此 PR 引起,提交者需请全组攻城湿喝饮料或吃冰棍(由被请者决定); 将 fork 的仓库与上游同步时,应使用 git fetch upstream && git rebase upstream/master(或 code sync -r ),而不是 git merge 或 code sync(这里 code 是指面向 CODE 系统的一个命令行工具),以保持清晰的提交历史,并防止覆盖他人的修改; 注意安全问题,对于 SQL 拼字符串,模版中有 |n 的,以及处理用户输入等地方都需要仔细 review,更多请参考 Web 安全开发指南 对于松散或娱乐性项目、小工具项目,并不会那么严格的 review,这也取决于 owner 自己,他可以借这个项目寻找到一位导师,来帮助他进行 review:

    举一个具体的例子,例如东西团队的 Android 版本的开发,实际上最开始只是团队内部的一些成员想学习 Android 开发自发组织起来的,但一开始就找了移动组的同学来随时帮助 review。

    对于 CODE 项目本身,所有工程师都可以向 CODE 上的任意项目提 PR,也都可以是 CODE 的 reviewer,同时所有工程师的代码都需要经过 review 才会被 merge 到 master 分支。

    发展到现在,豆瓣的 review 基本上都是自发,很少遇到需要 review 的代码堆积的情况。代码讨论区里据说时不时会出现美女图,这可能是刺激工程师们去 review 的因素之一;另外,CODE 系统本身也有奖励机制,鼓励大家去评论别人的代码。

    CODE 系统的奖励机制主要有积分和勋章这两个部分。积分的规则主要就两个:

    提交的 PR 被 merge,增加 100 点积分 提交的 PR 被评论,增加 5 点积分 目的就是鼓励多发 PR。一般来说,小 PR 要好过大 PR,不过有时候开发任务比较紧的时候,发出比较大的 PR 也是在所难免。

    勋章系统在 CODE 早期阶段就做了进去,早期的奖励规则主要跟代码提交相关,例如给开源项目发过 Patch 并被 merge 会有相应的徽章。现在 CODE 团队对勋章系统有一些新的规划:

    目前希望徽章系统可以独立出来,只是一套独立的 API,任何人任何产品线都可以去设置自己的奖励规则,让这种奖励变成不是一种公司行为,而是更小的行为。例如 antispam 组,可能就会有百人斩徽章,这个对于其他组可能就不是那么必要,当然如果你跨界帮助过 antispam 组,那么也有可能会获得这个徽章。

    CODE 上没有设置惩罚机制。

    测试

    相比 Github,CODE 有一些非常实用的地方,比如在提交代码入库之前可以先在 CI 里面完成自动测试,reviewer 可以直接看到代码测试是通过(绿色)还是失败(红色);代码完成 merge 之后还可以通过 DAE 直接往线上部署。持续集成、自动测试、监控、部署这些都是独立系统,与 CODE 都是靠 API 来进行交互。

    对于测试的实现,豆瓣对开发者有明确的要求:

    单元测试和集成测试的代码都是由开发者自己提供的,这是一种义务。测试的写法就是按照标准的 Python unittest 来写,断言比较推荐直接写 assert x == y。

    投奔 git 之路

    CODE 团队在下面的问答中分享了他们使用 git 的心得和经验,无论对正在使用 git 的团队还是还没有使用 git 的团队都是很好的参考。

    InfoQ:你们当时从 SVN、Mercurial 转换到 CODE,这个过程好像还挺顺利的?有没有遇到过一些阻力?如何化解的?

    清风:其实过程并没有想像的那么愉快与顺利,这个过程 CODE 虚拟团队还是做出了很大努力的,日常的营销(拉人下水是很重要的),对于需求的快速满足,这些才是保障 CODE 逐渐被接受的重要因素。

    开始的时候 CODE 功能比较简略,但大家毕竟都是用过 GitHub 的人,所以对功能上肯定是有要求的,经常会听到,某某某功能 GitHub 是有的,但 CODE 就没有,这个没法用啊,要不咱们还是尝试买 GitHub 企业版?这个时候其实也没有什么特别的方法,就是需要快速的去满足这个需求,并加以实现,可以比较自豪的说,CODE 的一些关键功能都是在 1-2 天,大一点的功能不超过 1 周实现的。

    而且有趣的是,甚至有些功能在一开始的时候会认为和 GitHub 不一致,但后来发现习惯了甚至变成了一个设置项。最早 CODE 是双击某一行来进行 line comment 的,但 GitHub 是点击行首的 “+” 号,进行 comment ,后来 CODE 也实现了行首点击 “+” 号来 comment 的功能,但很多成员觉得双击很方便。这个还产生了很多的争论,有从方便性说的,有从 UI 人机交互规范谈起的,为了统一这两个派别,于是我们只好把这个做成一个设置项。

    黄小毛:补充一下,豆瓣内部的团队是一个一个的迁移到使用 CODE 上的。在这个过程中,每个团队都会因为开发内容、使用工具、流程的不同,对 CODE 往往会提出不一样的需求,CODE 开发过程中都分别为这些团队做过相应的开发(其实也是因为这些功能之前都太简陋),一步一步的完善功能。常常是某个团队遇到了什么问题,CODE 开发人员都直接上门去解决,如果解决不到的,就加功能回来开发,一步一步得到使用团队的信任;同时因为 CODE 在豆瓣内部是架在 DAE 上的,常常需要 DAE 团队紧密的配合;各个产品部署上线的一套系统也逐渐使用 CODE 做为仓库之后,与 DAE 和 SA 的合作也逐步加深,就是这样慢慢的让整个产品线、平台部门、SA 部门,都转移到 CODE 上来的。

    说个笑话,以前 CODE 开发还不稳定的时候,极少在白天上班时间上线,担心如果有什么状况,豆瓣几乎所有的工程师都要等着,耽误开发,可见豆瓣工程师对这个开发环境的依赖。这也是一步一步过来的。

    InfoQ:对于 git,CODE 团队有没有特别的经验要分享?

    CODE:大体上 git 就像大家那么用没有什么问题,只是开发过程中其实发现了一些 git 的问题,比如 git 编码存在问题,没有对 commit log 和 path 做 encoding ,在 Web 显示的时侯需要额外的处理。可以参考下这篇文章。

    git 对大文件大仓库的支持不是很好,当一个项目变大的时候,适时的拆分一下。要不然就要像 Fackbook 那样去 hack hg 来支持大仓库。

    GitHub 的 fork 模式导致磁盘空间被大量重复文件占用,GitHub 的工程师解决了这个问题,CODE 团队也在尽量解决这个重复文件的问题。

    Java 实现的 git 库 CODE 团队还没有研究过,但是从 C、Python、Ruby 以及 Javascript 实现的 git 库并没有一个完美的解决方案,都需要混用 git 命令和第三方库。期待在不久的未来,libgit2 能完全取代 git 的核心库,成为一个完善的 git 库。目前 libgit2 还有一些内存泄漏的问题没有解决。

    InfoQ:如果现在让你们重新设计 CODE,会在哪些方面做调整和重新的规划?是否会考虑基于类似 GitLab 进行二次开发?

    CODE:现在的重构其实就是一次重新设计,最重要的方向就是把 API 和 UI 分离。

    最开始做 CODE 的时候,其实考察过 GitLab,可惜当时的 GitLab 还不够成熟。如果当时的 GitLab 足够成熟,很有可能就会基于 GitLab 来开发了,不过,幸好没有发生这样的事情。

    InfoQ:你是否会建议所有还在使用 svn 和 hg 的团队早日脱离苦海、投奔 git?

    CODE:这个问题我一直是这么看的:git 固然强大,但是能否正确使用也是关键。我走访过很多团队,有些团队可能连版本控制工具本身都还没有掌握,例如每天提交一次,甚至一周提交一次,commit log 乱写,不使用分支,等等,这些都是使用者的问题。像 Facebook 最近就表示他们 hg 用的就不错(当然他们做了很多修改)。如果已经有了版本控制工具的正确使用习惯,那么我个人还是推荐 git。

    相关资料

    相关演讲和分享

    DAE 系统的设计(洪强宁教授在 PyCon 2013 上的演讲,文字摘要参考这里) 清风在 C2D2 的分享(Slide) 段念在 QCon 上海 2013 大会的分享 CODE 相关开源项目列表

    CODE 框架 GPack Ellen Linguist PyCharlockHolmes Scanner Mikoto P CodeLive CODE 依赖的 Douban 开源库列表

    https://github.com/douban/douban-utils https://github.com/douban/douban-sqlstore https://github.com/douban/douban-mc https://github.com/douban/douban-quixote https://github.com/douban/python-libmemcached https://github.com/douban/douban-orz 感谢以下豆瓣的同学在本次采访中提供的支持

    段念:豆瓣网工程副总裁,本次采访的策划。 清风:CODE 团队 leader 黄小毛:CODE 团队成员 XTao:CODE 团队成员 大落:CODE 团队成员

  • 赶紧去试试吧,顺便反馈下人家的要求

  • 消息称雅虎总编辑已离职:原因不明

    新浪科技讯 北京时间 1 月 17 日午间消息,美国科技博客 Re/code 报道,在雅虎 CEO 玛丽莎·梅耶尔炒掉 COO 亨里克·德·卡斯特罗 (Henrique de Castro) 之后仅仅一天,雅虎总编辑杰·辛格 (Jai Singh) 也已离职。

      在卡斯特罗离职的消息传出后,梅耶尔昨天发送内部邮件对这一事件进行了说明,并宣布了该公司内部的一些调整。根据该邮件,雅虎的媒体及编辑部门将由首席市场营销官 (CMO) 凯西•萨维特 (Kathy Savitt) 负责。

      Re/code 的卡拉·斯韦什尔 (Kara Swisher) 评论称,让市场营销人员负责媒体运营足以使大量记者伤心离去,但辛格离职的原因尚不清楚。另外,据称萨维特在雅虎内部口碑 “褒贬不一 (礼貌地说)”。

      一位雅虎发言人向美国科技博客 BI 透露,辛格明天就会离开雅虎。

      辛格于 2011 年加盟雅虎并担任专为他而设置的总编辑一职。此前,他曾担任过互联网第一大报《赫芬顿邮报》(Huffington Post) 的执行主编,1996 年,他还创建了 CENT News.com 网站。

      BI 评论称,辛格与卡斯特罗的接连离开可能仅是巧合,但如此凑巧的时机难免引起各种猜测。

  • 身为码农,为 12306 说两句公道话

    by 西西河

    我曾在淘宝写过一段时间代码,2012 年在一家百强民企做电商副总,当时在极为艰苦的条件下带队开发了一个 B2C 网站,走支付宝和银联支付通道,年营业额千万级(当然实在太少了,我只是说这个网站投入了实际的运营)。

    也就在那个时候,我对 12306 嗤之以鼻,觉得他们做得太烂了,认为自己能带队花几百万半年时间做个好的出来。于是我狂妄地想做一个开源的订票系统给他们。我花了一个星期时间思考建立数据模型,思考到库存这一步的时候,我才发现,12306 的库存复杂性比淘宝、京东高很多倍,运算量也大很多倍。传统的分布式数据库、缓存、负载均衡技术并不能恰好满足 12306 的需求。

    在平时,12306 也就是个正常的电商网站。但一到黄金周,12306 就是一个全站所有商品都秒杀,所有 SKU 都是动态库存的变态。

    即使不考虑线下既有的电话、代售点等渠道,要实现一个 12306,最少最少也是千万级别的硬件投入(这是当时的估算,没有精算,可能与实际相差较大,总之,我说得不一定对,12306 的业务也许没我说的那么复杂,但也绝不是某些人喷的那么简单),软件和人力另算。那些叫嚣只要 40 台服务器、只要 2 个架构师 4 个程序员、大谈分库分表和前端 CDN 的人们,只是纸上谈兵罢了。所谓初生牛犊不怕虎,做了三年 CMS 和 BBS,就以这个经验来喷 12306,未免太天真了。

    媒体人喷 12306,是他们不懂技术,没有能力和耐心来分析背后的难度。技术人员喷,则是因为大部分的技术人员在短时间思考时,容易陷入过于乐观的误区,经典的例子就是估算工作量,程序员们往往容易估算出一个超短的工期,把写程序的工作乐观地想象成了打字员照稿敲键盘的工作。

    知乎那篇文章,我觉得不是洗地。排名第一和第二的答案都说得很客观。淘宝技术是比 12306 强大很多倍,淘宝现在的系统也是花了 10 倍于 12306 的钱、时间和人才做起来的。根本原因还是铁路运力不能满足春运需求,淘宝也解决不了这个问题。

    12306 这一年来进步非常大。从前段动画验证码、分时段抢票,到后端去小型机、虚拟化、内存数据库的运用。可以说,12306 是中国政府机关做的最强大的网站(电商系统),能在短短一两年内做出这样的改变,几乎是个奇迹,就连一些市场化的民企都望尘莫及,甚至一些上市公司都比不上它!(比如 51job 和 ctrip)。

    事非经过不知难,在网上批判 12306 的人,大部分还是形成了【国企 = 垄断 + 腐败 + 低效 】的思维定势。小部分是真的轻视了它的难度。

    至于 12306 一期工程 3 个亿(含硬件)贵不贵我不评价,我只提供一个数字供参考,百度一年的研发费用(不含硬件)是 10 亿,这个数字来自百度财报。网上能查到。3 亿看起来好大一个数字,真用到超大型的电商系统、搜索引擎系统里面,其实也不算什么天文数字了。

    再解释一下,为什么秒杀压力大,以及为什么 12306 的动态库存很复杂。

    先说秒杀。

    2013年12月25日前后,天猫搞了一个圣诞季积分兑换活动,持续几天。25 号上午 10 点 12 分,放出了 15000 个天猫魔盒(淘宝集市有人卖,大概 190-230 块),从成交记录上看,是 19 秒内全部抢完。

    实际上,我也参加秒杀了,那天的题目特别简单(请输入 xxx 汉字的拼音首字母),我应该是 5 秒内答题完成并提交订单,结果告诉我排队的人太多,挤不进去,并提示 14 秒以后重试。人太多就是因为题目太简单了,门槛越低,5 秒内挤进去的人也越多嘛,如果题目换成【2 克浓度为 3% 的 U235 在大亚湾核电站能发多少度电】,5 分钟之内也不会有 1 万 5 千人跟我竞争。

    我想,14 秒以后哪还有我的事情呀,于是重新答题秒杀,结果出现了服务器错误的页面。反复刷新几次,就告诉秒杀结束了。

    在群里问了一下同事,有不到 10 个人回答我,都说没秒到(也可能秒到的人闷声发大财,不回复我)。

    淘宝是什么技术水平呢,淘宝有至少 4000 技术人员,至少 4 万台服务器(这都是两年前的公开数据了,按规定可以谈论),2013年11月11日成交额 351 亿,2012 年全年成交额超过 1 万亿。

    淘宝拥有各种自主研发团队:服务器、交换机(网上可以搜索到淘宝公开的绿色服务器开放标准);操作系统(Linux Kernel taobao 版,yunos 手机操作系统是阿里云的,暂时不计入)、Web 服务器(Tengine)、Java 语言虚拟机(JVM taobao 版)、数据库(MySQL 内核 taobao 版,google 和 facebook 也有自己的版本,HBase 淘宝版、还有自己全部从头开发的 OceanBase)、负载均衡器(LVS,LVS 始创人就在淘宝,担任研究员)、Java 运行容器(Jboss,其创始人之一,王文彬,也在淘宝,担任副总裁)。

    淘宝还有数不清的开源项目和中间件,如高性能 Java 通信中间件 HSF、分布式数据库中间件 TDDL、异步消息系统 notify 等等等等。

    以淘宝这样的技术水平,也不能做到秒杀时让每个用户都没有拥挤感,为什么呢?

    一是要尊重物理原理,一台服务器一秒钟能承受的计算量是有极限的,任你怎么优化,采用多高效的算法和编程语言,都突破不了某个极限,比方说汽车发动机驱动的 F1 赛车至今也不能突破 400 公里的时速(超音速推进号那个 1 千多公里的时速不能算,那是飞机引擎驱动的)。再往深了说,就不容易懂了。感兴趣的可以从著名的 C10K 问题开始看起。

    二是要考虑经济效益,十一黄金周的时候,北京主城区到八达岭长城的路堵得严严实实,但不能因为黄金周的高峰,就把这段路修成长安街那样 10 车道的高速公路。否则的话,花费天文数字(真的是天文数字,12306 那 3 个亿大概只够修 1-3 公里)。修了一段路,黄金周是可以飙到 80 公里/小时了,可平时呢,拿来给两边的居民晒谷子?

    淘宝目前的硬件和带宽数量,已经超出日常运营的需求了,就是留了相当大的余量给大促销(众所周知的是双十一,双十二,其实基本每个季度都有大促销,每个月都有促销,甚至天天都在促销——聚划算)。amazon 当年就是为了应对黑色星期五的大促销购置了大量的服务器,平时订单量没那么大了,amazon 就把富余的服务器拿来搞云计算了。顺便说一下,阿里云是当今中国第一世界数一数二的云计算服务商,和 amazon 走的路也有点像。

    再说动态库存。

    淘宝秒杀天猫魔盒的时候,只有一个商品(行话叫做 SKU),它的库存是 15000 个。有一个人秒杀到了,库存就减 1,19 秒卖完的,一秒要成功产生 789 个订单(下订单的请求可能是 8 万个,只是可能啊,非实际数字,也可能是 1 万个,用于说明一下壮观程度)。想象一下,你在广场上卖火车票,一秒钟有 8 万人举着钱对你喊:卖给我!

    上过大学的人都知道,比秒小的时间单位还有毫秒、皮秒、飞秒。但交易系统登记一个交易可不像电子绕着原子核跑一圈那么简单,它要做这些事:检查是否恶意访问、取到系统时间、取到顾客默认收货地址、核对顾客秒杀资格(当时的规定是天猫 T2.T3 达人)、生成订单号、把顾客 ID 系统时间订单号收货地址写入订单系统、扣除顾客天猫积分、商品库存减一、给顾客打标记(每人只能秒一个,下次不能秒了)等等,这每一件事都要花费毫秒级别的时间,这些操作加起来的时间可能是接近 1 秒级别的,但由于淘宝的服务器比较强悍,而且采用了分布式和集群技术,结果比 1 秒理想一点。但即使有 1 万台服务器,也不能把这个时间稀释成万分之一秒,因为,商品只有一种,它有 15000 个库存,对应的数据库记录只有一行,所有的交易请求都要到这里来处理。

    能不能把这 15000 个拆分成 5000 个商品并分配到 5000 台服务器上呢?那样不就可以 5000 台服务器同时处理了吗?答案是不能,首先,5000 个商品,意味着有 5000 个商品详情页,5000 个购买按钮,这对前期的营销、引流是个灾难。基本上就没法做引流入口了,显然这违背了商业管理原则,人为增加了信息混乱程度。其次,天猫魔盒秒杀也不是啥大事,即使按官方标价 399 元来计算,也就 6 百万的交易。如果 6 百万的交易要花费那么大的配套成本,那就太不划算了。再次,淘宝有十几亿商品,这十几亿商品的展示交易和管理,本来就是分布到上万台服务器上去了。没有必要再把每个商品按库存拆成多个商品了。

    这 789 人抢到了,还不一定会付款(99 积分换天猫魔盒还好一点,不需要去网银,成本也极低,大部分是会付款的,3999 秒杀 iPhone 5S 就不一定,有人可能网银有问题,有人可能改变主意不想要了),所以就又带来订单取消重新恢复库存的问题。还有想要的消费者们,会认为还有机会,继续在前台刷一会儿,最终这个秒杀会被热情的消费者们猛刷 30 秒到 1 分钟。

    (超卖这一部分科普笔法写得有错误,鉴于 12306 目前全在内存数据库中读写,没有产生超卖问题,先把这个段落删去。感谢 @ 吹西门的雪 指正)

    好了,讲了这半天淘宝,可以说 12306 了吧?

    我以北京西到深圳北的 G71 次高铁为例(这里只考虑南下的方向,不考虑深圳北到北京西的,那是另外一个车次,叫 G72),它有 17 个站(北京西是 01 号站,深圳北是 17 号站),3 种座位(商务、一等、二等)。表面看起来,这不就是 3 个商品吗?G71 商务座、G71 一等座、G71 二等座。大部分轻易喷 12306 的技术人员(包括某些中等规模公司的专家、CTO)就是在这里栽第一个跟头的。

    实际上,G71 有 136 * 3 = 408 种商品(408 个 SKU),怎么算来的?请看:

    如果卖北京西始发的,有 16 种卖法(因为后面有 16 个站),北京西到:保定、石家庄、郑州、武汉、长沙、广州、虎门、深圳。。。。都是一个独立的商品,

    同理,石家庄上车的,有 15 种下车的可能,以此类推,单以上下车的站来计算,有 136 种票:16+15+14....+2+1=136。每种票都有 3 种座位,一共是 408 个商品。

    好了,再看出票时怎么减库存,由于商务、一等、二等三种座位数是独立的,库存操作也是一样的,下文我就不再提座位的差别的,只讨论出发与到达站。另外,下文说的是理论世界的模型,不是说 12306 的数据库就是这么设计的。

    旅客 A 买了一张北京西(01 号站)到保定东(02 号站)的,那【北京西到保定东】这个商品的库存就要减一,同时,北京西到石家庄、郑州、武汉、长沙、广州、虎门、深圳等 15 个站台的商品库存也要减一,也就是说,出一张北京到保定东的票,实际上要减 16 个商品的库存!

    这还不是最复杂的,如果旅客 B 买了一张北京西(01 号站)到深圳北(17 号站)的票,除了【北京西到深圳北】这个商品的库存要减一,北京西到保定东、石家庄、郑州、武汉、长沙、广州、虎门等 15 个站台的商品库存也要减 1,保定东到石家庄、郑州、武汉、长沙、广州、虎门、深圳北等 15 个站台的商品库存要减 1。。。总计要减库存的商品数是 16+15+14+。。。。+1=120 个。

    当然,也不是每一张票都的库存都完全这样实时计算,可以根据往年的运营情况,在黄金周这样的高峰时段,预先对票做一些分配,比如北京到武汉的长途多一点,保定到石家庄的短途少一点。我没有证据证实铁道部这样做了,但我相信,在还没有 12306 网站的时候,铁道部就有这种人工预分配的策略了。

    想象一下,8 万人举着钱对你高喊:卖给我。你好不容易在钱堆里找到一只手,拿了他的钱,转身找 120 个同事,告诉他们减库存,而这 120 个同事也和你一样被 8 万人围着;也和你一样,每卖出一个商品要找几十个人减库存。。。这就是 12306 动态库存的变态之处。比你平时买东西的任何网站的库存机制都复杂几十上百倍。

    再说一下抢票插件,机器永远比人快,当你好不容易从 8 万人里突出重围,来到了柜台前,你发现,我操,来了 10 万根绑着钱的竹竿,而且当有退票出来的时候,你要闯过 3 层人肉才能接近柜台,竹竿在 8 个人身后一伸,钱就到了柜台前。你低头看了一眼手机,票就没了,竹竿却永远在那里伸着,永不低头,永不眨眼。如果没有这 10 万根竹竿,虽然你很可能还是抢不到票,但不至于沮丧成这样:我 TM 为什么总是手最慢的一个?!!

    防机器人抢票,也不是加个图片验证码那么简单。我写过文章系统性分析过,图片验证码有 6 种机器暴力破解的办法,抢票插件用的是我说的第三种,OCR 识别。Google 采用的 Wave 波形字母已经能比较好地防住机器 OCR 了,ems.com.cn 上的验证码就是反面教材,机器 OCR 成功率接近 100%,12306 的比 ems 的图片验证码强一点。不过,验证码设置得复杂一点吧,人们要喷:这只是便宜大学生和办公室白领,农民工连 26 个字母都认不齐,怎么搞?搞动画验证码吧,也有人喷,视力不好的人怎么办?最后验证码搞得太简单了,皆大欢喜了,其实最高兴的是开发抢票插件的公司。

    就算采用了机器完全不可能识别的验证码,也防不住社会工程学的破解办法。招募一堆网吧打游戏的青少年朋友,每成功输入 50 个验证码给 1 块钱,或者等值的虚拟货币、游戏装备,我保证想赚这个钱的人数不胜数。这点钱对转卖车票的利润而言,是可以接受的成本。有没有什么技术可以防住社会工程学的破解办法呢?能防住网吧青少年的验证码只有【2 克浓度为 3% 的 U235 在大亚湾核电站能发多少度电】。

    以上讨论只是把 12306 当成和淘宝一样没有历史包袱从零起步的交易系统,实际上,它不是,它后面的票池,还有电话售票、火车站售票、代售点售票等多个传统渠道要服务。除了客运服务,12306 还有全国最大(很可能也是全球最大)的大宗物资货运系统。

    架空政策(包括定价政策、警方打击黄牛政策、身份验证政策)谈技术,是不可能解决春运抢票困局的,要想让春运的时候每个人在 12306 抢票都毫无拥挤感(但不一定能抢到票,铁路运力摆在那),那就是逼着 12306 买一大堆服务器对付春运,春运过去后,成为跟 amazon 一样牛逼的云计算服务商。和逼北京修一条 10 车道的高速公路去八达岭长城一个道理。

    目前的 12306 技术上是还有问题,比如,抢票高峰,输入个身份证号和图片验证码都卡得要死(本人亲测),服务器端繁忙,你浏览器端卡什么呀。

    但人家在进步。相信 2014 年春运的时候,技术已经不再是一票难求的主要问题。在铁路运力不可能神速增加(孙中山先生计划的 20 万公里铁路,土共修了快 70 年,才修到 10 万公里)的情况下,要做到春运更公平地买票,需要停靠政策调整。

    下文针对的是春节国庆这种非常暑期。其它时期,大部分线路保持现状就行了,问题不大,极少部分票源紧张的线路可以按春运处理:

    1. 拍卖法,价高者得之

    当硬座票拍出飞机票价格的时候,相信票就不难买了(可惜就是贵了),也没有那么多黄牛了。要说淘宝有什么能帮 12306 一下子搞定技术问题的,淘宝的拍卖系统可以帮忙,浙江省高院在淘宝拍卖一年多,成交 26 亿。

    可惜这个方法不可能实行。现在的高铁票价都被媒体和意见领袖喷成啥样了,何况是拍卖。再说,火车票毕竟是生存之刚需,票价 20 年来不涨本来就有照顾补贴的成分在里面,全拍卖可能也是不妥当。

    1. 抽签法,运气好者得之

    开车前 2 个月开放报名,开车前 7 天抽签,中途可取消。预存票款,抽不中退款。上传身份证和正脸自拍照,机器核对。

    这样的话,拦截黄牛的成功率就高很多了,黄牛可以预存票款,可以找到大量真实身份证号,你黄牛再让每个给你身份证号的人把身份证照片和脸部自拍也给你试试?即使有人真想找黄牛,给身份证照片还是会犹豫一下吧。而且中间手工操作多了很多,黄牛成本提高,还不一定搞得到票。反正都是碰运气,我想真正的消费者还是会选择自己先去碰运气吧。

    这个方法实施难度也大,无论怎么设计抽签规则,必然有人大叫 “有黑幕,不要相信政府”。

    开车前 7 天出抽签结果,改变行程的人应该在 7 天前就能决定改还是不改了。没抽到的也还有时间想别的办法。当然不一定是 7 天,15 天,10 天也可以,具体几天要有数据模型来算。

    1. 拍卖 + 抽签

    软卧、高铁商务座等高价位的,拍卖,反正买这个的是经济能力相对较强的。那就拼谁经济能力更强吧。

    硬座、站票抽签。

    1. 凭身份证进站,车票跟发票一样,是报销凭证,不是进站凭证;退票后钱进入 12306 账户,不可提现,只可该乘客下次乘车用;黄金周期间,个人账号最多订购 10 张票

    这个办法可以打击黄牛囤票再转卖

    运行一段时间后,按账户余额弄个排行榜就知道谁是黄牛了

    可惜这个需要车站设备改造配合。

    -----更新-----

    收到有同行质疑 136 个库存的合理性,他提出的方案是:

    北京西(01 号站)到深圳北(17 号站)一共设置 16 个商品(SKU):

    SKU01:01 号站 到 02 号站

    SKU02:02 号站 到 03 号站

    ...

    SKU16:16 号站 到 17 号站

    如果出一张 01 号站到 17 号站的票,就把 SKU01/SKU02....SKU16 这 16 个 SKU 的库存都减一。

    这种做法是可以运行的。我原来参与设计的一个 ERP 系统就是这样的做的。

    16 个商品方案的优点是商品数会比较少,缺点在于查询性能较低,要查询 16 次才能知道【北京西到深圳北】还有没有余票。而 136 个商品的设计,穷举了所有出发和到达站点的组合,出票前只需要查询 1 次库存就知道还有没有余票。

    大部分面向公众的网站,其业务特点都是读多写少。电商系统的库存查询次数更是远远多于库存修改次数的。在秒杀系统中,可能会出现 99% 的请求查库存 1% 请求改库存的情况。 像 12306 春运抢票这种场景,在秒杀工具(抢票软件)的推波助澜下,查询 1 万次库存才成功出一张票也不是没有可能。

    还有人说,KFC 的食品可以单卖,也可以套餐,为什么没像我一样搞出这么多 SKU,那是因为,KFC 门店的人肉查询频率非常低,没有必要为了优化查询性能把库存结构设计成那样。

    转自:http://www.cchere.com/topic/3965719#C3965719

  • 貌似只有 SERV-U MFT SERVER 版本的可以和 AD 集成

    [attach] 2254[/attach]

  • 一个 Serv-U 的单一实例可以用来建立多个虚拟 FTP 服务器,每个 FTP 服务器在管理程序中称之为一个域 (Domain)。因此要使 FTP 服务器可用最少需要创建一个域。每一个域都有用户、组和设置与之相关联,一个域至少有一个用户才有意义。在你第一次启动 Administrator 管理程序时,通常向导会引导你初始化一个域并创建一个用户。通常的层次结构如下: Serv-U Server Domain 1 User account 1 User account 2 User account 3 Domain 2 User account 1 User account 2 Domain 3 User account 1 User account 2 每一个域包括: name:该域的描述名称,用来标识该域,与 DNS 名称无关。 Domain ip address:该域所使用的 IP 地址,可以使用某个地址如 10.10.10.10,也可以使用 “Use any available IP address” 选项使用多个地址。如果 FTP 服务器的地址是动态分配的,那么此处可以不填。 在同一 FTP 服务器上的每一个域的 IP 与端口组合是唯一的。也就是说不能够创建多个虚拟 FTP 服务器,它们共享相同的 IP/端口,这与 IIS 的虚拟主机不同。 Domain type:有两种:store in .ini file 和 store in computer registry。也就是将域配置信息放置在 ServUDaemon.ini 文件中还是注册表中。对于小于 500 个用户的小型站点来说,建议将配置信息放在 ServUDaemon.ini 文件中,超过这个用户数目可以考虑使用注册表类型的域。因为 95/98/me.ini 类型的文本文件有 64K 的限制,nt/2000 虽然没有这方面的限制,但当文本文件太大时,操作会比较慢。 注册表位置是:\HKEY_LOCAL_MACHINE\Software\Cat Soft\Serv-U\Domains\ Ftp port number:域要监听的 Ftp 端口,通常为 21. General Max no. of users 同时连入域的最大用户数。不填为不限制。 Virtual path mappings 虚拟路径映射允许你将映射物理路径到本地或网络的其它目录中去。使用它你可以创建与你的物理目录完全不同的虚拟路径。如果用户被锁定在主目录下,这项功能将允许他们访问主目录之外的其他目录。

    虚拟路径包括: Physical path:真实路径。可以使用常规文件路径或者是 UNC 路径或者是网络驱动器。 Mapped to:映射到哪一处目录下。可以使用如下的变量: %HOME% - 代替用户的主目录 %USER% - 代替用户的账户名 Virtual name:最终通过 FTP 客户端显示给用户的路径名。

    上述概念比较难以理解,现举例说明: 假设你将 aboutnt 用户的主目录设置为 d:\aboutnt 并将用户限制于该目录下,那么用户将只能够访问 d:\aboutnt 下的目录和文件,如果用户这时想访问 c:\data 下的数据就需要虚拟路径的帮助。可以这样设置:在虚拟路径对话框下单击添加,物理路径填写:c:\data,映射到填写:%home% 或 d:\aboutnt,虚拟路径名称填写 data,也就是将 c:\data 物理目录映射到了用户的主目录下,文件夹的名称为 data。当用户通过 FTP 客户端连入服务器时,会在其主文件夹下出现一个名为 data 的子文件夹,而实际上这个文件夹并不物理的位于主目录下。 请注意,虚拟路径映射设置完毕后并不会立即生效,它需要用户对该物理目录进行目录访问规则设置,使该目录允许用户访问才行。 Links 关于 Unix 的一些设置。

  • changelist 现在是什么状态?

  • rename 经常出这种问题,具体原因不知,所以最好是删除重建,不要改名,包括 baseline 名字

    只要 branch type 中流的名称是重命名的名称就没有问题

  • 3、build 脚本

    <?xml version="1.0" encoding="UTF-8" ?>

    <!-- svn 比较项目最新路径 --> <!-- svn 备份路径-->

    <!-- 项目名称 --> <!-- 目标项目的 Web 名称(WEB-INF 上一级的目录名称)-->

    <!-- svn 改动文件列表信息 -->

    <!-- svn 导出/切出文件存放目录 --> <!-- svn 导出/切出文件编译后存放目录 --> <!-- svn 增量文件保存目录 --> <!-- svn 增量文件编译后保存目录 -->

    <!-- 利用 jdt 编译 class 解决泛型不能转换的问题 需要将 jdtCompilerAdapter.jar org.eclipse.jdt.compiler.tool_1.0.1.v_793_R33x.jar org.eclipse.jdt.core_3.3.3.v_793_R33x.jar org.eclipse.jdt.debug.ui_3.2.102.v20071002_r332.jar 复制到 ant_home/lib 目录下 -->

    <!-- that is to test i svnant is available //-->

    <!-- 比较差异 增量文件 -->

    <!-- 下载 切成 导出 服务器上最新代码 -->

    <!-- javac 编译 --> compile ${dest.path} ......

    <!-- -->

    <!-- 利用 JDT 编译 --> compile ${dest_path} ......

    <!-- 利用 JDT 编译 SVN 最新项目 --> <!-- 回调任务 -->

    <!-- 将全部项目的 class 建立 jar 包 -->

    <!-- 导出增量文件 -->

    <!-- 利用 JDT 编译增量文件 -->

    <!-- 全部打包 -->
    create war file.......

    <!--得到当前日期-->


    <!-- 全部打包 -->

    <!-- 全部打包 -->

    <!-- svn 全量包 --> <!-- 增量包 -->

    4、build 的配置文件内容

    #Mon, 04 Nov 2013 11:18:12 +0800 svn._url=http://172.31.100.100/svn/iMVS_DataComm2 bak.svn._url=http://172.31.100.100/svn/iMVS_DataComm svn.username=hoojo svn.password=mypass webapp.name=iMVS_DataComm web.root=WebRoot increment.file=patch.txt

    javac.debuglevel=source,lines,vars javac.target=1.6 javac.source=1.6 javac.debug=true

    运行 svn_war 任务可以打全部的包,也就是 svn 最新地址的项目工程包。

    运行 increment_war 任务可以打增量包,也会形成一个 war 文件。

    如果你需要发布到 tomcat 目录,可以写一个任务 copy 相关 war 包到 tomcat 的 webapps 的目录下,这个很简单~如果你需要调用 tomcat 的相关任务或命令,你需要在 build 脚本中加入

    <!-- tasks: deploy,undeploy,reload,stop,start,list,roles,resources --> 关于这些命令使用方法有兴趣的可以自己研究研究。

    四、总结

    整个流程稍微有点复杂,只要思路清晰这个 build 脚本还是很容易编写的。前提是你要懂得 Java 发布、编译、部署的流程,很多人用 eclipse 或 MyEclipse 来发布工程,好像很简单。其实在工具背后也是使用这些脚本完成的,知道在用户目录下有一个 .m2 的目录么,这个就是 eclipse 工具的 meven 的缓存和配置的内容。所以当我们不使用这些 ide 的情况下,你怎么编译、部署你的项目呢~!这篇文章只是一个抛砖引玉的效果,希望能给大家一个启示。在这之前,我在网上搜集了些资料也没有找到打增量包的比较好的方法,全都是手动方式。

    同时,在一些自动集成或持续集成的智能工具中,也大量的使用到了这方面的技术。如果你想更智能的完成这个项目的发布、部署的话,这里只是其中的第一步。有兴趣的朋友可以研究下 Continuous integration 或 Hudson 等相关自动化集成技术应用。

  • 貌似版上,上传过一个 perl 版本的。。。

  • 是不是一个文件经常出现冲突问题,才这样做的?

    比如自动 checkin 进去的是一种注释内容,手动 checkin 的一般注释不同,所以可能会导致冲突?

    这样的文件很多么?

  • update hook howto 有一个很好的管理共享中央代码库的实例。 https://www.kernel.org/pub/software/scm/git/docs/howto/update-hook-example.txt

    From: Junio C Hamano <[email] gitster@pobox.com[/email]> and Carl Baldwin <[email] cnb@fc.hp.com[/email]> Subject: control access to branches. Date: Thu, 17 Nov 2005 23:55:32 -0800 Message-ID: <[email] 7vfypumlu3.fsf@assigned-by-dhcp.cox.net[/email]> Abstract: An example hooks/update script is presented to implement repository maintenance policies, such as who can push into which branch and who can make a tag. Content-type: text/asciidoc

    How to use the update hook

    When your developer runs git-push into the repository, git-receive-pack is run (either locally or over ssh) as that developer, so is hooks/update script. Quoting from the relevant section of the documentation:

    Before each ref is updated, if $GIT_DIR/hooks/update file exists and executable, it is called with three parameters:

    $GIT_DIR/hooks/update refname sha1-old sha1-new

    The refname parameter is relative to $GIT_DIR; e.g. for the master head this is "refs/heads/master". Two sha1 are the object names for the refname before and after the update. Note that the hook is called before the refname is updated, so either sha1-old is 0{40} (meaning there is no such ref yet), or it should match what is recorded in refname.

    So if your policy is (1) always require fast-forward push (i.e. never allow "git-push repo +branch:branch"), (2) you have a list of users allowed to update each branch, and (3) you do not let tags to be overwritten, then you can use something like this as your hooks/update script.

    [jc: editorial note. This is a much improved version by Carl since I posted the original outline]


    #!/bin/bash

    umask 002

    If you are having trouble with this access control hook script

    you can try setting this to true. It will tell you exactly

    why a user is being allowed/denied access.

    verbose=false

    Default shell globbing messes things up downstream

    GLOBIGNORE=*

    function grant { $verbose && echo >&2 "-Grant- $1" echo grant exit 0 }

    function deny { $verbose && echo >&2 "-Deny- $1" echo deny exit 1 }

    function info { $verbose && echo >&2 "-Info- $1" }

    Implement generic branch and tag policies.

    - Tags should not be updated once created.

    - Branches should only be fast-forwarded unless their pattern starts with '+'

    case "$1" in refs/tags/) git rev-parse --verify -q "$1" && deny >/dev/null "You can't overwrite an existing tag" ;; refs/heads/) # No rebasing or rewinding if expr "$2" : '0*$' >/dev/null; then info "The branch '$1' is new..." else # updating -- make sure it is a fast-forward mb=$(git-merge-base "$2" "$3") case "$mb,$2" in "$2,$mb") info "Update is fast-forward" ;; *) noff=y; info "This is not a fast-forward update.";; esac fi ;; *) deny >/dev/null \ "Branch is not under refs/heads or refs/tags. What are you trying to do?" ;; esac

    Implement per-branch controls based on username

    allowed_users_file=$GIT_DIR/info/allowed-users username=$(id -u -n) info "The user is: '$username'"

    if test -f "$allowed_users_file" then rc=$(cat $allowed_users_file | grep -v '^#' | grep -v '^$' | while read heads user_patterns do # does this rule apply to us? head_pattern=${heads#+} matchlen=$(expr "$1" : "${head_pattern#+}") test "$matchlen" = ${#1} || continue

    # if non-ff, $heads must be with the '+' prefix test -n "$noff" && test "$head_pattern" = "$heads" && continue

    info "Found matching head pattern: '$head_pattern'" for user_pattern in $user_patterns; do info "Checking user: '$username' against pattern: '$user_pattern'" matchlen=$(expr "$username" : "$user_pattern") if test "$matchlen" = "${#username}" then grant "Allowing user: '$username' with pattern: '$user_pattern'" fi done deny "The user is not in the access list for this branch" done ) case "$rc" in grant) grant >/dev/null "Granting access based on $allowed_users_file" ;; deny) deny >/dev/null "Denying access based on $allowed_users_file" ;; *) ;; esac fi

    allowed_groups_file=$GIT_DIR/info/allowed-groups groups=$(id -G -n) info "The user belongs to the following groups:" info "'$groups'"

    if test -f "$allowed_groups_file" then rc=$(cat $allowed_groups_file | grep -v '^#' | grep -v '^$' | while read heads group_patterns do # does this rule apply to us? head_pattern=${heads#+} matchlen=$(expr "$1" : "${head_pattern#+}") test "$matchlen" = ${#1} || continue

    # if non-ff, $heads must be with the '+' prefix test -n "$noff" && test "$head_pattern" = "$heads" && continue

    info "Found matching head pattern: '$head_pattern'" for group_pattern in $group_patterns; do for groupname in $groups; do info "Checking group: '$groupname' against pattern: '$group_pattern'" matchlen=$(expr "$groupname" : "$group_pattern") if test "$matchlen" = "${#groupname}" then grant "Allowing group: '$groupname' with pattern: '$group_pattern'" fi done done deny "None of the user's groups are in the access list for this branch" done ) case "$rc" in grant) grant >/dev/null "Granting access based on $allowed_groups_file" ;; deny) deny >/dev/null "Denying access based on $allowed_groups_file" ;; *) ;; esac fi

    deny >/dev/null "There are no more rules to check. Denying access"

    This uses two files, $GIT_DIR/info/allowed-users and allowed-groups, to describe which heads can be pushed into by whom. The format of each file would look like this:

    refs/heads/master junio +refs/heads/pu junio refs/heads/cogito$ pasky refs/heads/bw/.* linus refs/heads/tmp/.* .* refs/tags/v[0-9].* junio

    With this, Linus can push or create "bw/penguin" or "bw/zebra" or "bw/panda" branches, Pasky can do only "cogito", and JC can do master and pu branches and make versioned tags. And anybody can do tmp/blah branches. The '+' sign at the pu record means that JC can make non-fast-forward pushes on it.

    原文链接: kernel.org 翻译: 伯乐在线 - cjpan 译文链接: http://blog.jobbole.com/54184/

  • 代码库管理

    代码库管理员使用下列工具来设置及维护开发者对代码库的访问。

    git-daemon(1) 允许匿名者从代码库下载 git-shell(1) 可以被用作为限制登录 shell,用于共享中央代码库的用户

    update hook howto 有一个很好的管理共享中央代码库的实例。 https://www.kernel.org/pub/software/scm/git/docs/howto/update-hook-example.txt

    实例

    我们假设下面的内容均在/etc/services 目录下。 $ grep 9418 /etc/services git 9418/tcp # Git Version Control System 从 inetd 运行 git-daemon 来服务于/pub/scm

    $ grep git /etc/inetd.conf git stream tcp nowait nobody \ /usr/bin/git-daemon git-daemon --inetd --export-all /pub/scm

    实际的配置应该在 1 行里。

    从 xinetd 运行 git-daemon 来服务于/pub/scm

    $ cat /etc/xinetd.d/git-daemon # default: off # description: The git server offers access to git repositories service git { disable = no type = UNLISTED port = 9418 socket_type = stream wait = no user = nobody server = /usr/bin/git-daemon server_args = --inetd --export-all --base-path=/pub/scm log_on_failure += USERID }

    [list=1] [] 检查 xinetd(8) 文档并设置,这个文档来自于 Fedora 系统。其他也许会不一样。 [] 授予开发者只推/拉访问操作权限。 [/list]

    $ grep git /etc/passwd <1> alice🇽1000:1000::/home/alice:/usr/bin/git-shell bob🇽1001:1001::/home/bob:/usr/bin/git-shell cindy🇽1002:1002::/home/cindy:/usr/bin/git-shell david🇽1003:1003::/home/david:/usr/bin/git-shell $ grep git /etc/shells <2> /usr/bin/git-shell [list=1] [] 登录 shell 被设置到/usr/bin/git-shell, 不允许 git push 和 git pull 以外的任何操作。用户应该会获得一个访问此机器的 ssh 权限。 [] 在许多发布版本中,/etc/shells 需要列出作为一个登录 shell 需要的内容。 [/list]

    CVS 风格的共享代码库

    $ grep git /etc/group <1> git🇽9418:alice,bob,cindy,david $ cd /home/devo.git $ ls -l <2> lrwxrwxrwx 1 david git 17 Dec 4 22:40 HEAD -> refs/heads/master drwxrwsr-x 2 david git 4096 Dec 4 22:40 branches -rw-rw-r-- 1 david git 84 Dec 4 22:40 config -rw-rw-r-- 1 david git 58 Dec 4 22:40 description drwxrwsr-x 2 david git 4096 Dec 4 22:40 hooks -rw-rw-r-- 1 david git 37504 Dec 4 22:40 index drwxrwsr-x 2 david git 4096 Dec 4 22:40 info drwxrwsr-x 4 david git 4096 Dec 4 22:40 objects drwxrwsr-x 4 david git 4096 Nov 7 14:58 refs drwxrwsr-x 2 david git 4096 Dec 4 22:40 remotes $ ls -l hooks/update <3> -r-xr-xr-x 1 david git 3536 Dec 4 22:40 update $ cat info/allowed-users <4> refs/heads/master alice|cindy refs/heads/doc-update bob refs/tags/v[0-9]* david [list=1] [] 把开发者置于同一 git 组中。 [] 将共享代码库配为可被组写。 [] 使用 Carl 的 update-hook 实例,这个实例在 Documentation/howto/, 讲述了分支策略控制。 [] alice 和 cindy 可以推送到主分支,只有 bob 可以推送进 doc-update。david 是发布经理,并且是唯一一位可以创建并推送版本标签的人。 [/list] 支持 dumb 协议传送的 HTTP 服务器 dev$ git update-server-info <1> dev$ ftp [email] user@isp.example.com[/email] <2> ftp> cp -r .git /home/user/myproject.git [list=1] [] 确保你的 info/refes 和 objects/info/packs 是最新的。 [] 上传到由你的 ISP 拥有的公共 HTTP 服务器。 [/list]

  • [i=s] 本帖最后由 scmroad 于 2013-12-27 11:14 编辑

    集成人员

    在一个团队项目中担任集成者的是一名相当重要的人员,他接受别人的修改,评审并且集成并且发布结果,供他人使用;除了那些参与者需要的命令之外,还会使用这些命令。

    git-am(1) 用来采用你的贡献者发电邮寄来的补丁文件。 git-pull(1) 用来从你的可信任的助手处合并内容。 git-format-patch(1) 用来准备并向你的贡献者发送建议选项。 git-revert(1) 用来撤销不好的提交。 git-push(1) 用来发布最新的内容。

    实例

    我典型的 GIT 一天。 $ git status <1> $ git show-branch <2> $ mailx <3> s 2 3 4 5 ./+to-apply s 7 8 ./+hold-linus q $ git checkout -b topic/one master $ git am -3 -i -s -u ./+to-apply <4> $ compile/test $ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus <5> $ git checkout topic/one && git rebase master <6> $ git checkout pu && git reset --hard next <7> $ git merge topic/one topic/two && git merge hold/linus <8> $ git checkout maint $ git cherry-pick master~4 <9> $ compile/test $ git tag -s -m "GIT 0.99.9x" v0.99.9x <10> $ git fetch ko && git show-branch master maint 'tags/ko-*' <11> $ git push ko <12> $ git push ko v0.99.9x <13>

    [list] [] 查看我正在做什么,如果有的话。 [] 查看我拥有的主题分支,并考虑它们的完成度。 [] 读邮件,保存合适的,并且保存那些尚未完成的。 [] 采用它们,交互式地,带着我的签名。 [] 按需创建主题分支,还是由我签名采用。 [] 为内部的还未合并到主分支,也没有作为稳定分支的一部分公开的主题分支重定基线。 [] 从接下来开始,每次都重启 pu。 [] 合并仍然在料理中的主题分支 [] 向后移植极其重要的修正。 [] 创建一个签名的标签。 [*] 确保我不会意外将主分支回滚到我已经推出来的内容。简写的 ko 指向我在 kernel.org 上已有的代码库里,看起来像这样: [/list]

    在从 git show-branch 的输出里,主分支应该有所有 ko-master 有的,并且 next 应该有 ko-next 有的所有内容。

    [list] [] 推出最新内容 [] 也推标签 [/list]

  • 个人开发者(参与开发)

    作为在一个团体项目里参与角色的开发人员,需要学习如何与他人沟通,除了那些单独开发者需要掌握的命令以外,还要使用这些命令。

    git-clone(1) 从上游代码库填充你的本地代码库。 git-pull(1) 和 git-fetch(1) 从 “origin” 得到最新的上游代码库。 git-push(1) 用来共享代码库,如果你采用 cvs 风格的代码库工作流的话。 git-format-patch(1) 用来准备 e-mail 提交,如果你使用 Linux 内核风格的公共论坛工作流的话。

    实例

    复制上游代码库并在其之上工作。提交修改到上游代码库

    $ git clone git://git.kernel.org/pub/scm/.../torvalds/linux-2.6 my2.6 $ cd my2.6 $ edit/compile/test; git commit -a -s <1> $ git format-patch origin <2> $ git pull <3> $ git log -p ORIG_HEAD.. arch/i386 include/asm-i386 <4> $ git pull git://git.kernel.org/pub/.../jgarzik/libata-dev.git ALL <5> $ git reset --hard ORIG_HEAD <6> $ git gc <7> $ git fetch --tags <8>

    [list=1] [] 按需重复。 [] 从你的分支中提取补丁文件,用于电子邮件提交。 [] git pull 命令默认从 “origin” 里取得内容并合并到当前的分支中去。 [] 在拉过内容之后,立即查看在上游仓库中从上次我们检查过之后提交的修改,只检查我们关心的区域。 [] 从一个指定代码库的一个指定分支获取内容并合并。 [] 撤销拉操作。 [] 从撤销的拉操作中回收残存的对象。 [] 不时地,从 origin 处获取官方的标签,并保存于.git/refs/tags/ 。 [/list]

    推进另一个代码库 satellite$ git clone mothership:frotz frotz <1> satellite$ cd frotz satellite$ git config --get-regexp '^(remote|branch).' <2> remote.origin.url mothership:frotz remote.origin.fetch refs/heads/:refs/remotes/origin/ branch.master.remote origin branch.master.merge refs/heads/master satellite$ git config remote.origin.push \ master:refs/remotes/satellite/master <3> satellite$ edit/compile/test/commit satellite$ git push origin <4>

    mothership$ cd frotz mothership$ git checkout master mothership$ git merge satellite/master <5>

    [list=1] [] mothership 机器在你的 home 目录下有一个 frotz 代码库;将它复制,以在 satellite 机器上启动一个代码库。 [] 复制操作默认设定这些配置变量。它安排 git pull 去抓取并保存 mothership 机上的分支到本地的 remotes/origin/* 的跟踪分支上。 [] 安排 git push 去推送本地的主分支到 mothership 机的 remotes/satellite/master 分支 [] 推操作会在 mothership 机的 remotes/satellite/master 的远程跟踪分支上收藏我们的工作。你可以用此作为一个备用方法。 [*] 在 mothership 机上,将 satellite 机上已完成的工作合并到 master 分支去。 [/list] 分支的特定标签

    $ git checkout -b private2.6.14 v2.6.14 <1> $ edit/compile/test; git commit -a $ git checkout master $ git format-patch -k -m --stdout v2.6.14..private2.6.14 | git am -3 -k <2> [list=1] [] 创建一个私有分支,基于熟知(但稍许过时的)标签。 [] 在没有正式的 “合并” 下,向前移植所有 private2.6.14 分支的修改到 master 分支上。 [/list]

  • [i=s] 本帖最后由 scmroad 于 2013-12-27 11:00 编辑

    个人开发者(单独开发)

    单独的个人开发者不会与他人交换修补程序,只用到下列命令,独自在单独的代码库上工作:

    git-init(1) 用来创建新代码库。 git-show-branch(1) 用来查看你在哪里。 git-log(1) 查看发生过什么。 git-checkout(1) 和 git-branch(1) 用来切换分支。 git-add(1) 用来管理索引文件。 git-diff(1) 和 git-status(1) 查看你正在做什么。 git-commit(1) 将内容推进现分支 git-reset(1) 和 git-checkout(1)(带路径名 参数)放弃修改。 git-merge(1) 用来合并本地分支 git-rebase(1) 用来维护主题分支 git-tag(1) 用来给已知点打标签

    实例

    用 Tar 包作为一个新代码库的起始点

    $ tar zxf frotz.tar.gz $ cd frotz $ git init $ git add . <1> $ git commit -m "import of frotz source tree." $ git tag v2.43 <2>

    [list=1] [] 添加现目录下的所有文件。 [] 打一个轻量的无注释的标签。 [/list]

    创建一个主题分支并开发

    $ git checkout -b alsa-audio <1> $ edit/compile/test $ git checkout -- curses/ux_audio_oss.c <2> $ git add curses/ux_audio_alsa.c <3> $ edit/compile/test $ git diff HEAD <4> $ git commit -a -s <5> $ edit/compile/test $ git reset --soft HEAD^ <6> $ edit/compile/test $ git diff ORIG_HEAD <7> $ git commit -a -c ORIG_HEAD <8> $ git checkout master <9> $ git merge alsa-audio <10> $ git log --since='3 days ago' <11> $ git log v2.43.. curses/ <12> [list=1] [] 创建一个主题分支。 [] 还原你在 curses/ux_audio_oss.c 文件里搞砸了的修改。 [] 如果你要添加一个新文件是,你需要告诉 git;之后,如果你使用 git commit -a, 删除和修改就会被捕获。 [] 查看你正在提交什么修改。 [] 提交你已签署了的所有已测试文件。 [] 退回到上一个提交,并保留工作树。 [] 查看自从上一个不成熟提交后的修改。 [] 使用原先写过的信息,重做在之前步骤中撤销了的提交。 [] 切换到主干分支。 [] 把主题分支合并到你的主分支。 [] 回顾提交记录;其他限制输出的形式也可以合并包含: –max-count=10(显示 10 个提交),–until=2005-12-10 等 [] 只查看影响到在 curses/目录里,从 v2.43 标签开始的修改。 [/list]

  • Hassle-Free Go in Production at 2013年11月20日

    Tips and Tricks Use Upstart for everything It’s built-in to ubuntu and can do everything supervisord can, except the pretty web interfaces, which don’t really scale to many machines anyway.

    Use IAM profiles to grant access to your debian packages IAM profiles configure your EC2 machines to automatically receive credentials when they boot up (the standard AWS libraries all know how to find them). This avoids shipping around your AWS keys which is generally a bad idea.

    Use Ubuntu cloud-init in userdata for server provisioning If you don’t know, cloud-init is a text file that you pass to Ubuntu during install that bootstraps the machine. EC2 allows you to pass this in whenever you boot a new machine through EC2 userdata.

    One of the best parts of using cloud-init is that you can setup an autoscaling configuration on EC2 that includes your cloud-init in the userdata and can then scale up and down with a single command and no other orchestration.

    Here’s what ours looks like (pardon the weird Medium formatting).

    It might make more sense to do this as a cronjob, but Upstart is eventually supposed to replace cron anyway (and I happened to be amidst writing a bunch of other Upstart jobs when I needed this).

    That’s all for now. Let me know if you have any issues or improvements to this setup — [email] ben@bubb.li[/email] or @newhouseb on Twitter.

  • Hassle-Free Go in Production at 2013年11月20日

    The Less Good Must deploy from a binary compatible machine This is a minor inconvenience if you develop on a Mac, but probably better in the long run because deploying from one OS to another is just asking for trouble. I know that there are some tools in the golang community for cross compiling, but I haven’t tried them out yet.

    Configuration isn’t super flexible At the moment, we ship a bunch of configuration in our Upstart scripts which are packaged into our .deb packages. This feels really dirty, but given how easy it is to deploy, it isn’t a huge issue. I know Instagram just refreshes configuration info from a redis server every 30 seconds, but something like serf (http://www.serfdom.io/) may work too.

    Deployment doesn’t self-repair in the case of failure When we deploy, I watch our aggregated logs (on http://papertrailapp.com) to make sure that the machines restart themselves properly and act accordingly if I see a flood of errors.