文章目录
快速回忆
总说
本节内容是对畅购商城项目(面试版)一 和 畅购商城项目(面试版)二 内容的总结
1、JMeter压测
做了哪些压测?
前台请求先经过nginx,然后nginx交给gulimall-Gateway,然后才能到达具体的服务,中间两个中间件nginx、Gateway会不会影响性能?
先压测localhost:10000,因为它没有使用中间件直接访问到首页,所以响应也挺快的;
然后压测gulimall.com,因为它有了nginx,有了Gateway,查看它的响应时间;
优化的方向:
1.动静分离:以前我们是动态请求、静态请求都是先找nginx,然后找Gateway,然后找具体的微服务。所以我们可以把静态资源上传到nginx上面,这样静态请求只需要找到nginx就看拿到对应的资源了。
2.业务代码也很影响性能。①查询三级分类时查询数据库要尽量一次拿到pms_category的所有数据,而不是一级分类查一下数据库、二级分类查一下数据库、三级分类再查一下数据库。一次性把数据查出来,然后按照parentId进行三级分类 ②使用Redis缓存,将三级目录的数据存放到缓存里面,这就不用来一个查一个。
3.优化JVM:堆内存太小,频繁GC。给gulimall-product设置-Xmx1024m -xms1024m
串起来:一个请求先经过
nginx
,然后经过业务代码
,代码底层有JVM
2、缓存击穿\缓存穿透\缓存雪崩
缓存击穿:一个非常“热点”的数据缓存失效,查询都落到db
缓存穿透:指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库
缓存雪崩:缓存雪崩是指在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB
3、加锁的方式
①使用Redis获得分布式锁lock=setIfAbsent(key,value)
就是如果这个key不存在的话就设置key-value,而且返回true;如果存在了就设置不了key-value,返回false
缺点:使用redis加锁时,加锁得保证原子性,解锁也得保证原子性:
- 加锁和给锁设置过期时间这两行代码存在时间差,万一在这个时间差内出现断电什么的也会被某个线程拿到没有被设置过期时间的锁——解决办法:加锁和设置过程时间弄成原子性操做,要么同时成功要么同时失败。
- 删锁时发现你的锁已经过期了,你的锁已经被别的线程拿到了,别的线程就会进来,你此时如果删锁那么删掉的是别人的锁——解决办法:给锁设置uuid,每个人都不一样,你就删不了别人的锁了。
②使用Redisson
RLock lock = redisson.getLock("my-lock");
lock.lock();
lock.unlock();
redisson的lock具有如下特点
- (1)阻塞式等待。默认的锁的时间是30s。
- (2)锁定的制动续期,如果业务超长,运行期间会自动给锁续上新的30s,无需担心业务时间长,锁自动被删除的问题。
4、如何保证缓存和数据库中的数据一致?
- 改了数据库顺带着改了缓存(高并发下有并发风险)
- 改了数据库顺带删了缓存(高并发下有并发风险)
- 使用SpringCache
5、缓存模块总结
一开始商城首页使用Redis
商城首页是一个非常热点的数据,redis数据过期后很容易大并发请求到数据库,容易发生缓存击穿
要解决缓存击穿就需要加锁,而加本地锁不行,只能加分布式锁,但是加分布式锁又要考虑一堆分布式并发问题,于是就有了Redisson;
用上Redisson后还要考虑缓存和数据库一致性问题,于是SpringCache应用而生。
用来SpringCache,常规数据的缓存你可以不用Redisson,因为SpringCache已经考虑到缓存雪崩、击穿、穿透问题了,它里面可以加锁,可以设置过期时间等等。
6、注册页面—验证码功能
发送验证码的操做:后端先验证一下验证码是否在60秒前发送过(接口防刷),如果没有就发送验证码功能
如何校验验证码是否在60秒前发送过?
redis中存储(key为"sms:code:“+phone,value为"验证码_当前时间”,过期时间是10分钟)
后台根据phone查询redis,能查到如果能够获取到这段信息就判断value中时间差是否小于60s,如果是就不进行发送验证码操做;否则,发送验证码并存放key-value到redis里面
7、注册页面—注册功能
前端传来UserRegistVo,后端先进行基本信息校验(手机号用户名是否重复、判断验证码是否正确),若校验未通过,则重定向至注册页面;若通过校验,则则重定向至登录页
8、登陆页面—用户名密码登录
后台拿到用户名密码比对完成登录,登录成功重定向到首页,登陆失败重定向到登录页
9、登陆页面—完成微博登录
用户点击微博登录,前台html页面会跳转到微博授权登录页面,用户授权后,微博端携带着code会重定向到后台的登录接口,
后台此时根据收到的code换取token——拿着code调用微博的接口换取SocialUser信息(token + userId),
后台根据userId判断用户是否是第一次用微博登录,如果是第一次的话我们就得拿着token到微博里面查询该用户的基本信息,然后insert到咱们的数据库里面,相当于注册用户;
如果该用户之前已经用微博登陆过,那就到数据库中更新一下token(后面可以拿着token到微博那里换取用户信息)
上面操作顺利的话,用户信息存放到Redis(或SpringSession)里面,然后重定向到http://gulimall.com;如果不顺利就拿着error信息重定向登录页面
10、SpringSession—session不共享、不跨域问题
之前我们学过解决跨域问题。现在是解决的是不同域名下没办法共享session问题。
问题一:session不能跨域问题:我们在auth.gulimall.com登陆成功后把用户信息存到session里面,但是登陆成功会跳转到gulimall.com不是同一个微服务,我们每个微服务都有自己的域名,它们域名不一样,就没办法共享session。
问题二:分布式下session共享问题:多台服务器都有会员服务,你在A服务器上把用户信息保存到内存上了,下次如果落在B服务器上,即使浏览器带着cookie来了,由于B服务器内存肯定没有存储用户信息,这也是问题。
解决办法:
使用Redis或者Spring-Session
因为http 是一种无状态协议,如果没有Session的话,服务器无法识别请求是否来自同一个用户。
使用Session结合浏览器Cookie,可以保持http会话状态。
浏览器发起请求到服务端,然后服务端根据用户信息生成sessionId,服务端把这个session信息要存到redis里面,并将SessionId返回给浏览器存到cookie里面(cookie的作用域不能仅仅是`auth.gulimall.com`而是要放大服务到`.gulimall.com`,此时浏览器访问其它任何服务都会带上这个cookie。)
用户每次通过浏览器访问服务器都会带上Cookie,服务端从cookie里面拿到SessionId信息,这样就可以判断每次的请求是不是同一个用户,解决http协议无状态问题。
如果你把redis里面的session清空,那就是把登陆过的用户信息清空,虽然前台的浏览器访问后台时携带了cookie信息,但是到redis里面查不到用户信息,所以你就得重新登陆。而且我们设置了redis里面的session默认30分钟过期,也就是30分钟后redis里面的用户信息就没有了
11、单点登录
1.为什么要单点登录?
你在新浪微博(https://weibo.com/)里面注册登录了,同时在新浪体育(https://sports.com/)、新浪新闻(https://news.com/)里面无需登录
2.单点登录的原理?
两个域名不一样的服务端client1和client2,还有一个负责登录的ssoserver,还有一个浏览器,它们四个之间的故事
先说明一下这个路径的含义:
http://ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees
的含义就是让你访问http://ssoserver.com:8080/login.html
登陆页面,而redirect_url=http:I/client1.com:8081/employees
的含义是当你完成登陆后会重定向到http:I/client1.com:8081/employees
的位置
第1-11步的解析:只有登陆了才能查看员工信息。一开始浏览器访问client1.com的员工信息
http:I/client1.com:8081/employees
,client1会根据这个url有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client1.com:8081/employees
,ssoserver.com会判断是否登陆过,没有登陆过就展示这个登陆页面,用户会输入账号密码进行登录,提交登陆请求http:/ssoserver.com:8080/doLogin?usermame,password,redirect_url
给ssoserver.com,那么ssoserver.com会保存用户状态到redis,同时ssoserver.com会命令重定向到http: /lclient1.com:8081/employees?token=dadadadsdeuieu
(浏览器访问路径),同时ssoserver.com会命令浏览器保存sso_token=dadadadsdeuieu
这样式的cookie。浏览器这次就可以访问员工信息了,他的访问路径是刚刚提到的http://lclient1.com:8081/employees?token=dadadadsdeuieu
比一开始访问员工信息的http:I/client1.com:8081/employees
多了token=dadadadsdeuieu
,这就回到第2步了,client1会根据有没有token参数判断是否登录,这次client1会觉得它登陆过了就可以访问员工信息了。
第12-19步解析:这次浏览器要访问客户端2的boss信息
http:I/client2.com:8081/boss
,client2会根据有没有token参数判断是否登录,由于没有token参数也就是没有登陆,服务端会命令浏览器重定向到ssoserver.com的登陆页面http:I/ssoserver.com:8080/login.html?redirect_url=http:I/client2.com:8081/boss
,ssoserver.com会判断是否登陆过,由于浏览器有sso_token=dadadadsdeuieu
这样式的cookie,而且从redis能查到,说明它之前在client1或者client2登陆过,ssoserver.com会命令重定向到http:/lclient2.com:8082/boss?token=dadadadsdeuieu
,所以浏览器就会访问http://lclient2.com:8082/boss?token=dadadadsdeuieu
,这就回到了第2步,client2会根据有没有token参数判断是否登录,登陆过就响应页面。
所以说,以后浏览器无论访问client1还是client2,由于浏览器中保存了cookie,所以ssoserver.com就会判定它登陆过,所以以后都不用登陆。
12、Feign远程调用丢失请求头问题
问题
测试的时候发现gulimall-order使用OpenFeign远程查询gulimall-cart的购物车信息时,经过gulimall-cart的登录拦截器时居然是没有登陆状态。你明明已经在gulimall-order中登录了,为什么会被远程调用的gulimall-cart的登录拦截器拦截下来?
解释
因为浏览器先发送http://order.gulimall.com/toTrade
给gulimall-order时携带了请求头(有请求头就有cookie,有cookie就能查到session),但是使用OpenFeign远程查询gulimall-cart的购物车信息时会新创建一个请求头,丢弃原来的请求头。
解决办法
使