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

完毕。

Lvs自动部署及监控shell脚本

先申明,本文现在已经在我公司的测试环境和生产测试环境使用。正式环境请用keepalived+lvs.

安装ipvsadm不多说了,先说说脚本的功能,脚本分为redirect server 端和realserver 端,脚本分别为 lvs_redirector.sh 和realserver.sh脚本。另外加一个监控脚本lvs_monitor.sh(此脚本来源网友,做了一点修改,算是 取之于网络,共享给网络吧)。

脚本功能

执行 lvs_redirector.sh nat|dr|tun|stop,中的一个选项可以启动或关闭相应的lvs模式,并调用lvs_monitor.sh 监控realserver。当realserver故障,或者重新启动时,自动删除,添加相应的realserver.当realserver 全故障时,自动添加redirector server 本地127.0.0.1的web页面的故障提示。当realserver只要有一台恢复时,自动添加相应的realserver,并删除127.0.0.1。

实验环境图

Lvs具体原理可以看我的博客:Lvs通俗易懂的总结(http://qiaomiao.blog.51cto.com/484197/1729587) 。

本文脚本的使用如下图的场景:

未分类

未分类

未分类

具体脚本

lvs_redirector.sh 脚本如下:

#!/bin/bash
# blog:http://qiaomiao.blog.51cto.com
#
# 20151223 v2.8 20160115
# home:http://www.qmlab.cn 
#
# description:this script is use start lvs (nat/dr/tun),and stop lvs.


#define the variable
  NAT_VIP="172.16.8.11"
  DR_VIP="10.0.8.20"
  TUN_VIP="10.0.8.20"
  DIP="10.0.8.11"
  RIP1="10.0.8.21"
  RIP2="10.0.8.22"
  RNET="10.0.8.0/24"
  DPORT=80
  RPORT1=80
  RPORT2=80
  GATEWAY01=172.16.8.254         #做nat模式时,redirector 服务器的外网卡的路由器网关地址。
  GATEWAY02=10.0.8.254           #路由器网关地址,做dr 和 tun 模式时用到。
                                 #两个GATEWAY,请根据具体网络环境做取舍。
  logfile=/tmp/myself_log/lvs.log

#create log dir
mkdir -p /tmp/myself_log            # 在这个目录可以看到启动lvs日志。
#general funiction
lvs_monitor(){
     if [ $1 = stop  ];then
        PS=`ps -ef |grep lvs_monitor.sh |awk '{printf $2" "}'`
        for X in `echo $PS`
             do
                kill -9 $X
             done
     else
        sh  ./lvs_monitor.sh &
     fi
}

# LVS/NAT mode    
# start
function nat_start {
     nat_stop
     VIP=$NAT_VIP
     echo 1 >/proc/sys/net/ipv4/ip_forward
 # start  NAT mode
     ifconfig eth1 $VIP netmask 255.255.255.0 up
     route del -net 0.0.0.0
     route add  -net 0.0.0.0 netmask 0.0.0.0 gw $GATEWAY01
     #ifconfig eth0:0 $VIP broadcast $VIP netmask 255.255.255.0 up
     ipvsadm -C
     ipvsadm -A -t $VIP:$DPORT -s wlc
     ipvsadm -a -t $VIP:$DPORT -r $RIP1:$RPORT1 -m -w 1
     ipvsadm -a -t $VIP:$DPORT -r $RIP2:$RPORT2 -m -w 2
     ipvsadm -ln
     ipvsadm -lnc
 }
# stop
function nat_stop {
     VIP=$NAT_VIP
     echo 0 >/proc/sys/net/ipv4/ip_forward
 # stop  NAT mode
     ifconfig eth1 down
     route del -net 0.0.0.0
     route add  -net 0.0.0.0 netmask 0.0.0.0 gw $GATEWAY02 > /dev/null 2>&1
     ipvsadm -C
 }


# LVS/dricert routing  mode
# start
function dr_start {
     dr_stop
     VIP=$DR_VIP
     echo 1 >/proc/sys/net/ipv4/ip_forward
     ifconfig eth0:0 $VIP broadcast $VIP netmask 255.255.255.255 up
     route add  -host $VIP dev eth0:0
 # start  DR  mode
     ipvsadm -C
     ipvsadm -A -t $VIP:$DPORT -s wlc
     ipvsadm -a -t $VIP:$DPORT -r $RIP1 -g -w 1
     ipvsadm -a -t $VIP:$DPORT -r $RIP2 -g -w 2
     ipvsadm -ln
     ipvsadm -lnc

 }
# stop
function dr_stop {
     VIP=$DR_VIP
     echo 0 >/proc/sys/net/ipv4/ip_forward
     ifconfig eth0:0 $VIP broadcast $VIP netmask 255.255.255.255 down
     route del -host $VIP dev eth0:0 > /dev/null 2>&1
 # stop  DR  mode
     ipvsadm -C

 }



# LVS/tunneling  mode
# start
function tun_start {
     tun_stop
     VIP=$TUN_VIP
 # set interface
     #echo 1 >/proc/sys/net/ipv4/ip_forward
     ifconfig tunl0 $VIP broadcast $VIP netmask 255.255.255.255 up
     route add -host $VIP dev tunl0
 # start  tun  mode
     ipvsadm -C
     ipvsadm -A -t $VIP:$DPORT -s wlc
     ipvsadm -a -t $VIP:$DPORT -r $RIP1 -i -w 1
     ipvsadm -a -t $VIP:$DPORT -r $RIP2 -i -w 2
     ipvsadm -ln
     ipvsadm -lnc

 }
# stop
function tun_stop {
     VIP=$TUN_VIP
 # set interface
     #echo 1 >/proc/sys/net/ipv4/ip_forward
     ifconfig tunl0 $VIP broadcast $VIP netmask 255.255.255.255 down
     ip addr flush tunl0
     route del  -host $VIP dev tunl0 >/dev/null 2>&1
 # stop  tun  mode
     ipvsadm -C
}


service iptables stop
case "$1" in
        nat)
        echo -e  "n  `date +%F" "%H:%M:%S` nat模式启动...." > $logfile
        tun_stop
        dr_stop
        nat_start
        lvs_monitor $1
        ;;
        dr)
        echo -e  "n  `date +%F" "%H:%M:%S` dr模式启动...." > $logfile
        tun_stop
        nat_stop
        dr_start
        lvs_monitor $1
        ;;
        tun)
        echo -e  " n  `date +%F" "%H:%M:%S ` tun模式启动...." > $logfile
        dr_stop
        nat_stop
        tun_start
        lvs_monitor $1
        ;;
        stop)
        echo -e "n `date +%F" "%H:%M:%S` 关闭lvs...." > $logfile
        dr_stop
        nat_stop
        tun_stop
        lvs_monitor $1
        ;;
        *)
        echo $"Usage: $0 {nat|dr|tun|stop}"  
        ;;
 esac

lvs_realserver.sh 脚本如下:

#!/bin/bash
# write by lijing QQ 858080796, 
# blog:http://qiaomiao.blog.51cto.com
# 20151223 v2.8 20160115
# home:http://www.qmlab.cn 
#
# description:this script is use start lvs (nat/dr/tun).

#define the variable
  #NAT_VIP="10.0.8.20"
  DR_VIP="10.0.8.20"
  TUN_VIP="10.0.8.20"
  DIP="10.0.8.11"
  RIP1="10.0.8.21"
  RIP2="10.0.8.22"
  RNET="10.0.8.0/24"
  DPORT=80
  RPORT1=80
  RPORT2=80
  GATEWAY="10.0.8.254"
#


# LVS/NAT mode
# start
function nat_start {
    nat_stop
 # set realserver gateway
    route del -net 0.0.0.0
    route add -net 0.0.0.0 netmask 0.0.0.0 gw $DIP

 }
# stop
function nat_stop {
 # set realserver gateway
    route del -net 0.0.0.0
    route add -net 0.0.0.0 netmask 0.0.0.0 gw $GATEWAY

 }


# LVS/dricert routing  mode
# start
function dr_start {
    dr_stop
    VIP=$DR_VIP
 #  set interface
    ifconfig lo:0 $VIP broadcast $VIP netmask 255.255.255.255 up
    route add -host $VIP dev lo:0
 # set realserver  
    echo 1 > /proc/sys/net/ipv4/conf/lo/arp_ignore
    echo 2 > /proc/sys/net/ipv4/conf/lo/arp_announce
    echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce

 }
# stop
function dr_stop {
    VIP=$DR_VIP
 #  set interface
    ifconfig lo:0 down
    route del -host $VIP dev lo:0 >/dev/null 2>&1
 # set realserver  
    echo 0 > /proc/sys/net/ipv4/conf/lo/arp_ignore
    echo 0 > /proc/sys/net/ipv4/conf/lo/arp_announce
    echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce

 }


# LVS/tunneling  mode
# start
function tun_start {
     tun_stop
     VIP=$TUN_VIP
 # set interface
     ifconfig tunl0 $VIP broadcast $VIP netmask 255.255.255.255 up
     route add -host $VIP dev tunl0
 # set realserver  
    echo 1 > /proc/sys/net/ipv4/conf/tunl0/arp_ignore
    echo 2 > /proc/sys/net/ipv4/conf/tunl0/arp_announce
    echo 1 > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo 2 > /proc/sys/net/ipv4/conf/all/arp_announce
    echo 0 > /proc/sys/net/ipv4/conf/tunl0/rp_filter
    echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
 }
# stop
function tun_stop {
     VIP=$TUN_VIP
 # set interface
 ip addr flush tunl0
     ifconfig tunl0 down
     route del -host $VIP dev tunl0  >/dev/null 2>&1
 # set realserver  
    echo 0 > /proc/sys/net/ipv4/conf/tunl0/arp_ignore
    echo 0 > /proc/sys/net/ipv4/conf/tunl0/arp_announce
    echo 0 > /proc/sys/net/ipv4/conf/all/arp_ignore
    echo 0 > /proc/sys/net/ipv4/conf/all/arp_announce
    echo 0 > /proc/sys/net/ipv4/conf/tunl0/rp_filter
    echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
 }

service iptables stop
case "$1" in
        nat)
        tun_stop
        dr_stop
        nat_start
        ;;
        dr)
        tun_stop
        nat_stop
        dr_start
        ;;
        tun)
        nat_stop
        dr_stop
        tun_start
        ;;
        stop)
        nat_stop
        dr_stop
        tun_stop
        ;;
        *)
        echo $"Usage: $0 {nat|dr|tun|stop}"  
        ;;
 esac

lvs_monitor.sh 脚本如下:(注意要放在lvs_redirector.sh在同一个目录下)

#!/bin/bash
# write by http://small.blog.51cto.com/259970/1728082
# 感谢 网友 super_color

rs=("10.0.8.21" "10.0.8.22")
vip="10.0.8.20"
port=80
logfile="/tmp/myself_log/lvs_monitor.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

结果验证

在验证结果之前,要保证你的路由器的端口映射是正确,且生效的,上面图中:

当外网客户端192.168.20.200访问时,nat模式路由器192.168.20.14映射到172.16.8.11这个IP,

dr和 tun模式映射到 10.0.8.20这个IP。

验证方法:先测试直接内网访问两台realserver web是不是正常,以及redirector server 的本地127.0.0.1 web 是不是正常,再测试访问192.168.20.14,当其中一台故障时是不是还可以访问,到全故障时,有没有切的本地127.0.0.1(故障提示页)的web,当其中只要有一台恢复时,会不会启动添加启用,并删除127.0.0.1的web.

未分类

未分类