这是最经典的 缓存+数据库 读写模式,操做以下:
①读的时候,先读缓存,缓存没有就读数据库,而后将取出的数据放到缓存,同时返回请求响应。nginx
②更新的时候,先删除缓存,而后更新数据库。数据库
①由于不少时候,缓存中放的并非简单的从数据中取出来值,可能要进行一个状态的替换,一些数据的计算,还有可能要进行数据的组合等等。缓存
②二八法则,即20%的数据,占用了80%的访问量,这个更新的数据,多是冷门数据,很久也访问不了不了一次,这样只会占用缓存内存。lazy思想,数据等你第一次要的时候再去加载,避免不必的内存和时间浪费。服务器
问题 :先修改数据库,再删除缓存,若是缓存删除失败了,那么就会早上数据库和缓存数据不一致。
解决思路 :先删除缓存,再修改数据库,若是删除缓存成功了,若是修改数据库失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。由于读的时候缓存没有,则读数据库中旧数据,而后更新到缓存中并发
问题 :数据发生了变动,先删除了缓存,而后要去修改数据库,此时还没修改。
一个请求过来,去读缓存,发现缓存空了,去查询数据库,查到了修改前的旧数据,放到了缓存中。数据变动的程序完成了数据库的修改。
这样就又致使了数据不一致异步
由于只有在对一个数据在并发的进行读写的时候,才可能会出现这种问题。jvm
为了解决上面的并发读写问题,能够考虑将更新和读取操做进行串行化。
①更新数据的时候,根据数据的惟一标识,将操做路由以后,发送到一个jvm内部的队列里面去。ide
②读取数据的时候,若是发现缓存中没有,那么将从数据库读取数据的操做和更新缓存的操做一块儿路由到同一个JVM内部的队列中去。高并发
③一个队列对应一个工做线程,而后线程从队列里面去取请求进行操做。测试
这样就能够将同一个key的操做进行串行化,一个数据变动的操做,先执行,删除缓存,而后再去更新数据库,可是还没完成更新,此时若是一个读请求过来,读到了空的缓存,那么能够先将缓存更新的请求发送到队列中,此时会在队列中积压,而后同步等待缓存更新完成。
这里有一个优化点,一个队列中,其实多个更新缓存请求串在一块儿是没意义的,所以能够作过滤,若是发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操做进去了,直接等待前面的更新操做请求完成便可。
待那个队列对应的工做线程完成了上一个操做的数据库的修改以后,才会去执行下一个操做,也就是缓存更新的操做,此时会从数据库中读取最新的值,而后写入缓存中。
若是请求还在等待时间范围内,不断轮询发现能够取到值了,那么就直接返回; 若是请求等待的时间超过必定时长,那么这一次直接从数据库中读取当前的旧值。
①读请求长时间阻塞问题
因为读请求进行了很是轻度的异步化,因此必定要注意读超时的问题,每一个读请求必须在超时时间范围内返回。
该解决方案,最大的风险点在于说,可能数据更新很频繁,致使队列中积压了大量更新操做在里面,而后读请求会发生大量的超时,最后致使大量的请求直接走数据库。
务必经过一些模拟真实的测试,看看更新数据的频繁是怎样的。
②读请求并发量太高
还有一个风险,就是忽然间大量读请求会在几十毫秒的延时hang在服务上,看服务能不能抗的住,须要多少机器才能抗住最大的极限状况的峰值。
可是由于并非全部的数据都在同一时间更新,缓存也不会同一时间失效,因此每次可能也就是少数数据的缓存失效了,而后那些数据对应的读请求过来,并发量应该也不会特别大。
③多服务实例部署的请求路由
可能这个服务部署了多个实例,那么必须保证说,执行数据更新操做,以及执行缓存更新操做的请求,都经过nginx服务器路由到相同的服务实例上。
④热点商品的路由问题。致使请求的倾斜 万一某个商品的读写请求特别高,所有打到相同的机器的相同的队列里面去了,可能形成某台机器的压力过大。 就是说,由于只有在商品数据更新的时候才会清空缓存,而后才会致使读写并发,因此更新频率不是过高的话,这个问题的影响并非特别大。 可是的确可能某些机器的负载会高一些。