1.什么是MemCache官方介绍:MemCache是??一个免费、开源、高性能、分布式的分布式内存对象缓存系统,用于动态Web应用程序,以减轻数据库的负载。它通过在内存中缓存数据和对象来减少数据库读取次数,从而提高网站访问速度。MemCache是一个存储键值对的HashMap。它是一种用于内存中任意数据(如字符串、对象等)的键值存储。数据可以来自数据库调用、API调用或页面呈现的结果。MemCache的设计理念是小而强大。其简单的设计促进了快速部署,易于开发,并解决了面对大规模数据缓存的许多问题。开放的API使MemCache能够在Java、C/C++/C#、Perl、Python、PHP、Ruby和大多数流行的编程语言中使用。补充:MemCache是??一个高性能的分布式内存对象缓存系统,用于各种动态应用,减轻数据库的负担。它通过在内存中缓存数据和对象来减少数据库读取次数,从而加速动态的、数据库驱动的应用程序。MemCache会在内存中开辟一块空间,建立一个统一的庞大哈希表,可以用来存储各种格式的数据,包括图片、视频、文件、数据库检索结果等。 MemCache和MemCached:MemCache是??本项目的名称,MemCached是服务器端主程序的名称。2.MemCached使用场景通常,我们在访问量大的网站和应用程序中使用MemCache来缓解数据库的压力,提高网站和应用程序的响应速度。在应用程序中,我们通常在以下节点使用MemCached:经常访问的数据库数据(身份令牌、主页动态)经常访问的查询条件和结果作为Session存储(提高Session访问性能)Pagecache更新频繁非重要数据(访问量、点击量)大量热点数据的常见工作流程(如下图):客户端请求数据,检查MemCached中是否有对应的数据。写入MemCached返回下次请求的数据,结束(注:更新数据库时,缓存在MemCached中的数据库数据要同时更新)三、MemCached工作原理终端启动后,监听客户端的请求以守护进程的形式出现。启动时可以指定监听IP(服务器内网ip/外网ip)、端口号(这样做分布式测试时,可以在一台服务器上启动多个不同端口号的MemCached进程)、大小使用的内存等关键参数。一旦启动,该服务将始终可用。为了提高性能,MemCached缓存的数据全部存储在MemCached管理的内存中,所以重启服务器后缓存的数据会被清除,不支持持久化。(1)在Memcached内存管理的内存结构体slab_class中,存储了一组chunk大小相同的slab。每个板包含几个页面。页面的默认大小为1M。如果slab大小为100M,则每个包含100页。page包含若干个chunk,chunk是数据的实际存储单元,每个slab中chunk的大小是相同的内存分配方式Memcached使用slab分配机制来分配和管理内存,它将分配的内存分为对于特定长度的内存块,将大小相同的内存块分成若干组。存储数据时,根据key值的大小匹配slab的大小,找到最近的slab存储,存在空间浪费。传统的内存管理方式是在使用完malloc分配的内存后,通过free回收内存。这种方式容易产生内存碎片,降低操作系统的内存管理效率。slab在存储数据时,首先需要申请内存,申请内存是以页为单位的。所以当第一个数据放入的时候,不管大小是多少,都会给slab分配一个1M的page。申请到一个page后,slab会根据chunk的大小划分这个page的内存,使其成为一个chunk数组,最后从chunk数组中选择一个来存储数据。示例:value在MemCache中的存储位置由value的大小决定,value会存储在最接近chunk大小的slab中,例如slab[1]的chunk大小为88字节,而chunkslab[2]的大小为112字节,slab[3]的chunk大小为144字节(默认情况下,相邻slab中的chunk基本以1.25的比例增长,可以在MemCache启动时用-f指定),那么一个100字节的值将被放置在2号slab中。内存回收方式当数据容量用完MemCached分配的内存时,会根据LRU(LeastRecentlyUsed)算法清理无效的缓存数据(放入数据时可以设置过期时间),或者最近最少使用的缓存数据将被清理,然后插入新数据。在LRU中,MemCached使用延迟过期策略。它不监控存储的key/vlue对是否过期,而是在获取key值时检查记录的时间戳,检查key/value对空间是否过期。这减少了服务器上的负载。需要注意的是,如果MemCache启动时不加-M,则表示禁止LRU。在这种情况下,如果内存不够用,就会报OutOfMemory错误。对于MemCache的内存分配和回收算法,我总结了三点:MemCache的内存分配chunk会浪费内存,88字节的值分配在一个128字节(后面是大的)的chunk中,30个字符丢失section,但是这也避免了管理内存碎片的问题。MemCache的LRU算法不是针对全世界的,而是针对slab的。应该可以理解为什么MemCache中存储的值的大小是有限的,因为当一个新的数据到来时,slab会先以页为单位申请一块内存,最大申请的内存量只有1M,所以这个值不能大于1M。(2)MemCached分布为了提高MemCached的存储容量和性能,我们应用的客户端可能会对应多个MemCached服务器来提供Service,这就是MemCached的分布。分布式实现原理目前版本的MemCached是用C语言实现的,采用单进程、单线程、异步I/O、基于事件(event_based)的服务方式。Libevent用作事件通知实现。多台服务器可以协同工作,但是这些服务器之间保存的数据是不同的,不通信(对比一下,比如JBossCache,当一台服务器有缓存数据更新时,会通知集群中的其他机器更新Cache或者清除缓存数据),每个服务器只管理自己的数据。客户端通过IP地址和端口号指定服务器,将要缓存的数据以key->value对的形式保存在服务器上。将key的值进行hash转换,根据hash值将value传递给对应的特定服务器。当需要获取对象数据时,也是根据key进行的。先对key进行hash,通过得到的值判断保存在哪个服务器上,然后向服务器发送请求。客户端只需要知道hash(key)的值存储在哪个服务器上即可。当从一个MemCached集群中存储/取出一个key/value时,MemCached客户端程序会根据一定的算法计算存储到哪个服务器上,然后将key/value存储到这个服务器上。换句话说,访问数据分为两个步骤。第一步是选择服务器,第二步是访问数据。分布式算法分析求余算法:先获取key的整型hash值(即key字符串的HashCODE值,什么是HashCode),然后除以服务器个数,根据余数确定接入服务器.这种方法简单有效。但是当memcached服务器增加或者减少时,几乎所有的缓存都会失效。哈希算法:先计算MemCached服务器的哈希值,分发到0到2的32次方的圈中,然后用同样的方法计算存储数据key的哈希值,映射到圈中,最后从数据映射的位置顺时针搜索,将数据保存到找到的第一个服务器上。如果数据超过2的32次方,仍然没有找到服务器,则将数据保存到第一个MemCached服务器。如果增加了MemCached服务器,则只影响环上逆时针方向第一个服务器上的key。(3)MemCached线程管理MemCached网络模型是典型的单进程多线程模型。它使用libevent来处理网络请求。主进程负责为工作线程分配新的连接,工作线程负责处理连接。有点类似于负载均衡。通过主进程分发给相应的工作线程。MemCached默认有7个线程,4个主工作线程,3个辅助线程。线程可以分为以下四种:主线程,负责MemCached服务器初始化,监听TCP、UnixDomain连接请求;工作线程池,MemCached默认4个工作线程,可通过启动参数修改,负责处理TCP、UDP、Unix域套接字链接上的请求;assoc维护线程,在MemCached内存中维护着一张巨大的哈希表,该线程负责哈希表的动态增长;slabMaintenancethread,即内存管理模块维护线程,负责类中slab的平衡,这个线程可以在MemCached启动选项中禁用。更多内容请看:MemCache-网络线程模型-原码分析4.MemCached的特点和限制MemCache中可以存储的item数据量没有限制,只要内存足够,一个MemCache使用的最大内存32位机单进程2G。上一篇文章中多次提到,但是64位机并没有限制key最大250字节。单个项目的最大数据不能超过这个长度存储。最大数据为1MB。MemCache服务器存储超过1MB的数据是不安全的。例如,如果你知道某个MemCache节点,你可以直接telnet到它,并使用flush_all立即使现有的键值对失效。因此,最好在内网环境下配置MemCache服务器,并使通过防火墙的可访问客户端无法遍历所有MemCache。item,因为这个操作比较慢,会阻塞其他操作。MemCached的高性能来源于两阶段的哈希结构:第一阶段在客户端,通过Hash算法根据Key值计算出一个节点;第二阶段在服务器端,通过内部的哈希算法,找到真正的物品返回给客户端。从实现的角度来看,MemCache是一个非阻塞的、基于事件的服务器程序。当MemCache设置添加某个Key值时,传入一个expiry为0表示该Key值永久有效,30天后该Key值也会过期,查看memcache.c源码:#defineREALTIME_MAXDELTA60*60*24*30staticrel_time_trealtime(consttime_texptime){if(exptime==0)return0;if(exptime>REALTIME_MAXDELTA){if(exptime<=process_started)return(rel_time_t)1;返回(rel_time_t)(exptime-process_started);}else{return(rel_time_t)(exptime+current_time);}}
