最近在开新坑,用的 CI (codeigniter) 写后台, ES (elasticsearch) 当数据库。其实之前用 codeigniter-restserver 写 RESTful API 挺方便的,但是后来把数据库改成 ES 后就各种蛋疼,简单记一下遇到的问题。
JSON
注意需要 php 的 zlib 扩展,否则可能出现 Response 0 或者 JSON 数据显示不全,在 php.ini
里把 zlib.output_compression
改成 on
即可,参考 https://github.com/chriskacerguis/codeigniter-restserver/issues/295。
另外若是 Content-type 为 application/json
时,就必须要用 $this->input->raw_input_stream
才能获取到 JSON 数据。如果用到 multipart/form-data
或者 application/x-www-form-urlencoded
,要先对 POST 过来的数据 urldecode() 后再操作,否则很可能出现奇怪的问题……
Mappings
ElasticSearch 原生支持 RESTful API ,本来以为用起来会很方便,但实际上手会有很多问题……
最坑爹的是如果不指定 mapping
的话 properties
中 field
的 type 会是自动设定的,而且一旦设定后想改的话非常麻烦。
比如说有个 stop_time 的 field,若是不指定 mapping 而是直接给它一个值 2015-10-22 14:21:22
,你可能会以为 ES 会把它自动设定为与 MySQL 类似的 DateTime
类型,但其实 ES 会把它设定为 string
类型…… 没有任何报错,但是根本无法正常用 stop_time 进行时间比较。
试了不知道多少次后发现如果直接给 stop_time 的是 UTC
格式的时间,如 2015-11-15T00:12:11+0800
, ES 可以自动识别为时间类型 "stop_time":{"type":"date","format":"dateOptionalTime"}
。
所以要是不想每次都指定 mapping 的话,也可以用模板(templates),详见 indices-templates。
后来加了一个新字段,用来保存 UUID
,看了下 mapping 里是 "uuid":{"type":"string"}
,好像没什么问题。众所周知 UUID 一般由数字、字母和分隔符组成,类似 43b21f32-9e5b-11e5-b0b5-5254004b4491
,但坑爹的是因为 -
的存在,ES 默认会把它解析(analysed)成五段,即 43b21f32
9e5b
11e5
b0b5
5254004b4491
,所以用 term filter
直接查询 UUID 时返回结果为空。
最后 Google 到了类似的问题:Elastic Search Hyphen issue with term filter。
回答中提到了两个方法,其中第二个方法是用 text filter
,不过看了下最新的文档 text filter 已被弃用了,所以只好用第一种也是最普遍的做法—— 重建索引。
为什么不直接改 mapping 呢,看看官方博客怎么说的吧:
至于如何无缝重建索引,请参考官方文章:Changing Mapping with Zero Downtime
DSL
为了图方便用了官方出的 elasticsearch-php,用 Composer 安装后这样加载就好:
<?php
defined('BASEPATH') OR exit('No direct script access allowed');
require 'vendor/autoload.php';
use Elasticsearch\ClientBuilder;
class Test extends CI_Controller {
public function __construct()
{
parent::__construct();
$this->client = ClientBuilder::create()
->setHosts('127.0.0.1:9200')->build();
// 默认返回 JSON 格式数据
header('Content-type: application/json');
}
}
不过用这个包的话就必须用 ES 的 DSL 查询语句—— 这玩意可真不好用,而且写起来也麻烦。
官方文档上的例子是这样的:
$params = [
'index' => 'my_index',
'type' => 'my_type',
'body' => [
'query' => [
'match' => [
'testField' => 'abc'
]
]
]
];
其实 body
可以直接用 JSON 语句代替,比如:
$json = '"query" : {
"match" : {
"testField" : "abc"
}';
$params = [
'index' => 'my_index',
'type' => 'my_type',
'body' => $json
];
$response = $this->client->search($params);
echo json_encode($response);
这样写在 DSL 语句很长时可以节省大量时间。
值得吐槽的是官方的 DSL 文档写的好烂…… 想找类似于 MySQL 里的 count
功能时找了半天才在角落里发现:
Deprecated in 2.0.0-beta1.count does not provide any benefits over query_then_fetch with a size of 0.
于是设个 "size" : 0
就只显示总数了(注意 ES 默认的 size 为 10,即默认只返回 10 条数据)。
Bug
某天突然出现了这种错误,各种折腾无果:
`error: ReduceSearchPhaseException[Failed to execute phase [query], [reduce] ]; nested: ClassCastException[java.lang.Long cannot be cast to org.apache.lucene.util.BytesRef];
status: 503`
我的 ES 版本是 1.7.3,最后找到解决办法,貌似在 2.0 的最新版本中解决了这个 bug,清除缓存就行:curl -XPOST 'http://localhost:9200/my_index/_cache/clear'
composer 安装elasticsearch-php
Severity: Warning Message: require_once(/var/www/html/tvupd/website/application/libraries/Elasticsearch/Endpoints/AbstractEndpoint.php): failed to open stream: No such file or directory Filename: ip/Loader.php Line Number: 11各种坑.. 开发服务器上没有问题
到生产环境上,真是无语:
A PHP Error was encountered