引言
本文是笔者对Memcached这个高性能分布式缓存组件的实践案例,Memcached是一种高性能的分布式内存对象缓存系统,用于减轻数据库负载,加速动态Web应用,提高网站访问速度。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高应用程序的性能。
本文涉及到的源码gitee地址:源码地址
一、Memcached介绍
主要特点:
-
键值存储:Memcached 使用键值对存储模型,其中键是一个字符串,值可以是任何数据,通常用于存储数据库查询结果、网页或对象的序列化形式。
-
内存存储:数据存储在内存中,这比从硬盘读取数据要快得多。但是,这也意味着数据是非持久化的,如果 Memcached 或操作系统重启,数据会丢失。
-
LRU 缓存替换策略:当内存不足时,Memcached 使用最近最少使用(Least Recently Used,LRU)算法来自动删除不常用的缓存项,以便为新数据腾出空间。
-
简单协议:Memcached 使用一个简单的文本行协议,这使得它易于实现和集成。
-
跨平台:Memcached 可以在多种操作系统上运行,包括 Linux 和 BSD 等。
-
高可用性和可扩展性:Memcached 支持分布式部署,可以在多个服务器上运行,以增加容量和冗余。
-
轻量级和快速:由于其简单的架构和内存存储特性,Memcached 能够提供非常低的延迟和高吞吐量。
适用场景:
-
缓存数据库查询结果。
-
存储用户会话信息。
-
缓存计算结果或频繁访问的内容。
-
存储用户个性化数据。
不适用场景:
-
缓存的数据需要持久化
-
key 的长度大于 250 字符
-
变化频繁且需要存入数据库
-
过大的数据不适宜放在 Memcached 中
与 Redis 比较相同点:
-
都是基于内存的数据库系统,最大存储量是根据机器内存大小而定
-
都有不同的过期策略,分布式数据的备份可以设置一主多从,也可以一主一从(Master-Slave)
-
都支持 key-value 数据缓存
与 Redis 比较不同点:
-
数据持久化支持:Redis 虽然是基于内存的存储系统,但是它本身是支持内存数据的持久化的,而且提供两种主要的持久化策略:RDB 快照和 AOF 日志。而 Memcached 是不支持数据持久化操作的。
-
灾难恢复:Memcached 挂掉后,数据不可恢复。 Redis 数据丢失后可以通过 AOF 恢复
-
IO 方面:Redis 使用的单线程 IO 复用网络模型, 而 Memcached 多线程非阻塞 IO 复用模型
-
数据支持类型:Redis 支持 key-value 数据类型,还有 list、set、zset、hash 等数据结构,而 Memcached 只支持 key-value 数据
-
Value 值大小不同:Redis 最大可以达到 512mb;Memcached 只有 1mb。
-
数据一致性:Memcached 提供了 CAS 命令,可以保证多个并发访问操作同一份数据的一致性问题。Redis 没有提供 CAS 命令,并不能保证这点,不过 Redis 提供了事务的功能,可以保证一串命令的原子性,中间不会被任何操作打断。
二、java客户端介绍
主要有三种不同的 Java 客户端来支持 Memcached, 分别为 Memcached-java-client、Spymemcached 和 XMemcached。
Memcached-java-client 客户端
- Memcached-java-client 客户端推出较早,应用最广泛,阻塞式 IO,稳定性高,高并发下性能低。较早推出的 Memcached JAVA 客户端 API,应用广泛,运行比较稳定,使用阻塞 IO,不支持 CAS 操作,已停止更新。
Spymemcached 客户端
- Spymemcached 客户端时基于 JDK 的 Concurrent 和 NIO,存取速度高,并发性能高,支持 CAS 操作,已停止更新。
XMemcached 客户端
- XMemcached 客户端同样基于 NIO 实现,减少了线程创建和切换的开销,这一点在高并发下特别明显,一直更新。推荐使用。
三、下载安装Memcached
地址:memcached下载
把这个链接复制到新的标签页打开自动下载。下载后的压缩包解压如下
使用管理员权限打开cmd,进入此路径
笔者下载的是1.4.4版本
memcached <1.4.5 版本安装
-
解压下载的安装包到指定目录。
-
在 1.4.5 版本以前 memcached 可以作为一个服务安装,使用管理员权限运行以下命令:
c:\memcached\memcached.exe -d install
注意:你需要使用真实的路径替代 c:\memcached\memcached.exe。
3、然后我们可以使用以下命令来启动和关闭 memcached 服务:
c:\memcached\memcached.exe -d start
c:\memcached\memcached.exe -d stop
然后使用sc query memcached查看命令是否成功:
打开 服务列表发现memcached服务已安装,并且是开机自启动,后续无需手动再执行
memcached.exe -d start命令了
四、各种类型客户端实践
先创建一个Memcached-Project的springboot应用,创建好后去resources下把application.properties改为application.yml
为了方便,三种java客户端,我都使用这个项目来展示。
4.1 Memcached-java-client
首先在pom引入如下依赖:
<!--Memcached-java-client-->
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</dependency>
<dependency>
<groupId>com.whalin</groupId>
<artifactId>Memcached-Java-Client</artifactId>
<version>3.0.2</version>
</dependency>
MemcachedConfig配置类:
package com.hulei.memcached.memcachedjavaclient;
import com.whalin.MemCached.MemCachedClient;
import com.whalin.MemCached.SockIOPool;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author hulei
* Memcached-java-client的配置
*/
@Configuration
public class MemcachedConfig {
@Value("${MemcachedJavaClient.memcached.servers}")
private String[] servers;
@Value("${MemcachedJavaClient.memcached.failover}")
private boolean failover;
@Value("${MemcachedJavaClient.memcached.initConn}")
private int initConn;
@Value("${MemcachedJavaClient.memcached.minConn}")
private int minConn;
@Value("${MemcachedJavaClient.memcached.maxConn}")
private int maxConn;
@Value("${MemcachedJavaClient.memcached.maintSleep}")
private int maintSleep;
@Value("${MemcachedJavaClient.memcached.nagel}")
private boolean nagel;
@Value("${MemcachedJavaClient.memcached.socketTO}")
private int socketTO;
@Value("${MemcachedJavaClient.memcached.aliveCheck}")
private boolean aliveCheck;
@Bean
public SockIOPool sockIOPool () {
SockIOPool pool = SockIOPool.getInstance();
pool.setServers(servers);
pool.setFailover(failover);
pool.setInitConn(initConn);
pool.setMinConn(minConn);
pool.setMaxConn(maxConn);
pool.setMaintSleep(maintSleep);
pool.setNagle(nagel);
pool.setSocketTO(socketTO);
pool.setAliveCheck(aliveCheck);
pool.initialize();
return pool;
}
@Bean
@ConditionalOnBean(SockIOPool.class)
public MemCachedClient memCachedClient(){
return new MemCachedClient();
}
}
yml配置:
MemcachedController测试类:
package com.hulei.memcached.memcachedjavaclient;
import com.whalin.MemCached.MemCachedClient;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
@RestController
public class MemcachedController {
@Resource
private MemCachedClient memCachedClient;
@RequestMapping("/memcachedIndex")
public String memcachedIndex() throws InterruptedException {
// 放入缓存
boolean flag = memCachedClient.set("a", 1);
System.out.println(flag);
// 取出缓存
Object a = memCachedClient.get("a");
System.out.println(a);
// 3s后过期
memCachedClient.set("b", "2", new Date(3000));
Object b = memCachedClient.get("b");
System.out.println(b);
Thread.sleep(3000);
b = memCachedClient.get("b");
System.out.println(a);
System.out.println(b);
return "abcd";
}
}
浏览器输入地址:http://localhost:8080/memcachedIndex,观察IDEA控制台输出:
4.2 SpyMemcached
pom中引入依赖:
<!--SpyMemcached-->
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
SpyMemcachedConfig配置类:
package com.hulei.memcached.spymemcached;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.spy.memcached.MemcachedClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.net.InetSocketAddress;
@Configuration
@Slf4j
public class SpyMemcachedConfig implements CommandLineRunner {
@Value("${SpyMemcached.memcached.ip}")
private String ip;
@Value("${SpyMemcached.memcached.port}")
private int port;
@Getter
private MemcachedClient client = null;
@Override
public void run(String... args) {
try {
client = new MemcachedClient(new InetSocketAddress(ip,port));
} catch (IOException e) {
log.error("SpyMemcached初始化失败",e);
throw new RuntimeException("SpyMemcached初始化失败");
}
}
}
yml配置:
SpyMemcachedController测试控制类:
package com.hulei.memcached.spymemcached;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class SpyMemcachedController {
@Resource
private SpyMemcachedConfig spyMemcachedConfig;
@RequestMapping("/spyMemcachedIndex")
public String spyMemcachedIndex() throws InterruptedException {
/*这个过期时间单位是秒,最大值是60*60*24*30*/
spyMemcachedConfig.getClient().set("spyMemcachedKey",1,"张三");
System.out.println("基于spyMemcached实现,现在的值为 "+spyMemcachedConfig.getClient().get("spyMemcachedKey"));
Thread.sleep(2000);
System.out.println("1秒后缓存内容清除,现在的值为: "+spyMemcachedConfig.getClient().get("spyMemcachedKey"));
return "SpyMemcached";
}
}
浏览器输入地址:http://localhost:8080/spyMemcachedIndex,观察IDEA控制台输出:
4.3 XMemcached
pom中再引入依赖:
<!--XMemcached -->
<dependency>
<groupId>com.googlecode.xmemcached</groupId>
<artifactId>xmemcached</artifactId>
<version>2.4.8</version>
</dependency>
XMemcachedConfig配置类:
package com.hulei.memcached.xmemcached;
import lombok.extern.slf4j.Slf4j;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.MemcachedClientBuilder;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.command.BinaryCommandFactory;
import net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class XMemcachedConfig {
@Value("${XMemcached.memcached.server}")
private String server;
@Value("${XMemcached.memcached.opTimeout}")
private Integer opTimeout;
@Value("${XMemcached.memcached.poolSize}")
private Integer poolSize;
@Value("${XMemcached.memcached.failureMode}")
private boolean failureMode;
@Value("${XMemcached.memcached.enabled}")
private boolean enabled;
@Bean(name = "memcachedClientBuilder")
public MemcachedClientBuilder getBuilder() {
MemcachedClientBuilder memcachedClientBuilder = new XMemcachedClientBuilder(server);
// 内部采用一致性哈希算法
memcachedClientBuilder.setSessionLocator(new KetamaMemcachedSessionLocator());
// 操作的超时时间
memcachedClientBuilder.setOpTimeout(opTimeout);
// 采用二进制传输协议(默认为文本协议)
memcachedClientBuilder.setCommandFactory(new BinaryCommandFactory());
// 设置连接池的大小
memcachedClientBuilder.setConnectionPoolSize(poolSize);
// 是否开起失败模式
memcachedClientBuilder.setFailureMode(failureMode);
return memcachedClientBuilder;
}
/**
* 由Builder创建memcachedClient对象,并注入spring容器中
*/
@Bean(name = "memcachedClient")
public MemcachedClient getClient(@Qualifier("memcachedClientBuilder") MemcachedClientBuilder memcachedClientBuilder) {
MemcachedClient client;
try {
client = memcachedClientBuilder.build();
} catch(Exception e) {
log.error("创建MemcachedClient对象失败", e);
throw new RuntimeException("创建MemcachedClient对象失败", e);
}
return client;
}
}
yml配置:
XMemcachedController测试服务类:
package com.hulei.memcached.xmemcached;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.rubyeye.xmemcached.MemcachedClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class XMemcachedController {
@Resource
private MemcachedClient memcachedClient;
@RequestMapping("/XMemcachedIndex")
public String XMemcachedIndex() {
try {
//新增操作
memcachedClient.set("XMemcachedKeyOne",0,"张三");
System.out.println((String)memcachedClient.get("XMemcachedKeyOne"));
//删除操作
memcachedClient.delete("XMemcachedKeyOne");
System.out.println((String)memcachedClient.get("XMemcachedKeyOne"));
//设置存活时间
memcachedClient.set("XMemcachedKeyTwo",1,"李四");
Thread.sleep(2000);
System.out.println((String)memcachedClient.get("XMemcachedKeyTwo"));
//更新操作
memcachedClient.set("XMemcachedKeyThree",0,"王五");
System.out.println((String)memcachedClient.get("XMemcachedKeyThree"));
memcachedClient.set("XMemcachedKeyThree",0,"王五他儿子");
System.out.println((String)memcachedClient.get("XMemcachedKeyThree"));
} catch (Exception e) {
log.error("XMemcachedIndex error",e);
}
return "xMemcachedIndex";