什么是OpenSearch
開放搜索(OpenSearch)是一款結構化數據搜索托管服務,為移動應用開發者和網站站長提供簡單、高效、穩定、低成本和可擴展的搜索解決方案。
OpenSearch基於阿里巴巴自主研發的大規模分布式搜索引擎平台,該平台承載了阿里巴巴全部主要搜索業務,包括淘寶、天貓、一淘、1688、ICBU、神馬搜索等業務。OpenSearch以平台服務化的形式,將專業搜索技術簡單化、低門檻化和低成本化,讓搜索引擎技術不再成為客戶的業務瓶頸,以低成本實現產品搜索功能並快速迭代。
使用OpenSearch搭建搜索服務,您只需:
- 創建搜索應用
- 編輯您的應用結構
- 上傳數據
- 從您的網站或應用程序提交搜索請求
簡單、高效、低成本和可擴展。但要是后期用好,還需慢慢調試。
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)來源表(如分庫分表的場景),並且還有一些字段處理插件,這個挺適合咱們的博客場景
-
提供A/Btest功能,方便進行優化(快速迭代算法)
缺點 -
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 查詢語句限制
- 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 等字段類型。
同步數據源
創建成功
數據上傳
上面我們是以RDS為例,激活應用后會默認開始導入全量數據,可以在應用管理 - 基本配置 - 索引重建中看到具體進度。
我們對Open Search的使用
API 分類
搜索方式
可以通過http(get、post)形式或Java、PHP sdk方式進行搜索與上傳。
目前在用的產品
APP中@ 操作
具體的Java 代碼
- 添加maven
<dependency>
<groupId>com.aliyun.opensearch</groupId>
<artifactId>aliyun-sdk-opensearch</artifactId>
<version>3.2.0</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 部分代碼
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
遇到的問題
- 表關聯問題
- 性能問題 (filter 字段建立索引)
- 子賬號沒有權限導入數據
- 目前redis中的數據不能進行關聯同步
- query分詞之后只能and進行搜索
引用:官方文檔
備注:
- LCU是用來衡量搜索應用計算能力的單位,一個LCU代表搜索集群中10millicores的計算能力,計算資源估算方法:LCU個數=QPS*compute_cost,millicores是CPU資源的單位,即一個核的1/1000, compute_cost是單次查詢計算消耗的LCU