SpringCloud与阿里巴巴的dubbo都是实现微服务架构的基础框架,由与我在学习的时候是提供SpringBoot来尝试构建微服务,因此我使用了SpringCloud。
SpringCloud的子项目非常多,在最开始学习微服务的第一步只需要学会微服务的服务注册,服务发现,服务调用等三块,使用Spring Cloud Netflix足以,至于zookeeper后面自然会介绍。
备注:Spring Cloud Netflix是Spring Cloud的子项目之一,主要内容是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路有(Zuul),客户端负载均衡(Ribbon)等。
服务注册中心即对于系统中的服务有一个统一的管理。所有的服务都需要在注册中心注册,这样服务消费者调用服务的时候就用注册中心查询,查询到具体服务提供者的信息(ip地址,端口等)从而发起RPC调用。
使用Spring Cloud Netflix完成服务注册中心
pom.xml
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.3.5.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eureka-serverartifactId>
dependency>
dependencies>
Main.java
@EnableEurekaServer
@SpringBootApplication
public class Main {
public static void main(String[] args) {
new SpringApplicationBuilder(Main.class).web(true).run(args);
}
}
同时需要写一个SpringBoot配置文件:
server.port=8080
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.serviceUrl.defaultZone=http://localhost:${server.port}/eureka/
对于上述操作,大部分都是SpringBoot相关的知识。但有几点需要强调一下:
- 一定要在主类中添加@EnableEurekaServer注解来开启服务注册功能
- 在配置文件中eureka.client.serviceUrl.defaultZone的值一定是项目url/eureka。否则服务无法被注册
- 配置文件中的其余两个配置是让服务器不要把自身当成服务提供者。遗忘服务提供者也是通过eureka.client.serviceUrl.defaultZone来注册服务的。
通过Maven打包为jar包后,构建Docker容器:
Dockerfile:
FROM registry.cn-hangzhou.aliyuncs.com/alirobot/oraclejdk8-nscd #基础源
add springCloudOne.jar app.jar #传递jar包
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
#运行命令
Docker构建完成后,运行Docker容器并映射本机8080端口到容器。
关于这方面知识参考:http://blog.csdn.net/canot/article/details/52888597
此时访问127.0.0.1:8080。观察到如下界面:
不难看出,此时没有任何服务去注册。
创建好服务注册中心后,下一步便创建服务提供者。即然是第一个服务例子,为了方便大家理解,我们创建一个很简单的服务:接受用户传递的姓名,然后返回hello,xxx。
public String sayHello(String name) {
String result = "Hello ,"+name;
return result;
}
创建SpringBoot项目,添加服务提供者依赖:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.3.5.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
dependencies>
<dependencyManagement>
Main.java:
@EnableDiscoveryClient
@SpringBootApplication
@ComponentScan("xxx")
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
通过@EnableDiscoveryClient来表面服务提供者。
SayHello.java(提供服务的核心类):
@RestController
public class SayHello {
private final Logger logger = Logger.getLogger(getClass());
@Autowired
private DiscoveryClient client;
@RequestMapping(value = "/sayHello" ,method = RequestMethod.GET)
public String sayHello(@RequestParam String name) {
ServiceInstance instance = client.getLocalServiceInstance();
String r = "hello,"+name;
logger.info("/add, host:" + instance.getHost() + ", service_id:" + instance.getServiceId() + ", result:" + r);
return r;
}
}
不难看出,微服务的服务提供者提供了RestAPI接口。其中我们使用了DiscoveryClient类(服务调用者信息)打印日志。
最后依然是配置文件:application.properties
spring.application.name=say-hello
server.port=8081
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka
上述的配置文件中,有几点要强调一哈:
- spring.application.name在这里变成了标志服务的名称,在众多服务中它的名称必须唯一(当然同一服务的集群自然是相同的)
- eureka.client.serviceUrl.defaultZone标志服务注册中心的地址
如果要构建该服务的集群,其余的全部不变,修改配置文件的端口即可。假设我们有创建了一个上述服务,端口为8082。
与上述一致,构建Docker 指定端口,运行。
此时访问localhost:8080则会看到我们注册的服务。
服务提供者创建成功后,自然需要调用才行,现在创建服务消费者来调用say-hello这个服务:
前面提到,Spring Cloud Netflix子项目包含了Ribbon。它就是来完成服务消费者的。
Ribbon
Ribbon是一个基于HTTP和TCP客户端的负载均衡器.
Ribbon可以在通过客户端中配置的ribbonServerList服务端列表去轮询访问以达到均衡负载的作用。
当Ribbon与Eureka联合使用时,ribbonServerList会被DiscoveryEnabledNIWSServerList重写,扩展成从Eureka注册中心中获取服务端列表。同时它也会用NIWSDiscoveryPing来取代IPing,它将职责委托给Eureka来确定服务端是否已经启动。
创建项目实战如何使用Ribbon来调用服务,并实现客户端对于say-hello的负载均衡:
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>1.3.5.RELEASEversion>
<relativePath/>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-ribbonartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
dependencies>
Main.java:
@SpringBootApplication
@EnableDiscoveryClient
@ComponentScan("xxx")
public class Main {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
创建RestTemplate实例,并通过@LoadBalanced注解开启均衡负载能力。
创建一个Service,这个Service就是最终调用服务提供者的:
@Service
public class RibbonService {
@Autowired
RestTemplate restTemplate;
public String sayHelloService() {
return restTemplate.getForEntity("http://SAY-HELLO/sayHello?name=jackman", String.class).getBody();
}
}
创建一个Controller。用户最终调用该Controller:
@RestController
public class ConsumerRibbonController {
@Autowired
public RibbonService service;
@RequestMapping(value = "/sayInRibbon", method = RequestMethod.GET)
public String add() {
return service.addService();
}
}
同理配置application.properties:
spring.application.name=ribbon-consumer
server.port=9090
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
与上述一致,构建该Docker容器,并启动由于我们指定了端口9090,此时在localhost:8080中可以看的该服务调用者:
此时我们在浏览器上访问:
http://127.0.0.1:9090/sayInRibbon
访问可以看的:”hello,jackman”.而访问两次,则会看的在两个say-hello服务提供者容器中各打印了一次。
Feign
Feign其实是包含了Ribbon,使用与上述稍有不同
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-feignartifactId>
dependency>
Main.java
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class Main {
public static void main(String[] args) {
SpringApplication.run(FeignApplication.class, args);
}
}
Feign有个好处就是支持注解:
Service.java
@FeignClient(value = "say-hello")
public interface FeignConsumer {
@RequestMapping(method = RequestMethod.GET, value = "/sayHello")
String say(@RequestParam(value = "name") String name);
}
创建对外提供Controller:
@RestController
public class FeignConsumerController {
@Autowired
public FeignConsumer feignConsumer;
@RequestMapping(value = "/addInFeign", method = RequestMethod.GET)
public Integer add() {
return feignConsumer.say("jack");
}
}
与上述一样,构建Docker,观察访问结果也与使用Ribbon时一致,说明Feign默认支持负载均衡调用。
至于两者的区别,后面会详细介绍的。