您好,登錄后才能下訂單哦!
上一節( 跟我學Spring Cloud(Finchley版)-01-開篇 )說過,Spring Cloud是一個快速構建分布式應用的工具集。本節,我們就來編寫一個簡單的分布式應用,并探討這個分布式應用有哪些問題。
本書使用服務提供者與服務消費者來描述微服務之間的調用關系。下表解釋了服務提供者與服務消費者。
表-服務提供者與服務消費者
名詞 | 定義 |
---|---|
服務提供者 | 服務的被調用方(即:為其他服務提供服務的服務) |
服務消費者 | 服務的調用方(即:依賴其他服務的服務) |
以電影售票系統為例。如圖,用戶向電影微服務發起了一個購票的請求。在進行購票的業務操作前,電影微服務需要調用用戶微服務的接口,查詢當前用戶的余額是多少、是不是符合購票標準等。在這種場景下,用戶微服務就是一個服務提供者,電影微服務則是一個服務消費者。
圍繞該場景,先來編寫一個用戶微服務,然后編寫一個電影微服務。
TIPS
服務消費者和服務提供者描述的只是微服務之間的調用關系,一般成對出現。例如本文,用戶微服務是是電影微服務的服務提供者,電影微服務是用戶微服務的服務消費者。很多初學者和筆者交流時,會描述提供者如何如何……仿佛消費者和提供者是微服務的固有屬性,這是不對的——例如A調用B,B調用C,那么B相對A就是提供者,B相對C就消費者。
Spring Boot/Spring Cloud時代后,應用開發基本遵循三板斧:
至于你的業務代碼,該怎么寫還怎么寫。
TIPS
對于懶人,可使用Spring Initilizr(IDEA、Spring Tool Suite等IDE上均有集成,也可在http://start.spring.io 使用網頁版)創建應用,它會給你生成項目的依賴以及項目的骨架。后續,筆者會以番外的形式更新相關教程。
創建一個Maven項目,依賴如下:
<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.itmuch.cloud</groupId>
<artifactId>microservice-simple-provider-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<!-- 引入spring boot的依賴 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 引入H2數據庫,一種內嵌的數據庫,語法類似MySQL -->
<dependency>
<groupId>com.h3database</groupId>
<artifactId>h3</artifactId>
</dependency>
<!-- 引入Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<!-- 引入spring cloud的依賴,不能少,主要用來管理Spring Cloud生態各組件的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 添加spring-boot的maven插件,不能少,打jar包時得用 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
其中,spring-boot-starter-web
提供了Spring MVC的支持;spring-boot-starter-data-jpa
提供了Spring Data JPA的支持;h3
是一種內嵌的數據庫,語法和MySQL類似(筆者實在沒有動力為了簡單的演示再寫一大堆內容去演示怎么安裝MySQL數據庫);lombok
則是一款開發利器,可以幫助你簡化掉N多冗余代碼。
WARNING
TIPS
創建實體類:
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column
private String username;
@Column
private String name;
@Column
private Integer age;
@Column
private BigDecimal balance;
}
創建DAO:
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}
創建Controller:
@RequestMapping("/users")
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/{id}")
public Optional<User> findById(@PathVariable Long id) {
return this.userRepository.findById(id);
}
}
其中, @GetMapping
,是Spring 4.3提供的新注解。它是一個組合注解,等價于@RequestMapping(method = RequestMethod.GET)
,用于簡化開發。同理還有@PostMapping
、@PutMapping
、@DeleteMapping
、@PatchMapping
等。
編寫啟動類:
@SpringBootApplication
public class ProviderUserApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderUserApplication.class, args);
}
/**
* 初始化用戶信息
* 注:Spring Boot2不能像1.x一樣,用spring.datasource.schema/data指定初始化SQL腳本,否則與actuator不能共存
* 原因詳見:
* https://github.com/spring-projects/spring-boot/issues/13042
* https://github.com/spring-projects/spring-boot/issues/13539
*
* @param repository repo
* @return runner
*/
@Bean
ApplicationRunner init(UserRepository repository) {
return args -> {
User user1 = new User(1L, "account1", "張三", 20, new BigDecimal(100.00));
User user2 = new User(2L, "account2", "李四", 28, new BigDecimal(180.00));
User user3 = new User(3L, "account3", "王五", 32, new BigDecimal(280.00));
Stream.of(user1, user2, user3)
.forEach(repository::save);
};
}
}
@SpringBootApplication
是一個組合注解,它整合了@Configuration
、@EnableAutoConfiguration
和@ComponentScan
注解,并開啟了Spring Boot程序的組件掃描和自動配置功能。在開發Spring Boot程序的過程中,常常會組合使用@Configuration
、@EnableAutoConfiguration
和@ComponentScan
等注解,所以Spring Boot提供了@SpringBootApplication
,來簡化開發。
在啟動時,我們使用了ApplicationRunner init(UserRepository repository)
初始化了三條數據,分別是張三、李四、王五。@Bean
則是一個方法注解,作用是實例化一個Bean并使用該方法的名稱命名。類似于XML配置方式的<bean id="init" class="...ApplicationRunner"/>
。
編寫配置文件application.yml
:
server:
# 指定Tomcat端口
port: 8000
spring:
jpa:
# 讓hibernate打印執行的SQL
show-sql: true
logging:
level:
root: INFO
# 配置日志級別,讓hibernate打印出執行的SQL參數
org.hibernate: INFO
org.hibernate.type.descriptor.sql.BasicBinder: TRACE
org.hibernate.type.descriptor.sql.BasicExtractor: TRACE
傳統Web應用開發中,常使用properties格式文件作為配置文件。Spring Boot以及Spring Cloud支持使用properties或者yml格式的文件作為配置文件。
yml文件格式是YAML(Yet Another Markup Language)編寫的文件格式,YAML和properties格式的文件可互相轉換,例如本節中的application.yml,就等價于如下的properties文件:
server.port=8000
spring.jpa.show-sql=true
logging.level.root=INFO
logging.level.org.hibernate=INFO
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
logging.level.org.hibernate.type.descriptor.sql.BasicExtractor=TRACE
從中不難看出,YAML比properties結構清晰;可讀性、可維護性也更強,并且語法非常簡潔。因此,本書使用YAML格式作為配置文件。但,yml有嚴格的縮進,并且key與value之間使用: 分隔,冒號后的空格不能少,請大家注意。
訪問http://localhost:8000/users/1
,可獲得結果:
{"id":1,"username":"account1","name":"張三","age":20,"balance":100.00}
我們已經編寫了一個服務提供者(用戶微服務),本節來編寫一個服務消費者(電影微服務)。該服務非常簡單,它使用RestTemplate調用用戶微服務的API,從而查詢指定id的用戶信息。
創建一個Maven項目,ArtifactId是microservice-simple-consumer-movie
。
加依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
創建實體類:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String name;
private Integer age;
private BigDecimal balance;
}
創建啟動類:
@SpringBootApplication
public class ConsumerMovieApplication {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerMovieApplication.class, args);
}
}
創建Controller:
@RequestMapping("/movies")
@RestController
public class MovieController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/users/{id}")
public User findById(@PathVariable Long id) {
// 這里用到了RestTemplate的占位符能力
User user = this.restTemplate.getForObject("http://localhost:8000/users/{id}", User.class, id);
// ...電影微服務的業務...
return user;
}
}
由代碼可知,Controller使用RestTemplate調用用戶微服務的RESTful API。
編寫配置文件application.yml
:
server:
port: 8010
拓展閱讀
本文使用RestTemplate實現了基于HTTP的遠程調用,事實上,Spring 5開始,WebFlux提供了Reactive的Web Client:WebClinet
,使用方式和RestTemplate基本類似,但性能更強,吞吐更好。有興趣的可前往<https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/web-reactive.html#webflux-client-builder> 了解。在這里,筆者對WebClient做了一些簡單的封裝,也可關注:<https://github.com/itmuch/thor-test/blob/master/src/main/java/com/itmuch/thor/httpclient/WebClientUtil.java>
訪問:http://localhost:8010/movies/users/1
,結果如下:
{"id":1,"username":"account1","name":"張三","age":20,"balance":100.00}
至此,我們已經實現了這個最簡單的分布式應用,應用之間通過HTTP通信。代碼非常簡單,但這些簡單的代碼里,存在著若干問題:
應用沒有監控,沒有畫板,一切指標都沒有。在這個Growth Hack逐漸成為主流的時代,不弄個Dashboard把系統壓力、QPS、CPU、內存、日活啥的可視化,你好意思出來混嗎……
地址硬編碼問題——電影微服務中將用戶微服務的地址寫死,如果用戶微服務地址發生變化,難道要重新上線電影微服務嗎?
你可能會質疑:用戶微服務地址為什么會變,讓它保持不變就行了啊,這不是問題。這里舉兩個例子:
例1:如果你用Docker,那么地址幾乎每次啟動都會變……
例2:你之前用的是TXYun,后來你想把用戶微服務遷移到Aliyun。這個時候IP就會發生變化。我相信你不會樂意找到哪些服務調用了用戶微服務的接口,然后所有調用用戶微服務的服務統一修改地址……
負載均衡如何考慮?難道得在電影微服務和用戶微服務之間加個NGINX做負載均衡嗎?聽起來是可行的,但如果有10000+服務(這并不夸張,我司的微服務數目是這個數字乘以N,N >= m,哈哈哈)那這個NGINX的配置得有多復雜……
服務之間沒有容錯機制,相信對技術有激情的你已經不止一次聽過容錯、降級、fallback、回退之類的詞匯。
如果應用發生故障,你怎么迅速找到問題所在?
如上詞匯,你可能看得懂,你也可能看不懂。沒有關系,請繼續閱讀,筆者將會用通俗的語言去描述,在你看完本系列后,你會知道,原來那些所謂的高大上的理論、術語、技術,原來也就是這么回事兒。
原文:http://www.itmuch.com/spring-cloud/finchley-2/ 轉載請說明出處。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。