用openresty实现动态upstream反向代理

前言

此文的读者定义为对openresty有一定了解的读者。

openresty:
https://github.com/openresty/lua-nginx-module

此文要讲什么

大家都知道openresty可以用ngx.location.capture和ngx.exec来实现内部跳转,
下面要讲怎么将ngx.location.capture和ngx.exec与upstream模块结合起来,实现一个动态的upstream。

下面的演示中:

80端口表示首次请求入口
8080端口表示upstream的出口

直接上配置和源码

配置: conf/nginx.conf

    worker_processes  1;  
    error_log logs/error.log;  
    events {  
        worker_connections 1024;  
    }  

    http {  

        log_format  main  '$msec $status $request $request_time '  
                          '$http_referer $remote_addr [ $time_local ] '  
                          '$upstream_response_time $host $bytes_sent '  
                          '$request_length $upstream_addr';  

        access_log  logs/access.log main buffer=32k flush=1s;  


        upstream remote_hello {  
            server 127.0.0.1:8080;  
        }  

        upstream remote_world {  
            server 127.0.0.1:8080;  
        }  

        server {  
            listen 80;  

            location /capture {  
                content_by_lua '  
                    local test = require "lua.test"  
                    test.capture_test()  
                ';  
            }  

            location /exec {  
                content_by_lua '  
                    local test = require "lua.test"  
                    test.exec_test()  
                ';  
            }  

            location /upstream {  
                internal;  

                set $my_upstream $my_upstream;  
                set $my_uri $my_uri;  
                proxy_pass http://$my_upstream$my_uri;  
            }  
        }  


        server {  
            listen 8080;  
            location /hello {  
                echo "hello";  
            }  

            location /world {  
                echo "world";  
            }  
        }  
    }  

源码: lua/test.lua

    local _M = { _VERSION = '1.0' }  

    function _M:capture_test()  
        local res = ngx.location.capture("/upstream",  
            {  
                 method = ngx.HTTP_GET,  
                 vars = {  
                     my_upstream = "remote_hello",  
                     my_uri = "/hello",  
                 },  
            }  
        )  
        if res == nil or res.status ~= ngx.HTTP_OK then  
            ngx.say("capture failed")  
            return  
        end  
        ngx.print(res.body)  
    end  

    function _M:exec_test()  
        ngx.var.my_upstream = "remote_world"  
        ngx.var.my_uri = "/world"  
        ngx.exec("/upstream")  
    end  

    return _M  

运行效果

未分类

OpenResty json 删除转义符

OpenResty 中删除 json 中的转义符

cjson 在 encode 时 “/” 会自动添加转义符 “”; 在 decode 时也会自动将转义符去掉。工作中有个特殊需求,需要手工删除转义符。记录备忘,代码如下:

#! /usr/bin/env lua
json = require "cjson"

result = {}
result["stream"] = "lufei"
result["app"] = "live/cartoon"
oldStr = json.encode(result)
local from, to, err = ngx.re.find(oldStr, [[\]])
ngx.say(from, to)
newStr, n, err = ngx.re.gsub(oldStr, [[\/]], [[/]])
ngx.say("oldStr: "..oldStr)
ngx.say("newStr: "..newStr )
t = json.decode(newStr)
ngx.say(t["app"])
dill@bunbun:~/openresty-test/locations$ curl -i localhost:6699/test
HTTP/1.1 200 OK
Server: openresty/1.11.2.2
Date: Wed, 19 Jul 2017 04:38:07 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive

1313
oldStr: {"app":"live/cartoon","stream":"lufei"}
newStr: {"app":"live/cartoon","stream":"lufei"}
live/cartoon

openresty设置用于access_log的自定义变量

期望:在access_log打印自定义变量define_error_code

nginx配置:

    worker_processes  1;  

    events {  
        worker_connections  1024;  
    }  


    http {  

        log_format main '[$time_local] $request $status $remote_addr $define_error_code';  

        server {  
            listen       80;  

            location / {  
                set $define_error_code '';  
                content_by_lua_block {  
                    ngx.var.define_error_code = 9527  
                    ngx.say("hello world")  
                }  
            }  

            access_log logs/access.log main;  
            error_log logs/error.log;  
        }  

    }  

测试结果:

未分类

Linux NFS共享文件系统安装配置

一、NFS服务简介

NFS 是Network File System的缩写,即网络文件系统。一种使用于分散式文件系统的协定,由Sun公司开发,于1984年向外公布。功能是通过网络让不同的机器、不同的操作系统能够彼此分享个别的数据,让应用程序在客户端通过网络访问位于服务器磁盘中的数据,是在类Unix系统间实现磁盘文件共享的一种方法。

NFS 的基本原则是“容许不同的客户端及服务端通过一组RPC分享相同的文件系统”,它是独立于操作系统,容许不同硬件及操作系统的系统共同进行文件的分享。

NFS在文件传送或信息传送过程中依赖于RPC协议。RPC,远程过程调用 (Remote Procedure Call) 是能使客户端执行其他系统中程序的一种机制。NFS本身是没有提供信息传输的协议和功能的,但NFS却能让我们通过网络进行资料的分享,这是因为NFS使用了一些其它的传输协议。而这些传输协议用到这个RPC功能的。可以说NFS本身就是使用RPC的一个程序。或者说NFS也是一个RPC SERVER。所以只要用到NFS的地方都要启动RPC服务,不论是NFS SERVER或者NFS CLIENT。这样SERVER和CLIENT才能通过RPC来实现PROGRAM PORT的对应。可以这么理解RPC和NFS的关系:NFS是一个文件系统,而RPC是负责负责信息的传输。

二、系统环境

系统平台:CentOS release 6.6
NFS Server IP:192.168.11.100

NFS Client IP:192.168.11.110
防火墙已关闭/iptables
SELINUX=disabled

三、安装NFS服务

NFS的安装是非常简单的,只需要两个软件包即可,而且在通常情况下,是作为系统的默认包安装的。
nfs-utils-* :包括基本的NFS命令与监控程序
rpcbind-* :支持安全NFS RPC服务的连接

1、查看系统是否已安装NFS

未分类

2、如果没有安装NFS软件包,则可以手动安装

# yum -y install nfs-utils rpcbind

3、安装完成后启动服务。

 注:启动时要先启动rpcbind在启动nfs

未分类

4、停止服务时也要先停止nfs在停止rpcbind

四、NFS配置文件

NFS的常用目录

  • /etc/exports NFS服务的主要配置文件

  • /usr/sbin/exportfs NFS服务的管理命令

  • /usr/sbin/showmount 客户端的查看命令

  • /var/lib/nfs/etab 记录NFS分享出来的目录的完整权限设定值

  • /var/lib/nfs/xtab 记录曾经登录过的客户端信息

4.1、NFS服务的配置文件为 /etc/exports,这个文件是NFS的主要配置文件,不过系统并没有默认值,所以这个文件不一定会存在,可能要使用vim手动建立,然后在文件里面写入配置内容。
/etc/exports文件内容格式:

[客户端1 选项(访问权限,用户映射,其他)] [客户端2 选项(访问权限,用户映射,其他)]

例:

vim /etc/exports

/home/data 192.168.11.0/24(rw,sync,no_root_squash)

4.2、常用配置项说明

访问权限选项
设置输出目录只读:ro
设置输出目录读写:rw

用户映射选项

  • all_squash:将远程访问的所有普通用户及所属组都映射为匿名用户或用户组(nfsnobody);

  • no_all_squash:与all_squash取反(默认设置);

  • root_squash:将root用户及所属组都映射为匿名用户或用户组(默认设置);

  • no_root_squash:与rootsquash取反;

  • anonuid=xxx:将远程访问的所有用户都映射为匿名用户,并指定该用户为本地用户(UID=xxx);

  • anongid=xxx:将远程访问的所有用户组都映射为匿名用户组账户,并指定该匿名用户组账户为本地用户组账户(GID=xxx);

其它选项

  • secure:限制客户端只能从小于1024的tcp/ip端口连接nfs服务器(默认设置);

  • insecure:允许客户端从大于1024的tcp/ip端口连接服务器;

  • sync:将数据同步写入内存缓冲区与磁盘中,效率低,但可以保证数据的一致性;

  • async:将数据先保存在内存缓冲区中,必要时才写入磁盘;

  • wdelay:检查是否有相关的写操作,如果有则将这些写操作一起执行,这样可以提高效率(默认设置);

  • no_wdelay:若有写操作则立即执行,应与sync配合使用;

  • subtree:若输出目录是一个子目录,则nfs服务器将检查其父目录的权限(默认设置);

  • no_subtree:即使输出目录是一个子目录,nfs服务器也不检查其父目录的权限,这样可以提高效率;

五、重启服务

 修改过配置文件后需要重启相关服务
#service rpcbind resatrt
#service nfs restart

六、客户端配置

6.1、客户端可通过showmount -e IP来查看NFS共享状态

未分类

6.2、客户端通过mount挂载

未分类

6.3、客户端取消挂载

umount /home/data
注:如遇到umount.nfs: /home/data: device is busy可使用-l参数来取消挂载
umount -l /home/data

6.4、挂载完成后,客户端和服务端做的修改都会及时同步

七、配置自动挂载nfs文件系统

 vim /etc/fstab

未分类

保存重启

八、有关权限的介绍

  1. 客户端连接时候,对普通用户的检查

    a. 如果明确设定了普通用户被压缩的身份,那么此时客户端用户的身份转换为指定用户;

    b. 如果NFS server上面有同名用户,那么此时客户端登录账户的身份转换为NFS server上面的同名用户;

    c. 如果没有明确指定,也没有同名用户,那么此时 用户身份被压缩成nfsnobody;

  2. 客户端连接的时候,对root的检查

    a. 如果设置no_root_squash,那么此时root用户的身份被压缩为NFS server上面的root;

    b. 如果设置了all_squash、anonuid、anongid,此时root 身份被压缩为指定用户;

    c. 如果没有明确指定,此时root用户被压缩为nfsnobody;

    d. 如果同时指定no_root_squash与all_squash 用户将被压缩为 nfsnobody,如果设置了anonuid、anongid将被压缩到所指定的用户与组;

九、相关命令

1、exportfs
如果我们在启动了NFS之后又修改了/etc/exports,是不是还要重新启动nfs呢?这个时候我们就可以用exportfs 命令来使改动立刻生效,该命令格式如下:

  • exportfs [-aruv]

  • -a 全部挂载或卸载 /etc/exports中的内容

  • -r 重新读取/etc/exports 中的信息 ,并同步更新/etc/exports、/var/lib/nfs/xtab

  • -u 卸载单一目录(和-a一起使用为卸载所有/etc/exports文件中的目录)

  • -v 在export的时候,将详细的信息输出到屏幕上。

具体例子:

  • exportfs -au 卸载所有共享目录

  • exportfs -rv 重新共享所有目录并输出详细信息

2、nfsstat
查看NFS的运行状态,对于调整NFS的运行有很大帮助。

3、rpcinfo
查看rpc执行信息,可以用于检测rpc运行情况的工具,利用rpcinfo -p 可以查看出RPC开启的端口所提供的程序有哪些。

4、showmount

  • -a 显示已经于客户端连接上的目录信息

  • -e IP或者hostname 显示此IP地址分享出来的目录

5、netstat

  • 可以查看出nfs服务开启的端口,其中nfs 开启的是2049,portmap 开启的是111,其余则是rpc开启的。

  • 最后注意两点,虽然通过权限设置可以让普通用户访问,但是挂载的时候默认情况下只有root可以去挂载,普通用户可以执行sudo。

  • NFS server 关机的时候一点要确保NFS服务关闭,没有客户端处于连接状态!通过showmount -a 可以查看,如果有的话用kill killall pkill 来结束,(-9 强制结束)

MongoDB shell常用操作

shell命令操作语法和JavaScript很类似,其实控制台底层的查询语句都是用javascript脚本完成操作的。使用shell 命令,需要启动mongo.exe。

常用shell命令如下:

1、查询本地所有数据库名称

> show dbs;

2、切换至指定数据库环境(若无指定的数据库,则创建新的库)

> use mydb;

3、查询当前库下的所有聚集集合collection(相当于table)

> show collections;

4、创建聚集集合

> db.createCollection('mycollection');

5、查询聚集集合中数据条数

> db.mycollection.count();

6、插入数据

> db.mycollection.insert({'username':'xyz_lmn','age':26,'salary':120});

往’mycollection’聚集集合中插上一条数库,name为’xyz_lmn’,age为’26’,salary为’120′

7、查询age等于26的数据

> db.mycollection.find({"age":26});

8、查询salary大于100的数据

> db.mycollection.find({salary:{$gt:100}});

9、查询age小于30,salary大于100的数据

> db.mycollection.find({age:{$lt:30}},{salary:{$gt:100}});

10、查询salary小于40或salary大于200的数据

> db.mycollection.find({$or: [{salary: {$lt:40}}, {salary: {$gt:200}}]});

11、查询指定列的数据

> db.mycollection.find({},{age:1,salary:1});

1表示显示此列的意思,也可以用true表示

12、查询username中包含’e’的数据

> db.mycollection.find({username:/e/});

13、查询以a打头的数据

> db.mycollection.find({username:/^a/});

14、查询age列数据,并去掉重复数据

> db.mycollection.distinct('age');

15、查询前10条数据

> db.mycollection.find().limit(10);

16、查询1条以后的所有数据

> db.mycollection.find().skip(1);

17、查询第一条数据

> db.mycollection.findOne();

18、查询结果集的记录数(查询salary小于40或大于100的记录数)

db.mycollection.find({$or: [{salary: {$lt:40}}, {salary: {$gt:100}}]}).count();

19、按salary升序排序

> db.mycollection.find().sort({salary:1});

按照salary字段升序排序

20、降序

> db.mycollection.find().sort({salary:-1});

按照salary字段降序排序

21、根据username修改age

> db.employee.update({username:'jim'},{$set:{age:22}},false,true);
db.collection.update( criteria, objNew, upsert, multi )
  • criteria : update的查询条件,类似sql update查询内where后面的

  • objNew : update的对象和一些更新的操作符(如$,$inc…)等,也可以理解为sql update查询内set后面的

  • upsert : 如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入。

  • multi : MongoDB默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新。

22、将指定username的age字段增加5

> db.mycollection.update({username:'jim'},{$inc:{age:5}},false,true);

将username为‘jim’的age字段加5

23、删除username为’rose’的数据

> db.mycollection.remove({uname:'rose'});

24、集合collection重命名

> db.mycollection.renameCollection('c_temp');

将mycollection集合重命名为’c_temp’

25、删除集合

> db.c_temp.drop();

删除名为’c_temp’的集合

26、删除当前数据库

> db.dropDatabase();

未分类

MongoDB如何添加用户

  1. 不需要验证启动mongodb
  2. 添加用户

创建用户

db.createUser({
    user: 'testname',  // 用户名
    pwd: 'testpwd', // 密码
    roles: [
        {
            role: 'readWrite', // 读写权限
            db: 'test'  // 所属数据库
        }
    ]
})

需要验证启动MongoDB

mongod --dbpath mongoData --auth
show dbs // 报错,因为没有验证
use test // 切换到test数据库
db.auth('testname', 'testpwd') // 1
show collections // 可以查看表
use local // 切换到local数据
show collections // 报错
db.auth('testname', 'testpwd') // 报错,因为testname是test数据的用户

需要了解的概念:

  1. mongodb可以同时存在多个数据库。

  2. mongodb的用户是属于某个数据库的。即,你创建的某个用户只能在一数据库中使用。

  3. mongodb启动有两种模式,需要验证和不需要验证。默认不需要验证,当你使用验证模式启动后,对数据的所有操作必须通过验证并且具有相应权限才可以操作。

修改密码

use test // 切换到test数据库
db.auth('testname', 'testpwd') // 验证
db.changeUserPassword('testname', 'newtestpwd') // 修改密码

配置及测试mongodb副本集

mongodb副本集即客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一旦主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。副本集中的副本节点在主节点挂掉后通过心跳机制检测到后,就会在集群内发起主节点的选举机制,自动选举一位新的主服务器。选举还有个前提条件,参与选举的节点数量必须大于副本集总节点数量的一半,如果已经小于一半了所有节点保持只读状态。因此,官方推荐我们的副本集机器数量至少为3个:一个主节点,两个副本节点。当然,mongodb副本集中可以有很多类型的节点,其中有一个仲裁节点,即当参与选举的节点无法选出主节点时仲裁节点充当仲裁的作用。仲裁节点不存储数据,只是仲裁。所以,我们的副本集可以设置为:1主+1从+1仲裁。

那么我们准备3台服务器:192.168.198.224(主),192.168.198.225(从),192.168.198.226(仲裁)。

1、分别在每台机器上下载解压mongodb安装包

curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.4.6.tgz
tar -zxvf mongodb-linux-x86_64-amazon-3.4.6.tgz
mv mongodb-linux-x86_64-amazon-3.4.6/ /usr/local/mongodb

2、分别在每台机器上创建mongodb的data、log以及配置目录

cd /usr/local/mongodb
mkdir -p data/db
mkdir log
touch log/mongod.log
mkdir etc
touch etc/mongod.conf

3、分别在每台机器上配置config文件

vim etc/mongod.conf

为了启动方便,我们可以将mongodb的启动配置项直接在配置文件中:

dbpath = /usr/local/mongodb/data/db        # 指定数据库路径
logpath = /usr/local/mongodb/log/mongod.log          # 指定mongodb日志文件
logappend = true        # 使用追加的方式写日志
port = 27017               #设置端口号为27017
fork = true                   #设置以守护进程的方式启动mongod
replSet = myrs            #设置副本集的名字为myrs,同一副本集群的replSet名称必需相同

4、分别在每台机器上启动副本集

cd /usr/local/mongodb
./bin/mongod -f ./etc/mongod.conf    #指定以mongod.conf配置启动mongod

5、初始化副本集

登录任意一台机器的mongodb后台管理shell:

cd /usr/local/mongodb
./bin/mongo
use admin
config = {
"_id":"rs0",
"members":[
  {"_id":0,"host":"192.168.198.223:27017"},
  {"_id":1,"host":"192.168.198.225:27017"},
  {"_id":2,"host":"192.168.198.226:27017",arbiterOnly:true}
]
}
rs.initiate(config);   #初始化配置

注意:如果执行rs.initiate(config)报错,那么我们需要检查三台服务器的防火墙27017端口是否开放。 如果没有问题,我们可以查看集群节点:

rs.status();

至此,整个副本集已经搭建成功了。
那么,我们可以测试一下副本集的基本功能:

1、测试副本集的数据复制功能

此时在我的机器上192.168.198.224是主节点,我们用mongo来登录shell。

cd /usr/local/mongodb
./bin/mongo
use test  #创建test数据库
db.testdb.insert({"name":"yaya"});       #插入数据

我们用副本节点登录shell,我这里是192.168.198.225:

cd /usr/local/mongodb
./bin/mongo
use test
show tables

此时会报错:

[thread1] Error: listCollections failed: {
"ok" : 0,
"errmsg" : "not master and slaveOk=false",
"code" : 13435,
"codeName" : "NotMasterNoSlaveOk"
} 

因为mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读。

myrs:SECONDARY> db.getMongo().setSlaveOk();

此时就可以读取数据了:

repset:SECONDARY> db.testdb.find();

控制台输出:{ “_id” : ObjectId(“59676d711881041abab44477”), “name” : “yaya” }
所以,数据复制的功能是可用的。

2、测试副本集的故障转移功能

将主节点192.168.198.224的mongod进程杀死:

myrs:PRIMARY> use admin
myrs:PRIMARY> db.shutdownServer()

此时可以看到,192.168.198.225原本是副节点,此时自动转换为主节点。可以通过rs.status()来查看。

MongoDB数据库文档操作示例

前面的话

本文将详细介绍MongoDB数据库关于文档的增删改查

插入文档

  要将数据插入到 MongoDB 集合中,需要使用 MongoDB 的 insert()或save()方法,还有insertOne()或insertMany()方法

【insert()】

insert()命令的基本语法如下

db.COLLECTION_NAME.insert(document)

在插入的文档中,如果不指定_id参数,那么 MongoDB 会为此文档分配一个唯一的ObjectId。_id为集合中的每个文档唯一的12个字节的十六进制数

如果数据库中不存在集合,则MongoDB将创建此集合,然后将文档插入到该集合中

未分类

要在单个查询中插入多个文档,可以在insert()命令中传递文档数组

未分类

可以使用js语法,插入多个文档

未分类

【save()】

插入文档也可以使用db.post.save(document)。 如果不在文档中指定_id,那么save()方法将与insert()方法一样自动分配ID的值。如果指定_id,则将以save()方法的形式替换包含_id的文档的全部数据。

也就是说save()方法和insert()方法的区别是,save()方法可以复写或修改,而insert()方法不可以

db.post.save(document)

未分类

未分类

【insertOne()】

使用db.collection.insertOne()方法可以将单个文档插入到集合中

未分类

【insertMany()】

  使用db.collection.insertMany()方法可以将多个文档插入到集合中

未分类

查询文档

【find()】

  要从MongoDB集合查询数据,需要使用MongoDB的find()方法,默认返回结果中的前20条文档,输入”it”显示接下来的20条文档。

  find()命令的基本语法如下:

db.COLLECTION_NAME.find(document)

  find()方法将以非结构化的方式显示所有文档

未分类

  可以限定查询条件

未分类

  可以通过find 的第二个参数来指定返回的键,值为1或true表示显示该键,值为0或false表示不显示该键

未分类

  find()方法下的count()方法可以显示符合条件的文档数量

未分类

【findOne()】

  findOne()方法只返回一个文档,该文档是最早被添加的文档

未分类

【比较操作符】

小于        {<key>:{$lt:<value>}}    
小于或等于    {<key>:{$lte:<value>}}    
大于        {<key>:{$gt:<value>}}    
大于或等于    {<key>:{$gte:<value>}}    
不等于      {<key>:{$ne:<value>}}
等于        {<key>:{$eq:<value>}}

  取得x小于2的值

未分类

  取得x大于等于2的值

未分类

  取得x不等于2的值

未分类

【逻辑操作符】

  可以使用逻辑操作符and、and、or来表示与、或

 { $and: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }
{ $nor: [ { <expression1> }, { <expression2> }, ... { <expressionN> } ] }

未分类

【正则表达式】

  文档查询可以使用正则表达式,但只支持字符串类型的数据

未分类

【$where】

  $where操作符功能强大而且灵活,它可以使用任意的JavaScript作为查询的一部分,包含JavaScript表达式的字符串或者JavaScript函数

未分类

  使用字符串

未分类

  使用函数

未分类

限制与跳过
【limit()】

  如果需要在MongoDB中读取指定数量的数据记录,可以使用MongoDB的Limit方法,limit()方法接受一个数字参数,该参数指定从MongoDB中读取的记录条数

  默认返回结果中的前20条文档,输入”it”显示接下来的20条文档

  如果没有指定limit()方法中的参数则显示集合中的所有数据

db.COLLECTION_NAME.find().limit(NUMBER)

未分类

【skip()】

  可以使用skip()方法来跳过指定数量的数据,skip方法同样接受一个数字参数作为跳过的记录条数

db.COLLECTION_NAME.find().skip(NUMBER)

未分类

排序
【sort()】

  在MongoDB中使用sort()方法对数据进行排序,sort()方法可以通过参数指定排序的字段,并使用 1 和 -1 来指定排序的方式,其中 1 为升序排列,而-1是用于降序排列

db.COLLECTION_NAME.find().sort({KEY:1})

未分类

更新文档
  MongoDB 使用update()或save()方法来更新集合中的文档

【update()】

  update() 方法用于更新已存在的文档。语法格式如下:

db.collection.update(<query>,<update>,{upsert:<boolean>, multi: <boolean>,writeConcern:<document>})
query : update的查询条件,类似sql update查询内where后面的
update : update的对象和一些更新的操作符(如$,$inc...)等,也可以理解为sql update查询内set后面的
upsert : 可选,这个参数的意思是,如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入
multi : 可选,mongodb 默认是false,只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新
writeConcern :可选,抛出异常的级别

 
 [注意]经过测试,upsert参数无法设置为true或者false,都可以插入新的字段

未分类

  mongodb默认只更新找到的第一条记录,将x:1,更新为x:10

未分类

  要特别注意的是,如果不使用$set,则将文档的内容替换为x:10

未分类

  更新全部记录,将x:10,更新为x:1

未分类

  mongodb默认只添加到更新找到的第一条记录,将x:1的记录,添加z:1

未分类

  将找到的x:2的全部记录,添加z:2

未分类

【save()】

  save()方法可以插入或更新文档,如果参数中的文档的_id与集合中所存在的_id都不同,则插入;如果相同,则更新

未分类

删除文档

  MongoDB remove()函数是用来移除集合中的数据

【remove()】

  默认地,mongodb删除符合条件的所有文档

db.collection.remove(<query>,{justOne: <boolean>, writeConcern: <document>})
query :删除的文档的条件。
justOne : (可选)如果设为 true 或 1,则只删除一个文档。
writeConcern :(可选)抛出异常的级别。

  只删除符合条件的第一个文档 

未分类

  删除符合条件的所有文档 

未分类

php-memcached扩展安装及使用方法

客户端下载

客户端下载:
https://docs-aliyun.cn-hangzhou.oss.aliyun-inc.com/cn/ocs/0.1.9/assets/libmemcached-1.0.16.gz?spm=5176.doc48432.2.1.eQ5tPY&file=libmemcached-1.0.16.gz

客户端介绍:
http://pecl.php.net/support.php?spm=5176.doc48432.2.2.eQ5tPY

客户端版本介绍:
http://pecl.php.net/package/memcached?spm=5176.doc48432.2.3.GstFhV

系统要求及环境配置

注意:您已经有 php memcache 等环境,请注意教程中的一些提示,以免生产环境被覆盖,导致业务不可用,在升级及再编译环境前请做好环境备份。

windows 系列版本

如果采用标准的 php memcached 扩展不能成功搭建,可以考虑换成手工拼包的形式来访问云数据库 Memcache,连接方式请参考如下链接,示例代码非常简单,与 php memcached 的区别就是仅支持主流接口,需自己补充某些特定接口,安装及使用方法请参见这里。

Centos 及 Aliyun Linux 6系列版本

注意:Memcached 2.2.0 扩展必须使用 libmemcached 1.0.x 的库,低于1.0的库不再能够成功编译。编译 libmemcached 时 GCC 要求在4.2及以上。

  • 确认是否安装了gcc-c++ 等组件(使用 gcc –v 查看版本是否为4.2及以上)。如没有请执行 yum install gcc+ gcc-c++。

  • 执行 rpm –qa | grep php 查看系统中是否有 PHP 环境,如果没有则执行 yum install php-devel,php-common,php-cli 安装包含源码编译的 PHP。
    建议使用 php 5.3及以上版本。php 5.2部分版本系列源代码会有 zend_parse_parameters_none 函数会出错,如需使用请参照 php 官方相关文档。如是源代码编译,请按照官方 php 编译升级的办法进行。

  • 检测是否有已安装了 SASL 相关环境包,如没有,则执行 yum install cyrus-sasl-plain cyrus-sasl cyrus-sasl-devel cyrus-sasl-lib 安装 SASL 相关环境。

  • 检测下是否有已安装了 libmemcached 源码包,若没有,则执行以下命令安装 libmemcached 源码包(推荐版本 libmemcached-1.0.18)。

 wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz 
 tar zxvf libmemcached-1.0.18.tar.gz
 cd libmemcached-1.0.18
 ./configure --prefix=/usr/local/libmemcached --enable-sasl
 make
 make install
 cd ..
  • 执行 yum install zlib-devel 安装 memcached 源码包(推荐版本为 memcached-2.2.0)。

注意:

  • 安装 memcached 前需要确认是否有 zlib-devel 包需要执行。

  • 请先检测下是否已安装了 memcached 客户端包(包含源码包)。如有则不需要安装,但需要重新编译增加 -enable-memcached-sasl 这个扩展。

wget http://pecl.php.net/get/memcached-2.2.0.tgz
tar zxvf memcached-2.2.0.tgz
cd memcached-2.2.0
phpize(如果系统中有两套PHP环境,需绝对路径调用该命令/usr/bin/phpize,该路径为使用云数据库Memcache的PHP环境路径)
./configure --with-libmemcached-dir=/usr/local/libmemcached --enable-memcached-sasl(注意这个参数)
make
make install
  • 修改 php.ini 文件(locate 找该文件,如果系统中有两套 PHP 环境,需找到使用云数据库 Memcache 的 PHP 环境路径,对应修改之),增加
extension=memcached.so memcached.use_sasl = 1。
  • 使用该页面最后的测试代码测试下是否环境部署成功,请修改代码中相应的地址、端口、用户名及密码。

Centos及 Aliyun Linux 5系列版本 【64位版本】

  • 确认是否安装了 gcc-c++ 等组件。如没有请执行 yum install gcc+ gcc-c++ 。

  • 执行rpm –qa | grep php 查看系统中是否有 php 环境,如果没有则执行 yum install php53 php53-devel 安装包含源码编译的 php;如有 php 则不要安装。建议使用 php 5.3(含)以上版。

php 5.2部分版本系列源代码会有 zend_parse_parameters_none 函数会出错,如需使用请参照 php 官方相关文档。

  • 执行 yum install cyrus-sasl-plain cyrus-sasl cyrus-sasl-devel cyrus-sasl-lib 安装 SASL 相关环境。

  • 检测下是否已安装了 libmemcached(包含源码包),如有则不需要安装,如没有则执行以下命令安装(推荐版本 libmemcached 1.0.2)。

 wget http://launchpad.net/libmemcached/1.0/1.0.2/+download/libmemcached-1.0.2.tar.gz
 tar -zxvf libmemcached-1.0.2.tar.gz
 cd libmemcached-1.0.2
 ./configure --prefix=/usr/local/libmemcached --enable-sasl
 make
 make install
 cd ..
  • 执行 yum install zlib-devel 安装源码包 memcached(推荐版本 memcached 2.0)。

注意:

  • 安装 memcached 前需要确认是否有 zlib-devel 包需要执行。

  • 请先检测下是否有已安装了 memcached 客户端包(包含源码包)。如有则不需要安装,但需要重新编译增加 -enable-memcached-sasl 这个扩展。

wget http://pecl.php.net/get/memcached-2.0.0.tgz tar -zxvf memcached-2.0.0.tgz 
cd memcached-2.0.0 phpize(如果系统中有两套PHP环境,需绝对路径调用该命令/usr/bin/phpize,该路径为使用云数据库Memcache的PHP环境路径,请在memcached源码目录内执行phpize) 
./configure --with-libmemcached-dir=/usr/local/libmemcached --enable-memcached-sasl(注意这个参数) 
make 
make install
  • 修改 php.ini 文件(locate 找该文件,yum 安装的一般在 /etc/php.ini。 如果系统中有两套 PHP 环境,需找到使用云数据库 Memcache 的 PHP 环境路径,对应修改之),增加 extension=memcached.so memcached.use_sasl = 1 。

  • 执行 php –m |grep ,memcached ,若显结果有 memcache 表示环境已支持 memcache。

  • 使用该页面最后的测试代码测试下是否环境部署成功,请修改代码中相应的地址、端口、用户名及密码。

Ubuntu Debian 等系列版本

  • 变更 ubuntu 源。

方案一:执行 vim /etc/apt/source.list,在最前面添加以下内容。

 deb http://mirrors.aliyun.com/ubuntu/ precise main restricted universe multiverse
 deb http://mirrors.aliyun.com/ubuntu/ precise-security main restricted universe multiverse
 deb http://mirrors.aliyun.com/ubuntu/ precise-updates main restricted universe multiverse
 deb http://mirrors.aliyun.com/ubuntu/ precise-proposed main restricted universe multiverse
 deb http://mirrors.aliyun.com/ubuntu/ precise-backports main restricted universe multiverse
 deb-src http://mirrors.aliyun.com/ubuntu/ precise main restricted universe multiverse
 deb-src http://mirrors.aliyun.com/ubuntu/ precise-security main restricted universe multiverse
 deb-src http://mirrors.aliyun.com/ubuntu/ precise-updates main restricted universe multiverse
 deb-src http://mirrors.aliyun.com/ubuntu/ precise-proposed main restricted universe multiverse
 deb-src http://mirrors.aliyun.com/ubuntu/ precise-backports main restricted universe multiverse
 apt-get update //更新一下列表

方案二: 通过 wget http://oss.aliyuncs.com/aliyunecs/update_source.zip 下载 update_source 的压缩包,解压后予执行权限 chmod 777 文件名 ,然后执行该脚本进行自动变更源操作。

  • 通过 ape-get 配置 GCC,G++ 。

首先需要使用 dpkg –s 安装包名,例如 dpkg –s gcc,确认是否安装了 gcc-c++ 等组件。如没有请执行 apt-get build-dep gcc apt-get install build-essential。

  • 安装 php5, php5-dev。

首先需要使用 dpkg –s 安装包名,例如 dpkg –s php,确认是否安装了 php 等组件。如没有请执行 apt-get install php5 php5-dev (同时会自动安装php5-cli和php5-common)。

  • 安装配置 sasl 支持。

首先需要使用 dpkg –s 安装包名, 例如 dpkg –s libsasl2,确认是否安装了 libsasl2 cloog-ppl 等组件,如没有请执行以下命令。

 apt-get install libsasl2-dev cloog-ppl
 cd /usr/local/src
  • 执行以下命令安装指定版本的 libmemcache。

注意:请先检测下是否有已安装了这些包(包含源码包),如有则不需要安装。

 wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
 tar -zxvf libmemcached-1.0.18.tar.gz
 cd libmemcached-1.0.18
 ./configure --prefix=/usr/local/libmemcached
 make
 make install
 cd ..
  • 执行以下命令安装指定版本的 memcached。

注意:请先检测下是否有已安装了 memcached 客户端包(包含源码包),如有则不需要安装,但需要重新编译增加 -enable-memcached-sasl 这个扩展。

 wget 
 http://pecl.php.net/get/memcached-2.2.0.tgz tar zxvf memcached-2.2.0.tgz 
 cd memcached-2.2.0 phpize5 
 ./configure --with-libmemcached-dir=/usr/local/libmemcached --enable-memcached-sasl 
 make 
 make install
  • 配置 php 支持 memcached,然后测试。
 echo "extension=memcached.so" >>/etc/php5/conf.d/pdo.ini echo "memcached.use_sasl = 1" >>/etc/php5/conf.d/pdo.ini
 php -m |grep mem memcached

如果显示出该组件代表安装完成,配置完毕。

PHP 代码示例

示例1:基本的连接云数据库 Memcache 及 set/get 操作

<?php
$connect = new Memcached;  //声明一个新的memcached链接
$connect->setOption(Memcached::OPT_COMPRESSION, false); //关闭压缩功能
$connect->setOption(Memcached::OPT_BINARY_PROTOCOL, true); //使用binary二进制协议
$connect->setOption(Memcached::OPT_TCP_NODELAY, true); //重要,php memcached有个bug,当get的值不存在,有固定40ms延迟,开启这个参数,可以避免这个bug
$connect->addServer('aaaaaaaaaa.m.yyyyyyyyyyy.ocs.aliyuncs.com', 11211); //添加OCS实例地址及端口号
$connect->setSaslAuthData('aaaaaaaaaa', 'password'); //设置OCS帐号密码进行鉴权,如已开启免密码功能,则无需此步骤;新版OCS的username可以置空
$connect->set("hello", "world");
echo 'hello: ',$connect->get("hello");
$connect->quit();
?>

示例2:在云数据库 Memcache 中缓存一个数组

<?php
$connect= new Memcached; //声明一个新的memcached链接
$connect->setOption(Memcached::OPT_COMPRESSION, false); //关闭压缩功能
$connect->setOption(Memcached::OPT_BINARY_PROTOCOL, true);//使用binary二进制协议
$connect->setOption(Memcached::OPT_TCP_NODELAY, true); //重要,php memcached有个bug,当get的值不存在,有固定40ms延迟,开启这个参数,可以避免这个bug
$connect->addServer('xxxxxxxx.m.yyyyyyyy.ocs.aliyuncs.com', 11211);//添加OCS实例地址及端口号
$connect->setSaslAuthData('xxxxxxxx', 'bbbbbbbb');//设置OCS帐号密码进行鉴权,如已开启免密码功能,则无需此步骤
$user = array(
    "name" => "ocs",
    "age" => 1,
    "sex" => "male"
); //声明一组数组  
$expire = 60; //设置过期时间
test($connect->set('your_name',$user,$expire), true, 'Set cache failed');
if($connect->get('your_name')){
$result =$connect->get('your_name');
}else{  
echo "Return code:", $connect->getResultCode();
echo "Retucn Message:", $connect->getResultMessage (); //如出现错误,解析出返回码
$result=" ";
}  
print_r($result);
$connect->quit();
function test($val, $expect, $msg)
{  
    if($val!= $expect) throw new Exception($msg);
}  
?>

示例3:云数据库 Memcache 与 MySQL 数据库结合使用

<?php
$connect = new Memcached; //声明一个新的memcached链接
$connect->setOption(Memcached::OPT_COMPRESSION, false);//关闭压缩功能
$connect->setOption(Memcached::OPT_BINARY_PROTOCOL, true);//使用binary二进制协议
$connect->setOption(Memcached::OPT_TCP_NODELAY, true); //重要,php memcached有个bug,当get的值不存在,有固定40ms延迟,开启这个参数,可以避免这个bug
$connect->addServer('xxxxxx.m.yyyyyyyy.ocs.aliyuncs.com', 11211);//添加实例地址  端口号
$connect->setSaslAuthData('xxxxxx', 'my_passwd');//设置OCS帐号密码进行鉴权,如已开启免密码功能,则无需此步骤
$user = array(
   "name" => "ocs",
   "age"  => 1,
   "sex"  => "male"
); //定义一组数组
if($connect->get('your_name'))
{
  $result =$connect->get('your_name');
  print_r($result);
  echo "Found in OCS, get data from OCS"; //如果获取到数据,则打印此数据来源于OCS
  exit;  
}
else
{
  echo "Return code:", $connect->getResultCode();
  echo "Retucn Message:", $connect->getResultMessage ();//抛出code返回码
  $db_host='zzzzzz.mysql.rds.aliyuncs.com'; //数据库地址
  $db_name='my_db';         //database name
  $db_username='db_user';    //数据库用户名
  $db_password='db_passwd';//数据库用户密码
  $connection=mysql_connect($db_host,$db_username,$db_password);
  if (!mysql_select_db($db_name, $connection))
  {
    echo 'Could not select database'; //数据库连接不成功则抛出错误信息
    exit;
  }
  $sql = "SELECT name,age,sex FROM test1 WHERE name = 'ocs'";
  $result = mysql_query($sql, $connection);
  while ($row = mysql_fetch_assoc($result))
  {
    $user = array(
      "name" => $row["name"],
      "age"  => $row["age"],
      "sex"  => $row["sex"],
    );
    $expire = 5; //设置数据在缓存中的过期时间
    test($connect->set('your_name',$user,$expire), true, 'Set cache failed'); //写入OCS缓存
  }
  mysql_free_result($result);
  mysql_close($connection);
}
print_r($connect->get('your_name')); //打印出  获取到的数据
echo "Not Found in OCS,get data from MySQL"; //确认从数据库获取的数据
$connect->quit();
function test($val, $expect, $msg)
{
  if($val!= $expect) throw new Exception($msg);
}
?>

memcached优化方法及应用场景

memcached分布式

Memcached尽管是“分布式”的缓存系统,但服务器端并没有分布式功能。各个 Memcached不会互相通信以共享信息。分布式完全取决于客户端实现。
Memcached的分布式客户端
客户端可以通过配置SockIOPool的servers参数保存服务器地址列表,通过
weight参数配置每台服务器的权重。SockIOPool提供了连接池的服务,可以通过 SockIOPool来配置memcahce服务器相关信息,如最大连接数,最小连接数等。

一个key只能存放在一台Memcache服务器上,是不会在多个服务器上有多份拷贝的,这样的话既可以防止出现刷新不同步的情况,也可以避免磁盘空间的浪费

Memcached的分布式特点

1: 服务器端不关心分布式。

2: 依靠客户端来实现分布式 。

3: 客户端存储着可以访问到的Memcached服务器列表 。

4: 在客户端用算法来保证,对同样key值的数据,读写都操作同一个服务器。

Memcached 的调优的目标

  1. 提高内存利用率,减少内存浪费。

  2. 提高命中率。

调优方法:

  1. f参数:
    factor增长因子,默认为1.25,曾经为2,值越小,slab中chunk size差距 越小,内存浪费越小。1.25适合缓存几百字节的对象。
    建议:计算一下数据的预期平均长度,调整factor,以获得最恰当的设置。

  2. n参数:chunk初始值

  • slab尾部剩余空间
    解决办法:规划slab=chunk*n整数倍

  • slab中chunk利用率低:申请的slab只存放了一个Item
    解决办法:规划slab=chunk

  • chunk存储Item浪费
    如Item是100,存到128字节chunk,就有28字节浪费
    解决办法:规划chunk=Item

Memcached 的限制

  • 在Memcached中可以保存的item数据量是没有限制的,只要内存足够。

  • Memcached单进程最大使用内存为2G,要使用更多内存,可以分多个端口开启多
    个Memcached进程。

  • Memcached设置Item为最大30天的过期时间,设置为永久的也会在这个时间过期,
    常量REALTIME_MAXDELTA为606024*30控制。

  • Memcached本身是为缓存而设计的服务器,因此并没有过多考虑数据的永久性问
    题,当内容容量达到指定值之后,就基于LRU(Least Recently Used)算法自动删
    除不使用的缓存。

  • 最大键长为250字节,大于该长度无法存储,单个item最大数据是1MB,超过1MB数据不予存储。

Memcached 的使用目的

通过缓存数据库查询结果,减少数据库访问次

数;还有就是缓存热点数据,以提高Web应用的速度、提高可扩展性。

  1. 缓存简单的查询结果:查询缓存存储了给定查询语句对应的整个结果集,最合适
    缓存那些经常被用到,但不会改变的SQL语句对应查询到的结果集,比如载入特
    定的过滤内容。

  2. 缓存简单的基于行的查询结果。

  3. 缓存的不只是SQL数据,可以缓存常用的热点数据,比如页面,以节省CPU时间使用分层的缓存。

  4. 特别注意:当数据更新时需要更新缓存。

Memcached 的典型使用场景

  1. 分布式应用。

  2. 数据库前段缓存。

  3. 服务器间数据共享。

  4. 变化频繁,查询频繁的数据,但是不一定写入数据库,比如:用户在线状态 。

  5. 变化不频繁,查询频繁,不管是否入库,都比较适合使用。

Memcached 不适合使用Memcached的场景

1: 变化频繁, 一变化就要入库类的应用,比如股票,金融。

2: 那些不需要“分布”的,不需要共享的,或者干脆规模小到只有一台服务器的应用,
memcached不会带来任何好处,相反还会拖慢系统效率,因为网络连接同样需要资源。

3: 缓存对象的大小大于1MB。

4: key的长度大于250字符。

相关文章

Memcached 安装使用存储
http://www.jianshu.com/p/2b3c43c1778c

java 使用memcached以及spring 配置memcached
http://www.jianshu.com/p/6f264bf5d9f9

memcached优化
http://www.jianshu.com/p/789d208036f5