看这本书时做的笔记. 总结一下:
1. 有众多可以参考的地方, 例如 Cron 的设计, 监控的改进, 新工具的推广方法
2. 对手头的系统和工具要非常了解, 这样就可以玩出很多招数
## 1. 介绍
* DevOps 在 Google 的实践
传统开发/运维分离的解决方案在规模扩大后沟通成本上升(“随时发布” vs. “不再改动”) -> 新型运维团队 SRE(50%-60%标准开发, 其他为85%-99%能力的开发, 为了开发系统代替手工操作) -> 最多 50% 时间用于运维工作, 余下开发系统来自动化
* SRE 方法论
* 运维工作最多占用 50% 时间
* 遇到故障事后写总结
* 因为信息系统的特点, 不是也不该追求 100% 可靠, 给出现实的可靠性. 在实现这个可靠性的前提下, SRE 可以做各种创新
* 监控, 通过预案/手册缩短平均恢复时间
* 70% 的事故源于部署变更 -> 渐进发布, 精确检测, 回滚机制
## 2. 生产环境
* 集群资源分配: Borg(分布式集群操作系统), 下一代 Kubernetes(2014)
* Large-scale cluster management at Google with Borg
* Borg, Omega, and Kubernetes
* 负责运行用户提交的任务. 每个任务由多个实例组成, Borg 会为每一个实例安排一台物理服务器, 执行具体的程序启动它
* 负责任务的监控, 如果异常, 终止并重启
* 命名: BNS: /bns/<cluster>/<user>/<task>/<instance>
* 任务需要在配置中声明其所需的具体资源(cpu/mem), 超过则立即 kill
* 存储
* 分布式存储, 小文件和大文件进不同的集群.
* 单个集群一年内会损失上千块硬盘, 数据中心有专门的团队来处理
* 网络
* 这些概念比较陌生, OpenFlow 的软件定义网络, 带宽控制器优化带宽.
* 从地理位置, 用户服务和远程调用三层进行负载均衡
* 监控报警
* 定时抓取指标, 超出触发报警
* **新旧版本的对比: 新版本是否让软件服务器更快了?**
* 检查资源用量随时间的变化, 制定资源计划.
* 服务
* 所有服务使用 RPC 通信, 开源实现为 gRPC
* 格式为 Protocol Buffer(与 Apache Thrift 相比) (大小比 xml 小 3-10 倍, 序列化/反序列化快 100 倍) (和 json 比?)
* 服务和存储根据流量分散到各大洲的机房
* 开发
* Code review
## 3. 拥抱风险
* 目标
* 没有 100% 可靠的服务, 达到一定程度的可靠性之后, 应把精力转向他处.
* **”当设立了一个可用性目标为99.99%时, 我们即使要超过这个目标, 也不会超过太多, 否则会浪费为系统增加新功能, 清理技术债务或者降低运营成本的机会.”**
* 可靠性目标成为错误预算: 提供明确和客观的指标决定服务在一个季度中接受多少不可靠性(用于 SRE 部门和产品部门的沟通). 只要错误预算耗尽, 新版本的发布就会暂停(?但是错误率由 SRE 部门提供, 而发布由产品决定?) -> 认为风险由产品开发决定, 一个变通是, 当错误预算即将用尽时, 降低发布的频率. 即使是网络中断或者数据中心故障影响了错误率, 发布频率也会降低, 因为”每个人”都有义务保障服务的正常运行.
* 可用性指标: 请求成功率. **用我们记录的请求成功率与用户期望的服务水平做对比.**
* 成本
* 可用性: 99.9% 到 99.99%; 收入: 1000000刀 -> 改进后的价值: 1000000 * 0.09% = 900 刀
* 需求
* 面向消费者需要低延迟(队列空为好), 离线计算需要吞吐量(队列满为好). 需要分别响应不同的需求. -> 两个集群, 低延迟/高吞吐量
## 4. 服务质量
* 质量度量
* 请求延迟 (Req time)
* 错误率 (Web errors)
* 吞吐量 (Web QPS)
* Google 云计算的可用性指标: 99.95% -> 60*24*365*0.0005 = 262.8 min/year -> 我们的可用性?
* 特色
* 4/5个指标, 多/少都不好.
* 监控, y 轴指数分布
* 数据收集每10秒一次, 每一分钟汇总一次. 目标像这样: **99% 的 get RPC 调用在 < 100ms 的时间内完成.**, 每天可以出一个这样的报表.
* 总结
* 指标越少越好, 少到不能更少
* 性能指标保持简单
* 从松散的目标开始, 逐渐收紧. 不要一开始就追求完美
* 对内指标要求可以比对外高一些, 留有余地
## 5. 琐事
* 与规模线性增长的手动事务
* 占用 Google SRE 大约 33% 的时间
## 6. 分布式系统的监控
* 方式
* 白盒: 系统内部数据
* 黑盒: 外部响应
* Dashboard: 可视化, 提供选择/过滤功能
* 警报
* 如何监控
* 对照组: 上周, 上一个版本
* 减少报警量(防止”狼来了”)
* 简单快速的逻辑. 不要自动学习阈值(这点和我想的不一样) **避免在监控系统中维护复杂的依赖关系**
* 方法论
* 故障, 警报, 定位和调试都必须保持简单!
* 4个关键指标: 延迟, 流量, 错误率, 饱和度(IO 带宽占用比, 磁盘占用比)
* 长尾
* 重要的是分布而不是平均 -> 直方图 Y 轴指数展示
* 复杂性管理
* 避免监控系统变得过于复杂
## 7. 自动化系统的演进
* shell 脚本 -> 改进后的 Python 单元测试框架(Prodtest, 改进的 Python 单元测试框架, 可用来对实际服务进行单元测试), 用于验证集群中的服务(比如 DNS 是否存在/成功)
* 在 Prodtest 出来之后, 又为每个 test 创建了对应的修复工具.
* 集群上线系统的测试-自动化修复套件的问题:
1. 需要维护!
2. 2. 分布式自动化依赖于 SSH, 需要 root 权限执行. => 需要将 SRE 完成任务所需权限降到最低. => 使用有 ACL 的本地 admin 进程取代 sshd, admin 记录 rpc 请求者, 参数和结果.
* Borg: 成功的核心是”把集群管理变成了一个可以发送 API 的中央协调主体”. 所以 shell 脚本 => Python 集群测试框架(自动化系统) => Borg(自治系统, 将集群管理抽象为单机环境) => 自治系统强调自我检查和自我修复.
* 自动化的问题: 自动化多了, 人就忘了手动该怎么做. 甚至系统将不再有手动操作的接口.
## 8. 发布
* 构建过程的封闭性, 不受构建机器上第三方类库和其他软件工具影响. 编译过程自包含, 不依赖编译环境之外的其他服务.
* 构建工具与被构建的项目放在同一个仓库. (而我们的做法是分离的. 应用开发者按照平台提供的规范走, 不管构建的事. 各有好处. 这样平台在构建过程上可发挥的空间更大)
* 单独的发布分支(避免引入之后主分支上的改动). 发布系统将创建新的发布分支, 编译, 跑单元测试. 每一步都有日志记录.
* 部署. “提供一系列可扩展的 Python 类, 支持任意部署流程”. 同时会对流程进行监控.
* 经验: 在规模不大时就考虑发布工程, 尽早采用最佳实践(尽早建立平台团队?)
## 9. 简单化
* 问题: 代码膨胀. 态度: “每一行新代码都是负担”, less is more.
* 方法: 最小 API(方法更少, 参数更少), 模块化(定义良好的边界)
## 10. 基于时序数据的报警
SRE 的职责层级关系(低 -> 高):
监控
应急处理
事后总结和问题根源分析
测试
容量规划
研发(50%+ 的精力)
监控首当其冲.
* 方法论: 单机问题报警没有意义(太频繁).
* 模型: 探针(脚本返回值 + 图形展示) 转到时序信息监控. 收集回来的数据同时进行展示和报警, 报警规则由数学表达式表示.
* 接口: /varz HTTP 列出所有的监控变量值, 由 Borgmon 定时抓取. (看来在应用所在每个机器上都有)
* 报警: 每条报警规则都有一个持续时间, 只有当警报持续时间超过一定范围之后才触发报警. (有借鉴意义, 尤其是对可自动恢复的问题); 同时多条连续的报警信息可以合并.
## 11. On-call
* 运维工作时间上限是50%工作时间, 其中不超过 25% 的 on-call.
* 分钟级的 ack. 比如 99.99% 可用的系统, 每个季度有 13 分钟的不可用时间, 那么 on-call 必须在 13 分钟之内解决问题(不过这里的”问题”看来是影响全局服务的大问题)
* “面临挑战时, 人有两种处理模式: 1. 依赖直觉, 快速, 自动化行动 2. 理性, 专注, 有意识认知活动. 为了确保 on-call 采用第二种方式, 必须减少其压力. 医学上讲, 压力状态下释放的荷尔蒙, 如 xx 和 yy, 可能造成恐惧, 进而影响正常认知..” 哈哈哈, 这个态度我喜欢, 必须把不理性的情况考虑到, 避免运维压力过大.
## 12. 故障排查
* 大型系统中, 遇到问题首要做的是尽可能恢复服务, 而不是查找问题根源.
* 将故障排查测试的项目明确写出来, 同时公布测试结果.
## 13. 紧急响应
* 演习: “SRE 故意破坏系统, 模拟事故, 然后针对失败模式进行预防以提高可靠性” => 鼓励主动测试
* Panic room: 专用的灾难安全屋, 有生产环境的专线连接.
## 14. 紧急事故管理
* 出了事故, on-call, 开发, 管理者, 这些关注到的人都在用自己的方法查找并尝试解决问题.
* 都在查找原因, 没有人有精力和时间思考如何通过其他手段缓解当前的问题.
* 没有时间清晰和有效地与其他人进行沟通, 没有人知道他们的同事在干什么.
* 解决: 职责分离. 有事故总控(需要明确声明现在开始全权负责, 任务分配), 事务处理团队, 发言人和规划负责人(提供支持).
## 15. 事后总结
* 总结: 1. 记录事故 2. 理清根源 3. 采取有效措施使得重现概率最低
* 对事不对人
* 总结报告需要评审
* 举办演习, 再现某篇事故总结事故, 一批工程师扮演文档中提到的角色
* 激励做正确事情的人(“良好的事后总结和事故处理可以赢得从 CEO 到工程师的一致好评”)
## 16. 跟踪故障
* 报警的聚合(一个问题引发了一连串报警)和加标签.
## 17. 测试可靠性
没看懂. 摘一下最后一句话: ”写出优质的测试需要付出的成本是很大的”. 现在写写单元测试没问题, 依赖众多的分布式软件的集成测试还不明白怎么弄.
## 18. SRE 部门中的软件工程实践
* 分析了一个案例, 对扩容需求做了一个应用, 包括需求的定义语法规则和对应的问题求解器. 一开始是简单的启发式逻辑, 后来使用线性规划使其更加聪明. 总结是体现了”发布与迭代”的思路, 不是一开始就期待完美的设计, 而是不断继续前进.
* 讨论了新工具的推广. 公告邮件和简单的演示是不够的, 需要持续和完整的推广方案, 用户的拥护和管理层的帮助. 设计的时候, 要时刻从用户角度提高可用性.
* 不要陷入对”完美的最终产物”的想象中. 一个”最小可行产品”是必要的, 在此基础上进行递进式的, 稳定的小型发布.
* 后期引入有统计学和数学优化背景的人进行优化.
## 19. 前端负载均衡
一致性哈希.
负载均衡器的包转发:
* NAT, 需要在内存中追踪每一个连接, 否定
* 修改数据链路层信息(MAC 地址), 需要后端服务器在一个局域网, 否定
* 包封装, 将请求使用路由封装协议封装到另一个 IP 包中, 使用后端服务器地址作为目标地址, ok.
## 20. 数据中心负载均衡
* 随机轮询:
1. 多个进程共享某个后端时, 其客户端请求速率可能是不同的. 如果一台后端上恰好跑的都是请求速率块的进程, 那么其负载就高.
2. 物理服务器不同
* 最闲轮询:
问题是, 一些任务在处理过程中是跑满了 cpu, 一些则是在阻塞. 但最闲轮询(基于任务数量)会认为负载是一样的.
* 加权轮询:
将最闲轮询的”任务数”替换为综合请求速率, cpu 占用率等计算出的值, 实践中效果更好.
## 21. 过载
## 22. 连锁故障
连锁故障由故障进入正反馈引发. 典型的例子是: 一个集群故障 => fallback 到另一个集群 => 另一个集群某服务受影响变慢 => RPC 超时, 大量重试 => 这个集群也不堪重负挂掉.
* 应对方法: 压力测试极限, 提供降级结果, 在可能导致问题时主动拒绝请求.
* 去除同层调用, 保持调用栈持久向下.
* 压力测试, 直到出现故障
## 23. 分布式共识
* 问题定义: 异步式分布式共识在消息传递可能无线延迟的环境下的实现
* 不能通过简单心跳实现
* 不稳定的条件下, 没有任何一种异步式分布式共识算法可以保证一定达成共识
* Paxos: 有严格顺序的提案被大多数接收者同意, 已被 zk, consul, etcd 等封装. 最出版本的 Paxos 有性能问题
* 复合式 Paxos: Paxos 两阶段: prepare/promise, 允许跳过第一阶段. 但是有锁住的危险. 更好的算法参考 Raft.
## 24. Cron
这里的 cron 是允许错过的, crond 不记录执行信息, 只会记录 schedule.
**定义问题**
cron 分为两类: 可重复的(垃圾回收)和不可重复的(邮件发送).
上面是跑多了的情况, 也有跑少了的情况: 有的 cron 允许错过一次(垃圾回收), 有的不允许(每月结算)
这两种情况导致 cron 的错误建模很复杂. **本文偏向于错过运行, 而不是运行两次**, 因为错过可以手动启动一次, 跑了两次是覆水难收的.
总结: 可以少跑, 不能重复跑
**大规模部署**
最小周期也是每分钟.
也是使用的容器, 主要考虑的功能是进程隔离: 一个进程不该影响到另外的进程.
容错. 调度器确保在数据中心的另一个地方有备份.
总结: 最小粒度每分钟, 调度器有多重实例, 通过 paxos 确保一致性
**Cron at Google**
对 cron 的状态, 有两种选择:
* 存到分布式存储(GFS)
* 存到 cron service
选择的第二种. (GFS 适合大文件存储)
使用 Paxos 算法确保 cron service 的一致性. paxos: 通过多重不可靠的副本达成可靠的一致性.
最重要的信息是**哪个 cron 已经跑过了.**
cron 调度器有多个备份待命, master 负责启动 cron job.
master 的工作流程:
sleep 直到时间到, 向 data center scheduler 发出启动指令,
同时发 paxos, => 其他 replica 知道任务已经启动
启动完毕后再发 paxos. => 其他 replica 知道任务启动完毕
=> cron job 跑完自己结束(解决了并行执行的问题), master 只管启动, 但是一定要成功启动.
确保只有一个调度器与 data center scheduler 交互, 一旦失去 master 地位, 就停止交互.
这里”发 paxos 告知开始启动”和”发 paxos 告知启动完成”中间有个时间段, 这个时间段就是启动时间. **这点很好, 现在 marathon 缺少这个, 你可以告诉他要做什么任务, 但他不会告诉你什么时候完成**
总结: cron 启动状态通过 paxos 与备份同步, 确保只有 master 能与 data center scheduler 交互, 只管启动不管结束
**启动失败**
再次重申, cron job 启动时候有两个同步点: 开始启动和启动完成. 如果 master 在这二者的中间失败, 我们就难以知道启动是否真正完成.
解决方法是使启动操作(master => 启动操作(data center scheduler 执行) => 完成)都是可重入的. 启动操作实际上是一系列的 rpc 操作(我想是类似 redarrow?)
不可避免的是失败. 如果在 rpc 发出 - 启动状态发送到 paxos 之间 master 挂掉, 那么新的 master 可能不知道这个 cron 已经启动, 还是可能导致重复启动. 这种小概率的事件就看你要不要继续付出代价来防了.
**存储状态**
paxos 基本是一个状态变化的连续日志. 这带来两个问题: 1. 日志分段 2. 日志存储
日志分段用打 snapshot 的方式解决: 我猜这里是把日志分成两部分: 之前的日志打成 snapshot, 之后的作为增量. snapshot 保险存放. 那么即使丢失, 丢掉的也是增量, 在一个可控的范围内.
对日志存储, 有两个选择: 分布式存储和系统本地卷. 选择是两种都做: 在所有 master 和备份的本地存(一共3个), 同时写到分布式存储.
**大规模带来的问题**
大规模: 为上千台节点的 data center 提供 cron 服务. 如果这些 cron 都集中到同一时间段就不好了. 举例: 当想要一个每天跑一次的 cron 时, 很多人都会写:
`0 0 * * *`, 拿这时候的 cron 密度就比较高.
提供了一种新格式, `? ? * * *` 表示由 cron master 选 cron 时间, cron master 利用这种写法将其均匀分布:
-
干货有, 可读性太差
2016-10-12
说可读性差, 一是语言不行(还老抖些自以为幽默的抱负), 二是事无巨细, 没突出重点.
比如说讲闭包. 闭包本质是允许嵌套的变量作用域. 有就能做闭包, 没有(比如 C, 变量只有本地和全局作用域)就没法做. 之后就是嵌套作用域的两种实现, 一个是(比如 Scheme)引用, 这时内部可以修改外面的变量; 一个是复制, 那就不能修改. Python 用的后者, 这时候落实到关键数据结构就行了, 事无巨细抓不住重点.
-
蛮欣赏这种风格的
2016-04-19
一本书讲清楚了 FP 的关键, 清晰简明又唠唠叨叨(不矛盾!). 前面事无巨细, 连代码尾巴上一堆反括号都要解释: "These are enclosing parentheses of ...", 反复提及第一页上的 The Ten Commandments, 一问一答的唠叨中把 Scheme basics 讲明白(同时还要提醒你 Do not rush through this book. Read carefully).
前6章的铺垫结束后, 8, 9, 10 章分别进入 CPS(听过大名, 看了 g9 的评论才知道, 这就是 CPS 啊.. 在 SICP 最后两章 assembler 里也出现了一段这样的程序, 当时看到还琢磨了一会), Y combinator(这个比较绕. 这里是 applicative-order 的, 和几个老大博客上的写法有点区别), interpreter. 接触不多的话这里会看得慢一点, 我看后3章的时间比前7章还长. 最后有列了一些参考, 作者推荐看一下再继续 Seasoned 和 Reasoned. 有时间把后两本也找来看啦.
-
Notes: Programming Google App Engine
2016-12-18
## Introduction
* 3 parts: application instances, data storage, scalable services.
* Application’s perspective: the app engine provides:1) storage between requests 2) able to distribute traffic among different servers 3) resource(cpu/memory/server) scalability.
* App engine’s perspective: 1) create/destroy application instances as needed 2) sandbox
* Supports Go/Java/Python
* db: use transaction
* Memcache/storage system
* Send/receive messages (mail/XMPP)
* Search
## Configuring an Application
* Py27: declare your code is thread safe by `threadsafe: true`. This is the recommended way (*compare*: conroutine, not thread-safe)
* Authorization:
```
handlers:
- url: /account/.*
script: account.py
login: required
```
App Engine configuration-based authorization (*compare*: App Engine api-based authorization (more fine-grained?))
* Service: under `/_ah/`
* Admin console (*compare*: application shell in bridge)
* Environment: Python, virtual env. Includes:
1) Standard library
2) Libraries/tools in App Engine SDK (like API for accessing services).
3) Other 3rd-party library the application use
## Request Handlers and Instances
* App Engine does load balancing / instance scaling for application, you can ignore instances and focus on request handlers
* Runtime environment: sandbox:
1) App cannot spawn additional process
2) App cannot make arbitrary network connections
3) App can only read its own part of the filesystem
4) App cannot see other applications/processes running on the server
* GAE Sandbox implementation: replacing standard library calls (and other methods?)
* Limitations: requests(response time/size of request, etc)/service(datastore/memcache)/deployment(resource files)
* Request handler has a pool of instances, App Engine starts/shuts down instances by need, requests are routed to instances on availability (GAE instance scale(*how? :”If all instances are busy, App Engine starts a new instance*) vs. DAE workers scale)
* Support multithreading in Instance
* Instance scale: starts new instance when all instances are busy.
* Instance busy(multithreading disabled): instance presently busy handling a request.
* Instance busy(multithreading enabled):
1) Current load (cpu/memory) from active request handlers
2) Historical load by previous requests
## Datastore Entities
* 2011-12: master/slave datastore -> high replication datastore (no scheduled maintenance)
```
class Book(db.Model):
title = db.StringProperty()
author = db.StringProperty()
copyright_year = db.IntegerProperty()
obj = Book(tital="", ..)
obj.put()
# Query
q = db.Query(Book)
q.filter('copyright year >', 2015).order('-title')
```
## Large Data and the Blobstore
* Unlimited file size, but API to Blobstore are limited to 32 megabytes.
## Fetching URLs and Web Resources
* URL fetch service by GAE, based on Google infrastructure.
* Overrides urllib/urllib2/httplib
## Task Queues and Scheduled Tasks
**Reason**
* [DAEMON] Updating an element of data may require several related but time-consuming updates,.
* [MQ] It’s often acceptable to record what work needs to get done, respond to user right away, then to the work later.
* [CRON] Scheduled updating/analysis
**MQ**
* Producer: enqueues task; Consumer: a process, separate from the producer, leases tasks on the queue.
* Operation: push/pull queues
* Enabled when you deploy your application
* Configuration file:
```
queue:
- name: default
rate: 10/s
mode: pull # pull/push
total_storage_limit: 200MB # data in queue
```
*Enqueue*
```
from google.appengine.api import taskqueue
taskqueue.add(queue_name='name')
```
* Feature: countdown: wait seconds before start
* Feature: ETA: expected start time, but tasks may be delayed.
* Feature: delete/retry/purge/find-.
* Feature: task chain: produce another task during task execution
* Task handlers are mapped to URLs like `/_ah/queue/name` (user requests are blocked for these paths), and report return status by sending http status.
* Handlers run in separate threads, and use the same scaling mechanism as user requests.
* No separate MQ logs, merged in application requests logs.
* Tasks queues may have different version and corresponding handlers.
**Scheduled Tasks (aka cron jobs)**
* App engine calls the URL by an empty Get request regularlly.
* Has deadline, no retry.
## Optimizing Service Calls
* Async calls for datastore/mc/URL fetch
```
rpc1 = urlfetch.make_fetch_call(urlfetch.create_rpc(), url1)
rpc2 = urlfetch.make_fetch_call(urlfetch.create_rpc(), url2)
# sync point, wait for the longest rpc call
combine(rpc1.get_result(), rpc2.get_result())
# other sync points
# in progress -> ready
rpc.wait()
# ready -> checked
rpc.check_result()
```
* Async db/mc
```
rpc1 = db.get_async(k)
rpc2 = mc_client.get_multi_async(k)
```
* Callbacks
```
self.rpc = urlfetch.make_fetch_call(urlfetch.create_rpc(), url)
self.rpc.callback = self.process_results
def process_results(self):
results = rpc.get_result()
```
## Deploying and Managing Applications
* Deploying / reverting / inspecting performance / analytic graphs of traffic and resource usage (like DAE bridge)
-
Tips
2013-01-08
前几周看了 C 的 HOPL, 有些东西挺有意思.
1. 这本书 Kernighan 和 DMR 分工明确, DMR 写的后面和 OS 相关的那章, 还有附录. 其他是 Kernighan 写的.
2. 语言的顺序是 BCPL->B->C. BCPL 不是 Ken 和 DMR 发明的, B 是Ken 做的, 一开始是对 BCPL 的模拟, C 是 DMR 做的, 一开始是对 B 的一个增强(当时 DMR 叫它 "new B" :> ), 后来 DMR 觉得区分够大可以算一门新语言了, 就顺着 B 取了 "C" 这个名字.
3. 先在 PDP-7 上有了 UNIX 的雏形后有 C. 不过那时候也不叫 UNIX, 是 Ken 在 PDP-7 上用汇编写的一个 OS. 后来 DMR 用 C 重写了. DMR 写了第一个 C 编译器(显然..).
4. DMR 大学读的物理, 但自觉能力不足以搞物理. 鄙人大学数学, 自觉能力不足以搞数学.. 看到这里我还是挺高兴的 :>
最早的那些牛们常常写过 OS 和编译器. 啧啧.