文章目录
什么是OpenSearch
开放搜索(OpenSearch)是一款结构化数据搜索托管服务,为移动应用开发者和网站站长提供简单、高效、稳定、低成本和可扩展的搜索解决方案。
- 创建搜索应用
- 编辑您的应用结构
- 上传数据
- 从您的网站或应用程序提交搜索请求
简单、高效、低成本和可扩展。但要是后期用好,还需慢慢调试。
Open Search 和 Elastic Search对比
open search
优点:
- 支持用户上传数据或同步云数据,实时性有保障。(可以节省1-2台服务器)
- 应用结构、排序相关性自由定制,搜索服务更个性化。可以自定义粗排精排算法,但是LCU 和查询命中的文档、召回的文档、formula的复杂度、查询的复杂度等都有关系。
- 基于阿里巴巴在搜索领域的积累,提供查询分析功能,对用户查询词进行纠错、词权重分析、停用词过滤,让搜索服务更智能。可以自定义分词器,下拉提示等。可以很方便的设置召回结果的粗排精排,并且内置了一些对应的函数。
- 可视化的界面、丰富的模板,不用精通代码也能快速创建自己的搜索应用。
- 一张OpenSearch表可以支持多个rds及TDDL(mysql)来源表(如分库分表的场景),并且还有一些字段处理插件,这个挺适合咱们的博客场景

- 提供A/Btest功能,方便进行优化(快速迭代算法)

缺点 - open search和es的命中文档数差了一个数量级 通用分词 和es中ik_smark对比
- 子账号没有权限
- 分词不能使用or进行query
- 目前主辅表,仅支持 N:1 或 1:1 的关系,不支持 1:N(即多表数据关联关系中,多的一方只能是主表,且主表只能有1个)。可以进行表拆分或者合并进行应对。但是改动可能较大。
- 主辅表需通过应用表外键与附表主键进行数据关联,且表外键只能关联辅表主键。
- 最多只支持2层关联。
ElasticSearch
对比
OpenSearch 和 es 查询语句限制
- open search直接使用默认的粗排精排算法

- es和open search 都查询title 和body
LCU 使用情况
目前ask的数据测试query,并发25左右,单次查询均值在5ms以内,平均值LCU在0.3左右。
人工评估结果
es查询评估平均得分3.50,opensearch查询评估平均得分2.76
Open Search 的创建
一共有三种创建方式:
- 通过模板创建应用结构
- 通过上传文档创建应用结构
- 通过数据源创建应用结构
步骤:
1).添加表




2).手动修改创建的应用结构

3). 定义索引结构
1⃣️需放到 query子句中的字段,必须创建为索引(浮点型不支持创建为索引),分词方式详情请参见字段和分词类型。
需放到 filter子句,sort子句,及函数中涉及字段有明确标识,需设置为属性的字段必须创建为属性。
分词字段类型无法配置为属性,例如 TEXT,SHORT_TEXT等都不支持,只支持数值字段类型及不分词字段类型配置为属性,例如 int,int_array,float,float_array,double,double_array,literal,literal_array 等字段类型。

同步数据源
我们对Open Search的使用
API 分类

搜索方式
可以通过http(get、post)形式或Java、PHP sdk方式进行搜索与上传。
目前在用的产品
- 添加maven
com.aliyun.opensearch
aliyun-sdk-opensearch
3.2.0
- 部分代码
private static SearcherClient searcherClient=null; static { //创建并构造OpenSearch对象 OpenSearch openSearch = new OpenSearch(Constants.ACCESSKEY, Constants.SECRET, Constants.HOST); //创建OpenSearchClient对象,并以OpenSearch对象作为构造参数 OpenSearchClient serviceClient = new OpenSearchClient(openSearch); //创建SearcherClient对象,并以OpenSearchClient对象作为构造参数 searcherClient = new SearcherClient(serviceClient); } / * 配置信息 * @return */ public Config getConfig(List
appNames,int start,int hit,List
fields){ //定义Config对象,用于设定config子句参数,指定应用名,分页,数据返回格式等等 Config config = new Config(appNames); config.setStart(start); config.setHits(hit); //设置返回格式为fulljson格式 config.setSearchFormat(SearchFormat.JSON); // 设置搜索结果返回应用中哪些字段 config.setFetchFields(fields); return config; } / * 粗排精排 * @return */ public Rank getRank(int size){ if(size>500){ size=500; } // 设置精排文档 Rank rank=new Rank(); rank.setReRankSize(size); return rank; } / * @param jsonParam 传过来的参数 * { * "filter":"username=\"Joanna_or_zhouzhou\"",//按照某个字段过滤 * "reRankSize":"500",//参与精排的条数 * "fetchFields":"id,title,nickname",//获取的域/字段 * "pageSize":"20",//每页多少条 * "index":"nickname",//索引 * "page":"0",//第几页 注:0为第一页 * "sort":"-id", //-按照某个字段降序 + 按照某个字段增序 最好不要使用sort 耗资源 * "queryWord":"巴掌大的脚印" , //搜索词 * "summary":[ * { * "snippet":"1",//片段数量 * "field":"nickname",//指定的生效的字段。此字段必需为可分词的text类型的字段。 * "len":"50",//片段长度 * "ellipsis":"...",//片段链接符 * "element":"em"//飘红标签 * } * ] * } * @return */ public ResultVo dataDispose(com.alibaba.fastjson.JSONObject jsonParam){ ResultVo resultVo=new ResultVo(); com.alibaba.fastjson.JSONArray jsonArray=new com.alibaba.fastjson.JSONArray(); List
appNames=new ArrayList<>(); String appName=jsonParam.getString("appName"); if(StringUtils.isEmpty(appName)){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0001); resultVo.setMessage("app应用名为空"); resultVo.setData(jsonArray); return resultVo; }else{ appNames.add(appName); } if(jsonParam==null || jsonParam.size()<=0){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0002); resultVo.setMessage("app参数为空"); resultVo.setData(jsonArray); return resultVo; } //搜索词 String queryWord=jsonParam.getString("queryWord"); if(queryWord==null || queryWord.isEmpty()){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0003); resultVo.setMessage("queryWord为必填项不能为空"); resultVo.setData(jsonArray); return resultVo; }else{ queryWord=queryWord.replaceAll("\\\\"," ").replaceAll("\'"," "); } int start=0; int hit=20; //第几页 注:0为第一页 config=start:20, hit:20, format:xml String page=jsonParam.getString("page"); //每页多少条 String pageSize=jsonParam.getString("pageSize"); //opensearch start+hit<=5000,超过5000会直接报错无结果。 if(page!=null && NumberUtils.isDigits(page) && Integer.valueOf(page)>=0){ if(pageSize!=null && NumberUtils.isDigits(pageSize) && Integer.valueOf(pageSize)>0){ hit=Integer.valueOf(pageSize); } start=Integer.valueOf(page)*hit; if(start+hit>Constants.MAXSEARCHRESULT){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0004); resultVo.setMessage("查询总数不能超过5000"); resultVo.setData(jsonArray); return resultVo; } } List
fetchFields=new ArrayList<>(); //获取的域/字段 String fetchFieldStr=jsonParam.getString("fetchFields"); if(fetchFieldStr==null){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0005); resultVo.setMessage("获取结果域为必填项"); resultVo.setData(jsonArray); return resultVo; }else{ fetchFields.addAll(Arrays.asList(fetchFieldStr.split(","))); } Config config = getConfig(appNames,start,hit,fetchFields); SearchParams searchParams = new SearchParams(config); //参与精排的条数 String reRankSize=jsonParam.getString("reRankSize"); if(reRankSize!=null && NumberUtils.isDigits(reRankSize)){ searchParams.setRank(getRank(Integer.valueOf(reRankSize))); }else{ searchParams.setRank(getRank(200)); } //索引 String index=jsonParam.getString("index"); //-按照某个字段降序 + 按照某个字段增序 最好不要使用sort 耗资源 String queryStr=""; if(index==null){ resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0007); resultVo.setMessage("索引不能为空"); resultVo.setData(jsonArray); return resultVo; }else{ queryStr=index+":"+"'"+queryWord+"'"; } //按照某个字段过滤 String filter=jsonParam.getString("filter"); if(filter!=null){ if(filter.indexOf("=")>-1){ int eindex = filter.indexOf("="); String pre = filter.substring(0, eindex); String next = filter.substring(eindex + 1); queryStr+=" AND "+pre+":"+next; } // //这里针对CSDN_User 做处理 提高查询效率 // if("CSDN_User".equals(appName)){ // // }else{ // searchParams.setFilter(filter); // } } String sort=jsonParam.getString("sort"); if(sort!=null){ queryStr=queryStr+"&&sort="+sort; } log.info("querystr:"+queryStr); searchParams.setQuery(queryStr); SearchParamsBuilder paramsBuilder = SearchParamsBuilder.create(searchParams); try { JSONArray summary = jsonParam.getJSONArray("summary"); if(summary!=null && summary.size()>0){ for (int i=0;i
0){ JSONObject jsonObject = compute_cost.getJSONObject(0); Object lcu = jsonObject.get("value"); log.info("查询成功,消耗LCU:"+lcu); } }else{ resultVo.setData(jsonArray); resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE00010); Object errors = obj.get("errors"); if(errors!=null){ resultVo.setMessage(errors+""); }else{ resultVo.setMessage("查询失败"); } log.error("查询失败:"+errors); return resultVo; } } catch (OpenSearchException e) { resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0008); resultVo.setMessage(e.getMessage()); resultVo.setData(jsonArray); log.error(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0008+" : "+e.getMessage()); return resultVo; } catch (OpenSearchClientException e) { resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0009); resultVo.setMessage(e.getMessage()); resultVo.setData(jsonArray); log.error(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0009+" : "+e.getMessage()); return resultVo; } return resultVo; } public ResultVo searchData(com.alibaba.fastjson.JSONObject jsonParam) { ResultVo resultVo = dataDispose(jsonParam); return resultVo; }
遇到的问题
- 表关联问题
- 性能问题 (filter 字段建立索引)
- 子账号没有权限导入数据
- 目前redis中的数据不能进行关联同步
- query分词之后只能and进行搜索
引用:官方文档
备注:
- LCU是用来衡量搜索应用计算能力的单位,一个LCU代表搜索集群中10millicores的计算能力,计算资源估算方法:LCU个数=QPS*compute_cost,millicores是CPU资源的单位,即一个核的1/1000, compute_cost是单次查询计算消耗的LCU
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/199841.html原文链接:https://javaforall.net
