Mongodb

MongoDB 安装

1、系统准备工作(NOSQL 建议关闭大页内存)

centos7 关闭大页内存:

vim /etc/rc.local 最后添加如下代码:

if test -f /sys/kernel/mm/transparent_hugepage/enabled; then

     echo never > /sys/kernel/mm/transparent_hugepage/enabled

fi

if test -f /sys/kernel/mm/transparent_hugepage/defrag; then

      echo never > /sys/kernel/mm/transparent_hugepage/defrag

fi

 

1.在usr/local/下创建mongodb文件夹

cd /usr/local/

mkdir mongodb

# 2.解压文件

tar -xzvf mongodb-linux-x86_64-rhel70-4.0.1.tgz

# 3.将解压后的文件下所有内容移动到mongodb文件夹下 # 注意这里不是将mongodb-linux-x86_64-rhel70-4.0.1文件夹移动到创建好的mongodb下,而是文件下的内容

mv mongodb-linux-x86_64-rhel70-4.0.1/* /usr/local/mongodb/

# 4.添加mongodb的环境变量

vi /etc/profile # 5.在文件末尾插入如下内容

export MONGODB_HOME=/usr/local/mongodb

export PATH=$PATH:$MONGODB_HOME/bin

# 5.修改保存后要重启系统配置,执行命令如下

source /etc/profile

 

6、启动及关闭

mongod -f /mongodb/27017/conf/mongodb.conf

关闭:

mongod -f /mongod/27017/conf/mongodb.conf --shutdown

 

mongodb 的

mongo 登录mongodb数据库

 help
        db.help()                    help on db methods
        db.mycoll.help()             help on collection methods
        sh.help()                    sharding helpers
        rs.help()                    replica set helpers
        help admin                   administrative help
        help connect                 connecting to a db help
        help keys                    key shortcuts
        help misc                    misc things to know
        help mr                      mapreduce

        show dbs                     show database names
        show collections             show collections in current database
        show users                   show users in current database
        show profile                 show most recent system.profile entries with time >= 1ms
        show logs                    show the accessible logger names
        show log [name]              prints out the last segment of log in memory, 'global' is default
        use <db_name>                set current database
        db.foo.find()                list objects in collection foo
        db.foo.find( { a : 1 } )     list objects in foo where a == 1
        it                           result of the last line evaluated; use to further iterate
        DBQuery.shellBatchSize = x   set default number of items to display on shell
        exit                         quit the mongo shell

 

相关命令

>show database

>show tables

查看当前数据库

>db

切换数据库

>use db_name

> show dbs;
admin   0.000GB
config  0.000GB
local   0.000GB
> pwd
[native code]
> db
test
> db
test
> show databases;
admin   0.000GB
config  0.000GB
local   0.000GB
> show dbs;
admin   0.000GB
config  0.000GB
local   0.000GB
> show tables;

 

mongodb 的命令有三大类(db,sh,)

>查看db下面的所有命令可以db+两次tab键

db[tab][tab]

查看库对象相关的详细命令

db.[库名].help()

> db.config.help()
DBCollection help
        db.config.find().help() - show DBCursor help
        db.config.bulkWrite( operations, <optional params> ) - bulk execute write operations, optional parameters are: w, wtimeout, j
        db.config.count( query = {}, <optional params> ) - count the number of documents that matches the query, optional parameters are: limit, skip, hint, maxTimeMS
        db.config.countDocuments( query = {}, <optional params> ) - count the number of documents that matches the query, optional parameters are: limit, skip, hint, maxTimeMS
        db.config.estimatedDocumentCount( <optional params> ) - estimate the document count using collection metadata, optional parameters are: maxTimeMS
        db.config.copyTo(newColl) - duplicates collection by copying all documents to newColl; no indexes are copied.
        db.config.convertToCapped(maxBytes) - calls {convertToCapped:'config', size:maxBytes}} command
        db.config.createIndex(keypattern[,options])
        db.config.createIndexes([keypatterns], <options>)
        db.config.dataSize()
        db.config.deleteOne( filter, <optional params> ) - delete first matching document, optional parameters are: w, wtimeout, j
        db.config.deleteMany( filter, <optional params> ) - delete all matching documents, optional parameters are: w, wtimeout, j
        db.config.distinct( key, query, <optional params> ) - e.g. db.config.distinct( 'x' ), optional parameters are: maxTimeMS
        db.config.drop() drop the collection
        db.config.dropIndex(index) - e.g. db.config.dropIndex( "indexName" ) or db.config.dropIndex( { "indexKey" : 1 } )
        db.config.dropIndexes()
        db.config.ensureIndex(keypattern[,options]) - DEPRECATED, use createIndex() instead
        db.config.explain().help() - show explain help
        db.config.reIndex()
        db.config.find([query],[fields]) - query is an optional query filter. fields is optional set of fields to return.
                                                      e.g. db.config.find( {x:77} , {name:1, x:1} )
        db.config.find(...).count()
        db.config.find(...).limit(n)
        db.config.find(...).skip(n)
        db.config.find(...).sort(...)
        db.config.findOne([query], [fields], [options], [readConcern])
        db.config.findOneAndDelete( filter, <optional params> ) - delete first matching document, optional parameters are: projection, sort, maxTimeMS
        db.config.findOneAndReplace( filter, replacement, <optional params> ) - replace first matching document, optional parameters are: projection, sort, maxTimeMS, upsert, returnNewDocument
        db.config.findOneAndUpdate( filter, update, <optional params> ) - update first matching document, optional parameters are: projection, sort, maxTimeMS, upsert, returnNewDocument
        db.config.getDB() get DB object associated with collection
        db.config.getPlanCache() get query plan cache associated with collection
        db.config.getIndexes()
        db.config.group( { key : ..., initial: ..., reduce : ...[, cond: ...] } )
        db.config.insert(obj)
        db.config.insertOne( obj, <optional params> ) - insert a document, optional parameters are: w, wtimeout, j
        db.config.insertMany( [objects], <optional params> ) - insert multiple documents, optional parameters are: w, wtimeout, j
        db.config.mapReduce( mapFunction , reduceFunction , <optional params> )
        db.config.aggregate( [pipeline], <optional params> ) - performs an aggregation on a collection; returns a cursor
        db.config.remove(query)
        db.config.replaceOne( filter, replacement, <optional params> ) - replace the first matching document, optional parameters are: upsert, w, wtimeout, j
        db.config.renameCollection( newName , <dropTarget> ) renames the collection.
        db.config.runCommand( name , <options> ) runs a db command with the given name where the first param is the collection name
        db.config.save(obj)
        db.config.stats({scale: N, indexDetails: true/false, indexDetailsKey: <index key>, indexDetailsName: <index name>})
        db.config.storageSize() - includes free space allocated to this collection
        db.config.totalIndexSize() - size in bytes of all the indexes
        db.config.totalSize() - storage allocated for all data and indexes
        db.config.update( query, object[, upsert_bool, multi_bool] ) - instead of two flags, you can pass an object with fields: upsert, multi
        db.config.updateOne( filter, update, <optional params> ) - update the first matching document, optional parameters are: upsert, w, wtimeout, j
        db.config.updateMany( filter, update, <optional params> ) - update all matching documents, optional parameters are: upsert, w, wtimeout, j
        db.config.validate( <full> ) - SLOW
        db.config.getShardVersion() - only for use with sharding
        db.config.getShardDistribution() - prints statistics about data distribution in the cluster
        db.config.getSplitKeysForChunks( <maxChunkSize> ) - calculates split points over all chunks and returns splitter function
        db.config.getWriteConcern() - returns the write concern used for any operations on this collection, inherited from server/db if set
        db.config.setWriteConcern( <write concern doc> ) - sets the write concern for writes to the collection
        db.config.unsetWriteConcern( <write concern doc> ) - unsets the write concern for writes to the collection
        db.config.latencyStats() - display operation latency histograms for this collection
>  

>创建库的命令

use app

如果新创建的库是空库时,使用show dbs 命令无法看到

删除库

db.dropDatabase()

 

文档操作

数据批量录入:

>  for(i=0;i<100000;i++) {db.log.insert({"uid":i,"name":"xiaoxiao"})}

> db.log.count()
100000

 

××××× 5 用户及权限管理

注意:

验证库:建立用户时use到的库,在使用用户时,要加上验证库才能登录。

对于管理员用户,必须在admin下创建

1、建用户时,必须在admin库下创建(use admin下),否则新创建的user只把当前的库当成验证库;

2、登录时,必须明确指定验证库才能登录(不指定不行,指定错了也不行);

3、通常,管理员用的验证库是admin,普通用户的验证库一般是所管理的库;

4、如果直接登录到数据库,不进行use,默认的验证库是test,不是我们生产建议的。

5、从3.6版本开始,默认监听的ip是127.0.0.1 。需要远程登录时需要修改bindIp参数;

6、规范的做法是一个用户管理一个库或一类业务的库,不能所有的库使用一个用户;

 

用户创建语法:

use admin

db.createUser

{

user: "name",

pwd:"password",

roles:[

         {role: "role1",

           db: "database"

          } ,

         {role:"role2",

          db:"database2"

         }

         ],

}

 

基本语法说明:

user: 用户名

pwd: 密码

role:

        role: 角色名(拥有的权限)

       db: 作用域(库名)

mongodb的角色有三个: root,readWrite,read

 

用户管理例子:

创建超级管理员: 管理所有数据库(必须use admin 再去创建)

$mongo

>use admin

>db.createUser(

{

      user: "root",

      pwd: "345.com",

     roles: [ {role: "root",db:"admin",db: "oldboy" } ]

 }

)

验证一下:返回1代表正常

> db.auth('root','345.com')
1

修改配置文件在配置文件开启验证功能:

..............................

security:

     authorization: enabled

 

..............................................

 

重启mongod 服务

systemctl restart mongod

 

验证数据库:如果大页内存不关闭在登录时会提示警告信息。

mongo -u root -p 345.com 192.168.1.145/admin

mongo -uroot -p 345.com 192.168.1.145:30000/admin

MongoDB shell version v4.0.10
connecting to: mongodb://192.168.1.145:27017/admin?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("57b4a362-0bb1-4b2f-a275-974fea1758c6") }
MongoDB server version: 4.0.10
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
        http://docs.mongodb.org/
Questions? Try the support group
        http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2019-07-04T16:30:39.542+0800 I CONTROL  [initandlisten] 
2019-07-04T16:30:39.542+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/enabled is 'always'.
2019-07-04T16:30:39.543+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2019-07-04T16:30:39.543+0800 I CONTROL  [initandlisten] 
2019-07-04T16:30:39.543+0800 I CONTROL  [initandlisten] ** WARNING: /sys/kernel/mm/transparent_hugepage/defrag is 'always'.
2019-07-04T16:30:39.543+0800 I CONTROL  [initandlisten] **        We suggest setting it to 'never'
2019-07-04T16:30:39.543+0800 I CONTROL  [initandlisten] 

 

临时关闭命令:

echo never > /sys/kernel/mm/transparent_hugepage/enabled 

echo never > /sys/kernel/mm/transparent_hugepage/defrag

永久关闭(系统重启后自动关闭)

vim /etc/rc.local

echo never > /sys/kernel/mm/transparent_hugepage/enabled 

echo never > /sys/kernel/mm/transparent_hugepage/defrag

 

关闭之后就不再出现告警信息:

[mongod@s9 ~]$ mongod -f /usr/local/mongodb/conf/mongodb.conf 
about to fork child process, waiting until server is ready for connections.
forked process: 2319
child process started successfully, parent exiting
[mongod@s9 ~]$ mongo -uroot -p 345.com 192.168.1.145/admin    
MongoDB shell version v4.0.10
connecting to: mongodb://192.168.1.145:27017/admin?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("8e20aee9-96d4-43f2-9321-67326aebfc35") }
MongoDB server version: 4.0.10

> 查看用户信息

> show tables;
system.users
system.version
> db.system.users.find()
{ "_id" : "admin.root", "userId" : UUID("1f1ff928-ab54-4163-bc2a-33f3b3c884fb"), "user" : "root", "db" : "admin", "credentials" : { "SCRAM-SHA-1" : { "iterationCount" : 10000, "salt" : "a6qvf1h91dIO5GSx1zc7NA==", "storedKey" : "gxy5CIV5Q+MW0paoXTOKvlrVysY=", "serverKey" : "5cjqoNr+pKovwncVcYp10VH7/GQ=" }, "SCRAM-SHA-256" : { "iterationCount" : 15000, "salt" : "58ml1INSK6xbmMNSAgNSpdbIUS8omL986+HdBA==", "storedKey" : "tR/QCfRCNCB1J1P1nN7oImfx+6g1jhuguayEhQdbCPY=", "serverKey" : "DOmwwQyAxyxs5x+EDstEVOPqrnUsjnCI95NgEmfhd0E=" } }, "roles" : [ { "role" : "root", "db" : "admin" } ] }
> db.system.users.find().pretty()
{
        "_id" : "admin.root",
        "userId" : UUID("1f1ff928-ab54-4163-bc2a-33f3b3c884fb"),
        "user" : "root",
        "db" : "admin",
        "credentials" : {
                "SCRAM-SHA-1" : {
                        "iterationCount" : 10000,
                        "salt" : "a6qvf1h91dIO5GSx1zc7NA==",
                        "storedKey" : "gxy5CIV5Q+MW0paoXTOKvlrVysY=",
                        "serverKey" : "5cjqoNr+pKovwncVcYp10VH7/GQ="
                },
                "SCRAM-SHA-256" : {
                        "iterationCount" : 15000,
                        "salt" : "58ml1INSK6xbmMNSAgNSpdbIUS8omL986+HdBA==",
                        "storedKey" : "tR/QCfRCNCB1J1P1nN7oImfx+6g1jhuguayEhQdbCPY=",
                        "serverKey" : "DOmwwQyAxyxs5x+EDstEVOPqrnUsjnCI95NgEmfhd0E="
                }
        },
        "roles" : [
                {
                        "role" : "root",
                        "db" : "admin"
                }
        ]
}

 

 

创建应用用户:

use app01

db.createUser(

    {

    user: "app01",

    pwd: "345.com",

   roles: [{role: "readWrite", db: "app01"}]

  }

)

验证:

> db.auth('app01','345.com')
1

使用app01 远程登录:

[mongod@s9 ~]$ mongo -uapp01 -p345.com 192.168.1.145/app01
MongoDB shell version v4.0.10
connecting to: mongodb://192.168.1.145:27017/app01?gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("5281a96b-244b-42ce-96dc-c06bdad76d45") }
MongoDB server version: 4.0.10


> db
app01

 

MongoDB 复制集RS(ReplicationSet)实时的无损的复制(自动监控自动投票选举)

基本原理:(面试题×××××)

基本构成是1主2从的结构,自带互相监控投票机制(使用Raft协议(mongodb)Poxos(mysql MGR用的是变种)

如果发生主库宕机,复制集内部会进行投票选举,选择一个新的主库替代原有的主库对外提供服务,

配置Replication Set 配置

 1、规划

三个以上的mongodb 实例或节点

2、多实例就需要使用不同的端口

db1: 27017 27018  db2: 27017 27018

3、mkdir /mongodb/2701{7,8}/{conf,data,log}

4、创建配置文件(注意配置文件的格式yaml),根据个人环境修改目录及监听的端口

systemLog:
  destination: file
  logAppend: true
  path: /mongodb/27017/log/mongod.log
storage:
  dbPath: /mongodb/27017/data
  journal:
    enabled: true
  directoryPerDB: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
processManagement:
  fork: true  # fork and run in background
  pidFilePath: /mongodb/27017/mongod.pid  # location of pidfile
  timeZoneInfo: /usr/share/zoneinfo
net:
  port: 27017
  bindIp: 0.0.0.0 

 

启动四个进程:

db1:

mongod -f /mongodb/27017/conf/mongodb.conf

mongod -f /mongodb/27018/conf/mongodb.conf

db2:

mongod -f /mongodb/27017/conf/mongodb.conf

mongod -f /mongodb/27018/conf/mongodb.conf

 

第一种结构:配置普通复制集(1主2从,从库普通从库)

登录3个节点中任意一个实例执行下面的操作:

mongo --port 27018 admin

config = {_id: 'my_repl',
...  members: [
...  {_id: 0, host: '192.168.1.141:27017'},
...  {_id: 1, host: '192.168.1.141:27018'},
...  {_id: 2, host: '192.168.1.145:27017'}
... ]}

第二种架构:配置1主1从1个arbiter(arbiter 仲裁只参与投票不参与复制),

config = {  _id: 'my_repl_1',

                 members: [

                                   {_id: 1, host: '192.168.1.141:27017'},

                                    {_id: 2, host: '192.168.1.141:27018'},

                                    {_id: 1, host: '192.168.1.145:27017',"arbiterOnly": true} ]

}

rs.initiate(config)

 

地三种架构: 配置特殊从节点(延时节点(Delay)、隐藏节点(Hidden))不提供服务,不参与选主

添加特殊节点(延时节点、隐藏节点)

配置延时节点(一般延时节点也配置成hidden)

1、把现在的配置赋值给一个变量

cfg=rs.conf()

2、修改cfg内容

cfg.members[2].priority=0    #priority 优先级

cfg.members[2].hidden=true

cfg.members[2].slaveDelay=120(min)

3、应用配置

rs.reconfig(cfg)  

 

cfg=rs.conf()

cfg.members[1].priority=0

cfg.members[1].hidden=true

cfg.members[1].slaveDelay=120

rs.reconfig(cfg)

 

#注意members[N], N 就是创建复制集时指定的_id: N 。id会根据当前最大的id +1. 例如:现在有四个实例_id 号为0,1,2,3。rs.remove('192.168.1.141:27018') 192.168.1.141对应的_id 号为2。现在我们在把192.168.1.141:27018 添加进去。现在的_id 号:变为0,1,3,4。

 

初始化复制集:

>rs.initiate(config)

查询复制集状态

>rs.status()

my_repl:SECONDARY> 
my_repl:SECONDARY> rs.status()
{
        "set" : "my_repl",
        "date" : ISODate("2019-07-05T07:01:41.201Z"),
        "myState" : 1,
        "term" : NumberLong(1),
        "syncingTo" : "",

..........................................省略

my_repl:SECONDARY> rs.
rs.add(                        rs.isMaster(
rs.addArb(                     rs.printReplicationInfo(
rs.apply(                      rs.printSlaveReplicationInfo(
rs.bind(                       rs.propertyIsEnumerable(
rs.call(                       rs.prototype
rs.compareOpTimes(             rs.reconfig(
rs.conf(                       rs.remove(
rs.config(                     rs.slaveOk(
rs.constructor                 rs.status(
rs.debug                       rs.stepDown(
rs.freeze(                     rs.syncFrom(
rs.hasOwnProperty(             rs.toLocaleString(
rs.help(                       rs.toString(
rs.initiate(                   rs.valueOf(

 

所有的线上修改操作必须在primary 节点上执行:

my_repl:SECONDARY> rs.add('192.168.1.145:27018')
{
        "operationTime" : Timestamp(1562312585, 1),
        "ok" : 0,
        "errmsg" : "replSetReconfig should only be run on PRIMARY, but my state is SECONDARY; use the \"force\" argument to override",
        "code" : 10107,
        "codeName" : "NotMaster",
        "$clusterTime" : {
                "clusterTime" : Timestamp(1562312585, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

 

在primary节点执行显示:

my_repl:PRIMARY> rs.add('192.168.1.145:27018')
{
        "ok" : 1,
        "operationTime" : Timestamp(1562312959, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1562312959, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}

 

rs.add('192.168.1.12:27020') 添加普通节点

rs.addArb('192.168.1.222:28029') 添加仲裁节点

rs.remove('192.168.1.145:27017') 注意: 该命令不仅会修改配置而且还会把该实例的进程kill 掉。所以,在测试时要注意!!!

复制集的其他命令:

+++++++++++++++++++生产中少用+++++++++++++++++

副本集角色切换(不要人为的随便操作)

>rs.stepDown()

>rs.freeze(300)  锁定从,使其不会转变成主库。freeze() 和stepDown单位都是秒。

++++++++++++++++++++++++++++++++++++++++++++++++++++++

设置副本节点可读,在副本节点执行

>rs.slaveOk()

 

监控主从延时的命令:

rs.printSlaveReplicationInfo()

 

MongoDB Sharding Cluster 分片集群

三大部分: Router (mongos),Config Servers(replica set)、Shard(replica set  ) 。

config server: zookeeper,etcd;同意配置服务

 

MongoDB Sharding Cluster 分片集群

1、规划

10个实例:38017-38026
(1)configserver:38018-38020
3台构成的复制集(1主两从,不支持arbiter)38018-38020(复制集名字configsvr)
(2)shard节点:
sh1:38021-23    (1主两从,其中一个节点为arbiter,复制集名字sh1)
sh2:38024-26    (1主两从,其中一个节点为arbiter,复制集名字sh2)
(3) mongos:
38017

10个实例:38017-38026
(1)configserver:38018-38020
3台构成的复制集(1主两从,不支持arbiter)38018-38020(复制集名字configsvr)
(2)shard节点:
sh1:38021-23    (1主两从,其中一个节点为arbiter,复制集名字sh1)
sh2:38024-26    (1主两从,其中一个节点为arbiter,复制集名字sh2)
(3) mongos:
38017

 

7.2 Shared 节点配置过程

7.2.1 目录创建

 

mkdir -p /mongodb/38021/conf  /mongodb/38021/log  /mongodb/38021/data
mkdir -p /mongodb/38022/conf  /mongodb/38022/log  /mongodb/38022/data
mkdir -p /mongodb/38023/conf  /mongodb/38023/log  /mongodb/38023/data
mkdir -p /mongodb/38024/conf  /mongodb/38024/log  /mongodb/38024/data
mkdir -p /mongodb/38025/conf  /mongodb/38025/log  /mongodb/38025/data
mkdir -p /mongodb/38026/conf  /mongodb/38026/log  /mongodb/38026/data

7.2.2 修改配置文件

第一组复制集搭建:21-23(1主1从1arb)

sharding:
  clusterRole: shardsvr --------------- 注意 这个角色是系统指定的不要修改

cat >  /mongodb/38021/conf/mongodb.conf  <<EOF
systemLog:
  destination: file
  path: /mongodb/38021/log/mongodb.log   
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/38021/data
  directoryPerDB: true
  #engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
net:
  bindIp: 10.0.0.51,127.0.0.1
  port: 38021
replication:
  oplogSizeMB: 2048
  replSetName: sh1
sharding:
  clusterRole: shardsvr
processManagement: 
  fork: true
EOF
\cp  /mongodb/38021/conf/mongodb.conf  /mongodb/38022/conf/
\cp  /mongodb/38021/conf/mongodb.conf  /mongodb/38023/conf/

sed 's#38021#38022#g' /mongodb/38022/conf/mongodb.conf -i
sed 's#38021#38023#g' /mongodb/38023/conf/mongodb.conf -i

7.2.2 搭建第二组sh1

 

第二组节点:24-26(1主1从1Arb)

cat > /mongodb/38024/conf/mongodb.conf <<EOF
systemLog:
  destination: file
  path: /mongodb/38024/log/mongodb.log   
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/38024/data
  directoryPerDB: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
net:
  bindIp: 10.0.0.51,127.0.0.1
  port: 38024
replication:
  oplogSizeMB: 2048
  replSetName: sh2
sharding:
  clusterRole: shardsvr
processManagement: 
  fork: true
EOF

\cp  /mongodb/38024/conf/mongodb.conf  /mongodb/38025/conf/
\cp  /mongodb/38024/conf/mongodb.conf  /mongodb/38026/conf/
sed 's#38024#38025#g' /mongodb/38025/conf/mongodb.conf -i
sed 's#38024#38026#g' /mongodb/38026/conf/mongodb.conf -i

 

7.2.3 启动所有节点,并搭建复制集

mongod -f  /mongodb/38021/conf/mongodb.conf 
mongod -f  /mongodb/38022/conf/mongodb.conf 
mongod -f  /mongodb/38023/conf/mongodb.conf 
mongod -f  /mongodb/38024/conf/mongodb.conf 
mongod -f  /mongodb/38025/conf/mongodb.conf 
mongod -f  /mongodb/38026/conf/mongodb.conf  
ps -ef |grep mongod

mongo --port 38021
use  admin
config = {_id: 'sh1', members: [
                          {_id: 0, host: '10.0.0.51:38021'},
                          {_id: 1, host: '10.0.0.51:38022'},
                          {_id: 2, host: '10.0.0.51:38023',"arbiterOnly":true}]
           }

rs.initiate(config)
  
 mongo --port 38024 
 use admin
config = {_id: 'sh2', members: [
                          {_id: 0, host: '10.0.0.51:38024'},
                          {_id: 1, host: '10.0.0.51:38025'},
                          {_id: 2, host: '10.0.0.51:38026',"arbiterOnly":true}]
           }
  
rs.initiate(config)

mongo --port 27021 
use  admin
config = {_id: 'sh1', members: [
                          {_id: 0, host: '192.168.1.141:27021'},
                          {_id: 1, host: '192.168.1.141:27022'},
                          {_id: 2, host: '192.168.1.141:27023',"arbiterOnly":true}]
           }

rs.initiate(config)
  
 mongo --port 27024 
 use admin
config = {_id: 'sh2', members: [
                          {_id: 0, host: '192.168.1.141:27024'},
                          {_id: 1, host: '192.168.1.141:27025'},
                          {_id: 2, host: '192.168.1.141:27026',"arbiterOnly":true}]
           }
  
rs.initiate(config)

 

7.2.4  配置配置服务器复制集

 

cat > /mongodb/38024/conf/mongodb.conf <<EOF
systemLog:
  destination: file
  path: /mongodb/38024/log/mongodb.log   
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/38024/data
  directoryPerDB: true
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
net:
  bindIp: 0.0.0.0
  port: 27019
replication:
  oplogSizeMB: 2048
  replSetName:  configReplSet
sharding:
  clusterRole: configsvr   ----------集群角色是系统定义的,不要更改。
processManagement: 
  fork: true
EOF


use  admin
 config = {_id: 'conf_server', members: [
                          {_id: 0, host: '192.168.1.141:27017'},
                          {_id: 1, host: '192.168.1.141:27018'},
                          {_id: 2, host: '192.168.1.141:27019'}]
           }
rs.initiate(config)  

 

注意: configserver 可以是一个节点,官方建议使用复制集。

configserver 不能有arbiter。3.4 版本之后要求必须是复制集且不知arbiter。

 

 

7.4 mongos (Router)路由功能

mongos - 分片路由,如果使用了 sharding 功能,则应用程序连接的是 mongos 而不是 mongod

7.4.1 创建目录

mkdir -p /mongodb/27026/{conf,log}

7.4.2 配置文件

cat > /mongodb/conf/mogos.conf  <<EOF

systemLog:
  destination: file
  path: /mongodb/27026/log/mongos.log
  logAppend: true
  
net:
  bindIp: 0.0.0.0
  port: 27026
  
sharding:
  configDB: configReplSet/192.168.1.141:27017,192.168.1.141:27018,192.168.1.141:27019
  
processManagement:
  fork: true

EOF

 

7.4.3 启动mongos

mongos -f  /mongodb/27026/conf/mongos.conf

7.5 分片集群添加节点

连接到其中一个mogos(我这里是192.168.1.141:27026),做一下配置

(1)连接到mongos的admin数据库

# su - mongod

$mongo 192.168.1.141:27026/admin

(2) 添加分片

db.runCommand( {addshard: "sh1/192.168.1.141:27020,192.168.1.141:27021,192.168.1.141:27022",name: "shard1"})

db.runCommand({ addshard: "sh2/192.168.1.141:27023,192.168.1.141:27024,192.168.1.141:27025",name: "shard2"})

(3) 列出分片

db.runCommand({ listshards: 1})

(4) 整体状态查看

sh.status();

下面是执行命令及返回的结果

mongos> db.runCommand( {addshard: "sh1/192.168.1.141:27020,192.168.1.141:27021,192.168.1.141:27022",name: "shard1"})
db.runCommand({ addshard: "sh2/192.168.1.141:27023,192.168.1.141:27024,192.168.1.141:27025",name: "shard2"}){
        "shardAdded" : "shard1",
        "ok" : 1,
        "operationTime" : Timestamp(1564039777, 7),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1564039777, 7),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}
mongos> db.runCommand({ addshard: "sh2/192.168.1.141:27023,192.168.1.141:27024,192.168.1.141:27025",name: "shard2"})
{
        "shardAdded" : "shard2",
        "ok" : 1,
        "operationTime" : Timestamp(1564039781, 5),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1564039781, 5),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}
mongos> db.runCommand({ listshards: 1})
{
        "shards" : [
                {
                        "_id" : "shard1",
                        "host" : "sh1/192.168.1.141:27020,192.168.1.141:27021",
                        "state" : 1
                },
                {
                        "_id" : "shard2",
                        "host" : "sh2/192.168.1.141:27023,192.168.1.141:27024",
                        "state" : 1
                }
        ],
        "ok" : 1,
        "operationTime" : Timestamp(1564039824, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1564039824, 1),
                "signature" : {
                        "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
                        "keyId" : NumberLong(0)
                }
        }
}
mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
        "_id" : 1,
        "minCompatibleVersion" : 5,
        "currentVersion" : 6,
        "clusterId" : ObjectId("5d3934d38f57123e8d8ca5ec")
  }
  shards:
        {  "_id" : "shard1",  "host" : "sh1/192.168.1.141:27020,192.168.1.141:27021",  "state" : 1 }
        {  "_id" : "shard2",  "host" : "sh2/192.168.1.141:27023,192.168.1.141:27024",  "state" : 1 }
  active mongoses:
        "4.0.10" : 1
  autosplit:
        Currently enabled: yes
  balancer:
        Currently enabled:  yes
        Currently running:  no
        Failed balancer rounds in last 5 attempts:  0
        Migration Results for the last 24 hours: 
                No recent migrations
  databases:
        {  "_id" : "config",  "primary" : "config",  "partitioned" : true }

 

 

7.6 使用分片集群

默认是随机存储数据,可能导致数据分布不均匀。所以需要我们指定一个shard 规则。

 

7.6.1  RANGE 分片配置及测试

注意: 一个chunk 的大小是64M,如果数据量小,不能沾满一个chunk,那会一直使用这个chunk进行存储。

对test库下的vast大表进行手工分片。步骤如下:

1、激活数据库分片功能

mongo --port 27026 admin

admin>({enablesharding: "数据库名称"})

eg:

admin> db.runCommand({ enablesharding: "test" })

2、指定分片键进行集合分片

###创建索引

use test

>db.vast.ensureIndex({ id: 1})

##开启分片

use admin

>db.runCommand({ shardcollection: "test.vast",key: {id: 1}} )      

#注意: id: 1 是指安装id的升序进行排序,-1 就是指安装降序进行排序。

3、集合分片验证

admin> use test

#时间比较长耐心等待

test>for (i=1;i<1000000;i++) {db.vast.insert({"id": i ","name": "shenzheng","age": 40,"date": new Date()}) }

 

test> db.vast.stats()

 

4、分片结果测试

shard1:

mongo --port 27020

db.vast.count()

 

shard2:

mongo --port 27023

db.vast.count()

 

7.6.2  Hash 分片

对oldboy 库下的vast大表进行hash

创建哈希索引:

(1) 对于oldboy开启分片功能

mongo --port 27026 admin

use admin
admin> db.runCommand( { enablesharding : "oldboy" } )
(2)对于oldboy库下的vast表建立hash索引
use oldboy
oldboy> db.vast.ensureIndex( { id: "hashed" } )
(3)开启分片 
use admin
admin > sh.shardCollection( "oldboy.vast", { id: "hashed" } )
(4)录入10w行数据测试
use oldboy;
for(i=1;i<100000;i++){ db.vast.insert({"id":i,"name":"shenzheng","age":70,"date":new Date()}); }
(5)hash分片结果测试
mongo --port 38021
use oldboy
db.vast.count();
mongo --port 38024
use oldboy

db.vast.count();
  

7.7 分片集群的查询及管理

7.7.1  判断是否shard集群

admin> db.runCommand( { isdbrid : 1 } )

7.7.2 列出开启分片的数据库

admin> use config

config> db.databases.find( { "partitioned": true } )

or

config>db.databases.find() //列出所有数据库分片情况

7.7.3 列出所有分片信息

admin> db.runCommand({ listshards :  1 })

7.7.5 查看分片的片键

config> db.collections.find().pretty()
{
    "_id" : "test.vast",
    "lastmodEpoch" : ObjectId("58a599f19c898bbfb818b63c"),
    "lastmod" : ISODate("1970-02-19T17:02:47.296Z"),
    "dropped" : false,
    "key" : {
        "id" : 1
    },
    "unique" : false

 

7.7.5 查看分片的详细信息

admin> sh.status()

7.7.6 删除分片节点(谨慎)

(1)确认blance是否在工作
sh.getBalancerState()
(2)删除shard2节点(谨慎)
mongos> db.runCommand( { removeShard: "shard2" } )
注意:删除操作一定会立即触发blancer。
  

7.8 balancer操作
7.8.1 介绍
mongos的一个重要功能,自动巡查所有shard节点上的chunk的情况,自动做chunk迁移。
什么时候工作?
1、自动运行,会检测系统不繁忙的时候做迁移
2、在做节点删除的时候,立即开始迁移工作
3、balancer只能在预设定的时间窗口内运行

有需要时可以关闭和开启blancer(备份的时候)
mongos> sh.stopBalancer()
mongos> sh.startBalancer()

7.8.2 自定义 自动平衡进行的时间段
https://docs.mongodb.com/manual/tutorial/manage-sharded-cluster-balancer/#schedule-the-balancing-window
// connect to mongos

use config
sh.setBalancerState( true )
db.settings.update({ _id : "balancer" }, { $set : { activeWindow : { start : "3:00", stop : "5:00" } } }, true )

sh.getBalancerWindow()
sh.status()

关于集合的balancer(了解下)
关闭某个集合的balance
sh.disableBalancing("students.grades")
打开某个集合的balancer
sh.enableBalancing("students.grades")
确定某个集合的balance是开启或者关闭
db.getSiblingDB("config").collections.findOne({_id : "students.grades"}).noBalance;


8. 备份恢复
8.1 备份恢复工具介绍:
(1)**   mongoexport/mongoimport (迁移时使用)


(2)***** mongodump/mongorestore (备份恢复)(日常运维)

在技术支持性的公司使用迁移的机会比较多,否则的话都是日常运维的备份恢复。

8.2 备份工具区别在哪里?
应用场景总结:
mongoexport/mongoimport: json csv 
1、异构平台迁移  mysql  <---> mongodb
2、同平台,跨大版本:mongodb 2  ----> mongodb 3

日常备份恢复时使用

mongodump/mongorestore 

8.3 导出工具mongoexport
mongoexport具体用法如下所示:
$ mongoexport --help  
参数说明:
-h:指明数据库宿主机的IP
-u:指明数据库的用户名
-p:指明数据库的密码
-d:指明数据库的名字
-c:指明collection的名字
-f:指明要导出那些列
-o:指明到要导出的文件名
-q:指明导出数据的过滤条件
--authenticationDatabase admin

1.单表备份至json格式
mongoexport -uroot -proot123 --port 27017 --authenticationDatabase admin -d oldboy -c log -o /mongodb/log.json

注:备份文件的名字可以自定义,默认导出了JSON格式的数据。

2. 单表备份至csv格式
如果我们需要导出CSV格式的数据,则需要使用----type=csv参数:

 mongoexport -uroot -proot123 --port 27017 --authenticationDatabase admin -d test -c log --type=csv -f uid,name,age,date  -o /mongodb/log.csv

8.4 导入工具mongoimport
$ mongoimport --help
参数说明:
-h:指明数据库宿主机的IP
-u:指明数据库的用户名
-p:指明数据库的密码
-d:指明数据库的名字
-c:指明collection的名字
-f:指明要导入那些列
-j, --numInsertionWorkers=<number>  number of insert operations to run concurrently                                                  (defaults to 1)
//并行
数据恢复:
1.恢复json格式表数据到log1
mongoimport -uroot -proot123 --port 27017 --authenticationDatabase admin -d oldboy -c log1 /mongodb/log.json
2.恢复csv格式的文件到log2
上面演示的是导入JSON格式的文件中的内容,如果要导入CSV格式文件中的内容,则需要通过--type参数指定导入格式,具体如下所示:
错误的恢复

注意:
(1)csv格式的文件头行,有列名字
mongoimport   -uroot -proot123 --port 27017 --authenticationDatabase admin   -d oldboy -c log2 --type=csv --headerline --file  /mongodb/log.csv

(2)csv格式的文件头行,没有列名字
mongoimport   -uroot -proot123 --port 27017 --authenticationDatabase admin   -d oldboy -c log3 --type=csv -f id,name,age,date --file  /mongodb/log.csv
--headerline:指明第一行是列名,不需要导入。

8.5 异构平台迁移案例
mysql   -----> mongodb  
world数据库下city表进行导出,导入到mongodb

(1)mysql开启安全路径
vim /etc/my.cnf   --->添加以下配置
secure-file-priv=/tmp

--重启数据库生效
/etc/init.d/mysqld restart

(2)导出mysql的city表数据
source /root/world.sql

select * from world.city into outfile '/tmp/city1.csv' fields terminated by ',';

(3)处理备份文件
desc world.city
  ID          | int(11)  | NO   | PRI | NULL    | auto_increment |
| Name        | char(35) | NO   |     |         |                |
| CountryCode | char(3)  | NO   | MUL |         |                |
| District    | char(20) | NO   |     |         |                |
| Population

vim /tmp/city.csv   ----> 添加第一行列名信息

ID,Name,CountryCode,District,Population

(4)在mongodb中导入备份
mongoimport -uroot -proot123 --port 27017 --authenticationDatabase admin -d world  -c city --type=csv -f ID,Name,CountryCode,District,Population --file  /tmp/city1.csv

use world
db.city.find({CountryCode:"CHN"});

-------------
world共100张表,全部迁移到mongodb

select table_name ,group_concat(column_name) from columns where table_schema='world' group by table_name;

select * from world.city into outfile '/tmp/world_city.csv' fields terminated by ',';

forexample:

select concat("select * from ",table_schema,".",table_name ," into outfile '/tmp/",table_schema,"_",table_name,".csv' fields terminated by ',';")
from information_schema.tables where table_schema ='world';

导入:
提示,使用infomation_schema.columns + information_schema.tables

mysql导出csv:
select * from test_info   
into outfile '/tmp/test.csv'   
fields terminated by ','    ------字段间以,号分隔
optionally enclosed by '"'   ------字段用"号括起
escaped by '"'           ------字段中使用的转义符为"
lines terminated by '\r\n';  ------行以\r\n结束

mysql导入csv:
load data infile '/tmp/test.csv'   
into table test_info    
fields terminated by ','  
optionally enclosed by '"' 
escaped by '"'   
lines terminated by '\r\n'; 

8.6 mongodump和mongorestore
8.6.1介绍
mongodump能够在Mongodb运行时进行备份,它的工作原理是对运行的Mongodb做查询,然后将所有查到的文档写入磁盘。
但是存在的问题时使用mongodump产生的备份不一定是数据库的实时快照,如果我们在备份时对数据库进行了写入操作,
则备份出来的文件可能不完全和Mongodb实时数据相等。另外在备份时可能会对其它客户端性能产生不利的影响。

8.6.2 mongodump用法如下:
$ mongodump --help
参数说明:
-h:指明数据库宿主机的IP
-u:指明数据库的用户名
-p:指明数据库的密码
-d:指明数据库的名字
-c:指明collection的名字
-o:指明到要导出的文件名
-q:指明导出数据的过滤条件
-j, --numParallelCollections=  number of collections to dump in parallel (4 by default)
--oplog  备份的同时备份oplog

8.6.3 mongodump和mongorestore基本使用
全库备份
mkdir /mongodb/backup
mongodump  -uroot -proot123 --port 27017 --authenticationDatabase admin -o /mongodb/backup

备份world库
$ mongodump   -uroot -proot123 --port 27017 --authenticationDatabase admin -d world -o /mongodb/backup/

备份oldboy库下的log集合
$ mongodump   -uroot -proot123 --port 27017 --authenticationDatabase admin -d oldboy -c log -o /mongodb/backup/

压缩备份


$ mongodump   -uroot -proot123 --port 27017 --authenticationDatabase admin -d oldguo -o /mongodb/backup/ --gzip
 mongodump   -uroot -proot123 --port 27017 --authenticationDatabase admin -o /mongodb/backup/ --gzip
$ mongodump   -uroot -proot123 --port 27017 --authenticationDatabase admin -d app -c vast -o /mongodb/backup/ --gzip

恢复world库
$ mongorestore   -uroot -proot123 --port 27017 --authenticationDatabase admin -d world1  /mongodb/backup/world

恢复oldguo库下的t1集合
[mongod@db03 oldboy]$ mongorestore   -uroot -proot123 --port 27017 --authenticationDatabase admin -d world -c t1  --gzip  /mongodb/backup.bak/oldboy/log1.bson.gz 

drop表示恢复的时候把之前的集合drop掉(危险)
$ mongorestore  -uroot -proot123 --port 27017 --authenticationDatabase admin -d oldboy --drop  /mongodb/backup/oldboy

8.7 mongodump和mongorestore高级企业应用(--oplog)
注意:这是replica set或者master/slave模式专用
--oplog
 use oplog for taking a point-in-time snapshot

8.7.1 oplog介绍
在replica set中oplog是一个定容集合(capped collection),它的默认大小是磁盘空间的5%(可以通过--oplogSizeMB参数修改).

位于local库的db.oplog.rs,有兴趣可以看看里面到底有些什么内容。
其中记录的是整个mongod实例一段时间内数据库的所有变更(插入/更新/删除)操作。
当空间用完时新记录自动覆盖最老的记录。
其覆盖范围被称作oplog时间窗口。需要注意的是,因为oplog是一个定容集合,
所以时间窗口能覆盖的范围会因为你单位时间内的更新次数不同而变化。
想要查看当前的oplog时间窗口预计值,可以使用以下命令:

 mongod -f /mongodb/28017/conf/mongod.conf 
 mongod -f /mongodb/28018/conf/mongod.conf 
 mongod -f /mongodb/28019/conf/mongod.conf 
 mongod -f /mongodb/28020/conf/mongod.conf 
 
 
 use local 
 db.oplog.rs.find().pretty()
"ts" : Timestamp(1553597844, 1),
"op" : "n"
"o"  :

"i": insert
"u": update
"d": delete
"c": db cmd

test:PRIMARY> rs.printReplicationInfo()
configured oplog size:   1561.5615234375MB <--集合大小
log length start to end: 423849secs (117.74hrs) <--预计窗口覆盖时间
oplog first event time:  Wed Sep 09 2015 17:39:50 GMT+0800 (CST)
oplog last event time:   Mon Sep 14 2015 15:23:59 GMT+0800 (CST)
now:                     Mon Sep 14 2015 16:37:30 GMT+0800 (CST)

8.7.2 oplog企业级应用
(1)实现热备,在备份时使用--oplog选项
注:为了演示效果我们在备份过程,模拟数据插入
(2)准备测试数据
[mongod@db01 conf]$ mongo --port 28018
use oldboy
for(var i = 1 ;i < 100; i++) {
    db.foo.insert({a:i});
}

my_repl:PRIMARY> db.oplog.rs.find({"op":"i"}).pretty()

oplog 配合mongodump实现热备
mongodump --port 28018 --oplog -o /mongodb/backup
作用介绍:--oplog 会记录备份过程中的数据变化。会以oplog.bson保存下来
恢复
mongorestore  --port 28018 --oplogReplay /mongodb/backup

8.8 oplog高级应用
背景:每天0点全备,oplog恢复窗口为48小时
某天,上午10点world.city 业务表被误删除。
恢复思路:
    0、停应用
    2、找测试库
    3、恢复昨天晚上全备
    4、截取全备之后到world.city误删除时间点的oplog,并恢复到测试库
    5、将误删除表导出,恢复到生产库

恢复步骤:
模拟故障环境:

1、全备数据库
模拟原始数据

mongo --port 28017
use wo
for(var i = 1 ;i < 20; i++) {
    db.ci.insert({a: i});
}

全备:
rm -rf /mongodb/backup/*
mongodump --port 28018 --oplog -o /mongodb/backup

--oplog功能:在备份同时,将备份过程中产生的日志进行备份
文件必须存放在/mongodb/backup下,自动命令为oplog.bson

再次模拟数据
db.ci1.insert({id:1})
db.ci2.insert({id:2})


2、上午10点:删除wo库下的ci表
10:00时刻,误删除
db.ci.drop()
show tables;

3、备份现有的oplog.rs表
mongodump --port 28018 -d local -c oplog.rs  -o /mongodb/backup

4、截取oplog并恢复到drop之前的位置
更合理的方法:登陆到原数据库
[mongod@db03 local]$ mongo --port 28018
my_repl:PRIMARY> use local
db.oplog.rs.find({op:"c"}).pretty();

{
    "ts" : Timestamp(1553659908, 1),
    "t" : NumberLong(2),
    "h" : NumberLong("-7439981700218302504"),
    "v" : 2,
    "op" : "c",
    "ns" : "wo.$cmd",
    "ui" : UUID("db70fa45-edde-4945-ade3-747224745725"),
    "wall" : ISODate("2019-03-27T04:11:48.890Z"),
    "o" : {
        "drop" : "ci"
    }
}

获取到oplog误删除时间点位置:
"ts" : Timestamp(1553659908, 1)

 5、恢复备份+应用oplog
[mongod@db03 backup]$ cd /mongodb/backup/local/
[mongod@db03 local]$ ls
oplog.rs.bson  oplog.rs.metadata.json
[mongod@db03 local]$ cp oplog.rs.bson ../oplog.bson 
rm -rf /mongodb/backup/local/
 
mongorestore --port 38021  --oplogReplay --oplogLimit "1553659908:1"  --drop   /mongodb/backup/

8.9 分片集群的备份思路(了解)
1、你要备份什么?
config server
shard 节点

单独进行备份
2、备份有什么困难和问题
(1)chunk迁移的问题
    人为控制在备份的时候,避开迁移的时间窗口
(2)shard节点之间的数据不在同一时间点。
    选业务量较少的时候       
        
Ops Manager 

 

 

 

 

 

 

 

 

 


  

 

 

 

 

 

 

 

启动报错原因:

1、配置文件参数设置错误;

2、目录及文件权限不正确

3、如果启动失败,在下次启动时需要删除dbPath指定目录下的文件后,再进行重新启动。

[mongod@s10 conf]$ mongod -f /mongodb/27018/conf/mongodb.conf  
about to fork child process, waiting until server is ready for connections.
forked process: 2347
ERROR: child process failed, exited with error number 100
To see additional information in this output, start without the "--fork" option.

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值