微服务介绍
要说微服务,那肯定就要说系统架构得演进过程。之前有篇文章是介绍淘宝得架构演进过程得,虽然说是淘宝的,但它几乎说明了普遍的架构变化历程。地址: 淘宝从几百到千万级并发的十四次架构演进之路!
这里,我就放一张简单的图:
All-in-one就是单体应用,所有的功能模块都写到一起,大家一起维护代码。我们公司最早就是这么干的,那时是2016年(恩,起步真是晚了一些,但那时的系统完全满足当时的业务量)。
垂直应用就不用多说了,就是MVC结构。
等过了一年多,到了2018年,我们已经感觉到随时可能出现的隐患了。所以单位大牛们就开始着手准备把一些服务迁出来。首先迁移出来的是订单服务,别的是陆续地在做。但最后还是出事故了,一次营销组负责的部分出现了panic,导致整个商城挂了。这就是单体应用的弊端。
后来有一些服务开始认真做迁移工作了,陆续地就是商品服务,优惠服务等。刚开始服务不多,主要使用thrift或者直接HTTP服务调用。后来服务多了,公司就自研了一套API网关。然而在这个阶段,依然会存在各个服务的业务复用,且没有服务的治理,降级等等管理服务费管理措施。
又过了一段时间,鉴于公司的现状,领导做了一个决定,微服务使用Java开发或重构,微服务管理使用Dubbo。因此,公司真正的SOA系统应运而生了。
一、微服务定义
微服务最早由Martin Fowler与James Lewis于2014年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信(HTTP或者RPC),这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
微服务不仅仅是服务本身,还需要系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
微服务的目的是有效的拆分应用,实现敏捷开发和部署 。
微服务提倡的理念团队间应该是 inter-operate, not integrate 。inter-operate是定义好系统的边界和接口,在一个团队内全栈,让团队自治,原因就是因为如果团队按照这样的方式组建,将沟通的成本维持在系统内部,每个子系统就会更加内聚,彼此的依赖耦合能变弱,跨系统的沟通成本也就能降低。
微服务架构核心:
从大类可以分为服务通信,服务发布,服务发现,服务治理,监控。
微服务可能要涉及到的知识点:
服务开发 Springboot、Golang等等
服务配置与管理 Netflix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper,Nacos等
服务通信 Rest、RPC
服务降级、熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Feign等
消息队列 Kafka、RabbitMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Spectator等
全链路追踪 Zipkin,Brave、Dapper等
服务部署 Docker、OpenStack、Kubernetes等
数据流操作开发包 SpringCloud Stream
事件消息总线 Spring Cloud Bus
服务限流
目前比较成熟的框架有:
- Dubbo 阿里开源的,已成为apache孵化项目。很吊
- gRPC 谷歌出品的一个比较好的RPC框架
- Spring Cloud
- 美团的 OCTO-RPC
- 小米Apus 基于 Facebook 开源项目 Swift 开发的 RPC 框架
目前采用比较多的应该是1和2。
服务通信:
服务通信本质上看是不同机器上的两个进程进行通信,底层可以采用HTTP或者Socket通信。调用方式可以是同步和异步,目前成熟的微服务框架一般都支持同步和异步。
对于微服务间的通信,不必关心底层通信(TCP,HTTP),只需要定义好上层的RPC协议即可。
目前常用的RPC序列化协议包括JSON,XML,Thrift,Hessian,ProtBuf,RMI(JAVA)等等。
现在Dubbo支持的协议比较多,可以说现在常用的它几乎都支持了。其中Thrift,Hessian都是在原生的基础上进行二次开发的。而grpc只支持Protocol Buffer。这个方面的确Dubbo做得更好。
目前Dubbo不支持跨语言,只适用于Java。不过后续有人基于此开发出了go版本的,还有其他语言的。但我总觉得这东西还是没有原生的好。单单就这方面来看,grpc要必dubbo好多了,grpc支持多种语言。thrift也支持多种语言,但它严格来说不算框架,不具备一个完整微服务架构应该有的功能。
关于协议介绍,可以看一下Dubbo的,我觉得写得比较详细: 协议参考
服务注册和发现
在之前的Rest服务,一般都是通过DNS+LVS+Nginx,实现服务发现。但是RPC服务更加复杂,涉及到的问题更多,因此在微服务中应运而生了注册中心。
目前微服务架构中,服务的注册和发现都要通过注册中心,服务间通过注册中心相互发现,实现解耦,且通过注册中心实现动态扩容,缩容。
服务提供者将服务注册到注册中心,注册中心负责暴露服务地址,并不断地进行心跳检测;服务消费者从注册中心拉取调用的服务。
目前较成熟的注册中心有很多,ZK,nacos,eureka,consul,etcd等等。我们公司用的是Nacos。
其实每种注册中心都有其自身的特点,就看在实际应用中我们侧重哪一方面了。
比如你特别在乎可用性,就不要选ZK。因为ZK无法保证高可用性。在一些极端情况下,它会丢掉一些请求,且在选举leader的过程中,集群也是不可用的。
此外也可以考虑包括健康监测,负载均衡,可扩展性等等方面。
这里直接放一张网上比较火的图:
下面是Nacos的控制台实例图:
消费和提供者都注册到注册中心后,在Nacos控制台可以看到对应的元信息:
看上面的配置信息,包括ip,端口,权重,健康状态以及元数据等。元数据包括方法名,dubbo版本号,接口等等。
服务治理
服务治理涉及到的内容比较多,在上面的图中也有提到。
1、负载均衡;
负载均衡的概念自不必说。在微服务中,少数的服务提供者可能会对应很多的消费者,因此更需要实现负载均衡。负载均衡主要分为两大类,一是服务端负载均衡,比如Nginx;其次是客户端负载均衡,比如Robbin。在微服务调用中一般都是采用的客户端负载均衡,即让不同的客户端选择不同的均衡策略,从而达到最好的效果。
一般的负载均衡都是基于权重、服务提供者负载、响应时间、标签等策略来实现的。
目前的注册中心,比如Nacos,可以配置服务提供者的机器权重。可以基于权重等实现负载均衡。
Robbin是一种比较著名的客户端负载均衡策略。看看Robbin自带的几种策略:
Ribbon 自带的负载均衡策略有如下几个(转自:链接:https://juejin.im/post/5d009834f265da1ba77c9ca3):
- RoundRibbonRule:轮询。也是默认的方法
- RandomRule:随机。
- AvailabilityFilteringRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,以及并发连接数超过阈值的服务,剩下的服务,使用轮询策略。
- WeightedResponseTimeRule:根据平均响应时间计算所有服务的权重,响应越快的服务权重越高,越容易被选中。一开始启动时,统计信息不足的情况下,使用轮询。
- RetryRule:先轮询,如果获取失败则在指定时间内重试,重新轮询可用的服务。
- BestAvailableRule:先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务。
- ZoneAvoidanceRule:复合判断 server 所在区域的性能和 server 的可用性选择服务器
在Dubbo中,主要采用的负载均衡策略有以下几种:
- Random LoadBalance 基于权重随机算法
- RoundRobinLoadBalance 基于加权轮询算法
- ConsistentHashLoadBalance 基于一致性Hash算法
- LeastActiveLoadBalance 基于最少活跃调用数算法
他们这几个实现类都继承自AbstractLoadBalance,该类重要的方法是select,就是选择合适的Invoker:
public <T> Invoker<T> select(List<Invoker<T>> invokers, URL url, Invocation invocation) {
if (invokers != null && !invokers.isEmpty()) {
if (invokers.size() == 1) {
return (Invoker)invokers.get(0);
} else {
String providerIp = invocation.getAttachment("_provider_ip_", "");
String providerPort = invocation.getAttachment("_provider_port_", "");
if (!providerIp.equals("") && !providerPort.equals("")) {
Optional<Invoker<T>> optional = invokers.stream().filter((it) -> {
return it.getUrl().getHost().equals(providerIp) && it.getUrl().getPort() == Integer.valueOf(providerPort);
}).findFirst();
if (optional.isPresent()) {
return this.doSelect(Arrays.asList((Invoker)optional.get()), url, invocation);
}
}
String consumerRegion = url.getParameter("consumer.region", "");
if (StringUtils.isNotEmpty(consumerRegion)) {
List<Invoker<T>> invokerList = (List)invokers.stream().filter((it) -> {
return it.getUrl().getParameter("provider.region", "").equals(consumerRegion);
}).collect(Collectors.toList());
if (invokerList.size() > 0) {
return this.doSelect(invokerList, url, invocation);
}
}
return this.doSelect(invokers, url, invocation);
}
} else {
return null;
}
}
上面代码中有一个重要的概念,即Invoker。Invoker在Dubbo中非常重要,它是一个实体域,可以是一个本地实现,也可以是远程调用实现,也可以是集群实现。
看一张表示消费端和服务端的Invoker的示意图:
从图中可以看到Dubbo服务的RPC调用最终都是要通过Invoker。
2、限流;
当高并发的系统瞬间并发量达到一定值时,需要对系统进行限流,从而达到保护系统稳定性的目的。
目前限流的方法有很多,比如计数器算法,滑动窗口法,令牌通算法啊等等。
在Nacos中,它时集合了阿里开源的哨兵Sentinel来实现流量控制的。当然Sentinel不仅仅有流量控制功能,还有熔断降级,系统负载保护等功能。其限流原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
该项目比较强悍,可以应用到多种微服务框架中,具体的可以参考: Sentinel: 分布式系统的流量防卫兵
3、降级熔断;
这个在高并发系统中,比如电商平台,一到双十一等活动,都会做得事情。在0点前,会将所有不重要的服务关掉,比如评论啊,产品推荐、购物车推荐等等不是特别重要的服务。其实降级的目的还是要在一定程度上限流,通过关掉不重要的服务,减小系统的压力。
4、健康检测;
在微服务架构中,若某个服务down掉了,假如不及时摘除该服务,客户端请求可能会达到不可用的服务上,这在实际应用中肯定是不允许的。因此目前很多的注册中心都有一套健康检测的机制,从而能够及时发现不可用服务,及时摘除。
目前比较普遍的机制是TTL机制,就是客户端(指的是注册到注册中心的提供者)必须要在一定的时间内发送心跳,否则就会摘除对应实例,ZK和Eureka都是采用这种机制。
除了上述的客户端检测机制,Nacos还支持服务端检测机制。即客户端暴露一个接口,服务端定期去调用接口,从而实现检测健康状态的目的。
。。。。。
参考资料:
阿里巴巴NACOS(5)- 主流微服务注册中心产品比较 Eureka、Consul、Nacos
微信分享/微信扫码阅读