OpenSearch 講解


 

什么是OpenSearch

開放搜索(OpenSearch)是一款結構化數據搜索托管服務,為移動應用開發者和網站站長提供簡單、高效、穩定、低成本和可擴展的搜索解決方案。

OpenSearch基於阿里巴巴自主研發的大規模分布式搜索引擎平台,該平台承載了阿里巴巴全部主要搜索業務,包括淘寶、天貓、一淘、1688、ICBU、神馬搜索等業務。OpenSearch以平台服務化的形式,將專業搜索技術簡單化、低門檻化和低成本化,讓搜索引擎技術不再成為客戶的業務瓶頸,以低成本實現產品搜索功能並快速迭代。
使用OpenSearch搭建搜索服務,您只需:

  1. 創建搜索應用
  2. 編輯您的應用結構
  3. 上傳數據
  4. 從您的網站或應用程序提交搜索請求
    簡單、高效、低成本和可擴展。但要是后期用好,還需慢慢調試。

Open Search 和 Elastic Search對比

open search

優點:

  1. 支持用戶上傳數據或同步雲數據,實時性有保障。(可以節省1-2台服務器)

  2. 應用結構、排序相關性自由定制,搜索服務更個性化。可以自定義粗排精排算法,但是LCU 和查詢命中的文檔、召回的文檔、formula的復雜度、查詢的復雜度等都有關系。

  3. 基於阿里巴巴在搜索領域的積累,提供查詢分析功能,對用戶查詢詞進行糾錯、詞權重分析、停用詞過濾,讓搜索服務更智能。可以自定義分詞器,下拉提示等。可以很方便的設置召回結果的粗排精排,並且內置了一些對應的函數。

    下拉提示例子,連衣裙 這個query,可以通過如下方式查詢得到:
    中文前綴:連,連衣;
    全拼前綴:l, li, lian, lianyi, lianyiqun, …
    簡拼前綴:l, ly, lyq;
    漢字加拼音: 連yi, 連衣qun;
    並且下拉提示可以進行人工干預如推薦名單和黑名單。

  4. 可視化的界面、豐富的模板,不用精通代碼也能快速創建自己的搜索應用。

  5. 一張OpenSearch表可以支持多個rds及TDDL(mysql)來源表(如分庫分表的場景),並且還有一些字段處理插件,這個挺適合咱們的博客場景
    在這里插入圖片描述

  6. 提供A/Btest功能,方便進行優化(快速迭代算法)
    在這里插入圖片描述
    缺點

  7. open search和es的命中文檔數差了一個數量級 通用分詞 和es中ik_smark對比

  8. 子賬號沒有權限

  9. 分詞不能使用or進行query

  10. 目前主輔表,僅支持 N:1 或 1:1 的關系,不支持 1:N(即多表數據關聯關系中,多的一方只能是主表,且主表只能有1個)。可以進行表拆分或者合並進行應對。但是改動可能較大。

  11. 主輔表需通過應用表外鍵與附表主鍵進行數據關聯,且表外鍵只能關聯輔表主鍵。

  12. 最多只支持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直接使用默認的粗排精排算法
    在這里插入圖片描述
  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).添加表
    在這里插入圖片描述
    在這里插入圖片描述
    在這里插入圖片描述
    在這里插入圖片描述
    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 代碼

  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

參考:OpenSearch 講解


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM