一.为什么选择 redis
在项目中使用 redis 做为缓存,还没有使用 memcache,考虑因素主要有两点:
1.redis 丰富的数据结构,其 hash,list,set 以及功能丰富的 String 的支持,对于实际项目中的使用有很大的帮忙。(可参考官网 redis.io)
2.redis 单点的性能也非常高效(利用项目中的数据测试优于 memcache).
基于以上考虑,因此选用了 redis 来做为缓存应用。
二.分布式缓存的架构设计
1.架构设计
由于 redis 是单点,项目中需要使用,必须自己实现分布式。基本架构图如下所示:
2.分布式实现
通过 key 做一致性哈希,实现 key 对应 redis 结点的分布。
一致性哈希的实现:
l hash 值计算:通过支持 MD5 与 MurmurHash 两种计算方式,默认是采用 MurmurHash,高效的 hash 计算。
l 一致性的实现:通过 java 的 TreeMap 来模拟环状结构,实现均匀分布
3.client 的选择
对于 jedis 修改的主要是分区模块的修改,使其支持了跟据 BufferKey 进行分区,跟据不同的 redis 结点信息,可以初始化不同的 ShardInfo,同时也修改了 JedisPool 的底层实现,使其连接 pool 池支持跟据 key,value 的构造方法,跟据不同 ShardInfos,创建不同的 jedis 连接客户端,达到分区的效果,供应用层调用
4.模块的说明
l 脏数据处理模块,处理失败执行的缓存操作。
l 屏蔽监控模块,对于 jedis 操作的异常监控,当某结点出现异常可控制 redis 结点的切除等操作。
整个分布式模块通过 hornetq,来切除异常 redis 结点。对于新结点的增加,也可以通过 reload 方法实现增加。(此模块对于新增结点也可以很方便实现)
对于以上分布式架构的实现满足了项目的需求。另外使用中对于一些比较重要用途的缓存数据可以单独设置一些 redis 结点,设定特定的优先级。另外对于缓存接口的设计,也可以跟据需求,实现基本接口与一些特殊逻辑接口。对于 cas 相关操作,以及一些事物操作可以通过其 watch 机制来实现。(参考我以前写的 redis 事物介绍)
以上是基于 redis 分布式架构的介绍!但是应用中读写都是在一起的。相关写是在应用操作后 flush 或者 update 的,有一定的耦合。为了使读写分离,以及缓存模块跟应用的耦合更小,考虑使用 mysql binlog 来刷新缓存。以下是基于 binlog 刷新可性行分析以及实现过程中需要注意的地方。
三.采用 binlog 架构刷新缓存可行性分析
1.Mysql 日志格式介绍可参考我以前的的介绍。
2.对于使用 MIXED 日志格式,此日志格式,记录的是对应数据库操作的 SQL 语句,采用此日志方式存在的问题:
l 对于一些未任何更新操作的 SQl 语句,像条件不满足,对应的 sql 也会记录到 binlog 日志中。
l SQL 语句记录的未必包括所有的更新操作。
l 对于一些分布式数据库,对于 SQL 中的 where 条件指定的是非均衡字段,也许会存在多条 SQL,跟设计有关!
基于以上考虑,采用 MIXED 的日志格式进行 binlog 解析是行不通的。(官网给出的指示是 failed statementsare not logged ,但不包括语法没错误,更新条件不符合对应的 SQL)
3.采用 ROW 日志格式
对于此日志格式,每行变化都有对应的记录,此日志格式,对于解析及采集数据都是非常方便的,也只有采用此日志格式,才能基于 binlog 修改,做刷新缓存相关方案的设计。但是基于此日志格式也存在一些问题:
l 需要考虑项目中是否有大量的批量的 update 操作,如果采用此日志格式,批量操作每一行修改都会记录一条日志,大量的批量操作所产生的日志量,以及所带来的 IO 开销是否可以接受。
通过以上分析,最终项目中还是考虑基于 ROW 日志格式进行缓存刷新,还有一个问题需要考虑,在应用层 DB 进行了相应的 update 操作后,所产生的 Binlog 是会带来一定的延迟,如果 Binlog 处理模块正常运行,数据是的延迟会非常少,MS 级别以内,对用户体验是没有感知的,但是 Binlog 模块是多点,异常,以及相应的延迟肯定会是存在的,这样,缓存数据肯定会存在脏数据。
不过通过以上方案,数据能达到最终一致性,因此 how to 权衡,需要考虑。
通过以上分析,是否采用 Binlog 来做缓存数据刷新相信大家有一个基本概念了
四.基于 binlog 刷新缓存的实现时注意的地方
1.如果是采用 java 做相关开发,可以使用开源的 tungstenAPI
2.Binlog 日志解析是按照 mysql 的 master/slave 同步流程来实现,即一个线程同步,一个线程解析。
3.设计是可分 Binlog 处理模块以及缓存处理 SqlEvent 两部分,其中 Binlog 处理解析好对应的 SqlEvent,然后对应的缓存刷新处理 SqlEvent,一个简单的生产者-消费者模式。
4.对于多个 Binlog 处理模块可以是单点,也可以是通过一些协同工具来管理,看需求。可以使用 ZooKeeper 等。
5.对于分布式缓存中的数据,对于 Binlog 来刷新的缓存数据会存在 load 数据的问题,为了减轻 DB 的额外压力,flush 操作可在 get 缓存数据处完成。看需求,如果读写完全分享的话此 DB 的额外压力可以接收的话也可行。
6.对于缓存数据性一致性要求比较高的,可以通过版本号来控制,即在应用层引入一定的耦合,在 DB 操作时带 mark ,缓存刷新是也 mark,另外 get 操作时比较双版本号来达到数据的一致性。(此跟 5 谈论的一定的联系,读写是否完全分离,以及相应一致性实现的一些方法)
若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏
扫描二维码,分享此文章