本地缓存与redis实现的分布式缓存
Java中本地缓存是指将数据存储在应用程序内存中,以便快速访问和提高性能。这种缓存方式适用于小型应用程序或者单机部署的应用程序。Java中本地缓存可以通过使用HashMap、ConcurrentHashMap等数据结构实现。本地缓存的优点是快速读取和写入,但是其容量受限于应用程序内存大小。
实现代码如下:
import java.util.HashMap;
import java.util.Map;
public class LocalCache {
private Map<String, Object> cache = new HashMap<>();
public void put(String key, Object value) {
cache.put(key, value);
}
public Object get(String key) {
return cache.get(key);
}
public void remove(String key) {
cache.remove(key);
}
}
Redis是一种分布式内存数据库,支持多种数据结构(如字符串、哈希表、列表、集合等),可以作为分布式缓存使用。Redis具有高性能、可扩展性和灵活性等优点,适用于大型分布式系统。在Redis中,数据被保存在内存中,并且可以定期将数据同步到磁盘上以保证持久化。
使用了Jedis客户端连接Redis数据库,实现代码如下
import redis.clients.jedis.Jedis;
public class RedisCache {
private Jedis jedis;
public RedisCache() {
jedis = new Jedis("localhost", 6379);
}
public void put(String key, String value) {
jedis.set(key, value);
}
public String get(String key) {
return jedis.get(key);
}
public void remove(String key) {
jedis.del(key);
}
}
与本地缓存相比,Redis作为分布式缓存具有以下优点:
-
可扩展性:Redis支持横向扩展,可以添加更多的节点来增加容量和吞吐量。
-
高可靠性:由于Redis采用主从复制模式来保证数据的可靠性,在主节点宕机时可以自动切换到从节点继续提供服务。
-
数据共享:Redis支持多个应用程序共享同一个缓存集群,提高了资源利用率。
-
高性能:Redis采用内存存储,读写速度非常快。
本地缓存适用于小型应用程序或者单机部署的应用程序,而Redis作为分布式缓存适用于大型分布式系统。选择哪种缓存方式需要根据具体的需求和场景来决定。
缓存会出现的一些经典问题
缓存穿透:高并发查询缓存中没有的数据,高并发会直接查询数据库。解决方案:直接把null结果缓存,并加入短暂的过期时间。
缓存雪崩:相同过期时间的key失效,请求全部来到数据库。解决方案:过期时间加段随机值。
缓存击穿:某些key是热点数据,当过期时请求全部来到数据库。解决方案:加锁,分布式情况需要加分布式锁。
本地锁和Redis实现的分布式锁
本地锁是通过synchronized关键字实现的。synchronized关键字可以用于方法或代码块,它保证了同一时刻只有一个线程能够访问被保护的代码。不过适用于单机环境下的并发控制,因为多个线程在同一台服务器上运行,它们可以共享内存和CPU资源。但是,在分布式系统中使用本地锁会存在问题,因为不同节点之间无法共享内存和CPU资源。
代码如下:
public class LocalLockExample {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) { // 获取锁
count++;
} // 释放锁
}
}
Redis提供了setnx(SET if Not eXists)命令,可以原子性地将一个值设置到key中,并且如果key不存在,则设置成功;如果key已经存在,则设置失败。
- 客户端向Redis发送setnx命令请求获取锁;
- 如果返回值为1,则表示获取到了锁;
- 如果返回值为0,则表示没有获取到锁;
- 在获取到锁后,客户端需要在规定时间内释放该锁;
- 如果在规定时间内未释放该锁,则其他客户端可以重新获取该锁。
注意!还要设置过期时间,避免锁一直占用,在释放锁的时候需要保证原子性的操作,以防如果出现了不可预料的问题,在不是原子性的操作下容易没释放锁就造成了死锁问题,要保证原子性,可以使用官方给出的Lua脚本实现。
if redis.call("get",KEYS[1]) == ARGV[1]
then
return redis.call("del",KEYS[1])
else
return 0
end
使用Java代码来进行实现:
RedisScript<Long> script = new DefaultRedisScript<>(
"if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end",
Long.class);
Long result = redisTemplate.execute(script, Arrays.asList("lock"), uuid);
redission实现的分布式锁
- 保证了原子性的执行
- 看门狗机制,默认设置锁过期时间为30s,每隔10s会重置锁的过期时间,持有锁过久也会续期,当机器宕机key会在30s后过期不会造成死锁
// 锁的名字就等于锁的粒度,分的越细越快
RLock lock = redisson.getLock("lock");
lock.lock();
lock.unlock();
缓存数据一致性的解决方案
- 缓存的数据都加上过期时间,在过期的时候触发更新
- 读写数据时使用分布式的读写锁,在经常读,不经常写的情况下使用。