1.为什么要使用memcache

由于网站的高并发读写需求,传统的关系型数据库开始出现瓶颈,例如:

1)对数据库的高并发读写:

关系型数据库本身就是个庞然大物,处理过程非常耗时(如解析SQL语句,事务处理等)。如果对关系型数据库进行高并发读写(每秒上万次的访问),那么它是无法承受的。

2)对海量数据的处理:

对于大型的SNS网站,每天有上千万次的数据产生(如twitter, 新浪微博)。对于关系型数据库,如果在一个有上亿条数据的数据表种查找某条记录,效率将非常低。

使用memcache能很好的解决以上问题。

在实际使用中,通常把数据库查询的结果保存到Memcache中,下次访问时直接从memcache中读取,而不再进行数据库查询操作,这样就在很大程度上减少了数据库的负担。

保存在memcache中的对象实际放置在内存中,这也是memcache如此高效的原因。

2.memcache的安装和使用

这个网上有太多教程了,不做赘言。

3.基于libevent的事件处理

libevent是个程序库,它将Linux的epoll、BSD类操作系统的kqueue等事件处理功能 封装成统一的接口。即使对服务器的连接数增加,也能发挥O(1)的性能。

memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。

参考:

4.memcache使用实例:

  1. <?php
  2. $mc = new Memcache();
  3. $mc->connect('127.0.0.1', 11211);
  4. $uid = (int)$_GET['uid'];
  5. $sql = "select * from users where uid='uid' ";
  6. $key = md5($sql);
  7. if(!($data = $mc->get($key))) {
  8. $conn = mysql_connect('localhost', 'test', 'test');
  9. mysql_select_db('test');
  10. $result = mysql_fetch_object($result);
  11. while($row = mysql_fetch_object($result)) {
  12. $data[] = $row;
  13. }
  14. $mc->add($key, $datas);
  15. }
  16. var_dump($datas);
  17. ?>

5.memcache如何支持高并发(此处还需深入研究)

memcache使用多路复用I/O模型,如(epoll, select等),传统I/O中,系统可能会因为某个用户连接还没做好I/O准备而一直等待,知道这个连接做好I/O准备。这时如果有其他用户连接到服务器,很可能会因为系统阻塞而得不到响应。

而多路复用I/O是一种消息通知模式,用户连接做好I/O准备后,系统会通知我们这个连接可以进行I/O操作,这样就不会阻塞在某个用户连接。因此,memcache才能支持高并发。

此外,memcache使用了多线程机制。可以同时处理多个请求。线程数一般设置为CPU核数,这研报告效率最高。

6.使用Slab分配算法保存数据

slab分配算法的原理是:把固定大小(1MB)的内存分为n小块,如下图所示:

slab分配算法把每1MB大小的内存称为一个slab页,每次向系统申请一个slab页,然后再通过分隔算法把这个slab页分割成若干个小块的chunk(如上图所示),然后把这些chunk分配给用户使用,分割算法如下(在slabs.c文件中):

(注:memcache的github项目地址:https://github.com/wusuopubupt/memcached)

  1. /**
  2. * Determines the chunk sizes and initializes the slab class descriptors
  3. * accordingly.
  4. */
  5. void slabs_init(const size_t limit, const double factor, const bool prealloc) {
  6. int i = POWER_SMALLEST - 1;
  7. unsigned int size = sizeof(item) + settings.chunk_size;
  8. mem_limit = limit;
  9. if (prealloc) {
  10. /* Allocate everything in a big chunk with malloc 通过malloc的方式申请内存*/
  11. mem_base = malloc(mem_limit);
  12. if (mem_base != NULL) {
  13. mem_current = mem_base;
  14. mem_avail = mem_limit;
  15. } else {
  16. fprintf(stderr, "Warning: Failed to allocate requested memory in"
  17. " one large chunk.\nWill allocate in smaller chunks\n");
  18. }
  19. }
  20. memset(slabclass, 0, sizeof(slabclass));
  21. while (++i < POWER_LARGEST && size <= settings.item_size_max / factor) {
  22. /* Make sure items are always n-byte aligned  注意这里的字节对齐*/
  23. if (size % CHUNK_ALIGN_BYTES)
  24. size += CHUNK_ALIGN_BYTES - (size % CHUNK_ALIGN_BYTES);
  25. slabclass[i].size = size;
  26. slabclass[i].perslab = settings.item_size_max / slabclass[i].size;
  27. size *= factor;//以1.25为倍数增大chunk
  28. if (settings.verbose > 1) {
  29. fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
  30. i, slabclass[i].size, slabclass[i].perslab);
  31. }
  32. }
  33. power_largest = i;
  34. slabclass[power_largest].size = settings.item_size_max;
  35. slabclass[power_largest].perslab = 1;
  36. if (settings.verbose > 1) {
  37. fprintf(stderr, "slab class %3d: chunk size %9u perslab %7u\n",
  38. i, slabclass[i].size, slabclass[i].perslab);
  39. }
  40. /* for the test suite:  faking of how much we've already malloc'd */
  41. {
  42. char *t_initial_malloc = getenv("T_MEMD_INITIAL_MALLOC");
  43. if (t_initial_malloc) {
  44. mem_malloced = (size_t)atol(t_initial_malloc);
  45. }
  46. }
  47. if (prealloc) {
  48. slabs_preallocate(power_largest);
  49. }
  50. }

上面代码中的slabclass是一个类型为slabclass_t结构的数组,其定义如下:

  1. typedef struct {
  2. unsigned int size;      /* sizes of items */
  3. unsigned int perslab;   /* how many items per slab */
  4. void **slots;           /* list of item ptrs */
  5. unsigned int sl_total;  /* size of previous array */
  6. unsigned int sl_curr;   /* first free slot */
  7. void *end_page_ptr;         /* pointer to next free item at end of page, or 0 */
  8. unsigned int end_page_free; /* number of items remaining at end of last alloced page */
  9. unsigned int slabs;     /* how many slabs were allocated for this class */
  10. void **slab_list;       /* array of slab pointers */
  11. unsigned int list_size; /* size of prev array */
  12. unsigned int killing;  /* index+1 of dying slab, or zero if none */
  13. size_t requested; /* The number of requested bytes */
  14. } slabclass_t;

借用别人的一张图说明slabclass_t结构:

由分割算法的源代码可知,slab算法按照不同大小的chunk分割slab页,而不同大小的chunk以factor(默认是1.25)倍增大。

使用memcache -u root -vv 命令查看内存分配情况(8字节对齐):

找到大小最合适的chunk分配给请求缓存的数据:

  1. /*
  2. * Figures out which slab class (chunk size) is required to store an item of
  3. * a given size.
  4. *
  5. * Given object size, return id to use when allocating/freeing memory for object
  6. * 0 means error: can't store such a large object
  7. */
  8. unsigned int slabs_clsid(const size_t size) {
  9. int res = POWER_SMALLEST;// 初始化为最小的chunk
  10. if (size == 0)
  11. return 0;
  12. while (size > slabclass[res].size) //逐渐增大chunk size,直到找到第一个比申请的size大的chunk
  13. if (res++ == power_largest)     /* won't fit in the biggest slab */
  14. return 0;
  15. return res;
  16. }

内存分配:

(此处参考:http://slowsnail.com.cn/?p=20

  1. static void *do_slabs_alloc(const size_t size, unsigned int id) {
  2. slabclass_t *p;
  3. void *ret = NULL;
  4. item *it = NULL;
  5. if (id < POWER_SMALLEST || id > power_largest) {//判断id是否会导致slabclass[]数组越界
  6. MEMCACHED_SLABS_ALLOCATE_FAILED(size, 0);
  7. return NULL;
  8. }
  9. p = &slabclass[id];//获取slabclass[id]的引用
  10. assert(p->sl_curr == 0 || ((item *)p->slots)->slabs_clsid == 0);//判断slabclass[id]是否有剩余的chunk
  11. if (! (p->sl_curr != 0 || do_slabs_newslab(id) != 0)) {//如果slabclass[id]中已经没有空余chunk并且试图向系统申请一个“页”(slab)的chunk失败,则返回NULL
  12. /* We don't have more memory available */
  13. ret = NULL;
  14. } else if (p->sl_curr != 0) {//slabclass[id]的空闲链表中还有chunk,则直接将其分配出去
  15. it = (item *)p->slots;//获取空闲链表的头指针
  16. p->slots = it->next;//将头结点指向下一个结点(取下头结点)
  17. if (it->next) it->next->prev = 0;//将新头结点的prev指针置空
  18. p->sl_curr--;//减少slabclass[id]空闲链表中的chunk计数
  19. ret = (void *)it;//将头结点赋给ret指针
  20. }
  21. if (ret) {//请求成功
  22. p->requested += size;//更新slabclass[id]所分配的内存总数
  23. MEMCACHED_SLABS_ALLOCATE(size, id, p->size, ret);
  24. } else {
  25. MEMCACHED_SLABS_ALLOCATE_FAILED(size, id);
  26. }
  27. return ret;
  28. }

do_slabs_allc()函数首先尝试从slot列表(被回收的chunk)中获取可用的chunk,如果有可用的就返回,否则从空闲的chunk列表中获取可用的chunk并返回。

删除过期item:

延迟删除过期item到查找时进行,可以提高memcache的效率,因为不必每时每刻检查过期item,从而提高CPU工作效率

使用LRU(last recently used)算法淘汰数据:

  1. /*
  2. * try to get one off the right LRU
  3. * don't necessariuly unlink the tail because it may be locked: refcount>0
  4. * search up from tail an item with refcount==0 and unlink it; give up after 50
  5. * tries
  6. */
  7. if (tails[id] == 0) {
  8. itemstats[id].outofmemory++;
  9. return NULL;
  10. }
  11. for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
  12. if (search->refcount == 0) { //refount==0的情况,释放掉
  13. if (search->exptime == 0 || search->exptime > current_time) {
  14. itemstats[id].evicted++;
  15. itemstats[id].evicted_time = current_time - search->time;
  16. STATS_LOCK();
  17. stats.evictions++;
  18. STATS_UNLOCK();
  19. }
  20. do_item_unlink(search);
  21. break;
  22. }
  23. }
  24. it = slabs_alloc(ntotal, id);
  25. if (it == 0) {
  26. itemstats[id].outofmemory++;
  27. /* Last ditch effort. There is a very rare bug which causes
  28. * refcount leaks. We've fixed most of them, but it still happens,
  29. * and it may happen in the future.
  30. * We can reasonably assume no item can stay locked for more than
  31. * three hours, so if we find one in the tail which is that old,
  32. * free it anyway.
  33. */
  34. tries = 50;
  35. for (search = tails[id]; tries > 0 && search != NULL; tries--, search=search->prev) {
  36. if (search->refcount != 0 && search->time + 10800 < current_time) { //最近3小时没有被访问到的情况,释放掉
  37. itemstats[id].tailrepairs++;
  38. search->refcount = 0;
  39. do_item_unlink(search);
  40. break;
  41. }
  42. }
  43. it = slabs_alloc(ntotal, id);
  44. if (it == 0) {
  45. return NULL;
  46. }
  47. }

从item列表的尾部开始遍历,找到refcount==0的chunk,调用do_item_unlink()函数释放掉,另外,search->time+10800<current_time(即最近3小时没有被访问过的item),也释放掉--这就是LRU算法的原理。

深入理解Memcache原理 [转]的更多相关文章

  1. node.js学习(三)简单的node程序&&模块简单使用&&commonJS规范&&深入理解模块原理

    一.一个简单的node程序 1.新建一个txt文件 2.修改后缀 修改之后会弹出这个,点击"是" 3.运行test.js 源文件 使用node.js运行之后的. 如果该路径下没有该 ...

  2. Atitit 图像处理 深刻理解梯度原理计算.v1 qc8

    Atitit 图像处理 深刻理解梯度原理计算.v1 qc8 1.1. 图像处理  梯度计算  基本梯度 内部梯度 外部梯度 方向梯度1 2. 图像梯度就是图像边缘吗?2 1.1. 图像处理  梯度计算 ...

  3. 深入理解PHP原理之变量作用域

    26 Aug 08 深入理解PHP原理之变量作用域(Scope in PHP)   作者: Laruence(   ) 本文地址: http://www.laruence.com/2008/08/26 ...

  4. 深入理解PHP原理之变量分离/引用

    19 Sep 08 深入理解PHP原理之变量分离/引用(Variables Separation) 作者: Laruence(   ) 本文地址: http://www.laruence.com/20 ...

  5. 《深入理解mybatis原理》 MyBatis事务管理机制

    MyBatis作为Java语言的数据库框架,对数据库的事务管理是其很重要的一个方面.本文将讲述MyBatis的事务管理的实现机制. 首先介绍MyBatis的事务Transaction的接口设计以及其不 ...

  6. 《深入理解mybatis原理》 Mybatis初始化机制具体解释

    对于不论什么框架而言.在使用前都要进行一系列的初始化,MyBatis也不例外. 本章将通过下面几点具体介绍MyBatis的初始化过程. 1.MyBatis的初始化做了什么 2. MyBatis基于XM ...

  7. 《深入理解mybatis原理》 MyBatis的架构设计以及实例分析

    作者博客:http://blog.csdn.net/u010349169/article/category/2309433 MyBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简 ...

  8. 轻松理解Redux原理及工作流程

    轻松理解Redux原理及工作流程 Redux由Dan Abramov在2015年创建的科技术语.是受2014年Facebook的Flux架构以及函数式编程语言Elm启发.很快,Redux因其简单易学体 ...

  9. 深入理解mybatis原理, Mybatis初始化SqlSessionFactory机制详解(转)

    文章转自http://blog.csdn.net/l454822901/article/details/51829785 对于任何框架而言,在使用前都要进行一系列的初始化,MyBatis也不例外.本章 ...

随机推荐

  1. 分析一个类似于jquery的小框架

    在网上下了一个类似于jQuery的小框架,分析源码,看看怎么写框架. 选择器Select //用沙箱闭包其整个代码,只有itcast和I暴漏在全局作用域 (function( window , und ...

  2. 珍惜每一滴水(kbmmw 中的内存调试)

    作为一个服务器端的应用,最基本的要求就是稳定,当然要做一个稳定的服务器端,需要涉及到很多方面, 内存泄露就是稳定的一个致命杀手,因为服务器的物理内存是有限的,即使一个功能有很小的内存泄露,经过 长时间 ...

  3. Node.js 中MongoDB的基本接口操作

    Node.js 中MongoDB的基本接口操作 连接数据库 安装mongodb模块 导入mongodb模块 调用connect方法 文档的增删改查操作 插入文档 方法: db.collection(& ...

  4. vim 大全用法

    vim中常用设置和操作: 在Linux系统下: 打开vi 文件: 0 数字0,跳转至行首    ^ 跳转至行第一个非空字符    $ 跳转至行尾 vim 括号匹配跳转操作: ctrl+] 跳转至函数或 ...

  5. c++截取英文和汉字(单双字节)混合字符串

    在C++里截取字符串可以使用CString.Mid(),可是这个函数只能按英文(单字节)来截取, 如果是汉字可能就要计算好字符个数,如果是汉字和英文混合,那就没辙了. 可是恰好我需要这样一个函数,于是 ...

  6. alhpa阶段回顾

    说明:本次课程,大家按照我下面列出来的问题进行回顾和反思,总结alhpa阶段以来的得失.每个组都要回答这些问题.由组长或者组员来回答都可以.目的依然是让大家养成软工的一些习惯和思维方式. 1.最初的需 ...

  7. User mode and kernel mode

    [User mode and kernel mode] 参考:https://msdn.microsoft.com/en-us/library/windows/hardware/ff554836(v= ...

  8. mongo管理工具

    启动 D:\Program Files\MongoDB\Server\3.4\bin\mongod.exe --dbpath d:\data\db 还原 D:\Program Files\MongoD ...

  9. CC1310电源管脚

    对于48pin脚的CC1310而言,属于电源类的管脚如下: 上述电源类管脚的关系如下: 1 VDDS类管脚 VDDS类管脚包括VDDS.VDDS2.VDDS3和VDDS_DCDC四个管脚.其中VDDS ...

  10. C++函数重载和函数模板

    1.函数重载 这是小菜鸟写的一个例子. 函数重载应该注意以下几点: 1.1重载函数有类似的功能: 1.2只能以参数的类型(形参个数和类型)来重载函数, int max(int a,int b);flo ...