单体项目的缺点:
随着项目越来越大,代码越来越多,存在以下缺点
1.编译难,部署难,测试难
2.技术选择难
3.扩展难
使用微服务架构可以解决这些缺点
什么是微服务架构:
微服务架构是一种架构风格,一个大型复杂软件应由多个微服务组成。系统中的各个微服务可被独立技术选型,独立开发,独立部署,独立运维,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个人物代表着一个小的业务能力。由各个微服务功能最终组成完整功能
微服务远程调用方式
RPC:远程过程调用,类似的还有RMI,自定义数据格式,基于原生TCP通信,速度快,效率高。早期的 webservice,现在热门的dubbo,都是RPC的典型。
Http:是一种网络传输协议,基于TCP,规定了数据传输的格式,现在客户端浏览器与服务端通信基本都是采用 Http协议,也可以用来进行远程服务调用。缺点是消息封装臃肿
现在热门的rest风格,就可以通过http协议来实现
如何选择:
1.速度来看,RPC快,虽然底层都是TCP,但是http协议的信息往往比较臃肿
2.难度来看,RPC复杂,http简单
3.灵活性来看,http好,跨平台跨语言
选型:
单体应用架构:中小型项目(功能相对较少),公司官网,管理系统,OA
微服务架构:大型项目(功能比较多) 商城,erp,人力资源等互联网项目
实现技术-SpringBoot
服务治理框架:
由于微服务架构中存在多个微服务,那么如何管理和协调这些服务呢?就需要服务治理框架,而
SpringCloud就是一个基于SpringBoot实现的服务治理工具包。
微服务架构是一种思想,需要技术落地,在Spring微服务解决方案中,SpringBoot使用用来实现单个服务,而服务间协调调用要使用服务治理框架SpringCloud来实现
总结:为什么使用SpringCloud?
他是用来管理和协调微服务架构中多个服务
SpringCloud组成部分
五大组件:
1.服务注册发现(注册中心)–netflix Eureka :帮我们管理服务的通信地址(ip,端口)
2.客户端负载均衡–NetFlix Ribbon\Feign :解决服务负载均衡调用的
3.断路器–NetFlix Hystrix:解决微服务故障的,保护微服务的
4.服务网关–NetFlix Zuul:统一访问入口,微服务的大门(安保大门)
5.分布式配置–Spring Cloud Config:统一管理微服务的配置
模拟springcloud微服务场景
1.创建一个springboot父项目,然后创建几个module
user-common是user-consumer-4020和user-provide-4010都需要的用户类,所以在user-common中创建实体类,然后在需要的地方引入,引入如下
<dependency>
<groupId>org.ouzhirou</groupId>
<artifactId>user-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
parent项目中要配置好依赖,springboot和springcloud的版本要对应,具体参照百度,依赖如下
<!--拥有的子项目-->
<modules>
<module>user-common</module>
<module>user-provide-4010</module>
<module>user-consumer-4020</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<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>Hoxton.SR12</spring-cloud.version><!--对应的版本-->
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
</parent>
<!--版本管理-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version><!--对应的版本-->
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
除这些外,也需要web等公共需要的依赖
2.接口调用间使用RestTemplate(以前用的httpClient)
@Configuration
public class UserConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
然后就可以直接调用了
@Autowired
private RestTemplate restTemplate;
restTemplate.getForObject(url,User.class);//调用
url例如 "http://localhost:4010/user/provide/1"
第一个参数是url,第二个参数是返回值类型
服务注册中心:netflix Eureka
总结:微服务启动的时候会把自己的通信地址提交到客户端形成一个通信地址列表,这个过程称为服务注册
微服务会定期从注册中心拉去一份微服务通信地址缓存到本地,然后微服务间调用会通过这个注册 中心的服务名找到地址然后发送请求,这个过程称为服务发现
微服务会定期通过心跳机制向注册中心发送请求进行服务续约,如果没有发送或者请求超时,那么 注册中心就会将此服务地址剔除
上图缺少续约
使用Eureka注册中心
1.创建Eureka项目,引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.配置
server:
port: 1010
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #是否要注册到eureka
fetch-registry: false #表示是否从eureka拿信息
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #单机配置
3.启动类添加注解
@SpringBootApplication
@EnableEurekaServer //启用eureka服务端
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class);
}
}
而后在需要注册的项目中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
添加配置
eureka:
client:
service-url:
defaultZone: http://localhost:1010/eureka #这里是eureka服务端配置文件的url
instance:
prefer-ip-address: true #当调用getHostname返回实例的hostname时,返回ip而不是hostname名称
ip-address: 127.0.0.1 #指定自己的ip地址,不指定的话会自己寻找
hostname: user-provide:4010 #主机名,自己设置
最后在SpringBoot启动类添加注解@EnableEurekaClient,表明这是客户端
都启动后就把客户端都添加到注册中心了,通过访问eureka配置的url直接访问,就可查看
使用注册中心动态获取url进行访问
@Autowired
private DiscoveryClient client;
List<ServiceInstance> instances = client.getInstances("user-provide"); //服务名spring.application.name下的
ServiceInstance info = instances.get(0);//这里只有一个叫user-provide的,多个就要做负载均衡
String host = info.getHost();
int port = info.getPort();
String urlPrefix = "http://"+host+":"+port;
String url = urlPrefix+"/user/provide/"+id;
return restTemplate.getForObject(url,User.class);
Eureka注册中心集群
为什么要做集群?
如果只有一个,会出现单点故障
配置eureka集群,三个注册中心相互注册
在C:\Windows/System32/drivers/etc/hosts中配置域名
然后用springboot支持多配置文件启动,
三份配置文件名字对应application-eureka1,2,3,
分别配置端口1010,1020,1030
对应的名字都改为eureka1,eureka2,eureka3
server:
port: 1010
eureka:
instance:
hostname: eureka1
client:
register-with-eureka: true #是否要注册到eureka
fetch-registry: false #表示是否从eureka拿信息
service-url:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka
spring:
application:
name: eureka1 #不要有下划线
随后通过配置文件启动三次,注意开启idea多实例支持
spring:
profiles:
active: eureka1 #默认启动eureka1配置文件 可以修改eureka1起到多启动的效果
随后在生产者和消费者配置文件中,全部注册服务中心
service-url:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka
同样配置生产者集群,相同原理,多配置文件,改端口,然后多实例启动,启动后结果如下
生产环境只需要打jar包部署在不同的服务器就行了,本地通过端口号模拟
负载均衡
Ribbon是一个客户端负载均衡器,它可以按照一定规则来完成一种服务多个服务实例负载均衡调用,且这种规则支持自定义
实现负载均衡,默认轮询
1.依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
2.在RestTemplate上加一个注解
@Configuration
public class UserConfig {
@Bean
@LoadBalanced //开启负载均衡调用
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
3.控制层直接写服务名
public static final String URL_PREFIX = "http://USER-PROVIDE";
//服务名直接去注册中心复制
@GetMapping("/{id}")
public User getUserById(@PathVariable("id") int id){
//从注册中心拿到地址
String url = URL_PREFIX+"/user/provide/"+id;
return restTemplate.getForObject(url,User.class);
}
如何实现自定义规则负载均衡?
在配置类中定义(@Configuration),比如定义随机
//负载均衡随机调用
@Bean
public IRule rule(){
return new RandomRule();
}
1、RoundRobinRule
默认的,轮询规则,也是很多高级规则中退避的一种策略
2、 AvailabilityFilteringRule
会过滤掉打开熔断的服务或者是高并发连接数量的服务
3、 WeightedResponseTimeRule
通过服务的平均响应时间,给每一个服务一个权重,响应时间越长,权重越小,开始统计信息不足,应用轮询策略
4、 RetryRule
先按照轮询策略,如果请求服务失败,会在指定时间内进行重试
5、 BestAvailableRule
先过滤掉断路器的服务,然后选择一个并发量最小的
6、 RandomRule
随机获取一个服务
7、ZoneAvoidanceRule
根据性能和可用性来选择
优化,在消费者配置文件中配置
2.在启动服务使用Ribbon进行调用的时候可能会失败,因为Ribbon不是启动服务的时候创建,而是调用的时候创建,Ribbon可能创建时间太久,导致服务调用失败
解决方式:饥饿加载,启动服务的时候就创建
ribbon:
eager-load:
enabled: true #启用饥饿加载
clients: user-provide #针对哪些服务进行饥饿加载
Feign
是一个声明的Web Service客户端,目的是让Web Service调用更加简单,解决Ribbon需要拼接URL的问题
Feign是以接口形式进行服务调用,而不是通过RestTemplate来调用地址,Feign底层还是ribbon,进行了封装
feign接口该谁写问题?
方案1.服务提供者写,单独写个maven模块给消费者依赖调用
方案2.服务消费者写-在自己工程写就ok了,建议这么做,自己把控,建一个clients包
使用feign实现服务间调用
1.在服务调用者处添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2.启动类添加注解
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
//@EnableFeignClients(basePackages = "org.ouzhirou.clients") //扫描feign的接口,如果包在入口类当前包或子包下,可以省略
public class UserConsumerApp {
public static void main(String[] args) {
SpringApplication.run(UserConsumerApp.class);
}
}
3.写客户端接口
//会交给spring管理产生一个动态代理类
@FeignClient("USER-PROVIDE") //服务名
@RequestMapping("/user/provide") //地址
public interface UserClient {
@GetMapping("/{id}")
User getUserById(@PathVariable("id") int id);
}
4.调用
@RestController
@RequestMapping("/user/consumer")
public class UserController {
//注入的是代理类而不是接口
@Autowired
private UserClient userClient;
@GetMapping("/{id}")
public User getUserById(@PathVariable("id") int id){
return userClient.getUserById(id);
}
}
5.配置,和ribbon一样,因为feign底层是ribbon
注意:feign实现负载均衡自定义规则和ribbon一样,和ribbon不一样的只是服务调用方式
雪崩现象:
当某一个服务出现延迟时,所有的请求都阻塞在依赖的服务,从而导致整个服务崩掉
解决方案:对依赖做隔离,Hystrix就是处理依赖隔离的框架
Hystrix是保证微服务群健壮框架,做了隔离,熔断,降级等操作,最终达到不会由某一个服务器出问题而导致雪崩现象,让整体群死掉
资源限流:服务给定一个最大线程数,到达最大后,就不再给它提供服务
熔断:正常情况下,断路器处于关闭状态
如果调用持续出错或者超时,电路被打开进入熔断状态,后续一段时间内的所有调用都会被拒绝
一段时间以后,保护器会尝试进入版熔断状态,允许少量请求进来尝试,
如果调用仍然失败,则回到熔断状态
如果调用成功,则回到电路闭合状态
服务降级:所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以准备一个本地的fallback回调,返回一个缺省值,虽然服务水平下降,但是能用。
HyStrix实现熔断:服务端实现
1.引入jar包
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2.启动类添加注解
@EnableHystrix //开启熔断处理
3.被调用的方法添加注解,且添加对应方法
@HystrixCommand(fallbackMethod = "getFallBack") //自己会熔断,只需要配置熔断降级处理
public User getFallBack(int id){
return new User(id,"系统错误,请联系管理员");
}
4.如果需要自定义超时时间,则在服务端配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #hystrix超时时间
当服务调用报错或者超时就会发生熔断并做降级处理
熔断是在服务提供方做的,HyStrix只能用ribbon调用,因为feign有自己的熔断
存在问题,每个接口每个方法都要写降级方法,控制层太冗余,解决方法,feign自带熔断
实现
1.服务调用者配置
feign:
hystrix:
enabled: true #开启熔断支持
client:
config:
remote-service:
connectTimeout: 3000
readTimeout: 3000
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
2.在客户端注解添加值
//会交给spring管理产生一个动态代理类
@FeignClient(value = "USER-PROVIDE",fallbackFactory =UserClientFallbackFactory.class)
@RequestMapping("/user/provide")
public interface UserClient {
@GetMapping("/{id}")
User getUserById(@PathVariable("id") int id);
}
3.添加注解值对应类,并实现接口FallbackFactory和添加泛型
@Component
public class UserClientFallbackFactory implements FallbackFactory<UserClient> {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public User getUserById(int id) {
return new User(id,"系统错误,请联系系统管理员");
}
};
}
}
总结:如果我们消费者实现的技术是ribbon,必须在服务提供者方通过Hystrix实现熔断
如果消费者实现的技术为feign,那么就必须在服务消费者通过feign的断路器实现熔断,底层还是hystrix
总结ribbon和feign区别
1.feign要在启动类添加注解@EnableFeignClients
2.ribbon需要配置类和注入RestTemplate,且要在此类添加注解 @LoadBalanced
3.feign直接注入代理类,写客户端接口,而ribbon是通过RestTemplate和url调用服务
5.feign实现熔断直接配置开启,然后客户端接口注解添加一个值,实现此值类
6.ribbon实现熔断需要使用hystrix,启动类添加注解@EnableHystrix,并且每个提供服务的方法都要添加一个 @HystrixCommand(fallbackMethod = “”)注解并且实现降级方法
7.ribbon和feign都是在服务消费者实现,但是熔断feign在客户端接口处实现,ribbon在服务端实现
Zuul:
是netflix开源的一个网关服务器,相当于设备和Netflix流应用的web网站后端所有请求的前门入口,也需要注册入Eureka
网关的作用,前端ui层直接访问网关,网关去eureka服务发现拿到地址,再去访问服务器
网关实现
1.依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
2.启动类添加注解
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulGatewayApp {
public static void main(String[] args) {
SpringApplication.run(ZuulGatewayApp.class);
}
}
3.配置文件
server:
port: 2010
eureka:
client:
service-url:
defaultZone: http://eureka1:1010/eureka,http://eureka2:1020/eureka,http://eureka3:1030/eureka
instance:
instance-id: zuul:2010
prefer-ip-address: false
spring:
application:
name: zuul-gateway
然后访问,正常访问地址
localhost:5010/user/provide/1
网关访问
localhost:2010/user-provide/user/provide/1 加上要访问的服务在注册中心的服务名小写
但是这样子服务名暴露了不安全,所以添加配置
zuul:
routes:
MyUser.serviceId: user-provide #服务名小写
MyUser.path: /myUser/** #暴露的资源
# 通过相同前缀来配对,前缀自定义,比如MyUser相同,如果有多个可以继续加,比如
MyOrder.serviceId: user-order #服务名小写
MyOrder.path: /myOrder/**
ignored-services: "*" #不能通过服务名访问
prefix: "/service" #加前缀
ZuulFilter过滤器
使用场景
请求鉴权:一般放在pre类型,如果发现没有访问权限,直接拦截
异常处理:一般会在error类型和post类型过滤器中结合来处理
服务调用时长统计:per和post结合使用
实现登录拦截
@Component //需要交给spring管理才有用
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";//过滤器类型,前缀过滤器
}
@Override
public int filterOrder() {
return 0;//过滤器顺序
}
@Override //是否需要过滤
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
//获取上下文路径
RequestContext currentContext = RequestContext.getCurrentContext();
//获取请求对象
HttpServletRequest request = currentContext.getRequest();
//获取token
String token = request.getHeader("token");
if(token==null||"".equals(token.trim())){
currentContext.setSendZuulResponse(false);//失败的响应
currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());//401没有权限
}
return null;
}
}
zuul默认集成了ribbon负载均衡和Hytrix熔断,但是所有的超时时间都是默认的,所以建议自己配置
ribbon: #负载均衡配置
ReadTimeout: 10000
ConnectTimeout: 10000
MaxAutoRetries: 1 #同一实例的重试次数
MaxAutoRetriesNextServer: 2 #同一服务不同实例的重试次数
hystrix: #熔断配置
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000 #hystrix超时时间
他的熔断降级处理默认是抛异常
SpringCloud Config分布式配置中心
微服务架构中,每个微服务都有一个yml配置,管理起来麻烦,使用config统一管理
实现:
1.将zuul配置文件上传至gitee
2.新建module config-server:3010 pom如下
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
3.配置文件,一般来说使用git仓库的,但是没网就可以使用本地的
server:
port: 3010
spring:
application:
name: config-server
cloud:
config:
server:
# git:
# uri: https://gitee.com/ou-zhirou/spring-cloud-config.git
# username: 1275774810@qq.com
# password: swrf369.
native:
search-locations: file:C:\\Users\\12757\\Desktop\\spring-cloud-config #本地
profiles:
active: native #使用本地配置
4.启动类添加注解
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApp.class);
}
}
5.改造zuul网关,pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
6.zuul配置,bootstrap.yml(全局配置)
spring:
cloud:
config:
name: application-zuul #gitee上名称
profile: dev #环境
label: master #分支
uri: http://127.0.0.1:3010 #服务器地址
这样子zuul的配置中心就搭建好了,其余的文件比如注册中心,服务提供者,服务消费者都是如此改造
但是这样子会存在一个问题,修改配置文件后需要重启,实际中不可能这样,所以使用SpringCloudBus
一但码云监听到有push,就会推送消息给SpringCloudBus(底层是消息队列),然后SpringCloudBus会推送消息给客户端,客户端就会重新拉取配置
使用SpringCloudBus操作
实现,1.配置中心服务端添加依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.66</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--消息总线-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
2.服务端添加配置类实现转换
@Component
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters){
FastJsonHttpMessageConverter httpMessageConverters = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setCharset(StandardCharsets.UTF_8);
//禁止循环引用
config.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
httpMessageConverters.setFastJsonConfig(config);
//所有序列化组件均保存在converters中
//添加扩展的组件到索引0的位置,便于Spring首先尝试
converters.add(0,httpMessageConverters);
}
}
3.服务端配置文件,添加消息队列配置和actuator配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#actuator配置
management:
endpoint:
health:
show-details: always #打印日志
endpoints:
web:
exposure:
include: "*" #向外暴露的接口,这里*则表示所有的actuator接口都可以被访问
enabled-by-default: true #开启actuator监控
4.zuul网关添加配置文件
bus: #为了产生一个bus.id
id: ${spring.application.name}:${spring.cloud.config.profile}:${random.value}
这样子就可以在每次push后,通过postman发送post请求来更新
如果要实现自动刷新,则需要打开内网穿透工具,获取外网可访问链接后,在gitee内配置,如下