谈下你对 Redis 的了解?
Remote Dictionary Server 远程字典服务
Redis 一般都有哪些使用场景?
public class LeaderboardService {
private final RedisTemplate<String, String> redisTemplate;
public LeaderboardService(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 添加玩家到排行榜
public void addPlayerToLeaderboard(String player, double score) {
redisTemplate.opsForZSet().add("leaderboard", player, score);
}
// 获取排行榜中前 N 名玩家 /rɪˈvɜːs/
public Set<ZSetOperations.TypedTuple<String>> getLeaderboard(int topN) {
return redisTemplate.opsForZSet().reverseRangeWithScores("leaderboard", 0, topN - 1);
}
// 更新玩家在排行榜中的分数
public void updatePlayerScore(String player, double score) {
redisTemplate.opsForZSet().incrementScore("leaderboard", player, score);
}
// 从排行榜中移除玩家
public void removePlayerFromLeaderboard(String player) {
redisTemplate.opsForZSet().remove("leaderboard", player);
}
}
/**
* 一等奖1个,二等奖2个,三等奖3个,参与奖100个
*/
public class LotteryService {
private final RedisTemplate<String, String> redisTemplate;
private final JdbcTemplate jdbcTemplate;
private static final String SPOP_USER_SETS = "pop:user:set";
public LotteryService(JdbcTemplate jdbcTemplate, RedisTemplate<String, String> redisTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.redisTemplate = redisTemplate;
}
public void initUserSet(String prize) {
String sql = "select id from user";
List<Map<String, Object>> userIds = jdbcTemplate.queryForList(sql);
SetOperations<String, String> ops = redisTemplate.opsForSet();
String[] ids = userIds.stream().map(item -> String.valueOf(item.get("id").toString())).toArray(String[]::new);
ops.add(SPOP_USER_SETS, ids);
}
public void drawAllPrize() {
List<String> firstPrize = drawPrize(1);
System.out.println("一等奖:" + firstPrize.get(0));
List<String> secondPrize = drawPrize(2);
System.out.println("二等奖:" + String.join(",", secondPrize));
List<String> thirdPrize = drawPrize(3);
System.out.println("三等奖:" + String.join(",", thirdPrize));
List<String> participationPrize = drawPrize(100);
System.out.println("参与奖:" + String.join(",", participationPrize));
}
/**
* 一个人最多获取一次奖品
* @param count
* @return
*/
public List<String> drawPrize(int count) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
// Remove and return count random members from set at key.
return ops.pop("prize_pool", count);
}
/**
* draw 抽取
* 允许一个人多次抽取奖品
* @param count
* @return
*/
public List<String> drawPrize4duplicate(int count) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
// Get count random elements from set at key.
return ops.randomMembers("prize_pool", count);
}
}
public class Stats4String {
private RedisTemplate<String, String> redisTemplate;
public Stats4String(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 比如点赞/收藏
public void incrementCount(String key) {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.increment(key);
}
// 比如取消点赞/取消收藏
public void decrementCount(String key) {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
ops.decrement(key);
}
// 获取统计数量
public Long getCount(String key) {
ValueOperations<String, String> ops = redisTemplate.opsForValue();
String countStr = ops.get(key);
if (countStr != null) {
return Long.parseLong(countStr);
} else {
return 0L;
}
}
// 清空统计数据
public void clearCount(String key) {
redisTemplate.delete(key);
}
}
基于HyperLogLog的统计
去重,不可减,不准确,节省内存
public class Stats4HyperLogLog {
private RedisTemplate<String, String> redisTemplate;
public Stats4HyperLogLog(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 添加元素到 HyperLogLog
public void addElement(String key, String... elements) {
HyperLogLogOperations<String, String> ops = redisTemplate.opsForHyperLogLog();
ops.add(key, elements);
}
// 获取 HyperLogLog 的基数估计值
public long getApproximateCount(String key) {
HyperLogLogOperations<String, String> ops = redisTemplate.opsForHyperLogLog();
return ops.size(key);
}
// 合并多个 HyperLogLog
public void mergeHyperLogLogs(String destinationKey, String... sourceKeys) {
HyperLogLogOperations<String, String> ops = redisTemplate.opsForHyperLogLog();
ops.union(destinationKey, sourceKeys);
}
// 清空 HyperLogLog
public void clearHyperLogLog(String key) {
redisTemplate.delete(key);
}
}
基于Set的统计
public class SetStats4Set {
private final RedisTemplate<String, String> redisTemplate;
public SetStats4Set(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
// 例如点赞
public void add(String key, String... elements) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
ops.add(key, elements);
}
// 例如取消点赞
public void remove(String key, Object... elements) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
ops.remove(key, elements);
}
// 例如点赞数
public long getDistinctCount(String key) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
Long size = ops.size(key);
return size != null ? size : 0L;
}
// 例如点赞的人
public Set<String> members(String key) {
SetOperations<String, String> ops = redisTemplate.opsForSet();
return ops.members(key);
}
// 清空集合
public void clearSet(String key) {
redisTemplate.delete(key);
}
}
Redis 有哪些常见的功能?
Redis 支持的数据类型有哪些?
Redis 为什么这么快?
综合上述因素,Redis 在数据存储、读写操作和网络通信等方面做出了高效的设计和优化,从而实现了快速的响应和高性能
什么是缓存穿透?怎么解决?
缓存中没有,数据库中也没有
缓存穿透是指在使用缓存系统时,恶意或非法的请求导致缓存无法命中,并且每次请求都会直接访问后端存储系统,对系统造成了极大的压力和性能问题。
常见的缓存穿透场景是当一个请求查询一个不存在的数据时,由于缓存中没有该数据的记录,请求会直接访问后端数据库,但由于数据不存在,后端数据库也无法返回结果,这样的请求会一直穿透缓存直达数据库。
为了解决缓存穿透问题,可以采取以下几种策略:
综合运用上述策略,可以有效地解决缓存穿透问题,减轻对后端存储系统的负载压力,提高系统的性能和稳定性。
什么是缓存击穿?如何解决?
数据库有,缓存中没有
缓存击穿是指在使用缓存系统时,某个热门数据过期或被删除后,恰好有大量的并发请求同时访问该数据,导致这些请求都无法命中缓存,直接访问后端存储系统,对后端系统造成巨大压力,可能引发系统崩溃或性能下降的问题。
解决方案:
什么是缓存雪崩?该如何解决?
大量缓存同时过期或者失效
缓存雪崩是指在使用缓存系统时,缓存中大量的数据同时过期或失效,导致大量的请求直接访问后端存储系统,使得后端系统承受巨大的压力,甚至引发系统崩溃的现象。
解决缓存雪崩问题的方法如下:
综合运用上述解决方案,可以有效地预防和应对缓存雪崩问题,保障系统的稳定性和性能。
怎么保证缓存和数据库数据的一致性?
先更新数据库再删除缓存,两步操作,防止第二步失败,所以对第二步加个重试(失败一定次数,告诉定时任务啥的)
利用阿里的开源组件Canal
Redis 持久化有几种方式?
Redis 怎么实现分布式锁?
Redis 淘汰策略有哪些?
noeviction 不淘汰,数据满了再写就报错。
volatile-ttl 在设置了过期时间的key中选,越早过期的越先被删除。
volatile-random(X2) 随机
LRU算法(X2) 最近最不长使用了(访问时间维度)
LRU 策略的核心思想:如果一个数据刚刚被访问,那么这个数据肯定是热数据,还会被再次访问。
按照这个核心思想,Redis 中的 LRU 策略,会在每个数据对应的 RedisObject 结构体中设置一个 lru 字段,用来记录数据的访问时间戳。在进行数据淘汰时,LRU 策略会在候选数据集中淘汰掉 lru 字段值最小的数据(也就是访问时间最久的数据)。
class LRU4LinkedHashMap {
public static Map<String, Object> map = new LinkedHashMap<>();
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
putValue("k1", "v1");
putValue("k2", "v2");
putValue("k3", "v3");
for (Map.Entry<String, Object> entry : map.entrySet()) {
sb.append(entry.getKey());
}
System.out.println(sb);
sb = new StringBuilder();
putValue("k2", "v22");
for (Map.Entry<String, Object> entry : map.entrySet()) {
sb.append(entry.getKey());
}
System.out.println(sb);
sb = new StringBuilder();
getValue("k1");
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.print(entry.getKey());
}
System.out.println(sb);
}
private static void putValue(String key, Object value) {
if (map.containsKey(key)) {
map.remove(key);
}
map.put(key, value);
}
private static Object getValue(String key) {
Object value = map.get(key);
if (value != null) {
map.remove(key);
map.put(key, value);
}
return value;
}
}
lfu(X2) 使用频率最少的(访问频率维度)
Redis 常见性能问题和解决方案?