• 微服务架构实践


    一、微服务架构图:

    二、技术介绍:(技术选型随着代码的编写会完成)

    关于技术选型,我盗了一张我老大的微服务技术栈的图,如下:原文:http://www.jianshu.com/p/2da6becfb019

     我将会用到上图中的如下技术

    • 服务注册和服务发现:consul
    • 服务健康检查:consul
    • 配置管理:consul、archaius
    • 集群容错:hystrix
    • 计数监控:codahale-metrics、java-statsd-client、hystrix-dashboard、turbine、statsd、graphite、grafana
    • 服务路由:ribbon
    • 服务通信:retrofit、AsyncHttpClient(不选择okhttp,是因为okhttp性能比较差)
    • 文档输出:swagger
    • 日志统计:logback+ELK
    • 简化代码:lombok
    • 消息队列:rabbitmq
    • 分布式锁:redis实现和consul实现
    • 本地缓存:guava cache
    • 链路跟踪:zipkin、brave
    • 基本技术:springboot
    • 安全鉴权:auth2、openId connect
    • 自动化构建与部署:gitlab + jenkins + docker + k8s

    三、基本流程:

    1. 各个服务启动的时候,都会将自己的信息注册到consulClient,consulClient将注册信息提交给consulServer,consulServer将信息提交给consulLeader(也是consulServer),consulLeader将自身的数据复制给其他的consulServer,服务注册完成!!!
    2. APP发出一个对gatewayX-server的request,该请求先到nginx,nginx选出一台gatewayX-server的服务器进行request的处理
    3. gatewayX-server通过myserviceA-client.jar来访问myserviceA-server的具体逻辑
      1. 首先从consulServer上拉取可用的myserviceA-server的服务器,服务发现完成!!!
      2. 根据负载均衡策略选出其中一个服务器来进行访问
      3. 访问的过程中通过熔断器来进行超时容错处理
    4. gatewayX-server通过myserviceB-client.jar来访问myserviceB-server的具体逻辑同3

    说明:如果仅仅只是前边这样的流程或者以前边这样的流程为基础并且myserviceB-server要调用myserviceA-server,那么上图中的myserviceB-server中的整个myserviceA-client.jar可以去掉,原因是gatewayX-server已经引入了myserviceA-client.jar。

    如果不是上边的流程,只是单纯的myserviceB-server要访问myserviceA-server,那么需要引入myserviceA-client.jar。

    注意:对于服务发现而言,consulServer会通过gossip协议将服务器数据广播给各个本地consul agent(通常是consulClient),所以我们不需要做本地缓存,当被调用服务的服务器列表发生改变时,会马上广播给consulClient。

    第二章 微服务架构搭建 + 服务启动注册

    一、首先编写微服务基础项目framework

    1、pom.xml

    1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     4 
     5     <modelVersion>4.0.0</modelVersion>
     6     
     7     <parent>
     8         <groupId>org.springframework.boot</groupId>
     9         <artifactId>spring-boot-starter-parent</artifactId>
    10         <version>1.3.0.RELEASE</version>
    11     </parent>
    12 
    13     <groupId>com.microservice</groupId>
    14     <artifactId>framework</artifactId>
    15     <version>1.0-SNAPSHOT</version>
    16     <packaging>jar</packaging>
    17 
    18     <properties>
    19         <java.version>1.8</java.version><!-- 官方推荐 -->
    20     </properties>
    21 
    22     <!-- 引入实际依赖 -->
    23     <dependencies>
    24         <dependency>
    25             <groupId>org.springframework.boot</groupId>
    26             <artifactId>spring-boot-starter-web</artifactId>
    27         </dependency>
    28         <!-- consul-client -->
    29         <dependency>
    30             <groupId>com.orbitz.consul</groupId>
    31             <artifactId>consul-client</artifactId>
    32             <version>0.10.0</version>
    33         </dependency>
    34         <!-- consul需要的包 -->
    35         <dependency>
    36             <groupId>org.glassfish.jersey.core</groupId>
    37             <artifactId>jersey-client</artifactId>
    38             <version>2.22.2</version>
    39         </dependency>
    40         <dependency>
    41             <groupId>com.alibaba</groupId>
    42             <artifactId>fastjson</artifactId>
    43             <version>1.1.15</version>
    44         </dependency>
    45         <dependency>
    46             <groupId>org.springframework.boot</groupId>
    47             <artifactId>spring-boot-starter-actuator</artifactId>
    48         </dependency>
    49         <dependency>
    50             <groupId>org.projectlombok</groupId>
    51             <artifactId>lombok</artifactId>
    52             <version>1.16.8</version>
    53             <scope>provided</scope>
    54         </dependency>
    55     </dependencies>
    56 
    57     <build>
    58         <plugins>
    59             <plugin>
    60                 <groupId>org.springframework.boot</groupId>
    61                 <artifactId>spring-boot-maven-plugin</artifactId>
    62             </plugin>
    63         </plugins>
    64     </build>
    65 </project>

    说明:

    • 上边的<packaging>jar</packaging>可以去掉。因为spring-boot-maven-plugin会打jar包的
    • 引入spring-boot-starter-actuator是为了注册服务的时候可以直接使用"http://localhost:8080/health"进行健康检查。见第二十章 springboot + consul
      • 注意:health的port不是固定的8080,而是服务启动的接口,如果服务是以8090启动,使用"http://localhost:8090/health"来检查

    2、com.microservice.framework.MySpringAplication

     1 package com.microservice.framework;
     2 
     3 import org.springframework.boot.SpringApplication;
     4 import org.springframework.boot.autoconfigure.SpringBootApplication;
     5 
     6 import com.microservice.framework.consul.ConsulRegisterListener;
     7 
     8 /**
     9  * 注意:@SpringBootApplication该注解必须在SpringApplication.run()所在的类上
    10  *
    11  */
    12 @SpringBootApplication
    13 public class MySpringAplication {
    14 
    15     public void run(String[] args) {
    16         SpringApplication sa = new SpringApplication(MySpringAplication.class);
    17         sa.addListeners(new ConsulRegisterListener());
    18         sa.run(args);
    19     }
    20     
    21     public static void main(String[] args) {
    22     }
    23 }

    注意:这里的main方法声明是要有的(否则无法install为jar)。

    3、com.microservice.framework.consul.ConsulRegisterListener

    1 package com.microservice.framework.consul;
     2 
     3 import java.net.MalformedURLException;
     4 import java.net.URI;
     5 
     6 import org.springframework.context.ApplicationListener;
     7 import org.springframework.context.event.ContextRefreshedEvent;
     8 
     9 import com.orbitz.consul.AgentClient;
    10 import com.orbitz.consul.Consul;
    11 
    12 /**
    13  * 监听contextrefresh事件
    14  */
    15 public class ConsulRegisterListener implements ApplicationListener<ContextRefreshedEvent> {
    16 
    17     @Override
    18     public void onApplicationEvent(ContextRefreshedEvent event) {
    19         Consul consul = event.getApplicationContext().getBean(Consul.class);
    20         ConsulProperties prop = event.getApplicationContext().getBean(ConsulProperties.class);
    21 
    22         AgentClient agentClient = consul.agentClient();
    23         try {
    24             agentClient.register(prop.getServicePort(), 
    25                                  URI.create(prop.getHealthUrl()).toURL(),
    26                                  prop.getHealthInterval(), 
    27                                  prop.getServicename(), 
    28                                  prop.getServicename(), // serviceId:
    29                                  prop.getServiceTag());
    30         } catch (MalformedURLException e) {
    31             e.printStackTrace();
    32         }
    33     }
    34 
    35 }

    注意:这个代码是关键,后边会讲改代码的作用。

    其中,ConsulProperties和Consul我们需要在代码中构建成Bean(如下变4和5),之后才能从容器中取出来,否则为null。

    4、com.microservice.framework.consul.ConsulProperties

    1 package com.microservice.framework.consul;
     2 
     3 import org.springframework.beans.factory.annotation.Value;
     4 import org.springframework.stereotype.Component;
     5 
     6 import lombok.Getter;
     7 import lombok.Setter;
     8 
     9 @Component
    10 @Getter @Setter
    11 public class ConsulProperties {
    12 
    13     @Value("${service.name}")
    14     private String servicename;
    15     @Value("${service.port:8080}")
    16     private int servicePort;
    17     @Value("${service.tag:dev}")
    18     private String serviceTag;
    19 //    @Value("${serviceIp:localhost}")
    20 //    private String serviceIp;
    21     
    22     @Value("${health.url}")
    23     private String healthUrl;
    24     @Value("${health.interval:10}")
    25     private int healthInterval;
    26     
    27 }

    注意:

    • 这里使用lombok简化了pojo
    • @value注解中可以指定默认值,查看上边":"后边的值就是

    5、com.microservice.framework.consul.ConsulConfig

    1 package com.microservice.framework.consul;
     2 
     3 import org.springframework.context.annotation.Bean;
     4 import org.springframework.context.annotation.Configuration;
     5 
     6 import com.orbitz.consul.Consul;
     7 
     8 @Configuration
     9 public class ConsulConfig {
    10 
    11     @Bean
    12     public Consul consul(){
    13         return Consul.builder().build();
    14     }
    15 }

    编写完上述代码后,执行"mvn clean install",如果成功的话,此时"framework-1.0-SNAPSHOT.jar"这个jar就会装载到本地的.m2/repository/com/microservice/framework/q.0-SNAPSHOT中了(mac中.m2默认在~下)

    二、开发第一个微服务myserviceA

    像上边所示,我们创建了client和server。

    • server:用于实现具体逻辑
    • client:用于封装server接口(通常就是server模块的controller中的各个url),提供给其他service或gateway甚至是app使用

    1、myserviceA

    pom.xml

    1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     4 
     5     <modelVersion>4.0.0</modelVersion>
     6 
     7     <parent>
     8         <groupId>org.springframework.boot</groupId>
     9         <artifactId>spring-boot-starter-parent</artifactId>
    10         <version>1.3.0.RELEASE</version>
    11     </parent>
    12 
    13     <groupId>com.microservice</groupId>
    14     <artifactId>myserviceA</artifactId>
    15     <version>1.0-SNAPSHOT</version>
    16     <packaging>pom</packaging>
    17 
    18     <properties>
    19         <java.version>1.8</java.version><!-- 官方推荐 -->
    20     </properties>
    21 
    22     <modules>
    23         <module>server</module>
    24         <module>client</module>
    25     </modules>
    26 
    27     <!-- 引入实际依赖 -->
    28     <dependencies>
    29         <dependency>
    30             <groupId>org.springframework.boot</groupId>
    31             <artifactId>spring-boot-starter-web</artifactId>
    32         </dependency>
    33     </dependencies>
    34 </project>

    2、myserviceA-server

    2.1、pom.xml

    1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     4 
     5     <modelVersion>4.0.0</modelVersion>
     6     
     7     <parent>
     8         <groupId>com.microservice</groupId>
     9         <artifactId>myserviceA</artifactId>
    10         <version>1.0-SNAPSHOT</version>
    11     </parent>
    12 
    13     <artifactId>myserviceA-server</artifactId>
    14 
    15     <!-- 引入实际依赖 -->
    16     <dependencies>
    17         <dependency>
    18             <groupId>com.microservice</groupId>
    19             <artifactId>framework</artifactId>
    20             <version>1.0-SNAPSHOT</version>
    21         </dependency>
    22         <dependency>
    23             <groupId>com.alibaba</groupId>
    24             <artifactId>fastjson</artifactId>
    25             <version>1.1.15</version>
    26         </dependency>
    27     </dependencies>
    28 
    29     <build>
    30         <plugins>
    31             <plugin>
    32                 <groupId>org.springframework.boot</groupId>
    33                 <artifactId>spring-boot-maven-plugin</artifactId>
    34             </plugin>
    35         </plugins>
    36     </build>
    37 </project>

    2.2、application.properties

    1 service.name=myserviceA
    2 service.port=8080
    3 service.tag=dev
    4 health.url=http://localhost:8080/health
    5 health.interval=10

    说明:

    • service.name(这是一个service在注册中心的唯一标识)
    • service.port
    • service.tag(该值用于在注册中心的配置管理,dev环境下使用dev的配置,prod下使用prod的配置,配置管理通常使用KV来实现的,tag用于构建Key)
    • health.url(健康检查的url)
    • health.interval(每隔10s ping一次health.url,进行健康检查)

    2.3、com.microservice.myserviceA.MyServiceAApplication

    1 package com.microservice.myserviceA;
     2 
     3 import org.springframework.boot.autoconfigure.SpringBootApplication;
     4 
     5 import com.microservice.framework.MySpringAplication;
     6 
     7 @SpringBootApplication
     8 public class MyServiceAApplication {
     9 
    10     public static void main(String[] args) {
    11         MySpringAplication mySpringAplication = new MySpringAplication();
    12         mySpringAplication.run(args);
    13     }
    14 }

    说明:这里调用了framework中的MySpringAplication的run(),该run()首先初始化了SpringApplication实例,之后为该实例添加ConsulRegisterListener实例,最后再执行SpringApplication的run()。

    ConsulRegisterListener的执行时机见附4 springboot源码解析-run(),简言之,就是

    • run()方法会先构建容器ApplicationContext,之后将各个BeanDefinition装入该容器,最后刷新容器,这时候执行ConsulRegisterListener中的onApplication方法,用于注册service到consul。

    3、myserviceA-client

    pom.xml

    1 <?xml version="1.0" encoding="UTF-8"?>
     2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     3     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
     4     
     5     <modelVersion>4.0.0</modelVersion>
     6     
     7     <parent>
     8         <groupId>com.microservice</groupId>
     9         <artifactId>myserviceA</artifactId>
    10         <version>1.0-SNAPSHOT</version>
    11     </parent>
    12 
    13     <artifactId>myserviceA-client</artifactId>
    14 
    15     <build>
    16         <plugins>
    17             <plugin>
    18                 <groupId>org.springframework.boot</groupId>
    19                 <artifactId>spring-boot-maven-plugin</artifactId>
    20             </plugin>
    21         </plugins>
    22     </build>
    23 </project>

    该client以后在需要用到的时候完成。

    测试:启动consul,开发环境下,直接使用"consul agent -dev"快速启动,查看consul UI,如下:

     

    启动"myserviceA-server",启动完成后,查看consul UI,如下:

    表示注册成功,我们还可以查看myserviceA的健康检查URL,如下:

    以上就完成了基本微服务架构的搭建与服务启动时自动注册!

    第一章 微服务网关 - 入门

    一、什么是服务网关

    服务网关 = 路由转发 + 过滤器

    1、路由转发:接收一切外界请求,转发到后端的微服务上去;

    2、过滤器:在服务网关中可以完成一系列的横切功能,例如权限校验、限流以及监控等,这些都可以通过过滤器完成(其实路由转发也是通过过滤器实现的)。

    二、为什么需要服务网关
    上述所说的横切功能(以权限校验为例)可以写在三个位置:

    • 每个服务自己实现一遍
    • 写到一个公共的服务中,然后其他所有服务都依赖这个服务
    • 写到服务网关的前置过滤器中,所有请求过来进行权限校验

    第一种,缺点太明显,基本不用;
    第二种,相较于第一点好很多,代码开发不会冗余,但是有两个缺点:

    • 由于每个服务引入了这个公共服务,那么相当于在每个服务中都引入了相同的权限校验的代码,使得每个服务的jar包大小无故增加了一些,尤其是对于使用docker镜像进行部署的场景,jar越小越好;
    • 由于每个服务都引入了这个公共服务,那么我们后续升级这个服务可能就比较困难,而且公共服务的功能越多,升级就越难,而且假设我们改变了公共服务中的权限校验的方式,想让所有的服务都去使用新的权限校验方式,我们就需要将之前所有的服务都重新引包,编译部署。

    而服务网关恰好可以解决这样的问题:

    • 将权限校验的逻辑写在网关的过滤器中,后端服务不需要关注权限校验的代码,所以服务的jar包中也不会引入权限校验的逻辑,不会增加jar包大小;
    • 如果想修改权限校验的逻辑,只需要修改网关中的权限校验过滤器即可,而不需要升级所有已存在的微服务。

    所以,需要服务网关!!!

    三、服务网关技术选型

    引入服务网关后的微服务架构如上,总体包含三部分:服务网关、open-service和service。

    1、总体流程:

    • 服务网关、open-service和service启动时注册到注册中心上去;
    • 用户请求时直接请求网关,网关做智能路由转发(包括服务发现,负载均衡)到open-service,这其中包含权限校验、监控、限流等操作
    • open-service聚合内部service响应,返回给网关,网关再返回给用户

    2、引入网关的注意点

    • 增加了网关,多了一层转发(原本用户请求直接访问open-service即可),性能会下降一些(但是下降不大,通常,网关机器性能会很好,而且网关与open-service的访问通常是内网访问,速度很快);
    • 网关的单点问题:在整个网络调用过程中,一定会有一个单点,可能是网关、nginx、dns服务器等。防止网关单点,可以在网关层前边再挂一台nginx,nginx的性能极高,基本不会挂,这样之后,网关服务就可以不断的添加机器。但是这样一个请求就转发了两次,所以最好的方式是网关单点服务部署在一台牛逼的机器上(通过压测来估算机器的配置),而且nginx与zuul的性能比较,根据国外的一个哥们儿做的实验来看,其实相差不大,zuul是netflix开源的一个用来做网关的开源框架;
    • 网关要尽量轻。

    3、服务网关基本功能

    • 智能路由:接收外部一切请求,并转发到后端的对外服务open-service上去;
      • 注意:我们只转发外部请求,服务之间的请求不走网关,这就表示全链路追踪、内部服务API监控、内部服务之间调用的容错、智能路由不能在网关完成;当然,也可以将所有的服务调用都走网关,那么几乎所有的功能都可以集成到网关中,但是这样的话,网关的压力会很大,不堪重负。
    • 权限校验:只校验用户向open-service服务的请求,不校验服务内部的请求。服务内部的请求有必要校验吗?
    • API监控:只监控经过网关的请求,以及网关本身的一些性能指标(例如,gc等);
    • 限流:与监控配合,进行限流操作;
    • API日志统一收集:类似于一个aspect切面,记录接口的进入和出去时的相关日志
    • 。。。后续补充

    上述功能是网关的基本功能,网关还可以实现以下功能:

    • A|B测试:A|B测试时一块比较大的东西,包含后台实验配置、数据埋点(看转化率)以及分流引擎,在服务网关中,可以实现分流引擎,但是实际上分流引擎会调用内部服务,所以如果是按照上图的架构,分流引擎最好做在open-service中,不要做在服务网关中。
    • 。。。后续补充 

    4、技术选型

    笔者准备自建一个轻量级的服务网关,技术选型如下:

      • 开发语言:java + groovy,groovy的好处是网关服务不需要重启就可以动态的添加filter来实现一些功能;
      • 微服务基础框架:springboot;
      • 网关基础组件:netflix zuul;
      • 服务注册中心:consul;
      • 权限校验:jwt;
      • API监控:prometheus + grafana;
      • API统一日志收集:logback + ELK;
      • 压力测试:Jmeter;
      • 。。。后续补充

    第二章 微服务网关基础组件 - zuul入门

    1、作用

    zuul使用一系列的filter实现以下功能

    • 认证和安全 - 对每一个resource进行身份认证
    • 追踪和监控 - 实时观察后端微服务的TPS、响应时间,失败数量等准确的信息
    • 日志 - 记录所有请求的访问日志数据,可以为日志分析和查询提供统一支持
    • 动态路由 - 动态的将request路由到后端的服务上去
    • 压力测试 - 逐渐的增加访问集群的压力,来测试集群的性能
    • 限流 - allocating capacity for each type of request and dropping requests that go over the limit
    • 静态响应 - 直接在网关返回一些响应,而不是通过内部的服务返回响应

    2、组件:

    • zuul-core:library which contains the core functionality of compiling and executing Filters
    • zuul-netflix:library which adds other NetflixOSS components to Zuul - using Ribbon for routing requests, for example.

    3、例子:

    • zuul-simple-webapp:webapp which shows a simple example of how to build an application with zuul-core
    • zuul-netflix-webapp:webapp which packages zuul-core and zuul-netflix together into an easy to use package

    github地址:https://github.com/Netflix/zuul/

    二、zuul filter

    1、关键元素

    • Type:most often defines the stage during the routing flow when the Filter will be applied (although it can be any custom string)
      • 值可以是:pre、route、post、error、custom
    • Execution Order: filter执行的顺序(applied within the Type, defines the order of execution across multiple Filters)
    • Criteria:filter执行的条件(the conditions required in order for the Filter to be executed)
    • Action: filter执行的动作(the action to be executed if the Criteria is met)

    注意点:

    • filters之间不会直接进行通讯交流,他们通过一个RequestContext共享一个state
      • 该RequestContext对于每一个request都是唯一的
    • filter当前使用groovy来写的,也可以使用java
    • The source code for each Filter is written to a specified set of directories on the Zuul server that are periodically polled for changes
    • zuul可以动态的read, compile, and run these Filters
      • 被更新后的filter会被从disk读取到内存,并动态编译到正在运行的server中,之后可以用于其后的每一个请求(Updated filters are read from disk, dynamically compiled into the running server, and are invoked by Zuul for each subsequent request)

    2、filter type(与一个典型的request的生命周期相关的filter type)

    • PRE Filters
      • 执行时机: before routing to the origin.
      • 这类filter可能做的事
        • request authentication
        • choosing origin servers(选机器)
        • logging debug info.
        • 限流
    • ROUTING Filters
      • 这类filter可能做的事:真正的向service的一台server(这台server是pre filter选出来的)发请求,handle routing the request to an origin,This is where the origin HTTP request is built and sent using Apache HttpClient or Netflix Ribbon.
    • POST Filters
      • 执行时机:after the request has been routed to the origin
      • 这类filter可能做的事
        • adding standard HTTP headers to the response
        • gathering statistics and metrics
        • streaming the response from the origin to the client
    • ERROR Filters
      • 执行时机:其他三个阶段任一阶段发生错误时执行(when an error occurs during one of the other phases)
    • CUSTOM Filters
      • 沿着默认的filter流,zuul允许我们创建一些自定义的Filter type,并且准确的执行他们。
      • 例如:我们自定义一个STATIC type的filter,用于从zuul直接产生响应,而不是从后边的services(we have a custom STATIC type that generates a response within Zuul instead of forwarding the request to an origin)

    三、zuul request lifecycle(filter流)

    说明:对应(二)的filter type来看

    四、zuul核心架构

    zuul的核心就是:filter、filter流与核心架构。这些在下一章会以代码的形式做展示。

  • 相关阅读:
    [转]进程间通信----pipe和fifo
    [转]udev
    [转]netlink
    [转]进程间通信-----管道
    [转]socket
    [转]armv8 memory system
    [转]内核态和用户态
    [转]dpdk内存管理
    meeting and robert rules
    notion
  • 原文地址:https://www.cnblogs.com/hanease/p/16305422.html
Copyright © 2020-2023  润新知