最近在写一个多台机器分布式的DHT磁力链爬虫,有大量的HashInfo需要去重好去拉种子取种子信息,之前用redis hash来实现去重,但内存消耗有点恐怖,mysql唯一索引去重也严重影响整个系统的性能,后面决定用bloomFilter算法进行去重,具体的算法可以自行搜索。

它的主要特点也就是三点:
1、非常低的内存消耗
2、存在的数据一定会返回已存在;
3、不存的数据可能有一定的概率返回已存在,但这不是问题万分之一的失败率也就是多丢弃几个HashInfo而以,可以通过调整hash分段等来调整失败概率。

搜索了下bloomfilter算法实现类都是单机的,无法实现多机共享。redis原生支持setbig和getbig命令可以使用bloomFilter算法,所以自己写了redis版的bloomFilter类。

受限于redis单bigset 512M内存上限(大概可放亿级数据量),如果数据量还大的话可以取模的方式hash到多个bigset Key来处理。

注【转载请注明来自 冻番茄's blog http://phpd.cn】

/**
 * redis实现的共享BloomFilter
 * @author zhiyong.xiongzy
 *
 */
public class RedisBloomFilter {

	private static final Logger	logger	     = Logger.getLogger(RedisBloomFilter.class);
	private static JedisPool	pool	     = null;

	private static final int[]	seeds	     = new int[] { 3, 7, 11, 13, 31, 37, 55 };

	private static final String	redisHost	 = "127.0.0.1";
	private static final int	redisPort	 = 6379;
	private static final String	redisPass	 = "123456";
	private static final String	key	         = "redis:bloom:filter";

	private BloomHash[]	        func	     = new BloomHash[seeds.length];

	public RedisBloomFilter() {
		for (int i = 0; i < seeds.length; i++) {
			func[i] = new BloomHash(2 << 26, seeds[i]);
		}
	}

	/**
	 * 加入一个数据
	 * @param value
	 */
	public void add(String value) {
		for (BloomHash f : func) {
			setBig(f.hash(value), true);
		}
	}

	/**
	 * 判重
	 * @param value
	 * @return
	 */
	public boolean contains(String value) {
		if (value == null) {
			return false;
		}
		boolean ret = true;
		for (BloomHash f : func) {
			ret = ret && getBig(f.hash(value));
		}
		return ret;
	}

	/**
	 * redis连接池初始化并返回一个redis连接
	 * @return redis连接实例
	 */
	private Jedis getJedis() {
		if (pool == null) {
			JedisPoolConfig config = new JedisPoolConfig();
			config.setMaxTotal(10);
			config.setMaxIdle(2);
			config.setMaxWaitMillis(2000);
			pool = new JedisPool(config, redisHost, redisPort, 120, redisPass);
		}
		return pool.getResource();
	}

	private boolean setBig(int offset, boolean value) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			return jedis.setbit(key, offset, value);
		} catch (Exception e) {
			logger.error("Redis hset error", e);
			return true;
		} finally {
			returnResource(jedis);
		}
	}

	private boolean getBig(int offset) {
		Jedis jedis = null;
		try {
			jedis = getJedis();
			return jedis.getbit(key, offset);
		} catch (Exception e) {
			logger.error("Redis hset error", e);
			//redis异常,返回true,保证bloomfilter规则之存在的元素一定是返回存在
			return true;
		} finally {
			returnResource(jedis);
		}
	}

	private void returnResource(Jedis redis) {
		if (redis != null) {
			pool.returnResource(redis);
		}
	}

	/**
	 * 一个简单的hash算法类,输出int类型hash值
	 * @author zhiyong.xiongzy
	 *
	 */
	public static class BloomHash {

		private int	cap;
		private int	seed;

		public BloomHash(int cap, int seed) {
			this.cap = cap;
			this.seed = seed;
		}

		public int hash(String value) {
			int result = 0;
			int len = value.length();
			for (int i = 0; i < len; i++) {
				result = seed * result + value.charAt(i);
			}
			return (cap - 1) & result;
		}
	}

	public static void main(String[] args) {
		String value = "95cea659143842e3f787f96910cac2bb2f32d207";
		RedisBloomFilter filter = new RedisBloomFilter();
		System.out.println(filter.contains(value));
		filter.add(value);
		System.out.println(filter.contains(value));

	}

}