灵魂拷问
在上篇文章咱们介绍了缓存的定义分类以及优缺点等,若是还没看的同窗能够移步这里node
据说你会缓存?mysql
当咱们的系统引入缓存组件以后,性能获得了大幅度提高,可是随之而来的是代码须要引入必定的复杂度,好比缓存的更新策略,写入策略,过时策略等,而其中最可能致使程序员加班的莫过于缓存和数据库的一致性问题了,既:缓存中的数据和数据库中的数据不一致。程序员
说到一致性问题,这算是分布式系统中不可避免的一个痛点,或者说分布式系统自然就自带了数据一致性问题,虽然能够利用不少分布式事务解决方案来作到一致性,可是实际的系统架构设计中,我仍是推崇避免分布式事务。缓存和数据库数据的一致性在产生原理上和分布式相似,其实能够把他们两个的关系看作是分布式系统中的两个操做节点。redis
凡是处于不一样物理位置的两个操做,若是操做的是相同数据,都会遇到一致性问题
产生数据一致性问题的根本缘由是对一个数据的多个操做过程,缓存和数据库数据的一致性也是这个原理,系统中最多见的操做流程是这样的:算法
从图中能够清楚的看到,对数据库的操做和对缓存的操做是两个不一样阶段的操做,在任何一个操做过程当中都会发生线程安全问题。好比说:sql
而这还不是最致命的,毕竟两个线程同时查询数据库,同时回写缓存数据在多数状况下缓存数据和数据库数据还能保持一致。最要命的是若是是两个线程都进行更新操做,最多见的更新过程是先更新数据库,而后更新缓存。下面就以最多见的用户积分场景为例,每一个用户都有本身的积分,假如发生如下过程:数据库
经过以上案例可见,解决缓存和数据库数据不一致的根本解决方案是须要把两个操做合并成逻辑上能保证事务的一个操做
在平时开发中,利用分布式锁可能算是比较常见的解决方案了。利用分布式锁把缓存操做和数据库操做封装为逻辑上的一个操做能够保证数据的一致性,具体流程为:设计模式
利用分布式锁是解决分布式事务的一种方案,可是在必定程度上会下降系统的性能,并且分布式锁的设计要考虑到down机和死锁的意外状况,而最多见的分布式锁就是利用redis,可是也会有很多坑,具体能够参考以前的文章缓存
相对于分布式锁的方案,而程序员实际中最喜欢使用的仍是删除缓存的方式,在一个可能会发生不一致的场景下,咱们会以数据库为主,在操做完数据库以后,不去更新缓存,而是删除缓存。这在必定意义上至关于只操做数据库,把须要维护的两个数据源变成了一个数据源。
这种方式要求必须先操做数据库,后操做缓存,否则的话发生不一致的概率会大不少。为何这么说呢?由于就算是先操做数据库也会有发生不一致的概率,可是毕竟在整个操做过程当中,删除缓存的操做只占整个流程时间的一小部分而已,并且咱们能够利用缓存的过时时间来保证数据的最终一致性,因此在一些能够容忍数据短暂不一致的场景下能够采用这种方案的。
删除缓存方案带来的另一个劣势是:若是一样的数据会被频繁更新,缓存会被频繁删除,当有读请求的时候又会被频繁的从数据库加载,因此这种方案适用于那种对缓存命中率不敏感的系统中。
发生缓存和数据库不一致的缘由在于多个线程的同时操做,若是相同的数据始终只会有一个线程去操做,不一致的状况就会避免了,好比nodejs,能够充分利用nodejs单线程的优点。提到单线程不能不提一下Actor模型,actor模型在对于一样的对象上能够看作是单线程模式,具体有兴趣的同窗能够查看以前的推文
单线程的模式基本上和分布式锁的方案相似,只不过单线程不须要锁就能够实现操做的顺序化,这也是单线程的优点所在。
若是是以缓存为主呢?假如咱们的应用程序只和缓存组件通讯,至于持久化数据库由专门的程序负责,这样行不行呢?在理论上是能够的
不过这种方案须要考虑几个方面:
以缓存为主的方案的优点是数据优先进入IO速度快的设备,对于那些请求量大,可是能够容忍必定数据丢失的应用很是合适,好比应用log数据的收集系统,这种系统其中一个最大的特色就是能够容忍必定数据的丢失,可是并发的请求数会很是大。因此咱们就能够利用缓存设备前置的方案来应对这种应用场景
更多精彩文章