概述
Mcrouter的开源地址GitHub,一个基于Memcached协议的路由器,适用于大规模集群,峰值每秒可以处理接近50亿个请求。
主要特性:
- 支持标准的Memcached ASCII协议,使得支持Memcached协议的所有客户端无需做任何修改即可支持Mcrouter。
- 多个客户端可以通过连接Mcrouter共享后端Memcached的连接池。
- 多种一致性Hash算法可供选择,允许给多个Memcached实例分配哈希值。
- 灵活的路由,支持前缀路由。
- 复制模式连接池,写操作复制到连接池中所有实例,读操作从其中一个实例读取。
- 支持流量复制,从生产环境复制流量,对新上的集群进行测试。
- 在线更新配置。
- 支持后端的Memcached健康检测和失败后的自动保护。
- 对cold集群热身。
- 在后端连接池/集群广播接收到的操作。
- 可靠删除。在后端某个实例删除失败时,Mcrouter将操作记录在redolog中,后台线程间隔性地重新执行删除操作。
- 多集群支持,拥有丰富的监控和调试命令。
- QoS支持。Mcrouter支持根据主机、连接池、集群进行流量控制,可以对任何操作进行流控,如get/set/delete。还可以对超限流的请求进行拒绝,也可以对流量整形。
- 支持超大对象,Mcrouter可以对不能放入Memcached Slab中的超大对象自动切分/重组。
- 支持本地缓存,实现多级高速缓存。
- IPv6和SSL的支持。
- 提供丰富的stats和debug命令,以及安全可靠的删除操作。
- 通过一个内核一个线程的方式充分利用多核系统的优势,在异步处理网络事件时,使用内部的轻量级线程,即纤程。
架构
任何要接入Memcached服务的客户端都会使用标准ASCII编码的Memcached协议。对于客户端来说,Mcrouter就像一个Memcached服务器;而对于服务器端来说,Memcached却又像一个普通的Memcached客户端。采用Memcached的通用API作为通信方式如下图所示:
路由算法
Mcrouter提供的路由算法:
- AllAsyncRoute:向children属性指定的所有Memcached集群发送相同的请求,不等待集群响应,返回由NullRoute定义的返回值。
- AllFastestRoute:向children属性指定的所有Memcached集群发送相同的请求,把第一个响应的集群返回内容返回给客户端。其他的Memcached集群响应被忽略。
- AllInitialRoute:向children属性指定的所有Memcached集群发送相同的请求,等待children属性指定的第一个Memcached集群返回,并将返回内容返回给客户端。其他的Memcached集群响应被忽略。
- AllMajorityRoute:向children属性指定的所有Memcached集群发送相同的请求,并等待半数以上Memcached集群成功返回,如果没有半数以上成功,则返回最后一个错误响应。
- AllSyncRoute:向children属性指定的所有Memcached集群发送相同的请求,并等所有Memcached集群返回,给客户端返回最坏的一个响应。
- DevNullRoute:和NullRoute类似,会同时返回stat报告。
- ErrorRoute:立即返回一个指定的错误信息。
- FailoverRoute:将请求转发给children属性指定的Memcached集群列表中的第一个集群,然后等待返回,如果成功,则将响应发送给客户端。如果失败,则轮询第二个集群,直到有一个成功。如果所有集群都失败,则返回给客户端最后接收到的失败消息,key miss不被当作错误,如果希望key miss做为一种错误处理,可以使用MissFailoverRoute。
- FailoverWithExptimeRoute:带失效时间的FailoverRoute,在某个集群被标记为失败后,隔一段时间重新路由到该集群,如果发生错误,重新进行failover。
- HashRoute:基于key的Hash路由。
- HostIdRoute:根据客户端主机ID计算hash,进行路由。
- LatestRoute:随机连接,如果返回错误则再次随机连接,最大随机次数为failover_count。
- MigrateRoute:迁移模式路由。
- MissFailoverRoute:路由同Failover模式,key miss认为后端集群出错。
- ModifyExptimeRoute:经过这个路由的请求,TTL将被重新赋值。
- NullRoute:为每个请求都返回一个空的响应。
- PrefixSelectorRoute:通过key的前缀进行路由。
- PoolRoute:路由到一个Memcached集群,类似HashRoute,但通常还有限流等功能。
- RandomRoute:随机路由到children属性指定的Memcached集群列表中的某一个集群。
- WarmUpRoute:set和delete命令发送到cold集群。get命令首先尝试从cold集群获取,如果key miss,则从warm集群获取,如果从warm集群取到,则返回客户端,并异步地更新cold集群。
适用场景
适用场景包括:
- 分片池:Sharded pools,就是常说的数据水平切分。Mcrouter可以将请求按照缓存key的哈希值发送到不同的Memcached服务器上。这样缓存数据将被均匀地分布在不同的Memcached服务器上,同时对同一个缓存的操作也将按照缓存key的哈希值发送到同一台Memcached服务器上。Mcrouter对一个服务器资源池默认使用分片池的方式管理。配置示例:
{
"pools": {
"A": {
"servers": [
"127.0.0.1:12345",
"127.0.0.1:12346"
]
}
},
"route": "PoolRoute|A"
}
配置解读:定义一个含有两个后端服务的连接池,路由规则是,将Key按照一致性Hash算法,分布到两个后端服务上。
- 复制池:Replicated pools,Mcrouter支持随机发送一个get请求到复制池中。如果请求在复制池中的第一个服务上失败,将从其他服务上获取数据。同时发送send和delete到复制池将被池内的所有服务主机接受。基于这种能力,可以设计出多个Memcached实例同步写入,多个Memcached实例同时提供读能力。配置示例:
{
"pools": {
"A": {
"servers": [
"127.0.0.1:12345",
"127.0.0.1:12346"
]
}
},
"route": {
"type": "OperationSelectorRoute",
"operation_policies": {
"add": "AllSyncRoute|Pool|A",
"delete": "AllSyncRoute|Pool|A",
"get": "LatestRoute|Pool|A",
"set": "AllSyncRoute|Pool|A"
}
}
}
配置解读:add、delete和set被发送到池A中的所有实例执行,get被随机发送到池A中的一个实例执行。如果get请求失败,自动尝试另一个实例,默认尝试5次。
- 前缀路由:Prefix Routing,Mcrouter可根据key前缀把客户端分配到不同的Memcahed池。配置举例:
{
"pools": {
"workload1": {"servers": [/* list of cache hosts for workload1 */]},
"workload2": {"servers": [/* list of cache hosts for workload2 */]},
"common_cache": {"servers": [/* list of cache hosts for common use */]}
},
"route": {
"type": "PrefixSelectorRoute",
"policies": {
"a": "PoolRoute|workload1",
"b": "PoolRoute|workload2"
},
"wildcard": "PoolRoute|common_cache"
}
}
配置解读:把以a
为前缀的所有key分配到workload1
池,把以b
为前缀的所有key分配到workload2
池,其他的key都分配到wildcard
池。JSON文件不支持注释,上面配置文件里的注释仅作示范用。
- 缓存预热:Cold cache warm up,每当有新的Memcached服务器加入到缓存服务集群中时,它是没有任何数据的,称这类缓存实例为
cold cache
。为了不对性能产生影响,Mcrouter提供方法来为cold cache
预热,用已有的warm cache
填充cold cache
。配置实例:
{
"pools": {
"cold": {"servers": [/* cold hosts */]},
"warm": {"servers": [/* warm hosts */]}
},
"route": {
"type": "WarmUpRoute",
"cold": "PoolRoute|cold",
"warm": "PoolRoute|warm"
}
}
配置解读:所有的set和delete的请求发送到cold cache
路由处理时。数据都将先从warm cache
路由处理(其中请求可能导致缓存命中)获取。如果warm cache
返回命中,将响应转发给客户端,同时异步请求更新cold cache
的路由处理。
- 两级缓存:Two level caching:当一个单一Memcached服务池出现超载情况时,可考虑使用二级高速缓存架构。例如,除了一个大容量的共享缓存,可以给客户端额外增加一个附加Memcached实例。数据获取逻辑为:
- 先从本地的Memcached实例读取;
- 如果未命中,再从共享池中读取;
- 如果数据存在于共享池,将其同步到本地的Memcached实例中。
配置如下:
{
"pools": {
"shared": {"servers": [/* shared memcached hosts */ ]},
"local": {"servers": [/* local memcached instance, e.g."localhost:<port>" */]}
},
"route": {
"type": "OperationSelectorRoute",
"operation_policies": {
"get": {
"type": "WarmUpRoute",
"cold": "PoolRoute|local",
"warm": "PoolRoute|shared",
"exptime": 10
}
},
"default_policy": {
"type": "AllSyncRoute",
"children": [
"PoolRoute|shared", {
"type": "ModifyExptimeRoute",
"target": "PoolRoute|local",
"exptime": 10
}
]
}
}
}
配置解读:设置一个时间间隔定时同步数据,假定系统可以在一定时间内容忍旧数据的读取。所有的set和delete都将发送到shared
共享池和local
本地池中。第一次从local
池读取数据;shared
池会监听是否命中。如果shared
池命中,则返回数据给客户端并且异步请求更新local
池中的值。客户端不会阻塞更新,所有数据在local
池中只存活10秒。
可扩展性
可扩展性与Twemproxy类似,Mcrouter集群内部也可以是多个相同配置的对等节点,可通过在LVS上增加节点的方式完成扩容。
缓存服务或缓存集群服务在容量上产生瓶颈时,都可以通过更改Mcrouter的配置,增加缓存服务器的数量。Mcrouter在扩容方面相对于Twemproxy有更完善的支持,可通过warm up方式对新加入的节点进行预热。
Mcrouter层中的Mcrouter各个实例是对等实例,通过LVS做高可用,节点宕掉时,LVS将流量摘除。Mcrouter通过以下能力确保在后端Memcached发生故障时的可靠性:
- 心跳检测和自动故障转移:Mcrouter能够通过心跳握手来检测每个Memcached实例的状态。一旦Mcrouter将一个Memcached实例标记为无响应,它会直接将所有的请求转移到另一个可用的Memcached实例上。同时,后台将向无响应Memcached发送心跳请求,只要Memcached的心跳恢复正常,Mcrouter将会重新启用这个Memcached实例。“软错误”(比如:数据超时)允许连续发生多次,但是一旦发生“硬错误”(比如:拒绝连接),Mcrouter将立即该Memcached实例标记为无响应。
- 多集群互备:通过在请求key里面增加自定义的前缀,可以把请求备份到多个Memcached池或者集群。
- 可靠的删除操作:Mcrouter尽量保证所有的删除操作都被执行。Mcrouter将所有的删除操作都记录到硬盘上,防止由于网络中断或者其他原因导致的Memcached不可用。当连接恢复之后,Mcrouter将启动一个单独的进程异步地重新执行这些删除操作。
- 保障服务质量:Mcrouter允许以主机、池或者集群为单位设置任何请求的速率阈值,当请求个数超过阈值时,剩下的请求将会被拒绝服务。
监控
Mcrouter提供丰富的stats和debug命令。通过stats
命令可导出很多内部计数器。Mcrouter还提供自我调试命令,能够反应在运行时一个特定的请求被分配到哪一个主机。默认在/var/Mcrouter/stats
路径下记录运行状态,每10秒更新一次。
stats命令返回的数据样式:
{
"libMcrouter.Mcrouter.5000.config_last_success": 1410744409,
"libMcrouter.Mcrouter.5000.uptime": 10,
"libMcrouter.Mcrouter.5000.result_busy_shadow": 0,
"libMcrouter.Mcrouter.5000.result_busy_failover": 0,
"libMcrouter.Mcrouter.5000.cmd_get_out_failover": 0,
"libMcrouter.Mcrouter.5000.cmd_meta": 0,
"libMcrouter.Mcrouter.5000.result_busy_failover_count": 0,
"libMcrouter.Mcrouter.5000.cmd_delete_out": 0,
"libMcrouter.Mcrouter.5000.result_error": 0
}
源码解析
Mcrouter大量采用C++11的语法,并引入fiber进行流程异步化组装。依赖于Facebook两个开源组件:Folly和fbthrift。在启动时,会启动多个相互独立的线程,使用libevent的线程来处理请求,源码参考EventBase::runInEventBaseThread
。处理请求路由到后端进行编排时,Mcrouter将任务包装成context,使用轻量级线程进行异步化处理,线程为folly::fibers::fiber
。
Mcrouter的管控能力和支持的集群模式都非常丰富,对生产环境中的各种场景、多机房复制等需求有着全面的支持,被Facebook、AWS、Reddit等大型互联网公司实践过,可以稳定可靠地运行。但Mcrouter在国内使用较少,主要原因是匮乏中文文档,Mcrouter本身实现较为复杂,掌握起来需要一定的时间。
参考
- 深入分布式缓存:从原理到实践