随着业务的发展,无数公司开始面向服务开发,转向微服务架构。
说句大实话,今年秋招,算法和 Java
岗人才爆炸,不容易。
忙着找工作,好久没写过博客了。华软新版本基本完成,趁着有时间正好给大家讲一讲传说中的微服务架构。
这里的微服务,并不是单指我们用的 spring-cloud
, spring-cloud
只是微服务的一种最佳实践。对于创业来说,在没有稳定的资金支撑自研中间件的情况下, spring-cloud
是我们最好的选择。
在提出微服务架构之前,我们先分析一下我们以往的系统有哪些问题。
单体架构,就像我们之前写得系统一样,一个 spring-boot
项目实现了所有的业务逻辑,部署的时候 mvn package
, java -jar
就算跑起来了一个后台项目。
缺点很明显,我们也遇到过。这里我列出我的几个观点:
如果这个实例宕机了,因为单体应用的强依赖性,整个系统都变为不可用的状态。
因为系统对 API
的强依赖,一旦 API
实例宕机了,整个系统就完了。
对于计量和学校的业务,要求还没有这么高,但是如果对于银行的项目,就没这么容易接受了。
除了加服务器,我们没有办法有针对性地解决高并发。
假设, Alice
的学生管理项目遇到了高并发导致的性能问题,我们只能这么干。
部署多个 API
节点,通过配置 nginx
分发策略进行负载均衡。
但是,可能有 100
接口,往往只有 5-10
个接口是学生/老师们常用的,像综合查询之类的不存在并发问题。这样部署属实浪费资源。
我们无法针对项目中某些常用的接口在硬件部署级别上实施优化。
这个是我在参考甲方提供的华软另一版本的 Web
项目得出的思考。
项目架构如下所示:
一个前后端不分离的 JavaEE
项目,去调用另一个专门提供华软接口的 .NET
项目。
这是不是看起来很酷,一个项目的后台,采用两种语言,两个服务之间通过 HTTP
调用进行通信。
如果服务之间是通过 @Autowired
在容器中进行依赖,那所有功能都需要使用 Java
实现,无法发挥各种语言的优势。
假设我们要保证验证码微服务的高可用,我们可以使用服务注册与发现:
服务注册中心,存储服务到实例地址的映射。
当系统启动时,右侧的三个验证码微服务会向服务注册中心进行注册,表示自己要提供服务。这个过程称为服务注册。
服务注册中心就存储着类似这样的数据:
VerificationCode: - code1.instrument.yunzhiclub.com - code2.instrument.yunzhiclub.com - code3.instrument.yunzhiclub.com
表示验证码有三个微服务提供,地址分别是 code1.instrument.yunzhiclub.com
、 code2.instrument.yunzhiclub.com
、 code3.instrument.yunzhiclub.com
。
如果前台要使用验证码服务, Gateway
会向服务注册中心询问,有哪个微服务提供验证码的功能,获取到列表,再进行转发。
这个向服务注册中心获取其他服务实例的过程,称为服务发现。
同理,其他微服务想调用该服务,执行的也是与网关相同的服务发现。
大家思考一下,为什么这样设计就能保证高可用呢?
微服务和服务注册中心之间是通过心跳来续约的,如果超过阀值,服务没有向注册中心发送数据,中心就认为这个实例已经死亡,将其从可用列表中剔除。
假设某天,验证码微服务1宕机了,不再发送自己的心跳信息,服务注册中心中的维护的数据就变成了(实例1被剔除):
VerificationCode: - code2.instrument.yunzhiclub.com - code3.instrument.yunzhiclub.com
这样能保证如果有实例宕机,可以让系统以最快的速度恢复。
哪怕用户目前正在请求 code1
实例,本次失败了,在一段时间的心跳过后,数据更新,用户重试,也能保证服务可用。
在系统部署时,服务注册中心往往是三台实例以上的集群。所以大可不必考虑这个问题。
服务注册中心之间会进行数据同步,我觉得这个和路由器之间交换路由表很类似。
上面的诸多技术都是理论,落实到的是 spring-cloud
中的一个个组件。
网关: spring-cloud-netflix-zuul
、 spring-cloud-gateway
。这里推荐使用 spring-cloud-gateway
。
zuul
采用阻塞 API
实现, spring
官方认为其存在性能问题,遂放弃维护 zuul
,启用新项目 spring-cloud-gateway
,采用非阻塞 API
实现。
https://stackoverflow.com/questions/47092048/how-is-spring-cloud-gateway-different-from-zuul
服务注册中心: spring-cloud-netflix-eureka
。
又是 netflix
家的,我看慕课网的实战课用的基本上都是 eureka
。
但是我看新闻好像是由于 netflix
公司的闭源, eureka
项目停止维护,替代方案: spring-cloud-consul
。
知识都学会了,换个组件还不是轻而易举?
微服务带来便利的同时,也为我们带来了服务雪崩问题。
本图片摘抄自博客,感谢原作者的分享: https://segmentfault.com/a/1190000005988895
就像这个图一样,因为服务之间相互依赖,如果 A
服务宕机了,那依赖它的 B
服务也不可用,同理,依赖 B
的 C
、 D
服务都不可用。
一个服务的崩溃,导致了整个系统的瘫痪,这就是服务雪崩。
我们发现雪崩的原因是虽然 B
可用,但是因为 A
的瘫痪,导致了 B
不可用。所以问题出在服务调用上,我们需要在服务调用时进行错误处理。
我们可以使用这种策略:当检测到 A
崩溃时, B
就不去请求 A
(反正请求也是失败),使用本地的降级策略。这个过程称为熔断降级。
降级可以理解为一种兜底策略,差强人意的意思。
比如查询用户数据,用户服务又去调用朋友圈服务的接口。假设朋友圈的接口宕机了,无法获取最新的数据,我们可以使用缓存策略返回上一次缓存的数据。
保证了后台出错,但是给用户的体验也不会很差。
熔断器组件: spring-cloud-hystrix
。
这个是从我刚开始学微服务就一直带着的一个问题。这么多服务,不可能每一个都搭一个 spring-security
吧?
直到上次计量项目中学习了潘老师对用户认证的实现,才解决这个问题。
所有用户认证放在网关,网关对前台的 COOKIE / X-AUTH-TOKEN
进行用户信息的认证,查询出用户后,根据用户信息签发一个 JWT Token
。
微服务之间的认证,使用网关签发的 JWT Token
。
在《重新定义Spring Cloud实战》一书中,也是推荐网关结合 jwt
的认证方式。
微服务架构还需要考虑的一个就是微服务之间的通信成本。
@Autowired private CourseService courseService;
/** * fallback 兜底策略 */ @FeignClient(value = "course", fallback = CourseClientHystrix.class) public interface CourseClient { @GetMapping(value = "{id}") CourseInfo getCourseInfo(@PathVariable Long id); }
看看代码就是成本的区别,过去就是 @Autowired
直接调,现在得用 feign
通过网络调,网络通信肯定比本地慢。
所以服务拆分要合理,如果一个服务要求高可用,并且可以接受通信成本的话,就可以单独拆分微服务。
服务拆分对架构师要求极高,从实现到性能,需要考虑周全。我这种小菜鸡只能写写博客吹吹牛了,现在我还不敢服务拆分呢(就怕拆的不好,把性能拆坏了)。
吹了一篇理论,让大家先了解了解微服务架构,到了自己学的时候也少走些弯路。
看着挺简单的,真正搭 spring-cloud
的时候没有看上去那么简单。照着慕课网搭过一个小的,花了好几天时间。
书到用时方恨少,事非经过不知难。