韩奇峰高级讲师
多年实战工作经验曾参与制作宝马Usage Training项目、DMS项目,奥迪全 息投影项目,奔驰等多家汽车门户行业大型项目,负责UI设计、界面设计、3D模型制作、前端开发等职务。
从事设计行业多年,精通PhotoShop、UI设计、AfterEffects、Flash、 Actionscript、HTML、CSS、JavaScript、jQuery、资深动画设计师,设计作品曾获得全国动画设计三等奖。
课程讲解注重实战应用,对讲述知识点穿插案例制作,使课程内容更加接近 工作中实际的项目。授课风格注重实战经验分析,深受学生喜欢。
自学java与java培训该如何选择
从事IT培训行业这些年,身边经常有朋友来咨询,问现在是否可以加入这个高薪行业,Java学习难不难,需要哪些条件?
在软件开发设计行业当中,使用**多的编程语言就属Java软件开发了,很多人都想在Java编程开发领域寻求一份工作。
Java是当前世界上比较流行的计算机编程语言,Java彻底改变了人们的生活,国内的开发人才需求量仍然在不断的增长,主要原因还是在于移动互联网的快速发展,衍生出一大批的新生企业,创业公司,预计在未来5年内Java软件开发人才的需求将会远大于供给,其中java程序员、java工程师**为缺乏。
自学java与java培训是两个绕不开的话题,两者的区别,前者知识是自己搜寻的,买书看或者网上找视频看,而后者,技术知识是老师教的,作为一个普通大学生实际上任何人都能够自学,只不过聪明程度和坚持的时间长短不同,花费的时间长一点短一点而已。
学习关乎到时间和金钱,没有效果和质量的培训,其实是没有任何意义的。Java培训班有哪些呢?市面上的Java培训良莠不齐,选择时应该慎重些。Java致力于打造符合企业需求的全能型人才。课程定位定位中高级JAVA开发工程师,课程内容涉及面广,内容深。课程涵盖了Java、Web,数据库,企业流行框架,Java等企业**佳实践、云计算大数据课程、内容由浅入深,剖析原理,让学员掌握实用技术,做企业和社会需要的稀缺人才。
此外,Java培训将为你提供就业保障,开设有就业指导课,设有专门的就业指导老师,在毕业前期,毕业之际,就业老师会为你做专门的就业指导,更有职业素养课程,不仅将你打造成Java开发技术达人,更是一位懂得职场之道的合格就业者。还有丰富的Java学习视频免费为你提供学习。
Java开发体系结构介绍 :
Java开发体系结构介绍 :
1、类加载器:为程序的执行加载所需要的全部类。类加载器将本地文件系 统的类名空间与来自远程网络源的类名空间相分离,本地类总是首先被加载,以增加安全性。当全部类被加载后,可执行文件的存储器格式被确定。这 时,特定的存储器地址被分配给符号引用并创建检索表格。由于存储器格式在运行时出现,因而Java解释器增加了保护以防止对限制代码区的非法进入 。
2、字节代码校验器:基于代码的规范包括语法语义的检查以及如上所述的 安全性检查。
3、Java运行时解释器:它是JVM的核心内容,实现把抽象的字节码指令映射 到本地系统平台下的库引用或指令。
4、API类库:实现标准Java平台API的一系列可执行代码。
5、硬件本地平台接口:提供对底层系统平台资源库调用的接口。
memcached源码分析-----item过期失效处理以及LRU爬虫
>
转载:http://blog.csdn.net/luotuo44/article/details/42963793
温馨提示:本文用到了一些可以在启动memcached设置的全局变量。关于这些全局变量的含义可以参考《memcached启动参数详解》。对于这些全局变量,处理方式就像《如何阅读memcached源代码》所说的那样直接取其默认值。另外, 本文会提及LRU队列,关于LRU队列的介绍可以参考《LRU队列与item结构体》。
过期失效处理:
一个item在两种情况下会过期失效:1.item的exptime时间戳到了。2.用户使用flush_all命令将全部item变成过期失效的。读者可能会说touch命令也可以使得一个item过期失效,其实这也属于前面说的**种情况。
超时失效:
对于**种过期失效,memcached的使用懒惰处理:不主动检测一个item是否过期失效。当worker线程访问这个item时,才检测这个item的exptime时间戳是否到了。比较简单,这里就先不贴代码,后面会贴。
flush_all命令:
第二种过期失效是用户flush_all命令设置的。flush_all会将所有item都变成过期失效。所有item是指哪些item?因为多个客户端会不断地往memcached插入item,所以必须要明白所有item是指哪些。是以worker线程接收到这个命令那一刻为界?还是以删除那一刻为界?
当worker线程接收到flush_all命令后,会用全局变量settings的oldest_live成员存储接收到这个命令那一刻的时间(准确地说,是worker线程解析得知这是一个flush_all命令那一刻再减一),代码为settings.oldest_live =current_time - 1;然后调用item_flush_expired函数锁上cache_lock,然后调用do_item_flush_expired函数完成工作。
[cpp] view plain copy void do_item_flush_expired(void) { int i; item *iter, *next; if (settings.oldest_live == 0) return; for (i = 0; i < LARGEST_ID; i ) { for (iter = heads[i]; iter != NULL; iter = next) { if (iter->time != 0 && iter->time >= settings.oldest_live) { next = iter->next; if ((iter->it_flags & ITEM_SLABBED) == 0) { do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey)); } } else { /* We ve hit the first old item. Continue to the next queue. */ break; } } } } do_item_flush_expired函数内部会遍历所有LRU队列,检测每一个item的time成员。检测time成员是合理的。如果time成员小于settings.oldest_live就说明该item在worker线程接收到flush_all命令的时候就已经存在了(time成员表示该item的**后一次访问时间)。那么就该删除这个item。
这样看来memcached是以worker线程接收到flush_all命令那一刻为界的。等等等等,看清楚一点!!在do_item_flush_expired函数里面,不是当item的time成员小于settings.oldest_live时删除这个item,而是大于的时候才删除。从time成员变量的意义来说,大于代表什么啊?有大于的吗?奇怪!@#@&¥
实际上memcached是以删除那一刻为界的。那settings.oldest_live为什么要存储worker线程接收到flush_all命令的时间戳?为什么又要判断iter->time是否大于settings.oldest_live呢?
按照一般的做法,在do_item_flush_expired函数中直接把哈希表和LRU上的所有item统统删除即可。这样确实是可以达到目标。但在本worker线程处理期间,其他worker线程完全不能工作(因为do_item_flush_expired的调用者已经锁上了cache_lock)。而LRU队列里面可能有大量的数据,这个过期处理过程可能会很长。其他worker线程完全不能工作是难于接受的。
memcached的作者肯定也意识到这个问题,所以他就写了一个奇怪的do_item_flush_expired函数,用来加速。do_item_flush_expired只会删除少量特殊的item。如何特殊法,在后面代码注释中会解释。对于其他大量的item,memcached采用懒惰方式处理。只有当worker线程试图访问该item,才检测item是否已经被设置为过期的了。事实上,无需对item进行任何设置就能检测该item是否为过期的,**settings.oldest_live变量即可。这种懒惰和前面**种item过期失效的处理是一样的。
现在再看一下do_item_flush_expired函数,看一下特殊的item。
[cpp] view plain copy void do_item_flush_expired(void) { int i; item *iter, *next; if (settings.oldest_live == 0) return; for (i = 0; i < LARGEST_ID; i ) { for (iter = heads[i]; iter != NULL; iter = next) { //iter->time == 0的是lru爬虫item,直接忽略 //一般情况下iter->time是小于settings.oldest_live的。但在这种情况下 //就有可能出现iter->time >= settings.oldest_live : worker1接收到 //flush_all命令,并给settings.oldest_live赋值为current_time-1。 //worker1线程还没来得及调用item_flush_expired函数,就被worker2 //抢占了cpu,然后worker2往lru队列插入了一个item。这个item的time //成员就会满足iter->time >= settings.oldest_live if (iter->time != 0 && iter->time >= settings.oldest_live) { next = iter->next; if ((iter->it_flags & ITEM_SLABBED) == 0) { //虽然调用的是nolock,但本函数的调用者item_flush_expired //已经锁上了cache_lock,才调用本函数的 do_item_unlink_nolock(iter, hash(ITEM_key(iter), iter->nkey)); } } else { //因为lru队列里面的item是根据time降序排序的,所以当存在一个item的time成员 //小于settings.oldest_live,剩下的item都不需要再比较了 break; } } } }懒惰删除:
现在阅读item的懒惰删除。注意代码中的注释。
可以看到,在查找到一个item后就要检测它是否过期失效了。失效了就要删除之。
除了do_item_get函数外,do_item_alloc函数也是会处理过期失效item的。do_item_alloc函数不是删除这个过期失效item,而是占为己用。因为这个函数的功能是申请一个item,如果一个item过期了那么就直接霸占这个item的那块内存。下面看一下代码。
flush_all命令是可以有时间参数的。这个时间和其他时间一样取值范围是 1到REALTIME_MAXDELTA(30天)。如果命令为flush_all 100,那么99秒后所有的item失效。此时settings.oldest_live的值为current_time 100-1,do_item_flush_expired函数也没有什么用了(总不会被抢占CPU99秒吧)。也正是这个原因,需要在do_item_get里面,加入settings.oldest_live<= current_time这个判断,防止过早删除了item。
这里明显有一个bug。假如客户端A向服务器提交了flush_all10命令。过了5秒后,客户端B向服务器提交命令flush_all100。那么客户端A的命令将失效,没有起到任何作用。
LRU爬虫:
前面说到,memcached是懒惰删除过期失效item的。所以即使用户在客户端使用了flush_all命令使得全部item都过期失效了,但这些item还是占据者哈希表和LRU队列并没有归还给slab分配器。
LRU爬虫线程:
有没有办法强制清除这些过期失效的item,不再占据哈希表和LRU队列的空间并归还给slabs呢?当然是有的。memcached提供了LRU爬虫可以实现这个功能。
要使用LRU爬虫就必须在客户端使用lru_crawler命令。memcached服务器根据具体的命令参数进行处理。
memcached是用一个专门的线程负责清除这些过期失效item的,本文将称这个线程为LRU爬虫线程。默认情况下memcached是不启动这个线程的,但可以在启动memcached的时候添加参数-o lru_crawler启动这个线程。也可以**客户端命令启动。即使启动了这个LRU爬虫线程,该线程还是不会工作。需要另外发送命令,指明要对哪个LRU队列进行清除处理。现在看一下lru_crawler有哪些参数。
LRU爬虫命令:
如果要启动LRU爬虫主动删除过期的item,需要这样做:首先使用lru_crawler enable命令启动一个LRU爬虫线程。然后使用lru_crawler tocrawl num命令确定每一个LRU队列**多检查num-1个item。**后使用命令lru_crawler crawl <classid,classid,classid|all> 指定要处理的LRU队列。lru_crawler sleep可以不设置,如果要设置那么可以在lru_crawler crawl命令之前设置即可。
启动LRU爬虫线程:
现在来看一下LRU爬虫是怎么工作的。先来看一下memcached为LRU爬虫定义了哪些全局变量。
可以看到worker线程在接收到” lru_crawler enable”命令后会启动一个LRU爬虫线程。这个LRU爬虫线程还没去执行任务,因为还没有指定任务。命令"lru_crawler tocrawl num"并不是启动一个任务。对于这个命令,worker线程只是简单地把settings.lru_crawler_tocrawl赋值为num。
清除失效item:
命令”lru_crawler crawl<classid,classid,classid|all>”才是指定任务的。该命令指明了要对哪个LRU队列进行清理。如果使用all那么就是对所有的LRU队列进行清理。
在看memcached的清理代码之前,先考虑一个问题:怎么对一条LRU队列进行清理?
**直观的做法是先加锁(锁上cache_lock),然后遍历一整条LRU队列。直接判断LRU队列里面的每一个item即可。明显这种方法有问题。如果memcached有大量的item,那么遍历一个LRU队列耗时将太久。这样会妨碍worker线程的正常业务。当然我们可以考虑使用分而治之的方法,每次只处理几个item,多次进行,**终达到处理整个LRU队列的目标。但LRU队列是一个链表,不支持随机访问。处理队列中间的某个item,需要从链表头或者尾依次访问,时间复杂度还是O(n)。
伪item:
memcached为了实现随机访问,使用了一个很巧妙的方法。它在LRU队列尾部插入一个伪item,然后驱动这个伪item向队列头部前进,每次前进一位。
这个伪item是全局变量,LRU爬虫线程无须从LRU队列头部或者尾部遍历就可以直接访问这个伪item。**这个伪item的next和prev指针,就可以访问真正的item。于是,LRU爬虫线程无需遍历就可以直接访问LRU队列中间的某一个item。
下面看一下lru_crawler_crawl函数,memcached会在这个函数会把伪item插入到LRU队列尾部的。当worker线程接收到lru_crawler crawl<classid,classid,classid|all>命令时就会调用这个函数。因为用户可能要求LRU爬虫线程清理多个LRU队列的过期失效item,所以需要一个伪item数组。伪item数组的大小等于LRU队列的个数,它们是一一对应的。
[cpp] view plain copy //这个结构体和item结构体长得很像,是伪item结构体,用于LRU爬虫 typedef struct { struct _stritem *next; struct _stritem *prev; struct _stritem *h_next; /* hash chain next */ rel_time_t time; /* least recent access */ rel_time_t exptime; /* expire time */ int nbytes; /* size of data */ unsigned short refcount; uint8_t nsuffix; /* length of flags-and-length string */ uint8_t it_flags; /* ITEM_* above */ uint8_t slabs_clsid;/* which slab class we re in */ uint8_t nkey; /* key length, w/terminating null and padding */ uint32_t remaining; /* Max keys to crawl per slab per invocation */ } crawler; static crawler crawlers[LARGEST_ID]; static int crawler_count = 0;//本次任务要处理多少个LRU队列 //当客户端使用命令lru_crawler crawl <classid,classid,classid|all>时, //worker线程就会调用本函数,并将命令的第二个参数作为本函数的参数 enum crawler_result_type lru_crawler_crawl(char *slabs) { char *b = NULL; uint32_t sid = 0; uint8_t tocrawl[POWER_LARGEST]; //LRU爬虫线程进行清理的时候,会锁上lru_crawler_lock。直到完成所有 //的清理任务才会解锁。所以客户端的前一个清理任务还没结束前,不能 //再提交另外一个清理任务 if (pthread_mutex_trylock(&lru_crawler_lock) != 0) { return CRAWLER_RUNNING; } pthread_mutex_lock(&cache_lock); //解析命令,如果命令要求对某一个LRU队列进行清理,那么就在tocrawl数组 //对应元素赋值1作为标志 if (strcmp(slabs, "all") == 0) {//处理全部lru队列 for (sid = 0; sid < LARGEST_ID; sid ) { tocrawl[sid] = 1; } } else { for (char *p = strtok_r(slabs, ",", &b); p != NULL; p = strtok_r(NULL, ",", &b)) { //解析出一个个的sid if (!safe_strtoul(p, &sid) || sid < POWER_SMALLEST || sid > POWER_LARGEST) {//sid越界 pthread_mutex_unlock(&cache_lock); pthread_mutex_unlock(&lru_crawler_lock); return CRAWLER_BADCLASS; } tocrawl[sid] = 1; } } //crawlers是一个伪item类型数组。如果用户要清理某一个LRU队列,那么 //就在这个LRU队列中插入一个伪item for (sid = 0; sid < LARGEST_ID; sid ) { if (tocrawl[sid] != 0 && tails[sid] != NULL) { //对于伪item和真正的item,可以用nkey、time这两个成员的值区别 crawlers[sid].nbytes = 0; crawlers[sid].nkey = 0; crawlers[sid].it_flags = 1; /* For a crawler, this means enabled. */ crawlers[sid].next = 0; crawlers[sid].prev = 0; crawlers[sid].time = 0; crawlers[sid].remaining = settings.lru_crawler_tocrawl; crawlers[sid].slabs_clsid = sid; //将这个伪item插入到对应的lru队列的尾部 crawler_link_q((item *)&crawlers[sid]); crawler_count ;//要处理的LRU队列数加一 } } pthread_mutex_unlock(&cache_lock); //有任务了,唤醒LRU爬虫线程,让其执行清理任务 pthread_cond_signal(&lru_crawler_cond); STATS_LOCK(); stats.lru_crawler_running = true; STATS_UNLOCK(); pthread_mutex_unlock(&lru_crawler_lock); return CRAWLER_OK; }
现在再来看一下伪item是怎么在LRU队列中前进的。先看一个伪item前进图。
从上面的图可以看到,伪item**与前驱节点交换位置实现前进。如果伪item是LRU队列的头节点,那么就将这个伪item移出LRU队列。函数crawler_crawl_q完成这个交换操作,并且返回交换前伪item的前驱节点(当然在交换后就变成伪item的后驱节点了)。如果伪item处于LRU队列的头部,那么就返回NULL(此时没有前驱节点了)。crawler_crawl_q函数里面那些指针满天飞,这里就不贴出代码了。
上面的图,虽然伪item遍历了LRU队列,但并没有删除某个item。这样画,一来是为了好看,二来遍历LRU队列不一定会删除item的(item不过期失效就不会删除)。
清理item:
前面说到,可以用命令lru_crawler tocrawl num指定每个LRU队列**多只检查num-1个item。看清楚点,是检查数,不是删除数,而且是num-1个。首先要调用item_crawler_evaluate函数检查一个item是否过期,是的话就删除。如果检查完num-1个,伪item都还没有到达LRU队列的头部,那么就直接将这个伪item从LRU队列中删除。下面看一下item_crawler_thread函数吧。
[cpp] view plain copy static void *item_crawler_thread(void *arg) { int i; pthread_mutex_lock(&lru_crawler_lock); while (do_run_lru_crawler_thread) { //lru_crawler_crawl函数和stop_item_crawler_thread函数会signal这个条件变量 pthread_cond_wait(&lru_crawler_cond, &lru_crawler_lock); while (crawler_count) {//crawler_count表明要处理多少个LRU队列 item *search = NULL; void *hold_lock = NULL; for (i = 0; i < LARGEST_ID; i ) { if (crawlers[i].it_flags != 1) { continue; } pthread_mutex_lock(&cache_lock); //返回crawlers[i]的前驱节点,并交换crawlers[i]和前驱节点的位置 search = crawler_crawl_q((item *)&crawlers[i]); if (search == NULL || //crawlers[i]是头节点,没有前驱节点 //remaining的值为settings.lru_crawler_tocrawl。每次启动lru //爬虫线程,检查每一个lru队列的多少个item。 (crawlers[i].remaining && --crawlers[i].remaining < 1)) { //检查了足够多次,退出检查这个lru队列 crawlers[i].it_flags = 0; crawler_count--;//清理完一个LRU队列,任务数减一 crawler_unlink_q((item *)&crawlers[i]);//将这个伪item从LRU队列中删除 pthread_mutex_unlock(&cache_lock); continue; } uint32_t hv = hash(ITEM_key(search), search->nkey); //尝试锁住控制这个item的哈希表段级别锁 if ((hold_lock = item_trylock(hv)) == NULL) { pthread_mutex_unlock(&cache_lock); continue; }&nb