SpringCloud学习之路

SpringCloud学习之路


1、使用IDEA搭建Eureka服务中心Server端启动

CAP定理

CAP

1.1、创建和配置注册中心Eureka

添加Eureka Server
第一步:创建项目

<?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 https://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.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yuange</groupId>
    <artifactId>eureka_server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka_server</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

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

第二步: 添加注解 @EnableEurekaServer
第三步:增加配置application.yml

server:
  port: 8761
  eureka:
    instance:
      hostname: localhost
    client:
      #声明自己是个服务端
      registerWithEureka: false
      fetchRegistry: false
      serviceUrl:
        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

1.2、使用Eureka案例

创建product-service(使用的轮询)

server:
  port: 8771

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#服务的名称
spring:
  application:
    name: product-service

启动多个节点
1、启动多个
2、指定端口号
两个节点

1.3、负载均衡器Ribbon

	RPC:
		远程过程调用,像调用本地服务(方法)一样调用服务器的服务
		支持同步、异步调用
		客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接
		PRC数据包小
			protobuf
			thrift
		rpc:编解码,序列化,链接,丢包,协议
	Rest(Http):
		http请求,支持多种协议和功能
		开发方便成本低
		http数据包大
		java开发:HttpClientURLConnection

Ribbon(软负载均衡,在客户端上进行)

使用ribbon. (类似httpClient,URLConnection) 
		启动类增加注解
		  @Bean
		  @LoadBalanced
		  public RestTemplate restTemplate() {
		       return new RestTemplate();
		  }

创建order-server
依赖

<?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 https://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.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yuange</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>

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

第一种启动方式(核心代码)

@Service
public class ProductOrderServiceImpl implements ProductOrderService {


    @Autowired
    private RestTemplate restTemplate;

    @Override
    public ProductOrder save(int userId, int productId) {
        //地址(服务名称/api),返回类型
        Object obj = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Object.class);

        System.out.println(obj);

        ProductOrder productOrder = new ProductOrder();
        productOrder.setCreateTime(new Date());
        productOrder.setUserId(userId);
        productOrder.setTradeNo(UUID.randomUUID().toString());

        return productOrder;
    }
}

第二种启动方式

@Service
public class ProductOrderServiceImpl implements ProductOrderService {


//    @Autowired
//    private RestTemplate restTemplate;

    @Autowired
    private LoadBalancerClient loadBalancerClient;

    @Override
    public ProductOrder save(int userId, int productId) {
        //第一种
        //地址(服务名称/api),返回类型
        //Map<String,Object> productMap = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Map.class);

        //第二种
        ServiceInstance instance = loadBalancerClient.choose("product-service");
        //地址,ip,端口号
        String url=String.format("http://%s:%s/api/v1/product/find?id="+productId,instance.getHost(),instance.getPort());
        RestTemplate restTemplate = new RestTemplate();
        Map<String,Object> productMap = restTemplate.getForObject(url, Map.class);


        ProductOrder productOrder = new ProductOrder();
        productOrder.setCreateTime(new Date());
        productOrder.setUserId(userId);
        productOrder.setTradeNo(UUID.randomUUID().toString());
        productOrder.setProductName(productMap.get("name").toString());
        productOrder.setPrice(Integer.parseInt(productMap.get("price").toString()));

        return productOrder;
    }
}

自定义负载均衡,从轮询到随机

策略选择:
1、如果每个机器配置一样,则建议不修改策略 (推荐)
2、如果部分机器配置强,则可以改为 WeightedResponseTimeRule

server:
  port: 8781

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#服务的名称
spring:
  application:
    name: order-service

#自定义负载均衡策略(从轮询到随机)
product-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

1.4、负载均衡器Feign

包含了Feign的调用超时时间

	Feign: 伪RPC客户端(本质还是用http)
	官方文档: https://cloud.spring.io/spring-cloud-openfeign/
	1、使用feign步骤讲解(新旧版本依赖名称不一样)
		加入依赖
			 <dependency>
		        <groupId>org.springframework.cloud</groupId>
		        <artifactId>spring-cloud-starter-openfeign</artifactId>
		    </dependency>
		启动类增加@EnableFeignClients
		增加一个接口 并@FeignClient(name="product-service")
	2、编码实战
	3、注意点:
		1、路径
		2、Http方法必须对应
		3、使用requestBody,应该使用@PostMapping
		4、多个参数的时候,通过@RequestParam("id") int id)方式调用

第一步,引入依赖(新旧版本依赖名称不一样)

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

第二步,启动类增加@EnableFeignClients
第三步,增加一个接口 并@FeignClient(name=“product-service”)

@FeignClient(name = "product-service")
public interface ProductClient {
    /**
     * 通过id查找
     * @param id
     * @return
     */
    @RequestMapping("/api/v1/product/find")
    String findById(@RequestParam(value = "id") int id);
}

创建一个JsonUtils

public class JsonUtils {


    private static final ObjectMapper objectMappper = new ObjectMapper();


    /**
     * json字符串转JsonNode对象的方法
     */
    public static JsonNode str2JsonNode(String str){
        try {
            return  objectMappper.readTree(str);
        } catch (IOException e) {
            return null;
        }
    }

}

第四步,调用

@Service
public class ProductOrderServiceImpl implements ProductOrderService {

    @Autowired
    private ProductClient productClient;

    @Override
    public ProductOrder save(int userId, int productId) {

        //使用Feign创建
        String response = productClient.findById(productId);

        JsonNode jsonNode = JsonUtils.str2JsonNode(response);

        ProductOrder productOrder = new ProductOrder();
        productOrder.setCreateTime(new Date());
        productOrder.setUserId(userId);
        productOrder.setTradeNo(UUID.randomUUID().toString());
        productOrder.setProductName(jsonNode.get("name").toString());
        productOrder.setPrice(Integer.parseInt(jsonNode.get("price").toString()));

        return productOrder;
    }
}

注意点:
1、路径
2、Http方法必须对应
3、使用requestBody,应该使用@PostMapping
4、多个参数的时候,通过@RequestParam(“id”) int id)方式调用

1.5、Feign核心源码解读和服务调用方式ribbon和Feign选择

	1、ribbon和feign两个的区别和选择
		选择feign
			默认集成了ribbon
			写起来更加思路清晰和方便
			采用注解方式进行配置,配置熔断等方式方便
	2、超时配置
		默认optons readtimeout是60,但是由于hystrix默认是1秒超时
		#修改调用超时时间
		feign:
		  client:
		    config:
		      default:
		        connectTimeout: 2000
		        readTimeout: 2000
		模拟接口响应慢,线程睡眠新的方式
		  try {
	            TimeUnit.SECONDS.sleep(1);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }

2、降级熔断Hystrix实战

1、熔断:
	保险丝,熔断服务,为了防止整个系统故障,包含子和下游服务
	下单服务 -》商品服务
			-》用户服务 (出现异常-》熔断)
2、降级:
	抛弃一些非核心的接口和数据
	旅行箱的例子:只带核心的物品,抛弃非核心的,等有条件的时候再去携带这些物品
3、熔断和降级互相交集
	相同点:
		1)从可用性和可靠性触发,为了防止系统崩溃
		2)最终让用户体验到的是某些功能暂时不能用
	不同点
		1)服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制

2.1、SpringCloud整合断路器的使用,用户服务异常情况(熔断)

第一步导入依赖

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

第二步启动类上添加注解
@EnableCircuitBreaker
注解越来越多可以使用SpringCloudApplication注解

第三步在api方法上增加 @HystrixCommand(fallbackMethod = “saveOrderFail”)
注意编写fallback方法实现,方法签名一定要和api方法签名一致
核心代码

@RestController
@RequestMapping("api/v1/order")
public class OrderController {


    @Autowired
    private ProductOrderService productOrderService;


    @RequestMapping("save")
    @HystrixCommand(fallbackMethod = "saveOrderFail")
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

    //注意方法签名一定要和api方法一致
    private Object saveOrderFail(int userId, int productId) {
        Map<String, Object> msg = new HashMap<>();
        msg.put("code", -1);
        msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试");
        return msg;
    }
    
}

PRODUCT-SERVICE宕机后
宕机后

补充: 修改maven仓库地址
pom.xml中修改

<repositories>
    <repository>
        <id>nexus-aliyun</id>
        <name>Nexus aliyun</name>
        <layout>default</layout>
        <url>http://maven.aliyun.com/nexus/content/groups/public</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <releases>
            <enabled>true</enabled>
        </releases>
    </repository>
</repositories>

2.2、SpringCloud整合断路器的使用,用户服务异常情况(降级)

1、feign结合Hystrix
	1)开启feign支持hystrix  (注意,一定要开启,旧版本默认支持,新版本默认关闭)
		feign:
		  hystrix:
		    enabled: true
	2)FeignClient(name="xxx", fallback=xxx.class ), class需要继承当前FeignClient的类
@FeignClient(name = "product-service",fallback = ProductClientFallback.class)
public interface ProductClient {
    /**
     * 通过id查找
     * @param id
     * @return
     */
    @RequestMapping("/api/v1/product/find")
    String findById(@RequestParam(value = "id") int id);
}
@Component
public class ProductClientFallback implements ProductClient {

    @Override
    public String findById(int id) {
        System.out.println("feign 调用product-service findbyid 异常");
        return null;
    }
}

2.3、熔断降级服务异常报警通知

加入redis依赖

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

配置redis链接信息

  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    timeout: 2000

使用
核心代码

        //监控报警(key-value形式)
        String saveOrderKey = "save-order";

        String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
        //知道那个节点ip信息
        final String ip = request.getRemoteAddr();

        //发送信息要异步,开一个子线程(使用了lambda表达式)
        new Thread(() -> {
            if (StringUtils.isBlank(sendValue)) {
                System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
                //发送一个http请求,调用短信服务 TODO (二十秒)
                redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
            } else {
                System.out.println("已经发送过短信,20秒内不重复发送");
            }
        }).start();

举例

@RestController
@RequestMapping("api/v1/order")
public class OrderController {


    @Autowired
    private ProductOrderService productOrderService;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @RequestMapping("save")
    @HystrixCommand(fallbackMethod = "saveOrderFail")
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

    //注意方法签名一定要和api方法一致
    private Object saveOrderFail(int userId, int productId, HttpServletRequest request) {

        //监控报警(key-value形式)
        String saveOrderKey = "save-order";

        String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
        //知道那个节点ip信息
        final String ip = request.getRemoteAddr();

        //发送信息要异步,开一个子线程(使用了lambda表达式)
        new Thread(() -> {
            if (StringUtils.isBlank(sendValue)) {
                System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
                //发送一个http请求,调用短信服务 TODO (二十秒)
                redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
            } else {
                System.out.println("已经发送过短信,20秒内不重复发送");
            }
        }).start();

        Map<String, Object> msg = new HashMap<>();
        msg.put("code", -1);
        msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试");
        return msg;
    }

}

2.4、Hystrix降级策略和调整超时时间

第一种注解形式设置commandProperties 属性(不友好)

    @RequestMapping("save")
    @HystrixCommand(fallbackMethod = "saveOrderFail", commandProperties = {
            @HystrixProperty(name = "", value = "")
    })
    public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
        Map<String, Object> data = new HashMap<>();
        data.put("code", 0);
        data.put("data", productOrderService.save(userId, productId));
        return data;
    }

第二种配置

这里是Hystrix的超时

#熔断器Hystrix超时时间调整
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 1000
            
#是否开启超时限制 (一定不要禁用)
hystrix:
  command:
    default:
     execution:
      timeout:
        enabled: false

3、断路器Dashboard监控仪表盘

第一步,加入依赖。

        <!--仪表盘监控依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

第二步,启动类型加上注解。
@EnableHystrixDashboard

第三步,配置文件增加endpoint。

#断路器Dashboard监控仪表盘(*代表暴露全部的监控信息)
management:
  endpoints:
    web:
      exposure:
        include: "*"

第四步,访问入口。

http://localhost:8781/hystrix
Hystrix Dashboard输入: http://localhost:8781/actuator/hystrix.stream

结果图
结果二


4、微服务网关zuul

拦截作用

什么是网关
API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能
		统一接入
			智能路由
			AB测试、灰度测试
			负载均衡、容灾处理
			日志埋点(类似Nignx日志)
		流量监控
			限流处理
			服务降级
		安全防护
			鉴权处理
			监控
			机器网络隔离
主流的网关
	zuul:是Netflix开源的微服务网关,和Eureka,Ribbon,Hystrix等组件配合使用,Zuul 2.0比1.0的性能提高很多
	
	kong: 由Mashape公司开源的,基于Nginx的API gateway
	
	nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求

导入依赖

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

启动类加入注解 @EnableZuulProxy

4.1、基本配置

server:
  port: 9000


#服务的名称
spring:
  application:
    name: api-gateway


#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/
默认访问规则  
http://gateway:port/service-id/**

不使用网关访问
http://localhost:8781/api/v1/order/save?product_id=6&user_id=2

使用网关访问(网关端口号加服务)
http://localhost:9000/order-service/api/v1/order/save?product_id=6&user_id=2

4.2、自定义路由映射

给单独给服务配置

#自定义路由映射
zuul:
  routes:
    order-service: /apigateway/**
http://localhost:9000/order-service/api/v1/order/save?product_id=6&user_id=2
也可以
http://localhost:9000/apigateway/api/v1/order/save?product_id=6&user_id=2

4.3、忽略整个服务对外提供接口

拒绝访问商品服务
例如

http://localhost:9000/product-service/api/v1/product/find?id=1

server:
  port: 9000


#服务的名称
spring:
  application:
    name: api-gateway



#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#/order-service/api/v1/order/save?user_id=2&product_id=1
#自定义路由映射
zuul:
  routes:
    order-service: /apigateway/**
    #忽略整个服务,对外提供接口
    ignored-services: product-service

4.4、通过正则的方式忽略整个服务对外提供接口

server:
  port: 9000

#服务的名称
spring:
  application:
    name: api-gateway

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#/order-service/api/v1/order/save?user_id=2&product_id=1
#自定义路由映射
zuul:
  routes:
    product-service: /apigateway1/**
    order-service: /apigateway2/**
  #统一入口为上面的配置,其他入口忽略
  ignored-patterns: /*-service/**
  #忽略整个服务,对外提供接口
  #ignored-services: product-service
  

4.5、处理http请求头为空的问题

#自定义路由映射
zuul:
  #处理http请求头为空的问题(如Cookies)
  sensitive-headers:

其他笔记

过滤器执行顺序问题 ,过滤器的order值越小,越先执行
共享RequestContext,上下文对象

4.6、自定义Zuul过滤器实现登录鉴权

zuul流程
zuul流程

1、新建一个filter包
2、新建一个类,实现ZuulFilter,重写里面的方法
3、在类顶部加注解,@Component,让Spring扫描

核心代码

package com.yuange.apigateway.filter;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * @author lichangyuan
 * @create 2021-04-25 9:47
 */
@Component
public class LoginFilter extends ZuulFilter {
    /**
     * 过滤器类型前置通知
     *
     * @return
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * 过滤器顺序,越小越先执行
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return 4;
    }

    /**
     * 过滤器是否生效
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
//        System.out.println(request.getRequestURI());//     /apigateway/product/api/v1/product/list
//        System.out.println(request.getRequestURL());//     http://localhost:9000/apigateway/product/api/v1/product/list
        
        //ACL,一些权限校验逻辑
        
        //如果返回true则进行拦截,执行业务逻辑
        if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())) {
            return true;
        } else if ("/apigateway/order/api/v1/order/list".equalsIgnoreCase(request.getRequestURI())) {
            return true;
        } else if ("/apigateway/order/api/v1/order/find".equalsIgnoreCase(request.getRequestURI())) {
            return true;
        }
        return false;
    }

    /**
     * 业务逻辑
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {
        //JWT
        RequestContext requestContext = RequestContext.getCurrentContext();
        HttpServletRequest request = requestContext.getRequest();

        //token对象
        String token = request.getHeader("token");

        //如果token为空,则从参数中查找
        if (StringUtils.isBlank((token))) {
            token = request.getParameter("token");
        }

        //登录校验逻辑  根据公司情况自定义 JWT
        if (StringUtils.isBlank(token)) {
            //这个请求最终不会被zuul转发到后端服务器
            requestContext.setSendZuulResponse(false);
            //设置响应信息       HttpStatus.UNAUTHORIZED.value()   为401未经授权
            requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

当不携带token时
返回结果
当携带token时,则能正常访问。
返回结果

4.7、高并发下接口限流

1、nginx层限流
2、网关层限流

package com.yuange.apigateway.filter;

import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;

import javax.servlet.http.HttpServletRequest;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * 只给订单接口限流
 */
public class OederRateLimiterFilter extends ZuulFilter {

    //每秒产生1000个令牌
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);

    @Override
    public String filterType() {

        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        //默认最小-3
        return -4;
    }

    @Override
    public boolean shouldFilter() {
        RequestContext currentContext = RequestContext.getCurrentContext();
        HttpServletRequest request = currentContext.getRequest();
        //只对订单接口做限流
        if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())) {
            return true;
        }
        return false;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext requestContext = RequestContext.getCurrentContext();
        //如果没有拿到令牌
        if (!RATE_LIMITER.tryAcquire()) {
            //这个请求最终不会被zuul转发到后端服务器
            requestContext.setSendZuulResponse(false);
            requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
        }

        return null;
    }
}

4.8、Zuul微服务网关集群搭建

集群


5、分布式链路追踪系统Sleuth和ZipKin

5.1、使用Sleuth

Sleuth是一个组件,专门用于记录链路数据的开源组件
加入依赖

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

自定义日志(由于第一次测试有,第二次测试没有说明日志级别问题)
LOGER选型
核心代码

import com.yuange.product_server.domain.Product;
import com.yuange.product_server.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;


/**
 * @author lichangyuan
 * @create 2021-04-19 14:57
 */
@Service
public class ProductServiceImpl implements ProductService {

    private final Logger logger= LoggerFactory.getLogger(getClass());
    
    @Override
    public Product findById(int id) {
        //打印日志
        logger.info("service findById product");
        return null;
    }
}

调用结果

[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false]
		1、第一个值,spring.application.name的值
		2、第二个值,96f95a0dd81fe3ab ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
		3、第三个值,852ef4cfcdecabf3、spanid 基本的工作单元,获取元数据,如发送一个http
		4、第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。

5.2、可视化链路追踪系统ZipKin部署

大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
知识拓展:OpenTracing
OpenTracing 已进入 CNCF,正在为全球的分布式追踪,提供统一的概念和数据标准。
通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。

使用docker安装

docker run -d -p 9411:9411 openzipkin/zipkin

可视化web界面

http://192.168.8.128:9411/zipkin/

结果图
导入依赖

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

此依赖包含了两个依赖

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

配置

#服务的名称
spring:
  application:
    name: order-service
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    timeout: 2000

    #zipkin服务所在地址
  zipkin:
    base-url: http://192.168.8.128:9411/
    #配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
  sleuth:
    sampler:
      probability: 1

结果
详细内容


6、微服务核心知识分布式配置中心Config

6.1、服务端

统一管理配置, 快速切换各个环境的配置
导入依赖和注册中心

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

启动类增加注解
@EnableConfigServer

配置

#服务的名称
spring:
  application:
    name: config-server
    #git配置
  cloud:
    config:
      server:
        git:
          uri: https://gitee.com/lichangyuan/config_cloud
          username: 15888488307
          password: l15888488307cy
          #超时时间
          timeout: 5
          #分支
          default-label: master

server:
  port: 9100

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

访问路径(一定要有‘-’,根据前缀匹配)
http://localhost:9100/product-service.yml

访问方式(一定要注意语法,如果有问题,会出错)

		多种访问路径,可以通过启动日志去查看
		例子 http://localhost:9100/product-service.yml
		/{name}-{profiles}.properties
		/{name}-{profiles}.yml
		/{name}-{profiles}.json
		/{label}/{name}-{profiles}.yml
		name 服务器名称
		profile 环境名称,开发、测试、生产
		lable 仓库分支、默认master分支

创建两个环境,两个分支。
两个分支

server:
  port: 8771

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

#服务的名称
spring:
  application:
    name: product-service
    #zipkin服务所在地址
  zipkin:
    base-url: http://192.168.8.128:9411/
    #配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
  sleuth:
    sampler:
      probability: 1

#这个为环境
env: dev
#这个为分支
branch: master

http://localhost:9100/master/product-service-dev.yml
结果

6.2、客户端

依赖

        <!--配置中心依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

配置,要把配置文件重命名为bootstrap.yml

		#指定注册中心地址
		eureka:
		  client:
		    serviceUrl:
		      defaultZone: http://localhost:8761/eureka/
		#服务的名称
		spring:
		  application:
		    name: product-service
		  #指定从哪个配置中心读取
		  cloud:
		    config:
		      discovery:
		        service-id: CONFIG-SERVER
		        #开发发现的功能默认为false
		        enabled: true
		      #指定环境
		      profile: test
		      #建议用lable去区分环境,默认是lable是master分支
		      #label: test

最终使用的master分支test环境(默认lable由服务端决定)

生产环境部署常见问题,配置中心访问路径变化

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      #一般注册中心和配置中心不会在同一台服务器,而且不会对外公布,使用内网ip
      defaultZone: http://localhost:8761/eureka/
  instance:
  instance-id: ${spring.cloud.client.ip-address}:${server.port}
  prefer-ip-address: true

7、微服务消息总线Bus结合消息队列RabbitMQ

	1、什么是消息
		一个事件,需要广播或者单独传递给某个接口
	2、为什么使用这个
		配置更新了,但是其他系统不知道是否更新

	安装步骤
		1)拉取镜像:docker pull rabbitmq:management
		2)查看当前镜像列表:docker images
		3)删除指定镜像:docker rmi  IMAGE_ID  (如果需要强制删除加 -f)
		
		4)创建容器
		docker run -d --name="myrabbitmq" -p 5671:5671 -p 15672:15672 rabbitmq:management
		 	-d: 后台运行容器,并返回容器ID
		 	-p: 端口映射,格式为:主机(宿主)端口:容器端口
		 	--name="rabbitmq": 为容器指定一个名称
	RabbitMQ默认创建了一个 guest 用户,密码也是 guest, 如果访问不了记得查看防火墙,端口或者云服务器的安全组
	管理后台:http://127.0.0.1:15672


		服务器启动
		 5672是项目中连接rabbitmq的端口(我这里映射的是5672),15672是rabbitmq的web管理界面端口(我映射为15672)
		rabbitmq: docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:management
		rabbitmq默认是5672,所以改为5672端口

导入依赖

	        <!--配置中心结合消息队列-->
	       
	        <dependency>
	            <groupId>org.springframework.boot</groupId>
	            <artifactId>spring-boot-starter-actuator</artifactId>
	        </dependency>
	        <dependency>
	            <groupId>org.springframework.cloud</groupId>
	            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
	        </dependency>

各个服务的配置文件

			spring:
			  rabbitmq:
			    host: localhost
			    port: 5672
			    username: guest
			    password: guest 
			#暴露全部的监控信息
			management:
			  endpoints:
			    web:
			      exposure:
			        include: "*"

通过修改git上的配置再通过手动触发钩子函数(post方式访问)
http://localhost:8771/actuator/bus-refresh
会刷新服务配置(达到应用不重启,动态更新配置)

7.1、实战案例

git里面新增对应项目的配置文件,都要添加下面的配置

	#服务的名称
	spring:
	  rabbitmq:
	    host: 192.168.8.128
	    port: 5672
	    username: guest
	    password: guest
	#暴露全部的监控信息
	management:
	  endpoints:
	    web:
	      exposure:
	        include: "*"

依赖

        <!--配置中心依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-client</artifactId>
        </dependency>

        <!--配置中心结合消息队列-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!--Bus 消息总线的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-bus-amqp</artifactId>
        </dependency>

修改application.properties为bootstrap.yml 并拷贝配置文件

		#指定注册中心地址
		eureka:
		  client:
		    serviceUrl:
		      defaultZone: http://localhost:8761/eureka/
		#服务的名称
		spring:
		  application:
		    name: order-service
		  #指定从哪个配置中心读取
		  cloud:
		    config:
		      discovery:
		        service-id: CONFIG-SERVER
		        enabled: true
		      #指定环境
		      profile: test

启动顺序从①注册中心到②配置中心到③对应的服务到④启动网关


8、安装Docker仓库

阿里云部署Docker

	1、什么是Dokcer
		百科:一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口;
		使用go语言编写,在LCX(linux容器)基础上进行的封装
		简单来说:
			1)就是可以快速部署启动应用
			2)实现虚拟化,完整资源隔离
			3)一次编写,四处运行(有一定的限制,比如Docker是基于Linux 64bit的,无法在32bit的linux/Windows/unix环境下使用)

	2、为什么要用
		1、提供一次性的环境,假如需要安装Mysql,则需要安装很多依赖库、版本等,如果使用Docker则通过镜像就可以直接启动运行   
		2、快速动态扩容,使用docker部署了一个应用,可以制作成镜像,然后通过Dokcer快速启动
		3、组建微服务架构,可以在一个机器上模拟出多个微服务,启动多个应用
		4、更好的资源隔离和共享
		一句话:开箱即用,快速部署,可移植性强,环境隔离
systemctl start docker     #运行Docker守护进程
systemctl stop docker      #停止Docker守护进程
systemctl restart docker   #重启Docker守护进程
systemctl enable docker    #设置Docker开机自启动

8.1、快速掌握Dokcer基础知识

镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

	1、概念:
		Docker 镜像 - Docker images:
				 容器运行时的只读模板,操作系统+软件运行环境+用户程序
				 
				 class User{
					 private String userName;
					 private int age;
				 }
		Docker 容器 - Docker containers:
				容器包含了某个应用运行所需要的全部环境
				
				 User user = new User()
		Docker 仓库 - Docker registeries: 
				用来保存镜像,有公有和私有仓库,好比Maven的中央仓库和本地私服
				镜像仓库:	
				
				(参考)配置国内镜像仓库:https://blog.csdn.net/zzy1078689276/article/details/77371782
		对比面向对象的方式
		Dokcer 里面的镜像 : Java里面的类 Class
		Docker 里面的容器 : Java里面的对象 Object
		通过类创建对象,通过镜像创建容器
4、Docker容器常见命令实战
简介:讲解Docker在云服务上的实际应用

	1、 常用命令(安装部署好Dokcer后,执行的命令是docker开头),xxx是镜像名称
		搜索镜像:docker search xxx
		
		列出当前系统存在的镜像:docker images
		
		拉取镜像:docker pull xxx
			xxx是具体某个镜像名称(格式 REPOSITORY:TAG)
			REPOSITORY:表示镜像的仓库源,TAG:镜像的标签
		运行一个容器:docker run -d --name "xdclass_mq" -p 5672:5672 -p 15672:15672 rabbitmq:management
			docker run - 运行一个容器
			-d 后台运行
			-p 端口映射
			rabbitmq:management  (格式 REPOSITORY:TAG),如果不指定tag,默认使用最新的
			--name "xxx"
		
		列举当前运行的容器:docker ps
		检查容器内部信息:docker inspect 容器名称
		删除镜像:docker rmi IMAGE_NAME
			 强制移除镜像不管是否有容器使用该镜像 增加 -f 参数,
		停止某个容器:docker stop 容器名称
		启动某个容器:docker start 容器名称
		移除某个容器: docker rm 容器名称 (容器必须是停止状态)

9、SpringCloud和Docker整合部署

9.1、构建springboot应用

添加配置pom.xml

    <properties>
        <!--变量前缀-->
        <docker.image.prefix>yuange</docker.image.prefix>
    </properties>
    <build>
        <!--项目打包的名称-->
        <finalName>docker-demo</finalName>
        <plugins>
            <!--一个插件,用于打包springboot应用成docker镜像-->
            <plugin>
                <groupId>com.spotify</groupId>
                <artifactId>dockerfile-maven-plugin</artifactId>
                <version>1.3.6</version>
                <configuration>
                    <!--指定打包成的镜像名称-->
                    <repository>${docker.image.prefix}/${project.artifactId}</repository>
                    <buildArgs>
                        <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
                    </buildArgs>
                </configuration>
            </plugin>
        </plugins>
    </build>


配置讲解
	Spotify 的 docker-maven-plugin 插件是用maven插件方式构建docker镜像的。
	${project.build.finalName} 产出物名称,缺省为${project.artifactId}-${project.version}

9.2、打包SpringCloud镜像并上传私有仓库并部署

创建Dockerfile

FROM java:8
VOLUME /tmp
COPY *.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

构建镜像(window的操作)
1.将springboot项目打成jar包
2.创建一个Dockerfile文件
3.将jar包和Dockerfile文件放到同一目录中
4.打成镜像(结尾有个点):
docker build -t docker-demo .
5.运行镜像
docker run -d -p 8080:8080 docker-demo
构建镜像(linux的操作)
mvn install dockerfile:build


10、其他

10.1、阿里云依赖

    <repositories>
        <repository>
            <id>nexus-aliyun</id>
            <name>Nexus aliyun</name>
            <layout>default</layout>
            <url>http://maven.aliyun.com/nexus/content/groups/public</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>

10.2、虚拟机环境开启

安装docker
打开docker

systemctl start docker

安装打开docker的rabbitmq

docker pull rabbitmq:management
docker run -d --name="myrabbitmq" -p 5671:5671 -p 15672:15672 rabbitmq:management

安装打开docker的zipkin

docker pull openzipkin/zipkin
docker run -d -p 9411:9411 openzipkin/zipkin

安装打开docker的nginx

docker pull nginx:latest
docker run --name nginx-test -p 8080:80 -d nginx

安装打开docker的MySQL(MYSQL_ROOT_PASSWORD=123456:设置 MySQL 服务 root 用户的密码。)

docker pull mysql:latest
docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
进入容器
docker exec -it 62349aa31687 /bin/bash

安装打开docker的redis(接着我们通过 redis-cli 连接测试使用 redis 服务)
命令说明:
redis-server --appendonly yes : 在容器执行redis-server启动命令,并打开redis持久化配置

docker pull redis:latest
docker run -d --name "yuange" -p 6379:6379 redis
docker run -itd --name redis-test -p 6379:6379 redis
访问redis容器里面,进行操作
docker exec -it 295058d2b92e redis-cli
#常用启动方式
docker run -d --name "yuange" -p 6379:6379 redis redis-server --appendonly yes

安装打开docker的Jenkins

-d 后台运行镜像

-p 10240:8080 将镜像的8080端口映射到服务器的10240端口。

-p 10241:50000 将镜像的50000端口映射到服务器的10241端口

-v /var/jenkins_mount:/var/jenkins_mount /var/jenkins_home目录为容器jenkins工作目录,我们将硬盘上的一个目录挂载到这个位置,方便后续更新镜像后继续使用原来的工作目录。这里我们设置的就是上面我们创建的 /var/jenkins_mount目录

-v /etc/localtime:/etc/localtime让容器使用和服务器同样的时间设置。

–name myjenkins 给容器起一个别名

docker pull jenkins/jenkins
mkdir -p /var/jenkins_mount
chmod 777 /var/jenkins_mount
运行
docker run -d -p 10240:8080 -p 10241:50000 -v /var/jenkins_mount:/var/jenkins_home -v /etc/localtime:/etc/localtime --name myjenkins jenkins/jenkins

docker常用命令

docker ps [OPTIONS]
-a :显示所有的容器,包括未运行的。

docker start :启动一个或多个已经被停止的容器
docker stop :停止一个运行中的容器
docker restart :重启容器

强制删除容器 db01、db02:
docker rm -f db01 db02

删除所有已经停止的容器:
docker rm $(docker ps -a -q)

使用镜像nginx:latest以后台模式启动一个容器,并将容器的latest端口映射到主机的nginx端口。
docker run -P -d nginx:latest

10.3、生产环境部署常见问题,配置中心访问路径变化

#指定注册中心地址
eureka:
  client:
    serviceUrl:
      #一般注册中心和配置中心不会在同一台服务器,而且不会对外公布,使用内网ip
      defaultZone: http://localhost:8761/eureka/
  instance:
  instance-id: ${spring.cloud.client.ip-address}:${server.port}
  prefer-ip-address: true
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

和烨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值