昨晚跟朋友一起讨论了一个问题,关于他问我怎么保持数据库的一致性,以及这其中衍生出来的一些问题,我觉得我应该写个博客,至少自己觉得懂了许多。
抛个问题出来:什么是数据库的一致性呢?
答:既然抽象,那就举个例子出来,方便理解;比如说,抢单问题,当有一件物品的数量只有10件,而参与抢单的人却有10万个,很明显,物品数量是远远不够的,假设这10万个人同时发送请求进来,第一个人抢了,本来数据应该减1变成9的,但是同时进来的那些人取到的数是没有修改的,也就是还是10个,这就很明显不符合逻辑;如果你觉得这还不够严重的话,我假如现在的商品已经只剩下1个了,也就是那10万人抢1个,本来第一个人抢了之后,数据就会变成0,接下来的人就抢不到了,但是由于数据未来得及更新,所以那些人可能也会抢到,这样会造成很严重的后果。所以这就是需要保持数据库的一致性的问题。
既然很严重,那么我们可以用什么样的方法来解决这个问题呢?
以下是我的见解,有不对的地方,希望可以指出来,把更好的见解发到我的邮箱:[email protected]。
可以用事务来解决这个问题,就如上个问题,可以为抢单那个动作加个事务,这个动作包含了select和update这两个步骤,数据库事务会使这两个操作保持一致性,即同一时间内只允许一个用户抢单,只要在select和update这两个操作结束之后才允许下一个用户继续抢单,这样就会使用户抢到的数据是最新的数据,即可保持了数据的一致性了;其实我想数据库事务里面也是用锁的机制,只有获得锁的操作才可以继续,否则就需要等待。
虽然解决了这样一致性问题,又会导致另一个问题的衍生:假设有10万个人同时进行请求,并且假设每个请求都会花费0.5秒,那么,第一个用户花了0.5秒去请求到,没问题,第二个用户花了1秒去请求到,也没问题,那第10万个用户呢,要花费5万秒去等待前面的人,这明显是很不实际的,这就是请求并发的问题。
其实这是矛盾的,既然数据库一致性是必然的,那么必然也会导致这样的请求并发的问题;既然不能够解决,那就必须优化它,使它的响应速度再快一些。这就该用到缓存的时候了。
这里先说明一点:数据库是存在硬盘上的,而缓存是可以存在内存的,内存的响应速度应该可以比硬盘快到10000倍吧大概,所以利用缓存来优化它。
我用到的缓存有两个:一个是memcache,一个是redis,两个各有优缺点,其中,memcache是没有事务的,它是通过队列的形式,让用户(请求)先进先出;而redis是有事务的;
关于redis和memcache的区别:
1、redis和memcache都是将数据存放在内存中,都是内存数据库,不过memcache还可以用于缓存其它东西,例如图片视频等;
2、redis不仅仅可以存储简单的key/value类型的数据,还提供了list,set,hash等数据结构的存储;
3、虚拟内存--redis当物理内存不足时,可以将一些很久没用到的value交换到磁盘中;
4、过期策略--memcache在set时就指定,而redis可以通过例如expire指定
5、分布式--设定memcache集群,利用magent做一主多从,redis可以做一主多从,也可以做一主一从;
6、存储数据安全--memcache挂掉之后,数据没了,redis可以定期保存到磁盘(持久化),
7、灾难恢复--memcache挂掉之后,数据不可恢复,redis可以通过aof恢复;
8、redis支持数据备份,即master-slave模式的数据备份;
回到上一个问题:
缓存的原理就是,当第一次请求进来时,把这一次请求到的数据全部放到缓存里去(即内存),然后设定一个过期时间,在这个时间内,如果有相同的请求进来,则不用再查找数据库,直接从内存上取;当过期时间已到,则把缓存中的数据删掉,再把最新一次请求的数据放到缓存,然后再设定一个过期时间;如此类推,在内存上取数据,肯定快了很多。
当然这里还有一个问题就是:如上面提到的10万个请求中,或许真正有效的请求都不够1000个,因为或许存在有一个黑客写了一段程序,不断执行这个请求,他的目的就是搞垮你的服务器,如何解决这个问题呢?
其实这个问题的思路也很简单,就是去检测一下他的ip地址,一般用户在一段时间内比如10秒,请求次数一般不会超过10次或者5次,那么就可以检测ip在一段时间内比如10秒的访问次数,如果达到一定的数量比如10次,那么标记为敏感地址,可以对其进行相应的操作;这只是一个思路。