前面我们通过springCloud提供的Eureka模块实现了注册中心和服务生产者的相关程序。
注册中心:EurekaServer承担起注册中心的工作,所有的EurekaClient(包括:生产者Producer程序中一般命名为server、消费者Consumer程序中一般命名为client,其他控制服务、基础服务等)都将自己注册到EurekaServer注册中心上,然后依据注册中心的反馈调用或提供自己的http接口服务。
生产者:其实就是提供http接口服务的server程序,通过将自己注册到EurekaServer,可以让其他系统通过server name发现自己的ip和端口,这样当单台服务器无法提供高并发保障的时候,就可以分布式发布生产者程序,提供多个可以处理相同http接口请求的服务生产厂,降低对硬件的刚性需求降低成本。
消费者:这个就是今天要学习实践的重点,我们对外提供服务自然要提供一个统一的入口,通过统一入口调用不同的server接口实现功能。
下面我们创建一个消费者,去调用我们之前创建的生产者应用,引入rest+ribbon和Feign两种方式实现消费者通过注册中心以负载均衡方式调用生产者接口服务。
注册中心和生产者的搭建和运行参考我之前的博文,开心的话多搞几台电脑或虚拟机进行分布式部署。
我的程序源码百度网盘备份:链接:https://pan.baidu.com/s/1SGR6I3VZqrrqJO7Mrqj6Ig 密码:k2fj
怎么运行?参考前面博文: 拉倒最下面有运行指令
强烈建议下载我的百度网盘源码。。。我连target文件下的可执行jar包都放上去了,粘贴指令运行就好。
一、rest+ribbon实现消费者调用生产者服务时的负载均衡
创建springboot工程,命名testConsumerRibbon,选择web依赖
为什么没有驼峰命名?IDEA不让啊。。。欢迎留言教我
修改pom文件,添加spring-cloud-starter-eureka和spring-cloud-starter-ribbon依赖,添加dependencyManagement和repositories标签,我还是直接粘贴源文件吧(留意,这次我没有引用之前博文中提及的父pom文件,其实主要是这次用的是spring-boot-starter-parent的1.5版本,实测2.0版本会报错。强迫症一定要用2.0的当我下面的博文全都没写,要自己解决找不到class的问题哦)
<?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.consumerribbon</groupId>
<artifactId>testconsumerribbon</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>testconsumerribbon</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.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>
</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-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RC1</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>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
</project>
修改配置文件application.properties,指定你的Eureka注册中心
server.port=8764
spring.application.name=constumer-ribbon
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
serverapplication.server1.name=server-client1
serverapplication.server1.interface1.name=hi
serverapplication.server1.interface1.param1 = name
当然了,这里的eureka.client.serviceUrl.defaultZone在本地测试还可以随时改,如果你已经准备好几台电脑进行分布式测试了,运行时使用java -jar ***.jar --eureka.client.serviceUrl.defaultZone=*** 重新指定你的注册中心url吧。或者干脆把配置文件放到jar包外面,使用 java -jar ***.jar --spring.config.location=classpath:/application.properties指定jar包同目录的配置文件,classpath不行的话干脆换成绝对路径。
serverapplication.***是我自己写的属性,跟后面的service层代码对应的,可以自己DIY,不过后面使用@Value注入的时候想着自己改哈。写到配置文件里也是为了方便后面发布的时候随时改。
修改入口类**application.java
添加@EnableDiscoveryClient将消费者也注册到Eureka Server注册中心去,添加@bean注解注入一个restTemplate实例,添加@LoadBalanced注解实现负载均衡。算了,还是贴代码吧。
package com.consumerribbon.testconsumerribbon;
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;
@SpringBootApplication
@EnableDiscoveryClient
public class TestconsumerribbonApplication {
public static void main(String[] args) {
SpringApplication.run(TestconsumerribbonApplication.class, args);
}
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
}
工程中创建controller和service曾,并写段测试代码。真的,没有强迫症的话直接写在**application.java里也是可以的奥。
TestControler源码如下
package com.consumerribbon.testconsumerribbon.controller;
import com.consumerribbon.testconsumerribbon.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestControler {
@Autowired
TestService testService;
@RequestMapping(value = "/testinterfacebyribbon")
public String test(@RequestParam String name){
return testService.testService(name);
}
}
TestService源码如下
package com.consumerribbon.testconsumerribbon.service;
public interface TestService {
String testService(String name);
}
TestServiceImpl源码如下
package com.consumerribbon.testconsumerribbon.service.impl;
import com.consumerribbon.testconsumerribbon.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class TestServiceImpl implements TestService {
@Value(value = "${serverapplication.server1.name}")
private String application_name;
@Value(value = "${serverapplication.server1.interface1.name}")
private String interface_name;
@Value(value = "${serverapplication.server1.interface1.param1}")
private String param_name;
@Autowired
RestTemplate restTemplate;
public String testService(String name) {
return restTemplate.getForObject("http://"+application_name+"/"+interface_name+"?" + param_name + "=" + name,String.class);
}
}
好了,我们的测试功能做好之后编译运行试试看,一定注意先运行EurekaServer和client啊,先把注册中心和服务生产者跑起来,不然我们的消费者上哪注册?上哪调服务?是吧。
所以上面强烈建议先下载我百度网盘里的程序备份资料,target里有我打包好的jar文件。
启动EurekaServer:cmd进入EurekaServer的target目录(包含jar文件的目录)java -jar ***.jar --server.port=8761
为什么制定了端口8761,我们刚写好的消费者程序的配置文件里指定了EurekaServer的ip和端口,如果你自己DIY过,那这个地方server.port一定跟自己DIY的端口匹配好。其实还要留意消费者配置文件中EurekaServer的ip。。。我现在是本地测试的,所以写的是localhost。
启动第一个生产者:同样找到jar包目录,输入指令java -jar ***.jar --server.port=8762 --spring.application.name=server-client1 --eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
实在嫌指令长的话写个批处理脚本或者干脆到我百度盘里备份的项目源码中找到配置文件复制到外面,去识别外置的配置文件吧。。。
启动第二个生产者:java -jar ***.jar --server.port=8763 --spring.application.name=server-client1 --eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
我只改了端口号。两个spring.application.name要保持一致,这样注册中心才会把两个生产者放到一起,消费的时候才有负载均衡可言,当然消费者配置文件中指定serverapplication.server1.name的时候也要保持一致哦。defaultZone我就不说了,写错了注册不上哈
注意指令最好自己手写,从我博文中粘贴下来的话可能会带上换行符或其他不可预计的符号,毕竟太长了。
现在EurekaServer注册中心跑起来了,两个生产者也跑起来了,我们看看是不是注册成功了。浏览器输入:
看到application名为server-client1的服务,Zones是2,Status中UP为2,后面跟着的就是两个服务的具体地址和端口了,分别简记为8762和8763服务。
好了,现在测试环境准备好了,我们准备启动刚弄好的消费者。运行前一定检查一下配置文件,就是下面这几行
serverapplication.server1.name=server-client1
serverapplication.server1.interface1.name=hi
serverapplication.server1.interface1.param1 = name
server-client相信你已经在上面的http://localhost:8761/浏览器页面里看到了,hi是我的生产者程序(8762、8763)开放的接口名,name也是入参,不写的话默认是testname。自己写生产者程序的自己DIY哈。
哎,编译运行吧。
浏览器输入http://localhost:8764/testinterfacebyribbon?name=qftest
刷新反复跑,可以看到负载均衡效果。8762和8763在轮询着提供服务。
二、feign实现消费者调用生产者服务时的负载均衡
Feign其实集成了Ribbon,会比Ribbon+rest的方式更简洁,只需要定义一个接口就好。当然了约定大于配置的思路啊。。。后面会有坑,部署的时候就知道了。
创建一个新的springboot工程,选择web依赖,命名为consumerfeign。留意一下,我这次又在testSpringCloud根目录下创建工程了,主要是使用原来的父pom.xml文件。
你没有其他两个工程不要紧,这个pom.xml文件可得有,源码翻看我前面的博文吧。
注意我上面搞Ribbon+rest的时候pom.xml文件中parent标签,springboot1.5版本。feign我又换回2.0版本了。
修改consumerfeign的pom.xml文件
parent标签还用我们本地的父pom.xml,额外引入spring-cloud-starter-netflix-eureka-client和spring-cloud-starter-openfeign和spring-boot-starter-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.testconsumerfeign</groupId>
<artifactId>consumerfeign</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>consumerfeign</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>com.springcloud</groupId>
<artifactId>testspringcloud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<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.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
修改application.properties文件
server.port=8765
spring.application.name=constumer-feign
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
哎,我没在配置文件里写生产者的信息(比如application名,接口名,接口参数等信息)不等于就不需要写了,而是没法写在配置文件里了,继续看吧。
修改入口类**application.java 只是添加注解,看下面源码吧
@SpringBootApplication
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class ConsumerfeignApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerfeignApplication.class, args);
}
}
创建controller和service,创建对应的java class文件
留意到了么?我没有创建impl和service的实现类。
controller类源码
@RestController
public class TestController {
@Autowired
TestService testService;
@GetMapping(value = "/testinterfacebyfeign")
public String sayHi(@RequestParam String name) {
return testService.testFunction( name );
}
}
@GetMapping注解指定了消费者提供http接口供浏览器调用
service接口源码
@FeignClient(value = "server-client1")
public interface TestService {
@RequestMapping(value = "/hi",method = RequestMethod.GET)
String testFunction(@RequestParam(value = "name") String name);
}
@FeignClient注解指定了生产者的application name,@RequestMapping注解指定了调用生产者的哪个接口,@RequestParam注解指定了参数。
这样我们使用Feign实现的消费者就完成了,启动Eureka Server注册中心和两个生产者接口服务,编译运行我们的Feign程序。
浏览器输入 http://localhost:8765/testinterfacebyfeign?name=qftest 看看效果吧
最后一个图是注册中心的页面,我们看到feign也注册上来了。如果开心的话可以mvn打包所有的程序尝试一下分布式实施。
当然了,Feign方式的负载均衡消费看似代码量进一步变少,框架为我们做了更多的事情,我们只需要定义一个service interface就可以放心的调用生产者提供的接口服务,可是。。。interface里的注解留意了么??
java的interface类只能声明final类型的变量,而final类型的变量只能以常量赋值。在Ribbon+rest方式实现负载均衡的时候我特意把生产者集群的信息(application名、interface名和参数)都写到了配置文件里,目的就是在发布的时候根据生产者集群的部署情况修改这些信息。现在呢?我要在消费者开发阶段就知道生产者集群的这些信息,写死到代码里。如果修改,我需要重新改写代码,重新打包发布。。。
反正我现在还没想到什么办法可以把配置文件中的属性注入到interface类的注解的属性中。欢迎留言解惑~