[Java 缓存] Java Cache之 DCache的简单应用.
前言
上次总结了下本地缓存Guava Cache的简单应用, 这次来继续说下项目中使用的DCache的简单使用.
这里分为几部分进行总结, 1)DCache介绍; 2)DCache配置及使用; 3)使用实例.
1, Dcache 介绍
- Dcache是Distribute Cache System的缩写,既分布式缓存系统。具有高性能,大容量,弹性扩容,服务隔离等特点。
- Dcache是一个高速缓存系统,单机测试qps可达到10000以上。可无限扩容,目前配置最高可达8T容量。不同服务间数据隔离保证安全性。
- Dcache目前支持的底层的实现包括memcache/redis。
- Dcache尽量保证数据的稳定存储。但在底层的cache出现问题重启,或是Dcache全部内存写满等情况会导致数据丢失。
- Dcache适用场景为key量较大,可以均匀分片,单个value大小不大的情况。如:用户的jupiter特征;社区话题的内容;用户最近一小时看到的广告等。不适用于key很少,单value很大的情况。
Dcache 模块介绍
- Dcache Client是一个java sdk,需要要调用的系统依赖Dcache Client并进行缓存数据的get, set, incr操作
- Raw Cache Client是Dcache Client的底层实现,可以是memcache client,可以是redis template,可以是其它no sql client。
- Dcache Server管理众多的Raw Cache Client。
对于一个实际的get/set请求,会先根据key进行分片,决定使用具体的Raw Cache Client,再调用其进行实际的get/set操作。 - Dcache Server是一个web服务,地址为xxxx。提供两个功能,一是提供Dcache Client需要的配置信息,包括Raw Cache Client的地址,端口等信息,支持读写的属性信息,写数据token等。二是提供Dcache Client的访问统计打点接口,记录Dcache Client的成功或失败的读写数目及大小。
Dcache 性能测试
底层使用4台1G的Memcache,单台4核32G公用测试机测试Dcache性能如下。(最终瓶颈为测试机的cpu) 考虑到单台Memcache的内存越大,单台支持qps成倍数增长,所以远没有达到Dcache的性能上限,仅供参考。
| 方法 | 并发线程数 | value字节数 | qps |
| ------| ---------- |-------------|-----|
| GET | 10 | 32 | 6400 |
| GET | 50 | 32 | 11000 |
| GET | 50 | 1k | 10000 |
| GET | 50 | 10k | 7600 |
| SET | 10 | 1k | 6000 |
| SET | 10 | 10k | 4700 |
| SET | 50 | 1k | 11000 |
| SET | 50 | 10k | 6200 |
2, DCache配置及使用
这里因为我们公司内部给配置好了DCache服务器, 所以我们需要直接接入使用.
使用方式很简单, 申请一个前缀和属性名
- DcacheClientKey
|参数 |类型| 备注|
|-------|----|-------|
|prefix| String| 属性前缀,如'a'|
|property| String |属性名称|
|key |String |具体的key,不建议太长,可以是md5或是appuser,贴子id等.
这里给出配置后台页面的截图:
这里都是自己申请前缀和属性名, 然后设置最大缓存时间和单个Key的最大value大小.
DCache中的使用配置
dcache.env=TEST
dcache.group=0
dcache.write.token=test_token
参数解读:
|参数 |取值| 备注|
|-------|----|-------|
|env| PRODUCTION, TEST, STAGING, DEV| 不要在测试时指向线上的配置|
|group| 集群组别, 后台管理系统配置的, 一般有Redis和Memcache两种 |
|token |写数据要求的token |token正确才能对属性进行写,否则只能读。token和项目绑定,需申请
dcache-client使用方式:
<!-- spring注入方式,在application.xml里配置,通过FactoryBean方式生成DcacheClient。-->
<bean id="dcacheFactoryBean" class="cn.mucang.dcache.client.factory.DcacheClientFactory" >
<property name="env">
<value>${dcache.env}</value>
</property>
<property name="group">
<value>${dcache.group}</value>
</property>
<property name="token">
<value>${dcache.write.token}</value>
</property>
</bean>
//代码中使用自动注入即可:
//注意不要在测试时指向线上的配置,会造成数据污染!!!
@Autowired
private DcacheClient dcacheClient;
//代码直接生成DcacheClient的方式,注意不要build多个dcacheClient对象
DcacheClient dcacheClient = new DcacheClientBuilder().setEnv(env)
.setGroup(group)
.setToken(token).build();
DCache方法详解
/**
* 设置缓存里的值
*
* @param key
* @param value 键值
* @param cacheSecond 缓存时间,要求正数
* @return 如果set成功返回true,否则返回false
*/
boolean set(DcacheClientKey key, String value, int cacheSecond);
/**
* 设置缓存里的值
*
* @param key
* @param value 键值
* @return 如果set成功返回true,否则返回false, 缓存时间为server设置的最大缓存时间
*/
boolean set(DcacheClientKey key, String value);
/**
* 设置缓存里的值,失败则抛出exception
*
* @param key
* @param value 键值
* @return 如果set成功返回true,否则返回false, 缓存时间为server设置的最大缓存时间
*/
boolean setOrException(DcacheClientKey key, String value) throws DcacheException;
/**
* 设置缓存里的值,失败则抛出exception
* @param key
* @param value
* @param cacheSecond
* @return
* @throws DcacheException
*/
boolean setOrException(DcacheClientKey key, String value, int cacheSecond) throws DcacheException;
/**
* 增加缓存里的值,如果在cache里没有此key,会设为num并返回num
*
* @param key
* @param num 要增加的值
* @param cacheSecond 缓存时间,要求正数
* @return 增加后的值,失败返回-1
*/
long incr(DcacheClientKey key, long num, int cacheSecond);
/**
* 增加缓存里的值,如果在cache里没有此key,会设为num并返回num
*
* @param key
* @param num 要增加的值
* @return 增加后的值,失败返回-1,缓存时间是server设置的最大缓存时间
*/
long incr(DcacheClientKey key, long num);
/**
* 增加缓存里的值,如果在cache里没有此key,会设为num并返回num。如果有异常抛出exception
*
* @param key
* @param num 要增加的值
* @return 增加后的值,失败返回-1,缓存时间是server设置的最大缓存时间
*/
long incrOrException(DcacheClientKey key, long num) throws DcacheException;
/**
* 增加缓存里的值,如果在cache里没有此key,会设为num并返回num。如果有异常抛出exception
* @param key
* @param num
* @param cacheSecond
* @return
* @throws DcacheException
*/
long incrOrException(DcacheClientKey key, long num, int cacheSecond) throws DcacheException;
/**
* 得到缓存里的值
*
* @param key
* @return 缓存的值,如有异常或值不存在返回null
*/
String get(DcacheClientKey key);
/**
* 得到缓存里的值,如果请求异常则抛出exception
*/
String getOrException(DcacheClientKey key) throws DcacheException;
/**
* 得到一组缓存里的值
*
* @param keyList
* @return map, key是请求,value是缓存里的值。如果请求不合法或是在缓存里不存在的值不会出现在map里
*/
Map<DcacheClientKey, String> batchGet(List<DcacheClientKey> keyList);
/**
* 得到一组缓存里的值
*
* @param keyList
* @param timeoutMills 每个key请求的超时时间(ms)
* @return map, key是请求,value是缓存里的值。如果请求不合法或是在缓存里不存在的值不会出现在map里
*/
Map<DcacheClientKey, String> batchGet(List<DcacheClientKey> keyList, long timeoutMills);
/**
* 删除一个key及对应的value
*
* @param key
* @return
*/
boolean remove(DcacheClientKey key);
/**
* 删除一个key及对应的value,如果失败返回异常
* @param key
* @return
* @throws DcacheException
*/
boolean removeOrException(DcacheClientKey key) throws DcacheException;
/**
* 得到一个属性下的全部的keys.
* MEMCACHE类型的DcacheClient不支持本方法
*
* @param property
* @return
*/
List<DcacheClientKey> keys(DcacheClientPrefixProperty property);
/**
* 得到一个属性的key的cursor,支持对key的遍历
* MEMCACHE类型的DcacheClient不支持本方法
*
* @param property
* @return
*/
DcacheCursor scan(DcacheClientPrefixProperty property); //使用方法和iterator一致,如下
while(cursor.hasNext()) {
DcacheClientKey key = cursor.next();
}
看到上面所有方法, 大家可以看出DCache对于Multiple Get支持的不是很好..
3, 使用实例
现在我自己所维护的一个小系统中主要使用了两种缓存, 第一个是GuavaCache, 第二个是DCache.
这里有只使用GuavaCache, 有只使用DCache, 也有GuavaCache和DCache组成二级缓存使用的. 这里会用实例说明后两种的使用.
- 首先申请项目前缀和属性名称, 以及配置缓存大小和缓存时间.
配置文件:
在本地环境配置:(说明: 这个0和2都是对应后台管理系统中配置的Redis, 其中0是使用本地Redis, 2是部署在aliyun上的.)dcache.env=TEST dcache.group=0 dcache.write.token=test_token
线上环境配置:
dcache.env=PRODUCTION dcache.group=2 dcache.write.token=xxxxx
Spring配置文件中引入DCacheClient
<description>DCache配置文件</description> <bean id="dcacheFactoryBean" class="cn.mucang.dcache.client.factory.DcacheClientFactory" > <property name="env"> <value>${dcache.env}</value> </property> <property name="group"> <value>${dcache.group}</value> </property> <property name="token"> <value>${dcache.write.token}</value> </property> </bean>
自己写一个DCacheClient包装类(这里只是封装了DCacheClient中的方法)
@Service public class DCacheManagerService { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 默认的前缀 */ protected String prefix = "c"; /** * 默认的属性 */ protected String property = "cartype-extend"; @Autowired protected DcacheClient dcacheClient; public <T> T get(String rawKey, Type type) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); String result = dcacheClient.get(dcacheClientKey); return JSON.parseObject(result, type); } public <T> T get(String rawKey, Class<T> clazz) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); String result = dcacheClient.get(dcacheClientKey); return JSON.parseObject(result, clazz); } public <T> T get(String rawKey, Callable<T> callable) { return get(rawKey, callable, -1); } public <T> T get(String rawKey, Callable<T> callable, int cacheSecond) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); return get(dcacheClientKey, callable, cacheSecond); } public <T> T get(DcacheClientKey clientKey, Callable<T> callable) { return get(clientKey, callable, -1); } public <T> T get(DcacheClientKey clientKey, Callable<T> callable, int cacheSecond) { Preconditions.checkNotNull(callable, "callable is null"); String result = dcacheClient.get(clientKey); if (result == null) { try { T data = callable.call(); if (cacheSecond == -1) { dcacheClient.set(clientKey, JSON.toJSONString(data)); } else { dcacheClient.set(clientKey, JSON.toJSONString(data), cacheSecond); } return data; } catch (Exception e) { e.printStackTrace(); } } final Type type = ((ParameterizedType) callable.getClass().getGenericInterfaces()[0]).getActualTypeArguments()[0]; try { return JSON.parseObject(result, type); } catch (Exception e) { logger.info("Json转换异常: {}, {}", result, e); throw e; } } public <K, V> Map<K, V> batchGet(List<DcacheClientKey> clientKeys, List<K> mapKey, Type mapType) { Map<DcacheClientKey, String> maps = dcacheClient.batchGet(clientKeys); Map<K, V> result = Maps.newLinkedHashMap(); for (int i = 0; i < clientKeys.size(); i++) { V value = JSON.parseObject(maps.get(clientKeys.get(i)), mapType); result.put(mapKey.get(i), value); } return result; } public <K, V> Map<K, V> batchGet(String prefixKey, List rowKeys, List<K> mapKey, Type mapType) { List<DcacheClientKey> clientKeys = Lists.newArrayList(); for (Object rowKey : rowKeys) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, prefixKey + rowKey); clientKeys.add(dcacheClientKey); } return batchGet(clientKeys, mapKey, mapType); } public boolean set(String rawKey, String value) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); return set(dcacheClientKey, value); } public boolean set(String rawKey, String value, int cacheSecond) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); return set(dcacheClientKey, value, cacheSecond); } public boolean set(DcacheClientKey clientKey, String value) { return set(clientKey, value, -1); } public boolean set(DcacheClientKey clientKey, String value, int cacheSecond) { if (cacheSecond != -1) { return dcacheClient.set(clientKey, value, cacheSecond); } else { return dcacheClient.set(clientKey, value); } } public boolean remove(String rawKey) { DcacheClientKey dcacheClientKey = new DcacheClientKey(prefix, property, rawKey); return dcacheClient.remove(dcacheClientKey); } public List<DcacheClientKey> keys(DcacheClientPrefixProperty property) { return dcacheClient.keys(property); } /** * 构建cache key */ protected String buildCacheRawKey(String prefix, Object... keys) { StringBuilder rawKeyBuilder = new StringBuilder(prefix); for (Object key : keys) { rawKeyBuilder.append(".").append(key); } return rawKeyBuilder.toString(); } }
好了, 这些准备工作做完之后就可以正式使用DCache了.
@Autowired
private DCacheManagerService managerService;
@Transactional
public List<ArticleComingCarDto> getComingCars() {
final String key = "getComingCars";
return managerService.get(key, new Callable<List<ArticleComingCarDto>>() {
@Override
public List<ArticleComingCarDto> call() {
//Do your logic here.
}
});
}
剩下还有很多可以延伸, 比如说可以写一个action, 支持手动刷新DCache缓存, 这里get 方法有好几种实现, 按照自己的需求可以使用不同的get方法.
再说就是GuavaCache和DCache的组合使用二级缓存的情况.
当项目刚启动刷入GuavaCache(一级缓存), 如果访问量过大, 那么系统压力就会很大, 这时我们可以用DCache当做二级缓存来缓解压力, 然后将DCache中取出的值放入到GuavaCache中. 而GuavaCache的多重取值又比较与DCache有很大的优势. 所以当我们的一组缓存key值较多时且经常需要多重取值时, 那么这两种缓存的组合形式将是不二之选了.
关于DCache的东西就说这么多了, 更多的是展示出的代码.
[Java 缓存] Java Cache之 DCache的简单应用.的更多相关文章
- [Java 缓存] Java Cache之 Guava Cache的简单应用.
前言 今天第一次使用MarkDown的形式发博客. 准备记录一下自己对Guava Cache的认识及项目中的实际使用经验. 一: 什么是Guava Guava工程包含了若干被Google的 Java项 ...
- 从Java视角理解CPU缓存(CPU Cache)
从Java视角理解系统结构连载, 关注我的微博(链接)了解最新动态众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多 ...
- Map实现java缓存机制的简单实例
缓存是Java中主要的内容,主要目的是缓解项目访问数据库的压力以及提升访问数据的效率,以下是通过Map实现java缓存的功能,并没有用cache相关框架. 一.缓存管理类 CacheMgr.java ...
- Java缓存相关memcached、redis、guava、Spring Cache的使用
随笔分类 - Java缓存相关 主要记录memcached.redis.guava.Spring Cache的使用 第十二章 redis-cluster搭建(redis-3.2.5) 摘要: redi ...
- (转)java缓存技术,记录
http://blog.csdn.net/madun/article/details/8569860 最近再ITEYE上看到关于讨论JAVA缓存技术的帖子比较多,自己不懂,所以上网大概搜了下,找到一篇 ...
- JAVA缓存技术
介绍 JNotify:http://jnotify.sourceforge.net/,通过JNI技术,让Java代码可以实时的监控制定文件夹内文件的变动信息,支持Linux/Windows/MacOS ...
- JAVA缓存技术之EhCache
最近再ITEYE上看到关于讨论JAVA缓存技术的帖子比较多,自己不懂,所以上网大概搜了下,找到一篇,暂作保存,后面如果有用到可以参考.此为转贴,帖子来处:http://cogipard.info/ar ...
- JAVA缓存技术之EhCache(转)
最近再ITEYE上看到关于讨论JAVA缓存技术的帖子比较多,自己不懂,所以上网大概搜了下,找到一篇,暂作保存,后面如果有用到可以参考.此为转贴,帖子来处:http://cogipard.info/ar ...
- Java缓存框架
JBossCache/TreeCache JBossCache是一个复制的事务处理缓存,它允许你缓存企业级应用数据来更好的改善性能.缓存数据被自动复制,让你轻松进行Jboss服务器之间的集群工作 ...
随机推荐
- 08.LoT.UI 前后台通用框架分解系列之——多样的Tag选择器
LOT.UI分解系列汇总:http://www.cnblogs.com/dunitian/p/4822808.html#lotui LoT.UI开源地址如下:https://github.com/du ...
- IE8/9 本地预览上传图片
本地预览的意思是,在选择图片之后先不上传到服务器,而是由一个<img>标签来预览本地的图片,非 IE8/9 浏览器可以从<input type="file"/&g ...
- PHP之用户验证和标签推荐的简单使用
本篇主要是讲解一些最简单的验证知识 效果图 bookmark_fns.php <?php require_once('output_fns.php'); require_once('db_fns ...
- 《JavaScript设计模式 张》整理
最近在研读另外一本关于设计模式的书<JavaScript设计模式>,这本书中描述了更多的设计模式. 一.创建型设计模式 包括简单工厂.工厂方法.抽象工厂.建造者.原型和单例模式. 1)简单 ...
- CSS 3学习——transition 过渡
以下内容根据官方规范翻译以及自己的理解整理. 1.介绍 这篇文档介绍能够实现隐式过渡的CSS新特性.文档中介绍的CSS新特性描述了CSS属性的值如何在给定的时间内平滑地从一个值变为另一个值. 2.过渡 ...
- ASP.NET Core 中文文档 第四章 MVC(4.1)Controllers, Actions 和 Action Results
原文:Controllers, Actions, and Action Results 作者:Steve Smith 翻译:姚阿勇(Dr.Yao) 校对:许登洋(Seay) Action 和 acti ...
- psoc学习
第一是:项目的路径需要放在Documents and Settings\,也就是默认的文件夹的地方,不然会报错错误范例为:Question:CY8CKIT-023 kit example projec ...
- 微软开源代码编辑器monaco-editor
官网上给出:”The Monaco Editor is the code editor that powers VS Code. A good page describing the code edi ...
- LINQ to SQL语句(7)之Exists/In/Any/All/Contains
适用场景:用于判断集合中元素,进一步缩小范围. Any 说明:用于判断集合中是否有元素满足某一条件:不延迟.(若条件为空,则集合只要不为空就返回True,否则为False).有2种形式,分别为简单形式 ...
- Centos 7 vsftpd ldap 配置
#ldap 安裝配置 环境Centos7#安装 yum install -y openldap openldap-clients openldap-servers migrationtools pam ...