分类目录归档:图片/文字

分享一个java写的redis共享的BloomFilter类

最近在写一个多台机器分布式的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));

	}

}

记录下java实现的二叉树算法实现

package com.yunos.tv.test;

/**
 * 二叉树算法
 * 
 * @author 冻番茄 phpd.cn
 */
import com.alibaba.fastjson.JSON;

public class BstTest {

	public static void main(String[] args) {
		//初始化一个二叉树
		int[] n = { 8, 3, 10, 1, 6, 14, 4, 7, 13, 13, 1 };
		Node root = null;
		for (int i : n) {
			root = insert(root, i);
		}
		System.out.println(JSON.toJSONString(root, true));
		System.out.println(JSON.toJSONString(search(root, 6), true));
		root = delete(root, 8);
		root = delete(root, 1);
		System.out.println(JSON.toJSONString(root, true));
	}

	/**
	 * 二叉树删除节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node delete(Node node, int key) {
		if (node == null) {
			return null;
		}
		//根据二叉树算法,当前节点小于key从左边查找并删除,反之从右边查找并删除
		if (key < node.getKey()) {
			node.setLeftNode(delete(node.getLeftNode(), key));
		} else if (key > node.getKey()) {
			node.setRightNode(delete(node.getRightNode(), key));
		} else {
			/*
			 * 查找到的话进行删除操作,删除分三种情况
			 * 1、该节点没有左右子节点则直接删除。
			 * 2、同时有左节点并且有右节点需要从最左边取最大节点替换或从最右边取最小节点替换。
			 * 3、只有左节点或只有右节点,把左节点或右节点直接替换被删除的节点
			 */
			if (node.getLeftNode() == null && node.getRightNode() == null) {
				node = null;
			} else if (node.getLeftNode() != null && node.getRightNode() != null) {
				//从左边查找最大节点进行替换
				node = deleteByLeft(node, node.getLeftNode(), key);
				//从右边查找最小节点进行替换
				//node = deleteByRight(node, node.getRightNode(), key);
			} else if (node.getLeftNode() != null) {
				node = node.getLeftNode();
			} else if (node.getRightNode() != null) {
				node = node.getRightNode();
			}
		}
		return node;
	}

	/**
	 * 要删除的节点同时有左右子节点时,从该删除节点右边查找出最小值的节点替换到删除的节点位置
	 * @param sourceNode
	 * @param rightNode
	 * @param key
	 * @return
	 */
	private static Node deleteByRight(Node sourceNode, Node rightNode, int key) {
		if (rightNode.getLeftNode() == null) {
			sourceNode.setKey(rightNode.getKey());
			Node tmp = delete(sourceNode.getRightNode(), rightNode.getKey());
			sourceNode.setRightNode(tmp);
			return sourceNode;
		}
		return deleteByRight(sourceNode, rightNode.getLeftNode(), key);

	}
	
	/**
	 * 要删除的节点同时有左右子节点时,从该删除节点左边查找出最大值的节点替换到删除的节点位置
	 * @param sourceNode
	 * @param leftNode
	 * @param key
	 * @return
	 */
	private static Node deleteByLeft(Node sourceNode, Node leftNode, int key) {

		if (leftNode.getRightNode() == null) {
			sourceNode.setKey(leftNode.getKey());
			Node tmp = delete(sourceNode.getLeftNode(), leftNode.getKey());
			sourceNode.setLeftNode(tmp);
			return sourceNode;
		}
		return deleteByLeft(sourceNode, leftNode.getRightNode(), key);

	}

	/**
	 * 在二叉树中快速查找一个节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node search(Node node, int key) {
		if (node == null) {
			return null;
		}
		if (node.getKey() == key) {
			return node;
		}
		if (node.getKey() > key) {
			return search(node.getLeftNode(), key);
		} else {
			return search(node.getRightNode(), key);
		}
	}

	/**
	 * 在二叉树中插入一个节点
	 * @param node
	 * @param key
	 * @return
	 */
	private static Node insert(Node node, int key) {
		if (node == null) {
			node = new Node();
			node.setKey(key);
		} else if (key == node.getKey()) {
			return node;
		} else if (key < node.key) {
			node.setLeftNode(insert(node.getLeftNode(), key));
			return node;
		} else {
			node.setRightNode(insert(node.getRightNode(), key));
		}
		return node;
	}

	public static class Node {

		private int		key;
		private Node	leftNode;
		private Node	rightNode;

		public int getKey() {
			return key;
		}

		public void setKey(int key) {
			this.key = key;
		}

		public Node getLeftNode() {
			return leftNode;
		}

		public void setLeftNode(Node leftNode) {
			this.leftNode = leftNode;
		}

		public Node getRightNode() {
			return rightNode;
		}

		public void setRightNode(Node rightNode) {
			this.rightNode = rightNode;
		}

	}

}

5分钟弄懂Docker!

尽管之前久闻Docker的大名了,但是天资愚钝,对其到底是个啥东西一直摸不清,最近花了一段时间整理了一下,算是整理出一点头绪来。

官网的介绍是这样的:

Docker is an open platform for developers and sysadmins to build, ship, and run distributed applications….
其实看完这句话还是不明白究竟是啥的,下面就慢慢解释。不过长话短说的话,把他想象成一个用了一种新颖方式实现的超轻量虚拟机,在大概效果上也是正确的。当然在实现的原理和应用上还是和VM有巨大差别的,并且专业的叫法是应用容器(Application Container)。

为啥要用容器?

那么应用容器长什么样子呢,一个做好的应用容器长得就好像一个装好了一组特定应用的虚拟机一样。比如我现在想用MySQL那我就找个装好MySQL的容器,运行起来,那么我就可以使用 MySQL了。

那么我直接装个 MySQL不就好了,何必还需要这个容器这么诡异的概念?话是这么说,可是你要真装MySQL的话可能要再装一堆依赖库,根据你的操作系统平台和版本进行设置,有时候还要从源代码编译报出一堆莫名其妙的错误,可不是这么好装。而且万一你机器挂了,所有的东西都要重新来,可能还要把配置在重新弄一遍。但是有了容器,你就相当于有了一个可以运行起来的虚拟机,只要你能运行容器,MySQL的配置就全省了。而且一旦你想换台机器,直接把这个容器端起来,再放到另一个机器就好了。硬件,操作系统,运行环境什么的都不需要考虑了。

在公司中的一个很大的用途就是可以保证线下的开发环境、测试环境和线上的生产环境一致。当年在 Baidu 经常碰到这样的事情,开发把东西做好了给测试去测,一般会给一坨代码和一个介绍上线步骤的上线单。结果代码在测试机跑不起来,开发就跑来跑去看问题,一会儿啊这个配置文件忘了提交了,一会儿啊这个上线命令写错了。找到了一个 bug 提上去,开发一看,啊我怎么又忘了把这个命令写在上线单上了。类似的事情在上线的时候还会发生,变成啊你这个软件的版本和我机器上的不一样……在 Amazon 的时候,由于一个开发直接担任上述三个职位,而且有一套自动化部署的机制所以问题会少一点,但是上线的时候大家还是胆战心惊。

若果利用容器的话,那么开发直接在容器里开发,提测的时候把整个容器给测试,测好了把改动改在容器里再上线就好了。通过容器,整个开发、测试和生产环境可以保持高度的一致。

此外容器也和VM一样具有着一定的隔离性,各个容器之间的数据和内存空间相互隔离,可以保证一定的安全性。

那为啥不用VM?

那么既然容器和 VM 这么类似为啥不直接用 VM 还要整出个容器这么个概念来呢?Docker 容器相对于 VM 有以下几个优点:

启动速度快,容器通常在一秒内可以启动,而 VM 通常要更久
资源利用率高,一台普通 PC 可以跑上千个容器,你跑上千个 VM 试试
性能开销小, VM 通常需要额外的 CPU 和内存来完成 OS 的功能,这一部分占据了额外的资源

为啥相似的功能在性能上会有如此巨大的差距呢,其实这和他们的设计的理念是相关的。 VM 的设计图如下:

VM 的 Hypervisor 需要实现对硬件的虚拟化,并且还要搭载自己的操作系统,自然在启动速度和资源利用率以及性能上有比较大的开销。而 Docker 的设计图是这样的:

Docker 几乎就没有什么虚拟化的东西,并且直接复用了 Host 主机的 OS,在 Docker Engine 层面实现了调度和隔离重量一下子就降低了好几个档次。 Docker 的容器利用了 LXC,管理利用了 namespaces 来做权限的控制和隔离, cgroups 来进行资源的配置,并且还通过 aufs 来进一步提高文件系统的资源利用率。

其中的 aufs 是个很有意思的东西,是 UnionFS 的一种。他的思想和 git 有些类似,可以把对文件系统的改动当成一次 commit 一层层的叠加。这样的话多个容器之间就可以共享他们的文件系统层次,每个容器下面都是共享的文件系统层次,上面再是各自对文件系统改动的层次,这样的话极大的节省了对存储的需求,并且也能加速容器的启动。

下一步

有了前面的这些介绍,应该对 Docker 到底是啥有些了解了吧, Docker 是 用 Go 语言编写的,源代码托管在 github 而且居然只有 1W 行就完成了这些功能。如果想尝试一下的话可以看 官方介绍了,应该上手会容易一些了。博主也是新手,如有错误欢迎拍砖指正。

JFinal整合velocity_layout+toolbox

公司项目一真用的是ssi(spring+springmvc+mybatis/ibatis)感觉还是比较重量级,所以找了个国人开发的JFinal框架学习,方便后续自己开发些东西的话能够快速开发。

但是JFinal默认使用的是freemarket模板引擎,可是一直以后用的都是velocity用顺手了模板引擎就不想变了,虽然支持velocity但不支持velocity的layout布局,也不支持toolbox。所以用JFinal之前先扩展这两个功能做为练手。

一惯的喜欢maven来进行管理代码,JFinal倡导的COC原则还是比较认同的,终于可以告别蛋疼的xml配置。
整完模板,以后开始弄注解方式@action @service和权限控制

	<dependency>
	<groupId>org.apache.velocity</groupId>
		<artifactId>velocity</artifactId>
		<version>1.7</version>
	</dependency>
	<dependency>
		<groupId>org.apache.velocity</groupId>
		<artifactId>velocity-tools</artifactId>
		<version>2.0</version>
	</dependency>

下面是重写的VelocityRender类,为了区分自带的VelocityRender,命名为VelocityLayoutRender

package net.yunbiz.shop.web.reader;


import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.apache.velocity.Template;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.tools.Scope;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.view.ViewToolContext;

import com.jfinal.core.JFinal;
import com.jfinal.log.Logger;
import com.jfinal.render.IMainRenderFactory;
import com.jfinal.render.Render;
import com.jfinal.render.RenderException;

/**
 * @author zhiyong.xiongzy
 */
public class VelocityLayoutRender extends Render {

	private static final long	              serialVersionUID	             = 1012573049421601960L;
	private transient static final String	  encoding	                     = getEncoding();
	private transient static final String	  contentType	                 = "text/html;charset=" + encoding;
	/**
	 * The velocity.properties key for specifying the servlet's error template.
	 */
	public static final String	              PROPERTY_ERROR_TEMPLATE	     = "tools.view.servlet.error.template";

	/**
	 * The velocity.properties key for specifying the relative directory holding
	 * layout templates.
	 */
	public static final String	              PROPERTY_LAYOUT_DIR	         = "tools.view.servlet.layout.directory";

	/**
	 * The velocity.properties key for specifying the servlet's default layout
	 * template's filename.
	 */
	public static final String	              PROPERTY_DEFAULT_LAYOUT	     = "tools.view.servlet.layout.default.template";

	/**
	 * The default error template's filename.
	 */
	public static final String	              DEFAULT_ERROR_TEMPLATE	     = "error.html";

	/**
	 * The default layout directory
	 */
	public static final String	              DEFAULT_LAYOUT_DIR	         = "layout/";

	/**
	 * The default filename for the servlet's default layout
	 */
	public static final String	              DEFAULT_DEFAULT_LAYOUT	     = "default.html";

	public static final String	              TOOLBOX_FILE	                 = "WEB-INF/vm-toolbox.xml";

	/**
	 * The context key that will hold the content of the screen.
	 * This key ($screen_content) must be present in the layout template for the
	 * current screen to be rendered.
	 */
	public static final String	              KEY_SCREEN_CONTENT	         = "screen_content";

	/**
	 * The context/parameter key used to specify an alternate layout to be used
	 * for a request instead of the default layout.
	 */
	public static final String	              KEY_LAYOUT	                 = "layout";

	/**
	 * The context key that holds the {@link Throwable} that broke the rendering
	 * of the requested screen.
	 */
	public static final String	              KEY_ERROR_CAUSE	             = "error_cause";

	/**
	 * The context key that holds the stack trace of the error that broke the
	 * rendering of the requested screen.
	 */
	public static final String	              KEY_ERROR_STACKTRACE	         = "stack_trace";

	/**
	 * The context key that holds the {@link MethodInvocationException} that
	 * broke the rendering of the requested screen.
	 * If this value is placed in the context, then $error_cause will hold the
	 * error that this invocation exception is wrapping.
	 */
	public static final String	              KEY_ERROR_INVOCATION_EXCEPTION	= "invocation_exception";

	protected static String	                  errorTemplate;
	protected static String	                  layoutDir;
	protected static String	                  defaultLayout;

	private transient static final Properties	properties	                 = new Properties();

	private transient static boolean	      notInit	                     = true;

	public VelocityLayoutRender(String view) {
		this.view = view;
	}

	public static void setProperties(Properties properties) {
		Set<Entry<Object, Object>> set = properties.entrySet();
		for (Iterator<Entry<Object, Object>> it = set.iterator(); it.hasNext();) {
			Entry<Object, Object> e = it.next();
			VelocityLayoutRender.properties.put(e.getKey(), e.getValue());
		}
	}

	public void render() {
		init();
		PrintWriter writer = null;
		try {

			VelocityEngine velocityEngine = new VelocityEngine();
			ViewToolContext context = new ViewToolContext(velocityEngine, request, response, JFinal.me().getServletContext());

			ToolManager tm = new ToolManager();
			tm.setVelocityEngine(velocityEngine);
			tm.configure(JFinal.me().getServletContext().getRealPath(TOOLBOX_FILE));
			if (tm.getToolboxFactory().hasTools(Scope.REQUEST)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.REQUEST));
			}
			if (tm.getToolboxFactory().hasTools(Scope.APPLICATION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.APPLICATION));
			}
			if (tm.getToolboxFactory().hasTools(Scope.SESSION)) {
				context.addToolbox(tm.getToolboxFactory().createToolbox(Scope.SESSION));
			}

			for (@SuppressWarnings("unchecked")
			Enumeration<String> attrs = request.getAttributeNames(); attrs.hasMoreElements();) {
				String attrName = attrs.nextElement();
				context.put(attrName, request.getAttribute(attrName));
			}

			Template template = Velocity.getTemplate(view);
			StringWriter sw = new StringWriter();
			template.merge(context, sw);
			context.put(KEY_SCREEN_CONTENT, sw.toString());

			response.setContentType(contentType);
			writer = response.getWriter(); // BufferedWriter writer = new
			                               // BufferedWriter(new
			                               // OutputStreamWriter(System.out));
			Object obj = context.get(KEY_LAYOUT);
			String layout = (obj == null) ? null : obj.toString();
			if (layout == null) {
				// no alternate, use default
				layout = defaultLayout;
			} else {
				// make it a full(er) path
				layout = layoutDir + layout;
			}

			try {
				// load the layout template
				template = Velocity.getTemplate(layout);
			} catch (ResourceNotFoundException e) {
				Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + layout + "\"", e);

				// if it was an alternate layout we couldn't get...
				if (!layout.equals(defaultLayout)) {
					// try to get the default layout
					// if this also fails, let the exception go
					try {
						template = Velocity.getTemplate(defaultLayout);
					} catch (ResourceNotFoundException e2) {
						Logger.getLogger(VelocityLayoutRender.class).error("Can't load layout \"" + defaultLayout + "\"", e2);
					}
				}
			}

			template.merge(context, writer);
			writer.flush(); // flush and cleanup
		} catch (ResourceNotFoundException e) {
			throw new RenderException("Error : cannot find template " + view, e);
		} catch (ParseErrorException e) {
			throw new RenderException("Syntax error in template " + view + ":" + e, e);
		} catch (Exception e) {
			throw new RenderException(e);
		} finally {
			if (writer != null)
				writer.close();
		}
	}

	private void init() {
		if (notInit) {
			String webPath = JFinal.me().getServletContext().getRealPath("/");
			properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, webPath);
			properties.setProperty(Velocity.ENCODING_DEFAULT, encoding);
			properties.setProperty(Velocity.INPUT_ENCODING, encoding);
			properties.setProperty(Velocity.OUTPUT_ENCODING, encoding);
			Velocity.init(properties);
			// setup layout
			errorTemplate = VelocityLayoutRender.properties.getProperty(PROPERTY_ERROR_TEMPLATE, DEFAULT_ERROR_TEMPLATE);
			layoutDir = VelocityLayoutRender.properties.getProperty(PROPERTY_LAYOUT_DIR, DEFAULT_LAYOUT_DIR);
			defaultLayout = VelocityLayoutRender.properties.getProperty(PROPERTY_DEFAULT_LAYOUT, DEFAULT_DEFAULT_LAYOUT);

			// preventive error checking! directory must end in /
			if (!layoutDir.endsWith("/")) {
				layoutDir += '/';
			}
			// for efficiency's sake, make defaultLayout a full path now
			defaultLayout = layoutDir + defaultLayout;

			// log the current settings
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Error screen is '" + errorTemplate + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Layout directory is '" + layoutDir + "'");
			Logger.getLogger(VelocityLayoutRender.class).info("VelocityRender: Default layout template is '" + defaultLayout + "'");
			notInit = false;
		}
	}

	public static final class VelocityLayoutRenderFactory implements IMainRenderFactory {

		public Render getRender(String view) {
			return new VelocityLayoutRender(view);
		}

		public String getViewExtension() {
			return ".html";
		}
	}
}

然后把在webapp下面建个目录layout用于放置layout文件,默认的layout文件路径是 layout/default.html

<!doctype html>
<html>
<title>Jfinal-velocity-layout-toolbox</title>
</head>
我是layout页面内容
<div class="wrap">
	$!{screen_content}
</div>
</body>
</html>

toolbox配置文件不可少,vm-toolbox.xml,路径参考VelocityLayoutRender.TOOLBOX_FILE
暂时放几个常用的,后续自己可以扩展功能

<?xml version="1.0"?>

<toolbox>
	<xhtml>true</xhtml>

	<tool>
		<key>stringUtils</key>
		<scope>application</scope>
		<class>org.apache.commons.lang.StringUtils</class>
	</tool>
	<tool>
		<key>esc</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.EscapeTool</class>
	</tool>
	<tool>
		<key>number</key>
		<scope>application</scope>
		<class>org.apache.velocity.tools.generic.NumberTool</class>
		<parameter name="format" value="#0.00" />
	</tool>
	<tool>
		<key>gson</key>
		<scope>application</scope>
		<class>com.google.gson.Gson
		</class>
	</tool>
</toolbox>

最后一步就简单了,在Config中指定视图处理器

public class ShopConfig extends JFinalConfig {

	/**
	 * 配置常量
	 */
	public void configConstant(Constants me) {
		// 加载少量必要配置,随后可用getProperty(...)获取值
		loadPropertyFile("a_little_config.txt");
		me.setDevMode(getPropertyToBoolean("devMode", false));
		RenderFactory.setMainRenderFactory(new VelocityLayoutRenderFactory());
	}
	
	//后面的代码省略...... 
}

博客被人挂马了还被利用发垃圾邮件

前几天博客搬家时,本来搬家是因为buyvm的vps不稳定,老是offline,直到昨天才发现原来是博客被人黑了。

被人挂上了后门用于发送垃圾邮件,导致CPU负载猛增服务掉线。找到问题就好办了,搜索所有的代码,把有嫌疑的文件全部干掉,删除不用的主题和插件。分析访问日志查到POST方式请求的文件删除。把不该有写入权限的文件夹去除写入权限,并修改ssh密码等。

去年也发现一个事,域名被人劫持了。现到在百度上都有一些乱七八糟的信息。

看来安全问题还是得注意了。

HttpClient容易忽视的细节连接关闭

Java代码  收藏代码
  1. HttpClient client = new HttpClient();
  2. HttpMethod method = new GetMethod(“http://www.apache.org”);
  3. try {
  4.   client.executeMethod(method);
  5.   byte[] responseBody = null;
  6.   responseBody = method.getResponseBody();
  7. } catch (HttpException e) {
  8.   // TODO Auto-generated catch block
  9.   e.printStackTrace();
  10. } catch (IOException e) {
  11.   // TODO Auto-generated catch block
  12.   e.printStackTrace();
  13. }finally{
  14.   method.releaseConnection();
  15. }

大部分人使用HttpClient都是使用类似上面的事例代码,包括Apache官方的例子也是如此。最近我在使用HttpClient是发现一次循环发送大量请求到服务器会导致APACHE服务器的链接被占满,后续的请求便排队等待。
我服务器端APACHE的配置

Java代码  收藏代码
  1. Timeout 30
  2. KeepAlive On   #表示服务器端不会主动关闭链接
  3. MaxKeepAliveRequests 100
  4. KeepAliveTimeout 180

因此这样的配置就会导致每个链接至少要过180S才会被释放,这样在大量请求访问时就必然会造成链接被占满,请求等待的情况。
在通过DEBUH后发现HttpClient在method.releaseConnection()后并没有把链接关闭,这个方法只是将链接返回给connection manager。如果使用HttpClient client = new HttpClient()实例化一个HttpClient connection manager默认实现是使用SimpleHttpConnectionManager。SimpleHttpConnectionManager有个构造函数如下

Java代码  收藏代码
  1. /**
  2.  * The connection manager created with this constructor will try to keep the
  3.  * connection open (alive) between consecutive requests if the alwaysClose
  4.  * parameter is set to <tt>false</tt>. Otherwise the connection manager will
  5.  * always close connections upon release.
  6.  *
  7.  * @param alwaysClose if set <tt>true</tt>, the connection manager will always
  8.  *    close connections upon release.
  9.  */
  10. public SimpleHttpConnectionManager(boolean alwaysClose) {
  11.     super();
  12.     this.alwaysClose = alwaysClose;
  13. }

看方法注释我们就可以看到如果alwaysClose设为true在链接释放之后connection manager 就会关闭链。在我们HttpClient client = new HttpClient()这样实例化一个client时connection manager是这样被实例化的

Java代码  收藏代码
  1. this.httpConnectionManager = new SimpleHttpConnectionManager();

因此alwaysClose默认是false,connection是不会被主动关闭的,因此我们就有了一个客户端关闭链接的方法。
方法一:
把事例代码中的第一行实例化代码改为如下即可,在method.releaseConnection();之后connection manager会关闭connection 。

Java代码  收藏代码
  1. HttpClient client = new HttpClient(new HttpClientParams(),new SimpleHttpConnectionManager(true) );

方法二:
实例化代码使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上

Java代码  收藏代码
  1. ((SimpleHttpConnectionManager)client.getHttpConnectionManager()).shutdown();

shutdown源代码很简单,看了一目了然

Java代码  收藏代码
  1. public void shutdown() {
  2.     httpConnection.close();
  3. }

方法三:
实例化代码使用:HttpClient client = new HttpClient();
在method.releaseConnection();之后加上
client.getHttpConnectionManager().closeIdleConnections(0);此方法源码代码如下:

Java代码  收藏代码
  1. public void closeIdleConnections(long idleTimeout) {
  2.     long maxIdleTime = System.currentTimeMillis() – idleTimeout;
  3.     if (idleStartTime <= maxIdleTime) {
  4.         httpConnection.close();
  5.     }
  6. }

将idleTimeout设为0可以确保链接被关闭。
以上这三种方法都是有客户端主动关闭TCP链接的方法。下面再介绍由服务器端自动关闭链接的方法。
方法四:
代码实现很简单,所有代码就和最上面的事例代码一样。只需要在HttpMethod method = new GetMethod(“http://www.apache.org”);加上一行HTTP头的设置即可

Java代码  收藏代码
  1. method.setRequestHeader(“Connection”, “close”);

看一下HTTP协议中关于这个属性的定义:
HTTP/1.1 defines the “close” connection option for the sender to signal that the connection will be closed after completion of the response. For example,
Connection: close
现在再说一下客户端关闭链接和服务器端关闭链接的区别。如果采用客户端关闭链接的方法,在客户端的机器上使用netstat –an命令会看到很多TIME_WAIT的TCP链接。如果服务器端主动关闭链接这中情况就出现在服务器端。
参考WIKI上的说明http://wiki.apache.org/HttpComponents/FrequentlyAskedConnectionManagementQuestions
The TIME_WAIT state is a protection mechanism in TCP. The side that closes a socket connection orderly will keep the connection in state TIME_WAIT for some time, typically between 1 and 4 minutes.
TIME_WAIT的状态会出现在主动关闭链接的这一端。TCP协议中TIME_WAIT状态主要是为了保证数据的完整传输。具体可以参考此文档:
http://www.softlab.ntua.gr/facilities/documentation/unix/unix-socket-faq/unix-socket-faq-2.html#ss2.7
另外强调一下使用上面这些方法关闭链接是在我们的应用中明确知道不需要重用链接时可以主动关闭链接来释放资源。如果你的应用是需要重用链接的话就没必要这么做,使用原有的链接还可以提供性能。