一、Memcached Client简要介绍
Memcached Client目前有3种:
这三种Client一直存在各种争议:
- Memcached Client for Java 比 SpyMemcached更稳定、更早、更广泛;
- SpyMemcached 比 Memcached Client for Java更高效;
- XMemcached 比 SpyMemcache并发效果更好。
用数据来说话,参考官方性能对比:
Memcached Client for Java: https://github.com/gwhalin/Memcached-Java-Client/wiki/PERFORMANCE
XMemcached: http://xmemcached.googlecode.com/svn/trunk/benchmark/benchmark.html
二、XMemcached特性
XMemcached特性:
- 高性能
- 支持完整的memcached文本协议,二进制协议。
- 支持JMX,可以通过MBean调整性能参数、动态添加/移除server、查看统计等。
- 支持客户端统计
- 支持memcached节点的动态增减。
- 支持memcached分布:余数分布和一致性哈希分布。
- 更多的性能调整选项。
xMemcached是memcached的一个Java客户端,基于Java nio,支持memcached的所有协议。本文简要介绍xMemcached的基本用法,以及与spring的集成配置。
/**
* XMemcachedClientBuilder是MemcachedClientBuilder的一个实现类,XMemcachedClient是MemcachedClient的一个实现类;
* 在实际使用中,应该使用接口,利用多态特性,这里直接使用实现类,是为了方便查看实现类的源码。
*/
/**
* MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.56.200:11211"));
* MemcachedClient memcachedClient = null;
*/
/**
* XMemcachedClientBuilder是MemcachedClientBuilder的一个实现类,XMemcachedClient是MemcachedClient的一个实现类;
* 在实际使用中,应该使用接口,利用多态特性,这里直接使用实现类,是为了方便查看实现类的源码。
*/
/**
* MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.56.200:11211"));
* MemcachedClient memcachedClient = null;
*/
xMemcached的主要方法示例
MemcachedClient 让spring管理,通过xml设置配置,一下是代码配置,下面有和spring的整合
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("192.168.56.200:11211 192.168.56.200:11212"));
XMemcachedClient xMemcachedClient = null;
// 注意处理相关异常
// 默认是采用余数哈希,可以修改为一致性哈希
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
// 启用二进制协议,getAndTouch等方法仅在二进制协议中支持
builder.setCommandFactory(new BinaryCommandFactory());
// build the memcached client
xMemcachedClient = (XMemcachedClient) builder.build();
// set: 第一个参数是key,第二个参数是超时时间,第三个参数是value
xMemcachedClient.set("first", 0, "tianjin");//添加或者更新
xMemcachedClient.add("second", 20, "chengdu");//添加,key不存在添加成功返回true,否则返回false
xMemcachedClient.replace("first", 0, "Beijing");//替换,key已经存在替换成功返回true,不存在返回false
// get:根据key获取value
String firstValue = xMemcachedClient.get("first");
String firstValue = xMemcachedClient.get("first",100);//100毫秒没有返回,超时
// delete: 删除item xMemcachedClient.delete("second"); xMemcachedClient.deleteWithNoReply("third"); // touch:修改item过期时间 xMemcachedClient.touch("first", 0); firstValue = xMemcachedClient.getAndTouch("first", 10); logger.info("getAndTouch op, first value: {}", firstValue); // append, prepend: 追加数据 xMemcachedClient.append("first", ", come on"); xMemcachedClient.prepend("first", "hello "); logger.info("append and prepend op, first value: {}", xMemcachedClient.get("first")); /** * cas: 通过gets操作返回GetResponse,其中包括value值和cas值 */
GetsResponse<Integer> result = client.gets("a"); long cas = result.getCas(); //尝试将a的值更新为2 if (!client.cas("a", 0, 2, cas)) { System.err.println("cas error"); }// cas: 重试更方便;第一个方法表示重试次数,第二个方法表示更新的值
xMemcachedClient.cas("first", 0, new CASOperation<String>() { @Override public int getMaxTries() { return 1;//重试一次 } @Override public String getNewValue(long currentCAS, String currentValue) { return "xian"; } }); // incr/decr: 值的增加减少 incr/decr操作的使用,两个操作类似Java中的原子类如AtomicIntger,用于原子递增或者递减变量数值 xMemcachedClient.incr("id", 3, 0);//自增3,初始值0(默认0) xMemcachedClient.incr("id", 5); xMemcachedClient.decr("id", 2);//递减最小到0
// namespace: set时指定命名空间,get时也需要指定,可以使命名空间中的所有items失效
String ns = "namespace" ; this.memcachedClient.withNamespace(ns, new MemcachedClientCallable<Void>() { public Void call(MemcachedClient client) throws MemcachedException, InterruptedException, TimeoutException { //a,b,c都在namespace下 client.set("a",1); client.set("b",1); client.set("c",1); return null; } }); //获取命名空间内的a对应的值 Integer aValue = memcachedClient.withNamespace(ns, new MemcachedClientCallable<Integer>() { public Integer call(MemcachedClient client) throws MemcachedException, InterruptedException, TimeoutException { return client.get("a"); } }); //使得命名空间失效 memcachedClient.invalidateNamespace(ns);// stats: 获取统计,也可以根据items获取统计Map<InetSocketAddress, Map<String, String>> stats = xMemcachedClient.getStats();for (InetSocketAddress addr: stats.keySet()) { logger.info("stats map: {}: {}", addr, stats.get(addr).toString());}// flush_all: 是所有item都过期xMemcachedClient.flushAll("192.168.56.200:11211");// 删除一个serverxMemcachedClient.removeServer("192.168.56.200:11212");
xMemcached
XMemcached由于是基于nio,因此通讯过程本身是异步的,client发送一个请求给memcached,你是无法确定memcached什么时候返回这个应答,客户端此时只有等待,因此还有个等待超时的概念在这里。客户端在发送请求后,开始等待应答,如果超过一定时间就认为操作失败,这个等待时间默认是5秒(1.3.8开始改为5秒,之前是1秒),上面例子展现的3个方法调用的都是默认的超时时间,这三个方法同样有允许传入超时时间的重载方法,例如
value=client.get(“hello”,3000);
xMemcached与Spring集成
applicationContext.xml的配置:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <!--定义一个server--> <bean name="server1" class="java.net.InetSocketAddress"> <constructor-arg><value>localhost</value></constructor-arg> <constructor-arg><value>11211</value></constructor-arg> </bean> <!--定义XMemcachedClientBuilder实例--> <bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder"> <constructor-arg name="addressList" value="localhost:11211"> </constructor-arg><!--连接超时-->
<property name="connectTimeout"><value>3000</value></property> <!--SASL验证开启 ,如果没有相关环境,请关闭 start--> <property name="authInfoMap"> <map> <entry key-ref="server1"> <bean class="net.rubyeye.xmemcached.auth.AuthInfo" factory-method="plain"> <constructor-arg name="username"><value>xxx</value></constructor-arg> <constructor-arg name="password"><value>123</value></constructor-arg> </bean> </entry> </map> </property> <!--SASL验证开启 end--> <!--设置线程池--> <property name="connectionPoolSize" value="2"></property> <!--使用二进制协议--> <property name="commandFactory"> <bean class="net.rubyeye.xmemcached.command.BinaryCommandFactory"></bean> </property> <!--设置序列化方式--> <property name="transcoder"> <bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder"></bean> </property> <!--设置一致性哈希--> <property name="sessionLocator"> <bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"></bean> </property> </bean> <!--定义memcachedClient,通过memcachedClientBuilder的build方法--> <bean name="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build" destroy-method="shutdown"> </bean></beans>
java测试类:
MemcachedClient memcachedClient = null;
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
try {
memcachedClient = (MemcachedClient) applicationContext.getBean("memcachedClient");
memcachedClient.set("spring", 0, "3.0.0.RELEASE");
logger.info("spring: {}", memcachedClient.get("spring"));
memcachedClient.replace("spring", 0, "4.0.0.RELEASE is coming");
logger.info("spring, {}", memcachedClient.get("spring"));
} catch (Exception e) {
logger.info("spring test", e);
}
遇到的坑:不要每次都使用XMemcachedClientBuilder.build();创建client,内部应该没有做单例,调用多了就会内存溢出,所以自己实现单例
使用下面的配置应该是spring启动时创建client
<!--定义memcachedClient,通过memcachedClientBuilder的build方法--> <bean name="memcachedClient" factory-bean="memcachedClientBuilder" factory-method="build" destroy-method="shutdown"> </bean>如果启动时没有创建成功,最简单的,再手动build一次
@Autowired private XMemcachedClientBuilder memcachedClientBuilder; @Autowired private MemcachedClient memcachedClient; private MemcachedClient createClient() throws Exception{ if(memcachedClient==null){//如果spring没有创建成功,再build一次 return memcachedClient = memcachedClientBuilder.build(); } return null; }问题2:默认连接超时connectTimeout 是60000毫秒,如果网络问题连不上服务器,线程会一直请求1分钟,堵塞时间太长,设置连接超时时间简要总结如下:<!--连接超时-->
<property name="connectTimeout"><value>3000</value></property>
- Memcached的Key,要杜绝使用空格,且长度控制在250个字符。
- Memcached的Value,要控制体积,必须小于1MB,必要时进行使用压缩。
- 失效时间,0为永久有效,最大值不得超过30天(2592000s),否则重新计算可能缓存只有1秒
- Memcached仅支持LRU算法,完全适用你的需要。
- 尽量不要将List这种重体积对象扔到Memcached中,传输、存储都会产生瓶颈。
- 使用一致性哈希算法实现,提高多个Memcacehd Server利用率。
- private MemcachedClientBuilder createMemcachedClientBuilder(
- Properties properties) {
- String addresses = properties.getProperty(ADDRESSES).trim();
- if (logger.isInfoEnabled()) {
- logger.info("Configure Properties:[addresses = " + addresses + "]");
- }
- MemcachedClientBuilder builder = new XMemcachedClientBuilder(
- AddrUtil.getAddresses(addresses));
- // 使用二进制文件
- builder.setCommandFactory(new BinaryCommandFactory());
- // 使用一致性哈希算法(Consistent Hash Strategy)
- builder.setSessionLocator(new KetamaMemcachedSessionLocator());
- // 使用序列化传输编码
- builder.setTranscoder(new SerializingTranscoder());
- // 进行数据压缩,大于1KB时进行压缩
- builder.getTranscoder().setCompressionThreshold(1024);
- return builder;
- }
主要有以下几点参考:
- 使用二进制文件模式
- 使用一致性哈希算法
- 使用序列化编码
- 对数据进行压缩
参考 * https://nkcoder.github.io/2014/02/16/xmemcached-usage-with-spring/ --快速应用 * * https://github.com/killme2008/xmemcached --官方相关链接: (写的非常好)
Memcached笔记——(一)安装&常规错误&监控
Memcached笔记——(二)XMemcached&Spring集成
Memcached笔记——(三)Memcached使用总结
Memcached笔记——(四)应对高并发攻击
2016-11-16 追加: 1:使用incr原子性 自增发送红包,应对高并发 2:使用add 作为分布式锁 坑1:set 设置过期时间如果超过3个月,接口返回成功,其实没有存上