码迷,mamicode.com
首页 > 系统相关 > 详细

memcached 原理及详细配置

时间:2016-07-05 12:30:35      阅读:737      评论:0      收藏:0      [点我收藏+]

标签:互联网   数据库   程序   memcached   

memcached:

  • 数据结构模型:

    • 结构化数据:关系型数据库;遵循前3个范式是最基本的条件;在各种场景中都是瓶颈;

    • 半结构化数据:xml,json,…(NoSQL是非关系型的数据库统称)等格式的数据;需要半结构化数据存储;

    • 非结构化数据:需要非结构化数据存储;比较常见是文件系统; 
      互联网公司中有句话叫:缓存为王;

比如在myslq中存一个数据,检索起来非常麻烦,先要查索引,有可能还有进行多表查询,基于多表查询要做多次IO才能把数据加载到内存中,在内存中还要进行排序,排序后还要根据指定的范围进行检索,这样一来二去,中间出现多次IO包括磁盘IO、网络IO,接着还需要大量的时钟周期才能完成,相当繁琐;

  • 如果把检索的结果缓存下来,所有的缓存都是k/v数据,k是查询语句或访问资源的的url,v是对应数据所存下来的文件对应的md5码或哈希码,每一个文件的名字也是哈希码,它还可分级存放;这种数据结构,使得在k/v当中检索一个文件,首先整个过程不需大量的程序去处理,而且k/v数据中检索效率是恒定的是O(1)标准;


  • BIG O标准:衡量一个程序随着时间复杂度的机制;

    • O(1)标准:恒定不变;

    • O(n) 线性复杂度;

    • O(logn) 对数复杂度;

    • O(n^2) 抛物线复杂度;

    • O(2^n) 指数复杂度;

  • k/v存储数据:是O(1)标准;

    • redis优势: 
      支持字典、列表、有序列表等等较多(丰富)的数据结构,在较重的负载下,有更好的表现;redis也被称为数据结构服务器;但是,所有数据都是在内存中操作;

      redis在缓存管理的算法上优于memcached;在较重负载下可能有更好的表现; 
      是持久存储,支持主从复制;支持各种横向扩展机制;从3以后也有复制集群; 
      所以,memcached只有在特别需要纯缓存的场景中,而且期望这个服务维护的足够简单时才使用;

    • memcached:是数据缓存(如果memcached挂了,数据也就挂了),把数据存储于内存中;数据类型简单,应用领域比较窄; 【一秒钟支持的事务数(性能、带宽都足够多的情况下)可达到几万到几十万次】

    • redis:是数据存储,也是把数据存储于内存中,但周期性地将数据同步于辅存(磁盘)上,意味着支持持久存储;【一秒钟支持的事务数可达到几百万次】

因此,memcached的一个data cache(数据缓存),redis是一个store(存储);

  • memcached: 
    Livejournal组织旗下的Danga Interactive公司所研发

  • 特性:

    • 是k/v缓存:可缓存所有的可序列化数据;存储时每个存储项由:key,value,flag(标志位),expire time(超时时间)进行定义;

    • 功能特性很诡异:功能实现一半依赖于服务端,一半依赖于客户端:是旁挂式缓存;

    • memcached又是一种分布式缓存:本来一个完整数据,被分散的存储到不同的主机上,互不通信的分布式集群;

    • O(1)的执行效率;

    • 清理过期数据cache manager进程使用(最近最少使用算法:LRU);

  • 2种清理缓存场景: 
    (1)缓存项过期清理; 
    (2)缓存空间用尽清理;

分布式缓存系统主机路由:

  • 缓存算法: 
    (1)取模法:容易造成雪崩,所有请求集中响应到后端,导致服务器奔溃;【现在的互联网系统高度依赖于缓存服务器的】 
    (2)一致性hash算法:哈希环;本质还是取模法,但是不再对服务器数量取模,而是对数据连续范围进行取模,然后基于弱点做服务器判断。缺陷:哈希环偏斜(某两个主机担任总环绝大部分工作);

  • 数据流式化;

    • 可序列化数据; 【序列化:任何数据如果要从内存存储于磁盘上,首先要做序列化,所谓序列化就是把一个数据打散,打散成流式的结果然后能存于磁盘上的过程,当然还需要反流式化还原数据;

    • 不可流式化的数据一般为面向对象的编程语言当中,在程序运行中所生成的对象;比如像java运行过程中各class生成的object,是有特定结构的,这种结构一般打散后很难还原,除非使用流式化框架;

memcached是k/v缓存数据,可缓存所有的可序列化数据;k是什么,v是什么由使用者自己定义;是旁挂式缓存,不是代理式(透传式)缓存;

  • 缓存系统种类,有两类:

    • (1)代理式缓存:例如nginx反向代理;多数情况下,每种缓存首先都得是代理;

    • (2)旁挂式缓存:客户端自己决定是否请求缓存;比如memcached;

  • 缓存服务器的类型: 
    透传式缓存:首先得有代理功能,工作在真实服务器前端,请求先到达缓存服务器,缓存服务器中有该请求的数据并在有效期内就直接使用缓存响应给用户,如果没有或过期则缓存服务器请求向后端的服务器请求该数据响应给客户并根据协议决定是否缓存在本地,缓存服务器对于客户端是透明的。 
    旁挂式缓存:工作在真实服务器后端,在真实服务器上要查询数据时,应用程序调用memcached的客户端|驱动首先查询memcached,如果memcached中有相应的缓存数据,取得数据并响应给用户,如果memcahed中没有相应的缓存数据则真实服务器向mysql中查询,并根据协议决定是否把查询结果缓存在memcached上。

  • 代理式缓存:nginx代理 【客户端请求来到nginx代理处时,本地有缓存服务,但缓存未命中时,nginx自己扮演成一个客户端向后端主机请求数据,根据后端服务器响应来判断是否可缓存,如果可缓存,则将数据存在缓存中,然后再封装响应报文给客户端;

  • 旁路式缓存:memcached 
    客户端发起请求,是自己去找缓存系统,如果缓存命中,直接下一步;如果没命中,客户端自己要找原始的存储数据;这种到底缓存哪些数据,如何到缓存去取数据是由客户端自行决定的;客户端参与一半的过程,所以对于旁路式缓存,要求整个系统中一半的只能在客户端,另一半只能在缓存端; 
    客户端自己要决定是否查缓存、查哪个缓存以及取得数据后是否缓存下来等等; 
    这就使得任何时候,想使用memcached缓存数据,必须得用编程语言写程序调用memcached的API才能实现与memcached交互;

memcached自身是一个缓存服务器,也有自己的协议叫memcached协议;要想与memcached通信,就得基于这个协议的接口,向memcached发请求;这个接口可为一个API,也可以是程序员写好的命令程序,但对memcached而言,命令程序没有价值除了测试之外,因为,是靠命令程序连接memcached;

memcached是分布式缓存,如有3台memcached主机,平均下来,每个主机只缓存了整个系统需要缓存的数据的三分之一;这样的带来的结果是,如果某人需要用到的数据有可能第一主机有一点,第二主机有一点,第三主机有一点,那么,就要跟3个主机通信,如果某时刻,只缓存一个数据项,要求找缓存时,应该找哪台主机?

如何判断缓存项被缓存在哪台主机? 
如何能够确保在分布式场景中,缓存的条目,下一次一次就能判断命中与否,而不会再找其它主机了;

  • 用缓存查询算法来实现;

    • 第一种算法叫取模法(余数): 
      例如有三台主机,缓存任何缓存项时,缓存数据是k/v,由于有客户端参与,客户端也有k/v,把key做哈希计算,注意哈希的是key而不是数据文件;计算以后得出的是十六进制数值,把这个数值对服务器的数量取模;那么这三台的余数应该是0,1,2,只要key不变,哈希码就不会变,即同一个数据计算多少遍哈希码都是一样的;因此,当在保存一条缓存时,第一次基于这种算法能挑选出一台主机,假设通过计算挑选为第三台主机,就把数据缓存在该主机上;下次查询时,同样的算法,先把key做哈希计算,对服务器取模,就会得出同样的结果,也就是第一次把该条目缓存在自己磁盘上的主机了;从而实现了,一次就能命中缓存在哪台主机上的效果;

  • 但是,取模的缓存查询算法,有一个比较大的缺陷: 
    如果其中一台缓存服务器掉线或是坏了,这样就变成了对2台缓存服务器取模,数据对2取模和对3取模的结果不一样,根据此缓存查询算法得出的结果落在的服务器节点极有可能不在是以前的节点,此前的所有key基本都统统失效;这带来的效应称为缓存的雪崩;

现代互联网系统中,是高度依赖缓存系统的;也就意味着设计出来的一个系统它的并发承载能力是3万个,因为后面有大量的缓存支撑才能保持如此高的并发量,缓存命中率可以达到80%,所以,只有20%的请求才会到后端存储去读取,80%的数据都由缓存提供;

一旦缓存出故障,所有并发请求都会发往后端,后端存储于是挂了,然后前端调度器挂了,接着就是整个系统挂了;

  • 于是就有了第二种缓存查询算法:一致性哈希算法 
    取模法的缺陷主要来自于:哈希所取模的数据太小了;

因此,所谓一致性哈希,事先假设有一个哈希环,哈希环上有很多节点,从0开始一直到(2^32)-1,可认为是一个32为的二进制数字范围内的所有可变取值;一旦环(节点)走完一圈,继续从0开始;

对每一个服务器的特性进行哈希计算,通常是对IP地址进行哈希,计算的结果对(2^32)取模,计算的结果肯定是在0到(2^32)-1之间的数值;

那么,再接着假设有3台缓存服务器,使用一致性哈希缓存查询算法,计算得出是在哈希上的3个数,分别落在环上不一样的3个点上;缓存项一定会有一个key一般是url,而value就是url对应的文件内容的数据流,所有key做缓存计算时,也是对key做哈希计算,计算结果对2^32取模(就可认为这个可以也一定在这个环上);取模后的结果也一定在这个环上,这样,缓存服务器也在这个环上,按顺时针,找离这个缓存项最近的缓存服务器当做它的缓存节点,把缓存数据保存在该节点服务器上即可;

查询缓存时,用一样的算法,只要找到key,对key做哈希计算对2^32取模,取模后找离它最近的缓存节点,这样就解决了;当有一台缓存服务器挂了的时候,此时只会影响:从这个挂了的服务器到逆时针的上一个缓存服务器节点之间的缓存项的查询;这样只会影响其中的一部分,而不再是全局的了;这就叫做一致性哈希算法;

其实,一致性哈希算法还是取模法,这不过不再是对服务器自身的数量取模,而是对数据连续范围进行取模,而后基于落点,进行做服务器判定;

但是极端情形时,一致性哈希算法也有缺陷,万一出现缓存服务器偏斜,就是3台服务器计算的结果在哈希环上的落点比较接近,瞬时针的第一台主机承载了绝大多数的缓存项查询,如果挂了,就相当于整个哈希环全挂了;

为了避免偏斜,为服务器引入虚拟节点,把每一台主机通过一些变量参与运算以后,一个服务器不是单独的节点,可以做几个影子节点,如果一台服务器做成虚拟的50个节点,那么,3台服务器,就有150个节点,这样,在哈希环上的落点可认为均匀分散的,避免了一台主机一个落点且3台主机的落点比较近的情况;

这样一来,如果一台缓存服务器挂了,影响的是50个小局部,而不是一个大局部;影响的数据分散开来不是集中在某个位置;这种就称为虚拟节点;在分布式式场景中,这种算法非常有用;

memcached的缓存是比较诡异:在同一个集群里的多个缓存节点之间不知道彼此的存在;所以是最不像集群的集群;

  • 安装memcached: 
    ]# yum info memcached 
    centos7中已经把memcached收录到base源中,在centos6中可能在epel源中;可想而知,memcached的重要性; 
    memcached是分布式高性能内存对象缓存系统;能够有效减轻数据存储的压力;用来补充mysql系统,提高它的系统性能,通常用于数据库缓存;将来构建的平台是lammp(或lnammp或lnmammp):

http只能处理静态请求,所有动态请求可以基于模块化机制,调用php模块处理,而php要访问数据,先要访问msyql数据库,而mysql处理数据性能通常是非常低的;如果数据库中的数据没有发生变化,那么两次访问处理的结果应该是相同的;所以,可以在mysql之外,补充一个memcached,任何时候php访问数据库先去找memcached,memcached如果命中,则返回结果,如果没有命中,php程序再自己去找mysql读取数据;找完后再把数据缓存到memcached中;所以这就叫ammp的应用;

]# yum -y install memcached
    安装memcached
]# rpm -ql memcached
/etc/sysconfig/memcached    【服务unit file脚本的配置文件】
/usr/bin/memcached              【主程序】
/usr/bin/memcached-tool                 【工具程序】
/usr/lib/systemd/system/memcached.service       【服务启动的unit file】

]# cat /etc/sysconfig/memcached
PORT="11211"          【监听的端口】
USER="memcached"   【以哪个用户的身份运行memcached】
MAXCONN="1024"     【并发连接数最大限制】
CACHESIZE="64"         【使用多大的内存空间,单位是M(兆),真正用时会很大,需要改动】
OPTIONS=""         【其它选项,如果还要直接添加即可】

]# systemctl start memcached.service
]# ss -tunl     【监听在tcp的11211端口,udp的11211端口;】
     【对memcached的所有应用都要通过它的API进行,要想与memcached通信要使用telnet是因为它的协议默认是文本格式的;使用telnet登录进来以后,可以使用文本方式发命令;但文本编码效率不高;】
     【memcached程序特性是通过memcached程序的选项来定义的,如果不想使用选项定义,可以在unit file环境配置文件中/etc/sysconfig/memcached定义memcached的工作特性;】
     【在centos6,7上都一样,只不过在centos6上被称作脚本配置文件,在centos7称为unit file配置文件】
]# yum -y install telnet
]# telnet 172.18.11.111 11211     【连接本机的memcached】
stats     【统计命令】
STAT pid 2556     【进程号】
STAT uptime 946     【运行时长,单位秒钟】
STAT time 1463045050     【当前系统时间戳】
STAT version 1.4.15     【程序版本号】
STAT libevent 2.0.21-stable     【基于哪种并发线程库来运行】
STAT cmd_get 0      【表示get命令运行的次数】
STAT cmd_set 0
STAT cmd_flush 0
STAT get_hits 0     【表示get命中多少次】
STAT get_misses 0      【表示get未命中多少次】
STAT incr_misses 0     【表示incr命令未命中的次数】
STAT incr_hits 0          【表示incr命令命中的次数】
STAT threads 4             【线程数】

stats items                  【命令表示查看有多少缓存项条目】
stats slabs                   【命令表示查看】
set                               【命令设置一个键的对应值】
add                             【命令新增一个键】
replace                       【命令替换一个键的值】
append                      【命令在一个键后面添加新值】
prepend                     【命令在一个键值前添加一个新值】

例如:

set mykey 0 60 5 
-------------------------------------
输入:hello
其中:mykey       【表示定义键名】
0                【表示flag标志】
60              【表示缓存时长,单位秒钟】
5                【表示键名存储的字节数】
hello          【表示为定义为5个字节的键名】

例如:get mykey      【查看键值】

stats items     【查看缓存项】
STAT items:1:number 1
STAT items:1:age 84
STAT items:1:evicted 0
STAT items:1:evicted_nonzero 0
STAT items:1:evicted_time 0
STAT items:1:outofmemory 0
STAT items:1:tailrepairs 0
STAT items:1:reclaimed 0
STAT items:1:expired_unfetched 0
STAT items:1:evicted_unfetched 0 stats               【查看统计信息】

add hikey 0 300 12     【添加缓存键,用法同set命令】
hello magedu get mykey             【查询缓存键,已经过期了】

append hikey 0 300 4    【在缓存键后添加新值】
输入.com

get hikey
显示:hello magedu.com

prepend hikey 0 300 4
输入:www 【此处有空格】

get hikey
显示:www .hello magedu.com

delete hikey 删除键

set mykey 0 300 1
0

incr mykey 1   【incr:增加多个值,此处为增加1个值】

get mykey
显示:1incr mykey 6
get mykey
显示:7
decr mykey 3
get mykey
显示:4
flush_all 清空
stats items
显示:
STAT items:1:number 1
   【清空后还有一个,这就是memcached的惰性清理机制;一个缓存过期后,只是把缓存的有效时间标记为0,并不把它真正删除;好处在于,不会没事就清理,只有空间用尽时一次再清理完;这是出于性能的考虑作出的设计;】

memcached:

11211/tcp,11211/udp

  • 【主程序:】memcached

  • 【环境配置(unit file)文件:】/etc/sysconfig/memcached

  • 协议格式: 
    【文本协议:默认工作于文本协议;使用基于telnet与memcached通信;】 
    【支持二进制协议:但仍然在研发中;】

  • **telnet进入memcached中的命令: 
    统计类:states,stats items,stats slabs,stats sizes 
    存储类:set,add,replace,append,prepend 
    获取数据类:get,delete,incr/decr 
    清空:flush_all

  • memcached程序的常用选项:

-l 监听的地址;
-d 以守护进程模式运行memcached
-u 指明以哪个用户的身份运行
-m 缓存空间大小,单位为MB,默认为64;
-c 最大并发连接数,默认为1024;
-R 如何执行并发响应,一般无需调整;
-k 锁定内存页,一般无需调整;
-p 监听的tcp端口,默认11211;
-U 监听udp端口,默认11211;,可关闭,使用-U 0;
-M 缓存空间耗尽时,向请求者返回错误信息,而不是基于LRU算法进行缓存清理;不允许新缓存,旧的也不清理;
-f 定义增长因子,groupth factor,默认为1.25倍;
-n 指定memcached最小内存块的大小;默认为48字节;
-t 处理用户请求的线程数;
-B 指定使用绑定的协议,默认为由客户端选择auto,也可指明ascii(文本协议)或binary(二进制协议)
-S 激活SASL(简单认证安全层)认证机制;
memcached默认没有认证机制,但可借助于SASL进行认证;

增长因子解释:在64M的缓存中,要不断插入新的缓存项,同时清理超时过期的缓存项;不同的缓存项占用内存的大小是不一样的,时间久了,第一,内存会产生碎片,可用率就会降低;第二,性能也就大大降低;

为了避免这个现象,事先将内存空间按照固定大小分隔好;例如分隔两个1M的,再分隔三个800k的等等;当缓存一个缓存项时,找一个能容纳这个缓存项的大小,但是分隔好的空闲内存片段,存进去即可;当然,存进去不可能正好合适,肯定会有一段空间是浪费的;这是memcached的为了避免内存碎片使用的策略;要设定最大的是多少空间,最小的是多少空间?假设最小为40k,再比40大的为多少字节?可以是60即为40的1.5倍,也可以是80即40的2倍;

这个跨度越小,保存缓存项时浪费的空间就越小,但是,这样整个缓存项目每一个变化的幅度会很多很多,像这种每一级比上一级大多少就叫做增长因子即增长倍数;

例如:

]# memcached -u memcached -f 1.1 -vv
显示部分内容:slab class 1: chunk size 96 perslab 10922
slab class 2: chunk size 112 perslab 9362
slab class 3: chunk size 128 perslab 8192
slab class 4: chunk size 144 perslab 7281
slab class 5: chunk size 160 perslab 6553
slab class 6: chunk size 176 perslab 5957
    【指明以memcached用户的身份运行,增长因子设为1.1倍,显示详细信息;】
其中:
   【显示的slab表示为内存分配器;每一个分隔的大小是一个slab class【特定大小的chunk的组】类别;】
【对于slab class 1类别在96字节大小的空间上,划分了10922个;其它同理;】

memcached最大只能缓存单个文件为1M的数据;大于1M不支持不会缓存;

增长因子越小,项目就越多;增长因子越大,找一个合适存放的空间的机会就越小,空间浪费就越大;

memcached在内存管理上,默认为64M内存,这个内存空间不是随意分配的,因为Linux内存管理是根据页面进行管理的,每一页为4k,所以,64M有n个4k组合起来,形成的这64M空间,每一个4k应该属于一个slab类别;slab类别当中应该有多个页框组成,每个页框中还要分成,假如最小为96字节,就表示每个页框中还要分成多个96字节空间;所以每个slab之上有10922个空间大小为96字节的chunk;每一个小的存储空间称为一个chunk【用于缓存记录的内存空间】;同类别的所有chunk集合起来,叫一个slab class;

slab class实际是在内存页上分配的,slab和chunk是抽象概念,只有内存页才是事实上的空间;

例如:

stats slabs
php连接memcached服务的模块:
php的连接扩展(相当于驱动):有2个,在centos 7上php这个两个模块名称被改了;
memcahce:php-pecl-memcache
memcached:php-pecl-memcached

libmemcached为C扩展,memcached被php做二次封装后成为了php-pecl-memcached;
在centos 7上php这个两个模块名称被改了
]# yum list all |grep memcache
显示部分内容:
libmemcached.i686 1.0.16-5.el7 base 补充memcached的功能
libmemcached.x86_64 1.0.16-5.el7 base
libmemcached-devel.i686 1.0.16-5.el7 base
libmemcached-devel.x86_64 1.0.16-5.el7 base
memcached-devel.i686 1.4.15-9.el7 base 补充memcached的功能
memcached-devel.x86_64 1.4.15-9.el7 base
php-pecl-memcache.x86_64 3.0.8-4.el7 base
php-pecl-memcached.x86_64 2.2.0-1.el7 epel
python-memcached.noarch 1.48-4.el7 base 【为python连接memcached的扩展】

]# yum info php-pecl-memcache
]# yum info php-pecl-memcached查看程序包信息

]# yum -y install libmemcached为memcached提供c程序接口
]# rpm -ql libmemcached
安装后生成的文件
显示部分内容:/usr/bin/memaslap
/usr/bin/memcapable
/usr/bin/memcat
/usr/bin/memcp
/usr/bin/memdump
/usr/bin/memerror
/usr/bin/memexist
/usr/bin/memflush
/usr/bin/memparse
/usr/bin/memping
/usr/bin/memrm
/usr/bin/memslap
/usr/bin/memstat
/usr/bin/memtouch
其中的这些工具程序,都可基于memcached协议连到memcached服务器上来获取数据;

例如:

]# memstat --help
]# memstat --servers=127.0.0.1  【连接到本机的memcached服务器,查看统计信息,同连接到memcached服务器使用stats命令效果一样;】
用此命令获取内容,就可用脚本分析了;

在centos 6上,还有个工具叫php memer admin基于php页面展示内部状态,并对其进行管理的图形界面接口; 
任何想用memcached的功能,程序必须得支持,程序得调用memcached的接口;

可提供工具程序的程序包:libmemcached;

  • LB Cluster(负载均衡集群)保持会话的方法:3种 
    session sticky 会话绑定 
    session cluster 会话集群 
    session server 会话服务器 
    php就支持把session放在memcached上;默认php是把session放在本地硬盘上的,可以放在memcached上,以后在保持会话时就不需会话绑定了,可以直接轮询;

使用方法参照老师给的文档:

yum install -y php-memcache  【php连接memcached服务的模块】
 一、配置php将会话保存至memcached中
 编辑php.ini文件,确保如下两个参数的值分别如下所示:
 vim /etc/php.ini
 ------------------------------------------------------
 session.save_handler = memcache
 session.save_path = "tcp://172.16.200.11:11211?persistent=1&weight=1&timeout=1&retry_interval=15" 
 
 vim /etc/httpd/conf.d/php.conf
 ---------------------------------------------------
 php_value session.save_handler "memcache"
 php_value session.save_path "tcp://172.18.71.202:11211?persistent=1&weight=1&timeout=1&retry_interval=15" 
 
 解释:
 session.save_handler:会话保持处理器;
 172.16.200.11 为memcached服务器的ip地址;
 persistent=1 连接memcached以后使用持久会话(保持);
 weight=1 权重为1;
 timeout=1 超时为1;
 retry_interval=15 重试时间间隔; 
 
 二、测试新建php页面setsess.php,为客户端设置启用session:设置session的页面
 <?php
 session_start();
 if (!isset($_SESSION[‘www.wqiang.com‘])) {
 $_SESSION[‘www.wqiang.com‘] = time();
 }
 print $_SESSION[‘
  print "<br><br>";
  print "Session ID: " . session_id();
  ?> 
  
  新建php页面showsess.php,获取当前用户的会话ID: 显示session的页面
  <?php
  session_start();
  $memcache_obj = new Memcache;
     // 获取memcache服务器中存储的session
  $memcache_obj->connect(‘172.16.200.11‘, 11211);
  $mysess=session_id();
  var_dump($memcache_obj->get($mysess));
  $memcache_obj->close();
  ?>
  可测试,被负载均衡时,由后端不同的RS响应,但session不变;



本文出自 “王强的博客” 博客,请务必保留此出处http://wqiang.blog.51cto.com/6074114/1795858

memcached 原理及详细配置

标签:互联网   数据库   程序   memcached   

原文地址:http://wqiang.blog.51cto.com/6074114/1795858

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!