Elasticsearch+Dubbo+Spring實踐

1. 需求

因為公司一直用的是阿里的dubbo作為業(yè)務(wù)工程的RPC通信框架,我現(xiàn)在的任務(wù)是在mentor的指導(dǎo)下嘗試重構(gòu)公司的NLP服務(wù),想把NLP的資源檢索,單輪對話,多輪對話這些功能模塊拆分成各自獨立的服務(wù)。第一個嘗試是優(yōu)化問答系統(tǒng)中信息檢索的過程,所以拿到的需求是調(diào)研用Elasticsearch來代替已有的資源檢索性能會不會更好。這樣也就產(chǎn)生了Elasticsearch+Dubbo+Spring這樣奇葩的組合方式,畢竟Elasticsearch提倡的是用RESTful API交互來簡化對數(shù)據(jù)的操作,而我要硬生生地用RPC的方式去調(diào)用。


想想都刺激

文章結(jié)構(gòu)

  1. 需求
  2. Dubbo框架搭建
  3. Elasticsearch功能添加
  4. 源碼

2. Dubbo框架搭建

環(huán)境準(zhǔn)備

  • Zookeeper注冊中心安裝,下載。
解壓
cd your_zookeeper_path/conf
cp zoo_sample.cfg zoo.cfg
vi zoo.cfg

根據(jù)自己的需要修改配置文件,參見,這里Zookeeper的端口號采用默認(rèn)配置2181。

  • Zookeeper啟動:
cd your_zookeeper_path
./bin/zkServer.sh start
  • Zookeeper停止:
cd your_zookeeper_path
./bin/zkServer.sh stop

服務(wù)提供者Demo

  1. 在項目中添加Spring,Dubbo,Zookeeper的依賴:
<dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <classifier>jdk15</classifier>
            <version>2.4</version>
        </dependency>
        <!-- dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.3</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- zkclient  -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!--  zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.3.6</version>
        </dependency>
    </dependencies>
  1. 新建服務(wù)提供者對外暴露的接口類:
public interface SearchService {
    public String sayHello(String name);
}
  1. 實現(xiàn)服務(wù)的接口:
public class SearchServiceImpl implements SearchService{
    public String sayHello(String name) {
        System.out.println("received from remote: "+name);
        return "Hello " + name;
    }
}
  1. 用 Spring 配置聲明暴露服務(wù):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <!-- 提供方應(yīng)用信息,用于計算依賴關(guān)系 -->
    <dubbo:application name="elastic-search"/>

    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />

    <!-- 用dubbo協(xié)議在20880端口暴露服務(wù) -->
    <dubbo:protocol name="dubbo" port="20880"/>

    <!-- 聲明需要暴露的服務(wù)接口 -->
    <dubbo:service interface="com.yyz.elasticsearch.SearchService" ref="demoService" />

    <!-- 和本地bean一樣實現(xiàn)服務(wù) -->
    <bean id="demoService" class="com.yyz.elasticsearch.SearchServiceImpl" />

</beans>
  1. 啟動服務(wù)提供者:
public class Starter {
    public static void main(String[] args) throws IOException {
        System.setProperty("java.net.preferIPv4Stack", "true");
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"server.xml"});
        context.start();
        System.in.read(); 
    }
}

服務(wù)消費者Demo

  1. 在pom.xml中添加spring,dubbo,zookeeper的依賴:
<dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>1.5.1.RELEASE</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <classifier>jdk15</classifier>
            <version>2.4</version>
        </dependency>
        <!-- dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.3</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- zkclient  -->
        <dependency>
            <groupId>com.github.sgroschupf</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.1</version>
        </dependency>
        <!--  zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.3.6</version>
        </dependency>
    </dependencies>

2.通過 Spring 配置引用遠(yuǎn)程服務(wù):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        ">
    <!-- 消費方應(yīng)用名,用于計算依賴關(guān)系,不是匹配條件,不要與提供方一樣 -->
    <dubbo:application name="demo-consumer"/>

    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />
    
    <!-- 生成遠(yuǎn)程服務(wù)代理,可以和本地bean一樣使用demoService -->
    <dubbo:reference id="demoService" check="false" interface="com.yyz.elasticsearch.SearchService"/>

</beans>
  1. 加載Spring配置,并調(diào)用遠(yuǎn)程服務(wù):
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Client {
    public static void main(String[] args) {
        String configName = "client.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{configName});
        context.start();
        SearchService searchService = (SearchService) context.getBean("demoService");

        while (true) {
            try {
                Thread.sleep(1000);
                // 執(zhí)行遠(yuǎn)程方法
                String hello = searchService.sayHello("world");
                // 顯示調(diào)用結(jié)果
                System.out.println( hello );
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }

    }
}

好啦,到這里用阿里的dubbo框架搭建的RPC通信的服務(wù)提供者和消費者已經(jīng)完成,可以運行起來看看效果,記得先啟動zookeeper注冊中心。

3. Elasticsearch功能添加

下面我們往上面的Demo中添加操作Elasticsearch分布式搜索分析引擎的功能。

Elasticsearch安裝

Elasticsearch是免安裝的,下載后解壓就好。關(guān)于下載版本的問題,我想發(fā)一個表情。


此處有好大一個坑。
sping data elasticsearch和elasticsearch的版本對應(yīng)關(guān)系

因為一開始并不知道Spring data和elasticsearch之間有嚴(yán)格的版本對應(yīng)關(guān)系,所以出了一堆莫名其妙的bug,這張對應(yīng)表可以在這里看,雖然我下面用的是spring boot,但是同樣有這個問題。
建議下載Elasticsearch2.4.0,下載后解壓就算是安裝完成了。
修改配置文件:

cd your_Elasticsearch
vi config/elasticsearch.yml

默認(rèn)的配置,elasticsearch使用的HTTP端口是9200,TCP端口是9300.
啟動Elasticsearch:

cd your_Elasticsearch/bin
./elasticsearch

Server Demo

  1. pom.xml中添加elasticsearch的依賴
        <!--  elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <version>2.0.1.RELEASE</version>
        </dependency>

這時會產(chǎn)生netty包的沖突,解決:

        <!-- dubbo -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.5.3</version>
            <exclusions>
                <exclusion>
                    <artifactId>spring</artifactId>
                    <groupId>org.springframework</groupId>
                </exclusion>
                <exclusion>
                    <artifactId>netty</artifactId>
                    <groupId>org.jboss.netty</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--  zookeeper -->
        <dependency>
            <groupId>org.apache.zookeeper</groupId>
            <artifactId>zookeeper</artifactId>
            <version>3.3.6</version>
            <exclusions>
                <exclusion>
                    <artifactId>netty</artifactId>
                    <groupId>io.netty</groupId>
                </exclusion>
            </exclusions>
        </dependency>
  1. 新建實體類:
@Document(indexName = "dialog", type = "qa", shards = 1, replicas = 0)
public class QA {
    @Id
    private int id;

    private String Q;

    @Field(type = FieldType.Nested)
    private List<String> A;

    public QA() {
    }

    public QA(int id, String q, List<String> a) {
        this.id = id;
        Q = q;
        A = a;
    }

    public int getId() {
        return id;
    }

    public String getQ() {
        return Q;
    }

    public void setQ(String q) {
        Q = q;
    }

    public List<String> getA() {
        return A;
    }

    public void setA(List<String> a) {
        A = a;
    }

    @Override
    public String toString() {
        return  " QA { " +
                "   id = " + id + "," +
                "   Q = '" + Q + "'," +
                "   A = " + A +
                '}';
    }
}
  1. 新建Repository類:
public interface QARepository extends ElasticsearchRepository<QA,Long> {
}
  1. 修改server的接口類,添加操作elasticsearch的方法:
public interface SearchService {
    public String sayHello(String name);
    public String search(final String query);
}
  1. 修改service的實現(xiàn),實現(xiàn)操作elasticsearch的方法:
@Service("demoService")
public class SearchServiceImpl implements SearchService {
    @Autowired
    private Client client;
    @Autowired
    private QARepository qARepository;
    public String sayHello(String name) {
        System.out.println("received from remote: "+name);
        return "Hello " + name;
    }

    /**
     * 插入一條數(shù)據(jù)
     * @param qa
     */
    public void addEntity(QA qa) {
        qARepository.save(qa);
    }

    /**
     * 查詢
     * @param query
     * @return
     */
    public String search(final String query) {

        System.out.println("query: " + query);

        QueryBuilder queryBuilder = QueryBuilders.matchQuery("q", query);

        SearchResponse response = client.prepareSearch()
                .setQuery(queryBuilder)
                .addHighlightedField("Q")
                .execute().actionGet();

        SearchHit[] searchHitArr = response.getHits().getHits();
        /**
         * 遍歷檢索到的結(jié)果,這里可以自定義排序算法
         */
        for (int i = 0; i < searchHitArr.length; ++i) {
            SearchHit searchHit = searchHitArr[i];
            System.out.print("index:"+searchHit.index()+" type:"+searchHit.getType()+" id: "+searchHit.getId()+" q:"+(String)searchHit.getSource().get("q"));
            List l  = (List)searchHit.getSource().get("a");
            for (int j = 0;j<l.size();j++){
                System.out.print(" a: "+l.get(j));
            }
            System.out.println("");
        }
        /**
         * 返回第一個相關(guān)文檔
         */
        String result = null;
        if(searchHitArr.length>0){
            SearchHit searchHit = searchHitArr[0];
            result = "index:"+searchHit.index()+" type:"+searchHit.getType()+" id: "+searchHit.getId()+" q:"+(String)searchHit.getSource().get("q");
            List l  = (List)searchHit.getSource().get("a");
            for (int j = 0;j<l.size();j++){
                result = result+ " a: "+l.get(j);
            }
        }
        return result;
    }
}

注意最上面的注解@service ,因為client和qARepository需要spring自動搜索注入。所以改成了注解的方式,不再在配置文件中顯示的配置。

  1. spring配置文件的改變:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:elasticsearch="http://www.springframework.org/schema/data/elasticsearch"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo
        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
        http://www.springframework.org/schema/data/elasticsearch
         http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch-1.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>

    <!-- 提供方應(yīng)用信息,用于計算依賴關(guān)系 -->
    <dubbo:application name="elastic-search"/>
    <!-- 配置注冊中心 -->
    <dubbo:registry protocol="zookeeper" address="127.0.0.1:2181" />
    <!-- 用dubbo協(xié)議在20880端口暴露服務(wù) -->
    <dubbo:protocol name="dubbo" port="20880"/>
    <!-- 配置elasticsearch 連接 -->
    <elasticsearch:transport-client id="client" cluster-nodes="127.0.0.1:9300" cluster-name="elasticsearch"/>
    <!-- spring data elasticsearch DAO 必須依賴 elasticsearchTemplate  -->
    <bean id="elasticsearchTemplate" class="org.springframework.data.elasticsearch.core.ElasticsearchTemplate">
        <constructor-arg name="client" ref="client" />
    </bean>
    <!-- 掃描DAO包 自動創(chuàng)建實現(xiàn) -->
    <elasticsearch:repositories base-package="com.yyz.elasticsearch.dao" />
    <!-- 掃描Service包 -->
    <context:component-scan base-package="com.yyz.elasticsearch.service" />
    <!-- 聲明需要暴露的服務(wù)接口 -->
    <dubbo:service interface="com.yyz.elasticsearch.SearchService" ref="demoService" />

</beans>

Client Demo

客戶端需要改變的地方很小,只需要改變調(diào)用的遠(yuǎn)程方法即可。

public interface SearchService {
    public String sayHello(String name);
    public String search(final String query);
}
public class Client {
    public static void main(String[] args) {
        String configName = "client.xml";
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{configName});
        context.start();
        SearchService searchService = (SearchService) context.getBean("demoService");

        while (true) {
            try {
                Thread.sleep(1000);
                // 執(zhí)行遠(yuǎn)程方法
                String hello = searchService.search("你怕黑嗎?");
                // 顯示調(diào)用結(jié)果
                System.out.println( hello );
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
    }
}

記得測試之前先啟動Elasticsearch。

4. 源碼

Demo已經(jīng)上傳到github,點擊ElasticsearchDemo查看。
這篇文章沒有詳細(xì)介紹導(dǎo)入數(shù)據(jù)的部分,可直接在命令行用Elasticsearch的語法插入數(shù)據(jù),也可以先用SearchServiceImpl的addEntity方法插入數(shù)據(jù),下一篇文章會介紹Elasticsearch如何批量插入數(shù)據(jù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 0 準(zhǔn)備 安裝注冊中心:Zookeeper、Dubbox自帶的dubbo-registry-simple;安裝Du...
    七寸知架構(gòu)閱讀 14,105評論 0 88
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,253評論 6 342
  • 一般和人告別我習(xí)慣用拜拜,很少用再見,因為我感覺,分別之后,可能就真的再難相見,為什么還要安慰自己說會再見哪?——...
    一杭o(jì)neline閱讀 326評論 0 1
  • 物理工程學(xué)院2016級物理學(xué)王曉 我不敢說生命是什么,我只能說生命像什么。 生命,就像,一列路途遙遠(yuǎn)的班車。有人上...
    曉love閱讀 377評論 0 0

友情鏈接更多精彩內(nèi)容