MongoDB索引的创建和使用详解

索引通常能够极大的提高查询的效率。在系统中使用查询时,应该考虑建立相关的索引。在MongoDB中创建索引相对比较容易。

mongodb中的索引在概念上和大多数关系型数据库如MySQL是一样的。当你在某种情况下需要在MySQL中建立索引,这样的情景同样适合于MongoDB。

基本操作

索引是一种数据结构,他搜集一个集合中文档特定字段的值。MongoDB的查询优化器能够使用这种数据结构来快速的对集合(collection)中的文档(collection)进行寻找和排序.准确来说,这些索引是通过B-Tree索引来实现的。

在命令行中,可以通过调用ensureIndex()函数来建立索引,该函数指定一个到多个需要索引的字段。沿用在前面的随笔中的例子,我们再things集合中的j字段上建立索引:

> db.things.ensureIndex({j:1}) 

EnsureIndex()函数自是在索引不存在的情况下才会创建。

一旦集合在某一个字段上建立索引后,对该字段的随机查询的访问速度会很快。如果没有索引,MongoDB会在遍历所有的键值对,然后去对应检查相关的字段。

> db.things.find({j:2}); //在建立了索引的字段上查询,速度快  

{ "_id" : ObjectId("4e24433dcac1e3490b9033be"), "x" : 4, "j" : 2 }  

> db.things.find({x:3});//在未建立索引的字段上查询,需要逐个字段匹配,速度慢  

{ "_id" : ObjectId("4e244315cac1e3490b9033bc"), "x" : 3 } 

通过在命令行中输入getIndexs()能够查看当前集合中的所有索引。

> db.things.getIndexes()  

[  

{  

"name" : "_id_",  

"ns" : "things.things",  

"key" : {  

"_id" : 1  

},  

"v" : 0  

},  

{  

"_id" : ObjectId("4e244382cac1e3490b9033d0  

"ns" : "things.things",  

"key" : {  

"j" : 1  

},  

"name" : "j_1",  

"v" : 0  

}  

] 

通过db.system.indexes.find()能够返回当前数据库中的所有索引

> db.system.indexes.find()  

{ "name" : "_id_", "ns" : "things.things", "key" : { "_id" : 1 }, "v" : 0 }  

{ "_id" : ObjectId("4e244382cac1e3490b9033d0"), "ns" : "things.things", "key" :{ "j" : 1 }, "name" : "j_1", "v" : 0 } 

默认索引

对于每一个集合(除了capped集合),默认会在_id字段上创建索引,而且这个特别的索引不能删除。_id字段是强制唯一的,由数据库维护。

嵌套关键字

在MongoDB中,甚至能够在一个嵌入的文档上(embedded)建立索引.

> db.things.ensureIndex({"address.city":1}) 

文档作为索引

任何类型,包括文档(document)都能作为索引:

> db.factories.insert({name:"xyz",metro:{city:"New York",state:"NY"}});  

> db.factories.ensureIndex({metro:1});  

> db.factories.find({metro:{city:"New York",state:"NY"}});//能够利用索引进行查询  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : < 

{ "city" : "New York", "state" : "NY" } }  

> db.factories.find({metro:{$gte:{city:"New York"}}});//能够利用索引进行查询  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find({metro:{state:"NY",city:"New York"}})//不能够返回结果,字段的顺序不对 

创建文档索引的一个替代方法是创建复合索引,例如:

> db.factories.ensureIndex({"metro.city":1,"metro.state":1})  

> db.factories.find({"metro.city":"New York","metro.state":"NY"});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find({"metro.city":"New York"});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find().sort({"metro.city":1,"New York":1});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } }  

> db.factories.find().sort({"metro.city":1});  

{ "_id" : ObjectId("4e244744cac1e3490b9033d2"), "name" : "xyz", "metro" : { "city" : "New York", "state" : "NY" } } 

组合关键字索引

除了基本的以单个关键字作为索引外,MongoDB也支持多个关键字的组合索引,和基本的索引一样,也是用ensureIndex()函数,该函数可以指定多个键。

> db.things.ensureIndex({j:1,name:-1}) 

当创建索引时,键后面的数字表明了索引的方向,取值为1或者-1,1表示升序,-1表示降序。升序或者降序在随机访问的时候关系不大,当时在做排序或者范围查询的时候就很重要了。

如果在建立了a,b,c这样一个复合索引,那么你可以在a,A,b和a,b,c上使用索引查询。

稀疏索引

和稀疏矩阵类似,稀疏索引就是索引至包含被索引字段的文档。

任何一个稀疏的缺失某一个字段的文档将不会存储在索引中,之所以称之为稀疏索引就是说缺失字段的文档的值会丢失。

稀疏索引的创建和完全索引的创建没有什么不同。使用稀疏索引进行查询的时候,某些由于缺失了字段的文档记录可能不会被返回,这是由于稀疏索引子返回被索引了的字段。可能比较难以理解,不过看几个例子就好理解了。

> db.people.ensureIndex({title:1},{sparse:true}) //在title字段上建立稀疏索引  

> db.people.save({name:"Jim"})  

> db.people.save({name:"yang",title:"prince"})  

> db.people.find();  

{ "_id" : ObjectId("4e244dc5cac1e3490b9033d7"), "name" : "Jim" }  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" }  

> db.people.find().sort({title:1})//自有包含有索引字段的记录才被返回  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" }  

> db.people.dropIndex({title:1})//删除稀疏索引之后,所有的记录均显示  

{ "nIndexesWas" : 2, "ok" : 1 }  

> db.people.find().sort({title:1})  

{ "_id" : ObjectId("4e244dc5cac1e3490b9033d7"), "name" : "Jim" }  

{ "_id" : ObjectId("4e244debcac1e3490b9033d8"), "name" : "yang", "title" : "prince" } 

唯一索引

MongoDB支持唯一索引,这使得不能插入在唯一索引项上已经存在的记录。例如,要保证firstname和lastname都是唯一的,命令如下

> db.things.ensureIndex({firstname:1,lastname:1},{unique:true}) 

缺失的键

当一个文档以唯一索引的方式保存到集合中去的时候,任何缺失的索引字段都会一null值代替,因此,不能在唯一索引上同时插入两条缺省的记录。如下:

>db.things.ensureIndex({firstname: 1}, {unique: true});  

>db.things.save({lastname: "Smith"});  

>db.things.save({lastname: "Jones"});// 会产生错误,因为firstname会有两个null. 

重复值

唯一索引不能够创建在具有重复值的键上,如果你一定要在这样的键上创建,那么想系统将保存第一条记录,剩下的记录会被删除,只需要在创建索引的时候加上dropDups这个可选项即可

>db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})  

Dropping Indexes 

删除一个特定集合上的索引:

>db.collection.dropIndexes(); 

删除集合中的某一个索引:

db.collection.dropIndex({x: 1, y: -1}) 

也可以直接执行命令进性删除

db.runCommand({dropIndexes:‘foo‘, index : {y:1}})//删除集合foo中{y:1}的索引  

// remove all indexes:  

db.runCommand({dropIndexes:‘foo‘, index : ‘*‘})//删除集合foo中所有的索引 

重建索引

可以所用如下命令重建索引:

db.myCollection.reIndex()  

// same as:  

db.runCommand( { reIndex : ‘myCollection‘ } ) 

通常这是不必要的,但是在集合的大小变动很大及集合在磁盘空间上占用很多空间时重建索引才有用。对于大数据量的集合来说,重建索引可能会很慢。

注:

MongoDB中索引是大小写敏感的。

当更新对象是,只有在索引上的这些key发生变化时才会更新。着极大地提高了性能。当对象增长了或者必须移动时,所有的索引必须更新,这回很慢 。

索引信息会保存在system.indexes 集合中,运行 db.system.indexes.find() 能够看到这些示例数据。

索引的字段的大小有最大限制,目前接近800 bytes. 可在大于这个值的字段上建立索引是可以的,但是该字段不会被索引,这种限制在以后的版本中可能被移除。

索引的性能

索引使得可以通过关键字段获取数据,能够使得快速查询和更新数据。

但是,必须注意的是,索引也会在插入和删除的时候增加一些系统的负担。往集合中插入数据的时候,索引的字段必须加入到B-Tree中去,因此,索引适合建立在读远多于写的数据集上,对于写入频繁的集合,在某些情况下,索引反而有副作用。不过大多数集合都是读频繁的集合,所以集合在大多数情况下是有用的。

使用sort()而不需要索引

如果数据集合比较小(通常小于4M),使用sort()而不需要建立索引就能够返回数据。在这种情况下,做好联合使用limit()和sort()。

MongoDB导出场景查询优化

引言

前段时间遇到一个类似导出数据场景,观察下来发现速度会越来越慢,导出100万数据需要耗费40-60分钟,从日志观察发现,耗时也是越来越高。

原因

从代码逻辑上看,这里采取了分批次导出的方式,类似前端的分页,具体是通过skip+limit的方式实现的,那么采用这种方式会有什么问题呢?我们google一下这两个接口的文档:

The cursor.skip() method is often expensive because it requires the server to walk from the beginning of the collection or index to get the offset or skip position before beginning to return results. As the offset (e.g. pageNumber above) increases, cursor.skip() will become slower and more CPU intensive. With larger collections, cursor.skip() may become IO bound.

简单来说,随着页数的增长,skip()会变得越来越慢,但是具体就我们这里导出的场景来说,按理说应该没必要每次都去重复计算,做一些无用功,我的理解应该可以拿到一个指针,慢慢遍历,简单google之后,我们发现果然是可以这样做的。

我们可以在持久层新增一个方法,返回一个cursor专门供上层去遍历数据,这样就不用再去遍历已经导出过的结果集,从O(N2)优化到了O(N),这里还可以指定一个batchSize,设置一次从MongoDB中抓取的数据量(元素个数),注意这里最大是4M.

/**
     * <p>Limits the number of elements returned in one batch. A cursor 
     * typically fetches a batch of result objects and store them
     * locally.</p>
     *
     * <p>If {@code batchSize} is positive, it represents the size of each batch of objects retrieved. It can be adjusted to optimize
     * performance and limit data transfer.</p>
     *
     * <p>If {@code batchSize} is negative, it will limit of number objects returned, that fit within the max batch size limit (usually
     * 4MB), and cursor will be closed. For example if {@code batchSize} is -10, then the server will return a maximum of 10 documents and
     * as many as can fit in 4MB, then close the cursor. Note that this feature is different from limit() in that documents must fit within
     * a maximum size, and it removes the need to send a request to close the cursor server-side.</p>
*/

比如说我这里配置的8000,那么mongo客户端就会去默认抓取这么多的数据量:

未分类

经过本地简单的测试,我们发现性能已经有了飞跃的提升,导出30万数据,采用之前的方式,翻页到后面平均要500ms,总耗时60039ms。而优化后的方式,平均耗时在100ms-200ms之间,总耗时16667ms(中间包括业务逻辑的耗时)。

使用

DBCursor cursor = collection.find(query).batchSize(8000);
while (dbCursor.hasNext()) {
  DBObject nextItem = dbCursor.next();
  //业务代码
  ... 
  //
}

那么我们再看看hasNext内部的逻辑好吗?好的.

    @Override
    public boolean hasNext() {
        if (closed) {
            throw new IllegalStateException("Cursor has been closed");
        }

        if (nextBatch != null) {
            return true;
        }

        if (limitReached()) {
            return false;
        }

        while (serverCursor != null) {
            //这里会向mongo发送一条指令去抓取数据
            getMore();
            if (nextBatch != null) {
                return true;
            }
        }

        return false;
    }


    private void getMore() {
        Connection connection = connectionSource.getConnection();
        try {
            if(serverIsAtLeastVersionThreeDotTwo(connection.getDescription()){
                try {
//可以看到这里其实是调用了`nextBatch`指令        
initFromCommandResult(connection.command(namespace.getDatabaseName(),
                                                             asGetMoreCommandDocument(),
                                                             false,
                                                             new NoOpFieldNameValidator(),
                                                             CommandResultDocumentCodec.create(decoder, "nextBatch")));
                } catch (MongoCommandException e) {
                    throw translateCommandException(e, serverCursor);
                }
            } else {
                initFromQueryResult(connection.getMore(namespace, serverCursor.getId(),
                                                       getNumberToReturn(limit, batchSize, count),
                                                       decoder));
            }
            if (limitReached()) {
                killCursor(connection);
            }
        } finally {
            connection.release();
        }
    }

最后initFromCommandResult 拿到结果并解析成Bson对象

总结

我们平常写代码的时候,最好都能够针对每个方法、接口甚至是更细的粒度加上埋点,也可以设置成debug级别,这样利用log4j/logback等日志框架动态更新级别,可以随时查看耗时,从而更能够针对性的优化,对于本文说的这个场景,我们首先看看是不是代码的逻辑有问题,然后看看是不是数据库的问题,比如说没建索引、数据量过大等,再去想办法针对性的优化,而不要上来就撸代码。

MongoDB安全介绍及配置身份认证

概述

MongoDB安全主要包括以下4个方面

1、物理隔离

系统不论设计的多么完善,在实施过程中,总会存在一些漏洞。如果能够把不安全的使用方与MongoDB数据库做物理上的隔离,即通过任何手段都不能连接到数据库,这是最安全的防护。但,通常这是不现实的。一些重要的数据可能会保存下来,放置到物理隔离的机房中

2、网络隔离

许多公司的开发机处于内网环境中。即使数据库存在漏洞,外部环境也没有机会利用,因为根本无法访问内网

3、防火墙隔离

可以利用防火墙配置IP白名单,只允许某些IP访问数据库,也可以从一定程度上增加MongoDB的安全性

4、用户名密码鉴权

相对于以上3种方式,用户名密码鉴权机制是最常见的MongoDB安全措施。如果密码设置的比较简单,或者连接环境不是加密环境,很可能被第三方获取到用户名和密码,从而造成MongoDB数据库的危险

权限认证

mongodb存储所有的用户信息在admin数据库的集合system.users中,保存用户名、密码和数据库信息。mongodb默认不启用权限认证,只要能连接到该服务器,就可连接到mongod。若要启用安全认证,需要更改配置文件参数authorization,也可以简写为auth。

未分类
  
然后,重启mongod。查看日志文件,发现权限认证已经开启

未分类
 
但是,不使用用户名和密码依然可以连接到数据库。这是因为,我们还没有创建用户。在用户创建,并且开启权限认证之后,如果不使用用户名和密码将不能够连接到数据库

角色管理

在进行用户管理之前,首先要先了解角色管理

MongoDB支持基于角色的访问控制(RBAC)来管理对MongoDB系统的访问。一个用户可以被授权一个或者多个:ref:角色 以决定该用户对数据库资源和操作的访问权限。在权限以外,用户是无法访问系统的

数据库角色在创建用户中的role参数中设置。角色分为内建角色和自定义角色

内建角色

MongoDB内建角色包括以下几类

1、数据库用户角色

read:允许用户读取指定数据库
readWrite:允许用户读写指定数据库

2、数据库管理员角色

dbAdmin:允许用户进行索引创建、删除,查看统计或访问system.profile,但没有角色和用户管理的权限
userAdmin:提供了在当前数据库中创建和修改角色和用户的能力
dbOwner: 提供对数据库执行任何管理操作的能力。这个角色组合了readWrite、dbAdmin和userAdmin角色授予的特权。

  
3、集群管理角色

clusterAdmin : 提供最强大的集群管理访问。组合clusterManager、clusterMonitor和hostManager角色的能力。还提供了dropDatabase操作
clusterManager : 在集群上提供管理和监视操作。可以访问配置和本地数据库,这些数据库分别用于分片和复制
clusterMonitor : 提供对监控工具的只读访问,例如MongoDB云管理器和Ops管理器监控代理。
hostManager : 提供监视和管理服务器的能力。

  
4、备份恢复角色

backup : 提供备份数据所需的能力,使用MongoDB云管理器备份代理、Ops管理器备份代理或使用mongodump
restore : 提供使用mongorestore恢复数据所需的能力

  
5、所有数据库角色

readAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读权限 
readWriteAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的读写权限 
userAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的userAdmin权限 
dbAdminAnyDatabase:只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。 

  
6、超级用户角色

root:提供对readWriteAnyDatabase、dbAdminAnyDatabase、userAdminAnyDatabase、clusterAdmin、restore和backup的所有资源的访问

  
7、内部角色

__system : 提供对数据库中任何对象的任何操作的特权

自定义角色

  • 除了使用内建的角色之外,MongoDB还支持使用db.createRole()方法来自定义角色

  • [注意]只能在admin数据库中创建角色,否则会失败

  • role: 自定义角色的名称

  • privileges: 权限操作 

  • roles:继承的角色。如果没有继承的角色,可以设置为空数组  

use admin
db.createRole(
   {
     role: "myClusterwideAdmin",
     privileges: [
       { resource: { cluster: true }, actions: [ "addShard" ] },
       { resource: { db: "config", collection: "" }, actions: [ "find", "update", "insert", "remove" ] },
       { resource: { db: "users", collection: "usersCollection" }, actions: [ "update", "insert", "remove" ] },
       { resource: { db: "", collection: "" }, actions: [ "find" ] }
     ],
     roles: [
       { role: "read", db: "admin" }
     ]
   },
   { w: "majority" , wtimeout: 5000 }
)

未分类

用户管理

创建用户

  • 使用createUser命令来创建用户

  • user: 用户名 pwd: 密码

  • customData: 对用户名密码的说明(可选项)

  • roles: {role:继承自什么角色类型,db:数据库名称}

db.createUser({user: "...",pwd: "...",customDate:"...",roles:[{role: "...",db: "..."}]})

1、创建管理员用户

MongoDB没有默认管理员账号,所以要先添加管理员账号。切换到admin数据库,添加的账号才是管理员账号

在admin数据库中,添加一个用户并赋予userAdminAnyDatabase角色

db.createUser({user: "admin",pwd: "123456",roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]})

  
未分类

2、重新登录数据库,并验证权限

如果auth()方法返回0则代表授权失败,返回1代表授权成功

db.auth()

未分类
  
3、添加普通用户

一旦经过认证的用户管理员,可以使用db.createUser()去创建额外的用户。 可以分配mongodb内置的角色或用户自定义的角色给用户

未分类

[注意]需要在admin数据库下进行认证,否则认证不成功

未分类

由于该用户只有读权限,所以会写入数据失败

未分类

4、创建超级用户

未分类

查看用户

db.system.users.find()

未分类

删除用户

db.dropUser()

未分类

添加用户权限

db.grantRolesToUser()

  
给在db1数据库中只读的x用户,添加写权限

未分类

修改密码

db.changeUserPassword()

未分类

Mongodb索引详解

1、参考

MongoDB权威指南(第2版) (https://www.amazon.cn/%E5%9B%BE%E4%B9%A6/dp/B00HLX035Q/ref=sr_1_1?ie=UTF8&qid=1501597098&sr=8-1&keywords=mongodb)

Mongodb Docs (https://docs.mongodb.com/manual/)

2、前言

建立索引对于任何需要提高查询速度的数据库来说都非常重要,那么索引究竟是一个什么?首先来看看下面是《区块链:技术驱动金融》 (https://www.amazon.cn/dp/B01KGYHBEM/ref=cngwdyfloorv2_recs_0?pf_rd_p=05f2b7d6-37ec-49bf-8fcf-5d2fec23a061&pf_rd_s=desktop-2&pf_rd_t=36701&pf_rd_i=desktop&pf_rd_m=A1AJ19PSB66TGU&pf_rd_r=16P2J6RAT3GC8PN1T4BJ&pf_rd_r=16P2J6RAT3GC8PN1T4BJ&pf_rd_p=05f2b7d6-37ec-49bf-8fcf-5d2fec23a061)这本书的前两章的目录。

第1章密码学及加密货币概述----------1
1.1密码学哈希函数----------4
1.2哈希指针及数据结构----------14
1.3数字签名----------19
1.4公钥即身份----------24
1.5两种简单的加密货币----------26
第2章比特币如何做到去中心化----------35
2.1中心化与去中心化----------37
2.2分布式共识----------39
2.3使用区块链达成没有身份的共识----------44
2.4奖励机制与工作量证明----------51
2.5总结----------59

通过目录,我们能很快很清楚的知道这本书写了什么,而我们也能很快从中查找到我们感兴趣的内容在哪一页,如果没有目录,我们将会一篇一篇的去翻阅我们想了解的内容,而索引可以比作数据库的目录。

3、效率

explain()是官方提供的一个用于返回当前查询过程信息的一个方法,通过这个命令,我们可以知道查询的过程,以便于我们进行优化,explain()支持这些操作的过程查询:

  • aggregate()
  • count()
  • distinct()
  • find()
  • group()
  • remove()
  • update()

explain()方法接受三种可选字符串作为参数和两种布尔值,官方是这样来介绍的:

  • 可选的。指定说明输出的详细程度模式。该模式会影响explain()返回信息的数量和行为。可能的模式有:”queryPlanner”, “executionStats”,和”allPlansExecution”。

  • 默认模式是”queryPlanner”。

  • 为了向后兼容早期版本 cursor.explain(),MongoDB解释true为 “allPlansExecution”和false “queryPlanner”。

  • aggregate()忽略verbosity参数并在queryPlanner模式下执行。

首先我们插入100000条数据,可以通过下面代码来循环插入:

for(var i = 0; i < 100000; i++) {
  db.test.insert({
    id: i,
    username: 'user' + i
  });
}

然后我们通过explain()方法看看查询含有username:user108键值的文档过程:

db.test.find({username: 'user108'}).explain(true);

返回的结果大概为:

{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "blog.test",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "username" : {
                "$eq" : "user4"
            }
        },
        "winningPlan" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user4"
                }
            },
            "direction" : "forward"
        },
        "rejectedPlans" : [ ]
    },
    "executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 48,
        "totalKeysExamined" : 0,
        "totalDocsExamined" : 100000,
        "executionStages" : {
            "stage" : "COLLSCAN",
            "filter" : {
                "username" : {
                    "$eq" : "user4"
                }
            },
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 37,
            "works" : 100002,
            "advanced" : 1,
            "needTime" : 100000,
            "needYield" : 0,
            "saveState" : 782,
            "restoreState" : 782,
            "isEOF" : 1,
            "invalidates" : 0,
            "direction" : "forward",
            "docsExamined" : 100000
        },
        "allPlansExecution" : [ ]
    },
    "serverInfo" : {
        "host" : "YizhoudeMacBook-Pro.local",
        "port" : 27017,
        "version" : "3.4.6",
        "gitVersion" : "c55eb86ef46ee7aede3b1e2a5d184a7df4bfb5b5"
    },
    "ok" : 1
}

在上面返回的过程中,我们只需要关注executionStats对象里面的值中的:

  • totalDocsExamined: 文档扫描总数
  • executionTimeMillis: 执行时间(毫秒)
  • nReturned: 返回的文档数量

一般通过这三个值就可以判断文档执行需不需要优化,比如上面返回的信息中表示这次查询文档扫描总数100000,执行时间48毫秒,返回的文档数量为1。

通过上面的返回信息其实我们可以看出,find()方法其实是扫描了整个集合来查询我们需要的,即使找到了我们需要的文档,但还会继续往下查询,因为find()方法是查询所有符合条件的文档,这里其实是浪费了服务器的资源。

我们已知username是唯一值,那么我们可以findOne,或者通过limit(1)来限制返回的数量,限制了返回的数量后,Mongodb内部查询是查询到符合的第一条文档就停止,比如我们要查询user5,那么查找的文档总数将是查询到第一条符合的文档数止到第一条文档的总和,这样看上去是没有问题了,但是一旦我们查询的是user99995,那么扫描的文档数量将是99995,这样的方式属于治标不治本。

4、建立索引

Mongodb建立索引是通过createIndex()方法建立,参数为一个对象,包含了需要建立索引的键:

db.test.createIndex({username: 1});

建立的索引其实相当于保存在Mongodb内部的一个单独的文档,这个文档存放了所有username的值和对应储存的物理位置,大概结构为(下面数据是比喻,并不代表真实性):

username Index

['user1', 0x00000001],
['user2', 0x00000002],
['user3', 0x00000003],
['user4', 0x00000004],
['user5', 0x00000005],
........
['user100000', 0x00100000]

5、索引选项

db.collection.createIndex(key,options);

我们可以通过多个选项对索引的文档进行限制,其中很有用的一个就是unique,使用方法如下:

db.test.createIndex({username: 1},{unique: 1});

通过上面的设置username键的值在整个集合中必须是唯一的,如果你试图插入两个username键值相等的文档,那么将会报错,比如下面这样:

db.test.createIndex({username: 1},{unique: 1});

db.test.insert({username: 'user200'});

db.test.insert({username: 'user200'});
----Error!!

它同样适用于复合索引,在对复合索引使用唯一值选项后,如果你试图插入两个及以上索引键的值都一样的两个文档,那么将报错,如果是两个文档的其中一个值不一样同样可以插入,比如下面代码所示:

db.test.createIndex({username: 1, age: 1},{unique: 1});

db.test.insert({username: 'user200', age:18});

db.test.insert({username: 'user200', age:20});

以上两个方式都是可以正常插入的,因为插入的这两个文档中的age不一样,但是如果像下面这样就会报错了:

db.test.createIndex({username: 1, age: 1},{unique: 1});

db.test.insert({username: 'user200', age:18});

db.test.insert({username: 'user200', age:18});
----Error!!

当然除了唯一值选项的设置之外,还有很多选项的设置,如果大家有兴趣可以到官方文档查看。

6、使用索引

建立了索引后,我们不需要特别的方式去查询,我们可以像普通的查询方式一样的去查询:

db.test.find({username: 'user9999'});

在查询的时候Mongodb会自动的查找我们是否为username键建立索引,如果有则扫描username的索引文档,找到相应的值后,然后在根据相应的物理地址去扫描对应的文档,如果没有则扫描test集合的所有文档。

建立了索引后,我们来看看查找username: ‘user9999’键值对的文档需要的时间以及查询上的效率:

db.test.find({username: 'user9999'}).explain(true);

返回的过程信息(只拿出executionStats属性):

"executionStats" : {
        "executionSuccess" : true,
        "nReturned" : 1,
        "executionTimeMillis" : 0,
        "totalKeysExamined" : 1,
        "totalDocsExamined" : 1,
        "executionStages" : {
            "stage" : "FETCH",
            "nReturned" : 1,
            "executionTimeMillisEstimate" : 0,
            "works" : 2,
            "advanced" : 1,
            "needTime" : 0,
            "needYield" : 0,
            "saveState" : 0,
            "restoreState" : 0,
            "isEOF" : 1,
            "invalidates" : 0,
            "docsExamined" : 1,
            "alreadyHasObj" : 0,
            "inputStage" : {
                "stage" : "IXSCAN",
                "nReturned" : 1,
                "executionTimeMillisEstimate" : 0,
                "works" : 2,
                "advanced" : 1,
                "needTime" : 0,
                "needYield" : 0,
                "saveState" : 0,
                "restoreState" : 0,
                "isEOF" : 1,
                "invalidates" : 0,
                "keyPattern" : {
                    "username" : 1
                },
                "indexName" : "username_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "username" : [ ]
                },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "username" : [
                        "["user9999", "user9999"]"
                    ]
                },
                "keysExamined" : 1,
                "seeks" : 1,
                "dupsTested" : 0,
                "dupsDropped" : 0,
                "seenInvalidated" : 0
            }
        },
        "allPlansExecution" : [ ]
    }

通过上面的返回信息中,我们可以看到

  • totalDocsExamined: 1 //扫描的文档数量为1
  • executionTimeMillis: 0 //执行的时间小于0毫秒
  • nReturned: 1 //返回的文档数为1

7、复合索引

符合索引指的是一个索引文档中存在两个值及以上的值,比如下面这样:

db.test.createIndex({username: 1, id: 1});

通过上面的语句可以创建符合索引,当我们每次查询这两个键的时候,都会从索引文档中查询,如果你经常会以两个键值对查询文档,那么符合索引非常适合你。

复合索引的第一个值可以单独查询,但是第二个值无法单独查询,什么意思呢,你可以理解成一个索引文档以第一个索引键命名,这样就很清楚了,当我们单独查询username的时候会找到这个索引文档,但是当我们单独查询id的时候无法找到这个索引文档。

那么根据上面的理论可以得出,只要第一个值匹配到索引文档,那么在复合索引中不管你跟的是复合索引中添加的哪个值都可以进行索引,比如下面的代码都可以进行复合索引:

db.test.createIndex({username: 1, id: 1, age: 1, adreess: 1});

db.test.find({username: 'user400'});

db.test.find({username: 'user400', age: 18});

db.test.find({username: 'user400', adreess: 'xxxx'});

8、对象索引

Mongodb可以支持对象索引,比如下面这样:

var a = {
    b: 1,
    c: 2
}

db.test.createIndex({a: 1});

或者对某个子键索引

db.test.createIndex({a.b: 1});

需要注意的是上面两种方法的索引效果截然不同,第一个建立索引是建立一个对象索引,对象中的所有值都会提高查询效率。而第二个建立的索引是建立一个子键索引,只对子键提高查询效率。

9、数组索引

Mongodb支持对数组索引,比如像下面这样:

var a = [
    {
        b: 1,
        c: 2
    },
    {
        b: 3,
        c: 4
    }
];

db.test.createIndex({a: 1});

或者对某个子键索引

db.test.createIndex({a.b: 1});

上面两种方式也是不同的,第一种是建立一个数组索引,并且Mongodb会对数组的每一个成员建立索引,这于对象是不一样的。而第二种是建立一个数组子成员的子键索引。

Mongodb只允许复合索引中出现一个数组,如果出现了一个以上的数组将是非法的,应当尽可能的不去使用整个数组索引。

10、获取索引

我们可以通过getIndexes()方法来获取当前集合所建立的所有索引信息,默认会返回一个_id索引,这个索引是Mongodb自动建立的。

getIndexes()使用方法:

db.test.getIndexes();

返回的大概信息:

[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "blog.test"
    },
    {
        "v" : 2,
        "key" : {
            "username" : 1
        },
        "name" : "username_1",
        "ns" : "blog.test"
    }
]

返回的信息当中,各个键值表示的是:

  • v:表示索引版本
  • key: 表示索引的键,值为表示正序倒序 1 或者 -1
  • name: 索引的标识符
  • ns: 作用于的集合

11、删除索引

Mongodb提供了dropIndex()来删除索引,它接受一个字符串参数,这个参数是索引的name值。删除索引可以根据getIndexes()方法查询到的name的值来删除,比如:

db.test.dropIndex('username_1');

12、注意事项

索引建立后,每次添加、修改、更新、删除数据,Mongodb都会更新索引文档,这也带来了一个问题,就是每当我们操作数据的时候,会比以前慢一点,因为操作数据的同时,Mongodb还会自动更新索引文档,为了不影响效率,一个集合最多只能存在64个索引。

开启MongoDB数据库密码验证

昨天我的mongodb被黑了,早先我就疑惑过怎么这东西开启服务就能用了,后来找了各方资料,直到MongoDB默认是没有鉴权的,出于侥幸心理和懒惰和忙…知道昨天我的数据库被黑了,黑客删光了我的数据,只留下了一条要我交0.2比特币来赎回我的数据的信息(如下图),我饮恨的同时,哭着给我的MongoDB加上了鉴权。

未分类

在创建用户之前,还得改变MongoDB的启动方式,在末尾加上–auth,如:mongod –dbpath ../db –auth

用户属性

要为MongoDB增加鉴权,首先需要创建用户,通过用户名密码及相应权限来告诉MongoDB我是否能动你,所以每个用户有三个属性,如下:

{
  "user":"username",
  "pwd":"password",
  "roles": ["readWrite"]
}

创建用户管理员

当然,在为你数据库创建用户之前,你需要创建用户管理员,用户管理员只能在admin表创建,roles为userAdminAnyDatabase,步骤如下:

> use admin
switched to db admin
> db.createUser({user:"admin",pwd:"123456",roles:["userAdminAnyDatabase"]})
Successfully added user: { "user" : "admin", "roles" : [ "userAdminAnyDatabase" ] }
> db.auth("admin","123456")     //立即使用此账号登录
1      //返回1即为成功

创建数据库用户

使用管理员登入,切换到你的数据库,如果还不存在,MongoDB为自动为你创建:

> use imageDB       //切换至imageDB库,不存在则创建imageDB库
switched to db imageDB
> db.createUser({user:"user",pwd:"123456",roles:["readWrite"]})      //为imageDB创建用户,并赋予读写权限
Successfully added user: { "user" : "user", "roles" : [ "readWrite" ] }
> db.auth("user","123456")     //使用刚创建的用户登录
1   //返回1即为成功

连接数据库(mongoose为例)

带鉴权的连接,文档 http://mongoosejs.com/docs/connections.html

mongoose.connect('mongodb://username:password@host:port/database?options...');
var mongoose = require('mongoose'),
    DB_URL = 'mongodb://localhost:27017/image-uploader',

    DB_URL_AUTH = 'mongodb://YOURUSERNAME:YOURPASSWORD@localhost:27017/image-uploader';//鉴权链接

mongoose.connect(DB_URL_AUTH);

/**
  * 连接成功
  */
mongoose.connection.on('connected', function () {    
    console.log('Mongoose connection open to ' + DB_URL);  
});    

/**
 * 连接异常
 */
mongoose.connection.on('error',function (err) {    
    console.log('Mongoose connection error: ' + err);  
});    

/**
 * 连接断开
 */
mongoose.connection.on('disconnected', function () {    
    console.log('Mongoose connection disconnected');  
});    

module.exports = mongoose;

至此,再也不怕数据库被黑了。

CentOS使用yum安装mariadb

创建文件:

/etc/yum.repos.d/MariaDB.repo

编辑文件:

# MariaDB 10.1 CentOS repository list - created 2016-08-04 02:04 UTC
# http://downloads.mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.1/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

执行命令:

sudo yum install MariaDB-server MariaDB-client

参考内容:

https://mariadb.com/kb/zh-cn/installing-mariadb-with-yum/

CentOS 7.0安装配置LAMP服务器(Apache+PHP+MariaDB)

未分类

准备篇

CentOS 7.0系统安装配置图解教程

http://www.osyunwei.com/archives/7829.html

1、配置防火墙,开启80端口、3306端口

CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙。

  • 关闭firewall:
systemctl stop firewalld.service #停止firewall

systemctl disable firewalld.service #禁止firewall开机启动
  • 安装iptables防火墙
yum install iptables-services #安装

vi /etc/sysconfig/iptables #编辑防火墙配置文件

# Firewall configuration written by system-config-firewall

# Manual customization of this file is not recommended.

*filter

:INPUT ACCEPT [0:0]

:FORWARD ACCEPT [0:0]

:OUTPUT ACCEPT [0:0]

-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

-A INPUT -p icmp -j ACCEPT

-A INPUT -i lo -j ACCEPT

-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT

-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT

-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT

-A INPUT -j REJECT --reject-with icmp-host-prohibited

-A FORWARD -j REJECT --reject-with icmp-host-prohibited

COMMIT

:wq! #保存退出

systemctl restart iptables.service #最后重启防火墙使配置生效

systemctl enable iptables.service #设置防火墙开机启动

2、关闭SELINUX

vi /etc/selinux/config

#SELINUX=enforcing #注释掉

#SELINUXTYPE=targeted #注释掉

SELINUX=disabled #增加

:wq! #保存退出

setenforce 0 #使配置立即生效

安装篇

1、安装Apache

未分类

yum install httpd #根据提示,输入Y安装即可成功安装

systemctl start httpd.service #启动apache

systemctl stop httpd.service #停止apache

systemctl restart httpd.service #重启apache

systemctl enable httpd.service #设置apache开机启动

在客户端浏览器中打开服务器IP地址,会出现下面的界面,说明apache安装成功

未分类

2、安装MariaDB

CentOS 7.0中,已经使用MariaDB替代了MySQL数据库

  • 安装MariaDB

未分类

yum install mariadb mariadb-server #询问是否要安装,输入Y即可自动安装,直到安装完成

systemctl start mariadb.service #启动MariaDB

systemctl stop mariadb.service #停止MariaDB

systemctl restart mariadb.service #重启MariaDB

systemctl enable mariadb.service #设置开机启动

cp /usr/share/mysql/my-huge.cnf /etc/my.cnf #拷贝配置文件(注意:如果/etc目录下面默认有一个my.cnf,直接覆盖即可)
  • 为root账户设置密码

未分类

mysql_secure_installation

回车,根据提示输入Y

输入2次密码,回车

根据提示一路输入Y

最后出现:Thanks for using MySQL!

MariaDB密码设置完成,重新启动 MariaDB:

systemctl restart mariadb.service #重启MariaDB

3、安装PHP

  • 安装PHP

未分类

yum install php #根据提示输入Y直到安装完成
  • 安装PHP组件,使PHP支持 MariaDB
yum install php-mysql php-gd libjpeg* php-ldap php-odbc php-pear php-xml php-xmlrpc php-mbstring php-bcmath php-mhash

#这里选择以上安装包进行安装,根据提示输入Y回车

systemctl restart mariadb.service #重启MariaDB

systemctl restart httpd.service #重启apache

配置篇

1、Apache配置

vi /etc/httpd/conf/httpd.conf #编辑文件

ServerSignature On  #添加,在错误页中显示Apache的版本,Off为不显示

Options Indexes FollowSymLinks  #修改为:Options Includes ExecCGI FollowSymLinks(允许服务器执行CGI及SSI,禁止列出目录)

#AddHandler cgi-script .cgi #修改为:AddHandler cgi-script .cgi .pl (允许扩展名为.pl的CGI脚本运行)

AllowOverride None  #修改为:AllowOverride All (允许.htaccess)

AddDefaultCharset UTF-8 #修改为:AddDefaultCharset GB2312 (添加GB2312为默认编码)

#Options Indexes FollowSymLinks   #修改为 Options FollowSymLinks(不在浏览器上显示树状目录结构)

DirectoryIndex index.html   #修改为:DirectoryIndex index.html index.htm Default.html Default.htm index.php(设置默认首页文件,增加index.php)

MaxKeepAliveRequests 500  #添加MaxKeepAliveRequests 500 (增加同时连接数)

:wq! #保存退出

systemctl restart httpd.service #重启apache

rm -f /etc/httpd/conf.d/welcome.conf /var/www/error/noindex.html #删除默认测试页

2、php配置

vi /etc/php.ini #编辑

date.timezone = PRC #把前面的分号去掉,改为date.timezone = PRC

disable_functions = passthru,exec,system,chroot,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,escapeshellcmd,dll,popen,disk_free_space,checkdnsrr,checkdnsrr,getservbyname,getservbyport,disk_total_space,posix_ctermid,posix_get_last_error,posix_getcwd, posix_getegid,posix_geteuid,posix_getgid, posix_getgrgid,posix_getgrnam,posix_getgroups,posix_getlogin,posix_getpgid,posix_getpgrp,posix_getpid, posix_getppid,posix_getpwnam,posix_getpwuid, posix_getrlimit, posix_getsid,posix_getuid,posix_isatty, posix_kill,posix_mkfifo,posix_setegid,posix_seteuid,posix_setgid, posix_setpgid,posix_setsid,posix_setuid,posix_strerror,posix_times,posix_ttyname,posix_uname

#列出PHP可以禁用的函数,如果某些程序需要用到这个函数,可以删除,取消禁用。

expose_php = Off #禁止显示php版本的信息

short_open_tag = ON #支持php短标签

open_basedir = .:/tmp/  #设置表示允许访问当前目录(即PHP脚本文件所在之目录)和/tmp/目录,可以防止php木马跨站,如果改了之后安装程序有问题(例如:织梦内容管理系统),可以注销此行,或者直接写上程序的目录/data/www.osyunwei.com/:/tmp/

:wq! #保存退出

systemctl restart mariadb.service #重启MariaDB

systemctl restart httpd.service #重启apache

测试篇

cd /var/www/html

vi index.php #输入下面内容

<?php

phpinfo();

?>

:wq! #保存退出

在客户端浏览器输入服务器IP地址,可以看到如下图所示相关的配置信息!

未分类

注意:apache默认的程序目录是/var/www/html

权限设置:chown apache.apache -R /var/www/html

至此,CentOS 7.0安装配置LAMP服务器(Apache+PHP+MariaDB)教程完成!

Fedora Server上安装配置MariaDb galera集群

下载与安装 MariaDB Galera Cluster

10.1之前的版本安装,输入以下命令进行安装:

sudo dnf install mariadb-galera-server

  
如果电脑上还没安装Mariadb Server,会顺便安装上,如果已经安装了,则会更新相关的包。

10.1之后的版本,Mariadb Server把群集功能打包在一起了,所以直接安装数据库,不用单独安装:

sudo dnf install mariadb-server

配置

基本步骤,使用–wsrep-new-cluster创建一个集群(只在第一个节点上使用),用–wsrep_cluster_address=gcomm://192.168.0.1加入节点。

由于文本使用的是Fedora,服务管理是systemd,所以需要修改mariadb.service,将参数加进去。

修改后的启动文件:

[Unit]
Description=MariaDB 10.1 database server
After=network.target

[Service]
Type=notify
User=mysql
Group=mysql

ExecStartPre=/usr/libexec/mysql-check-socket
# '%n' expands to 'Full unit name'; man systemd.unit
ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n
# MYSQLD_OPTS here is for users to set in /etc/systemd/system/[email protected]/MY_SPECIAL.conf
# Note: we set --basedir to prevent probes that might trigger SELinux alarms,
# per bug #547485
ExecStart=/usr/libexec/mysqld --basedir=/usr $MYSQLD_OPTS $_WSREP_NEW_CLUSTER $_WSREP_CLUSTER_ADDRESS
ExecStartPost=/usr/libexec/mysql-check-upgrade
ExecStopPost=/usr/libexec/mysql-wait-stop

# Setting this to true can break replication and the Type=notify settings
# See also bind-address mysqld option.
PrivateNetwork=false

KillMode=process
KillSignal=SIGTERM

# Don't want to see an automated SIGKILL ever
SendSIGKILL=no

# Restart crashed server only, on-failure would also restart, for example, when
# my.cnf contains unknown option
Restart=on-abort
RestartSec=5s

UMask=007

# Give a reasonable amount of time for the server to start up/shut down
TimeoutSec=300

# Place temp files in a secure directory, not /tmp
PrivateTmp=true

[Install]
WantedBy=multi-user.target

  
第一个节点的环境设置:

systemctl show-environment
LANG=en_US.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
_WSREP_CLUSTER_ADDRESS=--wsrep_cluster_address=gcomm://192.168.30.184
_WSREP_NEW_CLUSTER=--wsrep-new-cluster

第二个节点的环境配置:

systemctl show-environment
LANG=en_US.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
_WSREP_CLUSTER_ADDRESS=--wsrep_cluster_address=gcomm://192.168.30.184

  

测试

为测试方便,先将防火墙关闭,还有selinux。先启动第一个节点,再启动第二个节点。在其中一个节点上创建一个数据库,切换到另一个节点上就可以看到这个数据库。

通过数据命令可以查看集群的状态:

SHOW STATUS LIKE 'wsrep_%';
+------------------------------+-----------------------------------------+
| Variable_name                | Value                                   |
+------------------------------+-----------------------------------------+
| wsrep_apply_oooe             | 0.000000                                |
| wsrep_apply_oool             | 0.000000                                |
| wsrep_apply_window           | 1.000000                                |
| wsrep_causal_reads           | 0                                       |
| wsrep_cert_deps_distance     | 1.000000                                |
| wsrep_cert_index_size        | 2                                       |
| wsrep_cert_interval          | 0.000000                                |
| wsrep_cluster_conf_id        | 189                                     |
| wsrep_cluster_size           | 2                                       |
| wsrep_cluster_state_uuid     | e5b100e2-7f7e-11e7-9266-de309a905227    |
| wsrep_cluster_status         | Primary                                 |
| wsrep_commit_oooe            | 0.000000                                |
| wsrep_commit_oool            | 0.000000                                |
| wsrep_commit_window          | 1.000000                                |
| wsrep_connected              | ON                                      |
| wsrep_desync_count           | 0                                       |
| wsrep_evs_delayed            |                                         |
| wsrep_evs_evict_list         |                                         |
| wsrep_evs_repl_latency       | 0/0/0/0/0                               |
| wsrep_evs_state              | OPERATIONAL                             |
| wsrep_flow_control_paused    | 0.000000                                |
| wsrep_flow_control_paused_ns | 0                                       |
| wsrep_flow_control_recv      | 0                                       |
| wsrep_flow_control_sent      | 0                                       |
| wsrep_gcomm_uuid             | f023c418-7f81-11e7-bfbb-86741b99a879    |
| wsrep_incoming_addresses     | 192.168.30.184:3306,192.168.30.186:3306 |
| wsrep_last_committed         | 3                                       |
| wsrep_local_bf_aborts        | 0                                       |
| wsrep_local_cached_downto    | 1                                       |
| wsrep_local_cert_failures    | 0                                       |
| wsrep_local_commits          | 0                                       |
| wsrep_local_index            | 1                                       |
| wsrep_local_recv_queue       | 0                                       |
| wsrep_local_recv_queue_avg   | 0.125000                                |
| wsrep_local_recv_queue_max   | 2                                       |
| wsrep_local_recv_queue_min   | 0                                       |
| wsrep_local_replays          | 0                                       |
| wsrep_local_send_queue       | 0                                       |
| wsrep_local_send_queue_avg   | 0.000000                                |
| wsrep_local_send_queue_max   | 1                                       |
| wsrep_local_send_queue_min   | 0                                       |
| wsrep_local_state            | 4                                       |
| wsrep_local_state_comment    | Synced                                  |
| wsrep_local_state_uuid       | e5b100e2-7f7e-11e7-9266-de309a905227    |
| wsrep_protocol_version       | 7                                       |
| wsrep_provider_name          | Galera                                  |
| wsrep_provider_vendor        | Codership Oy <[email protected]>       |
| wsrep_provider_version       | 3.16(r5c765eb)                          |
| wsrep_ready                  | ON                                      |
| wsrep_received               | 8                                       |
| wsrep_received_bytes         | 2148                                    |
| wsrep_repl_data_bytes        | 0                                       |
| wsrep_repl_keys              | 0                                       |
| wsrep_repl_keys_bytes        | 0                                       |
| wsrep_repl_other_bytes       | 0                                       |
| wsrep_replicated             | 0                                       |
| wsrep_replicated_bytes       | 0                                       |
| wsrep_thread_count           | 2                                       |
+------------------------------+-----------------------------------------+

使用lvs-rrd工具监控存储lvs状态信息

1. 准备工作

rrdtool安装包下载:http://oss.oetiker.ch/rrdtool/pub/rrdtool-1.4.7.tar.gz

lvs-rrd安装包下载:http://tepedino.org/lvs-rrd/lvs-rrd-v0.7.tar.gz

yum install php
yum install httpd
service httpd start

chkconfig httpd on

2. 部署rrdtool

yum groupinstall -y "Development Tools" "Server Platform Development" "Base" "Fonts"
yum -y install  libpng-devel freetype-devel libart_lgpl-devel libxml2-devel gd gd-devel cairo-devel pango-devel perl-ExtUtils-*
tar xvzf rrdtool-1.4.7.tar.gz
cd rrdtool-1.4.7
./configure –prefix=/usr/local/rrdtool
make
make install

安装完毕后将rrdtool库文件添加至系统库文件搜索路径

echo ‘/usr/local/rrdtool/lib’ >>/etc/ld.so.conf
ldconfig –v
/usr/local/rrdtool/bin/rrdtool –v      #查看rrdtool版本信息

3. 部署lvs-rrd

tar xvzf lvs-rrd-v0.7.tar.gz
mv lvs-rrd-v0.7 /var/www/html/lvs-rrd
cd /var/www/html/lvs-rrd
mkdir -p rrd

修改配置文件

vim lvs.rrd.update
RRDTOOL=”/usr/local/rrdtool/bin/rrdtool”#rrdtool可执行程序路径
IPVSADM=”/sbin/ipvsadm”#ipvsadm命令路径
WORKDIR=”/var/www/html/lvs-rrd”#rrdtool收集的数据的存放路径
vim graph-lvs.sh
# WORKDIR must match the directory used in the update script.
WORKDIR=”/var/www/html/lvs-rrd/rrd” #rrdtool收集的数据存放的路径
RRDTOOL=”/usr/local/rrdtool/bin/rrdtool”#rrdtool可执行程序路径
# Where to put the graphs.
GRAPHS=“/var/www/html/lvs-rrd/graphs”#生成的图片保存路径
WEBPATH=”/lvs-rrd/graphs”#图片路径
vim lvs-rrd.php
<?php
header(“Cache-Control: max-age=300, must-revalidate”);
system(“/var/www/html/lvs-rrd/graph-lvs.sh -H”);#修改路径为部署服务的路径
?>
chown –R apache.apache /var/www/html/lvs-rrd
执行一次lvs.rrd.update生成初始rrd数据

配置计划任务

crontab –e

增加以下配置:

#lvs status
* * * * * /var/www/html/lvs-rrd/lvs.rrd.update 2>/dev/null >/dev/null

4. 配置httpd认证

在/etc/httpd/conf/httpd.conf中添加如下内容:

<Directory "/var/www/html/lvs">
Options All
AllowOverride AuthConfig
AuthType Basic
AuthBasicProvider file
AuthName "LVS Status"
AuthUserFile /etc/httpd/conf/.htpasswd
Require valid-user
</Directory>
htpasswd -cm /etc/httpd/conf/.htpasswd admin

5. 测试

服务配置完毕后,过几分钟之后,访问http://10.0.0.10/lvs-rrd(LVS与lvs-rrd所在服务器),输入帐号admin及其密码通过认证之后,即可看到当前lvs的连接状态等信息。

centos7.1 LVS健康检查bash脚本

环境:centos7.1

简介:

当脚本检测到某个RS的http服务掉线时,在LVS中自动移除RS;当所有RS的http服务掉线时,在LVS中移除所有RS,并将LVS调度器上的http服务加入到LVS中,作为告警页面。

当脚本检测到某个RS的http服务活跃时,自动将其加入到LVS中,如果有调度器本身的存在LVS中,将其删除。

该脚本经本人测试,完全达到要求。

以下为脚本:

#!/bin/bash
#
rs=("152.168.1.12" "152.168.1.13")
vip="152.168.1.10"
port=80
logfile="/usr/local/scripts/lvs.log"
function check_alldown {
   #有一个rs主机能访问,就说明不是全部掉了
   #检查到一个rs主机存活就退出检查
   #如果全部rs不能访问,说明主机全掉了
   for www in `echo ${rs[*]}`
   do
      curl --connect-timeout 1 http://$www &> /dev/null
      if [ $? -eq 0 ]
      then
          echo 0 
          exit 0 
      fi      
   done
   echo 100 
}
function lvs_add {
   ipvsadm -a -t $vip:$port -r $1
   echo "add rs host:$1 to lvs"
}
function lvs_rm {
   ipvsadm -d -t $vip:$port -r $1
   echo "remove rs host:$1 to lvs"
}
function lvs_local {
   #如果全部rs主机掉线,并且lvs中没有127.0.0.1就添加它
   #如果可以访问一个rs主机,并且lvs中有127.0.0.1就删除它
   all_down=`check_alldown`
   rip=$(ipvsadm -L -n | gawk '/127.0.0.1/')
   if [ $all_down -eq 100 ]
   then
       if [ "$rip" = "" ] 
       then   
           echo "`date +%F:%H-%M-%S` all rs host is down!" >> $logfile
           lvs_add "127.0.0.1"
       fi
   else
       if [ $all_down -eq 0 ] && [ ! "$rip" = "" ]  
       then
           echo "`date +%F:%H-%M-%S` one rs host is up,remove local rs host!" >> $logfile
           lvs_rm "127.0.0.1"
       fi
   fi
}
function lvs_rs {
   #如果可以访问一个rs主机,并且lvs中没有它就添加它
   #如果不能访问一个rs主机,并且lvs中有它就删除它
   lvs_local
   for www in `echo ${rs[*]}`
   do
      rip=$(ipvsadm -L -n | gawk "/$www/")
      curl --connect-timeout 1 http://$www &> /dev/null
      if [ $? -eq 0 ]
      then
          if [ "$rip" = "" ]
          then
              echo "`date +%F:%H-%M-%S` rs host:$www is up!" >> $logfile
              lvs_add "$www"
          fi
      else
          if [ ! "$rip" = "" ]
          then
              echo "`date +%F:%H-%M-%S` rs host:$www is down!" >> $logfile
              lvs_rm "$www"
          fi
      fi
   done
}
function lvs_monitor {
   while true
   do
     echo "check lvs rs health!"
     lvs_rs
     sleep 1
   done
}
lvs_monitor

完毕。