太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己( 六 )


  • 两个写库使用不同的初始值 , 相同的步长来增加id:1写库的id为0,2,4,6...;2写库的id为1,3,5,7...;
  • 不使用数据的id , 业务层自己生成唯一的id , 保证数据不冲突 。
实际中没有使用上述两种架构来做读写的“高可用” , 采用的是“双主当主从用”的方式:
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
仍是双主 , 但只有一个主提供服务(读+写) , 另一个主是“shadow-master” , 只用来保证高可用 , 平时不提供服务 。master挂了 , shadow-master顶上(vip漂移 , 对业务层透明 , 不需要人工介入) 。这种方式的好处:
  • 读写没有延时;
  • 读写高可用 。
不足:
  • 不能通过加从库的方式扩展读性能;
  • 资源利用率为50% , 一台冗余主没有提供服务 。
那如何提高读性能呢?进入第二个话题 , 如何提供读性能 。4、如何扩展读性能提高读性能的方式大致有三种 , 第一种是建立索引 。这种方式不展开 , 要提到的一点是 , 不同的库可以建立不同的索引 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
写库不建立索引;线上读库建立线上访问索引 , 例如uid;线下读库建立线下访问索引 , 例如time;第二种扩充读性能的方式是 , 增加从库 , 这种方法大家用的比较多 , 但是 , 存在两个缺点:
  • 从库越多 , 同步越慢;
  • 同步越慢 , 数据不一致窗口越大(不一致后面说 , 还是先说读性能的提高) 。
实际中没有采用这种方法提高数据库读性能(没有从库) , 采用的是增加缓存 。常见的缓存架构如下:
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
上游是业务应用 , 下游是主库 , 从库(读写分离) , 缓存 。实际的玩法:服务+数据库+缓存一套 。
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
业务层不直接面向db和cache , 服务层屏蔽了底层db、cache的复杂性 。为什么要引入服务层 , 今天不展开 , 采用了“服务+数据库+缓存一套”的方式提供数据访问 , 用cache提高读性能 。不管采用主从的方式扩展读性能 , 还是缓存的方式扩展读性能 , 数据都要复制多份(主+从 , db+cache) , 一定会引发一致性问题 。
5、如何保证一致性?主从数据库的一致性 , 通常有两种解决方案:中间件:
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
高并发下的数据安全 如果某一个key有写操作 , 在不一致时间窗口内 , 中间件会将这个key的读操作也路由到主库上 。这个方案的缺点是 , 数据库中间件的门槛较高(百度 , 腾讯 , 阿里 , 360等一些公司有) 。
强制读主:
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
上面实际用的“双主当主从用”的架构 , 不存在主从不一致的问题 。第二类不一致 , 是db与缓存间的不一致:
太傻了!下次二面再回答不好“秒杀系统“设计原理,我就捶死自己

文章插图
 
常见的缓存架构如上 , 此时写操作的顺序是:
  1. 淘汰cache;
  2. 写数据库 。
读操作的顺序是:
  1. 读cache , 如果cache hit则返回;
  2. 如果cache miss , 则读从库;
  3. 读从库后 , 将数据放回cache 。
在一些异常时序情况下 , 有可能从【从库读到旧数据(同步还没有完成) , 旧数据入cache后】 , 数据会长期不一致 。解决办法是“缓存双淘汰” , 写操作时序升级为:
  1. 淘汰cache;
  2. 写数据库;
  3. 在经过“主从同步延时窗口时间”后 , 再次发起一个异步淘汰cache的请求;
这样 , 即使有脏数据如cache , 一个小的时间窗口之后 , 脏数据还是会被淘汰 。带来的代价是 , 多引入一次读miss(成本可以忽略) 。除此之外 , 最佳实践之一是:建议为所有cache中的item设置一个超时时间 。如何提高数据库的扩展性?原来用hash的方式路由 , 分为2个库 , 数据量还是太大 , 要分为3个库 , 势必需要进行数据迁移 , 有一个很帅气的“数据库秒级扩容”方案 。如何秒级扩容?首先 , 我们不做2库变3库的扩容 , 我们做2库变4库(库加倍)的扩容(未来4->8->16) 。


推荐阅读