性能优化:跨服务使用分布式缓存的3个思考

《性能优化:使用本地缓存遇到实际问题的思考》里提到我们架构团队最近在和业务团队合作,承接一些业务性能优化的小项目。最近遇到的几个项目分别用到了本地环境和分布式缓存。对于各种类型,我们希望做成设计标杆,以后不管是业务团队同学自己开发还是我们架构团队帮助优化,都有一套标准的设计模版。

本文是使用Redis分布式缓存优化的项目。

要不要打破服务化的限制

当时拿到需求的时候有个纠结点:原来数据查询服务通过RPC调用数据存储服务,因为涉及RPC调用以及查数据库,耗时长。所以希望我们加一层缓存,绝大多数情况下直接从Redis取数据。如下图所示:

b9258b6f488531f114df874bc50042cd.png

通常的设计要做服务化,一个服务对外提供增删改查,而缓存这种优化应该放到服务内部。也就是说数据查询服务查询仍然需要通过API来调用数据存储服务,存储服务做不做缓存,数据查询服务应该是不感知的。如下图所示。

4bfd83dce04c9cb2683230b927841ad4.png

而需求方的原始需求会破环服务的封装性。这个矛盾怎样来解决呢?可以这样来考虑。作为一个服务,内部的数据处理,包括存储、逻辑处理这些是要封装在内部的。但是可以使用策略模式提供灵活的访问API。RPC调用是一种访问方式,redis调用是另外一种访问方式。这样就不算破坏封装行。如下图所示:

ba0ea0c77862702670545fbdf6a447e6.png

数据一致性校验算不算多余?

这个和需求方讨论没有达成一致。这也是为什么我连续三天都发文了。我不想破坏文章内容在实际实施时原本的先后顺序,但这一篇要赶在技术评审之前发出来,作为评审前跟需求方讨论的一个资料。

这个设计Redis和MySQL里数据各存储了一份,既然有异构的存储,架构团队这边认为数据一致性校验是要做的。而需求方认为既然都是消费MQ消息后处理,如果处理的没有问题就不会发生数据不一致的问题。所以只要有个手动运维补偿机制来处理生产故障即可,没有必要定时巡检来做数据一致性校验。

我一直遵从的理念是对于负责的服务或者功能,要做到:可观测、可衡量、可应对。自己开发的功能模块是正确的,怎样衡量呢?

数据一致性检查就是用来衡量正确性的。如果逻辑没有漏洞,数据一致性检查应该每次巡检对比数据都是一致的。一旦出现不一致,就是逻辑上出问题了,都是需要case by case分析并做逻辑的修改或者补充的。

如果逻辑本来就简单,跑了一年都没有检查出任何的数据不一致,这个检查是不是浪费呢?服务和功能都是要演进的,要做变更。变更要做到可灰度、可监控、可应急。数据一致性检查就是监控变更后逻辑正确性的手段。

总结来说:这个数据一致性校验属于业务巡检的一项,是用来发现问题的。发现问题可以通过在设计、开发阶段做严格的设计审查、代码Review来避免一部分。通过逻辑来保证是否属于过度设计?需求方对这个逻辑到底有哪些顾虑呢?

巡检逻辑会不会增加业务的复杂性、对数据库造成额外的压力?

这个巡检逻辑我们打算通过分布式调度任务来做。通过分布式调度平台,可以手动触发执行任务作为上线时初始化数据的手段,同时也是故障处理的应急预案,本来就是要做的,做成巡检只是每天定时执行一次,不会增加业务的复杂性。

对数据库的压力方面,这个巡检的确需要扫描数据库。但是我们会通过控制分页,采用>id,利用索引等手段来优化深度分页,并且会通过观察生产监控挑选低峰期执行,因为这是读数据,不会加互斥锁,表的数据量也不大,预计对数据库的压力可以忽略不计。

总结

我自己在沟通过程中犯了一个很严重的错误。在论证数据一致性巡检有必要做的时候,我说:「业界都是这么做的。」我之前确实是调研过各个做的比较好的大厂,他们对于业务巡检都非常重视。但是我的表达犯了在《批判性思维》这本书中介绍的「笃信权威」的错误。

更正确的处理方式是要从:优势、劣势、必要性、成本等角度来考虑。更要主动询问需求方的顾虑。

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
是的,上述代码中通过使用分布式缓存来实现生成access_token的方法。在这个例子中,使用的是Redis作为分布式缓存。 以下是使用Redis作为分布式缓存的代码示例: ```java @Autowired private RedisTemplate<String, String> redisTemplate; public ApiWxAuthorizeResponse generateAccessToken() { ApiWxAuthorizeResponse apiResponse = new ApiWxAuthorizeResponse(); try { String tokenCacheKey = "xcx_accessToken_" + Const.APP_ID; String redisData = redisTemplate.opsForValue().get(tokenCacheKey); if (StringUtils.isBlank(redisData)) { // 缓存中不存在access_token,需要从微信服务器获取 // 这里省略了获取access_token的具体实现,假设通过wxService的getToken方法获取到了access_token String accessToken = wxService.getToken(globalConfig.getAppId(), globalConfig.getAppSecret()); if (StringUtils.isNotBlank(accessToken)) { // 将access_token存储到redis缓存中 redisTemplate.opsForValue().set(tokenCacheKey, accessToken, expiresIn, TimeUnit.SECONDS); apiResponse.setAccessToken(accessToken); apiResponse.setExpiresIn(String.valueOf(expiresIn)); } else { // 从微信服务器获取access_token失败 // 这里可以根据具体需求进行处理,例如返回错误信息或者抛出异常 } } else { // 缓存中存在access_token,直接使用缓存中的值 apiResponse.setAccessToken(redisData); apiResponse.setExpiresIn(String.valueOf(redisTemplate.getExpire(tokenCacheKey, TimeUnit.SECONDS))); } return apiResponse; } catch (Exception e) { // 异常处理 log.error("异常:生成token接口失败", e); return apiResponse; } } ``` 上述代码中,使用了@Autowired注解注入了RedisTemplate对象,用于操作Redis缓存。在方法中,首先尝试从Redis缓存中获取access_token,如果缓存中不存在,则通过调用wxService的getToken方法从微信服务器获取access_token,并将其存储到Redis缓存中。最后,返回包含access_token和有效期的ApiWxAuthorizeResponse对象。 请注意,上述示例代码中的tokenCacheKey和expiresIn是需要根据实际情况进行替换和设置的变量。另外,为了简化示例,省略了一些具体的实现细节,例如如何获取access_token。实际应用中,可能还需要根据业务需求进行错误处理、日志记录等操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值