高并发完毕数据库设计是要结合不同的应用场景的,本文主要涉及到一下问题:
1、对数据库表的字段訪问比較均衡,业务导向明显(网上商城,多条业务线);
2、对数据库表的字段訪问比較均衡,业务导向不明显(对单一应用的高并发訪问);
3、对数据库表的单一字段訪问比較集中(秒杀、大量用户对同一账户操作)
只是对于一般的小型站点的应用,并发高的话 採用读写分离基本上就能解决这个问题,本文主要是针对大型站点高并发数据库设计讨论。
一、对数据库表的訪问比較均衡,业务导向明显(网上商城,多条业务线)
像这样的情况,一般採用数据库表垂直切分。
垂直切分就是要把表按模块划分到不同数据库中,这样的拆分在大型站点的演变过程中是非经常见的。当一个站点还在非常小的时候,仅仅有小量的人来开发和维护。各模块和表都在一起,当站点不断丰富和壮大的时候。也会变成多个子系统来支撑,这时就有按模块和功能把表划分出来的需求。例如以下图所看到的:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ2tlaHVhaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
事实上,相对于垂直切分更进一步的是服务化改造。说得简单就是要把原来强耦合的系统拆分成多个弱耦合的服务,通过服务间的调用来满足业务需求看,因此表拆出来后要通过服务的形式暴露出去,而不是直接调用不同模块的表,淘宝在架构不断演变过程。最重要的一环就是服务化改造,把用户、交易、店铺、宝贝这些核心的概念抽取成独立的服务,也很有利于进行局部的优化和治理。保障核心模块的稳定性。这样一种拆分方式也是有代价的:
- 表关联无法在数据库层面做
- 单表大数据量依旧存在性能瓶颈
- 事务保证比較复杂
- 应用端的复杂性添加
上面这些问题是显而易见的,处理这些的关键在于怎样解除不同模块间的耦合性,这说是技术问题。事实上更是业务的设计问题,仅仅有在业务上是松耦合的,才可能在技术设计上隔离开来。
没有耦合性,也就不存在表关联和事务的需求。
另外,大数据瓶颈问题能够採用水平切分。
二、对数据库表的字段訪问比較均衡,业务导向不明显(对单一应用的高并发訪问)
1)这样的情况一般採用对数据库表进行水平切分。
上面谈到垂直切分仅仅是把表按模块划分到不同数据库,但没有解决单表大数据量的问题,而水平切分就是要把一个表依照某种规则把数据划分到不同表或数据库里。比如像计费系统。通过按时间来划分表就比較合适。由于系统都是处理某一时间段的数据。而像SaaS应用。通过按用户维度来划分数据比較合适,由于用户与用户之间的隔离的,一般不存在处理多个用户数据的情况,以下是一个比較简单的按user_id来水平切分的样例:
水平切分没有破坏表之间的联系,全然能够把有关系的表放在一个库里。这样就不影响应用端的业务需求,而且这种切分能从根本上解决大数据量的问题。它的问题也是非常明显的:
- 当切分规则复杂时,添加了应用端调用的难度
- 数据维护难度比較大,当拆分规则有变化时,须要对数据进行迁移
对于第一个问题,能够參考怎样整合应用端和数据库端。
对于第二个问题能够參考一致性hash的算法,通过某些映射策略来减少数据维护的成本
2)当然还能够把水平切分和垂直切分结合起来
由上面可知垂直切分能更清晰化模块划分,区分治理,水平切分能解决大数据量性能瓶颈问题。因此经常就会把两者结合使用,这在大型站点里是种常见的策略。这能够结合两者的长处,当然缺点就是比較复杂,成本较高,不太适合小型站点,以下是结合前面两个样例的情况:
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ2tlaHVhaQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" >
三、对数据库表的单一字段訪问比較集中(秒杀、大量用户对同一账户操作)
对于这样的情况有非常多种解决方式。可是每一种都不是非常完美:
1)採用内存缓存或者缓存数据库来缓解数据的库的压力
详细做法是:在利用内存缓存或者缓存数据库把后台数据库server上相关的表数据载入到内存中,所用用户高并发的对内存数据进行处理,然后再定时轮询的方式把内存的数据刷新到后台数据库表中,这样的做法有下面问题:
a)不能非常好保持内存数据与数据库数据的一致性。
b)假设出现断电、内存损坏等情况,会有数据丢失;
2)採用对数据库表水平切分。然后在后台的程序中对各个表的数据总体控制
比如,有10000亿人民币为1亿人并发提供贷款业务。
在数据库中建立一个总表存下10000亿人民币,然后再建立10张分表。初始设为空;后台java程序在訪问数据库时会有一个控制程序(中间件),开10个 线程池。每一个线程池相应一个数据库分表,其中间件接受到贷款申请时,中间件就会依据用户的ID(能够ip地址,账户编号)hash到相应的线程去到总表中借款,这个借款数目能够依据总表的资金和用户的要借的资金去申请额度(比方用户申请10w,总表有1000亿,相应线程能够向总表申请10亿),存入相应分表。供这个用户提供贷款,假设再有下个用户再到此线程池操作数据库表,就直接操作。分表中金额不够的时候再到总表中借款。
这种设计攻克了,高并发存储数据库的问题,可是添加了后台的程序设计的难度,加大了程序的耦合度。
3)採用“记流水不记账“的方式应对
还用上一个样例,这样的方式。须要在数据库中设计两个表,一个用来存储账户金额(账户表),还有一个记录”流水“(流水表), 所谓”记流水“是指每当有个请求到来,就向流水表中插入一条记录。然后定时对所插入的记录进行统计,update账户表的数据,当然这样的方式,须要在内存中添加变量,来控制所用用户的贷款不能超过所贷款的总金额。这样的处理方式是数据库端处理秒杀、高并发集中訪问数据库表字段的有效方式,使用比較广泛。
4)针对网购秒杀还有其针对性的设计。由于网购秒杀和高并发操作银行账户不同,网购秒杀同意用户请求丢失,简单的来说,仅仅须要在内存缓存或者内存数据库(充当队列)中保存较早的用户请求。然后再异步的处理这些请求来操作数据库(更新数据库)。