最近在写一个多台机器分布式的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)); } }
我就是随便看看