有過dubbo/dubbox使用經驗的朋友,看到下面這張圖,一定很熟悉,就是SOA架構的最基本套路。
與dubbo對比,上圖的3大要素中,spring cloud是借助以下組件來實現的:
1、注冊中心:
spring cloud默認使用eureka server來做注冊中心,而dubbo默認使用的是zookeeper。eureka的注冊信息是保存在一個雙層的Map對象中的,換句話說在內存中,不象zookeeper是長久保存在節點中。
2、服務提供方:
spring-web(Spring MVC)提供了完善的http rest服務框架,用這一套就能提供rest服務。(目前spring cloud官方提供的示例基本上都是http rest服務,理論上講,應該也可以擴展成rpc服務,而dubbo是以rpc為主的,這點有些區別)
3、服務消費方:
依賴于spring-web,負載均衡采用ribbon組件來完成,大致原理是從注冊中心發現可用服務的信息,緩存在本地,然后按一定的負載均衡算法進行調用。(跟dubbo類似,只不過dubbo是自己實現的負載均衡)
下面是這三方的最基本示例:
一、項目結構
注:spring-cloud是完全基于Spring Boot來構建項目的,所以對spring boot不熟悉的,建議先看本博客的spring boot系列。
register-center 即 eureka 注冊中心
service-api 為服務契約
service-consumer 為服務消費方
service-provider 為服務提供方
二、register-center
2.1 依賴項
buildscript { repositories { maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE") } } apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE" } } dependencies { compile 'org.springframework.cloud:spring-cloud-starter-eureka-server' compile 'org.springframework.boot:spring-boot-starter-actuator' testCompile 'org.springframework.boot:spring-boot-starter-test' }
2.2 main入口程序
package com.cnblogs.yjmyzz.spring.cloud.study; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; /** * Created by 菩提樹下的楊過 on 2017/6/17. */ @SpringBootApplication @EnableEurekaServer public class RegisterServer { public static void main(String[] args) { SpringApplication.run(RegisterServer.class, args); } }
主要是靠最上面的@EnableEurekaServer這個注解,其它完全沒有花頭。
2.3 配置
server: port: 8000 eureka: client: register-with-eureka: false fetch-registry: false service-url: defaultZone: http://localhost:8000/eureka
解釋一下:
注冊中心本身也是一個服務,也可以當成普通服務向其它注冊中心來注冊,由于本示例中,只有一個eureka server自己就充當注冊中心,也不需要跟其它注冊中心同步注冊信息,所以都設置成false。最后一行的defaultZone,初次接觸可以先不管,先理解成注冊中心對外暴露的地址即可。
2.4 啟動
啟動后,瀏覽http://localhost:8000/,可以看到類似下圖:
現在沒有任何服務注冊,所以在Application里,顯示No instances available.
三、service-api
為了方便后面講解,先定義一個服務接口,以及對應的DTO
package com.cnblogs.yjmyzz.spring.cloud.study.api; import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO; /** * Created by 菩提樹下的楊過 on 2017/6/17. */ public interface UserService { UserDTO findUser(Integer userId); }
以及
package com.cnblogs.yjmyzz.spring.cloud.study.dto; import lombok.Data; /** * Created by 菩提樹下的楊過 on 2017/6/17. */ @Data public class UserDTO { private Integer userId; private String userName; }
四、service-provider
4.1 依賴項
buildscript { repositories { maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE") } } apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE" } } dependencies { compile(project(":service-api")) compile 'org.springframework.cloud:spring-cloud-starter-eureka' compile 'org.springframework.boot:spring-boot-starter-actuator' compile 'org.springframework.boot:spring-boot-starter-web' testCompile 'org.springframework.boot:spring-boot-starter-test' }
4.2 接口實現
package com.cnblogs.yjmyzz.spring.cloud.study.service.impl; import com.cnblogs.yjmyzz.spring.cloud.study.api.UserService; import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public UserDTO findUser(Integer userId) { UserDTO user = new UserDTO(); user.setUserId(userId); user.setUserName("菩提樹下的楊過"); return user; } }
這里只是隨便示意一下,直接返回一個固定的UserDTO實例。
4.3 controller
package com.cnblogs.yjmyzz.spring.cloud.study.controller; import com.cnblogs.yjmyzz.spring.cloud.study.api.UserService; import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public UserDTO findUser(@PathVariable Integer id) { return userService.findUser(id); } }
這里用了一個新的注解GetMapping,相當于之前SpringMVC中@RequestMapping(method = RequestMethod.GET),更簡潔而已。
到目前為止,都跟常規的SpringMVC無異。
4.4 main入口
package com.cnblogs.yjmyzz.spring.cloud.study; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * Created by yangjunming on 2017/6/17. */ @EnableDiscoveryClient @SpringBootApplication public class ServiceProvider { public static void main(String[] args) { SpringApplication.run(ServiceProvider.class, args); } }
依舊還是@EnableDiscoveryClient挑大梁,表明這是一個eureka的客戶端程序(即:能向eureka server注冊)
4.5 配置
server: port: 8001 spring: application: name: "service-provider-demo" eureka: instance: prefer-ip-address: true client: service-url: defaultZone: http://localhost:8000/eureka/
應該不難理解,最后那幾行,表示用自己IP地址向 http://localhost:8000/eureka/注冊
4.6 啟動
啟動成功后,再看eureka 剛才的頁面,會發現已經注冊進來了。
注:大家可以把service-provider多啟動幾個實例(端口錯開,不要沖突即可),然后再觀察下這個界面,可以看到注冊了多個provider實例
五、service-consumer
5.1 依賴項
buildscript { repositories { maven { url "http://maven.aliyun.com/nexus/content/groups/public/" } } dependencies { classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.4.RELEASE") } } apply plugin: 'spring-boot' dependencyManagement { imports { mavenBom "org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE" } } dependencies { compile(project(":service-api")) compile 'org.springframework.cloud:spring-cloud-starter-eureka' compile 'org.springframework.boot:spring-boot-starter-actuator' compile 'org.springframework.cloud:spring-cloud-starter-ribbon' compile 'org.springframework.boot:spring-boot-starter-web' testCompile 'org.springframework.boot:spring-boot-starter-test' }
5.2 建一個調用的Controller
package com.cnblogs.yjmyzz.spring.cloud.study.service.controller; import com.cnblogs.yjmyzz.spring.cloud.study.dto.UserDTO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.List; /** * Created by yangjunming on 2017/6/17. */ @RestController public class OrderController { @Autowired private RestTemplate restTemplate; @Autowired private LoadBalancerClient loadBalancerClient; @Autowired private DiscoveryClient discoveryClient; @GetMapping("/order/{userId}/{orderNo}") public String findOrder(@PathVariable Integer userId, @PathVariable String orderNo) { UserDTO user = restTemplate.getForEntity("http://SERVICE-PROVIDER-DEMO/user/" + userId, UserDTO.class).getBody(); if (user != null) { return user.getUserName() + " 的訂單" + orderNo + " 找到啦!"; } return "用戶不存在!"; } @GetMapping("/user-instance") public List<ServiceInstance> showInfo() { return this.discoveryClient.getInstances("SERVICE-PROVIDER-DEMO"); } @GetMapping("/log-instance") public ServiceInstance chooseInstance() { return this.loadBalancerClient.choose("SERVICE-PROVIDER-DEMO"); } }
這里暴露了3個url,一個個來看:
a. /order/{userId}/{orderNo} 這個用來示例如何調用service-provider中的方法,注意這里我們并沒有用http://localhost:8001/user/1 來調用,而通過http://service-provider-demo/user/ 指定service-provider的application name,讓系統從注冊中心去發現服務。
b. /user-instance , /log-instance 這二個url 用來輔助輸出從注冊中心發現的服務實例相關的信息,并非必須。
這里面還有二個注入的實例:restTemplate 、loadBalancerClient ,分別用來發起rest的http請求,以及使用負載均衡從可用的服務列表中,挑出一個可用實例。
5.3 main入口
package com.cnblogs.yjmyzz.spring.cloud.study.service; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.web.client.RestTemplate; @EnableDiscoveryClient @SpringBootApplication public class ServiceConsumer { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ServiceConsumer.class, args); } }
依然靠二個關鍵的注解:@EnableDiscoveryClient、@LoadBalanced,特別是@LoadBalanced,經過這個修飾的restTemplate,就不是普通的restTemplate了,而是具備負載均衡能力的restTemplate,即每次都會用負載均衡算法,從可用服務列表中,挑一個進行調用。
5.3 啟動
可以從eukera中看到,service-provider與service-consumer都注冊進來了。
調用一下試試:http://localhost:8002/order/1/1000,成功的話會看到下面的輸出
注:此時可以把注冊中心eureka server停掉,然后再調用下http://localhost:8002/order/1/1000,會發現仍然可以正常調用,說明注冊中心的服務列表,在本機是有緩存的,這跟dubbo/dubbox類似。
另外還可以驗證下負載均衡,方法如下:
先把service-provider啟2個,開二個終端窗口:
java -jar xxx.jar --server.port=9001
java -jar xxx.jar --server.port=9002
這樣就能跑二個應用起來,然后看注冊中心
然后再調用下consumer的log-instance
可以看到,這次選擇的是9002端口應對的實例,然后再刷新一下:
這回選擇的是另一個端口9001的實例,說明負載均衡確實起作用了。
至此,一個最基本的SOA框架雛形搭建起來了,當然還有很多地方需要完善,比如:注冊中心如何做到HA,服務融斷如何處理,注冊中心如何安全認證(防止其它服務亂注冊)等等,后面再講。
文章列表