图片设计网站有哪些免费注册域名的方法
图片设计网站有哪些,免费注册域名的方法,响应式网站和自适应网站,彩票网站开发合法吗1. 从一次“诡异”的缓存读取失败说起
那天下午#xff0c;我正在排查一个线上问题。用户反馈说#xff0c;登录后部分个性化设置偶尔会丢失。我第一反应是去查缓存#xff0c;因为我们把用户的偏好设置都放在了Redis里。我用我们项目里常用的 RedisTemplate 去读取一个已知…1. 从一次“诡异”的缓存读取失败说起那天下午我正在排查一个线上问题。用户反馈说登录后部分个性化设置偶尔会丢失。我第一反应是去查缓存因为我们把用户的偏好设置都放在了Redis里。我用我们项目里常用的RedisTemplate去读取一个已知的 key控制台返回了一个刺眼的null。我有点懵明明通过 Redis 的桌面客户端比如 Redis Desktop Manager能清清楚楚地看到这个 key 对应的值一串明明白白的 JSON 字符串怎么到代码里就没了呢我不信邪换了个法子用另一个注入的StringRedisTemplate去读同一个 key。结果数据“唰”地一下就出来了。那一刻我意识到问题不在 Redis 服务器也不在网络而在于我手里这两把看似一样的“钥匙”——StringRedisTemplate和RedisTemplate。它们底层用了完全不同的“翻译官”序列化器导致一个能看懂 Redis 里存的东西另一个却像看天书。如果你也在用 Spring Boot 操作 Redis并且项目里同时存在这两种 Template那你很可能正踩在同一个坑的边缘。这篇文章我就想和你彻底掰扯清楚它俩到底有啥不同更重要的是当你的项目不可避免地需要混合使用它们时怎么配置才能让它们“和平共处”不再出现这种数据“看得见摸不着”的灵异事件。简单来说StringRedisTemplate和RedisTemplate都是 Spring Data Redis 提供给我们的、用于和 Redis 交互的高级工具类封装了各种opsForValue、opsForHash等便捷操作。它们的关系就像亲兄弟StringRedisTemplate直接继承了RedisTemplate。但这对兄弟的性格和处事方式截然不同核心差异就体现在序列化Serialization这个环节上。你可以把序列化理解为数据存入 Redis 前的“编码”和取出时的“解码”。编码解码规则不一致数据自然就乱套了。2. 核心差异序列化机制决定了它们的世界观为什么同样的数据用不同的 Template 存取结果会天差地别根源就在于它们默认配置了不同的序列化器Serializer。这就像是StringRedisTemplate只说普通话而RedisTemplate默认只说方言两者无法直接沟通。2.1 StringRedisTemplate专精于字符串的“直球选手”我们先来看看StringRedisTemplate。它的源代码非常干净利落在它的无参构造函数里明确给自己定下了规矩public StringRedisTemplate() { RedisSerializerString stringSerializer new StringRedisSerializer(); this.setKeySerializer(stringSerializer); this.setValueSerializer(stringSerializer); this.setHashKeySerializer(stringSerializer); this.setHashValueSerializer(stringSerializer); }看到没它把 Key、Value、HashKey、HashValue 的序列化器全部设置成了StringRedisSerializer。这个序列化器是干嘛的呢它的工作极其单纯将字符串与字节数组进行直接转换。对于 Java 中的String对象它使用 UTF-8 编码将其转换成字节数组存入 Redis取出来时再用 UTF-8 解码回String。这种机制带来的好处非常直观人类可读你在 Redis 客户端如redis-cli或 Redis Desktop Manager里看到的数据就是原汁原味的字符串。比如你存入name: 张三看到的就是张三。跨语言友好因为存储的就是标准字符串或 JSON 字符串其他语言如 Python、Go的程序也能轻松读取和解析非常适合做简单的缓存或消息队列。性能开销小字符串转换是开销最小的操作之一。所以StringRedisTemplate就是一个纯粹的字符串操作模板。它只认字符串也只生产字符串。如果你的业务场景中Redis 纯粹用来存储文本、JSON 字符串、Token 等那么StringRedisTemplate是你的不二之选简单直接没有意外。2.2 RedisTemplate功能强大的“默认配置者”相比之下RedisTemplate就显得“博爱”且“复杂”得多。它的泛型是Object, Object设计初衷是为了能够存储任意类型的 Java 对象。为了实现这个目标它的默认序列化配置走了另一条路。默认情况下RedisTemplate使用的序列化器是JdkSerializationRedisSerializer。这个序列化器依赖 Java 原生的序列化机制即实现Serializable接口。它的工作流程是这样的存入时将你的 Java 对象哪怕是一个简单的String通过 Java 对象流ObjectOutputStream转换为一串特殊的字节数组。这串字节不仅包含对象的数据还包含类的描述信息等元数据。存储时这串字节数组被直接写入 Redis。读取时从 Redis 拿到字节数组再通过 Java 对象流ObjectInputStream反序列化回原来的 Java 对象。这个机制带来的现象就是Redis 中数据不可读你用客户端连接 Redis看到的 key 和 value 都是一堆乱码似的十六进制或乱码字符。比如你存入一个 key 为user:1001实际存储的 key 可能是\xac\xed\x00\x05t\x00\auser:1001。前面那一串\xac\xed\x00\x05t就是 JDK 序列化添加的魔术头。类型安全你可以直接存取复杂的对象如User、ListOrder取出后直接强制转换即可使用无需手动拼接 JSON。强 Java 绑定数据严重依赖于特定的 Java 类定义。如果类结构发生变化如增删字段反序列化可能会失败。其他语言程序基本无法解析这种数据。2.3 一个实验看清差异让我们用代码直观感受一下。假设我们在一个 Spring Boot 测试中同时注入这两个 TemplateSpringBootTest class RedisTemplateDiffTest { Autowired private RedisTemplateString, Object redisTemplate; // 默认JDK序列化 Autowired private StringRedisTemplate stringRedisTemplate; // 默认字符串序列化 Test void testDiff() { String key test:key; String value Hello Redis; // 场景1分别用自己存自己取都能成功 stringRedisTemplate.opsForValue().set(key, value); String fromStringTemplate stringRedisTemplate.opsForValue().get(key); System.out.println(StringTemplate 存取: fromStringTemplate); // Hello Redis redisTemplate.opsForValue().set(key, value); Object fromObjectTemplate redisTemplate.opsForValue().get(key); System.out.println(ObjectTemplate 存取: fromObjectTemplate); // Hello Redis // 场景2交叉存取会失败 stringRedisTemplate.delete(key); // 清空 redisTemplate.opsForValue().set(key, value); // 用RedisTemplate存 String crossGet stringRedisTemplate.opsForValue().get(key); // 用StringRedisTemplate取 System.out.println(交叉获取结果: crossGet); // null !!! } }运行这个测试前两个输出都是正常的Hello Redis。但第三个输出也就是交叉获取时你会得到null。这就是序列化不匹配的经典表现StringRedisTemplate试图用 UTF-8 去解码RedisTemplate用 JDK 序列化生成的那串“乱码”字节当然解不出来只能返回null。反过来也一样用StringRedisTemplate存的数据RedisTemplate会试图用 JDK 反序列化去解析一串普通的 UTF-8 字节大概率会抛出java.io.StreamCorruptedException异常。3. 为什么需要混合使用现实项目的复杂性看到这里你可能会想“那我统一用一个不就好了干嘛自找麻烦” 理想很丰满但现实项目往往更复杂混合使用场景其实非常普遍。场景一历史遗留与渐进式重构很多项目不是从零开始的。早期可能为了快速上线大量使用了StringRedisTemplate存储简单的字符串缓存比如短信验证码、页面 HTML 片段等。随着业务发展新的模块需要缓存复杂的用户会话对象包含多个字段和嵌套对象开发同学可能为了省事或者不知道区别直接用了RedisTemplate来存。于是一个 Redis 实例里就同时存在了两种编码格式的数据。场景二第三方库或框架的强制要求这是最典型、也最容易踩坑的情况。比如 Spring Session 模块它默认就使用RedisTemplate并配置了JdkSerializationRedisSerializer来存储 Session 数据。你的 Session 数据在 Redis 里就是一堆乱码。与此同时你的业务代码可能大量使用StringRedisTemplate来做业务缓存。当你的业务逻辑需要去读取 Session 中的某个属性比如当前用户ID时如果你直接用StringRedisTemplate去读 Spring Session 生成的 key肯定是读不到的。场景三团队协作与规范不统一在中大型团队中不同小组或不同时期的开发人员可能遵循了不同的“习惯”。有的小组觉得字符串简单明了全用StringRedisTemplate有的小组觉得RedisTemplate存对象方便不用手动转 JSON。如果没有在项目初期确立严格的规范并统一配置久而久之Redis 就成了数据格式的“大杂烩”。所以混合使用很多时候不是主动选择而是被动接受的状态。我们的目标不是杜绝混合使用在大型老项目中这很难而是让它们在混合使用时能够正确、一致地读写数据避免出现数据丢失或读取失败的线上故障。4. 实现和谐共处统一序列化配置的最佳实践要让StringRedisTemplate和RedisTemplate能互相读懂对方存的数据关键在于给它们配置相同的序列化规则也就是使用同一种“语言”。通常我们会选择将RedisTemplate的序列化方式向StringRedisTemplate看齐因为字符串格式更通用、更可读。4.1 方案一自定义配置类推荐这是最彻底、最规范的做法。通过一个Configuration类显式地定义我们需要的RedisTemplateBean覆盖掉 Spring Boot 的默认配置。Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(connectionFactory); // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值 StringRedisSerializer stringSerializer new StringRedisSerializer(); template.setKeySerializer(stringSerializer); template.setHashKeySerializer(stringSerializer); // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值 // 这是比单纯String更优的选择可以存储对象 Jackson2JsonRedisSerializerObject jsonSerializer new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); // 解决反序列化时Jackson无法识别对象类型的问题尤其是LocalDateTime om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); // 可选支持Java8时间类型 om.registerModule(new JavaTimeModule()); // 可选忽略未知属性 om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); jsonSerializer.setObjectMapper(om); template.setValueSerializer(jsonSerializer); template.setHashValueSerializer(jsonSerializer); template.afterPropertiesSet(); return template; } // StringRedisTemplate 保持默认无需额外配置 // Spring Boot 会自动为我们提供一个 StringRedisTemplate Bean }这个配置做了几件关键事Key 统一为字符串将RedisTemplate的 Key 和 HashKey 序列化器设置为StringRedisSerializer。这保证了 Redis 中所有的 key 都是可读的字符串格式StringRedisTemplate可以直接识别和操作。Value 使用 JSON将 Value 和 HashValue 序列化器设置为Jackson2JsonRedisSerializer。这比默认的 JDK 序列化优秀得多可读性强在 Redis 中存储的是 JSON 字符串人类可读其他语言也可解析。存储对象可以直接存储复杂的 Java 对象Jackson 会帮我们序列化成 JSON。避免类绑定不像 JDK 序列化那样强依赖特定的类字节码。只要 JSON 结构兼容反序列化到不同的类或字段略有不同也更有弹性。配置 ObjectMapper通过精细配置ObjectMapper我们解决了 Jackson 处理多态类型、Java 8 时间类型等常见问题让序列化反序列化更健壮。配置完成后项目中注入的RedisTemplateString, Object就会使用这个 Bean。它和StringRedisTemplate在 Key 的编码上达成一致在 Value 上StringRedisTemplate虽然不能直接反序列化 JSON 对象它拿到的是 JSON 字符串但至少能读到字符串内容不会返回null。而业务代码中我们都使用这个自定义的RedisTemplate来存取对象完美兼容。4.2 方案二运行时动态修改适用于已有实例如果你的项目已经运行无法轻易修改全局配置或者某个特定的RedisTemplate实例需要临时与其他组件兼容你可以在使用前动态修改它的序列化器。Component public class SomeService { Autowired private RedisTemplateString, Object redisTemplate; // 默认可能是JDK序列化 PostConstruct // 在Bean初始化后执行 public void initTemplate() { // 统一修改为字符串序列化简单值场景 redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(RedisSerializer.string()); // 注意修改后这个template就只能存字符串了存对象会出错。 } // 或者更安全的方法是创建一个新的Template副本 public RedisTemplateString, Object createCompatibleTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 使用Jackson template.afterPropertiesSet(); return template; } }注意RedisSerializer.string()是 Spring Data Redis 2.2 之后提供的便捷方法返回一个StringRedisSerializer。GenericJackson2JsonRedisSerializer是另一个开箱即用的 JSON 序列化器比手动配置Jackson2JsonRedisSerializer更方便但定制化能力稍弱。这种方法更灵活但风险也更高因为它改变了 Bean 的全局状态可能会影响其他依赖此 Bean 的代码。通常只作为临时解决方案或特定场景下的补救措施。4.3 关键配置项与常见陷阱在配置过程中有几个细节需要特别注意否则可能前功尽弃afterPropertiesSet()必须调用在手动创建或配置RedisTemplate后务必调用template.afterPropertiesSet()方法。这个方法会初始化模板确保序列化器等设置生效。忘记调用会导致配置无效。序列化器作用域setKeySerializer影响所有普通 key如SET key value中的 key。setValueSerializer影响所有普通 value。setHashKeySerializer影响 Hash 结构中的 field。setHashValueSerializer影响 Hash 结构中的 value。 如果你使用了 Hash 操作opsForHash务必同时配置 Hash 相关的序列化器否则 Hash 操作仍可能使用默认的 JDK 序列化。与 Spring Cache 集成如果你使用了Cacheable等注解Spring Cache 默认会使用你定义的RedisTemplateBean名为redisTemplate。确保你的自定义配置 Bean 的名字就是redisTemplate这样缓存注解才会使用你统一的序列化策略。序列化空值默认的JdkSerializationRedisSerializer可以序列化null。但StringRedisSerializer遇到nullvalue 会直接存成空字符串吗不一定这取决于客户端驱动。而Jackson2JsonRedisSerializer需要配置ObjectMapper来定义null的序列化行为如序列化为null字符串。如果缓存可能存null需要测试并明确其行为。5. 实战解决Spring Session与业务缓存混用难题让我们回到最开始的场景也是混合使用中最经典的冲突Spring Session 的 Redis 存储与业务缓存。Spring Session 默认配置下会使用一个 key 序列化器为StringRedisSerializer但 value 序列化器为JdkSerializationRedisSerializer的RedisTemplate。这导致 Session 数据在 Redis 中 key 可读但 value 是一堆乱码。假设你的业务代码需要从 Session 中读取当前用户ID存储在 Session 属性sessionAttr:userId中。Session 的完整 Redis key 可能类似于spring:session:sessions:expires:abc123。错误做法// 直接用StringRedisTemplate去读Hash结构的field肯定读不到因为value是JDK序列化的乱码 String userId (String) stringRedisTemplate.opsForHash().get(spring:session:sessions:abc123, sessionAttr:userId); // userId 为 null正确做法 你需要一个能理解 Session 序列化方式的RedisTemplate来读取。最好的办法是统一序列化。按照4.1节的方案配置一个使用GenericJackson2JsonRedisSerializer的RedisTemplateBean。同时修改 Spring Session 的配置让它也使用这个序列化器或兼容的序列化器。在application.yml中spring: session: store-type: redis redis: namespace: spring:session # 如果你的自定义RedisTemplate Bean名字就是redisTemplateSpring Session默认会用它或者你也可以通过配置类为 Spring Session 单独指定一个RedisSerializerBean public RedisSerializerObject springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(); // 与业务Template保持一致 }配置完成后无论是 Spring Session 存储的数据还是你的业务代码用自定义RedisTemplate或StringRedisTemplate存储的数据Key 都是字符串Value 都是 JSON 格式。虽然StringRedisTemplate读业务对象时拿到的是 JSON 字符串需要手动解析但至少不会丢数据而你的业务RedisTemplate可以无缝读写所有数据彻底解决了混用带来的不一致问题。踩过几次坑之后我的经验是在项目启动初期就通过配置类明确统一 Redis 的序列化方案。优先选择Key 用StringRedisSerializerValue 用Jackson2JsonRedisSerializer的组合。这几乎能覆盖 90% 以上的应用场景在数据可读性、跨语言能力、存储复杂对象的需求之间取得了最佳平衡。从此StringRedisTemplate和RedisTemplate不再是制造混乱的“麻烦兄弟”而是可以根据场景灵活选用的、和谐共处的得力工具。