Redis使用心得

1、通用命令

help exists

keys a*

keys *

del k1 k2 k3

expire a1

ttl a1

2、Redis的key允许有多个单词形成层级结构

多个单词之间用':'隔开,格式如下:

项目名:业务名:类型:id

3、Redis默认有16个仓库,编号从0至15

4、基本类型五种

5、jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。

6、lettuce 是 线程安全的,对集群模式支持很好

7、jedis使用基本步骤

1)引入依赖 2)创建jedis对象,建立连接

3).使用Jedis,方法名与Redis命令一致

4).释放资源

8、SpringDataRedis的使用步骤:

1).引入spring-boot-starter-data-redis依赖

2).在application.yml配置Redis信息

3).注入RedisTemplate

9、RedisTemplate可以接收任意Object作为值写入Redis:

只不过写入前会把Object序列化为字节形式,默认是采用JDK序列化(ObjectOutputStream字节形式)。

所以一般使用StringRedisTemplate 并手动序列化

11.RedisTemplate的两种序列化实践方案:

方案一:

1.自定义RedisTemplate

2.修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案二:

1.使用StringRedisTemplate

2.写入Redis时,手动把对象序列化为JSON

3.读取Redis时,手动把读取到的JSON反序列化为对象

18、缓存更新策略 一般有三种:内存淘汰 超时剔除 主动更新

19、缓存穿透

缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。

  • 缓存空对象
  • 布隆过滤
  • 增强id的复杂度,避免被猜测id规律
  • 做好数据的基础格式校验
  • 加强用户权限校验
  • 做好热点参数的限流

20、缓存雪崩

是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。

21、缓存击穿问题

也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

22、逻辑过期

redis字段里额外存一个过期时间字段

23、缓存击穿问题解决

  • 互斥锁
  • 逻辑过期

其实都是在解决缓存重建这一段时间发生的问题

24、后台系统一般可以用来导入热点数据

25、如何确保数据库与缓存操作的原子性?

单体系统:利用事务机制

分布式系统:利用分布式事务机制

26、使用数据库自增ID就存在一些问题:

  • id的规律性太明显
  • 受单表数据量的限制 (分库分表时会出现重复id的情况)

27、全局唯一ID生成策略:

•(1)UUID

•(2)Redis自增

•(3)snowflake算法 (采用的是当前机器的自增,需要维护一个机器id,对时钟依赖较高)

•(4)数据库自增(用一个单独的表存自增字段)

Redis自增ID策略:

•每天一个key,方便统计订单量

•ID构造是 时间戳 + 计数器

Redis实现全局唯一Id timestamp COUNT_BITS | count;

33、优惠券秒杀涉及两张表

tb_voucher:存优惠券(普通券+秒杀券)信息 tb_seckill_voucher:存秒杀券的扩展信息

tb_voucher_order:订单记录信息

34、秒杀下单流程

0

35、

乐观锁

不加锁,更新时判断其他线程是否在修改

1)版本号法 库存作为版本号

boolean success = seckillVoucherService.update()

          .setSql("stock= stock -1") //set stock = stock -1

          .eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update(); //where id = ? and stock = ?

这个方式要修改前后都保持一致,但是这样我们分析过,成功的概率太低,所以我们的乐观锁需要变一下,改成stock大于0 即可

v2版本:

boolean success = seckillVoucherService.update()

          .setSql("stock= stock -1")

          .eq("voucher_id", voucherId).update().gt("stock",0); //where id = ? and stock > 0

优点:性能好

缺点:存在成功率低的问题

悲观锁

1)添加同步锁,让线程串行执行

优点:简单粗暴

缺点:性能一般

36、乐观锁比较适合更新数据,而现在是插入数据,所以我们需要使用悲观锁操作

37、一人一单出现并发问题分析过程

1)、初始方案是封装了一个createVoucherOrder方法,同时为了确保他线程安全,在方法上添加了一把synchronized 锁

分析:添加锁,锁的粒度太粗了,在使用锁过程中,控制锁粒度 是一个非常重要的事情,因为如果锁的粒度太大,会导致每个线程进来都会锁住,所以我们需要去控制锁的粒度

2)、第二步控制粒度

intern() 这个方法是从常量池中拿到数据,如果我们直接使用userId.toString() 他拿到的对象实际上是不同的对象,new出来的对象,我们使用锁必须保证锁必须是同一把,所以我们需要使用intern()方法

分析:还是存在问题,问题的原因在于当前方法被spring的事务控制,如果你在方法内部加锁,可能会导致当前方法事务还没有提交,但是锁已经释放也会导致问题,所以我们选择将当前方法整体包裹起来,确保事务不会出现问题

3)、第三步保证事务的特性

分析:但是以上做法依然有问题,因为你调用的方法,其实是this.的方式调用的,事务想要生效,还得利用代理来生效,所以这个地方,我们需要获得原始的事务对象, 来操作事务

4)、第四步使用代理

0

并需要使用AopContext获取当前类的代理类:

0

或者当前类成员变量中 注入自己

分析:但是以上做法依然有问题,集群下仍然会导致问题

5)、第五步 请移步到43号看分布式解决方案

38、spirng事务失效的一种可能现象:

没有使用代理对象,没有被spring去管理,,比如下面的调用:

0

方案 这时需要使用AopContext获取当前类的代理类:

0

或者当前类成员变量中 注入自己

39、使用AopContext 注意点

需要在启动类 添加注解:

@EnableAspectJAutoProxy(exposeProxy = true) // 默认暴露代理对象

xml添加依赖:

dependency>

groupId>org.aspectjgroupId>

artifactId>aspectjweaverartifactId>

dependency>

40、集群下锁失效原因分析

锁监视器

不同的服务器,jvm不同, 锁对象也不是同一个

41、分布式锁:满足分布式系统或集群模式下多进程可见并且互斥的锁。

0

0

42、redis实现分布式锁的核心思路

核心思路:

我们利用redis 的setNx 方法,当有多个线程进入时,我们就利用该方法,第一个线程进入时,redis 中就有这个key 了,返回了1,如果结果是1,则表示他抢到了锁,那么他去执行业务,然后再删除锁,退出锁逻辑,没有抢到锁的哥们,等待一定时间后重试即可

43、

1)、针对分布式系统问题,利用redis实现分布式锁 版本1

利用setnx方法进行加锁,同时增加过期时间,防止死锁,此方法可以保证加锁和增加过期时间具有原子性

* 5-利用redis分布式锁 版本1 */ Long userId = UserHolder.getUser().getId(); //创建锁对象(新增代码) SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate); //获取锁对象 boolean isLock = lock.tryLock(1200); // 判断是否获取锁成功 //加锁失败 if (!isLock) { return Result.fail("不允许重复下单"); } try { //获取代理对象(事务) IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy(); return proxy.createVoucherOrder3(voucherId); } finally { //释放锁 lock.unlock(); }

分析:但是仍然存在问题,误删别人的锁

线程1拿到锁后业务阻塞,释放锁了;

线程2拿到锁后,执行逻辑,误删了锁。

解决方案:

在每个线程释放锁的时候,去判断一下当前这把锁是否属于自己,如果属于自己,则不进行锁的删除

2)、针对误删了锁问题,利用redis实现分布式锁 版本2

核心逻辑:在存入锁时,放入自己线程的标识,在删除锁时,判断当前这把锁的标识是不是自己存入的,如果是,则进行删除,如果不是,则不进行删除。

private static final String ID_PREFIX = UUID.randomUUID().toString(true) + "-";

@Override

public boolean tryLock(long timeoutSec) {

// 获取线程标示

String threadId = ID_PREFIX + Thread.currentThread().getId();

// 获取锁

Boolean success = stringRedisTemplate.opsForValue()

.setIfAbsent(KEY_PREFIX + name, threadId, timeoutSec, TimeUnit.SECONDS);

return Boolean.TRUE.equals(success);

}

@Override

public void unlock() {

// 获取线程标示

String threadId = ID_PREFIX + Thread.currentThread().getId();

// 获取锁中的标示

String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + name);

// 判断标示是否一致

if(threadId.equals(id)) {

// 释放锁

stringRedisTemplate.delete(KEY_PREFIX + name);

}

}

分析:但是极端情况下还是会出现误删问题,删锁时的原子性问题,之所以有这个问题,是因为线程1的拿锁,比锁,删锁,实际上并不是原子性的

解决方案:

Lua脚本解决多条命令原子性问题 ,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。

3)、针对删锁时的原子性问题,利用redis实现分布式锁 版本3

实现拿锁比锁删锁是一个原子性动作

public void unlock() {

// 调用lua脚本

stringRedisTemplate.execute(

UNLOCK_SCRIPT,

Collections.singletonList(KEY_PREFIX + name),

ID_PREFIX + Thread.currentThread().getId());

}

44、Lua脚本

Redis提供了Lua脚本功能,在一个脚本中编写多条Redis命令,确保多条命令执行时的原子性。Lua是一种编程语言,它的基本语法大家可以参考网站:Lua 教程 | 菜鸟教程

45、redis的事务可以保持原子性但是不能保证一致性

46、基于setnx实现的分布式锁存在下面的问题:

0

只能说setnx+lua就是一种经典方式实现分布式锁。

但企业中一般是引入redission。

47、什么是Redission呢

Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

Redission提供了分布式锁的多种多样的功能

48、Redission使用步骤

1)引入依赖

2)配置Redisson客户端

3)引入客户端方法

49、Redission可重入锁原理

在redission中,我们的也支持支持可重入锁

在分布式锁中,他采用hash结构(hset)用来存储锁,其中大key表示表示这把锁是否存在,用小key表示当前这把锁被哪个线程持有,小value表示重入次数,所以接下来我们一起分析一下当前的这个lua表达式

获取锁的lua脚本 和 释放锁的lua脚本

两个方法lock 和 unlock源码重点位置:

0

0

其实就是lua脚本。

如果使用了带参的trylock方法,利用了消息队列和信号量的知识,看门狗默认时间,

currenthashmap:

0

0

0

0

EXPIRATION_RENEWAL_MAP (会存每一个线程对应的一个执行更新有效时间的定时任务)

原理总流程图:

0

需要复习请看67集

50、Redisson分布式锁原理:

•可重入:利用hash结构记录线程id和重入次数

•可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制

•超时续约:利用watchDog,每隔一段时间(releaseTime / 3),重置超时时间

•主从一致性:利用MutiLock 加锁 (联锁)

51、分布式锁 三种方式总结

1)不可重入Redis分布式锁:

u原理:利用setnx的互斥性;利用ex避免死锁;释放锁时判断线程标示

u缺陷:不可重入、无法重试、锁超时失效

2)可重入的Redis分布式锁:

u原理:利用hash结构,记录线程标示和重入次数;利用watchDog延续锁时间;利用信号量控制锁重试等待

u缺陷:redis宕机引起锁失效问题

3)Redisson的multiLock:

u原理:多个独立的Redis节点,必须在所有节点都获取重入锁,才算获取锁成功

缺陷:运维成本高、实现复杂

55、使用队列的好处在于 解耦

56、Redis提供了三种不同的方式来实现消息队列:

list结构:基于List结构模拟消息队列

PubSub:基本的点对点消息模型

Stream:比较完善的消息队列模型

0

57、 Redis消息队列-基于Stream的消息队列

Stream 是 Redis 5.0 引入的一种新数据类型,可以实现一个功能非常完善的消息队列。

https://redis.io/commands/?group=stream

58、基于stream的单消费模式

  • 有消息漏读的风险

59、基于stream的消费者组模式

解决 消息漏读的风险

消费者组(Consumer Group):将多个消费者划分到一个组中,监听同一个队列。具备下列特点:

0

60、监听循环的思路:

0

61、spring注解使用

//在类初始化之后执行,因为当这个类初始化好了之后,随时都是有可能要执行的

@PostConstruct

private void init() {

SECKILL_ORDER_EXECUTOR.submit(new VoucherOrderHandler());

}

62、

0

63、点赞实现步骤

使用set 数据结构

实现步骤:

  • 给Blog类中添加一个isLike字段,标示是否被当前用户点赞
  • 修改点赞功能,利用Redis的set集合判断是否点赞过,未点赞过则点赞数+1,已点赞过则点赞数-1
  • 修改根据id查询Blog的业务,判断当前登录用户是否点赞过,赋值给isLike字段
  • 修改分页查询Blog业务,判断当前登录用户是否点赞过,赋值给isLike字段

64、点赞排行榜

使用sortedSet 数据结构

0

由于多了排行榜的需求,所以需要修改之前的逻辑。

注意保证点赞的顺序:

ORDER BY FIELD(id," + idStr + ")

65、关注是User之间的关系,是博主与粉丝的关系,数据库中有一张tb_follow表来标示

66、

共同关注 有交集并集补集的api set集合中的交集数据

选用数据结构 set

set交集 intersect

67、Feed流产品有两种常见模式

Timeline:不做内容筛选,简单的按照内容发布时间排序,常用于好友或关注。例如朋友圈

智能排序:利用智能算法屏蔽掉违规的、用户不感兴趣的内容。推送用户感兴趣信息来吸引用户

68、

采用Timeline的模式。该模式的实现方案有三种:

  • 拉模式
  • 推模式
  • 推拉结合

69、好友关注-推送到粉丝收件箱

核心的意思:就是我们在保存完探店笔记后,获得到当前笔记的粉丝,然后把数据推送到粉丝的redis中去。

选用数据结构 ZSet 方便后期按照时间顺序获取

70、滚动分页:

我们需要记录每次操作的最后一条,然后从这个位置开始去读取数据

71、附近商铺

Geo 经纬度

Geo 底层也是 zset

redis存储 店铺I'd+经纬度坐标

分组 stream groupingby

Maven helper 插件

31bit bitmap

4个字节

Hutool

72、Pv. Uv

Hyperloglog

73、

单机的Redis存在四大问题:

数据丢失问题

并发能力问题

存储能力问题

故障恢复问题

74、Redis有两种持久化方案:

- RDB持久化

- AOF持久化

Redis默认开启RDB

AOF默认关闭的,主要记录日志操作

75、RDB原理

0

RDB方式bgsave的基本流程?

  • fork主进程得到一个子进程,共享内存空间
  • 子进程读取内存数据并写入新的RDB文件
  • 用新RDB文件替换旧的RDB文件

RDB会在什么时候执行?save 60 1000代表什么含义?

  • 默认是服务停止时
  • 代表60秒内至少执行1000次修改则触发RDB

RDB的缺点?

  • RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
  • fork子进程、压缩、写出RDB文件都比较耗时

76、 AOF三种策略对比:

0

项目一般使用everysec

77、禁用RDB

配置文件中配置: save ""

78、AOF文件执行重写功能

通过执行bgrewriteaof命令,可以 减少文件体积,异步执行的

79、RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

0

rdb 备份

aof 日志

80、

主从模式 需要把 rdb开启, aof关闭

81、查看主从状态命令

info replication

82、

假设有A、B两个Redis实例,如何让B作为A的slave节点?

•在B节点执行命令:slaveof A的IP A的port

83、主从同步时master如何得知salve是第一次来连接呢??

有几个概念,可以作为判断依据:

  • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
  • offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。

84、主从全量同步完整流程描述:

  • slave节点请求增量同步
  • master节点判断replid,发现不一致,拒绝增量同步
  • master将完整内存数据生成RDB,发送RDB到slave
  • slave清空本地数据,加载master的RDB
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步

85、

主从第一次同步是全量同步,但如果slave重启后同步,则执行增量同步

86、增量同步注意点

0

87、主从同步优化

主从同步可以保证主从数据的一致性,非常重要。

可以从以下几个方面来优化Redis主从就集群:

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

主从从架构图:

0

88、主从同步小结

简述全量同步和增量同步区别?

  • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
  • 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

什么时候执行全量同步?

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

什么时候执行增量同步?

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时

89、slave节点宕机恢复后可以找master节点同步数据,那master节点宕机怎么办?

哨兵模式

90、Sentinel充当Redis客户端的服务发现来源

91、Sentinel原理

Sentinel基于心跳机制监测服务状态,每隔1秒向集群的每个实例发送ping命令:

•主观下线:如果某sentinel节点发现某实例未在规定时间响应,则认为该实例主观下线。

•客观下线:若超过指定数量(quorum)的sentinel都认为该实例主观下线,则该实例客观下线。quorum值最好超过Sentinel实例数量的一半。

0

92、选举机制

一旦发现master故障,sentinel需要在salve中选择一个作为新的master,选择依据是这样的:

  • 首先会判断slave节点与master节点断开时间长短,如果超过指定值(down-after-milliseconds * 10)则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高 (这一步比较关键)
  • 最后是判断slave节点的运行id大小,越小优先级越高。

93、Sentinel小结

Sentinel的三个作用是什么?

  • 监控
  • 故障转移
  • 通知

Sentinel如何判断一个redis实例是否健康?

  • 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
  • 如果大多数sentinel都认为实例主观下线,则判定服务下线

故障转移步骤有哪些?

  • 首先选定一个slave作为新的master,执行slaveof no one
  • 然后让所有节点都执行slaveof 新master
  • 修改故障节点配置,添加slaveof 新master

94、RedisTemplate集成哨兵机制

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户[[端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。

redis-demo.rar

95、分片集群应对海量数据

如图:

0

分片集群特征:

  • 集群中有多个master,每个master保存不同数据
  • 每个master都可以有多个slave节点
  • master之间通过ping监测彼此健康状态
  • 客户端请求可以访问集群任意节点,最终都会被转发到正确节点

96、分片集群小结

Redis如何判断某个key应该在哪个实例?

  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384取余
  • 余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个Redis实例?

  • 这一类数据使用相同的有效部分,例如key都以{typeId}为前缀

97、数据跟着插槽走,就能保证数据不丢失,因为插槽是可以转移的

98、分片集群转移插槽命令

reshard

100、两种

主从+哨兵

分片集群

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值