亲爱的朋友们,热烈欢迎你们来到 青云交的博客!能与你们在此邂逅,我满心欢喜,深感无比荣幸。在这个瞬息万变的时代,我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的博客,正是这样一个温暖美好的所在。在这里,你们不仅能够收获既富有趣味又极为实用的内容知识,还可以毫无拘束地畅所欲言,尽情分享自己独特的见解。我真诚地期待着你们的到来,愿我们能在这片小小的天地里共同成长,共同进步。
本博客的精华专栏:
【青云交社区】和【架构师社区】的精华频道:
展望未来,我将持续深入钻研前沿技术,及时推出如人工智能和大数据等相关专题内容。同时,我会努力打造更加活跃的社区氛围,举办技术挑战活动和代码分享会,激发大家的学习热情与创造力。我也会加强与读者的互动,依据大家的反馈不断优化博客的内容和功能。此外,我还会积极拓展合作渠道,与优秀的博主和技术机构携手合作,为大家带来更为丰富的学习资源和机会。
我热切期待能与你们一同在这个小小的网络世界里探索、学习、成长。你们的每一次点赞、关注、评论、打赏和订阅专栏,都是对我最大的支持。让我们一起在知识的海洋中尽情遨游,共同打造一个充满活力与智慧的博客社区。✨✨✨
衷心地感谢每一位为我点赞、给予关注、留下真诚留言以及慷慨打赏的朋友,还有那些满怀热忱订阅我专栏的坚定支持者。你们的每一次互动,都犹如强劲的动力,推动着我不断向前迈进。倘若大家对更多精彩内容充满期待,欢迎加入【青云交社区】或 【架构师社区】,如您对《 涨粉 / 技术交友 / 技术交流 / 内部学习资料 / 副业与搞钱 / 商务合作 》感兴趣的各位同仁, 欢迎在文章末尾添加我的微信名片:【QingYunJiao】(点击直达)【备注:CSDN 技术交流】。让我们携手并肩,一同踏上知识的广袤天地,去尽情探索。此刻,请立即访问我的主页 或【青云交社区】吧,那里有更多的惊喜在等待着你。相信通过我们齐心协力的共同努力,这里必将化身为一座知识的璀璨宝库,吸引更多热爱学习、渴望进步的伙伴们纷纷加入,共同开启这一趟意义非凡的探索之旅,驶向知识的浩瀚海洋。让我们众志成城,在未来必定能够汇聚更多志同道合之人,携手共创知识领域的辉煌篇章!
亲爱的 Java 和 大数据爱好者们,大家好!在我们之前的探索之旅中,一同领略了 Java 与大数据智能推荐系统的精妙之处(如《Java 大视界 – Java 与大数据智能推荐系统:算法实现与个性化推荐(四)》所述),也深入钻研了 Java 大数据机器学习从数据预处理到模型训练与部署的全过程(参考《Java 大视界 – Java 大数据机器学习应用:从数据预处理到模型训练与部署(三)》)。此刻,让我们将目光聚焦于 Java 大数据分布式缓存这一关键领域,它宛如一座桥梁,连接着数据与高效访问之间的沟壑,为提升数据访问性能提供了强有力的支持,开启我们在大数据处理领域优化数据访问效率的新篇章。
在大数据时代,数据量呈爆炸式增长,传统的缓存方式已难以满足系统对数据访问性能的严苛要求。分布式缓存应运而生,它如同数据访问的高速公路,能够显著减少数据访问延迟,提高系统的响应速度和吞吐量。以电商系统为例,在 “双十一” 等促销活动期间,海量用户同时访问商品信息,若没有分布式缓存,数据库将承受巨大压力,导致系统响应缓慢甚至崩溃。据统计,某知名电商平台在未使用分布式缓存前,促销活动高峰时商品详情页的平均响应时间长达 5 秒,大量用户因等待时间过长而放弃购买,导致订单转化率较低。而引入分布式缓存后,热门商品数据被缓存起来,用户请求优先从缓存中获取数据,数据库负载大幅减轻,商品详情页的平均响应时间缩短至 1 秒以内,订单转化率显著提高,用户体验得到极大改善。
分布式缓存通过在多个节点上存储数据副本,实现数据的分布式存储和管理。它采用一致性哈希算法等技术,将数据均匀分布到各个节点,确保节点负载均衡。当客户端请求数据时,缓存系统根据数据的键值计算其对应的缓存节点,直接从该节点获取数据。例如,在一个由 5 个节点组成的分布式缓存系统中,数据的键值经过哈希计算后,被映射到相应节点。假设数据键值为 “product_123”,通过一致性哈希算法计算得到其应存储在节点 3 上,后续对该数据的请求将直接路由到节点 3,快速获取数据。这种数据分布方式使得每个节点承担相对均衡的负载,避免了单个节点因负载过高而出现性能瓶颈。
Redis 是一款高性能的 key-value 分布式缓存数据库,以其卓越的速度和丰富的数据结构而闻名。它支持多种数据类型,如字符串、哈希表、列表、集合、有序集合等,为不同场景下的数据缓存提供了灵活的解决方案。在社交平台中,Redis 可用于存储用户的好友列表(列表类型)、用户信息(哈希表类型)以及热门话题的点赞数(有序集合类型)等。以下是一个更完善的 Java 代码示例,用于连接 Redis 并进行数据操作,包括数据的写入、读取、更新以及删除,同时展示了如何使用连接池来优化连接管理:
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class RedisExample {
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(5);
poolConfig.setTestOnBorrow(true);
// 创建连接池
jedisPool = new JedisPool(poolConfig, "localhost", 6379);
}
public static void main(String[] args) {
try (Jedis jedis = jedisPool.getResource()) {
// 设置键值对
jedis.set("name", "John");
// 获取值
String value = jedis.get("name");
System.out.println("获取到的值: " + value);
// 更新值
jedis.set("name", "Jane");
value = jedis.get("name");
System.out.println("更新后的值: " + value);
// 删除键值对
jedis.del("name");
value = jedis.get("name");
System.out.println("删除后的值: " + value);
} finally {
// 关闭连接池
jedisPool.close();
}
}
}
Memcached 是一款简洁高效的分布式缓存系统,专注于快速的内存对象缓存。它的设计理念是简单易用,通过将数据存储在内存中,实现了极低的延迟。在大型网站中,常用于缓存频繁访问的网页内容、数据库查询结果等。例如,新闻网站可将热门新闻文章缓存到 Memcached 中,用户访问时直接从缓存读取,减少数据库查询开销。以下是一个更详细的 Memcached 操作示例(使用 Java 客户端 Spymemcached),展示了如何处理数据的过期时间设置、批量操作以及异步操作,同时增加了对连接异常的处理:
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.internal.OperationFuture;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
public class MemcachedExample {
public static void main(String[] args) {
try {
// 连接 Memcached 服务器
MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("localhost", 11211));
// 设置键值对并设置过期时间为 60 秒
Future<Boolean> setFuture = memcachedClient.set("message", 60, "Hello, Memcached!");
// 批量获取多个键值对
List<String> keys = new ArrayList<>();
keys.add("message");
keys.add("another_key");
List<Object> values = memcachedClient.getBulk(keys);
for (Object value : values) {
if (value!= null) {
System.out.println("获取到的值: " + value);
} else {
System.out.println("未获取到对应的值");
}
}
// 异步设置键值对
OperationFuture<Boolean> asyncSetFuture = memcachedClient.set("async_message", 30, "Async data");
asyncSetFuture.addListener(() -> {
if (asyncSetFuture.getStatus().isSuccess()) {
System.out.println("异步设置成功");
} else {
System.out.println("异步设置失败");
}
});
// 关闭连接
memcachedClient.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}
主从架构是分布式缓存中常见的一种架构模式,其中主节点负责数据的写入和更新,从节点负责数据的读取。主节点将数据变更同步到从节点,确保数据的一致性。这种架构提高了系统的读取性能和可用性。例如,在一个电商系统中,主 Redis 节点处理商品库存的更新操作,多个从节点负责处理用户对商品库存的查询请求,有效分担了主节点的读取压力。
节点类型 | 职责 | 优点 | 缺点 |
---|---|---|---|
主节点 | 写入、更新数据 | 保证数据一致性,控制数据变更 | 单点故障风险,写入压力集中 |
从节点 | 读取数据 | 分担主节点读取压力,提高读取性能 | 数据同步延迟可能导致短暂数据不一致 |
集群架构则通过多个节点共同承担数据的存储和读写任务,实现了更高的可扩展性和性能。节点之间通过分布式一致性协议(如 Raft 或 Paxos)进行协调,确保数据的一致性和可用性。以 Redis Cluster 为例,它将数据槽分布到多个节点上,节点之间自动进行数据迁移和故障转移。在大规模社交平台中,Redis Cluster 可轻松应对海量用户的缓存需求,确保系统稳定运行。
架构类型 | 特点 | 适用场景 | 挑战 |
---|---|---|---|
集群架构 | 多节点共同存储读写,分布式协议协调 | 大规模数据存储与高并发读写,如社交平台、电商大促 | 数据分布与负载均衡策略复杂,节点间通信开销大 |
为了更直观地展示两种架构的差异,以下是一个简单的对比示意图:
在分布式缓存中,数据一致性是一个关键挑战。由于数据分布在多个节点上,且存在缓存更新操作,可能导致不同节点上的数据不一致。例如,在电商系统中,商品库存数据在缓存和数据库中同时存在,若缓存更新不及时,可能出现用户购买商品时显示库存充足,但实际库存已不足的情况。据统计,某电商平台曾因缓存与数据库数据不一致,导致在一次促销活动中,超卖问题严重,商家遭受了较大的经济损失,同时也影响了用户体验,引发了大量用户投诉。
为解决数据一致性问题,可采用多种缓存更新策略。
import redis.clients.jedis.Jedis;
public class RealTimeCacheUpdate {
public static void main(String[] args) {
// 假设这里是数据库更新操作后的回调函数
// 更新数据库后,立即更新 Redis 缓存
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 假设数据库中用户账户余额从 100 更新为 200
int newBalance = 200;
// 更新 Redis 中的缓存数据
jedis.set("user_balance", String.valueOf(newBalance));
System.out.println("实时更新缓存成功,用户账户余额为: " + jedis.get("user_balance"));
}
}
}
import java.util.Timer;
import java.util.TimerTask;
import redis.clients.jedis.Jedis;
public class ScheduledCacheUpdate {
private static final long UPDATE_INTERVAL = 24 * 60 * 60 * 1000; // 24 小时,单位毫秒
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 假设这里是从数据库获取最新的网站访问量数据并更新缓存
int newVisitCount = getLatestVisitCountFromDB();
jedis.set("website_visit_count", String.valueOf(newVisitCount));
System.out.println("定时更新缓存成功,网站访问量为: " + jedis.get("website_visit_count"));
}
}
}, 0, UPDATE_INTERVAL);
}
private static int getLatestVisitCountFromDB() {
// 这里模拟从数据库获取最新的访问量数据,实际应用中需要连接数据库并查询
return 1000; // 假设最新的访问量为 1000
}
}
合理的内存管理是提升分布式缓存性能的关键。可通过调整缓存的内存分配策略、设置合适的内存淘汰算法(如 LRU、LFU 等)来优化内存使用。例如,在 Redis 中,可根据业务需求配置最大内存限制和内存淘汰策略,确保缓存始终保持高效运行。以下是一个设置 Redis 内存淘汰策略的更详细配置示例,展示了如何根据不同的业务场景选择合适的淘汰策略:
# 设置最大内存为 4GB
maxmemory 4gb
# 使用 LRU 内存淘汰策略,适用于大部分缓存场景,优先淘汰最近最少使用的数据
maxmemory-policy allkeys-lru
# 如果业务对热点数据的访问频率非常高,且不希望热点数据被轻易淘汰,可以使用 LFU 内存淘汰策略
# maxmemory-policy allkeys-lfu
# 对于一些需要严格按照数据过期时间来淘汰的场景,可以使用 volatile-ttl 策略
# maxmemory-policy volatile-ttl
减少网络延迟对分布式缓存性能至关重要。可采用以下网络优化措施:
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class ConnectionPoolOptimization {
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 设置最大连接数,根据系统并发量合理调整,例如设置为 200
poolConfig.setMaxTotal(200);
// 设置最大空闲连接数,避免资源浪费,例如设置为 50
poolConfig.setMaxIdle(50);
// 设置最小空闲连接数,确保有足够的连接可用,例如设置为 10
poolConfig.setMinIdle(10);
// 设置在获取连接时是否进行有效性检查,确保获取到的连接可用
poolConfig.setTestOnBorrow(true);
// 创建连接池
jedisPool = new JedisPool(poolConfig, "localhost", 6379);
}
public static void main(String[] args) {
try (Jedis jedis = jedisPool.getResource()) {
// 在此处进行 Redis 操作
jedis.set("key", "value");
String value = jedis.get("key");
System.out.println("获取到的值: " " + value);
} finally {
// 关闭连接池
jedisPool.close();
}
}
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import net.spy.memcached.MemcachedClient;
import net.spy.memcached.internal.OperationFuture;
import java.net.InetSocketAddress;
public class CompressedDataTransfer {
public static void main(String[] args) {
try {
// 连接 Memcached 服务器
MemcachedClient memcachedClient = new MemcachedClient(new InetSocketAddress("localhost", 11211));
// 原始数据
String originalData = "This is a long piece of data that can be compressed to reduce network transfer.";
byte[] compressedData = compress(originalData.getBytes());
// 设置键值对,存储压缩后的数据
OperationFuture<Boolean> setFuture = memcachedClient.set("compressed_data", 3600, compressedData);
// 获取压缩后的数据
byte[] retrievedData = (byte[]) memcachedClient.get("compressed_data");
if (retrievedData!= null) {
// 解压缩数据
byte[] decompressedData = decompress(retrievedData);
String decompressedString = new String(decompressedData);
System.out.println("获取到的值: " + decompressedString);
} else {
System.out.println("未获取到对应的数据");
}
// 关闭连接
memcachedClient.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
private static byte[] compress(byte[] data) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(data);
gzip.close();
return bos.toByteArray();
}
private static byte[] decompress(byte[] data) throws IOException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
GZIPInputStream gis = new GZIPInputStream(bis);
ByteArrayOutputStream buf = new ByteArrayOutputStream();
int b;
while ((b = gis.read())!= -1) {
buf.write(b);
}
return buf.toByteArray();
}
}
在电商系统中,分布式缓存发挥着至关重要的作用。例如,京东将商品详情页、商品列表页、用户购物车等数据缓存到 Redis 中。商品详情页包含商品图片、描述、规格等信息,数据量大且访问频繁。通过缓存,用户访问商品详情页时,系统直接从 Redis 中获取数据,大大缩短了响应时间,提高了用户体验。同时,利用 Redis 的分布式锁功能,解决了高并发场景下购物车数据的一致性问题。在 “618” 促销活动期间,京东通过优化分布式缓存策略,使得商品详情页的平均响应时间从原来的 3 秒缩短到了 1 秒以内,用户购物车操作的并发处理能力提高了 50%,有效提升了系统的性能和用户满意度,订单量也实现了显著增长。
社交媒体平台如微博,使用分布式缓存存储用户动态、粉丝列表、热门话题等数据。用户动态的频繁更新和查询对系统性能要求极高。通过将用户动态缓存到 Redis 中,系统能够快速推送最新动态给用户,提高信息传播速度。在粉丝列表的查询中,利用缓存减少了数据库查询次数,提升了系统的响应性能。例如,微博在热门话题讨论高峰期,通过分布式缓存将热门话题的相关数据进行缓存,使得话题页面的加载速度提高了 40%,用户参与话题讨论的积极性明显增强,平台的活跃度和用户粘性得到了有效提升。
监控分布式缓存的性能指标对于及时发现和解决问题至关重要。常见的性能指标包括缓存命中率、内存使用率、连接数、请求延迟等。通过监控这些指标,可评估缓存系统的健康状况。例如,若缓存命中率持续下降,可能表示缓存配置不合理或数据更新频繁,需及时调整缓存策略。以下是一个使用 Java 结合 Prometheus 监控 Redis 性能指标的示例代码:
import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Gauge;
import io.prometheus.client.exporter.HTTPServer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.concurrent.TimeUnit;
public class RedisMetricsMonitoring {
private static final CollectorRegistry registry = new CollectorRegistry();
private static final Gauge cacheHitRate = Gauge.build()
.name("redis_cache_hit_rate")
.help("Redis cache hit rate")
.register(registry);
private static final Gauge memoryUsage = Gauge.build()
.name("redis_memory_usage")
.help("Redis memory usage in bytes")
.register(registry);
private static final Gauge activeConnections = Gauge.build()
.name("redis_active_connections")
.help("Number of active connections to Redis")
.register(registry);
private static final Gauge requestLatency = Gauge.build()
.name("redis_request_latency")
.help("Average request latency to Redis in milliseconds")
.register(registry);
private static final JedisPool jedisPool;
static {
// 配置连接池
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMaxTotal(100);
poolConfig.setMaxIdle(20);
poolConfig.setMinIdle(5);
poolConfig.setTestOnBorrow(true);
// 创建连接池
jedisPool = new JedisPool(poolConfig, "localhost", 6379);
}
public static void main(String[] args) {
try {
HTTPServer server = new HTTPServer(8080, registry);
while (true) {
try (Jedis jedis = jedisPool.getResource()) {
// 获取缓存信息
String info = jedis.info();
// 解析缓存命中率
String[] lines = info.split("\r\n");
for (String line : lines) {
if (line.startsWith("keyspace_hits")) {
long hits = Long.parseLong(line.split(":")[1]);
} else if (line.startsWith("keyspace_misses")) {
long misses = Long.parseLong(line.split(":")[1]);
double hitRate = (double) hits / (hits + misses);
cacheHitRate.set(hitRate);
} else if (line.startsWith("used_memory")) {
long memory = Long.parseLong(line.split(":")[1]);
memoryUsage.set(memory);
} else if (line.startsWith("connected_clients")) {
int clients = Integer.parseInt(line.split(":")[1]);
activeConnections.set(clients);
}
}
// 模拟计算请求延迟(这里简单设置为固定值,实际应用中需根据真实请求时间计算)
requestLatency.set(10);
TimeUnit.SECONDS.sleep(5);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
jedisPool.close();
}
}
}
分布式缓存系统可能面临节点故障、网络故障、数据丢失等问题。需建立有效的故障诊断机制,快速定位和解决故障。例如,Redis 提供了 INFO 命令,可获取缓存的详细运行信息,用于故障排查。同时,采用数据备份和恢复策略,确保数据的安全性。以下是一个简单的 Redis 数据备份和恢复的示例代码:
import redis.clients.jedis.Jedis;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class RedisBackupAndRestore {
public static void backup(String dumpFilePath) {
try (Jedis jedis = new Jedis("localhost", 6379);
OutputStream outputStream = new FileOutputStream(dumpFilePath)) {
// 使用 BGSAVE 命令进行后台数据备份
jedis.bgsave();
// 等待备份完成(这里简单等待 5 秒,实际应用中需根据备份情况进行更准确的判断)
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取备份文件数据并写入到指定文件
byte[] dumpData = jedis.dump("*");
outputStream.write(dumpData);
System.out.println("Redis 数据备份成功,保存到: " + dumpFilePath);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void restore(String dumpFilePath) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 清空现有数据
jedis.flushall();
// 从备份文件中恢复数据
jedis.restore("*", 0, dumpFilePath);
System.out.println("Redis 数据恢复成功");
}
}
public static void main(String[] args) {
// 备份数据到文件
backup("redis_dump.rdb");
// 模拟数据丢失后进行恢复
restore("redis_dump.rdb");
}
}
缓存穿透是指查询不存在的数据时,缓存和数据库都无法命中,导致大量请求直接穿透到数据库,影响系统性能。例如,恶意攻击者故意查询不存在的用户 ID,可能导致数据库压力过大。应对策略包括:
import redis.clients.jedis.Jedis;
public class CacheNullValue {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 假设查询不存在的用户 ID 为 999
String userId = "999";
// 从缓存中获取数据
String userData = jedis.get(userId);
if (userData == null) {
// 如果缓存未命中,查询数据库(这里简单模拟数据库查询结果为空)
// 缓存空值到 Redis,并设置过期时间为 60 秒
jedis.setex(userId, 60, "");
System.out.println("缓存空值成功");
} else {
System.out.println("从缓存中获取到用户数据: " + userData);
}
}
}
}
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import java.nio.charset.StandardCharsets;
public class BloomFilterExample {
private static final int EXPECTED_INSERTIONS = 1000000;
private static final double FPP = 0.01;
private static BloomFilter<String> bloomFilter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8), EXPECTED_INSERTIONS, FPP);
public static void main(String[] args) {
// 假设预先知道可能存在的用户 ID 列表,将其添加到布隆过滤器中
String[] existingUserIds = {"1", "2", "3", "4", "5"};
for (String userId : existingUserIds) {
bloomFilter.put(userId);
}
// 检查不存在的用户 ID
String nonExistingUserId = "999";
if (!bloomFilter.mightContain(nonExistingUserId)) {
System.out.println("该用户 ID 大概率不存在,无需查询缓存和数据库");
} else {
// 如果布隆过滤器判断可能存在,则继续查询缓存和数据库
System.out.println("该用户 ID 可能存在,进行缓存和数据库查询");
}
}
}
缓存雪崩是指大量缓存同时过期或失效,导致大量请求直接涌向数据库,造成数据库负载过高甚至宕机。例如,在电商促销活动中,大量商品缓存同时过期,可能引发缓存雪崩。应对策略包括:
import redis.clients.jedis.Jedis;
import java.util.Random;
public class RandomExpiration {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 假设要缓存 10 个商品数据,为每个商品设置随机的过期时间(范围为 300 - 600 秒)
Random random = new Random();
for (int i = 1; i <= 10; i++) {
String productKey = "product_" + i;
int expirationSeconds = 300 + random.nextInt(301);
jedis.setex(productKey, expirationSeconds, "Product data " + i);
System.out.println("设置 " + productKey + " 的过期时间为 " + expirationSeconds + " 秒");
}
}
}
}
import redis.clients.jedis.Jedis;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class HotDataUpdate {
private static final String HOT_PRODUCT_KEY = "hot_product_1";
private static final int UPDATE_INTERVAL_SECONDS = 60;
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 初始化热点数据到缓存(假设这里从数据库获取热点数据)
String hotProductData = getHotProductDataFromDB();
jedis.set(HOT_PRODUCT_KEY, hotProductData);
// 创建定时任务线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
// 定期更新热点数据
executorService.scheduleAtFixedRate(() -> {
try {
String updatedData = getUpdatedHotProductDataFromDB();
jedis.set(HOT_PRODUCT_KEY, updatedData);
System.out.println("热点数据更新成功");
} catch (Exception e) {
e.printStackTrace();
}
}, UPDATE_INTERVAL_SECONDS, UPDATE_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
}
private static String getHotProductDataFromDB() {
// 这里模拟从数据库获取热点数据
return "Hot product data";
}
private static String getUpdatedHotProductDataFromDB() {
// 这里模拟从数据库获取更新后的热点数据
return "Updated hot product data";
}
}
import redis.clients.jedis.Jedis;
import java.util.ArrayList;
import java.util.List;
public class CacheWarming {
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 假设这里是从数据库获取热点数据的列表
List<String> hotDataList = new ArrayList<>();
hotDataList.add("hot_data_1");
hotDataList.add("hot_data_2");
hotDataList.add("hot_data_3");
// 将热点数据加载到缓存中,并设置过期时间(这里假设过期时间为 3600 秒)
for (String data : hotDataList) {
jedis.setex(data, 3600, "Hot data value");
}
System.out.println("缓存预热完成");
}
}
}
缓存击穿是指热点数据过期时,大量并发请求同时查询该数据,导致这些请求直接穿透到数据库。例如,某个热门商品的缓存过期,大量用户同时访问该商品详情页,可能引发缓存击穿。应对策略包括:
import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;
public class CacheBreakdownMutex {
private static final String LOCK_KEY_PREFIX = "lock:";
private static final int LOCK_EXPIRE_SECONDS = 10;
public static String getCachedDataWithMutex(String key, Jedis jedis) {
// 尝试获取锁
String lockKey = LOCK_KEY_PREFIX + key;
String lockValue = "locked";
if (jedis.setnx(lockKey, lockValue) == 1) {
// 获取锁成功,设置过期时间,防止死锁
jedis.expire(lockKey, LOCK_EXPIRE_SECONDS);
try {
// 从缓存中获取数据
String data = jedis.get(key);
if (data == null) {
// 缓存未命中,从数据库查询数据(这里模拟从数据库获取数据)
data = getFromDB(key);
// 将数据存入缓存,并设置过期时间(假设过期时间为 300 秒)
jedis.setex(key, 300, data);
}
return data;
} finally {
// 释放锁
jedis.del(lockKey);
}
} else {
// 获取锁失败,等待一段时间后重试
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getCachedDataWithMutex(key, jedis);
}
}
private static String getFromDB(String key) {
// 这里模拟从数据库获取数据
return "Data for " + key;
}
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 模拟多个线程同时访问热点数据
for (int i = 0; i < 10; i++) {
new Thread(() -> {
String data = getCachedDataWithMutex("hot_data", jedis);
System.out.println("获取到的数据: " + data);
}).start();
}
}
}
}
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class CacheBreakdownLogicExpire {
private static final String CACHE_KEY_PREFIX = "cache:";
private static final String TIMESTAMP_KEY_PREFIX = "timestamp:";
private static final int UPDATE_INTERVAL_SECONDS = 60;
public static void main(String[] args) {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 初始化缓存数据和逻辑过期时间(这里模拟)
jedis.set(CACHE_KEY_PREFIX + "hot_data", "Initial data");
jedis.zadd(TIMESTAMP_KEY_PREFIX + "hot_data", System.currentTimeMillis() + 300 * 1000, "expire_time");
// 创建定时任务线程池,用于异步更新缓存
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
executorService.scheduleAtFixedRate(() -> {
try {
// 获取所有需要更新的缓存数据的键
Set<String> keys = jedis.keys(CACHE_KEY_PREFIX + "*");
for (String key : keys) {
String dataKey = key.substring(CACHE_KEY_PREFIX.length());
// 获取数据的逻辑过期时间
Set<Tuple> tuples = jedis.zrangeWithScores(TIMESTAMP_KEY_PREFIX + dataKey, 0, -1);
if (!tuples.isEmpty()) {
long expireTime = (long) tuples.iterator().next().getScore();
if (System.currentTimeMillis() > expireTime) {
// 缓存已过期,从数据库更新数据(这里模拟从数据库获取更新后的数据)
String updatedData = getUpdatedDataFromDB(dataKey);
jedis.set(key, updatedData);
// 更新逻辑过期时间
jedis.zadd(TIMESTAMP_KEY_PREFIX + dataKey, System.currentTimeMillis() + 300 * 1000, "expire_time");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}, UPDATE_INTERVAL_SECONDS, UPDATE_INTERVAL_SECONDS, TimeUnit.SECONDS);
// 模拟多个线程同时访问热点数据
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try (Jedis jedis = new Jedis("localhost", 6379)) {
// 从缓存中获取数据
String data = jedis.get(CACHE_KEY_PREFIX + "hot_data");
System.out.println("获取到的数据: " + data);
}
}).start();
}
}
}
private static String getUpdatedDataFromDB(String key) {
// 这里模拟从数据库获取更新后的数据
return "Updated data for " + key;
}
}
亲爱的 Java 和 大数据爱好者们,通过对 Java 大数据分布式缓存的深入探讨,我们详细了解了其原理、技术、架构设计、性能优化以及应用案例等方面的知识,也探讨了在实际应用中面临的挑战及应对策略。分布式缓存作为提升数据访问性能的关键技术,在大数据时代发挥着不可或缺的作用。
亲爱的 Java 和 大数据爱好者们,在结束本文之际,我们期待在《大数据新视界》和《 Java 大视界》专栏联合推出的第二阶段的文章《Java 大视界 – Java 大数据日志分析系统:基于 ELK 与 Java 技术栈(六)》中,与大家继续探索 Java 大数据领域的更多精彩内容,进一步提升我们在大数据处理和应用开发方面的技术能力和实践经验。
亲爱的 Java 和 大数据爱好者们,你在使用分布式缓存时遇到过哪些问题?你是如何解决缓存穿透、雪崩和击穿等问题的?欢迎在评论区或【青云交社区 – Java 大视界频道】留言分享你的经验和想法,让我们共同交流学习,一起在 Java 大数据的世界里不断进步!
若您有意与我交流互动,联系方式便捷如下:
微信 QingYunJiao 期待您的联络,公众号 “青云交” 会持续推送精彩。
版权声明:此文为原创心血结晶,版权珍贵如金,归作者专有。未经许可擅自转载,即为侵权。欲览更多深度内容,请移步【青云交】博客首页。
点击 ⬇️ 下方微信名片 ⬇️,踏入 青云交灵犀技韵交响盛汇社群。这里,科技精英荟萃,凭智慧创新,绘科技蓝图,交流结谊,探索逐梦。
青云交灵犀技韵交响盛汇社群 | 大数据新视界专栏 | AI & 人工智能专栏 | Java 虚拟机(JVM)专栏
✨ 【青云交】精品博文,皆为知识富矿,待您挖掘探索,启迪智慧之旅。