产生背景
系统架构演变
集中式架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。
存在的问题:
代码耦合,开发维护困难 无法针对不同模块进行针对性优化 无法水平扩展 单点容错率低,并发能力差
垂直拆分
当访问量逐渐增大,单一应用无法满足需求,此时为了应对更高的并发和业务需求,我们根据业务功能对系统进行拆分
优点:
系统拆分实现了流量分担,解决了并发问题 可以针对不同模块进行优化 方便水平扩展,负载均衡,容错率提高
缺点:
系统间相互独立,会有很多重复开发工作,影响开发效率
分布式服务
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式调用是关键。
优点:
将基础服务进行了抽取,系统间相互调用,提高了代码复用和开发效率
缺点:
系统间耦合度变高,调用关系错综复杂,难以维护
服务治理(SOA)
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键 以前出现了什么问题?
- 服务越来越多,需要管理每个服务的地址
- 调用关系错综复杂,难以理清依赖关系
- 服务过多,服务状态难以管理,无法根据服务情况动态管理
服务治理要做什么?
服务注册中心,实现服务自动注册和发现,无需人为记录服务地址 服务自动订阅,服务列表自动推送,服务调用透明化,无需关心依赖关系 动态监控服务状态监控报告,人为控制服务状态
缺点:
服务间会有依赖关系,一旦某个环节出错会影响较大 服务关系复杂,运维、测试部署困难,不符合DevOps思想
微服务
前面说的SOA,英文翻译过来是面向服务。微服务,似乎也是服务,都是对系统进行拆分。因此两者非常容易混淆,但其实缺有一些差别。
微服务的特点:
单一职责:微服务中每一个服务都对应唯一的业务能力,做到单一职责
微:微服务的服务拆分粒度很小,例如一个用户管理就可以作为一个服务。每个服务虽小,但“五脏俱全”。
面向服务:面向服务是说每个服务都要对外暴露服务接口API。并不关心服务的技术实现,做到与平台和语言无关,也不限定用什么技术实现,只要提供Rest的接口即可。
自治:自治是说服务间互相独立,互不干扰。
团队独立:每个服务都是一个独立的开发团队,人数不能过多。
技术独立:因为是面向服务,提供Rest接口,使用什么技术没有别人干涉。
前后端分离:采用前后端分离开发,提供统一Rest接口,后端不用再为PC、移动段开发不同接口
数据库分离:每个服务都使用自己的数据源。
部署独立,服务间虽然有调用,但要做到服务重启不影响其它服务。有利于持续集成和持续交付。每个服务都是独立的组件,可复用,可替换,降低耦合,易维护。
远程调用方式
常见的远程调用方式有以下几种:
RPC:Remote Produce Call远程过程调用,类似的还有RMI。自定义数据格式,基于原生TCP通信,速度快,效率高。早期的webservice,现在热门的dubbo,都是RPC的典型。
Http:http其实是一种网络传输协议,基于TCP,规定了数据传输的格式。现在客户端浏览器与服务端通信基本都是采用Http协议。也可以用来进行远程服务调用。缺点是消息封装臃肿。 现在热门的Rest风格,就可以通过http协议来实现。
RPC
RPC,即 Remote Procedure Call(远程过程调用),是一个计算机通信协议。 该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程。说得通俗一点就是:A计算机提供一个服务,B计算机可以像调用本地服务那样调用A计算机的服务。
Http
Http协议:超文本传输协议,是一种应用层协议。规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议。说到这里,大家可能觉得,Http与RPC的远程调用非常像,都是按照某种规定好的数据格式进行网络通信,有请求,有响应。没错,在这点来看,两者非常相似,但是还是有一些细微差别。
RPC并没有规定数据传输格式,这个格式可以任意指定,不同的RPC协议,数据格式不一定相同。 Http中还定义了资源定位的路径,RPC中并不需要 最重要的一点:RPC需要满足像调用本地服务一样调用远程服务,也就是对调用过程在API层面进行封装。Http协议没有这样的要求,因此请求、响应等细节需要我们自己去实现。
- 优点:RPC方式更加透明,对用户更方便。Http方式更灵活,没有规定API和语言,跨语言、跨平台
- 缺点:RPC方式需要在API层面进行封装,限制了开发的语言环境。
因此,两者都有不同的使用场景:
如果对效率要求更高,并且开发过程使用统一的技术栈,那么用RPC还是不错的。 如果需要更加灵活,跨语言、跨平台,显然http更合适
那么我们该怎么选择呢?
微服务,更加强调的是独立、自治、灵活。而RPC方式的限制较多,因此微服务框架中,一般都会采用基于Http的Rest风格服务。
Http客户端工具
介绍
HttpClient是Apache公司的产品,是Http Components下的一个组件。
特点:
基于标准、纯净的Java语言。实现了Http1.0和Http1.1 以可扩展的面向对象的结构实现了Http全部的方法(GET, POST, PUT, DELETE, HEAD, OPTIONS, and TRACE) 支持HTTPS协议。 通过Http代理建立透明的连接。 自动处理Set-Cookie中的Cookie。
Spring的RestTemplate
Spring提供了一个RestTemplate模板工具类,对基于Http的客户端进行了封装,并且实现了对象与json的序列化和反序列化,非常方便。RestTemplate并没有限定Http的客户端类型,而是进行了抽象,目前常用的3种都有支持:
HttpClient OkHttp JDK原生的URLConnection(默认的)
首先注册一个RestTemplate对象,可以在启动类位置注册
代码语言:javascript复制@SpringBootApplication
public class HttpDemoApplication {
public static void main(String[] args) {
SpringApplication.run(HttpDemoApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
在测试类中注入:
代码语言:javascript复制@RunWith(SpringRunner.class)
@SpringBootTest(classes = HttpDemoApplication.class)
public class HttpDemoApplicationTests {
@Autowired
private RestTemplate restTemplate;
@Test
public void httpGet() {
User user = this.restTemplate.getForObject("http://localhost:8088/user/1", User.class);
System.out.println(user);
}
}
通过RestTemplate的getForObject()方法,传递url地址及实体类的字节码,RestTemplate会自动发起请求,接收响应,并且帮我们对响应结果进行反序列化。
SpringCloud
SpringCloud是Spring旗下的项目之一,官网地址:点击
Spring最擅长的就是集成,把世界上最好的框架拿过来,集成到自己的项目中。
SpringCloud也是一样,它将现在非常流行的一些技术整合到一起,实现了诸如:配置管理,服务发现,智能路由,负载均衡,熔断器,控制总线,集群状态等等功能。其主要涉及的组件包括:
netflix
Eureka:注册中心 Zuul:服务网关 Ribbon:负载均衡 Feign:服务调用 Hystix:熔断器
微服务场景模拟
服务提供者
我们新建一个项目,对外提供查询用户的服务。 使用spring脚手架快速搭建 不懂的可以看这里下半部分springboot快速搭建 server-demo导入依赖
代码语言:javascript复制 <modules>
<module>user-service</module>
<module>consumer-demo</module>
<module>eureka-server</module>
</modules>
<packaging>pom</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.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>Finchley.SR1</spring-cloud.version>
<mapper.starter.version>2.0.3</mapper.starter.version>
<pageHelper.starter.version>1.2.5</pageHelper.starter.version>
<leyou.latest.version>1.0.0-SNAPSHOT</leyou.latest.version>
<fastDFS.client.version>1.26.1-RELEASE</fastDFS.client.version>
</properties>
<dependencyManagement>
<dependencies>
<!--springCloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--通用mapper启动器-->
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>${mapper.starter.version}</version>
</dependency>
<!--分页助手paperHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pageHelper.starter.version}</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--FastDFS客户端-->
<dependency>
<groupId>com.github.tobato</groupId>
<artifactId>fastdfs-client</artifactId>
<version>${fastDFS.client.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
pojo类
代码语言:javascript复制@Table(name = "user")
@Data
public class User implements Serializable {
@Id
@KeySql(useGeneratedKeys = true)
private Integer id; // id
private String name; // 用户名
private Integer age; // 登录名
private String email; // 密码
}
映射mapper
代码语言:javascript复制public interface UserMapper extends Mapper<User> {
}
service层
代码语言:javascript复制/**
* 服务提供方
* 这是一个新建的类
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public User findById(Long id){
return userMapper.selectByPrimaryKey(id);
}
@Transactional//导入jdbc依赖的时候 事务已经导入进来了
// 我们只需要在需要事务的方法上加入Transactional注解
public void insertUser(User user){
userMapper.insert(user);
}
}
controller层
代码语言:javascript复制@Slf4j
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("{id}")
@ResponseBody
public User hello(@PathVariable("id") Long id){
return userService.findById(id);
}
}
启动器
代码语言:javascript复制@SpringBootApplication
@MapperScan("cn.rpf.user.mapper")
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
配置文件
代码语言:javascript复制server:
port: 8081
spring:
datasource:
url: jdbc:mysql://localhost:3306/mybatis
username: root
password: 123456
mybatis:
type-aliases-package: cn.rpf.user.pojo
服务的调用者condumer-demo模组
导入依赖
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
pojo
代码语言:javascript复制@Table(name = "user")
@Data
public class User implements Serializable {
@Id
@KeySql(useGeneratedKeys = true)
private Integer id; // id
private String name; // 用户名
private Integer age; // 登录名
private String email; // 密码
}
控制层
代码语言:javascript复制@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
public User queryUserById(@PathVariable("id") Long id){
String url = "http://127.0.0.1/user/" id;
return this.restTemplate.getForObject(url, User.class);
}
}
启动类
代码语言:javascript复制@SpringBootApplication
public class UserConsumerDemoApplication {
@Bean
public RestTemplate restTemplate() {
// 这次我们使用了OkHttp客户端,只需要注入工厂即可
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(UserConsumerDemoApplication.class, args);
}
}
测试
访问
查询出来