“微服务应该是无状态的”

这句话几乎成了现在的共识。

不可否认,无状态的微服务系统在处理并发请求的时候拥有很好的理论优势。因为没有了状态,不再会发生对共享状态的竞争,而且可以方便地水平扩展。

然而,这个世界充满了状态。无状态的微服务系统只是一个理想。当单体系统拆分为一组微服务系统的时候,曾经随处可见的状态怎么会凭空消失?当我们看到岁月静好的时候,应该提醒自己一定是有人在负重前行。那些负重前行的状态管理者都外置成了第三方服务。是MySQL,HBase,Kafka们为无状态的微服务扛下了状态的压力。

所以,我们反问一句,无状态的微服务真的能够无限水平扩展吗?答案显而易见,是no。因为MySQL们自身的处理能力是有限的。

微服务外置状态的尴尬

为了改善MySQL的压力,微服务们往往需要集成缓存系统,成为了有状态系统。

需要注意的是,由于缓存系统的使用,微服务系统一致性级别从ACID降级成了BASE。而且上线时间开销大大增加,因为微服务启动之后还需要花一段时间来热身。

而且,MySQL主从系统在遇灾时,会启动主备切换。在这个时间,微服务系统是有一段时间无法及时响应的。

更有一些业务逻辑,在多系统实例的情况下,需要并发的竞争来更新状态。在状态外置的情况下,就需要分布式锁来协调。使用网络通信来协调锁状态,性能下降更大。而且当网络通信失败时,如何设计锁的超时机制也是一个需要权衡的问题。

难道微服务要内置状态?

“无状态计算”应该是个值得追求的理想目标。然而,在真实的系统中,计算和状态分离只能是宏观的目标。具体到了细粒度的微服务个体上,总有一些微服务偏重计算,有一些偏重状态管理。

这些偏重状态管理的微服务,就需要向偏重计算的微服务屏蔽第三方系统的不友好。

例如,第三方系统宕机开始主备切换的这个时间,应该对偏重计算的微服务不可见。

例如,把需要并发竞争更新的外部状态,在这里用API封装起来,异步地顺序更新。没有了竞争速度反而更快。又或者,把对分布式锁的使用内化为调用代码级别的锁,性能更好。

为了向偏重计算的微服务系统提供更好的服务,状态管理微服务甚至可以自身持有部分状态,充当第三方系统的“缓存”。所带来的代价无非是水平扩展能力下降。然而只要单点性能足够好,是不需要水平扩展的。此时,只需要为了保证可用性,做好状态的复制和主备快速切换就可以了。

而微服务系统恰恰做好了功能拆分,保证了功能单一,很方便提升单点性能。

持有状态并不是一件可怕的事情,不应该视之如狼。