微服务

1 微服务简介

1.1 项目演变过程

项目演变过程分为三个阶段:传统项目——>面向服务架构(SOA)——>微服务

传统项目其实就是基于MVC架构的单点应用,将所有的业务逻辑、数据访问层、控制层都放到一个项目中。优点:适合小项目小团队开发,不适用于大的项目开发。为什么?因为一个大的项目肯定有很多人一起开发,每个人需要开发一些功能然后整合到项目中,比如两个人同时修改了同一个类或者一个功能,在利用git或者SVN进行版本控制的时候,解决代码冲突问题就是一个很大的难题。

紧接着,分布式项目架构产生了,他根据业务需求将一个大的项目拆分成N多个子系统,每个子系统独立完成自己的业务功能,而每个系统之间的通信则采用RPC远程调用进行通讯,多个子系统相互协作完成一个业务流程。这样的好处的话呢,就是把模块拆分,使用接口通信,降低了模块之间的耦合度。项目进行拆分,不同团队负责不同的项目,代码冲突问题基本没有。增加业务功能只需要再增加一个子项目即可,然后调用其他的系统接口就OK。甚至可以进行灵活的分布式部署。有利也有弊,因为接口之间需要通信,网络延迟必不可少而且需要开发一些接口调用功能代码,所以无疑增加了开发工作量。每个子系统之间可能有一些相同的功能,但是由于一些原因可能每个子系统都要开发这部分代码,造成某些公共模块无法共用,其实说起来也是开发量的问题。

于是SOA(面向服务架构)出现了,为了解决上述分布式项目的问题,他会一个工程分成服务层、表现层两个工程。服务层中包含业务逻辑,对外暴露一些调用接口(其实就是服务),而表现层只需要处理和页面的交互,表现层通过rpc调用相应的接口即可完成底层的业务流程,让每个组件都独立离散、自治。主要针对企业级、采用ESB服务(ESB企业总线服务),非常重、需要序列化和反序列化,XML格式传输(webservice)。优点:简化维护,降低整体风险,伸缩灵活。。。举个例子他会将一个项目分为订单、支付、注册这样的系统,每个系统之间采用RPC远程调用进行通信(其实就是共同完成一整个业务流程)。

微服务架构的话呢?其实和SOA差不多,他的思想源于SOA服务架构,只不过他更加细分、粒度比较细、轻量独立运行采用http+json格式进行传输(restful风格)。各服务间隔离、自治。

1.2 一些名词

1.2.1 RPC

RPC 的全称是 Remote Procedure Call 是一种进程间通信方式。
它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即无论是调用本地接口/服务的还是远程的接口/服务,本质上编写的调用代码基本相同。

RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
RPC 会隐藏底层的通讯细节(不需要直接处理Socket通讯或Http通讯)
RPC 是一个请求响应模型。客户端发起请求,服务器返回响应(类似于Http的工作方式)
RPC 在使用形式上像调用本地函数(或方法)一样去调用远程的函数(或方法)。

1.2.2 Restful

restful是一种架构设计风格,提供了设计原则和约束条件,而不是架构。而满足这些约束条件和原则的应用程序或设计就是 RESTful架构或服务。

1.2.3 soap

soap象访问协议是一种数据交换协议规范,
是一种轻量的、简单的、基于XML的协议的规范。SOAP协议和HTTP协议一样,都是底层的通信协议,只是请求包的格式不同而已,SOAP包是XML格式的。
基于xml并封装成了符合http协议,因此,它符合任何路由器、 防火墙或代理服务器的要求。
soap可以使用任何语言来完成,只要发送正确的soap请求即可,基于soap的服务可以在任何平台无需修改即可正常使用。

1.2.4 rpc远程调用框架

几种比较典型的RPC的实现和调用框架。 
(1)RMI实现,利用java.rmi包实现,基于Java远程方法协议(Java Remote Method Protocol) 和java的原生序列化。 
(2)Hessian,是一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 基于HTTP协议,采用二进制编解码。 
(3)thrift是一种可伸缩的跨语言服务的软件框架。thrift允许你定义一个描述文件,描述数据类型和服务接口。依据该文件,编译器方便地生成RPC客户端和服务器通信代码。

(4)SpringCloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等。

(4) Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。

2 spring cloud

spring cloud其实就是一套微服务的解决方案,或者说RPC的远程调用框架。只不过他为开发人员提供了构建分布式系统的一些工具,包含配置管理、注册中心、路由策略、断路器、接口网关、服务发现注册、全局锁、分布式会话、反向代理、负载均衡等。

2.1 服务注册与发现

提供方:提供服务

消费方:消费服务

注册中心:管理发现服务

通讯:消费方要调用提供方的服务,首先需要到注册中心获取到提供方的服务(比如service_name),然后根据这个名称找到相应的接口地址,再直接和提供方进行通信。

1、搭建注册中心(Eureka注册中心)

依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itboy</groupId>
    <artifactId>eureka-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-server</name>
    <description>注册中心</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置文件:

server:
  port: 8888
eureka:
  instance:
    hostname: localhost
  client:
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

启动类:

package com.itboy.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

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

启动后访问http://localhost:8888/出现web页面即搭建成功。

2、创建服务提供方法

按照1中的方式在新建一个项目,提供一个web接口。

配置文件:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka/ #注册中心的地址
server:
  port: 8762
spring:
  application:
    name: service-member   #该服务的名称,以后访问只需要改名称即可访问该项目的功能

启动类注解改为:

@EnableEurekaClient

controller:

@RestController
public class MEMController {
    @RequestMapping("/member")
    public List<String> member(){
        ArrayList<String> list = new ArrayList<>();
        list.add("123445");
        list.add("zhangsan");
        list.add("judia");
        return list;
    }
}

启动后访问:http://localhost:8762/member看到输出信息,并且刷新http://localhost:8888/之后能看到刚才添加的服务。

3、服务消费者

一般来说springcloud使用rest、feign、ribbon(负载均衡)来调用服务。下面使用ribbon

基本上和服务提供者搭建以下,只不过改下地址即可,这里不粘贴。

@Service
public class OrderService {
    @Autowired
    private RestTemplate restTemplate;
    public List<String> getMember(){
        return restTemplate.getForObject("http://service-member/member",List.class);
    }
}

如此调用服务即可。

@SpringBootApplication
@EnableEurekaClient
public class EurekaOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaOrderApplication.class, args);
    }
    @Bean
    @LoadBalanced //负载均衡
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

访问:http://localhost:8764/member返回和http://localhost:8762/member即可调用成功。

2.2 spring cloud实现负载均衡

一般来说实现负载均衡的有nginx、lvs、haproxy、F5等工具,而在spring cloud中,实现负载均衡时利用ribbon在客户端实现的。

1、对于每个注册到注册中心的服务都有一个服务名称,相同服务的集群使用同一个服务名称

2、当有一个消费服务的客户端需要调用另一个服务提供方的服务时,他首先从注册中心根据服务名称(service-pay)获取到服务的地址

3、如果是多个地址,在客户端就会使用ribbon这个客户端工具根据负载均衡策略进行分发到相应的服务器进行服务调用。

ribbon是一个负载均衡客户端 类似nginx反向代理,可以很好的控制http和tcp的一些行为。Feign默认集成了ribbon。

按照上述要求搭建这样的一个集群服务

    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

然后在订单服务消费方利用@LoadBanlanced这个注解实现RestTemplate调用的负载均衡。

restTemplate.getForObject("http://service-member/member",List.class);

根据service-member这个服务名从注册中心找到服务名称对应的地址,进行服务调用。而这些都需要ribbon这个负载均衡客户端的支持,所以如果没有依赖请先添加依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

访问:http://127.0.0.1:8888/可以看到有两个服务,但是其中service-member有两个地址,所以其实看起来和nginx差不多,就是根据服务名然后轮询获取地址进行调用。访问http://127.0.0.1:8764/member并不断刷新,可以看到在两台服务器之间不停的切换。最好加一个识别,比如端口这样:

 @Value("${server.port}")
    private String serverPort;
    @RequestMapping("/member")
    public List<String> member(){
        ArrayList<String> list = new ArrayList<>();
        list.add("123445");
        list.add("zhangsan");
        list.add("judia");
        list.add("serverPort:"+serverPort);
        return list;
    }

效果更明显。

2.2.1 feign客户端调用工具

上面已经看到了rest方式的客户端调用,而且使用ribbon的@LoadBanlance实现负载均衡。

不过在真实项目中一般是feign完成这一部分的工作,feign其实就是一个声明式的伪http客户端,它使得http客户端变得简单。

使用这个东西,只需要编写一个接口并注解,具有可插拔的注解特性、默认集成了ribbon实现了负载均衡

添加依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itboy</groupId>
    <artifactId>eureka-order-feign</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>eureka-order-feign</name>
    <description>feign订单服务</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</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>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

service:

@Service
@FeignClient(serviceId = "service-member") //指明调用服务的服务名称
public interface FeignOrderService {
    @RequestMapping("/member") //在目标服务器上的访问路径
    List<String> member();
}

controller:

@RestController
public class FeignOrderController {
    @Autowired
    private FeignOrderService feignOrderService;
    @RequestMapping("/member")
    public List<String> member(){
        return feignOrderService.member();
    }
}

启动类:

@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class EurekaOrderFeignApplication {

    public static void main(String[] args) {
        SpringApplication.run(EurekaOrderFeignApplication.class, args);
    }
}

启动后访问http://localhost:8765/member可以看到实现了负载均衡。

2.3 接口网关

如图所示,对于客户端(搭建在client.service.com这个域名下),它利用ajax调用支付服务和订单服务,因为调用方和提供方在不同的域名下,所以肯定是有跨域问题的。并且这样暴露的话,以后如果提供方需要修改域名,调用方也得修改,增加了项目之间的耦合度。

因此接口网关出现了,类似nginx的反向代理、接口网关。他根据不同的项目名来区分真实服务,比如现在调用订单服务,他不再是走order.service.com而是走www.service.com/order进行调用然后zuul再进行转发到真实的服务地址。这样,以后增加修改项目都只需要修改一下网关即可,降低了项目之间的耦合度。至于跨域问题,就更好解决了,我只需要将接口网关www.service.com这个域名加入自己的白名单不就万事大吉了吗??

总的来说,接口网关其实就是拦截所有请求,然后路由转发到真实的服务器,作用即为路由、过滤。

首先在上述搭建好的每个服务中增加类似这样的一个接口用来区分布通的服务

    @RequestMapping("/orderService")
    public String pay(){
        return "这是订单服务项目";
    }

1、搭建接口网关服务

添加依赖:如果是idea开发直接勾选zuul和eureka server即可。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itboy</groupId>
    <artifactId>service-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-zuul</name>
    <description>zuul接口网关</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</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>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置文件:

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka/
server:
  port: 8769
spring:
  application:
    name: service-zuul
zuul:
  routes:
    api-a:
      path: /api-member/**   #类似反向代理中upstream后的名称,就是地址中以这个开头就转发到下面service-id对应的服务名称
      service-id: service-member #订单服务,与你注册到注册中心的服务一定对应起来
    api-b:
      path: /api-order/**
      service-id: service-order

启动类:

@SpringBootApplication
@EnableZuulProxy
@EnableEurekaClient
public class ServiceZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceZuulApplication.class, args);
    }
}

启动后访问http://127.0.0.1:8888/可以看到服务应注册到注册中心,访问http://127.0.0.1:8769/api-order/orderService跳转到订单服务,访问http://127.0.0.1:8769/api-member/payService跳转到支付服务。

访问http://127.0.0.1:8769/api-member/member后重新加载可以看到这个zuul居然默认实现了负载均衡????搞定!!!!!!!!!

以上就实现了zuul接口网关的路由功能,接下来实现拦截功能,为什么有拦截?可能我的有些服务不想给另一些服务访问,增加一些安全性嘛,这是必须的。比如说通过token来认证,虽然说这个可以放在真实的服务中,但是如果每个服务都要写这个功能,如果有几千个服务,那这些有点忒麻烦了,所以可以将这个拦截功能交给接口网关实现。

在zuul项目中添加如下代码:

@Component
public class MyFilter extends ZuulFilter {
    private static Logger log = LoggerFactory.getLogger(MyFilter.class);

    @Override
    public String filterType() {
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * 真实的过滤器
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        RequestContext context=RequestContext.getCurrentContext();
        HttpServletRequest request = context.getRequest();
        log.info("%s >>> %s",request.getMethod(),request.getRequestURL().toString());
        String token = request.getParameter("token");//获取token
        if(token!=null&&token.equals("123456")){
            return null;//放行
        }
        log.warn("token 为空或者不正确");
        context.setSendZuulResponse(false);
        context.setResponseStatusCode(401);
        try{
            context.getResponse().setCharacterEncoding("utf-8");
            context.getResponse().getWriter().write("token为空或者不正确");
        } catch (Exception e){

        }
        return null;
    }
}

重新启动项目,访问http://127.0.0.1:8769/api-member/payService?token=123456可以正确访问,可是token为空或者不等于123456则访问失败,返回错误页面。。

2.4 分布式配置中心

一般项目在不同环境使用的配置文件是不同的,比如开发环境使用dev,测试环境使用test、生产环境使用prod文件。

为什么需要分布式配置文件中心呢?其实很简单,比如我现在订单服务需要调用某一个服务比如支付服务,可是支付服务的服务名称改变了不是service-pay而是service-pay2。那么我以前配置在订单服务配置文件中的名称就必须要改,改完需要重新启动(发布)项目。因为这些文件读取之后是放在永久区、static修饰的嘛毕竟一般情况加载一次就心里,加载多次影响性能。

这看起来就很不爽,改个配置文件就要重新发布版本,所以spring cloud提供了分布式配置文件中心这个东东来处理这个问题。

那如果没有spring cloud config怎么解决呢?有一种说法是放在数据库中,这样修改数据库之后他也能读到这个改变。

不过既然用了spring cloud所以肯定是要物尽其用的嘛,首先搭建一个service-config服务来管理这些配置文件(这个本身就是一个服务,在为服务中,万物接可作为服务),这个service-config仅仅是起到一个缓存git远程配置文件信息的作用,真实文件是存放在git远程的。

这样订单服务需要读取配置文件信息时,他只需要向service-config请求即可读取到这个服务的缓存的配置文件信息,而如果Git上修改文件后,该服务只需要刷新一下缓存即可读取到最新的配置文件信息,避免了版本发布,实现了实时更新读取。

2.4.1 搭建分布式配置中心

1、使用gitee新建一个仓库,可以直接使用我的这个https://gitee.com/LoseMyFutrue/config-repo-demo,项目上传配置文件,存放如下信息:

info:
  userName: dev.service

2、新建一个server-config服务,使用idea最后勾选config-server即可。

依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itboy</groupId>
    <artifactId>config-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config-server</name>
    <description>分布式配置文件中心</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</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>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置文件:

spring:
  application:
    name: config-server
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/LoseMyFutrue/config-repo-demo.git #git远程项目地址
          username: #访问git仓库的用户名
          password: #访问git仓库的用户密码
          search-paths: respo #配置仓库路径
      label: master #配置仓库的分支
server:
  port: 8889

启动类:

@SpringBootApplication
@EnableConfigServer
public class ConfigServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(ConfigServerApplication.class, args);
    }
}

启动后从启动日志里看到很多的URL映射,那些就是操作配置文件的,比如根据他的Mapped {[/{label}/{name}-{profiles}.yml || /{label}/{name}-{profiles}.yaml]规则填写好URL访问http://127.0.0.1:8889/master/config-client-dev.yml即可看到我的配置文件信息。其他的这里不细说。

3、写个config-client来读取配置文件内容?

依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itboy</groupId>
    <artifactId>config-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>config-client</name>
    <description>分布式配置文件中心客户端</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</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>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>


</project>

配置文件bootstrap.properties,文件名不能改,后缀也不能

spring.application.name=config-client
spring.cloud.config.label=master
spring.cloud.config.profile=dev
spring.cloud.config.uri= http://localhost:8889/
server.port=8881

启动类:

@SpringBootApplication
@RestController
public class ConfigClientApplication {

    @Value("${info.userName}")
    private String userName;

    @RequestMapping("/getUserName")
    public String getUserName(){
        return userName;
    }

    public static void main(String[] args) {
        SpringApplication.run(ConfigClientApplication.class, args);
    }
}

启动后访问http://127.0.0.1:8881/getUserName可以查看到配置文件中的信息。高可用和动态刷新请往这里

2.5 断路器(hystrix)

为什么有这个东西?在为服务架构中,业务被拆分成一个个的服务,服务与服务之间可以相互调用,为了保证高可用,单个服务又必须集群部署。由于网络延迟或者自身的问题,服务并不能保证100%可用,如果是某个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务累积,导致服务瘫痪,甚至“雪崩”。为此hystrix出现了。

hystrix就是一个帮助解决分布式系统交互时超时处理和容错的类库,同样她也拥有保护系统的能力。

雪崩?分布式系统中经常出现某个基础服务不可用造成整个系统不可用的情况,这种情况就被称为服务雪崩。

如上图所示

1、搭建这样一个微服务集群,此时某个客户端需要调用订单服务的/order/getOrderUsers查询用户信息,所以订单服务又去调用用户管理服务的/user/getUsers/这个接口。

2、因为订单服务的最大并发数为50个,同一时刻50个请求都要调用/order/getOrderUsers接口服务,由于用户管理服务出现bug或者网络延迟,迟迟没有响应,这50个请求会一直霸占这些链接。导致其他的新连接处于阻塞状态,比如某一个新连接此时需要调用/order/addOrder来创建订单就只能等待先开始的50个连接关闭后方能处理这个请求。

3、对于2的这种单个基础服务导致整个服务不可用的问题,就是“雪崩”。

对于这种雪崩效应,一种常见的做法就是服务降级(就是对于那些迟迟不能响应的连接,默认给他们一个响应,比如说“唉,你超时了。”或者“程序中出现了错误”),这样保证了其他服务能继续服务。

2.5.1 模拟“雪崩”场景

1、利用线程休眠模拟程序运行,以达到延时的效果,代码就不用粘贴完整的了

用户管理服务:

    @RequestMapping("/member")
    public List<String> member(){
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ArrayList<String> list = new ArrayList<>();
        list.add("123445");
        list.add("zhangsan");
        list.add("judia");
        list.add("count:"+(++count));
        list.add("serverPort:"+serverPort);
        return list;
    }

订单服务:

    @Autowired
    private FeignOrderService feignOrderService;
    @RequestMapping("/member")
    public List<String> member(){
        return feignOrderService.member();
    }

    @RequestMapping("/addOrder")
    public String addOrder(){
        return "订单创建成功!!!!!";
    }

2、之后在订单服务(消费方)设置一下tomcat最大并发数为50,超时时间设长一点,不然的话会返回404,因为他默认请求超时是1s

server:
  port: 8765
  tomcat:
    max-threads: 50
#请求处理的超时时间
ribbon:
  ReadTimeout: 1200000
#请求连接的超时时间
  ConnectTimeout: 300000

3、访问http://localhost:8765/member可以看到3秒后才能看到结果

4、使用Jmeter工具压测,这里不介绍这个工具使用。设置线程数100,然后请求访问http://localhost:8765/member这个地址,之后立马访问http://localhost:8765/addOrder这个秒出结果的地址。可以看到http://localhost:8765/addOrder这个本来是秒出的,但是由于达到了线程的最大并发数,所以只能等待那些占用连接的请求处理完才继续处理,这就是雪崩。

那怎么解决呢?

1、超时机制——服务降级处理(比如上述的超时时间设置,如果为1s那么超过1s的就会返回404)

2、服务降级——当服务接口发生错误或者超时时,就不再去调用目标接口,而是调用本地方法fallback

3、熔断机制——就像家庭使用的保险丝一样,如果请求次数过大或者过慢,那么就熔断该服务的调用,对于后续请求不再调用目标服务,而是直接返回,快速释放资源,等以后情况好转之后再继续调用。

4、隔离机制——因为每个服务之间共享一定的线程数嘛,如果某一个接口全部占用了就会影响其他的接口服务,所以可不可以将所有接口隔离开来,互不影响呢?

5、限流机制——这种方式其实和熔断机制差不多,不过这是一个预防措施,熔断机制偏向于出错以后的容错处理,而限流则是避免出错。如果高于该服务接口的调用的阈值,直接返回,不再调用后续资源,当然这个不解决服务的依赖问题,不过解决了资源分配的问题。当然那些没有被限流的接口依然是有可能造成“服务雪崩”的。

2.5.2 使用hystrix实现服务降级

添加依赖:

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

超时调用方法:

@Component
public class FeignOrderFallback implements FeignOrderService {
    @Override
    public List<String> member() {
        List<String> listUser = new ArrayList<String>();
        listUser.add("服务调用或者超时。。。");
        return listUser;
    }
}

feign方式:

@Service
@FeignClient(serviceId = "service-member",fallback = FeignOrderFallback.class)
public interface FeignOrderService {
    @RequestMapping("/member")
    List<String> member();
}

rest方式:

@HystrixCommand(fallbackMethod = "orderError")
public List<String> getOrderUserAll() {
return restTemplate.getForObject("http://service-member/member", List.class);
}

public List<String> orderError() {
	List<String> listUser = new ArrayList<String>();
	listUser.add("服务调用或者超时。。。");
	return listUser;
}

配置文件:

feign:
   hystrix:
     enabled: true  #启用hystrix
hystrix:
###超时时间
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000

 

启动类加上@EnableHystrix注解接口,此时访问http://localhost:8765/member可看到调用fallback方法。hystrix超时一般设置为10秒比较正常。

此时使用Jmeter工具压测按照上述方法测试一下,发现解决了!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值