集中式架构 > 垂直拆分 > 分布式架构 > SOA > 微服务
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分:
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键
服务治理要做什么?
1.服务注册中心,实现服务自动注册和发现,无需人为记录服务地址
2.服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系
3.动态监控服务状态监控报告,人为控制服务状态
微服务的架构特征:
微服务的上述特性其实是在给分布式架构制定一个标准,进一步降低服务之间的耦合度,提供服务的独立性和灵活性。做到高内聚,低耦合。
因此,可以认为微服务是一种经过良好架构设计的分布式架构方案 。
无论是微服务还是SOA,都面临着服务间的远程调用。那么服务间的远程调用方式有哪些呢?常见的远程调用方式有以下2种:
如果你们公司全部采用Java技术栈,那么使用Dubbo作为微服务架构是一个不错的选择。相反,如果公司的技术栈多样化,而且你更青睐Spring家族,那么SpringCloud搭建微服务是不二之选。在我们的项目中,我们会选择SpringCloud套件,因此我们会使用Http方式来实现服务间调用。
已经有很多的http客户端工具,能够帮助我们做这些事情,例如:
接下来,不过这些不同的客户端,API各不相同
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:
使用步骤:
一、在Spring容器中注册RestTemplate
可以在启动类位置注册:
@SpringBootApplication
public class HttpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HttpDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
二、在测试类中注入,并调用其他服务的接口
@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Test
public void httpGet() {
// 调用springboot案例中的rest接口
User user = this.restTemplate.getForObject("http://localhost/user/1", User.class);
System.out.println(user);
}
}
通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验。
案例:从订单模块查询订单的同时,根据订单中包含的用户编号查询出用户信息
在进行模拟之前,我们先了解两个概念:服务拆分、远程调用
在集中式架构中,我们会将订单服务和用户服务的功能写在同一个项目中,缺点也很明显:功能模块耦合度高,不利于开发和维护
但是在分布式架构中,我们会将服务进行拆分,微服务也同样如此
一、服务拆分原则:
在调用关系中,我们要明确服务提供者和服务消费者
一、服务提供者对外提供接口
服务提供者只需要对外提供接口即可,不需要其他操作
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 根据用户编号查询用户信息
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
二、服务消费者注册RestTemplate
服务站消费者想要调用远程服务(Http方式)的前提是注册RestTemplate(此处是JDK原生的URLConnection), 我们可以在启动类中,注册RestTemplate实例
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
三、服务消费者远程调用服务
使用RestTemplate的getForObject()方法进行服务调用
@RestController
@RequestMapping("order")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private RestTemplate restTemplate;
@GetMapping("{orderId}")
public Order queryOrderByUserId(@PathVariable("orderId") Long orderId) {
Order order = orderService.queryOrderById(orderId);
// 远程调用(Http方式)
String url = "http://localhost:8081/user/" + order.getUserId();
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
return order;
}
}
问题总结
其实上面说的问题,概括一下就是分布式服务必然要面临的问题
问题分析
在刚才的案例中,user服务对外提供服务,需要对外暴露自己的地址。而order服务(调用者)需要记录服务提供者的地址。将来地址出现变更,还需要及时更新。这在服务较少的时候并不觉得有什么,但是在现在日益复杂的互联网环境,一个项目肯定会拆分出十几,甚至数十个微服务。此时如果还人为管理地址,不仅开发困难,将来测试、发布上线都会非常麻烦,这与DevOps的思想是背道而驰的。
一、order-service怎么知道user-service的实例地址
获取地址信息流程:
二、order-service怎么从多个user-service实例中选择具体的实例
三、order-service如何得知某个user-service实例是否依然健康,是不是已经宕机?
一、引入eureka依赖
引入SpringCloud为eureka提供的starter依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
使用SpringCloud的组件,例如spring-cloud-starter-netflix-eureka-server
,需要引入spring-cloud-dependencies
Springcloud的版本依赖问题(最全,包含springCloud所有的版本)
我们可以在父工程中定义
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR10version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
二、编写启动类
启动类一定要添加@EnableEurekaServer
注解,开启eureka的注册中心功能
package cn.itcast.eureka;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class, args);
}
}
三、编写application.yml
server:
port: 10086 # 端口
spring:
application:
name: eureka-server # 应用名称, 会在Eureka中显示
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要加上其它Server的地址。
defaultZone: http://127.0.0.1:${server.port}/eureka
四、启动服务
启动微服务,然后在浏览器访问:http://127.0.0.1:10086
下面,我们将user-service注册到eureka-server中去。
一、引入依赖
引入下面的eureka-client依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
二、编写application.yml
spring:
application:
name: user-service # 应用名称,注册到eureka后的服务名称
eureka:
client:
service-url: # EurekaServer地址
defaultZone: http://127.0.0.1:10086/eureka
三、启动多个user-service实例
为了演示一个服务有多个实例的场景,我们添加一个SpringBoot的启动配置,再启动一个user-service。将第二个实例的端口改为8082
查看eureka-server管理页面:
一、引入依赖(依赖与服务注册一直)
之前说过,服务发现、服务注册统一都封装在eureka-client依赖,因此这一步与服务注册时一致。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
二、编写application.yml(配置与服务注册一致)
服务发现也需要知道eureka地址,因此第二步与服务注册一致,都是配置eureka信息:
spring:
application:
name: order-service
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动项目后,查看eureka-server管理页面:
三、服务拉取和负载均衡
最后,我们要去eureka-server中拉取user-service服务的实例列表,并且实现负载均衡。不过这些动作不用我们去做,只需要添加一些注解即可。
在注册RestTemplate时,添加@LoadBalanced注解,实现负载均衡
四、修改远程接口路径
修改order-service服务中的cn.itcast.order.service包下的OrderService类中的queryOrderById方法。修改访问的url路径,用服务名代替ip、端口:
spring会自动帮助我们从eureka-server端,根据userservice这个服务名称,获取实例列表,而后完成负载均衡。