spring cache 接口层缓存的演进过程

在spring 体系中,使用spring cache并结合redis来进行数据缓存是很常见的做法。不过,针对于具体的业务场景,可能会有不同的处理方法。

像以下的1个业务场景,即有不同的处理方式。

前端访问后端的指定请求路径(GET类请求), 针对特定的条件下(对应cache condition),希望这个结果能够被缓存.同时,支持当资源修改之后,让此缓存失效掉。

此场景的典型方案就是使用spring cache的 @Cacheable 注解 和 @CacheEvict 注解,并结合实际场景进行混合处理。

在这个实际的场景当中,经历了 service层缓存,Controller层缓存,和Filter层缓存三个阶段,最终达到业务的需求,并且在性能上更接近于实际的需要。
本文就里面碰到的一些实际问题以及解决方式进行了简单描述,从复杂的框架层修改到简单的拦截处理,在思考思路上进行一个分享。

前提
本文中使用的序列化为jackson,即使用jackson将对象序列化为 json 字符串,再getBytes为 字节数组.

继续阅读“spring cache 接口层缓存的演进过程”

基于jgroups分布式缓存实现redis服务的fallback处理

现在的系统当中,实现缓存均是采用redis来完成,一个典型的场景即是将用户的会话信息存放于redis当中,以实现分布式缓存以及服务的高可用。当nginx在进行请求分发时,不需要采用ip hash,随机分发请求,后端直接读取中央缓存即可完成相应的业务处理。但这种场景同样存在一个问题,即是缓存的高可用问题。特别是相应的redis服务由外部提供时,这个问题更加明显。初期redis服务为一个单点时,当服务挂掉时,会导致整个服务将不再可用。

解决此问题有2个方案,一个是再使用同样的策略保证redis的高可用,如部署redis集群或者是sentinel模式,另外一种是直接放弃对redis的高依赖性,在应用层直接进行缓存的fallback支持。在本场景中,我们仅将redis作为一个缓存层使用,它的一些其它特性如发布订阅暂不考虑。

在本文中,我们使用了hystrix+caffeine+jgroups的方案,来完成整个redis的fallback化处理,通过hystrix实现对redis调用的断路及fallback处理,使用caffine提供高效本地缓存,使用jgroups完成多系统间的数据复制分发。 主要实现的目的在于,当redis挂掉之后,能够切换到本地分布式缓存,保证系统后续可用(不过之前存放在redis中的数据完全不再可见,会导致一部分的问题,如会话信息会丢失。可考虑仅将会话信息直接复制在本地缓存当中)

本方案适用于redis短时间由于网络或其它原因连接不上,但在相对短的时间内(如几分钟,不超过1个小时等)即会恢复的情况,这样本地缓存不会存储很多东西的情况.

继续阅读“基于jgroups分布式缓存实现redis服务的fallback处理”

使用树莓派部署nginx服务器提供dns负载

之前买了一个小小的树莓派,一直在吃灰. 本来自己也有一个对外的博客域名,就想怎么样将此机器用起来.(本来就长年24小时开机,当简单的闹钟用)
本文章的目的是让树莓派工作起来,并没有一定技术上的特定优势,仅作技术验证以及耗电技术验证.
先说一下我这边的硬件和网络环境
1 linode云主机,cpu 1核,内存 1g,对外公网ip,地址新加坡(备案你懂的), nginx,mysql,php程序,之前是独立wordpress应用
2 树莓派3代,没部任何程序 上海电信家庭宽带 封80端口(后来发现的) 路由器可作内网转发

做负载均衡有很多种做法,根据当前的网络环境,以及硬件设备.对于像wordpress这种博客应用来说,有以下两种

1 后端应用负载
即使用一个前端nginx,后端通过挂一个upstream,将相应的请求代理到2个后端应用中.
这样的好处在于,应用始终访问一个对外地址,由统一的代理程序决定如何访问后端应用,并且也可以根据后端的实际情况设置权重.nginx上设置代理也简单.
不足之处,也有许多,主要是后端应用需要部署多套,并且相应的底层存储数据需要共享或者是复制.
1.1 在树莓派上就需要安装mysql,php这种应用,同时要开始公网数据同步,开销可能有点大.树莓派本身能不能支持mysql这种不算小型的数据库,内存和cpu占用都是未知问题.
1.2 前端代理访问,nginx之前安装在linode主机,当前也不准备更换,如果代理到树莓派,就意味着访问先从国内访问到linode,然后linode再内部访问到树莓派,中间的延时肯定会有问题
1.3 当然也可以将nginx放在树莓派国内电信上,不过鉴于电信宽带本身的稳定性(ip变化,断电,或者临时被封等),肯定比不上固定的云主机,而且树莓派一挂,整个博客即挂掉

2 访问前端负载
更往前一点就可以作dns负载, 通过dns解析域名时产生多个ip,让访问者随机选择1个进行访问.
好像在于,原linode应用不作任何调整,接下来就工作就是让访问树莓派时能够输出相应的内容即可
不足之处,在于当前的dns解析(使用的dnspod)当前还不能作权重,不能设置权重信息. 树莓派不用后端处理,那么就需要加速访问才能达到效果.

最后的方案就是dns负载,树莓派加速网络访问,整个步骤如下

  1. 搭建支持https以及lua脚本支持的nginx
  2. 加速GET请求处理
  3. 反向代理POST请求
  4. 反向代理wp-admin后台管理
  5. dns负载配置,ip动态更新
  6. 树莓派小尾巴

继续阅读“使用树莓派部署nginx服务器提供dns负载”

排查redisson中订阅connection无故消失的问题

最近在项目中使用了redis结合spring cache一起作了一个缓存,并使用了订阅功能来达到进程间的数据同步。但在测试使用过程中,发现第二天一来,本来应该工作的订阅同步并不能进行。当时没在意,简单重启了事。但后来发现,每天早上相应的同步都不能进行,并且经测试。每个进程的同步都不进行,感觉是redis的订阅出问题了。

1. 验证问题

表现出来就是A程序修改了数据,B程序并没有识别到这次更新。那么就先尝试手动在redis中进行相应的set操作,因为订阅的是redis的空间事件,即key space event. 在控制台单独连接redis,执行相应的set 命令,在B程序中并没有任何表示。
怀疑是不是redis的发布订阅出问题了,就另开终端,手动地进行订阅,一切正常。
初步确定是程序出问题了。

2. 查看程序

把进程的stack打印一份,查看里面的线程信息,发现并没有redisson的线程信息。一般来说,没收到消息也可能是线程内阻塞了,但是直接就没有相应的线程,这就有问题了。
怀疑是不是相应的连接被断开之后就没有再连,也可能是redis server被重启了。

重新连接redis,使用status查看相应的状态,显示server端并没有重启过,其运行时间长达X天,即没有间断过。

回过来再看相应的redisson程序,里面有一个watch dog,是负责连接断开重连的。即如果连接被断掉了,它会尝试重连,但每一次的重连都会迟后一定时间,如 1 2 4 8 秒这样。这也是为什么要看stack的原因。简单看了下watchdog的实现,表示并没有明显的问题(或者就没问题)。

进一步怀疑是不是出现了某个异常,导致相应的watchdog重连直接被中断了。比如Error级错误。将相应的日志拉下来一份,因为一晚上都没人操作,日志信息本身也很少。直接在里面使用grep Exception查找日志信息,但一切也很正常,甚至没有异常发生。

再重新查看了redisson的源码,里面使用netty的channelInActive来进行重连尝试。在本地测试了一下这个机制,redis重启或者断开连接,它都能检测到。开始怀疑是不是日志信息不全,被吞掉了。重新调整日志级别,将redisson和netty的级别调到TRACE级别,结果中午过去一会,再回来看。相应的连接又不见了,订阅又不能正常工作。查看了日志,里面也没有任何信息,因为这期间无任何操作。

继续阅读“排查redisson中订阅connection无故消失的问题”

redis中aof备份策略中的配置参数

在使用redis时,都会配置相应的存储策略,以保证redis并不会由于意外挂掉,在短时间内重启时数据不会消失。在当前的版本中,redis提供了bgsave和aof两种策略,本文主要描述了aof中的相关参数以及为什么这样是可以足够安全的。
本文的描述主要参考redis的conf文件以及各项网络

appendonly

开启aof特性,这个控制是否启用aof.

appendfilename

写入文件的文件名。开启aof之后,每条命令(除读之外的命令),均会写入到文件中,这里即实际写入的文件.

appendfsync

写入策略,默认值everysec,每秒写一次(调用flush)。另外两个值,always | no,分别表示每次redis写命令之外就写文件,和由操作系统保证。always对硬盘压力大,everysec是一个平衡值,no对硬盘压力最小,但调度由系统控制,丢失数据风险最大.

继续阅读“redis中aof备份策略中的配置参数”

redisson中连接对象创建及断线重连

在redisson中,由于使用了netty来封装对redis的协议访问,因此对于连接对象的创建和释放,也借由相应的connection来实现。由篇由masterSlave的角度,描述整个redisson中对于client的封装,以及在网络中断情况下,客户端会得到什么样的反馈,如何实现重连的情况。

本篇主要介绍master slave情况下各个对象的关系图信息,以及在具体创建时的一些处理问题.Redisson版本:2.1.1

1 对象关系图

整个关系如上所示,由下图进行描述

  1. 由MasterSlaveServersConfig负责配置连接的各项参数,比如master地址,slave地址,连接数大小等,这个对象是在redision创建时,由Config对象负责创建的
  2. 在调用Redission.create时,创建起相应的connectionManager对象,其持有相应的master连接信息,以及相应slave的连接信息
  3. 相应的connectionManager负责创建卢相应masterEntry以及slaveEntry信息,并且保存相应的映射信息
  4. connectionManager负责当前客户端对于具体服务器端的各项配置以下,比如转码器,连接池,使用的各项协议等,其根据这些信息,使用netty创建起相应的redisClient对象
  5. entry将已经创建好的redisClient交由connectionEntry负责持有,因此这里的client仅表示一个特有的客户端连接信息。在初始化时并不自动创建相应的连接
  6. entry因此持有相应的client,因此也自然根据当前的需要创建出相应的connection对象,这里entry的创建工作交由client负责,同时将创建好的connection管理起来

继续阅读“redisson中连接对象创建及断线重连”