Spring cloud alibaba

1.微服务与微服务架构

微服务
强调的是服务的大小,它关注的是某一个点,是具体解决某一个问题/提供落地对应服务的一个服务应用,
狭意的看,可以看作Eclipse里面的一个个微服务工程/或者Module

微服务架构

微服务架构是⼀种架构模式,它提倡将单⼀应⽤程序划分成⼀组⼩的服务,服务之间互相协调、互相配合,为⽤户提供最终价值。每个服务运⾏在其独⽴的进程中,服务与服务间采⽤轻量级的通信机制互相协作(通常是基于HTTP协议的RESTful API)。每个服务都围绕着具体业务进⾏构建,并且能够被独⽴的部署到⽣产环境、类⽣产环境等。另外,应当尽量避免统⼀的、集中式的服务管理机制,对具体的⼀个服务⽽⾔,应根据业务上下⽂,选择合适的语⾔、⼯具对其进⾏构建。

2.什么是Spring cloud alibaba

Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。 依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里分布式应用解决方案,通过阿里中间件来迅速搭建分布式应用系统

3.为什么要使用spring cloud alibaba

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

4.注册中心nacos

nacos是阿里巴巴研发的一个集注册中心与配置中心于一体的管理平台,使用其他非常的简单。下载地址为:https://github.com/alibaba/nacos/releases
在这里插入图片描述

其中默认的登录名和密码是:nacos/nacos
4.1 nacos的用户名和密码修改
1.分别执行conf目录下的nacos-mysql.sql两个脚本文件
2. 重新配置密码
新建一个springboot项目,导入相关依赖

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

生成密码的代码

public static void main(String[] args) {
	BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
    // 生成的密码为:$2a$10$04MGTL.cJNZPpR3rFt/I2.43F.V75NH.5wdK.jngaO9Mc91mfonAO
	System.out.println(bCryptPasswordEncoder.encode("1"));
}

向数据库中插入相关的用户名和密码即可
3.配置数据库的连接
在conf目录下的application.properties目录下加入如下内容:

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

4.2 nacos集群配置
A. 将conf目录下的cluster.conf.example拷贝一份重命名为cluster.conf,在文件中加入所有集群节点的ip和端口号,文件内容如下:

127.0.0.1:8848
127.0.0.1:8849

B. 修改windows启动文件 startup.cmd 的配置,修改内容如下:

set MODE="standalone"  #默认的配置
set MODE="cluster"     #修改后的内容

注:如果是Linux环境不用作任何的修改。
C.启动两个nacos,界面中出现如下的内容,表示集群配置成功
在这里插入图片描述
4.3 nacos的应用

服务的提供方

jar包依赖

 <dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>0.9.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>

application.yml配置文件

server:
  port: 7071

spring:
  application:
    # 注册到注册中心的服务名
    name: microservice-provider
  cloud:
    nacos:
      discovery:
        # 开启nacos的服务发现
        enabled: true
        server-addr: 192.168.176.1:8848,192.168.176.1:8849

启动服务的提供方

服务页面结果
在这里插入图片描述
服务的消费方

pom.xml依赖配置

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>0.9.0.RELEASE</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>Greenwich.SR2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

application.yml配置

server:
  port: 8080
spring:
  application:
    # 服务名
    name: alibaba-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 172.18.96.177:8848,172.18.96.177:9948
        # 不将自己的服务注册到注册中心
        register-enabled: false

启动服务的消费方

服务页面结果
在这里插入图片描述
调用服务提供方的服务

@RequestMapping
    public Object getUsers() {
        List<ServiceInstance> list = discoveryClient.getInstances("alibaba-provider");
        String targetUrl = list.stream().map(si -> si.getUri() + "/user").findFirst().get();
        List<String> resultList = restTemplate.getForObject(targetUrl, List.class);
        return resultList;
    }

5.Ribbon负载均衡

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

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

@Configuration
public class WebConfig {
    **@LoadBalanced**
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

服务消费方的调用

// 直接写上服务名即可
List<String> resultList = restTemplate.getForObject("http://alibaba-provider/user", List.class);

负载均衡策略
Ribbon提供了一个很重要的接口叫做IRule,其中定义了很多的负载均衡策略,默认的是轮询的方式
在这里插入图片描述
改变Ribbon的负责负载均衡

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

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

@Configuration
public class ConsumerConfig {
    @LoadBalanced   //负载均衡
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
    // 把自定义的负载均衡添加到容器中
    @Bean
    public IRule iRule(){
        return new CustomizeRule();
    }
}

自定义负载均衡

/**
 *   自定义负载均衡策略
 *      自定义负载均衡策略需要继承AbstractLoadBalancerRule这个类 重写choose方法   然后将其注入到容器中
 */
public class CustomizeRule extends AbstractLoadBalancerRule{
    //定义每个服务可以接受请求的次数
    private Integer limit=3;
    /**
     *   map中的key是服务对应的名字  value是服务所对应的次数
     */
    Map<String, ServerInfo> map=new ConcurrentHashMap<>();

    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
    }

    /**
     *返回值的意思是:当前方法的返回什么的时候,那么Ribbon或者feign就调用谁
     * @param ket
     * @return
     */
    @Override
    public Server choose(Object ket) {
        //用来接收最后要返回的Server
        Server finalServer=null;
        ILoadBalancer iLoadBalancer=getLoadBalancer();
        //获取所有的服务
        List<Server> servers=iLoadBalancer.getAllServers();
        //获取所有可用的服务
        List<Server> serverList=iLoadBalancer.getReachableServers();
        //获取所有服务的长度
        int allServerLength=servers.size();
        //获取可用服务的长度
        int upCount=serverList.size();
        //如果所有服务的长度和所有可用的长度都为0的时候,直接返回null
        if (allServerLength==0||upCount==0){
            return null;
        }
        for (int i=0;i<allServerLength;i++){
            //获取当前的服务
            Server server=servers.get(i);
            //获取服务名
            String instanceId=server.getMetaInfo().getInstanceId();
            String serverName=instanceId.split("@@")[1];
            //获取对应的服务
            ServerInfo serverInfo=map.get(serverName);
            //第一次调用
            if (serverInfo==null){
                serverInfo=new ServerInfo(server,1);
//                serverInfo.setServer(server);
//                serverInfo.setNum(1);
                map.put(serverName,serverInfo);
                finalServer=server;
                break;
            }else {     //不是第一次调用,表示之前用服务调用过
                //判断当前遍历的服务与正在调用的服务是同一个服务
                if (server.getId().equals(serverInfo.getServer().getId())){
                    /**
                     *    1.如果服务调用的次数还没有达到次数,就继续走这个服务
                     *    2.如果服务调用的次数达到次数,就进行下一次服务调用 (这里需要判断是否有下一个服务)
                     */
                    int num=serverInfo.getNum();
                    //表示当前服务调用的次数还没有到达
                    if (num<limit){
                        serverInfo.setNum(++num);
                        finalServer=server;
                        break;
                    }else {  //表示当前的服务的调用次数已经到达次数
                        //判断是否有下一个服务
                        if (i==allServerLength-1){  //表示这个服务已经是最后一个服务了
                            //那么从第一个服务
                            Server newServer=servers.get(0);
                            ServerInfo firstServer=new ServerInfo(newServer,1);
                            map.put(serverName,firstServer);
                            finalServer=newServer;
                        }else{   //不是最后一个服务
                            Server nextServer=servers.get(i+1);
                            ServerInfo nextServerInfo=new ServerInfo(nextServer,1);
                            map.put(serverName,nextServerInfo);
                            finalServer=nextServer;
                        }
                        break;
                    }
                }
            }
        }
        return finalServer;
    }
}

6.Feign负载均衡

feign是基于Ribbon的另外一个负载均衡的客户端框架,只需要在接口上定义要调用的服务名即可,使用起来非常的简单

pom.xml依赖

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

启动类配置

@SpringBootApplication
@EnableFeignClients     //  需要加该注解  开启Feign负载均衡
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}

服务接口配置

@Service
@FeignClient(value = "microservice-provider",fallbackFactory = UserServiceFallback.class)
public interface UserService {
     @GetMapping("/user")
     ResponseData getAll();
     @GetMapping("/user/info")
     ResponseData getUserByInfo(@RequestParam Integer id, @RequestParam String name);
     @GetMapping("/user/{id}")
     ResponseData getById(@PathVariable Integer id);
     @PutMapping("/user")
     ResponseData updateUser(@RequestParam Integer id,@RequestParam String name);
     @PostMapping("/user")
     ResponseData addUser(User user);
     @DeleteMapping("/user/{id}")
     ResponseData deleteUser(@PathVariable Integer id);
}

7.熔断和服务降级
分布式系统中一个微服务需要依赖于很多的其他的服务,那么服务就会不可避免的失败。例如A服务依赖于B、C、D等很多的服务,当B服务不可用的时候,会一直阻塞或者异常,更不会去调用C服务和D服务。同时假设有其他的服务也依赖于B服务,也会碰到同样的问题,这就及有可能导致雪崩效应。
如下案例:一个用户通过通过web容器访问应用,他要先后调用A、H、I、P四个模块,一切看着都很美好。
在这里插入图片描述
由于某些原因,导致I服务不可用,与此同时我们没有快速处理,会导致该用户一直处于阻塞状态。
在这里插入图片描述
当其他用户做同样的请求,也会面临着同样的问题,tomcat支持的最大并发数是有限的,资源都是有限的,将整个服务器拖垮都是有可能的
在这里插入图片描述
Sentinel是一个用于分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖会不可避免的调用失败,例如超时,异常等,Sentinel能保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。

​断路器本身是一种开关装置,当某个服务单元发生故障后,通过断路器的故障监控(类似于保险丝),向调用者返回符合预期的,可处理的备选响应,而不是长时间的等待或者抛出无法处理的异常,这样就保证了服务调用的线程不会被长时间,不必要的占用,从而避免故障在分布式系统中的蔓延,乃至雪崩。

Sentinel在网络依赖服务出现高延迟或者失败时,为系统提供保护和控制;可以进行快速失败,缩短延迟等待时间;提供失败回退(Fallback)和相对优雅的服务降级机制;提供有效的服务容错监控、报警和运维控制手段。

​ 下载地址:https://github.com/alibaba/Sentinel/releases

jar包依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
**通常情况下我们只需要在服务提供方实现熔断或者服务降级即可,但是如果要相对服务消费方是实现限流,在服务的提供方和消费方都需要加如下配置。**
spring:
  cloud:
     sentinel:
      transport:
        dashboard: localhost:8081
        port: 8719

sentinel的控制面板讲解

实时监控

用于查看接口调用的QPS(Query Per Second)以及平均响应时间。

簇点链路

查看当前追踪的所有的访问接口,可以添加流量规则降级规则热点规则授权规则

流量规则
在这里插入图片描述资源名:**是需要控制的链路的名字,例如/student/all等

针对来源: 默认为default表示所有,也可以针对特定的服务进行设置。

阈值类型: 是指如何进行限制,可以是QPS,也可以是线程。

单机阈值: 是控制QPS或者线程的数量。

流量模式: 直接表示只是针对指定资源进行限制;关联是指当被关联的资源达到阈值时候,指定资源被限制访问;链路是更加细粒度的控制,控制指定资源对链路的限制。

流控效果: 快速失败是指,当无法访问的时候立即给用户一个错误响应;Warm Up(预热)是指经过指定的时间后才达到指定的阈值(sentinel内有值为 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值,参考地址:https://github.com/alibaba/Sentinel/wiki/限流---冷启动);排队等待是指匀速的通过(每秒指定的QPS),其他的请求进行排队,但是并不会一直排下去,超过指定的时间就会失败。阈值类型必须设置为QPS,不能为线程。

降级规则
降级规则
降级参数介绍

资源名 要实现降级的资源

RT(平均响应时间) 如果在一秒钟之内进入的请求的平均响应时间大于1ms,那么在未来5s钟之内所有的请求都会熔断降级。

异常比例 如果在一秒钟之内的请求数异常比例大于指定的数据,那么在未来的时间窗口内会一直熔断降级。统计单位为s.

异常数 如果在一分钟之内,异常数量大于指定的值,那么在指定的时间窗口内请求一直会熔断降级,注意时间窗口的值一般设置要大于60,因为设置如果小于60,可能会一直处于熔断状态。

热点规则

热点规则是针对具体的请求参数进行设置

@RequestMapping("/edit")
@SentinelResource("edit")   //必须的有
public Object edit(@RequestParam(required = false) String id,
				   @RequestParam(required = false) Integer age) {
	return this.studentService.commons();
}

热点规则
资源名: 是@SentinelResource中设置的值

参数索引: 对那个参数进行QPS限制,通过索引来指定。

单机阈值: 指定在统计时长内的阈值。

统计窗口时长: 统计的QPS的时间。

授权规则

授权规则是指可以将特定的访问应用加入黑名单或者白名单,但是必须在访问的时候携带应用的名称。

在这里插入图片描述

@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参数

@SentinelResource

@SentinelResource是sentinel中非常重要的注解,提供了简单易用的功能。其中blockHandler注解是限流的处理方法,fallback是服务降级的处理方法。

public class FeignUserFallback {
    public static Object getAllHandler(BlockException ex) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", -1);
        map.put("msg","手速太快,跟不上");
        return map;
    }

    public Object getAllFallBack(BlockException e){
        Map<String,Object> map=new HashMap<>();
        map.put("code",-2);
        map.put("msg","fallback错误");
        return map;
    }
}
 /**
     *     如果没有blockHandler,fallback无论是违反了什么规则,都执行fallback,但是无法区别它是什么规则
     *     blockHandler和fallback如果都配置,优先执行blockHandler,可以区分是什么规则
     *     blockHandler是将服务降级的方法与目标方法在同一个类中。如果不同的类中,有些方法需要使用相同的服务降级,
     *     就可以使用blockHandlerClass来定义个一类,然后通过blockHandler来指定对应的降级的方法。方法必须是静态
     *     fallback可以处理非 sentinel 的异常。
     */
 //   @SentinelResource(value ="getAll",blockHandlerClass=FeignUserFallback.class,blockHandler="getAllHandler")
//    @SentinelResource(value = "getAll",fallbackClass = FeignUserFallback.class,fallback = "getAllFallBack")   不可以?
    @RequestMapping
    //要求方法的类型和出现异常的类型要保持一致
    public Object getAll(){     //true
        try {
            TimeUnit.MILLISECONDS.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return userService.getAll();
    }

8 Feign与sentinel的整合

yml文件的配置

feign:
  sentinel:
    # 默认是没有提示的
    enabled: true

服务降级后的处理

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

@FeignClient(name="alibaba-provider", fallback = UserServiceFallback.class)
public interface UserService {


    @RequestMapping("/user")
    public List<String> getUsers();
}

对于fallback来讲,并不能追踪到异常信息,在实际的业务处理过程中,我们往往需要记录异常的信息,那么就要使用fallbackFactory属性来实现。

@Service
@FeignClient(value = "microservice-provider",fallbackFactory = UserServiceFallback.class)
public interface UserService {
     @GetMapping("/user")
     ResponseData getAll();
     @GetMapping("/user/info")
     ResponseData getUserByInfo(@RequestParam Integer id, @RequestParam String name);
 }
}
@Component
@Slf4j
public class UserServiceFallback implements FallbackFactory<UserService> {
    @Override
    public UserService create(Throwable throwable) {
        return new UserService(){
            public ResponseData getData(){
                ResponseData responseData=new ResponseData();
                responseData.setCode(-1);
                responseData.setMag("服务降级");
                return responseData;
            }
            @Override
            public ResponseData getAll() {
                log.error("查询全部用户出现了异常:"+throwable.getMessage());
                return getData();
            }

            @Override
            public ResponseData getUserByInfo(Integer id, String name) {
                log.error("根据条件查询用户出现了异常:"+throwable.getMessage());
                return getData();
            }

            @Override
            public ResponseData getById(Integer id) {
                log.error("根胡用户Id查询用户出现了异常:"+throwable.getMessage());
                return getData();
            }

            @Override
            public ResponseData updateUser(Integer id, String name) {
                log.error("更新用户出现了异常:"+throwable.getMessage());
                return getData();
            }

            @Override
            public ResponseData addUser(User user) {
                log.error("添加用户出现了异常:"+throwable.getMessage());
                return getData();
            }

            @Override
            public ResponseData deleteUser(Integer id) {
                log.error("删除用户出现了异常:"+throwable.getMessage());
                return getData();
            }
        };
    }
}

自定义注解@IgnoreWrapper 对数据进行二次封装

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface IgnoreWrapper {
}
@ControllerAdvice
public class ResponseWrapperAdvisor implements ResponseBodyAdvice {
    /**
     *  如果该方法返回true  那么beforeBodyWrite才会执行
     * @param returnType
     * @param converterType
     * @return
     */
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
         //如果注解加到类上  整个类志杰不进行封装
         IgnoreWrapper ignoreWrapper=returnType.getMethod().getDeclaringClass().getAnnotation(IgnoreWrapper.class);
         if (ignoreWrapper!=null){
             return false;
         }
         ignoreWrapper=returnType.getMethodAnnotation(IgnoreWrapper.class);
        //判断方法上是否有IgnoreWrapper注解 如果没有,则进行封装
        boolean flag=ignoreWrapper==null;
        System.out.println("flag:"+flag);
        return flag;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        //获取返回值类型
        Class<?> clazz=returnType.getMethod().getReturnType();
        //返回值类型为void或者ResponseData,则不进行封装
        if (clazz==void.class|| ResponseData.class==clazz){
            return body;
        }
        ResponseData responseData=new ResponseData();
        responseData.setCode(1);
        responseData.setMag("success");
        responseData.setData(body);
        return responseData;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值