不要急,不要急,马上好^_^...

项目复习


项目总体流程

通过Nginx搭建域名

  1. 首先使用Nginx搭建了一个域名访问环境。

    • 为什么需要要使用nginx搭建这个域名访问呢?

      首先从正向代理和反向代理来看:

      • 正向代理:就是客户端在访问某些网址的时候,通过代理服务器进行访问,这样就隐藏了自己的信息进行访问,保证客户端信息的安全
      • 反向代理:就是搭建项目环境的时候,通过一个代理服务器,屏蔽内网的服务器信息,对外保暴露的是代理服务器的信息,这样别人通过访问代理服务器,然后代理服务器转发请求到内网服务器上进行访问
      • image-20211026111446446
      • image-20211026111446447
    • 为什么使用Nginx搭配网关进行搭建?
      因为如果直接使用Nginx路由到端口上,那么如果在集群模式下,一个服务对应多个端口,就需要配置多个端口信息,这样很麻烦,所以通过网关进行路由,这样即使在集群环境下搭建的微服务,我们只需要路由到网管,然后让网关进行服务的调用即可

    image-20211026110547414

    如何搭建的?

    • 首先安装Nginx,然后修改Nginx的配置文件,因为要映射到本机器上的网关上,所以首先配置一个上游服务器地址请求转发到网关上,配置了这个地址之后,再配置Nginx的代理地址,这个代理地址指向的是上游服务器的地址,那么通过这个代理地址,我们每次访问Nginx,都会访问它的80默认端口,然后Nginx会通过代理地址转发到上游服务器地址,再通过上游服务器地址访问网关。

    遇到的问题:一开始在阿里云上部署的Nginx,然后通过本地的域名访问阿里云上的Nginx,发现Nginx一直无法访问本地服务器,一直报服务器错误,然后网上查了一下说,本地服务器需要备案才能访问内网,然后我去阿里云上看了一下需要备案,还要搞好多东西,我心里想着就是做一个项目,没想到这么麻烦,就没有备案,在网上查了好多方法,然后看到一个可以内网穿透的软件,通过那个软件可以代理一个内网穿透的地址,这个地址可以访问内网,然后我就花了十来块钱买了一个月,本来心里想着这下没问题了吧,但是后面使用动静分离那一块,一直出现静态资源无法访问,最终,没办法,就在本地安装了一个Nginx,好了一切问题都解决了,但是这个代理好像没什么太大意义!!!

通过认证服务完成了社交登陆功能(验证码,接口防刷)

  • 首先是注册功能,这里使用了阿里云的验证码进行验证,验证成功之后就可以进行注册了,但是因为验证码的条数是要钱的,所以如果一直被别人刷的话,那验证码的条数就浪费了,因此做了一个接口防刷的功能

    如何实现接口防刷的?

    • 这里使用到了Redis,每次发送短信验证码请求的时候,就会将这个手机号和验证码绑定在一起,然后一起存到redis中,并且设置过期时间为一分钟,如果下次相同的手机号再次发送短信验证,首先会将这个手机号获取,然后通过手机号查询redis中是否存在该手机号,如果存在,那么就直接返回错误信息,如果没有,那么就输入验证码进行注册功能。

    除了使用这个方法进行防刷,你还了解其他的吗?

    • 对单个手机号进行请求限制,如果这个手机号在规定时间段里发送的次数过多,就可以限制这个手机号此时间段内不能再发请求了
    • 对单个Ip进行限制,这样虽然可以防止在一个ip地址下,多个手机号被刷的次数,但是同样的,因为一个小区可能都使用的一个ip地址,如果此时别人也在使用注册功能,那么将是一个很大的问题
    • 网关进行控制,把当前时间段,同一个手机号多次恶意注册的请求拦截

    如何实现密码的加密的?

    • 密码加密主要是保证用户信息的安全性,即使用户信息不小心暴露出去,也不能通过账号和验证码进行登录。实现密码的加密方式主要是使用MD5进行加密的,因为在网上看到了很多对MD5进行破解的软件,因此这里我使用了MD5盐值加密,使用这个会随机的在密码的基础上加一个字符串,这样就能保证密码永远对应的都是不同MD5值,有更高的安全性
  • 社交登录功能

    什么是OAuth2.0

    • OAuth2.0就是开放授权,意思就是用户可以通过给第三方授权,然后获取自己的相关信息,而不需要将用户的账户和密码提供给第三方网站。

    说说OAuth2.0的工作原理?
    image-20211026165107270

    如何实现社交登陆的?

    • 这里首先是在微博的开放平台注册了一个个人应用,然后这个应用对应的域名就是本机服务的域名,注册成功。我们可以通过第三方的授权认证登录微博账户,然后通过这个微博注册的应用返回给我们该用户的相关信息,并且在登录的时候会判断这个用户信息是否存在,如果不存在则需要存储当前新用户,如果存在那么就登陆成功。

    • 存储的数据具体流程

      前端用户登录了第三方提供的登录地址,然后会返回一个code,然后通过code换取一个token,然后因为token过一段时间会变,所以得给token设置过期时间,然后不变的是uid,通过uid来识别不同的用户信息。

Session共享问题

session的原理?

image-20211026190414554

  • 首先用户第一次访问服务器进行登录,登陆成功之后,就会将用户信息保存在session中,然后session会将sessionId返回给浏览器,浏览器就会将这个sessionId存在cookie中,这样,下一次访问页面的时候会带上cookie进行访问。浏览器关闭,清除回话cookie,那么下一次再进来,就需要重新登录

session如果在集群环境下会出现什么问题?

image-20211026191241293

  • 如果是在同一个域名,但是不同服务的情况下:
    • 在这种情况下,如果浏览器第一次访问当了第一个服务器,那么会在该服务器存储session,并且返回sessionId存储在浏览器中,但是如果浏览器下一次访问的时候,通过负载均衡直接转到另一个服务器上,那么这个时候带上之前的cookie显然是不能进行访问的。
  • 如果是在不同域名,不同服务下,session是不能共享的

如何解决这个session不能共享的问题呢???

  • image-20211026191759759
  • image-20211026192011245
  • image-20211026192036706
  • image-20211026192234226

通过Spring Session解决了session不能共享的问题

  • 是怎么解决的呢?

    首先导入SpringSession的依赖,然后这里在配置文件中指定存储类型为redis,然后写一个session配置类,主要用于放大作用域和解决序列化问题。解决的流程就是,将登录的用户的session通过redis进行保存,这样以后用户在访问页面的时候,不管访问哪个服务器,服务器都可以直接从redis中找出对应的session与之匹配,然后进行访问

  • SpringSession的核心原理是什么?

    image-20211026202113997

    image-20211026202153693

    • 它的核心原理有两个重要的点:
      • 第一个:给容器中添加了一个SessionRepository的组件,这个组件主要的作用是让redis操作session,相当于redis的Dao
      • 第二个:SessionRepositoryFilter,存储过滤器,这个过滤器在创建的时候就会自动的从容器中获取SessionRepository,最重要的就是它里面的doFilter方法,这个方法将原生的request和responce都包装成了Wrapper类型的request和response,然后后续我们的操作中获取session中的信息,都是从SessionRepository中获取而不是在原生的服务器中获取。
      • 装饰者模式(责任链模式)

使用Redis做缓存提高性能

  • 使用缓存存在哪些问题呢?
    • 缓存穿透
      • image-20211028212006633
    • 缓存击穿
      • image-20211028212745319
    • 缓存雪崩
      • image-20211028212123327

分布式锁?怎么实现的?

实现?

先拿setnx来争抢锁,抢到之后,再用expire给锁加一个过期时间防止锁忘记了释放。

当然,因为在设置锁和释放锁的过程中,可能此时服务崩溃,或者其他的一些原因导致系统无法运行,这时候可能无法释放锁,或者业务逻辑执行完成,但是锁没有释放,那么这时候就需要使用redis提供的lua脚本进行实现。

为什么使用分布式锁?解决了什么问题?

在集群环境下,如果使用的都是本地锁,那么如果很多请求进来,每个服务都查询redis,里面都没有数据,那么他们就会针对每个服务都发送查询数据库的命令,这样就造成了很多不必要的查询,因此需要使用分布式锁,所有的请求进来,会首先抢占分布式锁,如果有一个线程抢占成功,那么就通过这一个线程来查询数据库,然后将查询到的数据存到redis中,这样就避免了多个线程,多次查询数据库的操作,造成不必要的资源浪费。

看门狗机制?

如果我们设置了锁的过期时间,那么程序不管是否运行完成,时间一过,锁就过期了,那么别的线程就会抢占锁。但是如果我们不设置过期时间,那么就会触发看门狗机制,看门狗会默认的设置为30s,而且会有一个自动续期的功能,只要时间超过默认时间的三分之一,就会进行一次续期操作

接口幂等性

  • 什么是接口幂等?
    • 接口幂等就是,比如我们在进行一个提交操作的时候,这时候如果因为网络延迟,我们点了多次提交,然后就会出现多次重复提交的问题,这样可能我们自身只需要一次提交,但是却提交了很多次,这就是接口幂等
  • 哪些情况需要防止接口幂等
    • 用户多次点击提交按钮
    • 用户页面回退,再次点击提交
    • 微服务互相调用,因为网络或者别的原因调用失败,使用feign的失败重试机制
    • 其他业务…
  • 怎么解决接口幂等性?
    • Token(令牌)机制
      • 用户在提交请求的时候,首先会输入验证码,然后请求会将验证码一起携带传到服务器,服务器会根据传过来的验证码,和存在于redis中的验证码作比较,如果存在,那么就表示请求是第一次进来,如果不存在则表示请求是重复请求。
      • 存在哪些危险性呢?
        • 验证完成之后,是先删除token再执行业务逻辑,还是先执行业务逻辑再删除呢?
          • 先删除token,可能业务还没有执行完成,但是服务器中断,导致这次请求调用失败
          • 后删除token,可能业务完成了,但是token没来的及删除,服务器中断,导致下一次请求又会重新执行一次业务
          • 最好是先删除token,这样即使服务调用失败,也不会造成什么损失
      • 如何解决呢?
        • Token获取、比较、删除必须是原子性的,所以如果使用令牌机制,那么就可以选择redis中的lua脚本进行原子性操作,这样就不会出现上述问题了。
    • 各种锁机制
    • 数据库唯一索引约束
    • redis防重
    • 全局请求唯一ID

多线程异步编排

  • 初始化线程有哪几种方式?

    有四种方式

    • 继承Thread类
    • 实现Runnable接口
    • 实现callable接口
    • 线程池
  • 说说Runnable和Callable的区别

    1)Runnable提供run方法,无法通过throws抛出异常,所有CheckedException必须在run方法内部处理。Callable提供call方法,直接抛出Exception异常。

    2)Runnable的run方法无返回值,Callable的call方法提供返回值用来表示任务运行的结果

    3)Runnable可以作为Thread构造器的参数,通过开启新的线程来执行,也可以通过线程池来执行。而Callable只能通过线程池执行。

    • Callable任务通过线程池的submit方法提交。且submit方法返回Future对象,通过Future的get方法可以获得具体的计算结果。而且get是个阻塞的方法,如果任务未执行完,则一直等待。
  • Future和FutureTask的区别

    • 对于Calleble来说,Future和FutureTask均可以用来获取任务执行结果,不过Future是个接口,FutureTask是Future的具体实现,而且FutureTask还间接实现了Runnable接口,也就是说FutureTask可以作为Runnable任务提交给线程池。
  • 什么是异步任务?什么情况下使用异步任务?

    • 不等任务执行完,直接执行下一个任务。

    • image-20211106110532332

      ​ 按照上面程序的执行顺序,如果是同步任务的话,会依次按照ABC进行执行,但是AC运行的比较快,如果等待B完成之后再完成AC,那么就会使整个程序运行的非常慢,因此使用异步任务,不用等B执行完成,直接运行AC,从而提高效率

  • 线程池的七大参数知道吗?

    • image-20211106111054642
    • 核心线程数:就是线程池创建的时候,里面就有的线程数,称为核心线程数
    • 最大线程数:如果此时阻塞队列满了,核心线程全部都在运行,那么就会开始最大线程数来执行任务
    • 存活时间:当开启了最大线程来完成了自己的任务的时候,并且任务执行完了,此时除了核心线程外,其他的额外线程就会在规定的时间里进行销毁
    • 时间单位:存活时间的单位
    • 阻塞队列:当线程都在运行,此时还有任务进来的时候,任务不会被直接,而是加入到阻塞队列中
    • 线程工厂:指定创建线程的工厂
    • 饱和策略:当开启了最大线程数,且阻塞队列也满了,但是现在还有任务正在加入,那么就会执行饱和策略,默认的饱和策略就是拒绝策略
      • Abort策略:默认策略,新任务提交时直接抛出未检查的异常RejectedExecutionException,该异常可由调用者捕获。
      • CallerRuns策略:为调节机制,既不抛弃任务也不抛出异常,而是将某些任务回退到调用者。不会在线程池的线程中执行新的任务,而是在调用exector的线程中运行新的任务。
      • Discard策略:新提交的任务被抛弃。
      • DiscardOldest策略:队列的是“队头”的任务,然后尝试提交新的任务。(不适合工作队列为优先队列场景)
  • 为什么使用线程池?

    image-20211106112026608

  • 在业务中, 你是怎么使用多线程异步任务来完成的?

    • 首先来看一下主要的方法
    • image-20211106144546689
    • image-20211106145259026
    • image-20211106145200436
    • 具体实现
    • image-20211106145633587
    • image-20211106145751611

image-20211106102203574

分布式事务

  • 分布式事务会存在那些问题?
    • 本地事务回滚,而远程服务不回滚
  • 怎么解决?
    • image-20211028095407700
  • CAP定理
    • image-20211028100639121
  • 面临问题
    现在互联网的集群规模越来越大,如果保证一致性,那么就要舍弃可用性,但是因为网络原因,或者节点故障而导致服务调用失败是常态,所以如果要保证可用性,就只能舍弃C而保证AP
  • BASE理论
    • BASE理论是对CAP的一种延伸,意思就是即使无法做到强一致性,也可以做到弱一致性(最终一致性)
  • 使用Seata解决分布式事务

消息队列

  • 消息队列的应用场景
    image-20211027105938326

    异步处理:比如一个注册功能,在注册的时候会首先将注册信息写入数据库,然后后面可能还需要发送邮件,发送信息等操作,这一系列操作都完成之后,才可以算真正的注册成功,但是这样非常的耗时,因此可以采用消息队列进行异步处理,也就是将用户的注册请求保存在消息队列中,然后直接让消息队列进行后续的异步处理逻辑

    image-20211027141911689

    应用解耦:比如现在有两个微服务,他们之间需要一起调用实现一组操作,这个时候,可以通过消息中间件将他们分开,也就是解耦,让后面的服务不再依赖前面的服务,前面服务只需要将请求和数据存到消息队列中,而后面的服务只需要取出这些请求和数据进行相应的处理即可

    image-20211027141923484

    流量控制:例如现在一个系统是百万级并发的秒杀系统,那么这时候可能会出现每秒百万的请求进来,这时候即使服务器能承受的了这么多的请求,但是等待处理的话,后续还有请求进来,将会一直阻塞,然后系统会崩溃,这时候就可以通过消息队列将这些请求全部存储起来,这样将请求的后续操作慢慢进行,就不会导致服务器崩溃的情况了。

  • 消息队列的定义
    image-20211027144735349

  • RabbitMQ的概念:

    image-20211027150940156

    • 首先会有一个生产者来生产消息
    • 消息是由消息头和消息体组成的,这其中消息头中有一个重要属性:Route-Key:路由键
    • 交换机用来接收生产者生产的消息的,并将消息路由给服务器中的队列
    • Broker消息中间键的服务器
    • Queue消息队列,用于存储消息的
    • VHost虚拟主机,主机之间互相隔离,互不影响
    • Consumer消费者

    具体工作流程:

    ​ 首先生产者生产信息,然后将这些信息发送到消息服务器中,服务器会根据消息找到指定的交换机,然后交换机会通过路由键找到指定的队列,通过交换机与队列的绑定关系,将信息发送到队列中,然后由消费者通过与服务器建立连接,通过信道进行信息的传输

  • 交换机类型
    image-20211027154236657

    image-20211027155524216

    image-20211027154304851

    image-20211027154317506

  • 消息确认机制
    image-20211027165225226

  • p->b

    image-20211027200533738

  • e->q

image-20211027211744153

  • q->c

    image-20211027211937323

  • 延时队列

    • 使用场景
      • 下单成功之后,但是一直没有完成支付,那么在规定的时间内,如果一直没完成,那么时间结束,就会自动关单,并且将锁定的库存释放

文章作者: Mr Hou
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mr Hou !
希望得到你的建议哦
  目录