<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; cache</title>
	<atom:link href="https://www.phppan.com/tag/cache/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sat, 04 Apr 2026 01:19:58 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>PHP缓存之APC-简介、存储结构和操作</title>
		<link>https://www.phppan.com/2012/06/php-opcode-cache-apc-1/</link>
		<comments>https://www.phppan.com/2012/06/php-opcode-cache-apc-1/#comments</comments>
		<pubDate>Mon, 04 Jun 2012 01:18:02 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[APC]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[opcode]]></category>
		<category><![CDATA[PHP缓存]]></category>
		<category><![CDATA[缓存]]></category>

		<guid isPermaLink="false">http://www.phppan.com/?p=1700</guid>
		<description><![CDATA[APC简介 APC，全称是Alternative PHP Cache，官方翻译叫”可选PHP缓存”。它为我们提 [&#8230;]]]></description>
				<content:encoded><![CDATA[<h2>APC简介</h2>
<p>APC，全称是Alternative PHP Cache，官方翻译叫”可选PHP缓存”。它为我们提供了缓存和优化PHP的中间代码的框架。  APC的缓存分两部分:系统缓存和用户数据缓存。</p>
<ul>
<li><strong>系统缓存</strong> 它是指APC把PHP文件源码的编译结果缓存起来，然后在每次调用时先对比时间标记。如果未过期，则使用缓存的中间代码运行。默认缓存  3600s(一小时)。但是这样仍会浪费大量CPU时间。因此可以在php.ini中设置system缓存为永不过期(apc.ttl=0)。不过如果这样设置，改运php代码后需要重启WEB服务器。目前使用较多的是指此类缓存。</li>
<li><strong>用户数据缓存</strong> 缓存由用户在编写PHP代码时用apc_store和apc_fetch函数操作读取、写入的。如果数据量不大的话，可以一试。如果数据量大，使用类似memcache此类的更加专著的内存缓存方案会更好。</li>
</ul>
<p>在APC中我们也可以享受APC带来的缓存大文件上传进度的特性，需要在php.ini中将apc.rfc1867设为1，并且在表单中加一个隐藏域  APC_UPLOAD_PROGRESS，这个域的值可以随机生成一个hash，以确保唯一。之前的一篇文章<a href="http://www.phppan.com/2012/04/php-upload-progress/"><strong>PHP文件上传进度的实现原理</strong></a>中有对此更为细致的说明。</p>
<h2>APC与PHP内核的交互</h2>
<p>APC是作为一个扩展添加到PHP体系中的。因此，按照PHP的扩展规范，它会有PHP_MINIT_FUNCTION、PHP_MSHUTDOWN_FUNCTION、PHP_RINIT_FUNCTION、PHP_RSHUTDOWN_FUNCTION等宏定义的函数。在PHP_MINIT_FUNCTION(apc)中有调用apc_module_init中，并且在此函数中通过重新给zend_compile_file赋值以替换系统自带的编译文件过程，从而将APC自带的功能和相关数据结构插入到整个PHP的体系中。</p>
<p>这里会有一个问题，如果出现多个zend_compile_file的替换操作呢？在实际使用过程，这种情况会经常出现，比如当我们使用xdebug扩展时，又使用了apc，此时PHP是怎么处理的呢？不管是哪个扩展，在使用zend_compile_file替换时，都会有一个自己的compile_file函数（替换用），还有一个作用域在当前扩展的，一个旧的编译函数：old_compile_file。相当于每个扩展当中都保留了一个对于前一个编译函数的引用，形成一个单向链表。并且，所有最终的op_array都是在新的zend_compile_file中通过old_compile_file生成，即都会沿着这条单向链表，将编译的最终过程传递到PHP的zend_compile_file实现。在传递过程中，每经过一个节点，这些节点都会增加一些属于自己的数据结构，以实现特定的需求。</p>
<h3>APC内部存储结构</h3>
<p>在APC内部，对于系统缓存和用户缓存分别是以两个全局变量存储，从代码逻辑层面就隔离了两种缓存，当然，这两种存储的实现过程和数据结构是一样的，它们都是apc_cache_t类型，如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">&nbsp;
    <span style="color: #808080; font-style: italic;">/* {{{ struct definition: apc_cache_t */</span>
    <span style="color: #993333;">struct</span> apc_cache_t <span style="color: #009900;">&#123;</span>
        <span style="color: #993333;">void</span><span style="color: #339933;">*</span> shmaddr<span style="color: #339933;">;</span>                共享缓存的本地进程地址
        cache_header_t<span style="color: #339933;">*</span> header<span style="color: #339933;">;</span>       缓存头，存储在共享内存中
        slot_t<span style="color: #339933;">**</span> slots<span style="color: #339933;">;</span>               缓存的槽数组，存储在共享内存中
        <span style="color: #993333;">int</span> num_slots<span style="color: #339933;">;</span>                存储在缓存中的槽个数
        <span style="color: #993333;">int</span> gc_ttl<span style="color: #339933;">;</span>                   GC列表中槽的最大生存时间
        <span style="color: #993333;">int</span> ttl<span style="color: #339933;">;</span>                      如果对槽的访问时间大于这个TTL，需要则移除这个槽
        apc_expunge_cb_t expunge_cb<span style="color: #339933;">;</span>  <span style="color: #808080; font-style: italic;">/* cache specific expunge callback to free up sma memory */</span>
        uint has_lock<span style="color: #339933;">;</span>                为可能存在的造成同一进程递归锁而存在的标记 <span style="color: #808080; font-style: italic;">/* flag for possible recursive locks within the same process */</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span>
    <span style="color: #808080; font-style: italic;">/* }}} */</span></pre></td></tr></table></div>

<p>对于一个缓存，apc_cache_t类型的变量是其入口，它包含了这个缓存的一些全局信息。每个缓存都会有多个缓存槽，包含在slots字段中，slots的个数包含在num_slots字段，槽的过程时间控制在于ttl字段。对于用户缓存和系统缓存，默认情况下系统缓存数量为1000，实际上APC创建了1031个，也就是说默认情况下APC最少可以缓存1031个文件的中间代码。当然这个值还需要考虑内存大小，计算slot的key后的分布等等。更多的关于缓存的统计信息存储在header字段中，header字段结构为cache_header_t，如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">&nbsp;
<span style="color: #993333;">struct</span> cache_header_t <span style="color: #009900;">&#123;</span>
        apc_lck_t lock<span style="color: #339933;">;</span>             读写锁，独占阻塞缓存锁
        apc_lck_t wrlock<span style="color: #339933;">;</span>           写锁，为防止缓存爆满
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> num_hits<span style="color: #339933;">;</span>     缓存命中数
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> num_misses<span style="color: #339933;">;</span>   缓存未命中数
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> num_inserts<span style="color: #339933;">;</span>  插入缓存总次数
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> expunges<span style="color: #339933;">;</span>     清除的总次数
        slot_t<span style="color: #339933;">*</span> deleted_list<span style="color: #339933;">;</span>       指向被清除的槽的链表
        time_t start_time<span style="color: #339933;">;</span>          以上计数器被重置的时间
        zend_bool busy<span style="color: #339933;">;</span>             当apc在忙于清除缓存时告诉客户端此时状态的标记
        <span style="color: #993333;">int</span> num_entries<span style="color: #339933;">;</span>            统计的实体数
        <span style="color: #993333;">size_t</span> mem_size<span style="color: #339933;">;</span>            统计的被用于缓存的内存大小
        apc_keyid_t lastkey<span style="color: #339933;">;</span>        用户缓存最后一写入的key
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>一个缓存包含多个slots，每个slot都是一个slot结构体的变量，其结构如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">&nbsp;
    <span style="color: #993333;">struct</span> slot_t <span style="color: #009900;">&#123;</span>
        apc_cache_key_t key<span style="color: #339933;">;</span>        槽的key
        apc_cache_entry_t<span style="color: #339933;">*</span> value<span style="color: #339933;">;</span>   槽的值
        slot_t<span style="color: #339933;">*</span> next<span style="color: #339933;">;</span>               链表中的下一个槽
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> num_hits<span style="color: #339933;">;</span>     这个bucket的命中数<span style="color: #808080; font-style: italic;">/* number of hits to this bucket */</span>
        time_t creation_time<span style="color: #339933;">;</span>       槽的初始化时间
        time_t deletion_time<span style="color: #339933;">;</span>       槽从缓存被移除的时间 <span style="color: #808080; font-style: italic;">/* time slot was removed from cache */</span>
        time_t access_time<span style="color: #339933;">;</span>         槽的最后一次被访问的时间
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>每个槽包含一个key，以apc_cache_key_t结构体存储；包含一个值，以apc_cache_entry_t结构体存储。如下：</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">&nbsp;
    <span style="color: #993333;">typedef</span> <span style="color: #993333;">struct</span> apc_cache_key_t apc_cache_key_t<span style="color: #339933;">;</span>
    <span style="color: #993333;">struct</span> apc_cache_key_t <span style="color: #009900;">&#123;</span>
        apc_cache_key_data_t data<span style="color: #339933;">;</span>
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">long</span> h<span style="color: #339933;">;</span>              <span style="color: #808080; font-style: italic;">/* pre-computed hash value */</span>
        time_t mtime<span style="color: #339933;">;</span>                 <span style="color: #808080; font-style: italic;">/* the mtime of this cached entry */</span>
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">char</span> type<span style="color: #339933;">;</span>
        <span style="color: #993333;">unsigned</span> <span style="color: #993333;">char</span> md5<span style="color: #009900;">&#91;</span><span style="color: #0000dd;">16</span><span style="color: #009900;">&#93;</span><span style="color: #339933;">;</span>        <span style="color: #808080; font-style: italic;">/* md5 hash of the source file */</span>
    <span style="color: #009900;">&#125;</span><span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>结构说明如下：</p>
<ul>
<li>data字段  apc_cache_key_data_t类型，一个联合体，存储key的关联信息，比如对于系统缓存，其可能会存储文件的路径或OS的文件device/inode；对于用户缓存可能会存储用户给定的标识或标识长度。</li>
<li>h字段 文件完整路径或用户给定的标识的hash值，使用的hash算法为PHP自带的time33算法；或者文件所在device和inode的和</li>
<li>mtime字段 缓存实体的修改时间</li>
<li>type字段 APC_CACHE_KEY_USER：用户缓存; APC_CACHE_KEY_FPFILE：系统缓存（有完整路径）;  APC_CACHE_KEY_FILE: 系统缓存（需要查找文件）</li>
<li>md5字段  文件内容的MD5值，这个字段与前面四个字段不同，它是可选项，可以通过配置文件的apc.file_md5启用或禁用。并且这个值是在初始化实体时创建的。看到这里源文件的md5值，想起之前做过一个关于MySQL数据表中访问路径查询的优化，开始时通过直接查询路径字段，在数据量达到一定级别时，出现了就算走索引还是会很慢的情况，各种方案测试后，采用了以新增一个关于访问路径的md5值查询解决。</li>
</ul>
<p>除了入口，APC在最终的数据存储上对于系统缓存和用户缓存也做了区分，在_apc_cache_entry_value_t分别对应file和user。</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="c" style="font-family:monospace;">&nbsp;
    <span style="color: #993333;">typedef</span> <span style="color: #993333;">union</span> _apc_cache_entry_value_t <span style="color: #009900;">&#123;</span>
        <span style="color: #993333;">struct</span> <span style="color: #009900;">&#123;</span>            
            <span style="color: #993333;">char</span> <span style="color: #339933;">*</span>filename<span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* absolute path to source file */</span>
            zend_op_array<span style="color: #339933;">*</span> op_array<span style="color: #339933;">;</span>     存储中间代码的op_array
            apc_function_t<span style="color: #339933;">*</span> functions<span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* array of apc_function_t's */</span>
            apc_class_t<span style="color: #339933;">*</span> classes<span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* array of apc_class_t's */</span>
            <span style="color: #993333;">long</span> halt_offset<span style="color: #339933;">;</span> <span style="color: #808080; font-style: italic;">/* value of __COMPILER_HALT_OFFSET__ for the file */</span>
        <span style="color: #009900;">&#125;</span> file<span style="color: #339933;">;</span>                         file结构体 系统缓存所用空间，包括文件名，，
        <span style="color: #993333;">struct</span> <span style="color: #009900;">&#123;</span>
            <span style="color: #993333;">char</span> <span style="color: #339933;">*</span>info<span style="color: #339933;">;</span>
            <span style="color: #993333;">int</span> info_len<span style="color: #339933;">;</span>
            zval <span style="color: #339933;">*</span>val<span style="color: #339933;">;</span>
            <span style="color: #993333;">unsigned</span> <span style="color: #993333;">int</span> ttl<span style="color: #339933;">;</span>           过期时间
        <span style="color: #009900;">&#125;</span> user<span style="color: #339933;">;</span>                         ser结构体 用户缓存所用空间
    <span style="color: #009900;">&#125;</span> apc_cache_entry_value_t<span style="color: #339933;">;</span></pre></td></tr></table></div>

<p>如图所示：<br />
<div id="attachment_1703" style="width: 672px" class="wp-caption aligncenter"><a href="http://www.phppan.com/wp-content/uploads/2012/06/apc.jpg"><img src="http://www.phppan.com/wp-content/uploads/2012/06/apc.jpg" alt="APC缓存存储结构" title="apc" width="662" height="580" class="size-full wp-image-1703" /></a><p class="wp-caption-text">APC缓存存储结构</p></div></p>
<h3>初始化</h3>
<p>在APC扩展的模块初始化函数（PHP_MINIT_FUNCTION(apc)）中，APC会调用apc_module_init函数初始化缓存所需要的全局变量，如系统缓存则调用apc_cache_create创建缓存全局变量apce_cache，默认情况下会分配1031个slot所需要的内存空间，用户缓存也会调用同样的方法创建缓存，存储在另一个全局变量apc_user_cache，默认情况下会分配4099个内存空间。这里分配的空间的个数都是素数，在APC的代码中有一个针对不同数量的素数表primes(在apc_cache.c文件)。素数的计算是直接遍历素数表，找到表中第一个比需要分配的个数大的素数。</p>
<h3>缓存key生成规则</h3>
<p>APC的缓存中的每个slot都会有一个key，key是 apc_cache_key_t结构体类型，除了key相关的属性，关键是h字段的生成。  h字段决定了此元素落于slots数组的哪一个位置。对于用户缓存和系统缓存，其生成规则不同。</p>
<ul>
<li>用户缓存通过apc_cache_make_user_key函数生成key。通过用户传递进来的key字符串，依赖PHP内核中的hash函数（PHP的hashtable所使用的hash函数：zend_inline_hash_func），生成h值。</li>
<li>系统缓存通过apc_cache_make_file_key函数生成key。通过APC的配置项apc.stat的开关来区别对待不同的方案。在打开的情况下，即  apc.stat= On  时，如果被更新则自动重新编译和缓存编译后的内容。此时的h值是文件的device和inode相加所得的值。在关闭的情况下，即apc.stat=off时，当文件被修改后，如果要使更新的内容生效，则必须重启Web服务器。此时h值是根据文件的路径地址生成，并且这里的路径是绝对路径。即使你是使用的相对路径，也会查找PG(include_path)定位文件，以取得绝对路径，所以使用绝对路径会跳过检查，可以提高代码的效率。</li>
</ul>
<h3>添加缓存过程</h3>
<p>以用户缓存为例，apc_add函数用于给APC缓存中添加内容。如果key参数为字符串中，APC会根据此字符串生成key，如果key参数为数组，APC会遍历整个数组，生成key。根据这些key，APC会调用_apc_store将值存储到缓存中。由于这是用户缓存，当前使用的缓存为apc_user_cache。执行写入操作的是apc_cache_make_user_entry函数，其最终调用apc_cache_user_insert执行遍历查询和写入操作。与此对应，系统缓存使用apc_cache_insert执行写入操作，其最终都会调用_apc_cache_insert。</p>
<p>不管是用户缓存还是系统缓存，大体的执行过程类似，步骤如下：</p>
<ol>
<li>通过求余操作，定位当前key的在slots数组中的位置： cache-&gt;slots[key.h % cache-&gt;num_slots];</li>
<li>在定位到slots数组中的位置后，遍历当前key对应的slot链表，如果存在slot的key和要写入的key匹配或slot过期，清除当前slot。</li>
<li>在最后一个slot的后面插入新的slot。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2012/06/php-opcode-cache-apc-1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
