1. 缺乏文档
这应该是大小公司都存在的问题。文档会极大降低开发效率,并且互联网项目的特点是易变和追求速度,详细文档不是很好的方案。这就要求方案和细节设计上的合理性和不要做 “精巧”方案。结构化设计,不要零散的组成,这样其他人即使没有文档也可以理解。
2. 项目中临时方案太多
导致后来看起来很别扭而且不容易理解;半截工程。系统中存在大量“精巧”的设计,导致后来者难以理解。这也告诉我们做设计的时候尽量简单通俗易懂,项目设计的可沟通性也是很重要的一方面。某位工程师说自己花了 1 周的时间才搞明白 Postfix 的收邮件并自动解析的过程是怎么运行的。
3. 代码质量参差不齐
代码质量问题每个大点的团队都没法保证,保持代码库的干净很重要。
4. 繁杂的业务
5. 代码的 BUG 和代码对环境的兼容性
之前的系统使用配置文件做主从读写分离,配置文件由其他系统控制。但是配置文件确保留在代码库中,这意味着假如代码回滚或者 check 分支出错,配置文件会发生改变。不该发生的全会发生,这样的事情确实发生了。导致部分操作写入从库,从库与主库同步失败,典型的脑裂问题。最后只好花了很长时间重做从库的同步。这样的问题处理并不复杂,复杂的在于如何发现这个问题的原因。业务系统各种奇怪的表现,有时候很难想到问题的根源。
迁移过程需要考虑的问题:1. 完善测试
性能测试可以采取流量镜像复制,读操作有很多简单可靠的流量复制工具,有时候根本不需要一个高大上的流量复制系统。并且大部分系统都是读多写少,测试不是什么难题。
功能性测试只能尽量做足,让熟悉系统的用户进行。
2. 无缝迁移
整个过程基本实现了平滑无缝迁移,系统的没有停止 1 分钟运行。由于项目的特点,比较少写操作,重点是读,暂停写操作后作将 HaProxy 后端逐步指向新集群,等全部流量导入新集群后修改 DNS 指向新集群。这里还涉及到 DNS TTL 从长变短再变长的修改过程。
缓存预热很重要,尤其是数据库的预热,这就要求新集群流量导入逐步进行,防止对整站延迟的影响。
3. 回退方案
由于暂时停止写操作,即使流量导入到新集群后测试发现问题仍然可以指回旧集群。
4. 改进还是保持原状
由于架构组件的选择余地很大,之前的各个组件的配置是否合理需要很长时间 Review。这里就要权衡保持原状还是一次性做好优化。比较好的方案是如果不是 BUG 则保持原状,等系统完成迁移再进行改进。
5. 性能的持续监控和对比测试
性能监控工具已经非常成熟了,比如 AppNeta 和 New Relic , 基本可以把控各个组件的性能。在迁移之前也可以进行镜像流量复制对比测试新旧集群的性能。
迁移带来的收益1. 重新设计的发布自动化
业务代码、系统配置、云架构配置的分离,任何操作的版本化,可回退。
2. 弹性扩展,总体成本的降低
迁移到亚马逊的主要原因就是高低峰流量差异很大。迁移后低峰期可以节约 1 半的机器成本。
3. 跨区域容灾,无单点故障
实现了 Multi-AZ,任意单点故障不影响业务运行。Web 前端服务器可以随手关掉,数据库的升级,配置改动也无任何影响,当然这归功于 RDS Multi-AZ 功能。
4. 运维难度的降低,无需运维
系统会自动根据负载进行增减机器,所以无需担心压力大把机器打垮,单机器的各种故障也无需人工处理。
关于亚马逊云平台的特点和各个组件以后还会有单独的文章详细解释。
Tags: AWS, Cloud, 亚马逊
Kafka 内部实现:
Kafka 官方说明了 Kafka 的各种优点,包含:
FileTransfer 的 zero copy 的 IO 实现;
直接写磁盘, 而不做复杂的内存和文件系统之间的转换;
磁盘顺序读写带来的高吞吐;
直接用系统的 page cache 做文件缓存管理, 程序本身不进行缓存管理;
实现了复制高可用;
依赖 Zookeeper 实现客户端的协调和状态存储;
但是这些简化设计是否是“优点”,是仁者见仁的。Broker 本身简单的设计带来的是客户端的复杂性。依赖 Zookeeper 也需要额外维护 Zookeeper 。
存储格式:
每个队列存储在一组序号自增、大小有上限的文件中。
数据持久化:
依赖操作系统的 pdflush 来做 pagecache 中的数据落地无法保证数据的可靠存储,所以 Kafka 提供了一个配置参数,可有由 Kafka 本身调用 fsync 强制落地。其实写文件不强制 fsync 系统也会用 pagecache 缓存这部分数据。
关于 pdflush 的原理可以看 ~gsmith/content/linux-pdflush.htm
可配置参数:
/proc/sys/vm/nr_pdflush_threads
/proc/sys/vm/dirty_writeback_centisecs
/proc/sys/vm/dirty_expire_centiseconds
/proc/sys/vm/dirty_background_ratio
更多关于 Linux IO 调度的内容:
顺序写盘:
——————-
Linux 文件系统中当打开文件的时候是顺序写盘模式;当都是顺序写入的时候也是顺序写盘。
MMAP 会由系统优化尽量顺序读写盘。
Kafka 相关代码:
new RandomAccessFile(file, “rw”).getChannel()
Zero-copy IO 优化:
——————-
Linux 系统底层调用为 sendfile
Java 提供 API:
public void transferTo(long position, long count, WritableByteChannel target);
channel.force(true); = fsync
channel.force(false); = fdatasync
更多参考和 Kafka 中的实现:
Zero copy 避免了应用级别的复制,减少几次调用,提高了效率。
https://www.ibm.com/developerworks/library/j-zerocopy/
灾难恢复:
———–
由于每个 message 都存储了对应的 CRC ,恢复的时候遍历文件中的数据并且校验数据就可以得到可恢复的位置。Kafka 中的实现为:
客户端负载均衡:
—————
所有 broker 节点和生产者、消费者注册到 Zookeeper 。节点变化触发相关客户端的平衡操作。节点都尝试自平衡直到达成一致。
原理和 Kafka 非常类似,但是接口和数据存储方式有些差异。
MMAP 文件的实现:
我在这里实现了 MetaQ 的 PHP 客户端,需要的可以使用:
https://github.com/doubaokun/metaq-php
Beanstalkd:简单的单线程应用,读写都使用内存,为了可以停机和恢复数据,写数据的时候会同时写 binlog 文件,binlog 文件的刷盘时间间隔可配置。
最新版本的实现已经将 fsync(2) 更换为 fdatasync 避免 2 次写盘 (即更新文件数据,又更新文件 meta 信息),新建文件为固定大小的文件。