Javascript 面向对象编程 | 酷壳 – CoolShell.cn

Javascript是一个类C的语言,他的面向对象的东西相对于C++/Java比较奇怪,但是其的确相当的强大,在 Todd 同学的“对象的消息模型”一文中我们已经可以看到一些端倪了。这两天有个前同事总在问我Javascript面向对象的东西,所以,索性写篇文章让他看去吧,这里这篇文章主要想从一个整体的角度来说明一下Javascript的面向对象的编程。(成文比较仓促,应该有不准确或是有误的地方,请大家批评指正

另,这篇文章主要基于 ECMAScript 5, 旨在介绍新技术。关于兼容性的东西,请看最后一节。

初探

我们知道Javascript中的变量定义基本如下:

1
2
3
var name = 'Chen Hao';;
var email = 'haoel(@)hotmail.com';
var website = 'http://coolshell.cn';

如果要用对象来写的话,就是下面这个样子:

1
2
3
4
5
var chenhao = {
    name :'Chen Hao',
    email : 'haoel(@)hotmail.com',
    website : 'http://coolshell.cn'
};

于是,我就可以这样访问:

1
2
3
4
5
6
7
8
9
//以成员的方式
chenhao.name;
chenhao.email;
chenhao.website;
 
//以hash map的方式
chenhao["name"];
chenhao["email"];
chenhao["website"];

关于函数,我们知道Javascript的函数是这样的:

1
2
3
var doSomething = function(){
   alert('Hello World.');
};

于是,我们可以这么干:

1
2
3
4
5
6
7
8
9
10
11
var sayHello = function(){
   var hello = "Hello, I'm "+ this.name
                + ", my email is: " + this.email
                + ", my website is: " + this.website;
   alert(hello);
};
 
//直接赋值,这里很像C/C++的函数指针
chenhao.Hello = sayHello;
 
chenhao.Hello();

相信这些东西都比较简单,大家都明白了。 可以看到javascript对象函数是直接声明,直接赋值,直接就用了。runtime的动态语言。

还有一种比较规范的写法是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//我们可以看到, 其用function来做class。
var Person = function(name, email, website){
    this.name = name;
    this.email = email;
    this.website = website;
 
    this.sayHello = function(){
        var hello = "Hello, I'm "+ this.name  + ", \n" +
                    "my email is: " + this.email + ", \n" +
                    "my website is: " + this.website;
        alert(hello);
    };
};
 
var chenhao = new Person("Chen Hao", "haoel@hotmail.com",
                                     "http://coolshell.cn");
chenhao.sayHello();

顺便说一下,要删除对象的属性,很简单:

1
delete chenhao['email']

上面的这些例子,我们可以看到这样几点:

  1. Javascript的数据和成员封装很简单。没有类完全是对象操作。纯动态!
  2. Javascript function中的this指针很关键,如果没有的话,那就是局部变量或局部函数。
  3. Javascript对象成员函数可以在使用时临时声明,并把一个全局函数直接赋过去就好了。
  4. Javascript的成员函数可以在实例上进行修改,也就是说不同实例相同函数名的行为不一定一样。

属性配置 – Object.defineProperty

先看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//创建对象
var chenhao = Object.create(null);
 
//设置一个属性
 Object.defineProperty( chenhao,
                'name', { value:  'Chen Hao',
                          writable:     true,
                          configurable: true,
                          enumerable:   true });
 
//设置多个属性
Object.defineProperties( chenhao,
    {
        'email'  : { value:  'haoel@hotmail.com',
                     writable:     true,
                     configurable: true,
                     enumerable:   true },
        'website': { value: 'http://coolshell.cn',
                     writable:     true,
                     configurable: true,
                     enumerable:   true }
    }
);

下面就说说这些属性配置是什么意思。

  • writable:这个属性的值是否可以改。
  • configurable:这个属性的配置是否可以改。
  • enumerable:这个属性是否能在for…in循环中遍历出来或在Object.keys中列举出来。
  • value:属性值。
  • get()/set(_value):get和set访问器。

Get/Set 访问器

关于get/set访问器,它的意思就是用get/set来取代value(其不能和value一起使用),示例如下:

1
2
3
4
5
6
7
8
9
10
11
var  age = 0;
Object.defineProperty( chenhao,
            'age', {
                      get: function() {return age+1;},
                      set: function(value) {age = value;}
                      enumerable : true,
                      configurable : true
                    }
);
chenhao.age = 100; //调用set
alert(chenhao.age); //调用get 输出101(get中+1了);

我们再看一个更为实用的例子——利用已有的属性(age)通过get和set构造新的属性(birth_year):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Object.defineProperty( chenhao,
            'birth_year',
            {
                get: function() {
                    var d = new Date();
                    var y = d.getFullYear();
                    return ( y - this.age );
                },
                set: function(year) {
                    var d = new Date();
                    var y = d.getFullYear();
                    this.age = y - year;
                }
            }
);
 
alert(chenhao.birth_year);
chenhao.birth_year = 2000;
alert(chenhao.age);

这样做好像有点麻烦,你说,我为什么不写成下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var chenhao = {
    name: "Chen Hao",
    email: "haoel@hotmail.com",
    website: "http://coolshell.cn",
    age: 100,
    get birth_year() {
        var d = new Date();
        var y = d.getFullYear();
        return ( y - this.age );
    },
    set birth_year(year) {
        var d = new Date();
        var y = d.getFullYear();
        this.age = y - year;
    }
 
};
alert(chenhao.birth_year);
chenhao.birth_year = 2000;
alert(chenhao.age);

是的,你的确可以这样的,不过通过defineProperty()你可以干这些事:
1)设置如 writable,configurable,enumerable 等这类的属性配置。
2)动态地为一个对象加属性。比如:一些HTML的DOM对像。

查看对象属性配置

如果查看并管理对象的这些配置,下面有个程序可以输出对象的属性和配置等东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//列出对象的属性.
function listProperties(obj)
{
    var newLine = "<br />";
    var names = Object.getOwnPropertyNames(obj);
    for (var i = 0; i < names.length; i++) {
        var prop = names[i];
        document.write(prop + newLine);
 
        // 列出对象的属性配置(descriptor)动用getOwnPropertyDescriptor函数。
        var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
        for (var attr in descriptor) {
            document.write("..." + attr + ': ' + descriptor[attr]);
            document.write(newLine);
        }
        document.write(newLine);
    }
}
 
listProperties(chenhao);

call,apply, bind 和 this

关于Javascript的this指针,和C++/Java很类似。 我们来看个示例:(这个示例很简单了,我就不多说了)

1
2
3
4
5
6
7
8
9
10
11
12
13
function print(text){
    document.write(this.value + ' - ' + text+ '<br>');
}
 
var a = {value: 10, print : print};
var b = {value: 20, print : print};
 
print('hello');// this => global, output "undefined - hello"
 
a.print('a');// this => a, output "10 - a"
b.print('b'); // this => b, output "20 - b"
 
a['print']('a'); // this => a, output "10 - a"

我们再来看看call 和 apply,这两个函数的差别就是参数的样子不一样,另一个就是性能不一样,apply的性能要差很多。(关于性能,可到 JSPerf 上去跑跑看看)

1
2
3
4
5
print.call(a, 'a'); // this => a, output "10 - a"
print.call(b, 'b'); // this => b, output "20 - b"
 
print.apply(a, ['a']); // this => a, output "10 - a"
print.apply(b, ['b']); // this => b, output "20 - b"

但是在bind后,this指针,可能会有不一样,但是因为Javascript是动态的。如下面的示例

1
2
3
4
var p = print.bind(a);
p('a');             // this => a, output "10 - a"
p.call(b, 'b');     // this => a, output "10 - b"
p.apply(b, ['b']);  // this => a, output "10 - b"

继承 和 重载

通过上面的那些示例,我们可以通过Object.create()来实际继承,请看下面的代码,Student继承于Object。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var Person = Object.create(null);
 
Object.defineProperties
(
    Person,
    {
        'name'  : {  value: 'Chen Hao'},
        'email'  : { value : 'haoel@hotmail.com'},
        'website': { value: 'http://coolshell.cn'}
    }
);
 
Person.sayHello = function () {
    var hello = "<p>Hello, I am "+ this.name  + ", <br>" +
                "my email is: " + this.email + ", <br>" +
                "my website is: " + this.website;
    document.write(hello + "<br>");
}
 
var Student = Object.create(Person);
Student.no = "1234567"; //学号
Student.dept = "Computer Science"; //系
 
//使用Person的属性
document.write(Student.name + ' ' + Student.email + ' ' + Student.website +'<br>');
 
//使用Person的方法
Student.sayHello();
 
//重载SayHello方法
Student.sayHello = function (person) {
    var hello = "<p>Hello, I am "+ this.name  + ", <br>" +
                "my email is: " + this.email + ", <br>" +
                "my website is: " + this.website + ", <br>" +
                "my student no is: " + this. no + ", <br>" +
                "my departent is: " + this. dept;
    document.write(hello + '<br>');
}
//再次调用
Student.sayHello();
 
//查看Student的属性(只有 no 、 dept 和 重载了的sayHello)
document.write('<p>' + Object.keys(Student) + '<br>');

通用上面这个示例,我们可以看到,Person里的属性并没有被真正复制到了Student中来,但是我们可以去存取。这是因为Javascript用委托实现了这一机制。其实,这就是Prototype,Person是Student的Prototype。

当我们的代码需要一个属性的时候,Javascript的引擎会先看当前的这个对象中是否有这个属性,如果没有的话,就会查找他的Prototype对象是否有这个属性,一直继续下去,直到找到或是直到没有Prototype对象。

为了证明这个事,我们可以使用Object.getPrototypeOf()来检验一下:

1
2
3
4
5
6
7
Student.name = 'aaa';
 
//输出 aaa
document.write('<p>' + Student.name + '</p>');
 
//输出 Chen Hao
document.write('<p>' +Object.getPrototypeOf(Student).name + '</p>');

于是,你还可以在子对象的函数里调用父对象的函数,就好像C++里的 Base::func() 一样。于是,我们重载hello的方法就可以使用父类的代码了,如下所示:

1
2
3
4
5
6
7
//新版的重载SayHello方法
Student.sayHello = function (person) {
    Object.getPrototypeOf(this).sayHello.call(this);
    var hello = "my student no is: " + this. no + ", <br>" +
                "my departent is: " + this. dept;
    document.write(hello + '<br>');
}

这个很强大吧。

组合

上面的那个东西还不能满足我们的要求,我们可能希望这些对象能真正的组合起来。为什么要组合?因为我们都知道是这是OO设计的最重要的东西。不过,这对于Javascript来并没有支持得特别好,不好我们依然可以搞定个事。

首先,我们需要定义一个Composition的函数:(target是作用于是对象,source是源对象),下面这个代码还是很简单的,就是把source里的属性一个一个拿出来然后定义到target中。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Composition(target, source)
{
    var desc  = Object.getOwnPropertyDescriptor;
    var prop  = Object.getOwnPropertyNames;
    var def_prop = Object.defineProperty;
 
    prop(source).forEach(
        function(key) {
            def_prop(target, key, desc(source, key))
        }
    )
    return target;
}

有了这个函数以后,我们就可以这来玩了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//艺术家
var Artist = Object.create(null);
Artist.sing = function() {
    return this.name + ' starts singing...';
}
Artist.paint = function() {
    return this.name + ' starts painting...';
}
 
//运动员
var Sporter = Object.create(null);
Sporter.run = function() {
    return this.name + ' starts running...';
}
Sporter.swim = function() {
    return this.name + ' starts swimming...';
}
 
Composition(Person, Artist);
document.write(Person.sing() + '<br>');
document.write(Person.paint() + '<br>');
 
Composition(Person, Sporter);
document.write(Person.run() + '<br>');
document.write(Person.swim() + '<br>');
 
//看看 Person中有什么?(输出:sayHello,sing,paint,swim,run)
document.write('<p>' + Object.keys(Person) + '<br>');

Prototype 和 继承

我们先来说说Prototype。我们先看下面的例程,这个例程不需要解释吧,很像C语言里的函数指针,在C语言里这样的东西见得多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var plus = function(x,y){
    document.write( x + ' + ' + y + ' = ' + (x+y) + '<br>');
    return x + y;
};
 
var minus = function(x,y){
    document.write(x + ' - ' + y + ' = ' + (x-y) + '<br>');
    return x - y;
};
 
var operations = {
    '+': plus,
    '-': minus
};
 
var calculate = function(x, y, operation){
    return operations[operation](x, y);
};
 
calculate(12, 4, '+');
calculate(24, 3, '-');

那么,我们能不能把这些东西封装起来呢,我们需要使用prototype。看下面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Cal = function(x, y){
    this.x = x;
    this.y = y;
}
 
Cal.prototype.operations = {
    '+': function(x, y) { return x+y;},
    '-': function(x, y) { return x-y;}
};
 
Cal.prototype.calculate = function(operation){
    return this.operations[operation](this.x, this.y);
};
 
var c = new Cal(4, 5);
 
c.calculate('+');
c.calculate('-');

这就是prototype的用法,prototype 是javascript这个语言中最重要的内容。网上有太多的文章介始这个东西了。说白了,prototype就是对一对象进行扩展,其特点在于通过“复制”一个已经存在的实例来返回新的实例,而不是新建实例。被复制的实例就是我们所称的“原型”,这个原型是可定制的(当然,这里没有真正的复制,实际只是委托)。上面的这个例子中,我们扩展了实例Cal,让其有了一个operations的属性和一个calculate的方法。

这样,我们可以通过这一特性来实现继承。还记得我们最最前面的那个Person吧, 下面的示例是创建一个Student来继承Person。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
function Person(name, email, website){
    this.name = name;
    this.email = email;
    this.website = website;
};
 
Person.prototype.sayHello = function(){
    var hello = "Hello, I am "+ this.name  + ", <br>" +
                "my email is: " + this.email + ", <br>" +
                "my website is: " + this.website;
    return hello;
};
 
function Student(name, email, website, no, dept){
    var proto = Object.getPrototypeOf;
    proto(Student.prototype).constructor.call(this, name, email, website);
    this.no = no;
    this.dept = dept;
}
 
// 继承prototype
Student.prototype = Object.create(Person.prototype);
 
//重置构造函数
Student.prototype.constructor = Student;
 
//重载sayHello()
Student.prototype.sayHello = function(){
    var proto = Object.getPrototypeOf;
    var hello = proto(Student.prototype).sayHello.call(this) + '<br>';
    hello += "my student no is: " + this. no + ", <br>" +
             "my departent is: " + this. dept;
    return hello;
};
 
var me = new Student(
    "Chen Hao",
    "haoel@hotmail.com",
    "http://coolshell.cn",
    "12345678",
    "Computer Science"
);
document.write(me.sayHello());

兼容性

上面的这些代码并不一定能在所有的浏览器下都能运行,因为上面这些代码遵循 ECMAScript 5 的规范,关于ECMAScript 5 的浏览器兼容列表,你可以看这里“ES5浏览器兼容表”。

本文中的所有代码都在Chrome最新版中测试过了。

下面是一些函数,可以用在不兼容ES5的浏览器中:

Object.create()函数
1
2
3
4
5
6
7
8
9
10
function clone(proto) {
    function Dummy() { }
 
    Dummy.prototype             = proto;
    Dummy.prototype.constructor = Dummy;
 
    return new Dummy(); //等价于Object.create(Person);
}
 
var me = clone(Person);
defineProperty()函数
1
2
3
4
5
6
7
8
9
10
function defineProperty(target, key, descriptor) {
    if (descriptor.value){
        target[key] = descriptor.value;
    }else {
        descriptor.get && target.__defineGetter__(key, descriptor.get);
        descriptor.set && target.__defineSetter__(key, descriptor.set);
    }
 
    return target
}
keys()函数
1
2
3
4
5
6
7
8
function keys(object) { var result, key
    result = [];
    for (key in object){
        if (object.hasOwnProperty(key))  result.push(key)
    }
 
    return result;
}
Object.getPrototypeOf() 函数
1
2
3
4
5
function proto(object) {
    return !object?                null
         : '__proto__' in object?  object.__proto__
         : /* not exposed? */      object.constructor.prototype
}
bind 函数
1
2
3
4
5
6
7
8
var slice = [].slice
 
function bind(fn, bound_this) { var bound_args
    bound_args = slice.call(arguments, 2)
    return function() { var args
        args = bound_args.concat(slice.call(arguments))
        return fn.apply(bound_this, args) }
}

参考

(转载时请注明作者和出处,请勿用于任何商业用途)

34

您可能也喜欢:

Javascript的两本书

你能做对下面这些JavaScript的题吗?

一个人脸识别的Javascript

Javascript 曲线表作图库

又一个Javascript试验田

无觅

16

eclipse3.6+nutch-1.2+tomcat6(nutch二次开发) – 追求 – iDeal Studio空间 – Powered by Discuz!NT

eclipse3.6+nutch-1.2+tomcat6(nutch二次开发) – 追求 – iDeal Studio空间 – Powered by Discuz!NT

Eclipse+nutch-1.2+tomcat(二次开发)

结合我的另一篇文章看http://www.ncepuideal.com/space/viewspacepost.aspx?postid=246

1. 测试环境

Nutch release 1.2

Eclipse 3.6

Java 1.6

Ubuntu

Win7

2. 开始之前

将nutch导入eclipse是非常棘手的问题,但是用eclipse来编译nutch要比用命令行的方式快许多。并且eclipse的调错是非常有用的。有的时候查看日志(logs/hadoop.log)是调错的一种更快的方式。

3. 步骤

对于windows的用户来说你必须首先安装cygwin,从http://www.cygwin.com/setup.exe 下载。

(cygwin是一个在windows平台上运行的linux模拟环境,是cygnus solutions公司开发的自由软件(该公司开发了很多好东西,著名的还有eCos,不过现已被Redhat 收购)。它对于学习unix/linux操作环境,或者从linux到windows的应用程序移植,或者进行某些特殊的开发工作,尤其是使用gnu工具集在windows上进行嵌入式系统开发,非常有用。随着嵌入式系统开发在国内日渐流行,越来越多的开发者对cygwin产生了兴趣。

cygnus当初首先把gcc,gdb,gas等开发工具进行了改进,使他们能够生成并解释win32的目标文件。然后,他们要把这些工具移植到windows平台上去。一种方案是基于win32 api对这些工具的源代码进行大幅修改,这样做显然需要大量工作。因此,他们采取了一种不同的方法——他们写了一个共享库(就是cygwin dll),把win32 api中没有的unix风格的调用(如fork,spawn,signals,select,sockets等)封装在里面,也就是说,他们基于win32 api写了一个unix系统库的模拟层。这样,只要把这些工具的源代码和这个共享库连接到一起,就可以使用unix主机上的交叉编译器来生成可以在windows平台上运行的工具集。以这些移植到windows平台上的开发工具为基础,cygnus又逐步把其他的工具(几乎不需要对源代码进行修改,只需要修改他们的配置脚本)软件移植到windows上来。这样,在windows平台上运行bash和开发工具、用户工具,感觉好像在linux上工作。)

   安装cygwin并且设置PATH变量,

Example PATH:

C:\Sun\SDK\bin;c:\cygwin\bin

如果你在cmd中运行bash命令,他应该能够成功运行cygwin了。

如果你在win7或vista上运行eclipse,你需要给cygwin管理员的权限或是关掉UAC。否则hadoop将不能正常运行。

4. 下载nutch

下载nutch1.2的源码(不是编译好的二进制文件)

5. 在eclipse中创建一个新的java项目名为Nutch

A.将apache-nutch-1.2-src.zip解压出来的/src/java目录下的代码全部复制到工程的src目录下。

B.将解压后的nutch-1.2目录下的lib、plugins、conf三个文件夹复制到新建工程的根目录下(与src同级)。

C.右键工程properties,切换到"Libraries"选择"Add Class Folder…" 按钮,从列表中选择"conf",将 conf加入到classpath中。

D. 右键工程properties,切换到"Libraries"选择"Add External JARs " 按钮,将lib文件夹中的所有jar加入到工程中。这两步完成后看代码应该不会有编译错误了。

E. 修改conf下的nutch-site.xml,在configuration标签对中添加如下代码:

<property>

    <name>http.agent.name</name>

    <value>Your Agent Name Here</value>

</property>

F.在conf下的nutch-default.xml中找到plugin.folders,将值设为./plugin,如果这里设置的不对,会报错:Job Failure,这也是一个常见的错误。

G.还是conf下,在crawl-urlfilter.txt中找到MY.DOMAIN.NAME,改为想抓取的网站的正则表达式,这里我们把这行改为/,试图抓取qq网站的内容。

H.在工程的根目录下建立urls文件夹,其中新建一个url.txt文件,该文件中写入想抓取的网站URL,其实也就是一个爬虫的种子url,这里为了抓取qq的网页,我们添加一个url为http://www.qq.com/,注意这里最后的斜线一定要加上,不然最后的索引结果为0

I.配置java项目的运行参数:crawl urls -dir crawl -depth 5 -threads 4 -topN 10(这个参数在上一篇文章里我已经说了http://www.ncepuideal.com/space/viewspacepost.aspx?postid=246

最后点击Eclipse的Run按钮,如果没有什么问题,命令行下面就会显示很多打印的抓取提示,然后是索引之类的。

6. 将nutch部署在tomcat中

其实做完上面的步骤,nutch的配置可以说就算完成了,因为我们已经可以用命令行来搜索了,但是为了更方便的展现,下面将nutch部署在Tomcat上,然后像访问Google那样访问其搜索页面。

这一步实际上非常简单,只要将nutch编译后的文件中的war包放在tomcat的webapps下就行了,为了访问方便,可以将文件夹的名字改成nutch。另外,为了和上面爬取的内容联系起来,还需要配置一下webappsnutch/WEB-INF/classes/nutch-site.xml文件,添加如下代码:

<property>

     <name>searcher.dir</name>

     <value>Your Crawl Result Dir</value>

</property>

其中爬取结果存放的路径应该是Nutch项目文件夹中的crawl文件夹,这样就能保证搜索的结果是从我们刚才爬取的结果中得到的。

这时候搜索,可以在tomcat的命令行中看到乱码,我们还要配置下tomcat的conf/server.xml文件,将其中的Connector元素改为如下形式:

 <Connector port="8080" protocol="HTTP/1.1"

               connectionTimeout="20000"

       URIEncoding="UTF-8"

       useBodyEncodingForURI="true"

               redirectPort="8443" />

修改完后,重启下tomcat,然后再访问http://localhost:8080/nutch 就可以得到正确结果了。

Solr 学习(1) —— 搭建环境 – 奔跑的面包 – ITeye技术网站

Solr 学习(1) —— 搭建环境

写在前面

     2010年参加了一个全文搜索的项目,开始学习和使用lucene,写了大半年的代码,在linux上实现了爬虫+建立索引(中文分词)+查询(分页、高亮)+规则(指定关键词组合)触发事件的功能。

     后来随着项目需求的增多,代码越写越多,最痛苦的是在更改实现方案,还有维护索引的时候,又要大量改代码,正当我快崩溃的时候,发现了solr,才知道自己以前实现的维护部分的功能大部分solr都集成了,而且更稳定,功能更强大,原来自己绕了弯路,做了重复发明轮子的工作,于是开始把以前的东西推到重来,全部用solr实现,从此项目在搜索方面稳定了N倍,由衷感谢solr项目给我们带来的便利。以下写一些心得记录自己的学习历程。

solr 安装

     linux下安装过程,windows类似

1、安装准备      JDK 1.6 或更高版本      Tomcat 6

     Solr 3.5  

2、Tomcat安装

    将Tomcat发行包解压到指定目录,如/home/tomcat/ 3、Solr安装      将Solr发行包(apache-solr-3.5.0.tgz )解压,复制example/webapp/solr.war到tomcat的webapps目录,例如/home/tomcat/webapps,复制Solr发行包中配置示范文件(/example/solr文件夹)到tomcat/bin目录,如/home/tomcat/bin。按照solr的要求,需要指定solr-home的路径,如不指定,默认当前运行目录(tomcat/bin),于是本文为了简单,直接把solrconf放到bin目录下

4、中文分词器安装

    中文分词在solr里面是没有默认开启的,需要我们自己配置一个中文分词器。目前可用的分词器有smartcn,IK,jeasy,庖丁。其实主要是两种,一种是基于中科院ICTCLAS的隐式马尔科夫HMM算法的中文分词器,如smartcn,ictclas4j,优点是分词准确度高,缺点是不能使用用户自定义词库;另一种是基于最大匹配的分词器,如IK ,Jeasy,庖丁,优点是可以自定义词库,增加新词,缺点是分出来的垃圾词较多。各有优缺点,看应用场合自己衡量选择吧。

下面给出两种分词器的安装方法,任选其一即可,推荐第一中,因为smartcn就在solr发行包的contrib/analysis-extras/lucene-libs/下,就是lucene-smartcn-3.5.0.jar

4.1 smartcn 分词器的安装

将contrib/analysis-extras/lucene-libs/lucene-smartcn-3.5.0.jar复制到/tomcat/webapps/solr/WEB-INF/lib,

打开/tomcat/bin/solr/conf/scheme.xml,编辑text字段类型如下,添加以下代码到shema中的相应位置,就是找到fieldType定义的那一段,在下面多添加这一段就好啦

  1. <fieldType name="text" class="solr.TextField">  
  2.     <analyzer class="org.apahce.lucene.analysis.cn.smart.SmartChineseAnalyzer"/>  
  3. </fieldType>  
<fieldType name="text" class="solr.TextField">
    <analyzer class="org.apahce.lucene.analysis.cn.smart.SmartChineseAnalyzer"/>
</fieldType>

 

4.2 IK 分词器的安装   IKAnalyer3.2.8 下载

将IKAnalyzer3.2.5发行包解压,复制IKAnalyzer3.2.8Stable.jar到Solr的lib中,如/home/tomcat/webapps/solr/WEB-INF/lib,复制配置文件IKAnalyzer.cfg.xml和ext_stopword.dic到tomcat/bin中,如/home/tomcat/bin。同样,到/tomcat/webapps/solr/WEB-INF/lib,

打开/tomcat/bin/solr/conf/scheme.xml,编辑text字段类型如下,添加到schema中

  1. <fieldType name="text" class="solr.TextField">   
  2.        <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/>   
  3. </fieldType>  
<fieldType name="text" class="solr.TextField"> 
       <analyzer class="org.wltea.analyzer.lucene.IKAnalyzer"/> 
</fieldType>

 

    另外,IK分词的google code上也有详细的使用说明,可以多参考下。作者首页 ,大家都去学习下

5.TOMCAT配置

5.1 server.xml

    (如/home/tomcat/conf/server.xml)     在server.xml主要设置侦听端口。一般来说,SOLR常用端口为8983。你也可以使用自己喜欢的端口,例如80或8080。 同时,注意将URIEncoding设置为UTF-8。例如AJP等更多Native优化,参考TOMCAT优化方案。

  1. <Connector port="8983" maxHttpHeaderSize="8192"   
  2. maxThreads="150" minSpareThreads="25" maxSpareThreads="75"   
  3. enableLookups="false" redirectPort="8443" acceptCount="100"   
  4. connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8" />   
<Connector port="8983" maxHttpHeaderSize="8192" 
maxThreads="150" minSpareThreads="25" maxSpareThreads="75" 
enableLookups="false" redirectPort="8443" acceptCount="100" 
connectionTimeout="20000" disableUploadTimeout="true" URIEncoding="UTF-8" /> 

5.2 solr.xml

      如果不想配置solrhome的位置,这一步可略过,因前面把/solr/conf放在了{$TOMCAT}/bin/下面,如果要把配置文件放在其它位置,则需要按这样配置。     新增solr.xml在{$TOMCAT}/conf/Catalina/localhost/下。TOMCAT在启动时将自动加载该CONTEXT。 docBase应设置为solr war包存放位置,例如/home/apache-solor-3.5.0/example/webapp/solr.war      solr/home的值应设置为你规划存放的索引根路径,例如/web/solr/。该目录在以下将称为{$SOLR_HOME}

  1. <Context docBase="/home/apache-solr-3.5.0/example/webapp/solr.war" debug="0" crossContext="true" >   
  2.     <Environment name="solr/home" type="java.lang.String" value="/web/solr" override="true" />  
  3. </Context>    
<Context docBase="/home/apache-solr-3.5.0/example/webapp/solr.war" debug="0" crossContext="true" > 
	<Environment name="solr/home" type="java.lang.String" value="/web/solr" override="true" />
</Context>  

  

6 启动Solr

Solr在 Tomcat里,所以启动Tomcat即可,建议初期用bootstrap.jar,方便发现配置中的错误,如

进入目录/home/tomcat/bin ,然后敲入 java -jar bootstrap.jar即可运行,不报错的话就可以正常访问了

http://127.0.0.1:8983/solr/

    下面再看下分词器有没配置成功

http://127.0.0.1:8983/solr/admin/analysis.jsp

 

输入:  solr中文分词器

如果分词的结果显示  solr|中文|分词|器   

恭喜你,分词器配置成功,solr环境搭建完成了

Solr笔记(2)_Schema.xml和solrconfig.xml分析 – escaflone的专栏 – 博客频道 – CSDN.NET

Solr笔记(2)_Schema.xml和solrconfig.xml分析

分类:
搜索引擎相关

2010-07-11 00:19
2185人阅读
评论(3)
收藏
举报

现在我们开始研究载入的数据部分(importing data)

在正式开始前,我们先介绍一个存储了大量音乐媒体的网站http://musicbrainz.org

这里的数据都是免费的,一个大型开放社区提供。

MusicBrainz每天都提供一个数据快照(snapshot)的SQL文件,这些数据可以被导入PostgreSQL数据库中。

一、字段配置(schema)

schema.xml位于solr/conf/目录下,类似于数据表配置文件,

定义了加入索引的数据的数据类型,主要包括type、fields和其他的一些缺省设置。

1、先来看下type节点,这里面定义FieldType子节点,包括name,class,positionIncrementGap等一些参数。

  • name:就是这个FieldType的名称。
  • class:指向org.apache.solr.analysis包里面对应的class名称,用来定义这个类型的行为。
  1. <schema name="example" version="1.2">  
  2.   <types>  
  3.     <fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true"/>  
  4.     <fieldType name="boolean" class="solr.BoolField" sortMissingLast="true" omitNorms="true"/>  
  5.     <fieldtype name="binary" class="solr.BinaryField"/>  
  6.     <fieldType name="int" class="solr.TrieIntField" precisionStep="0" omitNorms="true"   
  7.                                                                 positionIncrementGap="0"/>  
  8.     <fieldType name="float" class="solr.TrieFloatField" precisionStep="0" omitNorms="true"   
  9.                                                                 positionIncrementGap="0"/>  
  10.     <fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true"   
  11.                                                                 positionIncrementGap="0"/>  
  12.     <fieldType name="double" class="solr.TrieDoubleField" precisionStep="0" omitNorms="true"   
  13.                                                                 positionIncrementGap="0"/>  
  14.   </types>  
  15. </schema>  

必要的时候fieldType还需要自己定义这个类型的数据在建立索引和进行查询的时候要使用的分析器analyzer,包括分词和过滤,如下:

  1. <fieldType name="text_ws" class="solr.TextField" positionIncrementGap="100">  
  2.   <analyzer>  
  3.     <tokenizer class="solr.WhitespaceTokenizerFactory"/>  
  4.   </analyzer>  
  5. </fieldType>  
  6. <fieldType name="text" class="solr.TextField" positionIncrementGap="100">  
  7.   <analyzer type="index">  
  8.     <!–这个分词包是空格分词,在向索引库添加text类型的索引时,Solr会首先用空格进行分词  
  9.          然后把分词结果依次使用指定的过滤器进行过滤,最后剩下的结果,才会加入到索引库中以备查询。  
  10.       注意:Solr的analysis包并没有带支持中文的包,需要自己添加中文分词器,google下。    
  11.      –>  
  12.     <tokenizer class="solr.WhitespaceTokenizerFactory"/>  
  13.         <!– in this example, we will only use synonyms at query time  
  14.         <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt"   
  15.                                                   ignoreCase="true" expand="false"/>  
  16.         –>  
  17.         <!– Case insensitive stop word removal.  
  18.           add enablePositionIncrements=true in both the index and query  
  19.           analyzers to leave a ‘gap’ for more accurate phrase queries.  
  20.         –>  
  21.       <filter class="solr.StopFilterFactory"  
  22.                 ignoreCase="true"  
  23.                 words="stopwords.txt"  
  24.                 enablePositionIncrements="true"  
  25.                 />  
  26.       <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1"   
  27.               generateNumberParts="1" catenateWords="1" catenateNumbers="1"   
  28.               catenateAll="0" splitOnCaseChange="1"/>  
  29.       <filter class="solr.LowerCaseFilterFactory"/>  
  30.       <filter class="solr.SnowballPorterFilterFactory" language="English"   
  31.                                                        protected="protwords.txt"/>  
  32.     </analyzer>  
  33.     <analyzer type="query">  
  34.       <tokenizer class="solr.WhitespaceTokenizerFactory"/>  
  35.         <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true"   
  36.                                                                           expand="true"/>  
  37.         <filter class="solr.StopFilterFactory"  
  38.                 ignoreCase="true"  
  39.                 words="stopwords.txt"  
  40.                 enablePositionIncrements="true"  
  41.                 />  
  42.         <filter class="solr.WordDelimiterFilterFactory" generateWordParts="1"   
  43.                 generateNumberParts="1" catenateWords="0" catenateNumbers="0"   
  44.                                         catenateAll="0" splitOnCaseChange="1"/>  
  45.         <filter class="solr.LowerCaseFilterFactory"/>  
  46.         <filter class="solr.SnowballPorterFilterFactory" language="English"   
  47.                                                          protected="protwords.txt"/>  
  48.       </analyzer>  
  49. </fieldType>  

2、再来看下fields节点内定义具体的字段(类似数据库的字段),含有以下属性:

  • name:字段名
  • type:之前定义过的各种FieldType
  • indexed:是否被索引
  • stored:是否被存储(如果不需要存储相应字段值,尽量设为false)
  • multiValued:是否有多个值(对可能存在多值的字段尽量设置为true,避免建索引时抛出错误)
  1. <fields>  
  2.     <field name="id" type="integer" indexed="true" stored="true" required="true" />  
  3.     <field name="name" type="text" indexed="true" stored="true" />  
  4.     <field name="summary" type="text" indexed="true" stored="true" />  
  5.     <field name="author" type="string" indexed="true" stored="true" />  
  6.     <field name="date" type="date" indexed="false" stored="true" />  
  7.     <field name="content" type="text" indexed="true" stored="false" />  
  8.     <field name="keywords" type="keyword_text" indexed="true" stored="false" multiValued="true" />  
  9.     <!–拷贝字段–>  
  10.     <field name="all" type="text" indexed="true" stored="false" multiValued="true"/>  
  11. </fields>  

3、建议建立一个拷贝字段,将所有的 全文本 字段复制到一个字段中,以便进行统一的检索:

     以下是拷贝设置:

  1. <copyField source="name" dest="all"/>  
  2. <copyField source="summary" dest="all"/>  

4、动态字段,没有具体名称的字段,用dynamicField字段

如:name为*_i,定义它的type为int,那么在使用这个字段的时候,任务以_i结果的字段都被认为符合这个定义。如name_i, school_i

  1. <dynamicField name="*_i"  type="int"    indexed="true"  stored="true"/>  
  2. <dynamicField name="*_s"  type="string"  indexed="true"  stored="true"/>  
  3. <dynamicField name="*_l"  type="long"   indexed="true"  stored="true"/>  
  4. <dynamicField name="*_t"  type="text"    indexed="true"  stored="true"/>  
  5. <dynamicField name="*_b"  type="boolean" indexed="true"  stored="true"/>  
  6. <dynamicField name="*_f"  type="float"  indexed="true"  stored="true"/>  
  7. <dynamicField name="*_d"  type="double" indexed="true"  stored="true"/>  
  8. <dynamicField name="*_dt" type="date"    indexed="true"  stored="true"/>  

schema.xml文档注释中的信息:

1、为了改进性能,可以采取以下几种措施:

  • 将所有只用于搜索的,而不需要作为结果的field(特别是一些比较大的field)的stored设置为false
  • 将不需要被用于搜索的,而只是作为结果返回的field的indexed设置为false
  • 删除所有不必要的copyField声明
  • 为了索引字段的最小化和搜索的效率,将所有的 text fields的index都设置成field,然后使用copyField将他们都复制到一个总的 text field上,然后对他进行搜索。
  • 为了最大化搜索效率,使用java编写的客户端与solr交互(使用流通信)
  • 在服务器端运行JVM(省去网络通信),使用尽可能高的Log输出等级,减少日志量。

2、<schema name="example" version="1.2">

  • name:标识这个schema的名字
  • version:现在版本是1.2

3、filedType

<fieldType name="string" class="solr.StrField" sortMissingLast="true" omitNorms="true" />

  • name:标识而已。
  • class和其他属性决定了这个fieldType的实际行为。(class以solr开始的,都是在org.appache.solr.analysis包下)

可选的属性:

  • sortMissingLast和sortMissingFirst两个属性是用在可以内在使用String排序的类型上(包括:string,boolean,sint,slong,sfloat,sdouble,pdate)。
  • sortMissingLast="true",没有该field的数据排在有该field的数据之后,而不管请求时的排序规则。
  • sortMissingFirst="true",跟上面倒过来呗。
  • 2个值默认是设置成false

StrField类型不被分析,而是被逐字地索引/存储。

StrField和TextField都有一个可选的属性“compressThreshold”,保证压缩到不小于一个大小(单位:char)

<fieldType name="text" class="solr.TextField" positionIncrementGap="100">

solr.TextField 允许用户通过分析器来定制索引和查询,分析器包括 一个分词器(tokenizer)和多个过滤器(filter)

  • positionIncrementGap:可选属性,定义在同一个文档中此类型数据的空白间隔,避免短语匹配错误。

<tokenizer class="solr.WhitespaceTokenizerFactory" />

空格分词,精确匹配。

<filter class="solr.WordDelimiterFilterFactory" generateWordParts="1" generateNumberParts="1" catenateWords="1" catenateNumbers="1" catenateAll="0" splitOnCaseChange="1" />

在分词和匹配时,考虑 "-"连字符,字母数字的界限,非字母数字字符,这样 "wifi"或"wi fi"都能匹配"Wi-Fi"。

<filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true" />

同义词 

<filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" enablePositionIncrements="true" />

在禁用字(stopword)删除后,在短语间增加间隔

stopword:即在建立索引过程中(建立索引和搜索)被忽略的词,比如is this等常用词。在conf/stopwords.txt维护。

4、fields

<field name="id" type="string" indexed="true" stored="true" required="true" />

  • name:标识而已。
  • type:先前定义的类型。
  • indexed:是否被用来建立索引(关系到搜索和排序)
  • stored:是否储存
  • compressed:[false],是否使用gzip压缩(只有TextField和StrField可以压缩)
  • mutiValued:是否包含多个值
  • omitNorms:是否忽略掉Norm,可以节省内存空间,只有全文本field和need an index-time boost的field需要norm。(具体没看懂,注释里有矛盾)
  • termVectors:[false],当设置true,会存储 term vector。当使用MoreLikeThis,用来作为相似词的field应该存储起来。
  • termPositions:存储 term vector中的地址信息,会消耗存储开销。
  • termOffsets:存储 term vector 的偏移量,会消耗存储开销。
  • default:如果没有属性需要修改,就可以用这个标识下。

<field name="text" type="text" indexed="true" stored="false" multiValued="true" />

包罗万象(有点夸张)的field,包含所有可搜索的text fields,通过copyField实现。

<copyField source="cat" dest="text" />

<copyField source="name" dest="text" />

<copyField source="manu" dest="text" />

<copyField source="features" dest="text" />

<copyField source="includes" dest="text" />

在添加索引时,将所有被拷贝field(如cat)中的数据拷贝到text field中

作用:

  • 将多个field的数据放在一起同时搜索,提供速度
  • 将一个field的数据拷贝到另一个,可以用2种不同的方式来建立索引。

<dynamicField name="*_i" type="int" indexed="true" stored="true" />

如果一个field的名字没有匹配到,那么就会用动态field试图匹配定义的各种模式。

  • "*"只能出现在模式的最前和最后
  • 较长的模式会被先去做匹配
  • 如果2个模式同时匹配上,最先定义的优先

<dynamicField name="*" type="ignored" multiValued="true" />

如果通过上面的匹配都没找到,可以定义这个,然后定义个type,当String处理。(一般不会发生)

但若不定义,找不到匹配会报错。

5、其他一些标签

<uniqueKey>id</uniqueKey>

文档的唯一标识, 必须填写这个field(除非该field被标记required="false"),否则solr建立索引报错。

<defaultSearchField>text</defaultSearchField>

如果搜索参数中没有指定具体的field,那么这是默认的域。

<solrQueryParser defaultOperator="OR" />

配置搜索参数短语间的逻辑,可以是"AND|OR"。

二、solrconfig.xml

1、索引配置

mainIndex 标记段定义了控制Solr索引处理的一些因素.

  • useCompoundFile:通过将很多 Lucene 内部文件整合到单一一个文件来减少使用中的文件的数量。这可有助于减少 Solr 使用的文件句柄数目,代价是降低了性能。除非是应用程序用完了文件句柄,否则 false 的默认值应该就已经足够。

  • useCompoundFile:通过将很多Lucene内部文件整合到一个文件,来减少使用中的文件的数量。这可有助于减少Solr使用的文件句柄的数目,代价是降低了性能。除非是应用程序用完了文件句柄,否则false的默认值应该就已经足够了。
  • mergeFacor:决定Lucene段被合并的频率。较小的值(最小为2)使用的内存较少但导致的索引时间也更慢。较大的值可使索引时间变快但会牺牲较多的内存。(典型的 时间与空间 的平衡配置)
  • maxBufferedDocs:在合并内存中文档和创建新段之前,定义所需索引的最小文档数。段 是用来存储索引信息的Lucene文件。较大的值可使索引时间变快但会牺牲较多内存。
  • maxMergeDocs:控制可由Solr合并的 Document 的最大数。较小的值(<10,000)最适合于具有大量更新的应用程序。
  • maxFieldLength:对于给定的Document,控制可添加到Field的最大条目数,进而阶段该文档。如果文档可能会很大,就需要增加这个数值。然后,若将这个值设置得过高会导致内存不足错误。
  • unlockOnStartup:告知Solr忽略在多线程环境中用来保护索引的锁定机制。在某些情况下,索引可能会由于不正确的关机或其他错误而一直处于锁定,这就妨碍了添加和更新。将其设置为true可以禁用启动索引,进而允许进行添加和更新。(锁机制)

 2、查询处理配置

query标记段中以下一些与缓存无关的特性:

  • maxBooleanClauses:定义可组合在一起形成以个查询的字句数量的上限。正常情况1024已经足够。如果应用程序大量使用了通配符或范围查询,增加这个限制将能避免当值超出时,抛出TooMangClausesException。
  • enableLazyFieldLoading:如果应用程序只会检索Document上少数几个Field,那么可以将这个属性设置为true。懒散加载的一个常见场景大都发生在应用程序返回一些列搜索结果的时候,用户常常会单击其中的一个来查看存储在此索引中的原始文档。初始的现实常常只需要现实很短的一段信息。若是检索大型的Document,除非必需,否则就应该避免加载整个文档。

query部分负责定义与在Solr中发生的时间相关的几个选项:

概念:Solr(实际上是Lucene)使用称为Searcher的Java类来处理Query实例。Searcher将索引内容相关的数据加载到内存中。根据索引、CPU已经可用内存的大小,这个过程可能需要较长的一段时间。要改进这一设计和显著提高性能,Solr引入了一张“温暖”策略,即把这些新的Searcher联机以便为现场用户提供查询服务之前,先对它们进行“热身”。

  • newSearcher和firstSearcher事件,可以使用这些事件来制定实例化新Searcher或第一个Searcher时,应该执行哪些查询。如果应用程序期望请求某些特定的查询,那么在创建新Searcher或第一个Searcher时就应该反注释这些部分并执行适当的查询。

query中的智能缓存:

  • filterCache:通过存储一个匹配给定查询的文档 id 的无序集,过滤器让 Solr 能够有效提高查询的性能。缓存这些过滤器意味着对Solr的重复调用可以导致结果集的快速查找。更常见的场景是缓存一个过滤器,然后再发起后续的精炼查询,这种查询能使用过滤器来限制要搜索的文档数。
  • queryResultCache:为查询、排序条件和所请求文档的数量缓存文档 id 的有序集合。
  • documentCache:缓存Lucene Document,使用内部Lucene文档id(以便不与Solr唯一id相混淆)。由于Lucene的内部Document id 可以因索引操作而更改,这种缓存不能自热。
  • Named caches:命名缓存是用户定义的缓存,可被 Solr定制插件 所使用。

其中filterCache、queryResultCache、Named caches(如果实现了org.apache.solr.search.CacheRegenerator)可以自热。

每个缓存声明都接受最多四个属性:

  • class:是缓存实现的Java名
  • size:是最大的条目数
  • initialSize:是缓存的初始大小
  • autoWarmCount:是取自旧缓存以预热新缓存的条目数。如果条目很多,就意味着缓存的hit会更多,只不过需要花更长的预热时间。

对于所有缓存模式而言,在设置缓存参数时,都有必要在内存、cpu和磁盘访问之间进行均衡。统计信息管理页(管理员界面的Statistics)对于分析缓存的 hit-to-miss 比例以及微调缓存大小的统计数据都非常有用。而且,并非所有应用程序都会从缓存受益。实际上,一些应用程序反而会由于需要将某个永远也用不到的条目存储在缓存中这一额外步骤而受到影响。

Solr笔记(1)_概况 – escaflone的专栏 – 博客频道 – CSDN.NET

Solr笔记(1)_概况

分类:
搜索引擎相关

2010-07-08 11:20
1501人阅读
评论(3)
收藏
举报

一、简洁

       Solr是一个开源的,企业级搜索服务器。她已经是一个成熟的产品,用于强化网络站点的搜索功能,包括内部局域网。

她是用Java语言编写。使用HTTP和XML进行数据传输,Java的掌握对于学习Solr不是必须的。除了能返回搜索

结果外,还有包括高亮搜索关键字,方位导航(已广泛用于电子商务网站),查询关键字拼写校验,自动查询建议

和 “类似”查询 帮助更好定位搜索。

二、Lucene,solr的基础引擎

       在相信介绍Solr前,我们先从Apache Lucene开始,Solr的核心基础引擎。Lucene是一个开源的,高效的

文本搜索引擎。Lucene是由Doug Cutting在2000年开发的,并且伴随着强大的在线社区不断进化和成熟。

Lucene不是一个服务器,也不是一个网络爬虫。这一点非常重要,她没有任何配置文件。我们需要编写代码来

存贮和查询在磁盘上的索引。

下面是Lucene的一些主要特征:

  • 通过建立基于文本的反向索引来快速查询文件。
  • 通过丰富的文本分析器(analyzers),将字符串形式的文本信息转换为一系列terms,来联系索引和搜索。
  • 一个查询分析器,还有很多能 支持从简单查询到模糊查询 的查询类型(query types)
  • 一个听上去叫Information Retrieval(IR)的得分算法,以产生更多可能的候选结果,有很多灵活的方式来设置得分策略。
  • 在上下文中高亮显示被找到的查询关键字
  • 根据索引内容 来 检查 查询关键字拼写(更多关于查询关键字拼写 可以参考Lucene In Action)

三、Solr,是Lucene的服务器化产物

在对Lucene的了解后,Solr可以理解为Lucene的服务器化产品。但她不是对Lucene的一次简单封装,Solr的大多数特征都与Lucene不同。Solr 和 Lucene 的界限经常是模糊的。以下是Solr的主要特性:

  • 通过HTTP请求来 建立索引和搜索索引
  • 拥有数个缓存 来 加快搜索速度
  • 一个基于web的管理员控制台

          运行时做性能统计,包括缓存 命中/错过 率

          查询表单 来 搜索索引。

          以柱状图形式 展示 频繁被查询的关键字

          详细的“得分计算和文本解析”分析。

  • 用XML文件的方式 配置搜索计划和服务器

          通过配置XML 来添加和配置 Lucene的文本分析库

          引入“搜索字段类型”的概念(这个非常重要,然而在Lucne中没有)。类型用作表示日期和一些特殊的排序问题。

  • 对最终用户和应用成素,disjunction-max 查询处理器比Lucene基础查询器更实用。
  • 查询结果的分类
  • 拼写检查用于寻找搜索关键字 的类似词,优化查询建议
  • “更类似于”插件用以列出 于查询结果类似的 备选结果。
  • Solr支持分布式来应对较大规模的部署。

以上特征都会在下面的章节内详述。

四、Solr 于数据库技术的比较

对于开发人员而言,数据库技术(特别是关系数据库)已经成为一个必须学习的知识。数据库和Lucene的搜索索引并没有显著的不同。假设我们已经非常熟悉数据库知识,现在来描述下她和Lucene有什么不同。(这里来帮助更好了解Solr)

最大的不同是,Lucene可以理解为一个 只有一张简单表格 的数据库,没有任何的关系查询(即JOINS)。这听上去很疯狂,不过记住索引只是为了去支持搜索,而不是去标识一条数据。所以数据库可以去遵守“第三范式”,而索引就不会如此,表格中尽可能多的包含会被搜索到的数据而已。用来补充单表的是,一个域(列)中可以有多值。

其他一些显著的不同:

  • 更新(Update):整个文档可以被删除,然后再添加,但不能被更新。
  • 子字符串搜索与文本搜索:例如“Books”,数据库的Like匹配出“CookBooks”、“MyBooks”。Lucene基于查询分析器的配置,可以查到更多形式的词匹配“Books”,比如book(这里是大小写被忽略),甚至发音相似的词。运用ngram技术,她可以提取部分搜索条件的词干进行匹配。
  • 结果打分:Lucene的强大在于她可以根据结果的匹配程度来打分。例如查询条件中有部分是可选的(OR search),那匹配程度高的文档会得到更多的分。有一些其他因素,可以调整打分的方式。然而,数据库就没有这个功能,只是匹配或不匹配。Lucene也可以在需要的时候对结果进行排序。
  • 延迟提交:Solr的搜索速度通过建立缓存得以优化。当一个完成的文档需要被提交,所有的缓存会重新构建,根据其他一些因素,这可能花费几秒到一分钟。

五、正式开始Solr

Solr是用Java编写的,不过我们不需要对Java非常了解。如果需要扩展Solr的功能,那我们需要了解Java。

我们需要掌握的是命令行操作,包括Dos和Unix。

在正式开始前,我们可能需要安装以下一些包:

  • A Java Development Kit(JDK) v1.5 or later.
  • Apache Ant: Any recent version
  • Subversion or Git for source control of Solr: svn 或 git
  • Any Java EE servlet Engine app-server:Sole 已经自带了Jetty。
  • Solr:http://lucene.apache.org/solr ,现在官方是1.4版本。也可以去取源代码获得最新版本。(经过单元和集成测试,也是非常稳定和可靠的),通过 svn co http://svn.apache.org/repos/asf/lucene/solr/trunk/ solr_svn 将最新的源代码下载到本地solr_svn目录下。

 Solr 发布包下的目录结构:

  • client::包含特定的编程语言与Solr通信。这里其实只有Ruby的例子。Java的客户端在src/solrj
  • dist:这里包含Solr的Jar包和War包
  • example:这里有Jetty安装所需要的包(Solr自带Jetty),包括一些样本数据和Solr的配置文件。

          example/etc:Jetty的配置文件。可以修改监听端口(默认8983)

          example/multicore:多核环境下,solr的根目录(后面会具体讨论)

          example/solr:默认环境下的solr根目录

          example/webapps:Solr的WAR包部署在这里

  • lib:所有Solr依赖的包。一大部分是Lucene,一些Apache常用的工具包,和Stax(XML处理相关)
  • src:各种源码。可以归为以下几个重要目录:

          src/java:Solr的源代码,用Java编写。

          src/scripts:Unix的bash shell脚本,应用与在大型应用中部署多个Solr服务。

          src/solrj:Solr Java的客户端。

          src/webapp:Solr web端的管理员用户界面,包括Servlets和JSP。这些其实也都是War中的内容。

注意:要看Java源码的话,src/java下是主要的Solr源码;src/common下是一部分通用类,供server端和solrj客户端;src/test中是测试代码;src/webapp/src下是servlet代码;

六、Solr的根目录

Solr的根目录下包括Solr的配置和运行Solr实例需要的数据。

Sole有一个样例根目录,在example/solr下,我们将会使用这个

另一个更技术层面的,在example/solr下,也是Solr的一个根目录不过是用在多核的环境下,稍后讨论。

让我们来看下根目录下有些什么:

  • bin:如果想自己设置Solr,这里可以放脚本。
  • conf:配置文件。下面的2个文件很重要,这个文件夹下还包括一些其他文件,都是被这2个文件引用,为了一些其他配置,比如文本分析的细节。

          conf/schema.xml:这里是索引的概要,包括 域类型(field type)定义和相关分析器链。

          conf/sorconfig.xml:这是Solr配置的主文件。

          conf/xslt:这个目录薄厚一些XSLT文件,用来把Solr搜索结果(XML)转换为其他形式,例如Atom/RSS。

  • data:包含Lucene的索引数据,Solr自动生成。这些都是二进制数据(binary),我们基本不会去动它,除非需要删除。
  • lib:一些额外的,可选的Java Jar包,Solr会在启动时调用。当你不是通过修改Solr源码 强化Solr的一些功能,可以将包放在这里。

七、Solr如何找到自己的根目录

Solr启动后的第一件事是从根目录加载配置信息。这可以通过好几种方式来指定。

  • Solr先从Java的系统环境变量中搜寻 solr.solr.home这个变量。通常通过命令行设置,如启动Jetty时:java -Dsolr.solr.home = solr/  -jar start .jar ;也可以用JNDI 绑定路径到java:comp/env/solr/home,可以设置web.xml来让app-server维护这个变量(src/web-app/web/WEB-INF)
  1. <env-entry>  
  2.   <env-entry-name>solr/home</env-entry-name>  
  3.   <env-entry-value>solr/</env-entry-value>  
  4.   <env-entry-type>java.lang.String</env-entry-type>  
  5. </env-entry>  

这里修改了web.xml,需要使用ant dist-war重新打包部署。这里仅仅如此还不够,需要设置JNDI,这里就不深入了。

PS:JNDI需要设置2个环境变量,具体查看EJB相关笔记。

  • 如果根目录没有设置在环境变量或JNDI中,默认地址是 solr/。我们后面会沿用这个地址。(具体产品还是需要配置来设定,比较安全,可以使用绝对或相对路径)

设置完根路径后,在Solr启动中的log会显示:

Aug 7, 2008 4:59:35 PM org.apache.solr.core.Config getInstanceDirINFO: Solr home defaulted to ‘null’ (could not find system property or JNDI)Aug 7, 2008 4:59:35 PM org.apache.solr.core.Config setInstanceDirINFO: Solr home set to ‘solr/’

八、部署和运行Solr

部署就是apach-solr-1.4.war。这里不包含Solr的根目录。

这里我们以自带的Jetty为例子,进入example目录

cd example

java -jar start.jar

看到下面这句日志,即启动完成:

2010-07-09 15:31:06.377::INFO:  Started SocketConnector @ 0.0.0.0:8983

在控制台点击Ctrl-C 可以关闭服务器。

0.0.0.0表示她监听来自任务主机的请求,8983是端口号。

此时,可以进入连接:http://localhost:8983/solr ,如果启动失败会显示原因,如果成功即可看到管理员入口(http://localhost:8983/solr/admin/ )。

九、简单浏览下Solr

 

顶部灰色部分:

  • 头部信息,当启动多个Solr实例时,可以帮助了解在操作哪个实例。IP地址和端口号都是可见的。
  • example(Admin旁边)是对这个schema的引用,仅仅是标识这个schema。如果你有很多schema,可以用这个标识去区分。
  • 当前工作目录(cwd) ,和Solr的根目录(SolrHome)。

导航栏上的功能:

  • SCHEMA:显示当前的schema的配置文件。(不同浏览器显示可能不同,Firefox会高亮显示语法关键字)
  • CONFIG:显示当前的Solr config文件。
  • ANALYSIS:她用来诊断潜在的 文本分析 的查询/索引问题。这是高级功能,稍后做讨论。
  • SCHEMA BROWSER:这是一个简洁的 反映当前索引中实际存放数据的 视图,稍后做讨论。
  • STATISTICS:这里是 时间和缓存命中率统计。稍后做讨论。
  • INFO:她列出了Solr当前应用组件的版本信息,不是很常用。
  • DISTRIBUTION:这里包含了分布式/复制的状态信息,稍后讨论。
  • PING:可以忽略,她用来在分布式模式下提供健壮性检查。
  • LOGGING:可以在这里设置Solr不同部分的Logging levels。在Jetty下,输出的信息都在控制台。(Solr使用SLF4j)
  • JAVA PROPERTIES:列出了JAVA系统环境变量。
  • THREAD DUMP:这里显示了Java中的线程信息,帮助诊断问题。
  • FULL INTERFACE:一个更多选择的查询表单,可以帮助诊断问题。这个表单也是能力有限的,只能提交一小部分搜索选项给Solr

Assistance 部分包括一些在线的帮助信息。

十、装在示例数据

Solr有一些示例数据和装载脚本,在example/exampledocs下。

进入example/exampledoce下,输入:

java -jar post.jar *.xml (如果在unix环境下,就运行post.sh)

post.jar是一个简单的程序,会遍历所有的参数(这里就是*.xml),然后对本机正运行的Solr(example)服务器的默认配置(http://localhost:8983/solr/update ) 发送post请求(HTTP)。这里可以看下post.sh,就可以了解在干什么了。

可以在控制台命令行中看到发送的文件:

SimplePostTool: POSTing files to http://localhost:8983/solr/update ..SimplePostTool: POSTing file hd.xmlSimplePostTool: POSTing file ipod_other.xmlSimplePostTool: POSTing file ipod_video.xmlSimplePostTool: POSTing file mem.xmlSimplePostTool: POSTing file monitor.xmlSimplePostTool: POSTing file monitor2.xmlSimplePostTool: POSTing file mp500.xmlSimplePostTool: POSTing file payload.xmlSimplePostTool: POSTing file sd500.xmlSimplePostTool: POSTing file solr.xmlSimplePostTool: POSTing file utf8-example.xmlSimplePostTool: POSTing file vidcard.xmlSimplePostTool: COMMITting Solr index changes..

最后一行会执行commit操作,保证之前的文档都被保存,并可见。

理论上post.sh 和 post.jar是可以用在产品脚本上的,但这里仅仅用作示例。

这里取其中一个文件monitor.xml 看下:

 

  1. <add>  
  2.   <doc>  
  3.     <field name="id">3007WFP</field>  
  4.     <field name="name">Dell Widescreen UltraSharp 3007WFP</field>  
  5.     <field name="manu">Dell, Inc.</field>  
  6.     <field name="cat">electronics</field>  
  7.     <field name="cat">monitor</field>  
  8.     <field name="features">30" TFT active matrix LCD, 2560 x 1600,  
  9.                        .25mm dot pitch, 700:1 contrast</field>  
  10.     <field name="includes">USB cable</field>  
  11.     <field name="weight">401.6</field>  
  12.     <field name="price">2199</field>  
  13.     <field name="popularity">6</field>  
  14.     <field name="inStock">true</field>  
  15.   </doc>  
  16. </add>  

这个发送给Solr的文件非常简单。这里只用了一些简单的标签,不过都是非常重要的。

<add>标签中可以放置多个<doc>标签(一个doc代表一个document),在大量数据装载时这样做能提高性能。

Solr在每个POST请求中都会收到一个<commit/>标签。更多的一些特性会在之后介绍。

十一、一次简单的搜索。

在管理员界面,让我们运行一次简单的搜索。

在管理员界面,点击查询按钮,或进入FULL INTERFACE再作更详细的查询。

在我们查看XML输出文件之前,先看下URL和参数信息:

http://localhost:8983/solr/select/?q=monitor&version=2.2&start=0&rows=10&indent=on .

然后浏览器中会显示输出的用XML标识的搜索结果,如下:

  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <response>  
  3. <lst name="responseHeader">  
  4.   <int name="status">0</int>  
  5.   <int name="QTime">3</int><!–查询耗时(毫秒)Solr有一些缓存(保存过去的搜索结果),提高了搜索效率–>  
  6.   <lst name="params"><!–查询的参数–>  
  7.     <str name="indent">on</str><!–是否缩进XML文件–>  
  8.     <str name="rows">10</str><!–返回的结果条数–>  
  9.     <str name="start">0</str><!–搜索结果的开始位置–>  
  10.     <str name="q">monitor</str>  
  11.     <str name="version">2.2</str><!–版本信息–>  
  12.   </lst>  
  13. </lst>  
  14. <!–numFound是找到几条,start从第几条开始显示–>  
  15. <!–这里并没有显示得分情况(非full interface),但结果其实已经按照得分排序了(Solr默认)–>  
  16. <!–如果是full interface查询,result会包括maxScore属性,标识最高得分–>  
  17. <result name="response" numFound="2" start="0">  
  18. <doc>  
  19.   <!–如果是full interface查询,这里会有得分情况(默认)  
  20.   <float name="score">0.5747526</float>   
  21.   –>  
  22.   <!–默认情况Solr会列出所有存储的fields  
  23.   (不是所有field都需要存储,虽然可能根据它来查索引,但不用包含在就结果中)  
  24.    –>  
  25.   <!– 
  26.    注意,某些field是多值的,由arr标签标记的 
  27.    –>  
  28.   <arr name="cat"><str>electronics</str><str>monitor</str></arr>  
  29.   <arr name="features"><str>30" TFT active matrix LCD, 2560 x 1600,  
  30.                            .25mm dot pitch, 700:1 contrast</str></arr>  
  31.   <str name="id">3007WFP</str>  
  32.   <bool name="inStock">true</bool>  
  33.   <str name="includes">USB cable</str>  
  34.   <str name="manu">Dell, Inc.</str>  
  35.   <str name="name">Dell Widescreen UltraSharp 3007WFP</str>  
  36.   <int name="popularity">6</int>  
  37.   <float name="price">2199.0</float>  
  38.   <str name="sku">3007WFP</str>  
  39.   <arr name="spell"><str>Dell Widescreen UltraSharp 3007WFP</str>  
  40.   </arr>  
  41.   <date name="timestamp">2008-08-09T03:56:41.487Z</date>  
  42.   <float name="weight">401.6</float>  
  43. </doc>  
  44. <doc>  
  45. </doc>  
  46. </result>  
  47. </response>  

这只是一个简单的查询结果,可以加入例如高亮显示等查询条件,然后在result标记后会有更多信息。

十二、一些统计信息

进入http://localhost:8983/solr/admin/stats.jsp

在这里,当我们没有加载任何数据时,numDocs显示0,而现在显示19。

maxDocs的值取决于当你删除一个文档但却没有提交。

可以关注以下的一些handler:

/update,standard。

注意:这些统计信息都是实时的,不在磁盘上做保存。

十三、solrconfig.xml 

这里包含很多我们可以研究的参数,现在先让我们看下<requestHandler>下定义的 request handers。

 

  1. <requestHandler name="standard" class="solr.SearchHandler"  
  2.                 default="true">  
  3.   <!– default values for query parameters –>  
  4.   <lst name="defaults">  
  5.     <!–是否显示参数,none(都不显示),all(全显示,可以看到一些隐藏参数)–>  
  6.     <str name="echoParams">explicit</str>  
  7.     <!–  
  8.     <int name="rows">10</int>  
  9.     <str name="fl">*</str>  
  10.     <str name="version">2.1</str>  
  11.      –>  
  12. </lst>  
  13. </requestHandler>  

当我们通过POST通知Solr(如索引一个文档)或通过GET搜索,都会有个特定的request hander做处理。

这些handers可以通过URL来注册。之前我们加载文档时,Solr通过以下注册的handler做处理:

<requestHandler name="/update" class="solr.XmlUpdateRequestHandler" />

而当使用搜索时,是使用solr.SearchHandler(上面的XML定义了)

通过URL参数或POST中的参数,都可以调用这些request handler

也可以在solrconfig.xml中通过default,appends,invariants来指定。

这里的一些参数等于是默认的,就像已经放在了URL后面的参数一样。

十四、一些重要的Solr资源

十五、查询参数

fl=*,score&q.op=AND&start=0&rows=16&hl=true&hl.fl=merHeading&hl.snippets=3&hl.simple.pre=<font color=red>&hl.simple.post=</font>&facet=true&facet.field=merCategory&q=+(merHeading%3A%E4%BD%A0%E5%A5%BD+AND+merHeadingWithWord%3A%E6%BD%98 )  +merActualendTime:[1239264030468 TO 1240473630468]&sort=merActualendTime asc  

fl表示索引显示那些field(*表示所有field, score 是solr 的一个匹配热度) q.op 表示q 中 查询语句的 各条件的逻辑操作 AND(与) OR(或) start 开始返回条数 rows 返回多少条 hl 是否高亮 hl.fl 高亮field hl.snippets 不太清楚(反正是设置高亮3就可以了) hl.simple.pre 高亮前面的格式 hl.simple.post 高亮后面的格式 facet 是否启动统计 facet.field  统计field q 查询语句(类似SQL) 相关详细的操作还需lucene 的query 语法 sort 排序

十六、删除索引

post  "<delete><id>42</id></delete>"