软件开发定制认识微服务
软件开发定制服务架构演变
单体架构:软件开发定制将业务的所有功能集中软件开发定制在一个项目中开发,软件开发定制打成一个包部署
优点:架构简单、软件开发定制部署成本低
缺点:耦合度高
软件开发定制分布式架构:软件开发定制根据业务功能对系统进行拆分,软件开发定制每个业务模块作为独立项目开发,软件开发定制称为一个服务
优点:软件开发定制降低耦合度、软件开发定制有利于服务升级拓展
软件开发定制服务拆分的粒度、软件开发定制服务集群的地址、软件开发定制服务之间如何实现远程调用、软件开发定制服务健康状态如何感知
微服务:软件开发定制微服务是一种经过良好软件开发定制架构设计的分布式架构案例
软件开发定制微服务架构特征:
1、单一职责:软件开发定制微服务拆分力度更小,软件开发定制每一个服务都对应唯一软件开发定制的业务能力,软件开发定制做到单一职责,软件开发定制避免重复业务开发
2、面向服务:软件开发定制微服务对外暴露业务接口
3、自治:团队独立、技术独立、数据独立、部署独立
4、隔离性强:软件开发定制服务调用做好隔离、容错、降级,软件开发定制避免出现级联问题
微服务技术对比
微服务结构
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术,国内最知名的就是SpringCloud和 阿里巴巴的Dubbo
微服务技术对比
Dubbo | SpringCloud | SpringCloudAlibaba | |
---|---|---|---|
注册中心 | zookeeper、Redis | Eureka、Consul | Nacos、Eureka |
服务远程调用 | Dubbo协议 | Feign(HTTP协议) | Dubbo、Feign |
配置中心 | 无 | SpringCloudConfig | SpringCloudConfig、Nacos |
服务网关 | 无 | SpringCloudGateway、Zuul | pringCloudGateway、Zuul |
服务监控和保护 | dubbo-admin,功能弱 | Hystrix | Sentinel |
SpringCloud
SpringCloud是目前国内使用最广泛的微服务框架。
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的体验
SpringCloud与SpringBoot的版本兼容关系
这里使用Hoxton.SR10,对应的SpringBoot为2.3.x版本
微服务拆分案例
服务拆分
服务拆分注意事项
1、单一职责:不同微服务,不要重复开发相同业务
2、数据独立:不要访问其他微服务的数据库
3、面向服务:将自己的业务暴露为接口,供其他微服务调用
服务间调用
案例:根据订单id查询订单的同时,把订单所属的用户信息一起返回
此时的需求是:我们可以在订单模块中根据id查询订单,在用户模块中根据id查询用户,但是如果在根据订单id查询订单的同时,把订单所属的用户信息一起返回,就需要使用方式进行服务间的远程调用
远程调用:我们在浏览器中输入用户信息,请求一定会发出,用户模块接收到请求就会到数据库中查询用户信息,然后把对应的用户信息返回给浏览器。如果我们在订单模块发起一个请求,用户模块也应该返回对应的用户信息,然后订单模块在结合本地数据库查询订单信息,这里就引入了如何在Java代码中发起HTTP请求,这样就能在order的模块通过http发起请求。
1、注册RestTemplate
在order-service的OrderApplication中注入RestTemplate,这个工具就是Spring提供给我们发送HTTP请求的
package cn.itcast.order;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@MapperScan("cn.itcast.order.mapper")@SpringBootApplicationpublic class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } //创建RestTemplate对象,并注入Spring容器 @Bean public RestTemplate restTemplate(){ return new RestTemplate(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
2、服务远程调用RestTemplate
修改order-service中的OrderService的queryOrderById方法:
package cn.itcast.order.service;import cn.itcast.order.mapper.OrderMapper;import cn.itcast.order.pojo.Order;import cn.itcast.order.pojo.User;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.web.client.RestTemplate;@Servicepublic class OrderService { @Autowired private OrderMapper orderMapper; @Autowired private RestTemplate restTemplate; public Order queryOrderById(Long orderId) { // 1.查询订单 Order order = orderMapper.findById(orderId); //2、利用RestTemplate发起http请求,查询用户 //2.1获取url路径 String url = "http://localhost:8081/user/" + order.getUserId(); //2.2发起http请求,实现远程调用 //我们知道了url地址,在浏览器中敲回车,请求就会发出,得到的结果是json,但我们需要的是user对象 //RestTemplate会将json反序列化成user类型 User user = restTemplate.getForObject(url, User.class);//这里的url路径和我们在浏览器中输入的路径是一样的 //3、封装user到order中 order.setUser(user); // 4.返回 return order; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
远程调用结果
微服务调用的方式:基于RestTemplate发起的http请求实现远程调用,http请求做远程调用是与语言无关的调用,只要指导对方的ip、端口、接口路径、请求参数即可。
意外错误
Could not autowire. No beans of ‘xxxx’ type found…
解决方案:降低idea的Autowired检测级别,打开idea的配置
提供者与消费者
服务提供者:一次业务中,被其他微服务调用的服务(提供接口给其他微服务)
服务消费者:一次业务中,调用其他微服务的服务(调用其他微服务提供的接口)
所以一个服务既可以是提供者又可以是消费者,这里是相对的
Eureka注册中心
远程调用的问题
在之前的案例中,我们有一个订单服务(order)和一个用户服务(user),订单用户需要远程调用用户服务,其采用的方式是发起一次HTTP请求,但是在我们的代码中,我们将user-service的ip和端口在代码中String url = "http://localhost:8081/user/" + order.getUserId();
,这里的问题是每次环境的变更服务的地址是会发生变化的,而且为了应对等多的并发,user可能会部署成为多实例,变成一个集群时,硬编码就不行了。
因此,我们会面临下面三个问题
服务消费者该如何获取服务提供者的地址信息
如果有多个服务提供者,消费者该如何选择
消费者如何得知服务提供者的健康状态
eureka原理
Eureka的作用
当user-service在启动的时候,会把自己的信息注册给eureka(只要你是eureka的客户端启动的时候都需要注册,包括ip和端口),当order-service需要消费时,不需要自己记录信息,直接找eureka进行拉取服务获得user-server的列表,然后使用负载均衡算法向user-service发送请求(在这里的服务会每隔30秒向eureka发送一次心跳,确保服务提供者的健康状态,如果服务列表中的信息挂掉,Eureka会将将其剔除)。
在Eureka架构中,微服务角色有两类
EurekaServer:服务端,注册中心
记录服务信息
心跳监控
EurekaClient:客户端
1、Provider:服务提供者,user-service
注册自己的信息到EurekaService
每隔30秒向Eureka发送心跳
2、consumer:服务消费者,order-service
根据服务名称从EurekaServer拉取服务列表
基于服务列表做负载均衡,选中一个微服务后发起远程调用
搭建EurekaServer
Eureka的搭建需要搭建一个独立的微服务
1、创建一个新无模板的maven项目,引入 spring-cloud-starter-netflix-eureka-server
依赖
<dependencies> <!--eureka服务器--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> </dependencies>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2、编写启动类,添加注解@EnableEurekaServer
package cn.itcast.eureka;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;@EnableEurekaServer//自动装配的开关@SpringBootApplicationpublic class EurekaApplication { public static void main(String[] args) { SpringApplication.run(EurekaApplication.class,args); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
3、添加application.yml文件,编写下面的配置
# eureka自己也是微服务,所以eureka在启动的时候将自己也会注册到eureka上,这是为了以后eureka集群之间的通用所用server: port: 10086 #服务端口spring: application: name: eurekaserver #微服务的名称eureka: client: service-url: #eureka的地址信息 defaultZone: http://127.0.0.1:10086/eureka #这里如果是eureka集群,则需要使用,将其隔开
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4、注册结果,up表示Eureka实例正在运行
服务注册
在这里之前时eureka的服务端,现在user-service则是服务的客户端
将user-service服务注册到EurekaServer步骤如下:
1、在user-service项目引入spring-cloud-starter-netflix-eureka-client
依赖、order-service作为消费者也可使用同样方式进行部署
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
- 1
- 2
- 3
- 4
2、在application.yml文件中,编写下面配置
server: port: 8081spring: datasource: url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false username: root password: root driver-class-name: com.mysql.jdbc.Driver application: name: userservice # user服务的服务名称mybatis: type-aliases-package: cn.itcast.user.pojo configuration: map-underscore-to-camel-case: truelogging: level: cn.itcast: debug pattern: dateformat: MM-dd HH:mm:ss:SSSeureka: client: service-url: defaultZone: http://127.0.0.1:10086/eureka
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
3、部署多实例,将user-server多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:
4、刷新Eureka注册中心,可以看到部署的信息
服务发现
服务拉取是基于服务名称获取服务列表,然后再对服务列表做负载均衡
1、修改OrderService的代码,修改访问url路径,用服务名代替ip、端口用服务者提供的服务名称进行远程调用
String url = "http://userservice/user/" + order.getUserId();
- 1
2、在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡@LoadBalanced
注解:
package cn.itcast.order;import org.mybatis.spring.annotation.MapperScan;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cloud.client.loadbalancer.LoadBalanced;import org.springframework.context.annotation.Bean;import org.springframework.web.client.RestTemplate;@MapperScan("cn.itcast.order.mapper")@SpringBootApplicationpublic class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } //创建RestTemplate对象,并注入Spring容器 @Bean @LoadBalanced public RestTemplate restTemplate(){ return new RestTemplate(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
3、运行结果
Ribbon负载均衡原理
在上面我们发出的请求明明是http://userservice/user/1
,如何变成了http://localhost:8081
呢
SpringCloud底层其实是利用了一个名为Ribbon的组件,来实现负载均衡功能的。
源码跟踪
为什么我们只输入了service名称就可以访问了呢?之前还要获取ip和端口。
LoadBalancerInterceptor根据service名称,获得了服务实例的ip和端口,它会在对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表,随后利用负载均衡算法得到真实的服务地址信息,替换服务id。
LoadBalancerInterceptor
LoadBalancerInterceptor实现了ClientHttpRequestInterceptor(客户端Http请求的拦截器),其中的intercept方法,拦截了用户的HttpRequests请求做了几件事:
request.getURI()
:获取请求uri,本例中就是 http://user-service/user/8
originalUri.getHost()
:获取uri路径的主机名,其实就是服务id,userservice
this.loadBalancer.execute()
:处理服务id,和用户请求。
这里的this.loadBalancer是LoadBalancerClient类型
LoadBalancerClient
继续跟入execute方法:
getLoadBalancer(serviceId):根据服务id获取ILoadBalancer,而ILoadBalancer会拿着服务id去eureka中获取服务列表并保存起来。
getServer(loadBalancer):利用内置的负载均衡算法,从服务列表中选择一个。本例中,可以看到获取了8082端口的服务
放行后,再次访问并跟踪,发现获取的是8081:
这里就实现了负载均衡
负载均衡的策略
在刚才的代码中,可以看到获取服务使通过一个getServer
方法来做负载均衡:IRule利用内置负载均衡规则,从列表中选择一个,例如localhost:8081,RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
RestTemplate发送的请求http://userservice/user/1,会被LoadBalancerInterceptor负载均衡拦截器进行拦截,LoadBalancerInterceptor将拦截到的请求名称(userservice)交给RibbonLoadBalancerClient,RibbonLoadBalancerClient又将服务名称交给DynamicServerListLoadBalancerc,DynamicServerListLoadBalancerc,从eureka-server中得到eureka的拉取服务列表,得到多个服务的信息,然后从服务信息中通过IRule内置的负载均衡规则挑选某个服务,将其返回给RibbonLoadBalancerClient修改请求地址,用localhost:8081替代userservice,得到http://localhost:8081/user/1,发起真实请求
负载均衡策略
负载均衡的规则都定义在IRule接口中,而IRule有很多不同的实现类:
不同规则的含义如下
默认的实现就是ZoneAvoidanceRule,是一种轮询方案,也是一般小型企业用的
内置负载均衡规则类 | 规则描述 |
---|---|
RoundRobinRule | 简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。 |
AvailabilityFilteringRule | 对以下两种服务器进行忽略: (1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。 (2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上限,可以由客户端的<clientName>.<clientConfigNameSpace> .ActiveConnectionsLimit属性进行配置。 |
WeightedResponseTimeRule | 为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。 |
ZoneAvoidanceRule | 以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。而后再对Zone内的多个服务做轮询。 |
BestAvailableRule | 忽略那些短路的服务器,并选择并发数较低的服务器。 |
RandomRule | 随机选择一个可用的服务器。 |
RetryRule | 重试机制的选择逻辑 |
调整负载均衡策略
通过定义IRule实现可以修改负载均衡规则,有两种方式:
1、代码方式:在order-service中的OrderApplication类中,定义一个新的IRule,总的是IRule,但是实现可以是上表中的任意实现,比如以下代码就会将默认的轮询变为随机
而且:这里通过代码实现后,在orderservice中调用的所有服务都是自己配置的规则,算是一个全局变量
@Beanpublic IRule randomRule(){ return new RandomRule();//这样在具体的实现中写为任意的负载均衡类就行}
- 1
- 2
- 3
- 4
2、配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则
给某个微服务配置负载均衡规则,这里是userservice服务
userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则
- 1
- 2
- 3
饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon: eager-load: enabled: true #开启饥饿加载 clients: userservice # 指定对userservice这个服务进行饥饿加载
- 1
- 2
- 3
- 4
在这里如果需要对多个服务进行饥饿加载,在clients:就要换行写个-
来写多个服务名称