ElasticSearch公共方法封装
业务场景
1、RestClientBuilder初始化(同时支持单机与集群) 2、发送ES查询请求公共方法封装(支持sql、kql、代理访问、集群访问、鉴权支持) 3、判断ES索引是否存在(/_cat/indices/${indexName}) 4、判断ES索引别名是否存在 (/_cat/aliases/${indexName}) 5、判断ES索引指定字段/属性是否存在(这里字段支持多级,如:logObj.id) 6、判断ES索引指定字段/属性的类型(字段支持多级) 7、阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题) 8、创建索引别名(可用于支持sql查询,索引名中有特殊字符不能用作表名,可通过创建别名来解决) 9、索引别名创建结果解析( 判断acknowledged) 10、KQL查询ES(Kibana语法查询ElasticSearch) 11、SQL查询ES(标准SQL语法查询ElasticSearch) 12、Java在本地通过代理访问ES(可用于解决网络不能直接的问题) 13、Java 客户端访问ES集群(同时支持单机与集群) 14、Java ES客户端鉴权(安全需求)
软件环境
ElasticSearch 7.17.23 下载地址
ElasticSearch 7.17.23 帮助文档
ElasticSearch 8.17.2 下载地址
ElasticSearch 8.17.2 帮助文档
说明:当前例子中用的7,理论上8也通用
Kibana查询效果
KQL查询ES
SQL查询ES
下面讲java代码实现
Java类方法详解
1、RestClientBuilder初始化
同时支持单机与集群
/*** RestClientBuilder 初始化** @param host 同时支持单机与集群* 单机:host和port各司其职* 集群时:port参数无效,host中包含IP和PORT,多个实例用逗号分隔** eg:* 10.***.6.247* 或: host:10.***.6.247:9200,10.***.6.34:9200,10.***.6.120:9200* @param port* @return*/private RestClientBuilder buildClient(String host, Integer port){RestClientBuilder restClientBuilder = null;if(host.indexOf(",")==-1){// 单机 host:10.***.6.247, 只有单机会使用port参数restClientBuilder = RestClient.builder(new HttpHost( host, port, "http" ) );}else{// 集群 host:10.***.6.247:9200,10.***.6.34:9200,10.***.6.120:9200,10.***.6.9:9200,10.***.6.183:9200String[] hostArr = host.split("\\,");HttpHost[] httpHosts = new HttpHost[hostArr.length];for( int i=0; i<hostArr.length; i++ ){String[] addrs = hostArr[i].split("\\:");HttpHost httpHost = new HttpHost( addrs[0], Integer.valueOf(addrs[1]), "http" );httpHosts[i] = httpHost;}restClientBuilder = RestClient.builder( httpHosts );}return restClientBuilder;}
2、发送ES查询请求公共方法
- SQL支持
- KQL支持
- 支持代理访问支持
- 鉴权支持
/*** 发送ES 查询请求** @param host* @param port* @param username* @param password* @param method* @param endpoint ES接口* eg:* 1、创建别名: "/_aliases"* 2、判断索引是否存在: "/_cat/indices/myIndexName"* 2、判断索引别名是否存在: "/_cat/aliases/indexName"** @param jsonEntity 查询语句* eg:* 1、为索引创建别名(可用于支持sql查询,如果使用sql查询时原索引名中有特殊字符不能用作表名,可通过创建别名来解决)* String kqlJson = "{\"actions\" : [{ \"add\" : { \"index\" : \""+idxName+"\",\"alias\" : \""+idxAliases+"\" } }]} ";* String kqlJson= "";* 2、String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", " +* "\"params\": ["+cStart+","+cEnd+"]," +* " \"fetch_size\": 65536 }";** 实际业务场景举例:* POST _sql?format=txt* {* "query": "SELECT tags.svc_code, sum(iif(tags.response_code.keyword='0000',1,0)) as success_count, count(metric) as total* FROM order_service_***** where create_time between '2025-02-27T11:00:00+0800' and '2025-02-27T13:59:00+0800'* group by tags.svc_code having count(metric)>=50* order by 3 desc",* "fetch_size": 65536* }** @return* @throws IOException*/public String request(String host, Integer port,String username, String password,String method, String endpoint, String jsonEntity) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());/* ******* 能直连ES的不需要;如果本地不能直连ES的则加上,IP根据实际调整** */if("dev".equals(profile) && host.indexOf("10.***.137")==0 ) {httpClientBuilder.setProxy(new HttpHost("10.***.248.54", 8443, "http") //设置代理服务);}else if("dev".equals(profile) && host.indexOf("10.***.6")==0 ){httpClientBuilder.setProxy(new HttpHost( "192.***.66.30", 8443,"http") //设置代理服务);}return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request(method, endpoint );request.setJsonEntity( jsonEntity );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);return EntityUtils.toString(entity);}
3、判断ES索引是否存在
/*** 判断索引名是否存在** @param host* @param port* @param username* @param password* @param indexName 索引名* @return* @throws IOException*/public boolean isExistsIndex( String host, Integer port,String username, String password,String indexName ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/_cat/indices/"+indexName );try{Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();entity = new BufferedHttpEntity(entity);if (!StringUtils.hasLength(EntityUtils.toString(entity))) {System.out.println("Index exists.");return true;} else {System.out.println("Index does not exist.");return false;}}catch (Exception e){//如果不存在会报404的错误,返回false创建别名return false;}finally {restClient.close();}}
4、判断ES索引别名是否存在
/*** 判断索引别名是否存在** @param host* @param port* @param username* @param password* @param indexName* @return* @throws IOException*/public boolean isExistsAliases( String host, Integer port,String username, String password,String indexName ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/_cat/aliases/"+indexName );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);if (!StringUtils.isEmpty(EntityUtils.toString(entity))) {System.out.println("Index alias exists.");return true;} else {System.out.println("Index alias does not exist.");return false;}}
5、获取ES索引指定字段/属性是否存在
这里字段支持多级,如:logObj.id
/*** 判断索引 某个字段/属性是否存在* 说明: 这里字段支持多级,如:logObj.id** @param host* @param port* @param username* @param password* @param indexName* @param property eg:id、 logObj.id* @return* @throws IOException*/public boolean isExistsProperty( String host, Integer port,String username, String password,String indexName, String property ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/"+indexName+"/_mapping" );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);JSONObject obj = JSONObject.parseObject(EntityUtils.toString(entity));JSONObject properties = obj.getJSONObject( obj.keySet().iterator().next() ).getJSONObject("mappings" ).getJSONObject("properties" );String[] arr = property.split("\\.");for( int i=0; i<arr.length; i++ ){if(i==arr.length-1){}else{if(properties.containsKey( arr[i] )){properties = properties.getJSONObject(arr[i]).getJSONObject("properties" );}else{return false;}}}boolean bool = properties.containsKey( arr[arr.length-1] );log.info("property:{} , isExist:{}", property, bool );return bool;}
6、获取ES索引指定字段/属性的类型
同一个索引的同一字段,不同时间的数据类型可能不一样,从而影响sql语句的写法(sql语法不一样),所以个别场景要做判断
/*** 判断索引某个字段/属性的类型* 说明: 这里字段支持多级,如:logObj.id** @param host* @param port* @param username* @param password* @param indexName* @param property eg:id、 logObj.id* @return* @throws IOException*/public String getIndexPropertyType( String host, Integer port,String username, String password,String indexName, String property ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/"+indexName+"/_mapping" );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);JSONObject obj = JSONObject.parseObject(EntityUtils.toString(entity));JSONObject properties = obj.getJSONObject( obj.keySet().iterator().next() ).getJSONObject("mappings" ).getJSONObject("properties" );String[] arr = property.split("\\.");for( int i=0; i<arr.length; i++ ){if(i==arr.length-1){properties = properties.getJSONObject(arr[i]);}else{if(properties.containsKey( arr[i] )){properties = properties.getJSONObject(arr[i]).getJSONObject("properties" );}else{log.error( "判断字段类型时,发现字段不存在!property:{}", property );throw new RuntimeException( "判断字段类型时,发现字段不存在!property:"+property );}}}String type = properties.getString("type" );log.info("property:{} , type:{}", property, type );return type;}
7、阻塞线程直至索引就绪
为了应对跨日时索引名短时间可能不存在的问题(不处理可能导致程序报错)
/*** 阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)** @param host* @param port* @param username* @param password* @param indexName* @throws IOException* @throws InterruptedException*/@Overridepublic void waitIndexReady( String host, Integer port,String username, String password,String indexName ) throws IOException, InterruptedException {// 循环一次是10s,6次是1分钟,60次是10分钟for( int i=0; i<60 && !this.isExistsIndex(host, port,username, password,indexName) ; i++ ){// 共循环10分钟Thread.sleep( 10*1000 );}}
8、创建索引别名
有时候索引名带特殊字符,是sql的关键字,所以创建别名可供sql查询用作表名
/*** 创建索引别名** @param username* @param password* @param host* @param port* @param idxName 索引名* @param idxAliases 索引别名* @throws ParseException* @return*/public void createAliases( String username, String password,String host, int port,String idxName, String idxAliases ) throws ParseException {log.info("创建索引别名 :{}:{}, {}", host, port, idxAliases );String method = "POST";String endpoint = "/_aliases";// 为索引创建别名,用于支持sql查询String kqlJson = "{\"actions\" : [{ \"add\" : { \"index\" : \""+idxName+"\",\"alias\" : \""+idxAliases+"\" } }]} ";try{String body =null;body = this.request( host, port,username, password,method, endpoint, kqlJson );boolean acknowledged = parseCreateAliasesResult( body );if(acknowledged){log.info("别名创建成功");}else{log.error("别名创建失败");log.error("别名创建失败 kqlJson:{}", kqlJson );throw new RuntimeException("索引别名创建失败!");}}catch (Exception ex){log.error("创建索引别名异常!message:{}",ex.getLocalizedMessage());log.error("创建索引别名异常 kqlJson:{}", kqlJson );ex.printStackTrace();}}
9、索引别名创建结果解析
判断创建时返回值中的acknowledged属性值
/*** 判断body 中是否包含 acknowledged** @param body* @return*/private boolean parseCreateAliasesResult(String body ){JSONObject json = JSONObject.parseObject(body);if(json.containsKey("acknowledged") && json.getBoolean("acknowledged")){return true;}return false;}
完整代码实现
完整maven pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>person.brickman</groupId><artifactId>javaProject</artifactId><version>1.0-SNAPSHOT</version><!-- 统一管理jar包版本 --><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target><spring-boot.version>2.7.18</spring-boot.version><spring-cloud.version>2021.0.9</spring-cloud.version><spring-cloud-starter-bootstrap.version>3.1.9</spring-cloud-starter-bootstrap.version><elasticsearch-client.version>7.17.23</elasticsearch-client.version><commons-lang3.version>3.14.0</commons-lang3.version><fastjson2.version>2.0.53</fastjson2.version><lombok.version>1.18.28</lombok.version><testng.version>6.14.3</testng.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-bootstrap</artifactId><version>${spring-cloud-starter-bootstrap.version}</version></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId><version>${elasticsearch-client.version}</version></dependency><dependency><groupId>org.apache.httpcomponents.client5</groupId><artifactId>httpclient5</artifactId><version>5.3</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>${commons-lang3.version}</version></dependency><dependency><groupId>com.alibaba.fastjson2</groupId><artifactId>fastjson2</artifactId><version>${fastjson2.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version><optional>true</optional></dependency><!-- 测试相关 默认集成junit5 作者用testng,所以排除掉junit5 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId></exclusion><exclusion><groupId>org.mockito</groupId><artifactId>mockito-junit-jupiter</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.testng</groupId><artifactId>testng</artifactId><version>${testng.version}</version><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
</project>
完整ES公共组件类
package person.brickman.es;import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;import java.io.IOException;
import java.text.ParseException;/*** @Description: ES公共方法类(公共组件)* 1、RestClientBuilder初始化(同时支持单机与集群)* 2、发送ES查询请求公共方法封装(支持sql、kql、代理访问、集群访问、鉴权)* 3、判断ES索引是否存在(/_cat/indices/${indexName})* 4、判断ES索引别名是否存在 (/_cat/aliases/${indexName})* 5、判断ES索引指定字段/属性是否存在(这里字段支持多级,如:logObj.id)* 6、判断ES索引指定字段/属性的类型(字段支持多级)* 7、阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)* 8、创建索引别名(可用于支持sql查询,索引名中有特殊字符不能用作表名,可通过创建别名来解决)* 9、索引别名创建结果解析( 判断acknowledged)** @Author: brickman* @CreateDate: 2025/2/20 23:46* @Version: 1.0*/
@Slf4j
@Service
public class ESRestClientService {@Value("${spring.profiles.active}")private String profile;/*** RestClientBuilder 初始化** @param host 同时支持单机与集群* 单机:host和port各司其职* 集群时:port参数无效,host中包含IP和PORT,多个实例用逗号分隔** eg:* 10.***.6.247* 或: host:10.***.6.247:9200,10.***.6.34:9200,10.***.6.120:9200* @param port* @return*/private RestClientBuilder buildClient(String host, Integer port){RestClientBuilder restClientBuilder = null;if(host.indexOf(",")==-1){// 单机 host:10.***.6.247, 只有单机会使用port参数restClientBuilder = RestClient.builder(new HttpHost( host, port, "http" ) );}else{// 集群 host:10.***.6.247:9200,10.***.6.34:9200,10.***.6.120:9200,10.***.6.9:9200,10.***.6.183:9200String[] hostArr = host.split("\\,");HttpHost[] httpHosts = new HttpHost[hostArr.length];for( int i=0; i<hostArr.length; i++ ){String[] addrs = hostArr[i].split("\\:");HttpHost httpHost = new HttpHost( addrs[0], Integer.valueOf(addrs[1]), "http" );httpHosts[i] = httpHost;}restClientBuilder = RestClient.builder( httpHosts );}return restClientBuilder;}/*** 发送ES 查询请求,包含sql、kql、代理访问、鉴权支持** @param host 同时支持单机与集群* 单机:host和port各司其职* 集群时:port参数无效,host中包含IP和PORT,多个实例用逗号分隔* @param port* @param username* @param password* @param method* @param endpoint ES接口* eg:* 1、创建别名: "/_aliases"* 2、判断索引是否存在: "/_cat/indices/myIndexName"* 2、判断索引别名是否存在: "/_cat/aliases/indexName"** @param jsonEntity 查询语句* eg:* 1、为索引创建别名(可用于支持sql查询,如果使用sql查询时原索引名中有特殊字符不能用作表名,可通过创建别名来解决)* String kqlJson = "{\"actions\" : [{ \"add\" : { \"index\" : \""+idxName+"\",\"alias\" : \""+idxAliases+"\" } }]} ";* String kqlJson= "";* 2、String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", " +* "\"params\": ["+cStart+","+cEnd+"]," +* " \"fetch_size\": 65536 }";** 实际业务场景举例:* POST _sql?format=txt* {* "query": "SELECT tags.svc_code, sum(iif(tags.response_code.keyword='0000',1,0)) as success_count, count(metric) as total* FROM order_service_***** where create_time between '2025-02-27T11:00:00+0800' and '2025-02-27T13:59:00+0800'* group by tags.svc_code having count(metric)>=50* order by 3 desc",* "fetch_size": 65536* }** @return* @throws IOException*/public String request(String host, Integer port,String username, String password,String method, String endpoint, String jsonEntity) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());/* ******* 本地能直连ES的不需要;如果本地不能直连ES的则加上,IP根据实际调整** */if("dev".equals(profile) && host.indexOf("10.***.137")==0 ) {httpClientBuilder.setProxy(new HttpHost("10.***.248.54", 8443, "http") //设置代理服务);}else if("dev".equals(profile) && host.indexOf("10.***.6")==0 ){httpClientBuilder.setProxy(new HttpHost( "192.***.66.30", 8443,"http") //设置代理服务);}return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request(method, endpoint );request.setJsonEntity( jsonEntity );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);return EntityUtils.toString(entity);}/*** 判断索引名是否存在** @param host 同时支持单机与集群* 单机:host和port各司其职* 集群时:port参数无效,host中包含IP和PORT,多个实例用逗号分隔* @param port* @param username* @param password* @param indexName 索引名* @return* @throws IOException*/public boolean isExistsIndex( String host, Integer port,String username, String password,String indexName ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/_cat/indices/"+indexName );try{Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();entity = new BufferedHttpEntity(entity);if (!StringUtils.hasLength(EntityUtils.toString(entity))) {System.out.println("Index exists.");return true;} else {System.out.println("Index does not exist.");return false;}}catch (Exception e){//如果不存在会报404的错误,返回false创建别名return false;}finally {restClient.close();}}/*** 判断索引别名是否存在** @param host* @param port* @param username* @param password* @param indexName* @return* @throws IOException*/public boolean isExistsAliases( String host, Integer port,String username, String password,String indexName ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/_cat/aliases/"+indexName );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);if (!StringUtils.isEmpty(EntityUtils.toString(entity))) {System.out.println("Index alias exists.");return true;} else {System.out.println("Index alias does not exist.");return false;}}/*** 判断索引 某个字段/属性是否存在* 说明: 这里字段支持多级,如:logObj.id** @param host* @param port* @param username* @param password* @param indexName* @param property eg:id、 logObj.id* @return* @throws IOException*/public boolean isExistsProperty( String host, Integer port,String username, String password,String indexName, String property ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/"+indexName+"/_mapping" );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);JSONObject obj = JSONObject.parseObject(EntityUtils.toString(entity));JSONObject properties = obj.getJSONObject( obj.keySet().iterator().next() ).getJSONObject("mappings" ).getJSONObject("properties" );String[] arr = property.split("\\.");for( int i=0; i<arr.length; i++ ){if(i==arr.length-1){}else{if(properties.containsKey( arr[i] )){properties = properties.getJSONObject(arr[i]).getJSONObject("properties" );}else{return false;}}}boolean bool = properties.containsKey( arr[arr.length-1] );log.info("property:{} , isExist:{}", property, bool );return bool;}/*** 判断索引某个字段/属性的类型* 说明: 这里字段支持多级,如:logObj.id* 同一个索引的同一字段,不同时间的数据类型可能不一样,从而影响sql语句的写法(sql语法不一样),所以个别场景要做判断** @param host* @param port* @param username* @param password* @param indexName* @param property eg:id、 logObj.id* @return* @throws IOException*/public String getIndexPropertyType( String host, Integer port,String username, String password,String indexName, String property ) throws IOException {RestClientBuilder restClientBuilder = buildClient( host, port );if(!StringUtils.isEmpty(username)){final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));restClientBuilder.setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {@Overridepublic HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);//线程设置httpClientBuilder.setDefaultIOReactorConfig(IOReactorConfig.custom().setIoThreadCount(2).build());return httpClientBuilder;}});}RestClient restClient = restClientBuilder.build();Request request = new Request("GET", "/"+indexName+"/_mapping" );Response response = restClient.performRequest(request);HttpEntity entity=response.getEntity();restClient.close();entity = new BufferedHttpEntity(entity);JSONObject obj = JSONObject.parseObject(EntityUtils.toString(entity));JSONObject properties = obj.getJSONObject( obj.keySet().iterator().next() ).getJSONObject("mappings" ).getJSONObject("properties" );String[] arr = property.split("\\.");for( int i=0; i<arr.length; i++ ){if(i==arr.length-1){properties = properties.getJSONObject(arr[i]);}else{if(properties.containsKey( arr[i] )){properties = properties.getJSONObject(arr[i]).getJSONObject("properties" );}else{log.error( "判断字段类型时,发现字段不存在!property:{}", property );throw new RuntimeException( "判断字段类型时,发现字段不存在!property:"+property );}}}String type = properties.getString("type" );log.info("property:{} , type:{}", property, type );return type;}/*** 阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)** @param host* @param port* @param username* @param password* @param indexName* @throws IOException* @throws InterruptedException*/public void waitIndexReady( String host, Integer port,String username, String password,String indexName ) throws IOException, InterruptedException {// 循环一次是10s,6次是1分钟,60次是10分钟for( int i=0; i<60 && !this.isExistsIndex(host, port,username, password,indexName) ; i++ ){// 共循环10分钟Thread.sleep( 10*1000 );}}/*** 创建索引别名* 有时候索引名带特殊字符,是sql的关键字,所以创建别名可供sql查询用作表名** @param username* @param password* @param host* @param port* @param idxName 索引名* @param idxAliases 索引别名* @throws ParseException* @return*/public void createAliases( String username, String password,String host, int port,String idxName, String idxAliases ) throws ParseException {log.info("创建索引别名 :{}:{}, {}", host, port, idxAliases );String method = "POST";String endpoint = "/_aliases";// 为索引创建别名,用于支持sql查询String kqlJson = "{\"actions\" : [{ \"add\" : { \"index\" : \""+idxName+"\",\"alias\" : \""+idxAliases+"\" } }]} ";try{String body =null;body = this.request( host, port,username, password,method, endpoint, kqlJson );boolean acknowledged = parseCreateAliasesResult( body );if(acknowledged){log.info("别名创建成功");}else{log.error("别名创建失败");log.error("别名创建失败 kqlJson:{}", kqlJson );throw new RuntimeException("索引别名创建失败!");}}catch (Exception ex){log.error("创建索引别名异常!message:{}",ex.getLocalizedMessage());log.error("创建索引别名异常 kqlJson:{}", kqlJson );ex.printStackTrace();}}/*** 判断body 中是否包含 acknowledged** @param body* @return*/private boolean parseCreateAliasesResult(String body ){JSONObject json = JSONObject.parseObject(body);if(json.containsKey("acknowledged") && json.getBoolean("acknowledged")){return true;}return false;}
}
单元测试方法详解
作者用的testng
1、执行检索
/*** 执行检索(这里使用sql查询近一分钟的数据)** @throws IOException* @throws ParseException*/@Test(groups = "hlog", enabled = true )public void testRequest() throws IOException, ParseException {String method = "POST";String endpoint = "/_sql?format=json";RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();
// String time = "2025-02-21 17:48:00";String time = TimeUtils.calcWholeMinute();rangeTimeUtils.calcAllByTimeAndPeriod( time, 1);// "yyyy-MM-dd HH:mm:ss" --> "yyyy-MM-dd'T'HH:mm" 根据实际时间字段格式调整SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm" );Long cStart = rangeTimeUtils.getStartDate().getTime();Long cEnd = rangeTimeUtils.getStartDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );String idxAliases = "interf_dand_comm_undef_"+sdf_target.format(rangeTimeUtils.getEndDate());// 生产环境String sqlQuery2 = "SELECT logObj.province,server, logObj.app, logObj.node_ip, " +"SUBSTRING(logObj.uri,0,30) , count(id) as num, avg(logObj.costTime) latency , " +"sum(iif(logObj.code=200,1,0)) as success_count " +" FROM " + idxAliases + " "+
// "where cTime between '"+cStart+":00+0800' and '"+cEnd+":00+0800' " +" where cTime between "+cStart+" and "+cEnd+" " +"group by logObj.province,server, logObj.app, logObj.node_ip, SUBSTRING(logObj.uri,0,30) "; // 将index_name、field_name和value替换为相应的索引名称、字段名和值// max fetch_size is 65536 不同的版本单次查询最大数据限制不一样,这里测试只查 5 条String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", \"fetch_size\": 5}";String ret = service.request( host, port,username, password,method, endpoint, jsonEntity );log.debug("ret:{}",ret);}
2、判断索引是否存在
/*** 判断索引是否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsIndex() throws IOException {/* interf_dand_hig_c_trans_2024_12_03interf_dand_hig_c_trans_2025_02_20interf_dand_comm_undef_2024_12_03interf_dand_comm_undef_2025_02_20*/boolean ret = service.isExistsIndex( host, port,username, password,"interf_dand_comm_undef_2025_02_21");log.info("ret:{}",ret);}
3、判断索引别名是否存在
/*** 判断索引别名是否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsAliases() throws IOException {/* interf_dand_hig_c_trans_2025_02_21interf_dand_comm_undef_2025_02_21*/boolean ret = service.isExistsAliases( host, port,username, password,"interf_dand_comm_undef_2025_02_21");log.info("ret:{}",ret);}
4、判断索引字段时否存在
/*** 判断索引字段时否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsProperty() throws IOException {// logObj.node_ip logObj.statusboolean ret = service.isExistsProperty( host, port,username, password,"interf_dand_comm_undef_2025_02_21", "logObj.status");log.info("ret:{}",ret);}
5、获取索引列类型
/*** 获取索引列类型** @throws IOException*/@Test( enabled = true )public void testGetIndexPropertyType() throws IOException {/* interf_dand_hig_c_trans_2025_02_21interf_dand_comm_undef_2025_02_21*/String ret = service.getIndexPropertyType( host, port,username, password,"interf_dand_comm_undef_2025_02_21","logObj.status" );log.info("ret:{}",ret);}
6、阻塞线程直至索引就绪
/*** 阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)** @throws IOException*/@Test( enabled = true )public void testWaitIndexReady() throws IOException, ParseException, InterruptedException {RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );service.waitIndexReady( host, port,username, password,"interf_dand_comm_undef_2025_02_21");}
7、创建索引别名
/*** 创建索引别名** @throws IOException*/@Test( enabled = true )public void testCreateAliases() throws IOException, ParseException {RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );service.createAliases( username, password,host, port,"interf_dand-comm-undef.2025.02.21","interf_dand_comm_undef_2025_02_21");}
8、SQL查询ES
/*** sql查询** @throws IOException*/@Test( enabled = true )public void testSQLRequest() throws IOException, ParseException, InterruptedException {log.info("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口/不区分接口)服务级 :{}:{}", host, port );RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );List<Object> ret=null;String method = "POST";String endpoint = "/_sql?format=json";// 根据实际时间字段格式调整SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");long cStart = rangeTimeUtils.getStartDate().getTime();long cEnd = rangeTimeUtils.getEndDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );// interf_dand-comm-undef.2024.01.06String idxName = ESConsts.INTERFLOG_INDEX_NAME_PREFIX + sdf_source.format(rangeTimeUtils.getStartDate());// interf_dand_comm_undef_2024_01_06String idxAliases = ESConsts.INTERFLOG_INDEX_ALIASES_PREFIX + sdf_target.format(rangeTimeUtils.getStartDate());//等待索引就绪service.waitIndexReady(host, port,username, password,idxName);// 判断别名是否存在,不存在则创建if(!service.isExistsAliases( host, port,username, password,idxAliases)){service.createAliases( username, password, host, port,idxName, idxAliases);}/* logObj.status.keyword(对应String) 、还是 logObj.status(对应int ) 根据类型来 */String statusFieldName = getStatusFieldName(host, port,username, password,idxAliases );String sqlQuery2 = "SELECT logObj.province,server, logObj.app, '' node_ip, " +" '' uri, count(id) as num, avg(logObj.cost) latency , " +"sum(iif("+statusFieldName+"=200,1,0)) as success_count " +" FROM " + idxAliases + " " +// 算头不算尾"where cTime >= ? and cTime < ? " +"group by logObj.province,server, logObj.app ";// max fetch_size is 65536 , 已验证一次查询结果不会超过这个数,故不用做滚动查询String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", " +"\"params\": ["+cStart+","+cEnd+"]," +" \"fetch_size\": 10 }"; // 65536 -- 不同的版本单次查询最大数据限制不一样,这里测试只查10条String body =null;try{body = service.request( host, port,username, password,method, endpoint, jsonEntity );// 组装成实际需要的业务类型集合
// ret = ESIntfLoad3Parser.parseESresult4SvcLoad( body, IDC, host ,port, rangeTime);}catch (Exception ex){log.error("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口)服务级 指标异常!message:{}",ex.getLocalizedMessage());log.error("sqlQuery2:{}", sqlQuery2 );log.error("jsonEntity:{}", jsonEntity);ex.printStackTrace();}log.debug(" body:{}", body );}
9、KQL查询ES
/*** KQL查询** @throws IOException*/@Test( enabled = true )public void testKQLRequest() throws IOException, ParseException, InterruptedException {log.info("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口/不区分接口)服务级 :{}:{}", host, port );RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );List<Object> ret=null;String method = "GET";String endpoint = "/interf_dand_comm_undef_2025_02_21/_search";SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");// 这里cStart、cEnd两者相关1分钟,在上面做了初始化long cStart = rangeTimeUtils.getStartDate().getTime();long cEnd = rangeTimeUtils.getEndDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );// interf_dand-comm-undef.2024.01.06String idxName = ESConsts.INTERFLOG_INDEX_NAME_PREFIX + sdf_source.format(rangeTimeUtils.getStartDate());// interf_dand_comm_undef_2024_01_06String idxAliases = ESConsts.INTERFLOG_INDEX_ALIASES_PREFIX + sdf_target.format(rangeTimeUtils.getStartDate());//等待索引就绪service.waitIndexReady(host, port,username, password,idxName);// 判断别名是否存在,不存在则创建if(!service.isExistsAliases( host, port,username, password,idxAliases)){service.createAliases( username, password, host, port,idxName, idxAliases);}String kqlQuery2 = "{ " +" \"bool\": { " +" \"must\": [ " +" { \"match_phrase\": { \"logObj.app\":\"order-service\" } }, " +" { \"range\": { " +" \"cTime\": { " +" \"gte\": ?, " +" \"lt\": ? " +" } " +" }} " +" ] " +" } " +" }"; // 将index_name、field_name和value替换为相应的索引名称、字段名和值// max fetch_size is 65536 , 已验证一次查询结果不会超过这个数,故不用做滚动查询String jsonEntity = "{\"query\": \"" + kqlQuery2 + "\", " +"\"params\": ["+cStart+","+cEnd+"]," +" \"size\": 0,\" +\n" +" \"size\": 2 }"; // 65536 -- 不同的版本单次查询最大数据限制不一样, 这里测试只查两条String body =null;try{body = service.request( host, port,username, password,method, endpoint, jsonEntity );// 组装成实际需要的业务类型集合
// ret = ESIntfLoad3Parser.parseESresult4SvcLoad( body, IDC, host ,port, rangeTime);}catch (Exception ex){log.error("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口)服务级 指标异常!message:{}",ex.getLocalizedMessage());log.error("kqlQuery2:{}", kqlQuery2 );log.error("jsonEntity:{}", jsonEntity);ex.printStackTrace();}log.debug(" body:{}", body );}
完整单元测试实现
完整单元测试类
package person.brickman.es;import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.testng.AbstractTestNGSpringContextTests;
import org.testng.annotations.Test;
import person.brickman.MainApplication;
import person.brickman.constant.ESConsts;
import person.brickman.util.RangeTimeUtils;
import person.brickman.util.TimeUtils;import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.List;/*** 单元测试类** 1、RestClientBuilder初始化(同时支持单机与集群),因每个单元测试方法都会调,所以没有写独立的单元测试方法* 2、发送ES查询请求公共方法封装(支持sql、kql、代理访问、集群访问、鉴权),单元测试以SQL查询举例,参数直接拼的(使用占位符示例见:9、10)* 3、判断ES索引是否存在(/_cat/indices/${indexName})* 4、判断ES索引别名是否存在 (/_cat/aliases/${indexName})* 5、判断ES索引指定字段/属性是否存在(这里字段支持多级,如:logObj.id)* 6、判断ES索引指定字段/属性的类型(字段支持多级)* 7、阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)* 8、创建索引别名(可用于支持sql查询,索引名中有特殊字符不能用作表名,可通过创建别名来解决)* 9、SQL查询ES* 10、KQL查询ES** @Author: brickman* @CreateDate: 2025/2/21 22:14* @Version: 1.0*/
@Slf4j
@SpringBootTest(classes = MainApplication.class)
public class ESRestClientServiceImplTest extends AbstractTestNGSpringContextTests {@Value("${elasticsearch.dand.interf.hosts}")private String host;@Value("${elasticsearch.dand.interf.port}")private int port;@Value("${elasticsearch.dand.interf.username}")private String username;@Value("${elasticsearch.dand.interf.password}")private String password;@Autowiredprivate ESRestClientService service;/*** 执行检索(这里使用sql查询近一分钟的数据)** @throws IOException* @throws ParseException*/@Test(groups = "hlog", enabled = true )public void testRequest() throws IOException, ParseException {String method = "POST";String endpoint = "/_sql?format=json";RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();
// String time = "2025-02-21 17:48:00";String time = TimeUtils.calcWholeMinute();rangeTimeUtils.calcAllByTimeAndPeriod( time, 1);// "yyyy-MM-dd HH:mm:ss" --> "yyyy-MM-dd'T'HH:mm" 根据实际时间字段格式调整SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm" );Long cStart = rangeTimeUtils.getStartDate().getTime();Long cEnd = rangeTimeUtils.getStartDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );String idxAliases = "interf_dand_comm_undef_"+sdf_target.format(rangeTimeUtils.getEndDate());// 生产环境String sqlQuery2 = "SELECT logObj.province,server, logObj.app, logObj.node_ip, " +"SUBSTRING(logObj.uri,0,30) , count(id) as num, avg(logObj.costTime) latency , " +"sum(iif(logObj.code=200,1,0)) as success_count " +" FROM " + idxAliases + " "+
// "where cTime between '"+cStart+":00+0800' and '"+cEnd+":00+0800' " +" where cTime between "+cStart+" and "+cEnd+" " +"group by logObj.province,server, logObj.app, logObj.node_ip, SUBSTRING(logObj.uri,0,30) "; // 将index_name、field_name和value替换为相应的索引名称、字段名和值// max fetch_size is 65536 不同的版本单次查询最大数据限制不一样,这里测试只查 5 条String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", \"fetch_size\": 5}";String ret = service.request( host, port,username, password,method, endpoint, jsonEntity );log.debug("ret:{}",ret);}/*** 判断索引是否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsIndex() throws IOException {/* interf_dand_hig_c_trans_2024_12_03interf_dand_hig_c_trans_2025_02_20interf_dand_comm_undef_2024_12_03interf_dand_comm_undef_2025_02_20*/boolean ret = service.isExistsIndex( host, port,username, password,"interf_dand_comm_undef_2025_02_21");log.info("ret:{}",ret);}/*** 判断索引别名是否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsAliases() throws IOException {/* interf_dand_hig_c_trans_2025_02_21interf_dand_comm_undef_2025_02_21*/boolean ret = service.isExistsAliases( host, port,username, password,"interf_dand_comm_undef_2025_02_21");log.info("ret:{}",ret);}/*** 判断索引字段时否存在* @throws IOException*/@Test(groups = "hlog", enabled = true )public void testIsExistsProperty() throws IOException {// logObj.node_ip logObj.statusboolean ret = service.isExistsProperty( host, port,username, password,"interf_dand_comm_undef_2025_02_21", "logObj.status");log.info("ret:{}",ret);}/*** 获取索引列类型** @throws IOException*/@Test( enabled = true )public void testGetIndexPropertyType() throws IOException {/* interf_dand_hig_c_trans_2025_02_21interf_dand_comm_undef_2025_02_21*/String ret = service.getIndexPropertyType( host, port,username, password,"interf_dand_comm_undef_2025_02_21","logObj.status" );log.info("ret:{}",ret);}/*** 阻塞线程直至索引就绪(为了应对跨日时索引名短时间可能不存在的问题)** @throws IOException*/@Test( enabled = true )public void testWaitIndexReady() throws IOException, ParseException, InterruptedException {RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );service.waitIndexReady( host, port,username, password,"interf_dand_comm_undef_2025_02_21");}/*** 创建索引别名** @throws IOException*/@Test( enabled = true )public void testCreateAliases() throws IOException, ParseException {RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );service.createAliases( username, password,host, port,"interf_dand-comm-undef.2025.02.21","interf_dand_comm_undef_2025_02_21");}/*** sql查询** @throws IOException*/@Test( enabled = true )public void testSQLRequest() throws IOException, ParseException, InterruptedException {log.info("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口/不区分接口)服务级 :{}:{}", host, port );RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );List<Object> ret=null;String method = "POST";String endpoint = "/_sql?format=json";// 根据实际时间字段格式调整SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");long cStart = rangeTimeUtils.getStartDate().getTime();long cEnd = rangeTimeUtils.getEndDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );// interf_dand-comm-undef.2024.01.06String idxName = ESConsts.INTERFLOG_INDEX_NAME_PREFIX + sdf_source.format(rangeTimeUtils.getStartDate());// interf_dand_comm_undef_2024_01_06String idxAliases = ESConsts.INTERFLOG_INDEX_ALIASES_PREFIX + sdf_target.format(rangeTimeUtils.getStartDate());//等待索引就绪service.waitIndexReady(host, port,username, password,idxName);// 判断别名是否存在,不存在则创建if(!service.isExistsAliases( host, port,username, password,idxAliases)){service.createAliases( username, password, host, port,idxName, idxAliases);}/* logObj.status.keyword(对应String) 、还是 logObj.status(对应int ) 根据类型来 */String statusFieldName = getStatusFieldName(host, port,username, password,idxAliases );String sqlQuery2 = "SELECT logObj.province,server, logObj.app, '' node_ip, " +" '' uri, count(id) as num, avg(logObj.cost) latency , " +"sum(iif("+statusFieldName+"=200,1,0)) as success_count " +" FROM " + idxAliases + " " +// 算头不算尾"where cTime >= ? and cTime < ? " +"group by logObj.province,server, logObj.app ";// max fetch_size is 65536 , 已验证一次查询结果不会超过这个数,故不用做滚动查询String jsonEntity = "{\"query\": \"" + sqlQuery2 + "\", " +"\"params\": ["+cStart+","+cEnd+"]," +" \"fetch_size\": 10 }"; // 65536 -- 不同的版本单次查询最大数据限制不一样,这里测试只查10条String body =null;try{body = service.request( host, port,username, password,method, endpoint, jsonEntity );// 组装成实际需要的业务类型集合
// ret = ESIntfLoad3Parser.parseESresult4SvcLoad( body, IDC, host ,port, rangeTime);}catch (Exception ex){log.error("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口)服务级 指标异常!message:{}",ex.getLocalizedMessage());log.error("sqlQuery2:{}", sqlQuery2 );log.error("jsonEntity:{}", jsonEntity);ex.printStackTrace();}log.debug(" body:{}", body );}/*** kql查询** @throws IOException*/@Test( enabled = true )public void testKQLRequest() throws IOException, ParseException, InterruptedException {log.info("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口/不区分接口)服务级 :{}:{}", host, port );RangeTimeUtils rangeTimeUtils = new RangeTimeUtils();String time = "2025-02-21 20:00:00";rangeTimeUtils.calcAllByTimeAndPeriod( time, 1 );List<Object> ret=null;String method = "GET";String endpoint = "/interf_dand_comm_undef_2025_02_21/_search";SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm");// 这里cStart、cEnd两者相关1分钟,在上面做了初始化long cStart = rangeTimeUtils.getStartDate().getTime();long cEnd = rangeTimeUtils.getEndDate().getTime();// 原索引名是sql关键字,索引需要创建别名SimpleDateFormat sdf_source = new SimpleDateFormat("yyyy.MM.dd" );SimpleDateFormat sdf_target = new SimpleDateFormat("yyyy_MM_dd" );// interf_dand-comm-undef.2024.01.06String idxName = ESConsts.INTERFLOG_INDEX_NAME_PREFIX + sdf_source.format(rangeTimeUtils.getStartDate());// interf_dand_comm_undef_2024_01_06String idxAliases = ESConsts.INTERFLOG_INDEX_ALIASES_PREFIX + sdf_target.format(rangeTimeUtils.getStartDate());//等待索引就绪service.waitIndexReady(host, port,username, password,idxName);// 判断别名是否存在,不存在则创建if(!service.isExistsAliases( host, port,username, password,idxAliases)){service.createAliases( username, password, host, port,idxName, idxAliases);}String kqlQuery2 = "{ " +" \"bool\": { " +" \"must\": [ " +" { \"match_phrase\": { \"logObj.app\":\"inst-service\" } }, " +" { \"range\": { " +" \"cTime\": { " +" \"gte\": ?, " +" \"lt\": ? " +" } " +" }} " +" ] " +" } " +" }"; // 将index_name、field_name和value替换为相应的索引名称、字段名和值// max fetch_size is 65536 , 已验证一次查询结果不会超过这个数,故不用做滚动查询String jsonEntity = "{\"query\": \"" + kqlQuery2 + "\", " +"\"params\": ["+cStart+","+cEnd+"]," +" \"size\": 0,\" +\n" +" \"size\": 2 }"; // 65536 -- 不同的版本单次查询最大数据限制不一样, 这里测试只查两条String body =null;try{body = service.request( host, port,username, password,method, endpoint, jsonEntity );// 组装成实际需要的业务类型集合
// ret = ESIntfLoad3Parser.parseESresult4SvcLoad( body, IDC, host ,port, rangeTime);}catch (Exception ex){log.error("实时从ES统计接口请求数量、成功率、延迟指标 (所有接口)服务级 指标异常!message:{}",ex.getLocalizedMessage());log.error("kqlQuery2:{}", kqlQuery2 );log.error("jsonEntity:{}", jsonEntity);ex.printStackTrace();}log.debug(" body:{}", body );}/*** 查询索引 logObj.status 字段类型* 如果是 text 则使用 logObj.status.keyword* 如果是 long 则使用 logObj.status** @Author: brickman* @CreateDate: 2025-02-21 17:48:00* @Version: 1.0*/private String getStatusFieldName(String host, int port,String username, String password,String idxAliases ) throws IOException {// logObj.status 字段 default: longString statusFieldName = "logObj.status";// 查询索引 logObj.status 字段类型if("text".equalsIgnoreCase( service.getIndexPropertyType( host, port,username, password,idxAliases, "logObj.status" ) )){statusFieldName = "logObj.status.keyword";}return statusFieldName;}
}
单元测试效果
作者使用的testng,配置中心nacos
常量类
package person.brickman.constant;/*** @Description: ES 常量类* @Author: brickman* @CreateDate: 2025/2/21 22:05* @Version: 1.0*/
public class ESConsts {/** interf 索引名常量 */public static final String INTERFLOG_INDEX_NAME_PREFIX = "interf-log-dand-comm-undef.";/** interf 索引别名常量 */public static final String INTERFLOG_INDEX_ALIASES_PREFIX = "interf_log_dand_comm_undef_";/** csb 索引名常量 */public static final String CSB_INDEX_NAME_PREFIX = "csb-service.csb.";/** csb 索引别名常量 */public static final String CSB_INDEX_ALIASES_PREFIX = "csb_service_csb_";/** skywalking 索引名常量 */public static final String SW_INDEX_NAME_PREFIX = "sw_metrics-all-";/** skywalking 索引别名常量 */public static final String SW_INDEX_ALIASES_PREFIX = "sw_metrics_all_";
}
工具类
RangeTimeUtils
用于通过指定时间(如当前时间)生成开始时间、截止时间、采集周期、采集时间(跨度)范围、指标采集时间
package person.brickman.util;import lombok.Data;
import org.apache.commons.lang3.time.DateUtils;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;/*** @Description:** s_start String(19) 开始时间 因为是时间段的数据,所以增加4个字段* s_end String(19) 截止时间* period smallint 采集周期 多久采一次。默认1,单位分钟,与范围不同* n_range smallint 采集时间(跨度)范围 采多长时间的数据。默认1,单位分钟 eg:1min* time String(19) 指标采集时间 冗余字段,yyyy-MM-dd HH:mm:ss* 部分时序数据库以long形式显示timestamp,此字段便于查看** @Author: brickman* @CreateDate: 2025/02/21 9:03 PM* @Version: 1.0*/
@Data
public class RangeTimeUtils {/** 开始时间 yyyy-MM-dd HH:mm:ss */private String cStart;/** 截止时间 yyyy-MM-dd HH:mm:ss */private String cEnd;/** 采集周期 多久采一次。默认1,单位分钟,与范围不同 */private int period;/*** 采集时间(跨度)范围* @deprecated 一般与采集周期一致* */private int nRange;/*** 采集时间 yyyy-MM-dd HH:mm:ss* eg: 2025-02-21 17:48:00*/private String time;/*** 通过时间和周期计算所有字段值* 默认偏移1分钟,因之前调的默认方法且需要偏移,所有遵循开闭原则* @param time eg: 2025-02-21 17:48:00* @param period 单位:分钟* @return void**/public void calcAllByTimeAndPeriod( String time, int period ) throws ParseException {calcAllByTimeAndPeriod( time, period, -1 );}/*** @param shifting 波动时间、偏移时间* @return null**/public void calcAllByTimeAndPeriod( String time, int period, int shifting ) throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date dt = sdf1.parse(time);// dt:date timeSimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm");Date de = sdf2.parse(sdf2.format(dt));//de:date endDate ds = DateUtils.addMinutes(de, -period ); // ds: date start/* */ds = DateUtils.addMinutes(ds, shifting);de = DateUtils.addMinutes(de, shifting);this.cStart = sdf1.format(ds);this.cEnd = sdf1.format(de);this.period=period;// 采集时间(跨度)范围 与 采集周期一致this.nRange = this.period;this.time=time;}public Date getStartDate() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf1.parse(this.cStart);// dt:date time}public Date getEndDate() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf1.parse(this.cEnd);// dt:date time}public Date getGraphQLStartDate() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf1.parse(this.cStart);// dt:date time}public Date getGraphQLEndDate() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");return sdf1.parse(this.cEnd);// dt:date time}public String getGraphQLStart() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HHmm");return sdf1.format(getGraphQLStartDate());// dt:date time}public String getGraphQLEnd() throws ParseException {SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HHmm");return sdf1.format(getGraphQLEndDate());// dt:date time}
}
TimeUtils
取当前时间精确到分(取整分)
package person.brickman.util;import java.text.SimpleDateFormat;
import java.util.Date;/*** @Description: 时间工具类* 取当前时间精确到分(取整分)** @Author: brickman* @CreateDate: 2025/2/21 22:09* @Version: 1.0*/
public class TimeUtils {/** 取当前时间精确到分(取整分),可优化为取数据库时间 **/public static String calcWholeMinute(){SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");String time = sdf.format(new Date());System.out.println("#### WholeMinuteTime:"+time+":00");return time+":00";}/** 根椐入参取整分 **/public static String calcWholeMinute( Date date ){SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm");String time = sdf.format( date );System.out.println("#### WholeMinuteTime:"+time+":00");return time+":00";}
}
总结
1、ElasticSearch公共方法封装能降低开发成本、提高开发效率
2、非常通用的方法,如查Skywalking的索引数据、查自研的接口调用日志数据等
3、说本文将ES的日常开发代码一网代尽都不为过
附件一:ElasticSearch介绍
ElasticSearch介绍
在Elasticsearch(简称ES)中,它是一个基于Apache Lucene构建的开源、分布式、RESTful搜索引擎,旨在实时地存储、搜索和分析大量数据。Elasticsearch广泛应用于日志分析、全文搜索、实时分析等场景。下面我将介绍Elasticsearch的一些基本概念和功能:
1. 基本概念
-
索引(Index):在Elasticsearch中,索引类似于传统关系数据库中的“数据库”。它是存储相关文档(数据)的地方。
-
类型(Type):在Elasticsearch 7.x及以前版本中,每个索引可以包含多个类型。但从Elasticsearch 7.x开始,一个索引中只能有一个类型(默认为
_doc
),这一改动主要是因为Elasticsearch 8.x将完全废弃类型功能。 -
文档(Document):文档是Elasticsearch中最小的数据单元,可以是JSON格式的数据。每个文档都有一个唯一的ID。
-
字段(Field):文档由一个或多个字段组成,每个字段都有一个名称和一个值。
-
映射(Mapping):映射定义了索引中文档的结构,包括字段的类型、是否索引、是否存储等属性。
2. 核心功能
-
全文搜索:Elasticsearch提供了强大的全文搜索能力,支持模糊搜索、范围查询等。
-
实时性:数据输入Elasticsearch后即可被搜索,具有很高的实时性。
-
分布式特性:Elasticsearch可以分布式部署在多台服务器上,实现数据的分布式存储和查询,提高了系统的可扩展性和可靠性。
-
RESTful API:通过RESTful API可以方便地对Elasticsearch进行索引的创建、文档的增删改查等操作。
-
聚合(Aggregations):聚合允许你对数据进行复杂的分析,如分组统计、计算平均值、求和等。
-
多租户(Multi-tenancy):支持多租户模式,可以轻松地管理和隔离不同客户或项目的数据。
结语
Elasticsearch是一个功能强大且灵活的搜索引擎,适用于各种需要快速检索大量数据的场景。通过了解其基本概念和核心功能,你可以开始构建自己的搜索解决方案。随着不断学习和实践,你将能够充分利用Elasticsearch的潜力。
相关文章:
ElasticSearch公共方法封装
业务场景 1、RestClientBuilder初始化(同时支持单机与集群) 2、发送ES查询请求公共方法封装(支持sql、kql、代理访问、集群访问、鉴权支持) 3、判断ES索引是否存在(/_cat/indices/${indexName}) 4、判断ES…...
[Web 信息收集] Web 信息收集 — 手动收集 IP 信息
关注这个专栏的其他相关笔记:[Web 安全] Web 安全攻防 - 学习手册-CSDN博客 0x01:通过 DNS 服务获取域名对应 IP DNS 即域名系统,用于将域名与 IP 地址相互映射,方便用户访问互联网。对于域名到 IP 的转换过程则可以参考下面这篇…...
多源最短路径求解: Floyd-Warshall算法和Johnson 算法
多源最短路径问题是图论中的一个经典问题, 它要求找到图中所有顶点对之间的最短路径. 这个问题可以通过几种不同的算法来解决, 其中最为著名的包括 Floyd-Warshall Algorithm 和 Johnson’s Algorithm. Floyd-Warshall 算法 弗洛伊德-沃沙尔算法(Floyd-Warshall Algorithm) 是…...
解决IDEA使用Ctrl + / 注释不规范问题
问题描述: ctrl/ 时,注释缩进和代码规范不一致问题 解决方式 设置->编辑器->代码样式->java->代码生成->注释代码...
第9章 机器学习与统计模型
这一章重点探讨统计模型和机器学习模型,两个大的主题都建立在数据的基础之上,所以要熟练掌握对数据的处理与分析。实际上,机器学习本身就是统计模型的延伸,是在大数据背景下传统统计方法捉襟见肘了,所以才考虑引入机器…...
基于MATLAB的OFDM通信系统仿真设计
下面将为你详细介绍基于MATLAB的OFDM通信系统仿真设计的步骤和示例代码。 1. OFDM系统原理概述 正交频分复用(OFDM)是一种多载波调制技术,它将高速数据流通过串并转换,分配到多个正交的子载波上进行传输,这样可以有效…...
WebRTC学习七:WebRTC 中 STUN 协议详解
系列文章目录 第一篇 基于SRS 的 WebRTC 环境搭建 第二篇 基于SRS 实现RTSP接入与WebRTC播放 第三篇 centos下基于ZLMediaKit 的WebRTC 环境搭建 第四篇 WebRTC学习一:获取音频和视频设备 第五篇 WebRTC学习二:WebRTC音视频数据采集 第六篇 WebRTC学习三…...
力扣47. 全排列 II
思路 用 used 保存在一次答案中取过的数组索引。 先对数组进行排序,然后尝试取每个元素作为排列。 首先需要满足不重复取自己,即 !used.contains(i)。其次当前元素和前一个元素不同时可取,即 i 0 || nums[i] ! nums[i - 1]; 如…...
什么是将应用放在边缘服务器上创建?应用不是在用户手机上吗?边缘计算究竟如何优化?通过两个问题来辨析
元宇宙应用虽然可以在用户的手机等终端设备上运行,但大部分的计算和数据处理任务并不是完全在手机上完成的。元宇宙的运行需要庞大的计算资源和大量的数据交互,而这些是手机等终端设备难以独自承担的。因此,元宇宙应用需要借助边缘数据中心等…...
jmeter高级使用场景
JMeter 是一款功能强大的性能测试工具,除了基础的使用方法外,还有许多高级使用技巧,可帮助你更精准、高效地完成复杂的测试任务。以下为你详细介绍一些 JMeter 的高级使用方法: 分布式测试 当需要模拟大量并发用户来对系统进行压力测试时,单台机器的性能可能无法满足要求…...
智能升级、安全加倍,遨游防爆对讲机拉起通信安防线
在充斥着爆炸性气体和易燃物质的危险作业环境中,通信设备的选择关乎生命安全。一旦通信设备引发电火花,其后果将不堪设想。因此,专为防范易燃易爆环境而设计的防爆对讲机,凭借其独特的防爆技术和设计,成为了这些高风险…...
Flutter 上的 Platform 和 UI 线程合并是怎么回事?它会带来什么?
Flutter 在 3.29 发布了一个「重大」调整:从 3.29 开始,Android 和 iOS 上的 Flutter 将在应用的主线程上执行 Dart 代码,并且不再有单独的 Dart UI 线程 也许一些人对于这个概念还比较陌生,有时间可以看看以前发过的 《深入理解…...
IDEA关闭SpringBoot程序后仍然占用端口的排查与解决
IDEA关闭SpringBoot程序后仍然占用端口的排查与解决 问题描述 在使用 IntelliJ IDEA 开发 Spring Boot 应用时,有时即使关闭了应用,程序仍然占用端口(例如:4001 端口)。这会导致重新启动应用时出现端口被占用的错误&a…...
进程状态(R|S|D|t|T|X|Z)、僵尸进程及孤儿进程
文章目录 一.进程状态进程排队状态:运行、阻塞、挂起 二.Linux下的进程状态R 运行状态(running)S 睡眠状态(sleeping)D 磁盘休眠状态(Disk sleep)t 停止、暂停状态(tracing stopped)T 停止、暂停状态(stopp…...
Docker 搭建 Gitlab 服务器 (完整详细版)
参考 Docker 搭建 Gitlab 服务器 (完整详细版)_docker gitlab-CSDN博客 Docker 安装 (完整详细版)_docker安装-CSDN博客 Docker 日常命令大全(完整详细版)_docker命令-CSDN博客 1、Gitlab镜像 # 查找Gitlab镜像 docker search gitlab # 拉取Gitlab镜像 docker pull gitlab/g…...
Elasticsearch:使用经过训练的 ML 模型理解稀疏向量嵌入
作者:来自 Elastic Dai Sugimori 了解稀疏向量嵌入,理解它们的作用/含义,以及如何使用它们实现语义搜索。 Elasticsearch 提供语义搜索功能,允许用户使用自然语言进行查询并检索相关信息。为此,目标文档和查询必须首先…...
huggingface部署本地大模型DeepSeek-R1-Distill-Llama-70B使用streamlit构建交互式 Web 应用
文章目录 一、Streamlit介绍二、模型下载三 、模型部署四、效果展示 一、Streamlit介绍 Streamlit 是一个开源的 Python 库,专门用于快速构建和部署交互式 Web 应用程序,尤其适合数据科学和机器学习领域。以下是关于 Streamlit 的详细介绍: …...
中华人民共和国著作权法
目录 中华人民共和国著作权法 第一章 总则 第二章 著作权 第一节 著作权人及其权利 第二节 著作权归属 第三节 权利的保护期 第四节 权利的限制 第三章 著作权许可使用和转让合同 第四章 与著作权有关的权利 第一节 图书、报刊的出版 第二节 表 演 第…...
Maven 从下载到实战:一站式配置与使用指南
一、Maven 简介 Maven 是一款基于 POM(Project Object Model) 的 Java 项目管理工具,支持依赖管理、构建自动化、标准化项目结构等功能。其核心优势包括: 依赖管理:自动下载和管理第三方库(JAR 包…...
4部署kibana:5601
kibana 是一个基于浏览器页面的Elasticsearch前端展示工具,, 是一个开源和免费的工具 Kibana可以为 Logstash 和 ElasticSearch 提供的日志分析友好的 Web 界面, 可以帮你汇总、分析和搜索重要数据日志 1.安装-所有的es节点 # tar xf kibana-6.4.1-linux-x86_64.t…...
前端项目配置 Nginx 全攻略
在前端开发中,项目开发完成后,如何高效、稳定地将其部署到生产环境是至关重要的一步。Nginx 作为一款轻量级、高性能的 Web 服务器和反向代理服务器,凭借其出色的性能和丰富的功能,成为了前端项目部署的首选方案。本文将详细介绍在…...
Nmap网络安全审计
🍅 点击文末小卡片 ,免费获取网络安全全套资料,资料在手,涨薪更快 Nmap网络安全审计 什么是Nmap Nmap是由Gordon Lyon设计并实现的,于1997开始发布。最初设计Nmap的目的只是希望打造一款强大的端口扫描工具。但是随着…...
deepseek sse流式输出
链接 semi-ui-vue聊天组件 - 可以用这个组件优化界面 sse服务端消息推送 webflux&webclient Hi-Dream-Blog - 参考这个博客,可以在后台将markdown语法转为html 文章目录 链接效果代码pom.xmlDeepSeekControllerWebConfigDeepSeekClientAiChatRequestAiChatM…...
opencv(6): 形态学操作(二值化、自适应阈值、开闭、对比度)
如何在图片中识别出一些物体的位置。具体是什么不是形态学的范畴。 处理方法基本是对二进制图像进行处理。 卷积核决定着图像处理后的效果。 图像二值化 将图像的每个像素变成两种值, 如 0, 255。 全局二值化:全局按照某个阈值二值化 局部…...
P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值--完全 “二叉树” 不一定是 “满二叉树”
P8681 [蓝桥杯 2019 省 AB] 完全二叉树的权值 题目分析代码 题目 分析 我吧完全二叉树记成满二叉树了^^ 又卡我几分钟 代码 #include <iostream> #include <vector> #include <string> #include <algorithm> #include <math.h> #include <qu…...
Python驱动的餐饮企业智能数据分析:从数据清洗到可视化决策全流程实战
文章目录 Python驱动的餐饮企业智能数据分析:从数据清洗到可视化决策全流程实战引言一、案例背景1.1 需求分析1.2 数据准备1.2.1 模拟数据生成代码二、数据处理全流程2.1 数据清洗2.1.1 缺失值处理2.1.2 异常值检测2.2 核心指标计算2.2.1 营业额分析2.2.2 门店表现评估2.2.3 菜…...
深入理解IP子网掩码子网划分{作用} 以及 不同网段之间的ping的原理 以及子网掩码的区域划分
目录 子网掩码详解 子网掩码定义 子网掩码进一步解释 子网掩码的作用 计算总结表 子网掩码计算 子网掩码对应IP数量计算 判断IP是否在同一网段 1. 计算步骤 2. 示例 3. 关键点 总结 不同网段通信原理与Ping流程 1. 同网段通信 2. 跨网段通信 网段计算示例 3. P…...
Rust 中的内部可变性与 `RefCell<T>`
一、为什么需要内部可变性? 通常,Rust 编译器通过静态分析确保: 同一时刻只能存在一个可变引用,或任意多个不可变引用;引用始终保持有效。 这种严格的借用规则使得许多内存错误在编译阶段就能被捕获,但也…...
Android Audio实战——音频相关基础概念(附)
Android Audio 开发其实就是媒体源数字化的过程,通过将声波波形信号通过 ADC 转换成计算机支持的二进制的过程叫做音频采样 (Audio Sampling)。采样 (Sampling) 的核心是把连续的模拟信号转换成离散的数字信号。 一、声音的属性 1、响度 (Loudness) 响度是指人类可以感知到的…...
【Java项目】基于Spring Boot的教师人事档案管理系统
【Java项目】基于Spring Boot的教师人事档案管理系统 技术简介:采用Java技术、Spring Boot框架、MySQL数据库等实现。 系统简介:此系统的功能分为教师和管理员模块: 1、教师后台功能模块包括:首页、个人中心、个人档案管理、奖惩信…...
MySQL 中表和视图的关系
MySQL 中表和视图的关系 在 MySQL 中,表(Table) 是数据库中的基本存储结构,实际存储数据。而 视图(View) 是基于表或其他视图的虚拟表,它不存储数据,而是存储一条 SQL 查询的定义&a…...
BigDecimal线上异常解决方案:避免科学计数法输出的坑
文章目录 问题背景为什么BigDecimal会输出科学计数法?线上异常场景场景1:数据传递异常场景2:日志记录异常场景3:数据存储异常 解决方案1. 使用toPlainString()方法2. 设置格式化输出3. 自定义工具类 代码示例总结 在Java开发中&am…...
网络运维学习笔记(DeepSeek优化版)004网工初级(HCIA-Datacom与CCNA-EI)Console管理台使用、登录认证、破解恢复密码
文章目录 Console管理台使用、登录认证、破解恢复密码一、Console管理台使用和登录认证1.1 思科设备配置1.1.1 基本配置流程1.1.2 验证配置 1.2 华为设备配置1.2.1 本地密码认证1.2.2 AAA认证配置 二、远程管理协议Telnet和SSH配置2.1 思科Telnet基本配置2.2 华为Telnet基本配置…...
vmware系统磁盘扩容
扩展磁盘 关闭系统 编辑虚拟机设置,点击磁盘进行扩展 若无法点击检查是否有快照,若报错“在部分链上无法执行所调用的函数,请打开父虚拟磁盘”可查看解决方案 内部挂载 扩展分区 fdisk /dev/sda输入p,打印当前分区表删除/dev/…...
数据结构(陈越,何钦铭) 第四讲 树(中)
4.1 二叉搜索树 4.1.1 二叉搜索树及查找 Position Find(ElementTyoe X,BinTree BST){if(!BST){return NULL;}if(X>BST->Data){return Find(X,BST->Right)}else if(X<BST->Data){return Find(X,BST->Left)}else{return BST;} } Position IterFind(ElementTyp…...
OpenGL进阶系列19 - OpenGL SuperBible - basicfbo 例子学习
一:概述 在超级宝典之前的例子中,程序执行的所有渲染操作都是针对一个窗口,或者可能是计算机的主显示屏。片元着色器(fragment shader)的输出进入后台缓冲区(back buffer),而这个缓冲区通常由操作系统或窗口系统管理,并最终显示给用户。 当我们为渲染上下文选择格式时…...
猿大师播放器:交通水利、公安消防Web端Vue网页播放20路RTSP H.265 1080P监控视频流
随着互联网技术的飞速发展,视频监控已成为各行各业不可或缺的一部分。无论是交通物流、公安消防,还是水利农业、园区校园,视频监控都扮演着至关重要的角色。然而,传统的视频监控解决方案往往依赖于特定的客户端软件,这…...
文件下载技术的终极选择:`<a>` 标签 vs File Saver.js
文件下载技术的终极选择:<a> 标签 vs File Saver.js 在 Web 开发中,文件下载看似简单,实则暗藏玄机。工作种常纠结于 <a> 标签的原生下载和 File Saver.js 等插件的灵活控制之间。本文将从原理、优缺点、场景对比到实战技巧&…...
IDE(集成开发环境)
IDE(集成开发环境) 1. IDE 的定义 全称:Integrated Development Environment(集成开发环境)。中文:集成开发环境。作用:为程序开发提供全面的开发环境,集成了多种工具和服务&#x…...
数据安全_笔记系列02:国密算法(商用密码算法)详解
数据安全_笔记系列02:国密算法(商用密码算法)详解 国密算法是中国国家密码管理局(现国家密码管理局)制定的一系列自主可控的密码算法标准,旨在保障国内信息安全,满足合规要求。以下从 算法类型、技术细节、…...
全面汇总windows进程通信(三)
在Windows操作系统下,实现进程间通信(IPC, Inter-Process Communication)有几种常见的方法,包括使用管道(Pipe)、共享内存(Shared Memory)、消息队列(Message Queue)、命名管道(Named Pipe)、套接字(Socket)等。本文介绍如下几种: RPC(远程过程调用,Remote Pr…...
Python爬虫-破解字体加密技术
前言 本文是该专栏的第77篇,后面会持续分享python爬虫干货知识,记得关注。 字体加密是一种常见的反爬虫技术,通过自定义字体文件和字符映射来保护网页内容,防止爬虫直接获取文本信息。 而本文,笔者将针对“如何解决目标平台的字体加密技术,并获取目标数据”,进行详细介…...
Pytorch实现论文:基于多尺度融合生成对抗网络的水下图像增强
简介 简介:提出了一种新型的水下图像增强算法,基于多尺度融合生成对抗网络,名为UMSGAN,以解决低对比度和颜色失真的问题。首先经过亮度的处理,将处理后的图像输入设计的MFFEM模块和RM模块生成图像。该算法旨在适应各种水下场景,提供颜色校正和细节增强。 论文题目:Und…...
【Python量化金融实战】-第1章:Python量化金融概述:1.1量化金融的定义与发展历程
本小节学习建议:掌握Python编程、统计学(时间序列分析)、金融学基础(资产定价理论)三者结合,是进入量化领域的核心路径。 👉 点击关注不迷路 👉 点击关注不迷路 文章目录 1.1 量化金…...
大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(3)
Paimon的下载及安装,并且了解了主键表的引擎以及changelog-producer的含义参考: 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(1) 利用Paimon表做lookup join,集成mysql cdc等参考: 大数据组件(四)快速入门实时数据…...
【论文解读】《Training Large Language Models to Reason in a Continuous Latent Space》
论文链接 1. 背景与动机 语言空间与推理的矛盾 目前大多数大语言模型(LLMs)在解决复杂问题时采用链式思维(Chain-of-Thought, CoT)方法,即利用自然语言逐步推导出答案。然而,论文指出: 自然语言…...
Linux-CentOS 7安装
Centos 7镜像:https://pan.baidu.com/s/1fkQHYT64RMFRGLZy1xnSWw 提取码: q2w2 VMware Workstation:https://pan.baidu.com/s/1JnRcDBIIOWGf6FnGY_0LgA 提取码: w2e2 1、打开vmware workstation 2、选择主界面的"创建新的虚拟机"或者点击左上…...
【Web RCE 漏洞常见类型】
Web RCE 漏洞常见类型 1. 注入类漏洞2. 反序列化漏洞3. 文件处理漏洞4. 模板引擎漏洞5. 服务端请求伪造(SSRF)6. 框架/中间件漏洞7. 第三方组件漏洞8. 配置不当与协议滥用9. 其他边缘场景防御建议 以下是可以导致远程代码执行(RCE)…...
【蓝桥杯单片机】第十三届省赛第二场
一、真题 二、模块构建 1.编写初始化函数(init.c) void Cls_Peripheral(void); 关闭led led对应的锁存器由Y4C控制关闭蜂鸣器和继电器 2.编写LED函数(led.c) void Led_Disp(unsigned char ucLed); 将ucLed取反的值赋给P0 开启锁存器 关闭锁存…...
【够用就好006】-PC桌面管理ECS服务器的实操步骤
背景介绍解决思路拓展知识 背景介绍 #够用就好#知其然知其所以然#aigc创意人左边 我计划搭建个人网站,计划格式化我的ECS服务器,但是里面有我之前的实践项目,我舍不得删除,我想要保存到本地。 通常我都是在vscode中用remotes ssh…...