1.微服務場景模拟:
服務提供者
我們建立一個項目,對外提供查詢使用者的服務。
建立資料庫
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL,
`user_name` varchar(50) DEFAULT NULL,
`password` varchar(50) DEFAULT NULL,
`name` varchar(50) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`sex` int(11) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`created` date DEFAULT NULL,
`updated` date DEFAULT NULL,
`note` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'tom', '123456', 'tom', '20', '1', '1999-11-29', '2018-11-29', '2018-11-29', null);
INSERT INTO `tb_user` VALUES ('2', 'lucy', '123456', 'lucy', '20', '2', '2018-11-29', '2018-11-29', '2018-11-29', null);
Spring腳手架建立工程
借助于Spring提供的快速搭建工具:詳細建立工程步驟這裡就省略了!
建立好工程,依賴會自動引入:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kismet.demo</groupId>
<artifactId>user-service-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-service-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
當然,因為要使用通用mapper,是以我們需要手動加一條依賴:
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.0.2</version>
</dependency>
注意:SpringCloud中mysql驅動包預設是8.x,太高了,可能會引起問題,是以需要修改成自己的版本号
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
<version>8.0.20</version>
</dependency>
編寫代碼
添加一個對外查詢的接口:
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return this.userService.queryById(id);
}
}
Service:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User queryById(Long id) {
return this.userMapper.selectByPrimaryKey(id);
}
}
mapper:
@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User>{
}
實體類:
@Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// 使用者名
private String userName;
// 密碼
private String password;
// 姓名
private String name;
// 年齡
private Integer age;
// 性别,1男性,2女性
private Integer sex;
// 出生日期
private Date birthday;
// 建立時間
private Date created;
// 更新時間
private Date updated;
// 備注
private String note;
// 。。。省略getters和setters
}
屬性檔案,這裡我們采用了yaml文法,而不是properties:
server:
port: 8020
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb1?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
啟動并測試:
啟動項目,通路接口:http://localhost:8020/user/1
服務調用者
建立工程
詳細跟上面的工程一樣,但是需要注意的是,我們調用user-service的功能,是以不需要mybatis相關依賴了。
pom:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kismet.demo</groupId>
<artifactId>user-consumer-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>user-consumer-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
編寫代碼
首先注冊
RestTemplate
:
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(){
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
}
然後編寫UserDao,注意,這裡不是調用mapper查資料庫,而是通過RestTemplate遠端查詢user-service-demo中的接口:
@Component
public class UserDao {
@Autowired
private RestTemplate restTemplate;
public User queryUserById(Long id){
String url = "http://localhost:8081/user/" + id;
return this.restTemplate.getForObject(url, User.class);
}
}
然後編寫user-service,循環查詢UserDAO資訊
@Service
public class UserService {
@Autowired
private UserDao userDao;
public List<User> queryUserByIds(List<Long> ids){
List<User> users = new ArrayList<>();
for (Long id : ids) {
User user = this.userDao.queryUserById(id);
users.add(user);
}
return users;
}
}
編寫controller:
@RestController
@RequestMapping("consume")
public class ConsumerController {
@Autowired
private UserService userService;
@GetMapping
public List<User> consume(@RequestParam("ids") List<Long> ids) {
return this.userService.queryUserByIds(ids);
}
}
複制pojo
啟動測試:
因為我們沒有配置端口,那麼預設就是8080,我們通路:http://localhost:8080/consume?ids=1,2
暴露的問題!
簡單回顧一下,剛才我們寫了什麼:
- user-service-demo:一個提供根據id查詢使用者的微服務
- user-consumer-demo:一個服務調用者,通過RestTemplate遠端調用user-service-demo
流程如下:
存在什麼問題?
- 在consumer中,我們把url位址寫死到了代碼中,不友善後期維護
- consumer需要記憶user-service的位址,如果出現變更,可能得不到通知,位址将失效
- consumer不清楚user-service的狀态,服務當機也不知道
- user-service隻有1台服務,不具備高可用性
- 即便user-service形成叢集,consumer還需自己實作負載均衡
其實上面說的問題,概括一下就是分布式服務(SpringBoot搭建的微服務)必然要面臨的問題:
- 服務管理
- 如何自動注冊和發現服務
- 如何實作服務狀态的監管
- 如何實作動态路由,進而實作負載均衡
- 服務如何實作負載均衡
- 服務如何解決容災問題
- 服務如何實作統一配置
以上的問題,我們都将在SpringCloud中得到答案。成套的解決方案!
Eureka注冊中心:
原理見:微服務之 Eureka注冊中心,Ribbon負載均衡 --理論梳理
入門案例
接下來我們建立一個項目,啟動一個EurekaServer:
依然使用spring提供的快速搭建工具:
完整的Pom檔案:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kismet.demo</groupId>
<artifactId>eureka-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<!-- SpringCloud版本,是最新的F系列 -->
<spring-cloud.version>Hoxton.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<!-- Eureka服務端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- SpringCloud依賴,一定要放到dependencyManagement中,起到管理版本的作用即可 -->
<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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
編寫啟動類:
@SpringBootApplication
@EnableEurekaServer // 聲明這個應用是一個EurekaServer
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
編寫配置application.yml:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
register-with-eureka: false # 是否注冊自己的資訊到EurekaServer,預設是true
fetch-registry: false # 是否拉取其它服務的資訊,預設是true
service-url: # EurekaServer的位址,現在是自己的位址,如果是叢集,需要加上其它Server的位址。
defaultZone: http://127.0.0.1:${server.port}/eureka
啟動服務,并通路:http://127.0.0.1:10086/eureka
将user-service注冊到Eureka
注冊服務,就是在服務上添加Eureka的用戶端依賴,用戶端代碼會自動把服務注冊到EurekaServer中。
我們在user-service-demo中添加Eureka用戶端依賴:
先添加SpringCloud依賴:
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Spring的倉庫位址 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
然後是Eureka用戶端
<!-- Eureka用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在啟動類上開啟Eureka用戶端功能
通過添加
@EnableDiscoveryClient/@EnableEurekaClient
來開啟Eureka用戶端功能
@SpringBootApplication
@EnableEurekaClient // 開啟EurekaClient功能
public class UserServiceDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceDemoApplication.class, args);
}
}
編寫配置
server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb1?useUnicode=true&characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
application:
name: user-service # 應用名稱
main:
allow-bean-definition-overriding: true #當遇到同樣名字的時候,是否允許覆寫注冊
eureka:
client:
service-url: # EurekaServer位址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 當調用getHostname擷取執行個體的hostname時,傳回ip而不是host名稱
ip-address: 127.0.0.1 # 指定自己的ip資訊,不指定的話會自己尋找
啟動服務:
我們發現user-service服務已經注冊成功了
消費者從Eureka擷取服務
接下來我們修改consumer-demo,嘗試從EurekaServer擷取服務。
方法與消費者類似,隻需要在項目中添加EurekaClient依賴,就可以通過服務名稱來擷取資訊了!
1)添加依賴:
先添加SpringCloud依賴:
<!-- SpringCloud的依賴 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- Spring的倉庫位址 -->
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
然後是Eureka用戶端:
<!-- Eureka用戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2)在啟動類開啟Eureka用戶端
@SpringBootApplication
@EnableDiscoveryClient // 開啟Eureka用戶端
public class UserConsumerDemoApplication {
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
3)修改配置:
server:
port: 8080
spring:
application:
name: consumer # 應用名稱
eureka:
client:
service-url: # EurekaServer位址
defaultZone: http://127.0.0.1:10086/eureka
instance:
prefer-ip-address: true # 當其它服務擷取位址時提供ip而不是hostname
ip-address: 127.0.0.1 # 指定自己的ip資訊,不指定的話會自己尋找
4)修改user-consumer-demo項目中的UserDao代碼,用DiscoveryClient類的方法,根據服務名稱,擷取服務執行個體:
// 必須導入org.springframework.cloud.client.discovery.DiscoveryClient
@Autowired
private DiscoveryClient discoveryClient;
public User queryUserById(Long id){
//1、 根據user-service擷取user-serivce 的叢集的資訊
List<ServiceInstance> instances = discoveryClient.getInstances("user-service");
//2、由于我們沒有叢集,隻有一個,是以直接取出第一個
ServiceInstance instance = instances.get(0);
//3、拼接URL
String url = "http://"+instance.getHost()+":"+instance.getPort()+"/user/"+id;
// 使用restTemplate發起請求
ResponseEntity<User> entity = restTemplate.getForEntity(url, User.class);
// 擷取傳回對象
User user = entity.getBody();
return user;
}
通路結果:
高可用的Eureka Server
原理見:微服務之 Eureka注冊中心,Ribbon負載均衡 --理論梳理
動手搭建高可用的EurekaServer
我們假設要搭建兩條EurekaServer的叢集,端口分别為:10086和10087
1)我們修改原來的EurekaServer配置:
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其他Eureka服務的位址,而不是自己,比如10087
defaultZone: http://127.0.0.1:10087/eureka
所謂的高可用注冊中心,其實就是把EurekaServer自己也作為一個服務進行注冊,這樣多個EurekaServer之間就能互相發現對方,進而形成叢集。是以我們做了以下修改:
- 删除了register-with-eureka=false和fetch-registry=false兩個配置。因為預設值是true,這樣就會把自己注冊到注冊中心了。
- 把service-url的值改成了另外一台EurekaServer的位址,而不是自己
2)另外一台配置恰好相反:(可以複制出來一個EurekaServer,也可以新配置一個啟動器)
server:
port: 10087 # 端口
spring:
application:
name: eureka-server # 應用名稱,會在Eureka中顯示
eureka:
client:
service-url: # 配置其他Eureka服務的位址,而不是自己,比如10087
defaultZone: http://127.0.0.1:10086/eureka
因為idea無法同時啟動來個工程,中間肯定有時間差,是以其中有一個啟動的時候會報出異常!但是不用擔心,因為eureka的消息是心跳消息形式的,不間斷的去通路。當兩個都啟動起來,就會互相注冊成功,就不會報錯了!
3)啟動測試:
4)用戶端注冊服務到叢集
因為EurekaServer不止一個,是以注冊服務的時候,service-url參數需要變化:
eureka:
client:
service-url: # EurekaServer位址,多個位址以','隔開
defaultZone: http://127.0.0.1:10086/eureka,http://127.0.0.1:10087/eureka
服務提供者
服務提供者要向EurekaServer注冊服務,并且完成服務續約等工作。
服務注冊
服務提供者在啟動時,會檢測yml中配置屬性的:
eureka.client.register-with-erueka=true
參數是否正确,事實上預設就是true。如果值确實為true,則會向EurekaServer發起一個Rest請求,并攜帶自己的中繼資料資訊,Eureka Server會把這些資訊儲存到一個雙層Map結構中。第一層Map的Key就是服務名稱,第二層Map的key是服務的執行個體id。
服務續約
在注冊服務完成以後,服務提供者會維持一個心跳(定時-每隔30s-向EurekaServer發起Rest請求),告訴EurekaServer:“我還活着”。這個我們稱為服務的續約(renew);
有兩個重要參數可以修改服務續約的行為:
eureka:
instance:
lease-expiration-duration-in-seconds: 90
lease-renewal-interval-in-seconds: 30
- lease-renewal-interval-in-seconds:服務續約(renew)的間隔,預設為30秒
- lease-expiration-duration-in-seconds:服務失效時間,預設值90秒
服務消費者
擷取服務清單
當服務消費者啟動時,會檢測
eureka.client.fetch-registry=true
參數的值,如果為true,則會從Eureka Server服務的清單隻讀備份,然後緩存在本地。并且
每隔30秒
會重新擷取并更新資料。我們可以通過下面的參數來修改:
eureka:
client:
registry-fetch-interval-seconds: 5
失效剔除和自我保護
失效剔除
有些時候,我們的 服務提供方并不一定會正常下線,可能因為記憶體溢出、網絡故障等原因導緻服務無法正常工作。Eureka Server需要将這樣的服務剔除出服務清單。是以它會開啟一個定時任務,每隔60秒對所有失效的服務(超過90秒未響應)進行剔除。
可以通過
eureka.server.eviction-interval-timer-in-ms
參數對其進行修改,機關是毫秒,生成環境不要修改。
這個會對我們開發帶來極大的不便,你對服務重新開機,隔了60秒Eureka才反應過來。開發階段可以适當調整,比如10S
自我保護
我們關停一個服務,就會在Eureka面闆看到一條警告:
這是觸發了Eureka的自我保護機制。當一個服務未按時進行心跳續約時,Eureka會統計最近15分鐘心跳失敗的服務執行個體的比例是否超過了85%。在生産環境下,因為網絡延遲等原因,心跳失敗執行個體的比例很有可能超标,但是此時就把服務剔除清單并不妥當,因為服務可能沒有當機。Eureka就會把目前執行個體的注冊資訊保護起來,不予剔除。生産環境下這很有效,保證了大多數服務依然可用。
但是這給我們的開發帶來了麻煩, 是以開發階段我們都會關閉自我保護模式:
在eureka的yml檔案中配置
eureka:
server:
enable-self-preservation: false # 關閉自我保護模式(預設為打開)
eviction-interval-timer-in-ms: 1000 # 掃描失效服務的間隔時間(預設為60*1000ms)
負載均衡Ribbon
首先我們啟動兩個user-service執行個體,一個8020,一個8021。
Eureka監控面闆:
開啟負載均衡
因為Eureka中已經內建了Ribbon,是以我們無需引入新的依賴。直接修改代碼:
在RestTemplate的配置方法上添加
@LoadBalanced
注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
修改調用方式,不再手動擷取ip和端口,而是直接通過服務名稱調用:
@Service
public class UserService {
@Autowired
private RestTemplate restTemplate;
public List<User> queryUserByIds(List<Long> ids) {
List<User> users = new ArrayList<>();
// 位址直接寫服務名稱即可
String baseUrl = "http://user-service/user/";
ids.forEach(id -> {
// 我們測試多次查詢,
users.add(this.restTemplate.getForObject(baseUrl + id, User.class));
});
return users;
}
}
檢視結果:
源碼跟蹤
為什麼我們隻輸入了service名稱就可以通路了呢?之前還要擷取ip和端口。
顯然有人幫我們根據service名稱,擷取到了服務執行個體的ip和端口。它就是
LoadBalancerInterceptor
我們進行源碼跟蹤:(Debug運作user-consumer-demo項目)
繼續跟入execute方法:發現擷取了8082端口的服務
再跟下一次,發現擷取的是8081:
負載均衡政策
Ribbon預設的負載均衡政策是簡單的輪詢,我們可以測試一下:
編寫測試類,在剛才的源碼中我們看到攔截中是使用RibbonLoadBalanceClient來進行負載均衡的,其中有一個choose方法,是這樣介紹的:
現在這個就是負載均衡擷取執行個體的方法。
我們對注入這個類的對象,然後對其測試:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = UserConsumerDemoApplication.class)
public class LoadBalanceTest {
@Autowired //負載均衡器用戶端
RibbonLoadBalancerClient client;
@Test
public void test(){
for (int i = 0; i < 100; i++) {
ServiceInstance instance = this.client.choose("user-service");
System.out.println(instance.getHost() + ":" + instance.getPort());
}
}
}
結果:
符合了我們的預期推測,确實是輪詢方式。
我們是否可以修改負載均衡的政策呢?
繼續跟蹤源碼,發現這麼一段代碼:
我們看看這個rule是誰:
這裡的rule預設值是一個
RoundRobinRule
,看類的介紹:
這不就是輪詢的意思嘛。
我們注意到,這個類其實是實作了接口IRule的,檢視一下:
定義負載均衡的規則接口。
它有以下實作:
SpringBoot也幫我們提供了修改負載均衡規則的配置入口:
複制到消費者的yml中
user-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
格式是:
{服務名稱}.ribbon.NFLoadBalancerRuleClassName
,值就是IRule的實作類。
再次測試,發現結果變成了随機:
重試機制
當第一次調用某個服務失敗的時候,不是立刻傳回失敗結果,而是嘗試重新請求
預設情況下, 不會重試,隻會請求一次
那我應該何時重試呢?
重試幾次呢?
CAP原則:CAP原則又稱CAP定理,指的是在一個分布式系統中,Consistency(一緻性)、 Availability(可用性)、Partition tolerance(分區容錯性),三者不可兼得
Eureka的服務治理強調了CAP原則中的AP,即可用性和可靠性。它與Zookeeper這一類強調CP(一緻性,可靠性)的服務治理架構最大的差別在于:Eureka為了實作更高的服務可用性,犧牲了一定的一緻性,極端情況下它甯願接收故障執行個體也不願丢掉健康執行個體,正如我們上面所說的自我保護機制。
但是,此時如果我們調用了這些不正常的服務,調用就會失敗,進而導緻其它服務不能正常工作!這顯然不是我們願意看到的。
我們現在關閉一個user-service執行個體。
因為服務剔除的延遲,consumer并不會立即得到最新的服務清單,此時再次通路你會得到錯誤提示。
但是此時,8081服務其實是正常的。
是以Spring Cloud 整合了Spring Retry 來增強RestTemplate的重試能力,當一次服務調用失敗後,不會立即抛出一次,而是再次重試另一個服務。
隻需要簡單配置即可實作Ribbon的重試:---代碼user-consumer消費者中
spring:
cloud:
loadbalancer:
retry:
enabled: true # 開啟Spring Cloud的重試功能
user-service:
ribbon:
ConnectTimeout: 250 # Ribbon的連接配接逾時時間,機關毫秒
ReadTimeout: 1000 # Ribbon的資料讀取逾時時間,
OkToRetryOnAllOperations: true # 是否對所有操作都進行重試
MaxAutoRetriesNextServer: 3 # 切換執行個體的重試次數
MaxAutoRetries: 1 # 對目前執行個體的重試次數
根據如上配置,當通路到某個服務逾時後,它會再次嘗試通路下一個服務執行個體,如果不行就再換一個執行個體,如果不行,則傳回失敗。切換次數取決于
MaxAutoRetriesNextServer
參數的值
引入spring-retry依賴
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
我們重新開機user-consumer-demo,測試,發現即使user-service2當機,也能通過另一台服務執行個體擷取到結果!