![](https://img-blog.csdnimg.cn/2019092715111047.png?x-oss-process=image/resize,m_fixed,h_224,w_224)
Redis
学习redis缓存中间件和实战
Tellme3
任务艰巨在于漫长。
展开
-
52、分布式锁(lua脚本解决多条命令的原子性问题)
执行脚本(redis执行脚本EVAL),(EVAL“脚本语言”)3、脚本参数写死了的情况。redis锁业务流程。使用redis事务和mysql的事务是很不同的,其中而redis的事务是一个批处理(redis事务只能保证一个原子性,而不能保证事务的一致性)。我们判断判断后是不能马上拿到结果的,是最后一起执行的(只能利用redis的一个乐观锁企业判断下被修改没)。我们就推荐使用lua脚本了其中是一个弱类型的语言(不会声明明确的变量类型)执行脚本(redis执行脚本EVAL),(EVAL“脚本语言”)注意:原创 2022-11-08 08:40:55 · 241 阅读 · 0 评论 -
51、分布式锁(分布式锁的原子性问题)
前面的解决了(误删问题)还有问题:(还是有情况出现了误删操作)场景:我们的场景是在判断后进行删除的时候阻塞了线程1在准备释放锁的时候,出现了阻塞(jvm的gc垃圾器。当我们的jvm去做这个fgc的时候。jvm回去阻塞所有的代码,所以这里是jvm的问题。而不是业务的阻塞(我们就进行释放锁阻塞什么嘛))。这个若是阻塞时间若是太长线程1又出现了超出超时时间(redis锁自动删除)。这时线程2又是老套路来获取锁执行业务,线程1阻塞完了又来释放锁了(前面是已经判断了。是自己的(但是不是自己的是线程2的锁)我们的原创 2022-11-08 08:38:24 · 259 阅读 · 0 评论 -
50、分布式锁(解决redis分布式锁误删问题)
这里存入最好还是用uuid作为标识(因为若是用线程id作为锁标识。其中的线程id是jvm提供的一个递增的数字。若是我们再集群或者分布式的场景下,其中有不同jvm那么就统一出现线程id冲突的情况了)我们改造的是锁的实现第一步:(获取线程标识(更复杂一点))这里我们是uuid+线程id作为线程标识第二步:释放锁(判断一下标识)获取线程标识(threadId)获取锁里面的标识(也就是前面存的key)判断线程标识和锁标识是否一致释放锁原创 2022-11-08 08:36:53 · 409 阅读 · 0 评论 -
49、分布式锁(redis分布式锁误删问题)
解决思路:我们释放的锁的时候进行一个判断(判断锁的标识是否一致(可以存线程id(jvm提供)作为标识(uuid更好。分布式场景有多个jvm)))优化后的逻辑:(可以存一个线程id作为锁的标识。释放锁前先判断一下)以前释放锁的逻辑:(随意释放锁,没有判断这个锁是不是自己的)优化后的业务流程:(加了锁的标识,删除锁前先判断下标识)原创 2022-11-07 16:08:51 · 373 阅读 · 0 评论 -
48、分布式锁(基于redis实现分布式锁初级版本)
第五步:修改一下锁(之前是syn锁(关键字)(他是api自己加锁释放锁的))我们这里自己创建锁对象了(这里是lock锁了(是一个类))创建锁对象获取锁(尝试)判断是否获取锁成功失败就返回失败(我们这里的业务逻辑是防止别人恶意刷单所以就返回失败而不是让它重试)获取锁成功(前面正常的获取代理对象(事务))再try finally释放锁(如果出现异常就释放锁,没出现异常也释放锁了)原创 2022-11-07 16:07:44 · 462 阅读 · 0 评论 -
47、分布式锁(基本原理和不同实现对比)
分布式锁:分布式锁的实现:分布式锁:就是要满足分布式系统,或者集群模式下的不同进程的可见而且要互斥的锁原创 2022-11-07 16:02:32 · 120 阅读 · 0 评论 -
46、优惠券秒杀(集群下的线程并发问题(微服务集群))
解决思路:让多个jvm实现同一把锁(同一个锁监视器)2、集群(多个服务(tomcat))1、单个服务器(tomcat)1、单个服务器(tomcat)例如:我们在单个服务时候。是在一个jvm上的,有一个锁的监听器当线程1来获取锁的时候这锁的监听器就会记录线程1这个名称。线程2再来获取就不行。锁的监视器里面已经有了(也就是互斥了)。2、集群(多个服务器(tomcat))。另一个tomcat里面的jvm就是全新的了那么锁的监视器也是全新的(jvm都有各自的堆,栈,方法区,常量池啥的了。)当我们线程3来获取也原创 2022-11-07 16:01:10 · 400 阅读 · 0 评论 -
45、优惠券秒杀(实现一人一单)
优化1、(调用一个字符串方法,它的值相同返回的对象是一样的toString().intern())解决(优化)思路:(还是加锁啊,这里就不能更新再来判断了,就必须是悲观锁了(syn))优化2、我们应该是在事务提交后才释放锁(我们将syn加在这个函数调用前面去加锁了)第一步:查询订单嘛(才能判断(将下面的查询用户id提上来先用))确保当我们的用户的id一样的时候,锁是一样的。第三步:(优化:不要将整个方法锁住(提高效率),锁用户)第二步:判断是否存在了(不存在返回失败)坑2:(我们这里的锁范围又有点小了)原创 2022-11-07 15:58:24 · 615 阅读 · 0 评论 -
优惠券秒杀(乐观锁解决超卖(优化乐观锁的问题))
优惠券秒杀(乐观锁解决超卖(优化乐观锁的问题))总结:还有一些方案问题:一些数据不是库存不能通过>0这个来提高成功率,只能通过判断数据是否被修改过方法:我们可以才去分段加锁的方案(分段锁):我们将我们的资源分成好几份。比如我们的资源是100我们将我们的资源分成10份,每张表里有10个数据。我们去抢的时候就去这10张表里面分别去抢。这样成功率就放大了10倍。(每次锁的资源很少)这种乐观锁还是访问的数据库(其实再多线程高并发的场景下,一般还要优化)原创 2022-11-07 15:53:46 · 385 阅读 · 0 评论 -
43、优惠券秒杀(超卖问题分析(乐观锁解决思路))
想象一下线程1先查询数据库为1,然后卖出去了准备-1,在这期间有其他线程进来也查询库存(此时线程1进行扣减库存)所以查到的也是1,那么这这些线程都进行扣减库存操作(在这些个其他线程中他们的逻辑是没有问题的,但是我们的库存就一直在进行扣减操作了,出现了超卖)乐观锁操作:版本号法(维护一个版本号标志(每次修改库存版本号都要改变(例如+1)),来判断库存是否被修改过)注意:我们的乐观锁操作就是在更改库存的sql语句中在我们不仅要进行库存的扣减,版本号的+1更改,其中还有判断版本跟上面我们查询到的库存版本号和原创 2022-11-07 15:50:42 · 433 阅读 · 0 评论 -
41、优惠券秒杀(Redis实现全局唯一id)
优惠券秒杀(Redis实现全局唯一id)第一步:创建类和方法 第二步:编写逻辑(符号位我们可以先不管只要我们生成的是个正数就OK了) 1.1先自己生成一个时间设置为初始时间逻辑:先得到设置一个时间到秒,在将其时间转化为全为秒的。再将这个值设置为一个static静态变量使用。 1.2、相减生成时间戳 2.1、先注入StringredisTemplate 2.2、用redis的string类型的自增(这里自增的key还需要优化下,不要写死(因为随着时间的增加其中可能会被使用玩这个key)我们再给这个key原创 2022-11-07 15:48:06 · 183 阅读 · 0 评论 -
37、优惠券秒杀(全局唯一Id)
优惠券秒杀(全局唯一Id) 问题:我们这些id会发送给客户端,所以不能用数据库自增(容易让别人根据id猜测到信息(比如今天id10,明天id20。可能知道你就卖出10单)) 解决思路:我们使用redis的String类型(可以自增),符号位(永远为0表示正数)+时间戳+序列号(序列号也就是产生的id) 时间戳:是我们先设置一个开始时间,再减去我们目前的时间得到的值。原创 2022-11-07 15:34:24 · 84 阅读 · 0 评论 -
36、商户查询缓存(基于逻辑过期的方式解决缓存击穿的问题)
商户查询缓存(基于逻辑过期的方式解决缓存击穿的问题)原创 2022-11-07 15:32:09 · 107 阅读 · 0 评论 -
35、商户查询缓存(利用互斥锁解决缓存击穿)
商户查询缓存(利用互斥锁解决缓存击穿) 案例:我们这里如何模拟互斥锁:1、我们用redis的setnx命令来模拟锁,他的逻辑是如果不存在这个key的时候才改变。如果key存在就无法改变 第一步:(我们先设置两个方法分别设置锁和释放锁) 这里我们不直接返回这个flag。因为它的类型是Boolean,而他的方法是一个boolean(一个Boolean的包装类)我们如果直接拆箱的话可能会出现空指针。我们用Booleanuntil工具类。设置锁: 释放锁: 第二步:改变逻辑(再写一个解决缓存击穿的方法)我们是通原创 2022-11-06 09:51:52 · 327 阅读 · 0 评论 -
34、商户查询缓存(缓存击穿(也叫热点key问题)死锁)
逻辑过期:他还是会先获取互斥锁,但是他与上面的互斥锁操作的区别是他会开一个新的线程去查询数据重建缓存。而原来的线程就直接返回旧的数据了。(牺牲了数据一致性)若是再来一个线程查询缓存发现了逻辑过期,获取互斥锁失败了就直接返回旧数据了!!!互斥锁可能会出现死锁:我们有两个线程,A线程拿到锁1,B线程拿到锁2。这时候A线程想要去获得锁2访问下面的资源,A线程就去挂起等待B线程释放锁2咯。这时候B线程也想获得锁1访问锁1下的缓存资源,B线程也去挂起等待A线程释放锁1。结果是两个都无限挂起了,产生死锁。(也原创 2022-11-06 09:48:31 · 118 阅读 · 0 评论 -
33、用户查询缓存(缓存雪崩)
用户查询缓存(缓存雪崩)了解: *redis一段时间大量key的同时失效比如我们再添加一批数据的时候他们是统一时刻导入的他们的key可能是一样的后面一起失效解决思路:我们可以给不同的key的ttl(有效期)加一个随机值*redis直接宕机了我们机房直接挂了?解决思路当我们发现redis出现一些问题的时候我们就可以给缓存服务添加一些降级限流的策略(提前做一些容错处理)。比如快速失败,拒绝服务。而不是直接压倒我们的数据库去。原创 2022-11-06 09:46:12 · 131 阅读 · 0 评论 -
32、商户查询缓存(解决商铺查询的缓存穿透问题)
商户查询缓存(解决商铺查询的缓存穿透问题)这里我们的思路是用缓存空对象的思路来解决(在redis) 我们不能直接返回404了,而是我们将空值写入到redis中,然后再判断是否吗,命中redis后还要再判断下是否为空值,为空就直接结束。第一步: 第二步:(在命中redis后再加一个判断是否为空值) 测试:(我们查一个不存在的数据(这里的0号id)) 控制台有内容(还是有请求的) 我们再查一次控制台就没有了 !!!前面的我们的处理方式只是一被动的处理缓存击穿(缓存null和布隆过滤器)。我们其实还可以主动一点原创 2022-11-06 09:43:26 · 86 阅读 · 0 评论 -
商户查询缓存(缓存击穿)
商户查询缓存(缓存击穿)原创 2022-11-06 09:42:06 · 57 阅读 · 0 评论 -
31、商户查询缓存(缓存穿透)
!!!缓存穿透的理解:(大量请求穿透了redis缓存直接到达数据库)比如我们客户端随意给个id请求,在redis没有命中,在数据库也没有那么我们就只能返回个空给他,那么他收到空后。恶意的开多线程并发的向不存在的数据发起请求。那么这样就会有很多的请求都会到达我们的数据库,那么我们的数据库可能就会崩溃了。解决思路:!!!第一种:缓存空对象(暴力的)直接在redis中给他缓存一个null到redis,那么它访问的话直接redis给他返回了,就不会到数据库了。缺点:可能会缓存大量的无效垃圾(我编原创 2022-11-06 09:41:09 · 188 阅读 · 0 评论 -
30、商户查询缓存(实现商铺缓存和数据库的双写一致)
商户查询缓存(实现商铺缓存和数据库的双写一致)案例: 第一步:加入超时时间 优化下:还是常量池设置个常量高大上 第二步:更新 熟悉的接口层:再到具体的实现类 第二步:实现类实现具体逻辑1、更新数据库 2、删除缓存 3、对id先进行判断(而且下面两步操作要是同一个事务(@Transactional)) 我们这里是一个单体项目所以更新和删除方法在一起我们可以统一一个事务来保证原子性(如果是分布式系统的话删除可能就是另一个系统做的。这样我们就要通过MQ去异步的通知对方这要保证一致性就需要tcc的方案来原创 2022-11-06 09:39:43 · 79 阅读 · 0 评论 -
29、用户查询缓存(缓存更新策略)
4、先操作缓存,还是先操作数据库(线程安全的问题。也就是我们常说的多线程的读写保持数据一致性的问题)!!!(先操作数据库,再删缓存出现不安全的概率低的多):写的时候先写再数据库,再删缓存。读的时候没有命中缓存设置超时时间原创 2022-11-06 09:37:26 · 212 阅读 · 0 评论 -
28、商户查询缓存(练习题)
商户查询缓存(练习题) 注意考虑缓存的类型这里返回的一个集合(可以string因为一切的java对象都可转化为json字符串。还可以选择list)原创 2022-11-06 09:35:12 · 61 阅读 · 0 评论 -
27、商户查询缓存(添加商户缓存)
商户查询缓存(添加商户缓存)业务流程: 第一步:先把业务在controller搬到service里去 第二步:然后创建出上面的方法(接口/实现类) 再crtl+b接口找到实现类 第三步:根据流程图写出具体实现 优化:把这里的key前缀放到常量里 第一次耗时可能久点,后面有耗时就快多了 而且这个控制台就不会有关于这个商铺的sql语句了(这里是下面的优惠券)原创 2022-11-06 09:34:14 · 236 阅读 · 0 评论 -
26、商户查询缓存(什么是缓存)
商户查询缓存(什么是缓存) !!浏览器缓存:比如我们界面的一些静态的资源(css/图片)缓存在本地!!应用层缓存: 我们可以把我们的数据库查的数据存在一redis里我们就直接从redis里给浏览器请求(redis读写非常的快)!!数据库还可以缓存:可以缓存一些索引(mysql给id创建索引)Cpu缓存磁盘缓存原创 2022-11-05 10:48:59 · 68 阅读 · 0 评论 -
25、短信登录(登录拦截器的优化)
短信登录(登录拦截器的优化)在加一个拦截器(因为我们只有之前的那一个拦截器不能拦截所有的路径(比如首页界面是不需要登录)之前的只会拦截那些需要登录校验的路径)原创 2022-11-05 10:47:21 · 116 阅读 · 0 评论 -
24、短信登录(基于redis实现短信登录)
短信登录(基于redis实现短信登录)修改代码(之前的基于session)发送验证的逻辑:(更改就是将短信验证码存到redis中第一步:注入SrtingRedisTemplate (userserviceImpl中)改动2:用户信息判断后之前保存在session中,现在保存在redis中保存在redis我们用存储用hash存储注意redis的key用一个随机的token作为keyToken还需要我们手动返回给前端原创 2022-11-05 10:44:07 · 5037 阅读 · 1 评论 -
23、短信登录(基于redis实现共享session登录)
第一部分(发送验证码的区别)我们生成验证码就是保存在redis中了,而不是session了。我们存取验证码,根据session的要求则也需要满足其session是唯一的,所以我们这里存取验证码也要保持key唯一,所以我们用手机号为key第二部分(登录注册,保存用户信息的区别)我们保存用户信息,我们用随机token(也就是随机字符串:比如uuid生成)为key存储用户数据(唯一key的要求)!!!为什么我们这里保存用户信息不用手机号作为key了呢:因为也就是后面我们将手机号作为token返回给前原创 2022-11-05 10:38:01 · 807 阅读 · 0 评论 -
22、短信登录(隐藏用户的敏感信息)
短信登录(隐藏用户的敏感信息)我们返回给前端的信息太多了 问题:为什么我们这里全返回了呢?所以我们冲userHoler里取出来的信息就是完整的。 2、userHoler从哪里来(我们在拦截器的时候从session中获取后直接存到了userHolder) 解决:我们只要在查询后只返回给一些基本的信息就ok了。1、 这里我们定义了一个dto 2、我们在往session中存之前将user转化为user的dto(这里我们使用了util的工具类) 测试:原创 2022-11-05 10:36:00 · 466 阅读 · 0 评论 -
22、短信登录(Session共享的问题)
短信登录(Session共享的问题) 问题:其中当我们存取在session中的时候我们当我们tomcat集群,我们负载均衡到其他服务器的时候那我们的在其他服务器存起的session就无法获取。解决:用redis替代session原创 2022-11-05 10:33:34 · 105 阅读 · 0 评论 -
21、短信登录(实现登录校验拦截器)
短信登录(实现登录校验拦截器)这个就是请求所带cookie的sessionid 流程: 总体步骤:第一步:我们需要拦截(完成校验)第二步:我们需要将拦截发送到前端控制器(我们还要保证线程安全所以我们使用Threadlocal)我们将拦截器拦截到用户信息以后可以保存到ThreadLocal中(其中ThreadLocal是一个线程对象,又因为每一个进入tomcat的请求都是一个独立的线程。那么ThreadLocal就会在线程内开辟一个内存空间(map)保存对应的用户)具体实现流程:第一步:创建拦截器(继承Han原创 2022-11-05 10:32:39 · 160 阅读 · 0 评论 -
20、实现短信验证码的登录注册功能
实现短信验证码的登录注册功能 第一步:查看接口内容 为什么用@RequestBody因为其中我们前端传过来的是json数据那么后端我们就要用@requestBody注解来接收了。 查看写这个实体类 这里是因为我们前端除了有短信登录,还有密码登录所以有password 第二步:写方法(就是在这个controller中) 第三步:实现方法接口(interface) 真正的实现部分(impl) 第四步:实现真正的流程 !!!我们使用query()因为我们继承了这个ServiceImpl且指明了实体类(原创 2022-11-05 10:30:21 · 1048 阅读 · 0 评论 -
19、实现发送短信验证功能
实现发送短信验证功能第一步:首先编写一个业务的接口(来处理前端的请求) 第二步: 第二步:调用userService的方法 第三步:创建这个sendCode方法1、接口实现方法 第三步:在接口的实现类中实现真正的逻辑原创 2022-11-05 10:24:40 · 368 阅读 · 0 评论 -
18、基于session实现登录
基于session实现登录 理解:Sessiom是基于cookie的。每一个session都有一个session id保存在cookie当中。当我们的用户来请求的时候会携带上自己的cookie。我们就可以基于cookie拿到我们session并且冲session中拿到我们用户(自己看第二个流程我们在注册登录的时候我们也将我们用户信息存入到session了)我们再检验登录后若是我们判断用户存在后我们会把用户保存到Threadlocal中(后续我们的业务就会直接从threalocal中获取)threadloca原创 2022-11-05 10:22:24 · 615 阅读 · 0 评论 -
17、短信登录(黑马点评)
短信登录(黑马点评) 项目结构 第一步:导入后端(更改数据库)原创 2022-11-04 10:44:46 · 223 阅读 · 0 评论 -
15、Redis的java客户端(RedisTemplate的stringRedisTemplate操作hash)
Redis的java客户端(RedisTemplate的stringRedisTemplate操作hash)注意其中存使用的的.opsForHash().put();取用的是opsForHash().entries();原创 2022-11-04 10:42:58 · 222 阅读 · 0 评论 -
14、Redis的java客户端(StringRedisTemplate)
Redis的java客户端(StringRedisTemplate)问题:前面的json序列化会存放过多的内容 解决:节省内存空间(当以后存取对象过多)我们不会使用json序列化器,而是使用string序列化器。其只能存string的key和value存Java对象的时候只能手动。 第一种:spring提供的StringRedisTemplate类(默认为string。这样如果们是string的话。就可省略手动序列化过程) 第一步:字符串的 第二步:java对象的 总结:原创 2022-11-04 10:40:44 · 169 阅读 · 0 评论 -
13、Redis的java客户端(RedisTemplate入门)
Redis的java客户端(RedisTemplate入门) 第二步:引入依赖 第三步:写配置 第四步:测试 总结:原创 2022-11-04 10:39:08 · 164 阅读 · 0 评论 -
12、Redis的java客户端(SpringDataRedis快速入门)
Redis的java客户端(SpringDataRedis快速入门) !!!!我们之前使用jedis里放的都必须是一个string或者byte,若是存一个java对象就十分不方便。这里这序列化就是将java对象转换为字节或者字符串。反序列化就是将其字节或者字符串转换为java对象原创 2022-11-04 10:36:07 · 78 阅读 · 0 评论 -
11、Redis的java客户端(jedis连接池)
Redis的java客户端(jedis连接池) 第一步:工具类创建连接池 第二步: 注意其中下面的close就不再是关闭的而是归还returnResource。原创 2022-11-04 10:34:35 · 127 阅读 · 0 评论 -
Redis(jedis)快速入门
Redis(jedis)快速入门 第一步:mven创建项目 第二步:引入依赖 第三步:建立连接 第四步:测试set 第五步:!!!释放连接原创 2022-11-04 10:32:48 · 277 阅读 · 0 评论