1. 程式人生 > 其它 >newCoder社交網站專案學習

newCoder社交網站專案學習

newCoder社交網站專案

springboot,mysql

登入模組功能

表結構設計

(salt欄位(UUID隨機字串0-5)+password欄位)結合md5加密

啟用碼欄位:用於郵箱啟用賬號(UUID)

狀態欄位:用於驗證使用者是否啟用

註冊功能

  1. 啟用過程先存入使用者資料,生成salt欄位和啟用碼欄位

  2. 然後傳送啟用郵件(該郵件需要攜帶生成的啟用碼,啟用路徑(包含userID,啟用碼,用於訪問啟用控制器),郵件地址)

驗證碼功能

1、使用kaptchaProducer包生成驗證碼圖片image和字母text。

2、再使用UUID生成kaptchaOwner裝入cookie唯一標識該驗證碼,設定生存時間,響應給客戶端

3、使用kaptchaOwner作為鍵,text作為值存入redis中,並將image以image/png格式輸出給客戶端

請求的許可權校驗

構建登入憑證LoginTicket類

程式碼

public class LoginTicket {

    private int id;
    private int userId;
    private String ticket;
    private int status;
    private Date expired;
}

可以把他理解為一張許可權令牌,就像工牌,上面記錄著userId、狀態、Ticket(校驗碼)、獲取時間。

使用者登入時會生成該登入憑證例項,並使用Ticket為鍵登入憑證例項為值存入redis。

實現攔截器檢驗

後續請求使用攔截器實現許可權校驗,特點:攔截的是URL,從cookie中獲取憑證。一般情況下,所有的請求(URL)都需要做登陸校驗。

public class LoginTicketInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 從cookie中獲取憑證
        String ticket = CookieUtil.getValue(request, "ticket");

        if (ticket != null) {
            // 查詢憑證
            LoginTicket loginTicket = userService.findLoginTicket(ticket);
            // 檢查憑證是否有效
            if (loginTicket != null && loginTicket.getStatus() == 0 && loginTicket.getExpired().after(new Date())) {
                // 根據憑證查詢使用者
                User user = userService.findUserById(loginTicket.getUserId());
                // 在本次請求中持有使用者
                hostHolder.setUser(user);
            }
        }

        return true;
    }
}

這裡使用的是攔截器實現了HandlerInterceptor介面

參考(Spring 過濾器 攔截器 AOP區別通俗易懂):https://blog.csdn.net/dreamwbt/article/details/82658842

登入物件資訊持有

通過redis旁路快取(根據userID)的user物件,結合Thread Local實現登入物件資訊持有。一個請求對應一個伺服器執行緒(Thread-per-Request)來進行操作。

使用ThreadLoal儲存使用者資訊,用於代替session物件。建立HostHolder類封裝ThreadLoal物件

  • 因為每個 Thread 內有自己的例項副本,且該副本只能由當前 Thread 使用。這是也是 ThreadLocal 命名的由來。
  • 既然每個 Thread 有自己的例項副本,且其它 Thread 不可訪問,那就不存在多執行緒間共享的問題。

於是實際的流程就是:每個使用者的請求都會被LoginTicketInterceptor的preHandle攔截,然後判斷請求是否帶有憑證並檢查憑證是否有效(這裡也是使用鍵去redis中找),然後根據憑證中的userId查詢得到使用者所有資訊user(這一步也是去redis找)。找到後hostHolder.setUser(user);,將user存入該例項的ThreadLocal

ThreadLoal學習

ThreadLocal實現原理

Thread-》ThreadLocalMap

ThreadLocal類內部維護了一個靜態內部類ThreadLocalMap。ThreadLocalMap底層是一個Entry陣列。ThreadLocal維護的對映關係是:以當前threadlocal物件為鍵,以執行緒私有變數為值存入Entry。所以一個執行緒可以有多個ThreadLocal,它們的關係是一個threadlocal對於一個私有變數。

以下囉嗦的廢話:

一個執行緒內可以存在多個 ThreadLocal 物件,所以其實是 ThreadLocal 內部維護了一個 Map ,這個 Map 不是直接使用的 HashMap ,而是 ThreadLocal 實現的一個叫做 ThreadLocalMap 的靜態內部類,Thread類中又維護了一個ThreadLocal.ThreadLocalMap,用於指定唯一例項。

ThreadLocalMap底層是一個Entry陣列,但是一個Thread可以有多個ThreadLocal,一個ThreadLocal對應一個變數資料,封裝成Entry存到ThreadLocalMap中,所以就有多個Entry。比如有兩個變數,我就要建兩個ThreadLocal物件。

記憶體洩漏問題

記憶體洩漏:指的是堆記憶體由於某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導致程式執行速度減慢甚至系統崩潰等嚴重後果。

每個thread中都存在一個map, map的型別是ThreadLocal.ThreadLocalMap。下面簡稱為Map。

Map中的key為一個threadlocal例項,這個Map的確使用了弱引用,不過弱引用只是針對key。每個key都弱引用指向threadlocal。

如何避免記憶體洩露

使用完執行緒後呼叫 ThreadLocal 的 remove 方法。

參考:https://zhuanlan.zhihu.com/p/354153342

總的來說,ThreadLocal 適用於每個執行緒需要自己獨立的例項且該例項需要在多個方法中被使用,也即變數線上程間隔離而在方法或類間共享的場景。而且線上程中使用它可以給我們提供一個執行緒內的本地區域性變數,這樣就可以減少在一個執行緒中因為多函式之間的操作導致共享變數傳值的複雜性,說白了,我們使用ThreadLocal可以做到在一個執行緒內隨時隨地的取用,而且與其他的執行緒互不干擾。

參考:ThreadLocal原理分析與使用場景

thread、threadlocal、threadLocalMap的關係

redis應用場景

​ Redis 是一個獨立的系統軟體,和業務應用程式是兩個軟體,當我們部署了 Redis 例項後,它只會被動地等待客戶端傳送請求,然後再進行處理。所以,如果應用程式想要使用 Redis 快取,我們就要在程式中增加相應的快取操作程式碼。所以,我們也把 Redis 稱為旁路快取,也就是說,讀取快取、讀取資料庫和更新快取的操作都需要在應用程式中來完成。

減少資料庫的互動

優化登入模組

使用Redis快取使用者資訊(如findUserById方法)

  • 處理每次請求時,都要根據id憑證查詢使用者資訊,訪問的頻率非常高,並且很多業務也都需要用到user資訊。
  • 實現:在UserService中實現User資訊的只讀快取(更新即刪除)

使用Redis儲存登入憑證(如LoginTicketInterceptor攔截器中preHandle方法攔截請求驗證登入憑證)

  • 處理每次請求時,都要查詢使用者的登入憑證,訪問的頻率非常高,如findLoginTicket頻繁對LoginTicket表進行查詢。
  • 實現:在UserService中將原先的對MySQL中LoginTicket表的訪問轉變成對redis的訪問

使用Redis儲存驗證碼

  • 驗證碼需要頻繁的訪問與重新整理,對效能要求較高。
  • 驗證碼不需永久儲存,通常在很短的時間後就會失效。 -
  • 分散式部署時,存在Session共享的問題。

會話管理架構演進

首先敏感資料使用session存放。

粘性session,固定ip傳送固定伺服器。問題:負載均衡策略難以平衡各伺服器負載,不完美

資料庫叢集儲存會話資料——》NoSQL資料庫儲存

點贊,關注,取消關注功能

  • 需求

    開發關注、取消關注功能

    統計使用者的關注數、粉絲數

  • 關鍵實現

    關注的目標可以是使用者、帖子、題目等,在實現時將這些目標抽象為實體,以達到複用程式碼

  • 效能

    需要頻繁訪問和修改資料,即熱資料。另外redis是一個單執行緒的事務排程,即不用考慮多執行緒併發會帶來的相關執行緒安全問題。

  • 實現

    使用字首+(userId/entityId)等形式生成唯一鍵

session是儲存在應用伺服器的記憶體裡,redis會部署在單獨的伺服器上,會做叢集的。效能上都是訪問記憶體,差異不大。

問題

Q:mybatis本身是可以開啟名稱空間快取的,這個redis對使用者資訊的快取還有更多意義嗎?

A:MyBatis佔用應用伺服器的本地快取而Redis是分散式快取,層次不同,可以同時應用

kafka構建TB級非同步訊息系統

樣例:社交網站通知系統

技術使用問題

你這個功能為什麼要用kafka?

我覺得我這個系統使用kafka主要能在事務併發量很大的情況下減輕傳統模式的伺服器與資料庫端的壓力,並對整個系統起到一個解耦的效果。

如傳統模式就是業務系統呼叫資料庫系統介面,首先要保證事務的基本要素(原子性一致性隔離性永續性)就需要處理併發情況,每次的點贊,評論,關注的更新訊息就會直接提交至資料庫,資料庫雖然有事務處理機制,像MVCC的併發控制等。但即便這樣當資料量過大也會影響系統處理效能。如你提交一條評論或者點一個贊需要等待一個事務返回結果再同步返回結果給前端介面顯示,這樣就不一定能保證效能的要求,造成使用者體驗不足。

另一方面用訊息佇列你就可以轉儲訊息嘛,我服務端接收請求就往MQ裡面扔,扔完就正常返回沒我執行緒事了,起到一個解耦的作用。另一方面你消費者處理是你另外的事情,你自己創執行緒慢慢消費資料。

其實資料量不大情況沒啥意義。。。用個阻塞佇列也能實現。但kafka的機制比較完善,如失敗處理與可恢復性避免系統故障導致資料丟失,多伺服器的話就轉儲訊息分擔系統負載嘛,保證訊息的順序性等情況

為什麼需要訊息系統,mysql不能滿足嗎?

1.解耦:

允許你獨立的擴充套件或修改兩邊的處理過程,只要確保它們遵守同樣的介面約束。

2.冗餘:

訊息佇列把資料進行持久化直到它們已經被完全處理,通過這一方式規避了資料丟失風險。許多訊息佇列所採用的”插入-獲取-刪除”正規化中,在把一個訊息從佇列中刪除之前,需要你的處理系統明確的指出該訊息已經被處理完畢,從而確保你的資料被安全的儲存直到你使用完畢。

3.擴充套件性:

因為訊息佇列解耦了你的處理過程,所以增大訊息入隊和處理的頻率是很容易的,只要另外增加處理過程即可。

4.靈活性 & 峰值處理能力:

在訪問量劇增的情況下,應用仍然需要繼續發揮作用,但是這樣的突發流量並不常見。如果為以能處理這類峰值訪問為標準來投入資源隨時待命無疑是巨大的浪費。使用訊息佇列能夠使關鍵元件頂住突發的訪問壓力,而不會因為突發的超負荷的請求而完全崩潰。

5.可恢復性:

系統的一部分元件失效時,不會影響到整個系統。訊息佇列降低了程序間的耦合度,所以即使一個處理訊息的程序掛掉,加入佇列中的訊息仍然可以在系統恢復後被處理。

6.順序保證:

在大多使用場景下,資料處理的順序都很重要。大部分訊息佇列本來就是排序的,並且能保證資料會按照特定的順序來處理。(Kafka 保證一個 Partition 內的訊息的有序性)

7.緩衝:

有助於控制和優化資料流經過系統的速度,解決生產訊息和消費訊息的處理速度不一致的情況。

8.非同步通訊:

很多時候,使用者不想也不需要立即處理訊息。訊息佇列提供了非同步處理機制,允許使用者把一個訊息放入佇列,但並不立即處理它。想向佇列中放入多少訊息就放多少,然後在需要的時候再去處理它們。

訊息佇列系統介紹

一個訊息系統負責將資料從一個應用傳遞到另外一個應用,應用只需關注於資料,無需關注資料在兩個或多個應用間是如何傳遞的。分散式訊息傳遞基於可靠的訊息佇列,在客戶端應用和訊息系統之間非同步傳遞訊息。有兩種主要的訊息傳遞模式:點對點傳遞模式、釋出-訂閱模式。大部分的訊息系統選用釋出-訂閱模式。Kafka就是一種釋出-訂閱模式

  • 系統解耦
  • 非同步通訊
  • 由於佇列能轉儲訊息,對於超出系統承載能力的場景,可以用 MQ 作為 “漏斗” 進行限流保護,即所謂的流量削峰。
  • 我們還可以利用佇列本身的順序性,來滿足訊息必須按順序投遞的場景;利用佇列 + 定時任務來實現訊息的延時消費

任何 MQ 無外乎:一發一存一消費,這是 MQ 最核心的功能需求。另外,從技術維度來看 MQ 的通訊模型,可以理解成:兩次 RPC + 訊息轉儲。

外鏈:訊息佇列(mq)是什麼? - Lowry的回答 - 知乎 https://www.zhihu.com/question/54152397/answer/1802083263

阻塞佇列

什麼是阻塞?

  1. 這個佇列是執行緒安全的(內部進行了加鎖控制)。
  2. 當佇列滿的時候,往佇列裡插元素,就會阻塞,,直到佇列不滿才會進行插入操作。
    當佇列為空的時候,從佇列裡取出元素,此時也會阻塞。。一直阻塞到佇列不為空的時候才完成取元素操作。

阻塞佇列可以幫我們完成“生產者消費者模型”。在生產者與消費者之間構建一個快取佇列,起到執行緒通訊間的快取作用。

實現方式有如下幾種:

  1. BlockingQueue
    • 解決執行緒通訊的問題
    • 阻塞方法:put,take
  2. 生產者消費者模式
  3. 實現類(Java Api)
    • ArrayBlockingQueue
    • LinkedBlockingQueue
    • PriorityBlockingQueue、SynchronousQueue、DelayQueue等。

Kafka學習

  1. 什麼是kafka

    Kafka是一種分散式的釋出-訂閱模式的訊息系統。Kafka是一個分散式,可劃分的,冗餘備份的永續性的日誌服務,它主要用於處理流式資料。

    靈活性&峰值處理能力

術語解釋

Broker(伺服器,訊息的代理),內建Zookeeper

Topic(主題,訊息型別);Partiton(分割槽);Offset(索引)

Kafka叢集中的每臺伺服器叫Broker,整個叢集由Zookeeper進行管理
Kafka採用釋出訂閱模式,每條訊息都要傳送到指定的Topic上
每個Topic可分為多個Partition,這樣可以提高Kafka的併發執行能力

關係示例

上圖中一個topic配置了3個partition。Partition1有兩個offset:0和1。Partition2有4個offset。Partition3有1個offset。副本的id和副本所在的機器的id恰好相同。

如果一個topic的副本數為3,那麼Kafka將在叢集中為每個partition建立3個相同的副本。叢集中的每個broker儲存一個或多個partition。多個producer和consumer可同時生產和消費資料。

broker

Kafka 叢集包含一個或多個伺服器,伺服器節點稱為broker。

broker儲存topic的資料。如果某topic有N個partition,叢集有N個broker,那麼每個broker儲存該topic的一個partition。

如果某topic有N個partition,叢集有(N+M)個broker,那麼其中有N個broker儲存該topic的一個partition,剩下的M個broker不儲存該topic的partition資料。

如果某topic有N個partition,叢集中broker數目少於N個,那麼一個broker儲存該topic的一個或多個partition。在實際生產環境中,儘量避免這種情況的發生,這種情況容易導致Kafka叢集資料不均衡。

Topic

每條釋出到Kafka叢集的訊息都有一個類別,這個類別被稱為Topic。(物理上不同Topic的訊息分開儲存,邏輯上一個Topic的訊息雖然保存於一個或多個broker上但使用者只需指定訊息的Topic即可生產或消費資料而不必關心資料存於何處)

類似於資料庫的表名

Partition

topic中的資料分割為一個或多個partition。

每個topic至少有一個partition。每個partition中的資料使用多個segment檔案儲存。partition中的資料是有序的,不同partition間的資料丟失了資料的順序。如果topic有多個partition,消費資料時就不能保證資料的順序。在需要嚴格保證訊息的消費順序的場景下,需要將partition數目設為1。

參考連結https://www.cnblogs.com/qingyunzong/p/9004509.html

參考學習:阻塞佇列-訊息佇列https://blog.csdn.net/weixin_43771381/article/details/119565871

簡單看看訊息佇列應用場景:https://www.cnblogs.com/pony1223/p/9521613.html

kafka官網:http://kafka.apache.org

Zookeeper 在 Kafka 中的作用

1、Broker註冊

Broker是分散式部署並且相互之間相互獨立,但是需要有一個註冊系統能夠將整個叢集中的Broker管理起來,此時就使用到了Zookeeper。在Zookeeper上會有一個專門用來進行Broker伺服器列表記錄的節點:

/brokers/ids

每個Broker在啟動時,都會到Zookeeper上進行註冊,即到/brokers/ids下建立屬於自己的節點,如/brokers/ids/[0...N]。

Kafka使用了全域性唯一的數字來指代每個Broker伺服器,不同的Broker必須使用不同的Broker ID進行註冊,建立完節點後,每個Broker就會將自己的IP地址和埠資訊記錄到該節點中去。其中,Broker建立的節點型別是臨時節點,一旦Broker宕機,則對應的臨時節點也會被自動刪除。

連結:https://www.jianshu.com/p/a036405f989c

測試實現簡單樣例

windows啟動命令

# 啟動伺服器  (先啟動zookeeper伺服器,再啟動kafka)  !!!千萬不要手動暴力關閉,用下面的命令關閉
bin\windows\zookeeper-server-start.bat config\zookeeper.properties
bin\windows\kafka-server-start.bat config\server.properties

# 建立主題
kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1  --topic test

# 檢視當前伺服器的主題
kafka-topics.bat --list --bootstrap-server localhost:9092
# 建立生產者,往指定主題上發訊息
kafka-console-producer.bat --broker-list localhost:9092 --topic test
# 消費者
kafka-console-consumer.bat --bootstrap-server localhost:9092 --topic test --from-beginning

# 關閉kafka伺服器
bin\windows\kafka-server-stop.bat
# 關閉zookeeper伺服器 
bin\windows\zookeeper-server-stop.bat

Demo測試

@RunWith(SpringRunner.class)
@SpringBootTest
@ContextConfiguration(classes = CommunityApplication.class)
public class KafkaTests {

    @Autowired
    private KafkaProducer kafkaProducer;

    @Test
    public void testKafka() {
        kafkaProducer.sendMessage("test", "你好");
        kafkaProducer.sendMessage("test", "在嗎");

        try {
            Thread.sleep(1000 * 10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

@Component
class KafkaProducer {

    @Autowired
    private KafkaTemplate kafkaTemplate;

    public void sendMessage(String topic, String content) {
        kafkaTemplate.send(topic, content);
    }

}

@Component
class KafkaConsumer {

    @KafkaListener(topics = {"test"})
    public void handleMessage(ConsumerRecord record) {
        System.out.println(record.value());
    }


}

生產者通過KafkaTemplate的api傳送訊息,傳送訊息是主動的

消費者接收訊息是被動的,阻塞的(監聽主題後)

傳送系統通知

事件驅動

  1. 觸發事件

    評論,點贊,關注後傳送訊息,將訊息封裝後持久化進資料庫message表中

  2. 處理事件

    • 封裝事件物件

    • 開發事件的生產者

    • 開發事件的消費者

資料庫message表結構

from_id to_id conversation_id content status
傳送方id 接收方id 私信會話id:如111_151/站內訊息型別:comment或like、follow 私信會話內容/JSON格式內容 是否已讀

具體實現:

//封裝事件物件
public class Event {

    //事件型別(如被評論,被點贊,被關注三種事件型別)
    private String topic;
    //引起事件行為的使用者
    private int userId;
    //事件發生在哪個實體上(如帖子物件,評論物件)
    private int entityType;
    //實體Id
    private int entityId;
    //實體作者,即被通知的物件
    private int entityUserId;
    //記錄其他可能存在的額外資料,使該事件物件具有擴充套件性
    private Map<String, Object> data = new HashMap<>();
}
/*
* 事件消費者
* 作用:監聽訊息佇列,將獲取的訊息存入資料庫中
* */
@Component
public class EventConsumer implements CommunityConstant {

    private static final Logger logger = LoggerFactory.getLogger(EventConsumer.class);

    @Autowired
    private MessageService messageService;

    @KafkaListener(topics = {TOPIC_COMMENT, TOPIC_LIKE, TOPIC_FOLLOW})
    public void handleCommentMessage(ConsumerRecord record) {
        if (record == null || record.value() == null) {
            logger.error("訊息的內容為空!");
            return;
        }

        Event event = JSONObject.parseObject(record.value().toString(), Event.class);
        if (event == null) {
            logger.error("訊息格式錯誤!");
            return;
        }

        // 使用message實體接收通知訊息,複用私信表結構message
        Message message = new Message();
        message.setFromId(SYSTEM_USER_ID);
        message.setToId(event.getEntityUserId());
        //這裡將主題存入message表中的conversation_id欄位,用於區分私信與通知型別
        message.setConversationId(event.getTopic());
        message.setCreateTime(new Date());

        //構造內容map物件,存放
        Map<String, Object> content = new HashMap<>();
        content.put("userId", event.getUserId());
        content.put("entityType", event.getEntityType());
        content.put("entityId", event.getEntityId());

        //將envent中的key-value物件取出存入content中
        if (!event.getData().isEmpty()) {
            for (Map.Entry<String, Object> entry : event.getData().entrySet()) {
                content.put(entry.getKey(), entry.getValue());
            }
        }

        message.setContent(JSONObject.toJSONString(content));
        messageService.addMessage(message);
    }
}

訊息系統使用常見問題

https://www.cnblogs.com/yulove/p/13121636.html

Kafka如果消費者組成員失敗,訊息將如何處理?

叢集與分散式系統情況下:

每個使用者都將分配有分割槽。 假設我們有6個分割槽:

消費者1:Partiton 1和2
消費者2:分割槽3和4
使用者3:分割槽5和6

消費者1發生故障時,消費者組將重新平衡並將空閒分割槽分配給其他消費者,從而為我們提供以下設定:

消費者2:分割槽 1 和3和4
使用者3:分割槽 2 和5&6

其他使用者將從該分割槽上最後提交的偏移量開始。

Elasticsearch,分散式搜尋引擎

什麼是Elasticsearch?

著重功能就是用來做資料的檢索和分析

  • 應用程式搜尋
  • 網站搜尋
  • 企業搜尋
  • 日誌處理和分析
  • 基礎設施指標和容器監測
  • 應用程式效能監測
  • 地理空間資料分析和視覺化
  • 安全分析
  • 業務分析

相對於MySQL它的優點

1.響應時間

mysql使用like之類的模糊查詢,遍歷全表,效率較低。

2.分詞

mysql不支援分詞。例如,當用戶在搜尋框輸入“四川火鍋”時,資料庫通常只能把這四個字去進行全部匹配。可是在文字中,可能會出現“推薦四川好吃的火鍋”,這時候就沒有結果了。

3.相關性

例如,當用戶搜尋“咖啡廳”的時候,他很可能更想知道附近哪裡可以喝咖啡,而不是怎麼開咖啡廳。

4.視覺化介面

參考:https://blog.csdn.net/weixin_39819880/article/details/82083034

倒排索引

基本結構

單詞索引(Term Index ):為每個基礎單詞建立的索引,用於在龐大的單詞詞典中快速檢索,結合壓縮技術減小記憶體佔用等

單詞詞典(Term Dictionary):搜尋引擎的通常索引單位是單詞,單詞詞典是由文件集合中出現過的所有單詞構成的字串集合,單詞詞典內每條索引項記載單詞本身的一些資訊以及指向“倒排列表”的指標。

倒排列表(Posting List):倒排列表記載了出現過某個單詞的所有文件的文件列表及單詞在該文件中出現的位置資訊,每條記錄稱為一個倒排項(Posting)。根據倒排列表,即可獲知哪些文件包含某個單詞。

倒排檔案(Inverted File):所有單詞的倒排列表往往順序地儲存在磁碟的某個檔案裡,這個檔案即被稱之為倒排檔案,倒排檔案是儲存倒排索引的物理檔案。

比如我想查詢帖子中包含“Java”的內容。

使用類似MySQL中的B+樹索引的話,通過關鍵字模糊匹配查詢,會進行全表檢索,只能依次將欄位所有資料遍歷後判斷資料中是否包含 ”Java“ ;這樣效率十分低下。

但如果我們重新構建一個索引結構:

Term(詞典) Posting List
[1,3]
[2]
Java [4]
JavaScript [4,5]
hello [2]

即首先約定好最細粒度的詞彙,每次獲取內容時即對其拆分也就是所謂”分詞“

當要查詢的內容中包含“Java”的資料時,只需要通過這個索引結構查詢到 Posting List 中所包含的資料,再通過對映的方式查詢到最終的資料。

​ 如果是Java和JavaScript以及分詞後的詞典數量巨大呢?如何找到我們想要的詞?不可能遍歷吧?而且全一一存下來記憶體大小也不允許,這便要用到所謂的字典樹來存實現了。

事實上,我們可以再對這些詞進行索引構建:

再結合ES的一些壓縮技術,Term的索引結構可以簡化成這樣:

​ 如果我們是以 j 開頭的 Term 進行搜尋,首先第一步就是通過在記憶體中的 Term Index 查詢出以 j 打頭的 Term 在 Term Dictionary 字典檔案中的哪個位置(這個位置可以是一個檔案指標,可能是一個區間範圍)。

緊接著在將這個位置區間中的所有 Term 取出,由於已經排好序,便可通過二分查詢快速定位到具體位置;這樣便可查詢出 Posting List。

更多優化

當我們搜尋的內容分詞後得到多個Posting List後如何快速地找出它們的交集?

如Java和Spring和SpringBoot同時出現的內容,進行相關性的排序?

Term(詞典) Posting List
Java [1,2,3,5]
Spring [2,3]
SpringBoot [1,2,4]

顯然ID為2的資料項相關性最高。

這時我們便可使用 Bitmap 的方式進行儲存(還節省儲存空間),同時利用先天的位與計算便可得出結果。

Bit-map的基本思想就是用一個bit位來標記某個元素對應的Value,而Key即是該元素。由於採用了Bit為單位來儲存資料,因此在儲存空間方面,可以大大節省。(PS:劃重點 節省儲存空間)如:

[1, 2,3, 5] ⇒ 101110 

[2, 3] ⇒ 000110 
 
[1, 2, 4] ⇒ 010110 

原理很簡單,二進位制從0開始,即存在置1,不存在置0。然後求出它們的與運算。

[2] ⇒ 000100

同樣的查詢需求在 MySQL 中並沒有特殊優化,只是先將資料量小的資料篩選出來之後再篩選第二個欄位,效率自然也就沒有 ES 高。

總結一下

參考Elasticsearch查詢速度為什麼這麼快?:https://zhuanlan.zhihu.com/p/266116262

開發社群搜尋功能

  1. 搜尋服務
  • - 將帖子儲存至Elasticsearch伺服器。
  • - 從Elasticsearch伺服器刪除帖子。
  • - 從Elasticsearch伺服器搜尋帖子。
  1. 釋出事件
  • - 釋出帖子時,將帖子非同步的提交到Elasticsearch伺服器。
  • - 增加評論時,將帖子非同步的提交到Elasticsearch伺服器。
  • - 在消費元件中增加一個方法,消費帖子釋出事件。

使用RestFul風格接收請求

MQ,Es,驅動設計(DDD),具備大資料能力。

https://segmentfault.com/a/1190000038895410

https://www.cnblogs.com/sunsky303/p/9438737.html