海量数据的解决方案
1. 缓存和页面静态化
数据 量大 这个 问题 最 直接 的 解决 方案 就是 使用 缓存, 缓存 就是 将从 数据库 中 获取 的 结果 暂时 保存 起来, 在下 次 使用 的 时候 无需 重新 到 数据库 中 获取, 这样 可以 大大 降低 数据库 的 压力。
缓存的使用方式可以分为通过程序直接保存到内存中和使用缓存框架两种方式。 程序 直接 操作 主要 是 使用 Map, 尤其是 ConcurrentHashMap, 而 常用 的 缓存 框架 有 Ehcache、 Memcache 和 Redis 等。 缓存 使用 过程中 最重要 问题是 什么时候 创建 缓存 和 缓存 的 失效 机制。 缓存 可以 在 第一次 获取 的 时候 创建 也可以在 程序 启动 和缓 存 失效 之后 立即 创建, 缓存 的 失效 可以 定期 失效, 也可 以在 数据 发生 变化 的 时候 失效, 如果 按 数据 发生 变化 让 缓存 失效, 还可以 分 粗 粒度 失效 和 细粒 度 失效。
不过缓存也不是什么情况都适用, 它主要用于数据变化不是很频繁的情况。 而且如果是定期失效( 数据 修改 时不 失效) 的 失效 机制, 实时性要求也不能太高, 因为这样缓存中的数据和真实数据可能会不一致。 如果 是 文章 的 评论 则 关系 不是 很大, 但如 果是 企业 业务 系统 中 要 生成 报表 的 数据 则 问题 就 大 了。
跟缓存相似的另外一种技术叫页面静态化, 它在原理上跟缓存非常相似, 缓存是将从数据库中获取到的数据( 当然 也可以 是 别的 任何 可以 序列 化 的 东西) 保存起来, 而页面静态化是将程序最后生成的页面保存起来, 使用页面静态化后就不需要每次调用 都 重新生成页面了, 这样不但不需要查询数据库, 而且连应用程序处理都省了, 所以页面静态化同时对数据量大和并发量高两大问题都有好处。
页面静态化可以在程序中使用模板技术生成, 如常用的Freemarker 和 Velocity 都可以 根据 模板 生成 静态 页面, 另外 也可以 使用 缓存 服务器 在 应用 服务器 的 上一 层 缓存 生成 的 页面, 如 可以 使用 Squid, 另外 Nginx 也 提供 了 相应 的 功能。
2 .数据库优化
要解决数据量大的问题, 是避不开数据库 优化 的。 数据库 优化 可 以在 不 增加 硬件 的 情况下 提高 处理 效率, 这是 一种 用 技术 换 金钱 的 方式。 数据库 优化 的 方法 非常 多, 常 用的 有 表 结构 优化、 SQL 语句 优化、 分区 和 分表、 索引 优化、 使用 存储 过程 代替 直接 操作 等, 另外 有时候 合理 使用 冗余 也能 获得 非常 好的 效果。
表结构优化
表结构优化是数据库 中最 基础 也是 最重要的, 如果 表 结构 优化 得不 合理, 就可能 导致 严重 的 性能 问题, 具体 怎么 设计 更 合理 也没 有 固定 不变 的 准则, 需要 根据 实际情况 具体 处理。
SQL语句优化
SQL语句优化 也是 非常 重要的, 基础 的 SQL 优化 是 语法 层面 的 优化, 不过更重 要的 是 处理 逻辑 的 优化, 这也 需要 根据 实际情况 具体 处理, 而且 要和 索引 缓存 等 配合 使用。 不过 SQL 优化 有一个 通用 的 做法 就是, 首先 要将 涉及 大 数据 的 业务 的 SQL 语句 执行 时间 详细 记录 下来, 其次 通过 仔细 分析 日志( 同一 条 语句 对不 同 条件 的 执行 时间 也可能 不同, 这点 也需 要 仔细 分析) 找出 需要 优化 的 语句 和 其中 的 问题, 然后 再 有的放矢 地 优化, 而 不是 不分 重点 对 每条 语句 都 花 同样 的 时间 和 精力 优化。
分区
当数据量变多的时候, 如果 可以 分区 或者 分表, 那将 起到 非常 好的 效果。 当 一张 表中 的 数据 量变 多的 时候 操作 速度 就 慢了, 所以 很容易 想到 的 就是 将 数据 分到 多个 表中 保存, 但是 这么 做 之后 操作 起来 比较 麻烦, 想 操作( 增 删改 查) 一个 数据 还 需要 先 找到 对应 的 表, 如果 涉及 多个 表 还得 跨 表 操作。 其实 在 常用 的 数据库 中 可以 不分 表 而 达到 跟 分表 类似 的 效果, 那就 是 分区。 分区 就是 将 一张 表中 的 数据 按照 一定 的 规则 分到 不同 的 区 来 保存, 这样 在 查询 数据 时 如果 数据 的 范围 在 同一个 区内 那么 可以 只对 一个 区 的 数据 进行 操作,这样 操作 的 数据 量 更少, 速度 更快, 而且 这种 方法 对 程序 是 透明 的, 程序 不需 要做 任何 改动。
分表
如果一张表中的 数据 可以 分为 几种 固定 不变 的 类型, 而且 如果 同时 对 多种 类型 共同 操作 的 情况 不多, 那么 都可以 通过 分表 来 处理, 这也 需要 具体 情况 具体 对待。 笔者 之前 对 一个 业务 系统 进行 重 构 开发 时 就 将其 中 保存 工人 工作 卡片 的 数据表 分成 了 三个 表, 并且 对 每个 表 进行 分区, 在 同时 使用 缓存( 主要 用于 在 保存 和 修改 时 对 其他 表 的 数据 获取 中, 如 根据 工人 Id 获取 工人 姓名、 工人 类别、 所在单位、 所在 工段 及 班组 等 信息)、 索引、 SQL 优化 等 的 情况下 操作 速度 比 原来 提高 了 100 倍 以上。 那时 的 分表 是按 照 工作 卡片 的 类型 来 划分 的, 因为 当时的 要求 是要 保留 所有 的 记录。 比如, 修改 了 卡片 的 信息, 则需 要 保存 是 谁在 什么时候 对 卡片 进行 修改, 修改 前 的 数据 是什么, 添加 删除 也 一样, 这种 需求 一般 的 做法 就是 用 一个 字段 来做 卡片 状态 的 标志 位, 将 卡片 分成 不同 的 类型。 不过 这里 由于 数据 量 非常 大 所以 就 将 卡片 分别 保存 到了到了 三个 表中, 第一个 表 保存 正常 卡片, 第二个 表 保存 删除 后的 卡片, 第 三个 表 保存 修改 之前 的 卡片, 并且 对 每个 表 都 进行 了 分区。 由于 报表 一般 是按 月份、 季度、 半年 和 年 来做 的, 所以 分区 是按 月份 来 分 的, 每个 月 一个 分区, 这样 问题 就 解决 了。 当然 随着 时间 的 推移, 如果 总 数据 量 达到 一定程度, 还需 要 进一步 处理。
另外 一种 分表 的 方法 是将 一个 表中 不同 类型 的 字段 分到 不同 的 表中 保存, 这么 做 最 直接 的 好处 就是 增删 改 数据 的 时候 锁定 的 范围 减小 了, 没被 锁定 的 表中 的 数据 不受影响。 如果 一个 表 的 操作 频率 很高, 在 增删 改 其中 一部分 字段 数据 的 同时 另一 部分 字段 也可 能被 操作, 而且( 主要 指 查询) 用 不到 被 增 删改 的 字段, 那么 就可以 把 不同 类型 的 字段 分别 保存 到 不同 的 表中, 这样 可以 减少 操作 时 锁定 数据 的 范围。 不过 这样 分表 之后, 如果 需要 查询 完整 的 数据 就得 使用 多 表 操作 了。
索引优化
索引的大致原理 是在 数据 发生 变化( 增删 改) 的 时候 就 预先 按指 定 字段的 顺序 排列 后 保存 到 一个 类似 表 的 结构 中, 这样 在 查找 索引 字段 为 条件 的 记录 时 就可以 很快 地 从 索引 中 找到 对应 记录 的 指针 并从 表中 获取 到 记录, 这样 速度 就 快 多了。 不过 索引 也是 一把 双刃 剑, 它在 提高 查询 速度 的 同时 也 降低 了 增 删改 的 速度, 因为 每次 数据 的 变化 都 需要 更新 相应 的 索引。 不过 合理 使用 索引 对 提升 查询 速度 的 效果 非常 明显, 所以 对 哪些 字段 使用 索引、 使用 什么 类型 的 索引 都 需要 仔细 琢磨, 并且 最好 再做 一些 测试。
使用存储过程代替直接操作
在操作过程复杂而且调用频率高的业务中, 可以 通过 使用 存储 过程 代替 直接 操作 来 提高效率, 因为 存储 过程 只需 要 编译 一次, 而且 可以 在 一个 存储 过程 里面 做 一些 复杂 的 操作。
上面 这些 就是 经常 用到 的 数据库 优化 的 方法, 实际 环境 中 怎么 优化 还得 具体 情况 具体 分析。 除了 这些 优化 方法, 更重 要的 是 业务 逻辑 的 优化。
3.分离活跃数据
虽然有些数据总数据量非常大, 但是活跃数据并不多, 这种情况就可以将活跃数据单独保存起来从而提高处理效率。 比如, 对 网 站 来说, 用户 很多 时候 就是 这种 数据, 注册 用户 很多, 但是 活跃 用户 却不 多, 而 不 活跃 的 用户 中有 的 偶尔 也会 登录 网 站, 因此 还不 能 删除。 这时 就可以 通过 一个 定期 处理 的 任务 将不 活跃 的 用户 转移 到 别的 数据 表中, 在 主要 操作 的 数据表 中 只 保存 活跃 用户, 查询 时 先 从 默认 表中 查找, 如果 找 不到 再 从不 活跃 用户 表中 查找, 这样 就可以 提高 查询 的 效率。 判断 活跃 用户 可以 通过 最近 登录 时间, 也可以 通过 指定 时间 段 内 登录 次数。 除了 用户 外 还有 很多 这种 类型 的 数据, 如 一个 网 站上 的 文章( 特别是 新闻 类 的)、 企业 业务 系统 中 按时 间 记录 的 数据 等。
4.批量读取和延迟修改
批量读取和延迟修改的原理是通过减少操作的次数来提高效率, 如果 使用 得 恰当, 效率 将会 呈 数量级 提升。 批量 读取 是将 多次 查询 合并 到 一次 中 进行, 比如, 在 一个 业务 系统 中 需要 批量 导入 工人 信息, 在 导入 前 需要 检查 工人 的 编码 是否 已经 在 数据库 中、 工人 对应 的 部门 信息 是否 正确( 在 部门 表中 是否 存在)、 工人 的 工种 信息 在 工种 表中 是否 存在 等, 如果 每 保存 一条 记录 都 查询 一次 数据库, 那么 对 每个 需要 检查 的 字段, 都 需要 查询 与 要 保存 的 记录 条数 相同 次数 的 数据库, 这时 可以 先 将 所有 要 保存 的 数据 的 相应 字段 读取 到 一个 变量 中, 然后 使用 in 语句 统一 查询 一次 数据库, 这样 就可以 将 n( 要 保存 记录 的 条数) 次 查询 变为 一次 查询 了。 除了 这种 对 同一个 请求 中的 数据 批量 读取, 在 高 并发 的 情况下 还可以 将 多个 请求 的 查询 合并 到 一次 进行, 如 将 3 秒 或 5 秒 内 的 所有 请求 合并 到一起 统一 查询 一次 数据库, 这样 就可以 有效 减少 查询 数据库 的 次数, 这种 类型 可以 用 异步 请求 来 处理。
延迟修改主要针对高并发而且频繁修改(包括新增)的数据, 如一些统计数据。 这种情况可以先将需要修改的数据暂时保存到缓存中, 然后定时将缓存中的数据保存到数据库中, 程序在读取数据时可以同时读取数据库中和缓存中的数据。 这里的缓存和前面 介绍 的缓存有本质的区别, 前面的缓存在使用过程中, 数据库中的数据一直是最完整 的, 但这里数据库中的数据会有一段时间不完整。 这种方式下如果保存缓存的机器出现了问题将可能会丢失数据, 所以如果是重要的数据就需要做一些特殊处理。 笔者 之前 所在 的 单位 有一个 系统 需要 每月 月末 各厂 分别 导入 自己 厂 当月 的 相应 数据, 每到 月末 那个 系统 就 处于 基本 瘫痪 的 状态 了, 而且 各厂 从 整理 出 数据 到 导入 系统 只有 几天 的 时间, 所以 有的 厂 就 专门 等 晚上 人少 的 时候 才 进行 操作, 对于 这种 情况 就可 采用 延迟 修改 的 策略 来 解决。
5.读写分离
读写分离的本质是对数据库进行集群, 这样就可以在高并发的情况下将数据库的操作分配到多个数据库 服务器去处理从而降低单台服务器的压力, 不过由于数据库的特殊性—— 每 台 服务器 所 保存 的 数据 都 需要 一致, 所以 数据 同步 就成 了 数据库 集群 中最 核心 的 问题。 如果 多台 服务器 都可以 写 数据 那么 数据 同步 将 变得 非常 复杂, 所以 一般 情况下 是将 写 操作 交给 专门 的 一台 服务器 处理, 这 台 专门 负责 写的 服务器 叫做 主 服务器。 当 主 服务器 写入( 增删 改) 数据 后 从 底层 同步 到 别的 服务器( 从 服务器), 读 数据 的 时候 到 从 服务器服务器 读取, 从 服务器 可以 有 多台, 这样 就可以 实现 读写 分离, 并且 将 读 请求 分配 到 多个 服务器 处理。 主 服务器 向 从 服务器 同步 数据 时, 如果 从 服务器 数量 多, 那么 可以 让 主 服务器 先向 其中 一部分 从 服务器 同步 数据, 第一 部分 从 服务器 接收 到数 据 后再 向 另外 一部分 同步, 这时 的 结构 如图 1- 5 所示。
6.分布式数据库
分布式数据库是将不同的表存放到不同的数据库中然后再放到不同的服务器。 这样在处理请求时, 如果需要调用多个表, 则可以让多台服务器同时处理, 从而提高处理速度。
数据库集群(读写分离)的作用是将 多个 请求 分配 到 不同 的 服务器 处理, 从而 减轻 单 台 服务器 的 压力, 而 分布式 数据库 是 解决 单个 请求 本身 就 非常 复杂 的 问题, 它 可以 将 单个 请求 分配 到 多个 服务器 处理, 使用 分布式 后的 每个 节点 还可以 同时 使用 读写 分离, 从而 组成 多个 节点 群, 结构图 如图 1- 6 所示。
实际 使用 中 分布式 数据库 有很 多 复杂 的 问题 需要 解决, 如 事务处理、 多 表 查询 等。 分布式 的 另外 一种 使用 的 思路 是将 不同 业务 的 数据表 保存 到 不同 的 节点, 让 不同 的 业务 调用 不同 的 数据库, 这种 用法 其 实是 和 集群 一样 起 分流 的 作用, 不过 这种 情况 就不 需要 同步 数据 了。 使用 后面 这种 思路 时 架构 还是和 上面 图中 的 一样, 所以 技术 和 架构 只是 一个 工具, 真正 重要的 是 思路, 也就是 工具 的 使用方法。
7.NoSQL和Hadoop
NoSQL 是 近年来 发展 非常 迅速 的 一项 技术, 它的 核心 就是 非 结 构化。 我们 一般 使用 的 数据库( SQL 数据库) 都是 需要 先 将 表 的 结构 定义 出来, 一个 表 有几个 字段, 每个 字段 各 是什么 类型, 然后 才能 往里 面 按照 相应 的 类型 保存 数据, 而且 按照 数据库 范式 的 规定, 一个 字段 只能 保存 单一 的 信息, 不可以 包括 多层 内容, 这就 对使 用的 灵活性 带来 了 很大 的 制约, NoSQL 就是 突破 了 这些 条条框框, 可以 非常 灵活 地 进行 操作, 另外 因为 NoSQL 通过 多个 块 存储 数据 的 特点, 其 操作 大 数据 的 速度 也 非常 快, 这些 特性 正是 现 在的 互 联网 程序 最 需要 的, 所以 NoSQL 发展 得 非常 快。 现在 NoSQL 主要 使 用在 互 联网 的 程序 中, 在 企业 业务 系统 中 使用 的 还不 多, 而且 现在 NoSQL 还 不是 很 成熟, 但 由于 灵活 和 高效 的 特性, NoSQL 发展 的 前景 是非 常 好的。
Hadoop 是 专门 针对 大 数据处理 的 一套 框架, 随着 近年来 大 数据 的 流行 Hadoop 也 水涨船高, 出世 不久 就 红得发紫。 Hadoop 对 数据 的 存储 和 处理 都 提供 了 相应 的 解决 方案, 底层 数据 的 存储 思路 类似于 1. 4. 6 节 介绍 的 分布式 加 集群 的 方案, 不过 Hadoop 是将 同一个 表中 的 数据 分成 多块 保存 到 多个 节点( 分布式), 而且 每一 块 数据 都有 多个 节点 保存( 集群), 这里 集群 除了 可以 并行 处理 相同 的 数据, 还可以 保证 数据 的 稳定性, 在其中 一个 节点 出现 问题 后 数据 不会 丢失。 这里 的 每个 节点 都不 包含 一个 完整 的 表 的 数据, 但是 一个 节点 可以 保存 多个 表 的 数据, 结构图 如图 1- 7 所示。
Hadoop 对 数据 的 处理 是 先 对 每 一块 的 数据 找到 相应 的 节点 并进 行 处理, 然 后再 对 每一个 处理 的 结果 进行 处理, 最后 生成 最终 的 结果。 比如, 要 查找 符合 条件 的 记录, Hadoop 的 处理 方式 是 先 找到 每 一块 中 符合 条件 的 记录, 然后 再将 所有 获取 到 的 结果 合并 到一起, 这样 就可以 将同 一个 查询 分到 多个 服务器 处理, 处理 的 速度 也就 快了, 这一点 传统 的 数据库 是 做不到 的。
来自:韩路彪 著. 看透Spring MVC:源代码分析与实践 (Web开发技术丛书)