本示例主要介绍 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请求的参数、格式、地址等信息,实现远程接口调用,这样将使咱们的代码更易扩展和利用,复合面向对象编程实现。