当前位置: 首页 > 后端技术 > Java

如何通过缓存提升系统性能

时间:2023-04-01 20:50:18 Java

系统中缓存最耗性能的部分是对数据库的访问。一般来说,增删改查操作不会有性能问题,除非索引过多,数据量很大时,这三个操作都会引起性能问题。一般可以通过限制单表索引的个数来提高性能。比如单表索引的个数不能超过5个。大多数情况下,性能问题出在查询上。select操作提供了非常丰富的语法,包括函数、子查询、like子句、where子句等,这些查询非常消耗性能。大多数应用都是读多写少的应用,这样查询慢的问题就会被放大,导致查询慢成为系统性能的瓶颈,这就需要使用缓存来提高系统性能。为什么缓存可以提高系统性能?缓存通过减少系统对数据库的访问量来提高系统性能。试想一下,如果同时有100个对同一个数据的请求,在没有加缓存之前,需要访问数据库100次,而加缓存之后,可能只需要访问一次数据库,而数据其余99个请求中的一个将从缓存中检索。大大减少了访问数据库的次数。虽然也需要访问100次,但是数据库的读性能和缓存的读性能不是一个级别的,所以系统性能有明显的提升。为什么说可能需要访问一次数据库呢?这个跟过期有关,后面会讲到。更新方式既然知道了缓存可以显着提升系统性能,那我们就先来了解一下缓存是如何更新的。CacheAside(推荐)这应该是最常用的更新方式了。这种模式的大致流程是:如果不在缓存中则读取,然后从数据库中读取数据,拿到数据后放入缓存中。如果缓存中有,取完后直接返回。update先更新数据库中的数据,成功后,使缓存失效。为什么要使缓存失效而不是更新它?主要是因为两个并发写操作导致的脏数据。假设有两个线程A和B,他们想分别修改资源A的值为1和2。线程A先到数据库,更新数据为1,但是缓存还没有更新。由于时间片用完,线程B获得CPU,在此期间将数据库资源A的值和缓存值更新为2。线程B结束后,线程A夺回CPU,执行更新缓存,将资源A的值改为1,线程A结束。此时A在数据库中的值为2,而A在缓存中的值为1,数据不一致。所以使缓存失效就不会出现这个问题,保证缓存中的数据与数据库一致。那是不是说CacheAside模式就不会出现并发问题呢?不。例如,如果一个读操作没有命中缓存,它就会去数据中读取数据(A=1)。这时一个写操作会更新数据库数据(A=2)并使缓存失效。这时,读操作会将读取到的数据(A=1)写入缓存,从而产生脏数据。这种情况理论上是可以发生的,但现实中发生的概率极低。要做到这一点,在读操作发生时必须有一个并发的写操作,并且既要读操作在写操作之前读,又要写操作在写操作之后写入缓存。满足这个条件的概率不高。基于以上描述的问题,目前有两种合理的解决方案:通过2PC保证数据一致性(复杂);通过降低并发时出现脏数据的概率,设置合理的过期时间(简单,但在一定时间内是有错误率的,一般可以接受)。Read/WriteThrough在这种模式下,对于应用程序来说,所有的读写请求都直接和缓存打交道,关于数据库的数据完全由缓存服务更新(更新同步是一种同步操作)。这种模式下,过程相当简单,完全就是对缓存的读写。缺点:该模式对缓存服务依赖性强,需要缓存高可用。所以不应该有普遍的东西。WriteBehindCaching实际上,这种模式是Read/WriteThrough的一种变体。不同的是前者是异步更新,后者是同步更新。由于数据库是异步更新的,其相应的速度比Read/WriteThrough要高,而且还可以将对同一个数据的多个操作结合起来。这有点像MySQL的缓冲池的flush操作。缺点:异步意味着数据不强一致,也有数据丢失的风险,实现逻辑比较复杂。设计思路无状态服务在分布式系统中,无状态服务有利于横向扩展,所以缓存也应该独立于业务服务,设计成一个独立的服务,让业务服务无状态。许多公司选择使用Redis来构建他们的缓存系统,看中的是它的高速读写性能。命中率缓存服务的质量主要取决于命中率。一般来说,如果命中率保持在80%以上,就已经算是很高了,但是我们不能为了提高命中率,而将数据库中的数据全部写入缓存。这不符合缓存的设计理念,需要巨大的内存空间。一般来说,只有一小部分热点数据应该写入缓存。缓存是通过牺牲强一致性来换取性能来实现的,并不是所有的业务都适合使用缓存。有效时间缓存数据的有效时间不能太短,不能太长,不能太集中。太短会增加数据库访问次数。太长容易不用的数据停留在缓存中,浪费空间,一旦产生脏数据,进程的有效时间会导致脏数据延迟,影响更多的业务。过于集中会导致缓存雪崩。淘汰策略当内存不足时,缓存系统必须遵循淘汰策略,淘汰不适合保留在缓存中的数据,为新数据腾出空间。下面以Redis为例,给出淘汰策略:noeviction:不删除策略。当达到最大内存限制时,如果需要更多内存,则直接返回错误信息(极少数例外,如DEL)。allkeys-lru:所有key通用,优先删除最近最少使用(LRU)的key。(推荐)volatile-lru:只限于设置expire的部分,优先删除最近最少使用(lessrecentlyused,LRU)key。allkeys-random:所有key通用,部分key随机删除。volatile-random:只限于设置expire的部分,随机删除一些key。volatile-ttl:只限于设置expire的部分,优先删除剩余时间(timetolive,TTL)较短的key。可根据项目实际情况选用。总结缓存的目的是加快数据访问速度。它是数据库之上的一种机制。并非所有业务都适合使用缓存。更新策略和淘汰策略要根据具体情况来选择。来源:blog.csdn.net/qq_36011946/article/details/104164031