基于MongoDB實(shí)現(xiàn)聊天記錄的存儲問題小結(jié)
一、mongodb簡介
1.1 mongodb簡介
MongoDB是一個基于分布式文件存儲的數(shù)據(jù)庫,使用C++語言編寫。它旨在為WEB應(yīng)用提供可擴(kuò)展的高性能數(shù)據(jù)存儲解決方案。MongoDB介于關(guān)系數(shù)據(jù)庫和非關(guān)系數(shù)據(jù)庫之間,是非關(guān)系數(shù)據(jù)庫當(dāng)中功能最豐富、最像關(guān)系數(shù)據(jù)庫的。
MongoDB將數(shù)據(jù)存儲為一個文檔,數(shù)據(jù)結(jié)構(gòu)由鍵值(key=>value)對組成。MongoDB文檔類似于JSON對象,字段值可以包含其他文檔、數(shù)組及文檔數(shù)組。它支持的數(shù)據(jù)結(jié)構(gòu)非常松散,是類似json的bson格式,因此可以存儲比較復(fù)雜的數(shù)據(jù)類型。
MongoDB最大的特點(diǎn)是它支持的查詢語言非常強(qiáng)大,其語法有點(diǎn)類似于面向?qū)ο蟮牟樵冋Z言,幾乎可以實(shí)現(xiàn)類似關(guān)系數(shù)據(jù)庫單表查詢的絕大部分功能,而且還支持對數(shù)據(jù)建立索引。
此外,MongoDB還具有以下特點(diǎn):
- 面向集合存儲,易存儲對象類型的數(shù)據(jù)。
- 模式自由。
- 支持動態(tài)查詢。
- 支持完全索引,包含內(nèi)部對象。
- 支持查詢。
- 支持復(fù)制和故障恢復(fù)。
- 使用高效的二進(jìn)制數(shù)據(jù)存儲,包括大型對象(如視頻等)。
- 自動處理碎片,以支持云計算層次的擴(kuò)展性。
- 可通過網(wǎng)絡(luò)訪問。
在高負(fù)載情況下,添加更多的節(jié)點(diǎn)可以保證服務(wù)器性能。MongoDB也易于部署和使用,存儲數(shù)據(jù)非常方便。
總的來說,MongoDB是一個高性能、易部署、易使用的數(shù)據(jù)庫系統(tǒng),具有豐富的功能和特點(diǎn),適用于各種規(guī)模的應(yīng)用程序和場景。
1.2 mongodb利弊
優(yōu)點(diǎn):
- 靈活性:MongoDB采用文檔存儲方式,這意味著數(shù)據(jù)以鍵值對的形式存儲在BSON(二進(jìn)制JSON)格式中,這使得它能夠存儲復(fù)雜的數(shù)據(jù)類型,包括數(shù)組、嵌套文檔等。這種靈活性使得MongoDB能夠輕松地適應(yīng)各種數(shù)據(jù)模型。
- 易擴(kuò)展性:MongoDB支持自動分片,這使得它能夠輕松地擴(kuò)展到大量數(shù)據(jù)和復(fù)雜查詢場景。通過添加更多的節(jié)點(diǎn),MongoDB可以自動地將數(shù)據(jù)分布到不同的節(jié)點(diǎn)上,從而提高整體性能。
- 高性能:MongoDB使用內(nèi)存映射機(jī)制,將數(shù)據(jù)暫時存儲在內(nèi)存中,提高了IO效率,MongoDB支持快速的讀寫操作,尤其適用于大規(guī)模數(shù)據(jù)和高并發(fā)場景。它還提供了多種查詢方式,包括范圍查詢、排序、聚合等,這使得查詢操作比傳統(tǒng)的關(guān)系型數(shù)據(jù)庫更加快速。
- 社區(qū)支持:MongoDB有一個活躍的開源社區(qū),這意味著用戶可以很容易地找到幫助和資源,以及最新的技術(shù)更新和最佳實(shí)踐。
缺點(diǎn):
- 缺乏事務(wù)支持:MongoDB不支持傳統(tǒng)的事務(wù)處理特性,這意味著在處理多個文檔或集合之間的復(fù)雜操作時可能會遇到問題。雖然MongoDB提供了樂觀并發(fā)控制和文檔級鎖定來解決并發(fā)問題,但在需要完整的事務(wù)支持的場景下可能不夠用。
- 復(fù)雜性:由于MongoDB的靈活性,它可能比傳統(tǒng)的關(guān)系型數(shù)據(jù)庫更復(fù)雜。對于初學(xué)者來說,可能需要更長的時間來學(xué)習(xí)和理解其數(shù)據(jù)模型和查詢語言。
- 數(shù)據(jù)一致性:MongoDB采用最終一致性模型,而不是強(qiáng)一致性模型。這意味著在某些情況下,數(shù)據(jù)可能不會立即反映所有的更改。這對于需要強(qiáng)一致性的應(yīng)用來說可能是一個問題。
- 磁盤空間占用:由于MongoDB使用文件存儲數(shù)據(jù),因此可能會占用大量的磁盤空間。特別是在高寫入負(fù)載的情況下,由于數(shù)據(jù)文件的增長和收縮,可能會導(dǎo)致磁盤碎片的產(chǎn)生。
1.3 mongodb使用場景
MongoDB的使用場景非常廣泛,包括以下幾個方面:
- 內(nèi)容管理和發(fā)布系統(tǒng):MongoDB的靈活文檔模型和高性能寫入能力使其成為內(nèi)容管理和發(fā)布系統(tǒng)的理想選擇。它可以存儲和檢索各種類型的內(nèi)容,如文章、圖片、視頻等。
- 個性化推薦系統(tǒng):MongoDB可以存儲和查詢用戶的個人偏好和行為數(shù)據(jù),從而支持個性化推薦。通過使用MongoDB的高性能索引和聚合功能,可以快速地分析和提供個性化的推薦結(jié)果。
- 實(shí)時分析和大數(shù)據(jù)處理:MongoDB的分布式架構(gòu)和高可擴(kuò)展性使其非常適合實(shí)時分析和大數(shù)據(jù)處理任務(wù)。它可以處理大量的并發(fā)讀寫操作,并且支持復(fù)雜的查詢和聚合操作。
- 時序數(shù)據(jù)管理:MongoDB的存儲引擎和索引結(jié)構(gòu)對時序數(shù)據(jù)的管理非常高效。它可以存儲和查詢大量的時間序列數(shù)據(jù),如傳感器數(shù)據(jù)、日志數(shù)據(jù)等。
- 實(shí)時數(shù)據(jù)分析和監(jiān)控:MongoDB的副本集和分片功能可以實(shí)現(xiàn)實(shí)時數(shù)據(jù)分析和監(jiān)控。它可以處理大量的并發(fā)寫入操作,并提供實(shí)時的查詢結(jié)果。
- 社交網(wǎng)絡(luò)和協(xié)作平臺:MongoDB的文檔模型非常適合存儲和查詢社交網(wǎng)絡(luò)和協(xié)作平臺的數(shù)據(jù)。它可以存儲用戶的個人資料、關(guān)系圖譜、消息等。
- 位置數(shù)據(jù)管理和地理信息系統(tǒng):MongoDB的地理空間索引和查詢功能使其成為管理位置數(shù)據(jù)和地理信息系統(tǒng)的理想選擇。它可以存儲和查詢地理位置、地理邊界、地理特征等數(shù)據(jù)。
- 游戲場景:使用MongoDB存儲游戲用戶信息,用戶的裝備、積分等直接以內(nèi)嵌文檔的形式存儲,方便查詢、更新。
- 物流場景:使用MongoDB存儲訂單信息,訂單狀態(tài)在運(yùn)送過程中會不斷更新,以MongoDB內(nèi)嵌數(shù)組的形式來存儲,一次查詢就能將訂單所有的變更讀取出來。
總的來說,MongoDB適用于各種場景,從網(wǎng)站數(shù)據(jù)到大數(shù)據(jù)處理,再到社交網(wǎng)絡(luò)和游戲等領(lǐng)域,它都表現(xiàn)出強(qiáng)大的靈活性和可擴(kuò)展性。
1.4 mongodb存儲聊天記錄和mysql存儲的抉擇
選擇使用MySQL還是MongoDB來存儲聊天記錄取決于具體需求和場景。以下是兩者的一些比較:
MySQL
- 結(jié)構(gòu)化數(shù)據(jù):適用于存儲結(jié)構(gòu)化數(shù)據(jù),如聊天記錄中的文本、時間戳等。
- 事務(wù)處理:支持事務(wù)處理,可以保證數(shù)據(jù)的一致性和完整性。
- 成熟度與社區(qū)支持:是一個成熟的關(guān)系型數(shù)據(jù)庫管理系統(tǒng),擁有龐大的用戶基礎(chǔ)和豐富的社區(qū)支持。
- 查詢優(yōu)化:適合對復(fù)雜查詢和性能要求較高的場景。
MongoDB
- 非結(jié)構(gòu)化數(shù)據(jù):適用于存儲非結(jié)構(gòu)化數(shù)據(jù),如圖片、語音消息等。
- 靈活性:具有靈活的數(shù)據(jù)模型,可以輕松處理聊天記錄中的各種格式和結(jié)構(gòu)。
- 水平擴(kuò)展性:適用于大規(guī)模數(shù)據(jù)的存儲和管理,具有水平擴(kuò)展性。
- 實(shí)時性:適合需要實(shí)時處理和快速響應(yīng)的場景,如實(shí)時聊天應(yīng)用。
綜上所述,如果聊天記錄主要是結(jié)構(gòu)化數(shù)據(jù)并且需要事務(wù)處理和復(fù)雜查詢,MySQL可能是一個更好的選擇。如果聊天記錄包含大量非結(jié)構(gòu)化數(shù)據(jù)并且需要水平擴(kuò)展和實(shí)時處理能力,對事務(wù)的完整性要求不高對存取速度要求較高我建議使用新興的nosql類型數(shù)據(jù) MongoDB可能更適合。
二、業(yè)務(wù)場景
需求:我們的需求是實(shí)現(xiàn)一個與AI對話的聊天系統(tǒng),大概分為兩個部分,一個是會話,一個是聊天
我給大家放張圖幫助理解(左邊是會話,右邊是聊天)
三、聊天記錄的存儲和查詢
3.1 聊天記錄數(shù)據(jù)集合的設(shè)計,可以理解為數(shù)據(jù)表
會話collection:
@Data @Document(value = "agents_session") public class AgentsSession implements Serializable { private static final long serialVersionUID = 198529858452480909L; private String id; private String agentId; /** * session id */ private String sessionId; /** * 發(fā)送者id */ private String senderCode; /** * 消息(當(dāng)前會話組中最早的一次提問(也就是用戶想AI提問)) */ private String message; /** * 發(fā)送時間 */ private String sendTime; /** * 是否刪除 */ private Boolean isDeleted; }
聊天記錄collection:
@Data @Document(value = "agents_chat_messages") public class AgentsChatMessages implements Serializable { private static final long serialVersionUID = 823228953137629152L; private String id; /** * 會話id */ private String sessionId; /** * 消息內(nèi)容 */ private String message; /** * 接收狀態(tài) */ private Integer receiveStatus; /** * 發(fā)送者id */ private String senderCode; /** * 接收者id */ private String recipientCode; /** * 發(fā)送時間 */ private String sendTime; /** * 消息類型 文本、圖片、文件、語音等 */ private String messageType; /** * 消息內(nèi)容漢字個數(shù) */ private Integer tokens; /** * 當(dāng)前支持以下: * user: 表示用戶 * assistant: 表示對話助手 */ private String role; /** * 是否已讀 */ private Boolean isRead; /** * 是否刪除 */ private Boolean isDeleted; /** * 問答對匹配id */ private String questionAnswerId; }
3.2 聊天記錄存取的實(shí)現(xiàn)
service實(shí)現(xiàn)
public interface ChatMessagesService { /** * 分頁獲取會話列表 * @param dto * @return */ PageModel<AgentsSessionVO> queryAgentSessionPage(AgentsSessionDTO dto, PageRequestDTO page); /** * 通過會話id分頁獲取會話列表 * @param dto * @return */ PageModel<AgentsChatMessagesVO> queryAgentsChatMessagesPage(AgentsChatMessagesDTO dto, PageRequestDTO page); /** * 保存會話和聊天 * @param messagesDTO */ void saveSessionAndMessages(AgentsChatMessagesDTO messagesDTO); }
實(shí)現(xiàn)類:
@Service @Slf4j public class ChatMessagesServiceImpl implements ChatMessagesService { @Resource private MongoTemplate mongoTemplate; /** * 獲取會話列表 * * @param dto * @return */ @Override public PageModel<AgentsSessionVO> queryAgentSessionPage(AgentsSessionDTO dto, PageRequestDTO page) { try { // 創(chuàng)建分頁對象 Pageable pageable = PageRequest.of(page.getPage() - 1, page.getSize(), Sort.Direction.DESC, "sendTime"); // 注意:頁碼從0開始,所以需要減1 // 創(chuàng)建查詢對象 Query query = new Query(); query.addCriteria(Criteria.where("senderCode").is(dto.getUserCode()).and("isDeleted").is(false)); //設(shè)置模糊查詢 if (StringUtils.isNotEmpty(dto.getMessage())) { query.addCriteria(Criteria.where("message").regex(dto.getMessage())); } if (!CollectionUtils.isEmpty(dto.getAgentsIds())) { // in 條件查詢 Criteria criteria = Criteria.where("agentId").in(dto.getAgentsIds()); query.addCriteria(criteria); } // 排序 query.with(Sort.by(Sort.Order.desc("sendTime"))); // 設(shè)置分頁 query.with(pageable); List<AgentsSessionVO> list = mongoTemplate.find(query, AgentsSessionVO.class, CommonConstant.AGENTS_SESSION); list.forEach(s ->{ try { s.setMessage(AesEncryptionUtil.decrypt(s.getMessage())); } catch (Exception e) { throw new HxyAgentsXException("數(shù)據(jù)加載失敗", e); } }); long count = mongoTemplate.count(query, AgentsSessionVO.class, CommonConstant.AGENTS_SESSION); return new PageModel<AgentsSessionVO>(list, count, pageable); } catch (Exception e) { log.error("獲取會話列表異常"); throw new HxyAgentsXException("獲取會話列表異常", e); } } /** * 通過會話id分頁獲取聊天記錄 * * @param dto * @return */ @Override public PageModel<AgentsChatMessagesVO> queryAgentsChatMessagesPage(AgentsChatMessagesDTO dto, PageRequestDTO page) { try { // 創(chuàng)建分頁對象 Pageable pageable = PageRequest.of(page.getPage() - 1, page.getSize(), Sort.Direction.ASC,"sendTime"); // 注意:頁碼從0開始,所以需要減1 // 創(chuàng)建查詢對象 Query query = new Query(); //設(shè)置模糊查詢 if (StringUtils.isNotEmpty(dto.getMessage())) { query.addCriteria(Criteria.where("message").regex(dto.getMessage())); } query.addCriteria(Criteria.where("sessionId").is(dto.getSessionId()).and("isDeleted").is(false)); query.addCriteria(new Criteria().orOperator(Criteria.where("senderCode").is(dto.getUserCode()),Criteria.where("recipientCode").is(dto.getUserCode()))); // 排序 query.with(Sort.by(Sort.Order.asc("sendTime"))); // 設(shè)置分頁 query.with(pageable); List<AgentsChatMessagesVO> list = mongoTemplate.find(query, AgentsChatMessagesVO.class, CommonConstant.AGENTS_CHAT_MESSAGES); list.forEach(m ->{ try { m.setMessage(AesEncryptionUtil.decrypt(m.getMessage())); } catch (Exception e) { throw new HxyAgentsXException("數(shù)據(jù)加載失敗", e); } }); long count = mongoTemplate.count(query, AgentsChatMessagesVO.class, CommonConstant.AGENTS_CHAT_MESSAGES); return new PageModel<AgentsChatMessagesVO>(list, count, pageable); } catch (Exception e) { log.error("獲取聊天記錄列表異常"); throw new HxyAgentsXException("獲取聊天記錄列表異常", e); } } /** * 存會話聊天 * @param messagesDTO */ @Override public void saveSessionAndMessages(AgentsChatMessagesDTO messagesDTO) { try { Criteria.where("sessionId").is(messagesDTO.getSessionId()); AgentsSession agentsSessionOne = mongoTemplate.findOne(new Query(Criteria.where("sessionId").is(messagesDTO.getSessionId()). and("isDeleted").is(false)), AgentsSession.class); // 會話 if (agentsSessionOne == null){ AgentsSession agentsSession = new AgentsSession(); agentsSession.setId(UUIDUtils.getUUID()); agentsSession.setSessionId(messagesDTO.getSessionId()); agentsSession.setAgentId(messagesDTO.getAgentId()); agentsSession.setMessage(AesEncryptionUtil.encrypt(messagesDTO.getMessage())); agentsSession.setSenderCode(messagesDTO.getSenderCode()); agentsSession.setIsDeleted(false); agentsSession.setSendTime(LocalDateUtil.localDateTimeToString(LocalDateUtil.getLocalDateTime(),"yyyy-MM-dd HH:mm:ss")); mongoTemplate.insert(agentsSession); } // 聊天 AgentsChatMessages agentsChatMessages = new AgentsChatMessages(); agentsChatMessages.setId(UUIDUtils.getUUID()); agentsChatMessages.setSessionId(messagesDTO.getSessionId()); agentsChatMessages.setMessage(AesEncryptionUtil.encrypt(messagesDTO.getMessage())); agentsChatMessages.setMessageType("text"); agentsChatMessages.setRole(messagesDTO.getRole()); agentsChatMessages.setIsRead(true); agentsChatMessages.setIsDeleted(false); agentsChatMessages.setSenderCode(messagesDTO.getSenderCode()); agentsChatMessages.setRecipientCode(messagesDTO.getRecipientCode()); agentsChatMessages.setSendTime(LocalDateUtil.localDateTimeToString(LocalDateUtil.getLocalDateTime(),"yyyy-MM-dd HH:mm:ss")); agentsChatMessages.setQuestionAnswerId(messagesDTO.getQuestionAnswerId()); mongoTemplate.insert(agentsChatMessages); } catch (Exception e) { log.error("保存會話聊天失敗",e); throw new HxyAgentsXException("保存會話聊天失敗",e); } } }
聊天內(nèi)容加密:
public class AesEncryptionUtil { private static final String ALGORITHM = "AES"; private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding"; private static final byte[] keyValue = "yourSecretKey".getBytes(StandardCharsets.UTF_8); public static String encrypt(String valueToEncrypt) throws Exception { SecretKeySpec key = new SecretKeySpec(keyValue, ALGORITHM); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] encryptedByteValue = cipher.doFinal(valueToEncrypt.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(encryptedByteValue); } public static String decrypt(String encryptedValue) throws Exception { SecretKeySpec key = new SecretKeySpec(keyValue, ALGORITHM); Cipher cipher = Cipher.getInstance(TRANSFORMATION); cipher.init(Cipher.DECRYPT_MODE, key); byte[] originalValue = cipher.doFinal(Base64.getDecoder().decode(encryptedValue)); return new String(originalValue, StandardCharsets.UTF_8); } public static void main(String[] args) throws NoSuchAlgorithmException { // 創(chuàng)建AES密鑰生成器 KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); // 設(shè)置密鑰長度為256位 keyGenerator.init(128); // 生成密鑰 SecretKey secretKey = keyGenerator.generateKey(); // 將密鑰轉(zhuǎn)換為字符串 String keyString = Base64.getEncoder().encodeToString(secretKey.getEncoded()); System.out.println("Generated AES key (Base64): " + keyString); } }
3.3 保存問答聊天
在用戶問答的時候保存聊天內(nèi)容
在模型回答結(jié)束的時候保存聊天內(nèi)容
最后大家可以結(jié)合自己的業(yè)務(wù)來實(shí)現(xiàn)聊天記錄的存取。
到此這篇關(guān)于基于MongoDB實(shí)現(xiàn)聊天記錄的存儲的文章就介紹到這了,更多相關(guān)MongoDB聊天記錄的存儲內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Windows系統(tǒng)下安裝MongoDB并內(nèi)網(wǎng)穿透遠(yuǎn)程連接
這篇文章主要給大家介紹了關(guān)于Windows系統(tǒng)下安裝MongoDB并內(nèi)網(wǎng)穿透遠(yuǎn)程連接的相關(guān)資料,文中通過圖文將步驟介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用MongoDB具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03MongoDB中常用操作$addToSet、$pop和$rename
本文主要介紹了MongoDB中常用操作$addToSet、$pop和$rename,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12MongoDB數(shù)據(jù)庫查詢性能提高40倍的經(jīng)歷分享
大家在使用 MongoDB 的時候有沒有碰到過性能問題呢?下面這篇文章主要給大家分享了MongoDB數(shù)據(jù)庫查詢性能提高40倍的經(jīng)歷,需要的朋友可以參考借鑒,下面來一起看看吧。2017-02-02mongodb使用c#驅(qū)動數(shù)據(jù)插入demo
今天小編就為大家分享一篇關(guān)于mongodb使用c#驅(qū)動數(shù)據(jù)插入demo,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-01-01mongodb replica set 配置高性能多服務(wù)器詳解
mongodb的多服務(wù)器配置,以前寫過一篇文章,是master-slave模式的,master-slave模式,不能自動實(shí)現(xiàn)故障轉(zhuǎn)移和恢復(fù)。所以推薦大家使用mongodb的replica set,來實(shí)現(xiàn)多服務(wù)器的高性能。2014-07-07