<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>田加国的博客</title>
	<atom:link href="http://www.tianjiaguo.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.tianjiaguo.com</link>
	<description>A little bit of progress every day</description>
	<lastBuildDate>Wed, 16 May 2012 02:49:55 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>常用的maven配置</title>
		<link>http://www.tianjiaguo.com/code/%e5%b8%b8%e7%94%a8%e7%9a%84maven%e9%85%8d%e7%bd%ae/</link>
		<comments>http://www.tianjiaguo.com/code/%e5%b8%b8%e7%94%a8%e7%9a%84maven%e9%85%8d%e7%bd%ae/#comments</comments>
		<pubDate>Mon, 07 May 2012 12:30:30 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Code]]></category>
		<category><![CDATA[maven]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1303</guid>
		<description><![CDATA[1、profile 使用方法如：mvn -P dev &#60;profiles> &#60;profile> &#60;id>product&#60;/id> &#60;properties> &#60;package.environment>main&#60;/package.environment> &#60;/properties> &#60;/profile> &#60;profile> &#60;id>dev&#60;/id> &#60;properties> &#60;package.environment>test&#60;/package.environment> &#60;/properties> &#60;/profile> &#60;/profiles> 2、指定mvn库位置，当deploy时会向这两个地方upload相应的jar包，需要在.m2目录中添加settings.xml配置文件 &#60;distributionManagement> &#60;repository> &#60;id>nexus-releases&#60;/id> &#60;name>Nexus Release Repository&#60;/name> &#60;url>http://127.0.0.1:8081/nexus/content/repositories/releases&#60;/url> &#60;/repository> &#60;snapshotRepository> &#60;id>nexus-snapshots&#60;/id> &#60;name>Nexus Snapshot Repository&#60;/name> &#60;url>http://127.0.0.1:8081/nexus/content/repositories/snapshots&#60;/url> &#60;uniqueVersion>false&#60;/uniqueVersion> &#60;/snapshotRepository> &#60;/distributionManagement> 3、指定编译器选项 &#60;plugins> &#60;plugin> &#60;artifactId>maven-compiler-plugin&#60;/artifactId> &#8230; <a href="http://www.tianjiaguo.com/code/%e5%b8%b8%e7%94%a8%e7%9a%84maven%e9%85%8d%e7%bd%ae/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>1、profile<br />
使用方法如：mvn -P dev</p>
<pre class="prettyprint">
 &lt;profiles>
        &lt;profile>
            &lt;id>product&lt;/id>
            &lt;properties>
                &lt;package.environment>main&lt;/package.environment>
            &lt;/properties>
        &lt;/profile>
        &lt;profile>
            &lt;id>dev&lt;/id>
            &lt;properties>
                &lt;package.environment>test&lt;/package.environment>
            &lt;/properties>
        &lt;/profile>
    &lt;/profiles>
</pre>
<p>2、指定mvn库位置，当deploy时会向这两个地方upload相应的jar包，需要在.m2目录中添加settings.xml配置文件</p>
<pre class="prettyprint">
&lt;distributionManagement>
        &lt;repository>
            &lt;id>nexus-releases&lt;/id>
            &lt;name>Nexus Release Repository&lt;/name>
            &lt;url>http://127.0.0.1:8081/nexus/content/repositories/releases&lt;/url>
        &lt;/repository>
        &lt;snapshotRepository>
            &lt;id>nexus-snapshots&lt;/id>
            &lt;name>Nexus Snapshot Repository&lt;/name>
            &lt;url>http://127.0.0.1:8081/nexus/content/repositories/snapshots&lt;/url>
            &lt;uniqueVersion>false&lt;/uniqueVersion>
        &lt;/snapshotRepository>
    &lt;/distributionManagement>
</pre>
<p>3、指定编译器选项</p>
<pre class="prettyprint">
&lt;plugins>
            &lt;plugin>
                &lt;artifactId>maven-compiler-plugin&lt;/artifactId>
                &lt;version>2.3.2&lt;/version>
                &lt;configuration>
                    &lt;source>1.6&lt;/source>
                    &lt;target>1.6&lt;/target>
                    &lt;encoding>UTF-8&lt;/encoding>
                &lt;/configuration>
            &lt;/plugin>
        &lt;/plugins>
</pre>
<p>4、jetty配置</p>
<pre class="prettyprint">
&lt;plugin>
                &lt;groupId>org.mortbay.jetty&lt;/groupId>
                &lt;artifactId>jetty-maven-plugin&lt;/artifactId>
                &lt;version>8.1.1.v20120215&lt;/version>
                &lt;configuration>
                    &lt;connectors>
                        &lt;connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
                            &lt;port>8080&lt;/port>
                        &lt;/connector>
                    &lt;/connectors>
                    &lt;scanIntervalSeconds>10&lt;/scanIntervalSeconds>
                    &lt;stopKey>stopKey&lt;/stopKey>
                    &lt;stopPort>8888&lt;/stopPort>
                    &lt;scanTargetPatterns>
                        &lt;scanTargetPattern>
                            &lt;directory>src/main/webapp&lt;/directory>
                            &lt;includes>
                                &lt;include>**/*.xml&lt;/include>
                                &lt;include>**/*.properties&lt;/include>
                            &lt;/includes>
                        &lt;/scanTargetPattern>
                    &lt;/scanTargetPatterns>
                    &lt;webAppConfig>
                        &lt;contextPath>/&lt;/contextPath>
                    &lt;/webAppConfig>
                    &lt;systemProperties>
                        &lt;systemProperty>
                            &lt;name>org.apache.commons.logging.LogFactory&lt;/name>
                            &lt;value>org.apache.commons.logging.impl.LogFactoryImpl&lt;/value>
                        &lt;/systemProperty>
                    &lt;/systemProperties>
                &lt;/configuration>
            &lt;/plugin>
</pre>
<p>5、指定文件</p>
<pre class="prettyprint">
&lt;resources>
            &lt;resource>
                &lt;directory>${basedir}/src/main/resources&lt;/directory>
                &lt;includes>
                    &lt;include>***.properties&lt;/include>
                    &lt;include>***.xml&lt;/include>
                &lt;/includes>
            &lt;/resource>
        &lt;/resources>
</pre>
<p>5、打包</p>
<pre class="prettyprint">
// 1:pom.xml配置
&lt;plugin>
				&lt;artifactId>maven-assembly-plugin&lt;/artifactId>
				&lt;version>2.2&lt;/version>
				&lt;configuration>
					&lt;descriptors>
						&lt;descriptor>
							distribution.xml
						&lt;/descriptor>
					&lt;/descriptors>
				&lt;/configuration>
				&lt;executions>
					&lt;execution>
						&lt;id>make-assembly&lt;/id>
						&lt;phase>package&lt;/phase>
					&lt;/execution>
				&lt;/executions>
			&lt;/plugin>
//2:distribution.xml配置
&lt;assembly>
    &lt;id>dist&lt;/id>
    &lt;formats>
        &lt;format>zip&lt;/format>
    &lt;/formats>
    &lt;includeBaseDirectory>false&lt;/includeBaseDirectory>
    &lt;dependencySets>
        &lt;dependencySet>
            &lt;outputDirectory>lib&lt;/outputDirectory>
            &lt;scope>runtime&lt;/scope>
        &lt;/dependencySet>
    &lt;/dependencySets>
&lt;/assembly>
</pre>
<p>6、java doc,java source</p>
<pre class="prettyprint">
            &lt;plugin>
                &lt;groupId>org.apache.maven.plugins&lt;/groupId>
                &lt;artifactId>maven-javadoc-plugin&lt;/artifactId>
                &lt;version>2.7&lt;/version>
                &lt;executions>
                    &lt;execution>
                        &lt;id>attach-javadocs&lt;/id>
                        &lt;goals>
                            &lt;goal>jar&lt;/goal>
                        &lt;/goals>
                    &lt;/execution>
                &lt;/executions>
            &lt;/plugin>
            &lt;plugin>
                &lt;groupId>org.apache.maven.plugins&lt;/groupId>
                &lt;artifactId>maven-source-plugin&lt;/artifactId>
                &lt;version>2.1.2&lt;/version>
                &lt;executions>
                    &lt;execution>
                        &lt;id>attach-sources&lt;/id>
                        &lt;goals>
                            &lt;goal>jar-no-fork&lt;/goal>
                        &lt;/goals>
                    &lt;/execution>
                &lt;/executions>
            &lt;/plugin>
</pre>
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script><br />
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/code/%e5%b8%b8%e7%94%a8%e7%9a%84maven%e9%85%8d%e7%bd%ae/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>BitFlagUtil</title>
		<link>http://www.tianjiaguo.com/code/bitflagutil/</link>
		<comments>http://www.tianjiaguo.com/code/bitflagutil/#comments</comments>
		<pubDate>Mon, 07 May 2012 12:17:57 +0000</pubDate>
		<dc:creator>admin</dc:creator>
				<category><![CDATA[Code]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1298</guid>
		<description><![CDATA[从FlexSDK的代码中修改，用于传递多个bool类型。 /** * 12-5-7 下午8:18 * * @author jiaguotian Copyright 2012 Tianjiaguo.com Inc. All Rights Reserved. */ public class BitFlagUtil { public static boolean isSet(long flags, long flagMask) { return flagMask == (flags &#038; flagMask); } public static long update(long &#8230; <a href="http://www.tianjiaguo.com/code/bitflagutil/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>从FlexSDK的代码中修改，用于传递多个bool类型。</p>
<pre class="prettyprint">
/**
 * 12-5-7 下午8:18
 *
 * @author jiaguotian Copyright 2012 Tianjiaguo.com Inc. All Rights Reserved.
 */
public class BitFlagUtil {
    public static boolean isSet(long flags, long flagMask) {
        return flagMask == (flags &#038; flagMask);
    }

    public static long update(long flags, long flagMask, boolean value) {
        if (value) {
            if ((flags &#038; flagMask) == flagMask) {
                return flags;
            }
            flags |= flagMask;
        } else {
            if ((flags &#038; flagMask) == 0) {
                return flags;
            }
            flags &#038;= ~flagMask;
        }
        return flags;
    }
}
</pre>
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script><br />
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/code/bitflagutil/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>自增的主键的生成策略和分库分表的探索</title>
		<link>http://www.tianjiaguo.com/tech/%e8%87%aa%e5%a2%9e%e7%9a%84%e4%b8%bb%e9%94%ae%e7%9a%84%e7%94%9f%e6%88%90%e7%ad%96%e7%95%a5%e5%92%8c%e5%88%86%e5%ba%93%e5%88%86%e8%a1%a8%e7%9a%84%e6%8e%a2%e7%b4%a2/</link>
		<comments>http://www.tianjiaguo.com/tech/%e8%87%aa%e5%a2%9e%e7%9a%84%e4%b8%bb%e9%94%ae%e7%9a%84%e7%94%9f%e6%88%90%e7%ad%96%e7%95%a5%e5%92%8c%e5%88%86%e5%ba%93%e5%88%86%e8%a1%a8%e7%9a%84%e6%8e%a2%e7%b4%a2/#comments</comments>
		<pubDate>Sun, 18 Mar 2012 09:43:54 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[SQL]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[主键生成策略]]></category>
		<category><![CDATA[分库]]></category>
		<category><![CDATA[分表]]></category>
		<category><![CDATA[序列]]></category>
		<category><![CDATA[数据库设计]]></category>
		<category><![CDATA[自增]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1232</guid>
		<description><![CDATA[对于一个每天大量ＰＶ的站点来说，数据库无疑会有相当高的负载，通常我们会采用主从复制、分库、分表、分区的方式来解决单台机器访问更新负载高的问题，最大限度提高应用读取数据速度和并发量。分表分区有很多种方法，比如我们可以这么去做。 private long getDbIndex(long tableIndex) { return (tableIndex >> this.dbSeed); } private long getTableIndex(long id) { return (id >> tableSeed); } private String getTable(long tableIndex) { if (tableIndex == 0) { return this.tableName; } else { return this.tableName + "_" &#8230; <a href="http://www.tianjiaguo.com/tech/%e8%87%aa%e5%a2%9e%e7%9a%84%e4%b8%bb%e9%94%ae%e7%9a%84%e7%94%9f%e6%88%90%e7%ad%96%e7%95%a5%e5%92%8c%e5%88%86%e5%ba%93%e5%88%86%e8%a1%a8%e7%9a%84%e6%8e%a2%e7%b4%a2/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>对于一个每天大量ＰＶ的站点来说，数据库无疑会有相当高的负载，通常我们会采用主从复制、分库、分表、分区的方式来解决单台机器访问更新负载高的问题，最大限度提高应用读取数据速度和并发量。分表分区有很多种方法，比如我们可以这么去做。</p>
<pre class="prettyprint">
private long getDbIndex(long tableIndex) {
    return (tableIndex >> this.dbSeed);
}

private long getTableIndex(long id) {
    return (id >> tableSeed);
}

private String getTable(long tableIndex) {
    if (tableIndex == 0) {
        return this.tableName;
    } else {
        return this.tableName + "_" + tableIndex;
    }
}
...
// 查询操作
long tableIndex = getTableIndex(id);
long dbIndex = getDbIndex(tableIndex);
String sql = String.format("SELECT `%s` FROM `%s` WHERE `id` = ?", COLUMNS, getTable(tableIndex));
....
</pre>
<pre class="prettyprint">
&lt;bean id="testDbDao" class="com.tianjiaguo.site.dao.impl.TestDbDaoImpl">
    &lt;property name="tableSeed" value="21"/>
    &lt;property name="dbSeed" value="5"/>
    &lt;property name="tableName" value="TEST_DB"/>
    &lt;property name="cache" ref="testDbCache"/>
    &lt;property name="incrementer" ref="maxValueIncrementer"/>
    &lt;property name="dataSources">
        &lt;list>
            &lt;ref bean="testDataSource1"/>
            &lt;ref bean="testDataSource2"/>
        &lt;/list>
    &lt;/property>
&lt;/bean>
</pre>
<p>在getTableIndex中我们把id向右移位tableSeed位（２１），这样我们就能把大约2^21的数据放到第一张表（TEST_DB）中，第二个2^21的数据放到第二张表（TEST_DB_2）中，依次下去，随着我们的数据量的增多我们会有越来越多的表（TEST_DB_3,TEST_DB_4，TEST_DB_5&#8230;.）<br />
对于dataSources我们采用类似的做法，把前2^5的表放到第一个库（testDataSource1）中，第二个2^5的表放到第二个库（testDataSource1）中，依次类推。</p>
<p>用上面的程序我们把分表和分库做到灵活的扩展（还是不够灵活，不够随意，但系统就得这样，用一致的方法去解决变化），数据库分区和分表了，表的主键的生成就有问题了，因为我们必须保证所有的分表中的主键值都是不重复的。对于这个我们有许多的方案，像memcached有inc的操作可以使用等等，但我们都清楚memcached去生成主键也是相当的不靠谱（它里面放的数据可能会被其它的数据给LRU掉或者ＤＯＷＮ机失去），它里面的数据丢失，我们就要从数据库中加载最大的ＩＤ，可怕的是我们可能有Ｎ多的表，查询代价比较的大。（如果基于上面的分表的策略的话，我们只需要查询编号最大的表的那个最大的ＩＤ也还是勉强可以接受的），介绍一下一种主键的生成策略吧，参考了Spring的MySQLMaxValueIncrementer的代码（Spring同时还提供其它数据库的主键生成类），添加了step的支持（主键不连续）。</p>
<pre class="prettyprint">
/**
 * 12-2-9 上午10:48
 * 这个生成器生成的id是不连续的
 * @Update 2012.05.13 当上一次的step和这次的一样时，重新生成一个
 *
 * @author jiaguotian All Rights Reserved.
 */
public class StepMaxValueIncrementer implements DataFieldMaxValueIncrementer, InitializingBean {
    private static final Logger LOGGER = LoggerFactory.getLogger(StepMaxValueIncrementer.class);

    private DataSource dataSource;
    private DataFieldMaxValueIncrementer incrementer;
    private String tableName;
    private String columnName;
    private int step = 10;

    protected int paddingLength = 0;

    @Override
    public void afterPropertiesSet() throws Exception {
        MaxValueIncrementer incrementer = new MaxValueIncrementer();
        incrementer.setIncrementerName(this.tableName);
        incrementer.setColumnName(this.columnName);
        incrementer.setCacheSize(this.step);
        incrementer.setDataSource(this.dataSource);
        incrementer.afterPropertiesSet();

        this.incrementer = incrementer;
    }

    @Override
    public int nextIntValue() throws DataAccessException {
        return (int) nextLongValue();
    }

    @Override
    public long nextLongValue() throws DataAccessException {
        return this.incrementer.nextLongValue();
    }

    @Override
    public String nextStringValue() throws DataAccessException {
        String s = Long.toString(nextLongValue());
        int len = s.length();
        if (len &lt; this.paddingLength) {
            StringBuilder sb = new StringBuilder(this.paddingLength);
            for (int i = 0; i &lt; this.paddingLength - len; i++) {
                sb.append('0');
            }
            sb.append(s);
            s = sb.toString();
        }
        return s;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setColumnName(String columnName) {
        this.columnName = columnName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public void setStep(int step) {
        this.step = step;
    }

    private static class MaxValueIncrementer extends AbstractColumnMaxValueIncrementer {
        /**
         * The SQL string for retrieving the new sequence value
         */
        private static final String VALUE_SQL = "select last_insert_id()";

        /**
         * The next id to serve
         */
        private long nextId = 0;

        /**
         * The max id to serve
         */
        private long maxId = 0;

        public static final int[] inc_seq = {3, 5, 7, 11, 13};

        @Override
        public void afterPropertiesSet() {
            super.afterPropertiesSet();
            for (int seq : inc_seq) {
                if (super.getCacheSize() &lt;= seq + 10) {
                    throw new IllegalArgumentException("Property 'step' is too small");
                }
            }
        }

        /**
         * Default constructor for bean property style usage.
         *
         * @see #setDataSource
         * @see #setIncrementerName
         * @see #setColumnName
         */
        public MaxValueIncrementer() {
        }

        /**
         * Convenience constructor.
         *
         * @param dataSource      the DataSource to use
         * @param incrementerName the name of the sequence/table to use
         * @param columnName      the name of the column in the sequence table to use
         */
        public MaxValueIncrementer(DataSource dataSource, String incrementerName, String columnName) {
            super(dataSource, incrementerName, columnName);
        }

        @Override
        protected synchronized long getNextKey() throws DataAccessException {
            // nextId ++ 一直到为maxId，所以当nextId大于或等于maxId后需要重新取得新的id
            int seq = (int) (System.nanoTime() % inc_seq.length);
            if (seq == this.preSeq) {
                seq = ((seq + 1) &lt;&lt; 2) % inc_seq.length;
                if (seq == this.preSeq) {
                    seq = ((seq + 2) &lt;&lt; 2) % inc_seq.length;
                    if (seq == this.preSeq) {
                        System.out.println(seq);
                    }
                }
            }
            this.nextId += inc_seq[seq];
            if (this.maxId &lt;= this.nextId) {
                /*
                * Need to use straight JDBC code because we need to make sure that the insert and select
                * are performed on the same connection (otherwise we can't be sure that last_insert_id()
                * returned the correct value)
                */
                Connection con = DataSourceUtils.getConnection(getDataSource());
                Statement stmt = null;
                try {
                    stmt = con.createStatement();
                    DataSourceUtils.applyTransactionTimeout(stmt, getDataSource());
                    // Increment the sequence column...
                    String columnName = getColumnName();
                    stmt.executeUpdate("update " + getIncrementerName() + " set " + columnName +
                            " = last_insert_id(" + columnName + " + " + getCacheSize() + ")");
                    // Retrieve the new max of the sequence column...
                    ResultSet rs = stmt.executeQuery(VALUE_SQL);
                    try {
                        if (!rs.next()) {
                            throw new DataAccessResourceFailureException("last_insert_id() failed after executing an update");
                        }
                        this.maxId = rs.getLong(1);
                    } finally {
                        JdbcUtils.closeResultSet(rs);
                    }
                    this.nextId = this.maxId - getCacheSize() + inc_seq[seq];
                } catch (SQLException ex) {
                    throw new DataAccessResourceFailureException("Could not obtain last_insert_id()", ex);
                } finally {
                    JdbcUtils.closeStatement(stmt);
                    DataSourceUtils.releaseConnection(con, getDataSource());
                }
            }
            this.preSeq = seq;
            return this.nextId;
        }
    }
}
</pre>
<p>在spring的xml文件中按如下方法配置:</p>
<pre class="prettyprint">
&lt;bean id="maxValueIncrementer" class="com.tianjiaguo.site.jdbc.support.StepMaxValueIncrementer">
    &lt;property name="tableName" value="TEST_DB_SEQ"/>
    &lt;property name="columnName" value="id"/>
    &lt;property name="step" value="1000"/>
    &lt;property name="dataSource">
        &lt;bean class="org.logicalcobwebs.proxool.ProxoolDataSource">
            &lt;property name="driver" value="com.mysql.jdbc.Driver"/>
            &lt;property name="driverUrl" value="jdbc:mysql://xxxxxxxxxx:xxx/xxx?useUnicode=true&amp;characterEncoding=UTF-8"/>
            &lt;property name="user" value="xx"/>
            &lt;property name="password" value="xx"/>
            &lt;property name="maximumConnectionCount" value="30"/>
        &lt;/bean>
    &lt;/property>
&lt;/bean>
</pre>
<p>为了使用它我们需要一个额外的主键生成序列表（执久化最大的主键值，生成下一个主键），可执行下面的语句去创建这样的一个表：</p>
<pre class="prettyprint">
CREATE TABLE 'TEST_DB_SEQ' (
  `id` bigint(21) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8;
INSERT INTO `TEST_DB_SEQ` VALUES(10000);
</pre>
<p>在程序中使用只需要调用它的next的接口就行了</p>
<pre class="prettyprint">
long id = this.incrementer.nextLongValue();
</pre>
<p>我们这个生成器第一次使用时因为maxId和nextId相等，会让数据库给生成一个maxId（主键值＝主键值＋１０００；return 主键值；），程序把这个主键值保存起来，然后返回nextId的值（this.nextId = this.maxId &#8211; getCacheSize() + inc_seq[(int) (System.nanoTime() % inc_seq.length)];），当系统调用nextLong方法请求主键时，我们基于内存中缓存的数据得到候选nextId，然后比较它和当前maxId的值，如发现nextId大于或等于maxId时我们需要重新从数据库中申请id。<br />
我们每次从数据库中申请1000个id，如果并发访问量很大可以调大这个数值。</p>
<p>基于这种方式生成主键速度相当的快，而且我们不会因为机器down机而丢失主键最大值的数据。<br />
主键生成序列表只有一个字段一条记录，数据量很小所以更新和查询这张表是极快的。<br />
主键生成序列表并发访问量比较大，采用MyISMA能够提供较快的存储和检索。innoDB提供的高级的功能在这里不是太需要。</p>
<p>&#8211;EOF&#8211;
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/tech/%e8%87%aa%e5%a2%9e%e7%9a%84%e4%b8%bb%e9%94%ae%e7%9a%84%e7%94%9f%e6%88%90%e7%ad%96%e7%95%a5%e5%92%8c%e5%88%86%e5%ba%93%e5%88%86%e8%a1%a8%e7%9a%84%e6%8e%a2%e7%b4%a2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>flex定义事件的例子</title>
		<link>http://www.tianjiaguo.com/tech/flex%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9a%84%e4%be%8b%e5%ad%90/</link>
		<comments>http://www.tianjiaguo.com/tech/flex%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9a%84%e4%be%8b%e5%ad%90/#comments</comments>
		<pubDate>Tue, 13 Mar 2012 12:31:32 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[ActionScript]]></category>
		<category><![CDATA[技术]]></category>
		<category><![CDATA[actionscript]]></category>
		<category><![CDATA[flex]]></category>
		<category><![CDATA[事件]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1176</guid>
		<description><![CDATA[Flex程序中处处可见事件，几乎每一个控件都集成了大量的事件，控件状态的变化会触发的事件，然后我们通过监听事件就能做很多有趣的事情。事件很有用很强大，在flex的编程中可以想象没有事件的日子会是多么灰暗，但我们实际开发的过程中控件提供的事件并不总是够用，在开发的过程中我们常常会自己定义一些事件。自定义事件和自定义皮肤一样是开发者必须掌握的技能。 下面我们看一下如何定义一个简单的事件，然后我们还会看到如何接收我们自定义的事件。 定义事件 package com.tianjiaguo.test.events { import flash.events.Event; public class HelloEvent extends Event { public static const HELLO_START:String = 'helloStart'; public static const HELLO_END:String = 'helloEnd'; public function HelloEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); } &#8230; <a href="http://www.tianjiaguo.com/tech/flex%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9a%84%e4%be%8b%e5%ad%90/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Flex程序中处处可见事件，几乎每一个控件都集成了大量的事件，控件状态的变化会触发的事件，然后我们通过监听事件就能做很多有趣的事情。事件很有用很强大，在flex的编程中可以想象没有事件的日子会是多么灰暗，但我们实际开发的过程中控件提供的事件并不总是够用，在开发的过程中我们常常会自己定义一些事件。自定义事件和自定义皮肤一样是开发者必须掌握的技能。<br />
下面我们看一下如何定义一个简单的事件，然后我们还会看到如何接收我们自定义的事件。<br />
定义事件</p>
<pre class="prettyprint">
package com.tianjiaguo.test.events {
import flash.events.Event;

public class HelloEvent extends Event {
    public static const HELLO_START:String = 'helloStart';
    public static const HELLO_END:String = 'helloEnd';

    public function HelloEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
        super(type, bubbles, cancelable);
    }

    override public function clone():Event {
        return new HelloEvent(type, bubbles, cancelable);
    }
}
}
</pre>
<p>现在我们就有了一个HelloEvent的事件类，下面我们看看如何去使用它。<br />
首先我们要在使用它的class上声明它，声明告诉系统，在这个class中我会使用到那些event</p>
<pre class="prettyprint">
[Event(name="helloStart", type="com.tianjiaguo.test.events.HelloEvent")]
[Event(name="helloEnd", type="com.tianjiaguo.test.events.HelloEvent")]
public class HelloEventTest extends ....
</pre>
<p>声明之后我们需要在合适的位置注册这个事件的监听，比如我们可以组件add的时候注册</p>
<pre class="prettyprint">
/**
 *  @private
 */
override protected function partAdded(partName:String, instance:Object):void {
     super.partAdded(partName, instance);
     if (instance == helloObj) {
          this.helloObj.addEventListener(HelloEvent.HELLO_START, helloObj_helloStartHandler);
          this.helloObj.addEventListener(HelloEvent.HELLO_END, helloObj_helloEndHandler);
     }
     ...
}
</pre>
<p>在上面的程序中我们使用了还没有定义的helloObj_helloStartHandler和helloObj_helloEndHandler方法，我们马上就看看如何去实现它们</p>
<pre class="prettyprint">
private function helloObj_helloStartHandler(event:HelloEvent):void {
     // do something
}
private function helloObj_helloEndHandler(event:HelloEvent):void {
     // do something
}
</pre>
<p>如果我们要在这个事件行后继续抛出事件，可在相应事件的handler方法后把这个事件抛出，这个java处理异常是类似的</p>
<pre class="prettyprint">
dispatchEvent(event);
// 或
this.helloObj.dispatchEvent(event);
</pre>
<p>事件在控件add的时候添加到控件上，那么我们就需要在这个控件移出的时候remove掉它</p>
<pre class="prettyprint">
/**
 *  @private
 */
override protected function partRemoved(partName:String, instance:Object):void {
    super.partRemoved(partName, instance);
    if (instance == helloObj) {
        this.helloObj.removeEventListener(HelloEvent.HELLO_START, helloObj_helloStartHandler);
        this.helloObj.removeEventListener(HelloEvent.HELLO_END, helloObj_helloEndHandler);
    }
    ...
}
</pre>
<p>使用flex实现一个事件是如此的简单，所以大家在开发相应的程序时尽量使用事件这个机制吧，通过事件我们可以把大量的重复的功能抽取到控件中，事件使我们程序猿的生活不再灰暗。</p>
<p>&#8211;EOF&#8211;
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/tech/flex%e5%ae%9a%e4%b9%89%e4%ba%8b%e4%bb%b6%e7%9a%84%e4%be%8b%e5%ad%90/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>通过solr得到字串的关键词的代码</title>
		<link>http://www.tianjiaguo.com/tech/%e9%80%9a%e8%bf%87solr%e5%be%97%e5%88%b0%e5%ad%97%e4%b8%b2%e7%9a%84%e5%85%b3%e9%94%ae%e8%af%8d%e7%9a%84%e4%bb%a3%e7%a0%81/</link>
		<comments>http://www.tianjiaguo.com/tech/%e9%80%9a%e8%bf%87solr%e5%be%97%e5%88%b0%e5%ad%97%e4%b8%b2%e7%9a%84%e5%85%b3%e9%94%ae%e8%af%8d%e7%9a%84%e4%bb%a3%e7%a0%81/#comments</comments>
		<pubDate>Sun, 11 Mar 2012 16:40:34 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[lucence]]></category>
		<category><![CDATA[MMSEG]]></category>
		<category><![CDATA[solr]]></category>
		<category><![CDATA[中文分词]]></category>
		<category><![CDATA[关键字]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1163</guid>
		<description><![CDATA[有时我们会希望得到一段文本中所有的关键词，自己手动的去写这样一个程序是不现实的，因为要处理中文分词的问题，中文分词已经有许多实现我们可以拿过来用，老的比如苞丁解牛（作者已经很久不更新了，而且代码很复杂相当的看不懂），还有如mmseg分词算法，还有中科院的一个分词的算法，算法很多，我们直接可以拿过来用，比如我们可以定义这样的一个接口，然后针对各个分词算法去做相应的实现，但这带来一个问题，这些分词算法我们自己去写相应的实现有些复杂而且并不能通用，我们会希望找到一种方式，当有一种新的更好的分词算法我们要用时，不需要做太多的工作。我们的要求实在是不多，能实现么？of course，sure我们用solr这个工具，它通过简单的配置就能支持相当多的分词算法。 我们先来看看如何通过solr的接口实现这个需求吧。下面的所有接口都是和分词无关的，只要我们修改solr相应的分词算法，马上就可以使用新的分词算法去提取关键字，It is so 简单。 如何配置solr在附录中“solr搜索组件的配置”的文章中有写，需要的同学可去查看。 solr在servlet中使用时我们需要配置相应的SolrDispatchFilter，它会在request对象中存放一个SolrCore对象，我们可以通过如下的代码得到它（注意，这个是针对单solr的获取方法，如果你使用的是multi core，得用另一种方法得到，具体的不细说明了，大家看solr相应的文档就能知道如何获取）。 SolrCore core = (SolrCore) request.getAttribute("org.apache.solr.SolrCore"); if (core == null) { core = SolrCore.getSolrCore(); } 我们得到了solrCore之后，执行下面的方法就能得到相应的关键字。 private String[] getAnalyzerKeyWords(SolrCore core, String name, String val) throws IOException { Assert.notNull(core, "core required"); IndexSchema schema &#8230; <a href="http://www.tianjiaguo.com/tech/%e9%80%9a%e8%bf%87solr%e5%be%97%e5%88%b0%e5%ad%97%e4%b8%b2%e7%9a%84%e5%85%b3%e9%94%ae%e8%af%8d%e7%9a%84%e4%bb%a3%e7%a0%81/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>有时我们会希望得到一段文本中所有的关键词，自己手动的去写这样一个程序是不现实的，因为要处理中文分词的问题，中文分词已经有许多实现我们可以拿过来用，老的比如苞丁解牛（作者已经很久不更新了，而且代码很复杂相当的看不懂），还有如mmseg分词算法，还有中科院的一个分词的算法，算法很多，我们直接可以拿过来用，比如我们可以定义这样的一个接口，然后针对各个分词算法去做相应的实现，但这带来一个问题，这些分词算法我们自己去写相应的实现有些复杂而且并不能通用，我们会希望找到一种方式，当有一种新的更好的分词算法我们要用时，不需要做太多的工作。我们的要求实在是不多，能实现么？of course，sure我们用solr这个工具，它通过简单的配置就能支持相当多的分词算法。<br />
我们先来看看如何通过solr的接口实现这个需求吧。下面的所有接口都是和分词无关的，只要我们修改solr相应的分词算法，马上就可以使用新的分词算法去提取关键字，It is so 简单。<br />
如何配置solr在附录中“solr搜索组件的配置”的文章中有写，需要的同学可去查看。<br />
solr在servlet中使用时我们需要配置相应的SolrDispatchFilter，它会在request对象中存放一个SolrCore对象，我们可以通过如下的代码得到它（注意，这个是针对单solr的获取方法，如果你使用的是multi core，得用另一种方法得到，具体的不细说明了，大家看solr相应的文档就能知道如何获取）。</p>
<pre class="prettyprint">
SolrCore core = (SolrCore) request.getAttribute("org.apache.solr.SolrCore");
if (core == null) {
    core = SolrCore.getSolrCore();
}
</pre>
<p>我们得到了solrCore之后，执行下面的方法就能得到相应的关键字。</p>
<pre class="prettyprint">
private String[] getAnalyzerKeyWords(SolrCore core, String name, String val) throws IOException {
    Assert.notNull(core, "core required");

    IndexSchema schema = core.getSchema();
    FieldType ft = schema.getFieldTypes().get(name);
    Analyzer analyzer = ft.getAnalyzer();
    Set&lt;String> searchKeys = new LinkedHashSet&lt;String>();
    if (analyzer instanceof TokenizerChain) {
        TokenizerChain tchain = (TokenizerChain) analyzer;
        TokenizerFactory tfac = tchain.getTokenizerFactory();
        TokenFilterFactory[] filtfacs = tchain.getTokenFilterFactories();
        TokenStream tstream = tfac.create(tchain.charStream(new StringReader(val)));
        List&lt;Token> tokens = null;
        tokens = getTokens(tstream);
        for (TokenFilterFactory filtfac : filtfacs) {
            final Iterator&lt;Token> iter = tokens.iterator();
            tstream = filtfac.create(new TokenStream() {
                public Token next() throws IOException {
                    return iter.hasNext() ? iter.next() : null;
                }
            });
            tokens = getTokens(tstream);
            if (tokens != null) {
                for (Token token : tokens) {
                    String term = token.term().toString();
                    if (!searchKeys.contains(term)) {
                        searchKeys.add(term);
                    }
                }
            }
        }
    } else {
        SchemaField field = new SchemaField("fakefieldoftype:" + name, ft);
        TokenStream tstream = analyzer.reusableTokenStream(field.getName(), new StringReader(val));
        tstream.reset();
        List&lt;Token> tokens = null;
        tokens = getTokens(tstream);
        if (tokens != null) {
            for (Token token : tokens) {
                String term = token.term().toString();
                if (!searchKeys.contains(term)) {
                    searchKeys.add(term);
                }
            }
        }
    }
    return searchKeys.toArray(new String[0]);
}
</pre>
<p>代码很简单，扩展也很容易我们的想法都得到了实现，基于lucence的solr在执行分词的操作时用的时间很少，忘了当时的评测结果了。</p>
<p>当然我们无端的在项目中引入了solr这也是相当讨厌的，但如果我们项目中本来就有相应的搜索需求，引入这种方式的关键词提取算法不是正合我们意么，扩展性有了，性能也够可以，为什么不用呢？对吧</p>
<p>参考资料：<br />
<a target="null" href="http://technology.chtsai.org/mmseg/">MMSEG算法原文</a><br />
<a target="null" href="http://lucene.apache.org/solr/">http://lucene.apache.org/solr/</a><br />
<a target="null" href="http://lucene.apache.org/">http://lucene.apache.org/</a><br />
<a target="null" href="http://www.tianjiaguo.com/tech/solr%E6%90%9C%E7%B4%A2%E7%BB%84%E4%BB%B6%E7%9A%84%E9%85%8D%E7%BD%AE/">田加国的博客：solr搜索组件的配置</a></p>
<p>&#8211;EOF&#8211;
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script></p>
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/tech/%e9%80%9a%e8%bf%87solr%e5%be%97%e5%88%b0%e5%ad%97%e4%b8%b2%e7%9a%84%e5%85%b3%e9%94%ae%e8%af%8d%e7%9a%84%e4%bb%a3%e7%a0%81/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>安装windows8系统</title>
		<link>http://www.tianjiaguo.com/digital/1153/</link>
		<comments>http://www.tianjiaguo.com/digital/1153/#comments</comments>
		<pubDate>Sat, 10 Mar 2012 08:53:33 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[Digital]]></category>
		<category><![CDATA[window8]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1153</guid>
		<description><![CDATA[安装过程相当悲剧，一直都在报“缺少电脑所需的DVD驱动程序”的错误，而同样的文件在ffy和zgh的电脑上安装都是没有问题的。反复尝试了模式1和模式2，都不行。网上有人说在命令行中执行source/setup.exe，然后就不会报错，经我亲自试验这种方法也是不成的。 下面说一下我是如何解决这个问题的。 先说原因吧，提示找不到驱动程序是安装程序加载不到硬盘。原来我是把安装文件解压到D盘的，而D盘安装程序无法识别，就造成了之前的那个错误。找到原因就好解决了，C盘不是可以识别么，于是我把它们解压到C盘的根目录，然后再执行安装，奇迹出现了，可以正常安装了。然后一切顺利，呵呵 发现一个问题，D盘和E盘安装好的系统中无法识别。 把KEY文件列在下面，方便大家安装使用吧。 windows8：6RH4V-HNTWC-JQKG8-RFR3R-36498 PS： 硬盘无法识别的问题已经处理好了，无法识别的原因是之前用PQ无损分区把硬盘的参数表弄坏了，使用DiskGenius这个软件可以修复它。 又出现个问题，光驱占用了D盘的符号，分区的盘符从E开始，修改的方法极为简单，先禁用光驱，然后在cmd中输入mmc打开控制台，在其中添加磁盘管理，在磁盘管理中修改盘符为D，E。完了后再把光驱启用，则光驱盘符会从F开始。完成。]]></description>
			<content:encoded><![CDATA[<p>安装过程相当悲剧，一直都在报“缺少电脑所需的DVD驱动程序”的错误，而同样的文件在ffy和zgh的电脑上安装都是没有问题的。反复尝试了模式1和模式2，都不行。网上有人说在命令行中执行source/setup.exe，然后就不会报错，经我亲自试验这种方法也是不成的。<br />
下面说一下我是如何解决这个问题的。<br />
先说原因吧，提示找不到驱动程序是安装程序加载不到硬盘。原来我是把安装文件解压到D盘的，而D盘安装程序无法识别，就造成了之前的那个错误。找到原因就好解决了，C盘不是可以识别么，于是我把它们解压到C盘的根目录，然后再执行安装，奇迹出现了，可以正常安装了。然后一切顺利，呵呵</p>
<p>发现一个问题，D盘和E盘安装好的系统中无法识别。</p>
<p>把KEY文件列在下面，方便大家安装使用吧。<br />
windows8：6RH4V-HNTWC-JQKG8-RFR3R-36498</p>
<p>PS：<br />
硬盘无法识别的问题已经处理好了，无法识别的原因是之前用PQ无损分区把硬盘的参数表弄坏了，使用DiskGenius这个软件可以修复它。</p>
<p>又出现个问题，光驱占用了D盘的符号，分区的盘符从E开始，修改的方法极为简单，先禁用光驱，然后在cmd中输入mmc打开控制台，在其中添加磁盘管理，在磁盘管理中修改盘符为D，E。完了后再把光驱启用，则光驱盘符会从F开始。完成。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/digital/1153/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>spring mvc源代码</title>
		<link>http://www.tianjiaguo.com/tech/spring-mvc%e6%ba%90%e4%bb%a3%e7%a0%81%e7%9a%84%e5%a6%99%e7%94%a8/</link>
		<comments>http://www.tianjiaguo.com/tech/spring-mvc%e6%ba%90%e4%bb%a3%e7%a0%81%e7%9a%84%e5%a6%99%e7%94%a8/#comments</comments>
		<pubDate>Sat, 03 Mar 2012 03:29:25 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[mvc]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[spring mvc]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1127</guid>
		<description><![CDATA[spring是很完善的一套代码，无论是从代码结构还是层次逻辑上，来说都是如此，阅读它的代码可以有很多的启示。就说其mvn的代码，我们可以用它来做我们自己的mvc，用来映射uri到相应的处理方法。我们微博旧的服务器端架构中基于netty实现了几组twitterServer，它处理uri的方法是if &#8230; else if &#8230; else if &#8230; else&#8230;，这样带来的问题不仅仅是代码的冗长，可读性的降低。基于if判断来做mvn我们无法得到一个良好的代码结构，代码散落到各处，重构就不是一件容易的事了。（注：netty实现的server已经要被新的rpc框架代替，新的框架基于rmi。） 如果有了spring mvc的代码，我们又可以怎么去做呢？当然netty是没有我们熟悉的request、response等对象的，所以我们无法直接使用spring mvc去做。我们可以从spring mvc的源代码中提取我们所需。 在这里我们不讨论如何去扩展netty实现灵活的转发配置，它的代码量很多，我们需要实现自己的IResponse、IController、IDispatcher、IHandlerMapping、IMethodNameResolver，为了保证rpc的低耦合我们还要把netty的实现做成可随时替换的形式，这不是一个简单的任务，因此一个blog无以说明的清楚，但下面我会讲一个其它的实现的例子，它足够精简，很有代表性。 用了spring的代码和思想，自然要把spring的版权信息列出： /* * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use &#8230; <a href="http://www.tianjiaguo.com/tech/spring-mvc%e6%ba%90%e4%bb%a3%e7%a0%81%e7%9a%84%e5%a6%99%e7%94%a8/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>spring是很完善的一套代码，无论是从代码结构还是层次逻辑上，来说都是如此，阅读它的代码可以有很多的启示。就说其mvn的代码，我们可以用它来做我们自己的mvc，用来映射uri到相应的处理方法。我们微博旧的服务器端架构中基于netty实现了几组twitterServer，它处理uri的方法是if &#8230; else if &#8230; else if &#8230; else&#8230;，这样带来的问题不仅仅是代码的冗长，可读性的降低。基于if判断来做mvn我们无法得到一个良好的代码结构，代码散落到各处，重构就不是一件容易的事了。（注：netty实现的server已经要被新的rpc框架代替，新的框架基于rmi。）<br />
如果有了spring mvc的代码，我们又可以怎么去做呢？当然netty是没有我们熟悉的request、response等对象的，所以我们无法直接使用spring mvc去做。我们可以从spring mvc的源代码中提取我们所需。</p>
<p>在这里我们不讨论如何去扩展netty实现灵活的转发配置，它的代码量很多，我们需要实现自己的IResponse、IController、IDispatcher、IHandlerMapping、IMethodNameResolver，为了保证rpc的低耦合我们还要把netty的实现做成可随时替换的形式，这不是一个简单的任务，因此一个blog无以说明的清楚，但下面我会讲一个其它的实现的例子，它足够精简，很有代表性。</p>
<p>用了spring的代码和思想，自然要把spring的版权信息列出：</p>
<pre class="prettyprint">
/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
</pre>
<p>借用它的思想。我们实现了一个处理针对url的处理流程，我们用它的代码去实现抓取网站信息，对于这个实现可配置的原因是，不同网站对抓取有限制，比如图片网站可能会限制外链下载，我们针对那样的网站需要做特殊的处理。我们的实现的代码结构见下。<br />
worker/impl/mvc/impl/AbstractMultiActionProcessor.java<br />
worker/impl/mvc/impl/AbstractSimpleProcessor.java<br />
worker/impl/mvc/impl/ProcessorMappingImpl.java<br />
worker/impl/mvc/IMethodResolver.java<br />
worker/impl/mvc/IProcessor.java<br />
worker/impl/mvc/IProcessorMapping.java<br />
worker/impl/mvc/ProcessorDispatcher.java</p>
<p>ProcessorDispatcher，中心控制类，所有的抓取请求都会由它做分发处理，它先根据配置找到processor，然后判断processor类型，如果是简单processor则直接调用processor的execute方法，否则调用processor的getHandlerMethod方法取得需要执行的method，根据反射技术，执行method。</p>
<pre class="prettyprint">
public class ProcessorDispatcher {
    private IProcessorMapping processorMapping;

    public Object dispatcher(String url) throws Exception {
        IProcessor processor = (IProcessor) this.processorMapping.getHandlerInternal(url);
        if (processor instanceof AbstractMultiActionProcessor) {
            Method method = ((AbstractMultiActionProcessor) processor).getHandlerMethod(url);
            return method.invoke(processor, url);
        } else if (processor instanceof AbstractSimpleProcessor) {
            return ((AbstractSimpleProcessor) processor).execute(url);
        }
        return null;
    }

    public void setProcessorMapping(IProcessorMapping processorMapping) {
        this.processorMapping = processorMapping;
    }
}
</pre>
<p>我把接口的定义列在下面，具体实现不做细论。</p>
<pre class="prettyprint">
public interface IProcessorMapping {
    public Object getHandlerInternal(String url);
}
public interface IProcessor {
}
public interface IMethodResolver {
}
</pre>
<p>ProcessorMappingImpl的实现（buildPathExposingHandler方法的实现请参考spring的代码自己实现，它实现的功能是根据url从配置中寻找最佳的处理handler。）：</p>
<pre class="prettyprint">
    @Override
    public Object getHandlerInternal(String url) {
        Object handler = this.buildPathExposingHandler(url);
        if (handler == null) {
            Object rawHandler = defaultProcessor;
            if (rawHandler != null) {
                if (rawHandler instanceof String) {
                    String handlerName = (String) rawHandler;
                    handler = SpringBeanFactory.getBean(handlerName);
                } else {
                    handler = rawHandler;
                }
            }
        }
        return handler;
    }
</pre>
<p>下面就是实现抽象controller了。</p>
<pre class="prettyprint">
/**
 * 12-2-26 上午1:00
 *
 * @author jiaguotian Copyright 2012 Sohu.com Inc. All Rights Reserved.
 */
public abstract class AbstractSimpleProcessor implements IProcessor {
    public abstract Object execute(String url) throws Exception;
}
/**
 * 12-2-24 下午7:00
 *
 * @author jiaguotian Copyright 2012 Sohu.com Inc. All Rights Reserved.
 */
public abstract class AbstractMultiActionProcessor implements IProcessor {
    private PathMatcher pathMatcher = new AntPathMatcher();
    private Map&lt;String, Method> handlerMethodMap = new HashMap&lt;String, Method>();

    private Properties mappings;

    public Method getHandlerMethod(String urlPath) throws NoSuchMethodException {
        String methodName = this.getHandlerMethodNameForUrlPath(urlPath);
        if (methodName != null) {
            Method method = this.handlerMethodMap.get(methodName);
            return method;
        }
        throw new NoSuchMethodException("Can not found action method from uri[" + urlPath + "]");
    }

    private String getHandlerMethodNameForUrlPath(String urlPath) throws NoSuchMethodException {
        String methodName = this.mappings.getProperty(urlPath);
        if (methodName != null) {
            return methodName;
        }
        Enumeration propNames = this.mappings.propertyNames();
        while (propNames.hasMoreElements()) {
            String registeredPath = (String) propNames.nextElement();
            if (this.pathMatcher.match(registeredPath, urlPath)) {
                return (String) this.mappings.get(registeredPath);
            }
        }
        return null;
    }

    public void setMappings(Properties mappings) {
        this.mappings = mappings;
    }
}
</pre>
<p>然后我们的配置文件也极为简单</p>
<pre class="prettyprint">
    &lt;bean id="processorDispatcher" class="com.sohu.twitter.worker.impl.mvc.ProcessorDispatcher">
        &lt;property name="processorMapping" ref="processorMapping"/>
    &lt;/bean>
    &lt;bean id="processorMapping" class="com.sohu.twitter.worker.impl.mvc.impl.ProcessorMappingImpl">
        &lt;property name="defaultProcessor" ref="defaultProcessor"/>
        &lt;property name="processorMap">
            &lt;map>
                &lt;entry key="/**" value="defaultProcessor">&lt;/entry>
            &lt;/map>
        &lt;/property>
    &lt;/bean>
    &lt;bean id="defaultProcessor" class="com.sohu.twitter.worker.impl.processor.DefaultProcessorImpl">
        &lt;property name="storageBeanFactory" ref="storageBeanFactory"/>
    &lt;/bean>
    &lt;bean id="storageBeanFactory" class="com.sohu.twitter.worker.storage.impl.StorageBeanFactory">
        &lt;property name="storageConfig">
            &lt;bean class="com.sohu.twitter.worker.storage.impl.nfs.NfsStorageConfig">
                &lt;property name="mountPoint" value="/opt/imagepath"/>
                &lt;property name="uploadRoot" value="/pic"/>
                &lt;property name="uploadUrls">
                    &lt;list>
                        &lt;value>http://www.tianjiaguo.com/grab&lt;/value>
                    &lt;/list>
                &lt;/property>
            &lt;/bean>
        &lt;/property>
    &lt;/bean>
</pre>
<link href="/js/prettify/prettify.css" type="text/css" rel="stylesheet" />		<script type="text/javascript" src="/js/prettify/prettify.js"></script>		<script language="javascript" type="text/javascript" src="/js/jquery-1.4.2.min.js"></script>		<script language="javascript" type="text/javascript">		$("document").ready(function(){			prettyPrint();			$("pre.prettyprint").each(function(){				$(this).find("span").first().prepend("<span class=\"linenum\">"+1+"</span>");				$(this).find("span br").each(function(i){					$(this).after("<span class=\"linenum\">"+(i+2)+"</span>");				});			});		});		</script><br />
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/tech/spring-mvc%e6%ba%90%e4%bb%a3%e7%a0%81%e7%9a%84%e5%a6%99%e7%94%a8/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Facebook图片存储架构技术全解析</title>
		<link>http://www.tianjiaguo.com/tech/facebook%e5%9b%be%e7%89%87%e5%ad%98%e5%82%a8%e6%9e%b6%e6%9e%84%e6%8a%80%e6%9c%af%e5%85%a8%e8%a7%a3%e6%9e%90/</link>
		<comments>http://www.tianjiaguo.com/tech/facebook%e5%9b%be%e7%89%87%e5%ad%98%e5%82%a8%e6%9e%b6%e6%9e%84%e6%8a%80%e6%9c%af%e5%85%a8%e8%a7%a3%e6%9e%90/#comments</comments>
		<pubDate>Thu, 01 Mar 2012 12:55:29 +0000</pubDate>
		<dc:creator>guoguo</dc:creator>
				<category><![CDATA[技术]]></category>
		<category><![CDATA[facebook]]></category>
		<category><![CDATA[图片存储]]></category>
		<category><![CDATA[存储]]></category>
		<category><![CDATA[架构]]></category>

		<guid isPermaLink="false">http://www.tianjiaguo.com/?p=1125</guid>
		<description><![CDATA[Haystack提出了一种通用的基于HTTP的对象存储，它含有指针，映射到存储对象。在Haystack中以指针储存照片，把数以十万计的图像聚集到一个Haystack存储文件，从而消除了元数据负荷。这就使得元数据的开销非常小，并且使我们能够在存储文件和内存索引中存储每个指针的位置。这就使得能用少量的I/O操作来完成图像数据的检索，可以消除一切不必要的元数据开销。 【51CTO独家特稿】照片应用程序是Facebook最流行的功能。直至目前为止，Facebook的用户已经上传了超过150万幅照片，这使得Facebook成为最大的照片共享网站。对于每一个上传的照片， Facebook生成并保存成4种不同大小的图像，即总共有60亿的图片占1.5PB的存储容量。目前的增长速度是每星期220万个新照片，即每周消耗25TB的额外存储空间。在高峰期，平均每秒会上传550,000幅图像。这些数字给Facebook的照片存储基础架构带来了严重的挑战。 NFS照片基础架构 旧的照片基础架构包含几个层次： ◆上传层接收用户上传的照片，测量原始图像的大小并将其保存到NFS存储层。 ◆照片服务层接收HTTP照片请求，并向用户提供保存于NFS存储层的照片。 ◆NFS存储层建立于商业存储设备之上。 由于每个图像存储在自己的文件内，所以根据命名空间目录和文件inode（内节点），在存储层产生了大量的元数据。这些元数据量远远超过了NFS存储层的缓存能力，导致了上传和读取每张照片时成倍的I/O操作。整个照片服务的基础架构由于NFS存储层的大量元数据负荷而成为了一个瓶颈，这就是Facebook严重依赖CDNs来提供照片服务的原因之一。以下两个附加的优化部署，用来在一定程度上减轻这个问题： Cachr ：一个缓存服务层，用来缓存Facebook中较小的“个人资料”图像。 NFS文件句柄缓存——部署在照片服务层，消除了一些NFS存储级元数据负荷 Haystack照片基础架构 新的照片基础架构将照片服务层和存储层合并为一个物理层。它实现了一个基于HTTP的照片服务器，把照片存储在名为Haystack的通用对象中。对于新层次的主要要求是消除任何照片读取操作的不必要的元数据开销，使每个读取I/O操作只是读取实际照片数据（而不是文件系统元数据）。Haystack可划分为以下一些功能层- ◆HTTP服务器 ◆照片存储 ◆Haystack对象存储 ◆文件系统 ◆存储设备 以下各节中，我们会自底向上密切关注每一个功能层。 存储设备 Haystack部署于日常存储片之上。一个2U存储片的典型硬件配置的是- ◆2 x 4核CPUs ◆16GB – 32GB内存 ◆具有256MB – 512MB NVRAM缓存的硬件RAID控制器 ◆12+ 1TB SATA驱动器 每个存储片提供大约10TB的可用空间，配置为一个RAID-6分区，由硬件RAID控制器进行管理。RAID 6提供了足够的冗余性和出色的读取性能，可以降低存储成本。RAID控制器NVRAM回写高速缓存可以部分缓解低劣的写性能。由于读取大多是随机的，所以NVRAM缓存完全保留给写操作。磁盘高速缓存被禁用，以保证在系统崩溃或电源断电时数据的一致性。 文件系统 Haystack对象存储实现于一个文件之上，该文件存储在一个单一文件系统上，该文件系统建立于10TB volume（卷）大小的空间之上。 &#8230; <a href="http://www.tianjiaguo.com/tech/facebook%e5%9b%be%e7%89%87%e5%ad%98%e5%82%a8%e6%9e%b6%e6%9e%84%e6%8a%80%e6%9c%af%e5%85%a8%e8%a7%a3%e6%9e%90/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Haystack提出了一种通用的基于HTTP的对象存储，它含有指针，映射到存储对象。在Haystack中以指针储存照片，把数以十万计的图像聚集到一个Haystack存储文件，从而消除了元数据负荷。这就使得元数据的开销非常小，并且使我们能够在存储文件和内存索引中存储每个指针的位置。这就使得能用少量的I/O操作来完成图像数据的检索，可以消除一切不必要的元数据开销。<br />
【51CTO独家特稿】照片应用程序是Facebook最流行的功能。直至目前为止，Facebook的用户已经上传了超过150万幅照片，这使得Facebook成为最大的照片共享网站。对于每一个上传的照片， Facebook生成并保存成4种不同大小的图像，即总共有60亿的图片占1.5PB的存储容量。目前的增长速度是每星期220万个新照片，即每周消耗25TB的额外存储空间。在高峰期，平均每秒会上传550,000幅图像。这些数字给Facebook的照片存储基础架构带来了严重的挑战。</p>
<p>NFS照片基础架构</p>
<p>旧的照片基础架构包含几个层次：</p>
<p>◆上传层接收用户上传的照片，测量原始图像的大小并将其保存到NFS存储层。</p>
<p>◆照片服务层接收HTTP照片请求，并向用户提供保存于NFS存储层的照片。</p>
<p>◆NFS存储层建立于商业存储设备之上。</p>
<p>由于每个图像存储在自己的文件内，所以根据命名空间目录和文件inode（内节点），在存储层产生了大量的元数据。这些元数据量远远超过了NFS存储层的缓存能力，导致了上传和读取每张照片时成倍的I/O操作。整个照片服务的基础架构由于NFS存储层的大量元数据负荷而成为了一个瓶颈，这就是Facebook严重依赖CDNs来提供照片服务的原因之一。以下两个附加的优化部署，用来在一定程度上减轻这个问题：</p>
<p>Cachr ：一个缓存服务层，用来缓存Facebook中较小的“个人资料”图像。</p>
<p>NFS文件句柄缓存——部署在照片服务层，消除了一些NFS存储级元数据负荷</p>
<p>Haystack照片基础架构</p>
<p>新的照片基础架构将照片服务层和存储层合并为一个物理层。它实现了一个基于HTTP的照片服务器，把照片存储在名为Haystack的通用对象中。对于新层次的主要要求是消除任何照片读取操作的不必要的元数据开销，使每个读取I/O操作只是读取实际照片数据（而不是文件系统元数据）。Haystack可划分为以下一些功能层-</p>
<p>◆HTTP服务器</p>
<p>◆照片存储</p>
<p>◆Haystack对象存储</p>
<p>◆文件系统</p>
<p>◆存储设备</p>
<p>以下各节中，我们会自底向上密切关注每一个功能层。</p>
<p>存储设备</p>
<p>Haystack部署于日常存储片之上。一个2U存储片的典型硬件配置的是-</p>
<p>◆2 x 4核CPUs</p>
<p>◆16GB – 32GB内存</p>
<p>◆具有256MB – 512MB NVRAM缓存的硬件RAID控制器</p>
<p>◆12+ 1TB SATA驱动器</p>
<p>每个存储片提供大约10TB的可用空间，配置为一个RAID-6分区，由硬件RAID控制器进行管理。RAID 6提供了足够的冗余性和出色的读取性能，可以降低存储成本。RAID控制器NVRAM回写高速缓存可以部分缓解低劣的写性能。由于读取大多是随机的，所以NVRAM缓存完全保留给写操作。磁盘高速缓存被禁用，以保证在系统崩溃或电源断电时数据的一致性。</p>
<p>文件系统</p>
<p>Haystack对象存储实现于一个文件之上，该文件存储在一个单一文件系统上，该文件系统建立于10TB volume（卷）大小的空间之上。</p>
<p>照片读取请求导致read()系统调用请求读取文件中不同偏移量的信息，但为了执行读取操作，文件系统必须首先在实际物理卷上找到数据。在文件系统中，每个文件的是由一个名为inode的结构所描述，该结构包含一个块映射，可以把逻辑文件偏移量映射到物理卷中的物理块偏离量。对于大文件，根据所使用的文件系统类型的不同，块映射可能会相当庞大。</p>
<p>基于块的文件系统为每个逻辑块维护其映射信息，对于大文件，这些映射信息将不会像通常那样存入缓存的inode，而是储存在间接地址块，读取文件数据时需要进行转换。间接转化可能存在好几个层次，因此，根据间接地址块是否被缓存，单一的读取可能会导致若干个I/O操作。</p>
<p>基于范围的文件系统只为连续的块（区域）维护映射信息。对于一个连续大文件的块映射只由一个区域组成，此区域的大小正好可以装入inode之中。但是，如果该文件是严重地分散和不连续的，其区块在卷中不连续，那么其块映射可以随之增长。有了基于范围的文件系统，就可以通过积极分配一大块空间来减少碎片。</p>
<p>目前，所选择的文件系统是的XFS，基于范围的文件系统提供有效文件预分配。</p>
<p>Haystack对象存储</p>
<p>Haystack是一个简单日志结构（只追加）的对象存储，包含描述存储对象的指针。一个Haystack包括两个文件——实际的包含指针的Haystack存储文件，以及一个索引文件。下图显示了Haystack存储文件的结构布局：</p>
<p>第一个8KB的Haystack存储由超级块所占用。紧接着超级块的是指针，每个指针由页眉、数据、和页脚组成。</p>
<p>一个指针是由其﹤Offset（偏移量）, Key, Alternate Key(替换键),Cookie﹥元组唯一确定，其中偏移量是指在Haystack存储中的指针偏移量。Haystack对于关键字的值没有任何限制，有的指针可以有多个关键字。下图显示的是索引文件的结构布局—</p>
<p>在Haystack存储文件中，每个指针有一个相应的索引纪录，而且指针索引纪录的顺序必须与Haystack存储文件中相关的指针顺序相匹配。索引文件提供查找Haystack存储文件中某一特定指针所需的最小元数据。为了快速查找，把索引记录载入并组织到一个数据结构中，这是Haystack应用程序（在我们的情况下是照片存储）的职责。索引文件不是至关重要的，因为它可以根据所需从Haystack存储文件中重建。索引的主要目的是可以快速加载指针元数据到内存中，而无须遍历庞大的Haystack存储文件，这是因为索引的大小通常还不到存储文件的1％。</p>
<p>Haystack写操作</p>
<p>Haystack写操作同步添加新的指针到Haystack存储文件中。当指针成功添加到庞大的Haystack存储文件中之后，相应的索引记录也被写入索引文件。由于索引文件不是至关重要的，为了达到更快的性能，该索引记录是异步写。</p>
<p>索引文件还会定期被刷新到下面的存储设备，以便限制由硬件故障所引起的恢复操作的程度。在系统崩溃或突然断电的情况下，Haystack恢复程序丢弃所有存储中的不完整的指针，同时截断Haystack存储文件直到最后一个有效的指针，然后，在Haystack存储文件最后为所有跟踪的孤立指针写入丢失的索引记录。</p>
<p>Haystack不允许覆盖已存在的指针偏移量，因此，如果某个指针的数据需要修改，其修改后的新版本必须使用相同的﹤Key, Alternate Key, Cookie﹥元组。然后应用程序就可以认为，在那些有着多个关键字的指针中，具有最大偏移量的指针就是最新添加的指针。</p>
<p>Haystack读操作</p>
<p>传递给Haystack读操作的参数包括指针偏移量、关键字、替换键、Cookie和数据大小。然后Haystack添加页眉和页脚的大小到数据大小中，并从文件中读取整个指针。只有当关键字、替换键和Cookie符合参数类型，所传递的数据通过校验，并且指针没有被之前的操作删除时，读操作才能成功（见下文）。</p>
<p>Haystack删除操作</p>
<p>删除操作很简单——通过设置指针的标记域中的一个“deleted（已删除）”标记位，标记Haystack存储中的指针为已删除。然而，相关的索引记录并不进行任何方式的修改，因此一个应用程序可能会结束于引用某个已删除的指针。对于这样的指针的读操作会注意到“deleted”标记，然后终止操作，提示操作错误，给出错误信息。已删除的指针的空间不会以任何方式回收。回收已删除指针的空间的唯一方法是压缩c（见下文） 。</p>
<p>照片存储服务器</p>
<p>照片存储服务器负责接收HTTP请求，并转化成相应的Haystack存储操作。为了尽量减少读取照片所需的I/ O操作次数，服务器在内存中保存一个Haystack存储文件中所有照片偏移量的索引。启动时，服务器读取Haystack索引文件并生成一个内存中的索引。由于每个节点数以亿计的照片（并且该数字只会随着更大容量的驱动器而增加），我们必须确保该索引能够装入可用的内存中。这是通过在内存中保留最少数量的元数据来实现，只保留查找照片所需的信息。</p>
<p>当用户上传一个照片，该照片就被分配一个唯一的64位编号。然后将照片转化为4个不同大小的图片。每个图片具有相同的随机Cookie和64位关键字，合理的图像大小（大，中，小，缩略图）是储存在替换键中。然后上传服务器调用照片存储服务器，把所有4个图像存储在Haystack中。</p>
<p>内存中的索引为每张照片保存以下信息：</p>
<p>Haystack使用开源Google稀疏散列数据结构来减小内存中的索引，因为使用它，每条记录只占2位。</p>
<p>照片存储写/修改操作</p>
<p>写操作写入照片到Haystack，并更新内存索引。如果该索引中已经包含了具有相同关键字的记录，那么这就是一个修改现有照片的操作，那么只修改索引记录偏移量，以反映新图像在Haystack存储文件中的位置。照片存储总是假设存在重复的照片（具有相同关键字的照片），只有存储在最大偏移量位置的照片是有效的。</p>
<p>照片存储读操作</p>
<p>传递到读操作的参数包括Haystack id 和照片关键字、大小和COOKIE 。服务器根据照片关键字，执行一个在内存索引上的查找操作，然后得到含有所需照片的指针偏移量。如果发现调用的是Haystack读操作来读取照片，那么如上所述，Haystack删除操作并不更新Haystack索引文件记录。因此，一个新的内存索引可能会包含之前删除的照片的旧记录。读取之前删除的照片将会导致操作失败，并且内存中的索引会自动更新，设置已经删除图像的偏移量为0。</p>
<p>照片存储删除操作</p>
<p>在调用Haystack删除操作之后，内存中的索引被更新，设置特定图像的偏移量为0来表示该图像已经被删除。</p>
<p>压缩</p>
<p>压缩是一个联机操作，可以回收已被删除的指针和重复指针（具有相同关键字的指针）所占用的空间。它通过复制指针创建一个新的Haystack，跳过所有重复和已删除的指针。每次这样做，就会交换文件和内存中文件的结构。</p>
<p>HTTP服务器</p>
<p>我们使用的HTTP框架是由开源lib event图书馆所提供的简单的evhttp服务器。我们使用多线程，同一时间内，每个线程能够处理一个HTTP请求。因为我们的工作量最主要是由I/O操作产生，因此HTTP服务器的性能并不是至关重要的。</p>
<p>本文转自：<a href="http://developer.51cto.com/art/200905/123565.htm" target="_blank">51CTO.COM</a><br />
&#8211;EOF&#8211;</p>
]]></content:encoded>
			<wfw:commentRss>http://www.tianjiaguo.com/tech/facebook%e5%9b%be%e7%89%87%e5%ad%98%e5%82%a8%e6%9e%b6%e6%9e%84%e6%8a%80%e6%9c%af%e5%85%a8%e8%a7%a3%e6%9e%90/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

