谷粒商城面试重点

  • 主要是订单服务和购物车服务 秒杀服务  (计网  os 和 谷粒秒杀)

静态资源放到nginx中,实现动静分离 

谷粒商城面试重点_第1张图片

前端使用thymeleaf开发 引入gav,静态资源放到resource下的templates文件夹下边

在application.yml中导入关闭thymeleaf的缓存

spring:

thymeleaf:

cache:false

  • 查询一级分类(首页内容加载首页就需要加载这些数据)

@GetMapping("/")

public String getIndex(Model model){

List catagories = categoryService.getLevel1Catagories();

return "index";

}

catagoryService 接口

List getLevel1Catagories();

catagoryServiceImpl

@service

List getLevel1Catagories(){

List list  = this.baseMapper.selectList(new QueryWrapper().eq(“parent_cid”,0));  //查询父id为0的数据集合

return list;

}

三级分类查询,开始是先使用this.baseMapper.selectList(new QueryWrapper().eq("parent_id",0或者1或者2));  先查1级分类 根据一级查二级  这样查询的次数太多了

思路:查询表的数据,先查出一级分类,然后stream流遍历查询二级分类(根据stream.collect(toMap(k->k.getCategoryId, v->{

根据k查询二级分类 设置到vo中 重点就是 

设置一个vo承载传到前端的数据 最后记得collect.toList();

})))

一级分类的和二级分类的组合关系

Map>   1个一级分类的分类id  对应1个category2Vo的集合

category2Vo属性又有三级分类的类  

Vo属性只需要保存必要信息 父子关系 通过stream流组合到一起 不需要父子id等信息

只需要保存三级分类的结合因为需要返回前端展示

可以考虑将这些分类数据一次性的load到内存中,在内存操作,不用频繁的查DB

@ResponseBody

@GetMapping("/index/json")

public Map> getCatelogJson(){

String catalogJSON = redsiTemplate.opsForValue().get("catalogJSON");

if(StringUtils.isEmpty(catalogJSON)){

Map>  map = getfromDB();

String json = Json.toJSONString(catalogJSON);  //这里注意要转换为String 

//Json.toJSONString(); 

redisTemplate.opsForValue().set("catalogJSON",json);

return catalogJSON;

}

如果查出来了的话,需要从json转换回来

Map> = 

JSON.parseObject(catalogJSON,new TypeReference(Map>{}));

//先查出所有数据,查询条件为null 其他的要查询 this.baseMapper(new QueryWrapper().eq("parent_id",0));

  List list = this.baseMapper.selectList(null);  //一表多用的三级分类表

       List level1= getParent_cid(list,0);  //根据filter流完成分类查询

        //遍历各个1级分类和对应的他的二级分类的集合List

//如果是单字段 直接k-k.getCategoryId()  但是v->{ 需要return需要返回的数据}

Map> catalogJson =   //根据一级分类List集合来stream遍历

level1.stream().collect(Collectors.toMap(k->k.getCategoryId().toString()),

v->{

List level2 = getParent_cid(list,v.getCategoryId()); //二级分类然后放入到vo返回前端的

//防止查出null 使用stream流进行设置vo字段需要判空

   List catelog2Vos =

    level2.stream().map(k->{

        Category2Vo category2Vo = new Category2Vo(k.getCategoryId.ToString(),null,k.getcategoryId(),k.getName());

//根据遍历设置vo的值 返回前端

List level3    = getParent_id(list,k.getcategoryId()); //获得当前分类的三级分类

level3.stream().map(k->{

Category2Vo.Category3Vo catelog3Vo = new Category2Vo.Category3Vo(k.getgetCategoryId.toString(),l3.getCategoryId.ToString(),l3.getName;);

}).collect(Collectors.toList());

category2Vo.setcategory3Vo(catelog3Vo);}

return catelog2Vo;

}).collect(Collectors.toList());

});

}

//根据父亲id查找子类的集合 

List getParent_cid(List list, int Parentlevel){

List list =

 list.stream().filter(item->item.getParentId()==parentlevel).collect(Collectors.toList());

//使用stream流进行过滤 stream().filter(item->item.getParentId()==0);}


搭建域名访问环境

server块的配置     (配置匹配路径)

谷粒商城面试重点_第2张图片

listen 监听虚拟机的端口号

server_name 请求头的hosts信息 (http1.1才有这个hosts  1.0没有)(网页的请求头信息)匹配才能使用这个跳转 ,如果路径携带 /

proxy_pass  代理到http://gulimall网关的路径位置   这个代理到了自己的电脑ip /static/  代理到自己的虚拟机的具体位置

 

conf.d 配置反向代理的路径  以及匹配路径

nginx.conf 配置上游服务器的地址 (upstream)

谷粒商城面试重点_第3张图片

 这里就可以转发到网关了,网关配置路由规则,网关这时候要根据请求的host地址进行转发

- id: gulimall_host_route
  uri: lb://gulimall-product
  #负载均衡lb 到这个服务
  predicates:
    - Host=gulimall.com,item.gulimall.com  //根据域名host进行转发断言 转发到具体的模块

网页发送请求携带host:gulimall.com这个到nginx ,代理到网关的时候,会丢失请求头的host信息

然后去转发到网关丢失了数据不能断言

谷粒商城面试重点_第4张图片

设置 proxy_set_header Host  $host ;路由到网关携带host头,来让网关进行断言配置


  • 缓存使用

谷粒商城面试重点_第5张图片

缓存两种:本地缓存 (本地缓存缓存不共享,存在缓存一致性问题)分布式缓存(reids)

 整合redis,需要redis的序列化的配置文件 (序列化机制)转换为String等等

       
            org.springframework.boot
            spring-boot-starter-data-redis
       

spring:

        reids:

                host:192.168.124.130

                        port:6379

综上:整合redis

1.添加spring-boot-starter-data-redis

2.配置host等信息

3.配置序列化配置文件

改造三级分类业务,修改上边的三级分类(先查redis缓存,没有去查数据库)

后边这种使用了@Cacheable直接解决 查不到缓存直接使用查询缓存(读模式下查)


高并发情况下缓存击穿 缓存雪崩 缓存穿透 

注意查出的是JSON字符串,查出来后还需要转换为对象类型(序列化和反序列化);

//

String s = redisTemplate.opsForValue().get("key");

JSON.parseObject(catalogJson, new TypeReference>>()

缓存穿透:查询一个不存在的数据,缓存不命中,将来查db,也没有将这个查询的null写入缓存,

导致不存在的数据每次都要去db查,

解决:null结果缓存起来,并且加入短暂的过期时间

缓存雪崩:多个键设置了相同的过期时间,导致缓存同时失效,

解决:加上一个随机值

缓存穿透:某个热点key 失效的瞬间,大量的请求请求到了db

解决:加锁

单体应用下加锁,一般设置dcl(双检锁),先去redis查询没有的话去db查询,

这时候设置一个syn锁,然后再加一个查询redis确定一下,防止syn锁中重复查询db的情况

肯那个别的线程同时了sync这个点

这里我们使用了双端检锁机制来控制线程的并发访问数据库。一个线程进入到临界区之前,进行缓存中是否有数据,进入到临界区后,再次判断缓存中是否有数据,这样做的目的是避免阻塞在临界区的多个线程,在其他线程释放锁后,重复进行数据库的查询和放缓存操作。
if (instance == null) {
			synchronized (SingleInstance.class) {
				if (instance == null) {
					instance = new SingleInstance();
				}
			}
		}
		return instance;

//读模式下
//缓存穿透  空的结果也要缓存 :有缓存空数据的功能

//缓存雪崩  同一时间都过期 加上随机时间
//缓存击穿  枷锁
//缓存 redis
//json转换为对应的对象  传出去序列化为json json.tojsonstring  反序列化需要逆转


分布式情况下,分布式锁出现了

分布式锁所得是所有分布式的查询db的线程数量,并且存放到redis中

redis:可以实现分布式锁 set  key value nx  ex+时间  保证了站位+过期时间的原子性

setnx 不设置过期时间的话,不保证原子性的话可能没有设置上过期时间key就不能自动删除了

并且这个value设置为uuid 为了避免删除别人的key,只能删除自己的key

redisTemplate.opsForValue().setIfAbsent("lock",uuid,TimeUnit.SECONDS);

判断+删除        使用redis+Lua脚本(比较uuid的值,如果是自己的uuid那么就删除)

查完数据后 删除锁,删除时候的判断和删除必须保持原子性,因为防止验证成功延迟了会删除了别人的锁

自己设置自旋锁,一直去查询自旋

Integer lock1 = redisTemplate.execute(new DefaultRedisScript(script, Integer.class), Arrays.asList("lock"), uuid);

1.需要保证        站位+过期时间的原子性  判断+删除的原子性  还有锁的自动续期

public Map> getWithRedisLock(){

String uuid = UUID.random.toString();B

boolean lock =  redisTemplate.opsForValue().setIfAbsent("lock",uuid,TimeUnit.SECONDS);

set key value nx ex+time

if(lock){

getFromDb(); //锁的是查询数据库的内容

//获取值对比+对比成功删除=原子操作 Lua脚本解锁

 String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
                    "    return redis.call(\"del\",KEYS[1])\n" +
                    "else\n" +
                    "    return 0\n" +
                    "end";
            //删除锁 //获取值对比+对比成功删除=原子操作 Lua脚本解锁
            Integer lock1 = redisTemplate.execute(new DefaultRedisScript(script, Integer

你可能感兴趣的:(面试,java,职场和发展)