Quantcast
Channel: IT社区推荐资讯 - ITIndex.net
Viewing all 11804 articles
Browse latest View live

如何从代码层防御10大安全威胁中的 Xpath Injection?

$
0
0
普遍性和可检测性:

Xpath 注入是 OWASP TOP10 安全威胁中 A1 Injection 中的一种,注入漏洞发生在应用程序将不可信的数据发送到解释器时。虽然注入漏洞很容易通过审查代码发现,但是却不容易在测试中发现。

影响严重:

注入能导致数据丢失或数据破坏、缺乏可审计性或者是拒绝服务。注入漏洞有时候甚至能导致完全主机接管。

从代码层次如何防御:

首先我们先来看一下在 Java 中引用 xpath 需要用的 lib 库:

  • javax.xml.xpath
  • org.jdom.xpath
  • org.jdom2.xpath等

那么 xpath注入是从哪些途径进入到代码逻辑的呢?大家仔细思考一下无外乎三个途径: cookieheaderrequest parameters/input。如果我们能对这三个注入源头进行严格得入参检查是否就能够防御绝大部分的注入攻击了呢?

答案是可以防御大部分注入攻击,下面我们就一起来看一下如何进行有效得进行入参检查: 我们将入参都转化为 Map 对象, Map<K, Collection<V>> asMap();

然后通过CheckMap( finnal Map<String, String> params)方法,检查入参是否合法。

下面我们来实现这个CheckMap内部方法:

1. 通过遍历检查Map中key得合法性

    for (final String key : params.keySet()) {
        if (this.checkString(key)) {
            return true;
        }

2.检查每一个 key 对应得 value 得合法性:

  final Collection<String> coll = (Collection<String>)params.get((Object)key);
        for (final String input : coll) {
            if (this.checkString(input)) {
                return true;
            }
        }

做完这些就是细节方面得检测了,具体得就是 checkString 如何实现。笔者暂时能想到认为一个入参是 xpath 得检测条件大概有以下几点:

private boolean checkString(final String input) {
    return null != input && input.length() > 1 && (this.parser.IsOutBoundary(input) || (-1 != input.indexOf(39) && (this.parser.IsOutBoundary(input.replace("'", "''")) || this.parser.IsOutBoundary(input.replace("'", "\\'")))) || (-1 != input.indexOf(34) && (this.parser.IsOutBoundary(input.replace("\"", "\"\"")) || this.parser.IsOutBoundary(input.replace("\"", "\\\"")))) || this.parser.IsQuoteUnbalanced(input));
}

通过查 ASCII 码表我们知道39对应“'”,34对应“"”;所以有了检测条件

-1!=input.indexOf(39)&&(this.parser.IsOutBoundary(input.replace("'","''")
-1!=input.indexOf(34)&& this.parser.IsOutBoundary(input.replace("\"", "\"\""))

上述检测条件中用到两个关键方法 IsOutBoundaryIsQuoteUnbalance

public boolean IsOutBoundary(String input) {
    int offset = 0;
    if (null == input || input.length() <= 1) {
        return false;
    }
    input = input.toLowerCase();
    while (true) {
        final int x = this.getRawValue().indexOf(input, offset);
        final int y = x + input.length();
        if (-1 == x) {
            return false;
        }
        final int ceil = this.getCeiling(this.boundaries, x + 1);
        if (-1 != ceil && ceil < y) {
            return true;
        }
        offset = y;
    }
}

public boolean IsQuoteUnbalanced(String input) {
    input = input.toLowerCase();
    return this.getRawValue().contains(input) && this.stack.size() > 0 && input.indexOf(this.stack.peek()) != -1;
}

 public String getRawValue() {
        return this.input;
    }

 private int getCeiling(final List<Integer> boundaries, final int value) {
    for (final int x : boundaries) {
        if (x >= value) {
            return x;
        }
    }
    return -1;
}
漏洞攻击示例

看完代码是如何检查得我们来一个真真正正 Xpath 注入的示例;来检验一下我们代码是都有效。

WebGoat 是 OWASP 推出得一款开源的含有大量漏洞攻击的应用,在 Github 上可以直接搜到源码。

*我们找到 Xpath Injection 得 lession *,如下图:

如何从代码层防御10大安全威胁中的 Xpath Injection

hints提示我们攻击的入参

Try username: Smith' or 1=1 or 'a'='a and a password: anything

点击 Submit 之后神奇得事情出现了!

如何从代码层防御10大安全威胁中的 Xpath Injection

面对这样得一种攻击那么我们该如何防御呢?如果对代码感兴趣得同学可以把 WebGoat 得源码 down 下来;然后将上面得入参检测得方法封装一下嵌入到 WebGoat 得源码中,然后我们再攻击一下,那么接下来会发生什么样的事情呢?

Xpath 查询失败了,并没有返回任何结果,攻击被拦截之后,前端页面没有渲染任何东西。由此可见入参检查在注入类得漏洞防御中可以起到立竿见影得作用。

如何从代码层防御10大安全威胁中的 Xpath Injection

Xpath 查询失败了,并没有返回任何结果,攻击被拦截之后,前端页面没有渲染任何东西。由此可见入参检查在注入类得漏洞防御中可以起到立竿见影得作用。

参考文献:

[1] OWASP TOP10-2013 release

[2] ASCII(wikipedia)

[3] WebGoat

本文系 OneAPM架构师吕龙涛原创文章。如今,多样化的攻击手段层出不穷,传统安全解决方案越来越难以应对网络安全攻击。 OneASP自适应安全平台集成了预测、预防、检测和响应的能力,为您提供精准、持续、可视化的安全防护。想阅读更多技术文章,请访问 OneAPM 官方技术博客


影响lucene的评分的几种方法

$
0
0

评分功能,在全文检索中也算是一个非常重要的模块,因为评分的好坏,直接决定着用户搜索匹配的相关性,试想一下假如用户输入了一个搜索词,搜索引擎返回了一大堆不相关的信息,或者没有层次性,重点性的结果,那么看起来将是一件多么糟糕的事情。

lucene默认的评分机制,用的VSM(Vector  Space Model)空间向量模型,基于TF-IDF的评选方式,TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与资讯探勘的常用加权技术。用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性 随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询 之间相关程度的度量或评级。除了TF-IDF以外,因特网上的搜索引擎还会使用基于链接分析的评级方法,以确定文件在搜寻结果中出现的顺序。

TF-IDF模型,作为一种加权策略,在信息检索,搜索引擎,数据挖掘方面被广泛应用,这种模型在lucene中也得到了很好的实现。


我们先来看下,一般常用的方法加权,在索引时给某个
Field加权

<pre name="code" class="java"> Field field=   new Field("title", "过程", type);
   field.setBoost(10.0f);</pre>
这种方式在lucene4.x之前可以给文档和域分别进行加权,但是在4.x之后,只能给域加权,废弃了文档加权的方式,如果想给文档加权,就需要对每个域分别加权,来提升这个文档的权重。

对比索引时的加权,我们在检索时也可以设置加权boost,代码示例如下:

<pre name="code" class="java">Query q=parser.parse(term);
q.setBoost(8f);//检索时加权

</pre>
或者也可以用,queryparse的解析表达式表示:
<pre name="code" class="java">Query q=parser.parse("lucene^10 solr^5");</pre>

除了,上面的几种方式外,我们还可以自定义评分在源码级别改变一些打分策略:

1,coord(int overlap, int maxOverlap),协调因子,这个因素起什么作用呢,

举个例子现在我索引里面有2条数据:

(1)中国一个多民族国家
(2)中国是世界人口大国

当我们检索“中国”的时候,会发现这两个文档的评分一样,因为他们的长度也相等,
而当我们检索“中国   民族”的时候会发现第一个文档会排在前面而且得分要高,为什么呢?

overlap的个数,代表我们在文档中命中的个数
maxOverlap的个数,代表着检索条件里面的个数==&gt;“中国   民族”2个

由此我们假设其他的条件一样的情况下可以推算出1的得分=2/2=1
而第二个的评分是=1/2=0.5
所以文档1的评分会更好,因为它命中了更多的term。

在源码里方法如下:

<pre name="code" class="java"> public float coord(int overlap, int maxOverlap) {
    return overlap / (float)maxOverlap;
  }</pre>

2,影响评分的第二个因素queryNorm,这个因素,影响评分,但不影响排序的结果,举个例子,如果我们想要把lucene的所有的记录得分的结果,给整体变大,或变小一些,那么我们就可以调整个参数,来控制整体的得分比率。
在lucene的源码里表示如下方法:

<pre name="code" class="java"> public float queryNorm(float sumOfSquaredWeights) {
    return (float)(1.0 / Math.sqrt(sumOfSquaredWeights));
  }</pre>

3,影响评分的第三个因素,TF,这个因素代表着一个term在某一篇文档中,如果它出现的频次越大,那么对应的评分就越高,我们假设,其他的评分因子都一样,有如下2篇文档:

(1)中国人的一天是怎么度过的呀?
(2)我们是中国人,他们也是中国人


我们检索“中国人”,会发现文档2的得分会比文档1的高,因为中国人的这个term,在文档2中出现了2次,在文档1中,只出现了一次。由此计算评分得:

假设基数都一样是10,那么文档1的得分=10*1=10
而文档二的得分则是=10*2=20,假设其他因子都一样,那么此时
文档2的总体评分就会高于文档1,在显示结果时,会优先排在命中结果集的上方。
lucene源码里的方法如下:

<pre name="code" class="java"> public float tf(float freq) {
    return (float)Math.sqrt(freq);
  }</pre>

4,影响评分的第四个因素IDF,这个参数代表的含义是,在所有的文档中,如果某个term频繁出现,那么这个term就被认为是普遍词,所以它的得分就要被减免。

举例如下3个文档:
(1)狗是一种聪明的动物。
(2)猫和狗你更喜欢那个。
(3)狗的种类也有许多种。

现在我们检索“狗  猫”,结果呢,我们会发现文档2排在结果集的首位,为什么呢?
这其实就是IDF的思想,因为狗这个term在所有的文档中出现的次数大于猫,所以在IDF进行评分时,会降低其的评分。

在lucene源码里,idf的方法如下:
注意加1的二个作用第一个是为了避免除数的为0的情况,第二个是为了这个文档在整个文档中不存在的时候,避免其的评分为0的情况存在。

<pre name="code" class="java"> public float idf(long docFreq, long numDocs) {
    return (float)(Math.log(numDocs/(double)(docFreq+1)) + 1.0);
  }</pre>
5,影响评分的第五个因素lengthNorm,这个因素是基于文档内容的长度计算的。举例如下:

有2个文档:
(1)中国
(2)中国人

这个时候我们在检索“中国”的时候,文档1就会排在文档2的前面,为什么会这样呢,明明中国一词在他们中间都出现了一次,造成这样情况出现,恰恰是由于lucene在计算评分,会将文档的长度计算在里面,因为根据常识,较短文本里,出现命中的词,说明这个词更加重要。

lucene源码里的代码如下:

<pre name="code" class="java">public float lengthNorm(FieldInvertState state) {
    final int numTerms;
    if (discountOverlaps)//代表对同义词不出理
      numTerms = state.getLength() - state.getNumOverlap();
    else
      numTerms = state.getLength();
   return state.getBoost() * ((float) (1.0 / Math.sqrt(numTerms)));
  }</pre>


6,lucene里影响评分的第六个因素,载荷Payload,这个功能是一个高级的功能,可以存储时,存储额外的信息,从而在检索时,达到从某种类型的数据动态加权。

举个例子,我们可能希望某个XML里面被如果含 有&lt;keyword&gt;&lt;/keywrod&gt;标记的词从而拥有更高的加权,这时候我们就可以利用 载荷实现了,在索引的时候,我们判断term里的标签标记,如果出现了这个特定标签的标记的term,我们就额外存储它的加权载荷信息,从而再检索时,来 达到一个良好的检索结果。这时候使用载荷,是一个再好不过的选择了。

lucene的源码里对载荷的方法描述如下:

<pre name="code" class="java">  public float scorePayload(int doc, int start, int end, BytesRef payload) {
    return 1;
  }
</pre>

\上文介绍的6种因素外,加上散仙在文章开始部位介绍的boost放权,目前已经介绍了7种影响打分的因素,当然到这里,并不意味着,这些就是全部 的影响评分的方法了,事实上除了这些,还有一些其他的自定义评分的方式,这个散仙会在后续的文章里介绍,大部分的时候,我们了解,利用这些信息,就能解决 狠多业务上的需求了,所以我们可以在我们需要的任何时候,都可以继承DefaultSimilarity类,来重写和我们业务相关的最好的打分策略。

 

 

转载



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



有关Lucene的问题(7):用Lucene构建实时的索引

$
0
0

由于前一章所述的Lucene的事务性,使得Lucene可以增量的添加一个段,我们知道,倒排索引是有一定的格式的,而这个格式一旦写入是非常难以改变的,那么如何能够增量建索引呢?Lucene使用段这个概念解决了这个问题,对于每个已经生成的段,其倒排索引结构不会再改变,而增量添加的文档添加到新的段中,段之间在一定的时刻进行合并,从而形成新的倒排索引结构。

然而也正因为Lucene的事务性,使得Lucene的索引不够实时,如果想Lucene实时,则必须新添加的文档后IndexWriter需要commit,在搜索的时候IndexReader需要重新的打开,然而当索引在硬盘上的时候,尤其是索引非常大的时候,IndexWriter的commit操作和IndexReader的open操作都是非常慢的,根本达不到实时性的需要。

好在Lucene提供了RAMDirectory,也即内存中的索引,能够很快的commit和open,然而又存在如果索引很大,内存中不能够放下的问题。

所以要构建实时的索引,就需要内存中的索引RAMDirectory和硬盘上的索引FSDirectory相互配合来解决问题。

1、初始化阶段

首先假设我们硬盘上已经有一个索引FileSystemIndex,由于IndexReader打开此索引非常的慢,因而其是需要事先打开的,并且不会时常的重新打开。

我们在内存中有一个索引MemoryIndex,新来的文档全部索引到内存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打开,这两个操作时非常快的。

如下图,则此时新索引的文档全部能被用户看到,达到实时的目的。

 

2、合并索引阶段

然而经过一段时间,内存中的索引会比较大了,如果不合并到硬盘上,则可能造成内存不够用,则需要进行合并的过程。

当然在合并的过程中,我们依然想让我们的搜索是实时的,这是就需要一个过渡的索引,我们称为MergingIndex。

一旦内存索引达到一定的程度,则我们重新建立一个空的内存索引,用于合并阶段索引新的文档,然后将原来的内存索引称为合并中索引,并启动一个后台线程进行合并的操作。

在合并的过程中,如果有查询过来,则需要三个IndexReader,一个是内存索引的IndexReader打开,这个过程是很快的,一个是合并中索引的IndexReader打开,这个过程也是很快的,一个是已经打开的硬盘索引的IndexReader,无需重新打开。这三个IndexReader可以覆盖所有的文档,唯一有可能重复的是,硬盘索引中已经有一些从合并中索引合并过去的文档了,然而不用担心,根据Lucene的事务性,在硬盘索引的IndexReader没有重新打开的情况下,背后的合并操作它是看不到的,因而这三个IndexReader所看到的文档应该是既不少也不多。合并使用IndexWriter(硬盘索引).addIndexes(IndexReader(合并中索引)),合并结束后Commit。

如下图:

 

 

3、重新打开硬盘索引的IndexReader

当合并结束后,是应该重新打开硬盘索引的时候了,然而这是一个可能比较慢的过程,在此过程中,我们仍然想保持实时性,因而在此过程中,合并中的索引不能丢弃,硬盘索引的IndexReader也不要动,而是为硬盘索引打开一个临时的IndexReader,在打开的过程中,如果有搜索进来,返回的仍然是上述的三个IndexReader,仍能够不多不少的看到所有的文档,而将要打开的临时的IndexReader将能看到合并中索引和原来的硬盘索引所有的文档,此IndexReader并不返回给客户。如下图:

 

4、替代IndexReader

当临时的IndexReader被打开的时候,其看到的是合并中索引的IndexReader和硬盘索引原来的IndexReader之和,下面要做的是:

(1) 关闭合并中索引的IndexReader

(2) 抛弃合并中索引

(3) 用临时的IndexReader替换硬盘索引原来的IndexReader

(4) 关闭硬盘索引原来的IndexReader。

上面说的这几个操作必须是原子性的,如果做了(2)但没有做(3),如果来一个搜索,则将少看到一部分数据,如果做了(3)没有做(2)则,多看到一部分数据。

所以在进行上述四步操作的时候,需要加一个锁,如果这个时候有搜索进来的时候,或者在完全没有做的时候得到所有的IndexReader,或者在完全做好的时候得到所有的IndexReader,这时此搜索可能被block,但是没有关系,这四步是非常快的,丝毫不影响替代性。

如下图:

 

经过这几个过程,又达到了第一步的状态,则进行下一个合并的过程。

5、多个索引

有一点需要注意的是,在上述的合并过程中,新添加的文档是始终添加到内存索引中的,如果存在如下的情况,索引速度实在太快,在合并过程没有完成的时候,内存索引又满了,或者硬盘上的索引实在太大,合并和重新打开要花费太长的时间,使得内存索引以及满的情况下,还没有合并完成。

为了处理这种情况,我们可以拥有多个合并中的索引,多个硬盘上的索引,如下图:

 

  • 新添加的文档永远是进入内存索引
  • 当内存索引到达一定的大小的时候,将其加入合并中索引链表
  • 有一个后台线程,每隔一定的时刻,将合并中索引写入一个新的硬盘索引中取。这样可以避免由于硬盘索引过大而合并较慢的情况。硬盘索引的IndexReader也是写完并重新打开后才替换合并中索引的IndexReader,新的硬盘索引也可保证打开的过程不会花费太长时间。
  • 这样会造成硬盘索引很多,所以,每隔一定的时刻,将硬盘索引合并成一个大的索引。也是合并完成后方才替换IndexReader

大家可能会发现,此合并的过程和Lucene的段的合并很相似。然而Lucene的一个函数IndexReader.reopen一直是没有实现的,也即我们不能选择哪个段是在内存中的,可以被打开,哪些是硬盘中的,需要在后台打开然后进行替换,而IndexReader.open是会打开所有的内存中的和硬盘上的索引,因而会很慢,从而降低了实时性。



已有 7人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



有关Lucene的问题(8):用Lucene构建实时索引的文档更新问题

$
0
0

在有关Lucene的问题(7),讨论了使用Lucene内存索引和硬盘索引构建实时索引的问题。

然而有的读者提到,如果涉及到文档的删除及更新,那么如何构建实时的索引呢?本节来讨论这个问题。

1、Lucene删除文档的几种方式

 

  • IndexReader.deleteDocument(int docID)是用 IndexReader 按文档号删除。  
  • IndexReader.deleteDocuments(Term  term)是用 IndexReader 删除包含此词(Term)的文档。  
  • IndexWriter.deleteDocuments(Term  term)是用 IndexWriter 删除包含此词(Term)的文档。  
  • IndexWriter.deleteDocuments(Term[]  terms)是用 IndexWriter 删除包含这些词(Term)的文档。  
  • IndexWriter.deleteDocuments(Query  query)是用 IndexWriter 删除能满足此查询(Query)的文档。  
  • IndexWriter.deleteDocuments(Query[] queries)是用 IndexWriter 删除能满足这些查询(Query)的文档。

删除文档既可以用reader进行删除,也可以用writer进行删除,不同的是,reader进行删除后,此reader马上能够生效,而用writer删除后,会被缓存,只有写入到索引文件中,当reader再次打开的时候,才能够看到。

2、Lucene文档更新的几个问题

 

2.1、使用IndexReader还是IndexWriter进行删除

既然IndexReader和IndexWriter都能够进行文档删除,那么到底是应该用哪个来进行删除呢?

本文的建议是,用IndexWriter来进行删除。

因为用IndexReader可能存在以下的问题:

(1) 当有一个IndexWriter打开的时候,IndexReader的删除操作是不能够进行的,否则会报LockObtainFailedException

(2) 当IndexReader被多个线程使用的时候,一个线程用其进行删除,会使得另一个线程看到的索引有所改变,使得另一个线程的结果带有不确定性。

(3) 对于更新操作,在Lucene中是先删除,再添加的,然而删除的被立刻看到的,而添加却不能够立刻看到,造成了数据的不一致性。

(4) 即便以上问题可以通过锁来解决,然而背后的操作影响到了搜索的速度,是我们不想看到的。

2.2、如何在内存中缓存文档的删除

在上一节中,为了能够做到实时性,我们使用内存中的索引,而硬盘上的索引则不经常打开,即便打开也在背后线程中打开。

而要删除的文档如果在硬盘索引中,如果不重新打开则看不到新的删除,则需要将删除的文档缓存到内存中。

那如何将缓存在内存中的文档删除在不重新打开IndexReader的情况下应用于硬盘上的索引呢?

在Lucene中,有一种IndexReader为FilterIndexReader,可以对一个IndexReader进行封装,我们可以实现一个自己的FilterIndexReader来过滤掉删除的文档。

一个例子如下:

 

public class MyFilterIndexReader extends FilterIndexReader {

  OpenBitSet dels;

  public MyFilterIndexReader(IndexReader in) {

    super(in);

    dels = new OpenBitSet(in.maxDoc());

  }

  public MyFilterIndexReader(IndexReader in, List<String> idToDelete) throws IOException {

    super(in);

    dels = new OpenBitSet(in.maxDoc());

    for(String id : idToDelete){

      TermDocs td = in.termDocs(new Term("id", id)); //如果能在内存中Cache从Lucene的ID到应用的ID的映射,Reader的生成将快得多。

      if(td.next()){

        dels.set(td.doc());

      }

    }

  }

  @Override

  public int numDocs() {

    return in.numDocs() - (int) dels.cardinality();

  }

  @Override

  public TermDocs termDocs(Term term) throws IOException {

    return new FilterTermDocs(in.termDocs(term)) {

      @Override

      public boolean next() throws IOException {

        boolean res;

        while ((res = super.next())) {

          if (!dels.get(doc())) {

            break;

          }

        }

        return res;

      }

    };

  }

  @Override

  public TermDocs termDocs() throws IOException {

    return new FilterTermDocs(in.termDocs()) {

      @Override

      public boolean next() throws IOException {

        boolean res;

        while ((res = super.next())) {

          if (!dels.get(doc())) {

            break;

          }

        }

        return res;

      }

    };

  }

}

 

2.3、文档更新的顺序性问题

Lucene的文档更新其实是删除旧的文档,然后添加新的文档。如上所述,删除的文档是缓存在内存中的,并通过FilterIndexReader应用于硬盘上的索引,然而新的文档也是以相同的id加入到索引中去的,这就需要保证缓存的删除不会将新的文档也过滤掉,将缓存的删除合并到索引中的时候不会将新的文档也删除掉。

Lucene的两次更新一定要后一次覆盖前一次,而不能让前一次覆盖后一次。

所以内存中已经硬盘中的多个索引是要被保持一个顺序的,哪个是老的索引,哪个是新的索引,缓存的删除自然是应该应用于所有比他老的索引的,而不应该应用于他自己以及比他新的索引。

3、具有更新功能的Lucene实时索引方案

3.1、初始化

首先假设我们硬盘上已经有一个索引FileSystemIndex,被事先打开的,其中包含文档1,2,3,4,5,6。

我们在内存中有一个索引MemoryIndex,新来的文档全部索引到内存索引中,并且是索引完IndexWriter就commit,IndexReader就重新打开,其中包含文档7,8。

 

3.2、更新文档5

这时候来一个新的更新文档5, 需要首先将文档5删除,然后加入新的文档5。

需要做的事情是:

  • 首先在内存索引中删除文档5,当然没有文档5,删除无效。
  • 其次将对文档5的删除放入内存文档删除列表,并与硬盘的IndexReader组成FilterIndexReader
  • 最后,将新的文档5加入内存索引,这时候,用户可以看到的就是新的文档5了。
  • 将文档5放入删除列表以及将文档5提交到内存索引两者应该是一个原子操作,好在这两者都是比较块的。

注:此处对硬盘上的索引,也可以进行对文档5的删除,由于IndexReader没有重新打开,此删除是删不掉的,我们之所以没有这样做,是想保持此次更新要么全部在内存中,要么全部在硬盘中,而非删除部分已经应用到硬盘中,而新文档却在内存中,此时,如果系统crash,则新的文档5丢失了,而旧的文档5也已经在硬盘上被删除。我们将硬盘上对文档5的删除放到从内存索引向硬盘索引的合并过程。

 

如果再有一次对文档5的更新,则首先将内存索引中的文档5删除,添加新的文档5,然后将文档5加入删除列表,发现已经存在,则不必删除。

3.3、合并索引

然而经过一段时间,内存中的索引需要合并到硬盘上。

在合并的过程中,需要重新建立一个空的内存索引,用于合并阶段索引新的文档,而合并中的索引的IndexReader以及硬盘索引和删除列表所组成的FilterIndexReader仍然保持打开,对外提供服务,而合并阶段从后台进行。

后台的合并包括以下几步:

  • 将删除列表应用到硬盘索引中。
  • 将内存索引合并到硬盘索引中。
  • IndexWriter提交。

 

3.4、合并的过程中更新文档5

在合并的过程中,如果还有更新那怎么办呢?

  • 首先将合并中索引的文档5删除,此删除不会影响合并,因为合并之前,合并中索引的IndexReader已经打开,索引合并中索引的文档5还是会合并到硬盘中去的。此删除影响的是此后的查询在合并中索引是看不到文档5的。
  • 然后将文档5的删除放入删除列表,并同合并中索引的删除列表,已经硬盘索引一起构成FilterIndexReader。
  • 将新的文档5添加到内存中索引。
  • 提交在合并中索引对文档5的删除,将文档5添加到删除列表,提交在内存索引中对文档5的添加三者应该是一个原子操作,好在三者也是很快的。

 

3.5、重新打开硬盘索引的IndexReader

当合并中索引合并到硬盘中的时候,是时候重新打开硬盘上的索引了,新打开的IndexReader是可以看到文档5的删除的。

如果这个时候有新的更新,也是添加到内存索引和删除列表的,比如我们更新文档6.

 

3.6、替代IndexReader 

当IndexReader被重新打开后,则需要删除合并中的索引及其删除列表,将硬盘索引原来的IndexReader关闭,使用新的IndexReader。



已有 10人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



数据库连接池性能比对

$
0
0

背景

对现有的数据库连接池做调研对比,综合性能,可靠性,稳定性,扩展性等因素选出推荐出最优的数据库连接池 。     

NOTE: 本文所有测试均是mysql库

测试结论

   1:性能方面 hikariCP>druid>tomcat-jdbc>dbcp>c3p0 。hikariCP的高性能得益于最大限度的避免锁竞争。

   2:druid功能最为全面,sql拦截等功能,统计数据较为全面,具有良好的扩展性。

   3:综合性能,扩展性等方面,可考虑使用druid或者hikariCP连接池。

   4:可开启prepareStatement缓存,对性能会有大概20%的提升。

功能对比

功能dbcpdruidc3p0tomcat-jdbcHikariCP
是否支持PSCache
监控jmxjmx/log/httpjmx,logjmxjmx
扩展性
sql拦截及解析支持
代码简单中等复杂简单简单
更新时间2015.8.62015.10.10 2015.12.09 2015.12.3
特点依赖于common-pool阿里开源,功能全面历史久远,代码逻辑复杂,且不易维护 优化力度大,功能简单,起源于boneCP
连接池管理LinkedBlockingDeque数组 FairBlockingQueuethreadlocal+CopyOnWriteArrayList
  •  由于boneCP被hikariCP替代,并且已经不再更新,boneCP没有进行调研。
  • proxool网上有评测说在并发较高的情况下会出错,proxool便没有进行调研。
  •  druid的功能比较全面,且扩展性较好,比较方便对jdbc接口进行监控跟踪等。
  • c3p0历史悠久,代码及其复杂,不利于维护。并且存在deadlock的潜在风险。

性能测试

环境配置:

CPUIntel(R) Xeon(R) CPU E5-2430 v2 @ 2.50GHz,24core
msyql version5.5.46
tomcat-jdbc version8.0.28
HikariCP version2.4.3
c3p0 Version0.9.5-pre8
dbcpVersion2.0.1
druidVersion1.0.5

 

1:获取关闭连接性能测试

       测试说明:

  • 初始连接和最小连接均为5,最大连接为20。在borrow和return均不心跳检测
  • 其中打开关闭次数为: 100w次
  • 测试用例和mysql在同一台机器上面,尽量避免io的影响
  • 使用mock和连接mysql在不同线程并发下的响应时间

     图形:

 

 

   mock性能数据 (单位:ms)

 52050100
tomcat-jdbc4424471,0131,264
c3p04,4805,5277,44910,725
dbcp6766898671,292
hikari38333830
druid291293562985

mysql性能数据 (单位:ms)

 52050100
tomcat-jdbc4364531,0331,291
c3p04,3785,7267,97510,948
dbcp6716798971,380
hikari96828778
druid3044246901,130

测试结果:

  • mock和mysql连接性能表现差不多,主要是由于初始化的时候建立了连接后期不再建立连接,和使用mock连接逻辑一致。 
  • 性能表现:hikariCP>druid>tomcat-jdbc>dbcp>c3p0。
  •  hikariCP 的性能及其优异。hikariCP号称java平台最快的数据库连接池。
  •  hikariCP在并发较高的情况下,性能基本上没有下降。
  •  c3p0连接池的性能很差,不建议使用该数据库连接池。

   hikariCP性能分析:

  • hikariCP通过优化(concurrentBag,fastStatementList )集合来提高并发的读写效率。
  • hikariCP使用threadlocal缓存连接及大量使用CAS的机制,最大限度的避免lock。单可能带来cpu使用率的上升。
  • 从字节码的维度优化代码。 (default inline threshold for a JVM running the server Hotspot compiler is 35 bytecodes )让方法尽量在35个字节码一下,来提升jvm的处理效率。

 

2:查询一条语句性能测试

     测试说明:

  • 初始连接和最小连接均为8,最大连接为8。在borrow和return均不心跳检测
  • 查询的次数为10w次,查询的语句为 1:打开连接 2:执行 :select 1 3:关闭连接
  • 测试用例和mysql在同一台机器上面,尽量避免io的影响

图形:

   

 测试数据:

 582050100
tomcat-jdbc2,1781,4951,7691,8181,858
c3p03,2373,4514,4885,9947,906
dbcp2,8161,9352,0972,2432,280
hikari2,2991,5461,6821,7511,772
druid2,2971,5511,8001,9772,032

 

测试结果:

  •   在并发比较少的情况下,每个连接池的响应时间差不多。是由于并发少,基本上没有资源竞争。
  •   在并发较高的情况下,随着并发的升高,hikariCP响应时间基本上没有变动。
  •   c3p0随着并发的提高,性能急剧下降。

 

3:pscache性能对比

   测试说明:

  • 通过druid进行设置pscache和不设置pscache的性能对比
  • 初始连接和最小连接均为8,最大连接为8。在borrow和return均不心跳检测。并且执行的并发数为8.
  • 查询10w次。查询流程为:1:建立连接,2:循环查询preparestatement语句 3:close连接
  • 测试用例和mysql在同一台机器上面,尽量避免io的影响

   测试数据:

cache1,927
not cache2,134

  测试结果:

  • 开启psCache缓存,性能大概有20%幅度的提升。可考虑开启pscache.

  测试说明:

  • psCache是connection私有的,所以不存在线程竞争的问题,开启pscache不会存在竞争的性能损耗。
  • psCache的key为prepare执行的sql和catalog等,value对应的为prepareStatement对象。开启缓存主要是减少了解析sql的开销。


已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



[转载]关于Quartz 线程处理说明

$
0
0

Quartz定时任务默认都是并发执行的,不会等待上一次任务执行完毕,只要间隔时间到就会执行, 如果定时任执行太长,会长时间占用资源,导致其它任务堵塞。
1.在Spring中这时需要设置concurrent的值为false, 禁止并发执行。

1
 <property name="concurrent"value="true"/>

2.当不使用spring的时候就需要在Job的实现类上加@DisallowConcurrentExecution的注释
@DisallowConcurrentExecution 禁止并发执行多个相同定义的JobDetail, 这个注解是加在Job类上的, 但意思并不是不能同时执行多个Job, 而是不能并发执行同一个Job Definition(由JobDetail定义), 但是可以同时执行多个不同的JobDetail, 举例说明,我们有一个Job类,叫做SayHelloJob, 并在这个Job上加了这个注解, 然后在这个Job上定义了很多个JobDetail, 如sayHelloToJoeJobDetail, sayHelloToMikeJobDetail, 那么当scheduler启动时, 不会并发执行多个sayHelloToJoeJobDetail或者sayHelloToMikeJobDetail, 但可以同时执行sayHelloToJoeJobDetail跟sayHelloToMikeJobDetail

@PersistJobDataAfterExecution 同样, 也是加在Job上,表示当正常执行完Job后, JobDataMap中的数据应该被改动, 以被下一次调用时用。当使用@PersistJobDataAfterExecution 注解时, 为了避免并发时, 存储数据造成混乱, 强烈建议把@DisallowConcurrentExecution注解也加上。

 

 

@DisallowConcurrentExecution

此标记用在实现Job的类上面,意思是不允许并发执行,按照我之前的理解是 不允许调度框架在同一时刻调用Job类,后来经过测试发现并不是这样,而是Job(任务)的执行时间[比如需要10秒]大于任务的时间间隔 [Interval(5秒)],那么默认情况下,调度框架为了能让 任务按照我们预定的时间间隔执行,会马上启用新的线程执行任务。否则的话会等待任务执行完毕以后 再重新执行!(这样会导致任务的执行不是按照我们预先定义的时间间隔执行)

测试代码,这是官方提供的例子。设定的时间间隔为3秒,但job执行时间是5秒,设置@DisallowConcurrentExecution以后程序会等任务执行完毕以后再去执行,否则会在3秒时再启用新的线程执行

@PersistJobDataAfterExecution

此标记说明在执行完Job的execution方法后保存JobDataMap当中固定数据,在默认情况下 也就是没有设置 @PersistJobDataAfterExecution的时候 每个job都拥有独立JobDataMap

否则改任务在重复执行的时候具有相同的JobDataMap

原文链接 :http://www.open-open.com/lib/view/open1412993886655.html



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



小散的正确道路是趋势投资?

$
0
0
忠告粉丝:
1价值投资加分散投资是大概率赢的事情
这点上大多数人,特别是雪球上的大多数人士会认同的

2价值投资比趋势投资难10倍
这点上,雪球的大多数人会不认同,因为每个夹头都认为自己掌握了价值投资的精髓,掌握了价值投资的核心,其实不然,价值投资不是低PE,PB,价值投资必须掌握相当高深的财务知识,必须对行业的宏观经济有相当深的了解,由于股市包含全社会众多行业,所以对知识面的广度和深度,都有极高要求。

3最简单的正确道路是具备常识的趋势投资
现实是这条路被众人鄙视与不屑,然而这条路是大多数不具备财务知识和行业了解的人应该走的路

具体简单的说:就是在中低PE,PB的股票池中,用趋势的技术的方法选股,这条路才更适合大众,掌握基本的量价和趋势技术选股手段,比掌握财务知识和各个行业特点简单100倍


@管我财@唐朝@梁宏@江涛请大伙谈谈俺这个观点 [大笑]

本话题在雪球有118条讨论,点击查看。
雪球是一个投资者的社交网络,聪明的投资者都在这里。
点击下载雪球手机客户端 http://xueqiu.com/xz

面试常见十大类算法汇总

$
0
0

1.String/Array/Matrix

在Java中,String是一个包含char数组和其它字段、方法的类。如果没有IDE自动完成代码,下面这个方法大家应该记住: 

 

toCharArray() //get char array of a String
Arrays.sort()  //sort an array
Arrays.toString(char[] a) //convert to string
charAt(int x) //get a char at the specific index
length() //string length
length //array size 
substring(int beginIndex) 
substring(int beginIndex, int endIndex)
Integer.valueOf()//string to integer
String.valueOf()/integer to string

String/arrays很容易理解,但与它们有关的问题常常需要高级的算法去解决,例如动态编程、递归等。

下面列出一些需要高级算法才能解决的经典问题:

 

 

 

 

 

 

2.链表

在Java中实现链表是非常简单的,每个节点都有一个值,然后把它链接到下一个节点。 

 

class Node {
	int val;
	Node next;
	Node(int x) {
		val = x;
		next = null;
	}
}

比较流行的两个链表例子就是栈和队列。

栈(Stack) 

 

class Stack{
	Node top; 
	public Node peek(){
		if(top != null){
			return top;
		}
		return null;
	}
	public Node pop(){
		if(top == null){
			return null;
		}else{
			Node temp = new Node(top.val);
			top = top.next;
			return temp;	
		}
	}
	public void push(Node n){
		if(n != null){
			n.next = top;
			top = n;
		}
	}
}

队列(Queue)

 

class Queue{
	Node first, last;
	public void enqueue(Node n){
		if(first == null){
			first = n;
			last = first;
		}else{
			last.next = n;
			last = n;
		}
	}
	public Node dequeue(){
		if(first == null){
			return null;
		}else{
			Node temp = new Node(first.val);
			first = first.next;
			return temp;
		}	
	}
}

 

 

值得一提的是,Java标准库中已经包含一个叫做Stack的类,链表也可以作为一个队列使用(add()和remove())。(链表实现队列接口)如果你在面试过程中,需要用到栈或队列解决问题时,你可以直接使用它们。

在实际中,需要用到链表的算法有:

 

 

 

 

 

 

 

3.树&堆

这里的树通常是指二叉树。

class TreeNode{
	int value;
	TreeNode left;
	TreeNode right;
} 

  

下面是一些与二叉树有关的概念:

 

 

  • 二叉树搜索:对于所有节点,顺序是:left children <= current node <= right children;
  • 平衡vs.非平衡:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树;
  • 满二叉树:除最后一层无任何子节点外,每一层上的所有结点都有两个子结点;
  • 完美二叉树(Perfect Binary Tree):一个满二叉树,所有叶子都在同一个深度或同一级,并且每个父节点都有两个子节点;
  • 完全二叉树:若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

 

堆(Heap)是一个基于树的数据结构,也可以称为优先队列(  PriorityQueue),在队列中,调度程序反复提取队列中第一个作业并运行,因而实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

下面列出一些基于二叉树和堆的算法:

 

 

4.Graph 

与Graph相关的问题主要集中在深度优先搜索和宽度优先搜索。深度优先搜索非常简单,你可以从根节点开始循环整个邻居节点。下面是一个非常简单的宽度优先搜索例子,核心是用队列去存储节点。

 

第一步,定义一个GraphNode

 

class GraphNode{ 
	int val;
	GraphNode next;
	GraphNode[] neighbors;
	boolean visited;
	GraphNode(int x) {
		val = x;
	}
	GraphNode(int x, GraphNode[] n){
		val = x;
		neighbors = n;
	}
	public String toString(){
		return "value: "+ this.val; 
	}
}

  

 

 

第二步,定义一个队列

 

 

class Queue{
	GraphNode first, last;
	public void enqueue(GraphNode n){
		if(first == null){
			first = n;
			last = first;
		}else{
			last.next = n;
			last = n;
		}
	}
	public GraphNode dequeue(){
		if(first == null){
			return null;
		}else{
			GraphNode temp = new GraphNode(first.val, first.neighbors);
			first = first.next;
			return temp;
		}	
	}
}

 

 

第三步,使用队列进行宽度优先搜索

 

 

public class GraphTest {
	public static void main(String[] args) {
		GraphNode n1 = new GraphNode(1); 
		GraphNode n2 = new GraphNode(2); 
		GraphNode n3 = new GraphNode(3); 
		GraphNode n4 = new GraphNode(4); 
		GraphNode n5 = new GraphNode(5); 
		n1.neighbors = new GraphNode[]{n2,n3,n5};
		n2.neighbors = new GraphNode[]{n1,n4};
		n3.neighbors = new GraphNode[]{n1,n4,n5};
		n4.neighbors = new GraphNode[]{n2,n3,n5};
		n5.neighbors = new GraphNode[]{n1,n3,n4};
		breathFirstSearch(n1, 5);
	}
	public static void breathFirstSearch(GraphNode root, int x){
		if(root.val == x)
			System.out.println("find in root");
		Queue queue = new Queue();
		root.visited = true;
		queue.enqueue(root);
		while(queue.first != null){
			GraphNode c = (GraphNode) queue.dequeue();
			for(GraphNode n: c.neighbors){
				if(!n.visited){
					System.out.print(n + " ");
					n.visited = true;
					if(n.val == x)
						System.out.println("Find "+n);
					queue.enqueue(n);
				}
			}
		}
	}
}

 

 

输出结果:

 

value: 2 value: 3 value: 5 Find value: 5 
value: 4

实际中,基于Graph需要经常用到的算法:

 

 

5.排序

不同排序算法的时间复杂度,大家可以到wiki上查看它们的基本思想。

 

BinSort、Radix Sort和CountSort使用了不同的假设,所有,它们不是一般的排序方法。 

下面是这些算法的具体实例,另外,你还可以阅读:  Java开发者在实际操作中是如何排序的

 

 

6.递归和迭代

下面通过一个例子来说明什么是递归。

问题:

这里有n个台阶,每次能爬1或2节,请问有多少种爬法?

步骤1:查找n和n-1之间的关系

为了获得n,这里有两种方法:一个是从第一节台阶到n-1或者从2到n-2。如果f(n)种爬法刚好是爬到n节,那么f(n)=f(n-1)+f(n-2)。 

步骤2:确保开始条件是正确的

f(0) = 0; 
f(1) = 1; 

public static int f(int n){
	if(n <= 2) return n;
	int x = f(n-1) + f(n-2);
	return x;
}

 

递归方法的时间复杂度指数为n,这里会有很多冗余计算。

 

f(5)
f(4) + f(3)
f(3) + f(2) + f(2) + f(1)
f(2) + f(1) + f(2) + f(2) + f(1)

 

该递归可以很简单地转换为迭代。 

 

public static int f(int n) {
	if (n <= 2){
		return n;
	}
	int first = 1, second = 2;
	int third = 0;
	for (int i = 3; i <= n; i++) {
		third = first + second;
		first = second;
		second = third;
	}
	return third;
}

 

 

 

在这个例子中,迭代花费的时间要少些。关于迭代和递归,你可以去  这里看看。

7.动态规划

动态规划主要用来解决如下技术问题:

 

  • 通过较小的子例来解决一个实例;
  • 对于一个较小的实例,可能需要许多个解决方案;
  • 把较小实例的解决方案存储在一个表中,一旦遇上,就很容易解决;
  • 附加空间用来节省时间。

 

上面所列的爬台阶问题完全符合这四个属性,因此,可以使用动态规划来解决: 

 

public static int[] A = new int[100];
public static int f3(int n) {
	if (n <= 2)
		A[n]= n;
	if(A[n] > 0)
		return A[n];
	else
		A[n] = f3(n-1) + f3(n-2);//store results so only calculate once!
	return A[n];
}

 

 

一些基于动态规划的算法:

 

 

8.位操作

位操作符:

从一个给定的数n中找位i(i从0开始,然后向右开始)

 

public static boolean getBit(int num, int i){
	int result = num & (1<<i);
	if(result == 0){
		return false;
	}else{
		return true;
	}
}

 

例如,获取10的第二位:

 

i=1, n=10
1<<1= 10
1010&10=10
10 is not 0, so return true;

 

 

典型的位算法:

 

 

 

9.概率

通常要解决概率相关问题,都需要很好地格式化问题,下面提供一个简单的例子: 

有50个人在一个房间,那么有两个人是同一天生日的可能性有多大?(忽略闰年,即一年有365天)

算法:

public static double caculateProbability(int n){
	double x = 1; 
	for(int i=0; i<n; i++){
		x *=  (365.0-i)/365.0;
	}
	double pro = Math.round((1-x) * 100);
	return pro/100;
}

  

结果:

 

calculateProbability(50) = 0.97

10.组合和排列

组合和排列的主要差别在于顺序是否重要。

例1:

1、2、3、4、5这5个数字,输出不同的顺序,其中4不可以排在第三位,3和5不能相邻,请问有多少种组合?

例2:

有5个香蕉、4个梨、3个苹果,假设每种水果都是一样的,请问有多少种不同的组合?

基于它们的一些常见算法

 

 

 

来自: ProgramCreek  CSDN



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐




MySQL5.6.12 Waiting for commit lock导致从库hang住的问题剖析

$
0
0

nagios报警,线上一台从库检测不到slave状态,于是远程上去查看问题:


1,show slave status\G卡住:

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show slave status\G

show slave status卡住了,动弹不了,这种情况还是第一次遇到。


2,看w负载无压力:

[root@tmp3_72 ~]# w
 13:23:31 up 828 days, 21:54,  3 users,  load average: 0.08, 0.05, 0.00
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
root     tty1     -                29Sep13 827days  0.15s  0.15s -bash
root     pts/0    192.168.120.28   13:19    3:49   0.18s  0.14s mysql
root     pts/1    192.168.120.28   13:23    0.00s  0.08s  0.02s w
[root@tmp3_72 ~]# 
[root@tmp3_72 ~]# 
[root@tmp3_72 ~]#

3,Top看mysqld进程占据cpu才0.3%

这里写图片描述


4,查看error日志,没有发现异常记录:

[root@tmp3_72 mysql]# tail -f /usr/local/mysql/mysqld.log 
2015-12-29 20:35:31 29254 [Note]   - '::' resolves to '::';
2015-12-29 20:35:31 29254 [Note] Server socket created on IP: '::'.
2015-12-29 20:35:31 29254 [Note] Event Scheduler: Loaded 0 events
2015-12-29 20:35:31 29254 [Note] /usr/local/mysql/bin/mysqld: ready for connections.
Version: '5.6.12-log'  socket: '/usr/local/mysql/mysql.sock'  port: 3306  Source distribution
2015-12-29 20:35:40 29254 [Note] 'CHANGE MASTER TO executed'. Previous state master_host='', master_port= 3306, master_log_file='', master_log_pos= 4, master_bind=''. New state master_host='', master_port= 3306, master_log_file='mysql-bin.036282', master_log_pos= 120, master_bind=''.
2015-12-30 10:00:04 29254 [Note] 'CHANGE MASTER TO executed'. Previous state master_host='', master_port= 3306, master_log_file='', master_log_pos= 4, master_bind=''. New state master_host='192.168.12.71', master_port= 3306, master_log_file='mysql-bin.036282', master_log_pos= 120, master_bind=''.
2015-12-30 10:00:04 29254 [Note] Slave SQL thread initialized, starting replication in log 'mysql-bin.036282' at position 120, relay log './mysql-relay-bin.000001' position: 4
2015-12-30 10:00:04 29254 [Warning] Storing MySQL user name or password information in the master.info repository is not secure and is therefore not recommended. Please see the MySQL Manual for more about this issue and possible alternatives.
2015-12-30 10:00:04 29254 [Note] Slave I/O thread: connected to master 'repl@192.168.12.71:3306',replication started in log 'mysql-bin.036282' at position 120

5,查看到后台的mysql进程,有一个mysqldump备份进程,执行了很久,我kill掉试试看

[root@tmp3_72 mysql]# ps -eaf|grep mysql
root     16100 16094  0 12:00 ?        00:00:00 /bin/sh -c /home/data/mysql/backup/scripts/backup_full.sh
root     16104 16100  0 12:00 ?        00:00:00 /usr/local/mysql/bin/mysqldump -uroot --password= -R -E -h localhost --skip-opt --single-transaction --flush-logs --master-data=2 --add-drop-table --create-option --quick --extended-insert --set-charset --disable-keys --databases user_db plocc_system parking_db
root     18986 18967  0 13:19 pts/0    00:00:00 mysql
nagios   19486 19485  0 13:33 ?        00:00:00 /usr/local/nagios/libexec/check_mysql -unagios -P3306 -S -s /usr/local/mysql/mysql.sock -Hlocalhost --password=XXXXXXXXXXXX -d test -w 60 -c 100
root     19511 19121  0 13:34 pts/1    00:00:00 grep mysql
root     28242     1  0  2015 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/home/data/mysql/data --pid-file=/usr/local/mysql/mysqld.pid
mysql    29254 28242  9  2015 ?        16:56:36 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/home/data/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/usr/local/mysql/mysqld.log --open-files-limit=8192 --pid-file=/usr/local/mysql/mysqld.pid --socket=/usr/local/mysql/mysql.sock --port=3306
[root@tmp3_72 mysql]# kill -9 16104
[root@tmp3_72 mysql]# 
[root@tmp3_72 mysql]# 
[root@tmp3_72 mysql]# ps -eaf|grep mysql
root     18986 18967  0 13:19 pts/0    00:00:00 mysql
root     19555 19121  0 13:34 pts/1    00:00:00 grep mysql
root     28242     1  0  2015 ?        00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/home/data/mysql/data --pid-file=/usr/local/mysql/mysqld.pid
mysql    29254 28242  9  2015 ?        16:56:36 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/home/data/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/usr/local/mysql/mysqld.log --open-files-limit=8192 --pid-file=/usr/local/mysql/mysqld.pid --socket=/usr/local/mysql/mysql.sock --port=3306
[root@tmp3_72 mysql]#

6,kill掉备份进程后,执行一条简单的delete语句,会卡住,看来kill解决不了问题

mysql> select * from test;
+------+------+
| id   | name |
+------+------+
|    0 | NULL |
|    1 | NULL |
|    2 | NULL |
|    3 | NULL |
+------+------+
4 rows in set (0.02 sec)

mysql> 
mysql> 
mysql> delete from test where id=0;

去查询当前进程:

mysql> select User,State,count(1) from information_schema.processlist group by User,State;
+--------------+----------------------------------------+----------+
| User         | State                                  | count(1) |
+--------------+----------------------------------------+----------+
| cacti_user   |                                        |       17 |
| cacti_user   | executing                              |      657 |
| nagios       | executing                              |       38 |
| ploccsys_web |                                        |       10 |
| root         | executing                              |        1 |
| root         | init                                   |        2 |
| system user  | Queueing master event to the relay log |        1 |
| system user  | Waiting for commit lock                |        1 |
+--------------+----------------------------------------+----------+
8 rows in set (0.01 sec)

mysql>

有许多类似下面的线程:

| 516544 | cacti_user   | 192.168.11.12:42082 | NULL         | Query   |    375 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |
| 516564 | cacti_user   | 192.168.11.12:42208 | NULL         | Query   |    350 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |
| 516571 | cacti_user   | 192.168.11.12:42215 | NULL         | Query   |    330 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |
| 516575 | cacti_user   | 192.168.11.12:42218 | NULL         | Query   |    320 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |
| 516592 | cacti_user   | 192.168.11.12:42230 | NULL         | Query   |    315 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |
| 516598 | cacti_user   | 192.168.11.12:42359 | NULL         | Query   |    300 | executing                              | SHOW /*!50002 GLOBAL */ STATUS |

要不要重启mysql db或者是重启cacti服务器?想来还是继续再看看,万不得已不能用绝招。^_^


7,写了个脚本来kill这些来自cacti的show status的线程

[root@tmp3_72 scripts]# vim killsleep_cacti.sh 

#It is used to kill processlist of mysql sleep
#!/bin/sh
while :

do
  n=`mysqladmin processlist -uroot --password=""|grep -i sleep |wc -l`
  date=`date +%Y%m%d\[%H:%M:%S]`
  echo $n

  if [ "$n" -gt 10 ]
  then
  echo "begin kill the sleep processlist ......"
  for i in `mysqladmin processlist -uroot --password=""|grep -i sleep |awk '{print $2}'`
  do
     mysqladmin -uroot --password="" kill $i
  done
  echo "begin kill the executing cacti show status processlist ......"
  for i in `mysqladmin processlist -uroot --password=""|grep -i executing |grep -i SHOW|grep -i STATUS |grep -i cacti |awk '{print $2}'`
  do
     mysqladmin -uroot --password="" kill $i
  done

  echo "sleep is too many I killed it " >> /tmp/sleep.log
  echo "$date : $n" >> /tmp/sleep.log
  fi
  sleep 1
done

执行后,通过show full processlist;这些线程仍然存在,只是状态从excuteing变成了killed而已:其它问题仍然没有改观

mysql> select User,Command,count(1) from information_schema.processlist group by User,Command;
+--------------+---------+----------+
| User         | Command | count(1) |
+--------------+---------+----------+
| cacti_user   | Killed  |      792 |
| cacti_user   | Query   |        4 |
| cacti_user   | Sleep   |        1 |
| nagios       | Query   |       48 |
| ploccsys_web | Sleep   |       10 |
| root         | Killed  |        1 |
| root         | Query   |        2 |
| root         | Refresh |        1 |
| system user  | Connect |        2 |
+--------------+---------+----------+
9 rows in set (0.01 sec)

mysql>

7,重新查看slave状态,还是无效

mysql> show slave status\G
ERROR 2006 (HY000): MySQL server has gone away
No connection. Trying to reconnect...
Connection id:    523275
Current database: test

8,去查看engine状态

mysql> show engine innodb status\G
*************************** 1. row ***************************
  Type: InnoDB
  Name: 
Status: 
=====================================
2016-01-06 15:08:52 7f3ef5a19700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 50 seconds
-----------------
BACKGROUND THREAD
-----------------
srv_master_thread loops: 475489 srv_active, 0 srv_shutdown, 195322 srv_idle
srv_master_thread log flush and writes: 670800
----------
SEMAPHORES
----------
OS WAIT ARRAY INFO: reservation count 697461
OS WAIT ARRAY INFO: signal count 710269
Mutex spin waits 2340260, rounds 18635522, OS waits 498279
RW-shared spins 132111, rounds 3631441, OS waits 119612
RW-excl spins 25200, rounds 2213112, OS waits 70336
Spin rounds per wait: 7.96 mutex, 27.49 RW-shared, 87.82 RW-excl
------------
TRANSACTIONS
------------
Trx id counter 1247855846
Purge done for trx's n:o < 1247855835 undo n:o < 0 state: running but idle
History list length 2442
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 0, not started
MySQL thread id 523731, OS thread handle 0x7f3ef5a19700, query id 150089612 localhost root init
show engine innodb status
---TRANSACTION 0, not started
MySQL thread id 522540, OS thread handle 0x7f3ef7ea8700, query id 150086762 localhost root executing
SHOW /*!50002 GLOBAL */ STATUS
---TRANSACTION 1247853306, not started
MySQL thread id 35671, OS thread handle 0x7f4bf8083700, query id 0 Queueing master event to the relay log
---TRANSACTION 1247855447, ACTIVE 11330 sec
mysql tables in use 1, locked 1
2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 35672, OS thread handle 0x7f4bf8052700, query id 150059111 Waiting for commit lock
--------
FILE I/O
--------
I/O thread 0 state: waiting for i/o request (insert buffer thread)
I/O thread 1 state: waiting for i/o request (log thread)
I/O thread 2 state: waiting for i/o request (read thread)
I/O thread 3 state: waiting for i/o request (read thread)
I/O thread 4 state: waiting for i/o request (read thread)
I/O thread 5 state: waiting for i/o request (read thread)
I/O thread 6 state: waiting for i/o request (write thread)
I/O thread 7 state: waiting for i/o request (write thread)
I/O thread 8 state: waiting for i/o request (write thread)
I/O thread 9 state: waiting for i/o request (write thread)
Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,
 ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0
Pending flushes (fsync) log: 0; buffer pool: 0
9067 OS file reads, 129305477 OS file writes, 12754944 OS fsyncs
0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s
-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 11, seg size 13, 0 merges
merged operations:
 insert 0, delete mark 0, delete 0
discarded operations:
 insert 0, delete mark 0, delete 0
Hash table size 75692179, node heap has 2437 buffer(s)
0.00 hash searches/s, 0.00 non-hash searches/s
---
LOG
---
Log sequence number 418264709881
Log flushed up to   418264709881
Pages flushed up to 418264709881
Last checkpoint at  418264709881
0 pending log writes, 0 pending chkp writes
110060310 log i/o's done, 0.00 log i/o's/second
----------------------
BUFFER POOL AND MEMORY
----------------------
Total memory allocated 35165044736; in additional pool allocated 0
Dictionary memory allocated 1420595
Buffer pool size   2097144
Free buffers       1375231
Database pages     719476
Old database pages 265424
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 1474, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 91286, created 628190, written 34807582
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 719476, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
----------------------
INDIVIDUAL BUFFER POOL INFO
----------------------
---BUFFER POOL 0
Buffer pool size   262143
Free buffers       172002
Database pages     89836
Old database pages 33142
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 97, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11534, created 78302, written 5644943
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 89836, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 1
Buffer pool size   262143
Free buffers       171914
Database pages     89924
Old database pages 33174
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 259, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11425, created 78499, written 3352647
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 89924, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 2
Buffer pool size   262143
Free buffers       171570
Database pages     90267
Old database pages 33301
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 158, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11720, created 78547, written 2182721
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 90267, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 3
Buffer pool size   262143
Free buffers       172120
Database pages     89724
Old database pages 33100
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 141, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11117, created 78607, written 8155448
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 89724, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 4
Buffer pool size   262143
Free buffers       171665
Database pages     90167
Old database pages 33264
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 142, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11675, created 78492, written 7450709
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 90167, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 5
Buffer pool size   262143
Free buffers       172081
Database pages     89746
Old database pages 33108
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 185, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11258, created 78488, written 3505157
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 89746, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 6
Buffer pool size   262143
Free buffers       173275
Database pages     88569
Old database pages 32674
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 336, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 10622, created 77947, written 2488265
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 88569, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
---BUFFER POOL 7
Buffer pool size   262143
Free buffers       170604
Database pages     91243
Old database pages 33661
Modified db pages  0
Pending reads 0
Pending writes: LRU 0, flush list 0, single page 0
Pages made young 156, not young 0
0.00 youngs/s, 0.00 non-youngs/s
Pages read 11935, created 79308, written 2027692
0.00 reads/s, 0.00 creates/s, 0.00 writes/s
No buffer pool page gets since the last printout
Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s
LRU len: 91243, unzip_LRU len: 0
I/O sum[0]:cur[0], unzip sum[0]:cur[0]
--------------
ROW OPERATIONS
--------------
0 queries inside InnoDB, 0 queries in queue
0 read views open inside InnoDB
Main thread process no. 29254, id 139925894604544, state: sleeping
Number of rows inserted 71967166, updated 94419979, deleted 45975, read 7366029184
0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s
----------------------------
END OF INNODB MONITOR OUTPUT
============================

1 row in set (0.02 sec)

mysql>

9,去查看是否有locks信息

mysql> select * from information_schema.INNODB_TRX\G;
*************************** 1. row ***************************
                    trx_id: 1247855447
                 trx_state: RUNNING
               trx_started: 2016-01-06 12:00:02
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 3
       trx_mysql_thread_id: 35672
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 1
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 376
           trx_rows_locked: 1
         trx_rows_modified: 1
   trx_concurrency_tickets: 0
       trx_isolation_level: READ COMMITTED
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 10000
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.00 sec)

ERROR: 
No query specified

mysql>

mysql> select * from INNODB_LOCKS;
Empty set (0.00 sec)

mysql> 
mysql> 
mysql> 
mysql> select * from INNODB_LOCK_WAITS;
Empty set (0.00 sec)

mysql>

有一个事务是running状态,根据trx_mysql_thread_id: 35672查询processlist表:

mysql> select * from processlist where id=35672;
+-------+-------------+------+------+---------+-------+-------------------------+------+
| ID    | USER        | HOST | DB   | COMMAND | TIME  | STATE                   | INFO |
+-------+-------------+------+------+---------+-------+-------------------------+------+
| 35672 | system user |      | NULL | Connect | 11948 | Waiting for commit lock | NULL |
+-------+-------------+------+------+---------+-------+-------------------------+------+
1 row in set (0.01 sec)

mysql>

正常的从服务器是:

mysql> select * from processlist order by id limit 2;
+----------+-------------+------+------+---------+--------+------------------------------------------------------------------+------+
| ID       | USER        | HOST | DB   | COMMAND | TIME   | STATE                                                            | INFO |
+----------+-------------+------+------+---------+--------+------------------------------------------------------------------+------+
| 29903251 | system user |      | NULL | Connect | 624229 | Waiting for master to send event                                 | NULL |
| 29903252 | system user |      | NULL | Connect |      5 | Slave has read all relay log; waiting for the slave I/O thread t | NULL |
+----------+-------------+------+------+---------+--------+------------------------------------------------------------------+------+
2 rows in set (0.00 sec)

mysql>

而我的这台出故障的从服务器是:

mysql> select * from processlist order by id limit 2;
+-------+-------------+------+------+---------+--------+----------------------------------------+------+
| ID    | USER        | HOST | DB   | COMMAND | TIME   | STATE                                  | INFO |
+-------+-------------+------+------+---------+--------+----------------------------------------+------+
| 35671 | system user |      | NULL | Connect | 624374 | Queueing master event to the relay log | NULL |
| 35672 | system user |      | NULL | Connect |  12379 | Waiting for commit lock                | NULL |
+-------+-------------+------+------+---------+--------+----------------------------------------+------+
2 rows in set (0.01 sec)

mysql>

再次查看统计,确定问题所在为system user | Connect | Waiting for commit lock 线程导致:

mysql> select User,Command,State,count(1) from information_schema.processlist group by User,Command,State;
+--------------+---------+----------------------------------------+----------+
| User         | Command | State                                  | count(1) |
+--------------+---------+----------------------------------------+----------+
| cacti_user   | Killed  | executing                              |      792 |
| cacti_user   | Query   | executing                              |      344 |
| nagios       | Query   | executing                              |       72 |
| ploccsys_web | Sleep   |                                        |       10 |
| root         | Killed  | executing                              |        1 |
| root         | Killed  | init                                   |        5 |
| root         | Query   | executing                              |        1 |
| root         | Query   | Waiting for table flush                |        1 |
| root         | Refresh | init                                   |        1 |
| system user  | Connect | Queueing master event to the relay log |        1 |
| system user  | Connect | Waiting for commit lock                |        1 |
+--------------+---------+----------------------------------------+----------+
11 rows in set (0.02 sec)

mysql>

10,问题解决

事情到此地步,已经很明确了就是Waiting for commit lock 这个导致的全局锁,所以kill掉innodb_trx中的running事务的mysql线程:

mysql> kill 35672
    -> ;
Query OK, 0 rows affected (0.00 sec)

mysql>

然后再去check processlist;已经正常了,如下所示:

mysql> show full processlist;
+--------+--------------+-------------------+--------------------+---------+-------+----------------------------------+-----------------------+
| Id     | User         | Host              | db                 | Command | Time  | State                            | Info                  |
+--------+--------------+-------------------+--------------------+---------+-------+----------------------------------+-----------------------+
| 527023 | root         | localhost         | information_schema | Query   |     0 | init                             | show full processlist |
| 527165 | system user  |                   | NULL               | Connect |    54 | Waiting for master to send event | NULL                  |
| 527166 | system user  |                   | NULL               | Connect | 12548 | System lock                      | NULL                  |
| 527167 | ploccsys_web | 192.168.12.28:33006 | plocc_system       | Sleep   |    28 |                                  | NULL                  |
| 527168 | ploccsys_web | 192.168.12.28:33007 | plocc_system       | Sleep   |    28 |                                  | NULL                  |
| 527169 | ploccsys_web | 192.168.12.28:33008 | plocc_system       | Sleep   |    28 |                                  | NULL                  |
| 527170 | ploccsys_web | 192.168.12.28:33009 | plocc_system       | Sleep   |    28 |                                  | NULL                  |
| 527171 | ploccsys_web | 192.168.12.28:33010 | plocc_system       | Sleep   |    28 |                                  | NULL                  |
| 527172 | ploccsys_web | 192.168.12.29:43473 | plocc_system       | Sleep   |    16 |                                  | NULL                  |
| 527173 | ploccsys_web | 192.168.12.29:43474 | plocc_system       | Sleep   |    16 |                                  | NULL                  |
| 527174 | ploccsys_web | 192.168.12.29:43476 | plocc_system       | Sleep   |    16 |                                  | NULL                  |
| 527175 | ploccsys_web | 192.168.12.29:43477 | plocc_system       | Sleep   |    16 |                                  | NULL                  |
| 527176 | ploccsys_web | 192.168.12.29:43478 | plocc_system       | Sleep   |    16 |                                  | NULL                  |
+--------+--------------+-------------------+--------------------+---------+-------+----------------------------------+-----------------------+
13 rows in set (0.00 sec)

mysql>

在查看slave状态,是No状态,如下所示:

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: 
                  Master_Host: 192.168.12.71
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin.037579
          Read_Master_Log_Pos: 2172311
               Relay_Log_File: mysql-relay-bin.002685
                Relay_Log_Pos: 13453336
        Relay_Master_Log_File: mysql-bin.037578
             Slave_IO_Running: No
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 13453173
              Relay_Log_Space: 15626372
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 71
                  Master_UUID: 9b0dcf62-29f4-11e3-9471-677b33903869
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: 
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: 
            Executed_Gtid_Set: 
                Auto_Position: 0
1 row in set (0.00 sec)

mysql>

之后启动slave,start slave;搞定。


11,网友 Aeolus@普 的提示

DEADLOCK ON FLUSH TABLES WITH READ LOCK + SHOW SLAVE STATUS Problem: If a client thread on an slave does FLUSH TABLES WITH READ LOCK; then master does some updates, SHOW SLAVE STATUS in the same client will be blocked. Analysis: Execute FLUSH TABLES WITH READ LOCK on slave and at the same time execute a DML on the master. Then the DML should be made to stop at a state “Waiting for commit lock”. This state means that sql thread is holding rli->data_lock and waiting for MDL_COMMIT lock. Now in the same client session where FLUSH TABLES WITH READ LOCK was executed issue SHOW SLAVE STATUS command. This command will be blocked waiting for rli->data_lock causing a dead lock. Once this happens it will not be possible to release the global read lock as “UNLOCK TABLES” command has to be issued in the same client where global read lock was acquired. This causes the dead lock. Fix: Existing code holds the rli->data_lock for the whole duration of commit operation. Instead of holding the lock for entire commit duration the code has been restructured in such a way that the lock is held only during the period when rli object is being updated.

然后去查了下我这台db从服务器,确实有每我这台从库是 有做mysqldump备份的,一个小时备份一次,然后cacti监控也会每分钟就来抽取一次数据,可能还真的是 mysqldump+cacti的show slave status一起执行导致的bug 出现。我先去将备份改成4个小时一次,继续观察后续情况。

作者:mchdba 发表于2016/1/6 21:37:49 原文链接
阅读:0 评论:0 查看评论

一名网站数据分析师需要具备这9大本领

$
0
0

如果你想成为网站分析师!想加入网站分析这个即有前途又有“钱途”的行业,那么你至少需要具备本文所提到的9大本领。

1、玩转Excel

3417eb9bbd9017f56fce36
Excel是一个最原始而且最容易入手的分析工具之一,如果你有少量的 数据进行分析和汇总的话,Excel是你的不二之选,结合丰富的函数与公式,你能轻松的得到你想要的数据,如果你懂得计算机语言,会使用VBA进行编程那就更是如虎添翼了,并且还可以轻松的制作棒图,饼图,折线图等图表。但是Excel不可能是完美的分析工具,因为他的数据容量实在是太小了,超过1万行的数据请不要使用Excel。

1.1.常用函数:

3417eb9bbd9017f56fd637
1.2.常用功能:

3417eb9bbd9017f56fdc38
2、网站分析基础知识

3417eb9bbd9017f56fe539
了解一些网站分析的基础知识是必须的,你要知道什么是会话,什么是PV,什么是UU/UV等指标值的含义。如下图(摘自《网站分析基础教程第二章》)所示:

3417eb9bbd9017f56fee3a
3、网站开发的知识

3417eb9bbd9017f56ff73b
网站分析师通过衡量各种指标值的优劣来评价网站的状况,以及提出改善优化的对策,如果分析师自己对网站的开发和构筑知识一点都不了解,也就不能准确的通过分析指标值的高低衡量网站的运营状况。

作为一名合格的网站分析师,你需要了解一些网站建设和运营的知识,还有网站设计的知识,以及用户体验相关的知识。这样的话你才能提出更有高度和深度的分析报告。

4、网络营销的知识

3417eb9bbd9017f5702b3e
网站分析师的工作范围从宏观上可以分为“站内”和“站外”两大领域。站内重点在于改善用户体验,优化转化路径,SEO,分析用户行为等站内活动;站外的工作重点则在于如何更多更准确更优质的吸引用户进入网站。

所谓站外的工作主要就是指网络营销,网络营销按照具体的实现方式可以分为:展示广告(DisplayAdvertising)、PPC推广、SEO、邮件营销、视频推广、QQ群推广、博客营销、微博营销、SNS营销等。如果想成为网站分析师你需要学习如下知识:

4.1.广告类型

搜索引擎广告(PPC)

交换链接

横幅广告

邮件营销

传统媒体广告

4.2.广告相关指标

展现数(Impressions)

点击数

点击率(Click-throughRate)

CPC(CostPerClick)

CPA(CostPerAcquisition)

转化率(ConversionRate)

ROAS(ReturnOnAdvertisingSpend)

4.3.SEO知识

主流搜索引擎的排名算法

TITLE,META,Hn,h1等优化

5、测试方法

3417eb9bbd9017f570373f
当网站分析指标的数值变得不是非常乐观的时候,或者你想做一次大规模的推广的时候,也可能是你需要对网站进行改版的时候,作为分析师需要预知改善后的效果是否能够达到预期,这一点是光凭经验很难做到的事情,那么就需要网站分析师聪明的利用师验方法进行验证,这是最直接而且准确有效的方法。

做网站分析师需要学会使用如:A/B测试,多变量测试,用户体验测试等测试方法对改善方案进行预评估,以减少新方案的实施风险。

6、交流能力

3417eb9bbd9017f5704040
作为一名网站分析师,你需要和很多的人协同完成工作任务,其中包括项目经理,产品经理,运营经理,实施经理以及工具提供商等。高效率,准确的交流显得尤为重要。

对于交流来说,语言的表达能力作为最基本的能力要素不可或缺,但想要能顺畅的交流仅仅依靠语言是远远不够的,还需要有一定的资料的组织能力和总结能力,以及团队合作意识。

7、演讲的能力

3417eb9bbd9017f5704541
当以网站分析师为主导进行一次网站的改版或升级的时候,通常的做法是用数字和图表来说服决策层和保守派,但事实上并不那么简单,说服更多人除了靠准确的分析 数据以外,还需要网站分析师非常具有煽动性的演讲,以及面对质疑从容不迫的回应。网站分析师需要把自己的自信通过演讲的形式传播给参加会议的所有在场的人。

8、会做PPT

演讲和演示的时候,必备的利器!当然如果你能够做出很炫的动画效果将能感染更多的。

9、计划管理能力

如果你在一家小公司担任网站分析师职务的话,计划管理可能显得不那么重要,但如果你是一家大公司的网站运营经理,或者带领一个几十人的分析师团队的话,计划的管理能力将显得尤为重要。为了更好的和项目经理以及公司管理层的交流你需要具备这项技能,甚至有必要学习一些项目管理的相关知识,比如PMP认证等。

您可能也喜欢的文章:

什么数据库最适合数据分析师

如何成为网站数据分析师?

给网站数据分析师的五个忠告

网站数据分析:重点不在数据在于分析(一)

数据分析师必懂的10种分析思维
无觅

IE8,9,10 将在下周二寿终正寝

$
0
0

让一个软件死亡,那就是不再给它更新版本、修补漏洞以及给用户提供技术支持。

微软官方发布公告 ,「臭名昭著」的 Internet Explorer 8,9,10 三个版本的浏览器将会在下周二(1 月 12 日)迎来它们寿终正寝的日子,并告知升级 IE 浏览器的办法。

下周二,微软将会推送一个 KB31323303 的补丁催促依旧使用旧版 IE 浏览器的用户升级到最新版本的浏览器。

这对于开发者来说是一个好消息,因为它们再也不必为适配这些僵尸浏览器而感到郁闷了,因为微软也已经放弃了,一位程序员说道,它们听到 IE 死亡就如同魔法世界的人听到伏地魔被消灭一样感到兴奋。

微软说,IE 11 这次虽然能够苟延残喘,但它的日子也将不会久了,Windows 7,Windows 8.1 和 Windows 10 的用户仅会在很短的时间内接收到有关 IE 11 的安全更新,漏洞修补和技术支持。

如果你为了一些不知道为什么的奇怪理由继续使用旧版本的 IE 的话,你将可能面对很多的小问题而且得不到技术支持,如果你非要使用微软的浏览器的话,你不妨考虑使用 Windows 10 内置的 Edge 浏览器。


支付宝、钉钉、格瓦拉等接入优步API开放平台

$
0
0

【TechWeb报道】1月6日消息,优步中国宣布,除百度、穷游网以及渡鸦科技等首批合作伙伴外支付宝、钉钉、格瓦拉等APP也即将接入优步API。

130043503

支付宝、钉钉、格瓦拉等接入优步API开放平台

2014年8月Uber API开放平台最早在美国推出,接入优步开发者开放平台的公司包括微软、美联航、TripAdvisor、Open Table、希尔顿酒店等企业。这些公司直接在应用内集成优步的叫车服务,可以实现查看周围可用车辆、预估接驾时间和车费、查看历史行程等功能。

优步中国透露,截至到目前,通过中国优步开放平台的合作伙伴已经为Uber贡献超过5567万公里出行。

优步中国表示,优步提供简洁提供给用户简洁交互的体验背后,实际上隐藏了一系列复杂和庞大的技术支持,我们愿意把这些技术看作像原子转换一样的基础设施。Uber API支持多平台多语言开发,欢迎所有企业成为Uber API开放计划的合作伙伴,无论是成熟、中型亦或初创企业。(明宇)

蚕豆网微信

记一次 MySQL 数据库问题排查

$
0
0

最近遇到应用频繁的响应缓慢,无法正常访问。帮忙一起定位原因,最后定位到的问题说起来真的是很小的细节问题,但是就是这些小细节导致了服务不稳定,真是细节决定成败。这里尝试着来分享下,希望对大家有所帮助。

问题 1:占着茅坑不拉屎

遇到问题首先要看的还是服务器错误日志。

错误日志中看到频繁有这样的一个异常报错: Error: ER_CON_COUNT_ERROR: Too many connections。这个报错是因为数据库的所有连接被客户端都占有了,没有空闲的连接可以使用。MySQL 默认的最大并发连接数是 100,然而我们的应用这边最多可能的并发也就 30~40 个任务,怎么也不太可能报这样的错误,推测很有可能是代码里面建立连接后没有及时的进行关闭。于是我们重点看了下执行 SQL 部分的代码,大概是下面这样(使用了node-mysql库):

var mysql = require('mysql');
// 建立连接池
var pool = mysql.createPool({
    host: 'host',
    user: 'user',
    password: 'password',
    database: 'db'
});

exports.query = function(sql, cb) {
    // 从池子里面取一个可用连接
    pool.getConnection(function(err, connection) {
        if (err) throw err;
        // 执行sql
        connection.query(sql, function(err, rows, fields) {
            if (err) {
                return cosole.error(err);
            }
            cb(rows);
        });
        // 释放此连接
        connection.release();
    });

};

刚开始我还真没看出来有什么问题,后来仔细读了 node-mysql的文档及这个 issue,终于发现了我们的写法是有问题的。

再次看看上面的代码, pool.getConnection后我们执行 connection.query,然后没等 SQL 执行完,直接调用了 connection.release,由于 JavaScript 的异步特性(虽然 SQL 可能很快就执行完,但是我们也必须在 connection.query的 callback 里面才明确的知道 SQL 执行完了),这个时候此次连接是不会被释放的!代码里面所有的 SQL 执行都调用到这个函数,这意味着我们占着一堆数据库连接不释放,这时不断的有其他数据库连接过来,直接导致其他连接被阻塞,抛出连接太多的异常。这真是典型的“拉完不及时让坑,占着茅坑不拉屎”的行为。所以,我们一定要在 SQL 执行完成后就将连接及时进行释放。因为 SQL 执行一般是非常快的(零点几秒),如果我们执行完后不释放,在同一时间产生很多数据库连接时很有可能导致连接被阻塞,产生连接过多的异常。于是我们对代码进行了如下修改:

exports.query = function(sql, cb) {
    // 从池子里面取一个可用连接
    pool.getConnection(function(err, connection) {
        if (err) throw err;
        // 执行sql
        connection.query(sql, function(err, rows, fields) {
            // 释放连接(一定要在错误处理前,不然出错的时候也会导致该连接得不到释放)
           connection.release();

            if (err) {
                return cosole.error(err);
            }
            cb(rows);
        });
    });

};

也可以用更简单的写法 pool.query,这个方法内部会在合适的时机来释放连接,不用我们手动操作。

完成此次修改后,这个异常没有再复现,但是响应缓慢的情况依然没有得到缓解。

问题 2:一条 UPDATE 引发的血案

我们再次查看了错误日志,发现了另一个异常报错: Error: ER_LOCK_WAIT_TIMEOUT: Lock wait timeout exceeded; try restarting transaction。这个报错就非常令人费解了,原因是锁等待超时,当前事务在等待其它事务释放锁资源造成的。

我们先大概说下什么是事务(transaction)。事务应该具有 4 个属性:

  • 原子性(事务作为整体执行,操作要么全部执行、要么全部不执行)

  • 一致性(事务应该确保数据库状态从一个一致状态转变为另一个一致状态)

  • 隔离性(多个事务并发执行时,一个事务执行不影响其他事务执行)

  • 持久性(事务提交后,对数据库修改应该永久保存在数据库中)

对于隔离性,还会分出多个隔离级别:

隔离级别脏读不可重复读幻读
未提交读可能可能可能
已提交读不可能可能可能
可重复读不可能不可能可能
未串行化不可能不可能不可能
  • 脏读(Dirty Read):A 事务读到 B 事务未提交的修改。

  • 不可重复读(NonRepeatable Read):A 事务还没有结束时,B 事务也访问同一数据。在 A 事务的两次读取之间,由于 B 事务的修改,A 事务两次读到的数据可能是不一样的。

  • 幻读(Phantom Read):A 事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,B 事务也修改这个表中的数据,这种修改是向表中插入一行新数据。操作 A 事务的用户发现表中出现了 B 事务插入的行,就好象发生了幻觉一样。

MySQL 默认的级别是 REPEATABLE READ(可重复读),这表示在 MySQL 的默认情况下,“脏读”、“不可重复读”是不会发生的。这就需要在更新的时候进行必要的锁定(InnoDB 是采用行级锁的方式),从而保证一致性。需要注意的是 InnoDB 的行锁是通过给索引上的索引项加锁来实现的,这个特点意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!

我们数据库表是 InnoDB 引擎的表,而 MySQL 的 InnoDB 引擎是一个支持事务的引擎,其默认操作模式是 autocommit 自动提交模式。什么意思呢?除非我们显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行。

回到上面的报错,错误日志里抛出异常时执行的 SQL 语句,都是类似这样的一条 UPDATE 语句: update testScore set status=1,executionId='946012' where token='f7900c40-8f4b-11e5-b2f1-6feca76a1bf5'

问题产生的原因可以这样来描述了:我们在执行 UPDATE 语句时,MySQL 会将其当成一个事务,对表的行进行锁定,这时又有其他连接进来要 UPDATE 同样的表或者 SELECT 这张表时就必须等待锁资源,而这个等待时间太久,导致超时了。

什么?一个 UPDATE 语句居然会这么慢?这我简直不能接受啊!那我只能看看为啥这个语句如此慢了。

查看慢查询(slow_queries.log)日志里面对应的查询信息:

# Query_time: 56.855324  Lock_time: 48.054343 Rows_sent: 0  Rows_examined: 29400
update testScore set uiTaskId=81041 where token='e7d7d8f0-8f4b-11e5-99be-9dfbb419755e';

这样一条 UPDATE 语句花了 56 秒,扫了 29400 条表记录。看到这样的执行日志,也大概猜到原因了,没有为查询字段 token 加索引!这样 MySQL 在进行 update 操作时不会走行锁,直接锁定了整张表,而这个 update 语句本身也够慢(扫了全表),那并发多个 update 更新时导致了等待锁超时。

给 testScore 表的 token 字段增加了索引,终于,这个异常不再复现,响应时间开始回归正常。

参考资料

ThreadLocal的内存泄露

$
0
0

ThreadLocal的目的就是为每一个使用ThreadLocal的线程都提供一个值,让该值和使用它的线程绑定,当然每一个线程都可以独立地改变它绑定的值。如果需要隔离多个线程之间的共享冲突,可以使用ThreadLocal,这将极大地简化你的程序.

关于的ThreadLocal更多内容,请参考《 ThreadLocal》。

在阅读了ThreadLocal的源码后,我发现如果我们使用不恰当,可能造成内存泄露。经我测试,内存泄露的确存在。虽然该内存泄露,理论上上已经不算严重。

测试代码如下

ThreadLocalTest文件

package com.teleca.robin;

 

 

public class ThreadLocalTest {

 

 public ThreadLocalTest()

 {

 

 }

 ThreadLocal<Content> tl=new ThreadLocal<Content> ();

 void start()

 {

  System.out.println("begin");

  Content content=tl.get();

  if(content==null)

  {

   content= new Content();

   tl.set(content);

  }

  System.out.println("try to release content data");

  //tl.set(null);//@1

  //tl.remove();//@2

  tl=null;//@3

  content=null;//@4

  System.out.println("request gc");

  System.gc();

  try {

   Thread.sleep(1000);

  } catch (InterruptedException e) {

   // TODO Auto-generated catch block

   e.printStackTrace();

  }

  System.out.println("end");

 }

}

class Content

{

 byte data[]=new byte[1024*1024*10];

 protected void finalize()

 {

  System.out.println("I am released");

 }

}

运行结果

begin

try to release content data

request gc

end

注意我们尝试在@3和@4处,释放对tl和content的引用,以便JAVA虚拟机回收content。但是测试结果表明还有对content的引用,以致它没有能被JAVA虚拟机回收。

我们必须把@1或@2处的代码打开,才能把让它让JAVA虚拟机回收content.推荐打开@2而不是@1

把@1或@2处的代码打开后的运行结果如下:

begin

try to release content data

request gc

I am released

end

另外注意,@3其实并不影响运行结果。

 

 

事实上每个Thread实例都有一个ThreadLocalMap成员变量,它以ThreadLocal对象为key,以ThreadLocal绑定的对象为Value。

.我们调用ThreadLocal的set()方法,只是把要绑定的对象存放在当前线程的ThreadLocalMap成员变量中,以便下次通过get()方法取得它。

ThreadLocalMap和普通map的最大区别就是它的Entry是针对ThreadLocal弱引用的,即当ThreadLocal没有其他引用为空时,JVM就可以GC回收ThreadLocal,从而得到一个null的key。

关于ThreadLocalMap的更多内容请参考《 为ThreadLocal定制的ThreadLocalMap

 

 

ThreadlocalMap维护了ThreadLocal对象和其绑定对象之间的关系,这个ThreadLocalMap有threshold,当超过threshold时,

ThreadLocalMap会首先检查内部ThreadLocal引用(前文说过,ThreadLocal是弱引用可以释放)是否为null,如果存在 null,那么把绑定对象的引用设置为null,以便释放ThreadLocal绑定的对象,这样就腾出了位置给新的ThreadLocal。如果不存在 slate threadlocal,那么double threshold。

除此之外,还有两个机会释放掉已经废弃的ThreadLocal绑定的对象所占用的内存,

一、当hash算法得到的table index刚好是一个null 的key的threadlocal时,直接用新的ThreadLocal替换掉已经废弃的。

二、每次在ThreadLocalMap中存放ThreadLocal,hash算法没有命中既有Entry,需要新建一个Entry时,也调用cleanSomeSlots来遍历清理Entry数组中已经废弃的ThreadLocal绑定的对象的引用。

此外,当Thread本身销毁时,这个ThreadLocalMap也一定被销毁了(ThreadLocalMap是Thread对象的成员),

这样所有绑定到该线程的ThreadLocal的Object Value对象,如果在外部没被引用的话(通常是这样),也就没有任何引用继续保持,所以也就被销毁回收了。

 

 

从上可以看出 Java已经充分考虑了时间和空间的权衡,但是因为置为null的ThreadLocal对应的Object Value在无外部引用时,任然无法及时回收。

ThreadLocalMap只有到达threshold时或添加entry时才做检查,不似gc是定时检查,

不过我们可以手工通过ThreadLocal的remove()方法或set(null)解除ThreadLocalMap对ThreadLocal绑定 对象的引用,及时的清理废弃的threadlocal绑定对象的内存以。remove()往往还能做更多的清理工作,因此推荐使用它,而不使用 set(null).

需要说明的是,只要不往不用的threadlocal中放入大量数据,问题不大,毕竟还有回收的机制。

 

 

被废弃了的ThreadLocal所绑定对象的引用,会在以下4情况被清理。

如果此时外部没有绑定对象的引用,则该绑定对象就能被回收了:

1 Thread结束时。

2 当Thread的ThreadLocalMap的threshold超过最大值时。

3 向Thread的ThreadLocalMap中存放一个ThreadLocal,hash算法没有命中既有Entry,而需要新建一个Entry时。

4 手工通过ThreadLocal的remove()方法或set(null)。

 

 

因此如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。

 

http://www.2cto.com/kf/201112/113451.html



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



中文文本处理简要介绍

$
0
0

本文作者李绳,博客地址 http://acepor.github.io/。作者自述:

一位文科生曾励志成为语言学家
出国后阴差阳错成了博士候选人
三年后交完论文对学术彻底失望
回国后误打误撞成了数据科学家

作为一个处理自然语言数据的团队,我们在日常工作中要用到不同的工具来预处理中文文本,比如 Jieba Stanford NLP software。出于准确性和效率的考虑,我们选择了Stanford NLP software, 所以本文将介绍基于 Stanford NLP software 的中文文本预处理流程。

中文文本处理简要介绍

与拉丁语系的文本不同,中文并不使用空格作为词语间的分隔符。比如当我们说“We love coding.”,这句英文使用了两个空格来分割三个英文词汇;如果用中文做同样的表述, 就是“我们爱写代码。”,其中不包含任何空格。因而,处理中文数据时,我们需要进行分词,而这恰恰时中文自然语言处理的一大难点。

下文将介绍中文文本预处理的几个主要步骤:

  1. 中文分词
  2. 标注词性
  3. 生成词向量
  4. 生成中文依存语法树

Stanford NLP software 简要介绍

Stanford NLP software 是一个较大的工具合集:包括  Stanford POS tagger 等组件,也有一个包含所有组件的合集  Stanford CoreNLP。各个组件是由不同的开发者开发的,所以每一个工具都有自己的语法。当我们研究这些组件的文档时,遇到了不少问题。下文记录这些问题和相对应的对策,以免重蹈覆辙。

Stanford NLP 小组提供了一个简明的FAQ—— Stanford Parser FAQ 和一份详细的Java文档 —— Stanford JavaNLP API Documentation。在这两份文档中,有几点格外重要:

尽管PSFG分词器小且快,Factored分词器更适用于中文,所以我们推荐使用后者。

中文分词器默认使用GB18030编码(Penn Chinese Treebank的默认编码)。

使用 -encoding 选项可以指定编码,比如 UTF-8,Big-5 或者 GB18030。

中文预处理的主要步骤

1. 中文分词

诚如上面所言,分词是中文自然语言处理的一大难题。 Stanford Word Segmenter是专门用来处理这一问题的工具。FAQ请参见 Stanford Segmenter FAQ。具体用法如下:

bash -x segment.sh ctb INPUT_FILE UTF-8 0

其中 ctb是词库选项,即 Chinese tree bank,也可选用 pku,即 Peking University。 UTF-8是输入文本的编码,这个工具也支持 GB18030 编码。最后的0指定 n-best list 的大小,0表示只要最优结果。

2. 中文词性标注

词性标注是中文处理的另一大难题。我们曾经使用过 Jieba 来解决这个问题,但效果不尽理想。Jieba 是基于词典规则来标注词性的,所以任意一个词在 Jieba 里有且只有一个词性。如果一个词有一个以上的词性,那么它的标签就变成了一个集合。比如“阅读”既可以表示动词,也可以理解为名词,Jieba 就会把它标注成 n(名词),而不是根据具体语境来给出合适的 v(动词)或 n(名词)的标签。这样一来,标注的效果就大打折扣。幸好 Stanford POS Tagger 提供了一个根据语境标注词性的方法。具体用法如下:

java -mx3000m -cp "./*" edu.stanford.nlp.tagger.maxent.MaxentTagger -model models/chinese-distsim.tagger -textFile INPUT_FILE

-mx3000m指定内存大小,可以根据自己的机器配置选择。 edu.stanford.nlp.tagger.maxent.MaxentTagger用于选择标注器,这里选用的是一个基于最大熵(Max Entropy)的标注器。 models/chinese-distsim.tagger用于选择分词模型。

3. 生成词向量

深度学习是目前机器学习领域中最热门的一个分支。而生成一个优质的词向量是利用深度学习处理 NLP 问题的一个先决条件。除了 Google 的 Word2vec,Stanford NLP 小组提供了另外一个选项—— GLOVE

使用Glove也比较简单,下载并解压之后,只要对里面的 demo.sh 脚本进行相应修改,然后执行这个脚本即可。

CORPUS=text8                                    # 设置输入文件路径
VOCAB_FILE=vocab.txt                            # 设置输入词汇路径
COOCCURRENCE_FILE=cooccurrence.bin              
COOCCURRENCE_SHUF_FILE=cooccurrence.shuf.bin
BUILDDIR=build
SAVE_FILE=vectors                               # 设置输入文件路径
VERBOSE=2           
MEMORY=4.0                                      # 设置内存大小
VOCAB_MIN_COUNT=5                               # 设置词汇的最小频率
VECTOR_SIZE=50                                  # 设置矩阵维度
MAX_ITER=15                                     # 设置迭代次数
WINDOW_SIZE=15                                  # 设置词向量的窗口大小
BINARY=2
NUM_THREADS=8
X_MAX=10

4. 生成中文依存语法树

文本处理有时需要比词性更丰富的信息,比如句法信息,Stanford NLP 小组提供了两篇论文: The Stanford Parser: A statistical parserNeural Network Dependency Parser,并在这两篇论文的基础上开发了两个工具,可惜效果都不太理想。前者的处理格式是正确的中文依存语法格式,但是速度极慢(差不多一秒一句);而后者虽然处理速度较快,但生成的格式和论文 Discriminative reordering with Chinese grammatical relations features – acepor中的完全不一样。我们尝试了邮件联系论文作者和工具作者,并且在 Stackoverflow 上提问,但这个问题似乎无解。

尽管如此,我们还是把两个方案都记录在此:

java -cp "*:." -Xmx4g edu.stanford.nlp.pipeline.StanfordCoreNLP -file INPUT_FILE -props StanfordCoreNLP-chinese.properties -outputFormat text -parse.originalDependencies
java -cp "./*" edu.stanford.nlp.parser.nndep.DependencyParser -props nndep.props -textFile INPUT_FILE -outFile OUTPUT_FILE

结论

预处理中文文本并非易事,Stanford NLP 小组对此作出了极大的贡献。我们的工作因而受益良多,所以我们非常感谢他们的努力。当然我们也期待 Stanford NLP software 能更上一层楼。

本文原载于 https://acepor.github.io/2015/12/17/General-Pipelines/

您可能也喜欢:

LDA-math-文本建模

LDA-math-LDA 文本建模

上海R用户沙龙会议纪要(Jan 20, 2013 @联合创业办公社)

十八般武艺,谁主天下?

COS沙龙第30期(北京)纪要
无觅

360好搜份额超35% 齐向东:如被骗 我全赔

$
0
0

奇虎 360 旗下搜索产品,360 好搜举行更名一周年用户见面会,并且发布了基于大数据分析产生的 2015 年度热搜榜单。360 总裁齐向东透露,截止 2015 年底,好搜在 PC 端的市场份额已达到 35%,每日搜索请求超过 7 亿次,活跃用户数增长到 3.8 亿”,这已经是好搜连续 14 个季度保持环比增长。同时,他进一步表示,未来,360 好搜会在人工智能、大数据、云计算等方面继续突破,加速拥抱 IOT 时代,即万物互联时代。

据 360 好搜提供的数据,2012 年 8 月 16 日,360 搜索正式上线并迅速占领搜索市场 10% 的份额;2013 年,360 搜索上线一周年,市场份额超过 18%;2014 年 360 搜索用户使用率和市场占有率分别达到 30.20% 和 30.32%。2015 年 1 月 6 日,360 搜索更名好搜,成为独立搜索品牌。

齐向东表示,一个欺诈钓鱼网站一天高峰的时候换 500 次域名,换 500 次 IP 地址,一开始叫 ABC,被 360 拦了,立刻又叫 BCD,又被 360 拦了,然后又叫 CBD,一天换 500 个名字,所以网友需要更安全的搜索引擎。

好搜主打安全搜索,并推出了搜索全赔计划,如果使用好搜时上当受骗可以找理赔部门,360 将全部赔付用户损失。

本文链接

Elasticsearch集群入门

$
0
0

欢迎来到Elasticsearch的奇妙世界,它是优秀的全文检索和分析引擎。不管你对Elasticsearch和全文检索有没有经验,都不要紧。我们希望你可以通过这本书,学习并扩展Elasticsearch的知识。由于这本书也是为初学者准备的,我们决定先简单介绍一般性的全文检索概念,接着再简要概述Elasticsearch。

我们要做的第一件事就是安装Elasticsearch。与许多应用相同,你从安装和配置着手,并经常忘记这些步骤的重要性。我们会尽量引导你完成这些步骤,从而使你更容易记住要点。此外,我们将告诉你如何用最简单的方法来索引和检索数据,而不用陷入太多细节。读完本章,你将学到以下内容:

  • 全文检索;
  • 了解Apache Lucene;
  • 文本分析;
  • 学习Elasticsearch的基本概念;
  • 安装和配置Elasticsearch;
  • 使用Elasticsearch REST API来操纵数据;
  • 使用基本的URI请求来搜索。

1.1 全文检索

在全文检索只为一小部分工程师所知的时代,我们大多数人使用SQL数据库来执行搜索操作。当然它至少在一定程度上没什么问题。然而,当你越钻越深,就会看到这种方法的局限,如缺乏扩展性、不够灵活、缺乏语言分析(当然SQL数据库的全文检索对此有所作为)等。于是Apache Lucene( http://lucene.apache.org)出现了,它的目标是提供一个全文检索的功能库。它非常快速,可扩展,并提供不同语言的分析能力。

1.1.1 Lucene词汇表和架构

深入介绍分析处理的细节之前,我们先介绍一下Apache Lucene的词汇表和整体架构,下面是这个库的基本概念。

  • 文档(document):索引和搜索时使用的主要数据载体,包含一个或多个存有数据的字段。
  • 字段(field):文档的一部分,包含名称和值两部分。
  • (term):一个搜索单元,表示文本中的一个词。
  • 标记(token):表示在字段文本中出现的词,由这个词的文本、开始和结束偏移量以及类型组成。

Apache Lucene将所有信息写到一个称为 倒排索引(inverted index)的结构中。不同于关系型数据库中表的处理方式,倒排索引建立索引中词和文档之间的映射。你可以把倒排索引看成这样一种数据结构,其中的数据是面向词而不是面向文档的。来看一个简单的例子。我们有一些文档,只有它们的标题字段需要被索引,它们看起来如下所示:

  • Elasticsearch Server 1.0 (document 1);
  • Mastering Elasticsearch (document 2);
  • Apache Solr 4 Cookbook (document 3)。

那么,简化版的索引可以看成是这样的:

计数

文档

1.0

1

<1>

 

4

1

<3>

 

Apache

1

<3>

 

Cookbook

1

<3>

 

Elasticsearch

2

<1>,<2>

 

Mastering

1

<2>

 

Server

1

<1>

 

Solr

1

<3>

 

每一个词指向包含它的文档编号。这样就可以执行一种非常高效且快速的搜索,比如基于词的查询。此外,每个词有一个 计数,告诉Lucene该词出现的频率。

当然,Lucene实际创建的索引要比这个复杂得多,也先进得多,它创建的额外文件包含了 词向量(term vector)、 文档值(doc value)等信息。然而,到现在为止,你需要知道的是数据怎么组织,而不是具体怎么存储。

每个索引分为多个“写一次,读多次”(write once and read many time)的段(segment)。建立索引时,一个段写入磁盘后就不能再更新。因此,被删除文档的信息存储在一个单独的文件中,但该段自身不被更新。

然而,多个段可以通过 段合并(segments merge)合并在一起。当强制段合并或者Lucene决定合并时,这些小段就会由Lucene合并成更大的一些段。合并需要I/O。然而一些信息需要清除,因为在合并时,不再需要的信息将被删除(例如,被删除的文档)。除此之外,检索大段比检索存有相同数据的多个小段速度更快。这是因为在一般情况下,搜索只需将查询词与那些被编入索引的词相匹配。通过多个小段寻找和合并结果,显然会比让一个大段直接提供结果慢得多。

1.1.2 输入数据分析

当然,问题是,传入文档中的数据怎样转化成倒排索引,查询文本怎样变成可被搜索的词?这个数据转化的过程被称为 分析。你可能希望某些字段经语言分析器处理,使得car和cars在索引中被视为同一个。另外,你可能希望另一些字段只用空格或者小写划分。

分析的工作由 分析器完成,它由一个 分词器(tokenizer)和零个或多个 标记过滤器(token filter)组成,也可以有零个或多个 字符映射器(character mapper)。

Lucene中的分词器把文本分割成多个标记,基本就是词加上一些额外信息,比如该词在原始文本中的位置和长度。分词器的处理结果称为 标记流(token stream),它是一个接一个的标记,准备被过滤器处理。

除了分词器,Lucene分析器包含零个或多个标记过滤器,用来处理标记流中的标记。下面是一些过滤器的例子。

  • 小写过滤器(lowercase filter):把所有的标记变成小写。
  • 同义词过滤器(synonyms filter):基于基本的同义词规则,把一个标记换成另一个同义的标记。
  • 多语言词干提取过滤器(multiple language stemming filter):减少标记(实际上是标记中的文本部分),得到词根或者基本形式,即词干。

过滤器是一个接一个处理的。所以我们通过使用多个过滤器,几乎可以达到无限的分析可能性。

最后,字符映射器对未经分析的文本起作用,它们在分词器之前工作。因此,我们可以很容易地从文本的整体部分去除HTML标签而无需担心它们被标记。

索引和查询

我们可能想知道当使用Lucene和所有建立在它之上的软件时,上述所有功能对索引和查询的影响。建立索引时,Lucene会使用你选择的分析器来处理你的文档内容。当然,不同的字段可以使用不同的分析器,所以文档的名称字段可以和汇总字段做不同的分析。如果我们愿意,也可以不分析字段。

查询时,查询将被分析。但是,你也可以选择不分析。记住这一点很关键,因为一些Elasticsearch查询被分析,一些则不然。例如,前缀和词查询不被分析,匹配查询则被分析。可以在被分析查询和不被分析查询两者中选择非常有用。有时,你可能希望查询一个未经分析的字段,而有时你则希望有全文搜索的分析。如果我们查询LightRed这个词,标准分析器分析这个查询后,会去查询light和red;如果我们使用不经分析的查询类型,则会明确地查询LightRed这个词。

关于索引和查询分析,你应该记住的是,索引应该和查询词匹配。如果它们不匹配,Lucene不会返回所需文档。比如,你在建立索引时使用了词干提取和小写,那你应该保证查询中的词也必须是词干和小写,否则你的查询不会返回任何结果。重要的是在索引和查询分析时,对所用标记过滤器保持相同的顺序,这样被分析出来的词是一样的。

1.1.3 评分和查询相关性

另外还有件现在还没提到的事,就是 评分(scoring)。什么是文档的得分? 得分是根据文档和查询的匹配度用计分公式计算的结果。默认情况下,Apache Lucene使用 TF/IDF(term frequency/inverse document frequency,词频/逆向文档频率)评分机制,这是一种计算文档在我们查询上下文中相关度的算法。当然,它不是唯一可用的算法,2.2节将介绍其他算法。

如果你想阅读更多关于Apache Lucene TF/IDF评分公式的内容,请访问Apache Lucene Javadocs中的 TFIDFSimilarity类,网址: http://lucene.apache.org/core/4_6_0/core/org/apache/lucene/search/similarities/TFIDFSimilarity.html

请记住,Elasticsearch和Lucene计算的分数值越高,意味着文档越相关。一些参数(比如boost)、不同的查询类型(3.3节将讨论这些查询类型)、不同的评分算法,都会影响得分的计算。

如果您想更深入地了解Apache Lucene评分是如何工作的、默认算法是什么、分数是如何计算的,请参阅我们的书 Mastering ElasticSearch,Packt出版。

1.2 Elasticsearch基础

Elasticsearch是由Shay Banon发起的一个开源搜索服务器项目,2010年2月发布。迄今,该项目已发展成为搜索和数据分析解决方案领域的主要一员,广泛应用于声名卓著或鲜为人知的搜索应用程序。此外,由于其分布式性质和实时功能,许多人把它作为文档数据库。

1.2.1 数据架构的主要概念

让我们过一遍Elasticsearch的基本概念。如果你已经熟悉Elasticsearch架构,可以跳过本节。如果你不熟悉这种架构,请考虑阅读本节。我们将提到本书其余部分会用到的关键字。

1. 索引

索引(index)是Elasticsearch对逻辑数据的逻辑存储,所以它可以分为更小的部分。你可以把索引看成关系型数据库的表。然而,索引的结构是为快速有效的全文索引准备的,特别是它不存储原始值。如果你知道MongoDB,可以把Elasticsearch的索引看成MongoDB里的一个集合。如果你熟悉CouchDB,可以把索引看成CouchDB数据库索引。Elasticsearch可以把索引存放在一台机器或者分散在多台服务器上,每个索引有一或多个 分片(shard),每个分片可以有多个 副本(replica)。

2.文档

存储在Elasticsearch中的主要实体叫 文档(document)。用关系型数据库来类比的话,一个文档相当于数据库表中的一行记录。当比较Elasticsearch中的文档和MongoDB中的文档,你会发现两者都可以有不同的结构,但Elasticsearch的文档中,相同字段必须有相同类型。这意味着,所有包含 title字段的文档, title字段类型都必须一样,比如 string

文档由多个 字段组成,每个字段可能多次出现在一个文档里,这样的字段叫 多值字段(multivalued)。每个字段有类型,如文本、数值、日期等。字段类型也可以是复杂类型,一个字段包含其他子文档或者数组。字段类型在Elasticsearch中很重要,因为它给出了各种操作(如分析或排序)如何被执行的信息。幸好,这可以自动确定,然而,我们仍然建议使用映射。与关系型数据库不同,文档不需要有固定的结构,每个文档可以有不同的字段,此外,在程序开发期间,不必确定有哪些字段。当然,可以用模式强行规定文档结构。从客户端的角度看,文档是一个JSON对象(关于JSON格式的更多内容,参见 http://en.wikipedia.org/wiki/JSON)。每个文档存储在一个索引中并有一个Elasticsearch自动生成的唯一标识符和 文档类型。文档需要有对应文档类型的唯一标识符,这意味着在一个索引中,两个不同类型的文档可以有相同的唯一标识符。

3. 文档类型

在Elasticsearch中,一个索引对象可以存储很多不同用途的对象。例如,一个博客应用程序可以保存文章和评论。文档类型让我们轻易地区分单个索引中的不同对象。每个文档可以有不同的结构,但在实际部署中,将文件按类型区分对数据操作有很大帮助。当然,需要记住一个限制,不同的文档类型不能为相同的属性设置不同的类型。例如,在同一索引中的所有文档类型中,一个叫 title的字段必须具有相同的类型。

4. 映射

在有关全文搜索基础知识部分,我们提到了分析的过程:为建索引和搜索准备输入文本。文档中的每个字段都必须根据不同类型做相应的分析。举例来说,对数值字段和从网页抓取的文本字段有不同的分析,比如前者的数字不应该按字母顺序排序,后者的第一步是忽略HTML标签,因为它们是无用的信息噪音。Elasticsearch在映射中存储有关字段的信息。每一个文档类型都有自己的映射,即使我们没有明确定义。

1.2.2 Elasticsearch主要概念

现在,我们已经知道Elasticsearch把数据存储在一个或多个索引上,每个索引包含各种类型的文档。我们也知道了每个文档有很多字段,映射定义了Elasticsearch如何对待这些字段。但还有更多,从一开始,Elasticsearch就被设计为能处理数以亿计的文档和每秒数以百计的搜索请求的分布式解决方案。这归功于几个重要的概念,我们现在将更详细地描述。

1. 节点和集群

Elasticsearch可以作为一个独立的单个搜索服务器。不过,为了能够处理大型数据集,实现容错和高可用性,Elasticsearch可以运行在许多互相合作的服务器上。这些服务器称为 集群(cluster),形成集群的每个服务器称为 节点(node)。

2. 分片

当有大量的文档时,由于内存的限制、硬盘能力、处理能力不足、无法足够快地响应客户端请求等,一个节点可能不够。在这种情况下,数据可以分为较小的称为 分片(shard)的部分(其中每个分片都是一个独立的Apache Lucene索引)。每个分片可以放在不同的服务器上,因此,数据可以在集群的节点中传播。当你查询的索引分布在多个分片上时,Elasticsearch会把查询发送给每个相关的分片,并将结果合并在一起,而应用程序并不知道分片的存在。此外,多个分片可以加快索引。

3. 副本

为了提高查询吞吐量或实现高可用性,可以使用分片副本。 副本(replica)只是一个分片的精确复制,每个分片可以有零个或多个副本。换句话说,Elasticsearch可以有许多相同的分片,其中之一被自动选择去更改索引操作。这种特殊的分片称为 主分片(primary shard),其余称为 副本分片(replica shard)。在主分片丢失时,例如该分片数据所在服务器不可用,集群将副本提升为新的主分片。

4. 时光之门

Elasticsearch处理许多节点。集群的状态由时光之门控制。默认情况下,每个节点都在本地存储这些信息,并且在节点中同步。我们将在7.2节详细讨论时光之门模块。

1.2.3 索引建立和搜索

你可能会问实际上如何把所有的索引、分片和副本绑在单个环境里。理论上,当你必须知道你的文档在哪,哪台服务器、哪个分片上时,从集群获取数据非常困难。更为困难的是当一个搜索需要返回的文档分布在集群中不同节点的不同分片上时。这确实是一个复杂的问题,好在我们不需要关心,它由Elasticsearch本身自动处理。来看看下图:

图像说明文字

发送一个新的文档给集群时,你指定一个目标索引并发送给它的任意一个节点。这个节点知道目标索引有多少分片,并且能够确定哪个分片应该用来存储你的文档。可以更改Elasticsearch的这个行为。2.6.3节将对此进行讨论。现在你需要记住的重要信息是,Elasticsearch使用文档的唯一标识符来计算文档应该被放到哪个分片中。索引请求发送到一个节点后,该节点会转发文档到持有相关分片的目标节点中。

现在来看看关于执行搜索请求的图:

图像说明文字

尝试用文档标识符来获取文档时,发送查询到一个节点,该节点使用同样的路由算法来决定持有文档的节点和分片,然后转发查询,获取结果,并把结果发送给你。另一方面,查询过程更为复杂。除非使用了路由,查询将直接转发到单个分片,否则,收到查询请求的节点会把查询转发给保存了属于给定索引的分片的所有节点,并要求与查询匹配的文档的最少信息(默认情况下是标识符和得分)。这个过程称为 发散阶段(scatter phase)。收到这些信息后,该聚合节点(收到客户端请求的节点)对结果排序,并发送第2个请求来获取结果列表所需的文档(除了标识符和得分以外的所有信息)。这个阶段称为 收集阶段(gather phase)。这个阶段执行完毕后,结果返回到客户端。

现在问题来了,在前面描述的过程中,副本扮演了什么角色呢?在建立索引时,副本只作为额外的位置来存储数据。当执行查询时,默认情况下,Elasticsearch会尽量平衡分片和它的副本之间的负载,使它们承受均衡的压力。此外,记住我们可以改变该行为。3.2节将对此进行讨论。

1.3 安装并配置集群

有几个安装Elasticsearch所需的步骤,接下来几节将详细说明。

1.3.1 安装Java

为了建立Elasticsearch,第一步是确保正确安装Java SE环境。Elasticsearch需要Java 6或更高版本。你可以从 http://www.oracle.com/technetwork/java/javase/downloads/index.html下载。如果你想,也可以使用OpenJDK( http://openjdk.java.net/)。当然你可以使用Java 6,但它已经没有补丁的支持,所以建议安装Java 7。

1.3.2 安装Elasticsearch

http://www.elasticsearch.org/download/下载,解压。选择最新的稳定版本,安装完毕。

写这本书时,我们用的是Elasticsearch 1.0.0 GA。这意味着,我们已经跳过一些被标记为过时(deprecated)的属性的描述,它们已经或将在未来的Elasticsearch版本中被移除。

与Elasticsearch交互的主要接口是基于HTTP协议和REST的。这意味着你甚至可以使用Web浏览器来完成基本的查询和请求,但对于更复杂的情况,你需要额外的命令行工具,比如cURL。如果你使用Linux或OS X命令,cURL已经可用了。如果你使用Windows,可以从 http://curl.haxx.se/download.html下载。

1.3.3 在Linux上用二进制包安装Elasticsearch

安装Elasticsearch的另一个方法是使用提供的二进制包,RPM或DEB,视你的Linux发行版而定。这些二进制包可以在 http://www.elasticsearch.org/download/找到。

1. 使用RPM包安装Elasticsearch

下载RPM包后,你只需执行如下命令:

sudo yum elasticsearch-1.0.0.noarch.rpm

就这么简单。如果一切顺利,Elasticsearch应该安装好了,配置文件应该在/etc/sysconfig/elasticsearch中。如你的操作系统基于红帽,应该可以使用/etc/init.d/elasticsearch下的 init脚本。如果你的操作系统是SUSE Linux,可以使用/bin下的systemctl文件来启动和停止Elasticsearch服务。

2. 使用DEB包安装Elasticsearch

下载DEB包后,只需执行如下命令:

sudo dpkg -i elasticsearch-1.0.0.deb

就这么简单。如果一切顺利,Elasticsearch应该安装成功,配置文件存在/etc/elasticsearch/elasticsearch.yml。/etc/init.d/elasticsearch下的 init脚本可以用来启动和停止Elasticsearch。此外,/etc/default/elasticsearch下的文件包含了环境设置。

1.3.4 目录布局

现在,到新创建的目录中。应该可以看到下面的目录结构:

目录

描述

bin

运行Elasticsearch实例和插件管理所需的脚本

config

配置文件所在的目录

lib

Elasticsearch使用的库

Elasticsearch启动后,会创建如下目录(如果目录不存在):

目录

描述

data

Elasticsearch使用的所有数据的存储位置

logs

关于事件和错误记录的文件

plugins

存储所安装插件的地方

work

Elasticsearch使用的临时文件

1.3.5 配置Elasticsearch

Elasticsearch很容易入门,这是它越来越流行的原因之一,当然,不是全部原因。因为已为简单的环境配置了合理的默认值和自动设置,我们可以跳过配置,不需改变我们的配置文件中的任意一行而直接走到下一章。然而,为了真正理解Elasticsearch,学习一些可用的设置还是值得的。

现在,来探讨Elasticsearch的tar.gz存档提供的默认目录和文件的布局。整个配置位于config目录下,可以看到两个文件:elasticsearch.yml(或elasticsearch.json,如果有的话会被使用)和logging.yml。第一个文件负责设置服务器的默认配置值。重要的是,因为一些配置值可以在运行时更改,也可作为集群状态的一部分被保留,所以这个文件中的值可能不准确。有两个值不能在运行时更改,分别是 cluster.namenode.name

cluster.name属性保存集群的名字,不同的集群用名字来区分,配置成相同集群名字的各个节点形成一个集群。

node.name是实例(该节点)的名字,可以不定义此参数,这时,Elasticsearch自动选择一个唯一的名称。注意,此名称是每次启动时选择的,所以在每次重启后名称可能都不一样。在很长的时间区间或者重启过后,需要在API中提及具体实例名称,或者用监控工具查看节点,自定义一个名称还是很有帮助的。给你的节点想一个描述性的名字吧。

文件中的其他参数有很好的注释,所以建议你看看。不要担心不理解那些解释。希望在读完下面几章后,一切都变得清晰起来。

记住,大多数在elasticsearch.yml文件中设置的参数都可以用Elasticsearch REST API来覆盖。8.8节将介绍这些API。

第2个文件(logging.yml)定义了多少信息写入系统日志,定义了日志文件,并定期创建新文件。只有在调整监控、备份方案或系统调试时,才需要修改。然而如果想有一份更详细的日志,就需要相应调整。

我们保留这些配置文件不动。配置的一个重要部分是调整你的操作系统。在建立索引时,尤其是有很多分片和副本的情况下,Elasticsearch将创建很多文件。所以,系统不能限制打开的文件描述符小于32 000个。在Linux上,一般在/etc/security/limits.conf中修改,当前的值可以用 ulimit命令来查看。如果达到极限,Elasticsearch将无法创建新的文件,所以合并会失败,索引会失败,新的索引无法创建。

下一组设定关联到单个Elasticsearch实例的Java虚拟机(JVM)的堆内存限制。对小型部署来说,默认的内存限制(1024 M)就足够了,但对于大型项目不够。如果你在日志文件中发现 OutOfMemoryError异常的条目,把 ES_HEAP_SIZE变量设置到大于1024。当选择分配给JVM的合适内存大小时,记住,通常不应该分配超过50%的系统总内存。不过,所有的规则都有例外,稍后将更详细地讨论,但你应该经常监控JVM堆的使用量,需要时调整。

1.3.6 运行Elasticsearch

运行刚刚下载并解压的ZIP包,转到bin目录,然后根据不同的操作系统,运行如下命令:

  • Linux或OS X: ./elasticsearch
  • Windows: elasticsearch.bat

恭喜你!现在把Elasticsearch启动并运行起来了。它在工作时一般使用2个端口号:第1个是使用HTTP协议与REST API通信的端口,第2个是传输模块(transport module),是用来在集群内以及Java客户端和集群之间通信的端口。HTTP API的默认端口号是9200,所以可以在浏览器中打开 http://127.0.0.1:9200来检查搜索是否就绪,浏览器将显示类似下面这样的代码片段:

{"status":200,"name":"es_server","version":{"number":"1.0.0","build_hash":"a46900e9c72c0a623d71b54016357d5f94c8ea32","build_timestamp":"2014-02-12T16:18:34Z","build_snapshot":false,"lucene_version":"4.6"},"tagline":"You Know, for Search"}

输出是JSON结构的。如果你还不熟悉 JSON(JavaScript Object Notation),请花几分钟阅读 http://en.wikipedia.org/wiki/JSON上的这篇文章。

Elasticsearch很聪明。如果默认端口不可用,引擎将绑定到下一个可用端口,你可以在启动时的控制台上找到如下相关信息:

[2013-11-1611:56:12,101][INFO ][http][RedLotus]  
    bound_address {inet[/0:0:0:0:0:0:0:0%0:9200]},  
    publish_address {inet[/192.168.1.101:9200]}

注意[HTTP]的片段。Elasticsearch使用了一些端口完成各种任务。我们所使用的接口是由HTTP模块处理的。

现在,将使用cURL程序。例如,要检查集群健康度,会使用以下命令:

curl -XGET http://127.0.0.1:9200/_cluster/health?pretty

参数 -X是一个请求方法,默认值是 GET(所以在上面的例子中,可以忽略此参数)。暂时不要担心 GET这个值,本章的后面将更详细地描述它。

作为一个标准,API返回的JSON对象信息里,换行符是被省略的,在请求中加上 pretty参数是强制Elasticsearch在响应中加上换行符,使之更可读。你可以试着去掉 pretty参数运行上面的请求,看看有什么不同。

Elasticsearch在中小型应用程序中非常有用,但它的初衷是建成大型集群。所以,现在来建立由两个节点组成的大的集群。解压Elasticsearch到另一个目录,然后运行第二个实例。我们会在日志中看到如下内容:

[2013-11-1611:55:16,767][INFO ][cluster.service][Stane,Obadiah] detected_master [MarthaJohansson][vswsFRWTSjOa_fy7uPuOMA][inet[/192.168.1.19:9300]], added {[MarthaJohansson][vswsFRWTSjOa_fy7uPuOMA][inet[/192.168.1.19:9300]],}, reason: zen-disco-receive(from master  
[[MarthaJohansson][vswsFRWTSjOa_fy7uPuOMA][inet[/192.168.1.19:9300]]])

这意味着我们的第二个实例(名字为Stane,Obadiah)检测到了之前运行的实例(名字为Martha Johansson)。这里,Elasticsearch自动形成了一个新的双节点集群。

 请注意,在某些系统上,防火墙软件默认自动打开,可能导致节点无法找到其他节点。

1.3.7 关掉Elasticsearch

尽管我们期望集群或节点完美地一直运行下去,但仍可能需要正确地重启或者关闭,比如,为了维护。下面是三种可以关闭Elasticsearch的方法。

  • 如果节点是连接到控制台,按下Ctrl+C。
  • 第二种选择是通过发送TERM信号杀掉服务器进程(参考Linux上的 kill命令和Windows上的任务管理器)。
  • 第三种方法是使用REST API。

现在着重介绍第三种方法。可以执行以下命令来关掉整个集群:

curl -XPOST http://localhost:9200/_cluster/nodes/_shutdown

为关闭单一节点,假如节点标识符是 BlrmMvBdSKiCeYGsiHijdg,可以执行下面的命令:

curl –XPOST http://localhost:9200/_cluster/nodes/BlrmMvBdSKiCeYGsiHijdg/_shutdown

节点的标识符可以在日志中看到,或者使用 _cluster/nodes API,命令如下:

curl -XGET http://localhost:9200/_cluster/nodes/

1.3.8 Elasticsearch作为系统服务运行

Elasticsearch 1.0可以作为服务运行在基于Linux的系统和基于Windows的系统上。

1. 在Linux上运行系统服务

如果是从提供的二进制包安装的Elasticsearch,你已经完成了,什么都不用担心。但是,如果你刚刚下载归档文件,解压到所选择的目录,就需要做一些额外的工作。为了将Elasticsearch安装成一个Linux系统服务,将使用Elasticsearch service wrapper,你可以从 https://github.com/elasticsearch/elasticsearch-servicewrapper下载。

来看看使用Elasticsearch service wrapper建立Elasticsearch Linux服务的步骤。首先,执行以下命令来下载这个wrapper:

curl -L http://github.com/elasticsearch/elasticsearch-servicewrapper/tarball/master | tar -xz

假设Elasticsearch已经安装在/usr/local/share/elasticsearch下,执行如下命令来移动所需的wrapper文件:

sudo mv *servicewrapper*/service/usr/local/share/elasticsearch/bin/

执行如下命令来移除剩余的文件

rm -Rf*servicewrapper*

最后,通过执行install命令来安装服务:

sudo /usr/local/share/elasticsearch/bin/service/elasticsearch install

在这之后,需要创建一个符号链接指向/usr/local/bin/rcelasticsearch下的/usr/local/share/elasticsearch/bin/service/elasticsearch脚本。可通过运行如下命令来实现:

sudo ln -s 'readlink  -f /usr/local/share/elasticsearch/bin/service/elasticsearch'/usr/local/bin/rcelasticsearch

就这样。如果你想启动Elasticsearch,执行如下命令:

/etc/init.d/elasticsearch start

2. 在Windows上运行系统服务

在Windows下把Elasticsearch安装为系统服务非常容易,你只需转到Elasticsearch的安装目录,到bin子目录下,执行:

service.bat install

你会被问及操作权限,允许脚本运行,Elasticsearch就被安装成一个Windows服务。

如果你想看看所有被service.bat脚本文件暴露出来的命令,在相同目录下执行:

service.bat

例如,为了启动Elasticsearch,可执行如下命令:

service.bat start

1.4 用REST API操作数据

Elasticsearch REST API可用于各种任务。有了它,可以管理索引,更改实例参数,检查节点和群集状态,索引数据,搜索数据或者通过GET API检索文档。但是现在,我们将集中在API中的 CRUD(create-retrieve-update-delete,增删改查)部分,它让我们能像使用NoSQL数据库一样使用Elasticsearch。

1.4.1 理解Elasticsearch的RESTful API

在一个类REST的架构中,每个请求都指向地址路径所表示的一个具体对象。如果/books/是一个图书馆中图书列表的引用,/books/1则引用ID为1的那本书。注意这些对象可以嵌套,/books/1/chapter/6表示图书馆的第一本书的第6章,等等。我们的API调用有个主题。我们想执行的操作(比如 GETPOST操作)怎么样?请求类型就是用来指定这个的。HTTP协议给出了可以在API调用中用作动词的一组相当长的类型。合乎逻辑的选择是, GET用来获得请求对象的当前状态, POST来改变对象的当前状态, PUT创建一个对象,而 DELETE销毁对象,另外还有个 HEAD请求仅仅用来获取对象的基础信息。

现在来看看在1.3.7节中讨论的如下操作例子,一切都应该更容易理解。

  • GET http://localhost:9000/:这个命令用来获取Elasticsearch的基本信息。
  • GET http://localhost:9200/_cluster/state/nodes/:这个命令获取集群中节点的信息。
  • POST http://localhost:9200/_cluster/nodes/_shutdown:这个命令向集群中所有节点发送一个shutdown请求。

我们现在至少知道了REST的一般概念。你可以在 http://en.wikipedia.org/wiki/Representational_state_transfer上阅读更多关于REST的信息。现在,可以继续学习如何使用Elasticsearch API来存储、读取、修改和删除数据。

1.4.2 在Elasticsearch中存储数据

我们已经讨论过,在Elasticsearch中,所有的数据,即每个文档,都有定义好的索引和类型。每个文档可以包含一个或多个字段来保存数据。首先展示如何使用Elasticsearch为一个简单文档建立索引。

1.4.3 新建文档

现在,尝试索引一些文档。例如,为博客建立某种内容管理系统(CMS)。文章(article)是博客中的一个实体。

使用JSON标记,一个文档可以如下所示的例子来表示:

{"id":"1","title":"New version of Elasticsearch released!","content":"Version 1.0 released today!","priority":10,"tags":["announce","elasticsearch","release"]}

可以看到,JSON文档包含一组字段,每个字段可以有不同的形式。在以上示例中,我们有数字( priority)、文本( title)和字符串数组( tags)。以下示例将展示其他类型。如本章前面所述,Elasticsearch能猜出这些类型(因为JSON是半类型化的,例如,数字没有放在引号中),并自动定制这些数据在其内部结构中如何存储。

当然,我们希望为示例文档建立索引,并使其可用于搜索。我们将使用一个名为 blog的索引和名为 article的类型。为了把示例文档以给定类型、标识符为1建立在索引中,执行以下命令:

curl -XPUT http://localhost:9200/blog/article/1 -d '{"title": "New version of Elasticsearch released!", content": "Version 1.0 released today!", "tags": ["announce", "elasticsearch", "release"] }'

注意cURL命令的一个新选项: -d参数。此选项的值是将作为请求负载的文本,也即请求主体(request body)。这样,我们可以发送附加信息,如文档定义。同时,注意唯一标识符(1)是放在URL,而不是请求主体中。使用HTTP  PUT请求时,如果省略此标识符,该请求将返回以下错误:

No handler found for uri [/blog/article/]and method [PUT]

如果一切正确,Elasticsearch会返回一个JSON响应,与如下输出类似:

{"_index":"blog","_type":"article","_id":"1","_version":1}

前面的响应包含了此次操作状态的信息,并显示一个新的文档放在哪里,还包含了有关文档的唯一标识符( _id)和当前版本( _version)的信息。版本将由Elasticsearch每次更新时自动递增。

标识符的自动创建

在上面的示例中,我们自己指定了文档标识符。然而,Elasticsearch可以自动生成它。这似乎很方便,但只有当该索引是唯一的数据来源时,才能这么做。如果使用一个数据库来存储数据,用Elasticsearch全文搜索,那数据同步将会被阻碍,除非在数据库中也存储生成的标识符。使用HTTP POST请求类型并且不在URL中指定标识符,就可以生成一个唯一标识符。例如,看下面的命令:

curl -XPOST http://localhost:9200/blog/article/ -d '{"title": "New version of Elasticsearch released!", "content": "Version 1.0  released today!", "tags": ["announce", "elasticsearch", "release"] }'

注意,要使用HTTP  POST方法,而不是前面示例中的 PUT方法。参考前面关于REST动词的描述,我们想更改列表中的文档索引,而不是创建一个新的实体,所以使用 POST而不是 PUT。服务器应该返回类似下面的响应:

{"_index":"blog","_type":"article",   "_id":"XQmdeSe_RVamFgRHMqcZQg","_version":1}

注意加粗的那一行,这是一个Elasticsearch自动生成的标识符。

1.4.4 检索文档

我们已经将实例存储在了文档中,现在尝试通过标识符检索。首先执行以下命令:

curl -XGET http://localhost:9200/blog/article/1

Elasticsearch将返回类似下面的响应:

{"_index":"blog","_type":"article","_id":"1","_version":1,"exists":true,"_source":{"title":"New version of Elasticsearch released!","content":"Version 1.0 released today!","tags":["announce","elasticsearch","release"]}

在前面的响应中,除了索引、类型、标识符和版本,还可以看到说明“发现文件存在”( exists属性)以及此文档来源( _source属性)的信息。如果没有找到文档,得到的响应如下所示:

{"_index":"blog","_type":"article","_id":"9999","exists":false}

因为没有找到文档,当然也就没有版本或来源的信息。

1.4.5 更新文档

更新索引中的文档是一项更复杂的任务。在内部,Elasticsearch必须首先获取文档,从 _source属性获得数据,删除旧的文件,更改 _source属性,然后把它作为新的文档来索引。它如此复杂,因为信息一旦在Lucene的倒排索引中存储,就不能再被更改。Elasticsearch通过一个带 _update参数的脚本来实现它。这样就可以做比简单修改字段更加复杂的文档转换。下面用简单的例子看看的工作原理。

请记住之前建立的博客文章索引。为了更改其 content字段,运行以下命令:

curl -XPOST http://localhost:9200/blog/article/1/_update -d '{"script": "ctx._source.content = \"new content\""}'

Elasticsearch将返回如下响应:

{"_index":"blog","_type":"article","_id":"1","_version":2}

看上去更新操作执行成功了。为了确定,我们用它的标识符检索一下,执行如下命令:

curl -XGET http://localhost:9200/blog/article/1

Elasticsearch的响应应该包含修改过的 content字段,事实的确如此,它包含如下信息:

{"_index":"blog","_type":"article","_id":"1","_version":2,"exists":true,"_source":{"title":"New version of Elasticsearch released!","content":"new content","tags":["announce","elasticsearch","release"]}

Elasticsearch修改了文章的 content和该文档的版本号。注意,不必发送整个文档,只需发送改变的部分。但是请记住,为了使用更新功能,需要使用 _source字段,2.4节将描述如何使用 _source字段。

关于文档更新,还有一点,如果你的脚本需要更新文档的一个字段,你可以设置一个值用来处理文档中没有该字段的情况。例如,想增加文档中的 counter字段,而该字段不存在,你可以在请求中使用 upsert节来提供字段的默认值。看下面的例子:

curl -XPOST http://localhost:9200/blog/article/1/_update -d '{ "script":"ctx._source.counter += 1","upsert":{"counter":0}}'

执行这个示例,Elasticsearch会在示例文档中添加一个值为0的 counter字段。这是因为我们的文档没有 counter字段,而我们在更新请求中指定了 upsert节。

1.4.6 删除文档

我们已经看到如何创建( PUT)、检索( GET)和更新文档,不难猜到,删除文档的过程是类似的:需要使用 DELETE请求类型发送一个适当的HTTP请求。例如,要删除示例文档,运行以下命令:

curl -XDELETE http://localhost:9200/blog/article/1

Elasticsearch的响应如下所示:

{"found":true,"_index":"blog","_type":"article","_id":"1","_version":3}

这意味着我们找到并删除了该文档。

现在可以利用CRUD操作。我们已经可以使用Elasticsearch作为一个简单的键值存储来创建应用程序。但这仅仅是开始!

1.4.7 版本控制

在提供的例子中,你可能注意到了文档的版本信息,它看起来如下所示:

"_version":1

仔细观察,你会发现在更新相同标识符的文档后,这个版本是递增的。默认情况下,Elasticsearch在添加、更改或删除文档时都会递增版本号。除了告诉我们对文档所做更改的次数,还能够实现 乐观锁(optimistic locking, http://en.wikipedia.org/wiki/Optimistic_concurrency_control)。这允许我们在并发处理同一文档时避免问题。例如,在两个不同的应用程序中读取相同的文档,分别修改它,然后尝试更新到Elasticsearch。没有版本控制,我们将看到最后更新的版本。使用乐观锁,Elasticsearch保证数据的准确性,尝试写入一个已更改的文档将会失败。

1. 版本控制的一个例子

我们来看一个使用版本控制的示例。假设要删除 library索引中类型为 bookid为1的文档。我们也要确保如果文档没有更新,则删除操作成功。需要做的是添加一个值为1的 version参数,如下所示:

curl -XDELETE'localhost:9200/library/book/1?version=1'

如果索引中文档的版本不等于1,Elasticsearch将返回如下错误:

{"error":"VersionConflictEngineException[[library][4] [book][1]: 
    version conflict, current [2], provided [1]]","status":409}

在我们的示例中,Elasticsearch比较我们声明的版本号和在Elasticsearch中文档的版本号,发现不一样,所以操作失败。

2. 使用外部系统提供的版本

Elasticsearch也可基于我们提供给它的版本号。在版本存储在外部系统时,这是必要的。这种情况下,当你新索引一个文档时,应该如上面的示例一样提供一个 version参数。这时,Elasticsearch将只检查提供的版本是否比当前保存在索引中的版本大(大多少并不重要),如果是,操作成功,否则将失败。为了告诉Elasticsearch我们要使用外部版本跟踪,除了 version参数外,还需要添加 version_type=external参数。

例如,在系统添加文档版本123456,将运行如下命令:

curl -XPUT 'localhost:9200/library/book/1?version=123456'-d {...}

即使文档被移除后,Elasticsearch仍然可以检查版本号。这是因为Elasticsearch保留了删除文档的版本信息。默认情况下,此信息在删除的60秒内可用。可以通过修改 index.gc_deletes配置参数来更改这个值。

1.5 使用URI请求查询来搜索

进入Elasticsearch查询的详细信息之前,先使用其中简单的URI请求来搜索。当然,第3章将扩展使用Elasticsearch搜索的知识,但是现在,先使用最简单的方法。

1.5.1 示例数据

本节将创建一个简单的索引,它有两个文档类型。为此,运行以下命令:

curl -XPOST 'localhost:9200/books/es/1'-d '{"title":"Elasticsearch Server", "published": 2013}' 
curl -XPOST 'localhost:9200/books/es/2'-d '{"title":"Mastering Elasticsearch", "published": 2013}' 
curl -XPOST 'localhost:9200/books/solr/1'-d '{"title":"Apache Solr 4 Cookbook", "published": 2012}'

运行上述命令将创建 books索引,该索引包含两种类型: essolrTitlepublished字段将被索引。如果你想检查,可以通过运行以下命令映射API,2.2节将讨论映射:

curl -XGET 'localhost:9200/books/_mapping?pretty'

Elasticsearch将返回整个索引的所有映射。

1.5.2 URI请求

Elasticsearch的所有查询都发送到 _search端点。你可以搜索单个或多个索引,也可以将搜索范围缩小到给定的一个或多个文档类型。例如,为了寻找 books索引,运行以下命令:

curl -XGET 'localhost:9200/books/_search?pretty'

如果还有另一个索引叫 clients,也可对这两个索引执行一个查询:

curl -XGET 'localhost:9200/books,clients/_search?pretty'

以同样的方式,还可以选择搜索时要使用的类型。如果只想在 books索引的 es类型中搜索,将运行如下命令:

curl -XGET 'localhost:9200/books/es/_search?pretty'

请记住,为了搜索一个给定的类型,需要指定一个或多个索引。如果要寻找任意索引,只需要设置星号(*)为索引名称,或忽略索引名称。Elasticsearch在选择索引名称时支持相当丰富的语义。如果你有兴趣,请参考 http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/multi-index.html

还可以省略索引和类型来搜索所有索引。例如,以下命令将搜索集群中的所有数据:

curl -XGET 'localhost:9200/_search?pretty'

1. Elasticsearch查询响应

假设想找到 books索引中 title字段包含 elasticsearch一词的所有文档,可以运行以下查询:

curl -XGET 'localhost:9200/books/_search?pretty&q=title:elasticsearch

Elasticsearch返回的响应如下所示:

{"took":4,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max_score":0.625,"hits":[{"_index":"books","_type":"es","_id":"1","_score":0.625,"_source":{"title":"Elasticsearch Server","published":2013}},{"_index":"books","_type":"es","_id":"2","_score":0.19178301,"_source":{"title":"MasteringElasticsearch", "published":2013}}]}}

响应的第一部分告诉我们该请求花了多少时间( took属性,单位是毫秒),有没有超时( timed_out属性),执行请求时查询的分片信息,包括查询的分片数量( _shards对象的 total属性)、成功返回结果的分片数量( _shards对象的 successful属性)、失败的分片数量( _shards对象的 failed属性)。如果查询执行时间比预想的更长,它可能会超时(可以使用 timeout参数指定查询的最大执行时间)。可以使用超时参数,指定最大查询执行时间。失败的分片意味着分片出了问题或在执行搜索时不可用。

当然,上述信息很有用,但是通常我们对 hits对象中返回的结果感兴趣。我们有查询返回的文档总数( total属性)和计算所得的最高分( max_score属性),还有包含返回文档的 hits数组。在本例中,每个返回的文档包含索引( _index属性)、类型( _type属性)、标识符( _id属性)、得分( _score属性)和 _source字段(通常,这是发送到索引的JSON对象。这一内容将在2.4节讨论)。

2. 查询分析

你可能觉得奇怪为什么上一节运行的查询可以返回结果。用Elasticsearch建立索引,然后用 elasticsearch来执行查询,虽然大小写不同,还是可以找到相关文档,原因就是查询分析。在建立索引时,底层的Lucene库根据Elasticsearch配置文件分析文档并建立索引数据。默认情况下,Elasticsearch会告诉Lucene对基于字符串的数据和数字都做索引和分析。查询阶段也一样,因为URI请求查询会映射到 query_string查询(将在第3章讨论),Elasticsearch会分析它。

使用索引分析API(indices analyze API, http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/indices-analyze.html),可以看到分析过程是怎样的,在建立索引时发生了什么,在查询阶段又发生了什么。

为了看到 title字段上的短语“Elasticsearch Server”建立的索引具体是什么,可以执行以下命令:

curl -XGET 'localhost:9200/books/_analyze?field=title'-d 'Elasticsearch Server'

响应如下:

{"tokens":[{"token":"elasticsearch","start_offset":0,"end_offset":13,"type":"<ALPHANUM>","position":1},{"token":"server","start_offset":14,"end_offset":20,"type":"<ALPHANUM>","position":2}]}

可以看到,Elasticsearch把文本划分为两个词,第一个标记值(token value)为 elasticsearch,第二个标记值为 server

现在看看查询文本是如何被分析的,运行以下命令:

curl -XGET 'localhost:9200/books/_analyze?pretty&field=title'-d 'elasticsearch'

响应如下:

{"tokens":[{"token":"elasticsearch","start_offset":0,"end_offset":13,"type":"<ALPHANUM>","position":1}]}

可以看到,这个词和传到查询的原始值是一样的。我们不会详细介绍Lucene查询以及查询解析器如何构建查询,但总地来说,分析之后的索引词和分析之后查询词是一样的,因此,该文档与查询匹配并作为结果返回。

3. URI查询中的字符参数

有几个参数,可以用来控制URI查询行为,现在来讨论一下。查询中的每个参数应加上 &字符,如以下示例所示:

curl -XGET 'localhost:9200/books/_search?pretty&q=published:2013&df=title&explain=true&default_operator=AND'

请记得 '字符,因为在类Linux系统上, &字符会被Linux shell解析。

1. 查询

参数 q用来指定我们希望文件匹配的查询条件。可以使用Lucene查询语法来指定查询,1.5.3节会描述。例如,一个简单的查询可能类似 q=title:elasticsearch

2. 默认查询字段

使用 df参数,可以指定在 q参数中没有字段时应该默认使用的字段。默认情况下,将使用 _all字段。Elasticsearch把其他所有字段的内容复制到 _all字段。2.4节将更深入地讨论。一个 df参数的例子是 df=title

3. 分析器

可以将 analyzer属性定义用于分析查询的分析器名称。默认情况下,索引阶段对字段内容做分析的分析器将用来分析我们的查询。

4. 默认操作符

Default_operator属性可以设置成 ORAND,用来指定用于查询的默认布尔运算符。默认情况下,它设置为 OR,意味着只要有一个查询条件匹配,就将返回文档。此参数设置为 AND时,所有查询条件都匹配时才会返回文档。

5. 查询解释

如果将 explain参数设置为 true,Elasticsearch将在结果的每个文档里包括额外的解释信息,如文档是从哪个分片上获取的、计算得分的详细信息(5.7节将深入讨论)。记住,不要在正常的搜索查询中设置 explaintrue,因为它需要额外的资源并使查询的性能下降。下面的代码是一个例子:

{"_shard":3,"_node":"kyuzK62NQcGJyhc2gI1P2w","_index":"books","_type":"es","_id":"2","_score":0.19178301,"_source":{"title":"MasteringElasticsearch", "published":2013},"_explanation":{"value":0.19178301,"description":"weight(title:elasticsearch in 0)[PerFieldSimilarity], result of:","details":[{"value":0.19178301,"description":"fieldWeight in 0, product of:","details":[{"value":1.0,"description":"tf(freq=1.0), with freq of:","details":[{"value":1.0,"description":"termFreq=1.0"}]},{"value":0.30685282,"description":"idf(docFreq=1, maxDocs=1)"},{"value":0.625,"description":"fieldNorm(doc=0)"}]}]}}

6. 返回字段

默认情况下,返回的每个文档中,Elasticsearch将包括索引名称、类型名称、文档标识符、得分和 _source字段。我们可以修改这个行为,通过添加 fields参数并指定一个以逗号分隔的字段名称列表。这些字段将在存储字段(如果存在的话)或内部 _source字段中检索。默认情况下,字段的 fields参数值是 _source。一个例子是 fields=title

也可以加上 _source参数并把值设为 false,来禁用 _source字段的读取。

7. 结果排序

通过使用 sort参数,可以指定自定义排序。Elasticsearch的默认行为是把返回文档按它们的得分降序排列。如果想有不同的排序,则需要指定 sort参数。例如,添加 sort=published:desc,文档将按 published字段降序排序;添加 sort=published:asc,则告诉Elasticsearch把文档按 published字段升序排序。

如果指定自定义排序,Elasticsearch将省略计算文档的 _score字段。这可能不是你想要的。如果在自定义排序的同时还想保持追踪每个文档的得分,你应该把 track_scores=true添加到你的查询。请注意,进行自定义排序时跟踪分数,会使查询稍微慢一点(你可能根本察觉不到),因为需要处理能力来计算得分。

8. 搜索超时

默认情况下,Elasticsearch没有查询超时,但你可能希望查询在一段时间(比如5秒)后超时。Elasticsearch允许你设置 timeout参数。查询将执行到给定的 timeout值,在那一刻,收集的结果将返回。把 timeout=5s添加到你的查询,就可指定一个5秒的超时。

9. 查询结果窗口

Elasticsearch允许你指定结果窗口(应返回的结果列表中文件的范围)。有两个参数用来指定结果窗口大小: sizefromsize参数默认为10,它定义了返回结果的最大数量。 from参数的默认值为0,它指定结果应该从哪个记录开始返回。为了从第11个开始返回5个文档,我们将在查询中添加以下参数: size=5&from=10

10. 搜索类型

URI查询允许使用 search_type参数指定搜索类型,搜索类型默认为 query_then_fetch。我们可以使用以下6个值:

  • dfs_query_then_fetch
  • dfs_query_and_fetch
  • query_then_fetch
  • query_and_fetch
  • count
  • scan

3.2节将介绍更多搜索类型的相关知识。

11. 小写扩展词

一些查询使用查询扩展,比如前缀查询(prefix query),3.9节将讨论这一内容。可以使用 lowercase_expanded_terms属性来定义扩展词是否应该被转为小写。默认该属性为 true,意味着扩展词将被小写。

12. 分析通配符和前缀

默认情况下,通配符查询和前缀查询不会被分析。如果要更改此行为,可以把 analyze_wildcard属性设置为 true

1.5.3 Lucene查询语法

我们认为最好大致了解在URI查询里的 q参数中可以使用的语法。Elasticsearch中的一些查询,比如正在讨论的这个查询,支持使用Lucene查询解析器语法,这是一种用来构建查询的语言。来看看它并讨论一些基本功能。如果想阅读完整的Lucene查询语法,请访问如下网页: http://lucene.apache.org/core/4_6_1/queryparser/org/apache/lucene/queryparser/classic/package-summary.html

我们传到Lucene的查询被查询解析器分为词(term)和操作符(operator)。先从词开始,你可以区分两种类型的词:单词和短语。例如,为了查询 title字段中的 book一词,传入如下查询:

title:book

为了查询 title字段中 elasticsearch book这个短语,传入如下查询:

title:"elasticsearch book"`

你可能已经注意到,字段的名字在前面,单词或短语在后面。

前面说过,Lucene查询语法支持操作符。例如,操作符 +告诉Lucene给定部分必须在文档中匹配。操作符 -正相反,查询的这一部分不能出现在文档中。查询中既没有 +又没有 -操作符的部分将被视为可以匹配、但非强制性的查询。所以,如果想找 title字段包含 book一词但 description字段不包含 cat一词的文档,传入以下查询:

+title:book -description:cat

也可以用括号来组合多个词,如下面的查询:

title:(crime punishment)

还可以使用 ^操作符接上一个值来助推(boost)1一个词,比如以下查询:

1boost将加强查询对该词的相关性。——译者注

title:book^4

1.6 小结

本章介绍了什么是全文搜索,以及Apache Lucene是如何实现的;熟悉了Elasticsearch的基本概念和它的顶层架构;使用Elasticsearch REST API来索引、更新、检索,最终删除数据;最后,使用简单的URI查询搜索了我们的数据。下一章的重点是建立索引数据。我们将看到Elasticsearch索引的工作原理,主分片和其副本的作用。还会看到Elasticsearch如何处理它不知道的数据,或者说如何创建我们自己的映射,也就是描述索引结构的JSON结构。我们还将学习如何使用批量索引来加快索引过程,可以存储什么额外的信息来帮助实现目标。此外,我们将讨论什么是索引段,什么是段合并以及如何调整段。最后,我们将看到在Elasticsearch中路由是如何工作的,以及在谈到索引路由和查询路由时,有什么选择。

 

http://www.ituring.com.cn/tupubarticle/1620



已有 0人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



海量用户的积分排序问题算法的分析

$
0
0

Question

某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新。现在要为该网站设计一种算法,在每次用户登录时显示其当前积分排名。用户最大规模为2亿;积分为非负整数,且小于100万。

表(user_score)的结构可以如下设计:

FieldTypeNullKeyDefault
uidint(11)NOPRI0
scoreint(11)NO0

Algorithm 1

Thinking

通过使用一个简单的SQL语句查询出积分大于该用户积分的用户数量:

SELECT 1+ COUNT(t2.uid) AS rank FROM user_score t1, user_score t2 WHERE t1.uid = @uid AND t2.score > t1.score; 

缺点:需要对user_score进行全表扫描,还要考虑到查询的同时若有积分的更新会产生死锁。海量数据规模以及高并发的应用中不适用。

Algorithm 2 均匀分区设计

Thinking

  1. 用户排名是一个全局性的统计指标,而非用户的私有属性,缓存在这里并不适用。

  2. 具体的进行问题分析:真实的用户积分变化是有一定规律的,通常用户积分都不会暴增暴减。一般用户都是在低分区,即用户积分的分布总体来说是有区段的。同时,高分区用户的细微变化对低分段用户排名影响不大。

  3. 考虑按积分区段进行统计方法,引入分区积分表 score_range.

FieldTypeNullKeyDefault
from_scoreint(11)NOPRI0
to_scoreint(11)NOPRI0
countint(11)NO0

该表表示,在积分区间 [from_score, to_score) 有count个用户

对用户积分的更新要相应地更新该表的区间值。在 score_range的辅助下,查询积分为s的用户的排名,通过累加高积分区间的 count值,再计算用户在本区间内的排名即可获得结果。

该方法貌似通过区间聚合减少了查询计算量。不过有问题是:如果查询用户在本区间内的排名呢?

SELECT 1+COUNT(t2.uid) AS rank FROM user_score t1, user_score t2 WHERE t1.uid = @uid AND t2.score > t1.score AND t2.score < @to_score; 

如果对 score字段建立索引,我们期望该SQL语句将通过索引大大减少扫描的 user_score表的行数。

不过根据二八定律,对于大量低分区用户进行区间内排名查询的性能不及对少数高分区用户进行查询。对于一般用户来讲,并没有实质性的性能提升。

优点:通过建立积分区间,减少全表扫描。
缺点:积分分布的不均匀导致性能并不理想。

Algorithm 3 树形分区设计

Thinking

再次考虑,是否可以按照二八定律,把 score_range表设计为非均匀区间,把低分区划分密集一点。Eg:开始设置10分一个区间,然后区间逐渐变成100分,1000分……

不过该方法随机性较大,同时系统的积分分布会随着使用而逐渐变化。我们希望找到一种分区方法,既可以适应积分非均匀性,又可以适应系统积分分布的变化。这就是树形分区。

我们把[0, 1m)作为一级区间,再二分为两个2级区间[0, 500k), [500k, 1m),以此类推,最终获得21级区间[0,1), ... , [999999,1m)。

实际上把区间组织为了平衡二叉树结构。树形分区结构需要在更新时保持一种不变量:
非叶子结点的count值 == 左右子节点的count之和

每次用户积分有变化所需要更新的区间数量和积分变化量有关系,积分变化越小更新的区间层次越低。每次需要更新的区间数量是用户积分变量的 log n级别。

在该积分表的辅助下查询积分为s的用户排名,实际上市在一个区间树上由上至下明确s所在位置的过程。s积分的排名即是排在他前面的区间的 count的累加。

本算法的更新和查询都设计若干个操作,但如果为区间 from_scoreto_score建立索引,这些操作都是基于键的查询和更新,不会产生全表扫描。

同时该算法并不依赖关系数据模型和SQL运算,可以轻易地改造为NoSQL。而基于键的操作也很容易引入缓存机制进一步优化性能。

估算一下,树形区间的数目大约为 2billion个,考虑每个节点的大小,整个结构只需要 几十m空间。可以在内存建立区间树结构,,通过 user_score表在 O(n)时间内初始化区间树。

优点:
不受积分分布影响;
每次查询或更新的复杂度为积分最大值的 O(log n)级别,且与用户规模无关;
不依赖SQL,容易改造为内存数据结构

Algorithm 4 积分排名数组

Thinking

Algo3的时间复杂度只在n特别大的时候才具有优势,而实际应用中积分的变化情况往往不大,这时和 O(n)算法相比没有明显优势。

仔细观察积分变化对于排名的影响,可以发现某用户的积分从s变为s+n,只有积分在 [s, s+n)区间的用户排名会下降1名。我们可以用一个大小为1M的数组表示积分和排名的对应关系, rank[s]表示积分s所对应的排名。初始化时,rank数组可以由 user_score表在 O(n)的复杂度内计算而来。查询积分s所对应的排名直接返回 rank[s]即可,当用户积分从s变为s+n,只需要把 rank[s]rank[s+n-1]这n个元素的值加1即可,复杂度为 O(n)

参考

  1. 某年某月的《码农》期刊

Windbg入门实战讲解

$
0
0

聚锋实验室:gongmo

windbg作为windows调试的神器。是查看内核某些结构体,挖掘漏洞,调试系统内核,调试驱动等必不可少的工具。但是由于windbg命令众多,界面友好程度较差,从而造成新人上手不易,望而却步。本文抛砖引玉,从基础入手,讲解windbg。希望同作为新人的我们一起进步!

注意:本文省略部分为:1.如何加载系统符号。2.如何开启双机调试。因为这部分的内容,网络上太多了。读者可自行百度。但是请注意:这两部分也是很重要的。

0×1 程序代码

为了整体掌握windbg的调试流程。本文实例采用自己编写。好处是可以更为主动的熟悉windbg的调试命令,更加直观的查看windbg的显示结果。

0×2 windbg调试入口

打开windbg,点击:File->Open Executable,选中编译好的exe文件。Windbg会自动给程序下一个断点。但是我们不知道这个断点是否属于我们程序的区域。所以,我们先要看下,断点是断在了什么地方。我们在windbg命令中输入!address 断点地址。如下图所示:

图中不仅仅显示了断点所在的“领空区域”,还显示了一些文件的其他属性。由于此时的断点不再我们需要的领空,所以下面要使用上文提过的伪寄存器了。我们在windbg中输入:bp $exentry。也可以输入bp @$exentry。@的作用是让windbg不再去寻找系统符号,从而加快了执行速度。Bp呢,我们依旧可以看下windbg的帮助文档。从中,我们可以知道,bp就是给地址下一个断点。好让程序中断下来。那$exentry又是什么呢?我们可以在Help->Content点击索引,输入:pseudo查看。$exentry就是我们的程序入口点啦。

之后我们输入bl命令;可以查看我们下的断点。

输入g命令;g就是运行程序的意思。运行程序,程序就会停在我们的程序入口点了,也就是oep。

但这依然不是我们想要的。这下系统符号表的作用就体现出来了。虽然本程序加载的系统符号表是vs2015debug时候自动生成的,但是这个系统符号表与从微软下载的系统符号表的作用是一样的。

我们在windbg中输入:bp main;就这么简单。注意:这个符号表是利用的本地符号表。输入g命令;windbg会自动给我们断在main函数中。

G命令结束后,这里我们需要注意一下:点击windbg工具条的Source mode off。当Souce mode on的时候,debug的单步命令会直接按照函数的步骤执行,而不是从真正单步汇编命令,这点上大家可以尝试切换不同的开关。具体执行如下图所示:

0×3重点命令

1)   栈内容查看

这里很重要的一点是:本程序是为了体会windbg的流程和指令。所以,不会回避源代码的显示问题。我们单步到程序的第一个call函数中,可以用F8或者F11步入其中。输入命令:kv。或者点击View->Call Stack查看。此时,我们可以看到栈中的信息是一样的。从中,我们也可以看出kv就是显示堆栈详细信息的命令啦。k命令在windows漏洞挖掘,了解windows执行过程中是非常有用的命令之一。

从下图中,我们也可以看到kv命令后,001218a7正是第一个call函数的返回地址。00000001和00000002正是传递给f_add的参数。在CVE漏洞号码验证的程序中,经常看到大神门查看栈信息就是如此。而ChildEBP信息是什么呢?如下图所示,通过图所示看到:ChildEBP原来就是子函数栈基址的指针地址啦。RetAddr 就是返回的函数地址,Args to Child 就是显示的参数啦。

2)字符串的查看

继续F10,运行完第一个call函数后,windbg显示了一个‘string’的字符。那么想要知道这个字符是什么呢?怎么查看呢?我们这里使用了db命令,就是以byte的形式显示内存数据。Dd命令就没有后面的字符串啦,比较单调,读者可以自己尝试。

我们运行到四个参数的f_add函数中去,kb查看栈的信息,此时,发现Args to child只能显示3个参数,如果有多个参数怎么办呢?可以使用kp或者kP命令,他们的结果是一样的,知识换行与否。结果如下图所示:

3)结构查看

假如我们不知道st_m的结构,想要产看一下st_m的结构是什么,可以使用 dt st_m;可以看到如下结果。3个int类型,每个占用4字节。

有了这些知识,我们就可以简单的进行一些windows的调试;不信,看下面的例子。

0×4 Windows双机调试(实战)

此次利用的漏洞来源: www.exploit-db.com属于SEH Buffer Overflow类型。

执行前:

执行后:

1)寻找指定进程和附加

打开wavtomp3这个软件。我们通过.process 0 0命令,查看XP中运行的进程。然后找到指定进程后,通过.process /i 进程地址。切换到实际需要的进程中去。切换后;记得‘g’运行下。

2)寻找适合的断点

合适的断点在许多的调试中很重要,断点需要经验的积累和技术的积累。没有一招吃遍天下的断点。本文因为是SHE的缓冲区溢出。并且在用户层触发异常,所以这里我们直接可以下断:bp RtlpExecuteHandlerForException。也可以求稳一点;给ReadFile函数下断点: bp ReadFile 。但是请注意:一定要.reload /f下函数的符号表。否则断点不一定成功。下断后如下图所示:

3)分析代码

运行程序后;可以断在RtlDisPatchException部分函数内。通过r命令,查看寄存器,通过db查看内存字节。例如想查看esp寄存器的值,只需要:dd esp即可。如下图所示:

图中dex的数值是shellcode文本的长度。Eip已经已经指向了异常部分。Esp指向的是栈顶。通过db esp-100 L200查看了从esp这个地址从上往下的

0×200单位的字节。

单步执行下去(F10)。遇到第一个call;图中跟入如下图:

上图中,executehandler2()函数传递了5个参数。而shellcode执行就在executehandler2()中的call ecx。我们利用命令观看:dd 0104fb24地址中的第一个参数就是我们要的执行函数的地址。也是_EXCEPTION_REGISTRATION_RECORD结构的Handler回调函数地址 。

见下图: 图中通过!exchain查看了异常的地址。通过!slist $teb _EXCEPTION_REGISTRATION_RECORD查看了当前异常链的内容。从下图也能证明,异常链的Handler是回调函数。

继续单步跟入(F8),我们发现这里其实要弹出一个Messagebox的异常对话框的。内容如下:

继续单步,就会jmp到shellcode的内容了。或者我们可以用通过一种结构来观察。此时在windbg中,输入!teb;可以看到目前的teb结构目前的值。Dt  _NT_TIB又可以观察到nt_tib内部结构。

通过上下图对比,可以看到图中我们的shellcode:0x909006eb和0x004043a4就是覆盖了fs:[0],分别指向了下一个异常块和本次的回调函数。所以上文有一个call ecx,其实是call 0x004043a4。已经指向了我们想要的东西。

下面这个图;已经是我们想要执行的指令的代码了。三个部分的代码都是一样的。

4)结束语

本文主要强调对windbg的调试操作命令做说明。对日后如何调试windows系统有所帮助。在安全行业的道路上,希望大家共勉。

*原创作者:聚锋实验室(gongmo),转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

Hybrid APP架构设计思路

$
0
0

关于Hybrid模式开发app的好处,网络上已有很多文章阐述了,这里不展开。

本文将从以下几个方面阐述Hybrid app架构设计的一些经验和思考。

原文及讨论请到 github issue

通讯

作为一种跨语言开发模式,通讯层是Hybrid架构首先应该考虑和设计的,往后所有的逻辑都是基于通讯层展开。

Native(以Android为例)和H5通讯,基本原理:

  • Android调用H5:通过webview类的 loadUrl方法可以直接执行js代码,类似浏览器地址栏输入一段js一样的效果

    webview.loadUrl("javascript: alert('hello world')");
  • H5调用Android:webview可以拦截H5发起的任意url请求,webview通过约定的规则对拦截到的url进行处理(消费),即可实现H5调用Android

    var ifm = document.createElement('iframe');
    ifm.src = 'jsbridge://namespace.method?[...args]';

JSBridge即我们通常说的桥协议,基本的通讯原理很简单,接下来就是桥协议具体实现。

P.S:注册私有协议的做法很常见,我们经常遇到的在网页里拉起一个系统app就是采用私有协议实现的。app在安装完成之后会注册私有协议到OS,浏览器发现自身不能识别的协议(http、https、file等)时,会将链接抛给OS,OS会寻找可识别此协议的app并用该app处理链接。比如在网页里以 itunes://开头的链接是Apple Store的私有协议,点击后可以启动Apple Store并且跳转到相应的界面。国内软件开发商也经常这么做,比如支付宝的私有协议 alipay://,腾讯的 tencent://等等。

桥协议的具体实现

由于JavaScript语言自身的特殊性(单进程),为了不阻塞主进程并且保证H5调用的有序性,与Native通讯时对于需要获取结果的接口(GET类),采用类似于JSONP的设计理念:

hybrid jsbridge1

类比HTTP的request和response对象,调用方会将调用的api、参数、以及请求签名(由调用方生成)带上传给被调用方,被调用方处理完之后会吧结果以及请求签名回传调用方,调用方再根据请求签名找到本次请求对应的回调函数并执行,至此完成了一次通讯闭环。

H5调用Native(以Android为例)示意图:

hybrid jsbridge2

Native(以Android为例)调用H5示意图:

hybrid jsbridge3

基于桥协议的api设计(HybridApi)

jsbridge作为一种通用私有协议,一般会在团队级或者公司级产品进行共享,所以需要和业务层进行解耦,将jsbridge的内部细节进行封装,对外暴露平台级的API。

以下是笔者剥离公司业务代码后抽象出的一份HybridApi js部分的实现,项目地址:

hybrid-js

另外,对于Native提供的各种接口,也可以简单封装下,使之更贴近前端工程师的使用习惯:

// /lib/jsbridge/core.js
function assignAPI(name, callback) {
    var names = name.split(/\./);
    var ns = names.shift();

    var fnName = names.pop();
    var root = createNamespace(JSBridge[ns], names);

    if(fnName) root[fnName] = callback || function() {};
}

增加api:

// /lib/jsbridge/api.js
var assign = require('./core.js').assignAPI;
...
assign('util.compassImage', function(path, callback, quality, width, height) {
    JSBridge.invokeApp('os.getInfo', {
        path: path,
        quality: quality || 80,
        width: width || 'auto',
        height: height || 'auto',
        callback: callback
    });
});

H5上层应用调用:

// h5/music/index.js
JSBridge.util.compassImage('http://cdn.foo.com/images/bar.png', function(r) {
    console.log(r.value); // => base64 data
});

界面与交互(Native与H5职责划分)

本质上,Native和H5都能完成界面开发。几乎所有hybrid的开发模式都会碰到同样的一个问题:哪些由Native负责哪些由H5负责?

这个回到原始的问题上来:我们为什么要采用hybrid模式开发?简而言之就是同时利用H5的跨平台、快速迭代能力以及Native的流畅性、系统API调用能力。

根据这个原则,为了充分利用二者的优势,应该尽可能地将app内容使用H5来呈现,而对于js语言本身的缺陷,应该使用Native语言来弥补,如转场动画、多线程作业(密集型任务)、IO性能等。即总的原则是H5提供内容,Native提供容器,在有可能的条件下对Android原生webview进行优化和改造(参考阿里Hybrid容器的JSM),提升H5的渲染效率。

但是,在实际的项目中,将整个app所有界面都使用H5来开发也有不妥之处,根据经验,以下情形还是使用Native界面为好:

关键界面、交互性强的的界面使用Native

因H5比较容易被恶意攻击,对于安全性要求比较高的界面,如注册界面、登陆、支付等界面,会采用Native来取代H5开发,保证数据的安全性,这些页面通常UI变更的频率也不高。

对于这些界面,降级的方案也有,就是HTTPS。但是想说的是在国内的若网络环境下,HTTPS的体验实在是不咋地(主要是慢),而且只能走现网不能走离线通道。

另外,H5本身的动画开发成本比较高,在低端机器上可能有些绕不过的性能坎,原生js对于手势的支持也比较弱,因此对于这些类型的界面,可以选择使用Native来实现,这也是Native本身的优势不是。比如要实现下面这个音乐播放界面,用H5开发门槛不小吧,留意下中间的波浪线背景,手指左右滑动可以切换动画。

layout ui1

导航组件采用Native

导航组件,就是页面的头组件,左上角一般都是一个back键,中间一般都是界面的标题,右边的话有时是一个隐藏的悬浮菜单触发按钮有时则什么也没有。

移动端有一个特性就是界面下拉有个回弹效果,头不动body部分跟着滑动,这种效果H5比较难实现。

再者,也是最重要的一点,如果整个界面都是H5的,在H5加载过程中界面将是白屏,在弱网络下用户可能会很疑惑。

所以基于这两点,打开的界面都是Native的导航组件+webview来组成,这样即使H5加载失败或者太慢用户可以选择直接关闭。

在API层面,会相应的有一个接口来实现这一逻辑(例如叫 JSBridge.layout.setHeader),下面代码演示定制一个只有back键和标题的导航组件:

// /h5/pages/index.js
JSBridge.layout.setHeader({
    background: {
        color: '#00FF00',
        opacity: 0.8
    },
    buttons: [
        // 默认只有back键,并且back键的默认点击处理函数就是back()
        {
            icon: '../images/back.png',
            width: 16,
            height: 16,
            onClick: function() {
                // todo...
                JSBridge.back();
            }
        },
        {
            text: '音乐首页',
            color: '#00FF00',
            fontSize: 14,
            left: 10
        }
    ]
});

上面的接口,可以满足绝大多数的需求,但是还有一些特殊的界面,通过H5代码控制生成导航组件这种方式达不到需求:

layout ui2

如上图所示,界面含有tab,且可以左右滑动切换,tab标题的下划线会跟着手势左右滑动。大多见于app的首页(mainActivity)或者分频道首页,这种界面一般采用定制webview的做法:定制的导航组件和内容框架(为了支持左右滑动手势),H5打开此类界面一般也是开特殊的API:

// /h5/pages/index.js
// 开打音乐频道下“我的音乐”tab
JSBridge.view.openMusic({'tab': 'personal'});

这种打开特殊的界面的API之所以特殊,是因为它内部要么是纯Native实现,要么是和某个约定的html文件绑定,调用时打开指定的html。假设这个例子中,tab内容是H5的,如果H5是SPA架构的那么 openMusic({'tab': 'personal'})则对应 /music.html#personal这个url,反之多页面的则可能对应 /mucic-personal.html

至于一般的打开新界面,则有两种可能:

  • app内H5界面

    指的是由app开发者开发的H5页面,也即是app的功能界面,一般互相跳转需要转场动画,打开方式是采用Native提供的接口打开,例如:
    
    JSBridge.view.openUrl({
        url: '/music-list.html',
        title: '音乐列表'
    });
    再配合下面即将提到的离线访问方式,基本可以做到模拟Native界面的效果。
    
  • 第三方H5页面

    指的是app内嵌的第三方页面,一般由`a`标签直接打开,没有转场动画,但是要求打开webview默认的历史列表,以免打开多个链接后点回退直接回到Native主界面。
    

系统级UI组件采用Native

基于以下原因,一些通用的UI组件,如alert、toast等将采用Native来实现:

  • H5本身有这些组件,但是通常比较简陋,不能和APP UI风格统一,需要再定制,比如alert组件背景增加遮罩层

  • H5来实现这些组件有时会存在坐标、尺寸计算误差,比如笔者之前遇到的是页面load异常需要调用对话框组件提示,但是这时候页面高度为0,所以会出现弹窗“消失”的现象

  • 这些组件通常功能单一但是通用,适合做成公用组件整合到HybridApi里边

下面代码演示H5调用Native提供的UI组件:

JSBridge.ui.toast('Hello world!');

默认界面采用Native

由于H5是在H5容器里进行加载和渲染,所以Native很容易对H5页面的行为进行监控,包括进度条、loading动画、404监控、5xx监控、网络诊断等,并且在H5加载异常时提供默认界面供用户操作,防止APP“假死”。

下面是微信的5xx界面示意:

webview monitor

设计H5容器

Native除了负责部分界面开发和公共UI组件设计之外,作为H5的runtime,H5容器是hybrid架构的核心部分,为了让H5运行更快速稳定和健壮,还应当提供并但不局限于下面几方面。

H5离线访问

之所以选择hybrid方式来开发,其中一个原因就是要解决webapp访问慢的问题。即使我们的H5性能优化做的再好服务器在牛逼,碰到蜗牛一样的运营商网络你也没辙,有时候还会碰到流氓运营商再给webapp插点广告。。。哎说多了都是泪。

离线访问,顾名思义就是将H5预先放到用户手机,这样访问时就不会再走网络从而做到看起来和Native APP一样的快了。

但是离线机制绝不是把H5打包解压到手机sd卡这么简单粗暴,应该解决以下几个问题:

  1. H5应该有线上版本

    作为访问离线资源的降级方案,当本地资源不存在的时候应该走现网去拉取对应资源,保证H5可用。另外就是,对于H5,我们不会把所有页面都使用离线访问,例如活动页面,这类快速上线又快速下线的页面,设计离线访问方式开发周期比较高,也有可能是页面完全是动态的,不同的用户在不同的时间看到的页面不一样,没法落地成静态页面,还有一类就是一些说明类的静态页面,更新频率很小的,也没必要做成离线占用手机存储空间。
    
  2. 开发调试&抓包

    我们知道,基于file协议开发是完全基于开发机的,代码必须存放于物理机器,这意味着修改代码需要push到sd卡再看效果,虽然可以通过假链接访问开发机本地server发布时移除的方式,但是个人觉得还是太麻烦易出错。
    

为了实现同一资源的线上和离线访问,Native需要对H5的静态资源请求进行拦截判断,将静态资源“映射”到sd卡资源,即实现一个处理H5资源的本地路由,实现这一逻辑的模块暂且称之为 Local Url Router,具体实现细节在文章后面。

H5离线动态更新机制

将H5资源放置到本地离线访问,最大的挑战就是本地资源的动态更新如何设计,这部分可以说是最复杂的了,因为这同时涉及到H5、Native和服务器三方,覆盖式离线更新示意图如下:

workflow

解释下上图,开发阶段H5代码可以通过手机设置HTTP代理方式直接访问开发机。完成开发之后,将H5代码推送到管理平台进行构建、打包,然后管理平台再通过事先设计好的长连接通道将H5新版本信息推送给客户端,客户端收到更新指令后开始下载新包、对包进行完整性校验、merge回本地对应的包,更新结束。

其中,管理平台推送给客户端的信息主要包括项目名(包名)、版本号、更新策略(增量or全量)、包CDN地址、MD5等。

通常来说,H5资源分为两种,经常更新的业务代码和不经常更新的框架、库代码和公用组件代码,为了实现离线资源的共享,在H5打包时可以采用分包的策略,将公用部分单独打包,在本地也是单独存放,分包及合并示意图:

multi package

Local Url Router

离线资源更新的问题解决了,剩下的就是如何使用离线资源了。

上面已经提到,对于H5的请求,线上和离线采用相同的url访问,这就需要H5容器对H5的资源请求进行拦截“映射”到本地,即 Local Url Router

Local Url Router主要负责H5静态资源请求的分发(线上资源到sd卡资源的映射),但是不管是白名单还是过滤静态文件类型,Native拦截规则和映射规则将变得比较复杂。这里, 阿里去啊app的思路就比较赞,我们借鉴一下,将映射规则交给H5去生成:H5开发完成之后会扫描H5项目然后生成一份线上资源和离线资源路径的映射表(souce-router.json),H5容器只需负责解析这个映射表即可。

H5资源包解压之后在本地的目录结构类似:

$ cd h5 && tree
.
├── js/
├── css/
├── img/
├── pages
│   ├── index.html
│   └── list.html
└── souce-router.json

souce-router.json的数据结构类似:

{"protocol": "http","host": "o2o.xx.com","localRoot": "[/storage/0/data/h5/o2o/]","localFolder": "o2o.xx.com","rules": {"/index.html": "pages/index.html","/js/": "js/"
    }
}

H5容器拦截到静态资源请求时,如果本地有对应的文件则直接读取本地文件返回,否则发起HTTP请求获取线上资源,如果设计完整一点还可以考虑同时开启新线程去下载这个资源到本地,下次就走离线了。

下图演示资源在app内部的访问流程图:

url router

其中proxy指的是开发时手机设置代理http代理到开发机。

数据通道

  • 上报

由于界面由H5和Native共同完成,界面上的用户交互埋点数据最好由H5容器统一采集、上报,还有,由页面跳转产生的浏览轨迹(转化漏斗),也由H5容器记录和上报

  • ajax代理

因ajax受同源策略限制,可以在hybridApi层对ajax进行统一封装,同时兼容H5容器和浏览器runtime,采用更高效的通讯通道加速H5的数据传输

Native对H5的扩展

主要指扩展H5的硬件接口调用能力,比如屏幕旋转、摄像头、麦克风、位置服务等等,将Native的能力通过接口的形式提供给H5。

综述

最后来张图总结下,hybrid客户端整体架构图:

hybrid architecture

其中的 Synchronize Service模块表示和服务器的长连接通信模块,用于接受服务器端各种推送,包括离线包等。 Source Merge Service模块表示对解压后的H5资源进行更新,包括增加文件、以旧换新以及删除过期文件等。

可以看到,hybrid模式的app架构,最核心和最难的部分都是H5容器的设计。

Viewing all 11804 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>