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.mzph.cn/news/278035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

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…

在2019年6月Teams Community Call上分享的Teams app基础架构视频

我在2019年6月Teams Community Call(China)上分享的如何在azure上搭建典型的teams bot的基础架构 会议视频&#xff1a; 15:00 - 33:00 Download Video

什么是Microsoft Teams的App Studio

Teams的app studio很多用户可能不知道&#xff0c;但是对于一个teams平台的开发人员来说&#xff0c;这个是开发利器&#xff0c;利用这个工具你可以轻松的配置manifest文件&#xff0c;可以轻松的一站式创建teams app所需要的所有东西。而且你可以很方便的可视化配置adaptive …

如何使用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上线后也急…

图灵社区 和 大家网

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本…

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 #需…

白白的(baibaide)

白白的&#xff08;baibaide&#xff09; 有一个长度为 $n$ 的序列 $a_1, a_2, \dots, a_n$&#xff0c;一开始每个位置都是白色。如果一个区间中每个位置都是白色&#xff0c;则称这是一个白白的区间。如果一个白白的区间向左或向右延长后都不是白白的区间了&#xff0c;则称这…

使用Adaptive cards来构建Teams app的界面

Teams app的task module十分好用&#xff0c;当用户点击了一个卡片上的按钮是可以在Teams里弹出一个对话框&#xff0c;对话框的内容可以是开发人员自己的一个网页页面&#xff0c;或者是adaptive card。 在我的LuckyDraw bot里&#xff0c;我比较了这两种的优势和劣势&#xf…

Boosting(提升方法)之GBDT

一、GBDT的通俗理解 提升方法采用的是加法模型和前向分步算法来解决分类和回归问题&#xff0c;而以决策树作为基函数的提升方法称为提升树&#xff08;boosting tree&#xff09;。GBDT(Gradient Boosting Decision Tree)就是提升树算法的一种&#xff0c;它使用的基学习器是C…

CC攻击原理及防范方法

一、 CC攻击的原理&#xff1a; CC攻击的原理就是攻击者控制某些主机不停地发大量数据包给对方服务器造成服务器资源耗尽&#xff0c;一直到宕机崩溃。CC主要是用来消耗服务器资源的&#xff0c;每个人都有这样的体验&#xff1a;当一个网页访问的人数特别多的时候&#xff0c…

分享到Teams

在今年三月份末&#xff0c;Teams的官方文档推出了一个新功能&#xff1a;将网页&#xff08;一个URL&#xff09;分享到Teams里。 也就是说开发人员现在可以很方便的开发一个页面&#xff0c;页面里有一个Teams的图标&#xff0c;当访问此页面的最终用户点击这个图标后可以将…

MySQL命令行查询乱码解决方法

转自Agoly的博客&#xff0c;原文链接https://www.cnblogs.com/qmfsun/p/4846467.html 感谢博主Agoly这篇文章说的很详细很透彻。 MySQL会出现中文乱码的原因不外乎下列几点&#xff1a;1.server本身设定问题&#xff0c;例如还停留在latin1 2.table的语系设定问题(包含charact…

『流畅的Python』第1~4章笔记_数据结构、编码

由于1~4章内容零散且基础&#xff0c;所以统计一下涉及到的内容&#xff0c;记录一下&#xff0c;方便查阅&#xff08;第一张图右键新页面打开即可看到清晰大图&#xff09;

docker 安装ELK

参考文档&#xff1a; Docker ELK使用文档&#xff1a;http://elk-docker.readthedocs.io/ 1.拉取镜像 查看 Docker Hub 的镜像 docker search elk 拉取镜像 sudo docker pull sebp/elk 2.启动容器 docker run -p 5601:5601 -p 9200:9200 -p 5044:5044 -p 4560:4560 -it --na…

在Teams Hackathon上介绍LuckyDraw

很荣幸有机会在今天的Teams Hackathon上介绍LuckyDraw这个teams app。 因为到场的都是各路开发高手&#xff0c;所以当时在准备这个ppt的时候特别增加了难度等级&#xff0c;哈哈。 从如何构建云原生的Teams app&#xff0c;到IaC&#xff0c;重点讲了如何开发一个面向全球用户…