21.1 NoSQL介绍
SQL (Structured Query Language) 数据库,指关系型数据库。主要代表:SQL Server,Oracle,MySQL,PostgreSQL。
NoSQL(Not Only SQL)泛指非关系型数据库。主要代表:MongoDB,Redis,CouchDB。
k-v形式:memcached、redis适合存储用户信息,例如会话、配置文件、参数、购物车等。这些信息一般和ID(主键)挂钩,这种情景下键值数据库是个很好的选择; 文档数据库:mongodb将数据以文档的形式存储,每个文档都是一系列数据项的集合。每个数据项都有一个名称与对应的值,值既可以是简单的数据类型,如字符串、数字和日期等;也可以是复杂的类型,如有序列表和关联对象。数据存储的最小单位是文档,同一个表中存储的文档属性可以是不同的,数据可以使用xml、json或者jsonb等多种形式存储 列存储:Hbase 图:Neo4j、Infinite Graph、OrientDB
随着互联网的不断发展,各种类型的应用层出不穷,在这个云计算的时代,对技术提出了更多的需求,主要体现在下面这四个方面:
1. 低延迟的读写速度:应用快速地反应能极大地提升用户的满意度; 2. 海量的数据和流量:对于搜索这样大型应用而言,需要利用PB级别的数据和能应对百万级的流量; 3. 大规模集群的管理:系统管理员希望分布式应用能更简单的部署和管理; 4. 庞大运营成本的考量:IT经理们希望在硬件成本、软件成本和人力成本能够有大幅度地降低。
目前世界上主流的存储系统大部分还是采用了关系型数据库,其主要有一下优点:
1. 事务处理—保持数据的一致性; 2. 由于以标准化为前提,数据更新的开销很小(相同的字段基本上只有一处); 3. 可以进行Join等复杂查询。
虽然关系型数据库已经在业界的数据存储方面占据不可动摇的地位,但是由于其天生的几个限制,使其很难满足上面这几个需求:
1. 扩展困难:由于存在类似Join这样多表查询机制,使得数据库在扩展方面很艰难; 2. 读写慢:这种情况主要发生在数据量达到一定规模时由于关系型数据库的系统逻辑非常复杂,使得其非常容易发生死锁等的并发问题,所以导致其读写速度下滑非常严重; 3. 成本高:企业级数据库的License价格很惊人,并且随着系统的规模,而不断上升; 4. 有限的支撑容量:现有关系型解决方案还无法支撑Google这样海量的数据存储。
为了解决这些问题,NoSQL由此被设计出来,它有以下优缺点:
优点: 1. 简单的扩展:典型例子是Cassandra,由于其架构是类似于经典的P2P,所以能通过轻松地添加新的节点来扩展这个集群; 2. 快速的读写:主要例子有Redis,由于其逻辑简单,而且纯内存操作,使得其性能非常出色,单节点每秒可以处理超过10万次读写操作; 3. 低廉的成本:这是大多数分布式数据库共有的特点,因为主要都是开源软件,没有昂贵的License成本。
缺点: 1. 不提供对SQL的支持:如果不支持SQL这样的工业标准,将会对用户产生一定的学习和应用迁移成本; 2. 支持的特性不够丰富:现有产品所提供的功能都比较有限,大多数NoSQL数据库都不支持事务,也不像MS SQL Server和Oracle那样能提供各种附加功能,比如BI和报表等; 3. 现有产品的不够成熟:大多数产品都还处于初创期,和关系型数据库几十年的完善不可同日而语。
并不是任何场景,NoSQL都要优于关系型数据库,在这些场景它更加给力:
1. 数据库表schema经常变化。例如在线商城,NoSQL应用在这种场景,可以极大提升DB的可伸缩性,开发人员可以将更多的精力放在业务层; 2. 数据库表字段是复杂数据类型。对于复杂数据类型,NoSQL以json方式存储,提供了原生态的支持,在效率方面远远高于传统关系型数据库; 3. 高并发数据库请求。此类应用常见于web2.0的网站,很多应用对于数据一致性要求很低,而关系型数据库的事务以及大表join反而成了”性能杀手”; 4. 海量数据的分布式存储。海量数据的存储如果选用大型商用数据,如Oracle,那么整个解决方案的成本是非常高的,要花很多钱在软硬件上。NoSQL分布式存储,可以部署在廉价的硬件上,是一个性价比非常高的解决方案。
其实NoSQL数据库仅仅是关系数据库在某些方面(性能,扩展)的一个弥补,单从功能上讲,NoSQL的几乎所有的功能,在关系数据库上都能够满足,所以选择NoSQL的原因并不在功能上。
所以,我们一般会把NoSQL和关系数据库进行结合使用,各取所长,需要使用关系特性的时候我们使用关系数据库,需要使用NoSQL特性的时候我们使用NoSQL数据库,各得其所。
21.2 Memcached
Memcached介绍
Memcached是国外社区网站LiveJournal团队开发,能够将数据存储在内存中,目的是为了通过缓存数据库查询结果,减少数据库访问次数,从而提高动态web站点性能。
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保存在同一服务器上。
更多资料参考: