用redis存取读写数据是单线程的机制 用某一个key作为锁,并设置过期时间 这样就可以保证同一时期只有一个服务能获取锁 如果获取到锁,就让它执行业务,完成业务删除锁释放 另一个服务就可以获取锁 但是,有一个巨大的问题,删除锁有概率在别的服务业务执行过程中 因为如果业务时间很长,A服务已经完成并释放锁,B服务在执行业务 如何A把锁删掉了,但是B服务还在处理业务 导致CD甚至更多业务获取到了锁 解决方案就是把值设计为uuid,每个人必须匹配自己的锁 才能删除这个锁
用redis存取读写数据是单线程的机制
用某一个key作为锁,并设置过期时间
这样就可以保证同一时期只有一个服务能获取锁
如果获取到锁,就让它执行业务,完成业务删除锁释放
另一个服务就可以获取锁
但是,有一个巨大的问题,删除锁有概率在别的服务业务执行过程中
因为如果业务时间很长,A服务已经完成并释放锁,B服务在执行业务
如何A把锁删掉了,但是B服务还在处理业务
导致CD甚至更多业务获取到了锁
解决方案就是把值设计为uuid,每个人必须匹配自己的锁
才能删除这个锁
但是受限于网络通信原因,有概率出现过期时间到了 但是网络通信,即服务内验证的UUID值还是此前的UUID值,但是锁已经过期了! 下一个服务已经占有了锁,并且在执行业务了 当前业务虽然验证到了锁,并且锁内容也是正确的,但是本质上是幻读 锁内已经不是当前锁了,导致执行完业务流程时将下一个服务的锁给删除了 导致第三个服务又获取到了锁 所有验证锁和删除锁应该是一个原子化的操作,不应该是程序内通过网络通信完成的 应该采用LUA脚本的形式,以原子化的形式执行
但是受限于网络通信原因,有概率出现过期时间到了
但是网络通信,即服务内验证的UUID值还是此前的UUID值,但是锁已经过期了!
下一个服务已经占有了锁,并且在执行业务了
当前业务虽然验证到了锁,并且锁内容也是正确的,但是本质上是幻读
锁内已经不是当前锁了,导致执行完业务流程时将下一个服务的锁给删除了
导致第三个服务又获取到了锁
所有验证锁和删除锁应该是一个原子化的操作,不应该是程序内通过网络通信完成的
应该采用LUA脚本的形式,以原子化的形式执行
package com.alatus.mall.product.service.impl; import com.alatus.mall.product.service.CategoryBrandRelationService; import com.alatus.mall.product.vo.SubCatalogVo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.alatus.common.utils.PageUtils; import com.alatus.common.utils.Query; import com.alatus.mall.product.dao.CategoryDao; import com.alatus.mall.product.entity.CategoryEntity; import com.alatus.mall.product.service.CategoryService; import org.springframework.transaction.annotation.Transactional; @Service("categoryService") public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService { @Autowired private CategoryBrandRelationService categoryBrandRelationService; @Autowired private StringRedisTemplate redisTemplate; @Override public PageUtils queryPage(Map<String, Object> params) { IPage<CategoryEntity> page = this.page( new Query<CategoryEntity>().getPage(params), new QueryWrapper<CategoryEntity>() ); return new PageUtils(page); } @Override public List<CategoryEntity> listWithTree() { // 查出所有分类 List<CategoryEntity> entities = baseMapper.selectList(null); // 组装父子的树形结构 // 找到一级分类 List<CategoryEntity> levelMenuOne = entities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0 ).map((menu) -> { menu.setChildren(getChildren(menu,entities)); return menu; }).sorted((menu1,menu2) -> { return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort()); }).collect(Collectors.toList()); return levelMenuOne; } @Override public void removeMenuByIds(List<Long> list) { // 检查当前删除的菜单,是否被别的地方引用 baseMapper.deleteBatchIds(list); } @Override public Long[] findCatelogPath(Long catelogId) { List<Long> paths = new ArrayList<>(); List<Long> parentPath = findParent(catelogId, paths); Collections.reverse(parentPath); return (Long[])parentPath.toArray(new Long[parentPath.size()]); } // 级联更新所有数据 @Override @Transactional public void updateCascade(CategoryEntity category) { this.updateById(category); categoryBrandRelationService.updateCategory(category.getCatId(),category.getName()); } @Override public List<CategoryEntity> getLevelCategories(Integer catLevel) { return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level", catLevel)); } private Map<String, List<SubCatalogVo>> getCatalogJsonFromDb(){ List<CategoryEntity> categories = baseMapper.selectList(null); // 所有一级分类 List<CategoryEntity> levelCategories = getParentCid(categories,0L);; // 封装数据 Map<String, List<SubCatalogVo>> subCatalogVosMap = levelCategories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(),v -> { List<CategoryEntity> entities = getParentCid(categories,v.getCatId()); List<SubCatalogVo> subCatalogVos = null; if(entities!=null&&!entities.isEmpty()){ subCatalogVos = entities.stream().map(item -> { // 找三级分类 List<CategoryEntity> inCategories = getParentCid(categories,item.getCatId()); SubCatalogVo subCatalogVo = new SubCatalogVo(v.getCatId().toString(),null, item.getCatId().toString(), item.getName()); if(inCategories!=null&&!inCategories.isEmpty()){ // 封装成指定格式 List<SubCatalogVo.InCatalogVo> inCatalogVos = inCategories.stream().map(inCategory -> { SubCatalogVo.InCatalogVo inCatalogVo = new SubCatalogVo.InCatalogVo(item.getCatId().toString(),inCategory.getCatId().toString(),inCategory.getName()); return inCatalogVo; }).collect(Collectors.toList()); subCatalogVo.setCatalog3List(inCatalogVos); } return subCatalogVo; }).collect(Collectors.toList()); } return subCatalogVos; })); // 查到的数据保存到缓存中先 String jsonString = JSON.toJSONString(subCatalogVosMap); // 查到的数据放入缓存,将这个对象我们转为JSON redisTemplate.opsForValue().set("catalogJSON",jsonString,1, TimeUnit.DAYS); return subCatalogVosMap; } // TODO 堆外内存溢出,原因是lettuce使用了netty做网络通信,lettuce的bug导致了我们的堆外内存溢出 @Override public Map<String, List<SubCatalogVo>> getCatalogJson() { String uuid = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS); if(lock){ // 加入缓存机制 // 缓存中我们一律保存JSON字符串,因为JSON本身也是跨语言跨平台的 String catalogJSON = redisTemplate.opsForValue().get("catalogJSON"); if(StringUtils.isEmpty(catalogJSON)){ // 缓存中没有 Map<String, List<SubCatalogVo>> catalogJsonFromDb = this.getCatalogJsonFromDb(); redisTemplate.delete("lock"); return catalogJsonFromDb; } redisTemplate.delete("lock"); return JSON.parseObject(catalogJSON,new TypeReference<Map<String, List<SubCatalogVo>>>(){}); } else{ // TODO 加锁失败,休眠后再次尝试 return getCatalogJson(); } } private List<CategoryEntity> getParentCid(List<CategoryEntity> categories,Long catId) { return categories.stream().filter(item -> item.getParentCid() == catId).collect(Collectors.toList()); } private List<Long> findParent(Long catelogId,List<Long> paths){ // 查出当前分类的ID // 收集当前节点ID paths.add(catelogId); CategoryEntity id = this.getById(catelogId); if(id.getParentCid() != 0){ // 递归查找,每次都从父节点获取 findParent(id.getParentCid(),paths); } return paths; } // 递归查找所有菜单的子菜单 private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){ List<CategoryEntity> children = all.stream().filter(categoryEntity -> { return categoryEntity.getParentCid() == root.getCatId(); }).map(categoryEntity -> { categoryEntity.setChildren(getChildren(categoryEntity,all)); return categoryEntity; }).sorted((menu1,menu2) ->{ return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort()); }).collect(Collectors.toList()); return children; } }
package com.alatus.mall.product.service.impl; import com.alatus.mall.product.service.CategoryBrandRelationService; import com.alatus.mall.product.vo.SubCatalogVo; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.alatus.common.utils.PageUtils; import com.alatus.common.utils.Query; import com.alatus.mall.product.dao.CategoryDao; import com.alatus.mall.product.entity.CategoryEntity; import com.alatus.mall.product.service.CategoryService; import org.springframework.transaction.annotation.Transactional; @Service("categoryService") public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService { @Autowired private CategoryBrandRelationService categoryBrandRelationService; @Autowired private StringRedisTemplate redisTemplate; @Override public PageUtils queryPage(Map<String, Object> params) { IPage<CategoryEntity> page = this.page( new Query<CategoryEntity>().getPage(params), new QueryWrapper<CategoryEntity>() ); return new PageUtils(page); } @Override public List<CategoryEntity> listWithTree() { // 查出所有分类 List<CategoryEntity> entities = baseMapper.selectList(null); // 组装父子的树形结构 // 找到一级分类 List<CategoryEntity> levelMenuOne = entities.stream().filter(categoryEntity -> categoryEntity.getParentCid() == 0 ).map((menu) -> { menu.setChildren(getChildren(menu,entities)); return menu; }).sorted((menu1,menu2) -> { return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort()); }).collect(Collectors.toList()); return levelMenuOne; } @Override public void removeMenuByIds(List<Long> list) { // 检查当前删除的菜单,是否被别的地方引用 baseMapper.deleteBatchIds(list); } @Override public Long[] findCatelogPath(Long catelogId) { List<Long> paths = new ArrayList<>(); List<Long> parentPath = findParent(catelogId, paths); Collections.reverse(parentPath); return (Long[])parentPath.toArray(new Long[parentPath.size()]); } // 级联更新所有数据 @Override @Transactional public void updateCascade(CategoryEntity category) { this.updateById(category); categoryBrandRelationService.updateCategory(category.getCatId(),category.getName()); } @Override public List<CategoryEntity> getLevelCategories(Integer catLevel) { return baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("cat_level", catLevel)); } private Map<String, List<SubCatalogVo>> getCatalogJsonFromDb(){ List<CategoryEntity> categories = baseMapper.selectList(null); // 所有一级分类 List<CategoryEntity> levelCategories = getParentCid(categories,0L);; // 封装数据 Map<String, List<SubCatalogVo>> subCatalogVosMap = levelCategories.stream().collect(Collectors.toMap(k -> k.getCatId().toString(),v -> { List<CategoryEntity> entities = getParentCid(categories,v.getCatId()); List<SubCatalogVo> subCatalogVos = null; if(entities!=null&&!entities.isEmpty()){ subCatalogVos = entities.stream().map(item -> { // 找三级分类 List<CategoryEntity> inCategories = getParentCid(categories,item.getCatId()); SubCatalogVo subCatalogVo = new SubCatalogVo(v.getCatId().toString(),null, item.getCatId().toString(), item.getName()); if(inCategories!=null&&!inCategories.isEmpty()){ // 封装成指定格式 List<SubCatalogVo.InCatalogVo> inCatalogVos = inCategories.stream().map(inCategory -> { SubCatalogVo.InCatalogVo inCatalogVo = new SubCatalogVo.InCatalogVo(item.getCatId().toString(),inCategory.getCatId().toString(),inCategory.getName()); return inCatalogVo; }).collect(Collectors.toList()); subCatalogVo.setCatalog3List(inCatalogVos); } return subCatalogVo; }).collect(Collectors.toList()); } return subCatalogVos; })); // 查到的数据保存到缓存中先 String jsonString = JSON.toJSONString(subCatalogVosMap); // 查到的数据放入缓存,将这个对象我们转为JSON redisTemplate.opsForValue().set("catalogJSON",jsonString,1, TimeUnit.DAYS); return subCatalogVosMap; } // TODO 堆外内存溢出,原因是lettuce使用了netty做网络通信,lettuce的bug导致了我们的堆外内存溢出 @Override public Map<String, List<SubCatalogVo>> getCatalogJson() { String uuid = UUID.randomUUID().toString(); Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS); if(lock){ // 加入缓存机制 // 缓存中我们一律保存JSON字符串,因为JSON本身也是跨语言跨平台的 String catalogJSON = redisTemplate.opsForValue().get("catalogJSON"); if(StringUtils.isEmpty(catalogJSON)){ // 缓存中没有 Map<String, List<SubCatalogVo>> catalogJsonFromDb = this.getCatalogJsonFromDb(); redisTemplate.delete("lock"); return catalogJsonFromDb; } redisTemplate.delete("lock"); return JSON.parseObject(catalogJSON,new TypeReference<Map<String, List<SubCatalogVo>>>(){}); } else{ // TODO 加锁失败,休眠后再次尝试 return getCatalogJson(); } } private List<CategoryEntity> getParentCid(List<CategoryEntity> categories,Long catId) { return categories.stream().filter(item -> item.getParentCid() == catId).collect(Collectors.toList()); } private List<Long> findParent(Long catelogId,List<Long> paths){ // 查出当前分类的ID // 收集当前节点ID paths.add(catelogId); CategoryEntity id = this.getById(catelogId); if(id.getParentCid() != 0){ // 递归查找,每次都从父节点获取 findParent(id.getParentCid(),paths); } return paths; } // 递归查找所有菜单的子菜单 private List<CategoryEntity> getChildren(CategoryEntity root,List<CategoryEntity> all){ List<CategoryEntity> children = all.stream().filter(categoryEntity -> { return categoryEntity.getParentCid() == root.getCatId(); }).map(categoryEntity -> { categoryEntity.setChildren(getChildren(categoryEntity,all)); return categoryEntity; }).sorted((menu1,menu2) ->{ return (menu1.getSort()==null?0:menu1.getSort()) - (menu2.getSort()==null?0:menu2.getSort()); }).collect(Collectors.toList()); return children; } }