微服务 & 微服务架构 基本概念概述
微服务 & 微服务架构 单体架构 VS 微服务架构 单体架构
首先看看什么是单体架构 一个工程对应一个归档包(war),这个war包 包含了该工程的所有功 能。我们成为这种应用为单体应用,也就是我们常说的单体架构(一个 war包打天下)。 具体描述: 就是在我们的一个war包种,聚集了各种功能以及资源,比 如JSP JS,CSS等。而业务种包含了我们的用户模块,订单模块,支付模块等 。
单体架构图
单体架构概念和优缺点总结
一,单体架构概念:
将所有的业务场景的表示层,业务逻辑层和数据访问层打包放在一个工程中,最终经过编译打包,部署在一台服务器上。例如:典型的J2EE工程,把jsp,业务逻辑层的service,controller和数据访问层的dao,打包成wa包,部署在Tomcat或者Jetty或者其他容器上运行。
二.缺点:
1.业务越来越复杂,单体架构扩展性不足,业务扩展带来的代价越来越大;
2.用户越来越多,程序承受的并发越爱越高,单体应用的并发能力有限;
3.单体应用的业务都在同一个程序中,增删改业务修改,也会影响其他代码,给测试增加了难度
三.优点:
部署简单 :由于是完整的结构体,可以直接部署在一份服务器上即可
技术单一 :项目不需要复杂的技术栈,往往一套熟悉的技术栈就可以完成开发
用人成本低 :单个程序员可以完成业务接口道数据库的整个流程
四.服务器集群:
服务器集群:就是服务器集群就是指将很多服务器集中起来一起进行同一种服务,在客户端看来就像是只有一个服务器。集群可以利用多个计算机进行并行计算从而获得很高的计算速度,也可以用多个计算机做备份,从而使得任何一个机器坏了整个系统还是能正常运行。
随着业务的扩展,大部分公司会将单体应用进行集群式部署,并增加负载均衡器,还需要增加集群部署的缓存服务器和文件服务器,并将数据库读写分离,以应对用户量的增加带来的高并发访问量。
负载均衡器分发高并发的网络请求,用户的访问被分派到不同的应用服务器。用户增加时,添加应用服务器即可。
五.缓存服务器:缓解数据库的数据以及数据库读取的压力。
负载均衡器----》应用服务器----》缓存服务器,文件服务器,数据库读写分离
六.单体架构使用服务器集群及存在的不足:
1.代码可读性,可维护性差;
2.面对海量用户,数据库会成为瓶颈,需要使用分库分表;
3.持续交付能力差,业务越复杂,代码越多信任熟悉成本高。
微服务以及微服务架构
架构示意图
微服务定义
大概从16年开始,微服务的概念快速升温,基本上人人在谈。微服务来源于对SOA(Services Oriented Architecture)的优化重塑。经过这几年的发展微服务在敏捷开发和复杂的企业级应用开发中的基本上是事实标准。
现在的软件项目不再新开发巨大的单体应用,转而开发众多敏捷轻巧的小应用,共同完成一个大的目标。微服务就是这样一种应用拆分方案的技术落地。
从上图看到,每个微服务都有自己的业务层和数据库层。相互之间完全独立。彼此之间需要采用某种协议进行交互,常见的是HTTP、REST、Dubbo,甚至于使用消息系统交互
原则
1,单一职能原则
这个原则很熟了,在面向对象设计中就经常提到。一个方法,一个类,一个微服务都应该仅负责一件事。
2,围绕业务能力构建
微服务算是一种面向业务的服务架构设计。首先要做的是业务模块的拆解。
微服务的理念是一切为了业务服务。每个微服务要根据自己解决的业务特性而选择合适的技术栈。
这一点上和以前的单体应用有很大不同。在单体应用上,我们通常要考虑各个业务场景的限制,而选择一些能见过不能场景的这种方案。
3,持续运维
一个微服务开发完成后,会由开发团队长期进行后续的运维优化工作。
4,基础设施的自动化
5,应对故障
微服务强调故障应对
微服务强调监控
益处
1,根据业务选择合适的技术栈
2,因为现在的服务都比较小,进行颠覆性改造的成本和难度会小很多
3,服务集群的水平扩展和收缩非常方便
4,服务是自给自足的,所以服务底层平台的切换更容易。究竟是使用公有云,还是自己搭建私有云平台。
5,微服务可以方便的添加新功能,称为一个有机的系统(可以随着时间自己成长)
6,随着技术的发展演进,我们可以将一个微服务升级应用新的技术,而不需要升级整个系统,和前面的有点重复
7,在一个微服务中,可以有多个版本的某一服务
8,可以根据服务的边界,确定团队。在组织划分上便于打造小而专的团队
总结
架构选择的优劣只有在系统使用几年后才能真正显现出来
并不是说以前的单体架构就一无是处,通过真确的业务理解,优秀的设计,专业的开发人员。单体应用一样可以支撑业务
同样,对于微服务架构,一个蹩脚的架构设计,一样会导致低劣的产品出来。要知道,微服务各个组件之间的交互是很复杂的,难以管理和控制。
所以,是一个好的设计+优秀的团队带来优秀的产品。
我建议先从单体架构开始设计一个应用,但单体应用过于复杂,可以尝试拆分微服务。
微服务的适用场景
合适:
①:大型复杂的项目…(来自单体架构200W行代码的恐惧)。
②:快速迭代的项目…(来自一天一版的恐惧)。
③:并发高的项目…(考虑弹性伸缩扩容的恐惧)。
不合适:
①:业务稳定,就是修修bug ,改改数据 。
②:迭代周期长 发版频率 一二个月一次。
实践框架
Spring Cloud Alibaba
什么是什么是SpringCloud?
SpringCloud
我觉着SpringCloud就是一个分布式微服务架构的一站式解决方案,它提供了很多组件用来解决了分布式架构所带来的一些问题。
我在之前工作里用过Eureka[优瑞卡]、Ribbon[瑞本]、Feign[菲恩]、Hystrix[黑丝锤科丝],Zuul[入欧]这么几个组件。其中
Eureka在整个微服务架构中充当注册中心的角色,服务提供者将自身信息注册到Eureka Server[涩沃]中,然后服务消费者就可以从Eureka Server中获取注册的服务提供者的信息,然后就可以向服务提供者发起调用了。
Ribbon实现了客户端的负载均衡,它提供了轮询、轮询权重、随机等一些常用的负载均衡策略。
Feign我理解的就是简化服务之间的调用,让我们调用远程接口就像在调用本地方法一样。
Hystrix的主要功能就是服务熔断、降级和资源隔离,用来保护我们的调用链路,避免发生服务雪崩问题。
Zuul在整个微服务架构中充当服务网关的角色,提供请求转发和过滤的功能,可以在服务网关中实现统一身份验证、统一跨域请求处理等功能。以上就是我对SpringCloud的一些简单理解。
Eureka简介?
我理解的Eureka它在整个微服务架构中充当注册中心的角色,方便咱们管理各种微服务。Eureka又分为Eureka Server(服务端)和 Eureka Client(客户端)。每个微服务中都有一个Eureka Client专门负责将这个服务的信息注册到Eureka Server中。说白了就是告诉Eureka Server自己在哪台机器上监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号。服务之间进行调用的时候,消费者服务中的Eureka Client会把要调用的服务的IP地址、端口号等这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。然后服务之间进行调用的时候,消费者服务中的Eureka Client会把要调用的服务的IP地址、端口号等这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。紧接着就可以发送一个请求过去,调用相应服务的那个接口!
(2)Eureka心跳机制?
接下来我来说一下Eureka的心跳机制吧,就是Eureka Client每隔30秒会向Eureka Server发送一次心跳,就如同人还活着存在的信号一样,如果Eureka Server在90秒后还未收到发来的心跳时,那么它就会认定该服务已经死亡就会注销这个服务。但是这里注销并不是立即注销,而是会在60秒以后对在这个时间段内“死亡”的服务集中注销,如果立即注销,肯定会对Eureka造成极大的负担。另外刚才我说的这些时间参数都可以人为配置。
(3)Eureka自我保护机制?
Eureka还有自我保护机制,如果在15分钟内一直都是超过85%的节点都没有正常的心跳,那么Eureka Server就认为客户端与注册中心出现了网络故障,就会进入自我保护模式。进入自我保护模式之后Eureka Server就不会再接收心跳,也不会删除任何服务。当网络故障恢复后,Eureka Server会自动退出自我保护模式。这就是Eureka的自我保护机制。
(4)具体怎么使用Eureka搭建注册中心?
首先需要在pom文件中引入Eureka Server的依赖,之后在启动类中加入@EnableEurekaServer的注解证明该项目的作用是作为注册中心使用,并且在对应的application.properties中指明Eureka注册中心服务器的地址,方便服务提供者和消费者对其进行访问,为了保证注册中心的高可用,我们当时还搭建了Eureka集群,防止因为单台Eureka 导致的单点故障问题。
(5)服务怎么注册到Eureka Server中?
首先在需要注册到Eureka Server的服务pom中引入Eureka Client的依赖,然后在配置文件中配置服务名、Eureka Server的地址,最后在启动类上添加@EnableEurekaClient注解就行了。
(6)怎么搭建的Eureka Server集群?
其实就是对Eureka Server进行了集群部署,然后每台Eureka Server都要注册到另外两台Eureka Server中,它们之间会自动进行数据同步。服务在注册的时候要填写所有Eureka Server的地址,当然只写其中一个Eureka Server也行,但是这样做有风险,万一你写的这台Eureka Sever整好挂了那服务注册就失败了,所以为了稳妥起见,最好把所有Eureka Server的地址都写上。
(7)为什么要使用注册中心?
如果没有注册中心,消费者通过ip地址访问服务提供者,如果服务越来越多,调用也会越来越复杂,一旦服务提供者的ip地址发生变动,则所有用到的地方都需要进行改变,维护起来会特别麻烦;或者说服务提供者宕机了,所有访问的消费者也都会受到影响。通过注册中心可以让服务提供者注册到注册中心上去,并且可以进行负载均衡。这样就可以达到高可用以及提高并发的目的。消费者再调用服务提供者的时候,就可以通过注册中心获取地址列表,从地址列表中选一个地址进行调用,后续的维护各方面也会比较方便。
Feign的介绍?
Feign说白了就是一个用于简化服务间进行远程接口调用的工具,当然了不用Feign的话我们也可以进行远程接口调用,比如说我们可以使用HttpClient自己写代码去构造请求、发送请求和解析接口的响应结果,但是这样做太麻烦了,使用了Feign之后,我们调用远程接口就像在调用本地方法一样。那些构造请求、发送请求和解析接口响应结果的脏活累活Feign都帮我们做了,让我们可以更加专注于那些核心业务逻辑的编写。
(2)Feign的工作原理?
那在这里我就简单说一下我了解到的Feign的工作原理吧,首先在服务消费者微服务启动的时候,如果启动类上添加了@EnableFeignClients注解,那么Feign会进行包扫描,扫描那些添加了@FeignClient注解的接口,并按照注解的规则,创建接口的代理对象然后加入到IOC容器中。当接口中的方法被调用时,接口的代理对象会根据你在接口上的@RequestMapping、@PathVariable等注解,来动态构造出你要请求的服务的地址。最后针对这个地址,发起请求、解析响应结果。
(3)Feign具体怎么使用的?
首先我打个比方吧,比如说我现在有两个微服务一个是订单微服务一个是商品微服务,订单微服务在下订单的时候需要调用商品微服务的接口。那现在我的订单微服务就是消费者,商品微服务就是服务提供者。考虑到后续可能有更多的消费者都要调用商品微服务。我们应该给商品微服务新建一个对应的接口模块(项目),里面就放商品微服务对外暴露的东西。那此时如果说要使用Feign来进行订单微服务和商品微服务的远程接口调用的话,我们应该在商品微服务对应的接口模块下引入Feign的依赖,然后定义一个接口类,然后在接口上添加了@FeignClient注解并指定商品微服务的服务名,然后在这个接口类里面定义和商品微服务项目里面API接口对应的方法,而且方法要和对应API接口的请求方式、请求地址以及方法名、返回值、参数都要保持一致。这些做完了之后我们就可以在订单微服务中引入商品微服务接口模块的依赖,然后在启动类上添加@EnableFeignClients注解,然后在订单微服务的业务逻辑层注入前面定义好的FeignClient接口,然后调这个FeignClient接口里面的方法就可以实现对商品微服务API接口的调用了。
.Ribbon总结
Ribbon说白了就是一个客户端负载均衡的工具,当然我们在之前的项目里没有单独使用过Ribbon,因为我们使用的Feign已经内置了Ribbon。Ribbon内置了很多负载均衡策略,比如说轮询啊、随机啊、过滤掉不可用的服务啊、根据响应时间加权啊、轮询重试啊等,大概我就记得这些。Ribbon默认使用的负载均衡策略是轮询。当然如果说Ribbon自带的负载均衡策略满足不了你的需求的话,你也可以自定义Ribbon的轮询策略。其实就是新建一个类去继承Ribbon提供的一个什么Abstract…Rule什么的[故意说的不清楚],然后重写抽象方法,在里面实现你的负载均衡策略就可以了。
客户端负载均衡和服务端负载均衡的区别(被动)?
客户端负载均衡就是客户端自己来选择,服务端负载均衡就是服务端来选择。我打个现实生活中的例子吧,就是咱们去肯德基点餐,这个时候假设它有三个点餐窗口,每个点餐窗口都有一个服务员,现在每个点餐窗口都有排队的,这个时候如果是你自己选择点餐窗口那就是客户端负载均衡,如果是由服务员让你去指定的点餐窗口那就是服务端负载均衡。
Hystrix总结
(1)什么是服务雪崩?
在咱们的微服务系统中有可能发生服务雪崩的问题,比如说系统里面A服务需要调用B服务,B服务需要调用C服务,如果C服务出现问题,就会影响到B服务导致B服务出现问题,同样B服务出现问题,也会导致A服务也出问题,最后就会导致所有的微服务都不可用。就像咱们现实生活中,一个路口堵车,就会导致整条街慢慢拥堵,再后来影响的范围就会越来越大。为了避免出现服务雪崩问题,可以用SpringCloud中的Hystrix来解决这个问题。
(2)Hystrix是什么?
咱们都知道Hystrix可以进行熔断,降级和资源隔离。
(3)Hystrix的熔断机制?
熔断就是如果某个目标服务调用慢或者有大量超时,此时就熔断该服务的调用,对于后续的调用请求,不再继续调用目标服务,而是直接返回,快速释放资源。如果目标服务情况好转则恢复调用。
具体的原理是这样的,默认情况下在10s内,每当20个清求中,有50%失败时,就会触发熔断,导致hystrix熔断器从闭合状态切换到打开状态,这样就不需要每次都去调用远程微服务而是直接走调用者本地的降级方法。5秒后hystrix会进入到半打开状态,这时如果有新的请求过来hystrix就会放行1个请求尝试再次进行远程微服务调用,如果调用成功熔断器就会进入到闭合状态,如果不成功就会再次进入到打开状态,继续进行熔断。然后再过5秒再次进入半开状态。这个就是hystrix的自动检测并修复的能力。
(4)Hystrix的降级机制?
降级说白了就是进行熔断后需要执行本地的方法。也是为了保证微服务的高可用。
(5)Hystrix的隔离机制?
在Hystrix中,有两种资源隔离方式,信号量隔离和线程池隔离,我们主要用的是线程池隔离,通常在使用的时候我们会根据调用的远程服务划分出多个线程池,比如调用产品服务的线程放入一个线程池,调用会员服务的线程放入另一个线程池,这样就可以将运行环境隔离开。就算调用服务的代码存在BUG或者由于其他原因导致自己所在线程池被耗尽,也不会对系统的其他服务造成影响。
(6)Feign如何和Hystrix配合使用?
我们在项目中是用Feigin进行微服务之间的调用的,又因为Feign集成了Hystrix,所以我们当时通过在配置文件中开启Feign对Hystrix的支持,并且在具体使用Feign的接口上通过@FeignClient注解中的Fallback属性指定降级时候需要执行的类即可,这样在目标服务调用失败时或者熔断后就会自动调用Fallback类中对应的降级方法,在降级方法中可以记录下错误日志并且返回null这样使用Feign的客户端在调用远程微服务的方法后,判断返回值是否为null,如果是null,则证明走了降级方法,进行特殊处理就行。
Feign 还拥有负载均衡的特性,说白了它是靠Ribbon进行的负权均衡,在@FeignCilent的注解上指明要调用的微服务的名字,这样就可以通过该服务名从注册中心Eureka中获取对应的地址列表,方便进行负载均衡的调用。
还有一个需要注意的地方就是我们编写的FeignClient接口中的方法要和我们服务提供者项目中接口的请求方式,请求地址以及方法名,返回值,参数都要保持一致。
Zuul简介
Zuul在整个微服务系统中充当网关的角色,实现路由转发和过滤的功能能,是客户端与服务器之间的中间层。因为外部客户端的请求都是通过ZUUL网关路由到具体的微服务,所以为了保证微服务的安全,我们就在Zuul中自定义了过滤器,对所有微服务的安全进行统一的处理,还有,因为涉及到前后端分离,前端项目访问后端微服务涉及到跨域问题,所以我们在Zuul中也自定义了关于跨域的过滤器,进行统一处理。
(2)如何自定义跨域过滤器?
具体是这么做的,首先你得先定义一个继承于ZuulFiter的类,重写里面的filterType()方法filterOrder()方法,shouldFite()方法, 还有一个最核心的用来写具体业务逻辑的run()方法。其中filterType方法的作用是用来返回一个字符串,指明该过滤器的类型,经常用到的有pre类型,说白了就是在请求被发送到微服务之前调用:我们的微服务安全认证以及跨域这块都是用的pre类型的过滤器,这样对非法请求,就可以在发送到具体的微服务之前拒绝它;还有post类型,说白了就是微服务执行完后再执行该过滤器,filterOrder方法返回一个int类型的值,用来指明该过滤器的执行顺序,数宇越小表示优先级越高越先执行,shouldFilter()方法返回一个boolean值,用来指明该过滤器是否执行,true 表示执行,false表示不执行。
run方法中就是之前说的, 用来放具体的处理逻辑。
在run 方法中,首先获取一个RequestContext对象,之后就可以通过调用它的getRequest()方法来获取request对象;这样就可以获取头信息,按照基于token的方式进行接口的安全验证。
过程中特别需要注意的就是当验证不通过的时候,需要通过fastjson将要响应的数据转换为json格式的字符串,之后设置响应的内容类型为application/json并且指定utf-8的编码方式,用来处理中文乱码问题:通过setResponseBody将Json格式的字符串置为响应的内容,最后通过setSendZuulResponse为false,禁止路由转发。如果想要将zuul过滤器中的数据传递给后端微服务中使用,则需要通过addZuulRequestHeader方法来进行, 而后端微服务中就可以通过 request.getHeader来获取值。这里面需要特别注意的就是:如果传递的数据中含有中文则需要通过URLEncoder进行utf-8的编码, 同样在获取数据后也需要通过 URLDecoder进行解码。
最后想要使自定义的过滤器生效,得进行相关的配置。我通过创建一个配置类,并且在类上通过@Configuration 和方法上的@Bean结合起来,完成自定义filter的配置。
(3)Zuul集群的搭建?
Zuul的高可用非常关键,因为外部请求到后端微服务的流量都会经过Zuul。所以我们就对Zuul进行了集群部署,避免了单台Zuul会带来的单点故障问题,并且我们使用了Nginx对Zuul集群进行负载均衡。其实就是在Nginx配置文件里,把Zuul集群中每一台Zuul服务器的地址配上,然后由Nginx进行转发就行了。