springBoot上elasticsearch连接池设计(Jest)

本文介绍了一种通过定义连接池优化Elasticsearch搜索任务的方法,利用AOP监控ES请求,结合ThreadLocal和BlockingQueue确保多线程环境下的资源安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

开篇

  • 本篇文章是使用Jest的Api来接入elasticsearch,网上说的Jest不需要连接池,查看源码会发现,JestClient确实是有一个异步方法(executeAsync()),但是该方法会额外创建一个线程去执行搜索任务,有些类似OkHttp,在高并发高负载的系统上,就会额外的增加系统开销,上述仅个人想法。在这里,我讲述一种通过定义连接池的方式去优化我们的elasticsearch搜索任务。

思路

  • aop监控es搜索请求
    • before 通过holder取得connection
    • after 通过holder将该connection回收
  • ThreadLocal 来线程绑定connection
相关类的作用
  • aop
    • EsClientAop
    • EsJestClientAnn
  • holder
    • EsClientHolder
  • pool
    • EsClientPool

代码逻辑

EsClientPool

该类采用固定长度的方式,在类加载的时候就初始化了设定的连接数,这里通过BlockingQueue阻塞队列的生产-消费方式来保证多线程环境下的安全。其中Factory和Queue都是单利模式,get和remove都是包级别的访问权限。

/**
 * Created by yzz on 2018/9/15.
 */
public class EsClientPool {
    //阻塞队列
    private static BlockingQueue<JestClient> queue;
    //用户自定义配置的Bean
    private static EsConfigVO esConfigVO;
    //Jest工厂,这里是单利工厂
    private static JestClientFactory factory;

    //保证factory queue 的单利
    static {
        esConfigVO = (EsConfigVO) ApplicationHelper.getBeanByClass(EsConfigVO.class);
        queue = new ArrayBlockingQueue<>(esConfigVO.getMaxConnection());
        factory = new JestClientFactory();
        init();
    }
    //初始化工厂
    private static void init(){
        try {
            //初始化工厂
            Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create();
            HttpClientConfig clientConfig = new HttpClientConfig
                    .Builder(esConfigVO.getUrl())
                    .gson(gson)
                    .build();
            factory.setHttpClientConfig(clientConfig);
            //初始化阻塞队列
            //初始化连接
            initQueue();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化连接
     * @throws InterruptedException
     */
     static void initQueue() throws InterruptedException {
        int i = 0;
        int maxConnection = esConfigVO.getMaxConnection();
        while(i<maxConnection){
            JestClient client = factory.getObject();
            queue.put(client);
            i++;
        }
         System.err.println("es 初始化完成");
    }

    static JestClient get() throws InterruptedException {
        return queue.poll(1, TimeUnit.SECONDS);
    }

    static void remove(JestClient jestClient) throws InterruptedException {
        queue.put(jestClient);
    }

}
EsClientHolder

通过ThreadLocal来保证线程局部变量,实现多线程下的资源安全。该holder类和EsClientPool 在同一个包下。

/**
 * Created by yzz on 2018/9/15.
 */
public class EsClientHolder {

    private static ThreadLocal<JestClient> threadLocal = new ThreadLocal<>();

    static void set(){
        try {
            JestClient jestClient = EsClientPool.get();
            threadLocal.set(jestClient);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static JestClient get() throws Exception {
        JestClient client = threadLocal.get();
        System.out.println("当前线程:"+Thread.currentThread().getName()+" 当前ES:"+client);
        if (null == client) {
            throw new Exception("please try again !");
        }
        return client;
    }

    static void remove(){
        try {
            JestClient client = threadLocal.get();
            EsClientPool.remove(client);
            System.out.println("当前线程:"+Thread.currentThread().getName()+" 回收ES:"+client);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
AOP

通过对特定注解的监控,来做到在es搜索执行前后对connection做线程绑定和回收

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EsJestClientAnn {

}
@Component
@Aspect
@Order(-100)
public class EsClientAop {

    @Pointcut(value = "execution(public * com.yzz.boot.es.service ..*.*(..))")
    public void defaultJestClient(){}

    @Before(value = "defaultJestClient()&&@annotation(com.yzz.boot.es.util.EsJestClientAnn)")
    public void initClient(){
        EsClientHolder.set();
    }

    @After(value = "defaultJestClient()&&@annotation(com.yzz.boot.es.util.EsJestClientAnn)")
    public void recycle(){
        EsClientHolder.remove();
    }

}
EsConfigVO

该注解运用了springBoot的特性通过配置文件来填充该Bean。

@Component
@ConfigurationProperties(prefix = "es.config.common")
public class EsConfigVO {

    private String url;

    private int maxConnection = 10;

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public int getMaxConnection() {
        return maxConnection;
    }

    public void setMaxConnection(int maxConnection) {
        this.maxConnection = maxConnection;
    }
}
配置文件
es:
  config:
    common:
      url: http://192.168.1.12:9200
      maxConnection: 100
运用
@Service(value = "es_EsSearchService")
public class EsSearchService {

    @EsJestClientAnn
    public Object test() throws Exception {
        JestClient client = EsClientHolder.get();
        NodesStats nodesStats = new NodesStats.Builder().build();
        JestResult result = client.execute(nodesStats);
        return result.getJsonString();
    }
}

总结

在本次实践中,采用的pool方式是全部加载的方式,通过懒加载的方式会更好,下次会及时记录下来。该pool的设计优点是,在第一次访问的时候才会去初始化,优点懒加载的意思,也会避免es服务未启动导致项目启动失败的问题。

<think>我们正在整合Spring Boot与Jest(一个基于HTTP的Elasticsearch客户端)。根据用户需求,我们需要在Spring Boot项目中集成Jest客户端来操作Elasticsearch。 步骤: 1. 添加依赖:在pom.xml中添加Jest的依赖。 2. 配置Jest:在application.properties或application.yml中配置Elasticsearch的连接信息。 3. 创建Jest客户端:通过配置类或直接注入JestClientFactory来创建JestClient。 4. 使用JestClient:在需要的地方注入JestClient,执行索引、搜索等操作。 5. 关闭客户端:在应用关闭时关闭JestClient。 注意:Jest是一个独立的Elasticsearch客户端,不依赖于Spring Data Elasticsearch,因此我们不需要Spring Data Elasticsearch的配置。 具体步骤: 1. 添加依赖: 在pom.xml中添加以下依赖(注意版本匹配,特别是Jest版本与Elasticsearch版本的兼容性): ```xml <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>6.3.1</version> <!-- 请根据你的Elasticsearch版本选择 --> </dependency> ``` 2. 配置连接信息(以application.properties为例): ```properties # Elasticsearch服务器地址,多个用逗号分隔 spring.elasticsearch.jest.uris=http://localhost:9200 # 如果需要用户名和密码(如果Elasticsearch有安全插件) spring.elasticsearch.jest.username=your_username spring.elasticsearch.jest.password=your_password # 连接超时时间(可选) spring.elasticsearch.jest.connection-timeout=3000 spring.elasticsearch.jest.read-timeout=3000 ``` 3. 创建JestClient的配置类: 我们可以创建一个配置类,用于读取配置并创建JestClient。 ```java import io.searchbox.client.JestClient; import io.searchbox.client.JestClientFactory; import io.searchbox.client.config.HttpClientConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class JestConfig { @Value("${spring.elasticsearch.jest.uris}") private String[] uris; @Value("${spring.elasticsearch.jest.username:#{null}}") private String username; @Value("${spring.elasticsearch.jest.password:#{null}}") private String password; @Value("${spring.elasticsearch.jest.connection-timeout:3000}") private Integer connectionTimeout; @Value("${spring.elasticsearch.jest.read-timeout:3000}") private Integer readTimeout; @Bean public JestClient jestClient() { HttpClientConfig.Builder builder = new HttpClientConfig.Builder(Arrays.asList(uris)) .connTimeout(connectionTimeout) .readTimeout(readTimeout); // 如果配置了用户名和密码,则设置认证信息 if (username != null && password != null) { builder.defaultCredentials(username, password); } HttpClientConfig httpClientConfig = builder.build(); JestClientFactory factory = new JestClientFactory(); factory.setHttpClientConfig(httpClientConfig); return factory.getObject(); } } ``` 4. 使用JestClient: 在Service中注入JestClient,然后使用它执行操作。例如,索引一个文档: ```java import io.searchbox.client.JestClient; import io.searchbox.core.Index; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class ElasticsearchService { @Autowired private JestClient jestClient; public void indexDocument(String indexName, String type, String id, Object source) throws IOException { Index index = new Index.Builder(source).index(indexName).type(type).id(id).build(); jestClient.execute(index); } } ``` 5. 关闭客户端: 由于JestClient实现了Closeable接口,Spring会在应用关闭时自动关闭它(通过@Bean的destroyMethod)。但是,为了确保安全,我们可以在配置类中明确指定销毁方法: ```java @Bean(destroyMethod = "close") // 这样Spring会在上下文关闭时调用jestClient.close() public JestClient jestClient() { // ... 同上 } ``` 注意:在实际使用中,请根据实际情况调整配置和异常处理。 引用说明: 在Spring Boot中整合Jest客户端,我们主要参考了Jest的官方文档以及Spring Boot的外部化配置特性[^1][^2]。同时,我们注意到在配置中,我们使用了Spring的@Value注解来获取配置信息,这样能够灵活地适应不同的环境。 相关问题: 1. 如何通过Jest执行复杂的Elasticsearch查询? 2. Jest客户端与Spring Data Elasticsearch的主要区别是什么? 3. 在Spring Boot中如何配置Jest客户端使用HTTPS连接Elasticsearch? 4. 如何处理Jest操作Elasticsearch时可能出现的异常?</think>### Spring Boot整合Jest客户端指南 Jest是一个基于HTTP的Elasticsearch Java客户端,与Spring Boot集成可实现高效的ES操作。以下是整合步骤: #### 1. 添加依赖 在`pom.xml`中添加Jest依赖: ```xml <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>6.3.1</version> <!-- 匹配您的ES版本 --> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> ``` #### 2. 配置连接参数 在`application.properties`中配置: ```properties # ES集群节点 (逗号分隔) spring.elasticsearch.jest.uris=http://localhost:9200 # 连接超时(毫秒) spring.elasticsearch.jest.connection-timeout=3000 # 读取超时(毫秒) spring.elasticsearch.jest.read-timeout=5000 ``` #### 3. 创建Jest配置类 ```java @Configuration public class JestConfig { @Value("${spring.elasticsearch.jest.uris}") private String[] uris; @Bean public JestClient jestClient() { HttpClientConfig config = new HttpClientConfig.Builder( Arrays.asList(uris) // ES节点列表 ).connTimeout(3000) // 连接超时 .readTimeout(5000) // 读取超时 .build(); JestClientFactory factory = new JestClientFactory(); factory.setHttpClientConfig(config); return factory.getObject(); } } ``` #### 4. 使用JestClient执行操作 ```java @Service public class SearchService { @Autowired private JestClient jestClient; // 索引文档示例 public void indexDocument(String index, String type, String id, Object source) throws IOException { Index indexAction = new Index.Builder(source) .index(index) .type(type) .id(id) .build(); jestClient.execute(indexAction); } // 搜索文档示例 public List<Customer> searchCustomers(String query) throws IOException { Search search = new Search.Builder(query) .addIndex("customer_index") .build(); SearchResult result = jestClient.execute(search); return result.getSourceAsObjectList(Customer.class); } } ``` #### 5. 注意事项 1. **版本兼容性**:确保Jest版本与Elasticsearch版本匹配 2. **连接池管理**:Jest默认使用连接池,无需额外配置 3. **异常处理**:建议使用`try-catch`处理`IOException` 4. **关闭客户端**:Spring Boot会自动管理Bean生命周期 #### 6. 与Spring Data ES对比 | 特性 | Jest | Spring Data ES | |--------------|--------------------------|-------------------------| | 连接方式 | HTTP | 传输层(TCP) | | 配置复杂度 | 简单 | 中等 | | 查询灵活性 | 原生JSON查询 | 方法命名/QueryBuilder | | 版本兼容性 | 依赖明确版本匹配 | 与Spring Boot版本绑定 | > 提示:对于简单查询推荐使用Spring Data的Repository抽象,复杂查询建议直接使用Jest的DSL[^3]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值