转自:http://guoyunsky.iteye.com/blog/1169912

队列很常见,但大部分的队列是将数据放入到内存.如果数据过多,就有内存溢出危险,而且长久占据着内存,也会影响性能.比如爬虫,将要抓取的URL放到内存,而URL过多,内存肯定要爆.在读Heritrix源码中,发现Heritrix是基于Bdb实现了一个持久化队列,于是我就将这块代码独立出来,平时使用也蛮爽的,现在拿出来共享.同时数据已经持久化,相比放在内存的一次性,可以循环累加使用.

大家也知道BDB的高性能和嵌入式.但这个持久化队列我觉得比较适合单机.如果涉及到分布式,就不大适合了.毕竟分布式要通信,负载均衡,冗余等.可以用其他的数据库等替代.

这里大概先说下实现原理,BDB是Key-Value型数据库,而队列是FIFO.所以这个持久化队列以位置作为BDB的Key,数据作为BDB的Value.然后用两个变量,分别记录队列两头的位置,也就是头部和尾部.当有数据插入的时候,就以尾部的位置为这个数据的Key.而当要取出数据时,以头部位置作为Key,获取这个Key的数据.原理大概如此,这个类也继承AbstractQueue,这里贴上代码.以下代码需引用bdb-je,common-io,junit.请在附件中下载

1.自定义的BDB环境类,可以缓存StoredClassCatalog并共享

package com.guoyun.util;

import java.io.File;

import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
/**
* BDB数据库环境,可以缓存StoredClassCatalog并共享
*
* @contributor guoyun
*/
public class BdbEnvironment extends Environment {
StoredClassCatalog classCatalog;
Database classCatalogDB; /**
* Constructor
*
* @param envHome 数据库环境目录
* @param envConfig config options 数据库换纪念馆配置
* @throws DatabaseException
*/
public BdbEnvironment(File envHome, EnvironmentConfig envConfig) throws DatabaseException {
super(envHome, envConfig);
} /**
* 返回StoredClassCatalog
* @return the cached class catalog
*/
public StoredClassCatalog getClassCatalog() {
if(classCatalog == null) {
DatabaseConfig dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
try {
classCatalogDB = openDatabase(null, "classCatalog", dbConfig);
classCatalog = new StoredClassCatalog(classCatalogDB);
} catch (DatabaseException e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
return classCatalog;
} @Override
public synchronized void close() throws DatabaseException {
if(classCatalogDB!=null) {
classCatalogDB.close();
}
super.close();
} }

2.  基于BDB实现的持久化队列

package com.guoyun.util;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.AbstractQueue;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong; import org.apache.commons.io.FileUtils; import com.sleepycat.bind.EntryBinding;
import com.sleepycat.bind.serial.SerialBinding;
import com.sleepycat.bind.serial.StoredClassCatalog;
import com.sleepycat.bind.tuple.TupleBinding;
import com.sleepycat.collections.StoredMap;
import com.sleepycat.collections.StoredSortedMap;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.DatabaseExistsException;
import com.sleepycat.je.DatabaseNotFoundException;
import com.sleepycat.je.EnvironmentConfig;
/**
* 持久化队列,基于BDB实现,也继承Queue,以及可以序列化.但不等同于Queue的时,不再使用后需要关闭
* 相比一般的内存Queue,插入和获取值需要多消耗一定的时间
* 这里为什么是继承AbstractQueue而不是实现Queue接口,是因为只要实现offer,peek,poll几个方法即可,
* 其他如remove,addAll,AbstractQueue会基于这几个方法去实现
*
* @contributor guoyun
* @param <E>
*/
public class BdbPersistentQueue<E extends Serializable> extends AbstractQueue<E> implements
Serializable {
private static final long serialVersionUID = 3427799316155220967L;
private transient BdbEnvironment dbEnv; // 数据库环境,无需序列化
private transient Database queueDb; // 数据库,用于保存值,使得支持队列持久化,无需序列化
private transient StoredMap<Long,E> queueMap; // 持久化Map,Key为指针位置,Value为值,无需序列化
private transient String dbDir; // 数据库所在目录
private transient String dbName; // 数据库名字
private AtomicLong headIndex; // 头部指针
private AtomicLong tailIndex; // 尾部指针
private transient E peekItem=null; // 当前获取的值 /**
* 构造函数,传入BDB数据库
*
* @param db
* @param valueClass
* @param classCatalog
*/
public BdbPersistentQueue(Database db,Class<E> valueClass,StoredClassCatalog classCatalog){
this.queueDb=db;
this.dbName=db.getDatabaseName();
headIndex=new AtomicLong(0);
tailIndex=new AtomicLong(0);
bindDatabase(queueDb,valueClass,classCatalog);
}
/**
* 构造函数,传入BDB数据库位置和名字,自己创建数据库
*
* @param dbDir
* @param dbName
* @param valueClass
*/
public BdbPersistentQueue(String dbDir,String dbName,Class<E> valueClass){
headIndex=new AtomicLong(0);
tailIndex=new AtomicLong(0);
this.dbDir=dbDir;
this.dbName=dbName;
createAndBindDatabase(dbDir,dbName,valueClass);
}
/**
* 绑定数据库
*
* @param db
* @param valueClass
* @param classCatalog
*/
public void bindDatabase(Database db, Class<E> valueClass, StoredClassCatalog classCatalog){
EntryBinding<E> valueBinding = TupleBinding.getPrimitiveBinding(valueClass);
if(valueBinding == null) {
valueBinding = new SerialBinding<E>(classCatalog, valueClass); // 序列化绑定
}
queueDb = db;
queueMap = new StoredSortedMap<Long,E>(
db, // db
TupleBinding.getPrimitiveBinding(Long.class), //Key
valueBinding, // Value
true); // allow write
}
/**
* 创建以及绑定数据库
*
* @param dbDir
* @param dbName
* @param valueClass
* @throws DatabaseNotFoundException
* @throws DatabaseExistsException
* @throws DatabaseException
* @throws IllegalArgumentException
*/
private void createAndBindDatabase(String dbDir, String dbName,Class<E> valueClass) throws DatabaseNotFoundException,
DatabaseExistsException,DatabaseException,IllegalArgumentException{
File envFile = null;
EnvironmentConfig envConfig = null;
DatabaseConfig dbConfig = null;
Database db=null; try {
// 数据库位置
envFile = new File(dbDir); // 数据库环境配置
envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
envConfig.setTransactional(false); // 数据库配置
dbConfig = new DatabaseConfig();
dbConfig.setAllowCreate(true);
dbConfig.setTransactional(false);
dbConfig.setDeferredWrite(true); // 创建环境
dbEnv = new BdbEnvironment(envFile, envConfig);
// 打开数据库
db = dbEnv.openDatabase(null, dbName, dbConfig);
// 绑定数据库
bindDatabase(db,valueClass,dbEnv.getClassCatalog()); } catch (DatabaseNotFoundException e) {
throw e;
} catch (DatabaseExistsException e) {
throw e;
} catch (DatabaseException e) {
throw e;
} catch (IllegalArgumentException e) {
throw e;
} } /**
* 值遍历器
*/
@Override
public Iterator<E> iterator() {
return queueMap.values().iterator();
}
/**
* 大小
*/
@Override
public int size() {
synchronized(tailIndex){
synchronized(headIndex){
return (int)(tailIndex.get()-headIndex.get());
}
}
} /**
* 插入值
*/
@Override
public boolean offer(E e) {
synchronized(tailIndex){
queueMap.put(tailIndex.getAndIncrement(), e); // 从尾部插入
}
return true;
} /**
* 获取值,从头部获取
*/
@Override
public E peek() {
synchronized(headIndex){
if(peekItem!=null){
return peekItem;
}
E headItem=null;
while(headItem==null&&headIndex.get()<tailIndex.get()){ // 没有超出范围
headItem=queueMap.get(headIndex.get());
if(headItem!=null){
peekItem=headItem;
continue;
}
headIndex.incrementAndGet(); // 头部指针后移
}
return headItem;
}
} /**
* 移出元素,移出头部元素
*/
@Override
public E poll() {
synchronized(headIndex){
E headItem=peek();
if(headItem!=null){
queueMap.remove(headIndex.getAndIncrement());
peekItem=null;
return headItem;
}
}
return null;
} /**
* 关闭,也就是关闭所是用的BDB数据库但不关闭数据库环境
*/
public void close(){
try {
if(queueDb!=null){
queueDb.sync();
queueDb.close();
}
} catch (DatabaseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedOperationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} /**
* 清理,会清空数据库,并且删掉数据库所在目录,慎用.如果想保留数据,请调用close()
*/
@Override
public void clear() {
try {
close();
if(dbEnv!=null&&queueDb!=null){
dbEnv.removeDatabase(null, dbName==null?queueDb.getDatabaseName():dbName);
dbEnv.close();
}
} catch (DatabaseNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (DatabaseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
try {
if(this.dbDir!=null){
FileUtils.deleteDirectory(new File(this.dbDir));
} } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} }

3. 测试类,测试数据准确性和性能

package com.guoyun.util;

import java.io.File;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingQueue; import junit.framework.TestCase; public class BdbPersistentQueueTest extends TestCase{
Queue<String> memoryQueue;
Queue<String> persistentQueue; @Override
protected void setUp() throws Exception {
super.setUp();
memoryQueue=new LinkedBlockingQueue<String>();
String dbDir="E:/java/test/bdbDir";
File file=new File(dbDir);
if(!file.exists()||!file.isDirectory()){
file.mkdirs();
}
persistentQueue=new BdbPersistentQueue(dbDir,"pq",String.class);
} @Override
protected void tearDown() throws Exception {
super.tearDown();
memoryQueue.clear();
memoryQueue=null;
persistentQueue.clear();
persistentQueue=null;
} /**
* 排放值
* @param queue
* @return 排放的数据个数
*/
public int drain(Queue<String> queue){
int count=0;
while(true){
try {
queue.remove();
count++;
} catch (Exception e) {
return count;
}
} }
/**
*
* @param queue
* @param size
*/
public void fill(Queue<String> queue,int size){
for(int i=0;i<size;i++){
queue.add(i+"");
}
} public void checkTime(int size){
System.out.println("1.内存Queue插入和排空数据所耗时间");
long time=0;
long start=System.nanoTime();
fill(memoryQueue,size);
time=System.nanoTime()-start;
System.out.println("\t填充 "+size+" 条数据耗时: "+(double)time/1000000+" 毫秒,单条耗时: "+((double)time/size)+" 纳秒");
start=System.nanoTime();
drain(memoryQueue);
time=System.nanoTime()-start;
System.out.println("\t排空 "+size+" 条数据耗时: "+(double)time/1000000+" 毫秒,单条耗时: "+((double)time/size)+" 纳秒"); System.out.println("2.持久化Queue插入和排空数据所耗时间");
start=System.nanoTime();
fill(persistentQueue,size);
time=System.nanoTime()-start;
System.out.println("\t填充 "+size+" 条数据耗时: "+(double)time/1000000+" 毫秒,单条耗时: "+((double)time/size/1000000)+" 豪秒");
start=System.nanoTime();
drain(persistentQueue);
time=System.nanoTime()-start;
System.out.println("\t排空 "+size+" 条数据耗时: "+(double)time/1000000+" 毫秒,单条耗时: "+((double)time/size/1000)+" 豪秒"); } /**
* 十万数量级测试
*/
public void testTime_tenThousand(){
System.out.println("========测试1000000(十万)条数据=================");
checkTime(100000);
} /**
* 百万数量级测试
*/
public void testTime_mil(){
System.out.println("========测试1000000(百万)条数据=================");
checkTime(1000000);
} /**
* 千万数量级测试,注意要防止内存溢出
*/
public void testTime_tenMil(){
System.out.println("========测试10000000(千万)条数据=================");
checkTime(10000000);
} /**
* 测试队列数据准确性
* @param queue
* @param queueName
* @param size
*/
public void checkDataExact(Queue<String> queue,String queueName,int size){
if(queue.size()!=size){
System.err.println("Error size of "+queueName);
}
String value=null;
for(int i=0;i<size;i++){
value=queue.remove();
if(!((i+"").equals(value))){
System.err.println("Error "+queueName+":"+i+"->"+value);
}
}
} /**
* 测试队列中数据的准确性,包括长度
*/
public void testExact(){
int size=100;
fill(memoryQueue,size);
fill(persistentQueue,size); checkDataExact(memoryQueue,"MemoryQueue",100);
checkDataExact(persistentQueue,"PersistentQueue",100); } }

4.测试性能

========测试1000000(十万)条数据=================
1.内存Queue插入和排空数据所耗时间
 填充 100000 条数据耗时: 53.550787 毫秒,单条耗时: 535.50787 纳秒
 排空 100000 条数据耗时: 27.09901 毫秒,单条耗时: 270.9901 纳秒
2.持久化Queue插入和排空数据所耗时间
 填充 100000 条数据耗时: 1399.644305 毫秒,单条耗时: 0.01399644305 豪秒
 排空 100000 条数据耗时: 2104.765179 毫秒,单条耗时: 21.04765179 豪秒

持久化写入是内存写入的26倍,读取是77倍

========测试1000000(百万)条数据=================
1.内存Queue插入和排空数据所耗时间
 填充 1000000 条数据耗时: 699.105888 毫秒,单条耗时: 699.105888 纳秒
 排空 1000000 条数据耗时: 158.792281 毫秒,单条耗时: 158.792281 纳秒
2.持久化Queue插入和排空数据所耗时间
 填充 1000000 条数据耗时: 11978.132218 毫秒,单条耗时: 0.011978132218 豪秒
 排空 1000000 条数据耗时: 22355.617205 毫秒,单条耗时: 22.355617204999998 豪秒

持久化写入是内存写入的17倍,读取是141倍

========测试10000000(千万)条数据=================
1.内存Queue插入和排空数据所耗时间
 填充 10000000 条数据耗时: 9678.377046 毫秒,单条耗时: 967.8377046 纳秒
 排空 10000000 条数据耗时: 1473.416825 毫秒,单条耗时: 147.3416825 纳秒
2.持久化Queue插入和排空数据所耗时间
 填充 10000000 条数据耗时: 151177.036391 毫秒,单条耗时: 0.0151177036391 豪秒
 排空 10000000 条数据耗时: 361642.655135 毫秒,单条耗时: 36.164265513500006 豪秒

持久化写入是内存写入的15倍,读取是245倍

可以看出写入和遍历一条都是在毫秒级别,还有千万级的数据,BDB的性能着实牛逼.而且随着数据的增多,写的时间在缩短,读的时间在增长.

jar包:

commons-io-1.4.jar

junit-4.8.2.jar

je-4.0.71.jar

基于Berkeley DB实现的持久化队列的更多相关文章

  1. Berkeley DB的数据存储结构——哈希表(Hash Table)、B树(BTree)、队列(Queue)、记录号(Recno)

    Berkeley DB的数据存储结构 BDB支持四种数据存储结构及相应算法,官方称为访问方法(Access Method),分别是哈希表(Hash Table).B树(BTree).队列(Queue) ...

  2. berkeley db储存URL队列的简单实现增、删、查

     Berkeley DB(BDB)是一个高效的嵌入式数据库编程库,C语言.C++.Java.Perl.Python.Tcl以及其它非常多语言都有其相应的API. Berkeley DB能够保存随意 ...

  3. BDB (Berkeley DB)数据库简单介绍(转载)

    近期要使用DBD,于是搜了下相关的资料,先贴个科普性的吧: 转自http://www.javaeye.com/topic/202990 DB综述DB最初开发的目的是以新的HASH訪问算法来取代旧的hs ...

  4. 新浪研发中心: Berkeley DB 使用经验总结

    http://blog.sina.com.cn/s/blog_502c8cc40100yqkj.html NoSQL是现在互联网Web2.0时代备受关注的技术之一,被用来存储大量的非关系型的数据.Be ...

  5. Berkeley DB基础教程

    一.Berkeley DB的介绍 (1)Berkeley DB是一个嵌入式数据库,它适合于管理海量的.简单的数据.如Google使用其来保存账户信息,Heritrix用其来保存froniter. (2 ...

  6. Berkeley DB 使用

    http://www.ibm.com/developerworks/cn/linux/l-embdb/index.html UNIX/LINUX平台下的数据库种类非常多,参考资料1中 列举了其中的大部 ...

  7. BDB (Berkeley DB)简要数据库(转载)

    使用最近DBD.然后搜了下相关资料,首先公布的是一门科学: 转会http://www.javaeye.com/topic/202990 DB综述DB最初开发的目的是以新的HASH訪问算法来取代旧的hs ...

  8. Berkeley DB 使用经验总结

    作者:陈磊 NoSQL是现在互联网Web2.0时代备受关注的技术之一,被用来存储大量的非关系型的数据.Berkeley DB作为一款优秀的Key/Value存储引擎自然也在讨论之列.最近使用BDB来发 ...

  9. 了解 Oracle Berkeley DB 可以为您的应用程序带来 NoSQL 优势的原因及方式。

    将 Oracle Berkeley DB 用作 NoSQL 数据存储 作者:Shashank Tiwari 2011 年 2 月发布 “NoSQL”是在开发人员.架构师甚至技术经理中新流行的一个词汇. ...

随机推荐

  1. RTX登录其他系统

    前台: <html> <head> <title>签名验证</title> <meta http-equiv="Content-Lang ...

  2. 【leetcode】Subsets II

    Subsets II Given a collection of integers that might contain duplicates, S, return all possible subs ...

  3. 【leetcode】Subsets

    Subsets Given a set of distinct integers, S, return all possible subsets. Note: Elements in a subset ...

  4. iOS 中的第三方库管理工具

    xcode没有android studio中的gradle进行第三方库管理,但是有第三方的库管理工具CocoaPods,https://github.com/CocoaPods/CocoaPods/w ...

  5. Android 中的AsyncTask

    在后台下载图片,下载完成后更新UI是一个很常见的需求.在没有AsyncTask类之前,我们需要写许多thread和Handler的代码去实现这个功能,有了AsyncTask,一切变得简单了.下面摘抄谷 ...

  6. 高效PHP开发注意事项

    2015年2月26日 17:23:26 http://www.open-open.com/lib/view/open1332904714233.html

  7. 【python】linux将python改为默认3.4版本

    Python3.4默认是安装在/usr/local/lib/python3.4目录下,需要删除默认python link文件,重新建立连接关系. 使用ln -s命令来修改,命令如下: sudo rm ...

  8. CUDA学习笔记(四)——CUDA性能

    转自:http://blog.sina.com.cn/s/blog_48b9e1f90100fm5h.html 四.CUDA性能 CUDA中的block被划分成一个个的warp,在GeForce880 ...

  9. Linux用户名显示-bash-4.1$快速排查

    最近项目使用的的服务器有点多(100多台),很多开发同事经常问这个问题,现在整理如下: 几个可能导致的原因: 1 用户的家目录所属组被改为root,解决方法使用root执行cd /home/;chow ...

  10. oracle 10g 学习之创建和管理表(7)

    目标 通过本章学习,您将可以: l  描述主要的数据库对象. l  创建表. l  描述各种数据类型. l  修改表的定义. l  删除,重命名和清空表. 常见的数据库对象 表.视图.序列.索引.同义 ...