项目面试问题

系统常见问题

博客系统

问:利用 SpringSecurity 整合 JWT 实现安全登录和用户信息校验

在这里插入图片描述
在这里插入图片描述
①自定义登录接口

​ 前端提交用户名,密码记过UsernamPasswordAuthenticationFilter过滤器,疯转成Authentication对象

​ 调用ProviderManager的方法进行认证 如果认证通过生成jwt

​ 把用户信息存入redis中

​ ②自定义UserDetailsService

​ 在这个实现类中去查询数据库,加载响应的UserDetails,看看是否和用户输入的账号、密码、权限等信息匹配。

​ 注意配置passwordEncoder为BCryptPasswordEncoder

校验:

​ ①定义Jwt认证过滤器

​ 获取token

​ 解析token获取其中的userid

​ 从redis中获取用户信息

​ 存入SecurityContextHolder

使用自定义注解+AOP 实现日志记录

​ 自定义注解用@interface标注自定义注解类,

//Target注解决定 MyLog 注解可以加在哪些成分上,如加在类身上,或者属性身上,或者方法身上等成分
@Target({ ElementType.PARAMETER, ElementType.METHOD })
//Retention注解括号中的"RetentionPolicy.RUNTIME"意思是让 MyLog 这个注解的生命周期一直程序运行时都存在
@Retention(RetentionPolicy.RUNTIME)

然后导入aop的依赖坐标.让人然后建立切面类,使用@aspect注解

    /**
     * 设置操作日志切入点,这里介绍两种方式:
     * 1、基于注解切入(也就是打了自定义注解的方法才会切入)
     *    @Pointcut("@annotation(org.wujiangbo.annotation.MyLog)")
     * 2、基于包扫描切入
     *    @Pointcut("execution(public * org.wujiangbo.controller..*.*(..))")
     */
    @Pointcut("@annotation(org.wujiangbo.annotation.MyLog)")//在注解的位置切入代码

对浏览量和数据统计用redis 存储,缓解数据库压力
开启定时任务,将数据固化到数据库(使用在更新浏览次数)

使用@EnableScheduling注解开启定时任务功能

在要定时执行的代码上面使用@Scheduled 注解+cron表达式确定任务执行时间

使用CommandLineRunner接口启动时,初始时缓存,把博客的浏览量更新到redis中,再用定时任务 让他每隔10分钟就把Redis中的数据更新到数据库中

使用七牛云解决图片存储问题,调用 api 上传图片

外卖点餐管理系统

数据库主从复制利用 Sharding-JDBC 框架实现读写分离

导入Sharding-JDBC的maven坐标

在配置文件中配置读写分离规则,数据源信息

保证事务问题,同一线程且同一数据库连接内,如有写入操作以后的读操作均从主库读取,用于保证数据一致性

nginx 反向代理实现前后端分离

黑马点评

优惠券秒杀

先查询优惠券,然后判断秒杀是否开始,或者是否结束,再判断库存是否冲出,然后在扣减库存,再创建订单

Redis实现全局唯一id解决一人一单

自己使用 时间戳+序列号 31位的时间戳+32位的自增id

拼接的时候 就是先将时间戳算法左移(<<) 32位 再 或运算 上自增id ,因为算法左移32位 所以现在id二进制右32位是为0 进行或运算就是(第一个操作数的的第n位于第二个操作数的第n位 只要有一个是1,那么结果的第n为也为1,否则为0)

就是在扣减库存之前,根据优惠卷id和用户id查询是否已经下过这个订单,如果下过这个订单,则不再下单,否则进行下单

乐观锁解决超卖问题

写完优惠券秒杀后,利用jmeter发送请求测试时候发现,会出现超卖的问题,当扣减存的时候,写sql时只比较了优惠券id,就进行扣减,后来就想到了乐观锁的形式,就是在进行扣减库存的时候,加一个判断条件 就是扣减库存时的库存数量必须和之前查询到的库存一样的时候,才只能扣减成功,因为这代表着在中间没有别人修改库存.后来又测试的时候发现,成功率太低了,后来想想百度了一下,就发现原因是,当有很多线程去同时扣减库存的时候,就只会有一个线程能扣减成功,其他线程在修改的时候,库存的值已经被修改过了

然后就是在,sql语句上就改变了条件 ,只需要判断库存大于0就行了

乐观锁就是认为线程安全问题不一定会发生,因为不加锁,只是在更新数据时去判断有没有其它线程对数据进行来了修改,如果没修改,才会更新数据,如果被其他线程修改过了就说明发生了安全问题,这样的话就重试或者异常

乐观锁会有一个版本号,每次每次操作数据就会对版本号加一,再提交完数据后就比对一下,是否比之前的版本大一,如果大一就说明操作成功.

CAS

利用cas进行无锁化机制加锁,var5 是操作前读取的内存值,while中的var1+var2 是预估值,如果预估值 == 内存值,则代表中间没有被人修改过,此时就将新值去替换 内存值

缓存穿透

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

  • 缓存空对象
    • 优点:实现简单,维护方便
    • 缺点:
      • 额外的内存消耗
      • 可能造成短期的不一致
  • 布隆过滤
    • 优点:内存占用较少,没有多余key
    • 缺点:
      • 实现复杂
      • 存在误判可能
缓存击穿

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

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

互斥锁

假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁的资源,那么线程1就会再查询数据库重建缓存数据,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程2就可以进行到休眠,直到线程1把锁释放后,线程2获得到锁,然后再来执行逻辑,此时就能够从缓存中拿到数据了。

逻辑过期

我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把重建数据构建完后,其他线程才能走返回正确的数据。

在这里插入图片描述

解决数据库缓存一致性

先更新数据库,再更新缓存 问 题:可能出现数据库更新成功,缓存更新失败,多并发情况下可能会出现数据库中数据和缓存不一致的现象

先更新缓存再更新数据库 , 优点就是每次数据变化能及时的更新缓存,缺点就如果更新缓存频繁的话,会影响服务器性能

先删除缓存再更新数据库 也会导致缓存和数据库数据不一致

先更新数据库再删除缓存,缺点就是可能仍然存在缓存和数据库中数据不一致的情况,但是,我们可以使用重试机制操作.目前使用效果最好的解决方案

延迟双删 先删除缓存 再 更新数据库 ,等个几百毫秒再次删除缓存,这样 就是在mysql更新数据时,其他线程读取了老数据更新到缓存中也会 被删除 ,这样redis中的数据也就和数据库中的一致了

点赞功能怎么实现

就是一个用户只能点赞第一次,再次点赞则取消点赞

如果用户已经点赞按钮,就会显示点赞按钮高亮显示,(会在商品类上添加一个islike 字段)标识是否被当前用户点赞过

然后用Redis中的set集合判断是否点赞过,如果没有点赞过就点赞数+1,已点赞做则点赞数-1

利用 GEO 数据结构查看附近分店信息

Redis 3.2 版本之后加入了对GeO的支持,Redis 6.2提供的GEOSEARCH命令

在指定范围内搜索member,并按照与指定点之间的距离排序后返回。范围可以是圆形或矩形。

为什么使用MQ–面试

在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高系统吞吐量

现在,应用开发和部署—微服务。

超时订单未支付

消息队列 延迟队列

RabbitMQ  没有延迟队列,但可以使用 TTL+死信队列 组合实现延迟队列的效果。
##   
1.设置正常交换机和队列
2.是指死信交换机和死信队列
3.设置正常队列中死信交换机和路由key和TTL属性

下单的时候将订单消息发送到订单队列,订单编号为消息.
创建监听器,监听死信队列:
  去服务器中查询该订单的支付状态,判断是否处于未支付状态,
  关闭该交易,本地关闭订单&记录订单日志&回滚库存&回滚销量.

redis 过期时间监听 设置过期时间

下单时,订单状态是待支付。将订单编号作为key,下单的时间戳作为value,设置过期时间是30分钟。服务器监听redis的key过期事件,如果是订单过期(还会有其他key过期),则修改订单的状态为已取消。当30分钟后未支付则触发redis过期事件,只需修改订单状态即可。若30分钟内支付成功,则需要删除此订单在redis的值。当然,在支付时,需要检查订单是否已超时或已支付。很明确,只需要在应用中添加监听器监听redis过期即可。
## 坏处
1)由于Redis key过期删除是定时+惰性,当key过多时,删除会有延迟,回调通知同样会有延迟。因此性能较低
2)且通知是一次性的,没有ack机制,若收到通知后处理失败,将不再收到通知。需自行保证收到通知后处理成功。
3)通知只能拿到key,拿不到value
4)Redis将数据存储在内存中,如果遇到恶意下单或者刷单的将会给内存带来巨大压力
下单扣减库存问题

下单时扣减库存,下单后锁定库存,这样的话就没有超卖的问题。超时未支付时就回滚库存

订单表

订单id 用20位的字符串设置,前八位是年月日,后12位是交易流水号。这样设计方便以后将半年前或者几个月前的数据迁移到令外的表中。 流水号是自增的。会有一张serial_number表,记录它的最大索引。

## 三个字段  Name  value step  
order_serial     21    1   ## value记录的是下一次的索引,step是每次+几
## select name,value,step from serial_number where name = #{name} for update
## for update 排他锁  查询它就是为了修改它,为了防止别的线程在查询它的时候修改它

扣库存的时候直接扣 方法设置成boolean返回类型,sql语句where条件已经设置扣减数大于库存数的条件,如果失败就会返回false

@Transactional
public boolean decreaseStock(int itemId,int amount){
    int rows = itemStockMapper.decreaseStock(itemId,amount);
    return rows > 0 ;
}

生成订单第一步 校验传入的参数 userId, itemId(商品id), int amount(数量), Integer promotionId(活动id)

第二步 先扣减库存 把库存锁住

第三步 生成订单

第四步 更新销量
eStock(int itemId,int amount){
int rows = itemStockMapper.decreaseStock(itemId,amount);
return rows > 0 ;
}


生成订单第一步 校验传入的参数 userId, itemId(商品id), int amount(数量), Integer promotionId(活动id)

第二步 先扣减库存 把库存锁住

第三步 生成订单 

第四步 更新销量
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值