文章目录
一、前置说明及其框架搭建
1、思路详解
由于设备限制,使用五个端口模拟五个服务(两个注册中心、两个服务提供者、一个服务消费者)其中两个注册中心用来演示Eureka集群的高可用,两个服务提供者用来演示Ribbon的负载均衡,一个服务消费者用来进行远程调用)
#当然官网中是以三个注册中心来演示Eureka集群高可用的,我用两个当然也可以。
如果你了解过zookeeper、consul或者nacos作为注册中心,这个对于你来说就比较简单了。
2、框架搭建
通过聚合工程来创建一个父工程和五个子模块
二、代码编写
1、编写五个pom文件
1.1、父pom文件
<?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.hao</groupId>
<artifactId>cloud-eureka-demo</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-eureka-server</module>
<module>cloud-eureka-server02</module>
<module>service-provider</module>
<module>service-consumer</module>
<module>service-provider02</module>
</modules>
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.4.3</version>
</parent>
<properties>
<spring-cloud.version>2020.0.2</spring-cloud.version>
</properties>
<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>
</project>
父工程主要就一个cloud依赖和五个子模块以及继承的父依赖。
1.2、子模块pom
1、cloud-eureka-server、cloud-eureka-server02
<?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">
<parent>
<artifactId>cloud-eureka-demo</artifactId>
<groupId>com.hao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<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-security</artifactId>
</dependency>
</dependencies>
</project>
这两个eureka注册模块pom不被我们熟知的依赖是spring-cloud-starter-netflix-eureka-server,这个依赖继承了eureka相关的所有包(包括Ribbon);导入spring-boot-starter-security的原因是对我们的注册中心客户端需要登录认证
2、service-consumer
<?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">
<parent>
<artifactId>cloud-eureka-demo</artifactId>
<groupId>com.hao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
这个spring-cloud-starter-netflix-eureka-client和上面的spring-cloud-starter-netflix-eureka-server是两种服务,从字面意思也应该都明白。
3、service-provider和service-provider02
<?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">
<parent>
<artifactId>cloud-eureka-demo</artifactId>
<groupId>com.hao</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-provider</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
2、编写启动类
1、eureka-server子模块的启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class);
}
}
@EnableEurekaServer:该注解是自动识别为注册服务中心
2、eureka-server02子模块的启动类
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication02 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication02.class);
}
}
3、service-consumer子模块启动类
@SpringBootApplication
@EnableEurekaClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class);
}
}
@EnableEurekaClient注解在最新版已经可以省略
4、service-provider子模块启动类
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class);
}
}
5、service-provider02子模块启动类
@SpringBootApplication
@EnableEurekaClient
public class Provider02Application {
public static void main(String[] args) {
SpringApplication.run(Provider02Application.class);
}
}
3、application.yml文件编写
1、eureka-server子模块yml
server:
port: 8080
spring:
application:
name: cloud-eureka-server
security:
user:
name: root
password: root
eureka:
instance:
hostname: eureka01
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://root:root@127.0.0.1:${server.port}/eureka/ #注册中心对外暴露的注册地址
2、eureka-server02子模块yml
server:
port: 8081
spring:
application:
name: cloud-eureka-server02
security:
user:
name: root
password: root
eureka:
instance:
hostname: eureka02
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://root:root@127.0.0.1:8080/eureka/
3、eureka-consumer子模块yml
server:
port: 9090
spring:
application:
name: service-consumer
eureka:
instance:
hostname: consumer
prefer-ip-address: true
instance-id: http://${spring.cloud.client.ip-address}:${server.port}
client:
# fetch-registry: false
# register-with-eureka: false
service-url:
defaultZone: http://root:root@127.0.0.1:8080/eureka/,http://root:root@127.0.0.1:8081/eureka/
4、service-provider子模块yml
server:
port: 7070
spring:
application:
name: service-provider
eureka:
instance:
hostname: provider
prefer-ip-address: true
instance-id: http://${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://root:root@127.0.0.1:8080/eureka/,http://root:root@127.0.0.1:8081/eureka/
5、service-provider02子模块pom
server:
port: 7071
spring:
application:
name: service-provider #集群下的名字是相同的
eureka:
instance:
hostname: provider02
prefer-ip-address: true #是否使用ip形式显示
instance-id: http://${spring.cloud.client.ip-address}:${server.port}
client:
service-url:
defaultZone: http://root:root@127.0.0.1:8080/eureka/,http://root:root@127.0.0.1:8081/eureka/
4、config文件配置
cloud-eureka-server 、cloud-eureka-server02
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//关闭某个path下的csrf,否者provider无法注册进去
super.configure(http);
http.csrf().ignoringAntMatchers("/eureka/**");
}
}
service-consumer
@Configuration
public class ConsumerConfig {
@Bean
// @LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}
5、编写controller层
service-consumer
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping(value ="/order/{id}")
public Order getOrderById(@PathVariable("id") Integer id) {
return orderService.selectOrderById(id);
}
}
service-provider 、service-provider02
@RestController
@RequestMapping(value = "/product")
public class ProductController {
@Resource
private ProductService productService;
@GetMapping(value = "/list")
public List<Product> getProductList() {
return productService.selectProductList();
}
}
6、entity层
service-consumer
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Order {
private Integer id;
private String orderNo;
private String orderAddress;
private Double totalPrice;
private List<Product> productList;
}
service-provider 、service-provider02
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Integer id;
private String productName;
private Integer productNum;
private Double productPrice;
}
7、service层
service-consumer
public interface OrderService {
Order selectOrderById(Integer id);
}
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public Order selectOrderById(Integer id) {
return new Order(id, "order-1", "china", 1024D ,getProductListByLoadBalancerClient());
}
public List<Product> getProductListByDiscoveryClient() {
StringBuffer sb = null;
List<String> services = discoveryClient.getServices();
if (CollectionUtils.isEmpty(services)) {
return null;
}
//获取name为service-provider的服务实例
List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
if (CollectionUtils.isEmpty(instances)) {
return null;
}
ServiceInstance serviceInstance = instances.get(0);
sb = new StringBuffer();
//拼接地址
sb.append("http://"+serviceInstance.getHost()+":"+serviceInstance.getPort()+"/product/list");
ResponseEntity<List<Product>> exchange = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, new ParameterizedTypeReference<List<Product>>() {
});
return exchange.getBody();
}
public List<Product> getProductListByLoadBalancerClient() {
StringBuffer sb = null;
ServiceInstance instance = loadBalancerClient.choose("service-provider");
if (instance == null) {
return null;
}
sb = new StringBuffer();
sb.append("http://"+instance.getHost()+":"+instance.getPort()+"/product/list");
System.out.println(instance.getHost()+":"+instance.getPort());
ResponseEntity<List<Product>> exchange = restTemplate.exchange(sb.toString(), HttpMethod.GET, null, new ParameterizedTypeReference<List<Product>>() {
});
return exchange.getBody();
}
public List<Product> getProductListByAnnotation() {
ResponseEntity<List<Product>> exchange = restTemplate.exchange("http://service-provider/product/list", HttpMethod.GET, null, new ParameterizedTypeReference<List<Product>>() {
});
System.out.println(exchange);
return exchange.getBody();
}
}
这是演示的三种方式实现
service-provider 、service-provider02
public interface ProductService {
List<Product> selectProductList();
}
@Service
public class ProductServiceImpl implements ProductService {
@Override
public List<Product> selectProductList() {
return Arrays.asList(
new Product(1, "HuaWei nova3", 100, 2999.0),
new Product(2, "xiaomi", 99, 1999.0),
new Product(3, "vivo", 102, 2999.0)
);
}
}
#使用Arrays.asList模拟返回数据
三、测试
访问http://localhost:8080/(账号和密码是在配置文件中配置的)
Eureka的高可用集群也搭建成功了
访问http://localhost:9090/order/1
服务间调用也成功!
下面多次刷新该页面,查看控制台打印结果
负载均衡!