什么是OpenSearch

开放搜索(OpenSearch)是一款结构化数据搜索托管服务,为移动应用开发者和网站站长提供简单、高效、稳定、低成本和可扩展的搜索解决方案。

OpenSearch基于阿里巴巴自主研发的大规模分布式搜索引擎平台,该平台承载了阿里巴巴全部主要搜索业务,包括淘宝、天猫、一淘、1688、ICBU、神马搜索等业务。OpenSearch以平台服务化的形式,将专业搜索技术简单化、低门槛化和低成本化,让搜索引擎技术不再成为客户的业务瓶颈,以低成本实现产品搜索功能并快速迭代。

使用OpenSearch搭建搜索服务,您只需:


  1. 创建搜索应用
  2. 编辑您的应用结构
  3. 上传数据
  4. 从您的网站或应用程序提交搜索请求
    简单、高效、低成本和可扩展。但要是后期用好,还需慢慢调试。

Open Search 和 Elastic Search对比

open search

优点:



支持用户上传数据或同步云数据,实时性有保障。(可以节省1-2台服务器)



应用结构、排序相关性自由定制,搜索服务更个性化。可以自定义粗排精排算法,但是LCU 和查询命中的文档、召回的文档、formula的复杂度、查询的复杂度等都有关系。



基于阿里巴巴在搜索领域的积累,提供查询分析功能,对用户查询词进行纠错、词权重分析、停用词过滤,让搜索服务更智能。可以自定义分词器,下拉提示等。可以很方便的设置召回结果的粗排精排,并且内置了一些对应的函数。

下拉提示例子,连衣裙 这个query,可以通过如下方式查询得到:
中文前缀:连,连衣;
全拼前缀:l, li, lian, lianyi, lianyiqun, …
简拼前缀:l, ly, lyq;
汉字加拼音: 连yi, 连衣qun;
并且下拉提示可以进行人工干预如推荐名单和黑名单。



可视化的界面、丰富的模板,不用精通代码也能快速创建自己的搜索应用。



一张OpenSearch表可以支持多个rds及TDDL(mysql)来源表(如分库分表的场景),并且还有一些字段处理插件,这个挺适合咱们的博客场景
OpenSearch 讲解_搜索



提供A/Btest功能,方便进行优化(快速迭代算法)
OpenSearch 讲解_数据_02
缺点



open search和es的命中文档数差了一个数量级 通用分词 和es中ik_smark对比



子账号没有权限



分词不能使用or进行query



目前主辅表,仅支持 N:1 或 1:1 的关系,不支持 1:N(即多表数据关联关系中,多的一方只能是主表,且主表只能有1个)。可以进行表拆分或者合并进行应对。但是改动可能较大。



主辅表需通过应用表外键与附表主键进行数据关联,且表外键只能关联辅表主键。



最多只支持2层关联。

多表数据关联支持
表a->表b,表b->表c
表a->表d
不支持超过2层多表数据关联
表a->表b,表b->表c,表c->表d
不支持环状多表数据关联
表a->表b,表b->表a



ElasticSearch

1、在查询方面更加灵活

2、需要自己实现etl工作

对比

数据准备

问答数据:文档数257,250,存储容量为815M。

OpenSearch 和 es 查询语句限制


  1. open search直接使用默认的粗排精排算法
    OpenSearch 讲解_搜索_03
  2. 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).添加表
    OpenSearch 讲解_json_04
    OpenSearch 讲解_应用结构_05
    OpenSearch 讲解_搜索_06
    OpenSearch 讲解_搜索_07
    2).手动修改创建的应用结构
    OpenSearch 讲解_数据_08
    3). 定义索引结构
    1⃣️需放到 query子句中的字段,必须创建为索引(浮点型不支持创建为索引),分词方式详情请参见字段和分词类型。
    需放到 filter子句,sort子句,及函数中涉及字段有明确标识,需设置为属性的字段必须创建为属性。
    分词字段类型无法配置为属性,例如 TEXT,SHORT_TEXT等都不支持,只支持数值字段类型及不分词字段类型配置为属性,例如 int,int_array,float,float_array,double,double_array,literal,literal_array 等字段类型。
    OpenSearch 讲解_数据_09
    同步数据源
    OpenSearch 讲解_搜索_10

创建成功

OpenSearch 讲解_字段_11

数据上传

上面我们是以RDS为例,激活应用后会默认开始导入全量数据,可以在应用管理 - 基本配置 - 索引重建中看到具体进度。

我们对Open Search的使用

API 分类

OpenSearch 讲解_数据_12

搜索方式

可以通过http(get、post)形式或Java、PHP sdk方式进行搜索与上传。

目前在用的产品

APP中@ 操作

OpenSearch 讲解_字段_13

具体的Java 代码


  1. 添加maven

<dependency>
<groupId>com.aliyun.opensearch</groupId>
<artifactId>aliyun-sdk-opensearch</artifactId>
<version>3.2.0</version>
</dependency>

  • 1
  • 2
  • 3
  • 4
  • 5


  1. 部分代码

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<String> appNames,int start,int hit,List<String> 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<String> 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<String> 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<summary.size();i++){
com.alibaba.fastjson.JSONObject jsonObject = summary.getJSONObject(i);
String field = jsonObject.getString("field");
int snippet=1;
String snippetStr = jsonObject.getString("snippet");
if(snippetStr!=null && NumberUtils.isDigits(snippetStr)){
snippet=Integer.valueOf(snippetStr);
}
int len=50;
String lenStr = jsonObject.getString("len");
if(lenStr!=null && NumberUtils.isDigits(lenStr)){
len=Integer.valueOf(lenStr);
}
String ellipsis = jsonObject.getString("ellipsis");
String element = jsonObject.getString("element");
paramsBuilder.addSummary(field,len,element,ellipsis,snippet);
}

}
}catch (Exception e){
resultVo.setCode(Constants.ERRORHTTPCODE+Constants.PRODUCTCODE+Constants.CODE0006);
resultVo.setMessage(e.getMessage());
resultVo.setData(jsonArray);
return resultVo;
}
try {
SearchResult searchResult = searcherClient.execute(paramsBuilder);
String result = searchResult.getResult();
JSONObject obj = new JSONObject(result);
Object status = obj.get("status");
if(Constants.OK.equals(status+"")){
JSONObject map = (JSONObject)obj.get("result");
Object items = map.get("items");
resultVo.setData(JSON.parseArray(items.toString()));
resultVo.setCode("200");
resultVo.setMessage("查询成功");
com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray compute_cost = (com.aliyun.opensearch.sdk.dependencies.org.json.JSONArray)map.get("compute_cost");
if(compute_cost!=null && compute_cost.length()>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;
}

  • 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

遇到的问题


  1. 表关联问题
  2. 性能问题 (filter 字段建立索引)
  3. 子账号没有权限导入数据
  4. 目前redis中的数据不能进行关联同步
  5. query分词之后只能and进行搜索

引用:官方文档

备注:


  1. LCU是用来衡量搜索应用计算能力的单位,一个LCU代表搜索集群中10millicores的计算能力,计算资源估算方法:LCU个数=QPS*compute_cost,millicores是CPU资源的单位,即一个核的1/1000, compute_cost是单次查询计算消耗的LCU