SpringCloud學習之路
- 1、使用IDEA搭建Eureka服務中心Server端啟動
-
- 1.1、建立和配置注冊中心Eureka
- 1.2、使用Eureka案例
- 1.3、負載均衡器Ribbon
- 1.4、負載均衡器Feign
- 1.5、Feign核心源碼解讀和服務調用方式ribbon和Feign選擇
- 2、降級熔斷Hystrix實戰
-
- 2.1、SpringCloud整合斷路器的使用,使用者服務異常情況(熔斷)
- 2.2、SpringCloud整合斷路器的使用,使用者服務異常情況(降級)
- 2.3、熔斷降級服務異常報警通知
- 2.4、Hystrix降級政策和調整逾時時間
- 3、斷路器Dashboard監控儀表盤
- 4、微服務網關zuul
-
- 4.1、基本配置
- 4.2、自定義路由映射
- 4.3、忽略整個服務對外提供接口
- 4.4、通過正則的方式忽略整個服務對外提供接口
- 4.5、處理http請求頭為空的問題
- 4.6、自定義Zuul過濾器實作登入鑒權
- 4.7、高并發下接口限流
- 4.8、Zuul微服務網關叢集搭建
- 5、分布式鍊路追蹤系統Sleuth和ZipKin
-
- 5.1、使用Sleuth
- 5.2、可視化鍊路追蹤系統ZipKin部署
- 6、微服務核心知識分布式配置中心Config
-
- 6.1、服務端
- 6.2、用戶端
- 生産環境部署常見問題,配置中心通路路徑變化
- 7、微服務消息總線Bus結合消息隊列RabbitMQ
-
- 7.1、實戰案例
- 8、安裝Docker倉庫
-
- 8.1、快速掌握Dokcer基礎知識
- 9、SpringCloud和Docker整合部署
-
- 9.1、建構springboot應用
- 9.2、打包SpringCloud鏡像并上傳私有倉庫并部署
- 10、其他
-
- 10.1、阿裡雲依賴
- 10.2、虛拟機環境開啟
- 10.3、生産環境部署常見問題,配置中心通路路徑變化
1、使用IDEA搭建Eureka服務中心Server端啟動
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開發:HttpClient,URLConnection
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流程
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>
自定義日志(由于第一次測試有,第二次測試沒有說明日志級别問題)
核心代碼
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