本篇模擬訂單服務調用商品服務,同時商品服務採用集羣部署。html
註冊中心服務端口號7001,訂單服務端口號9001,商品集羣端口號:800一、800二、8003。java
各服務的配置文件這裏我這邊不在顯示了,和上篇博客配置同樣。博客地址:SpringCloud(3)---Eureka服務註冊與發現web
<?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.jincou</groupId> <artifactId>product</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>product</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> <!--定義當前springcloud版本--> <spring-cloud.version>Finchley.RELEASE</spring-cloud.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-test</artifactId> <scope>test</scope> </dependency> <!--代表是Eureka Client客戶端--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <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> </project>
@Data @NoArgsConstructor @AllArgsConstructor public class Product implements Serializable { private int id; //商品名稱 private String name; //價格,分爲單位 private int price; //庫存 private int store; }
public interface ProductService { //查找全部商品 List<Product> listProduct(); //根據商品ID查找商品 Product findById(int id); }
@Service public class ProductServiceImpl implements ProductService { private static final Map<Integer, Product> daoMap = new HashMap<>(); //模擬數據庫商品數據 static { Product p1 = new Product(1, "蘋果X", 9999, 10); Product p2 = new Product(2, "冰箱", 5342, 19); Product p3 = new Product(3, "洗衣機", 523, 90); Product p4 = new Product(4, "電話", 64345, 150); daoMap.put(p1.getId(), p1); daoMap.put(p2.getId(), p2); daoMap.put(p3.getId(), p3); daoMap.put(p4.getId(), p4); } @Override public List<Product> listProduct() { Collection<Product> collection = daoMap.values(); List<Product> list = new ArrayList<>(collection); return list; } @Override public Product findById(int id) { return daoMap.get(id); } }
@RestController @RequestMapping("/api/v1/product") public class ProductController { //集羣狀況下,用於訂單服務查看到底調用的是哪一個商品微服務節點 @Value("${server.port}") private String port; @Autowired private ProductService productService; //獲取全部商品列表 @RequestMapping("list") public Object list(){ return productService.listProduct(); } //根據id查找商品詳情 @RequestMapping("find") public Object findById(int id){ Product product = productService.findById(id); Product result = new Product(); BeanUtils.copyProperties(product,result); result.setName( result.getName() + " data from port="+port ); return result; } }
<?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.jincou</groupId> <artifactId>order</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>order</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> <spring-cloud.version>Finchley.RELEASE</spring-cloud.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-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> </dependencies> <dependencyManagement> <dependencies> <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> </project>
@Data @AllArgsConstructor @NoArgsConstructor public class ProductOrder implements Serializable { //訂單ID private int id; // 商品名稱 private String productName; //訂單號 private String tradeNo; // 價格,分 private int price; //訂單建立時間 private Date createTime; //用戶id private int userId; //用戶名 private String userName; }
/** * 訂單業務類 */ public interface ProductOrderService { //下單接口 ProductOrder save(int userId, int productId); }
@Service public class ProductOrderServiceImpl implements ProductOrderService { @Autowired private RestTemplate restTemplate; @Override public ProductOrder save(int userId, int productId) { //product-service是微服務名稱(這裏指向的商品微服務名稱),api/v1/product/find?id=? 就是商品微服務對外的接口 Map<String, Object> productMap = restTemplate.getForObject("http://product-service/api/v1/product/find?id=" + productId, Map.class); ProductOrder productOrder = new ProductOrder(); productOrder.setCreateTime(new Date()); productOrder.setUserId(userId); productOrder.setTradeNo(UUID.randomUUID().toString()); //獲取商品名稱和商品價格 productOrder.setProductName(productMap.get("name").toString()); productOrder.setPrice(Integer.parseInt(productMap.get("price").toString())); //由於在商品微服務配置了集羣,因此這裏打印看下調用了是哪一個集羣節點,輸出端口號。 System.out.println(productMap.get("name").toString()); return productOrder; } }
@RestController @RequestMapping("api/v1/order") public class OrderController { @Autowired private ProductOrderService productOrderService; @RequestMapping("save") public Object save(@RequestParam("user_id")int userId, @RequestParam("product_id") int productId){ return productOrderService.save(userId, productId); } }
@SpringBootApplication public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } //當添加@LoadBalanced註解,就表明啓動Ribbon,進行負載均衡 @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
多調幾回接口,看後臺打印算法
發現訂單服務去掉商品服務的時候,不是固定節點,並且集羣的每一個節點都有可能。因此經過Ribbon實現了負載均衡。spring
在springcloud中,引入Ribbon來做爲客戶端時,負載均衡使用的是被@LoadBalanced
修飾的RestTemplate
對象。數據庫
RestTemplate 是Spring本身封裝的http請求的客戶端,也就是說它只能發送一個正常的Http請求,這跟咱們要求的負載均衡是有出入的,還有就是這個請求的連接上的域名apache
是咱們微服的一個服務名,而不是一個真正的域名,那它是怎麼實現負載均衡功能的呢?api
咱們來看看RestTemplate的父類InterceptingHttpAccessor。app
從源碼咱們能夠知道InterceptingHttpAccessor中有一個攔截器列表List<ClientHttpRequestInterceptor>,若是這個列表爲空,則走正常請求流程,若是不爲空則走負載均衡
攔截器,因此只要給RestTemplate添加攔截器,而這個攔截器中的邏輯就是Ribbon的負載均衡的邏輯。經過爲RestTemplate配置添加攔截器。
具體的攔截器的生成在LoadBalancerAutoConfiguration這個配置類中,全部的RestTemplate的請求都會轉到Ribbon的負載均衡器上
(固然這個時候若是你用RestTemplate發起一個正常的Http請求時走不通,由於它找不到對應的服務。)這樣就實現了Ribbon的請求的觸發。
上面提到過,發起http後請求後,請求會到達到達攔截器中,在攔截其中實現負載均衡,先看看代碼:
咱們能夠看到在intercept()方法中實現攔截的具體邏輯,首先會根據傳進來的請求連接,獲取微服的名字serviceName,而後調用LoadBalancerClient的
execute(String serviceId, LoadBalancerRequest<T> request)方法,這個方法直接返回了請求結果,因此正真的路由邏輯在LoadBalancerClient的實現類中,
而這個實現類就是RibbonLoadBalancerClient,看看execute()的源碼:
首先是得到均衡器ILoadBalancer這個類上面講到過這是Netflix Ribbon中的均衡器,這是一個抽象類,具體的實現類是ZoneAwareLoadBalancer上面也講到過,
每個微服名對應一個均衡器,均衡器中維護者微服名下全部的服務清單。getLoadBalancer()方法經過serviceId得到對應的均衡器,getServer()方法經過對應的均衡器
在對應的路由的算法下計算獲得須要路由到Server,Server中有該服務的具體域名等相關信息。獲得了具體的Server後執行正常的Http請求,整個請求的負載均衡邏輯就完成了。
在微服中Ribbon和 Hystrix一般是一塊兒使用的,其實直接使用Ribbon和Hystrix實現服務間的調用並非很方便,一般在Spring Cloud中咱們使用Feign完成服務間的調用,
而Feign是對Ribbon和Hystrix作了進一步的封裝方便你們使用,對Ribbon的學習能幫你更好的完成Spring Cloud中服務間的調用。
我只是偶爾安靜下來,對過去的種種思忖一番。那些曾經的舊時光裏即使有過天真愚鈍,也不值得譴責。畢竟,日後的日子,還很長。不斷鼓勵本身,
天一亮,又是嶄新的起點,又是未知的征程(上校6)