应用架构的变迁
1、单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
2、垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
3、分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
4、流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
在分布式系统中,国内常用zookeeper+dubbo组合,而Spring Boot推荐使用全栈的Spring,Spring Boot+Spring Cloud
1、ZooKeeper
ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
2、Dubbo
Dubbo是Alibaba开源的分布式服务框架,它最大的特点是按照分层的方式来架构,使用这种方式可以使各个层之间解耦合(或者最大限度地松耦合)。从服务模型的角度来看,Dubbo采用的是一种非常简单的模型,要么是提供方提供服务,要么是消费方消费服务,所以基于这一点可以抽象出服务提供方(Provider)和服务消费方(Consumer)两个角色。
1、安装Zookeeper
①查看本地是否有Zookeeper的镜像
docker images
②加速下载Zookeeper
docker pull registry.docker-cn.com/library/zookeeper
如果加速下载不成功的话,可以不使用加速:
docker pull zookeeper
③运行Zookeeper:
docker run --name zk01 -p 2181:2181 --restart always -d 2a7f6fc5c8a1
2、创建服务提供者和服务消费者
①创建一个空项目dubbo_zk:此处只是为了展示方便,将服务提供者和服务消费者放在同一个空项目下
②在空项目下创建一个SpringBoot模块作为服务提供者provider_ticket
③在空项目下再创建一个SpringBoot模块作为服务消费者consumer_user
④在两个模块中都引入dubbo和zkclient的依赖
<dependency>
<groupId>com.alibaba.bootgroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>0.1.2.RELEASEversion>
dependency>
<dependency>
<groupId>com.github.sgroschupfgroupId>
<artifactId>zkclientartifactId>
<version>0.1version>
dependency>
⑤在服务提供者中编写服务接口和服务实现
接口:
package com.bdm.ticket.service;
public interface TicketService {
public String getTicket();
}
实现:
package com.bdm.ticket.service.impl;
import com.bdm.ticket.service.TicketService;
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《霸王别姬》";
}
}
⑥在Zookeeper中通过dubbo暴露服务,只需要在主配置文件文件中进行如下配置:
配置:
#bubbo应用名
dubbo.application.name=provider_ticket
#zookeeper服务器地址和端口
dubbo.registry.address=zookeeper://192.168.2.107:2181
#暴露的服务所在的包,该包下的接口若有使用dubbo的@Service注解的接口实现则会暴露为dubbo服务
dubbo.scan.base-packages=com.bdm.ticket.service
实现类加dubbo的@Service注解,并将该类纳入Spring容器:
package com.bdm.ticket.service.impl;
import com.alibaba.dubbo.config.annotation.Service;
import com.bdm.ticket.service.TicketService;
import org.springframework.stereotype.Component;
@Component //将实现纳入IOC
@Service //注意该注解是dubbo提供的注解,在com.alibaba.dubbo.config.annotation.Service包下
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "《霸王别姬》";
}
}
⑦将服务提供者的接口定义(不需要拷贝接口实现)复制一份到服务消费者项目consumer_user中,注意包名和接口名等要完全一致,这是因为dubbo识别服务靠的是包名,而不像SpringCloud靠的是应用名和Http接口名
⑧在服务消费者中配置dubbo:由于不需要暴露服务,只需要配置zookeeper地址和端口即可,也就是说只要配置了dubbo.registry.address就可以实现服务订阅,因此服务提供者肯定也实现了服务订阅
#bubbo应用名
dubbo.application.name=consumer_user
#zookeeper服务器地址和端口
dubbo.registry.address=zookeeper://192.168.2.107:2181
⑨在服务消费者中使用dubbo订阅远程服务:使用dubbo提供的@Reference注解,注意包名
package com.bdm.user.service;
import com.alibaba.dubbo.config.annotation.Reference;
import com.bdm.ticket.service.TicketService;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Reference //使用dubbo提供的该注解订阅服务
TicketService ticketService;
public void getTicket() {
String ticket = ticketService.getTicket();
System.out.println(ticket);
}
}
⑩测试:先启动服务提供者,再在服务消费者中调用getTikcet()
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerUserApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.getTicket();
}
}
在服务消费者的控制台中可以看到打印结果
1、服务提供者中配置了zookeeper的服务地址和端口以及暴露服务的包,这样在服务提供者启动时会将需要暴露的服务注册至zookeeper,这些暴露的服务必须要有使用dubbo的@Service注解的实现
2、服务消费者中也配置了zookeeper的服务地址和端口,这样服务消费者启动后就可以从zookeeper中获取到服务的远程实现,为了区别是使用本地实现(消费者端也可以提供本地实现)还是使用远程实现,在使用远程实现时需要使用dubbo的@Reference注解标明
3、服务消费者在消费服务的时候是根据包名或者说接口的全类名来区别服务的实现的,如果有多个该服务的实现的话,就会负载均衡算法进行调度,或者根据分组明确指定调用哪个服务实现
Spring Cloud是一个分布式的整体解决方案。Spring Cloud 为开发者提供了快速构建分布式系统的工具(配置管理、服务发现、熔断、路由、微代理、控制总线、一次性token、全局琐、leader选举、分布式session、集群状态),使用Spring Cloud的开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。
SpringCloud分布式开发五大常用组件:
服务发现——Netflix Eureka
客服端负载均衡——Netflix Ribbon
断路器——Netflix Hystrix
服务网关(过滤请求)——Netflix Zuul
分布式配置——Spring Cloud Config
1、创建一个空项目:此处为了展示方便将所有项目以模块的形式创建在同一个空项目中
2、搭建Eureka注册中心服务,以使其他应用能够注册在该Eureka服务中:在空项目中创建一个Eureka服务模块
①引入Eureka的eureka-server依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
②启用eureka功能:主启动类标注@EnableEurekaServer注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
③配置eureka
server:
port: 8761
eureka:
instance:
hostname: eureka-server # eureka实例的主机名
client:
register-with-eureka: false #不把自己注册到eureka上
fetch-registry: false #不从eureka上来获取服务的注册信息
service-url:
defaultZone: http://localhost:8761/eureka/
service-url是有默认值的:因此如果我们设置的端口不是8761的话,就必须显示设置service-url的值
this.serviceUrl.put("defaultZone", "http://localhost:8761/eureka/");
④启动测试:访问http://localhost:8761/
3、搭建服务提供者,并将服务提供者注册至注册中心:在空项目中新建一个服务提供者模块
①引入eureka-client依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
②编写service
@Service
public class TicketService {
public String getTicket(){
return "《春夏秋冬又一春》";
}
}
③编写controller
@RestController
public class TicketController {
@Autowired
TicketService ticketService;
@GetMapping("/ticket")
public String getTicket(){
return ticketService.getTicket();
}
}
TIP
:SpringCloud与Dubbo不同:SpringCloud是以http的方式访问远程接口,而Dubbo则是以RPC的方式访问远程服务,因此在SpringCloud中并不需要定义公共的interface供消费者使用,当然在使用服务器端负载均衡时是需要的(比如使用feign)。
④配置:如果要配置服务集群,则这些服务的spring应用名要相同
server:
port: 8001
spring:
application:
name: provider-ticket #spring应用的名字,也是注册在eureka中的应用标识,同一个应用多次注册至eureka时此名称要相同
eureka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的ip地址
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #eureka服务的地址
注意
:这里并没有在服务提供者应用的主启动类上标注有关eureka的注解,这是因为我们假定服务提供者不需要从注册中心发现服务,如果服务提供者需要从注册中心发现服务的话我们就必须给主启动类加@EnableDiscoveryClient注解
⑤启动测试:保证eureka服务已经启动,在启动服务提供者应用
在eureka中可以看到服务已经注册:
访问 http://localhost:8001/ticket:
⑥为测试负载均衡可以将服务提供者应用部署两个,只需要将同一个应用打两次包即可(由于我们是在同一台机器上测试,需要保证这两个应用的端口不同)
eureka中可以看到该服务有两台机器:
4、搭建服务消费者应用:在空项目中创建服务消费者模块
①引入eureka-client依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
②主启动类加注解@EnableDiscoveryClient启用服务发现,并将RestTemplate 加入容器,同时使用Ribbon的负载均衡机制
@EnableDiscoveryClient
@SpringBootApplication
public class ConsumerUserApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerUserApplication.class, args);
}
@Bean
@LoadBalanced //使用Ribbon的负载均衡机制
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
注意
:如果不使用@LoadBalanced 则会报错java.net.UnknownHostException: PROVIDER-TICKET,这是因为使用了@LoadBalanced 才会使用Ribbon,使用了Ribbon才能识别出在Eureka中注册的服务应用名(及yml中配置的Spring应用名),然后才能找到对应的服务进行远程调用,它们之间是环环相扣的。
③配置注册中心地址等,指明在哪个注册中心发现服务
server:
port: 8200
spring:
application:
name: consumer-user #spring应用的名字,也是注册在eureka中的应用标识,同一个应用多次注册至eureka时此名称要相同
eureka:
instance:
prefer-ip-address: true # 注册服务的时候使用服务的ip地址
client:
service-url:
defaultZone: http://localhost:8761/eureka/ #eureka服务的地址
④编写消费者端controller:使用RestTemplate调用远程服务
@RestController
public class UserController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/buyTicket")
public String buyTicket(String name) {
String ticket = restTemplate.getForObject("http://PROVIDER-TICKET/ticket", String.class);
return name + "购买了:" + ticket;
}
}
⑤启动测试:保证Eureka和两个服务提供者应用都已启动,启动服务消费者后
eureka中看到:
访问:http://localhost:8200/buyTicket?name=小七
这种使用RestTemplate(其实是Ribbon)调用远程接口的方式其实存在着负载均衡机制,这是一种客户端负载均衡,就是说其负载均衡是在客户端完成的,客户端或者说消费者端根据应用名从Eureka注册中心获取到对应的服务应用列表后根据负载均衡算法进行负载均衡调用,默认采用轮询的方式调用远程HTTP接口。Feign也是采用的客户端负载均衡机制。