1. 程式人生 > 其它 >Docker系列之三:映象和容器的匯入、匯出

Docker系列之三:映象和容器的匯入、匯出

一. 為什麼使用spring cloud alibaba

很多人可能會問,有了spring cloud這個微服務的框架,為什麼又要使用spring cloud alibaba這個框架了?最重要的原因在於spring cloud中的幾乎所有的元件都使用Netflix公司的產品,然後在其基礎上做了一層封裝。然而Netflix的服務發現元件Eureka已經停止更新,我們公司在使用的時候就發現過其一個細小的Bug;而其他的眾多元件預計會在明年(即2020年)停止維護。所以急需其他的一些替代產品,也就是spring cloud alibaba,目前正處於蓬勃發展的態式。

1、nacos 註冊中心文件
2、nacos 註冊中心jar包


3、sentinel 流量控制,斷路 文件
4、apache-skywalking-apm-bin 鏈路跟蹤
5、Rocketmq 的使用

二. 註冊中心Nacos

nacos是阿里巴巴研發的一個集註冊中心與配置中心於一體的管理平臺,使用其他非常的簡單。下載地址為:

https://github.com/alibaba/nacos/releases,nacos的主頁如下圖所示:

其中預設的登入名和密碼是:nacos/nacos

2.1 更改使用者名稱和密碼

A. 分別執行conf目錄下的nacos-mysql.sql兩個指令碼檔案,生成的資料表如下:

B. 重新配置密碼

新建一個springboot的專案,引入如下的依賴:

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

生成密碼的Java程式碼:

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

執行如下sql命令:

insert into users values('nacos','$2a$10$04MGTL.cJNZPpR3rFt/I2.43F.V75NH.5wdK.jngaO9Mc91mfonAO', 1);

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

C. 配置資料庫的連線

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

2.2 nacos叢集配置

A. 將conf目錄下的cluster.conf.example拷貝一份重新命名為cluster.conf,在檔案中加入所有叢集節點的ip和埠號,檔案內容如下:

127.0.0.1:8848
127.0.0.1:9948

B. 修改windows啟動檔案startup.cmd的配置,修改內容如下:

set MODE="standalone"  #預設的配置
set MODE="cluster" #修改後的內容

注:如果是Linux環境不用作任何的修改。

C.啟動兩個nacos,介面中出現如下的內容,表示叢集配置成功

三. 服務提供方

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>

配置

spring:
application:
# 服務名
name: alibaba-provider
cloud:
nacos:
discovery:
#必須配置ip地址
server-addr: 172.18.96.177:8848,172.18.96.177:9948

啟動類

@SpringBootApplication
@EnableDiscoveryClient //開啟nacos的服務發現
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
}

服務頁面結果

四. 服務消費方

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>

配置

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

啟動類配置

@SpringBootApplication
@EnableDiscoveryClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}

呼叫服務提供方

@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;
}

五. Ribbon負載均衡

Spring Cloud Ribbon是基於Netflix Ribbon實現的一套客戶端負載均衡的工具。其主要功能是提供客戶端的負載均衡演算法,並提供了完善的配置項如連線超時,重試等。簡單的說,就是配置檔案中列出Load Balancer後面所有的機器,Ribbon會自動的基於某種規則(如簡單輪詢,隨機連線等)去連線這些機器,當然我們也可以使用Ribbon自定義負載均衡演算法。

5.1 實現負載均衡

Ribbon只是一個客戶端的負載均衡器工具,實現起來非常的簡單,我們只需要在注入RestTemplate的bean上加上@LoadBalanced就可以了。如下:

@Configuration
public class WebConfig {

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

5.2 服務消費方呼叫

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

5.3 負載均衡策略

Ribbon提供了一個很重要的介面叫做IRule,其中定義了很多的負載均衡策略,預設的是輪詢的方式,以下是Ribbon的負載均衡策略:

類名描述
RoundRobbinRule 輪詢
RandomRule 隨機挑選
RetryRule 按照輪詢的方式去呼叫服務,如果其中某個服務不可用,但是還是會嘗試幾次,如果嘗試過幾次都沒有成功,那麼就不在呼叫該服務,會輪詢呼叫其他的可用服務。
AvailabilityFilteringRule 會先過濾掉因為多次訪問不可達和併發超過閾值的服務,然後輪詢呼叫其他的服務
WeightedResponseTimeRule 根據平均響應時間計算權重,響應越快權重越大,越容易被選中。服務剛重啟的時候,還未統計出權重會按照輪詢的方式;當統計資訊足夠的時候,就會按照權重資訊訪問
ZoneAvoidanceRule 判斷server所在的區域效能和可用性選擇伺服器
BestAvailableRule 會過濾掉多次訪問都不可達的服務,然後選擇併發量最小的服務進行呼叫,預設方式

改變Ribbon的負責均衡策略:

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

5.4 自定義負載均衡策略

我們自定義的負載均衡策略需要繼承AbstractLoadBalancerRule這個類,然後重寫choose方法,然後將其注入到容器中,如下所示:

public class Customize_Rule extends AbstractLoadBalancerRule {

private static Logger logger = LoggerFactory.getLogger(Customize_Rule.class);

private int currentIndex = 0; //當前呼叫的索引
private int num = 1; //次數
private int limit = 5;

/**
* 初始化工作
* @param iClientConfig
*/
@Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {

}

@Override
public Server choose(Object key) {
ILoadBalancer balancer = getLoadBalancer();
return choose(balancer, key);
}

private Server choose(ILoadBalancer balancer, Object key) {
Server server = null;

while(null == server) {
//獲取所有可用的服務
List<Server> reachableServers = balancer.getReachableServers();
if (0 == reachableServers.size()) {
logger.error("沒有可用的服務");
return null; //退出while迴圈
}

int total = reachableServers.size(); //可用服務的數量

synchronized (this) {
/**
* 有種極端情況,當我們在使用最後一個服務的時候,其他的服務都不可用,可能導致索引越界異常
*/
if (currentIndex + 1 > total) {
currentIndex = 0;
server = reachableServers.get(currentIndex); //獲取第一個服務
num = 0;
num++;
} else {
if(limit == num) {
currentIndex++;
num = 0;
if(currentIndex == total) {
currentIndex=0;
server = reachableServers.get(currentIndex); //獲取第一個服務
num++;
}else{
server = reachableServers.get(currentIndex);
num++;
}
}else {
server = reachableServers.get(currentIndex);
num++;
}
}
}
}
return server;
}
}

將其注入到容器中,方式如下:

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

六. Feign負載均衡

feign是基於Ribbon的另外一個負載均衡的客戶端框架,只需要在介面上定義要呼叫的服務名即可,使用起來非常的簡單。

pom.xml依賴

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

啟動類配置

需要在啟動類上加上@EnableFeignClients這個註解

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}

服務介面配置

@FeignClient(name="alibaba-provider")
public interface UserService {

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

七. 熔斷與服務降級

分散式系統中一個微服務需要依賴於很多的其他的服務,那麼服務就會不可避免的失敗。例如A服務依賴於B、C、D等很多的服務,當B服務不可用的時候,會一直阻塞或者異常,更不會去呼叫C服務和D服務。同時假設有其他的服務也依賴於B服務,也會碰到同樣的問題,這就及有可能導致雪崩效應。

如下案例:一個使用者通過通過web容器訪問應用,他要先後呼叫A、H、I、P四個模組,一切看著都很美好。

由於某些原因,導致I服務不可用,與此同時我們沒有快速處理,會導致該使用者一直處於阻塞狀態。

當其他使用者做同樣的請求,也會面臨著同樣的問題,tomcat支援的最大併發數是有限的,資源都是有限的,將整個伺服器拖垮都是有可能的。

Sentinel是一個用於分散式系統的延遲和容錯的開源庫,在分散式系統中,許多依賴會不可避免的呼叫失敗,例如超時,異常等,Hystrix能保證在一個依賴出現問題的情況下,不會導致整體服務失敗,避免級聯故障,以提高分散式系統的彈性。

斷路器本身是一種開關裝置,當某個服務單元發生故障後,通過斷路器的故障監控(類似於保險絲),向呼叫者返回符合預期的,可處理的備選響應,而不是長時間的等待或者丟擲無法處理的異常,這樣就保證了服務呼叫的執行緒不會被長時間,不必要的佔用,從而避免故障在分散式系統中的蔓延,乃至雪崩。

Sentinel在網路依賴服務出現高延遲或者失敗時,為系統提供保護和控制;可以進行快速失敗,縮短延遲等待時間;提供失敗回退(Fallback)和相對優雅的服務降級機制;提供有效的服務容錯監控、報警和運維控制手段。

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

7.1 依賴

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

7.2 配置

通常情況下我們只需要在服務提供方實現熔斷或者服務降級即可,但是如果要相對服務消費方是實現限流,在服務的提供方和消費方都需要加如下配置。

spring:
cloud:
sentinel:
transport:
# sentinel控制檯的地址
dashboard: localhost:8080
# 立即載入
eager: true

7.3 sentinel的控制面板

在瀏覽器輸入localhost:8080,使用sentinel/sentinel來訪問sentinel的控制面板:

接下來對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/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8);排隊等待只是勻速的通過(每秒指定的QPS),其他的請求進行排隊,但是並不會一直排下去,超過指定的時間就會失敗,閾值型別必須設定為QPS,不能為執行緒。

降級規則

資源名:要實現降級的資源。

降級策略:

1) RT(平均響應時間)

如果在一秒鐘之內進入的請求的平均響應時間小於1ms,那麼在未來5s鍾之內所有的請求都會熔斷降級。

2) 異常比例

如果在一秒鐘之內的請求數異常比例大於指定的資料,那麼在未來的時間視窗內會一直熔斷降級。統計單位為s.

3) 異常數

如果在一分鐘之內,異常數量大於指定的值,那麼在指定的時間視窗內請求一直會熔斷降級,注意時間視窗的值一般設定要大於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的時間。

系統規則

LOAD:僅對 Linux/Unix-like 機器生效,參考值一般是CPU cores * 2.5

RT:當單臺機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。

執行緒數:當單臺機器上所有入口流量的併發執行緒數達到閾值即觸發系統保護。

入口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引數,在sentinel的dashboard中可以作如下配置:

叢集流控

是否叢集:是否採用叢集

均攤閾值:就是每個叢集節點每秒的QPS.

叢集閾值模式:單機均攤是叢集中每個節點每秒的QPS, 總體閾值是整個叢集每秒的QPS.

叢集流控

7.4 @SentinelResource

@SentinelResource是sentinel中非常重要的註解,提供了簡單易用的功能。其中blockHandler註解是限流的處理方法,fallback是服務降級的處理方法。

@SentinelResource(value="edit", blockHandler="editBlock", fallback = "editFallback")
@RequestMapping("/edit")
public Object edit(@RequestParam(required = false) String id,
@RequestParam(required = false) Integer age) throws Exception {
Thread.sleep(20);
return this.studentService.commons();
}

// 限流的處理
public Object editBlock(String id, Integer age, BlockException ex) {
Map<String, Object> map = new HashMap<>();
map.put("msg", "限流了.");
return map;
}

//服務降級的處理方法
public Object editFallback(String id, Integer age) {
Map<String, Object> map = new HashMap<>();
map.put("msg", "fallback 服務降級了.");
return map;
}

八. Feign與Sentinel的整合

配置

feign:
sentinel:
# 預設是沒有提示的
enabled: true

8.1 服務降級後的處理

可以在@FeignClient中配置fallback,來指定服務降級後給使用者返回的什麼樣的資料,fallback的值為Class型別的物件,該類必須要實現該對應的介面。

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

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

UserServiceFallback的實現如下:

@Component
public class UserServiceFallback implements UserService {

@Override
public List<String> getUsers() {
return Arrays.asList("服務降級了, 這是降級後返回的資訊.");
}
}

8.2 服務降級的異常處理

對於fallback來講,並不能追蹤到異常資訊,在實際的業務處理過程中,我們往往需要記錄異常的資訊,那麼就要使用fallbackFactory屬性來實現。

@Component
public class UserServiceFallbackFactory implements FallbackFactory<UserService> {

private static Logger logger = LoggerFactory.getLogger(UserServiceFallbackFactory.class);

@Override
public UserService create(Throwable cause) {

return new UserService() {
@Override
public List<String> getUsers() {
logger.info(cause.getMessage());
return Arrays.asList("服務降級的方法");
}
};
}
}

九. Sentinel的持久化

通過接入 Sentinel Dashboard 後,在頁面上操作來更新規則,都無法避免一個問題,那就是服務重新後,規則就丟失了,因為預設情況下規則是儲存在記憶體中的。sentinel中持久化的方式有兩種,pull模式和push模式。

pull模式是指站在第三方持久化系統(redis, nacos)的角度,他們去到sentinel中定時去拉去配置資訊,可能會造成資料的不一致性。

push模式是站在sentinel的角度,將其配置資訊主動推送給第三方持久化系統,sentinel官方也推薦在線上使用該模式。

9.1 sentinel-dashboard改造

A. 將sentinel的原始碼clone到本地

B. 進入到sentinel-dashboard目錄下,修改pom.xml檔案

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

C. 修改 src\main\webapp\resources\app\scripts\directives\sidebar\sidebar.html 檔案

未修改之前的內容

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

修改之後的內容

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

D. 將src\test\java\com\alibaba\csp\sentinel\dashboard\rule\nacos目錄下的四個Java檔案拷貝到src\main\java\com\alibaba\csp\sentinel\dashboard\rule目錄下

E. 修改src\main\webapp\resources\app\scripts\controllers\identity.js檔案,修改內容如下:

F. 重新打包生成Jar包,進入到sentinel目錄下(注:不是sentinel-dashboard目錄),執行如下命令:

mvn clean         
mvn install -DskipTests

G. 進入到sentinel-dashboard/target目錄下,執行如下內容:

java -jar sentinel-dashboard.jar

9.2 配置

spring:
cloud:
sentinel:
datasource:
# 這個名字隨意,但是要有意義
flow:
nacos:
server-addr: 192.168.31.173:8848
groupId: SENTINEL_GROUP
rule-type: flow

9.3 測試

在sentinel-dashboard控制面板新增一個流量控制規則

https://www.cnblogs.com/lihaipengjava/p/12025242.html