SpringCloud

第一个SpringCloud项目

服务提供

@RestController
public class TestController {
    @RequestMapping("/test")
    public String test(){
        return "第一个SpringCloud服务提供者";
    }
}

服务消费

@RestController
public class TestController {
    @Resource
    private RestTemplate restTemplate;
    @RequestMapping("/test")
    public String test(){
        /**
         * RestTemplate 基于Http协议的工具对象
         * SpringCloud中利用这个对象访问服务提供者  这个对象可以new 也可以交给spring(建议)
         *
         * getForEntity 以get方式提交请求 @getMapping或@RequestMapping
         * 参数1 :需要访问的请求路径
         * 参数2 返回的数据类型
         * 参数3 可变长度为Object类型的数据,表示本次请求的url参数
         *
         * 注意 SpringCloud 返回的是rest风格的数据,因此数据类型全部都是string类型的json格式
         *
         * 返回值 ResponseEntity 封装本次请求的访问实体
         *
         *
         */
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://localhost:8081/test", String.class);
        System.out.println(forEntity.getStatusCode());//状态码
        System.out.println(forEntity.getHeaders());//响应码
        String body  = forEntity.getBody();
        return "第一个SpringCloud服务消费者"+body;
    }
}

服务的注册中心Eureka(保证AP)

 

 

 

CAP理论

搭建eruka注册中心

 

 配置文件

spring.application.name=eurkea-server
# 应用服务 WEB 访问端口
server.port=9001


eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/

启动文件

@EnableEurekaServer
@SpringBootApplication
public class EurkeaServerApplication {

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

}

页面打开

 

eruka服务提供

idea 创建项目

 

 

启动文件

@EnableEurekaClient
@SpringBootApplication
public class EurkeaClientProviderApplication {

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

}

配置文件

spring.application.name=eurkea-client-provider
# 应用服务 WEB 访问端口
server.port=8081
eureka.client.service-url.defaultZone=http://localhost:9001/eureka

eruka服务消费

RestTemplateConfig

@Configuration
public class RestTemplateConfig {
    /**
     * 在spring的应用上下文定义一个Rest模板对象
     *
     * LoadBalanced 注解用于标记当前的RestTemplate使用Ribbon的负载均衡访问服务的提供者
     * 必须使用 否则无法访问服务
     *
     * 默认Ribbon 的负载均衡是轮训的
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

启动文件

@EnableEurekaClient
@SpringBootApplication
public class EurkeaClientProviderApplication {

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

}

配置文件

# 应用名称
spring.application.name=eurkea-client-consumer
# 应用服务 WEB 访问端口
server.port=8080
eureka.client.service-url.defaultZone=http://localhost:9001/eureka/

controller

package com.springcloud.controller;

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 javax.annotation.Resource;

@RestController
public class TestController {
    @Resource
    private RestTemplate restTemplate;
    @RequestMapping("/test")
    public String test(){
        /**
         * 通过注册中心的注册中心发现服务并访问服务  EURKEA-CLIENT-PROVIDER 不区分大小写
         */
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://EURKEA-CLIENT-PROVIDER/test", String.class);
        String body  = forEntity.getBody();
        return "eruka服务消费者"+body;
    }
}

负载均衡

Ribbon 提供负载均衡 主要是客户端的负载均衡

在springcloud中使用注解@LoadBalanced,在RestTemplate开启负载均衡

 

 

 

服务端负载均衡

 

 

 

高可用注册中心

eruka集群(相互复制注册数据)

 

 

 搭建eruka集群

主要是创建多个eruka注册中心

注册中心

# 应用名称
spring.application.name=eurkea-server
# 应用服务 WEB 访问端口
server.port=9001
eureka.instance.hostname=eureka9001


eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/

9002

# 应用名称
spring.application.name=eurkea-cluster-server-9002
# 应用服务 WEB 访问端口
server.port=9002
eureka.instance.hostname=eureka9002


eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:9003/eureka/

9003

# 应用名称
spring.application.name=eurkea-cluster-server-9003
# 应用服务 WEB 访问端口
server.port=9003


eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:9001/eureka/

客户端

eureka.client.service-url.defaultZone=http://localhost:9001/eureka/,http://localhost:9002/eureka/,http://localhost:9003/eureka/

自我保护模式

服务端

 

 

 客服端配置

 

 REST请求

传递对象和集合

@RestController
public class TestController {
    @Resource
    private RestTemplate restTemplate;
    @RequestMapping("/getForEntity01")
    public String getForEntity01(){
        /**
         * 接收对象
         */
        ResponseEntity<User> forEntity = restTemplate.getForEntity("http://localhost:8081/getForEntity01", User.class);
        User body  = forEntity.getBody();
        return "第一个SpringCloud服务消费者"+body;
    }


    @RequestMapping("/getForEntity02")
    public String getForEntity02(){
        /**
         * 接收对象
         */
        ResponseEntity<List> forEntity = restTemplate.getForEntity("http://localhost:8081/getForEntity02", List.class);
        List body  = forEntity.getBody();
        return "第一个SpringCloud服务消费者"+body;
    }
}

参数传递

 @RequestMapping("/getForEntityParams01")
    public String getForEntityParams01(){
        /**
         * 参数传递
         * id={0}&name={1}&age={2} 请求路径中的占位符
         *
         */
        Object[] params={2L,"李四",34};
        String url = "http://localhost:8081/getForEntityParams01?id={0}&name={1}&age={2}";
        ResponseEntity<User> forEntity = restTemplate.getForEntity(url, User.class,params);
        User body  = forEntity.getBody();
        return "第一个SpringCloud服务消费者"+body;
    }
    @RequestMapping("/getForEntityParams02")
    public String getForEntityParams02(){
        /**
         * id={id}&name={name}&age={age} 请求路径中的占位符
         */
        String url = "http://localhost:8081/getForEntityParams01?id={id}&name={name}&age={age}";
        Map<String,Object>  params =new HashMap<String,Object>();
        params.put("id",3L);
        params.put("name","王五");
        params.put("age",23);
        ResponseEntity<User> forEntity = restTemplate.getForEntity(url, User.class,params);
        User body  = forEntity.getBody();
        return "第一个SpringCloud服务消费者"+body;
    }

getForObject

 

 @RequestMapping("/getForObject")
    public String getForObject(){
        /**
         * getForObject 和 getForEntity 方法使用完全相同 都是对应GetMapping或RequestMapping
         * 区别在于getForObject 直接将服务提供者的返回的数据以参数2 所指定的类型进行返回 不会以ResponseEntity对象返回
         * 如果不需要服务提供者的响应头文件信息时 完全可以使用getForObject来替代getForEntity
         */
        String url = "http://localhost:8081/getForObject";
        User user = restTemplate.getForObject(url,User.class);
        return "第一个SpringCloud服务消费者"+user;
    }

postForObject

 

 

@RequestMapping("/postForObject")
    public String postForObject(){
        /**
         *post相关的对应服务提供者POSTMapping或者RequestMapping
         *
         * postForObject:
         * 参数1:为请求的地址路径参数
         * 参数2:具体参数对象没有写null
         * 参数3:为本次响应的具体数据类型
         * 参数4:地址路径动态数据 Object类型的数据 可以是Map具体和get相似
         */
        LinkedMultiValueMap params = new LinkedMultiValueMap();
        params.add("name","钱七");
        params.add("id",6L);
        params.add("age",48);
        String url = "http://localhost:8081/postForObject";
        User user = restTemplate.postForObject(url,params,User.class);
        return "第一个SpringCloud服务消费者"+user;
    }

put

 

 

 @RequestMapping("/put")
    public String put(){
        String url = "http://localhost:8081/put";
        LinkedMultiValueMap params = new LinkedMultiValueMap();
        params.add("name","钱七");
        params.add("id",6L);
        params.add("age",48);
        restTemplate.put(url,params);
        return "服务消费者 执行put请求";
    }

delete

 

 

@RequestMapping("/delete")
    public String delete(){
        String url = "http://localhost:8081/delete?id={0}";
        restTemplate.delete(url,8L);
        return "服务消费者 执行delete请求";
    }

 服务熔断

熔断器服务保护机制

 

在启动文件中激活服务熔断机制

@SpringBootApplication
@EnableEurekaClient
//激活熔断机制
@EnableCircuitBreaker
public class EurekaClientHystrixConsumerApplication {

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

}

 

 

 

 

 

@HystrixCommand(fallbackMethod = "error")
    @RequestMapping("/test")
    public String test(){
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test", String.class);
        String body  = forEntity.getBody();
        return "熔断机制服务消费者"+body;
    }
    public String error(){
        return "服务已经被熔断";
    }

超时熔断

 

 

 

@HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
    @RequestMapping("/test02")
    public String test02(){
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test02", String.class);
        String body  = forEntity.getBody();
        return "熔断机制服务消费者"+body;
    }

服务降级

 

 

 

public String error(){
        return "服务已经被熔断";
    }

异常信息

 public String error(Throwable throwable){
    System.out.println(throwable.getClass());
    System.out.println(throwable.getMessage());
    return "服务已经被熔断"+throwable.getMessage();
}
@HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
    @RequestMapping("/test02")
    public String test02(){
        // 服务提供者延时异常
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test02", String.class);
        String body  = forEntity.getBody();
        return "熔断机制服务消费者"+body;
    }

    @HystrixCommand(fallbackMethod = "error",commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")})
    @RequestMapping("/test03")
    public String test03(){
        System.out.println(10/0); //抛出异常
        ResponseEntity<String> forEntity = restTemplate.getForEntity("http://eureka-client-hystrix-provider/test03", String.class);
        String body  = forEntity.getBody();
        return "熔断机制服务消费者"+body;
    }

忽略异常

 

 

 

@HystrixCommand(fallbackMethod = "error",
            commandProperties = {@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")},
            ignoreExceptions = {Exception.class}
    )

注意服务提供者和服务消费者所抛出的异常是不一样的

服务提供者统一抛出 HttpServerErrorException.InternalServerError异常,此异常有spring抛出

 

自定义异常熔断

自定义类

mport com.netflix.hystrix.HystrixCommand;
import org.springframework.web.client.RestTemplate;

/**
 * 自定义 异常熔断器
 */
public class MyHystrixCommand extends HystrixCommand {
    /**
     * 必须使用有参构造
     * @param setter
     */
    private RestTemplate restTemplate;
    private String url;
    public MyHystrixCommand(Setter setter, RestTemplate restTemplate,String url) {
        super(setter);
        this.restTemplate = restTemplate;
        this.url = url;
    }

    /**
     * 此方法不能手动调用 spring自动调用方法来访问我们的服务提供者
     * @return
     * @throws Exception
     */
    @Override
    protected Object run() throws Exception {
        return restTemplate.getForObject(url,String.class);
    }

    /**
     * 服务降级方法 当spring自动调用run方法之后,如果服务出现了异常则自动调用服务,用服务降级的方法来进行异常熔断
     * @return
     */
    @Override
    protected Object getFallback() {
        System.out.println(super.getFailedExecutionException().getClass());
        System.out.println(super.getFailedExecutionException().getMessage());
        return "自定义异常熔断器";
    }
}

使用熔断器

 @RequestMapping("/test04")
    public String test04(){
        String url = "http://eureka-client-hystrix-provider/test03";
        MyHystrixCommand command = new MyHystrixCommand(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("")),restTemplate,url);
        //调用处理结果
        // execute方法会自动调用run方法访问服务提供者,如果服务提供者抛出异常则会自动调用getFallback方法来返回响应数据
        String body  = (String) command.execute();
        return "熔断机制服务消费者"+body;
    }

Hystrix仪表盘监控

。。。

声明式消费服务feign

service

import com.springcloud.hystrix.MyFallback;
import com.springcloud.hystrix.MyFallbackFactory;
import com.springcloud.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;


/**
 * @FeignClient 用于标记当前接口是一个Feign声明式服务接口
 * spring会为这个接口生成动态代理
 * 属性
 *      name 用于指定注册中心某个服务的 名字
 *    fallback  自定义熔断器
 */
//@FeignClient(name = "eureka-client-feign-provider",fallback = MyFallback.class)
@FeignClient(name = "eureka-client-feign-provider",fallbackFactory = MyFallbackFactory.class)
public interface TestService {
    /**
     * 定义抽象方法 并使用RequestMapping标记这个方法用于服务提供者
     * 其中RequestMapping的参数 /test 应该与当前接口所标记的服务名称中的服务中的某个请求路径相同
     * @return
     */
    @RequestMapping("/test")
    String test();

    @RequestMapping("/testParams01")
    String testParams01(@RequestParam("name") String name, @RequestParam("age") Integer age);

    /**
     * 传递对象参数时 要加注解@RequestBody
     * @param user
     * @return
     */
    @RequestMapping("/testParams02")
    String testParams02(@RequestBody User user);

    @RequestMapping("/testReturnUser")
    User testReturnUser();

    @RequestMapping("/testReturnList")
    List<User> testReturnList();
}

controller

import com.springcloud.model.User;
import com.springcloud.service.TestService;
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 javax.annotation.Resource;
import java.util.List;

@RestController
public class TestController {

    @Resource
    private TestService testService;
    @RequestMapping("/test")
    public String test(){
        String result = testService.test();
        return "feign服务消费者"+result;
    }

    /**
     * 带参数
     * @return
     */
    @RequestMapping("/testParams01")
    public String testParams01(){
        String result = testService.testParams01("张三",23);
        return "feign服务消费者"+result;
    }

    @RequestMapping("/testParams02")
    public String testParams02(){
        User user = new User("李四", 23);
        String result = testService.testParams02(user);
        return "feign服务消费者"+result;
    }

    @RequestMapping("/testReturnUser")
    public String testReturnUser(){
        User result = testService.testReturnUser();
        return "feign服务消费者"+result.getName()+result.getAge();
    }

    @RequestMapping("/testReturnList")
    public String testReturnList(){
        List<User> result = testService.testReturnList();
        return "feign服务消费者"+result;
    }
}

自定义熔断器

import com.springcloud.model.User;
import com.springcloud.service.TestService;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 自定义熔断器类
 */
@Component
public class MyFallback implements TestService {
    @Override
    public String test() {
        return "test 请求熔断";
    }

    @Override
    public String testParams01(String name, Integer age) {
        return "testParams01 请求熔断";
    }

    @Override
    public String testParams02(User user) {
        return "testParams02 请求熔断";
    }

    @Override
    public User testReturnUser() {
        return null;
    }

    @Override
    public List<User> testReturnList() {
        return null;
    }
}
import com.springcloud.model.User;
import com.springcloud.service.TestService;
import feign.hystrix.FallbackFactory;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * 自定义熔断器类 实现Hystrix的降级回调的父接口
 * 注意
 *  这个父接口的泛型非常重要,这个泛型决定当前类要为那个声明式接口提供异常熔断
 */
@Component
public class MyFallbackFactory implements FallbackFactory<TestService> {
    @Override
    public TestService create(Throwable throwable) {
        return new TestService() {
            @Override
            public String test() {
                System.out.println(throwable.getMessage());
                System.out.println(throwable.getClass());
                return "test 熔断";
            }

            @Override
            public String testParams01(String name, Integer age) {
                return "testParams01 熔断";
            }

            @Override
            public String testParams02(User user) {
                return "testParams02 熔断";
            }

            @Override
            public User testReturnUser() {
                return null;
            }

            @Override
            public List<User> testReturnList() {
                return null;
            }
        };
    }
}

启动入口

@SpringBootApplication
@EnableEurekaClient
//激活声明式消费feign支持
@EnableFeignClients
public class EurekaClientFeignConsumerApplication {

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

}

Zuul网关

 建立springboot网关项目

 

 application.properties


# 应用名称
spring.application.name=eureka-client-zuul-gateway
# 应用服务 WEB 访问端口
server.port=9000


eureka.client.serviceUrl.defaultZone=http://localhost:9100/eureka/

# 配置
#zuul.routes.api-zuul.path=/api-zuul/**
## 用于对服务下的某个特定请求进行拦截
#zuul.routes.api-zuul.service-id=eureka-client-zuul-provider

# 简化写法 作用与上面两行完全相同(配置服务提供者者经过网关)
zuul.routes.eureka-client-zuul-provider=/api-zuul/**

# 忽略某些请求
# 当请求被忽略以后访问就会出现404,表示资源不存在 如需多个同时忽略 则需要用 ,分割如
# /api-zuul/test02,/api-zuul/test03
#在忽略请求时也可以使用* **
zuul.ignored-patterns=/api-zuul/test02

# 统一网关访问前缀 类似配置tomcat应用上下文路径

zuul.prefix=/myapi

#配置自我转发 转发到当前的网关工程中
zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/api/local

# 禁用异常拦截器 如果要使用自定义拦截器那么就必须先要禁用默认异常拦截器 (使用全局异常页面时关闭)
#zuul.SendErrorFilter.error.disable=true

Application

@SpringBootApplication
@EnableEurekaClient
// 激活zuul支持
@EnableZuulProxy
public class EurekaClientZuulGatewayApplication {

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

}

filter

@Component
public class AuthFilter extends ZuulFilter {
    //当前方法的返回值用于决定当前过滤器的类型的执行时期
    @Override
    public String filterType() {
        // 返回pre表示当前过滤器作为前置过滤器 需要在执行转发服务器(访问服务提供者)前执行 通常用于做身份认证
        return "pre";
    }

    //过滤器序号 如果有多个同类型的过滤器 那么根据返回值的大小决定执行的先后顺序
    @Override
    public int filterOrder() {
        //返回值越大 执行的序号越低
        return 0;
    }

    // 过滤器是否启动
    @Override
    public boolean shouldFilter() {
        return true;
    }

    // 返回过滤器的具体执行方法
    //注意 当前版本没有特殊作用所以返回null
    @Override
    public Object run() throws ZuulException {
        System.out.println(10/0); //抛出异常
        System.out.println("0----------------------------");
        //获取当前请求的上下文对象
        RequestContext currentContext = RequestContext.getCurrentContext();
        //获取用户请求对象
        HttpServletRequest request = currentContext.getRequest();
        //获取请求参数中的token (身份令牌用于验证身份是否请求合法)
        String token = request.getParameter("token");
        if(token==null || ! "123".equals(token)){
            //设定false表示请求不再继续执行 (不转发给服务提供者)
            currentContext.setSendZuulResponse(false);

            //设置响应编码为401
            currentContext.setResponseStatusCode(401);
            //设置响应类型和编码格式
            currentContext.addZuulResponseHeader("content-type","text/html;charset=utf-8");
            currentContext.setResponseBody("非法请求");


        }else {
            System.out.println("请求合法请继续执行请求准备进入下一个服务");
        }


        return null;
    }
}
package com.springcloud.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Component
public class MyErrorFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //异常过滤器
        return "error";
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext currentContext = RequestContext.getCurrentContext();
        //获取异常对象
        ZuulException throwable = (ZuulException) currentContext.getThrowable();

        HttpServletResponse response = currentContext.getResponse();
        response.setStatus(throwable.nStatusCode);
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = null;
        try {
            writer = response.getWriter();
            writer.println("出现异常code "+throwable.nStatusCode+" message "+throwable.getMessage());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (writer != null){
                writer.close();
            }
        }
        return null;
    }
}

全局异常

@RestController
public class MyErrorController implements ErrorController {
    /**
     * 全局异常页面和自定义异常可能会有冲突 二者选一即可   在在使用全局异常页面的时候要把默认异常打开
     * @return
     */
    @Override
    public String getErrorPath() {
        return "error";
    }
    @RequestMapping("/error")
    public String error(){
        ZuulException exception = (ZuulException) RequestContext.getCurrentContext().getThrowable();
        return "全局异常页面 code="+exception.nStatusCode+"  message="+exception.getMessage();
    }
}

消费者调用网关服务

@RestController
public class TestController {
    @Resource
    private RestTemplate restTemplate;

    @RequestMapping("/test")
    public String test(){
        String url = "http://eureka-client-zuul-provider/test";
        String result = restTemplate.getForObject(url,String.class);
        return "zuul服务消费者"+result;
    }

    @RequestMapping("/test02")
    public String test02(){
        String url = "http://eureka-client-zuul-gateway/api-zuul/test";
        String result = restTemplate.getForObject(url,String.class);
        return "经过zuul服务消费者"+result;
    }
}

 

posted @ 2021-11-06 18:51  qijunL  阅读(82)  评论(0)    收藏  举报