下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922

SpringCloud 微服务实战笔记,阿里P7架构师的独家分享

作者:Java架构师     来源: 今曰头条点击数:1015发布时间: 2019-11-22 16:44:48

标签: SpringCloud 视频SpringCloud 视频课程SpringCloud 课程

SpringCloud 微服务实战笔记,阿里P7架构师的独家分享_SpringCloud 视频_SpringCloud 视频课程_SpringCloud 课程_课课家

  一、SpringBoot 构建项目,在我们使用传统的 spring 开发一个 web 应用程序通常会想到一些基本的需要,我们一起看看啦,

  Web.xml 文件(配置 SpringMVC 的 DispatcherServlet,各种过滤器等等);

  启用了 SpringMVC 的 spring 配置文件;

  Mybatis 等数据库配置文件等。

  以上的这些仅仅只是基本的需求,无论是开发一个大型项目或者只是一个 hello word 程序,都需要配置几乎同等的配置文件,既然这些都是通用的东西,那有什么东西可以把这些给自动配置了呢?这时候 springboot 的自动配置功能就派上用场了,springboot 会为这些常用的配置进行自动配置。这些自动配置涉及很多方面,比如:java 持久化 api,各种 web 模板,springMVC 等等。

  1. 起步依赖

  平时我们使用 maven 创建一个 web 项目的时候,常常需要想项目需要哪些包,以及包的版本。但是在 springboot 创建 web 应用的时候,你只需你只需添加 springboot 的 Web 起步依赖(org.springframework.boot:spring-boot-starter-web)。它会根据依赖传递把其他所需依赖引入项目里面。

  而其它你需要的功能,你只需要引入相关的的起步依赖即可。

  2. 内嵌 Servlet 容器

  其实 springboot 并不是一个应用服务器,它之所以可以运行 web 应用程序,是因为其内部已经内嵌了一个 Servlet 容器(Tomcat、Jetty 或 Undertow),其运行原理是把 web 应用直接打包成为一个 jar/war,然后这个 jar/war 是可以直接启动的,不需要另外配置一个 Web Server。相关的 embed 类就是它的依赖包。

  3. Spring Initializr 构建 springboot 应用程序

  本文使用的是 intellij idea 中的 Spring Initializr 工具创建 springboot 应用程序。

  菜单栏中选择File=>New=>Project..,步骤大概是选择构建的工程类型,如:maven,Gradle;language 的选择;选择 Spring Boot 版本和起步依赖包等等。具体创建步骤这里就省略了。

  

 

  spring boot 项目结构如图所示,整个项目结构遵循了 maven 项目的布局,主要的应用程序代码位于 src/main/java 目录里,资源都在 src/main/resources 目录里,测试代码则在 src/test/java 目录里。不同的是,web 页面模板移到 templates 了,我的项目现在主要用 thymeleaf 模板作为 web 页面。

  在结构图你会发现一些与 springboot 密切项目的文件:

  WebGatewayApplication.java:应用程序的启动引导类(bootstrap class),也是主要的 Spring 配置类;

  application.properties:用于配置应用程序和 Spring Boot 的属性;

  ReadingListApplicationTests.java:一个基本的集成测试类。

  banner.txt:spring boot 应用程序启动时加载的文件。

  3.1 启动引导 Spring

  前面我们看到的 WebGatewayApplication.java 在 springboot 应用程序中主要有两个作用:配置和启动引导。而也是 Spring 的主要配置类。虽然 springboot 的自动配置免除了很多 Spring 配置,但你还需要进行少量配置来启用自动配置。

  程序清单:

  package com.crm;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication // 开启组件扫描和自动配置public class WebGatewayApplication { public static void main(String[] args) { SpringApplication.run(WebGatewayApplication.class, args);// 启动引导应用程序 }}

  3.2 配置应用程序属性

  用 Spring Initializr 生成的 application.properties 文件只是一个空文件,它可以删除完全不影响应用程序的运行,但是,如果你想修改应用程序的属性,你就得在里面配置相关属性了,比如你在里面配置了 server.port=9010,嵌入式的 tomcat 服务器的监听端口就不是默认的 8080 了,变成了 9010。而且这个属性文件是自动被加载的。

  这是我的项目 application.properties 属性配置:

  ###### MySQL配置spring.datasource.name=testspring.datasource.url=jdbc:mysql://localhost:3306/crm?characterEncoding=UTF8spring.datasource.username=zchspring.datasource.password=123456spring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.filters=statspring.datasource.maxActive=20spring.datasource.initialSize=1spring.datasource.maxWait=60000spring.datasource.minIdle=1spring.datasource.timeBetweenEvictionRunsMillis=60000spring.datasource.minEvictableIdleTimeMillis=300000spring.datasource.validationQuery=select 'x'spring.datasource.testWhileIdle=truespring.datasource.testOnBorrow=falsespring.datasource.testOnReturn=falsespring.datasource.poolPreparedStatements=truespring.datasource.maxOpenPreparedStatements=20###### mybatismybatis.typeAliasesPackage=com.joosure.integral.cloud.pojo.cloudmybatis.mapperLocations=classpath:mapper/*.xml####### thymeleafspring.thymeleaf.cache=falsespring.thymeleaf.check-template-location=truespring.thymeleaf.content-type=text/htmlspring.thymeleaf.enabled=truespring.thymeleaf.encoding=UTF-8spring.thymeleaf.excluded-view-names=spring.thymeleaf.mode=HTML5spring.thymeleaf.prefix=classpath:/templates/spring.thymeleaf.suffix=.htmlspring.thymeleaf.template-resolver-order=

  3.3 构建过程解释

  我的项目用的是 maven 作为构建工具,因此用 Spring Initializr 会生成 pom.xml 文件,这与创建普通的 maven 项目一样,代码清单如下:

  0.0.1-SNAPSHOT jar crm crm-system org.springframework.boot spring-boot-starter-parent 1.5.3.RELEASE UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-jdbc org.mybatis.spring.boot mybatis-spring-boot-starter 1.3.0 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin

  其中 Artifact ID 为 spring-boot-starter-xxx 的都是 spring boot 起步依赖;

  构建插件的主要功能是把项目打包成一个可执行的超级 JAR(uber-JAR),包括把应用程序的所有依赖打入 JAR 文件内,并为 JAR 添加一个描述文件,其中的内容能让你用 java -jar 来运行应用程序;

  Maven 构建说明中还将 spring-boot-starter-parent 作为上一级,这样一来就能利用 Maven 的依赖管理功能,继承很多常用库的依赖版本,在你声明依赖时就不用再去指定版本号了。

  

 

  二、服务注册与发现

  现在公司的积分联盟平台系统构建于公司内部的第 4 代架构中,而第 4 代就是 基于 SpringCloud 的微服务架构,趁着项目上手,花了几天研究了一下。

  SpringCloud 是一个庞大的分布式系统,它包含了众多模块,其中主要有:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端负载均衡(Ribbon)等。也就是说微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立维护、独立扩展,服务与服务间通过诸如 RESTful API 的方式互相调用。

  1. 创建服务注册中心

  在搭建 SpringCloud 分布式系统前我们需要创建一个注册服务中心,以便监控其余模块的状况。这里需要在 pom.xml 中引入:

  org.springframework.cloud spring-cloud-starter-eureka-server

  并且在 SpringBoot 主程序中加入@EnableEurekaServer 注解:

  @EnableEurekaServer@SpringCloudApplicationpublic class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); }}

  接下来在 SpringBoot 的属性配置文件 application.properties 中如下配置:

  server.port=9100eureka.client.register-with-eureka=falseeureka.client.fetch-registry=falseeureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

  server.port 就是你指定注册服务中心的端口号,在启动服务后,可以通过访问,服务发现页面,如下:

  

 

  我们可以发现其它系统在这里注册并显示在页面上了,想要注册到服务中心,需要在系统上做一些配置,步骤跟创建服务注册中心类似,这里 web-gateway 系统做例子:

  首先在 pom.xml 中加入:

  org.springframework.cloud spring-cloud-starter-eureka

  在 SpringBoot 主程序中加入@EnableDiscoveryClient 注解,该注解能激活 Eureka 中的DiscoveryClient实现,才能实现 Controller 中对服务信息的输出:

  @EnableDiscoveryClient@SpringBootApplicationpublic class WebGatewayApplication { public static void main(String[] args) { SpringApplication.run(WebGatewayApplication.class, args); }}

  在 SpringBoot 的属性配置文件 application.properties 中如下配置:

  spring.application.name=web-gatewayserver.port=9010eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/eureka.instance.leaseRenewalIntervalInSeconds=5

  

 

  三、服务消费者

  在系统与系统之间,如何进行相互间的调用呢?也就是说怎么去调用服务提供的接口内容呢?这里就要说一下 Ribbon 了,Ribbon 是一个基于 http 和 tcp 客户端的负载均衡器。

  下面我来简单介绍如何在 SpringCloud 分布式系统下使用 Ribbon 来实现负载均衡。

  首先在 pom.xml 中引入一下依赖:

  org.springframework.cloud spring-cloud-starter-ribbon

  然后在 spring boot 主程序中创建 RestTemplate 类,并为它加上@LoadBalanced 注解开启负载均衡的能力:

  @EnableDiscoveryClient@SpringBootApplicationpublic class WebGatewayApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(WebGatewayApplication.class, args); }}

  RestTemplate 类是 Spring 用于构建 Restful 服务而提供的一种 Rest 服务可客户端,RestTemplate 提供了多种便捷访问远程 Http 服务的方法。

  在 apllication.properties 配置文件中配置 eureka 服务,并注册到服务中心:

  spring.application.name=integral-serverserver.port=9600eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/

  在公司项目中正是通过 RestTemplate 来访问各个微服务提供的接口,比如在项目中要访问积分系统 integral-server,添加积分用户:

  JSONObject integralServerResult = restTemplate.postForObject("http://integral-server/shop/add", RequestHandler.getRestRawRequestEntity(integralShopJson), JSONObject.class);

  这样就可以调用 integral-server 系统的添加用户的接口实现在别的系统中添加用户了。

  我们也可以在 application.properties 配置文件中加入:

  ###### Ribbonribbon.ReadTimeout=60000

  这个是设置负载均衡的超时时间的。

  

 

  四、断路器

  微服务架构中,各个系统被拆分成一个个服务单元,链路调用可能包括很多个服务单元,而每个单元又会个 N 个服务单元提供服务,因此如果有一个服务单元出现故障,就可能导致其它依赖此服务的服务单元出现延迟,导致整个微服务系统出现雪崩效应。

  在 SpringCloud 模块中有一个叫 Netflix Hystrix 的断路器模块,就是专门解决这个问题而生的,Hystrix 是 Netflix 开源的微服务框架套件之一,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。

  下面来说一下 Hystrix 在微服务系统中的具体用法:

  首先还是在 pom.xml 中加入以下依赖:

  org.springframework.cloud spring-cloud-starter-hystrix

  在 spring boot 主程序中加入@EnableCircuitBreaker 注解开启断路器模式:

  @EnableEurekaClient@EnableCircuitBreaker@EnableDiscoveryClient@SpringBootApplicationpublic class WebGatewayApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(WebGatewayApplication.class, args); }

  如果在调用过程中返回类似这样的响应:

  Whitelabel Error PageThis application has no explicit mapping for /error, so you are seeing this as a fallback.Sat May 13 00:10:22 CST 2017There was an unexpected error (type=Internal Server Error, status=500).400 null

  断路器也就开启了。

  我们也可以在 application.properties 配置文件中加入:

  ## hystrixhystrix.commond.default.execution.isolation.thread.timeoutInMilliseconds=60000

  这个设置可以更改返回错误响应的超时时间。

  如果不想返回默认的错误响应信息,我们还可以通过自定义来更改错误响应信息,我们需要一个类中注入一个 RestTemplate 类:

  @Autowired RestTemplate restTemplate;

  这个类在上面已经通过 Spring 创建好了,这里直接注入在类中即可,接下来我们在类中写一个方法:

  @HystrixCommand(fallbackMethod = "addServiceFallback") public String addService() { return restTemplate.postForObject("http://integral-server/shop/add", RequestHandler.getRestRawRequestEntity(integralShopJson), JSONObject.class); } public String addServiceFallback() { return "error"; }

  当调用 integral-server 系统的添加接口超出延时的时间时,就会返回“error”。

  

 

  五、服务网关

  前面我们通过 Ribbon 实现服务的消费和负载均衡,但还有些不足的地方,举个例子,服务 A 和服务 B,他们都注册到服务注册中心,这里还有个对外提供的一个服务,这个服务通过负载均衡提供调用服务 A 和服务 B 的方法,那么问题来了,每个服务都变得有状态了,即每个服务都需要维护一套校验逻辑,这样会带来对外接口有污染。而且权限等不好集中管理,整个集群处于混乱之中。

  最好的方法就是把所有请求都集中在最前端的地方,这地方就是 zuul 服务网关。

  服务网关是微服务架构组件中处于最外一层,通过服务网关统一,可以将链路前端集中管理起来,除了具备服务路由、均衡负载功能之外,它还需要具备权限控制等功能。Spring Cloud Netflix 中的 Zuul 就担任了这样的一个角色,为微服务披上了一层保护层,也方便了权限校验集中管理,增加了接口的通用性。

  

 

  @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); Object accessToken = request.getParameter("accessToken"); if(accessToken == null) { log.warn("Access token is empty"); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); return null; } return null; }}

  在自定过滤器之后,我们还需要在 SpringBoot 主程序中加入@EnableZuulProxy 注解来开启 zuul 路由的服务过滤:

  @EnableZuulProxy@EnableEurekaClient@RibbonClients@SpringCloudApplicationpublic class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } @Bean PosPreFilter posPreFilter(){ return new PosPreFilter(); }

  到这里,微服务系统的 zuul 路由功能基本搭建完成。

  

 

  六、Feign

  之前说过了微服务间,我是通过 Spring 的 RestTemplate 类来相互调用的,它可通过整合 Ribbon 实现负载均衡,但发现了这样写不够优雅,且不够模板化,因此本篇介绍一下 Feign。

  Feign 是一种声明式、模板化的 HTTP 客户端,在 Spring Cloud 中使用 Feign 其实就是创建一个接口类,它跟普通接口没啥两样,因此通过 Feign 调用 HTTP 请求,开发者完全感知不到这是远程方法。

  1. 整合 Feign

  添加 Feign 依赖:

  org.springframework.cloud spring-cloud-starter-feign

  创建 一个 Feign 接口:

  @FeignClient(value = FeignConst.COUPON_PROVIDER, url = "${feign.coupon.url:}")public interface CouponClient { @GetMapping(value = "/coupon/list/page", headers = LocalsEncoder.CONTENT_TYPE_LOCALS_GET) RestResponse couponList(@ModelAttribute CouponCriteria criteria);}

  启动 Feign 类

  @EnableFeignClients(basePackages = {"com.objcoding"})@SpringCloudApplicationpublic class ProviderApplication {}

  2. 服务降级

  当网络不稳定时,一个接口响应非常慢,就会一直占用这个连接资源,如果长时间不做处理,会导致系统雪崩,幸好,Feign 已经继承了熔断器 Hystrix

  @FeignClient(value = FeignConst.COUPON_PROVIDER, url = "${feign.coupon.url:}", fallback = CouponClient.CouponClientFallBack.class)public interface CouponClient { @GetMapping(value = "/coupon/list/page", headers = LocalsEncoder.CONTENT_TYPE_LOCALS_GET) RestResponse couponList(@ModelAttribute CouponCriteria criteria); @Component class CouponClientFallBack implements CouponClient { @Override public RestResponse couponList(CouponCriteria criteria) { return RestResponse.failed("网络超时"); } }}

  3. 拦截器

  有时候微服务间的调用,需要传递权限信息,这些信息都包含在请求头了,这时我们可以通过 Feign 拦截器实现权限穿透:

  @Configurationpublic class WebRequestInterceptor { @Bean public RequestInterceptor headerInterceptor() { return template -> { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { return; } HttpServletRequest request = attributes.getRequest(); Enumeration headerNames = request.getHeaderNames(); if (headerNames != null) { while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); String values = request.getHeader(name); template.header(name, values); } } }; }}

  以上就是今天内容了,我们下期见。

赞(13)
踩(0)
分享到:
华为认证网络工程师 HCIE直播课视频教程