当装饰者模式遇上Read Through缓存,一场技术的浪漫邂逅

read,through · 浏览次数 : 0

小编点评

**缓存模式的几种常见类型** 1. **旁路缓存**:读取数据的时候,应用程序直接从数据库中读取数据并填充缓存。更新数据的时候,应用程序只更新缓存中的数据,而将数据库中的数据更新到缓存中。 2. **代理模式**:创建一个代理对象,将请求转发给目标对象。代理模式可以用于将多个对象之间进行交互。 3. **装饰器模式**:在不改变现有对象结构的情况下,动态地给该对象增加一些职责。 4. **Read Through**:当缓存未命中的时候,从数据库加载数据,填充缓存并返回给应用程序。 5. **Write Through**:当应用程序想要写入数据或更新值时,会先更新到缓存,同时更新到数据源,这两个操作在共一个事务中完成。 6. **Write Back**:当写入数据的时候,只是写到了缓存。当缓存过期的时候,才会被刷新到数据库。

正文

《经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案》一文中,我提到在系统中使用的缓存是旁路缓存模式,有读者朋友问,有没有用到过其他的缓存模式,本文将结合一个我曾经工作中的案例,使用装饰者模式实现Read Through缓存模式,助你轻松掌握设计模式和缓存。

一、缓存模式

不说废话,直入主题。缓存模式是指用于管理缓存数据的策略和方式。常见的有这几种:Cache Aside、Read Through、Write Through、Write Back等

Cache Aside

在平时的开发工作中,旁路缓存是最常用的一种缓存模式,“旁路”二字意味着读取缓存、读取数据、更新缓存和操作都是在应用程序中完成的,缓存和数据库之间是没有连接的

Read Through

在这种模式下,缓存与数据库是连接起来的。当缓存未命中的时候,缓存中数据库中加载数据,填充缓存并返回给应用程序。

虽然Read Through和Cache Aside非常相似,但至少有两个关键区别:

  1. 在Cache Aside中,应用程序负责从数据库获取数据并填充缓存。在Read Through中,此逻辑通常由库或独立缓存提供程序来实现。
  2. 与Cache Aside不同,Read Through缓存中的数据模型不能与数据库的数据模型不同。

Write Through

在穿透写入模式下,当应用程序需要更新数据时,它会先更新到缓存,同时更新到数据源,这两个操作在共一个事务中完成。因此只有2个都写成功了才会最终写成功,有助于保持缓存和数据源之间的数据一致性。

当应用程序想要写入数据或更新值时,会发生以下情况:

  1. 应用程序将数据直接写入缓存。
  2. 缓存更新主数据库中的数据。当写入完成后,缓存和数据库都具有相同的值,并且缓存始终保持一致。

Write Back

在这个模式下,当写入数据的时候,只是写到了缓存。当缓存过期的时候,才会被刷新到数据库。这个模式最大的一个问题就是如果缓存突然宕机,那么还没有刷新到数据库的数据就彻底丢失了。

说明

我认为严格来说,现在的缓存工作模式都归属于旁路缓存。如果在代码中的缓存层(类似于Mapper层)进行了封装的话;那么在业务代码中调用缓存层方法进行操作,这里屏蔽了缓存的实现细节,站在业务代码层面来看,可以暂且认为是实现了不同的缓存模式吧。

二、代理模式和装饰者模式

那如何优雅实现上述缓存层的封装,在开发中可以丝滑接入呢?接着看,这里需要使用一些设计模式。

这是代理模式、装饰者模式的代码结构示例:二者结构完全一致。

// 装饰者模式代码结构
public interface AInterface {
    void run();
}

public class A implements AInterface {
    @Override
    public void run() {
         // run
        System.out.println("A run...");
    }
}

public class ADercorator implements AInterface {

    private AInterface a;

    public ADercorator(AInterface a) {
        this.a = a;
    }

    @Override
    public void run() {
        // doSomething
        System.out.println("ADerocator do something...");
        a.run();
        // doSomething
    }
}

当然代理模式和装饰者模式还是有区别的,主要是看应用的场景。

代理模式主要是为其他对象提供一种代理,以控制对这个对象的访问

例如有一个案例这样的,一个RPC接口的提供方和调用方都是双机房部署的,原本的远程调用也是机房垂直调用(机房A只调用机房A,机房B只调用机房B)的;由于接口提供方接口性能原因,希望调用方改为跨机房调用(机房A同时调用机房A和机房B,机房B同时调用机房A和机房B)。我的实现方式是使用代理模式,在代码中新增一个接口调用的代理层,在代理层中注入机房A和机房B的RPC Consumer配置,根据时间戳的奇偶来判断调用哪个机房,从而实现跨机房调用。

而装饰者模式则指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式。在《Head First 设计模式》一书中,更是将装饰者模式称为“给爱用继承的人一个全新的设计眼界”。例如对于不同缓存模式的实现,可以使用装饰者模式。

三、装饰者模式实现Read Through缓存模式

这是我之前做过的一个RPC读接口性能优化的案例。系统架构比较简单,就是一个常规的Java Web应用,对外提供RPC服务,底层数据采用的是MySQL,缓存使用的是Redis。

该查询接口涉及数据库多张MySQL表和多个外部RPC接口的查询

在接口性能优化过程中,

对于多张表查询,检查慢SQL并进行了索引优化;

对于多外部RPC接口调用改为并行调用;

后续又使用了Redis缓存进行缓存数据。

当时刚进入职场不久,作为一个职场新人,年轻气盛,还想着彰显一下个人的技术,所以我打算使用装饰者模式来实现Read Through缓存模式;并且实现了一个当时认为稍微“展示一点技术”的方案:当缓存未命中时,从数据库加载数据,返回业务层,将写入缓存改为异步写入

大概代码如下,大家参考:

public interface CacheInterface {
    String get(String key);
    String set(String key, String value);
}

public class Cache implements CacheInterface {

    private Jedis jedis;

    @Override
    public String get(String key) {
         // run
        System.out.println("Cache get...");
        return jedis.get(key);
    }

    @Override
    public String set(String key, String value) {
        return jedis.set(key, value);
    }
}

public class CacheDecorator implements CacheInterface {

    private Repository myRepositoty = new MyRepository();

    private CacheInterface cache;

    public CacheDecorator(CacheInterface a) {
        this.cache = cache;
    }

    @Override
    public String get(String key) {
        // doSomething
        System.out.println("CacheDecorator do something...");
        String cacheResult = cache.get(key);
        if (!StringUtils.isEmpty(cacheResult)) {
            return cacheResult;
        }
        String result = myRepositoty.get(key);
        CompletableFuture.runAsync(() -> cache.set(key, result));
        return result;
    }

    @Override
    public String set(String key, String value) {
        return cache.set(key, value);
    }
}

public class Test {

    public static void main(String[] args) {
        Cache cache = new Cache();
        CacheDecorator derocator = new CacheDecorator(cache);
        derocator.get("a");
    }

}  

一起学习

欢迎各位在评论区或者私信我一起交流讨论,或者加我主页weixin,备注技术渠道(如博客园),进入技术交流群,我们一起讨论和交流,共同进步!

也欢迎大家关注我的博客园、公众号(码上暴富),点赞、留言、转发。你的支持,是我更文的最大动力!

参考资料

https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/

与当装饰者模式遇上Read Through缓存,一场技术的浪漫邂逅相似的内容:

当装饰者模式遇上Read Through缓存,一场技术的浪漫邂逅

在《经验之谈:我为什么选择了这样一个激进的缓存大Key治理方案》一文中,我提到在系统中使用的缓存是旁路缓存模式,有读者朋友问,有没有用到过其他的缓存模式,本文将结合一个我曾经工作中的案例,使用装饰者模式实现Read Through缓存模式,助你轻松掌握设计模式和缓存。

装饰器模式:让你的对象变得更强大

在日常开发中,当需要给一个现有类添加附加职责,而又不能采用生成子类的方法进行扩充时。例如,该类被隐藏或者该类是终极类或者采用继承方式会产生大量的子类。这时候,我们该怎么办呢?我们可以使用装饰器器模式来解决这个问题,**本文将从以下四个方面讲解装饰器器模式**。 - 简介 - 优缺点 - 应用场景 -

【23种设计模式】组合模式(八)

前言 组合模式,英文名称是:Composite Pattern。当我们谈到这个模式的时候,有一个物件和这个模式很像,也符合这个模式要表达的意思,那就是“俄罗斯套娃”。“俄罗斯套娃”就是大的瓷器娃娃里面装着一个小的瓷器娃娃,小的瓷器娃娃里面再装着更小的瓷器娃娃,直到最后一个不能再装更小的瓷器娃娃的那个

2.6 PE结构:导出表详细解析

导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。

穿透 wsl 和 ssh, 新版本 neovim 跨设备任意复制,copy anywhere!

最近一个星期,我入坑了 neovim, 然后开始配置各种插件。同一个时间点,我入手了一台 surface go2, 这是个 Windows 平板,我在上面也是装好了各种软件,配置了 wsl2, 并且配置了 ssh。然后我发现当我 ssh 连接到宿舍的高性能笔记本的时候,我打开 neovim 时候无法...

01背包问题的js解决方式

如果你有兴趣看这个相信你已经对背包问题有所了解,所以关于背包问题的描述,我就不写了。只记录一下自己对这个问题的一些看法和思考,于我而言,这个东西现在困扰我的是如何确定最优解。实质上关于背包问题网上的东西我大体都有看过,对于这个问题,常见的就是使背包重量动态增长,然后遍历每个要装入的这些包裹,当包裹的

前端说你的API接口太慢了,怎么办?

当有千万条海量数据时,前端调取接口发现接口响应的太慢,前端这时让你优化一下接口,你说有几千万条数据,觉得自己尽力了,前端觉得你好菜,别急,读完这篇文章,让前端喊你一声:大佬,厉害!!! 常用的方法总结 通过合理的分页加载、索引优化、数据缓存、异步处理、压缩数据等手段,可以有效地优化接口性能,提升系统

.NET App 与Windows系统媒体控制(SMTC)交互

当你使用Edge等浏览器或系统软件播放媒体时,Windows控制中心就会出现相应的媒体信息以及控制播放的功能,如图。 SMTC (SystemMediaTransportControls) 是一个Windows App SDK (旧为UWP) 中提供的一个API,用于与系统媒体交互。接入SMTC的好

如何安全地访问互联网

当你深夜在浏览器中输入 www.baidu.com 时有没有想过,除了月黑风高的夜和本机的浏览记录,还有谁知道你访问了它呢?要搞清楚这件事,首先我们要了解一下访问网站时,这其中发生了什么。 如果你在 10 年之前访问网站,大概率会在浏览器的地址栏中看到这样的网址 http://www.baidu.c

当面试官问出“Unsafe”类时,我就知道这场面试废了,祖坟都能给你问出来!

一、写在开头 依稀记得多年以前的一场面试中,面试官从Java并发编程问到了锁,从锁问到了原子性,从原子性问到了Atomic类库(对着JUC包进行了刨根问底),从Atomic问到了CAS算法,紧接着又有追问到了底层的Unsafe类,当问到Unsafe类时,我就知道这场面试废了,这似乎把祖坟都能给问冒烟