Spring Boot開發MongoDB應用實踐

本文繼續上一篇定時任務中提到的郵件服務,簡單講解Spring Boot中如何使用MongoDB進行應用開發。

上文中提到的這個簡易郵件系統大致設計思路如下:

1、發送郵件支持同步和異步發送兩種

2、郵件使用MongDB進行持久化保存

3、異步發送,直接將郵件批量保存在MongoDB中,然后通過后臺定時任務發送

4、同步發送,先調用Spring的發送郵件功能,接著將郵件批量保存至MongDB

5、不論同步還是異步,郵件發送失敗,定時任務可配置為進行N次重試

一、MongoDB

MongoDB現在已經是應用比較廣泛的文檔型NoSQL產品,有不少公司都拿MongoDB來開發日志系統。隨著MongoDB的不斷迭代更新,據說最新版已經支持ACID和事務了。不過鑒于歷史上MongoDB應用的一些問題,以及考慮到數據持久化和運維的要求,核心業務系統存儲的技術選型要非常慎重。

1、什么是MongoDB

MongoDB是由C++語言編寫的一個基于分布式文件存儲的開源數據庫系統。MongoDB將數據存儲為一個文檔,數據結構由鍵值(key=>value)對組成。MongoDB 文檔類似于 JSON 對象(也就是BSON,10gen開發的一個數據格式),字段值可以包含其他文檔,數組及文檔數組。主要優點可以概括如下:

(1)、SchemaLess,結構靈活,表結構更改非常自由,不用每次修改的時候都付出代價(想想RDBMS修改表結構要注意什么),適合業務快速迭代表結構非常不確定的場景,而且json和大多數的語言有天然的契合,還支持數組,嵌套文檔等數據類型

(2)、自帶高可用,自動主從切換(副本集)

(3)、自帶水平分片(分片),內置了路由,配置管理,應用只要連接路由,對應用來說是透明的

2、添加依賴

     <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>
mongodb

3、添加配置

配置MongoDB連接串:

spring.data.mongodb.uri=mongodb://name:pass@ip:port/database?maxPoolSize=256

如果使用多臺MongoDB數據庫服務器,參考配置如下:

spring.data.mongodb.uri=mongodb://user:pwd@ip1:port1,ip2:port2/database?maxPoolSize=512

連接串的一般配置,可以參考:猛擊這里

環境搭建好了,下面就是著手編碼了。

通常我們會有各種語言的MongoDB客戶端,直接引入調用API。在Spring Boot中,直接使用MongoTemplate即可。

4、定義DAO接口

package com.power.demo.mongodb;import com.power.demo.domain.MailDO;import java.util.Date;
import java.util.List;public interface MailDao {/*** 批量創建對象** @param entList*/void batchInsert(List<MailDO> entList);/*** 創建對象** @param ent*/void insert(MailDO ent);/*** 根據ID查詢對象** @param mailId* @return*/MailDO findByMailId(Long mailId);/*** 查詢一段時間范圍內待發送的郵件** @param startTime 開始時間* @param endTime   結束時間* @return*/List<MailDO> findToSendList(Date startTime, Date endTime);/*** 更新** @param ent*/void update(MailDO ent);/*** 刪除** @param mailId*/void delete(Long mailId);}
MailDao

5、實現DAO

package com.power.demo.mongodb;import com.power.demo.common.AppConst;
import com.power.demo.common.SendStatusType;
import com.power.demo.domain.MailDO;
import com.power.demo.util.CollectionHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;import java.util.Date;
import java.util.List;@Component
public class MailDaoImpl implements MailDao {@Autowiredprivate MongoTemplate mongoTemplate;public void batchInsert(List<MailDO> entList) {//分組批量多次插入 每次2000條List<List<MailDO>> groupList = CollectionHelper.spliceArrays(entList, AppConst.BATCH_RECORD_COUNT);for (List<MailDO> list : groupList) {mongoTemplate.insert(list, MailDO.class);}}public void insert(MailDO ent) {mongoTemplate.save(ent);}public MailDO findByMailId(Long mailId) {Query query = new Query(Criteria.where("mailId").is(mailId));MailDO ent = mongoTemplate.findOne(query, MailDO.class);return ent;}/*** 查詢一段時間范圍內待發送的郵件** @param startTime 開始時間* @param endTime   結束時間* @return*/public List<MailDO> findToSendList(Date startTime, Date endTime) {Query query = new Query(Criteria.where("create_time").gte(startTime).lt(endTime).and("has_delete").is(Boolean.FALSE).and("send_status").ne(SendStatusType.SendSuccess.toString()).and("retry_count").lt(AppConst.MAX_RETRY_COUNT)) //重試次數小于3的記錄.limit(AppConst.RECORD_COUNT); //每次取20條
List<MailDO> entList = mongoTemplate.find(query, MailDO.class);return entList;}public void update(MailDO ent) {Query query = new Query(Criteria.where("_id").is(ent.getMailId()));Update update = new Update().set("send_status", ent.getSendStatus()).set("retry_count", ent.getRetryCount()).set("remark", ent.getRemark()).set("modify_time", ent.getModifyTime()).set("modify_user", ent.getModifyUser());//更新查詢返回結果集的第一條mongoTemplate.updateFirst(query, update, MailDO.class);}public void delete(Long mailId) {Query query = new Query(Criteria.where("_id").is(mailId));mongoTemplate.remove(query, MailDO.class);}
}
MailDaoImpl

6、數據訪問對象實體

實體MailDO這里只列舉了我在實際開發應用中經常用到的字段,這個實體抽象如下:

package com.power.demo.domain;import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;import java.io.Serializable;
import java.util.Date;@Data
@Document(collection = "mailinfo")
public class MailDO implements Serializable {private static final long serialVersionUID = 1L;//唯一主鍵
    @Id@Field("mail_id")private String mailId;@Field("mail_no")private Long mailNo;//郵件類型 如:Text表示純文本、HTML等@Field("mail_type")private String mailType;//郵件發送人@Field("from_address")private String fromAddress;//郵件接收人@Field("to_address")private String toAddress;//CC郵件接收人@Field("cc_address")private String ccAddress;//BC郵件接收人@Field("bcc_address")private String bccAddress;//郵件標題@Field("subject")private String subject;//郵件內容@Field("mail_body")private String mailBody;//發送優先級 如:Normal表示普通@Field("send_priority")private String sendPriority;//處理狀態 如:SendWait表示等待發送@Field("send_status")private String sendStatus;//是否有附件@Field("has_attachment")private boolean hasAttatchment;//附件保存的絕對地址,如fastdfs返回的url@Field("attatchment_urls")private String[] attatchmentUrls;//客戶端應用編號或名稱  如:CRM、訂單、財務、運營等@Field("client_appid")private String clientAppId;//是否刪除@Field("has_delete")private boolean hasDelete;//發送次數@Field("retry_count")private int retryCount;//創建時間@Field("create_time")private Date createTime;//創建人@Field("create_user")private String createUser;//更新時間@Field("modify_time")private Date modifyTime;//更新人@Field("modify_user")private String modifyUser;//備注@Field("remark")private String remark;//擴展信息@Field("extend_info")private String extendInfo;public String getMailId() {return mailId;}public void setMailId(String mailId) {this.mailId = mailId;}public Long getMailNo() {return mailNo;}public void setMailNo(Long mailNo) {this.mailNo = mailNo;}public String getMailType() {return mailType;}public void setMailType(String mailType) {this.mailType = mailType;}public String getFromAddress() {return fromAddress;}public void setFromAddress(String fromAddress) {this.fromAddress = fromAddress;}public String getToAddress() {return toAddress;}public void setToAddress(String toAddress) {this.toAddress = toAddress;}public String getCcAddress() {return ccAddress;}public void setCcAddress(String ccAddress) {this.ccAddress = ccAddress;}public String getBccAddress() {return bccAddress;}public void setBccAddress(String bccAddress) {this.bccAddress = bccAddress;}public String getSubject() {return subject;}public void setSubject(String subject) {this.subject = subject;}public String getMailBody() {return mailBody;}public void setMailBody(String mailBody) {this.mailBody = mailBody;}public String getSendPriority() {return sendPriority;}public void setSendPriority(String sendPriority) {this.sendPriority = sendPriority;}public String getSendStatus() {return sendStatus;}public void setSendStatus(String sendStatus) {this.sendStatus = sendStatus;}public boolean isHasAttatchment() {return hasAttatchment;}public void setHasAttatchment(boolean hasAttatchment) {this.hasAttatchment = hasAttatchment;}public String[] getAttatchmentUrls() {return attatchmentUrls;}public void setAttatchmentUrls(String[] attatchmentUrls) {this.attatchmentUrls = attatchmentUrls;}public String getClientAppId() {return clientAppId;}public void setClientAppId(String clientAppId) {this.clientAppId = clientAppId;}public boolean isHasDelete() {return hasDelete;}public void setHasDelete(boolean hasDelete) {this.hasDelete = hasDelete;}public int getRetryCount() {return retryCount;}public void setRetryCount(int retryCount) {this.retryCount = retryCount;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime = createTime;}public String getCreateUser() {return createUser;}public void setCreateUser(String createUser) {this.createUser = createUser;}public Date getModifyTime() {return modifyTime;}public void setModifyTime(Date modifyTime) {this.modifyTime = modifyTime;}public String getModifyUser() {return modifyUser;}public void setModifyUser(String modifyUser) {this.modifyUser = modifyUser;}public String getRemark() {return remark;}public void setRemark(String remark) {this.remark = remark;}public String getExtendInfo() {return extendInfo;}public void setExtendInfo(String extendInfo) {this.extendInfo = extendInfo;}
}
MailDO

請大家注意實體上的注解,@Document(collection = "mailinfo")將會在文檔數據庫中創建一個mailinfo的表,@Id表示指定該字段為主鍵, @Field("mail_no")表示實體字段mailNo存在MongoDB中的字段名稱為mail_no。

根據MongoDB官方文檔介紹,如果在插入數據時沒有指定主鍵,MongoDB會自動給插入行自動加上一個主鍵_id,MongoDB客戶端把這個id類型稱為ObjectId,看上去就是一個UUID。我們可以通過注解自己設置主鍵類型,但是根據實踐,_id名稱是無法改變的。@Id和 @Field("mail_id")表面看上去是我想創建一個mail_id為主鍵的表,但是實際主鍵只有_id而沒有mail_id。當然,主鍵的類型不一定非要是UUID,可以是你自己根據業務生成的唯一流水號等等。

同時,還需要注意attatchment_urls這個字段,看上去數組也可以直接存進MongoDB中,畢竟SchemaLess曾經是MongoDB宣傳過的比RDBMS最明顯的優勢之一。

7、郵件接口

package com.power.demo.apiservice.impl;import com.google.common.collect.Lists;
import com.power.demo.apientity.request.BatchSendEmailRequest;
import com.power.demo.apientity.response.BatchSendEmailResponse;
import com.power.demo.apiservice.contract.MailApiService;
import com.power.demo.common.*;
import com.power.demo.domain.MailDO;
import com.power.demo.entity.vo.MailVO;
import com.power.demo.mongodb.MailDao;
import com.power.demo.service.contract.MailService;
import com.power.demo.util.ConfigUtil;
import com.power.demo.util.FastMapperUtil;
import com.power.demo.util.SerialNumberUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.util.Arrays;
import java.util.Date;
import java.util.List;@Component
public class MailApiServiceImpl implements MailApiService {@Autowiredprivate MailService mailService;@Autowiredprivate MailDao mailDao;/*** 發送郵件** @param request 請求* @return 發送失敗的郵件**/public BatchSendEmailResponse batchSendEmail(BatchSendEmailRequest request) {BatchSendEmailResponse response = new BatchSendEmailResponse();response.setSuccess("");if (request == null) {response.setFail("請求為空");} else if (request.getMailList() == null || request.getMailList().size() == 0) {response.setFail("待發送郵件為空");}if (response.getIsOK() == false) {return response;}List<MailVO> failedMails = Lists.newArrayList();//沒有處理成功的郵件//構造郵件對象List<MailVO> allMails = generateMails(request);failedMails = processSendMail(allMails);response.setFailMailList(failedMails);response.setSuccess(String.format("發送郵件提交成功,發送失敗的記錄為:%d", failedMails.size()));return response;}/*** 構造待發送郵件 特殊字段賦值** @param request 請求* @return 發送失敗的郵件**/private List<MailVO> generateMails(BatchSendEmailRequest request) {List<MailVO> allMails = Lists.newArrayList();for (MailVO mail : request.getMailList()) {if (mail == null) {continue;}//默認字段賦值mail.setCreateTime(new Date());mail.setModifyTime(new Date());mail.setRetryCount(0);mail.setHasDelete(false);mail.setMailNo(SerialNumberUtil.create());if (StringUtils.isEmpty(mail.getMailType())) {mail.setMailType(MailType.TEXT.toString());} else if (Arrays.stream(MailType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getMailType())).count() == 0) {mail.setMailType(MailType.TEXT.toString());}if (StringUtils.isEmpty(mail.getSendStatus())) {mail.setSendStatus(SendStatusType.SendWait.toString());} else if (Arrays.stream(SendStatusType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendStatus())).count() == 0) {mail.setSendStatus(SendStatusType.SendWait.toString());}if (StringUtils.isEmpty(mail.getSendPriority())) {mail.setSendPriority(SendPriorityType.Normal.toString());} else if (Arrays.stream(SendPriorityType.values()).filter(x -> x.toString().equalsIgnoreCase(mail.getSendPriority())).count() == 0) {mail.setSendPriority(SendPriorityType.Normal.toString());}if (StringUtils.isEmpty(mail.getMailId())) {mail.setMailId(String.valueOf(SerialNumberUtil.create()));}if (StringUtils.isEmpty(mail.getFromAddress())) {String fromAddr = ConfigUtil.getConfigVal(AppField.MAIL_SENDER_ADDR);mail.setFromAddress(fromAddr);}allMails.add(mail);}return allMails;}/*** 處理郵件** @param allMails 所有郵件* @return 發送失敗的郵件**/private List<MailVO> processSendMail(List<MailVO> allMails) {List<MailVO> failedMails = Lists.newArrayList();//沒有處理成功的郵件
List<MailVO> asyncMails = Lists.newArrayList();//待異步處理的郵件for (MailVO mail : allMails) {if (mail.isSync() == false) { //異步處理continue;}//同步調用BizResult<String> bizResult = safeSendMail(mail);//發送郵件成功if (bizResult.getIsOK() == true) {mail.setSendStatus(SendStatusType.SendSuccess.toString());mail.setRemark("同步發送郵件成功");} else {mail.setSendStatus(SendStatusType.SendFail.toString());mail.setRemark(String.format("同步發送郵件失敗:%s", bizResult.getMessage()));failedMails.add(mail);}}//批量保存郵件至MongoDB
        safeStoreMailList(allMails);return failedMails;}/*** 發送郵件** @param ent 郵件信息* @return**/private BizResult<String> safeSendMail(MailVO ent) {BizResult<String> bizSendResult = null;if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = mailService.sendSimpleMail(ent);} else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = mailService.sendHtmlMail(ent);}if (bizSendResult == null) {bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的郵件類型");}return bizSendResult;}/*** 批量保存郵件** @param entList 郵件信息列表* @return**/private boolean safeStoreMailList(List<MailVO> entList) {boolean isOK = storeMailList(entList);if (isOK == true) {return isOK;}for (int i = 1; i <= AppConst.MAX_RETRY_COUNT; i++) {try {Thread.sleep(100 * i);} catch (Exception te) {te.printStackTrace();}isOK = storeMailList(entList);if (isOK == true) {break;}}return isOK;}/*** 存儲郵件** @param entList 郵件信息列表* @return**/private boolean storeMailList(List<MailVO> entList) {boolean isOK = false;try {List<MailDO> dbEntList = Lists.newArrayList();entList.forEach(x -> {MailDO dbEnt = FastMapperUtil.cloneObject(x, MailDO.class);dbEntList.add(dbEnt);});mailDao.batchInsert(dbEntList);isOK = true;} catch (Exception e) {e.printStackTrace();}return isOK;}}
MailApiServiceImpl

到這里,MongoDB的主要存儲和查詢就搞定了。

二、郵件

在上面的郵件接口API實現中,我們定義了郵件發送服務MailService,在Spring Boot中發送郵件也非常簡單。

1、郵件配置

## 郵件配置
spring.mail.host=smtp.xxx.com //郵箱服務器地址
spring.mail.username=abc@xxx.com //用戶名
spring.mail.password=123456    //密碼
spring.mail.default-encoding=UTF-8
mail.sender.addr=abc@company.com  //發送者郵箱
mailsetting

2、簡單郵件

通過Spring的JavaMailSender對象,可以輕松實現郵件發送。

發送簡單郵件代碼:

    /*** 發送簡單文本郵件** @param ent 郵件信息**/public BizResult<String> sendSimpleMail(MailVO ent) {BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);try {if (ent == null) {bizResult.setFail("郵件信息為空");return bizResult;}if (StringUtils.isEmpty(ent.getToAddress())) {bizResult.setFail("簡單郵件,接收人郵箱為空");return bizResult;}//默認發件人設置if (StringUtils.isEmpty(ent.getFromAddress())) {ent.setFromAddress(senderAddr);}SimpleMailMessage message = new SimpleMailMessage();message.setFrom(ent.getFromAddress());message.setTo(ent.getToAddress());message.setCc(ent.getCcAddress());message.setBcc(ent.getBccAddress());message.setSubject(ent.getSubject());message.setText(ent.getMailBody());message.setSentDate(new Date());mailSender.send(message);bizResult.setSuccess("簡單郵件已經發送");} catch (Exception e) {e.printStackTrace();PowerLogger.error(String.format("發送簡單郵件時發生異常:%s", e));bizResult.setFail(String.format("發送簡單郵件時發生異常:%s", e));} finally {PowerLogger.info(String.format("簡單郵件,發送結果:%s", SerializeUtil.Serialize(bizResult)));}return bizResult;}
sendSimpleMail

3、HTML郵件

同理,我們經常要發送帶格式的HTML郵件,發送代碼可以參考如下:

    /*** 發送HTML郵件** @param ent 郵件信息**/public BizResult<String> sendHtmlMail(MailVO ent) {BizResult<String> bizResult = new BizResult<>(true, AppConst.SUCCESS);try {if (ent == null) {bizResult.setFail("郵件信息為空");return bizResult;}if (StringUtils.isEmpty(ent.getToAddress())) {bizResult.setFail("HTML郵件,接收人郵箱為空");return bizResult;}//默認發件人設置if (StringUtils.isEmpty(ent.getFromAddress())) {ent.setFromAddress(senderAddr);}MimeMessage message = mailSender.createMimeMessage();//true表示需要創建一個multipart messageMimeMessageHelper helper = new MimeMessageHelper(message, true);helper.setFrom(ent.getFromAddress());helper.setTo(ent.getToAddress());helper.setCc(ent.getCcAddress());helper.setBcc(ent.getBccAddress());helper.setSubject(ent.getSubject());helper.setText(ent.getMailBody(), true);//true表示是html郵件helper.setSentDate(new Date());//判斷有無附件 循環添加附件if (ent.isHasAttatchment() && ent.getAttatchmentUrls() != null) {for (String filePath : ent.getAttatchmentUrls()) {FileSystemResource file = new FileSystemResource(new File(filePath));String fileName = filePath.substring(filePath.lastIndexOf(File.separator));helper.addAttachment(fileName, file);}}mailSender.send(message);bizResult.setSuccess("HTML郵件已經發送");} catch (Exception e) {e.printStackTrace();PowerLogger.error(String.format("發送HTML郵件時發生異常:%s", e));bizResult.setFail(String.format("發送HTML郵件時發生異常:%s", e));} finally {PowerLogger.info(String.format("HTML郵件,發送結果:%s", SerializeUtil.Serialize(bizResult)));}return bizResult;}
sendHtmlMail

郵件附件的處理,本文僅僅是簡單示例,實際情況是通常都免不了要上傳分布式文件系統,如FastDFS等,有空我會繼續寫一下Spring Boot和分布式文件系統的應用實踐。

還記得上一篇文章里的定時任務發送郵件嗎?貼一下MailServiceImpl下的補償發送實現:

    /*** 自動查詢并發送郵件** @param startTime 開始時間* @param endTime   結束時間* @return**/public void autoSend(Date startTime, Date endTime) {StopWatch watch = DateTimeUtil.StartNew();List<MailDO> mailDOList = mailDao.findToSendList(startTime, endTime);for (MailDO dbEnt : mailDOList) {MailVO ent = FastMapperUtil.cloneObject(dbEnt, MailVO.class);BizResult<String> bizSendResult = null;if (MailType.TEXT.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = sendSimpleMail(ent);} else if (MailType.HTML.toString().equalsIgnoreCase(ent.getMailType())) {bizSendResult = sendHtmlMail(ent);}if (bizSendResult == null) {bizSendResult = new BizResult<>(false, AppConst.SUCCESS, "不支持的郵件類型");}if (bizSendResult.getIsOK() == true) {dbEnt.setSendStatus(SendStatusType.SendSuccess.toString());} else {dbEnt.setSendStatus(SendStatusType.SendFail.toString());}dbEnt.setRetryCount(dbEnt.getRetryCount() + 1);//重試次數+1
            dbEnt.setRemark(SerializeUtil.Serialize(bizSendResult));dbEnt.setModifyTime(new Date());dbEnt.setModifyUser("QuartMailTask");mailDao.update(dbEnt);}watch.stop();PowerLogger.info(String.format("本次共處理記錄數:%s,總耗時:%s", mailDOList.size(), watch.getTotalTimeMillis()));}
自動查詢并發送郵件

這里貼出來的示例代碼是線性的一個一個發送郵件,我們完全可以改造成多線程的并行處理方式來提升郵件發送處理能力。

三、MongoDB注意事項

1、常見參數設置問題

MongoDB的默認最大連接數是100,不同的客戶端有不同的實現,對于讀多寫多的應用,最大連接數可能成為瓶頸。

不過設置最大連接數也要注意內存開銷,合理配置連接池maxPoolSize。

其中,生產環境為了保證高可用,通常會配置副本集連接字符串格式mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?options

options 是連接配置中的可選項,replicaSet 是其中的一個子項。

最終的配置連接串可能形如:mongodb://username:password@host1:port1,host2:port2[,...,hostN:portN]/database?replicaSet=yourreplset&maxPoolSize=512

批量插入可以減少數據向服務器提交次數,提高性能,但是批量提交的BSON不能超過48M,不注意這個細節很容易造成數據丟失。

關于常用連接參數,可以參考這里。

2、MongoDB事務性

早期版本的MongoDB已經支持行級的事務,支持簡單的行級操作原子性,單行的操作要么全部成功,要么全部失敗。

MongoDB的WiredTiger引擎本身支持事務,官方在最新版本中,號稱完全支持ACID和事務。

3、MongoDB如何提升查詢速度

可以選取合適字段創建索引,和RDBMS一樣,MongoDB的索引也有很多種,如:單字段索引、復合索引、多Key索引、哈希索引等。

在常見的查詢字段上合理添加索引,或者定期歸檔數據,減少查詢數據量,這些手段都可以有效提高查詢速度。

還有一種非常常見的手段就是Sharding,也就是數據庫分片技術。當數據量比較大的時候,我們需要把數據分片運行在不同的機器中,以降低CPU、內存和IO的壓力。MongoDB分片技術類似MySQL的水平切分和垂直切分,主要由兩種方式做Sharding:垂直擴展和橫向切分。垂直擴展的方式就是進行集群擴展,添加更多的CPU,內存,磁盤空間等。橫向切分則是通過數據分片的方式,通過集群統一提供服務。

4、MongoDB的高可用方案

高可用是絕大多數數據庫管理系統的核心目標之一。真正的高可用系統,很少有單實例的應用形態存在。

MongoDB支持主從方式、雙機雙工方式(互備互援)和集群工作方式(多服務器互備方式),減少單點出故障的可能。

如果要想生產數據在發生故障后依然可用,就需要確保為生產數據庫多部署一臺服務器。MongoDB副本集提供了數據的保護、高可用和災難恢復的機制。在MongoDB 中,有兩種數據冗余方式,一種是 Master-Slave 模式(主從復制),一種是 Replica Sets 模式(副本集)。主從復制和副本集使用了相同的復制機制,但是副本集額外增加了自動化災備機制:如果主節點宕機,其中一個從節點會自動提升為從節點。除此之外,副本集還提供了其他改進,比如更易于恢復和更復雜地部署拓撲網絡。集群中沒有特定的主庫,主庫是選舉產生,如果主庫 down 了,會再選舉出一臺主庫。

?

參考:

<<MongoDB權威指南>>

https://docs.mongodb.com/

http://www.runoob.com/mongodb/mongodb-tutorial.html

https://www.cnblogs.com/binyue/p/5901328.html

https://yq.aliyun.com/articles/33726

https://yq.aliyun.com/articles/66623

http://www.cnblogs.com/l1pe1/p/7871790.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/278035.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/278035.shtml
英文地址,請注明出處:http://en.pswp.cn/news/278035.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Teams Bot如何做全球化

Office365在全球有大量的用戶&#xff0c;可以說是擁有最多用戶的商業SaaS平臺。Teams最近在發展迅猛&#xff0c;有1300萬日活用戶&#xff0c;已經超越了Slack。? Microsoft Teams overtakes Slack with 13 million daily users 我在設計Teams LuckyDraw bot的時候就希望我…

QuickBI助你成為分析師-郵件定時推送

創建報表過程中經常需要將報表情況定時推送給其他用戶&#xff0c;及時了解數據情況。高級版本郵件推送功能支持儀表板周期性推送到訂閱人&#xff0c;默認以當前登錄者視角查看&#xff0c;同時支持結合 行級權限進行權限控制 和 結合全局參數功能確定郵件推送內容參數&#x…

2019年5月 Teams Community Call (China)

這個月有四個話題&#xff1a; Tony Xia&#xff1a;這個月的Teams的產品更新&#xff0c;Teams開發能力的更新&#xff0c;開源項目更新&#xff0c;庫更新王遠&#xff1a;升級/遷移到Microsoft Teams劉鈺&#xff1a;Teams賬號注冊探索指南Paul Zhang/Cheung&#xff1a;Bu…

修改oracle 管理員密碼 cmd

1.sqlplus/nolog 2.conn / as sysdba 3.alter user 用戶名 identified by 新密碼;轉載于:https://www.cnblogs.com/taoqidexiaomao/p/9006927.html

在2019年6月Teams Community Call上分享的Teams app基礎架構視頻

我在2019年6月Teams Community Call(China)上分享的如何在azure上搭建典型的teams bot的基礎架構 會議視頻&#xff1a; 15:00 - 33:00 Download Video

解決 spring-cloud-starter-zipkin 啟動錯誤

應用場景&#xff1a;Spring Boot 服務添加 Zipkin 依賴&#xff0c;進行服務調用的數據采集&#xff0c;然后進行 Zipkin-Server 服務調用追蹤顯示。 示例pom.xml配置&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>s…

什么是Microsoft Teams的App Studio

Teams的app studio很多用戶可能不知道&#xff0c;但是對于一個teams平臺的開發人員來說&#xff0c;這個是開發利器&#xff0c;利用這個工具你可以輕松的配置manifest文件&#xff0c;可以輕松的一站式創建teams app所需要的所有東西。而且你可以很方便的可視化配置adaptive …

Spring Cloud-鴻鵠Cloud分布式微服務云系統—架構圖

這邊結合了當前大部分企業的通用需求&#xff0c;包括技術的選型比較嚴格、苛刻&#xff0c;不僅要用業界最流行的技術&#xff0c;還要和國際接軌&#xff0c;在未來的5~10年內不能out。作為公司的架構師&#xff0c;也要有一種放眼世界的眼光&#xff0c;不僅要給公司做好的技…

Teams bot的調用限制

上個月Teams團隊發布了對Teams app/bot調用api的頻率的限制。這也從側面說明Teams app越來越多&#xff0c;Teams團隊需要優先保證Teams本身的計算資源&#xff0c;來提供流暢的用戶體驗。 具體的每個限制指標在這里&#xff1a; https://docs.microsoft.com/en-us/microsoftt…

Array的sort方法

作為一個剛開始學習的前端&#xff0c;小結一下&#xff1a;sort方法&#xff1a; 如果調用該方法時沒有使用參數&#xff0c;將按字母順序對數組中的元素進行排序&#xff0c;說得更精確點&#xff0c;是按照字符編碼的順序進行排序。要實現這一點&#xff0c;首先應把數組的元…

如何使用ARM創建Teams Bot所需要的Azure資源

相信很多devops已經全面開始使用ARM來創建azure資源了&#xff0c;ARM有很多方便的地方&#xff0c;比如簡單易學&#xff0c;Infrastructure as Code&#xff0c;但是深入使用ARM開始會發現一些有待改進的方面。這篇文章主要是分享一下我在做Teams app的時候使用ARM來創建資源…

Bot Service自帶的數據分析統計功能

每個產品上線后都希望自己能實時看到多少用戶在使用我的產品&#xff0c;我的服務&#xff0c;有多少使用量&#xff0c;有沒有遇到問題。市面上做用戶數據、行為分析的公司也不少&#xff0c;但是大多數都需要我們修改一些代碼來集成第三方的sdk庫。 我的teams app上線后也急…

LuckyDraw bot有幸被提名為微軟2019的People's Choice app

上個月微軟進行了一個全世界提名活動&#xff0c;目標是選出微軟2019年度People’s Choice app。 很幸運&#xff0c;我的LuckyDraw bot得到了來自世界各地使用者的投票&#xff0c;其中也包含Teams中國社區和很多朋友的支持。 https://developer.microsoft.com/en-us/microso…

圖靈社區 和 大家網

http://www.ituring.com.cn/ http://club.topsage.com/ 大家論壇 http://www.topsage.com/ http://www.dxbbba.com/ 大學生必備吧 轉載于:https://www.cnblogs.com/onelikeone/p/9023267.html

Teams內嵌的卡片image的限制

我的LuckyDraw上線后收到了不少有價值的反饋&#xff0c;其中有一部分是針對圖片的&#xff0c;有一些用戶說他們填寫了image的url&#xff0c;但是圖片顯示不出來。 實際上這個問題在我提交這個應用到微軟審核團隊的時候&#xff0c;審核團隊也提出了類似問題。但這個是Teams本…

Python 面向對象編程(進階部分)

靜態方法&#xff1a; 通過 staticmethod 裝飾器即可把其裝飾的方法變為一個靜態方法。普通的方法&#xff0c;可以在實例化后直接調用&#xff0c;并且在方法里可以通過self.調用實例變量或類變量&#xff0c;但靜態方法是不可以訪問實例變量或類變量的&#xff0c;一個不能訪…

分享實錄|爭議不斷地EOS,我們如何才能理性看待?

1 EOS基本介紹 EOS是Block.One公司正在研發的一個區塊鏈底層公鏈系統&#xff0c;目的是解決現有的區塊鏈應用性能低、安全性差、開發難度高以及過度依賴手續費的問題&#xff0c;實現分布式應用的性能擴展。EOS提供帳戶&#xff0c;身份驗證&#xff0c;數據庫&#xff0c;異步…

Teams的Incoming Webhook

我在去年的一篇文章里介紹過Teams的outgoing webhook&#xff0c;這個可以用來實現一個簡單的用戶和service對話機制。 Teams除了outgoing webhook以外&#xff0c;還有一個incoming webhook&#xff0c;從名字上我們也可以立刻知道&#xff0c;這個webhook是用來處理進入Team…

Comet OJ - Contest #0題解

傳送門 菜爆了……總共只有一道題會做的……而且也沒有短裙好難過 為啥必須得有手機才能注冊賬號啊喂……歧視么…… \(A\) 解方程 推一下柿子大概就是 \[x-\sqrt{n}yz2\sqrt{yz}\] 如果\(\sqrt{n}\)是無理數&#xff0c;那么就是 \[xyz,{n\over 4}yz\] 那么要滿足\(n\)必須是\…

tornado 08 數據庫-ORM-SQLAlchemy-表關系和簡單登錄注冊

tornado 08 數據庫-ORM-SQLAlchemy-表關系和簡單登錄注冊 引言 #在數據庫&#xff0c;所謂表關系&#xff0c;只是人為認為的添加上去的表與表之間的關系&#xff0c;只是邏輯上認為的關系&#xff0c;實際上數據庫里面的表之間并沒有所謂的表關系 一、一對一表關系 Module #需…