PHP中使用ElasticSearch

时间:2021-07-16 09:31:12

在es中,使用组合条件查询是其作为搜索引擎检索数据的一个强大之处,在前几篇中,简单演示了es的查询语法,但基本的增删改查功能并不能很好的满足复杂的查询场景,比如说我们期望像mysql那样做到拼接复杂的条件进行查询该如何做呢?es中有一种语法叫bool,通过在bool里面拼接es特定的语法可以做到大部分场景下复杂条件的拼接查询,也叫复合查询

首先简单介绍es中常用的组合查询用到的关键词,

filter:过滤,不参与打分
must:如果有多个条件,这些条件都必须满足 and与
should:如果有多个条件,满足一个或多个即可 or或
must_not:和must相反,必须都不满足条件才可以匹配到 !非

1

2

3

4

5

6

7

{

    "bool": {

        "must": [],--必须满足的条件--and 

        "should": [],--可以满足也可以不满足的条件--or 

        "must_not": []--不能满足的条件--not

    }

}

发生 描述
must
该条款(查询)必须出现在匹配的文件,并将有助于得分。

filter
子句(查询)必须出现在匹配的文档中。然而不像 must查询的分数将被忽略。Filter子句在过滤器上下文中执行,这意味着评分被忽略,子句被考虑用于高速缓存。

should
子句(查询)应该出现在匹配的文档中。如果 bool查询位于查询上下文中并且具有mustor filter子句,则bool即使没有should查询匹配,文档也将匹配该查询 。在这种情况下,这些条款仅用于影响分数。如果bool查询是过滤器上下文 或者两者都不存在,must或者filter至少有一个should查询必须与文档相匹配才能与bool查询匹配。这种行为可以通过设置minimum_should_match参数来显式控制 。

must_not
子句(查询)不能出现在匹配的文档中。子句在过滤器上下文中执行,意味着评分被忽略,子句被考虑用于高速缓存。因为计分被忽略,0所有文件的分数被返回。

网上很多关于ES的例子都过时了,版本很久,这篇文章的测试环境是ES6.5

通过composer 安装

1

composer require 'elasticsearch/elasticsearch'

在代码中引入

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

require 'vendor/autoload.php';

use Elasticsearch\ClientBuilder;

// 配置

$client = ClientBuilder::create()->setHosts(['172.16.55.53'])->build();

// 配置

$client = ClientBuilder::create()->setHosts([

    [

        'host' => 'xxx',

        'port' => '9200',

        'scheme' => 'http',

        'user' => 'xxx',

        'pass' => 'xxxx'

    ],

])->build();

下面循序渐进完成一个简单的添加和搜索的功能。

首先要新建一个 index:

index 对应关系型数据(以下简称MySQL)里面的数据库,而不是对应MySQL里面的索引,这点要清楚

1

2

3

4

5

6

7

8

9

10

$params = [

    'index' => 'myindex', #index的名字不能是大写和下划线开头

    'body' => [

        'settings' => [

            'number_of_shards' => 2,

            'number_of_replicas' => 0

        ]

    ]

];

$client->indices()->create($params);

在MySQL里面,光有了数据库还不行,还需要建立表,ES也是一样的,ES中的type对应MySQL里面的表。

注意:ES6以前,一个index有多个type,就像MySQL中一个数据库有多个表一样自然,但是ES6以后,每个index只允许一个type,在往以后的版本中很可能会取消type。

type不是单独定义的,而是和字段一起定义

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

$params = [

    'index' => 'myindex',

    'type' => 'mytype',

    'body' => [

        'mytype' => [

            '_source' => [

                'enabled' => true

            ],

            'properties' => [

                'id' => [

                    'type' => 'integer'

                ],

                'first_name' => [

                    'type' => 'text',

                    'analyzer' => 'ik_max_word'

                ],

                'last_name' => [

                    'type' => 'text',

                    'analyzer' => 'ik_max_word'

                ],

                'age' => [

                    'type' => 'integer'

                ]

            ]

        ]

    ]

];

$client->indices()->putMapping($params);

在定义字段的时候,可以看出每个字段可以定义单独的类型,在first_name中还自定义了 分词器 ik,

这个分词器是一个插件,需要单独安装的,参考另一篇文章:ElasticSearch基本尝试

现在 数据库和表都有了,可以往里面插入数据了

概念:这里的 数据 在ES中叫 文档

1

2

3

4

5

6

7

8

9

10

11

$params = [

    'index' => 'myindex',

    'type' => 'mytype',

    //'id' => 1, #可以手动指定id,也可以不指定随机生成

    'body' => [

        'first_name' => '张',

        'last_name' => '三',

        'age' => 35

    ]

];

$client->index($params);

多插入一点数据,然后来看看怎么把数据取出来:

通过id取出单条数据:

插曲:如果你之前添加文档的时候没有传入id,ES会随机生成一个id,这个时候怎么通过id查?id是多少都不知道啊。

所以这个插入一个简单的搜索,最简单的,一个搜索条件都不要,返回所有index下所有文档:

1

$data $client->search();

现在可以去找一找id了,不过你会发现id可能长这样:zU65WWgBVD80YaV8iVMk,不要惊讶,这是ES随机生成的。

现在可以通过id查找指定文档了:

1

2

3

4

5

6

$params = [

    'index' => 'myindex',

    'type' => 'mytype',

    'id' =>'zU65WWgBVD80YaV8iVMk'

];

$data $client->get($params);

最后一个稍微麻烦点的功能:

注意:这个例子我不打算在此详细解释,看不懂没关系,这篇文章主要的目的是基本用法,并没有涉及到ES的精髓地方,

ES精髓的地方就在于搜索,后面的文章我会继续深入分析

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

$query = [

    'query' => [

        'bool' => [

            'must' => [

                'match' => [

                    'first_name' => '张',

                ]

            ],

            'filter' => [

                'range' => [

                    'age' => ['gt' => 76]

                ]

            ]

        ]

    ]

];

$params = [

    'index' => 'myindex',

//  'index' => 'm*', #index 和 type 是可以模糊匹配的,甚至这两个参数都是可选的

    'type' => 'mytype',

    '_source' => ['first_name','age'], // 请求指定的字段

    'body' => array_merge([

        'from' => 0,

        'size' => 5

    ],$query)

];

$data $this->EsClient->search($params);

上面的是一个简单的使用流程,但是不够完整,只讲了添加文档,没有说怎么删除文档,

下面我贴出完整的测试代码,基于Laravel环境,当然环境只影响运行,不影响理解,包含基本的常用操作:

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

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

use Elasticsearch\ClientBuilder;

use Elasticsearch\Common\Exceptions\Missing404Exception;

use services\CommonService;

use yii\base\UserException;

use yii\db\Exception;

class BaseEs

{

    /**

     * @var $instance Es类实例

     */

    private static $instance;

    /**

     * @var $debug 设置debug

     */

    protected $debug;

    /**

     * 客户端

     * @var \Elasticsearch\Client

     */

    private $client;

    /**

     * @var 索引名称

     */

    protected $index;

    /**

     * @var 索引别名

     */

    public $alias;

    protected $retries = 2;

    // 外网

    private $config = [];

    // 内网

    private $lanConfig = [];

    /**

     * 查询参数

     * @var array $queryParams

     */

    protected $queryParams = [];

    private function __construct()

    {

        if (DEBUG) {

            $this->config = [

                'xxx:9200'

            ];

            $this->lanConfig = [

                'xxx:9200'

            ];

        else {

            $this->config = [

                [

                    'host' => 'xxx',

                    'port' => '9200',

                    'scheme' => 'http',

                    'user' => 'xxx',

                    'pass' => 'xxx'

                ],

            ];

            $this->lanConfig = [

                [

                    'host' => 'xxx',

                    'port' => '9200',

                    'scheme' => 'http',

                    'user' => 'xxx',

                    'pass' => 'xxx'

                ],

            ];

        }

        try {

            $ip = CommonService::getIp();

            if (strpos($ip'222.222') !== false) {

                $config $this->lanConfig;

            else {

                $config $this->config;

            }

            if (!$this->client instanceof Elasticsearch\Client) {

                $this->client = ClientBuilder::create()

                    ->setHosts($config)

                    ->setRetries($this->retries)

                    ->build();

            }

        catch (\Exception $e) {

            throw new Exception('Es conn failed');

        }

    }

    public function select($params '')

    {

        $this->queryParams['select'] = str_replace(' ''', trim($params" ,\t\r\n"));

        return $this;

    }

    public function from($number = 0)

    {

        $this->queryParams['from'] = $number ? ($number-1)*10 : 0;

        return $this;

    }

    public function size($number = 10)

    {

        $this->queryParams['size'] = $number;

        return $this;

    }

    public function equal($params = [])

    {

        $this->queryParams['equal'][] = $params;

        return $this;

    }

    public function in($params = [])

    {

        $this->queryParams['in'][] = $params;

        return $this;

    }

    public function like($params = [])

    {

        $this->queryParams['like'][] = $params;

        return $this;

    }

    public function orlike($params = [])

    {

        $this->queryParams['orlike'][] = $params;

        return $this;

    }

    public function order($params = [])

    {

        $this->queryParams['order'] = $params;

        return $this;

    }

    /**

     * 根据条件删除文档

     *

     * @param array $params

     * @return array

     */

    public function deleteDoc($params = [])

    {

        return $this->client->deleteByQuery($params);

    }

    /**

     * 根据id删除文档

     *

     * @param int $id

     * @return array

     */

    public function deleteDocById($id = 0)

    {

        $params = [

            'index' => $this->index,

            'id' => $id

        ];

        $response $this->client->delete($params);

        return $response;

    }

    /**

     * 组装查询条件

     *

     * @param array $params

     * @return $this

     */

    private function parseQueryParams()

    {

        $queryParams = [

            'index' => $this->index,

            'body' => [

                'query' => [

                    'bool' => [

                        'must' => [],

                        'filter' => []

                    ]

                ]

            ],

            'from' => $this->queryParams['from'] ?? 0,

            'size' => $this->queryParams['size'] ?? 10

        ];

        $filter $must = [];

        if (!empty($this->queryParams['select'])) {

            $queryParams['_source'] = explode(','$this->queryParams['select']);

        }

        if (!empty($this->queryParams['equal'])) {

            foreach ($this->queryParams['equal'as $key => $row) {

                foreach ($row as $filed => $value) {

                    $filter[] = [

                        'term' => [$filed => $value]

                    ];

                }

            }

        }

        if (!empty($this->queryParams['in'])) {

            foreach ($this->queryParams['in'as $key => $row) {

                foreach ($row as $filed => $value) {

                    $filter[] = [

                        'terms' => [$filed => array_values(array_unique(array_filter($value)))]

                    ];

                }

            }

        }

        if (!empty($this->queryParams['like'])) {

            foreach ($this->queryParams['like'as $key => $row) {

                foreach ($row as $filed => $value) {

                    /*$must[] = [

                        'wildcard' => [$filed => '*'. $value. '*']

                    ];*/

                    $must[] = [

//                        'match' => [$filed => $value]

                        'match_phrase' => [$filed => $value]

                    ];

                }

            }

            $queryParams['body']['query']['bool']['must'] = $must;

        }

        if (!empty($this->queryParams['orlike'])) {

            foreach ($this->queryParams['orlike'as $key => $row) {

                foreach ($row as $filed => $value) {

                    $orlike[] = [

                        'match_phrase' => [$filed => $value]

                    ];

                }

            }

            $queryParams['body']['query']['bool']['must']['bool']['should'] = $orlike;

        }

        if (!empty($this->queryParams['order'])) {

            $queryParams['body']['sort'] = [

                key($this->queryParams['order']) => [

                     'order' => current($this->queryParams['order'])

                ]

            ];

        }

        $queryParams['body']['query']['bool']['filter'] = $filter;

        $this->queryParams = $queryParams;

        return $this;

    }

    /**

     * @param bool $isTotal isTotal=true时, 返回总数

     * @return array|string

     */

    public function query($isTotal = false)

    {

        try {

            $this->parseQueryParams();

            if ($this->debug) {

                return \GuzzleHttp\json_encode($this->queryParams);

            }

            if ($isTotal === true) {

                unset(

                    $this->queryParams['from'],

                    $this->queryParams['size'],

                    $this->queryParams['_source']

                );

                $count $this->client->count($this->queryParams);

                return (int)$count['count'];

            }

            if (!empty($this->queryParams)) {

                $result $this->client->search($this->queryParams);

                if (!empty($result['hits']['hits'])) {

                    $return = [];

                    foreach ($result['hits']['hits'as $row) {

                        $return[] = $row['_source'];

                    }

                    return $return;

                else {

                    return [];

                }

            }

        catch (\Exception $e) {

            $msg $e->getMessage();

            $msg '服务器开小差了~';

            throw new Exception($msg);

        }

    }

    /**

     * 创建文档

     *

     * @param array $body

     * @return array|bool

     */

    public function createDocument($body = [], $id = null)

    {

        try {

            if (!empty($body)) {

                $params = [

                    'index' => $this->index,

                    'body' => $body

                ];

                if ($id) {

                    $params['id'] = $id;

                }

                $column $params['body'];

                $indexMappings $this->mappings();

                $diff array_diff(array_keys($column), array_keys($indexMappings));

                if (!empty($diff)) {

                    throw new Exception('different from the default mappings'$diff);

                }

                return $this->client->index($params);

            }

        catch (\Exception $e) {

            throw $e;

        }

    }

    public function getIndex()

    {

        return $this->index;

    }

    /**

     * @return BaseEs|Es类实例

     * @throws Exception

     */

    public static function setEs($index '')

    {

        $class = get_called_class();

        if (!self::$instance || !self::$instance instanceof $class) {

            self::$instance new static();

        }

        if ($index) {

            self::$instance->index = $index;

        }

        return self::$instance;

    }

    protected function settings()

    {

        return [

            'number_of_shards' => 1,

            'number_of_replicas' => 0

        ];

    }

    /**

     * @return bool

     * @throws Exception

     */

    public function createIndex()

    {

        try {

            $params = [

                'index' => $this->index

            ];

            $check $this->indices()->exists($params);

            if ($check) {

                throw new Exception('index: ' $this->index . ' already exists');

            }

            $params = [

                'index' => $this->index,

                'body' => [

                    'settings' => $this->settings(),

                    'mappings' => [

                        '_source' => [

                            'enabled' => true,

                        ],

                        'properties' => $this->mappings()

                    ]

                ]

            ];

            $result $this->indices()->create($params);

            return $result['acknowledged'] === true;

        catch (\Exception $e) {

            throw new Exception($e->getMessage());

        }

    }

    /**

     * 删除索引

     *

     * @return bool

     * @throws Missing404Exception

     */

    public function deleteIndex()

    {

        try {

            $params = [

                'index' => $this->index

            ];

            $check $this->indices()->exists($params);

            if (!$check) {

                return true;

            }

            $result $this->indices()->delete([

                'index' => $this->index

            ]);

            return $result['acknowledged'] === true;

        catch (Missing404Exception $e) {

            throw new Missing404Exception('no such index ' $this->index);

        }

    }

    /**

     * 批量写入

     * type 1.增加/修改 2.删除

     * $data = [

     *      ['id' => 1, 'name' => 'llf', 'age' => 30],

     *      ['id' => 1, 'name' => 'llf', 'age' => 30],

     *      ['id' => 1, 'name' => 'llf', 'age' => 30],

     *      ['id' => 1, 'name' => 'llf', 'age' => 30],

     * ];

     * @param array $data

     */

    public function doBulkDocument($type = 1,$data = [])

    {

        try {

            $params = ['body' => []];

            if($type == 1){

                foreach ($data as $key => $row) {

                    $params['body'][] = [

                        'index' => [

                            '_index' => $this->index,

                            '_id' => $row['info_id'].'-'.$row['info_type']

                        ]

                    ];

                    $params['body'][] = $row;

                    if (($key+1)%10000 == 0) {

                        $this->client->bulk($params);

                        $params = ['body' => []];

                    }

                }

            }else{

                foreach ($data as $key => $row) {

                    $params['body'][] = [

                        'delete' => [

                            '_index' => $this->index,

                            '_id' => $row['info_id'].'-'.$row['info_type']

                        ]

                    ];

                }

            }

            if (!empty($params['body'])) {

                $this->client->bulk($params);

                return true;

            }

        catch (\Exception $e) {

            throw new Exception($e->getMessage());

        }

    }

    /**

     * @return array @todo

     */

    public function updateIndex()

    {

        $putParams = [

            'index' => $this->index,

            //'type' => '_doc',

            'body' => [

                'properties' => $this->mappings()

            ]

        ];

        return $this->indices()->putMapping($putParams);

    }

    /**

     * 方便调试, 直接返回拼接的query

     * @return $this

     */

    public function setDebug()

    {

        $this->debug = true;

        return $this;

    }

    private function indices()

    {

        return $this->client->indices();

    }

    public function analyze($text '')

    {

        $params = [

            'index' => $this->index,

            'body' => [

                'analyzer' => 'ik_smart',

                'text' => $text

            ]

        ];

        return $this->indices()->analyze($params);

    }

    public function update($id = 0, $updateParams = [])

    {

        $params = [

            'index' => $this->index,

            'id' => $id,

            'body' => [

                'doc' => $updateParams

            ]

        ];

        return $this->client->update($params);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值