Memcached进阶与优化 weir 2015-09-26 10:32:56.0 memcached,分布式 2532 说起memcached就不能不提redis,这两个在内存届赫赫有名,基本达到了高性能的目标,由于我还没有学习到redis,而网上又有很多这两个之间的比较,所以我也不好说就不做过多评论了,我们这里还是重点看看memcached。 讨论之前我先分享一个真实案例,我之前在一家公司,一个ERP的系统年数比较早了,所以现在用起来反映非常慢,打开一个表单或者保存一个表单有时候会停在那里十几分钟甚至更长,但是程序还没有出错,这种情况是无法忍受的。后来我们的一个同事把数据库安装到了内存里面,说到这里你就知道会出现什么情况了,整个系统的速度提升非常的明显,可以说是秒级的反应速度,这也是目前计算机整了发展的现状。就像Hadoop和spark在计算统计上面的差距一样,不是算法的快慢而是谁更好的利用硬件资源,而内存是实现高性能的不二选择。 启动Memcached服务端 ./memcached -d -m 10 -u root -l 192.168.137.2 -p 2222 -c 256 -P /weir/memcached.pid -d选项是启动一个守护进程 -m是分配给Memcache使用的内存数量,单位是MB,这里是10MB -u是运行Memcache的用户,这里是root -l是监听的服务器IP地址,这里指定了服务器的IP地址192.168.1.106 -p是监听的端口,这里设置了2222,最好是1024以上的端口 -c选项是最大运行的并发连接数,默认是1024,这里设置了256 -P是设置保存Memcache的pid文件,这里是保存在/tmp/memcached.pid 常用的还有几个需要了解: -f 块大小增长因子,默认是1.25 -n 最小分配空间, key+value+flags 默认是48byte -I 每个slab page的大小 -v/-vv 详细显示工作时各种参数 查看进程: ps -ef | grep memca 关闭Memcached,先用ps aux|grep memcached找到进程号,然后kill掉 Memcached的操作命令 标准协议:Memcached所有的标准协议包含在对item执行命令过程中,一个item包含两行: 第一行:Key Flags ExpirationTime Bytes Key:Key 用于查找缓存值 Flags:一个32位的标志值,客户机使用它存储关于键值对的额外信息 Expiration time:在缓存中保存键值对的时长(以秒为单位,0表示永远) Bytes:在缓存中存储的字节数 第二行:Value:存储的值(始终位于第二行) noreply:可以在命令的第一行后面加入noreply,以避免在处理交互命令的时候,等待服 务端的返回 向Memcached写入值 命令有:set、add、replace、append、prepend、cas 1:set:用于向缓存添加新的键值对,如果键已经存在,则之前的值将被替换 2:add:仅当缓存中不存在键时,add命令才会向缓存中添加一个键值对,如果缓存中已经存 在键,则之前的值将仍然保持,服务器响应NOT_STORED 3:replace:仅当键已经存在时,replace命令才会替换缓存中的键。如果缓存中不存在键, 服务器响应NOT_STORED 4:append:是在现有缓存数据后面新增数据。如果key不存在,服务器响应 NOT_STORED 5:prepend:是在现有缓存数据前面新增数据。如果key不存在,服务器响应 NOT_STORED 6:cas(Check And Set ):检查和更新,只有从你读取数据后,别人没有更新这 个数据,才能够正确保存。就是版本控制,通常和gets配合使用 获取数据的命令有:get 、gets get用来获取数据,gets获取的是数据+版本号 删除数据的命令:delete incr/decr命令:如果缓存数据中存储的是数字形式的字符串,则可以使用 incr/decr 对数据进行递增和递减操作,操作后的值不会为负数 stats命令:查询服务器的运行状态和其他内部数据,包含如下这些: 1:pid :服务器进程ID 2:uptime :服务器运行时间,单位秒 3:time:服务器当前的UNIX 时间 4:version :服务器的版本号 5:libevent:libevent的版本 6:pointer_size :服务器操作系统位数 分析CPU占用是否高 7:rusage_user:该进程累计的用户时间 8:rusage_system:该进程累计的系统时间 分析连接数是否过多 9:curr_connections :当前连接数 10:total_connections :服务器启动后总连接数 11:connection_structures :服务器分配的连接结构的数量 12:reserved_fds:内部使用的misc fds 数量 分析命中率 13:cmd_get :获取请求数量 14:get_hits :获取成功的总次数,命中次数 15:get_misses :获取失败的总次数 16:cmd_set :存储请求数量 17:cmd_flush :flush请求的数量 18:cmd_touch:touch请求的数量 19:delete_misses :删除失败次数 20:delete_hits :删除命中 21:incr_misses :递增失败次数 22:incr_hits :递增命中次数 23:decr_misses :递减命中次数 24:decr_hits :递减失败次数 25:cas_misses :Cas 原子设置操作失败次数 26:cas_hits :Cas 命中次数 27:cas_badval :Cas 操作找到key, 但是版本过期,没有设置成功 28:touch_hits:touch命中次数 29:touch_misses:touch失败次数 30:auth_cmds :认证次数(包括成功和失败) 31:auth_errors :认证失败次数 32:bytes :已用缓存空间 分析字节数流量 33:bytes_read :总共获取的数据量 34:bytes_written :总写入数量数 35:limit_maxbytes :总允许写入的数据量,和分配的内存有关 36:accepting_conns:允许的总连接数 37:listen_disabled_num :监听失败的次数 38:threads:需要的工作线程数 39:hash_bytes:当前使用的Hash table容量大小 40:hash_is_expanding:指定Hash table是否自动增长 41:malloc_fails:malloc内存分配失败的次数 分析对象LRU频率 42:curr_items :当前缓存item 数量 43:total_items :从服务启动后,总的存储缓存item 数量 44:evictions :通过删除item 释放内存的次数 这些数据隐含的几个基本关系: 1:缓存命中率= get_hits/cmd_get * 100% 2:get_misses的数字加上get_hits应该等于cmd_get stats sizes命令:输出所有Item的大小和个数,注意:会锁定服务,暂停处理请求 flush_all命令:使内存中所有的item失效。加入参数则表示在N秒后失效。这个操作并不 会真的释放内存空间,而是标志所有的item为失效 version命令:查看版本 stats settings查看设置 maxbytes:最大字节数限制,0无限制 maxconns:允许最大连接数 tcpport:TCP端口 udpport:UDP端口 verbosity:日志0=none,1=som,2=lots oldest:最老对象过期时间 evictions:on/off,是否禁用LRU domain_socket:socket的domain umask:创建Socket时的umask growth_factor:增长因子 chunk_size:key+value+flags大小 num_threads:线程数,可以通过-t设置,默认4 stat_key_prefix:stats分隔符 detail_enabled:yes/no,显示stats细节信息 reqs_per_event:最大IO吞吐量(每event) cas_enabled:yes/no,是否启用CAS,-C禁用 tcp_backlog:TCP监控日志 auth_enabled_sasl:yes/no,是否启用SASL验证 stats items数据项统计 number:该slab中对象数,不包含过期对象 age:LRU队列中最老对象的过期时间 evicted:LRU释放对象数 evicted_nonzero:设置了非0时间的LRU释放对象数 evicted_time:最后一次LRU秒数,监控频率 outofmemory:不能存储对象次数,使用-M会报错 tailrepairs:修复slabs次数 reclaimed:使用过期对象空间存储对象次数 stats slabs区块统计 chunk_size:chunk大小,byte chunks_per_page:每个page的chunk数量 total_pages:page数量 total_chunks:chunk数量*page数量 get_hits:get命中数 cmd_set:set数 delete_hits:delete命中数 incr_hits:incr命中数 decr_hits:decr命中数 cas_hits:cas命中数 cas_badval:cas数据类型错误数 used_chunks:已被分配的chunk数 free_chunks:剩余chunk数 free_chunks_end:分完page浪费chunk数 mem_requested:请求存储的字节数 active_slabs:slab数量 total_malloced:总内存数量 被浪费内存数=((total_chunks或者used_chunks) * chunk_size) - mem_requested,如 果太大,需要调整factor Memcached的Java客户端 Memcached的守护进程是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进 程通信 官方的Memcached的Java客户端API,主要提供的调用类是SockIOPool和MemCachedClient SockIOPool public static SockIOPool getInstance() 获得连接池的单态方法。这个方法有一个重载方法getInstance( String poolName ),每个 poolName只构造一个SockIOPool实例。缺省构造的poolName是default。如果在客户端配置多个 memcached服务,一定要显式声明poolName。 public void setServers( String[] servers ) 设置连接池可用的cache服务器列表,server的构成形式是IP:PORT(如:127.0.0.1:1111) public void setWeights( Integer[] weights ) 设置连接池可用cache服务器的权重,和server数组的位置一一对应。其实现方法是通过根据每 个权重在连接池的bucket中放置同样数目的server,因此所有权重的最大公约数应该是1,不然会引起 bucket资源的浪费。 public void setInitConn( int initConn ) 设置开始时每个cache服务器的可用连接数 public void setMinConn( int minConn ) 设置每个服务器最少可用连接数 public void setMaxConn( int maxConn ) 设置每个服务器最大可用连接数 public void setMaxIdle( long maxIdle ) 设置可用连接池的最长等待时间 public void setMaintSleep( long maintSleep ) 设置连接池维护线程的睡眠时间,设置为0,维护线程不启动。维护线程主要通过log输出 socket的运行状况,监测连接数目及空闲等待时间等参数以控制连接创建和关闭。 public void setNagle( boolean nagle ) 设置是否使用Nagle算法,因为我们的通讯数据量通常都比较大(相对TCP控制数据)而且要求响 应及时,因此该值需要设置为false(默认是true) ublic void setSocketTO( int socketTO ) 设置socket的读取等待超时值 public void setSocketConnectTO( int socketConnectTO ) 设置socket的连接等待超时值 public void setAliveCheck( boolean aliveCheck ) 设置连接心跳监测开关。设为true则每次通信都要进行连接是否有效的监测,造成通信次数倍 增,加大网络负载,因此该参数应该在对HA要求比较高的场合设为TRUE,默认状态是false。 public void setFailback( boolean failback ) 设置连接失败恢复开关,设置为TRUE,当宕机的服务器启动或中断的网络连接后,这个socket 连接还可继续使用,否则将不再使用,默认状态是true,建议保持默认。 public void setFailover( boolean failover ) 设置容错开关,设置为TRUE,当前socket不可用时,程序会自动查找可用连接并返回,否则返回 NULL,默认状态是true,建议保持默认。 public void setHashingAlg( int alg ) 设置hash算法 alg=0 使用String.hashCode()获得hash code,该方法依赖JDK,可能和其他客户端不兼容,建议不使用 alg=1 使用original 兼容hash算法,兼容其他客户端 alg=2 使用CRC32兼容hash算法,兼容其他客户端,性能优于original算法 alg=3 使用MD5 hash算法 采用前三种hash算法的时候,查找cache服务器使用余数方法。采用最后一种hash算法查找cache 服务时使用一致性hash方法 public void initialize() 设置完pool参数后最后调用该方法,启动pool。 MemCachedClient public void setCompressEnable( boolean compressEnable ) 设定是否压缩放入cache中的数据,默认值是ture,如果设定该值为true,需要设定 CompressThreshold public void setCompressThreshold( long compressThreshold ) 设定需要压缩的cache数据的阈值,默认值是30k public void setPrimitiveAsString( boolean primitiveAsString ) 设置cache数据的原始类型是String ,默认值是false。只有在确定cache的数据类型是string的 情况下才设为true,这样可以加快处理速度。 public void setDefaultEncoding( String defaultEncoding ) 当primitiveAsString为true时使用的编码转化格式,默认值是utf-8 。如果确认主要写入数据 是中文等非ASCII编码字符,建议采用GBK等更短的编码格式 set方法:将数据保存到cache服务器,如果保存成功则返回true 如果cache服务器存在同样的key,则替换 add方法:将数据添加到cache服务器,保存成功则返回true。如果存在同样key,则返回false replace方法:将数据替换cache服务器中相同的key,如果保存成功则返回true。 get方法:获取一个数据,如果写入时是压缩的或序列化的,则get的返回会自动解压缩及反序列化。 getMulti方法:从cache服务器获取一组数据 get方法的数组实现,输入参数keys是一个key数组,返回是一个map gets方法 gets除了会返回缓存值外,还会返回当前缓存的版本号,一般是用于协同CAS完成原 子操作使用 getMultiArray方法:返回缓存的数组 cas方法 原子设置缓存操作,通过版本号casUnique保证设置的唯一性,如果发现服务器的缓 存版本与传入的不同,则放弃设置缓存,返回false storeCounter方法: 初始化一个计数器 getCounter方法: 获取当前的计数器值 incr方法: 对计数器增量操作 decr方法: 对计数器减量操作 addOrIncr,addOrDecr:key存在则与incr和decr相同,不存在则相当于storeCounter public class CacheHelper { private static MemCachedClient mcc = new MemCachedClient(); private CacheHelper() { } static { String[] servers = { "192.168.137.106:2222" }; Integer[] weights = { 2 }; SockIOPool pool = SockIOPool.getInstance(); pool.setServers(servers); pool.setWeights(weights); pool.setInitConn(5); pool.setMinConn(5); pool.setMaxConn(250); pool.setMaxIdle(1000 * 60 * 60 * 6); pool.setMaintSleep(30); pool.setNagle(false);// 禁用nagle算法 pool.setSocketConnectTO(0); pool.setSocketTO(3000);// 3秒超时 pool.setHashingAlg(3);//设置为一致性hash算法 pool.initialize(); } public static MemCachedClient getMemCachedClient(){ return mcc; } } Memcached和Spring集成开发 <!--memcached 客户端SocketPool--> <bean id="memcachedPool" class="com.danga.MemCached.SockIOPool" factorymethod=" getInstance" init-method="initialize" destroy-method="shutDown"> <constructor-arg><value>neeaMemcachedPool</value></constructor-arg> <property name="servers"> <list> <value>192.168.137.106:2222</value> <value>192.168.137.106:2223</value> </list> </property> <property name="weights"> <list> <value>1</value> <value>2</value> </list> </property> <property name="initConn"> <value>5</value> </property> <property name="minConn"> <value>5</value> </property> <property name="maxConn"> <value>250</value> </property> <property name="maintSleep"> <value>30</value> </property> <property name="nagle"> <value>false</value> </property> <property name="maxIdle"> <value>6000</value> </property> <property name="socketTO"> <value>3000</value> </property> </bean> <!--memcached client--> <bean id="memCachedClient" class="com.danga.MemCached.MemCachedClient"> <constructor-arg> <value>neeaMemcachedPool</value> </constructor-arg> </bean> 其实像这种内存级别的数据查询是有他们的应用场景的,并不是所有的数据都适合这样做,这才是我们用好他们的前提。