注意:请使用官方建议的springboot和springcloud对应的版本
在之间的博客中已经写到如何建立高可用的注册中心,地址见高可用注册中心
现在我们来创建服务治理体系下的一个服务(即服务的提供者)
1.pom.xml
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>2.spring-cloud-service-provide</groupId> <artifactId>service-provide</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud Maven Webapp</name> <url>http://maven.apache.org</url> <!--springboot采用1.5.x 对应springcloud版本为 Dalston--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> </parent> <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>2.spring-cloud-service-provide</groupId> <artifactId>service-provide</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud Maven Webapp</name> <url>http://maven.apache.org</url> <!--springboot采用1.5.x 对应springcloud版本为 Dalston--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath/> </parent> <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> <!-- 这样变成可执行的jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.application.properties
#指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问 spring.application.name=service-provide #默认为8080 #server.port=8081 #注册中心地址 eureka.client.serviceUrl.defaultZone=http://testhost:8000/eureka/,http://testhost2:8001/eureka/
3.springboot启动类
package com.niugang; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.ComponentScan; /** * 服务提供者 * @author niugang * */ @SpringBootApplication @EnableDiscoveryClient @ComponentScan public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
4.演示controller
package com.niugang.controller; import java.util.List; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController public class ComputeController { private final Logger logger = Logger.getLogger(getClass()); @Autowired private DiscoveryClient client; @RequestMapping(value = "/hello", method = RequestMethod.GET) public String add() { //方法已过时,不建议使用 // ServiceInstance instance = client.getLocalServiceInstance(); List<String> services = client.getServices(); if (!services.isEmpty() && services.size() > 0) { String serviceId = services.get(0); List<ServiceInstance> instances = client.getInstances(serviceId); if (!instances.isEmpty() && instances.size() > 0) { ServiceInstance serviceInstance = instances.get(0); logger.info( "/hello, host:" + serviceInstance.getHost() + ", service_id:" + serviceInstance.getServiceId()); } } return "hello spring cloud"; } }
java -jar service-provide-0.0.1-SNAPSHOT.jar --server.port=8081打成jar文件,服务提供者,通过不同端口启动,做到负载均衡,(启动之前要启动,注册中心)
java -jar service-provide-0.0.1-SNAPSHOT.jar --server.port=8082
注册中心分别启动,为了高可用
java -jar spring-cloud-0.0.1-SNAPSHOT.jar --spring.profiles.active=testhost
java -jar spring-cloud-0.0.1-SNAPSHOT.jar --spring.profiles.active=testhost2
上面都启动完成之后,通过http://testhost:8000访问
,通过http://testhost2:8001访问访问的结果和上面截图一样。
现在注册中心和服务提供者都已经有了,接下来需要来构建服务的消费者,它主要来完成发现服务和消费服务。服务的发现由Eureka客户端完成,而服务的消费者任务由Ribbon完成。Ribbon是一个基于HTTP和TCP的客户端负载均衡,它可以在通过客户端配置ribbonServerList的服务列表去轮训访问以达到负载均衡的作用。
第一种方式:构建服务的发现和服务的消费
1.pom.xl
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>4.spring-cloud-ribbon-consumer</groupId> <artifactId>ribbon-consumer</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <name>spring-cloud Maven Webapp</name> <url>http://maven.apache.org</url> <!--springboot采用1.5.x 对应springcloud版本为 Dalston --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.2.RELEASE</version> <relativePath /> </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>Dalston.RELEASE</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--引入ribbon 负载均衡 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-ribbon</artifactId> <!-- 搞了好久踩的坑,就是在复制上面代码的时候 顺手把scope test复制了,其意思为紧参与junit类似的测试工作,所以一直报找不到服务或者或者拒绝连接 --> <!-- <scope>test</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> <!-- 这样变成可执行的jar --> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
2.application.properties
#指定微服务的名称后续在调用的时候只需要使用该名称就可以进行服务的访问 spring.application.name=ribbon-consumer server.port=9001 #负载平衡RestTemplate可以配置为重试失败的请求。默认情况下,该逻辑被禁用 spring.cloud.loadbalancer.retry.enabled=true #注册中心地址 eureka.client.serviceUrl.defaultZone=http://testhost:8000/eureka/
3.启动类
package com.niugang; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.web.client.RestTemplate; /** * 负责服务的发现与消费 * * @author niugang * */ @SpringBootApplication /* * 默认情况下,DiscoveryClient的实现将使用远程发现服务器自动注册本地Spring * Boot服务器。可以通过在@EnableDiscoveryClient中设置autoRegister=false来禁用此功能。 */ /* * 默认情况下,ServiceRegistry实现将自动注册正在运行的服务。要禁用该行为,有两种方法。您可以设置@EnableDiscoveryClient( * autoRegister=false)永久禁用自动注册。您还可以设置spring.cloud.service-registry.auto- * registration.enabled=false以通过配置禁用该行为。 */ @EnableDiscoveryClient @ComponentScan public class Application { @Bean RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
4.controller演示
package com.niugang.controller; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.loadbalancer.LoadBalancerClient; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ComputeController { private final Logger logger = org.slf4j.LoggerFactory.getLogger(ComputeController.class); @Autowired LoadBalancerClient loadBalancerClient; @Autowired RestTemplate restTemplate; @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) public String ribbon() { logger.info("start invoke service"); //从负载平衡器中为指定的服务选择ServiceInstance ServiceInstance serviceInstance = loadBalancerClient.choose("service-provide"); String url = "http://" + serviceInstance.getHost() + ":" + serviceInstance.getPort() + "/hello"; logger.info("access service url:{}",url); return restTemplate.getForEntity(url, String.class).getBody(); } }
启动服务消费者,访问注册中心:
通过浏览器访问,消费者,http://localhost:9001/ribbon-consumer
看控制台打印的日志:已经做负载均衡了
第二种方式:
pom.xml文件不变和application.properties修改访问端口为9002
启动类:
package com.niugang; 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.context.annotation.ComponentScan; import org.springframework.web.client.RestTemplate; /** * 负责服务的发现与消费 * * @author niugang * */ @SpringBootApplication @EnableDiscoveryClient @ComponentScan public class Application { /* * 要创建负载平衡RestTemplate,创建RestTemplate @Bean并使用@LoadBalanced限定符。 * * 警告: 通过自动配置不再创建RestTemplate bean。它必须由单个应用程序创建。 * */ @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
演示controller
package com.niugang.controller; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; @RestController public class ComputeController { private final Logger logger = org.slf4j.LoggerFactory.getLogger(ComputeController.class); @Autowired private RestTemplate restTemplate; @RequestMapping(value = "/ribbon-consumer", method = RequestMethod.GET) public String ribbon() { logger.info("start invoke service"); //URI需要使用虚拟主机名(即服务名称,而不是主机名) return restTemplate.getForEntity("http://service-provide/hello", String.class).getBody(); } }
配置多个RestTemplate(以下来自官方文档)通过查看service-provide控制台信息,可以看到ribbon通过轮训的方式访问了,不同端口的服务。
如果你想要一个没有负载平衡的RestTemplate
,创建一个RestTemplate
bean并注入它。要创建@Bean
时,使用@LoadBalanced
限定符来访问负载平衡RestTemplate
。
重要 |
请注意下面示例中的普通RestTemplate 声明的@Primary 注释,以消除不合格的@Autowired 注入。 |
@Configuration public class MyConfiguration { @LoadBalanced @Bean RestTemplate loadBalanced() { return new RestTemplate(); } @Primary @Bean RestTemplate restTemplate() { return new RestTemplate(); } } public class MyClass { @Autowired private RestTemplate restTemplate; @Autowired @LoadBalanced private RestTemplate loadBalanced; public String doOtherStuff() { return loadBalanced.getForObject("http://stores/stores", String.class); } public String doStuff() { return restTemplate.getForObject("http://example.com", String.class); } }
提示 |
如果您看到错误java.lang.IllegalArgumentException: Can not set org.springframework.web.client.RestTemplate field com.my.app.Foo.restTemplate to com.sun.proxy.$Proxy89 ,请尝试注入RestOperations 或设置spring.aop.proxyTargetClass=true
|
微信公众号:
JAVA程序猿成长之路
分享资源,记录程序猿成长点滴。专注于Java,Spring,SpringBoot,SpringCloud,分布式,微服务。