Spring cloud Alibaba

一. 为什么使用spring cloud alibaba

很多人可能会问,有了spring cloud这个微服务的框架,为什么又要使用spring cloud alibaba这个框架了?最重要的原因在于spring cloud中的几乎所有的组件都使用Netflix公司的产品,然后在其基础上做了一层封装。然而Netflix的服务发现组件Eureka已经停止更新,我们公司在使用的时候就发现过其一个细小的Bug;而其他的众多组件预计会在明年(即2020年)停止维护。所以急需其他的一些替代产品,也就是spring cloud alibaba,目前正处于蓬勃发展的态式。

二. 注册中心Nacos

nacos是阿里巴巴研发的一个集注册中心与配置中心于一体的管理平台,使用其他非常的简单。下载地址为:
https://github.com/alibaba/nacos/releases,nacos的主页如下图所示:
其中默认的登录名和密码是:nacos/nacos
lpl:与相比网飞的e瑞卡需要加入config

2.1 更改用户名和密码(使用nacos-server-1.1.3中的表,运行的是nacos-server-1.3.1)

1.执行D:\nacos-server-1.1.3\nacos\conf目录下的nacos-mysql.sql两个脚本文件,生成的数据表如下:
2.重新配置密码
新建一

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>

个springboot的项目,引入如下的依赖:

生成密码的Java代码:

// 密码加密处理
public static void main(String[] args) {
    BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    //使用admin作为密码,每次执行后生成的加密的密码都会不同,然后插入到数据库
    System.out.println(bCryptPasswordEncoder.encode("admin"));
}

执行如下sql命令:

insert into users values('admin','$2a$10$D3DjLck63Xx0WHCRF51NhugbemMokx56TDoc4pAZmI3BvfApyOTKa', 1);

insert into roles values('admin', 'ROLE_ADMIN')

3.配置数据库的连接
在D:\nacos-server-1.3.1\nacos\conf目录下的application.properties加入如下内容:

spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?useSSL=false&serverTimezone=UTC&serverTimezone=Asia/Shanghai
db.user=root
db.password=root

4.重启启动nacos测试即可

2.2 nacos集群配置

1.修改D:\nacos-server-1.3.1\nacos\conf目录下的application.properties文件,配置当前网卡地址和数据库连接

### Specify local server's IP:
nacos.inetutils.ip-address=127.0.0.1

spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?useSSL=false&serverTimezone=UTC&serverTimezone=Asia/Shanghai
db.user=root
db.password=root

2.一旦网卡地址被指定,则需要修改springcloud-alibaba-microservice-provider-7070和springcloud-alibaba-microservice-consumer-8080工程中的application.yml配置文件中的server-addr

spring:
  application:
    # 服务提供方的名称
    name: microservice-provider

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848,127.0.0.1:8849

server:
  port: 7070
spring:
  application:
    # 服务消费方的名称
    name: microservice-consumer

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848,127.0.0.1:8849

server:
  port: 8080

3.将D:\nacos-server-1.3.1\nacos\conf目录下的cluster.conf.example拷贝一份重命名为cluster.conf,在文件中加入所有集群节点的ip和端口号,文件内容如下:
127.0.0.1:8848
127.0.0.1:8849
4.修改windows启动文件 startup.cmd 的配置,修改内容如下:
set MODE=”standalone” #默认的配置
set MODE=”cluster” #修改后的内容
5.拷贝一份D:\nacos-server-1.3.1\nacos目录,然后修改其中一个conf目录下的application.properties中的端口号为8849
### Default web server port:
server.port=8849
6.分别启动两个nacos,界面中出现如下的内容,表示集群配置成功
7.启动springcloud-alibaba-microservice-provider-7070和springcloud-alibaba-microservice-consumer-8080工程测试即可

三. 服务提供方

1.创建maven父工程 springcloud-alibaba-microservice-manager ,修改 pom.xml 文件,添加如下代码:

<packaging>pom</packaging>

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

2.创建子工程 springcloud-alibaba-microserivce-commons ,创建 User 类

package com.qf.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer uid;
    private String username;

}

3.创建子工程 springcloud-alibaba-microservice-provider-7070 ,修改 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">
    <parent>
        <artifactId>springcloud-alibaba-microservice-manager</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-alibaba-microservice-provider-7070</artifactId>

    <dependencies>

        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud-alibaba-microserivce-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

3.在 resources 目录下创建 application.yml

spring:
  application:
    # 服务提供方的名称
    name: microservice-provider

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848

server:
  port: 7070

4.在子工程 springcloud-alibaba-microservice-provider-7070中,创建 MicroServiceProvider 类

package com.qf;

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

@SpringBootApplication
@EnableDiscoveryClient
public class MicroServiceProvider {

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

5.创建 UserController

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("provider-user")
public class UserController {

    @RequestMapping("findAll")
    public List<User> findAll(){

        return Arrays.asList(new User(1001,"jack"),new User(1002,"tom"));
    }
}

四. 服务消费方

1.创建子工程 springcloud-alibaba-microservice-consumer-8080,修改 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">
    <parent>
        <artifactId>springcloud-alibaba-microservice-manager</artifactId>
        <groupId>com.qf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>springcloud-alibaba-microservice-consumer-8080</artifactId>


    <dependencies>

        <dependency>
            <groupId>com.qf</groupId>
            <artifactId>springcloud-alibaba-microserivce-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

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

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR6</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

2.在 resources 目录下创建 application.yml

spring:
  application:
    # 服务消费方的名称
    name: microservice-consumer

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848

server:
  port: 8080

4.在子工程 springcloud-alibaba-microservice-consumer-8080中,创建 MicroServiceConsumer类

package com.qf;

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

@SpringBootApplication
@EnableDiscoveryClient
public class MicroServiceConsumer {

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

5.创建 WebConfig

package com.qf.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class WebConfig {ghui]'41



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

6.创建 UserController

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("consumer-user")
public class UserController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;//用于发送网络请求

    @RequestMapping("getUsers")
    public List<User> getUsers(){
        //通过服务提供方的名称拿到服务,由于服务提供方可能是集群,所以使用List封装:每一个服务封装成一个ServiceInstance
        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("microservice-provider");
        ServiceInstance serviceInstance = serviceInstances.get(0);//获取服务
        String host = serviceInstance.getHost();//获取主机名
        int port = serviceInstance.getPort();//获取端口号
        String url = "http://"+host+":"+port+"/provider-user/findAll";

        List<User> users = (List<User>)restTemplate.getForObject(url, List.class);

        return users;
    }
}

分别启动服务方以及消费方,然后访问:http://localhost:8080/consumer-user/getUsers 进行测试

五. Ribbon负载均衡

Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。其主要功能是提供客户端的负载均衡算法,并提供了完善的配置项如连接超时,重试等。简单的说,就是配置文件中列出Load Balancer后面所有的机器,Ribbon会自动的基于某种规则(如简单轮询,随机连接等)去连接这些机器,当然我们也可以使用Ribbon自定义负载均衡算法。

5.1 实现负载均衡

Ribbon只是一个客户端的负载均衡器工具,实现起来非常的简单,我们只需要在注入RestTemplate的bean上加上@LoadBalanced就可以了。如下:

package com.qf.config;

import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class WebConfig {

    @LoadBalanced//添加注解
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

5.2 启动多个服务提供方测试

1.修改springcloud-alibaba-microservice-consumer-8080工程中的UserController

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import java.util.List;

@RestController
@RequestMapping("consumer-user")
public class UserController {

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;//用于发送网络请求

//    @RequestMapping("getUsers")
//    public List<User> getUsers(){
//        //通过服务提供方的名称拿到服务,由于服务提供方可能是集群,所以使用List封装:每一个服务封装成一个ServiceInstance
//        List<ServiceInstance> serviceInstances = discoveryClient.getInstances("microservice-provider");
//        ServiceInstance serviceInstance = serviceInstances.get(0);//获取服务
//        String host = serviceInstance.getHost();//获取主机名
//        int port = serviceInstance.getPort();//获取端口号
//        String url = "http://"+host+":"+port+"/provider-user/findAll";
//
//        List<User> users = (List<User>)restTemplate.getForObject(url, List.class);
//
//        return users;
//    }

    @RequestMapping("getUsers")
    public List<User> getUsers(){
        //直接写上服务名即可
        List<User> users = (List<User>)restTemplate
                .getForObject("http://microservice-provider/provider-user/findAll", List.class);

        return users;
    }

}

2.修改springcloud-alibaba-microservice-provider-7070工程中的UserController

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("provider-user")
public class UserController {

    @RequestMapping("findAll")
    public List<User> findAll(){

        //先启动7070,然后修改application.yml配置文件,端口设置为7071再启动
        System.out.println("7070");
        //System.out.println("7071");

        return Arrays.asList(new User(1001,"jack"),new User(1002,"tom"));
    }
}

3.设置springcloud-alibaba-microservice-provider-7070工程多次启动
4.修改端口号,启动多个provider,然后启动consumer,访问浏览器进行测试

5.3 负载均衡策略

Ribbon提供了一个很重要的接口叫做IRule,其中定义了很多的负载均衡策略,默认的是轮询的方式,以下是Ribbon的负载均衡策略:

类名 描述

改变Ribbon的负载均衡策略:

package com.qf.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class WebConfig {

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

    //创建对象实现改变Ribbon的负载均衡策略
    @Bean
    public IRule getRule() {
        return new RandomRule();
    }
}

5.4 自定义负载均衡策略

我们自定义的负载均衡策略需要继承AbstractLoadBalancerRule这个类,然后重写choose方法,然后将其注入到容器中。

1.创建ServerInfo类
package com.qf.rule;

import com.netflix.loadbalancer.Server;

public class ServerInfo {
    private Server server;
    private int num;

    public ServerInfo() {
    }

    public ServerInfo(Server server, int num) {
        this.server = server;
        this.num = num;
    }

    public Server getServer() {
        return server;
    }

    public void setServer(Server server) {
        this.server = server;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }
}

2.创建CustomizeRule类

package com.qf.rule;

import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CustomizeRule extends AbstractLoadBalancerRule {

    private int limit = 5;

    /**
     * map的key是服务的名字,value是该服务调用的次数
     */
    private Map<String, ServerInfo> map = new ConcurrentHashMap<>();

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) { }

    /**
     * 返回值的意思是,当该方法返回什么的时候,那么Ribbon或者Feign就调用谁。
     */
    @Override
    public Server choose(Object key) {
        Server finalServer = null;
        ILoadBalancer loadBalancer = getLoadBalancer();
        // 获取所有的服务
        List<Server> servers =  loadBalancer.getAllServers();

        // 获取所有的可用的服务
        List<Server> reachableServers = loadBalancer.getReachableServers();

        int allServiceSize = servers.size(); //获取所有的服务的长度
        int upCount = reachableServers.size(); //获取所有的可用的服务的长度

        if(0 == allServiceSize || 0 == upCount) {
            return null;
        }

        for(int i = 0; i < allServiceSize; i++) {
            Server server = servers.get(i);  // 获取当前遍历的server

            String instanceId = server.getMetaInfo().getInstanceId();
            String providerName = instanceId.split("@@")[1]; //获取服务名

            ServerInfo serverInfo = map.get(providerName); //获取对应服务

            // 首次调用
            if(null == serverInfo) {
                serverInfo = new ServerInfo(server, 1);
                map.put(providerName, serverInfo);
                finalServer = server;
                break;
            } else { //不为空,表示之前肯定调用过
                // 当前遍历的server与正在调用的server是同一个server
                if(serverInfo.getServer().getId().equals(server.getId())) {
                    /**
                     * 1. 如果没有满5次,接着走该服务。
                     * 2. 如果满了5次,接着下个
                     */
                    int num = serverInfo.getNum(); //获取已经调用的次数
                    if(num >= limit) { //超出了5次
                        // 超出了次数,要走下一个,需要判断是否有下一个,如果没有下一个,就回到第一个
                        if(i == (allServiceSize - 1)) { //表示当前服务为整个集群的最后一个服务,拿第一个
                            Server firstServer = servers.get(0);  //如果为最后一个就拿第一个
                            ServerInfo firstServerInfo = new ServerInfo(firstServer, 1);
                            map.put(providerName, firstServerInfo);
                            finalServer = firstServer;
                        }else { //不是最后一个
                            Server nextServer = servers.get(i + 1); //取下一个
                            ServerInfo nextServerInfo = new ServerInfo(nextServer, 1);
                            map.put(providerName, nextServerInfo);
                            finalServer = nextServer;
                        }
                        break;
                    }else {
                        serverInfo.setNum(++num);
                        finalServer = server;
                        break;
                    }
                }
            }
        }
        return finalServer;
    }
}

3.修改WebConfig,添加配置

package com.qf.config;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import com.qf.rule.CustomizeRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class WebConfig {

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

//    @Bean
//    public IRule getRule() {
//        return new RandomRule();
//    }

    //自定义均衡负载服务器
    @Bean
    public IRule getRule() {
        return new CustomizeRule();
    }
}

六. Feign负载均衡

6.1 Feign介绍

feign是基于Ribbon的另外一个负载均衡的客户端框架,只需要在接口上定义要调用的服务名即可,使用起来非常的简单。
1.在springcloud-alibaba-microservice-consumer-8080的pom.xml中添加依赖

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

2.在启动类上加上@EnableFeignClients这个注解

package com.qf;

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

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class MicroServiceConsumer {

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

3.创建UserService

package com.qf.service;

import com.qf.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.List;

@Service
@FeignClient("microservice-provider")
public interface UserService {

    @RequestMapping("/provider-user/findAll")
    public List<User> getUsers();
}

4.创建FeignUserController

package com.qf.controller;

import com.qf.pojo.User;
import com.qf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("feign")
public class FeignUserController {

    @Autowired
    private UserService userService;

    @RequestMapping("getUsers")
    public List<User> getUsers(){

        return userService.getUsers();
    }
}

5.修改端口号,启动多个provider,然后启动consumer,访问浏览器进行测试

6.2服务之间的参数传递

1.在springcloud-alibaba-microservice-provider-7070工程中的UserController添加CRUD方法

package com.qf.controller;

import com.qf.pojo.User;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
@RequestMapping("provider-user")
public class UserController {

    @RequestMapping("findAll")
    public List<User> findAll(){

        //先启动7070,然后修改application.yml配置文件,端口设置为7071再启动
        //System.out.println("7070");
        System.out.println("7071");

        return Arrays.asList(
                new User(1001,"jack"),new User(1002,"tom"));//[{user1},{user2}]
    }

    //查询单个
    @GetMapping("findById")
    public User findById(@RequestParam("uid") Integer uid){
        System.out.println("findById:"+uid);
        return new User(uid,"张三");
    }

    //添加用户
    @PostMapping("add")
    public String add(@RequestBody User user){
        System.out.println("add:"+user);
        return "success";
    }

    //修改用户
    @PutMapping("update")
    public String update(@RequestBody User user){
        System.out.println("update:"+user);
        return "success";
    }

    //删除用户
    @DeleteMapping("delete/{uid}")
    public String delete(@PathVariable("uid") Integer uid){
        System.out.println("delete:"+uid);
        return "success";
    }

}

2.在springcloud-alibaba-microservice-consumer-8080工程中的UserService添加对应方法

package com.qf.service;

import com.qf.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Service
@FeignClient("provider-microservice")
public interface UserService {

    @RequestMapping("/provider-user/findAll")
    public List<User> findAll();


    //查询单个
    @GetMapping("/provider-user/findById")
    public User findById(@RequestParam("uid") Integer uid);

    //添加用户
    @PostMapping("/provider-user/add")
    public String add(@RequestBody User user);

    //修改用户
    @PutMapping("/provider-user/update")
    public String update(@RequestBody User user);

    //删除用户
    @DeleteMapping("/provider-user/delete/{uid}")
    public String delete(@PathVariable("uid") Integer uid);
}

3.在springcloud-alibaba-microservice-consumer-8080工程中的FeignUserController添加对应方法

package com.qf.controller;

import com.qf.pojo.User;
import com.qf.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("feign")
public class FeignUserController {

    @Autowired
    private UserService userService;

    @RequestMapping("getAll")
    public List<User> getAll(){
        return userService.findAll();
    }

    //查询单个
    @GetMapping("findById")
    public User findById(@RequestParam("uid") Integer uid){
        return userService.findById(uid);
    }

    //添加
    @PostMapping("add")
    public String add(User user){
        return userService.add(user);
    }

    //修改
    @PutMapping("update")
    public String update(User user){
        return userService.update(user);
    }

    //删除
    @DeleteMapping("delete/{uid}")
    public String delete(@PathVariable("uid") Integer uid){
        return userService.delete(uid);
    }
}

4.在postman中选择对应的请求方式进行测试

七. 熔断与服务降级

分布式系统中一个微服务需要依赖于很多的其他的服务,那么服务就会不可避免的失败。例如A服务依赖于B、C、D等很多的服务,当B服务不可用的时候,会一直阻塞或者异常,更不会去调用C服务和D服务。同时假设有其他的服务也依赖于B服务,也会碰到同样的问题,这就及有可能导致雪崩效应。
如下案例:一个用户通过通过web容器访问应用,他要先后调用A、H、I、P四个模块,一切看着都很美好。
SpringCloudAlibaba Path1 - 图1
由于某些原因,导致I服务不可用,与此同时我们没有快速处理,会导致该用户一直处于阻塞状态。

SpringCloudAlibaba Path1 - 图2
当其他用户做同样的请求,也会面临着同样的问题,tomcat支持的最大并发数是有限的,资源都是有限的,将整个服务器拖垮都是有可能的。
SpringCloudAlibaba Path1 - 图3
Sentinel是一个用于分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖会不可避免的调用失败,例如超时,异常等,Sentinel能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于保险丝),向调用者返回符合预期的,可处理的备选响应,而不是长时间的等待或者抛出无法处理的异常,这样就保证了服务调用的线程不会被长时间,不必要的占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。
Sentinel在网络依赖服务出现高延迟或者失败时,为系统提供保护和控制;可以进行快速失败,缩短延迟等待时间;提供失败回退(Fallback)和相对优雅的服务降级机制;提供有效的服务容错监控、报警和运维控制手段。
下载地址:https://github.com/alibaba/Sentinel/releases

7.1 依赖

在springcloud-alibaba-microservice-consumer-8080和springcloud-alibaba-microservice-provider-7070工程的pom.xml文件导入依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.1.RELEASE</version>
</dependency>

在D:\sentinel-dashboard目录使用cmd打开,输入 java -jar sentinel-dashboard-1.7.2.jar —server.port=8081 命令启动

7.2 配置

在springcloud-alibaba-microservice-consumer-8080和springcloud-alibaba-microservice-provider-7070工程的application.yml 中添加 sentinel 配置

spring:
  application:
    # 服务消费方的名称
    name: microservice-consumer

  cloud:
    nacos:
      discovery:
        enabled: true
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        # 推送数据
        dashboard: localhost:8081
        # 是接收数据,当在sentinel的面板中配置一些参数,会通过该端口传送过来
        port: 8719

server:
  port: 8080

启动服务提供方以及消费方并调用服务(多调用几次),然后查看sentinel的控制面板

7.3 sentinel的控制面板

在浏览器输入localhost:8081,用户名和密码都是 sentinel,然后访问sentinel的控制面板
接下来对sentinel的控制面板一一讲解:
实时监控
用于查看接口调用的QPS(Query Per Second)以及平均响应时间。
簇点链路
查看当前追踪的所有的访问接口,可以添加流量规则、降级规则、热点规则、授权规则。
流量规则
SpringCloudAlibaba Path1 - 图4
资源名:是需要控制的链路的名字,例如/student/all等
针对来源: 默认为default表示所有,也可以针对特定的服务进行设置。
阈值类型: 是指如何进行限制,可以是QPS,也可以是线程。
单机阈值: 是控制QPS或者线程的数量。
流量模式: 直接表示只是针对指定资源进行限制;关联是指当被关联的资源达到阈值时候,指定资源被限制访问;链路是更加细粒度的控制,控制指定资源对链路的限制。
流控效果: 快速失败是指,当无法访问的时候立即给用户一个错误响应;Warm Up(预热)是指经过指定的时间后才达到指定的阈值(sentinel内有值为 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值,参考地址:https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—-%E5%86%B7%E5%90%AF%E5%8A%A8);排队等待是指匀速的通过(每秒指定的QPS),其他的请求进行排队,但是并不会一直排下去,超过指定的时间就会失败。阈值类型必须设置为QPS,不能为线程。
降级规则
SpringCloudAlibaba Path1 - 图5
资源名: 要实现降级的资源。
降级策略:
1.RT(平均响应时间)
如果在一秒钟之内进入的请求的平均响应时间大于1ms,那么在未来5s钟之内所有的请求都会熔断降级。
2.异常比例
如果在一秒钟之内的请求数异常比例大于指定的数据,那么在未来的时间窗口内会一直熔断降级。统计单位为s.
3.异常数
如果在一分钟之内,异常数量大于指定的值,那么在指定的时间窗口内请求一直会熔断降级,注意时间窗口的值一般设置要大于60,因为设置如果小于60,可能会一直处于熔断状态。
@SentinelResource是sentinel中非常重要的注解,提供了简单易用的功能
在springcloud-alibaba-microservice-consumer-8080工程中创建FeignUserFallback

package com.qf.fallback;

import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;

import java.util.HashMap;
import java.util.Map;

public class FeignUserFallback {

    // 针对 getUsers 方法流控的降级处理
    public static Object getUsersHandler(BlockException ex) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", -1);
        if(ex instanceof DegradeException) {
            System.out.println("降级");
        }else if(ex instanceof FlowException) {
            System.out.println("限流");
        }else if(ex instanceof ParamFlowException) {
            System.out.println("热点");
        } else if(ex instanceof AuthorityException) {
            map.put("code", "-3");
            map.put("msg", "来源不合法");
        }
        return map;
    }
}

修改springcloud-alibaba-microservice-consumer-8080工程中的FeignUserController中对应的方法进行测试

/**
     * blockHandler是将服务降级的方法与目标方法在同一个类中。如果不同的类中,有些方法需要使用相同的服务降级,
     * 就可以使用blockHandlerClass来定义个一类,然后通过blockHandler来指定对应的降级的方法。方法必须是静态。
     */
//    @SentinelResource(value = "getUsers", blockHandlerClass =  FeignUserFallback.class, blockHandler = "getUsersHandler")
//    @RequestMapping("getUsers")
//    public Object getUsers() {
//        return userService.getUsers();
//    }

/**
 1. 如果没有用blockHandler,fallback无论是违反了什么规则,都走fallback; 但是无法区别是限流、降级、热点等。
 2. blockHandler和 fallback 如果都配置了,优先走blockHandler,可以区别限流、降级、热点等。
 3. blockHandler是将服务降级的方法与目标方法在同一个类中。如果不同的类中,有些方法需要使用相同的服务降级, 就可以使用blockHandlerClass来定义个一类,然后通过blockHandler来指定对应的降级的方法。方法必须是静态。
 4. fallback可以处理非 sentinel 的异常。
 */
    @SentinelResource(value = "getUsers", fallback = "getUsersFallback")
    @RequestMapping("getUsers")
    public Object getUsers() {
        return userService.getUsers();
    }

    public Object getUsersFallback(Throwable throwable) {
        //int i = 1/0;
        System.out.println(throwable.getMessage());
        Map<String, Object> map = new HashMap<>();
        map.put("error", -2);
        return map;
    }

热点规则
热点规则是针对具体的请求参数进行设置,例如如下的方法:

//查询单个
@GetMapping("findById/{uid}")
@SentinelResource("findById")//添加配置
public User findById(@PathVariable("uid") Integer uid){
    return userService.findById(uid);
}

SpringCloudAlibaba Path1 - 图6
SpringCloudAlibaba Path1 - 图7
资源名: 是@SentinelResource中设置的值
参数索引: 对那个参数进行QPS限制,通过索引来指定。
单机阈值:指定在统计时长内的阈值。
统计窗口时长: 统计的QPS的时间。
系统规则
SpringCloudAlibaba Path1 - 图8
LOAD: 仅对 Linux/Unix-like 机器生效,参考值一般是 CPU cores 2.5
RT: 当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
线程数: 当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口QPS: 当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
*授权规则

授权规则是指可以将特定的访问应用加入黑名单或者白名单,但是必须在访问的时候携带应用的名称
1.在springcloud-alibaba-microservice-consumer-8080工程创建SentinelOriginParser类

@Component
public class SentinelOriginParser implements RequestOriginParser {

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        String origin = httpServletRequest.getParameter("origin");

        if(StringUtil.isBlank(origin)) {
            throw new IllegalArgumentException("origin parameter must be specified.");
        }

        return origin;
    }
}

加上了来源解析后,在往后的访问中必须要携带origin参数,在sentinel的dashboard中可以作如下配置:
注意:browser 是 origin的值,黑白名单表示是否允许访问
SpringCloudAlibaba Path1 - 图9
集群流控
SpringCloudAlibaba Path1 - 图10
是否集群: 是否采用集群
均摊阈值: 就是每个集群节点每秒的QPS.
集群阈值模式: 单机均摊是集群中每个节点每秒的QPS, 总体阈值是整个集群每秒的QPS.

八. Feign与Sentinel的整合

1.在springcloud-alibaba-microservice-consumer-8080工程中的pom.xml中添加 feign 的配置

server:
  port: 8080

spring:
  application:
    # 服务消费方名称
    name: consumer-microservice
  cloud:
    nacos:
      discovery:
        # 开启nacos
        enabled: true
        server-addr: 127.0.0.1:8848
    # 配置 sentinel
    sentinel:
      transport:
        dashboard: localhost:8081

feign:
  sentinel:
    enabled: true

8.1 服务降级后的处理

可以在@FeignClient中配置fallback,来指定服务降级后给用户返回的什么样的数据,fallback的值为Class类型的对象,该类必须要实现该对应的接口。
2.在 UserService 上添加 fallback 注解

@Service
@FeignClient(value = "provider-microservice",fallback = UserServiceFallback.class)
public interface UserService {

    @RequestMapping("/provider-user/findAll")
    public List<User> findAll();
}

3.编写 UserServiceFallback 类

package com.qf.service;

import com.qf.pojo.User;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Component
public class UserServiceFallback implements UserService {

    @Override
    public List<User> findAll() {
        return Arrays.asList(new User(-1001,"服务降级"));
    }
}

4.在sentinel面板上设置限流或者降级配置,启动测试即可

九. Sentinel的持久化(了解)

通过接入 Sentinel Dashboard 后,在页面上操作来更新规则,都无法避免一个问题,那就是服务重新后,规则就丢失了,因为默认情况下规则是保存在内存中的。sentinel中持久化的方式有两种,pull模式和push模式。
pull模式是指站在第三方持久化系统(redis, nacos)的角度,他们去到sentinel中定时去拉去配置信息,可能会造成数据的不一致性。
push模式是站在sentinel的角度,将其配置信息主动推送给第三方持久化系统,sentinel官方也推荐在线上使用该模式。

9.1 sentinel-dashboard改造

A. 将sentinel的源码clone到本地
B. 进入到sentinel-dashboard目录下,修改pom.xml文件

<!-- 修改之前的内容 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <scope>test</scope>
</dependency>
<!-- 修改之后的内容 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

C. 修改 src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html 文件
未修改之前的内容


<!-- <li ui-sref-active="active" ng-if="entry.appType==0">  -->
<!-- <a ui-sref="dashboard.flow({app: entry.app})">  -->
<!-- <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则 V1</a>  -->
<!-- </li> -->
<li ui-sref-active="active" ng-if="!entry.isGateway">
    <a ui-sref="dashboard.flowV1({app: entry.app})">
        <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则
    </a>
</li>

修改之后的内容

<li ui-sref-active="active" ng-if="entry.appType==0">
    <a ui-sref="dashboard.flow({app: entry.app})">
        <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则
    </a>
</li>
<!--   <li ui-sref-active="active" ng-if="!entry.isGateway">-->
<!--       <a ui-sref="dashboard.flow({app: entry.app})">-->
<!--            <i class="glyphicon glyphicon-filter"></i>&nbsp;&nbsp;流控规则
           </a>   -->
<!--    </li>-->

D. 将 src\test\java\com\alibaba\csp\sentinel\dashboard\rule\nacos 目录下的四个Java文件拷贝到src\main\java\com\alibaba\csp\sentinel\dashboard\rule 目录下
SpringCloudAlibaba Path1 - 图11
E. 修改 src\main\webapp\resources\app\scripts\controllers\identity.js 文件,修改内容如下:
SpringCloudAlibaba Path1 - 图12
F. 重新打包生成Jar包,进入到sentinel目录下(注:不是sentinel-dashboard目录),执行如下命令:

mvn clean                 
mvn install -DskipTests

G. 进入到sentinel-dashboard/target目录下,执行如下内容:

java -jar sentinel-dashboard.jar

9.2 配置

spring:
  cloud:
    sentinel:
      datasource:
        # 这个名字随意,但是要有意义
        flow: 
          nacos:
            server-addr: 192.168.31.173:8848
            groupId: SENTINEL_GROUP
            rule-type: flow

9.3 测试

在sentinel-dashboard控制面板添加一个流量控制规则
SpringCloudAlibaba Path1 - 图13
SpringCloudAlibaba Path1 - 图14

Dubbo —>zookeerer Springcloud —>eukre SpringCloudAlibaba —>nacos HSF:https://blog.csdn.net/lemon89/article/details/90742258

常用的五大组件

Eureka(服务发现)
Ribbon(负载均衡)
Hystrix(熔断器)
Zuul,GetWay(网关)
SpringCloudConfig(分布式配置)
Seata(分布式事务)
Sleuth(链路跟踪)

SpringCloudAlibaba有一个版本限制

CAP原则

只能满足两点

一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本) 可用性(A):保证每个请求不管成功或者失败都有响应。 分区容错性(P):系统中任意信息的丢失或失败不会影响系统的继续运作。

彩虹表

彩虹表是一个用于加密散列函数逆运算的预先计算好的表,马丁·赫尔曼早期提出的简单算法的应用。

QPS

QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
2000qbs

防重放攻击

对于非幂等性接口的攻击
加入参数

令牌桶算法,漏斗算法

一种限流的方法
如果要让自己的系统不被打垮,用令牌桶。如果保证别人的系统不被打垮,用漏桶算法

二级缓存

内容放到集合中而不是内存

nacos原理

服务注册方法:以Java nacos client v1.0.1为例子,服务注册的策略的是每5秒向nacos server发送一次心跳, 心跳带上了服务名,服务ip,服务端口等信息。同时nacos server也会向client主动发起健康检查,支持tcp/http检查。 如果1 5秒内无心跳且健康检查失败则认为实例不健康,如果30秒内健康检查失败则剔除实例。