缓存的引入

    xiaoxiao2022-07-12  127

    高性能读,数据的读取,延时性很低

    一、设计数据库

    1.1t_menu

    Id pid Name Icon

    INSERT INTO `t_menu` VALUES (1, 0, '家用电器', NULL); INSERT INTO `t_menu` VALUES (2, 0, '电脑/数码/运营商', NULL); INSERT INTO `t_menu` VALUES (3, 1, '电视', NULL); INSERT INTO `t_menu` VALUES (4, 1, '空调', NULL); INSERT INTO `t_menu` VALUES (5, 1, '洗衣机', NULL); INSERT INTO `t_menu` VALUES (6, 2, '手机通讯', NULL); INSERT INTO `t_menu` VALUES (7, 2, '运营商', NULL); INSERT INTO `t_menu` VALUES (8, 2, '手机配件', NULL); INSERT INTO `t_menu` VALUES (9, 3, '超薄', NULL); INSERT INTO `t_menu` VALUES (10, 3, '高清电视', NULL); INSERT INTO `t_menu` VALUES (11, 3, '智能电视', '');

    1.2生成实体和数据库的操作对象

    1.3导入实体

    给Menu 添加一个字段

    1.4java 接口(user-api)

    /** * 操作菜单的接口 * @author CodeLab * */ public interface MenuService { /** *加载所有的菜单(代表一次性将所有的树加载出来) *Menu 里面的nodes 是否为空? * @return */ List<Menu> loadAllMenu(); /** * 通过父id 查询它的字节点 * @param pid * @return */ List<Menu> loadMenu(Integer pid); }

    1.5实现类

    @Service public class MenuServiceImpl implements MenuService { @Autowired private MenuMapper menuMapper; /* * 一次性加载所有 全部加载 *(non-Javadoc) * @see com.sxt.service.MenuService#loadAllMenu() * 如何加载全部的菜单: * 1 2 层循环加载(缺点:树的深度必须确认下来 优点:速度快) * 全查询 * 将查询的数据自己拼接为一个树 * 2 使用递归(优点:树的深度可以事先不知道缺点:速度慢,使用的内存大,不好理解) * */ @Override public List<Menu> loadAllMenu() { return loadAllMenu(0); } /** * 递归查询树菜单 * @param pid * @return */ public List<Menu> loadAllMenu(Integer pid){ List<Menu> menus = new ArrayList<Menu>(); MenuExample menuExample = new MenuExample(); Criteria createCriteria = menuExample.createCriteria(); createCriteria.andPidEqualTo(pid); menus.addAll(menuMapper.selectByExample(menuExample)); for (Menu menu : menus) { menu.setNodes(loadAllMenu(menu.getId())); } return menus ; } /** * 根据父id 加载子菜单,不全部加载 * select * from t_menu where pid = #{id} */ @Override public List<Menu> loadMenu(Integer pid) { Assert.notNull(pid, "加载子菜单时,父id 不能为null"); // MenuExample 查询对象,使用它可以生成各式各样的sql 语句,以后查询的sql ,不需要我们自己写 MenuExample menuExample = new MenuExample(); // 查询条件,在该对象里面可以添加任意的条件 Criteria createCriteria = menuExample.createCriteria(); createCriteria.andPidEqualTo(pid); List<Menu> menus = menuMapper.selectByExample(menuExample); return menus; } }

    1.6测试

    单个数据:json.cn 整个树: http://www.bejson.com/jsonviewernew/

    1.7结果

    查询速度慢,缓存解决

    二、新的问题

    2.1 使用jvm的空间换时间

    当缓存达到一定程度后,容易引发OOM问题

    2.2 该缓存位于一个jvm 里面

    该缓存不能共享 需要redis 解决

    2.3 缓存的模型对代码的改造太大,不容易扩展

    Spring aop ?

    2.3.1 引入切面的包

    aspectj

    <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>

    不需要classmate 该依赖了

    2.3.2 定义一个切面类

    2.3.3 切点的切入

    大家有会切面表达时的写法 @Around(“execution (* com.sxt.service.impl.MenuServiceImpl.loadAllMenu())”)

    2.3.4 实现环绕通知的逻辑

    @Component @Aspect public class CacheAspect { private static Map<String,Object> caches = new HashMap<>(); private static final String ALL_MENU_LABEL = "alll-menu-data"; @Around("execution (* com.sxt.service.impl.MenuServiceImpl.loadAllMenu())") public Object cache(ProceedingJoinPoint ponit) { if(caches.containsKey(ALL_MENU_LABEL)) { System.out.println("缓存里面有"); return caches.get(ALL_MENU_LABEL); } Object result = null ; try { System.out.println("执行真实方法的调用"); result = ponit.proceed(ponit.getArgs()); // 在此实现了真实方法的调用 // 放入缓存 caches.put(ALL_MENU_LABEL, result); } catch (Throwable e) { e.printStackTrace(); } return result; } }

    2.3.5 开启切面的注解开发

    新增一个配置文件 Spring-aop.xml

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"> <!-- 1 包扫描 --> <context:component-scan base-package="com.sxt.aspect"></context:component-scan> <!-- 2 开启切面的注解开发 --> <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy> </beans>

    2.3.6 改造代码测试

    三、缓存的其他问题

    3.1 缓存的击穿

    Select * from t_user where id = -10; 查询的数据一致无法命中缓存(缓存里面一直没有) 那缓存击穿的本质原因是: 数据库没有这个值,查询数据库都是无用的操作 1 在执行查询之前,如果预先知道数据库里面没有这个值,我可以不查询数据库 2 查询数据库,发现没有该值,现在我在缓存里面缓存一个null 值

    3.2 缓存的雪崩

    当黑客使用大量的数据测试(不止是-1 ,-1–> 无穷)则缓存里面可能缓存很多null,null 也占用空间,过一个缓存慢了,无法存入新的值了,该现象被称为缓存的雪崩。

    3.3 击穿和雪崩的解决方案

    产生的原因是:数据库里面,没有该值产生的 若实现知道该数据库里面没有该值,这个问题就可以解决。要求不能查数据。 HashSet

    3.4 问题

    3.5 解决方案

    什么数据结构不占内存 1 int = 4个字节=48 = 32 bit 1bit 内存占用下来了 图片怎么存储(19801080p) 位图-> 存储图片 借着该图片的存储,我们可以使用BloomFilter

    /** * 基于java bit集合 做bloomfilter ,之后将使用Redis的bitset 来实现 * @author CodeLab * */ public class BloomFilter { /** * 位集合的大小 */ private static final Integer BIT_SIZE = 2<<(30-1) ; /** * 位集合 */ private static BitSet bitSet = new BitSet(BIT_SIZE); /** * hash的个数 */ private static final Integer HASH_SIZE = 5 ; /** * hash 函数的种子 */ private static final Integer []SEEDS = new Integer[] {17,19,23,29,31}; /** * 5 个hash 函数 */ private static HashFun []hashs = new HashFun[HASH_SIZE]; static { for (int i = 0; i < hashs.length; i++) { hashs[i] = new HashFun(SEEDS[i]); } } /** * 将对象放入位集合里面 * * @param object */ public static void putBit(Object object) { for (HashFun hashFun : hashs) { Integer hashCode = hashFun.hash(object); Integer pos = hashCode & (BIT_SIZE - 1); // 将该hashcode 映射在位集合上面 bitSet.set(pos);// 把改点描黑 } } /** * 判断该位集合里面是否存在该值 * @param object * 该对象 * @return * 存在:true * 不存在:false */ public static boolean isExist(Object object) { for (HashFun hashFun : hashs) { Integer hashCode = hashFun.hash(object); Integer pos = hashCode & (BIT_SIZE-1);// 将该hashcode 映射在位集合上面 boolean flag = bitSet.get(pos); if(!flag) { //描黑了 return false; } } return true; // 代表所有的点都符合,则为true } public static void main(String[] args) { String name1 = "MAYUN"; String name2 = "MAHAATENG"; String name3 = "WANGJIAN"; String name4 = "CUIHUA"; putBit(name1); putBit(name2); putBit(name3); putBit(name4); if(isExist(name1)) { System.out.println("ok"); } if(isExist(name2)) { System.out.println("ok"); } if(isExist(name3)) { System.out.println("ok"); } if(isExist(name4)) { System.out.println("ok"); } } }

    3.5.1 往位集合里面存数据

    1 得到该数据的5个hash值 2 使用该hash 值映射在位集合上面 3 将位集合的该点设置为1

    /** * 将对象放入位集合里面 * * @param object */ public static void putBit(Object object) { for (HashFun hashFun : hashs) { // hashcode Integer hashCode = hashFun.hash(object); // 映射到位上 Integer pos = hashCode & (BIT_SIZE - 1); // 将该hashcode 映射在位集合上面 //设置为1 bitSet.set(pos);// 把改点描黑 } }

    3.5.2 在位集合里面怎么判断是否有该数据

    1 算该对象的hash 值 2 使用该hash 映射在位上面 3 看该为上面是否为1 若为1 继续往下判断,直到所有的hash 都 判断通过

    public static boolean isExist(Object object) { for (HashFun hashFun : hashs) { Integer hashCode = hashFun.hash(object); Integer pos = hashCode & (BIT_SIZE-1);// 将该hashcode 映射在位集合上面 boolean flag = bitSet.get(pos); if(!flag) { //描黑了 return false; } } return true; // 代表所有的点都符合,则为true }

    3.5.3 位集合

    只能存储0/1 的集合

    3.5.4 hash 的生成

    参考了String 的源码实现

    private Integer seed = null ; public HashFun(Integer seed) { this.seed = seed ; } public Integer hash(Object object) { // 得到该对象的string char[] value = object.toString().toCharArray(); int h = 0; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = seed * h + val[i]; // hash的种子数31质数 } } return h; }

    四、将Hash函数推广

    每个hash 函数都能提出一个对象的特征 推荐到人脸识别? 人脸(鼻子,耳朵,眼睛,额骨,眉毛) 我们有5 个hash 函数能得到该5 个特征的值 高斯模糊-> 得到一个鼻子的特征 需要5 个高斯模糊,即可得到该5 个特征 人工智能(核过滤)

    如何判断该人脸是德华不是你 5 个特征函数得到5个特征值 你的脸:【1001,1004,101,1007,1006】 德华的脸:【999,782,865,431,107】 求导,稀疏矩阵的求导 机器人:对抗神经网络(淘宝的实现)

    第一次:T-shirt 点击到七匹狼的T-shirt 第二次搜索:T-shirt ->七匹狼的T-shirt - > 纯棉 第三次搜索:七匹狼的纯棉T-shirt

    最新回复(0)