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. lua学习笔记(一)

    lua是一种嵌入式的语言,首先安装. lua安装依赖于readline库,下载readline tar -zxvf readline-6.2.tar.gz cd readline-6.2 ./conf ...

  2. python包下载地址

    https://pypi.python.org/pypi http://www.lfd.uci.edu/~gohlke/pythonlibs/ 当在线安装安装不了时,需要将安装包下载到本地,进行本地p ...

  3. DataRow循环取出

    string strName = null; DataRow dRow = DB.GetDataRow(Sql语句); if (null != dRow) { foreach (DataRow ite ...

  4. 机器学习中的算法(1)-决策树模型组合之随机森林与GBDT

    版权声明: 本文由LeftNotEasy发布于http://leftnoteasy.cnblogs.com, 本文可以被全部的转载或者部分使用,但请注明出处,如果有问题,请联系wheeleast@gm ...

  5. NTFS u盘支持 - centos6

    uname -r bash-4.1# uname -r2.6.32-431.el6.x86_64 ---- http://repository.it4i.cz/mirrors/repoforge/re ...

  6. android 多线程下载

    看代码: public class MainActivity extends AppCompatActivity { private final String TAG = MainActivity.c ...

  7. 线程+IO流

    第十八天知识点总结 线程的停止: 1.停止一个线程,一般是通过一个变量来控制. 2.如果需要停止一个处于一个等待状态的线程,那么需要配合interrupt方法来完成 守护线程(后台线程):在一个进程中 ...

  8. 第三课:sea.js模块加载原理

    模块加载,其实就是把js分成很多个模块,便于开发和维护.因此加载很多js模块的时候,需要动态的加载,以便提高用户体验. 在介绍模块加载库之前,先介绍一个方法. 动态加载js方法: function l ...

  9. ajaxfileupload.js的简单使用

    上传文件 未选择任何文件 引入 <script src="../javaScript/ajaxfileupload.js"></script> <bu ...

  10. EMIF接口的寻址问题

    2014年8月26日,一个网友提出了一个关于EMIF访问地址很典型的问题,在此我阐述一下我的个人理解. 一.提出问题 他问的问题没有这么简单,但是差不多就是这种意思,我在晚上找个典型事例,如下: DS ...