使用查詢分離后,從20s優(yōu)化到500ms,牛哇!
那咱們先來聊聊,為啥之前查詢那么慢呢?其實啊,原因也簡單,就是因為數(shù)據(jù)量太大了。你想啊,一個表里有幾千萬條數(shù)據(jù),每次查詢還得關聯(lián)十幾個子表,每個子表的數(shù)據(jù)也是上億條,這能不慢嗎?咱們雖然用了索引、優(yōu)化了SQL,但效果還是不明顯。這就像是你讓一個胖子去跑馬拉松,他跑得動嗎?跑不動啊!
所以啊,咱們就得想辦法給這個“胖子”減減肥,這就是查詢分離的思路啦。咱們在寫數(shù)據(jù)的時候,順便把數(shù)據(jù)發(fā)到一個消息隊列(MQ)里,然后異步地寫到Elasticsearch(ES)里去。這樣,查詢的時候就不去主表湊熱鬧了,直接去ES里查,那速度可就快多了。
那具體怎么實現(xiàn)呢?咱們來看看代碼。假設咱們用的是Java,首先,咱們得在寫數(shù)據(jù)庫的時候,把數(shù)據(jù)也發(fā)到MQ里去。這里咱們用RabbitMQ作為例子:
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.Channel;
public class MessageSender {
private final static String QUEUE_NAME = "data_queue";
public void send(String message) throws Exception {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("localhost");
try (Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println(" [x] Sent '" + message + "'");
}
}
}
這段代碼就是往MQ里發(fā)消息的。咱們在寫數(shù)據(jù)庫的時候,調(diào)用這個send方法,把數(shù)據(jù)作為消息發(fā)出去。
然后,咱們得有個消費者來監(jiān)聽這個MQ,把消息異步地寫到ES里去。這里咱們用Elasticsearch的Java客戶端來操作ES:
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;
public class EsDataWriter {
private RestHighLevelClient client;
public EsDataWriter(RestHighLevelClient client) {
this.client = client;
}
public void writeToEs(String indexName, String jsonData) throws Exception {
IndexRequest indexRequest = new IndexRequest(indexName);
indexRequest.source(jsonData, XContentType.JSON);
IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
System.out.println("Data written to ES with id: " + indexResponse.getId());
}
}
這段代碼就是往ES里寫數(shù)據(jù)的。咱們在MQ的消費者里,拿到消息后,調(diào)用這個writeToEs方法,把數(shù)據(jù)寫到ES里去。那這樣,查詢的時候咱們就不去主表查了,直接去ES里查。那速度,嗖嗖的,500毫秒就出結(jié)果了。
但是啊,這里有個問題,就是數(shù)據(jù)還沒同步到ES的時候,立馬去查,查不到怎么辦?這個嘛,咱們也有辦法。咱們可以在數(shù)據(jù)庫里加個字段,比如叫es_synced,表示數(shù)據(jù)是否已經(jīng)同步到ES了。ES消費者寫入ES后可以更新一下這個字段,查詢單條數(shù)據(jù)的時候,咱們先查這個字段,如果已經(jīng)同步了,就直接去ES里查;如果還沒同步,就等一會兒再查,或者從主表里查。
如果是批量查多條數(shù)據(jù)那就不用做這個處理了,只允許查出來已經(jīng)同步到ES的數(shù)據(jù)就可以了!
那歷史數(shù)據(jù)怎么遷移呢?這個其實也不難。咱們可以寫個腳本,把主表里的數(shù)據(jù)分批查出來,然后發(fā)到MQ里去,讓消費者異步地寫到ES里去。這樣,歷史數(shù)據(jù)也就遷移到ES里了。
總的來說啊,這個查詢分離的思路還是挺實用的。它就像是一個減肥的方法,讓咱們的“胖子”數(shù)據(jù)庫跑得快了起來。當然啦,這個方法也不是萬能的,比如如果數(shù)據(jù)量實在太大了,寫入速度也會受影響。但是啊,對于大部分場景來說,這個方法還是挺好用的。
好啦,今天咱們就聊到這里啦。如果你也有類似的困擾,不妨試試這個方法~