静态资源放到nginx中,实现动静分离
前端使用thymeleaf开发 引入gav,静态资源放到resource下的templates文件夹下边
在application.yml中导入关闭thymeleaf的缓存
spring:
thymeleaf:
cache:false
@GetMapping("/")
public String getIndex(Model model){
List
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
思路:查询表的数据,先查出一级分类,然后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块的配置 (配置匹配路径)
listen 监听虚拟机的端口号
server_name 请求头的hosts信息 (http1.1才有这个hosts 1.0没有)(网页的请求头信息)匹配才能使用这个跳转 ,如果路径携带 /
proxy_pass 代理到http://gulimall网关的路径位置 这个代理到了自己的电脑ip /static/ 代理到自己的虚拟机的具体位置
conf.d 配置反向代理的路径 以及匹配路径
nginx.conf 配置上游服务器的地址 (upstream)
这里就可以转发到网关了,网关配置路由规则,网关这时候要根据请求的host地址进行转发
- id: gulimall_host_route uri: lb://gulimall-product #负载均衡lb 到这个服务 predicates: - Host=gulimall.com,item.gulimall.com //根据域名host进行转发断言 转发到具体的模块
网页发送请求携带host:gulimall.com这个到nginx ,代理到网关的时候,会丢失请求头的host信息
然后去转发到网关丢失了数据不能断言
设置 proxy_set_header Host $host ;路由到网关携带host头,来让网关进行断言配置
缓存两种:本地缓存 (本地缓存缓存不共享,存在缓存一致性问题)分布式缓存(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
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