本示例主要介紹 Spring Cloud 系列中的 Eureka,使你能快速上手負載均衡、聲明式服務、服務註冊中心等php
Eureka 是 Netflix 的子模塊,它是一個基於 REST 的服務,用於定位服務,以實現雲端中間層服務發現和故障轉移。html
服務註冊和發現對於微服務架構而言,是很是重要的。有了服務發現和註冊,只須要使用服務的標識符就能夠訪問到服務,而不須要修改服務調用的配置文件。該功能相似於 Dubbo 的註冊中心,好比 Zookeeper。java
Eureka 採用了 CS 的設計架構。Eureka Server 做爲服務註冊功能的服務端,它是服務註冊中心。而系統中其餘微服務則使用 Eureka 的客戶端鏈接到 Eureka Server 並維持心跳鏈接git
Eureka Server 提供服務的註冊服務。各個服務節點啓動後會在 Eureka Server 中註冊服務,Eureka Server 中的服務註冊表會存儲全部可用的服務節點信息。github
Eureka Client 是一個 Java 客戶端,用於簡化 Eureka Server 的交互,客戶端同時也具有一個內置的、使用輪詢負載算法的負載均衡器。在應用啓動後,向 Eureka Server 發送心跳(默認週期 30 秒)。若是 Eureka Server 在多個心跳週期內沒有接收到某個節點的心跳,Eureka Server 會從服務註冊表中將該服務節點信息移除。web
簡單理解:各個微服務將本身的信息註冊到server上,須要調用的時候從server中獲取到其餘微服務信息
複製代碼
Spring Cloud Ribbon 是基於 Netflix Ribbon 實現的一套客戶端負載均衡工具,其主要功能是提供客戶端的軟件負載均衡算法,將 Netflix 的中間層服務鏈接在一塊兒。算法
Ribbon 提供多種負載均衡策略:如輪詢、隨機、響應時間加權等。spring
Feign是聲明式、模板化的HTTP客戶端,能夠更加快捷優雅的調用HTTP API。在部分場景下和Ribbon相似,都是進行數據的請求處理,可是在請求參數使用實體類的時候顯然更加方便,同時還支持安全性、受權控制等。 Feign是集成了Ribbon的,也就是說若是引入了Feign,那麼Ribbon的功能也能使用,好比修改負載均衡策略等數據庫
<?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.easy</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>cloud-feign</artifactId>
<groupId>com.easy</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</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>
複製代碼
server:
port: 9000
spring:
application:
name: eureka-server
eureka:
instance:
hostname: localhost # eureka 實例名稱
client:
register-with-eureka: false # 不向註冊中心註冊本身
fetch-registry: false # 是否檢索服務
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 註冊中心訪問地址
複製代碼
package com.easy.eurekaServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
複製代碼
package com.easy.helloServiceApi.vo;
import lombok.Getter;
import java.io.Serializable;
@Getter
public class Result implements Serializable {
private static final long serialVersionUID = -8143412915723961070L;
private int code;
private String msg;
private Object data;
private Result() {
}
private Result(int code, String msg) {
this.code = code;
this.msg = msg;
}
private Result(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static Result success() {
return success(null);
}
public static Result success(Object data) {
return new Result(200, "success", data);
}
public static Result fail() {
return fail(500, "fail");
}
public static Result fail(int code, String message) {
return new Result(code, message);
}
}
複製代碼
package com.easy.helloServiceApi.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/** * 訂單類 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
private String orderId;
private String goodsId;
private int num;
}
複製代碼
package com.easy.helloServiceApi.client;
import com.easy.helloServiceApi.vo.Result;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@FeignClient(value = "hello-server")
public interface GoodsServiceClient {
@RequestMapping("/goods/goodsInfo/{goodsId}")
Result goodsInfo(@PathVariable("goodsId") String goodsId);
}
複製代碼
package com.easy.helloServiceApi.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
/** * 商品類 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Goods {
private String goodsId;
private String name;
private String descr;
// 測試端口
private int port;
}
複製代碼
package com.easy.helloService.controller;
import com.easy.helloService.service.GoodsService;
import com.easy.helloServiceApi.model.Goods;
import com.easy.helloServiceApi.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService goodsService;
@RequestMapping("/goodsInfo/{goodsId}")
public Result goodsInfo(@PathVariable String goodsId) {
Goods goods = this.goodsService.findGoodsById(goodsId);
return Result.success(goods);
}
}
複製代碼
package com.easy.helloService.service;
import com.easy.helloServiceApi.model.Goods;
public interface GoodsService {
Goods findGoodsById(String goodsId);
}
複製代碼
package com.easy.helloService.service.impl;
import com.easy.helloService.service.GoodsService;
import com.easy.helloServiceApi.model.Goods;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class GoodsServiceImpl implements GoodsService {
// 模擬數據庫
private static Map<String, Goods> data;
static {
data = new HashMap<>();
data.put("1", new Goods("1", "華爲", "華爲手機", 8081)); //表示調用8081端口的數據,實際上數據會放在數據庫或緩存中
data.put("2", new Goods("2", "蘋果", "蘋果", 8081));
}
@Override
public Goods findGoodsById(String goodsId) {
return data.get(goodsId);
}
}
複製代碼
package com.easy.helloService;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class HelloServiceApplication {
public static void main(String[] args) {
SpringApplication.run(HelloServiceApplication.class, args);
}
}
複製代碼
server:
port: 8081
spring:
application:
name: hello-server
eureka:
instance:
instance-id: goods-api-8081
prefer-ip-address: true # 訪問路徑能夠顯示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
複製代碼
server:
port: 8082
spring:
application:
name: hello-server
eureka:
instance:
instance-id: goods-api-8082
prefer-ip-address: true # 訪問路徑能夠顯示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
複製代碼
package com.easy.helloService.service.impl;
import com.easy.helloService.service.GoodsService;
import com.easy.helloServiceApi.model.Goods;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class GoodsServiceImpl implements GoodsService {
// 模擬數據庫
private static Map<String, Goods> data;
static {
data = new HashMap<>();
data.put("1", new Goods("1", "華爲", "華爲手機", 8082)); //表示8082端口的數據,實際上數據會放在數據庫或緩存中
data.put("2", new Goods("2", "蘋果", "蘋果", 8082));
}
@Override
public Goods findGoodsById(String goodsId) {
return data.get(goodsId);
}
}
複製代碼
server:
port: 8083
spring:
application:
name: hello-server
eureka:
instance:
instance-id: goods-api-8083
prefer-ip-address: true # 訪問路徑能夠顯示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
複製代碼
package com.easy.helloService.service.impl;
import com.easy.helloService.service.GoodsService;
import com.easy.helloServiceApi.model.Goods;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class GoodsServiceImpl implements GoodsService {
// 模擬數據庫
private static Map<String, Goods> data;
static {
data = new HashMap<>();
data.put("1", new Goods("1", "華爲", "華爲手機", 8083)); //表示8083端口的數據,實際上數據會放在數據庫或緩存中
data.put("2", new Goods("2", "蘋果", "蘋果", 8083));
}
@Override
public Goods findGoodsById(String goodsId) {
return data.get(goodsId);
}
}
複製代碼
<?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.easy</groupId>
<artifactId>feign-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>feign-consumer</name>
<description>Demo project for Spring Boot</description>
<parent>
<artifactId>cloud-feign</artifactId>
<groupId>com.easy</groupId>
<version>1.0.0</version>
</parent>
<dependencies>
<!-- springmvc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- eureka 客戶端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<!-- feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.easy</groupId>
<artifactId>hello-service-api</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
複製代碼
引入openfeign、ribbon、eureka-client等依賴,openfeign用來實現聲明式服務調用,ribbon用來實現負載均衡,eureka-client用來註冊、發現服務apache
package com.easy.feignConsumer.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
/** * 隨機選取負載均衡策略 * @return */
@Bean
public IRule testRule() {
return new RandomRule();
}
}
複製代碼
package com.easy.feignConsumer.service;
import com.easy.helloServiceApi.model.Goods;
import com.easy.helloServiceApi.vo.Result;
public interface GoodsService {
Result placeGoods(Goods goods);
}
複製代碼
package com.easy.feignConsumer.service.impl;
import com.easy.feignConsumer.service.GoodsService;
import com.easy.helloServiceApi.client.GoodsServiceClient;
import com.easy.helloServiceApi.model.Goods;
import com.easy.helloServiceApi.vo.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class GoodsServiceImpl implements GoodsService {
@Autowired
private GoodsServiceClient goodsServiceClient;
@Override
public Result placeGoods(Goods order) {
Result result = this.goodsServiceClient.goodsInfo(order.getGoodsId());
if (result != null && result.getCode() == 200) {
log.info("=====獲取本地商品====");
log.info("接口返回數據爲==>{}", ToStringBuilder.reflectionToString(result.getData()));
}
return result;
}
}
複製代碼
package com.easy.feignConsumer.controller;
import com.easy.feignConsumer.service.GoodsService;
import com.easy.helloServiceApi.model.Goods;
import com.easy.helloServiceApi.vo.Result;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/goods")
public class GoodsController {
@Autowired
private GoodsService orderService;
@RequestMapping("/place")
public Result placeGoods(Goods goods) {
Result result = this.orderService.placeGoods(goods);
return result;
}
}
複製代碼
package com.easy.feignConsumer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableFeignClients(basePackages = {"com.easy"})
@EnableEurekaClient
@SpringBootApplication
public class FeignConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(FeignConsumerApplication.class, args);
}
}
複製代碼
server:
port: 8100
spring:
application:
name: feign-consumer
eureka:
instance:
instance-id: order-api-8100
prefer-ip-address: true # 訪問路徑能夠顯示 IP
client:
service-url:
defaultZone: http://localhost:9000/eureka/ # 註冊中心訪問地址
複製代碼
1個服務註冊中心,3個服務提供者,1個服務消費者
地址欄輸入:http://localhost:9000/,咱們看到5個服務註冊成功而且都是運行狀態了(UP狀態),效果以下:
地址欄輸入:http://localhost:8100/goods/place?goodsId=1,返回數據結果爲:
{
code: 200,
msg: "success",
data: {
goodsId: "1",
name: "華爲",
descr: "華爲手機",
port: 8081
}
}
複製代碼
@Test
public void testFeignConsumer() {
Goods goods = new Goods();
goods.setGoodsId("1");
Result result = this.restTemplate.getForObject("http://HELLO-SERVER/goods/goodsInfo/" + goods.getGoodsId(), Result.class);
log.info("成功調用了服務,返回結果==>{}", ToStringBuilder.reflectionToString(result));
}
複製代碼
消費端每一個請求方法中都須要拼接請求服務的 URL 地址,存在硬編碼問題而且這樣並不符合面向對象編程的思想
@FeignClient(value = "hello-server")
public interface GoodsServiceClient {
@RequestMapping("/goods/goodsInfo/{goodsId}")
Result goodsInfo(@PathVariable("goodsId") String goodsId);
}
複製代碼
@Autowired
private GoodsServiceClient goodsServiceClient;
@Override
public Result placeGoods(Goods order) {
Result result = this.goodsServiceClient.goodsInfo(order.getGoodsId());
return result;
}
複製代碼
經過編寫簡單的接口和插入註解,就能夠定義好HTTP請求的參數、格式、地址等信息,實現遠程接口調用,這樣將使咱們的代碼更易擴展和利用,複合面向對象編程實現。