缓存相关代码的演变

问题引入

上次我参与某个大型项目的优化工作,由于系统要求有比较高的TPS,因此就免不了要使用缓冲。

该项目中用的缓冲比较多,有MemCache,有Redis,有的还需要提供二级缓冲,也就是说应用服务器这层也可以设置一些缓冲。

当然去看相关实现代代码的时候,大致是下面的样子。

?


1

2

3

4

5

6

7

8

9

10

11

12

13

public void saveSomeObject(SomeObject someObject){

    MemCacheUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = MemCacheUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象

         MemCacheUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}

很明显与缓冲相关的代码全部是耦合到原来的业务代码当中去的。

后来由于MemCache表现不够稳定,而且MemCache的功能,也可以由Redis完全进行实现,于是就决定从系统中取消MemCache,换成Redis的实现方案,于是就改成如下的样子:

?


1

2

3

4

5

6

7

8

9

10

11

12

public void saveSomeObject(SomeObject someObject){

    RedisUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = RedisUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象 <span></span>RedisUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}

这一通改下来,开发人员已经晕头晕脑的了,后来感觉性能还是不够高,这个时候,要把一些数据增加二级缓冲,也就是说,本地缓冲有就取本地,本地没有就取远程缓冲 

于是,上面的代码又是一通改,变成下面这个样子:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

public void saveSomeObject(SomeObject someObject){

    LocalCacheUtil.put("SomeObject",someObject.getId(),someObject);

    RedisUtil.put("SomeObject",someObject.getId(),someObject);

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject = LocalCacheUtil.get("SomeObject",id);

    if(someObject!=null){

        return someObject;

    }

    someObject = RedisUtil.get("SomeObject",id);

    if(someObject!=null){

         someObject=//真实的获取对象

         RedisUtil.put("SomeObject",someObject.getId(),someObject);

    }

    return someObject;

}

但是这个时候就出现一个问题:

由于在某一时刻修改值的只能是某一台计算机,这个时候,其它的计算机的本地缓冲实际上与远程及数据库中的数据会不一致,这个时候,可以有两种办法实现,一种是利用Redis的请阅发布机制进行数据同步,这种方式,会保证数据能够被及时同步。

另外一种方法就是设置本地缓冲的有效时间比较短,这样,允许在比较短的时间段内出现数据不一致的情况。

不管怎么样,功能是实现了,程序员小伙伴这个时候已经改得眼睛发黑,手指发麻,几乎接近崩溃了。

很明显这种实现方式是不好的,于是项目组又提出了改进意见,能否采用注解方式进行标注,让程序员只要声明就可以?Good idea,于是,又变成了下面的样子:

?


1

2

3

4

5

6

7

8

9

10

@Cache(type="SomeObject",parameter="someObject",key="${someObject.id}")

public void saveSomeObject(SomeObject someObject){

    //下面是真实保存对象的代码

 

}

@Cache("SomeObject",key="${id}")

public SomeObject getSomeObject(String id){

    SomeObject someObject=//真实的获取

    return someObject;

}

这个时候,程序员们的代码已经非常清爽了,里面不再有与缓冲相关的部分内容,但是引入一个新的问题,就是处理注解的代码怎么写?需要引入容器,比如:Spring,这些Bean必须被容器所托管,如果直接new一个实例,就没有办法用缓冲了。还有一个问题是:程序员的工作量虽然有所节省,但是还是要对程序代码有侵入性,需要引入这些注解,如果要增加超越现有注解的功能,还是需要重新写过这些类,引入其它的注解,修改现有的注解。

所以,上面是个可以接受的方案,但明显还不是很好的方案。

假如有一个程序员火大了,他发出下面的抱怨:“我只管做我的业务,放不放缓冲和我有1毛钱关系么?总因为缓冲的事情让我改来改去,程序改得乱七八糟不说,我的时间,我的工作进度都影响了谁来管?以后和缓冲相关的事情别他妈的来烦我!”,作为架构师的你,你怎么看?最起码,我觉得他是说得非常有道理的。我们再返过头来看看最原始的代码:

?


1

2

3

4

5

6

7

8

public void saveSomeObject(SomeObject someObject){

    //下面是真实保存对象的代码

 

}

public SomeObject getSomeObject(String id){

    SomeObject someObject=//真实的获取

    return someObject;

}

这里是干干净净的业务代码,和缓冲没有一点关系。后来由于性能方面的要求,需要做缓冲,OK,这一点是事实,但是用什么缓冲或怎么缓冲,与程序员确实是没有什么关系的,因此,是不是可以不让程序员参与,就可以优雅的做到添加缓冲功能呢?答案当然是肯定的。

需求整理

  1. 代码当中,不要体现与缓冲相关的内容,也就是说做不做缓冲及怎么做缓冲不要影响到业务代码
  2. 不管是从容器中取实例还是new实例,都可以同样的起作用,也就是说可以不必依赖具体的容器

解决思路:

放不放缓冲、怎么放缓冲、缓冲有效时间等等,这些内容是在运行期发现存在性能瓶颈,然后提交给程序员来进行优化的。为此,我们设计了一个配置来描述这些缓冲相关的声明。

当然,这个配置文件的结构,可以根据自己所采用的缓冲框架来进行相应的定义。

比如:

?


1

2

3

4

5

6

7

8

9

10

11

12

13

14

<redis-caches>

  <redis-cache type="org.tinygroup.redis.test.UserDao">

     <redis-method method-name="saveUser">

           <redis-expire value="1000"></redis-expire>

           <redis-string type="user" key="${user.id}" paramter-name="user"><redis-string>

     </redis-method>

  </redis-cache>

  <redis-cache type="org.tinygroup.redis.test.UserDao">

     <redis-method method-name="getUser">

           <redis-expire value="1000"></redis-expire>

           <redis-string type="user" key="${id}" ><redis-string>

     </redis-method>

  </redis-cache>

</redis-caches>

我们在实际应用当中,配置比上面的示例更完善,那现在我先讲一下上面的两段配置的含义。

在UserDao的saveUser的时候,会同步的把User数据放到Redis中进行缓冲,缓冲时间为1秒,存放的缓冲数据的类型为user,键值为${user.id},也就是要保存的用户的主健。实际进入到Redis的时候,在Redis中的健值是由上面类型与key的共同组成的。

在调用UserDao的getUser的时候,会先从缓冲中获取类型为user,键值为${id}的数据,如果缓冲中在,则取出并返回,如果缓冲中没有,则从原有业务代码中取出值并放入缓冲,然后返回此对象。

哇,这个时候,就非常爽了,只要通过声明就可以做到对缓冲的处理了,但是一个问题就出来了,如何实现上面的需求呢?

通过配置文件外置,确实做到了对业务代码的0侵入,但是如何为原有业务增加缓冲相当的业务逻辑呢?由于需求2要求可以new,也可以从容器中获取对象实例,因此利用容器AOP解决的跑就被封死了,因此,就得引入字节码的方式来进行解决。

具体实现

写一个基于Maven的缓冲代码处理插件,在编译后增加此处理插件,根据配置文件对原有代码进行扫描并修改其字节码以添加缓冲相关处理逻辑。

现在只要使用Maven进行compile或install就可以自动添加缓冲相关的逻辑到class文件中了。

至此,我们已经分析了缓冲代码直接耦合到代码中,并分析了其中的缺点,最终演化了注解方式,外置配置方式,并简要介绍了实现方法。

具体实现,采用的技术就比较多了,有Maven插件、有Asm、有模板引擎还有Tiny框架的一些基础工程,如:VFS,FileResolver等等。

如果采用Tiny框架,可以直接拿来用,如果不用Tiny框架,可以参照上面的思路做自己的实现。

时间: 2024-09-20 00:23:48

缓存相关代码的演变的相关文章

CI框架中redis缓存相关操作文件示例代码_php实例

本文实例讲述了CI框架中redis缓存相关操作文件.分享给大家供大家参考,具体如下: redis缓存类文件位置: 'ci\system\libraries\Cache\drivers\Cache_redis.php' <?php /** * CodeIgniter * * An open source application development framework for PHP 5.2.4 or newer * * NOTICE OF LICENSE * * Licensed under

Nginx反向代理和前端全缓存相关配置教程

最近一直在研究这方面的配置,只是脚本比较复杂.在网上用力找了几天,主要教程还是那些东西.并且有点乱七八糟,笔者进行了一些整理.告诉你最简单的方法,来实现这二个方面的内容: 1.nginx最简单的反向代理脚本 2.nginx最简单的前端缓存反向代理脚本 3.只提供简单应用,自动更新等操作不讲解 4.最简单的脚本,容易学会到渣 切记:配置完成后,需要刷新nginx配置,以下2条命令都可以: /root/lnmp reload /etc/init.d/nginx reload 一.最简单的反向代理脚本

Android编程之Sdcard相关代码集锦_Android

本文实例讲述了Android编程之Sdcard相关代码.分享给大家供大家参考,具体如下: 1. 检测Sdcard是否可用: public static boolean sdCardIsAvailable() { String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { return false; } return true; } 2. 获得程序在s

一个简单至极的PHP缓存类代码_php技巧

网上关于 PHP 缓存类的资料很多,不过这个类应该是我见过功能满足需求,但又无比简洁的一个.废话不多说,直接看代码吧!使用说明:1.实例化$cache = new Cache(); 2.设置缓存时间和缓存目录$cache = new Cache(60, '/any_other_path/'); 第一个参数是缓存秒数,第二个参数是缓存路径,根据需要配置. 默认情况下,缓存时间是 3600 秒,缓存目录是 cache/3.读取缓存$value = $cache->get('data_key'); 4

asp、html、js 禁止缓存的代码_应用技巧

ASP Response.Buffer = True Response.ExpiresAbsolute = Now() - 1 Response.Expires = 0 Response.CacheControl = "no-cache" Response.AddHeader "Pragma", "No-Cache"   HTML <meta http-equiv="Content-Type" content="

Android编程之Sdcard相关代码集锦

本文实例讲述了Android编程之Sdcard相关代码.分享给大家供大家参考,具体如下: 1. 检测Sdcard是否可用: public static boolean sdCardIsAvailable() { String status = Environment.getExternalStorageState(); if (!status.equals(Environment.MEDIA_MOUNTED)) { return false; } return true; } 2. 获得程序在s

ThinkPHP文件缓存类代码分享

  ThinkPHP文件缓存类代码分享         取自ThinkPHP的文件缓存类代码,这里就不多废话了,小伙伴们自己看注释吧. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63

Android编程之分辨率处理相关代码段合集_Android

本文实例讲述了Android编程之分辨率处理相关代码段.分享给大家供大家参考,具体如下: 1. 通常我们所说的屏幕分辨率如800x480.960x540等.这些分辨率是可以通过代码获取到的.手机屏幕分辨率计算: // 计算手机屏幕分辨率 private void computeDisplayMetrics() { // 手机屏幕分辨率为heightxwidth DisplayMetrics dm = new DisplayMetrics(); this.getWindowManager().ge

patchoat相关代码在Android M版中的变化(1)

patchoat相关代码在Android M版中的变化(1) 在Android L中,patchoat被framework直接调用的情况还是不少的,但是在Android M中,这个命令被取消了.所以虽然功能上没有什么新意,逻辑上还是有点小变化的.下面我们分析一下. Android L上SystemServer的patchoat流程 我们先看看SystemServer的patchoat的过程吧. ZygoteInit.performSystemServerDexOpt 先说SystemServer