从mysql数据库到Elasticsearch再到qdrant向量数据库搜索的过程

前言

在常规搜索中,我们通常依赖数据库的直接SELECT查询,对于文本内容,多数情况下使用LIKE进行模糊匹配,并通过添加判断规则来限制条件,以确保返回结果符合预期。然而,面对手中这批关于留学的问题和答案,当前的检索需求不仅要求考虑时效性(即信息易过期),而且单纯的模糊搜索已不足以满足需求。因此,我们决定研发一个更为先进的搜索模式,以便更好地维护数据并满足复杂的搜索需求。

1、尝试使用ES

ES是我听过最多做搜索的引擎,首先,使用全文检索引擎如Elasticsearch或Solr来处理大量文本数据,支持复杂查询,包括关键词搜索、模糊搜索、近似匹配和权重排序。其次,采用自然语言处理(NLP)技术提升搜索的智能化和准确性,包括分词与词干提取、同义词识别和实体识别。此外,我们将引入时效性处理机制,通过时间戳过滤和时间权重,确保数据的时效性。同时,通过分析用户的搜索和点击行为,利用点击率和停留时间来优化搜索结果排序。

  • 创建索引结构
{
	"settings": {
		"index": {
			"number_of_shards": "5",
			"number_of_replicas": "1",
			"max_result_window": "10000",
			"analysis": {
				"filter": {
					"remote_synonym": {
						"type": "dynamic_synonym",
						"synonyms_path": "http://192.168.0.138:1616/knowledge/getKnowledgeEsIkWorks?type=2",
						"interval": 30
					}
				},
				"char_filter": {
					"html_strip": {
						"type": "html_strip"
					}
				},
				"analyzer": {
					"ik_max_syno": {
						"type": "custom",
						"tokenizer": "ik_max_word",
						"filter": [
							"remote_synonym"
						]
					}
				},
				"tokenizer": {
					"custom_tokenizer": {
						"type": "standard"
					}
				}
			}
		}
	},
}

mapping我就删掉了,可以自己加入对应表结构的mapping!针对不同的分析场景
也可以多写一点分析器,列如:对于客户场景,加入停用词场景!

  • 完善分词器,加入IK远程词典同义词和停用词

分词器需要提前安装IK分词器和elasticsearch-analysis-dynamic-synonym

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict"></entry>
	 <!--用户可以在这里配置自己的扩展停止词字典-->
	<entry key="ext_stopwords"></entry>
	<!--用户可以在这里配置远程扩展字典 -->
	<entry key="remote_ext_dict">http://localhost:1616/knowledge/getKnowledgeEsIkWorks?type=1</entry> 
	<!--用户可以在这里配置远程扩展停止词字典-->
	<entry key="remote_ext_stopwords">http://localhost:1616/knowledge/getKnowledgeEsIkWorks?type=3</entry> 
</properties>

其中两个词典的位置直接用服务端写一个请求就好,根据官网,请求头携带ETag和Last-Modified
这块分词器刷新词典的时间是一分钟一次,所以如果词典数据较多的话,记得添加缓存和通过这两个请求头来限制刷新时间

  • 将mysql的数据洗进去
@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.clusterNodes}")
    private String clusterNodes;

    @Value("${elasticsearch.clusterName}")
    private String clusterName;

    @Bean
    public RestHighLevelClient restHighLevelClient() {
        // 解析hostlist配置信息
        String[] split = clusterNodes.split(",");

        // 创建HttpHost数组,存放es主机和端口的配置信息
        HttpHost[] httpHostArray = new HttpHost[split.length];
        for(int i = 0; i < split.length; i++) {
            String item = split[i];
            httpHostArray[i] = new HttpHost(item.split(":")[0], Integer.parseInt(item.split(":")[1]), "http");
        }

        // 创建RestHighLevelClient客户端
        RestClientBuilder builder = RestClient.builder(httpHostArray);
        RestHighLevelClient client = new RestHighLevelClient(builder);

        return client;
    }


}

我是用java写的,其他语音也类似,直接build,对着mapping的字段拼成想要json串

  • 尝试简单匹配搜索
{ 
    "query":{
        "match":{
            "title":"阿大"
        }
    }
}

我这边是把多个字段拼成一个去搜索,这一步完成,已经和数据库的模糊查重差不多了!

  • 增加条件和脚本
    简单的完了,咋来一个不简单的
{
  "query": {
    "function_score": {
      "query": {
        "multi_match": {
          "query": "阿大",
          "fields": [
            "knowledge.class^0.35",
            "knowledge.schoolId^0.3",
            "title^0.2",
            "content^0.15"
          ],
          "type": "best_fields",
          "operator": "or",
          "analyzer": "ik_max_word"
        }
      }
    }
  },
  "sort": {
    "_script": {
      "type": "number",
      "script": {
        "source": "def releaseDate = doc['releastime'].value.toInstant().toEpochMilli();def now = System.currentTimeMillis();def time1 = now - (30 * 24 * 60 * 60 * 1000);def time2 = now - (90 * 24 * 60 * 60 * 1000);def time3 = now - (180 * 24 * 60 * 60 * 1000);return _score * (releaseDate > time1 ? 1.2 : (releaseDate > time2 ? 1.1 : (releaseDate > time3 ? 1 : 0.4)));",
        "params": {
          "time1": "now-1M",
          "time2": "now-3M",
          "time3": "now-6M"
        }
      },
      "order": "desc"
    }
  }
}

惊不惊喜,意不意外!!

对指定字段的评分乘积以及时间衰减做了简单的查询调整
这块ES7貌似是支持时间衰减的,目前没有具体的业务规则,就先用自己先的脚本试试了

  • 到这个程度,通过维护了一段时间的停用词和同义词,以及分词的关键词,ES搜索就告一段落了,ES搜索还有很多强大的功能有待发掘,学无止境学无止境

2、尝试使用向量数据库

ES虽然对相似度,匹配度上已经有了提升,但是语义查询,还在更高版本才支持。
这边一边维护旧版本,一边也在找新的思路,就找到了Qdrant向量数据库

2023年7月Vector DB Bench向量数据库排行榜Top50

首先尝试的要求首先就是开源,所以我试了Qdrant和Weaviate

向量数据库和ES有异曲同工之处,也是创建索引,洗数据,查询这一套基本流程
区别就在于mysql数据的文本转向量的效果会直接影响到查询的效果,所以选择一个强大的模型就成了至关重要的事。

huggingface 开源模型
这个网站可以需要小飞机,而且容易下载断开,下载的时候去搜一下huggingface-cli
好评五分

我这边选的文本转向量的模型是bge-large-zn-v1.5,还有其他的图片转向量,人脸转向量,如果有其他的场景可以尝试其他的模型!其中向量大小也会影响查询效果,对于文本更多的数据选用更大的向量宽

看到这的记得给个赞!!哈哈哈(插一波广告)

from flask import Flask, request, jsonify
from sentence_transformers import SentenceTransformer
import numpy as np

app = Flask(__name__)
#model_path = 'D:\\project\\python_project\\models\\bert-base-uncased'
model_path = 'D:\\project\\python_project\\models\\bge-large-zn-v1.5'
model = SentenceTransformer(model_path)

@app.route('/encode', methods=['POST'])
def encode_text():
    # 接收POST请求中的JSON数据
    data = request.json
    text = data['text']
    vector_size = data['vector_size']

    # 将文本数据转换为指定大小的向量
    encoded_vector = model.encode([text], normalize_embeddings=True, convert_to_tensor=False)[0][:vector_size]

    # 返回结果
    return jsonify(encoded_vector.tolist())

if __name__ == '__main__':
    app.run(debug=True)

通过phthon提供转向量的接口,完成洗数据的工作,简单查询

post /collections/knowledge_index/points/search
{
  "vector": [0.1, 0.2, 0.3, 0.4, 0.5],
  "top": 5
}

有了语义查询,查询的相关词更加准确了,最终也确定了Qdrant向量数据库

3、开始尝试ES和Qdrant混合

ES有较好的分词、停用词功能
Qdrant有较好的语义查询

①. 首先在洗数据方面就开始先通过ES分词拆开,再把重复词去掉,再转向量的方式,保证重复词占用的比重
public String analyzeText(String text) throws IOException {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost request = new HttpPost(ES_URL);
            request.setHeader("Content-Type", "application/json");

            // Create JSON payload
            String jsonPayload = "{ \"analyzer\": \"ik_smart\", \"text\": \"" + text + "\" }";
            request.setEntity(new StringEntity(jsonPayload, "UTF-8"));

            // Execute the request and get the response
            try (CloseableHttpResponse response = httpClient.execute(request)) {
                String jsonResponse = EntityUtils.toString(response.getEntity());

                // Parse the JSON response
                JsonNode tokensNode = OBJECT_MAPPER.readTree(jsonResponse).path("tokens");

                // Extract unique tokens
                Set<String> uniqueTokens = new HashSet<>();
                if (tokensNode.isArray()) {
                    for (JsonNode tokenNode : tokensNode) {
                        uniqueTokens.add(tokenNode.path("token").asText());
                    }
                }
                String result = uniqueTokens.stream().collect(Collectors.joining(""));

                // Print the unique tokens (you can also return or assert them as needed)
                return result;
            }
        }
    }
②. 查询的时候讲输入词也用同样的去重操作,转向量,查询
③. 最后通过payload做一些简单的数据过滤,完成最终的查询

总结:ES是一个强大的搜索引擎,我开发深入的程度还很低,也需要更深层次的学习,才能完成更加复杂的查询
向量数据库是一个好的解决方案,针对人脸识别、图片识别、文本匹配都有较好的解决方案,实际使用过程中也看查询效果,也看实际成本。
学无止境学无止境!!!
(可以评论一起学习,我会看)

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python通过使用`elasticsearch`库和`mysql-connector-python`库可以实现MySQL数据库同步到Elasticsearch的功能。下面是一个300字的回答。 首先,安装所需的库。可以使用以下命令安装`elasticsearch`库和`mysql-connector-python`库: ``` pip install elasticsearch mysql-connector-python ``` 接下来,导入所需的库并连接到MySQL数据库Elasticsearch: ```python import mysql.connector from elasticsearch import Elasticsearch # 连接到MySQL数据库 conn = mysql.connector.connect( host="localhost", user="root", password="password", database="mydatabase" ) # 连接到Elasticsearch es = Elasticsearch([{'host': 'localhost', 'port': 9200}]) ``` 然后,执行MySQL查询语句来获取数据,并将其插入到Elasticsearch中: ```python # 创建MySQL游标对象 cursor = conn.cursor() # 执行MySQL查询语句 cursor.execute("SELECT * FROM mytable") # 获取查询结果 results = cursor.fetchall() # 将结果插入到Elasticsearch for row in results: document = { 'id': row[0], # 假设MySQL表中有一个id列 'name': row[1], # 假设MySQL表中有一个name列 # 添加其他需要同步的字段 } es.index(index='myindex', doc_type='mytype', body=document) ``` 最后,关闭MySQL数据库连接和Elasticsearch连接: ```python # 关闭MySQL数据库连接 conn.close() # 关闭Elasticsearch连接 es.close() ``` 以上是用Python实现MySQL数据库同步到Elasticsearch的基本步骤。可以根据具体需求对代码进行更改和优化,例如使用配置文件来管理数据库连接信息和Elasticsearch的索引名称等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值