反向代理(web 服务器)
微服务
微服务:
主从复制、主主复制、联合、分片、非规范化
关系型数据库扩展包括许多技术:主从复制、主主复制、联合、分片、非规范化和 SQL 调优。
不利之处:复制共性
- 如果主库在将新写入的数据复制到其他节点前挂掉,则有数据丢失的可能。
- 写入会被重放到负责读取操作的副本。副本可能因为过多写操作阻塞住,导致读取功能异常。
- 读取从库越多,需要复制的写入数据就越多,导致更严重的复制延迟。
- 在某些数据库系统中,写入主库的操作可以用多个线程并行写入,但读取副本只支持单线程顺序地写入。
- 复制意味着更多的硬件和额外的复杂度。
不利之处:主从复制
- 将从库提升为主库需要额外的逻辑。
不利之处: 主主复制
- 你需要添加负载均衡器或者在应用逻辑中做改动,来确定写入哪一个数据库。
- 多数主-主系统要么不能保证一致性(违反 ACID),要么因为同步产生了写入延迟。
- 随着更多写入节点的加入和延迟的提高,如何解决冲突显得越发重要。
- 参考不利之处:复制中,主从复制和主主复制共同的问题。
不利之处:分片
- 你需要修改应用程序的逻辑来实现分片,这会带来复杂的 SQL 查询。
- 分片不合理可能导致数据负载不均衡。例如,被频繁访问的用户数据会导致其所在分片的负载相对其他分片高。
- 再平衡会引入额外的复杂度。基于一致性哈希的分片算法可以减少这种情况。
- 联结多个分片的数据操作更复杂。
- 分片需要更多的硬件和额外的复杂度。
不利之处:非规范化
- 数据会冗余。
- 约束可以帮助冗余的信息副本保持同步,但这样会增加数据库设计的复杂度。
- 非规范化的数据库在高写入负载下性能可能比规范化的数据库差。
SQL 调优
SQL 调优是一个范围很广的话题,有很多相关的书可以作为参考。
利用基准测试和性能分析来模拟和发现系统瓶颈很重要。
基准测试和性能分析可能会指引你到以下优化方案。
改进模式
- 为了实现快速访问,MySQL 在磁盘上用连续的块存储数据。
- 使用
CHAR类型存储固定长度的字段,不要用VARCHAR。CHAR在快速、随机访问时效率很高。如果使用VARCHAR,如果你想读取下一个字符串,不得不先读取到当前字符串的末尾。
- 使用
TEXT类型存储大块的文本,例如博客正文。TEXT还允许布尔搜索。使用TEXT字段需要在磁盘上存储一个用于定位文本块的指针。 - 使用
INT类型存储高达 2^32 或 40 亿的较大数字。 - 使用
DECIMAL类型存储货币可以避免浮点数表示错误。 - 避免使用
BLOBS存储实际对象,而是用来存储存放对象的位置。 VARCHAR(255)是以 8 位数字存储的最大字符数,在某些关系型数据库中,最大限度地利用字节。- 在适用场景中设置
NOT NULL约束来提高搜索性能。
使用正确的索引
- 你正查询(
SELECT、GROUP BY、ORDER BY、JOIN)的列如果用了索引会更快。 - 索引通常表示为自平衡的 B 树,可以保持数据有序,并允许在对数时间内进行搜索,顺序访问,插入,删除操作。
- 设置索引,会将数据存在内存中,占用了更多内存空间。
- 写入操作会变慢,因为索引需要被更新。
- 加载大量数据时,禁用索引再加载数据,然后重建索引,这样也许会更快。
避免高成本的联结操作
- 有性能需要,可以进行非规范化。
分割数据表
- 将热点数据拆分到单独的数据表中,可以有助于缓存。
来源及延伸阅读
NoSQL
system-design-primer/README-zh-Hans.md at master · donnemartin/system-design-primer · GitHub
文档类型存储:将文档作为值的键-值存储
列型存储:嵌套的 ColumnFamily<RowKey, Columns<ColKey, Value, Timestamp>> 映射
图数据库:图
缓存
- 客户端缓存
- CDN 缓存:也算一种
- Web 服务器缓存:反向代理可以直接提供静态火动态内容
- 数据库缓存:bufferpool、changepool 的概念?
- 应用缓存:Redis 等
- 数据库查询级别的缓存:SQL 查询结果的缓存?
- 对象级别的缓存
更新缓存
- 旁路缓存:缓存无,直接查询 DB
- 缺点:访问两个服务;DB 更新后缓存数据过期
- 直接缓存:读写都访问缓存,缓存负责从数据库中读写数据。
- 缺点:缓存中可能很多数据不会使用,需要设置 TTL 机制。
- 回写缓存:读写都访问缓存,缓存异步写入数据库中。
- 缺点:缓存可能在其内容成功存储之前丢失数据,复杂度问题
- 刷新缓存:定时任务将需要的数据定时 or 提前刷入到缓存中
- 缺点:不能准确预测到未来需要用到的数据可能会导致性能不如不使用刷新。
异步
消息队列 Kafka/RabbitMQ、任务队列 Celery
背压:如果队列开始明显增长,那么队列大小可能会超过内存大小,导致高速缓存未命中,磁盘读取,甚至性能更慢。背压可以通过限制队列大小来帮助我们,从而为队列中的作业保持高吞吐率和良好的响应时间。一旦队列填满,客户端将得到服务器忙或者 HTTP 503 状态码,以便稍后重试。客户端可以在稍后时间重试该请求,也许是指数退避。
缺点:增加延迟和复杂性
通讯协议
HTTP、TCP、UDP、REST、RPC。
在 RPC 中,客户端会去调用另一个地址空间(通常是一个远程服务器)里的方法。调用代码看起来就像是调用的是一个本地方法,客户端和服务器交互的具体过程被抽象。远程调用相对于本地调用一般较慢而且可靠性更差,因此区分两者是有帮助的。热门的 RPC 框架包括 Protobuf、Thrift 和 Avro。
RPC 是一个“请求-响应”协议:
- 客户端程序 ── 调用客户端存根程序。就像调用本地方法一样,参数会被压入栈中。
- 客户端 stub 程序 ── 将请求过程的 id 和参数打包进请求信息中。
- 客户端通信模块 ── 将信息从客户端发送至服务端。
- 服务端通信模块 ── 将接受的包传给服务端存根程序。
- 服务端 stub 程序 ── 将结果解包,依据过程 id 调用服务端方法并将参数传递过去。
缺点:RPC
-
RPC 客户端与服务实现捆绑地很紧密。
-
一个新的 API 必须在每一个操作或者用例中定义。
-
RPC 很难调试。
-
你可能没办法很方便的去修改现有的技术。举个例子,如果你希望在 Squid 这样的缓存服务器上确保 RPC 被正确缓存的话可能需要一些额外的努力了。
安全
安全是一个宽泛的话题。
拓展链接:
附录 TODO
扩展阅读
Scalability, Availability & Stability Patterns | PPT
AWS re:Invent 2017: Scaling Up to Your First 10 Million Users (ARC201) - YouTube