NoSQL Memcached

21.1 NoSQL介绍


SQL (Structured Query Language) 数据库,指关系型数据库。主要表明:SQL Server,Oracle,MySQL,PostgreSQL。javascript

NoSQL(Not Only SQL)泛指非关系型数据库。主要表明:MongoDB,Redis,CouchDB。php

k-v形式:memcached、redis适合存储用户信息,例如会话、配置文件、参数、购物车等。这些信息通常和ID(主键)挂钩,这种情景下键值数据库是个很好的选择;

文档数据库:mongodb将数据以文档的形式存储,每一个文档都是一系列数据项的集合。每一个数据项都有一个名称与对应的值,值既能够是简单的数据类型,如字符串、数字和日期等;也能够是复杂的类型,若有序列表和关联对象。数据存储的最小单位是文档,同一个表中存储的文档属性能够是不一样的,数据可使用xml、json或者jsonb等多种形式存储

列存储:Hbase

图:Neo4j、Infinite Graph、OrientDB

随着互联网的不断发展,各类类型的应用层出不穷,在这个云计算的时代,对技术提出了更多的需求,主要体如今下面这四个方面:css

1. 低延迟的读写速度:应用快速地反应能极大地提高用户的满意度;

2. 海量的数据和流量:对于搜索这样大型应用而言,须要利用PB级别的数据和能应对百万级的流量;

3. 大规模集群的管理:系统管理员但愿分布式应用能更简单的部署和管理;

4. 庞大运营成本的考量:IT经理们但愿在硬件成本、软件成本和人力成本可以有大幅度地下降。

目前世界上主流的存储系统大部分仍是采用了关系型数据库,其主要有一下优势:html

1. 事务处理—保持数据的一致性;

2. 因为以标准化为前提,数据更新的开销很小(相同的字段基本上只有一处);

3. 能够进行Join等复杂查询。

虽然关系型数据库已经在业界的数据存储方面占据不可动摇的地位,可是因为其天生的几个限制,使其很难知足上面这几个需求:java

1. 扩展困难:因为存在相似Join这样多表查询机制,使得数据库在扩展方面很艰难;

2. 读写慢:这种状况主要发生在数据量达到必定规模时因为关系型数据库的系统逻辑很是复杂,使得其很是容易发生死锁等的并发问题,因此致使其读写速度下滑很是严重;

3. 成本高:企业级数据库的License价格很惊人,而且随着系统的规模,而不断上升;

4. 有限的支撑容量:现有关系型解决方案还没法支撑Google这样海量的数据存储。

为了解决这些问题,NoSQL由此被设计出来,它有如下优缺点:node

优势:

1. 简单的扩展:典型例子是Cassandra,因为其架构是相似于经典的P2P,因此能经过轻松地添加新的节点来扩展这个集群;

2. 快速的读写:主要例子有Redis,因为其逻辑简单,并且纯内存操做,使得其性能很是出色,单节点每秒能够处理超过10万次读写操做;

3. 低廉的成本:这是大多数分布式数据库共有的特色,由于主要都是开源软件,没有昂贵的License成本。

缺点:

1. 不提供对SQL的支持:若是不支持SQL这样的工业标准,将会对用户产生必定的学习和应用迁移成本;

2. 支持的特性不够丰富:现有产品所提供的功能都比较有限,大多数NoSQL数据库都不支持事务,也不像MS SQL Server和Oracle那样能提供各类附加功能,好比BI和报表等;

3. 现有产品的不够成熟:大多数产品都还处于初创期,和关系型数据库几十年的完善不可同日而语。

并非任何场景,NoSQL都要优于关系型数据库,在这些场景它更加给力:mysql

1. 数据库表schema常常变化。例如在线商城,NoSQL应用在这种场景,能够极大提高DB的可伸缩性,开发人员能够将更多的精力放在业务层;

2. 数据库表字段是复杂数据类型。对于复杂数据类型,NoSQL以json方式存储,提供了原生态的支持,在效率方面远远高于传统关系型数据库;

3. 高并发数据库请求。此类应用常见于web2.0的网站,不少应用对于数据一致性要求很低,而关系型数据库的事务以及大表join反而成了”性能杀手”;

4. 海量数据的分布式存储。海量数据的存储若是选用大型商用数据,如Oracle,那么整个解决方案的成本是很是高的,要花不少钱在软硬件上。NoSQL分布式存储,能够部署在廉价的硬件上,是一个性价比很是高的解决方案。

其实NoSQL数据库仅仅是关系数据库在某些方面(性能,扩展)的一个弥补,单从功能上讲,NoSQL的几乎全部的功能,在关系数据库上都可以知足,因此选择NoSQL的缘由并不在功能上。nginx

因此,咱们通常会把NoSQL和关系数据库进行结合使用,各取所长,须要使用关系特性的时候咱们使用关系数据库,须要使用NoSQL特性的时候咱们使用NoSQL数据库,各得其所。web


21.2 Memcached


Memcached介绍

Memcached是国外社区网站LiveJournal团队开发,可以将数据存储在内存中,目的是为了经过缓存数据库查询结果,减小数据库访问次数,从而提升动态web站点性能。redis

Memcached官网,memcached有如下特色:

数据结构简单(k-v),数据存放在内存里
多线程
基于C/S架构,协议简单
基于libevent的事件处理
主内存存储处理(slab allocation)
数据过时方式:Lazy Expiration和LRU

slab allocation的原理:

将分配的内存分隔成各类尺寸的块(chunk),并把尺寸相同的块分红组(chunk的集合),各个chunk集合被称为slab;

memcached的内存分配以page为单位,page默认值为1M,能够在启动时经过-I参数来指定;

slab是由多个page组成的,page按照指定大小切割成多个chunk。即chunk组成page,page组成slab。

chunk的大小差别由growth factor控制:

memcached在启动时经过 -f 选项能够指定growth factor因子。该值控制chunk大小的差别。默认值为1.25;

经过memcached-tool命令查看指定memcached实例的不一样slab状态,能够看到个ltem所占大小(chunk大小)为1.25。

Memcached的数据过时方式有两种:

  • Lazy Expiration:
memcached内部不会监视记录是否过时,而是在get时查看记录的时间戳,检查记录是否过时。

这种技术称为lazy expiration,主要特色是memcached不会在过时监视上耗费CPU资源。

  • LRU:
memcached会优先使用已超时的记录的空间,但即使如此,也会发生追加新纪录时空间不足的状况,
此时就要使用名为“Least Recently Used(LRU)”机制来分配空间,即删除“最近最少使用”的记录的机制。

所以,当内存空间不足(没法从slab class获取到新的空间时),就从最近未被使用的记录中搜索,
并将其空间分配给新的记录。从缓存的实用角度来看,该模型十分理想。


Memcached安装

  • yum安装memcached:
# yum install -y libevent libmemcached memcached				#安装memcached须要先安装libevent

  • 启动memcached:
# systemctl start memcached# ps aux |grep memcachedmemcach+  28622  0.0  0.1 344080  1656 ?        Ssl  16:17   0:00 /usr/bin/memcached -u memcached -p 11211 -m 64 -c 1024   

# netstat -lntp |grep 11211tcp        0      0 0.0.0.0:11211           0.0.0.0:*               LISTEN      28622/memcached     
tcp6       0      0 :::11211                :::*                    LISTEN      28622/memcached

上面,

-u		指定运行memcached服务的用户
-p		指定监听端口
-m		指定memcached分配内存
-c		指定最大并发数

  • 修改配置文件:
# vim /etc/sysconfig/memcachedPORT="11211"USER="memcached"MAXCONN="1024"CACHESIZE="64"OPTIONS=""

除了上面几个参数以外,memcached还有如下经常使用参数:

-l		指定监听IP
    
-d		以守护进程(daemon)启动


查看Memcached运行状态

memcached启动以后,咱们能够查看memcached服务的状态,有几种方式。

  • memcached自带有tool工具:
# memcached-tool 127.0.0.1:11211 stats				#这里是stats,而不是status#127.0.0.1:11211   Field       Value
         accepting_conns           1
               auth_cmds           0
             auth_errors           0
                   bytes           0
              bytes_read           7
           bytes_written           0
              cas_badval           0
                cas_hits           0
              cas_misses           0
               cmd_flush           0
                 cmd_get           0
                 cmd_set           0
               cmd_touch           0
             conn_yields           0
   connection_structures          11
        curr_connections          10
              curr_items           0				#关注这个数据,表示目前在memcached中的项目
               decr_hits           0
             decr_misses           0
             delete_hits           0
           delete_misses           0
       evicted_unfetched           0
               evictions           0
       expired_unfetched           0
                get_hits           0				#关注这个数据,表示命中了多少,命中率就是命中数除以项目数
              get_misses           0
              hash_bytes      524288
       hash_is_expanding           0
        hash_power_level          16
               incr_hits           0
             incr_misses           0
                libevent 2.0.21-stable
          limit_maxbytes    67108864
     listen_disabled_num           0
                     pid       28622
            pointer_size          64
               reclaimed           0
            reserved_fds          20
           rusage_system    0.049655
             rusage_user    0.019310
                 threads           4                    time  1534840454
       total_connections          11
             total_items           0
              touch_hits           0
            touch_misses           0                  uptime         985
                 version      1.4.15

  • 或者安装nc(ncat简称nc):
# yum install -y nc# echo stats |nc 127.0.0.1 11211STAT pid 28622
STAT uptime 1517
STAT time 1534840986
STAT version 1.4.15
STAT libevent 2.0.21-stable
STAT pointer_size 64
STAT rusage_user 0.027098
STAT rusage_system 0.067747
STAT curr_connections 10
STAT total_connections 12
STAT connection_structures 11
STAT reserved_fds 20
STAT cmd_get 0
STAT cmd_set 0
STAT cmd_flush 0
STAT cmd_touch 0
STAT get_hits 0
STAT get_misses 0
STAT delete_misses 0
STAT delete_hits 0
STAT incr_misses 0
STAT incr_hits 0
STAT decr_misses 0
STAT decr_hits 0
STAT cas_misses 0
STAT cas_hits 0
STAT cas_badval 0
STAT touch_hits 0
STAT touch_misses 0
STAT auth_cmds 0
STAT auth_errors 0
STAT bytes_read 13
STAT bytes_written 1025
STAT limit_maxbytes 67108864
STAT accepting_conns 1
STAT listen_disabled_num 0
STAT threads 4
STAT conn_yields 0
STAT hash_power_level 16
STAT hash_bytes 524288
STAT hash_is_expanding 0
STAT bytes 0
STAT curr_items 0
STAT total_items 0
STAT expired_unfetched 0
STAT evicted_unfetched 0
STAT evictions 0
STAT reclaimed 0
END

  • 以前安装了libmemcached:
# memstat --servers=127.0.0.1:11211Server: 127.0.0.1 (11211)
	 pid: 28622
	 uptime: 1802
	 time: 1534841271
	 version: 1.4.15
	 libevent: 2.0.21-stable
	 pointer_size: 64
	 rusage_user: 0.041055
	 rusage_system: 0.071847
	 curr_connections: 10
	 total_connections: 13
	 connection_structures: 11
	 reserved_fds: 20
	 cmd_get: 0
	 cmd_set: 0
	 cmd_flush: 0
	 cmd_touch: 0
	 get_hits: 0
	 get_misses: 0
	 delete_misses: 0
	 delete_hits: 0
	 incr_misses: 0
	 incr_hits: 0
	 decr_misses: 0
	 decr_hits: 0
	 cas_misses: 0
	 cas_hits: 0
	 cas_badval: 0
	 touch_hits: 0
	 touch_misses: 0
	 auth_cmds: 0
	 auth_errors: 0
	 bytes_read: 30
	 bytes_written: 2071
	 limit_maxbytes: 67108864
	 accepting_conns: 1
	 listen_disabled_num: 0
	 threads: 4
	 conn_yields: 0
	 hash_power_level: 16
	 hash_bytes: 524288
	 hash_is_expanding: 0
	 bytes: 0
	 curr_items: 0
	 total_items: 0
	 expired_unfetched: 0
	 evicted_unfetched: 0
	 evictions: 0
	 reclaimed: 0


Memcached命令行

  • 进入memcached命令行:
# yum install -y telnet# telnet 127.0.0.1 11211Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

  • 存储数值:
set key2 0 30 2				#key2表示ID,0为标志,30表示过时时间为30s,2表示存储的字节数为212
STORED				#STORED表示value 12 已经存储set key1 0 30 3				#ID为key1,设置valueabc
STORED

get key2				#查看key2的valueVALUE key2 0 2
12
END

get key1				#查看key1的valueVALUE key1 0 3
abc
END

get key2				#过一段时间以后再次查看END				#发现value已经消失,这是由于已通过期了get key1
END

  • memcached命令行语法规则:
\r\n\r\n				# \r\n在window下表示Enter键是一个16位的无符号的整数(以十进制的方式表示)。该标志将和存储的数据一块儿存储,并在客户端get数据时返回。
客户端能够将此标志用做特殊用途,此标志对服务器来讲是不透明的。为过时的时间。若为0表示存储的数据永远不过时(但可被服务器算法:LRU等替换);
若不为0(unix时间或者距离此时的秒数),当过时后,服务器能够保证用户得不到该数据(以服务器时间为标准)须要存储的字节数,当用户但愿存储空数据时能够为0须要存储的内容,输入完成后,客户端须要加上\r\n(直接点击Enter)做为结束标志能够是set,add,replace

set表示按照相应的存储该数据,没有的时候增长,有的时候覆盖

add表示按照相应的添加该数据,可是若是该已存在则会操做失败

replace表示按照相应的替换数据,可是若是该不存在则会操做失败客户端须要保存数据的

  • 替换replace:
set key3 1 110 4
1234
STORED

replace key3 1 0 5 
abcde
STORED

get key3
VALUE key3 1 5
abcde
END

  • 删除delete:
delete key3
DELETED

get key3  
END

Tips:假如输入错误是直接点击Backspace是没用的,能够按Ctrl+Backspace来删除输入的错误。


Memcached数据导出和导入

若是想要重启memcached服务,那在重启以前,最好将数据导出一下,重启完以后再导入进去。

  • 先写入几条数据做为演示:
set name 1 0 6 
lzxlzx
STOREDset psd 1 0 5
12345
STOREDset age 1 0 2
22
STORED

^]				#输入Ctrl+]telnet> quit				#再输入quit退出Connection closed.

  • 导出数据:
# memcached-tool 127.0.0.1:11211 dump > data.txtDumping memcache contents
  Number of buckets: 1
  Number of items  : 3
Dumping bucket 1 - 3 total items# cat data.txtadd psd 1 1534853776 5
12345
add name 1 1534853776 6
lzxlzx
add age 1 1534853776 2
22				#导出来的数据带有时间戳,由于系统在你建立数据的时候就已经加上了时间戳,以前没有的数据是由于已通过期了

  • 导入回去:
# nc 127.0.0.1 11211 < data.txtNOT_STORED
NOT_STORED
NOT_STORED				#提示NOT_STORED,这是由于里面的数据已经有了,并且data.txt里面是add,它不会覆盖已经存在的数据# systemctl restart memcached				#数据存在内存中,重启服务可让以前数据消失# nc 127.0.0.1 11211 < data.txtSTORED
STORED
STORED# telnet 127.0.0.1 11211Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.get name
END

get psd 
END

get age
END				#能够看到,查看刚刚导入的数据,发现没有value# date -d '+1 hour' +%s1534862308# vim data.txtadd psd 1 1534862308 5				#修改时间戳,留一个以前的时间戳做对比12345
add name 1 1534862308 6
lzxlzx
add age 1 1534853776 2
22# systemctl restart memcached# nc 127.0.0.1 11211 < data.txtSTORED
STORED
STORED# telnet 127.0.0.1 11211Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.get age
END

get name
VALUE name 1 6
lzxlzx
END

get psd 
VALUE psd 1 5
12345
END				#能够看到,修改了时间戳以后就能够查看value了,没修改的就仍是查看不到,由于已通过期


PHP链接Memcached

  • 先安装php的memcached扩展:
# cd /usr/local/src/# wget http://www.apelearn.com/bbs/data/attachment/forum/memcache-2.2.3.tgz# tar zxf memcache-2.2.3.tgz # cd memcache-2.2.3# /usr/local/php-fpm/bin/phpize Configuring for:
PHP Api Version:         20131106
Zend Module Api No:      20131226
Zend Extension Api No:   220131226				#这里可能提示要安装autoconf,yum安装便可# ./configure --with-php-config=/usr/local/php-fpm/bin/php-config# echo $?0# make# echo $?0# make install Installing shared extensions:     /usr/local/php-fpm/lib/php/extensions/no-debug-non-zts-20131226/# ls /usr/local/php-fpm/lib/php/extensions/no-debug-non-zts-20131226/memcache.so  opcache.a  opcache.so				#有memcache.so文件

  • 修改php.ini文件:
# vim /usr/local/php-fpm/etc/php.ini				#添加下面一行extension=memcache.so;session.save_handler = files				#前面增长分号将该行注销# /usr/local/php-fpm/bin/php -m				#查看模块[PHP Modules]Core
ctypecurldatedom
ereg
exif
fileinfo
filterftpgdhashiconvjson
libxml
mbstring
mcrypt
memcache				#有这个模块就说明成功mysql
openssl
pcre
PDO
pdo_sqlite
Phar
posix
Reflection
session
SimpleXML
soap
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zlib[Zend Modules]

  • 下载测试脚本:
# curl www.apelearn.com/study_v2/.memcache.txt > 1.php 2>/dev/null# cat 1.php<?php
//链接Memcache Memcache$mem = new Memcache;$mem->connect("localhost", 11211);//保存数据$mem->set('key1', 'This is first value', 0, 60);$val = $mem->get('key1');echo "Get key1 value: " . $val ."
";//替换数据$mem->replace('key1', 'This is replace value', 0, 60);$val = $mem->get('key1');echo "Get key1 value: " . $val . "
";//保存数组数据$arr = array('aaa', 'bbb', 'ccc', 'ddd');$mem->set('key2', $arr, 0, 60);$val2 = $mem->get('key2');echo "Get key2 value: ";print_r($val2);echo "
";//删除数据$mem->delete('key1');$val = $mem->get('key1');echo "Get key1 value: " . $val . "
";//清除全部数据$mem->flush();$val2 = $mem->get('key2');echo "Get key2 value: ";print_r($val2);echo "
";//关闭链接$mem->close();?># /usr/local/php-fpm/bin/php 1.php Get key1 value: This is first value<br>Get key1 value: This is replace value<br>Get key2 value: Array(    [0] => aaa    [1] => bbb    [2] => ccc    [3] => ddd #有这样的数听说明已经支持memcached扩展)<br>Get key1 value: <br>Get key2 value: <br>          


Memcached中存储session

在作了负载均衡的同时,如何让用户的session保存在同一台服务器上呢?这就须要在memcached中作些修改。

  • 先在php中lzx.conf对应的pool中作修改:
# vim /usr/local/php-fpm/etc/php-fpm.conf				#添加下面两行配置php_value[session.save_handler] = memcache
php_value[session.save_path] = " tcp://127.0.0.1:11211 "

  • 编辑一个能够存session记录的脚本:
# vim session.php				#写入下面内容<?php
session_start();if (!isset($_SESSION['TEST'])) {$_SESSION['TEST'] = time();}$_SESSION['TEST3'] = time();print $_SESSION['TEST'];print "

";print $_SESSION['TEST3'];print "

";print session_id();?># cat /usr/local/nginx/conf/nginx.conf #查看nginx配置文件user nobody nobody;worker_processes 2;error_log /usr/local/nginx/logs/nginx_error.log crit;pid /usr/local/nginx/logs/nginx.pid;worker_rlimit_nofile 51200;events{    use epoll;    worker_connections 6000;}http{    include mime.types;    default_type application/octet-stream;    server_names_hash_bucket_size 3526;    server_names_hash_max_size 4096;    log_format combined_realip '$remote_addr $http_x_forwarded_for [$time_local]'    ' $host "$request_uri" $status'    ' "$http_referer" "$http_user_agent"';    sendfile on;    tcp_nopush on;    keepalive_timeout 30;    client_header_timeout 3m;    client_body_timeout 3m;    send_timeout 3m;    connection_pool_size 256;    client_header_buffer_size 1k;    large_client_header_buffers 8 4k;    request_pool_size 4k;    output_buffers 4 32k;    postpone_output 1460;    client_max_body_size 10m;    client_body_buffer_size 256k;    client_body_temp_path /usr/local/nginx/client_body_temp;    proxy_temp_path /usr/local/nginx/proxy_temp;    fastcgi_temp_path /usr/local/nginx/fastcgi_temp;    fastcgi_intercept_errors on;    tcp_nodelay on;    gzip on;    gzip_min_length 1k;    gzip_buffers 4 8k;    gzip_comp_level 5;    gzip_http_version 1.1;    gzip_types text/plain application/x-javascript text/css text/htm    application/xml;    server    {        listen 80;        server_name localhost;        index index.html index.htm index.php;        root /usr/local/nginx/html; #将上面写好的脚本放到这个目录下        location ~ \.php$        {            include fastcgi_params;            fastcgi_pass unix:/tmp/php-fcgi.sock;            fastcgi_index index.php;            fastcgi_param SCRIPT_FILENAME /usr/local/nginx/html$fastcgi_script_name;        }        }}# mv session.php /usr/local/nginx/html/ #移动到/usr/local/nginx/html/目录下

  • 进行测试:
# ls /tmp/ks-script-kWjJSk  php-fcgi.sock
mysql.sock        systemd-private-65239b999e6a467d80882d3bafffb812-chronyd.service-ILuCrK
pear              yum.log# curl localhost/session.php				#访问一下session.php1532089311<br><br>1532089311<br><br>rpqvi8bd8hnkjrei5gdak50jh4				#产生记录# ls /tmp/				#再次查看/tmp目录,发现多了一个session记录ks-script-kWjJSk  sess_rpqvi8bd8hnkjrei5gdak50jh4
mysql.sock        systemd-private-65239b999e6a467d80882d3bafffb812-chronyd.service-ILuCrK
pear              yum.log
php-fcgi.sock# curl localhost/session.php				#再访问一次1532091866<br><br>1532091866<br><br>o8225a4bqqfnf5oqsa7u3fl776# telnet 127.0.0.1 11211Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.get o8225a4bqqfnf5oqsa7u3fl776				#这里根据键名能够查到它的value,说明存储在memcached中VALUE o8225a4bqqfnf5oqsa7u3fl776 0 37
TEST|i:1532091866;TEST3|i:1532091866;END

至此,就实现了在负载均衡地状况下,将用户session保存在同一服务器上。


更多资料参考:

实战Memcached缓存系统系列