前言: 当前高流量、大数据时代,分布式缓存已成为构建高性能、可扩展 Java 应用的关键技术。
目录
一、初窥缓存门径
缓存为何物
缓存,简而言之,就是把数据暂存到一个读取速度超快的地方。就好比你经常要翻看某本书的某几页内容,每次都要从书架上拿下来翻找未免太慢了。要是能直接把这几页复印一份,放在触手可及的桌面上,以后查看不就方便多了?在计算机系统里,这个 “桌面” 就相当于缓存。它能让那些频繁使用的数据瞬间被获取,大大减少了去硬盘、数据库这些 “书架” 上费力寻找的时间。对于 Java 应用来说,用好缓存,就像是给一辆汽车装上了涡轮增压,性能瞬间提升。
二、解锁分布式缓存
Redis 基础魔法
(一)Redis 是啥?
Redis,一个听起来就很高大上的名字,其实就是个开源的高性能键值对存储系统。它把数据都存到内存里,所以速度极快。而且它不仅仅局限于简单的键值对,还能存储像列表、集合、哈希表这些复杂的数据结构。这就让 Redis 可以在各种复杂场景下大展拳脚。
(二)简单 Java 操作 Redis
先得引入 Jedis 依赖,这是跟 Redis 打交道的工具。在 Maven 项目里,只需要在 pom.xml
加上这么一段代码:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>4.2.3</version>
</dependency>
然后就可以写代码操作 Redis 了。看下面这个例子,把一个简单的字符串存进去,再取出来:
import redis.clients.jedis.Jedis;
public class RedisBasicExample {
public static void main(String[] args) {
// 连接到本地的 Redis 服务器
Jedis jedis = new Jedis("localhost", 6379);
// 如果 Redis 设置了密码,就加上这句
jedis.auth("你的密码");
// 往 Redis 里存个键值对
jedis.set("name", "张三");
// 再从 Redis 里取出来
String name = jedis.get("name");
System.out.println("从 Redis 里取出来的名字是:" + name);
// 关闭连接
jedis.close();
}
}
运行这段代码,就会看到控制台输出:从 Redis 里取出来的名字是:张三。这就说明数据成功存进去又取出来了,简单吧?
(三)Redis 高级用法:用它来存用户信息
假设我们有个用户信息类,里面包含姓名、年龄这些信息。我们可以用 Redis 的哈希结构来存这个用户信息,这样以后查某个用户的具体信息就特别方便。代码是这样的:
import redis.clients.jedis.Jedis;
import java.util.HashMap;
import java.util.Map;
public class RedisUserExample {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("你的密码");
// 存储用户信息到 Redis 哈希结构里
Map<String, String> user = new HashMap<>();
user.put("name", "李四");
user.put("age", "25");
jedis.hmset("user:001", user);
// 获取用户信息里的名字
String userName = jedis.hget("user:001", "name");
System.out.println("用户的名字是:" + userName);
// 获取用户的所有信息
Map<String, String> userInfo = jedis.hgetAll("user:001");
System.out.println("用户的所有信息:" + userInfo);
jedis.close();
}
}
运行后,控制台会先打印出用户的名字是:李四,接着打印出用户的所有信息。这下是不是感觉很实用?我们把一个复杂的对象拆开,存到了 Redis 里,而且还能灵活地查各个字段。
三、Memcached
简洁高效的力量
(一)Memcached 简介
Memcached 也是一个分布式缓存系统,它专注于把数据快速地存取。它的设计很简洁,只提供基本的键值对存储功能。这就像是一个专门用来存东西的盒子,虽然没 Redis 那么多功能,但存取速度极快。
(二)用 Java 玩转 Memcached
要操作 Memcached,得先引入客户端库。比如用 Spymemcached,在 pom.xml
加上:
<dependency>
<groupId>net.spy</groupId>
<artifactId>spymemcached</artifactId>
<version>2.12.3</version>
</dependency>
然后写代码,把数据存进去,再取出来看看:
import net.spy.memcached.AddrUtil;
import net.spy.memcached.MemcachedClient;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MemcachedExample {
public static void main(String[] args) {
MemcachedClient memcachedClient = null;
try {
// 连接到 Memcached 服务器
memcachedClient = new MemcachedClient(AddrUtil.getAddresses("localhost:11211"));
// 存数据,存 60 秒就自动消失
memcachedClient.set("greeting", 60, "Hello,Memcached!");
// 取数据
String greeting = (String) memcachedClient.get("greeting");
System.out.println("从 Memcached 里取出来的问候语是:" + greeting);
} catch (IOException | InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
if (memcachedClient != null) {
try {
memcachedClient.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行这段代码,就能看到控制台打印出:从 Memcached 里取出来的问候语是:Hello,Memcached! 这就证明 Memcached 把数据存好并取回来了。
四、Redis 与 Memcached 的对决
选谁?
(一)功能对比
Redis 就像是个全能选手,能存各种复杂的数据结构,还有持久化功能,能保证数据不会因为服务器重启就丢了。Memcached 就像是个专注于速度的短跑健将,只提供简单的存取功能,数据都存在内存里,服务器一重启,数据就都没了。
(二)性能对比
在存取简单的数据时,Memcached 的速度可能会稍微快一点,因为它没有那么多复杂的功能拖累。不过 Redis 也很快,而且它的那些强大功能在很多场景下是不可替代的。
(三)应用场景对比
要是你需要存复杂的数据结构,比如存一个购物车里各种商品的信息,或者需要数据持久化,Redis 是首选。要是你只是简单地缓存一些数据,比如网页的静态内容,Memcached 足够用了。
五、实践案例
把理论变成实战
(一)电商网站缓存商品信息
在电商网站里,商品信息页是访问量最大的页面之一。每次都去数据库查商品信息,速度肯定跟不上。我们可以用 Redis 来缓存这些商品信息,把数据库的压力降下来。
import redis.clients.jedis.Jedis;
public class EcommerceProductCache {
public static void main(String[] args) {
Jedis jedis = new Jedis("localhost", 6379);
jedis.auth("你的密码");
String productId = "10001";
// 先从 Redis 缓存里取商品信息
String productInfo = jedis.get("product:" + productId);
if (productInfo == null) {
System.out.println("缓存里没有商品信息,正在查询数据库...");
// 模拟从数据库查询商品信息
productInfo = "商品名称:iPhone 15,价格:6999 元,库存:100 台";
// 把商品信息存到 Redis 缓存里,5 分钟后自动过期
jedis.setex("product:" + productId, 300, productInfo);
} else {
System.out.println("缓存里有商品信息,直接使用");
}
System.out.println("商品信息:" + productInfo);
jedis.close();
}
}
运行这段代码,第一次运行会去查数据库,然后把商品信息存到 Redis 缓存里。之后的请求就直接从 Redis 缓存里取数据,速度那叫一个快。
(二)内容管理系统缓存文章内容
对于一个内容管理系统,文章的内容一旦发布,一般不会频繁修改。我们可以用 Memcached 把文章内容缓存起来,减少对数据库的查询。
import net.spy.memcached.AddrUtil;
import net.spy.memcached.MemcachedClient;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class CMSArticleCache {
public static void main(String[] args) {
MemcachedClient memcachedClient = null;
try {
memcachedClient = new MemcachedClient(AddrUtil.getAddresses("localhost:11211"));
String articleId = "article001";
// 先从 Memcached 缓存里取文章内容
String articleContent = (String) memcachedClient.get(articleId);
if (articleContent == null) {
System.out.println("缓存里没有文章内容,正在查询数据库...");
// 模拟从数据库查询文章内容
articleContent = "这是一篇关于分布式缓存技术的深度分析文章,探讨了 Redis 和 Memcached 在 Java 应用中的实践...";
// 把文章内容存到 Memcached 缓存里,10 分钟后自动过期
memcachedClient.set(articleId, 600, articleContent);
} else {
System.out.println("缓存里有文章内容,直接使用");
}
System.out.println("文章内容:" + articleContent);
} catch (IOException | InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
if (memcachedClient != null) {
try {
memcachedClient.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行这个程序,第一次运行会去数据库查询文章内容并存到 Memcached 缓存里。之后的请求直接从缓存里取,响应速度提升明显。
六、总结
分布式缓存,性能提升的利器
(一)Redis 和 Memcached 的总结
Redis 和 Memcached 都是 Java 开发中极具价值的分布式缓存工具。Redis 功能丰富,支持多种复杂的数据结构,还能持久化数据,适合处理复杂业务场景,例如缓存用户信息、订单信息、实现排行榜等。Memcached 则以简洁和高效著称,专注于快速的键值对存储,适合简单的缓存需求,比如缓存网页内容、session 数据等。
(二)合理选择和使用缓存的建议
-
根据业务需求选择 :如果业务场景需要缓存复杂数据结构或要求数据持久化,Redis 是更好的选择。对于简单的、读密集型的缓存需求,且对数据持久性没有要求,Memcached 则能提供更高的性能。
-
设置合理的缓存过期时间 :根据数据的更新频率和重要性,合理设置缓存的过期时间,既能保证数据的新鲜度,又能避免缓存数据无限增长。
-
缓存穿透和雪崩的应对 :在使用缓存时,要注意这两个问题。缓存穿透是指查询一个不存在的数据,导致每次请求都击穿缓存直接查询数据库。可以通过布隆过滤器来解决。缓存雪崩是指大量缓存同时过期,导致瞬间对数据库造成巨大压力。可以通过给缓存设置不同的过期时间来缓解。
(三)实践中的注意事项
-
数据一致性 :由于缓存和数据库是两个独立的存储系统,在更新数据时要保证两者的一致性。可以在更新数据库后,及时更新或删除缓存中的对应数据。
-
缓存预热 :在应用启动时,预先将一些常用的数据加载到缓存中,这样可以避免初始请求时频繁查询数据库,提升用户体验。
-
监控和优化 :定期监控缓存的命中率、内存使用情况等指标,根据实际情况对缓存策略进行优化,例如调整缓存过期时间、清理无效缓存等。