架构
软件架构
架构设计的主要目的是为了解决软件系统复杂度带来的问题
“这么多需求,从哪里开始下手进行架构设计呢?”
——通过熟悉和理解需求,识别系统复杂性所在的地方,然后针对这些复杂点进行架构设计。
“架构设计要考虑高性能、高可用、高扩展……这么多高 XX,全部设计完成估计要 1 个月,但老大只给了 1 周时间”
——架构设计并不是要面面俱到,不需要每个架构都具备高性能、高可用、高扩展等特点,而是要识别出复杂点然后有针对性地解决问题。
“业界 A 公司的架构是 X,B 公司的方案是 Y,两个差别比较大,该参考哪一个呢?”
——理解每个架构方案背后所需要解决的复杂点,然后才能对比自己的业务复杂点,参考复杂点相似的方案。
高性能
- 负载均衡(横向, LB本身引入潜在问题), 微服务(纵向, 访问链路变复杂)
读写分离
读写分离仅仅是将访问流量进行了分离, 并没有对底层存储分离
- 主从复制延迟
- 同步期间, 读请求约束到主机
- 二次读: 从机读取失败后, 再读一次主机
- 业务分离: 关键业务始终约束到主机, 保证一致性; 非关键业务使用读写分离
- 实现分配机制: interceptor识别SQL读写操作
- 服务端实现一个中间层TDDL
- sidecar代理: Atlas, MySQL Router
分库分表
分库分表既分离流量, 又分离底层存储, 但同时导致join, count, order by等单表函数失效
- 业务分库很少用, 因为会有函数失效, 分布式事务, 高成本等问题
- 水平分表常用, 当记录达到千万级别, 就需要关注性能, 考察是否水平分表
- 查询路由[分段, 简单hash, 一致性hash, 路由表...]
- 手工实现函数
- join: 模拟笛卡尔积
- count: 扫描汇总或单独维护记录数表
- order by: 分别查询后汇总排序
- 实现分表机制: interceptor识别SQL读写操作 + 操作表 + 操作函数
- 中间层
- sidecar
NoSQL
关系型数据库保证了完整的ACID事务功能, 但也牺牲了一定灵活性, 其与NoSQL的关系类似于 TCP 与 UDP+定制上层协议
- 关系型数据库记录的最小单元是行, 无数据结构抽象, 即便是简单的pop操作也要多条SQL语句才能实现
- K-V存储[Redis, Memcache...]牺牲ACID事务特性, 底层实现了V数据结构
- 适用于社交媒体
- 关系型数据库通过单独的schema库定义各表的字段, 当通过DDL修改字段时, 可能锁表; 如果操作不存在的字段, 会报错
- 文档存储[MongoDB, ...]存储的是plain text(一般是json), 直接定义数据即可, 如果操作不存在的属性, 会返回null
- 牺牲了ACID事务特性, 无法join
- 适用于电商商品库, 游戏等具有明显分类属性的场景
- 关系型数据库按行存储, 所以对结果集多字段的操作能保证高效和ACID; 但面对大数据场景, 可能只需按照少数字段进行筛选, 而关系型数据库必须将整行读入内存, IO成为瓶颈
- 列式存储[HBase, ...]按字段存储, 且单行的相似度更高, 相比行式存储, 具有更高的IO和压缩比
- 牺牲了多列更新的效率, 多个列字段被存储在不同的页, 需要随机读取; 且更新时还需解压压缩操作
- 适用于离线大数据, 统计, 数据仓库等无需更新的场景
- 关系型数据库通过索引实现快速查询, 但面对全文搜索, 索引的局限性很高, like只能高效右模糊匹配; 索引不能过多, 而全文搜索的业务场景往往需要多个的条件字段进行组合
- 全文检索[ElasticSearch, Solr...]使用倒排索引
Cache
- 缓存穿透
- 客观不存在 -> 设置默认值
- 缓存雪崩
- 过期时间随机化
- zk分布式锁, 让流量等待, 缓存拿到最新值后解锁
- 后台定时更新, 缓存不设过期时间, 只有在内存不够时才会失效(LRU)
- 在更新周期的空挡内, 高并发访问可能miss, 此时可以用异步消息通知后台任务更新缓存
- 后台更新策略还可用于主动缓存预热(区别于用户行为重放预热)
- 缓存过热
- 采用一主多从思想, 设置多个从缓存, 分担读请求
高可用(冗余实现)
- 主备结构, 一致性保证
- 节点状态协商策略: 选举协议, 多数取胜 -> 存在分区不一致性(脑裂) -> 多数取胜, 且投票人数过半 -> 可用性受损
可拓展性
设计模式的核心就是,封装变化,隔离可变性
低成本
- 优先级低于上3, 与上3(冗余)矛盾
- 一般只有创新才能解决(发明中间件和组织结构)
架构3原则
合适优于先进>演化优于一步到位>简单优于复杂

浙公网安备 33010602011771号