带着问题学习
- 什么是微服务
- 微服务之间如何独立通讯的
- springCloud和Dubbo的区别
- SpringBoot和SpringCLoud,请你谈谈对他们的理解
- 什么是服务熔断?什么是服务降级?
- 微服务的优缺点分别是什么?说下你在项目开发中碰到的坑
- 你所知道的微服务技术栈有哪些?请列举一二
- eureka和zookeeper都可以提供服务注册和发现的功能,请说说两个的区别?
........................
Spring Cloud简介
Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中的配置管理、服务发现、断路器、智能路由、微代理、控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring Cloud CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring Cloud CLI等项目。
Spring Cloud是一个主流的微服务框架。微服务的概念源于2014年3月Martin Fowler所写的一篇文章Microservices。
微服务是一种解决复杂应用的架构模式。它的理念是将复杂应用拆分成一个个微小的服务,每个服务运行在独立的环境(进程、虚拟机、容器等)中,彼此之间互不干扰,独立开发,独立构建和部署。最终集成在一起,相互协调,为用户提供价值。
微服务框架可以为这些微小的服务提供统一的管理和必要的工具,使它们更易于开发和维护。
微服务架构
“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的产品社区也变得越来越活跃(比如:netflix、dubbo),Spring Cloud也因Spring社区的强大知名度和影响力也被广大架构师与开发者备受关注。
那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。
对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的《Microservices》,本文不做更多的介绍和描述。
为什么需要微服务?
微服务解决了什么痛点?你的应用到底需不需要微服务?
首先,如果对于业务简单、并发量较小的应用来说,其实是不需要使用微服务架构的,比如一些小的网站。但对于复杂的大型应用来说,是推荐使用微服务的。先来看看一个大型的单体应用可能有哪些痛点?
- 所有代码集成到一个项目,任何业务修改都需要重新部署整个项目
- 一个项目里面的代码量太大,难以管理,很难重构,出了BUG也很难排查
- 所有业务逻辑都在一个项目,测试量很大,每次都需要跑很久测试
- 难以水平扩展,很难处理突发流量激增的情况。 … 在微服务概念出现以前,有的团队为了解决上述痛点,开始使用“分布式系统”的理念去设计系统。
把应用拆成一个个小的应用,独立开发和部署。但由于缺乏统一的服务治理工具,所以需要集成一些外部的工具来完善这个系统。比如负载均衡、权限网关、路由、配置管理、状态监控等等工作都得自己做或者使用其它软件。这在一定程度上其实也增加了复杂性,尤其是运维的复杂性。
为什么使用Spring Cloud?
在Spring Cloud流行以前,其实已经有一些微服务解决方案。使用最广泛的就是阿里的Dubbo了。但Dubbo只提供了一些基础的功能,有点类似于上面提到的“分布式系统”的升级版,还不具备一个完善的微服务框架的功能。所以Dubbo往往也需要与其他框架进行集成,比如使用Zookeeper进行服务注册和发现。
Spring Cloud是Spring团队开发和维护,具有更完善的功能、更快的迭代速度、用户更多的特点。所以Spring Cloud是企业开始使用微服务的一个很好的技术选型。
还有最近比较火的Service Mesh的概念,被称为“下一代微服务”,它具有更先进的设计理念,把服务划分为一个个服务网格,并且架构更抽象,使得开发和运维分离,开发人员不需要学习太多的微服务知识便可以实现微服务。
服务治理机制
服务提供者向注册中心发送心跳,保证服务提供者一直在,没挂掉;服务续约30s一次,服务消费者可以获取,发请求去拿;获取之后服务消费者调用服务提供者提供的服务;服务下线:服务注册中心将服务提供者置为down状态;当心跳3次没正常连接,服务中心有60s的定时器,默认会在注册表中扫,90s之内没有发送心跳过来的请求,直接进行失效剔除;当我们网络存在波动,会开启自我保护机制(发送的心跳低于默认85%阈值时,进入自我保护机制,客户端不会被下线)
带着问题去思考和学习
解决方案
多级缓存,保证强可用,可以直接操作内存 读写缓存默认180s过期;
注册表内存本来就是ConcurrentMap线程安全的,为什么还要加缓存?
存在读写竞争,虽然操作很快,但是存在时效性问题,因此要加一级缓存,保证时刻可用,因为用的谷歌cache,所以也需要读写缓存
Eureka是什么
微服务框架可以为这些微小的服务提供统一的管理和必要的工具,使它们更易于开发和维护。
“统一的管理和必要的工具”正是微服务框架与单纯地“分布式应用”的区别所在。我们在后续的文章也主要是介绍这样一些Spring Cloud提供的管理微服务的组件和工具。
微服务注册中心最主要的功能正是用来“管理”微服务的。它相当于一个信息中心,保存着每个微服务的ip地址等信息。这样当其它微服务要调用这个微服务时,就可以去注册中心拿到ip,再根据ip去访问相应的微服务。如下图:
为什么需要注册中心?
注册中心相当于“婚介所”或者“房屋中介所”。试想一下,如果没有一个这样的中间角色,当一个微服务(消费者)需要调用另一个微服务(生产者)时,它需要保存生产者的地址(如ip,端口等信息)。而这个时候一旦生产者的地址产生了变化,比如:
- 新增了节点
- 减少了节点
- 原有结点的ip或端口变化
就会导致所有的消费者更改配置。传统分布式系统是对每个API创建一个对外的网关,一般使用ELB等负载均衡工具再配上DNS系统来实现
这样虽然一定程度上能解决服务调用之间的问题,但仍然需要维护大量的ELB/DNS等信息,而且当结点想要动态地增加或删除时,需要去修改ELB的配置。
使用注册中心后,结点的注册、删除、健康检查等功能都能自动化。消费者再也不需要手动去挨个寻找和维护任何生产者的地址信息,只是定时去注册中心取一下就行。
注册中心技术选型
关于分布式CAP的理论,有兴趣的读者自己去了解一下分布式事物。
C代表一致性,A代表高可用性,P代表分区容错性。在分布式系统中,三者不能同时满足,而P是一定需要满足的,所以常见的分布式系统要么满足AP,要么满足CP。
服务注册与发现
在简单介绍了Spring Cloud和微服务架构之后,下面回归本文的主旨内容,如何使用Spring Cloud搭建服务注册与发现模块。
这里我们会用到Spring Cloud Netflix,该项目是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。
所以,我们这里的核心内容就是服务发现模块:Eureka。下面我们动手来做一些尝试。
构建高可用的注册中心
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,
同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
在实际的项目中,单机往往是不够的,而需要注册中心集群,实现注册中心的高可用。如下图所示:
服务注册
在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
服务发现
在服务治理框架下,服务间的调用不再通过指定具体的实例地址来实现,而是通过服务名发起请求调用实现。服务调用方通过服务名从服务注册中心的服务清单中获取服务实例的列表清单,通过指定的负载均衡策略取出一个服务实例位置来进行服务调用。
Eureka服务端
Eureka服务端,即服务注册中心。它同其他服务注册中心一样,支持高可用配置。依托于强一致性提供良好的服务实例可用性,可以应对多种不同的故障场景。
Eureka服务端支持集群模式部署,当集群中有分片发生故障的时候,Eureka会自动转入自我保护模式。它允许在分片发生故障的时候继续提供服务的发现和注册,当故障分配恢复时,集群中的其他分片会把他们的状态再次同步回来。集群中的的不同服务注册中心通过异步模式互相复制各自的状态,这也意味着在给定的时间点每个实例关于所有服务的状态可能存在不一致的现象。
Eureka客户端
Eureka客户端,途中的即服务提供者,主要处理服务的注册和发现。客户端服务通过注册和参数配置的方式,嵌入在客户端应用程序的代码中。在应用程序启动时,Eureka客户端向服务注册中心注册自身提供的服务,并周期性的发送心跳来更新它的服务租约。同时,他也能从服务端查询当前注册的服务信息并把它们缓存到本地并周期行的刷新服务状态。
这里给读者解释一下什么是注册,什么是续约,什么是下线,什么是Relicate
-
1.注册:表示服务提供者需要向注册中心注册服务,在服务治理框架中,通常都会构建一个注册中心,每个服务单元向注册中心登记自己提供的服务,包括服务的主机与端口号、服务版本号、通讯协议等一些附加信息。
注册中心按照服务名分类组织服务清单,同时还需要以心跳检测的方式去监测清单中的服务是否可用,若不可用需要从服务清单中剔除,以达到排除故障服务的效果。
-
2.续约:这里类似于心跳检测机制,默认是每隔90秒,服务提供者需要向注册中心进行续约,目的是隔一段时间Service Provider调用接口,告诉Eureka Server它还活着没挂,不要把它T了。通俗的说就是它们两之间的心跳检测,避免服务提供者被剔除掉
-
3.下线:表示服务提供者断开
创建“服务注册中心”
创建一个基础的Spring Boot工程,并在pom.xml中引入需要的依赖内容:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
通过@EnableEurekaServer注解启动一个服务注册中心提供给其他应用进行对话。这一步非常的简单,只需要在一个普通的Spring Boot应用中添加这个注解就能开启此功能,比如下面的例子:
@EnableEurekaServer
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,所以我们需要禁用它的客户端注册行为,只需要在application.properties中问增加如下配置:
server.port=1111
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
为了与后续要进行注册的服务区分,这里将服务注册中心的端口通过server.port属性设置为1111。
启动工程后,访问:http://localhost:1111/
可以看到下面的页面,其中还没有发现任何服务
创建“服务提供方”
下面我们创建提供服务的客户端,并向服务注册中心注册自己。
假设我们有一个提供计算功能的微服务模块,我们实现一个RESTful API,通过传入两个参数a和b,最后返回a + b的结果。
首先,创建一个基本的Spring Boot应用,在pom.xml中,加入如下配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
其次,实现/add请求处理接口,通过DiscoveryClient对象,在日志中打印出服务实例的相关内容。
@RestController
public class ComputeController {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/add" ,method = RequestMethod.GET)
public Integer add(@RequestParam Integer a, @RequestParam Integer b) {
ServiceInstance instance = client.getLocalServiceInstance();
Integer r = a + b;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}
最后在主类中通过加上@EnableDiscoveryClient注解,该注解能激活Eureka中的DiscoveryClient实现,才能实现Controller中对服务信息的输出。
@EnableDiscoveryClient
@SpringBootApplication
public class ComputeServiceApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ComputeServiceApplication.class).web(true).run(args);
}
}
我们在完成了服务内容的实现之后,再继续对application.properties做一些配置工作,具体如下:
spring.application.name=compute-service
server.port=2222
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
通过spring.application.name属性,我们可以指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问。
eureka.client.serviceUrl.defaultZone属性对应服务注册中心的配置内容,指定服务注册中心的位置。
为了在本机上测试区分服务提供方和服务注册中心,使用server.port属性设置不同的端口。
启动该工程后,再次访问:http://localhost:1111/
可以看到,我们定义的服务被注册了。
服务消费者
我们已经成功创建了“服务注册中心”,实现并注册了一个“服务提供者:COMPUTE-SERVICE”。那么我们要如何去消费服务提供者的接口内容呢?
Ribbon Ribbon是一个基于HTTP和TCP客户端的负载均衡器。Feign中也使用Ribbon,后续会介绍Feign的使用。
Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。
当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。
下面我们通过实例看看如何使用Ribbon来调用服务,并实现客户端的均衡负载。
准备工作
- 启动Chapter-9-1-1中的服务注册中心:eureka-server
- 启动Chapter-9-1-1中的服务提供方:compute-service
- 修改compute-service中的server-port为2223,再启动一个服务提供方:compute-service
可以看到COMPUTE-SERVICE服务有两个单元正在运行:
- 192.168.21.101:compute-service:2222
- 192.168.21.101:compute-service:2223
使用Ribbon实现客户端负载均衡的消费者
构建一个基本Spring Boot项目,并在pom.xml中加入如下内容:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在应用主类中,通过@EnableDiscoveryClient注解来添加发现服务能力。创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。
@SpringBootApplication
@EnableDiscoveryClient
public class RibbonApplication {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RibbonApplication.class, args);
}
}
创建ConsumerController来消费COMPUTE-SERVICE的add服务。通过直接RestTemplate来调用服务,计算10 + 20的值。
@RestController
public class ConsumerController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public String add() {
return restTemplate.getForEntity("http://COMPUTE-SERVICE/add?a=10&b=20", String.class).getBody();
}
}
application.properties中配置eureka服务注册中心
spring.application.name=ribbon-consumer
server.port=3333
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
启动该应用,并访问两次:http://localhost:3333/add
然后,打开compute-service的两个服务提供方,分别输出了类似下面的日志内容:
- 端口为2222服务提供端的日志:
2016-06-02 11:16:26.787 INFO 90014 --- [io-2222-exec-10] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
- 端口为2223服务提供端的日志:
2016-06-02 11:19:41.241 INFO 90122 --- [nio-2223-exec-1] com.didispace.web.ComputeController : /add, host:192.168.21.101, service_id:compute-service, result:30
可以看到,之前启动的两个compute-service服务端分别被调用了一次。到这里,我们已经通过Ribbon在客户端已经实现了对服务调用的均衡负载。
Feign
Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单。我们只需要使用Feign来创建一个接口并用注解来配置它既可完成。它具备可插拔的注解支持,包括Feign注解和JAX-RS注解。Feign也支持可插拔的编码器和解码器。Spring Cloud为Feign增加了对Spring MVC注解的支持,还整合了Ribbon和Eureka来提供均衡负载的HTTP客户端实现。
下面,通过一个例子来展现Feign如何方便的声明对上述computer-service服务的定义和调用。
创建一个Spring Boot工程,配置pom.xml,将上述的配置中的ribbon依赖替换成feign的依赖即可,具体如下:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
在应用主类中通过@EnableFeignClients注解开启Feign功能,具体如下:
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class FeignApplication {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
定义compute-service服务的接口,具体如下:
@FeignClient("compute-service")
public interface ComputeClient {
@RequestMapping(method = RequestMethod.GET, value = "/add")
Integer add(@RequestParam(value = "a") Integer a, @RequestParam(value = "b") Integer b);
}
- 使用@FeignClient("compute-service")注解来绑定该接口对应compute-service服务
- 通过Spring MVC的注解来配置compute-service服务下的具体实现。
在web层中调用上面定义的ComputeClient,具体如下:
@RestController
public class ConsumerController {
@Autowired
ComputeClient computeClient;
@RequestMapping(value = "/add", method = RequestMethod.GET)
public Integer add() {
return computeClient.add(10, 20);
}
}
application.properties中不用变,指定eureka服务注册中心即可,如:
spring.application.name=feign-consumer
server.port=3333
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
启动该应用,访问几次:http://localhost:3333/add
再观察日志,可以得到之前使用Ribbon时一样的结果,对服务提供方实现了均衡负载。
这一节我们通过Feign以接口和注解配置的方式,轻松实现了对compute-service服务的绑定,这样我们就可以在本地应用中像本地服务一下的调用它,并且做到了客户端均衡负载。
参考:
- 中文网 www.springcloud.cc/
- blog.didispace.com/springcloud…
- 微服务Eureka原理springcloud Eureka java高级开发java架构师进阶-咕泡学院 www.bilibili.com/video/BV1S7…
- SpringCloud-拜托!面试请不要再问我Spring Cloud底层原理 blog.csdn.net/Anbang713/a…
- www.cnblogs.com/huangjuncon…
- 拜托!面试请不要再问我Spring Cloud底层原理! blog.csdn.net/qq_42046105…
- 程铭程铭你快成名 wangchengming.blog.csdn.net/article/det…