NoSQL(Memcache)
php项目执行流程
【简单理解】
读取源码 -> 词法分析 -> 创建opcode -> 执行opcode 重复创建会增加额外的内存和CPU开销
【解决方案】
安装ZendOptimizer或APC2.0等可以加速PHP代码访问
主要用于缓存opcode而不是每次重复编译减少CPU和内存开销(php5.5+后不需要安装第三方软件,直接开启PHP配置文件中Opcache即可。
步骤 在php.ini中 输入一下代码 并重启Apache
;声明opcache扩展文件所在位置
zend_extension="PHP安装目录\php-5.6.27-nts\ext\php_opcache.dll"
;是否开启opcode缓存
opcache.enable=1
;OPcache 的共享内存大小,以兆字节为单位
opcache.memory_consumption=128
;用来存储临时字符串的内存大小,以兆字节为单位。 PHP 5.3.0 之前的版本会忽略此配置指令
opcache.interned_strings_buffer=8
#OPcache 哈希表中可存储的脚本文件数量上限。
opcache.max_accelerated_files=4000
;检查脚本时间戳是否有更新的周期,以秒为单位。设置为 0 会导致针对每个请求,OPcache 都会检查脚本更新
opcache.revalidate_freq=60
;打开快速关闭, 打开这个在PHP Request Shutdown的时候回收内存的速度会提高
opcache.fast_shutdown=1
;开启CLI
opcache.enable_cli=1
通过 phpinfo 检测是否开启成功 查看是否有OPcache
大型项目优化方向
代码优化
开启opcode缓存,减少额外CPU和内存开销,加快代码运行速度
重模型轻控制器,减少冗余
符合PHP-FIG规范和phpDoc规范,增强代码可读性&可扩展性有利于团队开发
类: 花括号独占一行
类中方法:华括号独占一行
类名:大驼峰
方法名:小驼峰
命名空间:后面空一行
常量名:大写,多个单词用下划线分割
数据库优化
架构:主从复制、读写分离
设计:存储引擎、字段类型、三范式
功能:缓存、分区、索引
缓存优化
好处:减少数据库查询,将数据缓存,加快查询数据
实现:
内存(memcache、redis)
文件(使用TP3.2的大S或大F方法 TP5中Cache类)
数据库
架构优化
负载均衡、集群、动静分离、读写分离、主从复制、CDN加速等
负载均衡:将用户的请求分配给多个服务器处理(轮着来,ip哈希)
集群:多个服务器实现相同的业务
分布式概念:多台服务器实现不同的业务
动静分离:将静态资源单独放一台服务器
主从复制:insert/update/delete到主服务器执行,所有从服务器检测主服务器有写入数据则自动同步
读写分离:通过PHP判断,如果是insert/update/delete交给主处理,如果是select交给从服务器
CDN加速:将静态资源缓存到用户所在城市,加快访问速度
Memcache
概括:就是一个数据库、但是数据存在内存中
作用:常用来做缓存服务器、将从数据库查询的数据缓存起来,减少数据库查询、加快查询速度
与mysql比较 :
共同点:都是C/S架构(客户端client/服务器server)
不同点:mysql存磁盘文件、memcache内存
不同点:mysql存储数据得先创建数据库再创建表、memcache直接以键值对形式存储
安装与配置Memcached(C/S结构)
服务端软件安装(win)
说明:关于memcached.exe相关参数
-l localhost 服务器ip地址(默认本机)
-p port 端口(11211)
-d install/uninstall/start 管理memcache /安装/卸载/开启
-m memory 内存大小,默认64M
步骤 1 : 将下载的memcache解压到 指定目录中
步骤 2 : 安装memcache 服务器 (命令 cd 进入所在目录 -d install)
步骤 3: 启动memcached服务 memcached -d start
步骤 4: 检测是否启用成功 netstat -ano 查看是否有11211端口占用
通过putty连接memcache 数据库
操作Memcached
命令手册 <a>http://www.runoob.com/memcached/memcached-tutorial.html</a>
增/改 (set)
语法:set 键 是否压缩 缓存时间 数据长度
键 - 对打长度不超过250字符
是否压缩 - 1-是,0-否(以空间换时间)
缓存时间 - 0-理论永久,其他-单位秒(注:最大存储时间30天)
数据长度 - 写数字,单位字节(注:回车输入具体内容)
说明: 键存在-则修改,键不存在-则创建
获取键值(get)
语法: get 键
添加键数据(add)
语法 : add 键 是否压缩 缓存时间 字节长度
如果键存在则报错
修改键数据 (replace )
语法 : replace 键 是否压缩 缓存时间 字节长度
注 : 只能修改不能添加(键不存在则报错)
删除键(delete)
语法 : delete 键
删除所有键 (flush_all)
语法 : flush_all
递增(incr)递减(decr)
语法:incr 键 数字
语法:decr 键 数字
说明:返回增长后的结果,键必须存在
查看当前Memcached 运行状态 (stats)
stats
其中的数据 :
cmd_get:get命令请求次数
cmd_set:set命令请求次数
cmd_flush:flush命令请求次数
curr_items:当前存储的数据总数
total_items:启动以来存储的数据总数(包括过期的)
在PHP操作Memcached
在php开启php_memcache.dll扩展
下载扩展,网站 : <a>http://pecl.php.net/package/memcache/3.0.8/windows</a>
可通过phpinfo() 查看php版本 位数 .. 下载对应的扩展
在将下载的扩展文件移到到php ext目录下
修改php.ini扩展文件
extension dir = "php路径/ext" (扩展文件路径)
extension = php_memcache.dll
重启apache 并通过phpinfo验证是否开启成功
操作
官方手册:<a>https://secure.php.net/manual/zh/memcache.set.php</a>
常用命令:
创建mem对象:$mem = new Memcache;
连接服务器: $mem->connect(服务ip地址,端口)
关闭服务器: $mem->close();
设置数据: $mem->set(键,值 [,是否压缩,缓存时间])
获取数据: $mem->get(键)
递增: $mem->incrment(键,数字);
递减: $mem->decrment(键,数字);
探究Memcache 能存储的数据类型
PHP 基本类型 : 字符串 布尔型 整型 浮点型
复合类型 数组 对象
特殊类型 null 资源(resource)
JS 基本类型 字符串 布尔 数值型(整型/浮点)
复合类型:数组 对象
特殊类型 unll undefined
验证存储的类型
<?php
//1.创建memcache对象
$mem = new memcache;
//2.连接服务
$mem->connect('localhost', 11211);
//3.设置服务
$rs = $mem->set('int1', 8);
var_dump($rs);
$rs = $mem->set('int2', 8.88);
var_dump($rs);
$rs = $mem->set('int3', 'php12');
var_dump($rs);
$rs = $mem->set('int4', true);
var_dump($rs);
echo '<hr />';
$rs = $mem->get('int1'); //int
var_dump($rs);
$rs = $mem->get('int2'); // float
var_dump($rs);
$rs = $mem->get('int3'); //string
var_dump($rs);
$rs = $mem->get('int4'); //bool
整型 浮点型 字符串 布尔型 都可以
<?php
//1.创建memcache对象
$mem = new memcache;
//2.连接服务
$mem->connect('localhost', 11211);
//3.设置服务
$data1 = array('name'=>'zhangsan','age'=>18);
class test { public $name = 123;}
$data2 = new test();
$rs = $mem->set('arr1', $data1);
var_dump($rs);
$rs = $mem->set('obj2', $data2);
var_dump($rs);
echo '<hr />';
$rs = $mem->get('arr1'); //arr
var_dump($rs);
echo '<br />';
$rs = $mem->get('obj2');//object
var_dump($rs);
die;
注: 虽然数组和对象都可以存入 但其实通过putty查看arr1 发现是字符串 从根本上memcache是不支持数组的 但是通过php底层将数组序列化为字符串存储 在反序列化以数组显示
<?php
//1.创建memcache对象
$mem = new memcache;
//2.连接服务
$mem->connect('localhost', 11211);
//3.设置服务
$data = fopen('xxxx', 'a+');
$rs = $mem->set('resource', $data);
var_dump($rs);
$rs = $mem->set('null1', null);
var_dump($rs);
echo '<hr />';
$rs = $mem->get('resource'); //int(0)
var_dump($rs);
echo '<br />';
$rs = $mem->get('null1'); //null
var_dump($rs);
die;
null 是支持的 但是资源型不支持 虽然存储进入返回结果是true 但是打印出来是整0(false)
小结:
整型 浮点型 布尔型 数组(序列化为字符串) 对象 null 是可以存储的
资源型可以存储在 get出为int 0 (false) 也就是存储无效
Memcache的相关算法
惰性过期机制( lazy expiration )
memcached 内部不会监视记录是否过期
检查记录是否过期.这种技术被称为惰性过期
好处 : 减少监控过期产生的开销
#关闭服务
#启动服务
#查看状态当前运行状态(stats)
#设置一个键
#有效时间内,再查看运行状态(stats)
#有效时间后,再查看运行状态(stats)
#获取(get)后查看运行状态(stats)
最近最少使用算法 (LRU:Least Recently Used)
缓存空间已满 采用LRU策略
将使用频率最低数据进行删除
Memcache常见问题
1 / 单个key键名最大长度为多少?
最大长度是250个字符
2 / 每个item 选项最大存储多少数据
单个键最大存储数据为 1M
3 / 键的最大存储时间
最大存储时间为30天 (3600*24*30) 内都可以
0 为理论上永久 重启后数据会消失 数据满后 LRU也可以移除
验证 :
<?php
#步骤1;创建memcache对象
$mem = new Memcache;
#步骤2:连接服务器
$mem->connect('localhost', 11211);
#步骤3:设置数据
$rs1 = $mem->set('data1', 111, 0, 3600*24*30 -1); //30天-1秒
$rs2 = $mem->set('data2', 111, 0, 3600*24*30); //30天
$rs3 = $mem->set('data3', 111, 0, 3600*24*30 + 1); //30天+1秒
var_dump($rs1);
var_dump($rs2);
var_dump($rs3);
echo '<hr />';
var_dump($mem->get('data1')); //正常
var_dump($mem->get('data2')); //正常 true
var_dump($mem->get('data3')); //false
memcached的cache机制是怎样的 ?
惰性过期机制 + 最近最少使用
惰性过期机制:不监控数据是否过期,每次获取判断
最近最少使用:磁盘空间占满,则删除最少使用的数据
memcache里面适合存储哪些数据
明确使用场景 : 缓存服务器
适合存储的数据
访问比较频繁的数据,安全性差的数据,丢失无所谓的数据。
数据更新,比较频繁的数据,比如用户的在线状态。
数据的单个键值不能太大,不要超过1Mb数据
分布式Memcache服务器
分布式原理
集群概念 : 多台服务器实现相同的业务
分布式概念 : 多台服务器实现不用的业务
使用PHP实现分布式Memcache
<?php
#步骤1;创建memcache对象
$mem = new Memcache;
//连接服务器$mem->connect('localhost', 11211);
#设置memcache服务器连接池
$mem->addServer("192.168.43.37", 11211);
$mem->addServer("192.168.43.51", 11211);
$mem->addServer("127.0.0.1", 11211);
#步骤3:设置数据
$rs1 = $mem->set('name', 'dog');
$rs2 = $mem->set('age', 18);
$rs3 = $mem->set('sex', 'boy');
var_dump($rs1);
var_dump($rs2);
var_dump($rs3);
echo '<hr />';
var_dump($mem->get('name'));
var_dump($mem->get('age'));
var_dump($mem->get('sex'));
分别在三台服务器查看 数据通过底层算法分配
Session入Memcache缓存
说明 : 通过修改session的存储方式,可以将session存储到内存中
方法一 : 永久修改
修该php.ini配置文件
重启apache ....
验证 :
<?php
#文件存储:会判断是否生成和session_id同名的文件(有-则读取文件,没有-则创建)
#内存存储:将session_id作为键(键已存在-则获取数据保存到$_SESSION数组中,不存在-则创建空数据)
session_start();
$_SESSION['php13_name'] = '苍苍';
$_SESSION['age'] = 18;
print_r($_SESSION);
echo '<hr />';
echo session_id(); //获取键 使用键查询数据
方法二 : 临时改变
通过ini_set 函数来临时设置session的存储方式
<?php
#通过ini_set函数临时更改配置文件
ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', 'tcp://127.0.0.1:11211,tcp://192.168.43.37:11211');
#session_start作用
#文件存储:会判断是否生成和session_id同名的文件(没有-则创建,有-则读取文件)
#内存存储:会判断是否生成和session_id同名的键 (没有-则创建,有-则读取文件)
session_start();
$_SESSION['php13_name'] = 'abc';
$_SESSION['age'] = 18;
print_r($_SESSION);
echo '<hr />';
echo session_id();
方法三 : 通过设置用户自定义会话存储函数
语法:session_set_save_handler(开启session机制函数,关闭session机制函数,
读取session数据函数,写入session函数,销毁session函数,后手过期session函数)
作用:自定义session会话处理方式,交给指定函数处理
自 PHP 5.4 开始,可以使用SessionHandlerInterface接口实现(注:接口里面都是抽象方法)
抽象类:有抽象方法的类就是抽象类
抽象方法:用关键词abstract并且没有函数体的方法
接口:特殊的抽象类(注:因为里面的方法都是抽象方法)
<?php
class MemcacheSessionHandler implements \SessionHandlerInterface
{
private $memcache;
//自定义用户更改session的处理方式
public static function start()
{
#更改session的存储方式
session_set_save_handler(new self, true);
#开启会发,会触发open方法
session_start();
}
public function open($savePath, $sessionName) {
$this->memcache = new Memcache;
$this->memcache->addServer('127.0.0.1', 11211);
return true;
}
public function close() {
return true;
}
public function read($sessionId) {
return $this->memcache->get( $sessionId ) ? : '';
}
public function write($sessionId, $data) {
return $this->memcache->set($sessionId, $data);
}
public function destroy( $sessionId ) {
//$this->memcache->delete($sessionId)
return true;
}
public function gc( $lifetime ) {
return true;
}
}
MemcacheSessionHandler::start();
$_SESSION['aa'] = 123;
$_SESSION['bb'] = 456;
echo session_id();
实现session共享
修改hosts文件,增加俩个域名解析到本机
hosts:
127.0.0.1 shop.test.com
127.0.0.1 blog.test.com
修改apache虚拟主机文件,增加二个站点 并重启apache
<VirtualHost _default_:80>
ServerName shop.test.com
DocumentRoot "E:\www2\a"
<Directory "E:\www2\a">
Options +Indexes +FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
<VirtualHost _default_:80>
ServerName blog.test.com
DocumentRoot "E:\www2\b"
<Directory "E:\www2\b">
Options +Indexes +FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
分别在站点目录下创建test.php文件 ,输入以下代码
<?php
ini_set('session.cookie_domain','test.com');
ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', 'tcp://127.0.0.1:11211,tcp://192.168.43.37:11211');
session_start();
$_SESSION['userinfo'] = array(
'id' => 1,
'username' => '小泽'
);
echo session_id() . '<hr />';
print_r($_SESSION);
//--------------------------------------------
<?php
ini_set('session.cookie_domain','test.com');
ini_set('session.save_handler', 'memcache');
ini_set('session.save_path', 'tcp://127.0.0.1:11211,tcp://192.168.43.37:11211');
session_start();
echo session_id() . '<hr />';
print_r($_SESSION);
单点登录
修改hosts文件,增加两个域名解析到本机
hosts:
127.0.0.1 shop.php.com
127.0.0.1 blog.php.com
修改apache虚拟主机文件,增加二个站点 并重启apache
<VirtualHost _default_:80>
ServerName shop.php.com
DocumentRoot "E:\www2\shop"
<Directory "E:\www2\shop">
Options +Indexes +FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
<VirtualHost _default_:80>
ServerName blog.php.com
DocumentRoot "E:\www2\blog"
<Directory "E:\www2\blog">
Options +Indexes +FollowSymLinks +ExecCGI
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
将写好的shop和blog目录放在分别配置的虚拟机主机中
例 : 访问 shop.php.com/shop/index.php
登录后,在去访问 blog.php.com/blog/index.php 发现已登录
原理 :
在shop..index.php 登录时将用户名保存到session
在访问blog..index.php 时检测session中是否有用户名 有则用
ini_set() — 为一个配置选项设置值
string ini_set ( string $varname , string $newvalue )
成功时返回旧的值,失败时返回 FALSE
记录访问者信息
如何避免重复记录用户ip地址
连接数据库插入数据 (瑕疵 : 用户量大出现重复IP )
<?php
//1.创建mem对象
$mem = new Memcache;
//2.连接服务器
$mem->connect('127.0.0.1', 11211);
$ip = '192.168.9.'.rand(0, 10);
//3.创建用户信息数据结构
$person_info = [
'ip' => $ip,
'page'=> 'http://baidu.com',
'referrer'=> 'http://baidu.com',
];
.
//4.判断是否记录IP:已记录-不管,未记录-入库
if( !$mem->get($ip) ) {
//入库
$filename = $ip . '_' . time();
$mem->add($ip, $person_info);
file_put_contents('./ip/'.$filename, 1);
}
加锁限制(排队操作避免出现重复ip插入)
<?php
//1.创建mem对象
$mem = new Memcache;
//2.连接服务器
$mem->connect('127.0.0.1', 11211);
$ip = '192.168.1.'.rand(0, 10);
//3.创建用户信息数据结构
$person_info = [
'ip' => $ip,
'page'=> 'http://baidu.com',
'referrer'=> 'http://baidu.com',
];
//加锁不成功的,进行排队等待(注:第一个用户进来未处理完,后面的用户循环等待)
while ( !$mem->add('lock', 'lock', 0, 0) ){
//进行休息
usleep(1000); //usleep单位是微秒,1秒 = 1000毫秒 ,1毫秒 = 1000微秒
}
//4.判断是否记录IP:已记录-不管,未记录-入库
if( !$mem->get($ip) ) {
//入库
$filename = $ip . '_' . time();
$mem->add($ip, $person_info);
//因为memcache不能查看所有键,所以通过创建文件方式检测是否有重复
file_put_contents('./'.$filename, 1);
}
//5.删除锁(注:操作完毕后,释放锁,让后面用户进来)
$mem->delete('lock');