`
lvwenwen
  • 浏览: 930836 次
  • 性别: Icon_minigender_1
  • 来自: 魔都
社区版块
存档分类
最新评论

正确读取与设置缓存方法

阅读更多

 http://donlianli.iteye.com/blog/1897642

前言:
代码简洁与性能高效无法两全其美,本文章专注于大并发程序的性能,如果您追求代码简洁,本文章可能不太适合,因为本文章主要讨论如何写出在高并发下也能运行很好的代码。
 

     如果你感觉到缓存的重要性,那么,恭喜你,你的技术水平已经从初级上升了一个层次,意识到性能的重要性。你不再仅限于完成用户提出的功能,而是更注重提高系统的性能和软件的质量了。但是,仅仅在软件中随便加一个memcache或者osCache包就以为能够解决性能问题的话,那你就大错特错了。缓存只是提高性能一小步,提高性能更多是从设计层次来提高,但必有的编程技巧也是解决性能问题的一个主要因素。

本文章主要从如何查询及构建缓存开始,主要参考了Java Concurrency In Practice的一些章节,及网上的一些资料,结合实际的项目,做了一些应用。

首先,你一般设置缓存是否是这样写的呢?

Java代码  收藏代码
  1. //计算缓存的key  
  2. String cacheKey = getCacheKey(param1,param2);  
  3. //查询memcached  
  4. List<Long> list = (List<Long>)memcached.get(cacheKey);  
  5. if(list == null){  
  6.     //memcache 已经失效或者不存在,去查询数据库  
  7.     list = getFromDB(param1,param2);  
  8.     memcached.set(list,5*60);  
  9.     return list;  
  10. }  

 这个方法在并发小的时候,应该不存在问题,但是当是一个高并发的系统时,那么这样的写法可能会导致缓存失效时,向数据库发起多个查询,然后查询完之后,还要向memcache Set多次。为什么,因为在如果同时过来10个请求,都发现缓存中没有数据(list == null),那么就都会去查询数据库,然后直到其中一个最先获得结果的线程,将结果设置到memcache,之后到来的线程,才会走缓存,但已经进来的线程,则还会继续查数据库,然后再将结果设置到memcache,这显然是我们不想看到的。那么如何处理呢,在方法上面加synchronized锁?开销太大。

这时,我们可以看看专家的意见,在设计高效的线程安全的缓存--JCIP5.6读书笔记 中讲了一种方法,可以既可以不使用锁,又保证多个线程同时请求时只有一个线程会访问数据库执行查询,其他线程都只读取计算结果的方法。如果你对里面讲的内容一头雾水的话,那么,你可以看看我写的这个示例,你只需要构建一个自己的Callable类,就能正确的设置与读取缓存。

 

假设concurrentService是一个先读缓存,没有缓存则读取数据库的方法,其代码如下:

Java代码  收藏代码
  1. public List<Long> concurrentService(int para1,int param2){  
  2.     long beginTime = System.nanoTime();  
  3.     final String cacheKey = "IamKey";  
  4.     List<Long> list = (List<Long>)memCachedClient.get(cacheKey);  
  5.     if(list == null){  
  6.         Callable<Object> caller = new Callable<Object>() {  
  7.             public Object call() throws InterruptedException {  
  8.                 System.out.println(" go to dao or rmi");  
  9.                 List<Long> list = new ArrayList<Long>();  
  10.                 list.add(1l);list.add(2l);  
  11.                 //将计算结果缓存  
  12.                 System.out.println("结果计算完毕,存入分布式缓存中");  
  13.                 memCachedClient.set(cacheKey, 5*60, list);  
  14. /                   Thread.sleep(500);  
  15.                 //计算结果,通常是访问数据库或者远程服务  
  16.                 return list;  
  17.             }  
  18.         };  
  19.         List<Long> result = (List<Long>)TaskUtils.getInTask(cacheKey,caller);  
  20.         long end = System.nanoTime();  
  21.         useTimes.add(end-beginTime);  
  22.         return result;  
  23.     }  
  24.     else {  
  25.         System.out.println("1.缓存命中,直接返回");  
  26.         long end = System.nanoTime();  
  27.         useTimes.add(end-beginTime);  
  28.         return list;  
  29.     }  
  30. }  

 

 其中的TaskUtils.getInTask定义如下:

Java代码  收藏代码
  1. import java.util.concurrent.Callable;  
  2. import java.util.concurrent.ConcurrentHashMap;  
  3. import java.util.concurrent.ConcurrentMap;  
  4. import java.util.concurrent.FutureTask;  
  5.   
  6. public class TaskUtils {  
  7.     private static final ConcurrentMap<String, FutureTask<Object>> cache = new ConcurrentHashMap<String, FutureTask<Object>>();  
  8.     public static Object getInTask(String cacheKey, Callable<Object> caller) {  
  9.         System.out.println("1.缓存未命中,将查询数据库或者调用远程服务");  
  10.         //未命中缓存,开始计算  
  11.         FutureTask<Object> f = cache.get(cacheKey);  
  12.         if (f == null) {  
  13.             FutureTask<Object> ft = new FutureTask<Object>(caller);  
  14.             f = cache.putIfAbsent(cacheKey, ft);  
  15.             if (f == null) {  
  16.                 System.out.println("2.任务未命中,将查询数据库或者调用远程服务");  
  17.                 f = ft;  
  18.                 ft.run();  
  19.             }  
  20.         }  
  21.         else {  
  22.             System.out.println("2.任务命中,直接从缓存取结果");  
  23.         }  
  24.         try {  
  25.             Object result = f.get();  
  26.             System.out.println("取回的结果result:"+result);  
  27.             return result;  
  28.         } catch (Exception e) {  
  29.             e.printStackTrace();  
  30.         }  
  31.         finally{  
  32.             //最后将计算任务去掉,虽然已经移除任务对象,但其他线程  
  33.             //仍然能够获取到计算的结果,直到所有引用都失效,被垃圾回收掉  
  34.             boolean success = cache.remove(cacheKey,f);  
  35.             System.out.println(success);  
  36.         }  
  37.         return null;  
  38.     }  
  39. }  

       经过测试,使用这种方法读取与设置缓存,比使用synchronized方法和锁定键值的方法要快3-10倍,不信大家可以试试。

        有人可能有疑问,如果并发的线程很多,同时都没有命中缓存,那么不就会产生很多Callable<Object>对象吗?这样岂不会浪费很大内存吗?其实,我们仔细分析一下代码,可以看到Callable对象不管创建了多少,但最终经过putIfAbsent方法之后,就留下了一个有效的对象,其他的对象都成为失效对象,随时可以被GC掉。因此,使用这种方法,并不会造成JVM的内存溢出。

     另外,Callable<Object>就是一个普通的对象,跟线程一点关系都没有,里面虽然包括了一个runnable方法,但是并不是说这个会启动一个线程。里面的runnable方法在本代码中是在调用者线程中执行,但执行结果共享给了其他没有命中缓存的线程。

        赶紧回去review你们项目的代码吧,你们设置缓存的方式对吗?

        实际上这个TaskUtil的方法只是使用了两个重要的并发工具类,一个是ConcurrentMap,主要支持并发中经常使用的putIfAbsent方法,和一个FutureTask对象,这个对象的get方法能够阻塞调用者线程,直到结果可用。

 

 

对这类话题感兴趣?欢迎发送邮件至donlianli@126.com

 

关于我:邯郸人,擅长Java,Javascript,Extjs,oracle sql。

 

更多我之前的文章,可以访问:http://hi.baidu.com/donlian

分享到:
评论

相关推荐

    用PHP读取文件的正确方法

    PHP不缺读取和解析文件的有效方法。诸如 fread 之类的典型函数可能在大多数时候都是最佳的选择,或者当 readfile 刚好能满足任务需要时,您可能会发现自己更为 readfile 的简单所吸引。它实际上取决于所要完成的操作...

    论文研究-基于内存映射文件技术的海量影像数据快速读取方法.pdf

    随着信息技术和传感器技术的飞速发展,使得遥感图像的数据量呈几何级数的递增,而传统的利用文件指针来读取文件的方法,只能正确读取2GB以下的数据。针对此种情况,提出了新方法,并分析了其关键技术,用VC 给出了...

    C#读写串口数据实现代码

    C#中SerialPort类中...如果有必要修改主 Form 或 Control 中的元素,必须使用 Invoke 回发更改请求,这将在正确的线程上执行.进而要想将辅助线程中所读到的数据显示到主线程的Form控件上时,只有通过Invoke方法来

    fancycache for disk v0.8简体中文破解版.rar

    软件介绍: 安装说明:安装之后将FancyCcD.exe覆盖到C:\Program Files...使用时正确设置缓存粒度及大小,延时写入及启用二级缓存,可以明显提高系统性能。压缩包内附分区版及硬盘版,附有破解文件,覆盖原文件即可。

    应用笔记LAT1266+使用Framebuffer-Analyzer工具调试图像显示

    根据我们的经验,在使用 STM32GUI 开发平台做 GUI 开发过程中, 经常会遇到一些问题, 如 LCD 无法显示、显示闪烁、花屏等问题。...目的是用于协助 GUI 开发工程师,分析帧缓存图形数据在不同数据处理/显示阶段是否正确

    Yourphp_UTF8_v3.0_Released_20141119_Alipay_Alipay_Alipay_Go-Pay_

    并设置全部文件和文件夹为可读取权限,linux系统下都设置为777 注意事项: 1.安装完系统后先要进入后台-&gt;更新缓存. 2.安装为子目录时请不要使用yourphp为子目录名称. 3.安装在子目录下时请先进后台修复栏目数据和...

    2014企业建站模板-宝来网版

    并设置全部文件和文件夹为可读取权限,linux系统下都设置为777 注意事项: 1.安装完系统后先要进入后台-&gt;更新缓存. 2.安装为子目录时请不要使用zrcmphp为子目录名称. 3.安装在子目录下时请先进后台修复栏目数据和...

    read-through-cache:一个简单、一致的磁盘缓存

    读取缓存 一个简单的通读磁盘缓存。 设计目标 不知道缓存的内容。 只要它以可读字节流的形式到达,它就可以缓存。 将尽可能多的请求合并到同一件事上。 直到第一个字节从 ReadStream 流出,它将每个请求合并为一个...

    ccache的mk脚本

    ccache是一个编译器缓存,该工具会高速缓存编译生成的信息,并在编译的特定部分使用高速缓存的信息, 比如头文件,这样就节省了通常使用cpp解析这些信息所需要的时间。如果某头文件中包含对其他头文件的引用,ccache...

    Yourphp企业网站管理系统 v3.0 build 20141119

    并设置全部文件和文件夹为可读取权限,linux系统下都设置为777注意事项:1.安装完系统后先要进入后台-&gt;更新缓存.2.安装为子目录时请不要使用yourphp为子目录名称.3.安装在子目录下时请先进后台修复栏目数据和更新...

    您可能没有权限使用网络资源。请与这台服务器的管理员联系以查明您是否有访问权限。拒绝访问解决方法

    3) 双方都正确设置了网内IP地址,且必须在一个网段中; 4) 双方的计算机中都关闭了防火墙,或者防火墙策略中没有阻止网上邻居访问的策略。 Windows 98/2000/XP/2003访问XP的用户验证问题 首先关于启用Guest为什么...

    数据库设计准则及方法论.docx

    方法三:合理设计存储 方法四:优化数据库参数,减少资源竞争 优化配置数据库的参数,包括各种缓存池的大小,内存区的配置,刷新脏页的策略,锁的策略等。虽然各个数据库都不相同,但是所有的出发点都是为了通过...

    SQL Server 完整备份遇到的一个不常见的错误及解决方法

    在SQL Server中,进行insert, update, delete时,数据并没有直接写入数据库对应的mdf文件中,而是写入了缓存里,这时,就要提到一个非常重要机制:CheckPoint,它主要作用是把缓存中的数据写入mdf文件中。...

    Spi_flash.zip

    ESP32 W25Q128 驱动程序,基于正点原子移植, uint16_t W25QXX_ReadID(void); //读取FLASH ID uint8_t W25QXX_ReadSR(void); //读取状态寄存器S void W25QXX_Write_SR(uint8_t ... //读取缓存中正确的数据的总大小

    Ofstar 2.0 Final 到 phpwind1.3.1的转换程序

    这次直接读取你的ofstar设置文件,所以不需要设置路径。 一定要改成你的用户所在的实际目录。 2.把所有php文件上传到服务器ofstar论坛安装目录下。 3.运行转换程序。 此时运行...

    微信小程序-微信小程序之24点计算游戏

    微信小程序之24点计算游戏 这是我第一次尝试用小程序开发的小游戏。...本次尝试用到了小程序的数据缓存存储、读取以及用户信息读取等API;用到各种常用组件,数据存储、读取、判断、循环等知识点。

    workers-asset-cache:Cloudflare Workers脚本可以代理和缓存来自Workers KV上外部来源的静态内容

    工人资产缓存 Cloudflare Workers脚本可以... 有关基本原理和设置的更多信息,请参见博客文章。 使用Workers环境变量SERVICE作为URL,并使用正确的CloudFlare帐户,区域ID和路由更新wrangler.toml。 示例:SERVICE |

    店盟淘宝客程序V12.1.rar

    1.优化了缓存读取方式,尽量避免读写冲突。 2.修改缓存机制,缓存多加一层目录分布,确保每个目录文件数量不会过多,目前应该每个目录缓存文件可以控制在一百以内。这样可以增加读取效率,减少CPU消耗。 3.修正...

Global site tag (gtag.js) - Google Analytics