SpringCloud组件配置解析【yml分析】

  • Eureka单机

Eureka客户端【client】配置

​
server:

  port: 8080

spring:

  application:

    name: eureka-client-a

#eureka客户端配置

eureka:

  client:

    service-url:

      defaultZone: http://localhost:8761/eureka   #指定服务端

    register-with-eureka: true    #是否向eureka-server注册

    fetch-registry: true      #是否从注册中心拉取注册列表【关闭后将无法查找到其他已注册的服务】

    registry-fetch-interval-seconds: 10       #为了缓解服务列表脏读问题,配置拉取间隔,默认为30秒[间隔过小会浪费性能]

  instance:

    hostname: localhost     #主机名称

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

    prefer-ip-address: true     #是否显示IP

    lease-renewal-interval-in-seconds: 10     #续约时间间隔

​

浏览器访问:

http://localhost:8761

Eureka服务端【server】配置【单server版本】

server:

  port: 8761

spring:

  application:

    name: eureka-server

eureka:

  server:

    eviction-interval-timer-in-ms: 10000   #定期删除操作的间隔

    renewal-percent-threshold: 0.85    #当85%的服务没有续约时,eureka进行数据保护而不再删除

  instance:   #实例配置

#    instance-id: localhost:eureka-server:8761    #主机名:应用名:端口

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}    #主机名:应用名:端口

    hostname: localhost                             #主机名或服务IP

    prefer-ip-address: true                           #以IP形式显示具体服务信息

    lease-renewal-interval-in-seconds: 5               #服务实例续约的时间间隔[单位秒]eureka给自己续约【要小于上方的定期删除时间间隔】

server日志:【每隔十秒进行一次删除注册】                                                                                                                         驱逐任务

2023-03-04 17:09:25.012  INFO 1908 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 3ms

2023-03-04 17:09:35.019  INFO 1908 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 6ms

2023-03-04 17:09:45.027  INFO 1908 --- [a-EvictionTimer] c.n.e.registry.AbstractInstanceRegistry  : Running the evict task with compensationTime 7ms

Eureka集群

Eureka服务端【server】配置

server:

  port: 8761

spring:

  application:

    name: eureka-server

eureka:

  client:

    service-url:

      defaultZone: http : //peer2:8762/eureka , http : //peer3:8763/eureka       【这个server将自己注册进入集群内其他两个server,搭建成集群】

  instance:

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

    hostname: peer1                                                                                            【本server所在电脑IP:peer1】

    prefer-ip-address: true

    lease-renewal-interval-in-seconds: 5

释:

localhost被改成了peer1,peer2等,这是因为要建立集群需要多个电脑,于是在 hosts 文件内进行了分片配置来模拟

127.0.0.1 peer1

127.0.0.1 peer2

127.0.0.1 peer3

像这样模拟了三台电脑,IP为:peer1,peer2,peer3

浏览器访问:

http://localhost:8761

http://localhost:8762

http://localhost:8763

Eureka客户端【client】配置【集群版本】

server:

  port: 8080

spring:

  application:

    name: eureka-client-a

eureka:       #eureka客户端配置

  client:

    service-url:

      defaultZone: http : //peer1:8761/eureka                                             【该client注册入的server为所在电脑IP为 peer1】

    register-with-eureka: true      #是否向eureka-server注册

    fetch-registry: true      #是否从注册中心拉取注册列表【关闭后将无法查找到其他已注册的服务】

    registry-fetch-interval-seconds: 10       #为了缓解服务列表脏读问题,配置拉取间隔,默认为30秒[间隔过小会浪费性能]

  instance:

    hostname: localhost     #主机名称

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

    prefer-ip-address: true

    lease-renewal-interval-in-seconds: 10

释:

将客户端注入IP为peer1的电脑上的server,

因为该server搭建了集群,所以注册进入这个server后,

其他集群内的server【peer2,peer3】会在扩散作用下也获取到这个client的信息

为了防止注册入的这个server故障,可以将这个client注册进入三个server:【不清楚这种情况下置扩散于何地  黑人问号.jpg 】

defaultZone: http : //peer1:8761/eureka,http : //peer2:8762/eureka,http : //peer3:8763/eureka

提及知识点:

分布式数据一致性协议:

协议:raft,Paxos

eureka没有分布式数据一致性机制,节点都是相同的

nacos:raft

Zookeeper:Paxos

Nacos:raft

zk:Paxos

Ribbon

关于Ribbon组件:

 Ribbon组件被OpenFeign进行了内嵌,未来不会单独作为组件使用了。

服务消费者

Ribbon依赖:【注:ribbon依赖只需要在服务消费者进行配置】

<dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>

</dependency>

application类:

@SpringBootApplication

@EnableEurekaClient

public class Consumer02Application {



    public static void main(String[] args) {

        SpringApplication.run(Consumer02Application.class, args);

    }



    @Bean

    @LoadBalanced

    public RestTemplate restTemplate(){

        return new RestTemplate();

    }

}

释:

添加了@LoadBalance后,这个RestTemplate交由ribbon管理,会走ribbon的代码进行负载均衡

controller类:

@RestController

public class ConsumerController {



    @Autowired

    private RestTemplate restTemplate;                                         

 

    @GetMapping("testRibbon")

    public String testRibbon(String serviceName){

        String forObject = restTemplate.getForObject("http://" + serviceName + "/hello", String.class);

        return forObject;

    }

}

这样配置时,调用要:http://localhost:8082/testRibbon?serviceName=provider

参数serviceName正是由controller内方法的形参接收的

配置了RestTemper的@Bean后,自动注入【@Autoware】的成员变量已经交由ribbon负载均衡管理,必然会执行以下的操作:

问题前导:

      【ribbon如何将http://provider/hello成功发送的呢】

      【理论格式:http://127.0.0.1:8080/hello】

ribbon执行逻辑:

1 . 拦截这个请求

2 . 截取主机名

3 . 借助eureka根据服务名provider进行服务发现,获取list<>

4 . 负载均衡算法获取服务ip和port端口号

5 . reConstructURL重构

6 . 发起请求

因此,交由ribbon管控的,由@Autoware自动注入的RestTemple不能再正确读取理论格式的url了

这种情况下想要使用 未交由ribbon的RestTemple对象,需要手动new:

RestTemplate restTemplate1 = new RestTemplate();

YML配置:

​
server:

     port: 8082

spring:

     application:

          name: consumer

eureka:

     client:

          service-url:

               defaultZone: http://192.168.159.130:8761/eureka

     instance:

          hostname: localhost

          prefer-ip-address: true

          instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

ribbon:

     eager-load:

          enabled: true      

     eureka:

          enabled: true    

     http:

          client:

               enabled: false 

     okhttp:

          enabled: false

​

释:

1 . eager-load配置:true对应饥饿加载,即服务启动后会立即进行 服务列表拉取 。【默认情况下为false,懒加载,直到被调用才会进行服务列表拉取】

2 . eurekaenable:开启eureka注册中心对ribbon的支持

3 . httpclient:开启httpclient关于http请求发送的服务,替代默认的httpUrlConnection进行http请求发送。

httpclient支持连接池,比不支持连接池的httpUrlConnection效率更高

4 . okhttp:另一款http请求发送工具,优点是轻量级

 

概:

ribbon的yml相关配置都不是必须的,均可不配,并且仅有服务消费者需要进行配置即可

 

服务提供者

ribbon的服务提供者不需要进行任何特殊配置

只需要正常注册入配置中心,记好服务的访问路径等信息,在服务提供者内进行正确调用即可

关于启动服务

注意,如果先启动服务消费者,再依次进行服务提供者的话,

服务消费者就可能在 同种的服务提供者 没有全部启动 时就进行了 服务列表拉取 ,

导致尚没有启动完成的服务提供者无法参与到轮询内

直到达到了服务列表拉取的时间间隔,进行了第二次服务列表拉取,轮询才能正常执行

故,为了避免以上问题,需要先将服务提供者全部启动后,再启动服务消费者,这样拉取到的表单就是完整的了

提及知识点:

轮询算法与自旋锁

Feign

服务消费者:

1 . Application类内额外添加了@EnableFeignClient注解

   

@SpringBootApplication

@EnableEurekaClient

@EnableFeignClients         //开启feign客户端功能

public class UserServiceApplication {



    public static void main(String[] args) {

        SpringApplication.run(UserServiceApplication.class, args);

    }



    @Bean

    public Logger.Level level(){

        return Logger.Level.FULL;

    }

}

释:

日志级别配置

2 . java目录下添加了feign目录:

@FeignClient(value = "order-service")

public interface UserOrderFeign {



    @GetMapping("doOrder")

    public String doOrder();                               【方法签名】

}

释:

@FeignClient注解内置需要调用的 服务提供者的服务名

类内添加 需要调用的服务内的方法的 方法签名

方法签名:

即,将服务提供者内controller层内的方法copy一份过来,仅保留方法声明部分,去除方法体

【public是可以省略的,因为@FeignClient内的方法签名默认是public】

3 . pom内添加openFeign依赖:

 <dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-openfeign</artifactId>

 </dependency>

4 . yml内添加控制服务间调用细节的一些配置

​
server:

     port: 8081

spring:

     application:

          name: user-service

eureka:

     client:

          service-url:

               defaultZone: http://192.168.159.130:8761/eureka

ribbon:                  

     ReadTimeout: 3000                                      #三秒调用超时【三秒内  下一个服务未执行完成  ,则报错】

     ConnectTimeout: 3000                                #三秒链接超时【三秒内  没有连接到  下一个服务,则报错】

logging:

     level:

          com.misakimei.feign.UserOrderFeign: debug

​

释:

feign只是封装了远程调用的功能,所以需要  对其封装的ribbon进行配置  来控制响应时间等参数

logging配合Application类内的日志配置,对指定组件的日志输出进行调控

5 . controller层使用feign来完成可能出现的远程调用操作:

@RestController

public class UserController {



    @Autowired

    public UserOrderFeign userOrderFeign;

 

   

    @GetMapping("userDoOrder")

    public String userDoOrder(){

        System.out.println("userService successful ...");

        String s = userOrderFeign.doOrder();

        return s;

    }

}

释:

定义feign目录下 对应的接口 为成员变量,在方法内直接调用成员变量内对应的 方法签名,远程调用就完成了

6 . 关于有参请求:

@RestController

public class ParamController {



    @GetMapping("testUrl/{name}/and/{age}")

    public String testUrl(@PathVariable("name")String name,@PathVariable("age")Integer age){                  多个或一个常规参数

        System.out.println(name);

        System.out.println(age);

        return name + age.toString();

    }



    @GetMapping("oneParam")

    public String oneParam(@RequestParam(required = true) String name){      required默认为true,表示请求如果不携带参数将会报错,为false则可以不带参数

        System.out.println(name);

        return name;

    }





    @GetMapping("twoParam")

    public String twoParam(@RequestParam(required = true)String name,@RequestParam(required = true)Integer age){

        System.out.println(name);

        System.out.println(age);

        return name + age.toString();

    }





    @PostMapping("oneObj")

    public Order oneObj(@RequestBody Order order){                                       Post请求接一个对象类参数

        System.out.println(order);

        return order;

    }



    @PostMapping("oneObjOneParam")

    public Order oneObjOneParam(@RequestBody Order order,@RequestParam("name") String name){

        System.out.println(order);

        System.out.println(name);

        return order;

    }

Hystrix

服务雪崩:

在微服务状态下的多级调用内,当访问链内存在一个服务模块无法正常工作时,他的上级服务会进行一段时间的  响应等待

在这个等待的过程中,报错的服务的上级服务会  保留这个等待停止的服务的线程

高并发的情况下,大量这样的保留行为会导致  资源大量被占用  ,随后其上级服务因此而崩溃

多级服务依次崩溃,级联报错,引起的大片服务崩溃的现象就是  服务雪崩

于是,为了应对服务雪崩,出现了hystrix组件

hystrix组件一旦将一个服务判定为故障后,会直接进行报错返回

这样,虽然这个服务仍然处于故障中,但是通过  快速返回错误信息  的方法来避免了  等待故障服务响应  的过程,使得故障不会因此扩散到其他服务,以此避免了服务雪崩的发生

服务消费者:

yml配置:

​
server:

  port: 8081

spring:

  application:

    name: cuntomer-service

eureka:

  client:

    service-url:

      defaultZone: http://192.168.159.130:8761/eureka

  instance:

    hostname: localhost

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

feign:

  hystrix:

    enabled: true

hystrix:

  command:

    default:                   #全局配置,可改成服务名

      circuitBreaker:

        enable: true                                             #熔断器功能【默认为开启】

        requestVolumeThreshold: 3                   #失败次数【阈值】

        sleepWindowInMilliseconds: 20000       #窗口时间

        errorThresholdPercentage: 60                #失败率

      execution:

        isolation:

          Strategy: thread                                    #隔离方式

          thread:

            timeoutInMilliseconds: 3000              #超时时间

      fallback:

        isolation:

          semaphore:

            maxConcurrentRequests: 1000

​

释:

紫色 true:开启hystrix功能

红色:熔断器细节参数配置

窗口时间:

设置窗口  失败阈值失败率计算 的间隔时间

阈值:

设置 失败多少次后进行hystrix失败信息的返回

失败率:

百分比为多少的服务失败后,判定为失败

【阈值和失败率实质上是两种形式的对访问失败这一事件的判定,可以只配一个,也可以同时生效】

隔离方式:

对于 服务消费者 针对多个 服务提供者的隔离方式:【未掌握,可能存在很多认知不足】

线程隔离:对于每一个  消费者 ---> 提供者 的体系,分别设置线程池进行线程管理,

通过规定 线程池的数量上限 来限制 最高并发数

信号量隔离:设置一个所有服务调用共用的 原子计数器 ,每当进行了一次服务调用就会使计数器加一,每次服务结束释放线程就会减一

通过对 计数上限 进行控制来限制 最高并发数

hystrix包配置:

@Component

public class CustomerRentFeignHystrix implements CustomerRentFeign {



    @Override

    public String rent() {

        return "备选方案启动,服务异常 ...";

    }

}

释:

feign目录下建立hystrix目录,目录下创造  继承feign接口  的类,重写feign接口内的 方法签名

这个  重写的方法签名  就是  熔断后执行错误反馈  的方法

feign组件配置:

@FeignClient(value = "rent-car-service",fallback = CustomerRentFeignHystrix.class)

public interface CustomerRentFeign {



    @GetMapping("rent")

    public String rent();



}

释:

fallback:当这个网关内的方法签名执行失败时,会走fallback属性内指向的hystrix类内的失败方案

pom依赖:


       

​
 <dependency>

    <groupId>org.springframework.cloud</groupId>

    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>

 </dependency>

​

服务提供者:

无特殊配置

模块抽取

对feign和hystrix进行单独抽取为 common-api 类,对domain进行单独抽取为 project-domain 类

使用时,直接将 api 和 domain 以 pom依赖 的形式引入对应的工程,即可使用里面全部的接口和类

这样,在进行controller创建时,就不用依照原本的步骤,先创建controller再在对应 服务消费者的feign内 进行 方法标签 注册了

直接在api的feign内进行对应方法标签的创建,然后让导入了api依赖的服务的controller类实现feign接口,再重写接口内的方法即可

对于hystrix组件的使用如常,还是直接在hystrix类上继承feign,重写方法即可,feign内的fallback标签也如常配置

Zipkin链路追踪

yml配置:

​
server:

  port: 8080

spring:

  application:

    name: order-service

  zipkin:

    base-url: http://localhost:9411

  sleuth:

    sampler:

      probability: 1        #采集率,1表示百分百采集,默认为0.1,百分之十

      rate: 10              #采集时间间隔

eureka:

  client:

    service-url:

      defaultZone: http://192.168.159.130:8761/eureka

  instance:

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

    hostname: localhost

​

释:

base-url为zipkin的图形化界面,可以查看每次服务调用发起时不同组件间响应时间等具体信息

注:使用时首先要启动zipkin的jar包来启动服务

pom依赖:【添加在api类内,因为】

<dependency>

            <groupId>org.springframework.cloud</groupId>

            <artifactId>spring-cloud-starter-zipkin</artifactId>

</dependency>

Admin监控:

创建admin的服务端【server】:

注:需要admin进行服务监控时,需要进行服务端和客户端的配置。理论上需要在每一个服务内配置客户端,让服务端对每一个需要监控的组件进行监管

显然,这样的配置太过于繁琐,于是可以将这个admin的服务端作为eureka的客户端注册到eureka的配置中心内,然后admin就可以直接从服务端内进行客户列表的拉

取,省去了分设客户端的配置。

pom依赖:

<dependency>

    <groupId>de.codecentric</groupId>

    <artifactId>spring-boot-admin-starter-server</artifactId>

</dependency>

yml配置:

​
server:

     port: 10086

spring:

     application:

          name: admin-server

eureka:

     client:

          service-url:

               defaultZone: http://192.168.159.130:8761/eureka

  instance:

       hostname: localhost

       instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

management:

     endpoints:

          web:

               exposure:

                    include: "*"    #暴漏所有的监控端点

​

暴漏需要开启监管的组件:

<dependency>

        <groupId>org.springframework.boot</groupId>

        <artifactId>spring-boot-starter-actuator</artifactId>

</dependency>

Gateway网关

gateway:路由转发 和 过滤器链

执行流程:-- filter1 --> filter2 --> interceptor --> controller --> interceptor --> filter2 --> filter1 --

filter与interceptor:

filter过滤器 拦截所有的访问请求,interceptor拦截器 拦截所有对controller发起的请求

Gateway配置:

Gateway作为 独立的模块 进行配置

pom依赖:

<dependency>

         <groupId>org.springframework.cloud</groupId>

         <artifactId>spring-cloud-starter-gateway</artifactId>

</dependency>

yml实现网关配置:

​
server:

     port: 80                                                      #网关端口

spring:

     application:

          name: gateway-server                          #网关服务名

  cloud:

       gateway:

            enabled: true                                      #网关开启【添加了网关依赖后这里默认为开启】

            routes:

                 - id: login-service-route                #路由的自定义id,保证唯一即可

                   uri: http://localhost:8081

                   predicates:

                     - Path=/doLogin                       #访问路径

​

 

释:

当出现针对网关端口80进行的,path为/doLogin的访问,就会在gateway的作用下转发到 uri所指的路径去

config实现网关配置:

@Configuration

public class RouteConfig {



    @Bean

    public RouteLocator customRouteLocator(RouteLocatorBuilder builder){

        return builder.routes()

                .route("test-id",r->r.path("/doLogin").uri("http://localhost:8081/"))      1:

 .route("test-id",r->r.path("/doLogin").uri("http://localhost:8082/doLogin"))          2:                   注:1,2等价

                .build();

    }

}

释:

config类实现的网关配置与yml不冲突

当gateway的端口为80时,进行如上配置,对 localhost/doLogin 进行访问,就会跳转到http://localhost:8081/doLogin网址下

即:ip+网关端口+path属性  会跳转到对应的 uri+path 所示的路径下

gateway动态路由:

yml配置【网关服务内】:

​
server:

  port: 80

spring:

  application:

    name: gateway-server

  cloud:

       gateway:

            enabled: true   #默认开启

            routes:

              - id: login-service-route

#                uri: http://localhost:8081

#                uri: lb://login-service                                                                                                                                                  【一】

                predicates:

                     - Path=/doLogin               #指定请求路径

        - After=日期                      #指定日期后这个请求才允许访问                      【三】断言,不符合即404

      - Method=GET,POST     #指定请求格式                                                            断言 不能 对动态路由生效

      - Query=name,url          #指定请求必须要携带的参数

      - . . .

      discovery:

           locator:

                enabled: true                                                #开启动态路由  开启通过应用名称发现服务的功能                 【二】

                lower-case-service-id: true                          #开启服务名称小写

eureka:

  client:

    service-url:

      defaultZone: http://192.168.159.130:8761/eureka

    registry-fetch-interval-seconds: 3     #拉取服务列表的间隔

  instance:

    hostname: localhost

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

​

释:

 配置enabled: truelower-case-service-id: true后,可以通过  服务名称+path的方式进行访问  ,并且同名服务负载均衡

最终这些 服务名+path 的请求将会发送到uri下所指的路径,使用 lb 则指定转发到的服务名称,所有同名服务负载均衡访问

gateway过滤器:

一般用法:

gatewayFilter:针对某个服务

记录该服务的访问次数

进行限流操作

globalFilter:针对全局

ip校验

token校验

参数校验【防止sql注入】

全局过滤器:

创建filter目录,下设过滤器类:【如下】

@Component                                                                                                注入容器

@Order(-1)                                                                                                    设置优先级

public class MyGlobalFilter implements GlobalFilter {                                  继承全局过滤器



    /**

     * 疑似使用了webFlux的api(

     * @param exchange

     * @param chain

     * @return

     */



    @Override

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {



        /**

         *         过滤请求,拿到请求相关参数

         */

        ServerHttpRequest request = exchange.getRequest();              

对请求的过滤

       

//        获取路径【http请求端口号后的所有路径,也能将动态路由到的路径拿来】

        String path = request.getURI().getPath();

        System.out.println(path);

//        获取请求头

        HttpHeaders headers = request.getHeaders();

        System.out.println(headers);

//        获取方法【请求的形式,如:Get,Post】

        String name = request.getMethod().name();

        System.out.println(name);

//        获取方法名

        String methodName = request.getRemoteAddress().getHostName();

        System.out.println(methodName);

//        获取远端地址内的主机ip【】

        String hostName = request.getRemoteAddress().getHostName();

        System.out.println(hostName);





        /**

         *         过滤响应,拿到响应相关参数

         */

        ServerHttpResponse response = exchange.getResponse();     

对响应反馈的过滤【进行了失败相关的反馈】

       

        /**

         * 模拟拦截,并发送json错误提示

         */

//        通过json进行前后端间的交互

//        设置响应头里的字符编码,防止中文乱码

        response.getHeaders().set("context-type","application/json;charset=utf-8");

//        组装业务返回值

        HashMap<Object, Object> map = new HashMap<>();

        map.put("code", HttpStatus.UNAUTHORIZED.value());                           传入json值到哈希表【UNAUTHORIZED:错误反馈码】

        map.put("msg","未授权");

//        字符转换器

        ObjectMapper objectMapper = new ObjectMapper();                                    hashMap转换成数组

        byte[] bytes = new byte[0];

        try {

            bytes = objectMapper.writeValueAsBytes(map);

        } catch (JsonProcessingException e) {

            throw new RuntimeException(e);

        }

//        通过buffer工厂将字节数组包装成一个数据包

        DataBuffer wrap = response.bufferFactory().wrap(bytes);                                未知部分【涉及知识盲区】

        return response.writeWith(Mono.just(wrap));

//        放行到下一个过滤器

//        return chain.filter(exchange);

    }



}

对ip权限的判断及反馈模拟:

@Component

@Order(-2)

public class IpCheckFilter implements GlobalFilter {



    @Override

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

//        拿到请求

        ServerHttpRequest request = exchange.getRequest();

//        拿到ip

        String ip = request.getHeaders().getHost().getHostString();

//        从数据库表内查询这个ip是否在黑名单

//        此处用true代替了判断

        if(true){                           此处进行ip的审核,从数据库获取并比对权限【注:不要在网关内对Mysql进行访问,效率过低】

//            放行

            return chain.filter(exchange);

        }





//        如判定失败,没有权限,走如下代码

        ServerHttpResponse response = exchange.getResponse();

       

response.getHeaders().set("content-type","application/json;charset=utf-8");

        HashMap<String, Object> stringOrderHashMap = new HashMap<>();

        stringOrderHashMap.put("code",438);                                                                  自定义的错误反馈码

        stringOrderHashMap.put("msg","false");

//        转换器

        ObjectMapper objectMapper = new ObjectMapper();

        byte[] bytes = new byte[0];

        try {

//            将哈希表转换成字节型数组

            bytes = objectMapper.writeValueAsBytes(stringOrderHashMap);

        } catch (JsonProcessingException e) {

            throw new RuntimeException(e);

        }

//        打包成数据包

        DataBuffer wrap = response.bufferFactory().wrap(bytes);

        return response.writeWith(Mono.just(wrap));

    }

}

token校验:【注意开启redis】

流程:

示意图:

具体配置:

以下程序执行逻辑:

执行设置为网关拦截器白名单的login-service方法,传入用户名和密码,在mysql数据库内验证用户名和密码,

判定无误后login-service生成一个token令牌,将这个令牌存入redis数据库

这时,对网关内白名单之外的服务进行访问,会因没有权限而被token过滤器拦截

当在请求内的Authorization内添加了token令牌后,token过滤器会检测到这个令牌,并对该请求放行

pom依赖:【需要redis的有注册服务【login-service】和网关服务】


 

<dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis</artifactId>

 </dependency>

配置过滤器:

@Order(2)

@Component

public class TokenCheckFilter implements GlobalFilter {        继承过滤器



//    指定被放行的路径【白名单】

    public static final List<String> ALLOW_URL = Arrays.asList("/login-service/doLogin","/myUrl");



创建一个链表【ALLOW_URL】,对允许放行的路径进行设置



//    连接redis

    @Autowired

    private StringRedisTemplate stringRedisTemplate;



进行使用redis的配置



    /**

     * 确认token的位置:一般在请求头,一般以Authorization【授权】为key,bearer前缀 + token为value

     * 1 . 拿到请求url

     * 2 . 判断放行白名单用户

     * 3 . 拿到请求头

     * 4 . 拿到token

     * 5 . 校验

     * 6 . 放行/拦截

     *

     * @param exchange

     * @param chain

     * @return

     */

    @Override           重写过滤器方法

    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

//        获取请求

        ServerHttpRequest request = exchange.getRequest();

//        获取路径

        String path = request.getURI().getPath();

//        如果在白名单,放行

        if (ALLOW_URL.contains(path)){

            return chain.filter(exchange);

        }

        //检查

//        拿到请求头

        HttpHeaders headers = request.getHeaders();

//        拿到token令牌【Authorization可以设置多个】

        List<String> authorization = headers.get("Authorization");          post请求内的令牌存放以Authorization为key

//        如果有这个key

(检查集合  是否为空  的小组件         【注意取反】

        if (!CollectionUtils.isEmpty(authorization)){

//            获取token

            String token = authorization.get(0);

//            判断是否为空

(检查字符串  是否有值  的小组件

            if (StringUtils.hasText(token)){

//                将固定前缀【bearer 】替换去掉,获取真实的token               注意,是【bearer空格】

                String realToken = token.replaceFirst("bearer ", "");         前缀替换为空,即删去

//            判断是否为空且redis内是否包含这个token

                if (StringUtils.hasText(realToken)&&stringRedisTemplate.hasKey(realToken)){

return chain.filter(exchange);       放行

                }

            }

        }



        //能走到这里,就说明没有权限,需要拦截



        ServerHttpResponse response = exchange.getResponse();

       

response.getHeaders().set("content-type","application/json;charset=utf-8");

        HashMap<String, Object> map = new HashMap<>(4);

//        返回401

        map.put("code", HttpStatus.UNAUTHORIZED.value());

        map.put("msg","未授权");

        ObjectMapper objectMapper = new ObjectMapper();

        byte[] bytes = new byte[0];

        try {

            bytes = objectMapper.writeValueAsBytes(map);

        } catch (JsonProcessingException e) {

            throw new RuntimeException(e);

        }

        DataBuffer wrap = response.bufferFactory().wrap(bytes);

        return response.writeWith(Mono.just(wrap));

    }

}

login-service的controller配置:

@RestController

public class LoginController {



    @Autowired

    public StringRedisTemplate redisTemplate;



    @GetMapping("doLogin")

    public String doLogin(String name, String pwd){

        System.out.println(name);

        System.out.println(pwd);

//        假设查询了数据库

此处应该对mysql进行查询,查看请求内包含的用户名和密码是否正确

        User user = new User(1,name,pwd,18);



//        获取到一个token令牌

        String token = UUID.randomUUID().toString();

 

//        存进redis

数据有效时长【即,超出7200秒后,redis内的这个token就会自动消失】

        redisTemplate.opsForValue().set(token,user.toString(), Duration.ofSeconds(7200));

        return token;             将自动生成的token令牌返回到页面

    }



}

gateway的redis限流:

关于令牌和桶机制:

网关依赖:【修改redis配置】

<dependency>

            <groupId>org.springframework.boot</groupId>

            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>

</dependency>

yml配置:

​
server:

  port: 80

spring:

  application:

    name: gateway-server

  redis:

    host: localhost

    port: 6379

  cloud:

    gateway:

      enabled: true   #默认开启

      routes:

        - id: login-service-route

          uri: http://localhost:8081

#          uri: lb://login-service

          predicates:

            - Path=/doLogin

          filters:

            - name: RequestRateLimiter                                                                            在对应的断言内配置过滤器

              args:

                key-resolver: '#{@ipKeyResolver}'    #通过spel表达式获取ioc容器内bean的值,此处取到的是ip过滤器的值                 

                redis-rate-limiter.replenishRate: 1  #生成令牌的速度

                redis-rate-limiter.burstCapacity: 3  #桶容量

      discovery:

        locator:

          enabled: true   #开启动态路由  开启通过应用名称发现服务的功能

          lower-case-service-id: true  #开启服务名称小写

eureka:

  client:

    service-url:

      defaultZone: http://192.168.159.130:8761/eureka

    registry-fetch-interval-seconds: 3     #拉取服务列表的间隔

  instance:

    hostname: localhost

    instance-id: ${eureka.instance.hostname}:${spring.application.name}:${server.port}

​

在config下进行过滤器配置:

@Configuration

public class RequestLimitConfig {



    /**

     * 针对某一个IP限流

     */

    @Bean

    @Primary                //主要的

    public KeyResolver ipKeyResolver(){

        return exchange -> Mono.just(exchange.getRequest().getHeaders().getHost().getHostString());

    }





    /**

     * 针对某一个路径限流

     */

    @Bean

    public KeyResolver apiKeyResolver(){

        return exchange -> Mono.just(exchange.getRequest().getPath().value());

    }

}

Nacos配置中心

差异:

1 . Nacos的服务中心NacosServer是官方搭建好的,而EurekaServer的是需要自己配置的

2 . Nacos服务端登录默认就有密码设置,EurekaServer需要自己继承SpringSecurity进行安全设置

3 . Nacos隔离的更彻底,

Eureka内两个应用注册了同名方法时,会根据服务名形成一种类似集群的形态,导致对服务的访问在集群内随机挑选一个服务,可能会因此报错

Nacos内有namespace的概念,不同namespace内的服务默认情况下是不可互相访问的,namespace下还有group概念,进行进一步隔离

报错:

1 . Nacos 2.X的客户端不能注册进入Nacos 1.X的客户端

报错:

Request nacos server failed:

namingService subscribe failed, properties:NacosDiscoveryProperties{serverAddr='localhost:8848', endpoint='', namespace='', watchDelay=30000, logName='', service='nacos-client-a', weight=1.0, clusterName='DEFAULT', group='DEFAULT_GROUP', namingLoadCacheAtStart='false', metadata={preserved.register.source=SPRING_CLOUD}, registerEnabled=true, ip='192.168.157.1', networkInterface='', port=-1, secure=false, accessKey='', secretKey='', heartBeatInterval=null, heartBeatTimeout=null, ipDeleteTimeout=null, failFast=true}

Application类:

@SpringBootApplication

@EnableDiscoveryClient                            2.X版本这个注解不再必须配置

public class NacosClientBApplication {

    public static void main(String[] args) {

        SpringApplication.run(NacosClientBApplication.class, args);

    }

}

yml配置:

server:

     port: 8081

spring:

     application:

          name: nacos-client-b

  cloud:

       nacos:

            server-addr: localhost:8848                #Nacos服务端

            username: nacos              用户名密码

            password: nacos

            discovery:

                 namespace: 6b7ad443-78b1-4ce1-b3ff-4f75f3a78960  #注册进的命名空间的id

                 group: A_GROUP  #注册进的组

                 service: user-service  #Nacos列表内的服务名,不进行配置时,默认以applicationName为服务名

 

 

Nacos服务发现:

controller:


 

@RestController

public class TestController {

    @Autowired

    public DiscoveryClient discoveryClient;



    @GetMapping("test")

    public String test(){

        List<ServiceInstance> instances = discoveryClient.getInstances("user-service");      发现指定名称的服务【必须在同一个namespace,同一个group】

        System.out.println(instances);       对此处添加断点,当运行到此处  size=1 时。说明发现成功, size=2时,发现失败

        return "success ...";

    }

}



Nacos集成Gateway和OpenFeign

Gateway的yml文件:

server:

  port: 80

spring:

  application:

    name: gateway

  cloud:

    nacos:

      server-addr: localhost:8848

      username: nacos

      password: nacos

      discovery:

        namespace: 6b7ad443-78b1-4ce1-b3ff-4f75f3a78960

        group: A_GROUP

    gateway:

      discovery:

        locator:

          enabled: true   #动态路由开启

          lower-case-service-id: true   #服务名称转小写

pom依赖:

 

以下配置替代了parents

  

<dependencyManagement>

        <dependencies>



            <dependency>

                <groupId>org.springframework.boot</groupId>

                <artifactId>spring-boot-dependencies</artifactId>

                <version>${spring-boot.version}</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>



            <dependency>

                <groupId>org.springframework.cloud</groupId>

                <artifactId>spring-cloud-dependencies</artifactId>

                <version>${spring-cloud.version}</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>



            <dependency>

                <groupId>com.alibaba.cloud</groupId>

                <artifactId>spring-cloud-alibaba-dependencies</artifactId>

                <version>${spring-cloud-alibaba.version}</version>

                <type>pom</type>

                <scope>import</scope>

            </dependency>



        </dependencies>

    </dependencyManagement>

 

注:Nacos来自于alibabaDependence,而Gateway所需的组件来自springclouddependence,所以都需要配置

至于版本匹配问题,查阅github文档

配置完成Gateway后,在服务消费者内添加openfeign组件相关的依赖【注:openFeign组件同样来自 原生spring cloud dependence 内,也需要添加对应的dpendence management】

feign配置与eureka时相同:

@FeignClient(value = "user-service")                 注意,此处是注册入Nacos时配置的那个服务名

public interface TestFeign {



    @GetMapping("info")         需要进行跨服务调用的方法的方法签名

    public String info();

}

接下来,对gateway调用指定服务的指定方法,在动态路由和feign远程调用的作用下,访问就顺利成功了

nacos配置文件中心:

可以在nacos内创建yaml配置文件,供集群等对相同文件进行复用的情况的出现

直接在nacos内 配置详情 处创建,定义文件类型,注意namespace和group,在 配置内容 部分进行配置文件的书写

【注:当想要让配置中心内的配置文件返回修改前的状态 即:回滚,可以点击 更多 ->历史版本 选项,里面存放了三十天之内的全部历史版本,存放在数据库内】

对于想要使用这个文件的服务,在bootstrap.yml内进行如下配置,即可正常使用:

server:

     port: 8081

spring:

     application:

          name: nacos-config-a

     cloud:

          nacos:                                                                            原本application.yml内的配置

               config:

                    server-addr: localhost:8848

                    username: nacos

                    password: nacos

                    prefix: nacos-config-a       #读取的配置文件的名称                        新增

                    file-extension: yml            #读什么类型的文件

     profiles:                    这个值将以   - 值  的形式拼接在prefix后面,构成配置文件名

  active: dev                没有这个配置时,prefix后的名称就是配置文件的名称

 

这种情况下,将会读取配置中心内名称为: nacos-config-dev.yml   的文件【这种格式时,文件类型后缀是必须的】

 

 

domain读取配置中心的文件进行初始化:

@Data

@AllArgsConstructor

@NoArgsConstructor

@Component                        注入ioc容器

@RefreshScope                     将这个类加入动态刷新的作用域内【添加这个注解后,对配置中心的文件进行修改后,不再需要刷新对应服务,即可生效】

public class Person {

   

    @Value("${person.name}")

    private String name;



    @Value("${person.age}")

    private Integer age;



    @Value("${person.address}")

    private String address;

}

 

 

Controller读取配置中心的文件:

@RestController

public class TestController {

   

    @Autowired

    public Person person;

 

    @GetMapping("info")

    public String infoPerson(){

        return person.toString();                     注:未知原因,此处不能直接返回person对象,会报错或者重复打印

    }

}

同时使用配置中心内多个不同group的配置文件:【同namespace】

yml配置:【方法一】

server:

  port: 8082

spring:

  application:

    name: nacos-config-test

  cloud:

    nacos:

      config:

        server-addr: localhost:8848                          Nacos服务端IP

        username: nacos

        password: nacos

        namespace: c96607d7-016a-454c-b978-faea64c68ecb            namespace的ID

        file-extension: yml

        extension-configs:                                  #读多个配置文件,需要在同一个命名空间内

          - data-id: user-center-dev.yml          #文件名

            group: A_GROUP                             #group名                                                            #指定第一个配置文件

            refresh: true                                     #支持动态刷新  

          - data-id: member-center-dev.yml   #文件名

            group: B_GROUP                             #group名                                                            #指定第二个配置文件

            refresh: false                                    #不支持动态刷新

 

注:使用这种方式时,不能使用 profiles:active 进行拼接了

 

yml配置:【方法二】【使用 共享配置文件 的方式】

【一】

server:

  port: 8082

spring:

  application:

    name: nacos-config-test

  cloud:

    nacos:

      config:

        server-addr: localhost:8848

        username: nacos

        password: nacos

        namespace: c96607d7-016a-454c-b978-faea64c68ecb

        prefix: user-center

        file-extension: yml                      第一个配置文件【组A_GROUP内】

        group: A_GROUP

        shared-configs:                           #共享配置文件

          - application-dev.yml               #这里填写共享的文件名称【只能在   DEFAULT_GROUP   内】

 profiles:

    active: dev

 

 

【二】

server:

  port: 8082

spring:

  application:

    name: nacos-config-test

  cloud:

    nacos:

      config:

        server-addr: localhost:8848

        username: nacos

        password: nacos

        namespace: c96607d7-016a-454c-b978-faea64c68ecb

        prefix: user-center

        file-extension: yml                      第一个配置文件【组A_GROUP内】

        group: A_GROUP

        shared-configs:                           #共享配置文件

  - data-id: application-dev.yml

     group: C_GROUP                              这样可以将任意组内的共享配置文件进行配置,并可以进行 动态刷新 的配置

     refresh: true

profiles:

    active: dev

 

【这几种方式,都成功读取到了不同   组(group) 的配置文件】

关于使用了配置中心的远端配置文件后,bootstrap.application配置文件和远端配置文件分别要配置什么:

bootstrap.yml:【本地】

1 . 应用名称spring:application:name

2 . nacos的 注册 和 远端配置文件 的 拉取

远端:【将能放在远端的东西都放在远端,这样修改后不需要重启服务】

1 . 端口

2 . 数据源

3 . Redis

4 . Mq

 ….

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MisakiMei释光

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值