前言:本文为原创 若有错误欢迎评论!

准备工作

1.新建一个空的maven项目
2.在pom文件中

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <!--要注意springboot的版本要和springcloud对应-->
        <spring-cloud.version>Greenwich.SR3</spring-cloud.version>
        <spring-boot.version>2.1.6.RELEASE</spring-boot.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- springCloud -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- Spring Boot -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.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>

			<!--指定编译时的java版本-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <compilerVersion>${java.version}</compilerVersion>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                    <!-- prevents endPosTable exception for maven compile -->
                    <useIncrementalCompilation>false</useIncrementalCompilation>
                </configuration>
            </plugin>
        </plugins>

    </build>
  • 查看版本匹配:https://start.spring.io/actuator/info

3.下面说的每个部分都是在新建一个这个moudle的子模块

一.注册中心

1.依赖:

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

<!--必须用web启动器 不然会报错-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>

2.配置文件:

server:
  port: 8761

spring:
  application:
    name: ...

eureka:
  instance:
    hostname: localhost
  client:
	#声明自己是个服务端
    registerWithEureka: false
    fetchRegistry: false
    serviceUrl:
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
	server:
      #是否开启自我保护模式,默认为true(开启后因为网络分区故障 微服务本身其实是健康的,此时本不会注销这个微服务)
      enable-self-preservation: true
      #eureka server清理无效节点的时间间隔,默认60000毫秒,即60秒
      eviction-interval-timer-in-ms: 10000

3.在启动类注解:

@EnableEurekaServer

二.注册服务

1.依赖:

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

2.配置文件:

server: 
  port: 877?

eureka: 
  client: 
    serviceUrl: 
      #注册中心地址(必须设置
      defaultZone: http://localhost:8761/eureka/
   	#去注册中心注册的最长时间(可以不设置用默认的)
    registry-fetch-interval-seconds: 5
  instance:
    #心跳时间(可以不设置用默认的)
    lease-renewal-interval-in-seconds: 5
    #过期时间(可以不设置用默认的)
    lease-expiration-duration-in-seconds: 15

#服务的名称
spring: 
  application: 
    name: ???

4.在启动类注解

@EnableDiscoveryClient

三.调用方式ribbon

1.依赖:

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

2.在启动类增加:

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

3.使用:

@Autowrid
private RestTemplate restTemplate;

Object obj=restTemplate.getForObject("http://注册过的服务名/?/?/ ? = ",  .class)

四.调用方式fegin

1.依赖:

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

2.在启动类注解

@EnableFeginClients

3.使用:

  1. 创建一个interface 并注解 (该注解会使自动注入spring容器 就不用再加@Component注解)

    @FeginClient(name=“注册过的服务名”)

  2. 在该接口中要写方法 返回值和提供者controller中对应方法返回值相同 但也可以是提供者方法返回值的一部分 并给方法注解

    @RquestMapping(value=“被注解服务要调用的controller的类的路由/方法的路由”,method=“GET”)

  3. 方法的参数 : @RequestParam(value=“被调用服务方法的参数名”)

    @GetMapping("/api/v1/find")
    String findById(@RequestParam(value=“id”) int id)

  • 注意
    • 参数的类型和参数名一定要和别调用服务的方法的参数类型相同
    • RequestParam 里必须给 value 属性赋值
    • 如果是提供服务的方法返回的对象 但调用者的返回参数的是String类型 则返回的是目标对象的json字符串 用JsonNode obj=ObjectMapper.readTree(str)得对象
  1. 在要使用请求会的目标的类注入上个interface

    @Autowrid
    private …FeginClient feginclient; //@FeginClient注解会使注入spring容器

  • 注意:
    • 路径
    • Http方法必须对应(即:请求方式、参数类型、参数名必须相同)
    • 当被调用服务的方法参数为用@RequestBody注解时,调用方应该使用@PostMapping
    • @RequestParam 里的 value 属性必须赋值

4.超时配置

#修改调用超时时间
feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 2000

5.如果用@FeginClient对此注解同一个被调用的服务在springboot>2.1时 会报多次注入同一个bean的错误 需要加

spring:
  main:
    allow-bean-definition-overriding: true

7.ribbon和feign的选择

  • 选择feign

    1. 默认集成了ribbon
    2. 写起来更加思路清晰和方便
    3. 采用注解方式进行配置,配置熔断等方式方便
  • 使用fegin的正确姿势

    1. 在服务提供者建立一个对外提供服务的moudle 该moudle里有
      1)、服务提供者的实体类(不然返回对象时只有返回json字符串 然后被调用者去解析json字符串)
      2)、对外提供api的多个接口 每个接口对应一个controller类 并且保证类上的@RequestMappeing路径、方法上的路由、方法的参数与接口一致
    2. 在服务调用者依赖提供者对外提供服务的moudle 然后新建多个接口分别继承每个接口 并给继承后接口注解@FeginClient(“服务名”) 之后这个就可以通过这个接口调用提供者的方法
      (如果不这样做那么每个服务的调用者都要知道并且写一遍提供方的:路由、方法名、方法参数)
  • 注意:微服务的每个entiyty实体类都要 implements Serializable

五.Hystrix断路器

1.依赖:

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

2.启动类注解:

@EnableCircuitBreaker

3.使用

  1. 在要调用服务的最外层(即controller层)方法上加注解

    @HystrixCommand(failbackMethod=“失败时调用的方法名”)

  2. 实现失败时调用的方法(注意参数要与被调用服务的方法包括参数名完全一致)

4.Fegin请求时的熔断机制:

  1. 执行完3的全部

  2. 开启feign支持hystrix (旧版本默认支持,新版本默认关闭)

    feign:
      hystrix:
         enabled: true
    
  3. 给fegin的接口加注解

@FeginClient(name=" ",fallback=?.class)

  1. 建一个失败回调类(名字和failback的名字相同) 注解spring、继承Fegin的接口
    请求其他服务失败时
    第一次降级:每个被继承的方 在方法体里写的代码
    第二次降级:执行Fegin是实现类的代码会跳出到执行最外层controller 继续降级 执行failbackMethod

  2. 监控报警机制:
    使用redis缓存(为了获得标识位)
    从redis中取值 判断是否为空
    开启一个子线程
    给redis存值与失效时间
    进行发短信等预警操作

    	//监控报警
        String saveOrderKye = "save-order";
        String sendValue = redisTemplate.opsForValue().get(saveOrderKye);
        final String ip = request.getRemoteAddr();
        new Thread( ()->{
            if (StringUtils.isBlank(sendValue)) {
                System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
                //发送一个http请求,调用短信服务 TODO
                redisTemplate.opsForValue().set(saveOrderKye, "save-order-fail", 20, TimeUnit.SECONDS);
            }else{
                System.out.println("已经发送过短信,20秒内不重复发送");
            }
        }).start();
    

6.超时时间
hystrix引入之后配置超时时间还需要加一个:

feign:
  client:
    config:
      default:
        connectTimeout: 2000
        readTimeout: 2000

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 4000

注意:和fegin集成ribben的超时时间 两个一起才生效

六.网关

1.依赖:

	<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    </dependency>
    <!--注意:还要引入Eureka Discovery来注册-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>

2.配置文件:

  1. 端口号
  2. 应用名
  3. 在Erueka注册

3.在启动类注解

@EnableZuulProxy

注意:@EnableZuulProxy默认集成了@EnableCircuitBreaker

4.设置路径

  1. 服务路由映射
    zuul:
      routes:
        order-service: /apigateway/**
    
  2. 忽略整个服务
    zuul:
      ignored-service: order-service
    
  3. 忽略路径
    zuul:
      ignored-patterns: /*-service/**
    

4.示例

zuul:
  routes:
    login:
      path: /login
      serviceId: shared-parking-mange-auth
      #是否携带前缀 默认true为不带 
      stripPrefix: false
    auth:
      path: /auth/**
      serviceId: shared-parking-mange-auth
      #是否携带前缀 如果不带请求路径是对应微服务的auth后 /**
      stripPrefix: false
    user:
      path: /user/**
      serviceId: shared-parking-mange-user-service
      stripPrefix: false
  #微服务要设置cookie就必须携带host
  add-host-header: true
  #覆盖默认敏感头信息,将其设置为空就可以,不然像 Set-Cookie...这类respone的header无法返回
  sensitive-headers: 

5.自定义过滤器

  1. 新建一个类 并注解【@Component】注入

  2. 如何获取HttpServletRequest

    RequestContext context=RequestContext.getCurrentContext();
    HttpServletRequest request=context.getRequest();
    
  3. extend ZuulFilter 重写四个方法

  4. 在shouldFilter() 里拿到请求的相对路径然后判断是否拦截(如果项目大放在拦截路径放在redis中)

  5. 在run()中 对要拦截的路径进行校验(推荐使用JWT加密算法 可以避免数据库查询)

  6. 校验不通过

    requestContext.setSendZuulResponse(false)
    requestContext.setResponseStatusCode(HttpStatus.?.value()); 
    
  7. run()方法通不通过都return null

    @Component
    /**
    *  注入读取配置文件的实体类:JwtProperties(jwt相关配置)、FilterPropertiess(要拦截的路径)
    *  jwt不会的话参考我的:https://blog.csdn.net/weixin_43934607/article/details/101356581
    * 
    *  jwt:
    *    pubKeyPath: C:\\tmp\\rsa\\rsa.pub # 公钥地址
    *    cookieName: SHOP_TOKEN # cookie的名称
    *  filter:
    *    allowPaths:
    *      - /api/auth
    *      - /api/search
    *      - /api/user/register
    *      - /api/user/check
    *      - /api/user/code
    *      - /api/item
    */
    @EnableConfigurationProperties({JwtProperties.class, FilterPropertiess.class})
    @Component
    public class LoginFilter extends ZuulFilter {
    
    	@Autowired
        private JwtProperties jwtProperties;
    
        @Autowired
        private FilterPropertiess filterPropertiess;
    
    
    	//过滤器类型 一般用pre 最先拦截
        @Override
        public String filterType() {
            return "pre";
        }
    
    	//顺序 值越大越靠前
        @Override
        public int filterOrder() {
            return 10;
        }
    
        @Override
        public boolean shouldFilter() {
        	//获取要拦截的路径
            List<String> allowPaths=filterPropertiess.getAllowPaths();
            //获取HttpServletRequest
            RequestContext context=RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            //获取请求路径
            String url=request.getRequestURI();
    
            for (String allowPath : allowPaths) {
                if(StringUtils.contains(url, allowPath)){
                	//拦截 进入run()
                    return false;
                }
            }
            //不拦截
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
        	 //获取请求路径
            RequestContext context=RequestContext.getCurrentContext();
            HttpServletRequest request = context.getRequest();
            //获取cookie中的token
            String token= CookieUtils.getCookieValue(request,jwtProperties.getCookieName());
    
            try {
                JwtUtils.getInfoFromToken(token,jwtProperties.getPublicKey());
            } catch (Exception e) {
            	//jwt校验失败 不放行
                context.setSendZuulResponse(false);
                context.setResponseStatusCode(HttpStatus.FORBIDDEN.value());
            }
    
            return null;
        }
    
    
    }
    
    

七.链路追踪器 Zipkin

  • 端口 9411

1.安装:

  1. docker run -d -p 9411:9411 openzipkin/zipkin

  2. 访问 http://192.168.56.129:9411/

2.依赖:
注意:是给每个服务引入 不用给网关等引入

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

该依赖里面包含 spring-cloud-starter-sleuth、spring-cloud-sleuth-zipkin

3.配置文件:

#暴露全部监控信息
management:
  endpoints:
    web:
      exposure
        include: "*"

#zipkin所在地址
spring
  zipkin
    base-url: http://localhost:9411/

#zipkin采样百分比
spring
  sleuth
    sampler
      probability: 

八.配置中心

1.git服务器:

  1. n创建一个私有仓库
  2. 创建.yml文件 文件名和注册的每个服务名相同(建议所有服务的命名都带‘-’)

2.配置中心:

  1. 依赖:

    在Spring Initlizar中选 Cloud Config-> Config Server
    注意:还要引入Eureka Discovery来注册

  2. 配置文件(配置中心):

    server:
      port: 9000
    
    eureka:
      client:
         serviceUrl:
            defaultZone: http://localhost:8761/eureka/
    
     #服务的名称
    spring:
      application:
        name: server-config
    #git配置
       cloud:
         config:
           server:
             git:
               uri: (git仓库绝对路径 且去除.git后缀)
               username: ???
               password: ???
     #超时时间
               timeout: 5
     #分支
               default-label: master
    
  3. 启动类注解:

    @EnableConfigServer

  4. 访问方式:如http://localhost:9100/product-service.yml

    /{name}-{profiles}.yml
    /{label}/{name}-{profiles}.yml

    注意:.yml .properties .json设置一个 访问时可以互相转化

    name 服务名称
    profile 环境名称,开发、测试、生产
    lable 仓库分支、默认master分支)

3.每个服务:

  1. 依赖:

    <dependency>
       <groupId>org.springframework.cloud</groupId>
       <artifactId>spring-cloud-config-client</artifactId>
    </dependency>
    
  2. 配置文件改名为:bootstrap.yml (加载顺序的问题)

  3. 配置文件:

    eureka:
      client:
         serviceUrl:
            defaultZone: http://localhost:8761/eureka/
    
     #服务的名称
    spring:
      application:
        name: product-service
    #指定从哪个配置中心读取
      cloud:
        config:
          discovery:
            service-id: 配置中心名称
            enabled: true
          profile: ?
     #建议用lable去区分环境,默认是lable是master分支
        #label: ?
    (profile lable和discovery同级)
    

九.消息总线

  • rabbitmq 15672、5672

1.安装rabbitmq:

docker run -d --name=“rabbitmq” -p 5672:5672 -p 15672:15672 rabbitmq:management

2.依赖:

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-bus-amqp</artifactId>
            </dependency>

3.配置文件(使用配置中心的都加):

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

#暴露全部的监控信息
management:
  endpoints:
    web:
      exposure:
        include:  "*"

4.需要从配置文件读取信息的的类(即有@Value("${}")注解的类要加这个注解才可以读到配置):

@RefreshScope

5.发送刷新消息(post方式):

http://需要刷新的服务ip:该服务端口/actuator/bus-refresh
注意:如果有个服务有多个节点 刷新一个就可以全部刷新该服务其他节点

如果用了配置中心想要启动多个节点 通过改 idea的-Dserver.port:8888 无法再启动多节点 因为bootstarp.yml 先于idea的配置文件 所以只要改了git服务器的 再次启动 idea就可以

十.配置文件改造(使用配置中心与消息总线)

  1. 给服务和网关引入依赖:
<!--配置中心客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

        <!--config server-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

2.把服务和网关配置的所有application.yml内容放到git服务器(名称和每个服务同名 网关也是 可以给每个名字后面加上“-test“者”-dev“作为profile来区分环境)

3.给git服务器每个文件加上:

#服务的名称
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest

#暴露全部的监控信息
management:
  endpoints:
    web:
      exposure:
        include: "*"

4.要用git服务器配置的服务和网关 将配置文件改名为:bootstrap.yml

5.要用git服务器配置的服务和网关 其配置文件只留下:

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#服务的名称
spring:
  application:
    name: order-service
#指定从哪个配置中心读取
  cloud:
    config:
      discovery:
        service-id: 配置中心名称
        enabled: true
      profile: ?

6.给配置中心修改git路径

7.各个项目启动顺序

  1. 注册中心
  2. 配置中心
  3. 对应的服务:商品服务、订单服务。。。
  4. 启动网关

在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐