首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring Boot + Spring Cloud 实现权限管理系统

Spring Boot + Spring Cloud 实现权限管理系统

作者头像
朝雨忆轻尘
发布2019-06-18 19:27:03
5940
发布2019-06-18 19:27:03
举报
文章被收录于专栏:朝雨忆轻尘朝雨忆轻尘

Spring Cloud体系里应用比较广泛的服务调用方式有两种:

1. 使用 RestTemplate 进行服务调用,可以通过 Ribbon 注解 RestTemplate 模板,使其拥有负载均衡的功能。

2. 使用 Feign 进行声明式服务调用,声明之后就像调用本地方法一样,Feign 默认使用 Ribbon实现负载均衡。

两种方式都可以实现服务之间的调用,可根据情况选择使用,下面我们分别用实现案例来进行讲解。

服务提供者

新建项目

新建一个项目 kitty-producer,添加以下依赖。

Swagger:API文档。

Consul :注册中心。

Spring Boot Admin:服务监控。

pom.xml

<?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>

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

    <groupId>com.louis</groupId>
    <artifactId>kitty-producer</artifactId>
    <version>${project.version}</version>
    <packaging>jar</packaging>
    
    <name>kitty-producer</name>
    <description>kitty-producer</description>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <project.version>1.0.0</project.version>
        <java.version>1.8</java.version>
        <swagger.version>2.8.0</swagger.version>
        <mybatis.spring.version>1.3.2</mybatis.spring.version>
        <druid.version>1.1.10</druid.version>
        <spring.boot.admin.version>2.0.0</spring.boot.admin.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>

    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!--spring-boot-admin-->
           <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>${spring.boot.admin.version}</version>
        </dependency>
        <!--consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--test-->
        <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>

配置文件

在配置文件添加内容如下,将服务注册到注册中心并添加服务监控相关配置。

application.yml

server:
  port: 8003
spring:
  application:
    name: kitty-producer
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        serviceName: ${spring.application.name}    # 注册到consul的服务名称
  boot:
    admin:
      client:
        url: "http://localhost:8000"
# 开放健康检查接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

启动类

修改启动器类,添加 @EnableDiscoveryClient 注解,开启服务发现支持。

KittyProducerApplication.java

package com.louis.kitty.producer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class KittyProducerApplication {

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

添加服务

新建一个 HelloController,提供一个 hello 接口, 返回字符串信息。

package com.louis.kitty.producer.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello() {
        return "hello kitty !";
    }
}

为了模拟均衡负载,复制一份上面的项目,重命名为 kitty-producer2 ,修改对应的端口为 8004,修改 hello 方法的返回值为:"hello kitty 2!"。

依次启动注册中心、服务监控和两个服务提供者,启动成功之后刷新Consul管理界面,发现我们注册的kitty-producer服务,并有2个节点实例。

查看两个服务提供者已经注册到注册中心。

查看两个服务提供者已经成功显示在监控列表中。

返回结果如下。

返回结果如下。

服务消费者

新建项目

新建一个项目 kitty-producer,添加以下依赖。

Swagger:API文档。

Consul :注册中心。

Spring Boot Admin:服务监控。

pom.xml

<?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>

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

    <groupId>com.louis</groupId>
    <artifactId>kitty-consumer</artifactId>
    <version>${project.version}</version>
    <packaging>jar</packaging>

    <name>kitty-consumer</name>
    <description>kitty-consumer</description>

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

    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- swagger -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>${swagger.version}</version>
        </dependency>
        <!--spring-boot-admin-->
           <dependency>
            <groupId>de.codecentric</groupId>
            <artifactId>spring-boot-admin-starter-client</artifactId>
            <version>${spring.boot.admin.version}</version>
        </dependency>
        <!--consul-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-consul-discovery</artifactId>
        </dependency>
        <!--test-->
        <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>

添加配置

修改配置文件如下。

application.yml

server:
  port: 8005
spring:
  application:
    name: kitty-consumer
  cloud:
    consul:
      host: localhost
      port: 8500
      discovery:
        serviceName: ${spring.application.name}    # 注册到consul的服务名称
  boot:
    admin:
      client:
        url: "http://localhost:8000"
# 开放健康检查接口
management:
  endpoints:
    web:
      exposure:
        include: "*"
  endpoint:
    health:
      show-details: ALWAYS

启动类

修改启动器类,添加 @EnableDiscoveryClient 注解,开启服务发现支持。

KittyConsumerApplication.java

package com.louis.kitty.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

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

服务消费者

添加消费服务测试类,添加两个接口,一个查询所有我们注册的服务,另一个从我们注册的服务中选取一个服务,采用轮询的方式。

ServiceController.java

package com.louis.kitty.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceController {

    @Autowired
    private LoadBalancerClient loadBalancerClient;
    @Autowired
    private DiscoveryClient discoveryClient;

   /**
     * 获取所有服务
     */
    @RequestMapping("/services")
    public Object services() {
        return discoveryClient.getInstances("kitty-producer");
    }

    /**
     * 从所有服务中选择一个服务(轮询)
     */
    @RequestMapping("/discover")
    public Object discover() {
        return loadBalancerClient.choose("kitty-producer").getUri().toString();
    }
}

添加完成之后,启动项目,服务消费者已经成功注册到注册中心。

服务消费者已经成功显示在监控列表中。

访问 http://localhost:8005/services,返回两个服务,分别是我们注册的8003和8004。

[{
    "serviceId": "kitty-producer",
    "host": "GG20J1G2E.logon.ds.ge.com",
    "port": 8003,
    "secure": false,
    "metadata": {
        "secure": "false"
    },
    "uri": "http://GG20J1G2E.logon.ds.ge.com:8003",
    "scheme": null
}, {
    "serviceId": "kitty-producer",
    "host": "GG20J1G2E.logon.ds.ge.com",
    "port": 8004,
    "secure": false,
    "metadata": {
        "secure": "false"
    },
    "uri": "http://GG20J1G2E.logon.ds.ge.com:8004",
    "scheme": null
}]

反复访问 http://localhost:8005/discover,结果交替返回服务8003和8004,因为默认的负载均衡器是采用轮询的方式。

8003 和 8004 两个服务会交替出现,从而实现了获取服务端地址的均衡负载。

大多数情况下我们希望使用均衡负载的形式去获取服务端提供的服务,因此使用第二种方法来模拟调用服务端提供的 hello 方法。

创建 CallHelloController.java

package com.louis.kitty.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class CallHelloController {

    @Autowired
    private LoadBalancerClient loadBalancer;

    @RequestMapping("/call")
    public String call() {
        ServiceInstance serviceInstance = loadBalancer.choose("kitty-producer");
        System.out.println("服务地址:" + serviceInstance.getUri());
        System.out.println("服务名称:" + serviceInstance.getServiceId());

        String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class);
        System.out.println(callServiceResult);
        return callServiceResult;
    }

}

使用 RestTemplate 进行远程调用。添加完之后重启 kitty-consumer 项目。

在浏览器中访问地址: http://localhost:8005/call 依次往复返回结果如下:

负载均衡器(Ribbon)

在上面的教程中,我们是这样调用服务的,先通过 LoadBalancerClient 选取出对应的服务,然后使用 RestTemplate 进行远程调用。

LoadBalancerClient 就是负载均衡器,默认使用的是 Ribbon 的实现 RibbonLoadBalancerClient,采用的负载均衡策略是轮询。

1. 查找服务,通过 LoadBalancer 查询服务。

ServiceInstance serviceInstance = loadBalancer.choose("kitty-producer");

2.调用服务,通过 RestTemplate 远程调用服务。

String callServiceResult = new RestTemplate().getForObject(serviceInstance.getUri().toString() + "/hello", String.class);

这样就完成了一个简单的服务调用和负载均衡。接下来我们说说Ribbon。

Ribbon是Netflix发布的负载均衡器,它有助于控制HTTP和TCP的客户端的行为。为Ribbon配置服务提供者地址后,Ribbon就可基于某种负载均衡算法,自动地帮助服务消费者去请求。Ribbon默认为我们提供了很多负载均衡算法,例如轮询、随机等。当然,我们也可为Ribbon实现自定义的负载均衡算法。

ribbon内置负载均衡策略:

策略名

策略声明

策略描述

实现说明

BestAvailableRule

public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule

选择一个最小的并发请求的server

逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server

AvailabilityFilteringRule

public class AvailabilityFilteringRule extends PredicateBasedRule

过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值)

使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态

WeightedResponseTimeRule

public class WeightedResponseTimeRule extends RoundRobinRule

根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。

一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。

RetryRule

public class RetryRule extends AbstractLoadBalancerRule

对选定的负载均衡策略机上重试机制。

在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server

RoundRobinRule

public class RoundRobinRule extends AbstractLoadBalancerRule

roundRobin方式轮询选择server

轮询index,选择index对应位置的server

RandomRule

public class RandomRule extends AbstractLoadBalancerRule

随机选择一个server

在index上随机,选择index对应位置的server

ZoneAvoidanceRule

public class ZoneAvoidanceRule extends PredicateBasedRule

复合判断server所在区域的性能和server的可用性选择server

使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

修改启动类

我们修改一下的启动器类,注入 RestTemplate,并添加 @LoadBalanced 注解(用于拦截请求),以使用 ribbon 来进行负载均衡。

KittyConsumerApplication.java

package com.louis.kitty.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableDiscoveryClient
@SpringBootApplication
public class KittyConsumerApplication {

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

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

添加服务

新建一个 RibbonHelloController 类,注入 RestTemplate,并调用服务提供者的hello服务。

package com.louis.kitty.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class RibbonHelloController {

    @Autowired
    private RestTemplate restTemplate;
    
    @RequestMapping("/ribbon/call")
    public String call() {
        // 调用服务, service-producer为注册的服务名称,LoadBalancerInterceptor会拦截调用并根据服务名找到对应的服务
        String callServiceResult = restTemplate.getForObject("http://kitty-producer/hello", String.class);
        return callServiceResult;
    }
}

测试效果

启动消费者服务,依次返回结果如下:

说明 ribbon 的负载均衡已经成功启动了。

负载策略

修改负载均衡策略很简单,只需要在配置文件指定对应的负载均衡器即可。如这里把策略修改为随机策略。

application.yml

#ribbon 负载均衡策略配置, service-producer为注册的服务名
service-producer:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

如上,修改成随机负载均衡策略之后,负载均衡器会随机选取注册的服务。

服务消费(Feign)

Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端。它使得编写Web服务客户端变得更加简单。我们只需要通过创建接口并用注解来配置它既可完成对Web服务接口的绑定。它具备可插拔的注解支持,包括Feign注解、JAX-RS注解。它也支持可插拔的编码器和解码器。Spring Cloud Feign还扩展了对Spring MVC注解的支持,同时还整合了Ribbon来提供均衡负载的HTTP客户端实现。

添加依赖

修改 kitty-consumer 的 pom 文件,添加 feign 依赖。

pom.xml

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

启动类

修改启动器类,添加 @EnableFeignClients 注解开启扫描Spring Cloud Feign客户端的功能:

KittyConsumerApplication.java

package com.louis.kitty.consumer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class KittyConsumerApplication {

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

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

添加Feign接口

添加 KittyProducerService接口, 在类头添加注解 @FeignClient("kitty-producer") ,kitty-producer是要调用的服务名。

添加跟调用目标方法一样的方法声明,只需要方法声明,不需要具体实现,注意跟目标方法定义保持一致。

KittyProducerService.java

package com.louis.kitty.consumer.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(name = "kitty-producer")
public interface KittyProducerService {

    @RequestMapping("/hello")
    public String hello();
}

添加控制器

添加 FeignHelloController控制器,注入 KittyProducerService,就可以像使用本地方法一样进行调用了。

FeignHelloController.java

package com.louis.kitty.consumer.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.louis.kitty.consumer.feign.KittyProducerService;

@RestController
public class FeignHelloController {

    @Autowired
    private KittyProducerService kittyProducerService;
    
    @RequestMapping("/feign/call")
    public String call() {
        // 像调用本地服务一样
        return kittyProducerService.hello();
    }
}

测试效果

启动成功之后,发现调用成功,且依次往复返回如下结果。

因为Feign是声明式调用,会产生一些相关的Feign定义接口,建议将Feign定义的接口都统一放置管理,以区别内部服务。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018-11-05 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 服务提供者
    • 新建项目
      • 配置文件
        • 启动类
          • 添加服务
          • 服务消费者
            • 新建项目
              • 添加配置
                • 启动类
                  • 服务消费者
                    • 负载均衡器(Ribbon)
                      • 修改启动类
                        • 添加服务
                          • 测试效果
                            • 负载策略
                            • 服务消费(Feign)
                              • 添加依赖
                                • 启动类
                                  • 添加Feign接口
                                    • 添加控制器
                                      • 测试效果
                                      相关产品与服务
                                      负载均衡
                                      负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
                                      领券
                                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档