《面向模式的软件体系结构3资源管理模式》读书笔记(5) Caching模式

3.1 Caching模式

      Caching(缓存)模式描述了如何通过用完资源后不立刻释放资源来避免对资源的昂贵的重新获取。资源会维持它们的标识,并保留在某种可以快速访问的存储器中。它们可以被重用,从而避免了重新获取。

1.问题

      对相同资源的重复的获取、初始化以及释放造成了不必要的开销。在系统的同一个组件或者多个组件访问相同资源的情形下,重复的获取和初始化带来了CPU周期和系统整体性能的开销。应该减少获取、访问和释放频繁使用的资源的开销,以提高性能。为了解决这个问题,需要关注以下几点:

      1)性能(Performance)。重复获取、初始化和释放资源的开销必须最小化。

      2)复杂性(Complexity)。解决方案不应当使得获取和释放资源的操作变得更复杂,更麻烦。此外,解决方案也不应对资源访问增加不必要的间接层。

      3)可用性(Availability)。解决方案应当在资源提供者暂时不可用的情况下也使得资源可以被访问。

      4)可伸缩性(Scalability)。解决方案应该对于资源的数目而言是可伸缩的。

2.解决方案

      暂时把资源存储在称做“缓存”的快速访问缓冲区中。当资源后来被访问时,用缓存来获取和返回资源,而不需要从资源提供者(比如管理资源的操作系统)重新获取它。缓存通过资源标识(比如指针、引用、主键)来识别资源。

      保持频繁访问的资源,不释放它们,这避免了重新获取和释放资源的开销。使用缓存简化了对访问资源的组件的管理。

      当缓存中的资源不再需要时,它们就被释放了。缓存的实现决定了如何去除不再需要的资源。这一行为受策略控制。

3.结构

      资源使用者使用资源。

      资源是一个实体(比如连接)。

      资源缓存(Resource Cache)缓冲了资源使用者释放的资源。

      资源提供者拥有并管理多种资源。

      

      

4.实现

1)选择资源。选择可以从缓存获益的资源。这些通常是获取代价高昂,使用频繁的资源。缓存经常在发现性能瓶颈之后作为优化手段被引入。

    在分布式系统中,存在两种缓存,客户端缓存和服务器端缓存。客户端的缓存对于节省带宽以及节省用于重复地从服务端传送数据到客户端的时间很有效。而服务端缓存则有助于解决多个客户请求导致重复获取和释放服务端的相同资源的问题。

2)决定缓存接口。资源若要被释放,并被资源使用者直接从缓存中重新获取,就必须设计一个合适的接口。这个接口需要提供release()和acquire()方法。

public interface Cache{
    
    public void release(Resource resource);

    public Resource acquire(Identity id);
}

    上面的接口依赖于独立的资源标识。但不是所有的资源都有独立的标识。在某些情况下,资源标识可能需要从资源的属性计算得出。

    资源使用者把资源释放到缓存(而不是释放给资源提供者)时会调用release()方法。

3)实现缓存。Cache接口中acquire()和release()方法的实现提供了缓存的主要功能。 

public class CacheImpl implements Cache {

    public void release(Resource resource) {
        String id = resource.getId();
        map.put(id, resource);
    }

    //....

    HashMap map;

     release()方法把资源添加到map中,这样稍后对acquire()的调用就可以通过它的标识找到资源。为了达到优化的目的,建议使用哈希表,因为哈希表可以执行几乎是常数时间复杂度的查找。

     缓存实现中的acquire()方法应当负责基于标识从表中查找资源。另一种变体是,当资源从缓存中获取失败时,那就意味着没有找到所需标识的资源,那么缓存本身可以从资源提供者获取资源。
public class CacheImpl implements Cache {

    public Resource acquire(Identity id) {
   
        Resource resource = map.get(id);
        if (resource == null)
            throw new ResourceNotFound();
        return resource;

    }
 4)决定如何整合缓存(可选)。如果必须透明地集成缓存,就可以使用Interceptor(截取器)模式或者Cache Proxy模式。引入Interceptor或者Cache Proxy把操作变得透明,从而降低了显式地释放资源到缓存以及从缓存重新获取资源的复杂性。
5)决定清除策略。存储在缓存中的资源会占用存储空间。若许久未用,那么保留这些资源就变得低效了。因此,应该使用Evictor模式来删除不再需要使用的资源。
6)确保一致性。很多资源都有相关联的状态,必须在资源创建时正确地初始化这些状态。此外,当资源被写操作所访问时,需要在原来的资源和镜像原来资源的缓存中的资源之间保持一致性。
 
5.结论
优点:
1)性能。对频繁使用的资源的快速访问是缓存的一个明显好处。和Pooling模式不同,缓存确保资源维持它们的标识。因此,当相同的资源需要被再次访问时,资源就不需要从别的地方获取,它已经在那里了。
2)可伸缩性。避免资源获取和释放是缓存的一个“隐藏”的好处。本质上缓存的实现方式是保存频繁访问的资源。因此,和Pooling模式一样,Caching模式有助于避免获取和释放资源的开销。这对于频繁的使用尤其有好处,从而提升了可伸缩性。
3)使用复杂性。缓存确保了从使用者的角度获取和释放资源的复杂性不会增加,虽然需要有额外的步骤来检查资源是否在缓存中可获得。
4)可用性。缓存资源在资源提供者暂时不可用的时候增加了资源的可用性,因为被缓存的资源还是可用的。
5)稳定性。因为Caching模式减少了释放和重新获取资源的操作量,所以降低了内存碎片的可能性,从而会带来更好的系统的稳定性,这和Pooling模式类似。
 
缺点:
1)同步复杂性。取决于资源的类型,复杂性会增加,因为需要保证被缓存的资源和缓存资源所代表的原始数据的状态的一致性。这一复杂性在集群环境中变得更加显著,在某些极端的情况下会使得这个模式毫无用处。
2)持久性。对被缓存的资源的改变在系统崩溃时可能会丢失。但是,如果使用了同步缓存,那么这个问题可以被避免。
3)空间开销。系统的运行时空间开销会增加,因为很可能没被使用的资源也被缓存了。但是,如果使用Evictor模式,那么可以把这种未使用的被缓存的资源的数目最小化。 
原文地址:https://www.cnblogs.com/pennant/p/2722162.html