JAVA-第十二部分-MongoDB

621 阅读3分钟

业务应用场景

  • 高并发、高存储、高可扩展性和可用性
  • NoSql,松散型数据库
  • 社交场景,存储用户信息,地理位置;游戏场景,查询方便,高效率存储和访问;物流场景,订单状态不断更新;物联网场景,存储所有接入的智能设备信息以及日志信息;视频直播,互动信息和用户信息
  • 数据量大、写入操作频繁、价值较低,对事务性要求不高
  • 不需要事务及复杂json支持;需要TB设置PB级别数据存储;存储的数据不丢失;大量的地理位置查询、文本查询
  • 无模式,没有具体的链,类似于json的bson结构(二进制的json);文档型数据库,最像关系型数据库的非关系型数据库

mac

启动

  • bash_profile添加环境变量
vim ~/.bash_profile
export PATH=/usr/local/mongodb/bin:$PATH
source ~/.bash_profile
  • 服务启动
sudo mongod --dbpath=(某文件夹下的)/data/db/
  • 客户端启动 mongo
  • 客户端连接指定端口 mongo --host=127.0.0.1 --port=27017

配置

  • 创建文件夹
sudo mkdir -p single/log
sudo mkdir -p single/data/db
  • 创建配置文件
sudo vi single/mongod.conf
  • 配置文件
systemLog:
 //发送所有日志输出的目标指定为文件
 destination: file
 //mongod或mongos发送日志的目标文件
 path: /usr/local/mongodb/single/log/mongod.log
 //实例重新启动时,会将新条目附加到现有日志末尾
 logAppend:true

storage:
 //存储目录
 dbPath: /usr/local/mongodb/single/data/db
 journal:
  //启动或禁用持久性日志以确保数据文件保持有效和可恢复
  enabled:true
processManagement:
 //启动在后台运行服务器的守护进程模式
 fork:true
net:
 //访问ip
 bindIp: localhost
 //端口
 port:27017
  • 配置文件启动,报1或者48的错误,一般是配置文件写错了,配置文件是yml的格式,空格缩近
sudo mongod -f mongod.conf
  • 或者去要给文件设置权限
sudo chown apple /usr/local/mongodb/replica-sets/myrs_27019/log/mongod.log
sudo chown apple /usr/local/mongodb/replica-sets/myrs_27019/data/db/
  • 查看服务
ps aux | grep mongod //查看进程pid

关闭

  • 客户端关闭
use admin
db.shutdownServer()
  • kill关闭
ps aux | grep mongod //查看进程pid
sudo kill -2 pid

基本常用命令

数据库操作

  • 查看所有数据库 show dbs
  • 新建数据库 use articledb,刚创建时只存在内存中,并没有持久化,当有一个集合的时候才会持久化
  • 查看当前数据库 db
  • admin存放用户信息权限等;local这个数据永远不会被复制,存储限于本地单台服务器的任意集合;config用于Mongo分片设置事,在内部使用,保存分片相关信息
  • 删除数据库 db.dropDatabase()

集合操作

  • 显式创建 db.createCollection("article")
  • 查看集合 show collections
  • 删除集合 db.article.drop()

文档操作

插入和查询

  • 单条插入 db.comment.insert({"articleid":"10000", "content":"好累", "userid":"102"})
  • 查询 db.comment.find()
  • 多条插入
db.comment.insertMany([{"_id":"1", "articleid":"10000", "content":"好累", "userid":"102"},{"_id":"2", "articleid":"10001", "content":"不累", "userid":"105"},{"_id":"3", "articleid":"10002", "content":"好不累", "userid":"104"},{"_id":"4", "articleid":"10003", "content":"不好累", "userid":"103"}]);
  • 条件查询 db.comment.find({"_id":"1"})
  • 条件查询返回一条数据 db.comment.findOne({articleid:"10000"})
  • 显示部分字段 db.comment.find({articleid:"10000"},{articleid:1, content:1, _id:0}) _id默认显示,排除需要置0;需要显示的字段置1
  • 插入多条有异常通过try...catch捕捉
try {
//插入语句
} catch(e) {
 print(e);
}

更新

  • 覆盖修改 db.comment.update({_id:"1"},{content:"一点都不累"}),第二个参数覆盖整条数据,默认修改第一条符合条件的记录,如果没有这个属性则自动添加
  • 局部修改 db.comment.update({_id:"2"},{$set:{content:"一点都不累",userid:"200"}})
  • 批量修改 db.comment.update({userid:"104"}, {$set:{content:"累不累"}}, {multi:true}),设置第三个参数
  • 列值增长 db.comment.update({"userid":"104"},{$inc:{likenum:NumberInt(32)}}) ,增加32

删除

  • 条件删除 db.comment.remove({_id:ObjectId("6142ab897f0555879ed562a7")})
  • 删除所有 db.comment.remove({})

查询

  • 统计查询
//查询所有
db.comment.count()
//条件查询
db.comment.count({userid:"104"})
  • 查询前几条 db.comment.find().limit(2)
  • 跳过前几条 db.comment.find().limit(2).skip(2)
  • 排序查询 db.comment.find({}, {userid:1}).sort({userid:1}) 1为升序,-1为降序
  • 正则表达式查询 db.comment.find({content:/不/}),用正则表达式去匹配内容
  • 比较查询
//userid大于102的文档
db.comment.find({userid:{$gt:"102"}})
lt 小于
gte 大于等于
lte 小于等于
ne 不等于
  • 包含查询 db.comment.find({_id: {$in:["2","3"]}})
  • 条件查询
//id>1 userid<105
db.comment.find({$and:[{_id:{$gt:"1"}}, {userid:{$lt:"105"}}]})
//id>2 or id>=4
db.comment.find({$or:[{_id:{$lt:"2"}},{_id:{$gte:"4"}}]})
//id<2 || (likenum==1000 && id==ObjectId("6142ae7e7f0555879ed562a8"))
db.comment.find({$or:[{_id:{$lt:"2"}},{$and:[{likenum:NumberInt(1000)},{_id:ObjectId("6142ae7e7f0555879ed562a8")}]}]})

索引

  • 提高查找顺序,B树;mysql索引是B+树

索引管理

  • 查看当前索引
db.comment.getIndexes()
"v" : 2, 索引引擎版本
"key" : {
    "_id" : 1 //名称 升序
},
"name" : "_id_", 索引名
"ns" : "articledb.comment" 集合名
  • 创建索引 db.comment.createIndex({userid:1})
  • 创建复合索引 db.comment.createIndex({userid:1, articleid:-1})
  • 删除索引
//删除userid为升序的索引
db.comment.dropIndex({userid:1})
//删除索引名"userid_1_articleid_-1"
db.comment.dropIndex("userid_1_articleid_-1")
//删除所有索引
db.comment.dropIndexes()

索引使用

  • 执行计划,查询性能。db.comment.find({userid:"104"}).explain(),可以查看这个条件是否用通过索引查询,先获取到IXSCAN索引集合,再通过FETCH找到目标字段

覆盖查询

  • 查询的内容就是索引,并且需要返回的内容也就是索引,就可以直接在索引中找,不需要通过索引继续去集合中找了
db.comment.find({userid:"104"}, {userid:1,_id:0})

Spring集成mongodb

配置

  • pom.xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
  • application.yml
spring: #数据源配置
 data:
  mongodb:
   # 主机地址
   host: 127.0.0.1
   # 数据库
   database: articledb
   # 默认端口是27017
   port: 27017
   #也可以使用uri连接
   #uri: mongodb://192.168.40.134:27017/articledb

实体类

  • 标注实体类
//可以省略,如果省略,则默认使用类名小写映射集合
@Document(collection="comment")
  • 标注主键
//主键标识,该属性的值会自动对应mongodb的主键字段"_id",
//如果该属性名就叫“id”,则该注解可以省略,否则必须写
@Id
private String id;//主键
  • 标注字段
//该属性对应mongodb的字段的名字,如果一致,则无需该注解
@Field("content")
private String content;//吐槽内容
  • 标注索引
// 添加了一个单字段的索引
@Indexed
private String userid;//发布人ID
  • 标注符合索引,标注在类上。建议在命令台创建,因为未来建立的索引,可能现在还没有对应的属性值
// 复合索引
@CompoundIndex( def = "{'userid': 1, 'nickname': -1}")

增删改查

  • 定义dao层接口
public interface CommentRepository extends MongoRepository<Comment, String> {
}
  • 定义service业务层功能
@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepository;
    /**
     * 保存一个评论
     * @param comment */
    public void saveComment(Comment comment){ 
        //如果需要自定义主键,可以在这里指定主键;如果不指定主键,MongoDB会自动生成主键 
        //设置一些默认初始值。。。
        //调用dao
        commentRepository.save(comment);
    }
    /**
     * 更新评论
     * @param comment */
    public void updateComment(Comment comment){ 
        commentRepository.save(comment);
    }
    /**
     * 根据id删除评论 * @param id
     */
    public void deleteCommentById(String id){ 
        commentRepository.deleteById(id);
    }
    /**
     * 查询所有评论 * @return
     */
    public List<Comment> findCommentList(){
        return commentRepository.findAll();
    }
    /**
     * 根据id查询评论 * @param id
     * @return
     */
    public Comment findCommentById(String id){
        return commentRepository.findById(id).get();
    }
}

案例-分页查询

  • dao
//方法名必须要按照这个格式,对象的属性名要相同
Page<Comment> findByParentid(String paraentid, Pageable pageable);
  • service
public Page<Comment> findCommentListByParentid(String parentid, int page, int size) {
    //查询的page从0开始,size指的是一页几条
    return commentRepository.findByParentid(parentid, PageRequest.of(page-1,size));
}
  • 调用
//id为3的文档,每页两条,取第四页
Page<Comment> cbp = commentService.findCommentListByParentid("3", 4, 2);
//数量
System.out.println(cbp.getTotalElements());
//内容
for (Comment comment : cbp.getContent()) {
    System.out.println(comment);
}

MongoTemplate-实现评论点赞

  • service
@Autowired
private MongoTemplate mongoTemplate;
//方法名跟控制台的方法名差不多
public void updateCommentLikenum(String id) {
    //查询条件
    Query query = Query.query(Criteria.where("_id").is(id)).addCriteria(Criteria.where("likenum").lt(889));
    //更新
    Update update = new Update();
    update.inc("likenum");
    mongoTemplate.updateFirst(query,update,Comment.class);
}

副本集

  • 一组维护相同数据集的服务,可提供冗余和高可用性
  • 主从复制,有一个固定的主和固定的从;副本集,没有固定的主节点,整个集群通过投票机制选出一个主节点,总有一个活跃点(主)和一个或者多个备份节点(从)
  • 两种类型。主节点,数据操作的主要节点,可读写;次要节点,数据冗余备份节点,可以读或者选举
  • 三种角色。主要成员,主要接受所有写操作;副本成员,从主节点通过复制操作以维护相同的数据库,不可写操作,可以读操作;仲裁者,不保留任何数据的副本,只具有投票选举的作用,副本成员可以同时是仲裁者,也是一种从节点类型

搭建

  • 增加配置
replication: 
 replSetName: myrs
  • 主节点初始化 rs.initiate()
  • 查看配置 rs.conf()
  • 查看状态 rs.status()
  • 添加从节点 rs.add("127.0.0.1:27018")
  • 添加仲裁节点 rs.addArb("127.0.0.1:27019") / rs.add("127.0.0.1:27019", true)
  • 修改主节点ip
var config = rs.config();
config.members[0].host="180.76.159.126:27017";
rs.reconfig(config)

数据读写操作

  • 主节点正常读写
db.comment.insert({"articleid":"100000","content":"今天天气真好,阳光明媚","userid":"1001","nickname":"Rose","createdatetime":new Date()})
db.comment.find()
  • 从节点设置 只可以读 rs.slaveOk()
  • 从节点取消读权限 rs.slaveOk(false)
  • 从节点只可以读 db.comment.find()

仲裁节点

  • 不存放任何业务数据,可以登录查看
  • 设置 rs.slaveOk()
  • 只有一个库 local  0.000GB 只有配置信息

选举原则

  • 触发选举。主节点故障;主节点网络不可大(默认心跳信息10秒);人工干预 rs.stepDown(600)
  • 选举规则。票数最高,大多数成员支持;票数相同时,较新的节点获胜,通过操作日志oplog对比
  • 可以设置某台服务器的优先级,优先级越高,越有可能成为主节点。通过rs.conf()可以查看"priority" : 1,,仲裁节点优先级是0,不可能成为主节点
  • 修改优先级
//返回副本集数组
cfg=rs.conf()
//设置对应成员的优先级
cfg.members[1].priority=2
//重新加载配置
rs.reconfig(cfg)

故障测试

  • 副本节点宕机,不影响主节点的写入,重新连接后,自动同步数据
  • 主节点宕机,触发选择,副本集是3,仲裁节点也给从节点投了一票,从节点被选举成了主节点
  • 仲裁节点和主节点都宕机,就剩从节点自己,但是没有大多数成员,只有自己本身,但是当重连之后,会成为主节点;当剩下的从节点数量为3个以上时,就会立即选出新的主节点
  • 仲裁节点和从节点都宕机,过了10秒,服务降级,被降级成从节点,重连后,还是恢复主节点身份

Spring连接副本集

  • uri格式
mongodb://host1,host2,host3/articledb? connect=replicaSet&slaveOk=true&replicaSet=副本集名字
  • 配置yml
spring: #数据源配置
 data:
  mongodb:
   uri连接
   uri: mongodb://127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs

分片集群

  • 副本集,所有数据都是同步的,只能存在一台服务器上
  • 分片,将数据拆分,将其分散在不同的机器上
  • 解决系统增长的方法。垂直扩展,加硬盘;水平扩展,加机器
  • 组件。分片,存储数据的,每个角落分片都可以部署为副本集;mongos,路由,在客户端应用程序和分片集群之间提供借口;config servers,调度配置,配置服务器存储群集的元数据和配置设置,必须部署为副本集 image.png
  • 用户通过路由访问配置集,查询到数据存在哪个分片,再去分片中查找目标数据

配置分片和配置文件

  • 分片配置文件
replication:
 //副本集名称
 replSetName: myshardrs01
sharding:
 //分片角色
 clusterRole: shardsvr
  • 配置节点配置文件
replication:
 replSetName: myconfigrs
sharding:
 clusterRole: configsvr
  • 初始化分片副本集,与上面的副本集操作一样
  • 初始化配置分片,没有仲裁节点,都是副本节点

配置路由

  • 路由节点配置文件,不需要data目录,只需要log目录
systemLog:
 destination: file
 path: /usr/local/mongodb/sharded_cluster/mymongos_27017/log/mongod.log
 logAppend: true
processManagement:
 fork: true
net:
 bindIp: localhost
 port: 27017
sharding:
 //指定配置节点副本集
 configDB: myconfigrs/127.0.0.1:27019,127.0.0.1:27119,127.0.0.1:27219
  • 配置连接路由节点与分片
sh.addShard("myshardrs01/localhost:27018,127.0.0.1:27118,127.0.0.1:27218")
sh.addShard("myshardrs02/localhost:27318,127.0.0.1:27418,127.0.0.1:27518")
  • 路由查看状态 sh.status()
  • 路由开启分片功能,打开对应的数据库 sh.enableSharding("articledb")
  • 对集合进行分片,指定集合和分片键。分片规则,哈希策略;范围策略。一个集合只能按照一个策略进行分片,一旦对一个集合分片,分片键和分片值就不可改变。 如:不能给集合选择不同的分片键、不能更新分片键的值。
//哈希策略,计算字段值的哈希值,根据这个哈希值进行分片
sh.shardCollection("articledb.comment", {"nickname":"hashed"})
//范围策略,根据值的范围大小进行分片
sh.shardCollection("articledb.author", {"age":1})
  • 增加路由节点,一样的配置,不同的端;正常连接后,可以直接使用,不需要再配置

使用

  • 通过路由客户端,循环插入1000条数据,测试哈希策略
for(var i=1;i<=1000;i++) {db.comment.insert({_id:i+"",nickname:"BoBo"+i})}
  • 测试范围规则,因为默认的数据块尺寸为64m,为了测试需要改小
//修改数据块
use config
db.settings.save({_id:"chunksize", value: 1})
//插入
for(var i=1;i<=20000;i++) {db.author.save({"name":"BoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoB oBoBoBoBoBoBoBoBo"+i,"age":NumberInt(i%120)})}
//改回来
db.settings.save({_id:"chunksize", value: 64})
  • 范围策略效果
{ "age" : { "$minKey" : 1 } } -->> { "age" : 0 } on : myshardrs01 Timestamp(2, 0) 
{ "age" : 0 } -->> { "age" : 46 } on : myshardrs01 Timestamp(3, 0) 
{ "age" : 46 } -->> { "age" : 92 } on : myshardrs02 Timestamp(3, 1) 
{ "age" : 92 } -->> { "age" : 111 } on : myshardrs02 Timestamp(1, 6) 
{ "age" : 111 } -->> { "age" : { "$maxKey" : 1 } } on : myshardrs02 Timestamp(1, 3)

spring链接分片集群

  • 配置yml
spring: #数据源配置
 data:
  mongodb:
   uri: mongodb://127.0.0.1:27017,127.0.0.1:27117/articledb

安全认证

  • 通过角色对用户授予相应数据库资源的操作权限,每个角色当中的权限可以显式指定,也可以通过继承其他角色的权限
  • 查看所有内置权限 db.runCommand({rolesInfo:1, showBuiltinRoles:true}) image.png image.png

单实例环境

  • 创建超级管理员用户 db.createUser({user:"myroot", pwd: "12345", roles:["root"]}),只能在admin
  • 创建专门管理admin的用户 db.createUser({user:"myadmin",pwd:"123456",roles: [{role:"userAdminAnyDatabase",db:"admin"}]})
  • 查看已经创建的用户情况 db.system.users.find()
  • 删除用户 db.dropUser("myadmin")
  • 修改密码 db.changeUserPassword("myroot", "111111")
  • 验证用户 db.auth("myroot", "111111"),登陆前,要切换到对应的数据库
  • 报错 too many users are authenticated,一次最好就认证一个账户,不要一个客户端多次认证
  • 创建普通用户 db.createUser({user: "manager", pwd: "123456", roles: [{ role: "readWrite", db: "articledb" }]})
  • 服务端开启安全认证
//加参数
mongod -f mongod.conf --auth
//修改配置文件
securit:
 authorization: enabled

spring安全连接

  • yml配置
spring: #数据源配置
 data:
  mongodb:
   # 主机地址
   host: 127.0.0.1
   # 数据库
   database: articledb
   # 默认端口是27017
   port: 27017
   # 用户名和密码要是字符串形式
   username: "manager"
   password: "123456"
#   uri: mongodb://manager:123456@127.0.0.1:27017/articledb

副本集环境

  • admin下创建超级管理员 db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
  • 生成key文件
//创建,生成的文件要删除 头尾两行
sudo openssl genrsa -out mongo.keyfile 1024
//修改权限 只针对当前用户可读
sudo chmod 400 mongo.keyfile
//拷贝到各个成员文件夹下
sudo cp mongo.keyfile myrs_27019
  • 修改配置文件
security:
 keyFile: /usr/local/mongodb/replica-sets/myrs_27017/mongo.keyfile
 authorization: enabled
  • 其他操作跟单实例一致
  • 创建普通账户 db.createUser({user:"zhangsan",pwd:"123456",roles:["readWrite"]})
  • spring访问,yml配置 uri: mongodb://zhangsan:123456@127.0.0.1:27017,127.0.0.1:27018,127.0.0.1:27019/articledb?connect=replicaSet&slaveOk=true&replicaSet=myrs

分片集群

  • 每个服务器中都要放一个keyfile,操作同上