思考微服务
在构建小型、简单应用程序的时候,创建单体应用是显而易见的方式。如果到后期,你发现你的应用需要更多新的特性的时候,你会想我直接添加代码就可以,同时你也这么做了,但是随着你加的越来越多,你会发现,他慢慢变成了一个复杂的应用,甚至有自己的思想。就像电影《小鬼怪》( Gremlins)里的 Mogwai一样,如果你一直喂它,它最终会变成一个与你作对的怪物。
首先看一下单体应用遇到的挑战
■单体应用难以理解: 代码库越大,理解每个组件在整个应用程序中所担任的角色就越困难。
■单体应用难以测试: 随着应用的不断增长,全面的集成和验收测试会变得更加复杂
■单体应用更容易出现库冲突:实现某个特性所需要的依赖可能会与其他特定的依赖不兼容。
■单体应用的扩展较为低效:如果处于扩展的目的要将应用程序部署到更多的硬件上,那么我们必须要将整个应用部署到更多的服务器上,即便应用程序中很小的一部分需要扩展也同样如此。
■单体应用中的技术决策是针对整个单体应用的:当为应用程序选择语言、运行时平台、框架和库的时候,整个应用程序都会遵循我们的选择,即便我们所做的选择只是为了支持某个单独的用户场景时同样如此。
■单体应用需要大量的操作过程才能投入生产环境: 当应用程序只有一个部署单元时,似乎更容易将其投入生产环境。事实上并非如此,单体应用程序的规模和复杂性通常需要更严格的开发过程和更周全的测试周期,这样才能保证所部署的应用程序是高质量的,才能避免引入bug。
在过去的几年间,微服务架构的出现致力于解决这些挑战。简而言之,微服务架构是将应用程序分解为可独立开发和部署的小规模、微型应用的一种方式。这些微服务之间互相协作,以实现更大的应用程序的功能。
与单体应用程序架构相比,微服务架构有如下特点:
■微服务易于理解:每个微服务与应用程序的其他微服务之间有一个很小且有限揽更约。因此,微服务更加专注于目标,作为一个单元,微服务更易于理解。
■微服务易于测试:事情越小,就越便于测试。当你思考单元测试、集成测试和验收测试的时候,这一点非常明显。它也适用于微服务与单体应用之间的测试。
■微服务较少受到库的不兼容的影响:因为每个微服务都有自己的构建依赖项的集合而这些依赖项不会与其他的微服务共享,所以不太可能会出现库冲突的现象。
■微服务能移独立扩展:如果指定的微服务需要更多的处理能力,那么内存分配和/或实例数量可以按比例增加,而不会影响整体应用中其他微服务的内存和实例数量
■每个微服务可以选择不同的技术: 每个微服务可以选择完全不同的语言、平台、框架和库。实际上,某个使用Java编写的微服务与另一个使用C#编写的微服务进行协作是完全合理的。
■微服务可以更加频繁地发布到生产环境中:尽管微服务架构的应用是由许多微服务组成的,但是部署每个微服务的时候,并不需要其他的微服务都已经部署就绪。而且,因为它们更小、更集中、更易于测试,所以将微服务投入到生产环境不需要那么多的繁文缛节。从产生想法到将其投入生产的耗时可以用分钟和小时计量,而不是用周和月。
显然,微服务能够让事情变得更简单。但是公平地讲,微服务架构并不是免费的午餐。微服务架构是一种分布式架构,有自己需要应对的挑战,包括网络延迟。在迁移至微服务架构时,我们需要记住这一点, 因为很多的远程调用会累积并降低应用的速度。你还要考虑是否应该将应用构建为微服务,因为并不是所有的应用程序都需要这种架构,或者说能从这种架构中受益。如果你的应用相对比较小或者比较简单,那么最初最好依然采用单体架构。随着它的不断发展,再考虑将其拆分为微服务。
搭建服务注册中心
Snring Cloud是一个非常大的伞形项目,由多个独立的子项目,以某种形式支撑着微服务的开发,其中有一个子项目叫作Spring Cloud Netflix,提供了很多组件,包括了Netflix的服务注册中心Eureka。
要开始使用Spring Cloud和Eureka,我们需要首先为Eureka 本身创建一个全新的项目。最简单的方式是使用Spring lnitializ在选择starter依赖的时候,我们只需要一项依赖:带有Eureka Sever标签的复选框。在创建完新项目之后,pom.xml将会包含如下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
添加完依赖之后就是要启用Eureka服务器,我们还需要做一件事就是为应用的主引导类添加@EnableEurekaServer
配置Eureka
Eureka不喜欢单独工作,并相信数量多会更加安全的理念,希望能够成为服务器集群的一部分。 如果有多个Eureka服务器,其中一个遇到问题,就不会出现单点故障。因此,Eureka 的默认行为是与其他Eureka服务器建立关联,尝试获取其他Eureka服务器的服务注册中心,甚至还会将自身注册为其他Eureka 服务器的服务。
在生产环境中,Eureka 的高可用是非常有价值的。但是,对于开发阶段来说启动多个Eureka服务器既不方便也没有必要。为了达到开发的目的,有一个单独的 Eureka服务器就足够了。除非我们正确配置了Eureka 服务器,否则它会以日志文件中异常的形式每隔30秒就抱怨孤独状态。这是因为,每隔30秒,Eureka服务器就会尝试与另外的Eureka服务器建立关联,以注册自己并共享其注册中心中的信息。
我们需要做的就是配置Eureka 使其接受当前的孤独状态。为了实现这一点,我们需要在application.yml中设置一些属性, 代码片段如下所示:
server:
port: 8761
eureka:
server:
enable-self-preservation: false
instance:
hostname: localhost
client:
fetch-registry: false
register-with-eureka: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka
首先,我们将eureka.instance.hostname属性设置为localhost。 这会告诉Eureka 它正运行在哪个主机(host)上。这个属性是可选的,如果我们不指定它,那么Eureka会尝试通过环境变量确定它的主机。明确设置这个属性能够让我们更加确定它的值。
接下来的两个属性是eureka.client.fetch-registry和eureka.client.register-with-eureka.在其他的微服务中,我们可能会通过这两个属性告诉它们该如何与Eureka服务器进行交互。但是,不要忘了,Eureka也是一个微服务, 所以这些属性也可以用到Eureka 服务器上,以便于告诉它该如何与其他Eureka服务器进行交互。
这两个属性的默认值都是true,表明Eureka应该从其他的Eureka 实例获取注册信息,并且应该将自身注册为其他Eureka 服务器中的服务。因为在开发模式下并没有其他的Eureka 服务器,所以我们将它们设置为false, 这样Eureka 将不会尝试与其他的Eureka服务器建立关联。
最后,我们还设置了eureka.client.service-uri属性。这个属性包含了zone 名称与该zone下一个或多个Eureka服务器之间的映射关系。defaultZone是一个特殊的 key,如果客户端没有指定所需的zone,就将会使用这个zone,因为我们只有一个Eureka,映射到默认zone的URL就是Eureka 服务器本身,所以这里使用了占位符变量,由其他属性填充它的值。
指定Eureka的服务器端口,我们将端口设置成了8761.
禁用自我保护模式
Burcka 希望服务实例能够注册上来,并且每隔30秒发送注册更新的请求。通常,如果Eureka在3个更新周期(或者说90秒)内没有收到服务的更新请求,就会将该服务注销。若Eureka 假定出现了网络问题,进入自我保护模式,所以不会注销服务实例。
在生产环境中,自我保护模式是很好的,可以防止在出现网络故障时更新请求无法发送至Eureka所导致的活跃服务被注销。所以在开发过程中我们可以将eureka.server.enable-self-preservation 属性设置为false, 从而禁用自我保护模式。
这个属性在开发环境中是非常有用的。在开发环境中,基于各种原因,Eureka 可能会收不到更新请求。在这种环境下,我们可能会频繁地启动或关闭服务实例,自我保护模式会将已停止服务的注册项保留下来,另一个服务访问已经不可用的服务时就会产生问题。禁用自我保护模式将会防止这种诡异的问题。
虽然我们在开发环境可以禁用自我保护模式,但是在投人生产环境时需要将其启用。
注册服务
添加依赖
在pom.xml中添加如下,或者在复选框中选择Eureka Discovery
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
</properties>
starter依赖会添加通过Eureka发现服务所需的所有内容,包括Eureka的客户端库以及Ribbon 负载均衡器。我们只需要将这个依赖添加进来,就能将应用变成Eureka服务注册中心的客户端。当应用启动的时候,它会尝试联系在本地运端口为8761 的Eurcka服务器,并将自身基于UNKNOWN名称进行注册。
配置Eureka客户端属性
对于开发阶段来说,默认位置的Eureka 服务器是可以接受的,如果我们要将服务部署到lcalhost之外,就需要覆盖它的值。另外,默认的服务名为UNKNOWN,这是一个非常糟糕的选择,默认会使得所有的服务都是一个名称,那就不能区别开了,更改服务在Eureka中的注册名称非常简单,我们只需要设置spring. aplicationn namne属性就可以了。例如,想要注册一个处理消费者相关操作的服务,那么在application.yml中:
spring:
application:
name: provider-service
server:
port: 8787
设置完这个属性之后,我们就可以按照provider-service名称来查找服务了。
我们可以访问http://localhost:8761来查看服务是否注册进去
现在,我们要考虑Eureka服务器的位置。默认情况下,Eureka客户端会假定Eureka服务器在本地运行(8761 端口)。对于开发期来说,这种方式很不错,但是在生产环境中,大多数情况并非如此。因此,我们需要指定Eureka 服务器的位置。这与Eureka服务器本身的实现方式完全相同,都是要使用eureka.client.service-url属性:
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
通过这样的配置,客户端会使用eurekal .主机(端口8761 )上的Eureka服务器进行注册。只要Eureka服务器在运行,这种方式就是没有问题的,但是一-旦Eureka服务器因为某种原因而停机,服务注册就会失败。为了避免注册失败,最好是为服务配置两个或更多的Eureka实例:
当服务启动的时候,它会尝试使用zone中的第一个服务器进行注册。如果因为某种原因失败,它将会使用列表中的下一个服务器来进行注册。最终,如果出现故障的服务器重新恢复在线状态,它将会从对等的端上复制注册信息,这样就能将该服务的注册条目包含进来。
消费服务
使RestTemplate消费服务
如果我们将应用变成Eureka客户端,就可以声明支持负载均衡的RestTemplate了。我们需要做的就是声明一个常规的RestTemplate bean,并为带有@Bean注解的方法再添加上@LoadBalanced
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@LoadBalaced注解有两个目的。首先, 也是最重要的,它会告诉Sping Cloud,这个RestTemplate 能够通过Ribbon 来查找服务。其次,它会作为一个注入限定符( qualifer ),所以有两个或更多RestTemplate bean 的话,我们可以在注人的地方声明此处想要支持负载均衡的RestTemplate
随后我们就可以使用了
@RestController
public class OutService {
public final RestTemplate restTemplate;
@Autowired
public OutService(@LoadBalanced RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
/**
* 消费provider-service的服务
* @return
*/
@GetMapping("/out")
public String Test(){
return restTemplate.getForObject("http://provider-service/out",String.class);
}
}
在主机名和端口的位置上,我们使用了服务名provider-service. 在内部,RestTemplate会要求Ribbon根据名称查找服务并从中选择一个实例。Ribbon 非常乐于效力,它会将URL重写为选定服务实例的主机和端口,然后让RestTemplate像以往那样进行处理。
我们可以看到,使用支持负载均衡的RestTemplate与标准RestTemplate并没有太大的差异。关键的不同点在于客户端需要使用服务名,而不是显式的主机名和端口。
定义Feign接口
Feign最初是Netlix的一一个项目, 后来变成了独立的开源项目,
要使用Feign,我们首先需要将依赖添加到项目的构建文件中。在pom.xml文件中
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
然后定义Feign接口
@FeignClient("provider-service")
public interface Feign {
@GetMapping("/out")
String getPort();
}
这是一个很简单的接口,并没有实现类。在运行期,当Feign发现它的时候,这一切就都不重要了,Feign 会自动创建一个实现类并将其暴露为Spring应用上下文中的bean。仔细观察一下, 我们会发现其中有一些注解在发挥作用,并将所有功能组合在了 一起。接口上的@FeignClient注解会指定该接口上的所有方法都会对名为provier-service的服务发送请求。在内部,服务将会通过Ribbon 进行查找,这与支持负载均衡的RestTemplate 运行方式是样的。
随后就是getPort()方法, 它使用了@GetMapping注解。你会发现,这个注解来源于Spring MVC。确实,就是同一个注解。现在它用在了客户端,而不是用在控制器上。它表明,任何对getPort()的调用都会对“/out" 路径发起GET请求
@RestController
public class FeignImp {
private final Feign feign;
@Autowired
public FeignImp(Feign feign) {
this.feign = feign;
}
@GetMapping("/out1")
public String getPortFeign(){
return feign.getPort();
}
}
我们请求out1路径就会出现
Port is8787
负载均衡器
负载均衡是我们处理高并发、缓解网络压力和进行服务器扩容的重要手段之一。
服务端负载均衡
服务器端负载均衡可通过硬件设备及软件来实现,硬件比如:F5、Array等,软件比如:LVS、Nginx等。
用户请求先到达负载均衡器(也相当于一个服务),负载均衡器根据负载均衡算法将请求转发到微服务。负载均衡算法有:轮训、随机、加权轮训、加权随机、地址哈希等方法,负载均衡器维护一份服务列表,根据负载均衡算法将请求转发到相应的微服务上,所以负载均衡可以为微服务集群分担请求,降低系统的压力
客户端负载均衡
Ribbon是Netflix公司开源的一个负载均衡的项目(https://github.com/Netflix/ribbon),它是一个基于HTTP、TCP的客户端负载均衡器。
1、在消费微服务中使用Ribbon实现负载均衡,Ribbon先从EurekaServer中获取服务列表。
2、Ribbon根据负载均衡的算法去调用微服务。
服务器端负载均衡 VS 客户端负载均衡的特点如下:
服务器端负载均衡 客户端先发送请求到负载均衡服务器,然后由负载均衡服务器通过负载均衡算法,在众多可用的服务器之中选择一个来处理请求。
客户端负载均衡 客户端自己维护一个可用服务器地址列表,在发送请求前先通过负载均衡算法选择一个将用来处理本次请求的服务器,然后再直接将请求发送至该服务器。
来源:
Spring实战5
https://www.cnblogs.com/zhenghongxin/p/10812137.html
https://blog.csdn.net/pengjunlee/article/details/86594934