跟我學Spring Cloud(Finchley版)-02-構建分散式應用
上一節(http://www.itmuch.com/spring-cloud/finchley-1/)說過,Spring Cloud是一個快速構建分散式應用的工具集。本節,我們就來編寫一個簡單的分散式應用,並探討這個分散式應用有哪些問題。
服務消費者 & 提供者
本書使用服務提供者與服務消費者來描述微服務之間的呼叫關係。下表解釋了服務提供者與服務消費者。
表-服務提供者與服務消費者
名詞 | 定義 |
---|---|
服務提供者 | 服務的被呼叫方(即:為其他服務提供服務的服務) |
服務消費者 | 服務的呼叫方(即:依賴其他服務的服務) |
以電影售票系統為例。如圖,使用者向電影微服務發起了一個購票的請求。在進行購票的業務操作前,電影微服務需要呼叫使用者微服務的介面,查詢當前使用者的餘額是多少、是不是符合購票標準等。在這種場景下,使用者微服務就是一個服務提供者,電影微服務則是一個服務消費者。
圍繞該場景,先來編寫一個使用者微服務,然後編寫一個電影微服務。
TIPS
服務消費者和服務提供者描述的只是微服務之間的呼叫關係,一般成對出現。例如本文,使用者微服務是是電影微服務的服務提供者,電影微服務是使用者微服務的服務消費者。很多初學者和筆者交流時,會描述提供者如何如何……彷彿消費者和提供者是微服務的固有屬性,這是不對的——例如A呼叫B,B呼叫C,那麼B相對A就是提供者,B相對C就消費者。
Spring Boot/Spring Cloud應用開發套路
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.h2database</groupId> <artifactId>h2</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的支援;h2
是一種內嵌的資料庫,語法和MySQL類似(筆者實在沒有動力為了簡單的演示再寫一大堆內容去演示怎麼安裝MySQL資料庫);lombok
則是一款開發利器,可以幫助你簡化掉N多冗餘程式碼。WARNING
- Lombok之前,必須為你的IDE安裝Lombok外掛!可參考:http://www.cnblogs.com/shindo/p/7550790.html
TIPS
- Lombok快速上手:https://blog.csdn.net/motui/article/details/79012846
- Lombok官方網站:https://projectlombok.org/
-
建立實體類:
@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、回退之類的詞彙。
-
如果應用發生故障,你怎麼迅速找到問題所在?
-
使用者認證和授權呢?被狗吃了嗎?
如上詞彙,你可能看得懂,你也可能看不懂。沒有關係,請繼續閱讀,筆者將會用通俗的語言去描述,在你看完本系列後,你會知道,原來那些所謂的高大上的理論、術語、技術,原來也就是這麼回事兒。
配套程式碼
- GitHub:
- Gitee:
原文:http://www.itmuch.com/spring-cloud/finchley-2/ 轉載請說明出處。