观摩了这篇文章后https://www.cnblogs.com/zyblog-coder/p/15013804.html 学到了php还有操作文件扩展属性的扩展
快速安装了一下
sudo apt-get install xattr sudo pecl install xattr
然后编辑php.ini加一下扩展开启
extension=xattr
然后查看了一下手册学到了这几个函数
xattr_get($file) 获取文件存储单个key的值 xattr_list($file) 获取文件存储的key列表 xattr_remove($file, $key) 移除文件存储的某个key xattr_set($file, $key, $value) 设置文件存储的某个key的值 xattr_supported($file) 查看文件是否支持xattr
然后迅速实践了一下,发现在我这个ubuntu系统里面,单个文件,如果只设置一个key,最大长度能写入4040个字符
<?php $file = "test.log"; is_file($file) || touch($file); for ($i=0; $i < 5000; $i++) { $value = str_repeat("1", $i); $result = xattr_set($file, '0', $value); if(!$result){ var_dump($i);//打印出来的是4041 说明只能存储到4040 exit; break; } }
随着你设置的key越多,那单个key能存储的上限也就逐渐降低下来。
如果设置2个key,平均每个key最大长度能写入2008个字符
如果设置10个key,平均每个key最大长度能写入384个字符
如果设置26个key,平均每个key最大长度能写入136个字符
由此可见,如果你想尽可能多的存储数据,可以只存一个key,然后把数据放入序列化存储,如果你的数据不大想快速的获取数据,你可以把每个key都存储起来,比如一张表的所有字段(当然要求这些字段不是那种很长的text blob类型)
这个扩展属性的优势在于其写入速度很快基本上写入单个key ,value存储4040个字符的时候是0.03-0.04毫秒,读取的时候也基本上是0.04毫秒。
读取使用代码
<?php $file = "test.log"; is_file($file) || touch($file); $attr_key_list = xattr_list($file); foreach ($attr_key_list as $attr_key) { $new_data[$attr_key] = xattr_get($file, $attr_key); }
但是随着你的key越来越多比如你存储50个key,每个key存储很短的20个字符的小数据,你会发现写入这50个字段耗时0.2-0.4毫秒左右,读取也是0.2-0.3毫秒左右
同等情况下50个key,每个key存储20个字符,json_encode 序列化然后file_put_contents 耗时 0.16 毫秒,file_get_contents 读取内容然后json_decode耗时0.046毫秒左右
同等情况下50个key,每个key存储20个字符,serialize 序列化然后file_put_contents 耗时 0.15毫秒,file_get_contents 读取内容然后unserialize耗时0.031毫秒左右
同等情况下50个key,每个key存储20个字符,serialize 序列化然后file_put_contents 耗时 0.18毫秒,file_get_contents 读取内容然后unserialize耗时0.027毫秒左右
这里可以做一个表格
存储方式 |
1个key序列化并写入(毫秒) |
1个key读取并反序列化(毫秒) | 10个key序列化并写入(毫秒) | 10个key读取并反序列化(毫秒) | 50个key序列化并写入(毫秒) | 50个key读取并反序列化(毫秒) |
xattr key存储 | 0.03 | 0.02 | 0.07 | 0.23 | 0.2-0.4 | 0.2-0.3 |
json+file_put_contents | 0.15 | 0.03 | 0.16 | 0.028 | 0.16 | 0.046 |
serialize+file_put_contents | 0.14 | 0.021 | 0.11 | 0.017 | 0.15 | 0.031 |
igbinary_serialize+file_put_contents | 0.165 | 0.04 | 0.14 | 0.018 | 0.18 | 0.027 |
我们发现多key的时候 xattr并没有什么优势,只有在少量的几个key的时候它的优势才是非常明显的,而且我发现一个奇怪的现象file_put_contents对已有文件写入反而慢,如果之前没有文件使用file_put_contents反而快。
而xattr的操作是已有文件比较快,没有文件的话,还得自己手动新建一个文件就会比较慢。
这说明了这种xattr存储非常适合少量key数据的写入,因为总体来看写入速度是很快的,其读取速度随着key的增加而变慢,毕竟这里用了循环读取key,而序列化的方式还是读取一次文件+序列化一次文件,其成本没有太大开销,所以在key增多的时候反而更加有优势。
比较经典的案例场景是
场景1:有一个总列表数据做了文件缓存,但是有时候业务场景下列表页面只想知道其数量count,这种情况下,去file_get_contents unserialize/json_decode 再count 是很不划算的,因为那样拿到了全部的数据但是又只用到count,此时可以预先在该列表数据文件扩展属性上面增加 total_count 属性,单个key读取其属性返回,根本不用操作文件本身内容,大大提高了速度,可以把一个1ms左右(打开读取并序列化取count一般这样的操作一个文件要1ms)的操作提速50倍。
场景2:还可以用于少量信息及时存储场景,比如有数据需要及时存储投递,但是不需要后续处理,后续处理可以交给定时器异步,那么就可以用这样的方式生成一个临时文件并存储少量4000个(左右)字符内的数据,及时返回写入结果,让后续的处理交给另外的程序来解决,可以将写操作省下大量时间,毕竟写单个小文件是要0.15ms的。(更别提redis连接connect set等一系列操作了)
让我们更加极端一些,就只写入非常简单的字符串。
写入
1. attr写入
<?php $file = "test.log"; $start_time = microtime(true); xattr_set($file, 'test_key', 'test_data'); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n";//耗费时间0.03ms
2. file_put_contents 文件写入
<?php $file = "test.log"; $start_time = microtime(true); file_put_contents($file, 'test_data'); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n";//耗费时间0.15ms 即使是这么小的文件写入也要0.15ms
3. redis 写入 (为了排除连接connect耗费的时间,我特意把它拿出计算时间的区间外了,只记录一次set时间)
<?php $file = "test.log"; $redis = new Redis(); $redis->connect("127.0.0.1", 6379); $start_time = microtime(true); $redis->set($file, "test_data"); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n"; //首次set耗费时间7.5ms 后面有了key之后再set耗费时间0.1299ms
看到了吗,写入速度是file写入的5倍!
如果你沉迷与内存数据库写入,认为redis非常快,那你错了,redis是很快,但是再快也是有上限的,毕竟虽然大家都说每秒50000次写入,这意味着每次写入要0.02ms,可是我们在本机测试的时候并不会达到这个速度的,我本地写入redis 每秒也就30000次写入撑死了,其速度远低于xattr写入速度。
由此可见,作为小数据内容存储并写入其速度是很快的,很适合高速写入。
读取
1. attr读取
<?php $file = "test.log"; $start_time = microtime(true); $attr = xattr_get($file, 'test_key'); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n";//耗费时间0.026ms
2. file_get_contents 文件读取
<?php $file = "test.log"; $start_time = microtime(true); $attr = file_get_contents($file); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n";//耗费时间0.034ms
3. redis读取 (同样为了排除连接connect耗费的时间,我特意把它拿出计算时间的区间外了,只记录一次get时间)
<?php $file = "test.log"; $redis = new Redis(); $redis->connect("127.0.0.1", 6379); $start_time = microtime(true); $attr = $redis->get($file); $end_time = microtime(true); echo ($end_time - $start_time) * 1000 . " ms \r\n";//get耗费时间0.12ms
读取速度快于file读取23%!而且如果是刚刚描述的场景1下面文件有大量数据,但是只想获得一个count,那就更快了,毕竟文件越大,file_get_contents 越慢,但是获取attr的速度几乎不变。
我们看到redis的读取速度并不是那么理想,也许是我电脑给redis的配置不是最优,但是根据大家所记录的redis get 每秒钟10万次请求来看那就是单次读取0.01ms,我认为这个10万次应该不是单进程读取而是能支撑的多线程多进程读取总量,可是我们往往无法在本机达到那样的速度,这是确实让人遗憾。
总之,综合来看,xattr速度是非常的高啊,非常适合特殊场景进行使用,推荐大家尝试!
另外给这个https://www.cnblogs.com/zyblog-coder/p/15013804.html作者点赞,是他给我提供了一个这样的知识点和灵感