当网站流量很小时,只需要一个应用,所有功能部署在一起,减少部署节点成本的框架称之为集中式框架。此时,用于简化增删改查工作量的数据访问框架(ORM)是影响项目开发的关键。
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
典型代表有两个:流动计算架构和微服务架构;
流动计算架构:
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。流动计算架构的最佳实践阿里的Dubbo。
微服务架构
与流动计算架构很相似,除了具备流动计算架构优势外,微服务架构中的微服务可以独立部署,独立发展。且微服务的开发不会限制于任何技术栈。微服务架构的最佳实践是SpringCloud。
跳转到目录
大家谈起的微服务,大多来讲说的只不过是种架构方式。其实现方式很多种:Spring Cloud,Dubbo,华为的Service Combo,Istio…。那么这么多的微服务架构产品中,我们为什么要用Spring Cloud?因为它后台硬、技术强、群众基础好,使用方便;
Spring Cloud从技术架构上降低了对大型系统构建的要求和难度,使我们以非常低的成本(技术或者硬件)搭建一套高效、分布式、容错的平台,但Spring Cloud也不是没有缺点,小型独立的项目不适合使用。
Spring Cloud是一系列分布式微服务技术的有序整合,把非常流行的微服务的技术整合到一起。它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发。
SpringBoot | SpringCloud |
---|---|
1.2.x | Angel版本 |
1.3.x | Brixton版本 |
1.4.x | Camden版本 |
1.5.x | Dalston版本、Edgware |
2.0.x | Finchley版本 |
2.1.x | Greenwich GA版本 (2019年2月发布) |
鉴于SpringBoot与SpringCloud关系,SpringBoot建议采用2.1.x版本
跳转到目录
模拟开发过程中的服务间关系。抽象出来,开发中的微服务之间的关系是生产者和消费者关系。
目标:模拟一个最简单的服务调用场景,场景中保护微服务提供者(Producer)和微服务调用者(Consumer),方便后面学习微服务架构
注意:实际开发中,每个微服务为一个独立的SpringBoot工程。
目标: 新建一个父项目itheima_parent
实现步骤:
实现过程:
创建maven工程,itheima_parent
添加起步依赖坐标:SpringBoot、SpringCloud
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.1.6.RELEASEversion>
parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
目标: 新建一个项目provider_service,对外提供查询用户的服务
实现步骤:
实现过程:
数据库连接信息
创建User表、创建实体User
-- 创建数据库
CREATE database springcloud CHARACTER SET utf8 COLLATE utf8_general_ci;
-- 使用springcloud数据库
USE springcloud;
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL COMMENT '用户名',
`password` varchar(100) DEFAULT NULL COMMENT '密码',
`name` varchar(100) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`sex` int(11) DEFAULT NULL COMMENT '性别,1男,2女',
`birthday` date DEFAULT NULL COMMENT '出生日期',
`created` date DEFAULT NULL COMMENT '创建时间',
`updated` date DEFAULT NULL COMMENT '更新时间',
`note` varchar(1000) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES ('1', 'zhangsan', '123456', '张三', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '张三');
INSERT INTO `tb_user` VALUES ('2', 'lisi', '123456', '李四', '13', '1', '2006-08-01', '2019-05-16', '2019-05-16', '李四');
实体bean:
public class User {
private Integer id;//主键id
private String username;//用户名
private String password;//密码
private String name;//姓名
private Integer age;//年龄
private Integer sex;//性别 1男性,2女性
private Date birthday; //出生日期
private Date created; //创建时间
private Date updated; //更新时间
private String note;//备注
//getter setter
}
编写三层架构:Mapper、Service、controller,编写查询所有的方法
@Repository
@Mapper
public interface UserMapper {
User findById();
}
service类:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> findAll() {
return userMapper.findAll();
}
@Override
public User findById(Integer id) {
return userMapper.findById(id);
}
}
controller:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
/**
* 查询所有
* @return
*/
@RequestMapping("/findAll")
public List<User> findAll() {
return userService.findAll();
}
/**
* 根据id查询
* @param id
* @return
*/
@RequestMapping("/findById")
public User findById(Integer id) {
return userService.findById(id);
}
}
配置Mapper映射文件
<mapper namespace="com.itheima.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from tb_user
select>
<select id="findById" parameterType="Integer" resultType="user">
select * from tb_user where id = #{id}
select>
mapper>
在application.properties中添加MyBatis配置,扫描mapper.xml和mapper
server:
port: 9091
# DB 配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
password: root
username: root
# 扫描实体
mybatis:
type-aliases-package: com.itheima.domain
# mapper.xml配置文件路径
mapper-locations: classpath:mapper/*Mapper.xml
访问测试地址
http://localhost:9091/user/findById?id=1
目标: 新建一个项目consumer_service,调用用户微服务提供的查询用户服务
实现步骤:
实现过程:
编写代码
在启动类中注册RestTemplate
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
//注册RestTemplate
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
编写ConsumerController,在Controller中直接调用RestTemplate,远程访问User-service
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@GetMapping("{id}")
public User queryById(@PathVariable Long id){
String url = String.format("http://localhost:9091/user/%d", id);
return restTemplate.getForObject(url,User.class);
}
}
启动并测试,访问:http://localhost:8080/consumer/1[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JO5DGeaI-1588154558558)(day02-SpringCloud-讲义.assets\1557987811610.png)]
producer_service:对外提供用户查询接口
consumer_service:通过RestTemplate访问接口查询用户数据
存在的问题:
其实上面说的问题,概括一下就是微服务架构必然要面临的问题
跳转到目录
Eureka解决了第一个问题:服务的管理,注册和发现、状态监管、动态路由。
Eureka负责管理记录服务提供者的信息。服务调用者无需自己寻找服务,Eureka自动匹配服务给调用者。
Eureka与服务之间通过心跳
机制进行监控;
基本架构图
Eureka:就是服务注册中心
服务提供者:启动后向Eureka注册自己的信息(地址,提供什么服务)
服务消费者:向Eureka订阅服务,Eureka会将对应服务的所有提供者地址列表发送给消费者,并且定期更新
心跳(续约):提供者定期通过http方式向Eureka刷新自己的状态
步骤: 分三步
在启动类EurekaServerApplication声明当前应用为Eureka服务使用@EnableEurekaServer
注解
编写配置文件application.yml
# 端口
server:
port: 10086
spring:
application:
name: eureka-server # 应用名称,会在Eureka中作为服务的id标识(serviceId)
eureka:
client:
service-url: # EurekaServer的地址,现在是自己的地址,如果是集群,需要写其它Server的地址。
defaultZone: http://127.0.0.1:10086/eureka
启动EurekaServerApplication
关闭注册自己
# 是否抓取注册列表
# 是否注册服务中心Eureka
eureka:
client:
fetch-registry: false
register-with-eureka: false
在服务提供者user_service工程中添加Eureka客户端依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
在启动类上开启Eureka客户端发现功能@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient // 开启Eureka客户端发现功能
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class,args);
}
}
修改配置文件:spring.application.name指定应用名称,作为服务ID使用
# 配置应用基本信息 和 DB
server:
port: 9091
spring:
application:
name: user-service
datasource:
driver-class-name: com.mysql.jdbc.Driver
password: root
url: jdbc:mysql://127.0.0.1:3306/springcloud?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
# 配置eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
完成之后重启项目
客户端代码会自动把服务注册到EurekaServer中
在Eureka监控页面可以看到服务注册成功信息
在服务消费者spring_cloud_itcast_consumer_service工程中添加Eureka客户端依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Greenwich.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
在启动类开启Eureka客户端@EnableDiscoveryClient
@SpringBootApplication
@EnableDiscoveryClient//开启服务发现
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class,args);
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
修改配置文件:加入EurekaServer地址
# 配置应用基本信息
server:
port: 8080
spring:
application:
name: consumer-demo
# 配置eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
启动服务,在服务中心查看是否注册成功
修改代码
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("{id}")
public User queryById(@PathVariable Long id){
String url = String.format("http://localhost:9091/user/%d", id);
//1、获取Eureka中注册的user-service实例列表
List<ServiceInstance> serviceInstanceList = discoveryClient.getInstances("user-service");
//2、获取实例
ServiceInstance serviceInstance = serviceInstanceList.get(0);
//3、根据实例的信息拼接的请求地址
url = String.format("http://%s:%s/user/%d", serviceInstance.getHost(), serviceInstance.getPort(), id);
//发生请求
return restTemplate.getForObject(url,User.class);
}
}
Debug跟踪运行
服务提供者地址拼接成功
这里服务的host地址有什么问题!?
# 默认注册时使用的是主机名,想用ip进行注册添加如下配置
eureka:
instance:
# ip地址
ip-address: 127.0.0.1
# 更倾向于使用ip,而不是host名
prefer-ip-address: true
Eureka架构中的三个核心角色
服务提供者要向EurekaServer注册服务,并完成服务续约等工作
服务每隔30秒会向注册中心续约(心跳)一次,如果没有续约,租约在90秒后到期,然后服务会被失效。每隔30秒的续约操作我们称之为:心跳
检测。
#向Eureka服务中心集群注册服务
eureka:
instance:
# 租约续约间隔时间,默认30秒
lease-renewal-interval-in-seconds: 30
# 租约到期,服务时效时间,默认值90秒
lease-expiration-duration-in-seconds: 90
每隔30秒服务会从注册中心中拉取一份服务列表
这个时间可以通过配置修改
#向Eureka服务中心集群注册服务
eureka:
client:
# 每隔多久获取服务中心列表,(只读备份)
registry-fetch-interval-seconds: 30
registry-fetch-interval-seconds
修改服务中心每隔一段时间(默认60秒)将清单中没有续约的服务剔除。
通过eviction-interval-timer-in-ms
配置可以对其进行修改,单位是毫秒
Eureka会统计服务实例最近15分钟心跳续约的比例是否低于85%,如果低于则会触发自我保护机制。
服务中心页面会显示如下提示信息
含义:紧急情况!Eureka可能错误地声称实例已经启动,而事实并非如此。续约低于阈值,因此实例不会为了安全而过期。
自我保护模式下,不会剔除任何服务实例
自我保护模式保证了大多数服务依然可用
通过enable-self-preservation
配置可用关停自我保护,默认值是打开
#向Eureka服务中心集群注册服务
eureka:
server:
enable-self-preservation: false # 关闭自我保护模式(缺省为打开)