马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
“10000条”问题(个人称谓)
- 症状: 在数据量不大的环境下,可能还会使用from + size的传统分页方式,但是数量受限,只能取前10000条的数据。
- 缘由:ES限值10000条,是ES团队挑选一个不大不小的数作为阈值,为了避免深度分页的策略。
- 调整:max_result_window 用于控制在搜索查询中可以检索到的最大文档数,是有符号int范例,最大可设置231 - 1,调大可以,但随着数据量(不是max_result_window 值)增长会有性能问题。
- 返回bool
- $params = [
- 'index' => 'performance_test',
- 'body' => [
- 'index' => [
- 'max_result_window' => 2147483647 //用于控制在搜索查询中可以检索到的最大文档数,有符号int类型,最大可设置2^31 - 1,但随着数据量增加会有性能问题。
- ]
- ]
- ];
- $response = $client->indices()->putSettings($params);
- dd($response->asBool());
复制代码 hits total值统计总数不精确解决方案(另一种“10000条”问题)
- 症状:调用search()方法,可能查询到的结果有20000条(count()方法统计得出),ES也返回有10000条数据匹配。
- {
- "took": 101,
- "timed_out": false,
- "_shards": {
- "total": 1,
- "successful": 1,
- "skipped": 0,
- "failed": 0
- },
- "hits": {
- "total": {
- "value": 10000, //这里的统计结果不准,解决的就是这里的问题
- "relation": "gte"
- },
- "max_score": 1.0342529,
- "hits": [
- ......
- }
- }
复制代码
- 缘由:ES团队挑选一个不大不小的数作为阈值,处于性能和业务思量,毕竟用户基本没耐心看数万条之后的数据。
- 解决:添加track_total_hits为true即可。
- $params = [
- 'index' => 'performance_test',
- 'body' => [
- 'query' => [
- 'match' => [
- 'content' => '的'
- ]
- ],
- 'track_total_hits' => true
- ]
- ];
- $response = $client->search($params);
- dd($response->asArray());
复制代码 大数据深度分页性能问题
- 极简概括:在大数据环境下,查询很多页后的数据,ES响应速度会变慢甚至崩溃。
- 问题由来:假设亿级数据,要查询某页(页数很大)之后的数据,ES底层就必要把前面很多页的数据都要过一遍才气定位到指定页,这是一个巨大的开销。以是大数据深度分页一般不推荐使用from + size的传统分页方式。也算是一类业界难题。
- 测试:花了3个小时向ES插入了1.09亿条中文数据,实测每页显示2条数据,翻页到2000万页,效果如下:
- 深度分页会把内存干爆,导致java程序OutOfMemoryError,ES进程被终止。
- [2024-07-27T09:01:39,206][INFO ][o.e.c.r.a.AllocationService] [localhost.localdomain] current.health="YELLOW" message="Cluster health status changed from [RED] to [YELLOW] (reason: [shards started [[performance_test][0]]])." previous.health="RED" reason="shards started [[performance_test][0]]"
- [2024-07-27T09:01:58,487][INFO ][o.e.m.j.JvmGcMonitorService] [localhost.localdomain] [gc][21] overhead, spent [331ms] collecting in the last [1.1s]
- [2024-07-27T09:02:00,495][INFO ][o.e.m.j.JvmGcMonitorService] [localhost.localdomain] [gc][23] overhead, spent [334ms] collecting in the last [1s]
- [2024-07-27T09:02:02,674][WARN ][o.e.m.j.JvmGcMonitorService] [localhost.localdomain] [gc][24] overhead, spent [2s] collecting in the last [2.1s]
- [2024-07-27T09:02:04,181][WARN ][o.e.m.j.JvmGcMonitorService] [localhost.localdomain] [gc][25] overhead, spent [1.2s] collecting in the last [1.5s]
- [2024-07-27T09:02:07,837][WARN ][o.e.m.j.JvmGcMonitorService] [localhost.localdomain] [gc][26] overhead, spent [3.5s] collecting in the last [1.8s]
- java.lang.OutOfMemoryError: Java heap space
- Dumping heap to data/java_pid20168.hprof ...
复制代码 索引与映射结构(下文要用)
- $params = [
- 'index' => 'performance_test',
- 'body' => [
- 'settings' => [
- 'analysis' => [
- 'analyzer' => [
- 'ik_analyzer' => [
- 'type' => 'ik_max_word',
- ],
- ],
- ],
- ],
- 'mappings' => [
- 'properties' => [
- 'id' => [
- 'type' => 'integer',
- ],
- 'content' => [
- 'type' => 'text',
- 'analyzer' => 'ik_analyzer',
- ],
- ],
- ],
- ],
- ];
复制代码 方案1(业务需求优化)
从业务需求源头限定深度分页。
例如某度搜索,深度分页也就50~76页是末了一页(甚至不让用户从小页码直接跳转大页码),某宝搜索最大100页。
这个信息自然无法全部汇集。它们的定位不是要汇聚更全的信息,而是找出最精准的(或者金主爸爸打钱)数据。
人为的也几乎没有耐心看完那么多页的词条。
可以说深度分页并非适合所有业务场景,而是要做权衡(说人话就是要和产品经理沟通以上文案)。
方案2 (Scroll API)
- 辅助理解:类比愚公移山。操尴尬刁难象太大,那就每次少量的处理,然后不停的分批迭代。这和分页的分批处理头脑相似,不过不用手动处理分页问题。
- 优点:答应处理超大规模的数据集。
- 缺点:不支持页跳转(第M页跳转N页),只支持递增页和递减页。scroll查询的相应数据是非实时的,如果遍历过程是,其它请求要返回的数据进行了写操纵,是查询不到最新的结果的。
- $params = [
- 'index' => 'performance_test',
- 'scroll' => '1m', //用于设置滚动上下文的存活时间,1m是一分钟,过了这个时间,scroll_id参数就失效了,有这个id,好比创建了一个专门的任务,让ES去处理这件事
- 'size' => 2,
- 'body' => [
- 'query' => [
- 'match' => [
- 'content' => '袁隆平' //要搜索整个文档中,包含袁隆平的
- ]
- ]
- ]
- ];
- $response = $client->search($params);
- dump($response->asArray()); //常规的处理
- $scroll_id = $response['_scroll_id'];
- while (true) {
- $response = $client->scroll([
- 'scroll_id' => $scroll_id,
- 'scroll' => '1m'
- ]);
- // 检查是否有数据返回
- if (empty($response['hits']['hits'])) {
- break;
- }
- // 处理数据,这里只做打印
- foreach ($response['hits']['hits'] as $hit) {
- dump($hit['_source']);
- }
- // 更新 scroll ID
- $scroll_id = $response['_scroll_id'];
- }
- // 清理Scroll请求,释放资源
- $client->clearScroll([
- 'scroll_id' => $scroll_id
- ]);
复制代码 方案3 (Search After 推荐方案)
- 辅助理解:上一页的末了一条数据,作为当前页的起始偏移量。操尴尬刁难象太大,那就每次少量的处理,然后不停的迭代,这和分页的分批处理头脑相似。
- 优点:ES针对深度分页的新解决方案,不会有太多的性能问题。
- 缺点:必须指定排序字段,否则起始偏移量参考系参数无法使用。
- $params = [
- 'index' => 'performance_test',
- 'body' => [
- 'query' => [
- 'match_all' => new stdClass()
- ],
- 'sort' => [
- ['id' => 'asc']
- ],
- 'size' => 2 //这个排序是必须的,不一定是id,但通常是id,排序是为了去上一条数据的时候定位不会错乱
- ]
- ];
- //发起第一次查询,获取第一页结果
- $response = $client->search($params);
- //模拟处理第一页的结果
- foreach ($response['hits']['hits'] as $hit) {
- //模拟处理
- dump($hit['_source']['content']);
- }
- /**
- * @function SearchAfter方法封装
- * @param $client object ElasticSearch对象
- * @param $params array search()所需要的参数
- * @param $response object|null search()方法查询出来的结果
- * @return object
- */
- function searchAfter($client, $params, $response) {
- if(is_object($response)) {
- $response = $response->asArray();
- }
- if(empty($response['hits']['hits'])) {
- return null;
- }
- $last_data = end($response['hits']['hits']);
- //在其余查询条件不变的情况下,只修改search_after值。
- $params['body']['search_after'] = $last_data['sort'];
- //然后再次查询
- $response = $client->search($params);
- return $response;
- }
- //模拟处理第二页的数据
- $response = searchAfter($client, $params, $response);
- dd($response->asArray());
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |