优化左侧导航栏(流程整理以及面试可能的提问)

发布于:2022-07-25 ⋅ 阅读:(353) ⋅ 点赞:(0)
  • 优化左侧导航栏

步骤:

  • 设计数据库:首先保证数据库中有父类标签字段(可以为空,一级分类没有父类标签)
  • 设计实现类:因为要实现分类 因此在要实现分类的entity中添加一个子类集合 (list<entity> children),因为不是数据库中的字段因此要添加@TableField(exist=false)注解
  • 导航栏的实现:查找数据库中父类标签为0的分类
  • 二三级菜单的实现:遍历父类的标签为0的分类,并查找对应的子分类放入list<entity> children,遍历二级分类找到三级分类。(因为此过程要多次查询数据库,所以我们应首先查一次全表,并将全表数据放入到list中,减少io的次数)
  • 使用redis缓存和分布式锁来优化商城首页。

优化:

为什么使用缓存?

  • 因为左侧导航栏的查询需要花费大量时间,每一浏览器发出请求,服务器重新查一遍会大大减少我们网站的吞吐量,因此我们可以将查询好的分类数据存在内存当中,这样除了一次浏览器发出请求需要查一遍数据库,其余请求我们直接在内存中取就可以了。
  • 但是因为我们的系统是分布式的,我们每台服务器都存一次导航栏数据的话比较浪费资源,也不利于我们缓存的管理和一致性。因此我们将缓存放在redis中。

如何保证在分布式系统中我们做到只需要查一次库存的功能?

  • 使用redis锁机制,并在上锁的时候保证保证每一个线程的锁都有自己的唯一标识并在上锁的时候设置过期时间,其上锁与设置过期时间要保证原子性
  • 使用lua脚本保证判断是不是该线程的锁与删除锁操作的原子性
  • 更新缓存要在释放锁之前完成
  • 缺点就是无法解决自动续期(业务时间超过过期时间导致锁被提前释放)

最佳实践:

整合redisson:配置redissonClient;(使用同一个名字就是同一把锁)
看门狗机制:
如果指定了到期时间,则即使业务没有执行完,看门狗也不会自动续期,而直接释放锁
原理:如果我们传入时间,redisson直接执行lua脚本,进行占锁,超时释放
如果未指定,默认传入-1,就是用30s(lockwatchTimeOut看门狗默认时间),只要占锁成功,就会开启一个定时任务(重新给锁设置过期时间30s){定时时长为10s}

整合springCache:引入依赖,在启动类加上@EnableCaching

@Cacheable:触发将数据保存到缓存,在方法上标注,表示当前方法的结果需要缓存,如果缓存中有,方法不用调用,如果缓存中没有,会调用方法,最后将方法的结果放入缓存。通常用在查询方法上

@CacheEvict:触发将数据从缓存中删除,一般用在更新或者删除的方法上。

@CachePut:不影响方法执行更新缓存,通常用在新增方法上。

@Caching:组合以上多个操作

@CacheConfig:在类级别共享缓存的相同配置

资源动静分离:动态页面和静态页面交给不同的服务器来解析,来加快解析速度,提高请求的访问效率,降低原来单个服务器的压力。

Nginx正向代理与反向代理

代理:配置客户机实现让一台服务器代理客户机,客户的所有请求都交给代理服务器处理。

反向代理:用一台服务器代理真实服务器,用户访问时不是访问真实服务器而是代理服务器

可能产生的问题:

高并发下的缓存问题即解决方案?

  • **缓存穿透** 指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询null写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义
  • 风险:利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃
  • 解决: null 结果缓存,并加入短暂过期时间
  • **缓存雪崩** 缓存雪崩是指在我们设置缓存时 key 采用了相同的过期时间,导致缓存在某一时刻同时失效, 请求全部转发到 DB,DB 瞬时 压力过重雪崩。
  • 解决: 原有的失效时间基础上增加一个随机值,比如 1-5 分钟随机,这样每一个缓存的过期时间的重 复率就会降低,就很难引发集体失效的事件。
  • **缓存击穿** * 对于一些设置了过期时间的 key,如果这些 key 可能会在某些时间点被超高并发地访问,是 一种非常“热点”的数据。如果这个 key 在大量请求同时进来前正好失效,那么所有对这个 key 的数据查询都落到 db,我们称为缓存击穿。
  • 解决:加锁。大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db;

分布式缓存的缓存一致性的解决?

  • 双写模式:当数据更新时,更新数据库时同时更新缓存;存在问题,由于卡顿等原因,导致写缓存,这是暂时性的脏数据问题,但是在数据稳定,缓存过期以后,又能得到最新的正确数据

 

  • 失效模式:写完数据库,不用写缓存,而是删缓存,等有请求进来读数据的时候,缓存中没有,就会查数据库,然后主动放到缓存里面。也叫触发主动更新;存在问题,当两个请求同时修改数据库,一个请求已经更新成功并删除缓存时又有读数据的请求进来,这时候发现缓存中无数据就去数据库中查询并放入缓存,在放入缓存前第二个更新数据库的请求成功,这时候留在缓存中的数据依然是第一次数据更新的数据

 

无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?

  • 如果是并发几率的数据,不用考虑这个问题,缓存数据加上过期时间,每隔一段时间触发读的主动更新即可
  • 如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
  • 通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心脏数据,允许临时脏数据可忽略)

总结:

  • 我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保证每天拿到当前最新数据即可。
  • 我们不应该过度设计,增加系统的复杂性,遇到实时性、一致性要求高的数据,就应该查数据库。

压力测试

Jmeter在一定的访问压力下,看程序运行是否稳定/服务器运行是否稳定

流程:指定http请求,指定线程数,监控

主要指标:吞吐量,90%响应时间,错误率,99%响应时间

JVisualVM

主要指标:堆内存使用量,线程数,加载的类,cpu占用率,visualGC

Jvm调优:

针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,通常把最大、最小设置为相同的值;

年轻代和年老代将根据默认的比例(1:2)分配堆内存,如果应用存在大量的临时对象,应

该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。(尽可能减少FGC的次数,在不影响FGC的条件下增大年轻代)

Jvm内存模型

 

 

对象的创建数组空间的分配在堆里,元数据区操作物理内存,编辑代码缓存在jit里

  1. 如果新对象在eden区放不下则会发生youngGC,将eden区未使用的对象回收,使用的对象放在survivor区
  2. 如果还放不下则发生fullGC 全面清除未使用的对象。如果survivor对象存活超过阈值,则进入old区
  3. FullGC速度慢,应避免频繁的FGC

网站公告

今日签到

点亮在社区的每一天
去签到