`
donlianli
  • 浏览: 340436 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Group-logo
Elasticsearch...
浏览量:218563
社区版块
存档分类
最新评论

正确读取与设置缓存方法

阅读更多

 

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

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

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

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

//计算缓存的key
String cacheKey = getCacheKey(param1,param2);
//查询memcached
List<Long> list = (List<Long>)memcached.get(cacheKey);
if(list == null){
	//memcache 已经失效或者不存在,去查询数据库
	list = getFromDB(param1,param2);
	memcached.set(list,5*60);
	return list;
}

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

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

 

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

	public List<Long> concurrentService(int para1,int param2){
		long beginTime = System.nanoTime();
		final String cacheKey = "IamKey";
		List<Long> list = (List<Long>)memCachedClient.get(cacheKey);
		if(list == null){
			Callable<Object> caller = new Callable<Object>() {
				public Object call() throws InterruptedException {
					System.out.println(" go to dao or rmi");
					List<Long> list = new ArrayList<Long>();
					list.add(1l);list.add(2l);
					//将计算结果缓存
					System.out.println("结果计算完毕,存入分布式缓存中");
					memCachedClient.set(cacheKey, 5*60, list);
//					Thread.sleep(500);
					//计算结果,通常是访问数据库或者远程服务
					return list;
				}
			};
			List<Long> result = (List<Long>)TaskUtils.getInTask(cacheKey,caller);
			long end = System.nanoTime();
			useTimes.add(end-beginTime);
			return result;
		}
		else {
			System.out.println("1.缓存命中,直接返回");
			long end = System.nanoTime();
			useTimes.add(end-beginTime);
			return list;
		}
	}

 

 其中的TaskUtils.getInTask定义如下:

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.FutureTask;

public class TaskUtils {
	private static final ConcurrentMap<String, FutureTask<Object>> cache = new ConcurrentHashMap<String, FutureTask<Object>>();
	public static Object getInTask(String cacheKey, Callable<Object> caller) {
		System.out.println("1.缓存未命中,将查询数据库或者调用远程服务");
		//未命中缓存,开始计算
		FutureTask<Object> f = cache.get(cacheKey);
		if (f == null) {
			FutureTask<Object> ft = new FutureTask<Object>(caller);
			f = cache.putIfAbsent(cacheKey, ft);
			if (f == null) {
				System.out.println("2.任务未命中,将查询数据库或者调用远程服务");
				f = ft;
				ft.run();
			}
		}
		else {
			System.out.println("2.任务命中,直接从缓存取结果");
		}
		try {
			Object result = f.get();
			System.out.println("取回的结果result:"+result);
			return result;
		} catch (Exception e) {
			e.printStackTrace();
		}
		finally{
			//最后将计算任务去掉,虽然已经移除任务对象,但其他线程
			//仍然能够获取到计算的结果,直到所有引用都失效,被垃圾回收掉
			boolean success = cache.remove(cacheKey,f);
			System.out.println(success);
		}
		return null;
	}
}

       经过测试,使用这种方法读取与设置缓存,比使用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

 

1
1
分享到:
评论
18 楼 dzxiang 2013-07-08  
dzxiang 写道
我把楼主的原始代码稍微改了一下,想和大家探讨一下:如果用以下方法读取设置缓存是否可行?
		......
		private ReentrantLock lock = new ReentrantLock();
		......

		//计算缓存的key  
		String cacheKey = getCacheKey(param1,param2);  
		//查询memcached  
		List<Long> list = (List<Long>)memcached.get(cacheKey);  
		if(list == null){
			lock.lock();
			try{
				if(list == null){	
			        //memcache 已经失效或者不存在,去查询数据库  
			        list = getFromDB(param1,param2);  
			        memcached.set(list,5*60);  
				}
		    }finaly{
		    	lock.unlock();
		    }				
		}  
		    
		return list;  

====
再修改一下:
...... 
private static final ReentrantLock lock = new ReentrantLock(); 
......
17 楼 dzxiang 2013-07-08  
我把楼主的原始代码稍微改了一下,想和大家探讨一下:如果用以下方法读取设置缓存是否可行?
		......
		private ReentrantLock lock = new ReentrantLock();
		......

		//计算缓存的key  
		String cacheKey = getCacheKey(param1,param2);  
		//查询memcached  
		List<Long> list = (List<Long>)memcached.get(cacheKey);  
		if(list == null){
			lock.lock();
			try{
				if(list == null){	
			        //memcache 已经失效或者不存在,去查询数据库  
			        list = getFromDB(param1,param2);  
			        memcached.set(list,5*60);  
				}
		    }finaly{
		    	lock.unlock();
		    }				
		}  
		    
		return list;  
16 楼 evanzzy 2013-07-05  
406657836 写道
evanzzy 写道
406657836 写道
evanzzy 写道
406657836 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。


缓存特大规模不命中,那是什么情况?那是缓存服务器挂了,上百台分布式缓存一块儿挂了,这事儿出现的几率几乎没有。在京东我目前还没听说过有这种问题出现。他们要用CDN的,大量前端数据块都在CDN里面,这个写法用不上。我倒不是说这种写法没有用,而是怀疑它的使用场景,什么地方用上了这种写法。我们可以简单计算一下这个使用场景:如果10个并发同时提向服务器需要这么做,那么算缓存命中率90%,那也就是所有需要查询数据库的请求的10%(不是所有PV的10%),按照并发占有所有查询数据库请求的1%计算,那么1秒钟之内,就有1000个提交直接查询数据库的请求(不包括增删改),10000个包括缓存命中的查询请求。每天光数据库查询请求最少就有4、5个亿,这个网站每天的pv根据经验计算要几十个亿!

亲爱的,你做什么网站能有每天几十个亿的PV,看看alex上的统计,这个网站世界上只有一个——Google


没看懂你 最后这些数据是怎么出来的。并发量是一方面,还有一方面,如果在同样并发的情况下,优化的程序10台机器可以支撑,不优化的程序需要12台或更多,你选择那个呢?不是pv多才有必要优化的!
这个应用场景太多了,在高并发的数据缓存中都很适用。cdn是内容缓存和楼主说这个数据缓存不是一个层次的。cdn确实可以缓存静态内容的压力,那动态数据呢?还是得你自己的服务器来支撑。良好的缓存设计更能避开性能瓶颈。


把具体压力数值算出来,才能确定用什么解决方案,脱离需求谈解决方案没用。这跟性能高低没关系,而是说的这种方案是高性能解决方案,但是它的应用场景远远超过了我们实际中遇到的要求,所以我算这些数据,目的就是说明普通几百万PV的网站,甚至上千万PV的网站,根本就用不到这种解决方案。


真用的着,日均千万的网站,还是压力大了。当然要看你什么业务。这样做减少不必要的db查询,而且也不复杂,为什么不呢?


日均千万PV,你算算每秒多少,这其中能查询数据库的有多少,再其中不能命中缓存的有多少,还其中存在并发的有多少?算下来你就能明白我为什么说用不着这么NB的解决方案,你要拿出具体数据来做分析,架构师就是干这个的,你不能光说压力大,要有分析报告作支持的。
15 楼 donlianli 2013-07-05  
1楼2楼的兄弟,请别再争论了,我提供的这个方法其实是Java Concurrency in Practice里面推荐的方法,我本人并发原创,具体是否有必要使用这方法,得看你的网站是否存在性能问题。如果你的网站基本上没有负载,说明你的DB比较厉害或者你系统的设计比较NB,如果说你的Leader需要你尽量降低对DB的访问,采用这种方法是很有必要的。
14 楼 406657836 2013-07-05  
evanzzy 写道
406657836 写道
evanzzy 写道
406657836 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。


缓存特大规模不命中,那是什么情况?那是缓存服务器挂了,上百台分布式缓存一块儿挂了,这事儿出现的几率几乎没有。在京东我目前还没听说过有这种问题出现。他们要用CDN的,大量前端数据块都在CDN里面,这个写法用不上。我倒不是说这种写法没有用,而是怀疑它的使用场景,什么地方用上了这种写法。我们可以简单计算一下这个使用场景:如果10个并发同时提向服务器需要这么做,那么算缓存命中率90%,那也就是所有需要查询数据库的请求的10%(不是所有PV的10%),按照并发占有所有查询数据库请求的1%计算,那么1秒钟之内,就有1000个提交直接查询数据库的请求(不包括增删改),10000个包括缓存命中的查询请求。每天光数据库查询请求最少就有4、5个亿,这个网站每天的pv根据经验计算要几十个亿!

亲爱的,你做什么网站能有每天几十个亿的PV,看看alex上的统计,这个网站世界上只有一个——Google


没看懂你 最后这些数据是怎么出来的。并发量是一方面,还有一方面,如果在同样并发的情况下,优化的程序10台机器可以支撑,不优化的程序需要12台或更多,你选择那个呢?不是pv多才有必要优化的!
这个应用场景太多了,在高并发的数据缓存中都很适用。cdn是内容缓存和楼主说这个数据缓存不是一个层次的。cdn确实可以缓存静态内容的压力,那动态数据呢?还是得你自己的服务器来支撑。良好的缓存设计更能避开性能瓶颈。


把具体压力数值算出来,才能确定用什么解决方案,脱离需求谈解决方案没用。这跟性能高低没关系,而是说的这种方案是高性能解决方案,但是它的应用场景远远超过了我们实际中遇到的要求,所以我算这些数据,目的就是说明普通几百万PV的网站,甚至上千万PV的网站,根本就用不到这种解决方案。


真用的着,日均千万的网站,还是压力大了。当然要看你什么业务。这样做减少不必要的db查询,而且也不复杂,为什么不呢?
13 楼 evanzzy 2013-07-05  
406657836 写道
evanzzy 写道
406657836 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。


缓存特大规模不命中,那是什么情况?那是缓存服务器挂了,上百台分布式缓存一块儿挂了,这事儿出现的几率几乎没有。在京东我目前还没听说过有这种问题出现。他们要用CDN的,大量前端数据块都在CDN里面,这个写法用不上。我倒不是说这种写法没有用,而是怀疑它的使用场景,什么地方用上了这种写法。我们可以简单计算一下这个使用场景:如果10个并发同时提向服务器需要这么做,那么算缓存命中率90%,那也就是所有需要查询数据库的请求的10%(不是所有PV的10%),按照并发占有所有查询数据库请求的1%计算,那么1秒钟之内,就有1000个提交直接查询数据库的请求(不包括增删改),10000个包括缓存命中的查询请求。每天光数据库查询请求最少就有4、5个亿,这个网站每天的pv根据经验计算要几十个亿!

亲爱的,你做什么网站能有每天几十个亿的PV,看看alex上的统计,这个网站世界上只有一个——Google


没看懂你 最后这些数据是怎么出来的。并发量是一方面,还有一方面,如果在同样并发的情况下,优化的程序10台机器可以支撑,不优化的程序需要12台或更多,你选择那个呢?不是pv多才有必要优化的!
这个应用场景太多了,在高并发的数据缓存中都很适用。cdn是内容缓存和楼主说这个数据缓存不是一个层次的。cdn确实可以缓存静态内容的压力,那动态数据呢?还是得你自己的服务器来支撑。良好的缓存设计更能避开性能瓶颈。


把具体压力数值算出来,才能确定用什么解决方案,脱离需求谈解决方案没用。这跟性能高低没关系,而是说的这种方案是高性能解决方案,但是它的应用场景远远超过了我们实际中遇到的要求,所以我算这些数据,目的就是说明普通几百万PV的网站,甚至上千万PV的网站,根本就用不到这种解决方案。
12 楼 406657836 2013-07-05  
evanzzy 写道
406657836 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。


缓存特大规模不命中,那是什么情况?那是缓存服务器挂了,上百台分布式缓存一块儿挂了,这事儿出现的几率几乎没有。在京东我目前还没听说过有这种问题出现。他们要用CDN的,大量前端数据块都在CDN里面,这个写法用不上。我倒不是说这种写法没有用,而是怀疑它的使用场景,什么地方用上了这种写法。我们可以简单计算一下这个使用场景:如果10个并发同时提向服务器需要这么做,那么算缓存命中率90%,那也就是所有需要查询数据库的请求的10%(不是所有PV的10%),按照并发占有所有查询数据库请求的1%计算,那么1秒钟之内,就有1000个提交直接查询数据库的请求(不包括增删改),10000个包括缓存命中的查询请求。每天光数据库查询请求最少就有4、5个亿,这个网站每天的pv根据经验计算要几十个亿!

亲爱的,你做什么网站能有每天几十个亿的PV,看看alex上的统计,这个网站世界上只有一个——Google


没看懂你 最后这些数据是怎么出来的。并发量是一方面,还有一方面,如果在同样并发的情况下,优化的程序10台机器可以支撑,不优化的程序需要12台或更多,你选择那个呢?不是pv多才有必要优化的!
这个应用场景太多了,在高并发的数据缓存中都很适用。cdn是内容缓存和楼主说这个数据缓存不是一个层次的。cdn确实可以缓存静态内容的压力,那动态数据呢?还是得你自己的服务器来支撑。良好的缓存设计更能避开性能瓶颈。
11 楼 evanzzy 2013-07-05  
406657836 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。


缓存特大规模不命中,那是什么情况?那是缓存服务器挂了,上百台分布式缓存一块儿挂了,这事儿出现的几率几乎没有。在京东我目前还没听说过有这种问题出现。他们要用CDN的,大量前端数据块都在CDN里面,这个写法用不上。我倒不是说这种写法没有用,而是怀疑它的使用场景,什么地方用上了这种写法。我们可以简单计算一下这个使用场景:如果10个并发同时提向服务器需要这么做,那么算缓存命中率90%,那也就是所有需要查询数据库的请求的10%(不是所有PV的10%),按照并发占有所有查询数据库请求的1%计算,那么1秒钟之内,就有1000个提交直接查询数据库的请求(不包括增删改),10000个包括缓存命中的查询请求。每天光数据库查询请求最少就有4、5个亿,这个网站每天的pv根据经验计算要几十个亿!

亲爱的,你做什么网站能有每天几十个亿的PV,看看alex上的统计,这个网站世界上只有一个——Google
10 楼 406657836 2013-07-04  
evanzzy 写道
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。



哥们 认真看下程序,在大规模缓存不命中的时候这样更有优势,请注意代码低18行,ft.run(); 他是回调给当前线程去重建缓存的,这招相当英明,不会额外启动线程。所以这样做是非常高效的。

你要说特大规模不命中,那么你的缓存肯定本身就存在热点故障。而且,不用这种方式会挂的更快,因为会带来大量的db查询(db挂),或者锁竞争(连接数太多 web server 挂),所以这么做真的是上策。
9 楼 mmBlue 2013-07-04  
mark一下,的确很高明,改天再来。
8 楼 406657836 2013-07-04  
ft.run(); 是调用的当前线程。这样的设计确实很高明,之前理解错了,以为每次不命中都去开一个线程。调用当前线程的情况下,即使大面积不命中也比直接设置缓存高效。确实是一个好方案。
7 楼 evanzzy 2013-07-04  
donlianli 写道
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。


我不是互联网行业的,但是京东我很熟悉,相当熟悉。
淘宝京东确实需要这么干,他们量很大。如果考虑到缓存失效的问题,这么写其实没必要了。因为你用在访问量很大的互联网站上,几乎所有服务器都在运维监管范围以内的,而且缓存都是分布式部署的,不可能一起都失效的。
6 楼 donlianli 2013-07-03  
evanzzy 写道
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?


这种技术只是对性能的优化,防止缓存失效时,大量查询同时打到数据库,对其他业务造成影响。不知道你是从事是不是互联网行业,一般的任何一个互联网系统,像淘宝,京东,并发访问量是非常之大的,任何系统都需要为性能让道。
5 楼 evanzzy 2013-07-03  
donlianli 写道
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。


我说的是对象,我没说起线程啊。另外需要用到你的这个并发缓存技术,大概要多大访问量的系统才有必要使用呢?
4 楼 donlianli 2013-07-03  
evanzzy 写道
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。


首先,Callable<Object> 并不会产生一个线程,他就好比你是new 了一个对象,然后调用了对象的一个方法,他是在当前线程执行的,不是创建了一个线程来执行的。
另外,声明一下,这个案例是从Java Concurrency in Practice的5.6章的例子中延伸出来的一个应用,既然是并发编程,我们就要严格的对待并发,这样才让程序运行在正确的状态。而不是说只做到90%命中就行了。
3 楼 donlianli 2013-07-03  
一二楼的童鞋,你在哪儿看到起线程了?Callable<Object>这个对象不会起线程的。有疑问的话,可以好好看看Java并发编程实战。ConcurrentHashMap这个map只是一个暂存的容器,每次设置完缓存就会把里面的对象remove调的,所以里面不会有太多对象的。
2 楼 406657836 2013-07-03  
嗯,在缓存大面积不命中的时候你得起多少线程啊,至少也得放在线程池中,还要控制线程池队列满的情况。
还有ConcurrentHashMap 的容量限制。特别是遇到热点故障的时候 估计直接就OOM了!
1 楼 evanzzy 2013-07-03  
哥们儿,这招儿行么?虽然降低了并发更新缓存,但是创造了好几个多线程对象,代码也长了好多,虚拟机内存开销也变大了啊。

其实我倒是建议还按照第一种比较简单的方式写缓存读取代码,只不过把memcache进行集群,按照权值到不同的memcache点上去取缓存数据。

真正需要用到你这个方法的系统,那得多大并发啊?因为如果缓存命中率能有90%的话,剩下那10%能达到通过缓存并发查询数据库并造成实际压力的系统,并发恐怕要相当相当的高了。

相关推荐

    电脑知识\让系统快步如飞正确设置Windows缓存

    ### 电脑知识:让系统快步如飞——正确设置Windows缓存 在计算机科学领域,缓存技术是一种广泛采用的方法,用于提高数据访问速度并减少系统延迟。通过将频繁访问的数据存储在更快的存储介质中(通常是内存),可以...

    SAS缓存-关闭工具(服务器磁盘读取慢可以用此工具)

    1. 缓存策略不当:如果缓存策略设置不合理,如写缓存策略设置为“回写”模式,而在断电或异常关机时,未写入的数据可能会丢失,导致系统性能下降。 2. 缓存容量不足:如果缓存大小不足以应对大量并发的I/O请求,缓存...

    IE缓存读取

    **读取IE缓存的方法** 1. **手动查看**:用户可以在IE浏览器的“工具”菜单中选择“Internet选项”,在“常规”选项卡下的“浏览历史”部分点击“设置”来查看和管理缓存。 2. **编程访问**:开发者可以通过编程方式...

    C#读取web.config配置,建立高速缓存机制

    此外,缓存的生命周期在IIS默认设置下随着应用程序重启而结束,可以通过IIS管理工具或编程方式配置缓存的持久化。 在实际开发中,应考虑到缓存的大小限制,因为应用程序域中的内存资源有限。如果需要缓存较大的数据...

    基于Retrofit2okhttp3的数据缓存cache技术修改了缓存时间能够设置缓存时间了.rar

    此外,还需要确保在无网络连接时,能够正确处理缓存数据的读取。 通过以上步骤,你可以在你的Android应用中实现基于Retrofit2和OkHttp3的数据缓存,并根据需要调整缓存时间。这不仅提高了应用的性能,还能在离线...

    Android13 14系统 app获取第三方应用缓存的方法

    总结来说,虽然Android 13和14对第三方应用的缓存访问增加了难度,但通过正确申请权限、使用新的API以及遵循隐私规定,还是可以实现这一功能的。在开发过程中,务必谨慎处理数据,尊重用户隐私,确保应用的合法性与...

    SC超级缓存设置教程和工具

    本教程将详细解释SC超级缓存的配置方法,并提供相应的工具来帮助你更好地管理和利用这个功能。 一、SC超级缓存的基本原理 SC超级缓存的核心是将频繁访问的数据存储在高速缓存中,当用户或系统需要这些数据时,可以...

    SC超级缓存设置和工具.rar

    SC超级缓存是一种高效的数据存储...通过正确设置和使用SC超级缓存,不仅可以减少硬件损耗,还可以显著提升服务质量和用户体验。因此,深入理解和掌握SC超级缓存的原理和配置技巧,对于任何IT管理员来说都是至关重要的。

    geoserver配置图层缓存

    这样,在后续的请求中,Geoserver可以直接读取这些缓存的图像,而不是每次都要实时计算和渲染,从而显著提高了服务的响应速度和效率。 二、配置图层缓存的步骤 1. 登录Geoserver管理界面:首先,你需要通过浏览器...

    Xpath读取xml文件,实现文件缓存。

    9. **测试与调试**: 对于这样的功能,编写单元测试是非常重要的,可以确保XPath表达式的正确性和缓存机制的稳定性。可以使用诸如JUnit(Java)、pytest(Python)或Mocha/Chai(JavaScript)等测试框架进行测试。 ...

    HP 服务器高速缓存的设置

    正确的高速缓存设置能够有效提高数据读写速度,从而改善整体服务器性能。当然,在实际操作过程中还需要注意各种细节,确保设置正确无误。希望本文能为需要对HP服务器进行高速缓存设置的用户提供帮助。

    两次请求相同的一个URL,会产生缓存问题。

    4. **服务器未正确设置缓存指令**:若服务器未正确设置缓存控制头,可能导致浏览器无法正确判断何时应获取新资源。 在开发过程中,开发者需要理解和正确处理这些问题,以确保用户体验的一致性和数据的准确性。工具...

    Android文件缓存与内存缓存

    当用户请求数据时,系统首先查找内存缓存,如果找到则直接返回,否则再从文件缓存中读取,若仍未找到,则从网络或其他源获取,同时将数据存入内存和文件缓存,为后续请求提供服务。 在Android中,对于ListView加载...

    cpu二级缓存设置

    另一种开启CPU二级缓存的方法是在BIOS中进行设置。这种方法适用于大多数计算机,尤其是那些允许用户自定义硬件设置的计算机。 **步骤1**:重启电脑,并在开机过程中按下DEL键(或根据屏幕提示的其他按键),进入...

    java文件读取方法.doc

    在进行文件读取时,需要注意错误处理,如在给定的代码中,使用了`try-catch-finally`块确保资源的正确关闭。此外,为了提高性能,通常会使用缓冲技术,如`BufferedReader`和`BufferedInputStream`,它们可以在内部...

    ssd缓存软件服务器设置缓存的最佳选择

    综上所述,正确设置和使用SSD缓存软件能显著提升服务器性能,但需谨慎选择软件、合理配置参数,并保持对系统状态的密切关注。通过这些方法,企业可以充分发挥SSD的潜力,为业务提供更高效、可靠的存储解决方案。

    Cache 缓存数据和删除缓存的简单示例

    例如,在一个ASP.NET MVC的控制器方法中,可以使用`[OutputCache(Duration = 60)]`注解来设置60秒的缓存期。 Data Cache则主要用于存储应用程序级别的数据,例如数据库查询结果。开发者可以使用`System.Web.Caching...

    php页面缓存方法小结

    该函数通常在输出缓冲结束前被调用,以确保页面内容被正确地写入缓存文件。 这些缓存方法可以显著减少数据库查询次数,并降低服务器负载,尤其适合于对性能要求较高的Web应用。需要注意的是,缓存机制在提高性能的...

Global site tag (gtag.js) - Google Analytics