当前位置:   article > 正文

ES+HBase【案例】仿百度搜索04:开发仿百度搜索项目_如何实现类似百度的搜索引擎

如何实现类似百度的搜索引擎

一、介绍

这个搜索引擎项目主要涉及到数据采集、数据存储、建立索引和数据展现环节。

针对一个搜索引擎项目而言,它的数据基本上都是来源于互联网上的公开数据,想要获取这些数据就需要使用爬虫工具了,目前市面上有一些爬虫产品,但是在使用的时候基本上都需要二次开发,所以企业里面都会有专门的爬虫工程师负责这个工作。

我们在开发这个搜索引擎项目的时候就不再针对爬虫数据采集模块进行扩展了,到时候我会提供一个数据接口,大家通过接口可以直接获取到一些互联网上的公开数据。

1、数据格式

数据大致格式是这样的:

在这里插入图片描述
我们在开发这个搜索引擎项目的时候会重点实现数据存储、建立索引这两个环节。

2、步骤

大致的开发步骤是这样的:
1:调用接口获取数据导入HBase和Redis(存储Rowkey)。
2:通过ES对HBase中的数据建立索引。
3:对接Web项目,提供页面检索功能。

3、查询要求

在具体开发项目之前,我们首先分析一下项目中的数据和具体的查询要求,这样就知道ES中索引库的mapping应该如何设计了。
在这个搜索引擎项目中,我们需要对爬虫采集到的文章数据在ES中建立索引。

由于文章数据的核心内容主要在标题、描述和正文这3个字段中,所以在查询的时候需要用到这3个字段。
那ES在返回满足条件的数据的时候,都需要返回哪些字段呢?
参考一下百度的结果列表界面,可以发现,百度的列表页面会显示标题、描述和作者信息。

在这里插入图片描述
那针对我们这里的文章数据,还有一个时间字段,其实也可以显示在列表页面中,具体显示哪些字段,可以根据工作中的具体需求来定。
在这里我们希望在列表页面显示的是文章的标题、作者、描述、时间这4个字段。

针对文章的ID,在这里直接作为ES中数据的ID,ES中的ID是必须要存储和建立索引的。

4、总结

所以最终总结一下是这样的:

在这里插入图片描述
1、文章ID:需要建立索引,并且存储,这是ES中ID字段必须要具备的特性。

2、标题:因为查询的时候会用到,所以需要建立索引,并且在返回结果列表信息的时候需要直接从ES中返回,所以需要存储。

3、作者:查询用不到,所以不需要建立索引,但是需要在返回结果列表信息的时候一块返回,所以需要存储。

4、描述:查询会用到,返回的结果列表信息中也有这个字段内容,所以需要建立索引,并且存储。

5、正文:因为查询的时候会用到,所以需要建立索引,但是在返回结果列表信息的时候不需要返回这个字段,所以不需要存储。其实还有一点很重要的原因是因为这个字段内容太长了,如果在ES中存储,会额外占用很多的存储空间,最终会影响ES的性能。

6、时间:查询用不到,所以不需要建立索引,但是需要在返回结果列表信息的时候一块返回,所以需要存储

5、指定索引库参数

接下来我们来手工指定一下索引库的settings和mapping参数。
由于这里的字段比较多,最好把settings和mapping信息写到一个文件中,使用起来比较方便。
创建一个文件:article.json,内容如下:

{
        "settings":{
                "number_of_shards":5,
                "number_of_replicas":1
        },
        "mappings":{
				"dynamic":"strict",
				"_source":{"excludes":["content"]},
				"properties":{
						"title":{"type":"text","analyzer":"ik_max_word"},
						"author":{"type":"text","index":false},
						"describe":{"type":"text","analyzer":"ik_max_word"},
						"content":{"type":"text","analyzer":"ik_max_word"},
						"time":{"type":"date","index":false,"format":"yyyy-MM-dd HH:mm:ss"}
				}
        }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

解释:
dynamic参数有4个选项值:

1、true是默认的,表示开启动态映射
2、false表示忽略没有定义的字段,
3、strict表示遇到未知字段时抛出异常
4、runtime表示遇到未知字段时将它作为运行时字段,运行时字段是在ES7.11版本中增加的,运行时字段不会被索引,但是可以从_source中获取运行时字段内容,所以runtime可以适合公共字段已知,并且想兼容未知扩展字段的场景。

在这里插入图片描述
dynamic具体选择哪个参数,就需要根据需求来定了,在这里不希望在ES中保存未知字段,所以使用strict。

将article.json上传到/data/soft/elasticsearch-7.13.4目录下

[root@bigdata01 elasticsearch-7.13.4]# ll article.json 
-rw-r--r--. 1 root root 534 Apr  2 17:44 article.json
  • 1
  • 2

接下来开始开发项目。

二、调用接口获取数据导入HBase和Redis

在Idea中直接打开已有项目:db_fullsearch,里面有一个web_fullsearch子model项目。

1、下载项目

项目下载地址:

链接:https://pan.baidu.com/s/1fe8u_hnAMrwYxD1tZ_mocQ?pwd=tya0 
提取码:tya0 
  • 1
  • 2

接着再创建一个子Model项目:data_manager
在子Model项目data_manager的pom.xml中添加此项目需要用到的依赖。

<dependencies>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-api</artifactId>
	</dependency>
	<dependency>
		<groupId>org.slf4j</groupId>
		<artifactId>slf4j-log4j12</artifactId>
	</dependency>
	<dependency>
		<groupId>com.alibaba</groupId>
		<artifactId>fastjson</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.httpcomponents</groupId>
		<artifactId>httpclient</artifactId>
	</dependency>
	<dependency>
		<groupId>org.apache.hbase</groupId>
		<artifactId>hbase-client</artifactId>
	</dependency>
	<dependency>
		<groupId>org.elasticsearch.client</groupId>
		<artifactId>elasticsearch-rest-high-level-client</artifactId>
	</dependency>
	<dependency>
		<groupId>redis.clients</groupId>
		<artifactId>jedis</artifactId>
	</dependency>
</dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在子Model项目data_manager中添加log4j.properties配置文件。

log4j.rootLogger=info,stdout

log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 
log4j.appender.stdout.layout.ConversionPattern=[%p] %m%n
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在子Model项目data_manager中创建package:com.imooc.core和com.imooc.utils
在com.imooc.utils包中引入工具类:HttpUtil、RedisUtil、HBaseUtil(开发)。
接着在com.imooc.core中创建类:DataImport,负责实现数据导入到HBase和Redis。

2、HBaseUtil代码如下

package com.imooc.utils;

import com.sun.org.apache.bcel.internal.generic.TABLESWITCH;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * HBase工具类
 * 
 */
public class HBaseUtil {
    private HBaseUtil(){}

    private static Connection conn = getConn();

    private static Connection getConn(){
        //获取hbase链接
        Configuration conf = new Configuration();
        //指定hbase使用的zk地址
        //注意:需要在执行hbase hava代码的机器上配置zk和hbase集群的主机名和ip的映射关系
        conf.set("hbase.zookeeper.quorum","bigdata01:2181");
        //指定hbase在hdfs上的根目录
        conf.set("hbase.rootdir","hdfs://bigdata01:9000/hbase");
        //创建HBase数据库链接
        Connection co = null;
        try{
            co = ConnectionFactory.createConnection(conf);
        }catch (IOException e){
            System.out.println("获取链接失败:"+e.getMessage());
        }
        return co;
    }

    /**
     * 对外提供的方法
     * @return
     */
    public static Connection getInstance(){
        return conn;
    }

    /**
     * 创建表
     * @param tableName
     * @param cfs
     */
    public static void createTable(String tableName,String... cfs) throws Exception {
        Admin admin = conn.getAdmin();
        ArrayList<ColumnFamilyDescriptor> cfArr = new ArrayList<ColumnFamilyDescriptor>();
        for (String cf : cfs) {
            ColumnFamilyDescriptor cfDesc = ColumnFamilyDescriptorBuilder
                    .newBuilder(Bytes.toBytes(cf))
                    .build();
            cfArr.add(cfDesc);
        }
        TableDescriptor tableDesc = TableDescriptorBuilder
                .newBuilder(TableName.valueOf(tableName))
                .setColumnFamilies(cfArr)
                .build();
        admin.createTable(tableDesc);
        admin.close();
    }

    /**
     * 添加一个单元格(列)的数据
     * @param tableName
     * @param rowKey
     * @param columnFamily
     * @param column
     * @param value
     * @throws Exception
     */
    public static void put2HBaseCell(String tableName,String rowKey,String columnFamily,String column,String value)throws Exception{
        Table table = conn.getTable(TableName.valueOf(tableName));
        Put put = new Put(Bytes.toBytes(rowKey));
        put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(column),Bytes.toBytes(value));
        table.put(put);
        table.close();
    }

    /**
     * 向hbase中添加一批数据
     * @param tableName
     * @param list
     * @throws Exception
     */
    public static void put2HBaseList(String tableName, List<Put> list)throws Exception{
        Table table = conn.getTable(TableName.valueOf(tableName));
        table.put(list);
        table.close();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

3、DataImport代码如下

注意修改校验码!!

package com.imooc.core;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.imooc.utils.HBaseUtil;
import com.imooc.utils.HttpUtil;
import com.imooc.utils.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

/**
 * 通过接口获取文章数据,入库HBase和Redis(Rowkey)
 *
 * 注意:HBase建表语句 create 'article','info'
 * 
 */
public class DataImport {
    private final static Logger logger = LoggerFactory.getLogger(DataImport.class);

    public static void main(String[] args) {
        //通过接口获取文章数据
        String dataUrl = "http://data.xuwei.tech/a1/wz1";
        JSONObject paramObj = new JSONObject();
        paramObj.put("code","JD3B37868104C5F2A");//校验码
        paramObj.put("num",100);//数据条数,默认返回100条,最大支持返回1000条
        JSONObject dataObj = HttpUtil.doPost(dataUrl, paramObj);
        boolean flag = dataObj.containsKey("error");
        if(!flag){
            JSONArray resArr = dataObj.getJSONArray("data");
            for(int i=0;i<resArr.size();i++){
                JSONObject jsonObj = resArr.getJSONObject(i);
                //System.out.println(jsonObj.toJSONString());
                //文章ID作为HBase的Rowkey和ES的ID
                String id = jsonObj.getString("id");
                String title = jsonObj.getString("title");
                String author = jsonObj.getString("author");
                String describe = jsonObj.getString("describe");
                String content = jsonObj.getString("content");
                String time = jsonObj.getString("time");
                Jedis jedis = null;
                try{
                    //将数据入库到HBase
                    String tableName = "article";
                    String cf = "info";
                    HBaseUtil.put2HBaseCell(tableName,id,cf,"title",title);
                    HBaseUtil.put2HBaseCell(tableName,id,cf,"author",author);
                    HBaseUtil.put2HBaseCell(tableName,id,cf,"describe",describe);
                    HBaseUtil.put2HBaseCell(tableName,id,cf,"content",content);
                    HBaseUtil.put2HBaseCell(tableName,id,cf,"time",time);
                    //将Rowkey保存到Redis中
                    jedis = RedisUtil.getJedis();
                    jedis.lpush("l_article_ids",id);
                }catch (Exception e){
                    //注意:由于hbase的put操作属于幂等操作,多次操作,对最终的结果没有影响,所以不需要额外处理
                    logger.error("数据添加失败:"+e.getMessage());
                }finally {
                    //向连接池返回连接
                    if(jedis!=null){
                        RedisUtil.returnResource(jedis);
                    }
                }

            }

        }else{
            logger.error("获取文章数据失败:"+dataObj.toJSONString());
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

三、通过ES对HBase中的数据建立索引

在com.imooc.core中创建类:DataIndex,负责实现在ES中对数据建立索引。
在开发DataIndex代码的时候,需要向Es中写入数据,所以最好封装一个EsUtil工具类。
同时需要对HBaseUtil工具类进行完善,增加一个getFromHBase方法,负责从HBase中获取数据。

1、EsUtil工具类代码如下

package com.imooc.utils;

import org.apache.http.HttpHost;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;

import java.io.IOException;
import java.util.Map;

/**
 * ES工具类
 * 
 */
public class EsUtil {
    private EsUtil(){}
    private static RestHighLevelClient client;
    static{
        //获取RestClient连接
        //注意:高级别客户端其实是对低级别客户端的代码进行了封装,所以连接池使用的是低级别客户端中的连接池
        client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("bigdata01",9200,"http"),
                        new HttpHost("bigdata01",9200,"http"),
                        new HttpHost("bigdata01",9200,"http"))
                        .setHttpClientConfigCallback(new RestClientBuilder.HttpClientConfigCallback() {
                            public HttpAsyncClientBuilder customizeHttpClient(HttpAsyncClientBuilder httpClientBuilder) {
                                return httpClientBuilder.setDefaultIOReactorConfig(
                                        IOReactorConfig.custom()
                                                //设置线程池中线程的数量,默认是1个,建议设置为和客户端机器可用CPU数量一致
                                                .setIoThreadCount(1)
                                                .build());
                            }
                        }));
    }

    /**
     * 获取客户端
     * @return
     */
    public static RestHighLevelClient getRestClient(){
        return client;
    }

    /**
     * 关闭客户端
     * 注意:调用高级别客户单的close方法时,会将低级别客户端创建的连接池整个关闭掉,最终导致client无法继续使用
     * 所以正常是用不到这个close方法的,只有在程序结束的时候才需要调用
     * @throws IOException
     */
    public static void closeRestClient()throws IOException {
        client.close();
    }

    /**
     * 建立索引
     * @param index
     * @param id
     * @param map
     * @throws IOException
     */
    public static void addIndex(String index, String id, Map<String,String> map)throws IOException{
        IndexRequest request = new IndexRequest(index);
        request.id(id);
        request.source(map);
        //执行
        client.index(request, RequestOptions.DEFAULT);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

2、HBaseUtil工具类代码如下

package com.imooc.utils;

import com.sun.org.apache.bcel.internal.generic.TABLESWITCH;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * HBase工具类
 * 
 */
public class HBaseUtil {
    private HBaseUtil(){}

    private static Connection conn = getConn();

    private static Connection getConn(){
        //获取hbase链接
        Configuration conf = new Configuration();
        //指定hbase使用的zk地址
        //注意:需要在执行hbase hava代码的机器上配置zk和hbase集群的主机名和ip的映射关系
        conf.set("hbase.zookeeper.quorum","bigdata01:2181");
        //指定hbase在hdfs上的根目录
        conf.set("hbase.rootdir","hdfs://bigdata01:9000/hbase");
        //创建HBase数据库链接
        Connection co = null;
        try{
            co = ConnectionFactory.createConnection(conf);
        }catch (IOException e){
            System.out.println("获取链接失败:"+e.getMessage());
        }
        return co;
    }

    /**
     * 对外提供的方法
     * @return
     */
    public static Connection getInstance(){
        return conn;
    }

    /**
     * 创建表
     * @param tableName
     * @param cfs
     */
    public static void createTable(String tableName,String... cfs) throws Exception {
        Admin admin = conn.getAdmin();
        ArrayList<ColumnFamilyDescriptor> cfArr = new ArrayList<ColumnFamilyDescriptor>();
        for (String cf : cfs) {
            ColumnFamilyDescriptor cfDesc = ColumnFamilyDescriptorBuilder
                    .newBuilder(Bytes.toBytes(cf))
                    .build();
            cfArr.add(cfDesc);
        }
        TableDescriptor tableDesc = TableDescriptorBuilder
                .newBuilder(TableName.valueOf(tableName))
                .setColumnFamilies(cfArr)
                .build();
        admin.createTable(tableDesc);
        admin.close();
    }

    /**
     * 添加一个单元格(列)的数据
     * @param tableName
     * @param rowKey
     * @param columnFamily
     * @param column
     * @param value
     * @throws Exception
     */
    public static void put2HBaseCell(String tableName,String rowKey,String columnFamily,String column,String value)throws Exception{
        Table table = conn.getTable(TableName.valueOf(tableName));
        Put put = new Put(Bytes.toBytes(rowKey));
        put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(column),Bytes.toBytes(value));
        table.put(put);
        table.close();
    }

    /**
     * 向hbase中添加一批数据
     * @param tableName
     * @param list
     * @throws Exception
     */
    public static void put2HBaseList(String tableName, List<Put> list)throws Exception{
        Table table = conn.getTable(TableName.valueOf(tableName));
        table.put(list);
        table.close();
    }

    /**
     * 根据Rowkey获取数据
     * @param tableName
     * @param rowKey
     * @return
     * @throws IOException
     */
    public static Map<String,String> getFromHBase(String tableName,String rowKey)throws IOException{
        Table table = conn.getTable(TableName.valueOf(tableName));
        Get get = new Get(Bytes.toBytes(rowKey));
        Result result = table.get(get);
        List<Cell> cells = result.listCells();
        HashMap<String, String> resMap = new HashMap<String, String>();
        for (Cell cell: cells) {
            //列
            byte[] column_bytes = CellUtil.cloneQualifier(cell);
            //值
            byte[] value_bytes = CellUtil.cloneValue(cell);
            resMap.put(new String(column_bytes),new String(value_bytes));
        }
        return resMap;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125

3、DataIndex代码如下

package com.imooc.core;

import com.imooc.utils.EsUtil;
import com.imooc.utils.HBaseUtil;
import com.imooc.utils.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Map;

/**
 * 在ES中对HBase中的数据建立索引
 * 
 */
public class DataIndex {
    private final static Logger logger = LoggerFactory.getLogger(DataIndex.class);
    public static void main(String[] args) {
        List<String> rowKeyList = null;
        Jedis jedis = null;
        try {
            //1:首先从Redis的列表中获取Rowkey
            jedis = RedisUtil.getJedis();
            //brpop如果获取到了数据,返回的list里面有两列,第一列是key的名称,第二列是具体的数据
            rowKeyList = jedis.brpop(3, "l_article_ids");
            while (rowKeyList != null) {
                String rowKey = rowKeyList.get(1);
                //2:根据Rowkey到HBase中获取数据的详细信息
                Map<String, String> map = HBaseUtil.getFromHBase("article", rowKey);
                //3:在ES中对数据建立索引
                EsUtil.addIndex("article",rowKey,map);

                //循环从Redis的列表中获取Rowkey
                rowKeyList = jedis.brpop(3, "l_article_ids");
            }
        }catch (Exception e){
            logger.error("数据建立索引失败:"+e.getMessage());
            //在这里可以考虑把获取出来的rowKey再push到Redis中,这样可以保证数据不丢
            if(rowKeyList!=null){
                jedis.rpush("l_article_ids",rowKeyList.get(1));
            }
        }finally {
            //向连接池返回连接
            if(jedis!=null){
                RedisUtil.returnResource(jedis);
            }
            //注意:确认ES连接不再使用了再关闭连接,否则会导致client无法继续使用
            try{
                EsUtil.closeRestClient();
            }catch (Exception e){
                logger.error("ES连接关闭失败:"+e.getMessage());
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

四、对接Web项目,提供页面检索功能

完善web_fullsearch项目中和大数据相关的代码。
最核心的代码就是EsUtil中的search方法:

/**
 * 全文检索功能
 * @param key
 * @param index
 * @param start
 * @param row
 * @return
 * @throws IOException
 */
public static Map<String, Object> search(String key, String index, int start, int row) throws IOException {
	SearchRequest searchRequest = new SearchRequest();
	//指定索引库,支持指定一个或者多个,也支持通配符
	searchRequest.indices(index);

	//指定searchType
	searchRequest.searchType(SearchType.DFS_QUERY_THEN_FETCH);

	//组装查询条件
	SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
	//如果传递了搜索参数,则拼接查询条件
	if(StringUtils.isNotBlank(key)){
		searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key,"title","describe","content"));
	}
	//分页
	searchSourceBuilder.from(start);
	searchSourceBuilder.size(row);

	//高亮
	//设置高亮字段
	HighlightBuilder highlightBuilder =  new HighlightBuilder()
			.field("title")
			.field("describe");//支持多个高亮字段
	//设置高亮字段的前缀和后缀内容
	highlightBuilder.preTags("<font color='red'>");
	highlightBuilder.postTags("</font>");
	searchSourceBuilder.highlighter(highlightBuilder);

	//指定查询条件
	searchRequest.source(searchSourceBuilder);

	//执行查询操作
	SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
	//存储返回给页面的数据
	Map<String, Object> map = new HashMap<String, Object>();
	//获取查询返回的结果
	SearchHits hits = searchResponse.getHits();
	//获取数据总量
	long numHits = hits.getTotalHits().value;
	map.put("count",numHits);
	ArrayList<Article> arrayList = new ArrayList<>();
	//获取具体内容
	SearchHit[] searchHits = hits.getHits();
	//迭代解析具体内容
	for (SearchHit hit: searchHits) {
		Map<String, Object> sourceAsMap = hit.getSourceAsMap();
		String id = hit.getId();
		String title = sourceAsMap.get("title").toString();
		String author = sourceAsMap.get("author").toString();
		String describe = sourceAsMap.get("describe").toString();
		String time = sourceAsMap.get("time").toString();

		//获取高亮字段内容
		Map<String, HighlightField> highlightFields = hit.getHighlightFields();
		//获取title字段的高亮内容
		HighlightField highlightField = highlightFields.get("title");
		if(highlightField!=null){
			Text[] fragments = highlightField.getFragments();
			title = "";
			for (Text text : fragments) {
				title += text;
			}
		}
		//获取describe字段的高亮内容
		HighlightField highlightField2 = highlightFields.get("describe");
		if(highlightField2!=null){
			Text[] fragments = highlightField2.fragments();
			describe = "";
			for (Text text : fragments) {
				describe += text;
			}
		}
		//把文章信息封装到Article对象中
		Article article = new Article();
		article.setId(id);
		article.setTitle(title);
		article.setAuthor(author);
		article.setDescribe(describe);
		article.setTime(time);
		//最后再把拼装好的article添加到list对象汇总
		arrayList.add(article);
	}
	map.put("dataList",arrayList);
	return map;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

五、从0~1运行项目

1、检查服务状态

首先要确保Hadoop、Zookeeper、HBase、Redis、Elasticsearch这些服务都是正常的。

jps
ps -ef | grep redis
  • 1
  • 2

2、创建表

首先在HBase中创建表:article

hbase(main):001:0> create 'article','info'
  • 1

3、创建索引库

在ES中创建索引库:article,通过article.json文件指定索引库的settings和mapping。

[root@bigdata01 elasticsearch-7.13.4]# curl -H "Content-Type: application/json" -XPUT 'http://bigdata01:9200/article' -d @article.json
  • 1

确认一下索引库article的mapping信息:

[root@bigdata01 elasticsearch-7.13.4]# curl -XGET 'http://bigdata01:9200/article/_mapping?pretty'
{
  "article" : {
    "mappings" : {
      "dynamic" : "strict",
      "_source" : {
        "excludes" : [
          "content"
        ]
      },
      "properties" : {
        "author" : {
          "type" : "text",
          "index" : false
        },
        "content" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        },
        "describe" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        },
        "time" : {
          "type" : "date",
          "index" : false,
          "format" : "yyyy-MM-dd HH:mm:ss"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        }
      }
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

注意:需要确认ES集群中已经集成了ik分词器,否则这里执行会报错。

4、导入数据

在本地执行data_manager项目中的DataImport代码,将数据导入到HBase和Redis。
修改项目中用到的HBaseUtil和RedisUtil工具类中的节点信息。

注意:DataImport代码执行需要消耗一段时间。

5、到HBase中验证数据

hbase(main):003:0> count 'article'
100 row(s)
Took 0.2357 seconds                                                   
=> 100
  • 1
  • 2
  • 3
  • 4

6、到Redis中验证数据

[root@bigdata04 redis-5.0.9]# redis-cli 
127.0.0.1:6379> lrange l_article_ids 0 -1
  1) "0e52f3fb-50a7-43e6-ba1a-8f862464436e"
  2) "0e0fbb25-421c-4419-9ccc-bbfcd7bc783f"
  3) "0e0cbf00-d63c-4c1f-aa2e-9f96a5826029"
  4) "0ddaccc1-af4c-47b1-b526-5a32b2d68aa5"
  5) "0db74ee1-6243-4c3e-ba01-2eacba76188d"
  6) "0d9eae28-b614-4347-9126-ad0ee7519ef1"
  7) "0d7bd4de-159e-46ef-a75c-5d88de612def"
  8) "0d5aeec8-3aa4-4fa5-b7eb-1ba0d0f2d715"
  9) "0d286033-dcad-4514-85e1-2ae052ff531c"
 10) "0d239fca-c524-47ab-be9a-05133ea57a4f"
 11) "0d20beb1-a5c5-4638-99c5-9f69084b44c8"
 12) "0cd0ece6-9d61-4fda-bbcd-a9d9f0d7ff70"
 13) "0cc57b16-fbe6-488e-b080-dab0f0a71930"
 14) "0cb28936-e83f-46e9-8d36-70ae77018fdf"
 15) "0c85c890-57de-40cb-b7f6-8615eb1896fe"
 16) "0c30950e-8fb0-4212-ae13-e8e659515ff3"
 17) "0c2c9dca-b0db-42bc-aa6c-ad180501169d"
 18) "0bfdef9f-8ba9-4c2d-95e4-74284cf07500"
 19) "0bf5aef2-9ac4-4550-80f5-be862a04deba"
 20) "0be8351d-dd82-4e43-92c7-cb8819421b25"
 21) "0bbc79e8-9ab1-44c1-beec-ef2b22c48cfe"
 22) "0baf900c-2812-4bdf-8188-962ac9d17f23"
 23) "0ba2b80b-0b43-4916-9d04-8d67aa4e95b2"
 24) "0b672fc3-0737-467b-b32c-b888de1eaab6"
 25) "0b3dc9c2-96b6-4340-8af3-e07c8bac0ba8"
 26) "0b0161f5-d1d4-4109-8f12-a4b47dbff0e5"
 27) "0a088500-f708-470a-bda1-9e5a714c0b81"
 28) "09e8bd66-17e0-4e28-b804-a67df7312f90"
 29) "09d6e535-4f39-45ba-9857-c6ab01fe8823"
 30) "09a76b9e-030c-465b-8c80-b88fe5bbac16"
 31) "09a09479-56c1-4fd3-8893-3e4d307951b9"
 32) "09193761-e549-48ee-86fb-89295094f1e4"
 33) "090b766d-9f8e-4cb9-9a5f-b8c3373a9eb6"
 34) "09089c93-9848-4244-9eff-9034d434865b"
 35) "09036b9f-8b67-4275-837a-b884c8945a31"
 36) "08d8762e-dc47-49f5-ad14-de52f7fbb04e"
 37) "08839fde-1984-4739-bac2-f854989cdc6c"
 38) "0872be3a-2545-4a98-ba33-6ec148999130"
 39) "0862af37-2047-470d-a8b8-c57740a0df8b"
 40) "085f3acc-1f92-4dd2-b516-d0991b660680"
 41) "08210f1a-f553-4d3c-a09f-420dc8e6fe8c"
 42) "080639ea-ee70-47ff-b7d1-de75eb1e1196"
 43) "07ef53b0-9179-416e-bf8b-7720079fc87a"
 44) "07d79c1d-ceaa-4cd0-a3be-96186037b0c1"
 45) "07b71e20-a4b3-4f9d-b7c6-6db163cbabd1"
 46) "07928b77-a31e-4646-aff0-79ab61f8b605"
 47) "076077f1-8324-4ef3-9e8f-66c806644db6"
 48) "074a36a1-c7b4-4d1a-b8a1-3a01d0c1a2ec"
 49) "0734a9d3-4aa6-4206-aa12-9e26e67f0ef2"
 50) "072b6883-3258-4cff-8e90-77d4f011acfe"
 51) "06d75712-1fac-4a19-927f-f4c13ceba899"
 52) "06d498d1-97cd-4384-8ec1-82e9a1e0070b"
 53) "06beaece-c734-4eba-bcf7-64a9e43d6c55"
 54) "0682b625-f247-4c70-a628-4919946ff588"
 55) "0659a1c3-2f34-4342-827b-7c867a3394e3"
 56) "0647046c-2129-4c16-83f9-7857e9da6e2c"
 57) "0643961e-74ea-46d2-8da8-02309c6cc489"
 58) "061776ad-5742-4fcc-b997-9a25f5b6a4e0"
 59) "05ec598f-4b02-448f-88b6-f17f3891bd3f"
 60) "058ca914-aa70-4cf5-b69f-852e03729b52"
 61) "0504f099-995c-475d-97e8-f4d691673cbb"
 62) "04fce8dc-e27c-4f1c-bb4c-34597fad1810"
 63) "04b00310-c09a-44e2-9895-1fd22cd14148"
 64) "04997210-1552-4dcb-b13d-16e79ced1320"
 65) "04943196-717d-4594-bc83-a76189187c2e"
 66) "04940647-ff85-497d-b2fd-44f43600a569"
 67) "04755a1e-32b2-42b7-933e-405c8143eb8f"
 68) "047139c9-c351-4275-83be-c96b8105811b"
 69) "0451dbce-f8da-4862-b563-bee0bc5abdc6"
 70) "042bed42-fbb9-4c51-a424-089f579697e0"
 71) "03f3a775-6736-4ffb-bda0-b6e3ae215394"
 72) "03dde3c0-300f-4431-9f58-82dc3869020a"
 73) "03dd67bf-9044-4c00-a34d-c480d221afdb"
 74) "03b79d39-ddcb-48b2-8cdf-bd480041018d"
 75) "039b6212-3760-4261-a3ad-66427ca3ece2"
 76) "034e7e37-0e9a-49d0-86f7-f74956ad9c1c"
 77) "032f6cc9-0ec8-4014-a37c-ee233afad174"
 78) "02ce202c-4b37-4e21-b8c6-241323daaf4f"
 79) "02c32611-8f4c-44cc-85e6-9aabe50c5deb"
 80) "02617a09-af48-4885-9193-e36c73e1cc93"
 81) "022ec4b4-9061-489e-9c1f-d702187d9173"
 82) "022bfe1c-c04a-4dfd-83d1-545a2fa37d62"
 83) "0221116d-1140-4161-976e-780166b409ee"
 84) "0211f787-5122-4b95-90b2-36a60da5072c"
 85) "01f80b6f-ef70-4cf3-af3c-0c39cc1ca6df"
 86) "01ae3dff-19bf-4dfe-a417-f00c5cbee89e"
 87) "017b4fc5-a5b4-40f4-a22c-c0585dce1b8c"
 88) "0157ba2a-e6ea-4272-8257-be4d07278f41"
 89) "01476c80-aa75-46ed-b912-cad708cfb6da"
 90) "0125e066-aeb7-4053-ab85-e358076a23b0"
 91) "00ded12e-3974-46ff-b425-a1f4202d5380"
 92) "00b5546f-04a5-4d98-80b2-dbf2254f8f81"
 93) "0087283d-3773-4788-a86e-3a3a425d2240"
 94) "005dc64e-0134-43d8-b9bf-b3390e59ab12"
 95) "004ecf75-54b9-4aa7-a3ff-7ee2e0907f85"
 96) "004a2fb2-1547-4c63-8bfb-abfe812681aa"
 97) "0025b235-f4cd-45e4-90c3-46acbb3165fb"
 98) "001b046c-f1c9-457d-a8b7-cd157bc5d889"
 99) "00119d1d-b587-478f-86c0-ceb4f15c5cf7"
100) "00093f78-11c7-43e1-abed-86c735252155"
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102

7、建立索引

在本地执行data_manager项目中的DataIndex代码,在ES中建立索引。

注意:需要修改项目中用到的EsUtil工具类中的节点信息。
  • 1

到ES中验证数据:

[root@bigdata01 ~]# curl -XGET 'http://bigdata01:9200/article/_search?pretty'
{
  "took" : 393,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 100,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "article",
        "_type" : "_doc",
        "_id" : "01476c80-aa75-46ed-b912-cad708cfb6da",
        "_score" : 1.0,
        "_source" : {
          "author" : "腾讯新闻",
          "describe" : "  浙江在线11月11日讯由于感情纠葛,阿松(化名)的婚外恋女友一怒之下抄起剪刀,几乎剪断了他的整根“命根子”。所幸医生及时抢救,周密施术,才保住了他的“男儿身”。昨天,正在义乌进行康复治疗的阿松,回想起前几天的经历,依旧百感交集。  “小三”怒剪命根只因疑其不忠  三十多岁的阿松是重庆人,在义乌工作,妻子女儿也都在义乌。  阿松还有一个婚外恋女友,是在一家工厂里认识的,两个人相恋已经有一年多了。女友30多岁,也有自己的家庭。  阿松说,最近一段时间,女友屡屡怀疑阿松“不忠”,据说是听到了一些传言。  十几天前,被妒忌之火冲昏头脑的女友,居然想到了断其“命根”的“教训”办法。当时是下午3点多,",
          "time" : "2021-10-17 11:13:19",
          "title" : "“小三”怀疑男友不忠怒剪其“命根”"
        }
      },
      {
        "_index" : "article",
        "_type" : "_doc",
        "_id" : "01f80b6f-ef70-4cf3-af3c-0c39cc1ca6df",
        "_score" : 1.0,
        "_source" : {
          "author" : "腾讯新闻",
          "describe" : "    两岸几十年的隔阂已经使共同的汉语言演变出一些差异。图为在台湾书店里的内地游客。    台湾中华语文研习所董事长何景贤,手捧两种版本的《两岸现代汉语常用词典》。  中新网11月11日电教育部语言文字应用管理司司长王登峰在接受香港《文汇报》访问时表示,两岸合编《中华大辞典》主要将以民间方式进行,届时将推出简体字括注繁体字以及繁体字括注简体字两个版本。另有消息人士透露,预计该项目将于年底启动。  第五届两岸经贸文化论坛共同建议7月份曾就两岸合编《中华大辞典》达成初步意向。王登峰表示,大陆方面已经作好充分的准备工作,将主要以1996年两岸合编的《两岸现代汉语常用词典》为工作基础。两岸有关部门达",
          "time" : "2021-10-17 11:13:19",
          "title" : "两岸将合编中华大辞典收集新词汇(组图)"
        }
      }
......
      }
    ]
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

重点关注_source字段中是否包含content字段,如果包含此字段内容说明前面的mapping配置有问题,如果不包含此字段内容说明是正确的。

8、打包项目

对web_fullsearch项目打包并运行。
web_fullsearch项目是一个Javaweb项目,所以需要打war包,最终在Web容器中运行,在这里我们使用Tomcat这个web容器。
打war包。

D:\IdeaProjects\db_fullsearch\web_fullsearch>mvn clean package -DskipTests
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 24.974s
[INFO] Final Memory: 35M/348M
[INFO] ------------------------------------------------------------------------
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

9、上传到到tomcat

使用之前在学习ES课程的时候在bigdata04上部署的tomcat,将war包上传到tomcat的webapps目录下:

[root@bigdata04 apache-tomcat-8.0.52]# cd webapps/
[root@bigdata04 webapps]# ll
total 84888
drwxr-xr-x. 14 root root     4096 Mar 16 20:37 docs
drwxr-xr-x.  6 root root       83 Mar 16 20:37 examples
drwxr-xr-x.  5 root root       87 Mar 16 20:37 host-manager
drwxr-xr-x.  5 root root      103 Mar 16 20:37 manager
drwxr-xr-x.  3 root root     4096 Mar 16 20:45 ROOT
-rw-r--r--.  1 root root 86913853 Oct 17  2021 web_fullsearch.war
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

10、启动tomcat

[root@bigdata04 apache-tomcat-8.0.52]# bin/startup.sh 
  • 1

11、访问项目

http://bigdata04:8080/web_fullsearch/article
  • 1

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/636555
推荐阅读
相关标签
  

闽ICP备14008679号