第 4-1 课:Spring Boot 操作 Memcache

《精通 Spring Boot 42 讲》共分五⼤部分,第四部分主要讲解 Spring Boot 和中间件的使⽤,共 10
课,中间件是互联⽹公司⽀撑⾼并发业务的必备组件,常⽤的组件有缓存、消息中间件、 NoSQL 数据
库、定时任务等。常⽤的缓存中间件有 Memcache Redis ,缓存主要⽀撑业务架构中⾼速读写;常
⽤的消息中间件有 ActiveMQ RabbitMQ ,使⽤消息中间件的意义是,尽快地完成主线交易,其他⾮
实时业务异步或者解耦完成;最主流的 NoSQL MongoDB ElasticSearch ,前者主要是解决分布式
存储和检索的问题,后者主要解决分布式⽂档检索的解决⽅案;定时任务常常使⽤开源框架 Quartz
以上的这些内容我们都会在第四部分进⾏学习。
在常⻅的企业架构中,随着公司业务⾼速发展,最先出现瓶颈的是数据库,这个时候很多企业就会考虑使⽤
缓存来缓解数据库的压⼒,这是缓存使⽤最多的场景之⼀;另外在⾼并发抢购、分布式 Session 等场景下,
也会使⽤缓存来提⾼系统的⾼可⽤性。常⽤的缓存中间件有 Memcache Redis ,今天我们先来学习 Memcache 的使⽤。

Memcache 介绍

Memcache 是⼀个⾃由和开放源代码、⾼性能、分配的内存对象缓存系统。简单来说, Memcache 是⼀个⾼
性能的分布式内存对象的 key-value 缓存系统,⽤于加速动态 Web 应⽤程序,减轻数据库负载,现在也有很
多⼈将它作为内存式数据库在使⽤。
 
它可以应对任意多个连接,使⽤⾮阻塞的⽹络 IO ,由于它的⼯作机制是在内存中开辟⼀块空间,然后建⽴⼀
Hash 表, Memcached ⾃动管理这些 Hash 表。
 
Memcache 由国外社区⽹站 LiveJournal 开发团队开发,设计理念就是⼩⽽强⼤,它简单的设计促进了快速
部署、易于开发并解决⾯对⼤规模的数据缓存的许多难题,⽽所开放的 API 使得 Memcache 能⽤于 Java
C/C++/C# Perl Python PHP Ruby 等⼤部分流⾏的程序语⾔。
 
Memcache Memcached 的区别: Memcache 是这个项⽬的名称,⽽ Memcached 是服务器端的主 程序名称。

Memcache 特点

协议简单

Memcache 的服务端客户端通信使⽤简单的⽂本协议,通过 Telnet 即可在 Memcached 上存取数据。

基于 Libevent 的事件处理

Libevent 是⼀套跨平台的事件处理接⼝的封装,能够兼容包括这些操作系统: Windows/Linux/BSD/Solaris
等操作系统的的事件处理,包装的接⼝包括: poll select Windows )、 epoll Linux )、 GitChat
kqueue BSD /dev/pool Solaris )。
Memcache 使⽤ Libevent 来进⾏⽹络并发连接的处理,能够保持在很⼤并发情况下,仍旧能够保持快速的响 应能⼒。

内置内存存储⽅式

Memcache 中保存的数据都存储在 Memcache 内置的内存存储空间中。由于数据仅存在于内存中,因此重启
Memcache 、重启操作系统会导致数据全部丢失。 Memcache LRU Least Recently Used )算法⾃动删除不
使⽤的缓存,不过这个功能是可以配置的, Memcache 启动时通过 “-M” 参数可以禁⽌ LRU 。不过,
Memcache 本身是为缓存⽽设计的,建议开启 LRU

不适应场景

  • 缓存对象不能⼤于 1 MB
  • key 的⻓度⼤于 250 字符
  • Memcache 未提供任何安全策略
  • 不⽀持久化

Memcache 安装

Centos 下安装使⽤ yum 命令安装 Memcache ⾮常简单:
yum install -y memcached
启动:
/usr/bin/memcached -b -p 11211 -m 150 -u root >> /tmp/memcached.log &
启动参数可以配置,常⽤的命令选项如下:
  • m 内存
  • c 最⼤链接数
  • p 端⼝
  • u ⽤户
  • t 线程数
查看 memcached 是否在运⾏:
ps -ef | grep memcached

Memcache 客户端

Memcached Client ⽬前有 3 种: GitChat
  • Memcached Client for Java(已经停⽌更新)
  • SpyMemcached(已经停⽌更新)
  • XMemcached(主流使⽤)
Memcached Client for Java SpyMemcached 更稳定、更早、更⼴泛; SpyMemcached Memcached
Client for Java 更⾼效; XMemcached SpyMemcache 并发效果更好。
 
曾经有⼀段时间 SpyMemcached 使⽤⽐较⼴泛,我简单介绍⼀下。

Spymemcached 介绍

Spymemcached 是⼀个采⽤ Java 开发的异步、单线程的 Memcached 客户端,使⽤ NIO 实现。
Spymemcached Memcached 的⼀个流⾏的 Java Client 库,性能表现出⾊,⼴泛应⽤于 Java +
Memcached 项⽬中。
 
Spymemcached 最早由 Dustin Sallings 开发, Dustin 后来和别⼈⼀起创办了 Couchbase (原
NorthScale ),职位为⾸席架构师, 2014 年加⼊ Google
 

XMemcached 简介

现在使⽤最⼴泛的 Memcache Java 客户端是 XMemcached ,它是⼀个新的 Java Memcache Client
Memcached 通过它的⾃定义协议与客户端交互,⽽ XMemcached 就是它的⼀个 Java 客户端实现。相⽐其
他客户端, XMemcached 有什么优点呢?

XMemcached 的主要特性

XMemcached ⽀持设置连接池、宕机报警、使⽤⼆进制⽂件、⼀致性哈希算法、进⾏数据压缩等操作,总结
如下:
  • ⾼性能,由 Nio ⽀持;
  • 协议完整,Xmemcached ⽀持所有的 Memcached 协议,包括 1.4.0 正式开始使⽤的⼆进制协议;
  • ⽀持客户端分布,提供了⼀致性哈希(Consistent Hash)算法的实现;
  • 允许设置节点权重,XMemcached 允许通过设置节点的权重来调节 Memcached 的负载,设置的权重越 ⾼,该 Memcached 节点存储的数据将越多,所承受的负载越⼤;
  • 动态增删节点,Memcached 允许通过 JMX 或者代码编程实现节点的动态添加或者移除,⽅便⽤户扩展 和替换节点等;
  • XMemcached 通过 JMX 暴露的⼀些接⼝,⽀持 Client 本身的监控和调整,允许动态设置调优参数、查 看统计数据、动态增删节点等;
  • ⽀持客户端连接池,对同⼀个 Memcached 可以创建 N 个连接组成连接池来提⾼客户端在⾼并发环境下 的表现,⽽这⼀切对使⽤者来说却是透明的;
  • 可扩展性,XMemcached 是基于 Java Nio 框架 Yanf4j 实现的,因此在实现上结构相对清楚,分层⽐较 明晰。

快速上⼿

上⾯介绍了这么多,最需要关注的是 XMemcached 是最佳的选择,下⾯我们先⽤⼀个示例,来感受⼀下
Spring Boot 使⽤ Xmemcached 集成 Memcache

添加配置

添加依赖包:


 com.googlecode.xmemcached
 xmemcached
 2.4.5
添加配置⽂件:
# 单个 Memcached 配置
memcached.servers=192.168.0.161:11211
# 连接池
memcached.poolSize=10
#操作超时时间
memcached.opTimeout=6000
配置 Memcached 的地址和端⼝号、连接池和操作超时时间,使⽤集群时可以拼接多个地址: "host1:port1
host2:port2 …"
创建 XMemcachedProperties 类,读配置信息:
@Component
@ConfigurationProperties(prefix = "memcached")
public class XMemcachedProperties {
 private String servers;
 private int poolSize;
 private long opTimeout;
 //省略 getter/setter
}
启动加载
利⽤ @Confifiguration 注解,在启动时对 Memcached 进⾏初始化。 GitChat
@Configuration
public class MemcachedBuilder {
 protected static Logger logger = LoggerFactory.getLogger(MemcachedBuilder.cla
ss);
 @Resource
 private XMemcachedProperties xMemcachedProperties;
 @Bean
 public MemcachedClient getMemcachedClient() {
 MemcachedClient memcachedClient = null;
 try {
 MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.
getAddresses(xMemcachedProperties.getServers()));
 builder.setConnectionPoolSize(xMemcachedProperties.getPoolSize());
 builder.setOpTimeout(xMemcachedProperties.getOpTimeout());
 memcachedClient = builder.build();
 } catch (IOException e) {
 logger.error("inint MemcachedClient failed ",e);
 }
 return memcachedClient;
 }
}
因为 XMemcachedClient 的创建有⽐较多的可选项,所以提供了⼀个 XMemcachedClientBuilder 类⽤于构建
MemcachedClient MemcachedClient 是主要接⼝,操作 Memcached 的主要⽅法都在这个接⼝,
XMemcachedClient 是它的⼀个实现。
 
在⽅法 getMemcachedClient() 添加 @Bean 注解,代表启动时候将⽅法构建好的实例注⼊到 Spring 容器
中,后⾯在需要使⽤的类中,直接注⼊ MemcachedClient 即可。

进⾏测试

我们创建⼀个 MemcachedTests 类,来测试 Memcached 配置信息是否配置正确。
@RunWith(SpringRunner.class)
@SpringBootTest
public class MemcachedTests {
 @Autowired
 private MemcachedClient memcachedClient;
}
测试 Memcached get set ⽅法。 GitChat
@Test
public void testGetSet() throws Exception {
 memcachedClient.set("hello", 0, "Hello,xmemcached");
 String value = memcachedClient.get("hello");
 System.out.println("hello=" + value);
 memcachedClient.delete("hello");
}
存储数据是通过 set ⽅法,它有三个参数,第⼀个是存储的 key 名称,第⼆个是 expire 时间(单位秒),超
过这个时间, memcached 将这个数据替换出去, 0 表示永久存储(默认是⼀个⽉),第三个参数就是实际存
储的数据,可以是任意的 Java 可序列化类型。
 
获取存储的数据是通过 get ⽅法,传⼊ key 名称即可;如果要删除存储的数据,可以通过 delete ⽅法,它也
是接受 key 名称作为参数。
 
执⾏ testMemcached() 单元测试之后,控制台会输出:
hello=Hello,xmemcached
证明 Memcached 配置、设置和获取值成功。

XMemcached 语法介绍

XMemcached 有⾮常丰富的语法来⽀持,我们对缓存使⽤的各种场景,接下来⼀⼀介绍。

常⽤操作

除过上⾯的 get set delete 等⽅法外, Memcache 还有很多常⽤的操作。 GitChat
@Test
public void testMore() throws Exception {
 if (!memcachedClient.set("hello", 0, "world")) {
 System.err.println("set error");
 }
 if (!memcachedClient.add("hello", 0, "dennis")) {
 System.err.println("Add error,key is existed");
 }
 if (!memcachedClient.replace("hello", 0, "dennis")) {
 System.err.println("replace error");
 }
 memcachedClient.append("hello", " good");
 memcachedClient.prepend("hello", "hello ");
 String name = memcachedClient.get("hello", new StringTranscoder());
 System.out.println(name);
 memcachedClient.deleteWithNoReply("hello");
}
  • add 命令,⽤于将 value(数据值)存储在指定的 key(键)中。如果 add key 已经存在,则不会更 新数据(过期的 key 会更新),之前的值将仍然保持相同,并且将获得响应 NOT_STORED
  • replace 命令,⽤于替换已存在的 key(键)的 value(数据值)。如果 key 不存在,则替换失败,并且 将获得响应 NOT_STORED
  • append 命令,⽤于向已存在 key(键)的 value(数据值)后⾯追加数据。
  • prepend 命令,⽤于向已存在 key(键)的 value(数据值)前⾯追加数据。
  • deleteWithNoReply ⽅法,这个⽅法删除数据并且告诉 Memcached,不⽤返回应答,因此这个⽅法不 会等待应答直接返回,⽐较适合于批量处理。

Incr Decr

Incr Decr 类似数据的增和减,两个操作类似 Java 中的原⼦类如 AtomicIntger ,⽤于原⼦递增或者递减变
量数值,示例如下:
@Test
public void testIncrDecr() throws Exception {
 memcachedClient.delete("Incr");
 memcachedClient.delete("Decr");
 System.out.println(memcachedClient.incr("Incr", 6, 12));
 System.out.println(memcachedClient.incr("Incr", 3));
 System.out.println(memcachedClient.incr("Incr", 2));
 System.out.println(memcachedClient.decr("Decr", 1, 6));
 System.out.println(memcachedClient.decr("Decr", 2));
}
为了防⽌数据⼲扰,在测试开始前前调⽤ delete() ⽅法清除两个 key 值。
 
输出:
12
15
17
64
Incr Decr 都有三个参数的⽅法,第⼀个参数指定递增的 key 名称,第⼆个参数指定递增的幅度⼤⼩,第
三个参数指定当 key 不存在的情况下的初始值,两个参数的重载⽅法省略了第三个参数,默认指定为 0

Counter

Xmemcached 还提供了⼀个称为计数器的封装,它封装了 incr/decr ⽅法,使⽤它就可以类似 AtomicLong
样去操作计数,示例如下:
 
@Test
public void testCounter() throws Exception {
 MemcachedClient memcachedClient = memcachedUtil.getMemcachedClient();
 Counter counter=memcachedClient.getCounter("counter",10);
 System.out.println("counter="+counter.get());
 long c1 =counter.incrementAndGet();
 System.out.println("counter="+c1);
 long c2 =counter.decrementAndGet();
 System.out.println("counter="+c2);
 long c3 =counter.addAndGet(-10);
 System.out.println("counter="+c3);
}
  • memcachedClient.getCounter("counter",10) ,第⼀个参数为计数器的 key,第⼆参数当 key 不存在时的默认值;
  • counter.incrementAndGet() ,执⾏⼀次给计数器加 1
  • counter.decrementAndGet() ,执⾏⼀次给计数器减 1
查看 counter.addAndGet(-10) 源码(如下),发现 addAndGet() 会根据传⼊的值的正负来判断,选择直接给
对应的 key 加多少或者减多少,底层也是使⽤了 incr() decr() ⽅法。
public long addAndGet(long delta) throws MemcachedException, InterruptedException,
 TimeoutException {
 return delta >= 0L ? this.memcachedClient.incr(this.key, delta, this.initialVa
lue) : this.memcachedClient.decr(this.key, -delta, this.initialValue);
}
Counter 适合在⾼并发抢购场景下做并发控制。

CAS 操作GitChat

Memcached 是通过 CAS 协议实现原⼦更新,所谓原⼦更新就是 Compare and Set ,原理类似乐观锁,每次
请求存储某个数据同时要附带⼀个 CAS 值, Memcached ⽐对这个 CAS 值与当前存储数据的 CAS 值是否相
等,如果相等就让新的数据覆盖⽼的数据,如果不相等就认为更新失败,这在并发环境下特别有⽤。
XMemcached 提供了对 CAS 协议的⽀持(⽆论是⽂本协议还是⼆进制协议), CAS 协议其实是分为两个步
骤:获取 CAS 值和尝试更新,因此⼀个典型的使⽤场景如下:
GetsResponse result = client.gets("a");
long cas = result.getCas(); 
//尝试将 a 的值更新为 2
if (!client.cas("a", 0, 2, cas)) {
 System.err.println("cas error");
}
⾸先通过 gets ⽅法获取⼀个 GetsResponse ,此对象包装了存储的数据和 CAS 值,然后通过 CAS ⽅法尝试
原⼦更新,如果失败打印 “cas error” 。显然,这样的⽅式很繁琐,并且如果你想尝试多少次原⼦更新就需要⼀
个循环来包装这⼀段代码,因此 XMemcached 提供了⼀个 CASOperation 接⼝包装了这部分操作,允许你
尝试 N 次去原⼦更新某个 key 存储的数据,⽆需显式地调⽤ gets 获取 CAS 值,上⾯的代码简化为 :
client.cas("a", 0, new CASOperation() {
 public int getMaxTries() {
 return 1;
 }
 public Integer getNewValue(long currentCAS, Integer currentValue) {
 return 2;
 }
});
CASOpertion 接⼝只有两个⽅法,⼀个是设置最⼤尝试次数的 getMaxTries ⽅法,这⾥是尝试⼀次,如果尝
试超过这个次数没有更新成功将抛出⼀个 TimeoutException ,如果你想⽆限尝试(理论上),可以将返回值
设定为 Integer.MAX_VALUE ;另⼀个⽅法是根据当前获得的 GetsResponse 来决定更新数据的
getNewValue ⽅法,如果更新成功,这个⽅法返回的值将存储成功,其两个参数是最新⼀次 gets 返回的
GetsResponse 结果。

设置超时时间

XMemcached 由于是基于 nio ,因此通讯过程本身是异步的, client 发送⼀个请求给 Memcached ,你是⽆法
确定 Memcached 什么时候返回这个应答,客户端此时只有等待,因此还有个等待超时的概念在这⾥。客户
端在发送请求后,开始等待应答,如果超过⼀定时间就认为操作失败,这个等待时间默认是 5 秒,也可以在
获取的时候配置超时时间。
 
value=client.get("hello",3000);
就是等待 3 秒超时,如果 3 秒超时就跑出 TimeutException ,⽤户需要⾃⼰处理这个异常。因为等待是通过 GitChat
调⽤ CountDownLatch.await(timeout) ⽅法,所以⽤户还需要处理中断异常 InterruptException ,最后的
MemcachedException 表示 Xmemcached 内部发⽣的异常,如解码编码错误、⽹络断开等异常情况。

更新缓存过期时间

经常有这样的需求,就是希望更新缓存数据的超时时间( expire time ),现在 Memcached 已经⽀持 touch
协议,只需要传递 key 就更新缓存的超时时间:
client.touch(key,new-expire-time);
有时候你希望获取缓存数据并更新超时时间,这时候可以⽤ getAndTouch ⽅法(仅⼆进制协议⽀持):
client.getAndTouch(key,new-expire-time);
如果在使⽤过程中报以下错误:
 
Caused by: net.rubyeye.xmemcached.exception.UnknownCommandException: Response erro
r,error message:Unknow command TOUCH,key=Touch
 at net.rubyeye.xmemcached.command.Command.decodeError(Command.java:250)
说明安装的 Memcached 服务不⽀持 touch 命令,建议升级。
测试示例:
@Test
public void testTouch() throws Exception {
 memcachedClient.set("Touch", 2, "Touch Value");
 Thread.sleep(1000);
 memcachedClient.touch("Touch",6);
 Thread.sleep(2000);
 String value =memcachedClient.get("Touch",3000);
 System.out.println("Touch=" + value);
}

Memcached 集群

Memcached 的分布是通过客户端实现的,客户端根据 key 的哈希值得到将要存储的 Memcached 节点,并
将对应的 value 存储到相应的节点。
 
XMemcached 同样⽀持客户端的分布策略,默认分布的策略是按照 key 的哈希值模以连接数得到的余数,对
应的连接就是将要存储的节点。如果使⽤默认的分布策略,不需要做任何配置或者编程。
 
XMemcached 同样⽀持 ⼀致性哈希 Consistent Hash) ,通过编程设置:
 
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses
("server1:11211 server2:11211 server3:11211"));
builder.setSessionLocator(new KetamaMemcachedSessionLocator());
MemcachedClient client=builder.build();
XMemcached 还提供了额外的⼀种哈希算法 —— 选举散列,在某些场景下可以替代⼀致性哈希:
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
 AddrUtil.getAddresses("server1:11211 server2:1
1211 server3:11211"));
builder.setSessionLocator(new ElectionMemcachedSessionLocator());
MemcachedClient mc = builder.build();
在集群的状态下可以给每个服务设置不同的权重:
MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses
("localhost:12000 localhost:12001"),new int[]{1,3});
MemcachedClient memcachedClient=builder.build();

SASL 验证

Memcached 1.4.3 开始⽀持 SASL 验证客户端,在服务器配置启⽤ SASL 之后,客户端需要通过授权验证才
可以跟 Memcached 继续交互,否则将被拒绝请求, XMemcached 1.2.5 开始⽀持这个特性。假设
Memcached 设置了 SASL 验证,典型地使⽤ CRAM-MD 5 或者 PLAIN 的⽂本⽤户名和密码的验证机制,假
设⽤户名为 cacheuser ,密码为 123456 ,那么编程的⽅式如下:
MemcachedClientBuilder builder = new XMemcachedClientBuilder(
 AddrUtil.getAddresses("localhost:11211"));
builder.addAuthInfo(AddrUtil.getOneAddress("localhost:11211"), AuthInfo
 .typical("cacheuser", "123456"));
// Must use binary protocol
builder.setCommandFactory(new BinaryCommandFactory());
MemcachedClient client=builder.build();
请注意,授权验证仅⽀持⼆进制协议。

查看统计信息

Memcached 提供了统计协议⽤于查看统计信息:
 
Map> result=client.getStats();
getStats ⽅法返回⼀个 map ,其中存储了所有已经连接并且有效的 Memcached 节点返回的统计信息,你也
可以统计具体的项⽬,如统计 items 项⽬:
 
Map> result=client.getStatsByItem("items");
只要向 getStatsByItem 传⼊需要统计的项⽬名称即可,我们可以利⽤这个功能,来做 Memcached 状态监控
等。

总结

Memcached 是⼀款⾮常流⾏的缓存中间件,被⼴泛应⽤在各场景中,使⽤缓存可以环境数据库压⼒,某些
场景下使⽤缓存可以⼤⼤提⾼复⽤的 Tps XMemcached Memcached 的⼀个⾼性能 Nio 客户端,⽀持
Memcached 底层各种操作,并且在 Memcached 协议的基础上进⾏了封装和完善,提供了连接池、集群、
数据压缩、分布式算法等⾼级功能,不论是完善度和性能各⽅⾯来看, XMemcached 都是⽬前最为推荐的⼀
Memcached 客户端。
点击这⾥下载源码

你可能感兴趣的:(Spring,Boot,Memcached,XMemcached)