通过上节《memcached演练(1) 搭建memcached服务》,已经成功在linux虚拟机上安装了memcached服务,并设置了自启动。
创新互联是一家专业提供泗县企业网站建设,专注与网站设计、成都做网站、HTML5建站、小程序制作等业务。10年已为泗县众多企业、政府机构等服务。创新互联专业网站建设公司优惠进行中。
STAT version 1.4.29 Mem: 1891
主要内容
使用telnet命令行工具,操作memcached服务
使用java spymemcached工具访问memcached服务
监控插入不同数据量的数据,响应时间
监控memcached主机繁忙&清闲对比情况
调整memcached的内存指标,插入相同的数据量的数据,什么情况下性能最高
1.使用telnet命令行工具,操作memcached服务
1.1 验证set&add添加命令
[hadoop@hadoop1 ~]$ ps -ef |grep memcached nobody 2047 1 1 07:27 ? 00:08:59 /usr/local/memcached/bin/memcached -d -p 11211 -u nobody -m 64 -c 1024 -P /var/run/memcached/memcached.pid hadoop 9037 6485 0 16:59 pts/0 00:00:00 grep memcached #连接 [hadoop@hadoop1 ~]$ telnet 127.0.0.1 11211 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. #获取key1的值 get key1 VALUE key1 0 4 abcd END #调用set命令,为一个已存在的key赋值成功 set key1 0 0 4 1234 STORED get key1 VALUE key1 0 4 1234 END #调用set命令,为一个已存在的key赋值失败 add key1 0 0 4 abcd NOT_STORED get key1 VALUE key1 0 4 1234 END
结论:add和set命令虽然都可以添加数据,但两者使用上存在区别,set可以为已存在的key赋值,而add命令不可以。
1.2 验证get&gets获取命令
#获取1个键值 get key1 VALUE key1 0 4 1234 END #获取1个键值 get key2 VALUE key2 0 4 word END #同时获取2个键值 get key1 key2 VALUE key1 0 4 1234 VALUE key2 0 4 word END --------------------------------------------- gets key1 VALUE key1 0 4 7695009 1234 END gets key2 VALUE key2 0 4 7695010 word END gets key1 key2 VALUE key1 0 4 7695009 1234 VALUE key2 0 4 7695010 word END #gets命令返回比get命令,多了1个"不知名数字" #修改key1的值 set key1 0 0 5 world STORED #确认变化 get key1 VALUE key1 0 5 world END #不知名数字从7695009变为7695011 gets key1 VALUE key1 0 5 7695011 world END #key2值没有变化,所以不知名数字也没有变 gets key2 VALUE key2 0 4 7695010 word END #再次修改key1的值 set key1 0 0 3 old STORED #用增加了1 gets key1 VALUE key1 0 3 7695012 old END
结论gets命令比get命令,多返回的数字,类似数据库中的“变更版本号似的”,而且这个版本号是在各键值之间共享。
1.3测试下incr &decr命令
#某key不存在情况 incr seq 1 NOT_FOUND set seq 0 0 1 1 STORED get seq 1 VALUE seq 0 1 1 END incr seq 1 2 ... get seq VALUE seq 0 1 4 END gets seq VALUE seq 0 1 7695016 4 END incr seq 1 5 #调用incr 命令,增加变更版本号 gets seq VALUE seq 0 1 7695017 5 END incr seq 10 15 decr seq 1 14 #不允许为负数 decr seq -1 CLIENT_ERROR invalid numeric delta argument decr seq 10 4 #最小值为0 decr seq 10 0 #有最大值限制 decr seq 10000000000000000000000 CLIENT_ERROR invalid numeric delta argument set seq2 0 0 1 a STORED #如果键值非数字,会报错 incr seq2 1 CLIENT_ERROR cannot increment or decrement non-numeric value
通过实验验证:incr&decr命令,影响key的长度;最小值为0;增量可变化;必须作用在数字上;命令参数不允许为负数值。
1.4更新命令 append&preppend&replace
set updkey 0 0 5 world STORED get updkey VALUE updkey 0 5 world END #在后面添加字符 append updkey 0 0 5 hello STORED get updkey VALUE updkey 0 10 worldhello #在前面添加字符 prepend updkey 0 0 10 1234567890 STORED gets updkey VALUE updkey 0 20 7695030 1234567890worldhello END #更改有效时间为1 prepend updkey 0 1 1 #一段时间后,还存在(说明更新操作不会影响有效时间) get updkey VALUE updkey 0 22 1u1234567890worldhello END #什么都不添加 prepend updkey 0 0 0 STORED #虽然没有变更值,但变更版本号也发生了变化 gets updkey VALUE updkey 0 22 7695037 1u1234567890worldhello #replace命令 replace updkey 0 10 1 m STORED get updkey VALUE updkey 0 1 m END #replace命令变更了有效时间 gets updkey END
结论:prepend 与append命令作用类似,一个在从前面添加字符,一个在后面追加字符。每一次行为,都会影响版本号;不会影响原来的有效时间;replace命令,不仅可以修改数据,而且可以变更有效时间。
还有其他一些指令,就不玩了。
2.使用java spymemcached工具访问memcached服务
2.1 引入依赖
net.spy spymemcached 2.11.5
2.2 客户端工具类
package com.nap.memcachedexample.service; import net.spy.memcached.MemcachedClient; import java.io.IOException; import java.net.InetSocketAddress; /** * Created by Administrator on 2016/8/7. */ public class MemcachedUtil { private static MemcachedClient cachedClient = null; static { try { cachedClient = new MemcachedClient(new InetSocketAddress("192.168.163.146",11211)); } catch (IOException e) { e.printStackTrace(); } } public static MemcachedClient getSpyMemcachedClient(){ return cachedClient; } }
2.2 测试对象
Employee.java package com.nap; import org.apache.commons.lang.builder.ToStringBuilder; import java.io.Serializable; import java.util.*; public class Employee implements Serializable{ private long empNo; private String name; private Date birthDate; private int age; private double salary; private ListfavoriteFoods; public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthDate() { return birthDate; } public long getEmpNo() { return empNo; } public void setEmpNo(long empNo) { this.empNo = empNo; } public void setBirthDate(Date birthDate) { this.birthDate = birthDate; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public double getSalary() { return salary; } public void setSalary(double salary) { this.salary = salary; } public List getFavoriteFoods() { return favoriteFoods; } public void setFavoriteFoods(List favoriteFoods) { this.favoriteFoods = favoriteFoods; } public String toString(){ return ToStringBuilder.reflectionToString(this); } } -------------------------------------------------------------------------------------- EmployeeFactory.java package com.nap; import com.nap.memcachedexample.service.MemcachedUtil; import net.spy.memcached.MemcachedClient; import net.spy.memcached.internal.OperationFuture; import org.apache.commons.lang.RandomStringUtils; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; public class EmployeeFactory { static MemcachedClient mcc = MemcachedUtil.getSpyMemcachedClient(); public static final String EMPNOSEQ = "empnoseq"; static{ Object cacheSeq = mcc.get("empnoseq"); if(cacheSeq==null){ OperationFuture seqFuture = mcc.set(EMPNOSEQ, 900, "0"); try { if(seqFuture.get()){ System.out.println("set status: ok"); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } /** * 构造测试对象(一个对象大约40B) * @return */ public static Employee newEmployee(){ long empNo = MemcachedUtil.getSpyMemcachedClient().incr("empnoseq",1); Calendar cal = Calendar.getInstance(); Employee emp = new Employee(); emp.setEmpNo(empNo); emp.setAge(new Random().nextInt(80)); emp.setName(RandomStringUtils.randomAlphabetic(15)); cal.set(Calendar.MONTH, new Random().nextInt(30)); cal.set(Calendar.DAY_OF_YEAR, new Random().nextInt(366)); emp.setBirthDate(cal.getTime()); emp.setSalary(new Random().nextFloat()); List foods = new ArrayList (); for(int j=0;j< new Random().nextInt(10);j++){ foods.add("foods_"+RandomStringUtils.randomAlphabetic(10)); } emp.setFavoriteFoods(foods); return emp; } }
package com.nap; import com.nap.memcachedexample.service.MemcachedUtil; import junit.framework.TestCase; import net.spy.memcached.MemcachedClient; import java.util.concurrent.ExecutionException; public class SpyMemcachedClientTest extends TestCase { //测试set public void testAddBean() throws ExecutionException, InterruptedException { MemcachedClient mcc = MemcachedUtil.getSpyMemcachedClient(); Employee emp = EmployeeFactory.newEmployee(); System.out.println(emp); String key = "emp_" + emp.getEmpNo(); System.out.println(key); mcc.set(key, 19000, emp); // Shutdowns the memcached client mcc.shutdown(); } //测试get public void testGetBean() throws ExecutionException, InterruptedException { MemcachedClient mcc = MemcachedUtil.getSpyMemcachedClient(); Object o = mcc.get("emp_1"); System.out.println(o); // Shutdowns the memcached client mcc.shutdown(); } }
telnet输出结果
get emp_1 VALUE emp_1 1 345 srcom.nap.Employee favoriteFoodstLjava/util/List;LnametLjava/lang/String;xpetLjava/util/Date;L ?佚U
通过结果分析,通过客户端已经成功保存了。
3.监控插入不同数据量的数据,响应时间
存储单位转换器
package com.nap; public enum SizeConverter { /** 转换任意单位的大小, 返回结果会包含两位小数但不包含单位. */ Arbitrary { @Override public String convert(float size) { while (size > 1024) { size /= 1024; } return String.format(FORMAT_F, size); } }, // ----------------------------------------------------------------------- // 有单位 /** 转换单位为B的大小, 返回结果会包含两位小数以及单位. 如: 1024B->1KB, (1024*1024)B->1MB */ B { @Override public String convert(float B) { return converter(0, B); } }, /** 转换单位为B的大小, 返回结果会包含两位小数以及单位. */ KB { @Override public String convert(float KB) { return converter(1, KB); } }, /** 转换单位为MB的大小, 返回结果会包含两位小数以及单位. */ MB { @Override public String convert(float MB) { return converter(2, MB); } }, /** 转换单位为GB的大小, 返回结果会包含两位小数以及单位. */ GB { @Override public String convert(float GB) { return converter(3, GB); } }, /** 转换单位为TB的大小, 返回结果会包含两位小数以及单位. */ TB { @Override public String convert(float TB) { return converter(4, TB); } }, // ----------------------------------------------------------------------- // trim没单位 /** 转换任意单位的大小, 返回结果小数部分为0时将去除两位小数, 不包含单位. */ ArbitraryTrim { @Override public String convert(float size) { while (size > 1024) { size /= 1024; } int sizeInt = (int) size; boolean isfloat = size - sizeInt > 0.0F; if (isfloat) { return String.format(FORMAT_F, size); } return String.format(FORMAT_D, sizeInt); } }, // ----------------------------------------------------------------------- // trim有单位 /** 转换单位为B的大小, 返回结果小数部分为0时将去除两位小数, 会包含单位. */ BTrim { @Override public String convert(float B) { return trimConverter(0, B); } }, /** 转换单位为KB的大小, 返回结果小数部分为0时将去除两位小数, 会包含单位. */ KBTrim { @Override public String convert(float KB) { return trimConverter(1, KB); } }, /** 转换单位为MB的大小, 返回结果小数部分为0时将去除两位小数, 会包含单位. */ MBTrim { @Override public String convert(float MB) { return trimConverter(2, MB); } }, /** 转换单位为GB的大小, 返回结果小数部分为0时将去除两位小数, 会包含单位. */ GBTrim { @Override public String convert(float GB) { return trimConverter(3, GB); } }, /** 转换单位为TB的大小, 返回结果小数部分为0时将去除两位小数, 会包含单位. */ TBTrim { @Override public String convert(float TB) { return trimConverter(4, TB); } }; /*** *将指定的大小转换到1024范围内的大小. 注意该方法的最大单位为PB, 最小单位为B, * 任何超出该范围的单位最终会显示为**.
* * @param size 要转换的大小, 注意是浮点数, 不要以×××的方式传入, 容易造成溢出. * (如: 1024*1024*1024*1024*1024会溢出, 使结果为0, 因为它先将结果以int相乘后再转换为float; * 而1024.0F*1024.0F*1024.0F*1024.0F*1024.0F就不会溢出) * @return */ abstract public String convert(float size); // ----------------------------------------------------------------------- // 单位转换 private static final String[] UNITS = new String[] { "B", "KB", "MB", "GB", "TB", "PB", "**" }; private static final int LAST_IDX = UNITS.length-1; private static final String FORMAT_F = "%1$-1.2f"; private static final String FORMAT_F_UNIT = "%1$-1.2f%2$s"; private static final String FORMAT_D = "%1$-1d"; private static final String FORMAT_D_UNIT = "%1$-1d%2$s"; // ----------------------------------------------------------------------- private static String converter(int unit, float size) { int unitIdx = unit; while (size > 1024) { unitIdx++; size /= 1024; } int idx = unitIdx < LAST_IDX ? unitIdx : LAST_IDX; return String.format(FORMAT_F_UNIT, size, UNITS[idx]); } private static String trimConverter(int unit, float size) { int unitIdx = unit; while (size > 1024) { unitIdx++; size /= 1024; } int sizeInt = (int) size; boolean isfloat = size - sizeInt > 0.0F; int idx = unitIdx < LAST_IDX ? unitIdx : LAST_IDX; if (isfloat) { return String.format(FORMAT_F_UNIT, size, UNITS[idx]); } return String.format(FORMAT_D_UNIT, sizeInt, UNITS[idx]); } // ----------------------------------------------------------------------- public static String convertBytes(float B, boolean trim) { return trim ? trimConvert(0, B, true) : convert(0, B, true); } public static String convertKB(float KB, boolean trim) { return trim ? trimConvert(1, KB, true) : convert(1, KB, true); } public static String convertMB(float MB, boolean trim) { return trim ? trimConvert(2, MB, true) : convert(2, MB, true); } /*** *存储大小单位间的转换. 注意该方法的最大单位为PB, 最小单位为B, * 任何超出该范围的单位最终会显示为**.
* * @param unit 从哪个单位开始 * @param size 存储大小, 注意是float, 不要以×××的形式传入, 否则会溢出(如:1024*1024这种, * 它是先将1024*1024作为int相乘再转换为float的, 如果值过大的话就会溢出了, * 所以这么写1024.0F*1024.0F) * @param withUnit 返回的结果字符串是否带有对应的单位 * @return */ private static String convert(int unit, float size, boolean withUnit) { int unitIdx = unit; while (size > 1024) { unitIdx++; size /= 1024; } if (withUnit) { int idx = unitIdx < LAST_IDX ? unitIdx : LAST_IDX; return String.format(FORMAT_F_UNIT, size, UNITS[idx]); } return String.format(FORMAT_F, size); } /*** *存储大小单位间的转换, 如果转换后小数部分为0, 则去除小数部分. * 注意该方法的最大单位为PB, 最小单位为B, 任何超出该范围的单位最终会显示为**.
* * @param unit 从哪个单位开始 * @param size 存储大小, 注意是float, 不要以×××的形式传入, 否则会溢出(如:1024*1024这种, * 它是先将1024*1024作为int相乘再转换为float的, 如果值过大的话就会溢出了, * 所以这么写1024.0F*1024.0F) * @param withUnit 返回的结果字符串是否带有对应的单位 * @return */ private static String trimConvert(int unit, float size, boolean withUnit) { int unitIdx = unit; while (size > 1024) { unitIdx++; size /= 1024; } int sizeInt = (int) size; boolean isfloat = size - sizeInt > 0.0F; if (withUnit) { int idx = unitIdx < LAST_IDX ? unitIdx : LAST_IDX; if (isfloat) { return String.format(FORMAT_F_UNIT, size, UNITS[idx]); } return String.format(FORMAT_D_UNIT, sizeInt, UNITS[idx]); } if (isfloat) { return String.format(FORMAT_F, size); } return String.format(FORMAT_D, sizeInt); } public static void main(String[] args) { System.out.println(SizeConverter.BTrim.convert(1029000f)); } }
public class SpyMemcachedClientTest extends TestCase{ //逻辑非常简单,仅仅批量插入数量数据,经过估算一个Employee 对象,一般为40B。 public void testBatchAddBean() throws ExecutionException, InterruptedException { MemcachedClient mcc = MemcachedUtil.getSpyMemcachedClient(); long startTime=System.currentTimeMillis(); int size=10000000; for (int i=0;i结论(memcached服务内存参数为64m,每次测试前最好执行下flush,降低换入换出影响)
数据量 数据大小 耗时(ms) 1000 39.06KB 1191 10000 390.63KB 3155 20000 781.25KB 5142 30000 1.14MB 7057 40000 1.53MB 8611 50000 1.91MB 10611 100000 3.81MB 19594 500000 19.07MB 89549 通过分析得知,当数据量为10000-20000左右时,单位时间保存的数据最多。
(0.32 kb/ms,降至0.26kb/s)
4.监控memcached主机繁忙&清闲对比情况
由于仅仅考虑写入情况,暂时不需要考虑命中率其他情况。主要记录以下指标
(暂时不考虑内存使用情况,IO等待)
4.1CPU利用率
[root@hadoop1 ~]# sar -u 2 100 Linux 2.6.32-358.el6.i686 (hadoop1) 08/07/2016 _i686_ (1 CPU) 07:06:47 PM CPU %user %nice %system %iowait %steal %idle 07:06:49 PM all 0.50 0.00 0.50 0.00 0.00 98.99 07:06:51 PM all 0.00 0.00 0.51 0.00 0.00 99.49 07:06:53 PM all 0.00 0.00 0.50 0.00 0.00 99.50 07:06:55 PM all 0.00 0.00 0.50 0.50 0.00 99.00 ---------------start------------------------------- 07:06:57 PM all 0.00 0.00 5.18 0.00 0.00 94.82 07:06:59 PM all 0.58 0.00 41.62 0.00 0.00 57.80 07:07:01 PM all 0.00 0.00 61.69 0.00 0.00 38.31 07:07:03 PM all 0.65 0.00 65.36 0.00 0.00 33.99 07:07:05 PM all 0.62 0.00 70.19 0.00 0.00 29.19 07:07:07 PM all 1.23 0.00 69.14 0.00 0.00 29.63 07:07:09 PM all 0.00 0.00 70.55 0.00 0.00 29.45 07:07:11 PM all 0.00 0.00 70.19 0.00 0.00 29.81 07:07:13 PM all 0.00 0.00 70.81 0.00 0.00 29.19 07:07:15 PM all 0.00 0.00 70.89 0.00 0.00 29.11 07:07:17 PM all 0.00 0.00 71.52 0.00 0.00 28.48 07:07:19 PM all 0.00 0.00 71.60 0.00 0.00 28.40 07:07:21 PM all 1.27 0.00 69.43 0.00 0.00 29.30 07:07:23 PM all 0.00 0.00 70.13 0.00 0.00 29.87 07:07:25 PM all 0.66 0.00 69.74 0.00 0.00 29.61 07:07:27 PM all 0.00 0.00 71.34 0.00 0.00 28.66 07:07:29 PM all 1.85 0.00 69.75 0.00 0.00 28.40 07:07:31 PM all 0.00 0.00 70.81 0.00 0.00 29.19 07:07:33 PM all 0.00 0.00 70.89 0.00 0.00 29.11 07:07:35 PM all 0.00 0.00 41.48 0.00 0.00 58.52 -------------------end----------------------------------------- 07:07:37 PM all 0.00 0.00 0.50 0.00 0.00 99.50 07:07:39 PM all 0.00 0.00 0.50 0.00 0.00 99.50在核心级别(kernel)运行所使用 CPU 总时间的百分比突然提升了40%-70%。
相对来说,用户级别的CPU 总时间百分比变化不大。这一点有点出乎意料。
输出项说明:
CPU all 表示统计信息为所有 CPU 的平均值。 %user 显示在用户级别(application)运行使用 CPU 总时间的百分比。 %nice 显示在用户级别,用于nice操作,所占用 CPU 总时间的百分比。 %system 在核心级别(kernel)运行所使用 CPU 总时间的百分比。 %iowait 显示用于等待I/O操作占用 CPU 总时间的百分比。 %steal 管理程序(hypervisor)为另一个虚拟进程提供服务而等待虚拟 CPU 的百分比。 %idle 显示 CPU 空闲时间占用 CPU 总时间的百分比。 4.2换页情况
sar -B 06:59:29 PM pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff 06:59:35 PM 0.00 0.00 15.50 0.00 53.00 0.00 0.00 0.00 0.00 06:59:37 PM 0.00 0.00 15.58 0.00 53.27 0.00 0.00 0.00 0.00 06:59:39 PM 0.00 0.00 15.58 0.00 53.27 0.00 0.00 0.00 0.00 -----start----------- 06:59:41 PM 0.00 0.00 16.76 0.00 61.62 0.00 0.00 0.00 0.00 06:59:43 PM 0.00 0.00 123.78 0.00 111.59 0.00 0.00 0.00 0.00 06:59:45 PM 0.00 0.00 20.25 0.00 92.64 0.00 0.00 0.00 0.00 06:59:47 PM 0.00 22.78 20.89 0.00 94.30 0.00 0.00 0.00 0.00 06:59:49 PM 0.00 0.00 24.84 0.00 105.73 0.00 0.00 0.00 0.00 06:59:51 PM 0.00 0.00 19.02 0.00 92.02 0.00 0.00 0.00 0.00 06:59:53 PM 0.00 0.00 19.50 0.00 93.08 0.00 0.00 0.00 0.00 06:59:55 PM 0.00 0.00 19.38 0.00 95.00 0.00 0.00 0.00 0.00 06:59:57 PM 0.00 0.00 128.12 0.00 123.75 0.00 0.00 0.00 0.00由于缺少必要的查询操作,换页监控,感觉意义不大。
输出项说明:
pgpgin/s 每秒钟从磁盘读入的系统页面的 KB 总数 pgpgout/s 每秒钟向磁盘写出的系统页面的 KB 总数 fault/s 系统每秒产生的页面失效(major + minor)数量 majflt/s 系统每秒产生的页面失效(major)数量 4.3上下文切换次数(提高了2个数量级)
[root@hadoop1 ~]# sar -w 2 100 Linux 2.6.32-358.el6.i686 (hadoop1) 08/07/2016 _i686_ (1 CPU) 06:57:10 PM proc/s cswch/s 06:57:12 PM 0.00 14627.04 06:57:14 PM 0.00 14884.81 06:57:16 PM 0.62 14411.11 06:57:18 PM 0.00 1530.41 06:57:20 PM 0.00 118.00 06:57:22 PM 0.00 121.61 06:57:24 PM 0.00 118.094.4队列的长度
sar -q 06:10:01 PM runq-sz plist-sz ldavg-1 ldavg-5 ldavg-15 06:20:01 PM 0 230 0.00 0.00 0.00 06:30:01 PM 0 230 0.00 0.00 0.00 06:40:01 PM 0 230 0.00 0.00 0.00 06:50:01 PM 0 230 0.00 0.00 0.00 07:00:01 PM 0 232 0.00 0.00 0.00 07:10:01 PM 0 230 0.00 0.00 0.00 Average: 0 228 0.00 0.00 0.00基本上没什么变化,可以说明瓶颈不再CPU上。
输出项说明:
runq-sz 运行队列的长度(等待运行的进程数) plist-sz 进程列表中进程(processes)和线程(threads)的数量 ldavg-1 最后1分钟的系统平均负载(System load average) ldavg-5 过去5分钟的系统平均负载 ldavg-15 过去15分钟的系统平均负载 5.调整memcached的内存指标,测试性能
数据量 数据量大小 64m 响应时间
(ms)
128m 响应时间
(ms)
512m响应时间 1000 39.06KB 1191 761 753 10000 390.63KB 3155 3116 3168 20000 781.25KB 5142 4965 4978 30000 1.14MB 7057 6893 6829 40000 1.53MB 8611 8629 8725 50000 1.91MB 10611 10660
10334 100000 3.81MB 19594 19984 19760 结论:发现一个奇怪问题。数据量到了40000时,内存大的反而效率低(多次测试,结果不太稳定,这种情况经常发生)。
个人真有点摸不着头脑了,有清楚麻烦告知下。
我分别对比了换入换出指标,CPU利用率情况等,均没有发现能引起注意的地方。难道是网络问题?
本文由于缺少查询操作,所以未考虑命中率,在以后调优演练时补上。
---------------------------
The End
memcached 正常使用了。接下来,借助memcached实现tomcat集群效果
本文标题:memcached演练(2)访问memcached服务
当前链接:http://cxhlcq.com/article/pipood.html