Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁

目录

Day5  制作SKU

1. 制作SKU

2. 多表查询如何写? 

3. 制作SKU 

4. Thymeleaf 

Day06 商品详情

1. 获取分类信息

2. 获取最新价格信息

3. 获取销售信息

4. 实现商品切换

5. 获取海报信息

6. Sku对应的平台属性 -- 规格参数

7. 创建远程feign 调用

8. service-item 汇总数据

9. 搭建 web、访问测试

Day07 Redis缓存、redssion分布式锁

1. 项目如何优化? 

2. 讲讲 redis 主从、集群原理 

3. 缓存穿透、缓存击穿、缓存雪崩 ★

4. 本地锁 -- Gateway网关配置

5. redis 分布式锁

6. redisson 解决分布式锁

7. 锁机制


Day5  制作SKU

1. 制作SKU

SPU:一组可复用易检索的标准化信息集合

SKU:每种商品均对应唯一的编号

sku 图片 都应该是基于 SPU 选择的!

sku 应该有属于自己的url 地址!

SKU 相关表结构:
spuInfo:
id,spuName,category3Id,tmId,createTime,updateTime,isDelete
1, 红旗手机, 61  ,       1 

skuInfo:    库存单元表
id,skuName,spuId,price,category3Id,defaultImage,isSale,createTime,updateTime,isDelete
1 ,红旗1  , 1                         0/1
2 ,红旗2  , 1 
skuImage:    库存单元图片表
id,imgName,skuId,imgUrl,createTime,updateTime,isDelete
1,    xx1        1      http://
2,    xx2        1
3,    xx3        1

sku 与 销售属性值Id 有关系么?
skuSaleAttrValue:    sku 与 销售属性值Id 中间表    spuSaleAttrValue
id,skuId,spuSaleAttrId, spuSaleAttrValueId,createTime,updateTime,isDelete
1, 1 ,        1,                1
2, 1 ,        2,                1

sku 与 平台属性值Id 有关系么?    
skuAttrValue:    sku 与 平台属性值Id 中间表    baseAttrValue 
id,skuId,saleAttrId, saleAttrValueId,createTime,updateTime,isDelete
1, 1 ,        1,                1
2, 1 ,        2,                1

商品数量:会单独有一个库存系统
        skuId ,    skuNum
            1,    1000
            2,    10000

2. 多表查询如何写? 

        1. 找表 (字段从哪些表中去获取)

        2. 找关联关系:主外键、名字相同的、名字不同含义相同...

        3. 根据业务添加筛选条件,分组,排序,分页...

        4. sql调优:小表Join大表、使用索引覆盖,避免全盘扫描...

3. 制作SKU 

为什么要回显这个功能? 
在商品检索页面的时候,能够保证用户可以通过平台属性值Id 进行检索出 sku

功能分析:
1. 回显平台属性与平台属性值
        http://localhost/admin/product/attrInfoList/2/13/61

2. 回显销售属性与销售属性值
        http://localhost/admin/product/spuSaleAttrList/12






    
        

        
        
            
        
    

    
    
        ssa.id,
        ssa.spu_id,
        ssa.base_sale_attr_id,
        ssa.sale_attr_name,
        ssav.id sale_attr_value_id,
        ssav.sale_attr_value_name
    

    
    

3. 回显spuImage!
select * from spu_image where spu_id = ? and is_delete = 0;
skuImage 都是从spuImage 中选择的!

http://localhost/admin/product/spuImageList/12
4. 保存skuInfo
    http://localhost/admin/product/saveSkuInfo

(需操作4张表)
测试:
    小米:
        skuId = 25 小米(MI)CC9 屏幕指纹美颜自拍手机 仙女渐变色(美图定制版)6GB+128GB
        skuId = 26 小米(MI)CC9 屏幕指纹美颜自拍手机 仙女渐变色(美图定制版)6GB+64GB
    华为:
        skuId = 27    荣耀(HONOR) 荣耀V30 PRO 双模5G全网通手机 麒麟990处理器 V30 Pro【幻夜星河】 全网通(8GB+256GB)
        skuId = 28    荣耀(HONOR) 荣耀V30 PRO 双模5G全网通手机 麒麟990处理器 V30 Pro【冰岛幻境】 全网通(8GB+256GB)

5. 根据分类Id 查询skuInfo 列表
Request URL: http://localhost/admin/product/list/1/10?category3Id=61
/admin/product/list/{page}/{limit}

select * from skuInfo where category3_id = ? and is_delete = 0 order by id desc limit 0, 10;   mysql;

6. 商品上架- 下架:
http://localhost/admin/product/onSale/28
http://localhost/admin/product/cancelSale/22

7. 修改SKU (多表联查) 课后

    //通过ID回显SkuInfo
    @Override
    public SkuInfo getSkuInfoById(Long skuId) {
        //skuImageList
        List skuImageList = skuImageMapper.selectList(new QueryWrapper().eq("sku_id", skuId));
        //skuAttrValueList (Join 多表联查)
        List skuAttrValueList = skuAttrValueMapper.selectBySkuId(skuId);
        //skuSaleAttrValueList (Join 多表联查)
        List skuSaleAttrValueList = skuSaleAttrValueMapper.selectBySkuId(skuId);
        //skuInfo
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        skuInfo.setSkuSaleAttrValueList(skuSaleAttrValueList);
        skuInfo.setSkuImageList(skuImageList);
        skuInfo.setSkuAttrValueList(skuAttrValueList);
        return skuInfo;
    }
    //修改SKU
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateSkuInfo(SkuInfo skuInfo) {
        skuInfoMapper.updateById(skuInfo);
        Long skuId = skuInfo.getId();
        //先删除,后新增
        skuAttrValueMapper.delete(new QueryWrapper().eq("sku_id",skuId));
        skuImageMapper.delete(new QueryWrapper().eq("sku_id",skuId));
        skuSaleAttrValueMapper.delete(new QueryWrapper().eq("sku_id",skuId));
        //复用新增,插入skuInfo时判断是否存在id
        this.saveSkuInfo(skuInfo);
    }

4. Thymeleaf 

渲染:Thymeleaf ---> 使用!
th:text        显示文本
th:value    给 value 属性赋值 ,在表单提交的时候,都是提交的value 值

              

点击提交的时候,会将value 属性值 传递到后台

@RequestMapping("login")
public void login(String userName,String pwd){
    userName = admin;
    pwd = "123456"
}

th:if        符合判断:
th:unless    不符合判断:
th:each        循环遍历集合
th:include    内嵌页{页面,【css,js,图片】--- 静态资源 springmvc }
th:utext -- 页面解析样式!
th:session    接收session数据
th:href        超链接

扩展:存储数据对象使用!
//    商品详情页面渲染使用!
HashMap hashMap = new HashMap<>();
hashMap.put("id","1002");
hashMap.put("stuName","atguigu");
hashMap.put("stuAge","9");

model.addAllAttributes(hashMap);


配置:
1.    Thymeleaf 有默认的前缀,后缀

2.    Thymeleaf 还可以开启热部署,修改页面之后,不需要重启服务!
spring.thymeleaf.cache=false

1.    Thymeleaf 有默认的前缀,后缀

2.    Thymeleaf 还可以开启热部署,修改页面之后,不需要重启服务!
spring.thymeleaf.cache=false

Day06 商品详情

 功能划分:
1、    分类数据的获取!

2、    skuInfo 的基本信息,skuName,defaultImage,weight

3、    价格单独查询 -- 保证价格实时性!

4、    skuImage 集合

5、    销售属性回显并锁定!

6、    点击不同的销售属性值实现切换功能!

7、    查询海报信息!

8、    规格与包装!
        包含商品的平台属性数据!
        暂时使用商品的平台属性进行渲染!

----------------------------------------------------------------------------------------------                    
9、    商品评价 远程调用 spuId;

10、  手机社区BBS 论坛!

模板划分: 
web-all :页面渲染

service-item : 商品详情微服务

service-product : 商品数据提供微服务

server-gateway :  接收用户所有的请求: http://item.gmall.com/28.html 
            解析域名    item.gmall.com    --->web-all !
            

server-gateway ---> web-all ---> service-item{汇总数据使用map 进行存储!} ---> service-product{查询数据}

从分布式微服务来讲,service-item 汇总数据不能省。

Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁_第1张图片

Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁_第2张图片

1. 获取分类信息

sku是挂在三级分类下面的,我们的分类信息分别在base_category1、base_category2、base_category3这三张表里面,目前需要通过sku表的三级分类id获取一级分类名称、二级分类名称和三级分类名称

解决方案:

我们可以建立一个视图(view),把三张表关联起来,视图id就是三级分类id,这样通过三级分类id就可以查询到相应数据,效果如下

create or replace view base_category_view as
select c3.id id,
       c1.id c1_id,c1.name c1_name,
       c2.id c2_id,c2.name c2_name,
       c3.id c3_id,c3.name c3_name
from base_category1 c1 join  base_category2 c2
on c1.id = c2.category1_id
join base_category3 c3
on c2.id = c3.category2_id;
    public BaseCategoryView getCategoryViewByCategory3Id(Long category3Id) {
        //select * from base_category_view where id = 61
        /*#使用一条语句:view视图:随着原数据的变化而变化 id = category3Id
        # 如果数据频繁更变,开发组中明确规定不能使用视图
        */
        return baseCategoryViewMapper.selectById(category3Id);
    }

2. 获取最新价格信息

    public BigDecimal getSkuPrice(Long skuId) {
        //SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        QueryWrapper wrapper = new QueryWrapper<>();
        wrapper.eq("id",skuId);
        //设置指定查询字段
        wrapper.select("price");
        SkuInfo skuInfo = skuInfoMapper.selectOne(wrapper);
        if(skuInfo!=null){
            return skuInfo.getPrice();
        }
        return new BigDecimal("0");
    }

3. 获取销售信息

查询销售属性+销售属性值;查询skuId 对应的销售属性值Id 是谁;将a,b 结合!

#单独限制某一张表,条件放在on后面
select
    ssa.id,
    ssa.spu_id,
    ssa.base_sale_attr_id,
    ssa.sale_attr_name,
    ssav.id sale_attr_value_id,
    ssav.sale_attr_value_name,
       sv.sku_id,
       if(sv.sku_id is null,0,1) is_checked
from spu_sale_attr ssa join spu_sale_attr_value ssav
    on ssa.spu_id = ssav.spu_id
    and ssa.base_sale_attr_id = ssav.base_sale_attr_id
left join sku_sale_attr_value sv
    on sv.sale_attr_value_id = ssav.id
    and sv.sku_id = ?
where ssa.spu_id = ?
    and ssa.is_deleted = 0
    and ssav.is_deleted = 0
    and (sv.is_deleted = 0 or sv.is_deleted is null)
order by ssav.base_sale_attr_id,ssav.id;
    
    

4. 实现商品切换

用户通过点击不同的销售属性值,来切换到不同的skuId

思路:在后台将所有的销售属性值Id 与 skuId 进行组合
3742|3745 = 28、3743|3745 = 27
{"3742|3745":28,"3743|3745":27} --- Json 对象
必须是同一组:skuId , 过滤条件 必须是同一个商品 spuId

# 3746|3744  3747|3744
# group_concat(要拼接的字段 排序字段 分隔符);不写默认,
# 排序字段复杂一点,可以按照销售id排序,切换的时候要保证与回显数据一致,可能会导致数据拼接或切换顺序错乱
select group_concat(ssav.sale_attr_value_id order by ssav.id desc separator '|')value_ids,
       sku_id
from sku_sale_attr_value ssav
where ssav.spu_id = ?
group by sku_id;
    

    
    
    

5. 获取海报信息

    public List findSpuPosterBySpuId(Long spuId) {
        List spuPosterList = spuPosterMapper.selectList(new QueryWrapper().eq("spu_id", spuId));
        return spuPosterList;
    }

6. Sku对应的平台属性 -- 规格参数

根据 skuId 平台属性数据:---- 规格参数

select bai.id,bai.attr_name,bai.category_id,bai.category_level,
       bav.id attr_value_id,bav.value_name
from base_attr_info bai join base_attr_value bav
on bai.id = bav.attr_id
join sku_attr_value sav
on sav.value_id = bav.id and sav.sku_id = ?
    
    

7. 创建远程feign 调用

Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁_第3张图片

8. service-item 汇总数据

@Service
public class ItemServiceImpl implements ItemService {

    @Qualifier("com.atguigu.gmall.product.client.ProductFeignClient")
    @Autowired
    private ProductFeignClient productFeignClient;

    //根据skuId 获取渲染数据
    @Override
    public Map getItem(Long skuId) {
        HashMap map = new HashMap<>();
        // 获取商品的基本信息 + 商品图片列表
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        //获取分类数据
        BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
        //获取价格
        BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
        //获取销售属性+属性值+锁定
        List spuSaleAttrList = productFeignClient.getSpuSaleAttrListCheckBySku(skuId, skuInfo.getSpuId());
        //获取海报
        List spuPosterList = productFeignClient.getSpuPosterBySpuId(skuInfo.getSpuId());
        //获取数据,转换 json 字符串
        Map skuValueIdsMap = productFeignClient.getSkuValueIdsMap(skuInfo.getSpuId());
        String strJson = JSON.toJSONString(skuValueIdsMap);
        //获取商品规格参数--平台属性
        List attrList = productFeignClient.getAttrList(skuId);
        if(!CollectionUtils.isEmpty(attrList)){
            List> attrMapList = attrList.stream().map(baseAttrInfo -> {
                // 为了迎合页数据存储,定义一个map集合
                HashMap hashMap = new HashMap<>();
                hashMap.put("attrName", baseAttrInfo.getAttrName());
                hashMap.put("attrValue", baseAttrInfo.getAttrValueList().get(0).getValueName());
                return hashMap;
            }).collect(Collectors.toList());

            // 保存规格参数:只需要平台属性名:平台属性值
            map.put("skuAttrList",attrMapList);
        }
        map.put("skuInfo",skuInfo);
        map.put("categoryView",categoryView);
        map.put("price",skuPrice);
        map.put("spuSaleAttrList",spuSaleAttrList);
        map.put("spuPosterList",spuPosterList);
        map.put("valuesSkuJson",strJson);
        return map;
    }

}

9. 搭建 web、访问测试

Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁_第4张图片

访问测试: http://item.gmall.com/22.html

Day137-139.尚品汇:制作SKU、商品详情、项目优化:Redis缓存、redssion分布式锁_第5张图片

Loc锁 synchronized 区别?

线程间如何通信?

Day07 Redis缓存、redssion分布式锁

1. 项目如何优化? 

使用各种中间件

TomCat默认并发500,设置两个参数;服务器带宽

1. 架构优化:单体架构-垂直架构-rpc-soa-分布式微服务;时间成本(学习-代码重构)

2. 增加服务器:各种集群

mysql集群:mycat 垂直分库(按照业务) 水平分表(安装某个字段)

Redis主从复制+集群

3. 减少io:减少用户访问次数 -- redis缓存;

        索引优化:explain: all --> index ---> 【range ---> ref】---> er_ref ---> const --->sysetm

        单表:全值匹配、最佳左前缀、否定会导致索引失效 != <> is not null、范围右边会失效、like

        关联:被驱动表建立索引,不用内连接

        子查询:改成 A left join B where A.id is null;

        分组-排序:无过滤不索引;顺序错,必排序;方向反,必排序

        建议:不要写 * ,尽量使用覆盖索引;减少回表,尽量使用索引下推

4. 减少同步:减少同步操作 -- mq异步解耦

5. redis优化

2. 讲讲 redis 主从、集群原理 

集群原理:

16384个槽 (slot) 与 key进行运算:crc32 算法 获取到一个值,根据这个值决定在那一组节点上存放数据。
redis - 哨兵 主从复制原理:
1. 从机发送复制请求 sync 异步命令
2. 全量复制:主机进行bgsave: 拍个快照把当前所有状态全都发过去
3. 异步增量复制:后续将写入或修改命令 发送给从机!

3. 缓存穿透、缓存击穿、缓存雪崩 ★

缓存穿透:查询一个不存在的数据,由于缓存不存在,直接去查询数据库,但是数据库也无此记录,所以我们没有将null写入缓存。但这导致每次请求都会访问数据库,别人可以利用不存在的 key频繁攻击我们的应用。

解决方案缓存null对象 或 使用布隆过滤器

缓存击穿是指对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:如果这个key在大量请求同时进来之前正好失效,那么所有对这个key的数据查询都落到DB,我们称为缓存击穿。

解决方案redis;redisson 分布式锁   在分布式的环境下,应使用分布式锁来解决,分布式锁的实现方案有多种,比如使用Redis的setnx、使用Zookeeper的临时顺序节点等来实现

缓存雪崩Redis宕机;或在设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。

解决方案

1. 事前:redis高可用,主从+哨兵;事中:本地缓存ehcache+限流组件Hystrix;防止宕机;事后:redis持久化 快速恢复缓存数据

2. 原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

与缓存雪崩的区别:

1. 击穿是一个热点key失效

2. 雪崩是很多key集体失效

4. 本地锁 -- Gateway网关配置

总结:本地锁的局限性 --- 分布式微服务集群部署时,锁不住资源

routes:
- id: service-product
  uri: lb://service-product
  predicates:

    - Path=/*/product/** # 路径匹配
小知识点:port 的优先级,nacos 的优先级高于 运行类配置 高于配置文件! 

Gateway网关负载均衡 默认轮询

改网关访问这三个微服务:
    8206
    8216
    8226
ab  -n 5000 -c 100 http://192.168.200.1/admin/product/test/testLock
    synchronized 锁不住资源!

5. redis 分布式锁

分布式锁实现的解决方案

1. 基于数据库实现分布式锁;IO限制
2. 基于缓存(Redis等) 性能高 基于io

setnx key value; 判断当前这个key是否存在,存在则不生效;

Boolean result = redisTemplate.opsForValue().setIfAbsent("lock","ok")
3. 基于Zookeeper 安全性高

ZNode节点;持久化节点 非持久化节点,将节点看做锁

性能redis最高;可靠性zookeeper最高

Redis 分布式锁,电商方式         

1、setnx key value:容易出现死锁;需要清空锁

2、设置锁并加默认的过期时间:多线程误删锁
        expire key timeout             不具备原子性!
        setex  key timeout value    ---    key 与 过期时间具有原子性!

redis 2.6.12 版本以后:setnx + setex 命令整合
        set key value ex/px timeout nx/xx;    具有原子性
NX :键不存在时,才对键进行设置操作;XX :键已经存在时,才对键进行设置操作。

3、设置一个uuid 防止误删锁:删除锁缺乏原子性

4、使用lua 脚本保证删除锁具有原子性:集群情况下锁不住资源

lock,unlock 可能产生锁死,不需等待自旋机制;trylock可设定过期时间,需要等待自旋机制。

//  这个脚本只在客户端传入的值和键的口令串相匹配时,才对键进行删除
String scriptText = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Long.class);
defaultRedisScript.setScriptText(scriptText);
//  第一个参数:defaultRedisScript 第二个参数:键值 第三个参数:口令串
this.redisTemplate.execute(defaultRedisScript, Arrays.asList("lock"),uuid);

5、redisson:Redisson 提供了使用Redis的最简单和最便捷的方法,Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

6. redisson 解决分布式锁

官方文档地址:Home · redisson/redisson Wiki · GitHub 

单节点:redisson - Lock、TryLock 

集群:redisson - redLock

1、最常用的方式:lock.lock();lock.unlock();

redisson : 有个宠物 --- 作用防止锁死状态!  --- 监控redis, 如果redis 实例宕机了,则会默认设置锁的有效期,默认是30秒钟; 也可以通过 Config.lockWatchdogTimeout 设置。
    
2、在加锁的时候自定义锁的有效期:加锁以后10秒钟自动解锁
lock.lock(10, TimeUnit.SECONDS);

3、尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
   try {
     //    业务逻辑
   } finally {
       lock.unlock();
   }
}else {

   //等待、自旋

}

底层源码:lua脚本,AQS

7. 锁机制

可重入锁:某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。

公平锁:按照申请锁的顺序去获得锁,线程会直接进入队列,先进先出。

连锁:若干锁同时上锁成功才算成功。new RedissonMultiLock(lock...)

红锁:大部分节点上加锁就算成功。new RedissonRedLock(lock...)

读写锁:读读可并发,其他都不可并发

信号量(Semaphore):多线程访问多资源,控制资源量;单资源加锁即可

闭锁(CountDownLatch倒计时线程控制):倒计时线程控制;减少计数为0时执行。

你可能感兴趣的:(尚品汇,servlet,java,数据库)