尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

用豆包辅助从头开始学习java spring cloud(七)

用豆包辅助从头开始学习java spring cloud(七) 用豆包辅助从头开始学习java spring cloud七昨天学习了数据库事务以及一些基本概念今天学习redis基本内容。Redis 是开源的内存型 Key‑Value 数据库是内存数据库主要用来做缓存减轻 DB 压力解决高性能、分布式业务场景优势是速度快、数据结构多受内存限制要注意大 key 和数据丢失问题。具体理论大家自行搜索下面进入今天的学习内容Day5 Redis基础实操 前提本地启动Redis 1. SpringBoot项目引入spring‑boot‑starter‑data‑redis 2. yml配置redis连接RedisTemplate简单使用字符串读写。 3. 概念理解缓存穿透、缓存击穿、缓存雪崩不需要手写完整解决方案理解概念每种问题对应的解决思路即可。之前安装数据库的时候安装了docker所以这次直接拉一个redis镜像回来直接使用docker pull redis:7-alpine docker run -d --name redis-local -p 6379:6379 redis:7-alpine第一部分、代码准备1、引入 Maven 依赖pom.xml 添加 redis 启动器之后刷新 Maven。!-- redis --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId/dependency2、application.yml 配置 Redisspring:redis:host:127.0.0.1port:6379password:database:0###database:0使用第 0 号库默认。3、写测试 Controller操作 RedisTemplatepackagecom.single.bootsingledemo.controller;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjavax.annotation.Resource;importjava.util.concurrent.TimeUnit;RestControllerRequestMapping(/redis)publicclassRedisTestController{ResourceprivateRedisTemplateString,ObjectredisTemplate;/* * 设置字符串缓存 * */GetMapping(/set)publicStringsetCache(){//key,value,过期时间单位秒redisTemplate.opsForValue().set(user:name,张三,60,TimeUnit.SECONDS);return写入redis成功;}/* * 获取缓存 * */GetMapping(/get)publicObjectgetCache(){returnredisTemplate.opsForValue().get(user,name);}/* * 删除缓存 * */GetMapping(/del)publicStringdelCache(){redisTemplate.delete(user:name);return删除成功;}}4、测试分别访问http://127.0.0.1:8080/redis/set写入/get查询/del删除,每一步测试时在redis客户端查看key的变化1.写入​ a.写入之前​ b.调接口写入启动项目前注意一下昨天写得代码其中AccountServiceImpl.java中的AutowiredprivateAccountServiceself;testInnerCall()方法 还有inti1/0删除掉或者注释掉AccountService中的testInnerCall()方法也需要去掉AccountController中的innerCall()部分也去掉启动后直接浏览器访问写入接口查看redis客户端可以观察60秒后是不是会失效2.查询3.删除以上就是对redis的增删查。下面开始准备链接数据库。第二部分、给【根据 id 查询用户】接口增加 Redis 缓存业务逻辑根据 id 查询用户优先查 Redis 缓存Redis 有数据 → 直接返回不走 MySQLRedis 没有 → 查询 MySQL 数据库MySQL 查到数据 → 把用户对象存入 Redis设置过期时间再返回给前端MySQL 查不到直接返回 null后面可以处理缓存穿透1修改UserService.javapackagecom.single.bootsingledemo.service;importcom.baomidou.mybatisplus.extension.service.IService;importcom.single.bootsingledemo.entity.User;publicinterfaceUserServiceextendsIServiceUser{UsergetUserById(Longid);}2.修改UserServiceImpl.java改造getById查询方法packagecom.single.bootsingledemo.service.impl;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.single.bootsingledemo.entity.User;importcom.single.bootsingledemo.mapper.UserMapper;importcom.single.bootsingledemo.service.UserService;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.concurrent.TimeUnit;ServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{ResourceprivateRedisTemplateString,ObjectredisTemplate;/* * 根据id查询用户带redis缓存 * */publicUsergetUserById(Longid){//1.组装redis key规范userid值Stringkeyuser:id;//先查redisUsercacheUser(User)redisTemplate.opsForValue().get(key);if(cacheUser!null){//缓存命中直接返回不访问数据库returncacheUser;}//缓存没命中查询mysqlUserdbUserbaseMapper.selectById(id);//数据库查到写入redis设置120秒过期if(dbUser!null){redisTemplate.opsForValue().set(key,dbUser,120,TimeUnit.SECONDS);}returndbUser;}}3.修改 UserController,增加一个接口GetMapping(/cache/{id})publicUsergetCacheUser(PathVariableLongid){returnuserService.getUserById(id);}4.重启项目测试新接口本地地址/user/cache/1第一次访问接口缓存没有执行 MySQL 查询控制台打印 mybatis 的 sql 日志同时把数据存入 Redis刷新浏览器第二次访问直接读取 Redis控制台看不到 SQL 语句缓存命中不走数据库。于是尝试访问后发现不太对也执行了查询但是没有正常返回但是正常查询接口是正常的那么这个问题就是通过redis的时候产生的问题也就是RedisTemplate 默认使用 JDK 序列化要求存入 Redis 的实体类User必须实现 Serializable序列化接口现在 User 实体没有实现保存对象到 Redis 直接序列化失败 500 报错。解决办法有两种方法一在User类上面直接加上实现Serializable就正常了。packagecom.single.bootsingledemo.entity;importcom.baomidou.mybatisplus.annotation.IdType;importcom.baomidou.mybatisplus.annotation.TableId;importcom.baomidou.mybatisplus.annotation.TableName;importlombok.Data;importjava.io.Serializable;DataTableName(user)publicclassUserimplementsSerializable{//增加implements SerializableTableId(typeIdType.AUTO)privateLongid;privateStringname;privateIntegerage;privateStringemail;}重启后正常访问但是有一个隐患可能会有缓存穿透问题这个问题我们随后再说先说第二个方法方法二不改动User类新建配置类com.single.bootsingledemo.config.RedisConfig.javapackagecom.single.bootsingledemo.config;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;importorg.springframework.data.redis.serializer.StringRedisSerializer;ConfigurationpublicclassRedisConfig{BeanpublicRedisTemplateString,ObjectredisTemplate(RedisConnectionFactoryfactory){RedisTemplateString,ObjecttemplatenewRedisTemplate();template.setConnectionFactory(factory);//key使用字符串序列化StringRedisSerializerstringRedisSerializernewStringRedisSerializer();template.setKeySerializer(stringRedisSerializer);template.setHashKeySerializer(stringRedisSerializer);//value 使用jackson json序列化实体类不需要实现SerializableJackson2JsonRedisSerializerObjectjsonRedisSerializernewJackson2JsonRedisSerializer(Object.class);template.setValueSerializer(jsonRedisSerializer);template.setHashValueSerializer(jsonRedisSerializer);template.afterPropertiesSet();returntemplate;}}使用这个配置的好处Redis 里面存的是可读 JSON 字符串User 实体不需要 implements Serializable。直接用这个配置就解决序列化报错企业项目基本都是这种 JSON 序列化方案。建议直接采用这个 RedisConfig 配置类不用修改 User 实体一步解决序列化报错。第一次访问第二次访问额。。。。又遇到问题了现在是第一次请求查数据库把 User 存入 RedisJackson 序列化变成 JSON✅第二次请求从 Redis 读出 JSONJackson 把它解析成 LinkedHashMap不会自动转成 User 实体直接强转 User 就抛类型转换异常。redisTemplate.opsForValue().get(key)返回的是 ObjectJSON 反序列化出来是 Map不是 User 对象不能直接强转 User。现在需要对UserServiceImpl.java进行修改packagecom.single.bootsingledemo.service.impl;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.single.bootsingledemo.entity.User;importcom.single.bootsingledemo.mapper.UserMapper;importcom.single.bootsingledemo.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.concurrent.TimeUnit;ServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{ResourceprivateRedisTemplateString,ObjectredisTemplate;AutowiredprivateObjectMapperobjectMapper;/* * 根据id查询用户带redis缓存 * */publicUsergetUserById(Longid){//1.组装redis key规范userid值Stringkeyuser:id;//先查redisObjectcacheObjredisTemplate.opsForValue().get(key);// User cacheUser (User) redisTemplate.opsForValue().get(key);if(cacheObj!null){//缓存命中直接返回不访问数据库returnobjectMapper.convertValue(cacheObj,User.class);}//缓存没命中查询mysqlUserdbUserbaseMapper.selectById(id);//数据库查到写入redis设置120秒过期if(dbUser!null){redisTemplate.opsForValue().set(key,dbUser,120,TimeUnit.SECONDS);}else{redisTemplate.opsForValue().set(key,null,30,TimeUnit.SECONDS);}returndbUser;}}现在重启项目进行第一次访问不再赘述第二次访问redis有缓存所以第二次没有再次访问数据库。5.缓存穿透这时访问一个不存在的id如/user/cache/8多次查询依然每次都在查询数据库但是redis实际已经缓存了我们再次修改代码packagecom.single.bootsingledemo.service.impl;importcom.baomidou.mybatisplus.extension.service.impl.ServiceImpl;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.single.bootsingledemo.entity.User;importcom.single.bootsingledemo.mapper.UserMapper;importcom.single.bootsingledemo.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importjavax.annotation.Resource;importjava.util.concurrent.TimeUnit;ServicepublicclassUserServiceImplextendsServiceImplUserMapper,UserimplementsUserService{ResourceprivateRedisTemplateString,ObjectredisTemplate;AutowiredprivateObjectMapperobjectMapper;/* * 根据id查询用户带redis缓存 * */publicUsergetUserById(Longid){//1.组装redis key规范userid值Stringkeyuser:id;//先查redisObjectcacheObjredisTemplate.opsForValue().get(key);// User cacheUser (User) redisTemplate.opsForValue().get(key);if(cacheObj!null){//缓存命中直接返回不访问数据库returnobjectMapper.convertValue(cacheObj,User.class);}//缓存没命中查询mysqlUserdbUserbaseMapper.selectById(id);//数据库查到写入redis设置120秒过期if(dbUser!null){redisTemplate.opsForValue().set(key,dbUser,120,TimeUnit.SECONDS);}else{redisTemplate.opsForValue().set(key,,30,TimeUnit.SECONDS);//把null改为了一个空值}returndbUser;}}改完重启第二次访问时报错了空字符串我们加入一个判断OverridepublicUsergetUserById(Longid){Stringkeyuser:id;ObjectcacheObjredisTemplate.opsForValue().get(key);if(cacheObj!null){// 是空字符串标记代表数据库不存在这条数据直接返回null不要去做对象转换if(.equals(cacheObj)){returnnull;}// 正常的用户JSON才做转换returnobjectMapper.convertValue(cacheObj,User.class);}UserdbUserbaseMapper.selectById(id);if(dbUser!null){redisTemplate.opsForValue().set(key,dbUser,120,TimeUnit.SECONDS);}else{//存入空字符串占位30秒过期解决缓存穿透redisTemplate.opsForValue().set(key,,30,TimeUnit.SECONDS);}returndbUser;}修改之后 对id8进行多次访问不在多次查询数据库页面正常返回且redis有缓存。多次访问均为同一返回值没再报其他错误。后台也没再多次查询数据库6.一些知识点缓存穿透查不存在的数据缓存查不到全部打数据库。方案缓存空值、布隆过滤器。缓存击穿热点 key 过期大量并发打同一个 key 到数据库。方案分布式锁、逻辑永不过期。缓存雪崩大量 key 同一时刻过期流量压垮数据库。方案过期时间加随机值、Redis 集群、限流降级。我们在上面演示了缓存穿透但是缓存击穿和雪崩无法演示代码在后期做实战项目时再进行尝试现在停留在理解概念的位置。第三部分小结一、环境使用 Docker 启动 Redis 服务安装 Redis 可视化客户端连接 Redis方便查看 key、TTL、value我这里安装的是Another-Redis-Desktop-Manager-win-1.7.2-x64SpringBoot 单体项目引入spring‑boot‑starter‑data‑redis依赖yml 配置 Redis 连接信息使用RedisTemplate操作 Redis。二、代码实操通过用户查询增加缓存逻辑实际业务为通过id查询用户优先读取 Redis 缓存缓存命中直接返回缓存数据不访问 MySQL缓存未命中查询 MySQL数据库查到数据存入 Redis设置过期时间返回数据数据库查不到数据不能存nullJackson 序列化会直接丢弃 key缓存失效存入空字符串 “” 作为空标记设置较短过期时间解决缓存穿透。踩坑点读到空字符串标记时不能交给 ObjectMapper 做实体转换直接 return null否则抛出类型转换异常。三、三大redis缓存问题1. 缓存穿透现象查询数据库不存在的数据缓存一直不命中请求全部打到数据库。解决缓存空标记本次代码实现、布隆过滤器、参数合法性校验2. 缓存击穿现象单个热点 key 过期瞬间大量并发请求过来缓存失效全部打数据库。解决分布式互斥锁、热点 key 逻辑永不过期、主动提前刷新缓存。3. 缓存雪崩现象大批量 key 同一时刻过期大量流量压垮数据库。解决过期时间增加随机偏移、Redis 集群高可用、服务限流降级。区分记忆穿透数据本身不存在击穿单个热点 key 失效雪崩大批 key 集体失效。四、额外知识点更新业务优先更新数据库删除缓存不直接更新缓存复杂场景使用延时双删降低数据不一致时间窗口。五、明日学习计划Day6 架构理论 RESTful规范2h不需要敲大量代码重点思考和笔记。架构演进单体架构的痛点 → 垂直拆分 → SOA → 微服务微服务优缺点服务拆分原则按业务域、单一职责。RESTful API规范GET查询、POST新增、PUT修改、DELETE删除路径命名规范。思考练习题如果做一个电商系统如何拆分为微服务哪些拆成独立服务写下你的拆分思路。2. 缓存击穿现象单个热点 key 过期瞬间大量并发请求过来缓存失效全部打数据库。解决分布式互斥锁、热点 key 逻辑永不过期、主动提前刷新缓存。3. 缓存雪崩现象大批量 key 同一时刻过期大量流量压垮数据库。解决过期时间增加随机偏移、Redis 集群高可用、服务限流降级。区分记忆穿透数据本身不存在击穿单个热点 key 失效雪崩大批 key 集体失效。四、额外知识点更新业务优先更新数据库删除缓存不直接更新缓存复杂场景使用延时双删降低数据不一致时间窗口。五、明日学习计划Day6 架构理论 RESTful规范2h不需要敲大量代码重点思考和笔记。架构演进单体架构的痛点 → 垂直拆分 → SOA → 微服务微服务优缺点服务拆分原则按业务域、单一职责。RESTful API规范GET查询、POST新增、PUT修改、DELETE删除路径命名规范。思考练习题如果做一个电商系统如何拆分为微服务哪些拆成独立服务写下你的拆分思路。
返回列表