ElasticSearch實戰-編碼實踐

哥不是小蘿莉發表於2015-08-11

1.概述

  前面在《ElasticSearch實戰-入門》中給大家分享如何搭建這樣一個叢集,在完成叢集的搭建後,今天給大家分享如何實現對應的業務功能模組,下面是今天的分享內容,目錄如下所示:

  • 編碼實踐
  • 效果預覽
  • 總結

2.編碼實踐

  由於 ES 叢集支援 Restful 介面,我們可以直接通過 Java 來呼叫 Restful 介面來查詢我們需要的資料結果,並將查詢到的結果在在我們的業務介面視覺化出來。我們知道在 ES 叢集的 Web 管理介面有這樣一個入口,如下圖所示:

  我們可以在此介面的入口中拼接 JSON 字串來查詢我們想要的結果,下面,我們通過 Java 的 API 去呼叫 Restful 介面來查詢我們想要的結果。

2.1 字串拼接實現

  接著,我們去實現要查詢的核心程式碼,具體內容實現如下所示:

public String buildQueryString(Map<String, Object> param) throws ParseException {
        SimpleDateFormat dfs = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        StringBuilder builder = new StringBuilder("{\"query\":{\"bool\":{\"must\":[");
        if (param.get("msgType") != null) {
            Integer msgType = (int) param.get("msgType") == 0 ? 2 : 1;
            builder.append("{\"term\":{\"msg_type\":").append(msgType).append("}}");
        }if (param.get("start") != null && param.get("end") != null) {
            String start = String.valueOf(dfs.parse(param.get("start").toString()).getTime()).substring(0, 10);
            String end = String.valueOf(dfs.parse(param.get("end").toString()).getTime()).substring(0, 10);
            builder.append(",{\"range\":{\"itime\":{\"from\":" + start + ",\"to\":" + end + "}}}");
        }
        if (param.get("receiverValue") != null) {
            builder.append(",{\"wildcard\":{\"receiver_value\":\"*").append(param.get("receiverValue")).append("*\"}}");
        }
        builder.append("],\"must_not\":[],\"should\":[]}}");
        builder.append(",\"sort\":[{\"itime\":\"desc\"}],\"facets\":{}");
        builder.append(",\"from\": ").append(param.get("startIndex")).append(",\"size\": ").append(param.get("pageSize")).append("}");
        LOG.info("API Query -> " + builder.toString());
        return builder.toString();
    }

2.2 查詢實現核心程式碼

  接著是實現查詢的核心程式碼,具體內容實現如下所示:

public SerachResponse<ApiSent> querySent(Map<String, Object> param) {
        SerachResponse<ApiSent> search_result = null;
        try {
            long time = System.currentTimeMillis();
            ResponseWrapper wrapper = httpUtils.sendJson(configService.loadConfig(Configure.API_SENT), buildQueryString(param));
            if (wrapper.responseCode == HttpStatus.SC_OK) {
                search_result = _gson.fromJson(wrapper.responseContent, new TypeToken<SerachResponse<ApiSent>>() {
                }.getType());
                LOG.info(String.format("API query ES spent time=%sms", (System.currentTimeMillis() - time)));
                return search_result;
            } else {
                LOG.info(String.format("API query ES spent time=%sms", (System.currentTimeMillis() - time)));
                LOG.error(String.format("api sent request es server response not 200,response=%s,exception=%s", wrapper.error, wrapper.exceptionString));
            }
        } catch (Exception ex) {
            LOG.error(String.format("parsed es sent data exception.", ex));
        }
        return search_result;

    }
  • Configure類
public class Configure {
    public static final String API_SENT = "API_SENT";
}

2.3 DAO層獲取 ES 叢集的連線資訊

public class ConfigService {

    private static Log logger = LogFactory.getLog(ConfigService.class);

    @Autowired
    private ConfigDao configDao;

    @Cacheable("sysConfigCache")
    public String loadConfig(String type) {
        String value = configDao.getConfig(type);
        logger.info(String.format("Load Config,type=%s,value=%s", type, value));
        return value;
    }
}
  • ConfigDao介面
public interface ConfigDao {
    
    String getConfig(String type);

}

  其對應的實現內容如下所示:

<select id="getConfig" parameterType="String" resultType="String">
   select value from t_system_config where type=#{type}
</select>

  DB庫儲存的 ES 連線資訊,如下圖所示:

2.4 HTTP 介面的實現程式碼

  關於 HttpUtils 的程式碼實現較為簡單,這裡直接附上程式碼的實現內容,如下所示:

  • IHttpUtils 介面
public interface IHttpUtils {
    public ResponseWrapper sendJson(String url, String content);
}
  • HttpUtils 類實現介面
public class HttpUtils implements IHttpUtils {

    private static Logger LOG = Logger.getLogger(HttpUtils.class.getName());
    protected static Gson _gson = new Gson();

    protected void initSSL() {
        try {
            TrustManager[] tmCerts = new javax.net.ssl.TrustManager[1];
            tmCerts[0] = new SimpleTrustManager();
            javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext.getInstance("SSL");
            sc.init(null, tmCerts, null);
            javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
            HostnameVerifier hv = new SimpleHostnameVerifier();
            HttpsURLConnection.setDefaultHostnameVerifier(hv);
        } catch (Exception e) {
            LOG.error("init SSL exception.", e);
        }
    }

    @Override
    public ResponseWrapper sendJson(String url, String content) {
        return sendJson(url, content, null);
    }

    @Override
    public ResponseWrapper sendJson(String url, String content, String authCode) {
        return sendRequest(url, content, METHOD_POST, authCode, CONTENT_TYPE_JSON);
    }

public ResponseWrapper sendRequest(String url, String content, String method, String authCode,
            String contentType) {
        LOG.info("Send request to - " + url + ", with content - " + content);
        HttpURLConnection conn = null;
        OutputStream out = null;
        StringBuffer sb = new StringBuffer();
        cn.jpush.utils.ResponseWrapper wrapper = new ResponseWrapper();

        try {
            if (StringUtils.isSSL(url)) {
                initSSL();
            }

            if (METHOD_GET.equals(method)) {
                if (!Strings.isNullOrEmpty(content)) url += "?" + content;
            }
            URL aUrl = new URL(url);
            wrapper.address = aUrl.getHost();

            conn = (HttpURLConnection) aUrl.openConnection();
            conn.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);
            conn.setReadTimeout(DEFAULT_SOCKET_TIMEOUT);
            conn.setUseCaches(false);
            conn.setRequestMethod(method);
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Accept-Charset", CHARSET);
            conn.setRequestProperty("Charset", CHARSET);
            conn.setRequestProperty("Authorization", authCode);
            conn.setRequestProperty("Send-Source", "portal");
            conn.setRequestProperty("Content-Type", contentType);

            if (METHOD_POST.equals(method)) {
                conn.setDoOutput(true);
                byte[] data = content.getBytes(CHARSET);
                conn.setRequestProperty("Content-Length", String.valueOf(data.length));
                out = conn.getOutputStream();
                out.write(data);
                out.flush();
            } else {
                conn.setDoOutput(false);
            }
            int status = conn.getResponseCode();
            InputStream in = null;
            if (status == 200) {
                in = conn.getInputStream();
            } else {
                in = conn.getErrorStream();
            }
            InputStreamReader reader = new InputStreamReader(in, CHARSET);
            char[] buff = new char[1024];
            int len;
            while ((len = reader.read(buff)) > 0) {
                sb.append(buff, 0, len);
            }

            String responseContent = sb.toString();
            wrapper.responseCode = status;
            wrapper.responseContent = responseContent;

            String quota = conn.getHeaderField(RATE_LIMIT_QUOTA);
            String remaining = conn.getHeaderField(RATE_LIMIT_Remaining);
            String reset = conn.getHeaderField(RATE_LIMIT_Reset);
            wrapper.setRateLimit(quota, remaining, reset);

            if (status == 200) {
                LOG.debug("Succeed to get response - 200 OK");
                LOG.debug("Response Content - " + responseContent);

            } else if (status > 200 && status < 400) {
                LOG.warn("Normal response but unexpected - responseCode:" + status
                        + ", responseContent:" + responseContent);

            } else {
                LOG.warn("Got error response - responseCode:" + status + ", responseContent:"
                        + responseContent);

                switch (status) {
                    case 400:
                        LOG.error("Your request params is invalid. Please check them according to error message.");
                        wrapper.setErrorObject();
                        break;
                    case 401:
                        LOG.error("Authentication failed! Please check authentication params according to docs.");
                        wrapper.setErrorObject();
                        break;
                    case 403:
                        LOG.error("Request is forbidden! Maybe your is listed in blacklist?");
                        wrapper.setErrorObject();
                        break;
                    case 410:
                        LOG.error("Request resource is no longer in service. Please according to notice on official website.");
                        wrapper.setErrorObject();
                    case 429:
                        LOG.error("Too many requests! Please review your request quota.");
                        wrapper.setErrorObject();
                        break;
                    case 500:
                    case 502:
                    case 503:
                    case 504:
                        LOG.error("Seems encountered server error. Maybe is in maintenance? Please retry later.");
                        break;
                    default:
                        LOG.error("Unexpected response.");
                }

            }

        } catch (SocketTimeoutException e) {
            if (e.getMessage().contains(KEYWORDS_READ_TIMED_OUT)) {
                LOG.error(KEYWORDS_READ_TIMED_OUT, e);
            }
            wrapper.exceptionString = e.getMessage();

        } catch (IOException e) {
            LOG.error(KEYWORDS_CONNECT_TIMED_OUT, e);
            wrapper.exceptionString = e.getMessage();

        } finally {
            if (null != out) {
                try {
                    out.close();
                } catch (IOException e) {
                    LOG.error("Failed to close stream.", e);
                }
            }
            if (null != conn) {
                conn.disconnect();
            }
        }
        LOG.info(String.format("Send Response to - %s, Response Wrapper - %s", url, wrapper));
        return wrapper;
    }
}

3.截圖預覽

  下面給大家附上一張業務介面視覺化的資料結果預覽圖,如下圖所示:

  上圖為我傳送的測試資料,通過收集模組將我傳送的資料收集並儲存到 ES 叢集,通過介面程式碼將這部分資料視覺化到業務介面進行展示。

4.總結

  總體來說,ES 叢集從搭建部署到編碼實現都較為簡單,在使用 JSON 字串拼接查詢時需要細心點,後續有時間可以為大家分享下 ES 的查詢的效率,及其他方面的效能指標。

5.結束語

  這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

相關文章