| zrange sort 0 -1 | 获取指定序号的值,-1代表全部 |
| zrangebyscore sort 0 5 | 分数符合范围的值 |
| zrangebyscore sort 0 5 limit 0 1 | 分页 limit 0代表页码,1代表每页显示数量 |
| zrem sort java | 移除元素 |
| zremrangebyrank sort 0 1 | 按照排名范围删除元素 |
| zremrangebyscore sort 0 1 | 按照分数范围删除元素 |
| zrevrank sort c# | 返回有序集合中指定成员的排名,有序集成员按分数值递减(从大到小)排序 |
发布订阅
开启两个客户端
A客户端订阅频道: subscribe redisChat (频道名字为 redisChat)
B客户端发布内容: publish redisChat “Hello, this is my wor” (内容是 hello…)
A客户端即为自动收到内容, 原理图如下:
命令 | 说明 |
---|---|
pubsub channels | 查看当前redis 有多少个频道 |
pubsub numsub chat1 | 查看某个频道的订阅者数量 |
unsubscrible chat1 | 退订指定频道 |
psubscribe java.* | 订阅一组频道 |
Redis 事务
Redis 事务可以一次执行多个命令, 并且带有以下三个重要的保证:
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行
- 在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中
一个事务从开始到执行会经历以下三个阶段:
- 开始事务
- 命令入队
- 执行事务
注意:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚
命令 | 说明 |
---|---|
multi | 标记一个事务开始 |
exec | 执行事务 |
discard | 事务开始后输入命令入队过程中,中止事务 |
watch key | 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断 |
unwatch | 取消 WATCH 命令对所有 key 的监视 |
Redis 服务器命令
命令 | 说明 |
---|---|
flushall | 删除所有数据库的所有key |
flushdb | 删除当前数据库的所有key |
save | 同步保存数据到硬盘 |
Redis 数据备份与恢复
Redis SAVE 命令用于创建当前数据库的备份
如果需要恢复数据,只需将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可。获取 redis 目录可以使用 CONFIG 命令
Redis 性能测试
redis 性能测试的基本命令如下:
redis目录执行:redis-benchmark [option] [option value]
// 会返回各种操作的性能报告(100连接,10000请求)
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
// 100个字节作为value值进行压测
redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
Java Redis
Jedis
redis.clients jedis 2.8.2Jedis配置
############# redis Config #############
Redis数据库索引(默认为0)
spring.redis.database=0
Redis服务器地址
spring.redis.host=120.79.88.17
Redis服务器连接端口
spring.redis.port=6379
Redis服务器连接密码(默认为空)
spring.redis.password=123456
连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
JedisConfig
@Configuration
public class JedisConfig extends CachingConfigurerSupport {
@Value(“${spring.redis.host}”)
private String host;
@Value(“${spring.redis.port}”)
private int port;
@Value(“${spring.redis.password}”)
private String password;
@Value(“${spring.redis.max-idle}”)
private Integer maxIdle;
@Value(“${spring.redis.min-idle}”)
private Integer minIdle;
@Bean
public JedisPool redisPoolFactory(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(3000L);
int timeOut = 3;
return new JedisPool(jedisPoolConfig, host, port, timeOut, password);
}
}
基础使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = KerwinBootsApplication.class)
public class ApplicationTests {
@Resource
JedisPool jedisPool;
@Test
public void testJedis () {
Jedis jedis = jedisPool.getResource();
jedis.set(“year”, String.valueOf(24));
}
}
SpringBoot redis staeter RedisTemplate
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2############# redis Config #############
Redis数据库索引(默认为0)
spring.redis.database=0
Redis服务器地址
spring.redis.host=120.79.88.17
Redis服务器连接端口
spring.redis.port=6379
Redis服务器连接密码(默认为空)
spring.redis.password=123456
连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=200
连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=1000ms
连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=8
连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
连接超时时间(毫秒)
spring.redis.timeout=1000ms
// Cache注解配置类
@Configuration
public class RedisCacheConfig {
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(“.”);
stringBuilder.append(method.getName());
stringBuilder.append(“[”);
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append(“]”);
return stringBuilder.toString();
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationWithTtl(15),
// 指定 key 策略
this.getRedisCacheConfigurationMap()
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
redisCacheConfigurationMap.put(“redisTest”, this.getRedisCacheConfigurationWithTtl(15));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
// RedisAutoConfiguration
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
@SuppressWarnings(“all”)
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
// 基础使用
@Resource
RedisTemplate<String,Object> redisTemplate;
redisTemplate.opsForList().rightPush(“user:1:order”, dataList.get(3).get(“key”).toString());
// 注解使用
@Cacheable(value = “redisTest”)
public TestBean testBeanAnnotation () {}
Redis使用场景
类型 | 适用场景 |
---|---|
String | 缓存,限流,计数器,分布式锁,分布式session |
Hash | 存储用户信息,用户主页访问量,组合查询 |
List | 微博关注人时间轴列表,简单队列 |
Set | 赞,踩,标签,好友关系 |
Zset | 排行榜 |
或者简单消息队列,发布订阅实施消息系统等等
String - 缓存
// 1.Cacheable 注解
// controller 调用 service 时自动判断有没有缓存,如果有就走redis缓存直接返回,如果没有则数据库然后自动放入redis中
// 可以设置过期时间,KEY生成规则 (KEY生成规则基于 参数的toString方法)
@Cacheable(value = “yearScore”, key = “#yearScore”)
@Override
public List findBy (YearScore yearScore) {}
// 2.手动用缓存
if (redis.hasKey(???) {
return …
}
redis.set(find from DB)…
String - 限流 | 计数器
// 注:这只是一个最简单的Demo 效率低,耗时旧,但核心就是这个意思
// 计数器也是利用单线程incr…等等
@RequestMapping(“/redisLimit”)
public String testRedisLimit(String uuid) {
if (jedis.get(uuid) != null) {
Long incr = jedis.incr(uuid);
if (incr > MAX_LIMITTIME) {
return “Failure Request”;
} else {
return “Success Request”;
}
}
// 设置Key 起始请求为1,10秒过期 -> 实际写法肯定封装过,这里就是随便一写
jedis.set(uuid, “1”);
jedis.expire(uuid, 10);
return “Success Request”;
}
String - 分布式锁 (重点)
/***
- 核心思路:
-
分布式服务调用时setnx,返回1证明拿到,用完了删除,返回0就证明被锁,等...
-
SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
-
EX second:设置键的过期时间为second秒
-
PX millisecond:设置键的过期时间为millisecond毫秒
-
NX:只在键不存在时,才对键进行设置操作
-
XX:只在键已经存在时,才对键进行设置操作
- 1.设置锁
-
A. 分布式业务统一Key
-
B. 设置Key过期时间
-
C. 设置随机value,利用ThreadLocal 线程私有存储随机value
- 2.业务处理
-
...
- 3.解锁
-
A. 无论如何必须解锁 - finally (超时时间和finally 双保证)
-
B. 要对比是否是本线程上的锁,所以要对比线程私有value和存储的value是否一致(避免把别人加锁的东西删除了)
*/
@RequestMapping(“/redisLock”)
public String testRedisLock () {
try {
for(;😉{
RedisContextHolder.clear();
String uuid = UUID.randomUUID().toString();
String set = jedis.set(KEY, uuid, “NX”, “EX”, 1000);
RedisContextHolder.setValue(uuid);
if (!“OK”.equals(set)) {
// 进入循环-可以短时间休眠
} else {
// 获取锁成功 Do Somethings…
break;
}
}
} finally {
// 解锁 -> 保证获取数据,判断一致以及删除数据三个操作是原子的, 因此如下写法是不符合的
/if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {
jedis.del(KEY);
}/
// 正确姿势 -> 使用Lua脚本,保证原子性
String luaScript = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’,KEYS[1]) else return 0 end”;
Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue()));
}
return “锁创建成功-业务处理成功”;
}
String - 分布式Session(重点)
// 1.首先明白为什么需要分布式session -> nginx负载均衡 分发到不同的Tomcat,即使利用IP分发,可以利用request获取session,但是其中一个挂了,怎么办?? 所以需要分布式session
注意理解其中的区别 A服务-用户校验服务 B服务-业务层
情况A:
A,B 服务单机部署:
cookie:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询数据库获取用户信息
cookie+redis:登录成功后,存储信息到cookie,A服务自身通过request设置session,获取session,B服务通过唯一key或者userid 查询redis获取用户信息
情况B:
A服务多节点部署,B服务多节点部署
B服务获取用户信息的方式其实是不重要的,必然要查,要么从数据库,要么从cookie
A服务:登录成功后,存储唯一key到cookie, 与此同时,A服务需要把session(KEY-UserInfo)同步到redis中,不能存在单纯的request(否则nginx分发到另一个服务器就完犊子了)
官方实现:
spring-session-data-redis
有一个内置拦截器,拦截request,session通过redis交互,普通使用代码依然是request.getSession… 但是实际上这个session的值已经被该组件拦截,通过redis进行同步了
List 简单队列-栈
// 说白了利用redis - list数据结构 支持从左从右push,从左从右pop
@Component
public class RedisStack {
@Resource
Jedis jedis;
private final static String KEY = “Stack”;
/** push **/
public void push (String value) {
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
return jedis.lpop(KEY);
}
}
@Component
public class RedisQueue {
@Resource
JedisPool jedisPool;
private final static String KEY = “Queue”;
/** push **/
public void push (String value) {
Jedis jedis = jedisPool.getResource();
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
Jedis jedis = jedisPool.getResource();
return jedis.rpop(KEY);
}
}
List 社交类APP - 好友列表
根据时间显示好友,多个好友列表,求交集,并集 显示共同好友等等…
疑问:难道大厂真的用redis存这些数据吗???多大的量啊… 我个人认为实际是数据库存用户id,然后用算法去处理,更省空间
Set 抽奖 | 好友关系(合,并,交集)
// 插入key 及用户id
sadd cat:1 001 002 003 004 005 006
// 返回抽奖参与人数
scard cat:1
// 随机抽取一个
srandmember cat:1
// 随机抽取一人,并移除
spop cat:1
Zset 排行榜
根据分数实现有序列表
微博热搜:每点击一次 分数+1 即可
— 不用数据库目的是因为避免order by 进行全表扫描
常见面试题
Q1:为什么Redis能这么快
1.Redis完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高。
2.Redis使用单进程单线程模型的(K,V)数据库,将数据存储在内存中,存取均不会受到硬盘IO的限制,因此其执行速度极快,另外单线程也能处理高并发请求,还可以避免频繁上下文切换和锁的竞争,同时由于单线程操作,也可以避免各种锁的使用,进一步提高效率
3.数据结构简单,对数据操作也简单,Redis不使用表,不会强制用户对各个关系进行关联,不会有复杂的关系限制,其存储结构就是键值对,类似于HashMap,HashMap最大的优点就是存取的时间复杂度为O(1)
5.C语言编写,效率更高
6.Redis使用多路I/O复用模型,为非阻塞IO
7.有专门设计的RESP协议
针对第四点进行说明 ->
常见的IO模型有四种:
同步阻塞IO(Blocking IO):即传统的IO模型。
同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO
同步异步,阻塞非阻塞的概念:
假设Redis采用同步阻塞IO:
Redis主程序(服务端 单线程)-> 多个客户端连接(真实情况是如开发人员连接redis,程序 redispool连接redis),这每一个都对应着一个客户端,假设为100个客户端,其中一个进行交互时候,如果采用同步阻塞式,那么剩下的99个都需要原地等待,这势必是不科学的。
IO多路复用
Redis 采用 I/O 多路复用模型
I/O 多路复用模型中,最重要的函数调用就是
select
,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,select
方法就会返回可读以及可写的文件描述符个数
注:redis默认使用的是更加优化的算法:epoll
select poll epoll 操作方式 遍历 遍历 回调 底层实现 数组 链表 哈希表 IO效率 每次调用都进行线性遍历,时间复杂度为O(n) 每次调用都进行线性遍历,时间复杂度为O(n) 事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪fd放到readyList里面,时间复杂度O(1) 最大连接数 1024(x86)或2048(x64) 无上限 无上限 所以我们可以说Redis是这样的:服务端单线程毫无疑问,多客户端连接时候,如果客户端没有发起任何动作,则服务端会把其视为不活跃的IO流,将其挂起,当有真正的动作时,会通过回调的方式执行相应的事件
Q2:从海量Key里查询出某一个固定前缀的Key
A. 笨办法:KEYS [pattern] 注意key很多的话,这样做肯定会出问题,造成redis崩溃
B. SCAN cursor [MATCH pattern][COUNT count] 游标方式查找
Q3:如何通过Redis实现分布式锁
见上文
Q4:如何实现异步队列
上文说到利用 redis-list 实现队列
假设场景:A服务生产数据 - B服务消费数据,即可利用此种模型构造-生产消费者模型
1. 使用Redis中的List作为队列
2.使用BLPOP key [key…] timeout -> LPOP key [key …] timeout:阻塞直到队列有消息或者超时
(方案二:解决方案一中,拿数据的时,生产者尚未生产的情况)
3.pub/sub:主题订阅者模式
基于reds的终极方案,上文有介绍,基于发布/订阅模式
缺点:消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的,此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列
Q5:Redis支持的数据类型?
见上文
Q6:什么是Redis持久化?Redis有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
Redis 提供了两种持久化方式:RDB(默认) 和AOF
RDB:
rdb是Redis DataBase缩写
功能核心函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
RDB: 把当前进程数据生成快照文件保存到硬盘的过程。分为手动触发和自动触发
手动触发 -> save (不推荐,阻塞严重) bgsave -> (save的优化版,微秒级阻塞)
shutdowm 关闭服务时,如果没有配置AOF,则会使用bgsave持久化数据
bgsave - 工作原理
会从当前父进程fork一个子进程,然后生成rdb文件
缺点:频率低,无法做到实时持久化
AOF:
Aof是Append-only file缩写,AOF文件存储的也是RESP协议
每当执行服务器(定时)任务或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof写入保存:
WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:根据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保存到磁盘中。
存储结构:
内容是redis通讯协议(RESP )格式的命令文本存储
原理:
相当于存储了redis的执行命令(类似mysql的sql语句日志),数据的完整性和一致性更高
比较:
1、aof文件比rdb更新频率高
2、aof比rdb更安全
3、rdb性能更好
PS:正确停止redis服务 应该基于连接命令 加再上 shutdown -> 否则数据持久化会出现问题
Q7:redis通讯协议(RESP)
Redis 即 REmote Dictionary Server (远程字典服务);
而Redis的协议规范是 Redis Serialization Protocol (Redis序列化协议)
RESP 是redis客户端和服务端之前使用的一种通讯协议;
RESP 的特点:实现简单、快速解析、可读性好
协议如下:
客户端以规定格式的形式发送命令给服务器
set key value 协议翻译如下:
- 3 -> 表示以下有几组命令
$ 3 -> 表示命令长度是3
SET$6 -> 表示长度是6
keykey$5 -> 表示长度是5
value完整即:
- 3
$ 3
SET
$6
keykey
$5
value
服务器在执行最后一条命令后,返回结果,返回格式如下:
For Simple Strings the first byte of the reply is “+” 回复
For Errors the first byte of the reply is “-” 错误
For Integers the first byte of the reply is “:” 整数
For Bulk Strings the first byte of the reply is “$” 字符串
For Arrays the first byte of the reply is “*” 数组
// 伪造6379 redis-服务端,监听 jedis发送的协议内容
public class SocketApp {
/***
- 监听 6379 传输的数据
- JVM端口需要进行设置
*/
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6379);
Socket redis = serverSocket.accept();
byte[] result = new byte[2048];
redis.getInputStream().read(result);
System.out.println(new String(result));
} catch (Exception e) {
e.printStackTrace();
}
}
}
// jedis连接-发送命令
public class App {
public static void main(String[] args){
Jedis jedis = new Jedis(“127.0.0.1”);
jedis.set(“key”, “This is value.”);
jedis.close();
}
}
// 监听命令内容如下:
*3
$3
SET
$3
key
$14
Q8:redis架构有哪些
单节点
主从复制
Master-slave 主从赋值,此种结构可以考虑关闭master的持久化,只让从数据库进行持久化,另外可以通过读写分离,缓解主服务器压力
哨兵
Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时自动进行故障转移。其中三个特性:
监控(Monitoring): Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
提醒(Notification): 当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。
特点:
1、保证高可用
2、监控各个节点
3、自动故障迁移
缺点:主从模式,切换需要时间丢数据
没有解决 master 写的压力
集群
从redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。
特点:
1、无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。
3、可扩展性,可线性扩展到 1000 个节点,节点可动态添加或删除。
4、高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本
5、实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 Slave到 Master 的角色提升。
缺点:
1、资源隔离性较差,容易出现相互影响的情况。
2、数据通过异步复制,不保证数据的强一致性
Q9:Redis集群-如何从海量数据里快速找到所需?
按照某种规则去划分数据,分散存储在多个节点上。通过将数据分到多个Redis服务器上,来减轻单个Redis服务器的压力。
既然要将数据进行分片,那么通常的做法就是获取节点的Hash值,然后根据节点数求模,但这样的方法有明显的弊端,当Redis节点数需要动态增加或减少的时候,会造成大量的Key无法被命中。所以Redis中引入了一致性Hash算法。该算法对2^32 取模,将Hash值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为0、1、2…2^32-1,之后将每个服务器进行Hash运算,确定服务器在这个Hash环上的地址,确定了服务器地址后,对数据使用同样的Hash算法,将数据定位到特定的Redis服务器上。如果定位到的地方没有Redis服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。
Hash环的数据倾斜问题
Hash环在服务器节点很少的时候,容易遇到服务器节点不均匀的问题,这会造成数据倾斜,数据倾斜指的是被缓存的对象大部分集中在Redis集群的其中一台或几台服务器上。
如上图,一致性Hash算法运算后的数据大部分被存放在A节点上,而B节点只存放了少量的数据,久而久之A节点将被撑爆。 引入虚拟节点
例如上图:将NodeA和NodeB两个节点分为Node A#1-A#3 NodeB#1-B#3。
Q10:什么是缓存穿透?如何避免?什么是缓存雪崩?如何避免?什么是缓存击穿?如何避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
如何避免?
1:对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
2:对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的Bitmap中,查询时通过该bitmap过滤。
3:由于请求参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(Bloomfilter)或压缩filter提前进行拦截,不合法就不让这个请求进入到数据库层
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
如何避免?
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4:启用限流策略,尽量避免数据库被干掉
缓存击穿
概念 一个存在的key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到DB,造成瞬时DB请求量大、压力骤增。
解决方案 A. 在访问key之前,采用SETNX(set if not exists)来设置另一个短期key来锁住当前key的访问,访问结束再删除该短期key
B. 服务层处理 - 方法加锁 + 双重校验:
// 锁-实例
private Lock lock = new ReentrantLock();public String getProductImgUrlById(String id){
// 获取缓存
String product = jedisClient.get(PRODUCT_KEY + id);
if (null == product) {
// 如果没有获取锁等待3秒,SECONDS代表:秒
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 获取锁后再查一次,查到了直接返回结果
product = jedisClient.get(PRODUCT_KEY + id);
if (null == product) {
// …
}
return product;
} catch (Exception e) {
product = jedisClient.get(PRODUCT_KEY + id);
} finally {
// 释放锁(成功、失败都必须释放,如果是lock.tryLock()方法会一直阻塞在这)
lock.unlock();
}
} else {
product = jedisClient.get(PRODUCT_KEY + id);
}
} catch (InterruptedException e) {
product = jedisClient.get(PRODUCT_KEY + id);
}
}
return product;
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。[外链图片转存中…(img-0qxsYoWc-1713738519666)]
[外链图片转存中…(img-45AyTUSb-1713738519666)]
[外链图片转存中…(img-MqibjxmM-1713738519667)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
最后
针对最近很多人都在面试,我这边也整理了相当多的面试专题资料,也有其他大厂的面经。希望可以帮助到大家。
[外链图片转存中…(img-Wbd1PYZk-1713738519667)]
上述的面试题答案都整理成文档笔记。 也还整理了一些面试资料&最新2021收集的一些大厂的面试真题(都整理成文档,小部分截图)
[外链图片转存中…(img-unXKgcQG-1713738519667)]
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!