Springcloud
一、什么是微服务
1.简介
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
- 单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
- 面向服务:微服务对外暴露业务接口
- 自治:团队独立、技术独立、数据独立、部署独立
- 隔离性强:服务调用做好隔离、容错、降级,避免出现级联问题
2.单体架构和分布式架构的比较
单体架构特点?
- 简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构特点?
- 松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
微服务:一种良好的分布式架构方案
- 优点:拆分粒度更小、服务更独立、耦合度更低
- 缺点:架构非常复杂,运维、监控、部署难度提高
3.微服务的架构
4.微服务的技术对比
二、SpringCloud
1.简介
SpringCloud是目前国内使用最广泛的微服务框架。官网地址:springcloud官网
SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
2.springcloud和springboot的兼容版本关系:
3.服务的远程调用(rpc远程调用)
远程调用就是说在一个服务里,调用别人开放出来的接口,去操作别人的数据库或者业务。
一般使用HttpClient(org.apache.httpcomponents)来进行http协议的接口调用。
HttpClient基本介绍:
- 实现了所有 HTTP 的方法(GET、POST、PUT、HEAD、DELETE、HEAD、OPTIONS 等)
- 支持 HTTPS 协议
- 支持代理服务器(Nginx等)等
- 支持自动(跳转)转向
步骤:
添加maven依赖:
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.66</version>
</dependency>
HttpClient工具类:
private static RequestConfig requestConfig = null;
private static final Log log = LogFactory.getLog(HttpClientUtils.class);
// post请求返回结果
private static CloseableHttpClient httpClient = HttpClients.createDefault();
static {
// 设置请求和传输超时时间
requestConfig = RequestConfig.custom().setSocketTimeout(2000).setConnectTimeout(2000).build();
}
4.问题及优化
随着业务接口或者说服务的增多,当ip+端口固定写死之后,当提供者的ip端口发生变化变化的时候,消费者也需要改动,这样手动的更改非常的麻烦,所以我们就要使用服务注册中心来解决这个问题。服务注册中心在当下enraeka和nacos比较流行。
三、enreka服务注册中心
1.简介
2.快速的使用enreka
一共三部,搭建一个注册中心,也就是enreka服务端,然后将提供者的服务注册到enreka上
,enreka中会记录提供者对外暴漏的接口,而消费者只需要去注册中心拉取这个服务名称就可以。
搭建EurekaServer服务步骤如下:
1.创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
2.编写启动类,添加@EnableEurekaServer注解
3.添加application.yml文件,编写下面的配置:
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
4.将user-service服务注册到EurekaServer步骤如下:
在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在application.yml文件,编写下面的配置:
spring:
application:
name: userservice
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka/
5.在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
**修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
String url = “http://userservice/user/” + order.getUserId();
**在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
四、Ribbon负载均衡
- ribbon负载均衡流程:
- Ribbon的负载均衡规则是一个叫做IRule的接口来定义的,每一个子接口都是一种规则:
通过定义IRule实现可以修改负载均衡规则,有两种方式:
代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
配置文件方式:在order-service的application.yml文件中,添加新的配置也可以修改规则:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule# 负载均衡规则
- 饥饿加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true # 开启饥饿加载
clients: userservice # 指定对userservice这个服务饥饿加载
五、 Nacos注册中心
1.简介
Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
下载完之后,找一个英文目录下解压
之后进入nacos的bin目录,编辑startup.cmd的批处理文件,MODE默认是cluster,即集群模式,应为我们是单体电脑,并没有部署集群,所以启动会失败,需要改成单机模式,即standalone。
127.0.0.1启动之后会有根据自己电脑的ip给定一个url,复制到浏览器打开既可以进入nacos官网。
登录时账号密码都是 nacos
2.nacos快速入门
- 创建项目,并创建两个模块,分别为消费者和生产者,分别引入以下依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
- 添加配置文件,标注注册时的服务名称,nacos注册名称就是你的项目名称,配置nacos的地址
server:
port: 8080
spring:
application:
name: mayikt-member #服务名称 在 注册中心展示服务名称 --
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #nacos服务注册中心Server端的地址
- 编写业务即可
六、nacos配置中心
1.简介
在我们的一个springboot项目中,一个业务假如需要读取我们的配置文件里面的内容,每次启动项目就可以读到。
我们知道spring默认情况将一个bean注入到我们的ioc容器中,会使用单例模式,只会创建一个对象,之后在注入还是同一个对象,这样的话,如果我们修改了配置文件中的内容,我们需要保存之后重新在启动项目,如果时集群的话,整个集群都需要修改,并且都需要重新启动,这样非常的麻烦,并且非常的耗费时间和人力。那我们怎么去解决这个问题呢??由此我们提出了配置中心的概念。
分布式配置中心可以实现不需要重启我们的服务器,动态的修改我们的配置文件内容,常见的配置中心有以下三种:
-
携程的阿波罗
构建环境非常的复杂,底层的架构粒度拆分的非常细… -
SpringCloud Config
没有任何界面,对开发人员非常的不友好… -
Nacos
轻量,对开发人员比较友好…
在这里我们比较推荐使用nacos作为我们的配置中心
上传我们的配置文件要注意格式,在我们nacos配置中心,名称就是我们的服务名称.yml
2.nacos配置中心的快速入门
想要用nacos配置中心,首先开启nacos服务,然后进入nacos管理页面,然后点击加号,进行添加
引入nacos配置中心的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-nacos-config</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
然后在本地配置文件中设置一下自己服务的项目名称,这里的项目名称很关键,和nacos配置中心的是一致的…
添加nacos的服务端地址:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos的服务端地址
file-extension: yml #默认为properties,注意修改为yml
然后就可以编写一个简单的业务去测试一下:
访问该接口可以看到成功的从nacos配置中心将我们写好的yml配置文件读到了本地
3.nacos实现动态刷新配置文件内容
当我们在nacos配置中心修改云端数据时,本地的缓存会跟着立即发生改变。我们知道我们的项目启动时回去优先读取本地的缓存,本地没有缓存才会去云端拉取,而项目启动时怎么实现动态获取呢?这就和我们的bean注入IOC容器有关联了,我们都知道默认bean注入的对象都是单例,只有一个对象,我们配置中心启动时,项目会把之前创建好的对象删除掉,重新创建一个实例对象然后去读取本地缓存或者云端配置文件内容,进行对象的实例化,然后再将注入到IOC容器当中去。这种方法需要在类上加上 @RefreshScope 注解
实践操作:
1.我们原本是mayupi
访问该接口也是显示正确
接下来我们在接口实现类上添加**@RefreshScope**注解,重启项目,再次访问依然地址是mayupi
然后我们修改配置中心的配置,修改为 www.baidu.com,然后我们再次访问该接口发现已经动态修改了
通过观看底层源码,不难发现这是通过长轮询的方式进行心跳确认来实现的,就是说间隔性的拿云端数据去和本地的缓存数据进行比较,如果内容一样,就继续,如果数据不一致,就会删除本地缓存,重新从配置中心拉取数据来加载到本地,保证每次项目访问都是和云端保持一致的。
4.nacos在windows环境下的集群部署
正常情况下我们的nacos是需要以集群的形式部署的,这样的话我们就很有必要进行集群的部署。
第一步:打开nacos文件的config目录,运行这个nacos的sql文件,创建一个本地数据库
第二步:进入config目录下的application.properties进行编辑,修改端口号为对应的端口号,并且打开关于数据库的那些配置,这里注意user和password要改成你本地数据库的账号和密码.
第三步:将config目录下的cluster.conf.example修改为cluster.conf
第四步:打开cluster.conf进行集群的ip+端口的配置
其它剩余的服务器重复以上步骤即可。最分别启动这几个nacos服务即可,我这里配置了三个
第五步:启动三个nacos,看到successfully表示启动成功。
![在这里插入图片描述](https://img-blog.csdnimg.cn/235c01b0597242adaa291503bcbf1282.png
我们打开浏览器,访问一下nacos服务中心可以看到都是在线状态,到这里我们的集群就搭建完毕了
七、Feign客户端
1.简介
Feign是一个声明式的http客户端,其作用就是帮我们优雅的实现http请求的发送。解决restemplate带来的臃肿问题。
Feign 是一个声明式的 Web Service 客户端。它的出现使开发 Web Service 客户端变得很简单。使用 Feign 只需要创建一个接口加上对应的注解,比如:@FeignClient 注解。 Feign 有可插拔的注解,包括 Feign 注解和 AX-RS 注解。Feign 也支持编码器和解码器,Spring Cloud Open Feign 对 Feign 进行增强支持 Spring Mvc 注解,可以像 Spring Web 一样使用 HttpMessageConverters 等。
Feign 是一种声明式、模板化的 HTTP 客户端。在 Spring Cloud 中使用 Feign,可以做到使用 HTTP 请求访问远程服务,就像调用本地方法一样的,开发者完全感知不到这是在调用远程方法,更感知不到在访问 HTTP 请求。接下来介绍一下 Feign 的特性,具体如下:
- 可插拔的注解支持,包括 Feign 注解和AX-RS注解。
- 支持可插拔的 HTTP 编码器和解码器。
- 支持 Hystrix 和它的 Fallback。
- 支持 Ribbon 的负载均衡。
- 支持 HTTP 请求和响应的压缩。Feign 是一个声明式的 WebService 客户端,它的目的就是让 Web Service
调用更加简单。它整合了 Ribbon 和 Hystrix,从而不需要开发者针对 Feign 对其进行整合。Feign 还提供了 HTTP
请求的模板,通过编写简单的接口和注解,就可以定义好 HTTP 请求的参数、格式、地址等信息。Feign 会完全代理 HTTP的请求,在使用过程中我们只需要依赖注入 Bean,然后调用对应的方法传递参数即可。
2.feign的快速入门
第一步:添加openfeign的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
第二步:在启动类上添加**@EnableFeignClients**注解
第三步:编写fiegn的客户端,一个接口,里面填写需要调用的服务的对应信息
第四步:调用对应方法即可
3.Feign的性能优化
Feign的底层实现是基于默认的客户端实现,是URLConnection,是不支持连接池的,这样的话每次访问服务,都要重新建立连接,用完断开连接,我们知道每次连接都需要进行三次挥手,四次握手。这样的话不仅浪费时间,还浪费性能,由此我们需要更改底层来进行优化。
Feign的客户端实现:
- URLConnection,不支持连接池
- Apache HttpClient,支持连接池
- OKClient,支持连接池
因此优化Feign客户端的性能主要包括:
(1)使用连接池代理默认的URLConnection
(2) 日志级别最好用basic或者none
第一步:引入feign-httpclient的依赖
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>9.4.0</version>
</dependency>
第二步:打开配置文件,配置如下的文件
feign:
httpclient:
enabled: true
max-connections: 200 #最大连接数
max-connections-per-route: 50 #每条路径上的最大连接数
这样的话优化就完成了
4.feign的最佳实践
单独创建一个项目用来封装feign请求的发送,类似于一种提取思想,这样的话可以避免代码的重复编写,造成的资源浪费。
八、gateway网关
1.简介
Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,和限流等。网关(Gateway)又称网间连接器,网关在网络层实现服务之间网络互连。用程序的话来说就是请求转发。他就好比小区的门一样,你想进来小区,必须通过门才能进来。为什么要有这个门?为了方便管理以及安全考虑,就好比现在疫情期间,动不动小区封了,假如小区没有门,想要封小区还得费特别大的劲将小区给围住。
2.网关的作用:
- 对用户请求做身份认证,权限校验
- 将用户请求路由到微服务,并实现负载均衡
- 对用户请求做限流
3.gateway的快速入门
- 通过时间匹配
Predicate 支持设置一个时间,在请求进行转发的时候,可以通过判断在这个时间之前或者之后进行转发。比如我们现在设置只有在 2019 年 1 月 1 日才会转发到我的网站,在这之前不进行转发,我就可以这样配置:
spring:
cloud:
gateway:
routes:
- id: time_route
uri: http://ityouknow.com
predicates:
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
Spring 是通过 ZonedDateTime 来对时间进行的对比,ZonedDateTime 是 Java 8 中日期时间功能里,用于表示带时区的日期与时间信息的类,ZonedDateTime 支持通过时区来设置时间,中国的时区是:Asia/Shanghai。
After Route Predicate 是指在这个时间之后的请求都转发到目标地址。上面的示例是指,请求时间在 2018 年 1 月 20 日 6 点 6 分 6 秒之后的所有请求都转发到地址http://ityouknow.com。+08:00是指时间和 UTC 时间相差八个小时,时间地区为Asia/Shanghai。
添加完路由规则之后,访问地址http://localhost:8080会自动转发到http://ityouknow.com。
Before Route Predicate 刚好相反,在某个时间之前的请求的请求都进行转发。我们把上面路由规则中的 After 改为 Before,如下:
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://ityouknow.com
predicates:
- Before=2018-01-20T06:06:06+08:00[Asia/Shanghai]
就表示在这个时间之前可以进行路由,在这时间之后停止路由,修改完之后重启项目再次访问地址http://localhost:8080,页面会报 404 没有找到地址。
除过在时间之前或者之后外,Gateway 还支持限制路由请求在某一个时间段范围内,可以使用 Between Route Predicate 来实现。
spring:
cloud:
gateway:
routes:
- id: after_route
uri: http://ityouknow.com
predicates:
- Between=2018-01-20T06:06:06+08:00[Asia/Shanghai], 2019-01-20T06:06:06+08:00[Asia/Shanghai]
这样设置就意味着在这个时间段内可以匹配到此路由,超过这个时间段范围则不会进行匹配。通过时间匹配路由的功能很酷,可以用在限时抢购的一些场景中。
- 通过 Cookie 匹配
Cookie Route Predicate 可以接收两个参数,一个是 Cookie name , 一个是正则表达式,路由规则会通过获取对应的 Cookie name 值和正则表达式去匹配,如果匹配上就会执行路由,如果没有匹配上则不执行。
spring:
cloud:
gateway:
routes:
- id: cookie_route
uri: http://ityouknow.com
predicates:
- Cookie=ityouknow, kee.e
使用 curl 测试,命令行输入:
curl http://localhost:8080 --cookie “ityouknow=kee.e”
则会返回页面代码,如果去掉–cookie “ityouknow=kee.e”,后台汇报 404 错误。
Header Route Predicate 和 Cookie Route Predicate 一样,也是接收 2 个参数,一个 header 中属性名称和一个正则表达式,这个属性值和正则表达式匹配则执行。
spring:
cloud:
gateway:
routes:
- id: header_route
uri: http://ityouknow.com
predicates:
- Header=X-Request-Id, \d+
使用 curl 测试,命令行输入:
curl http://localhost:8080 -H “X-Request-Id:666666”
则返回页面代码证明匹配成功。将参数-H "X-Request-Id:666666"改为-H "X-Request-Id:neo"再次执行时返回 404 证明没有匹配。
- 通过 Host 匹配
Host Route Predicate 接收一组参数,一组匹配的域名列表,这个模板是一个 ant 分隔的模板,用.号作为分隔符。它通过参数中的主机地址作为匹配规则。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://ityouknow.com
predicates:
- Host=**.ityouknow.com
使用 curl 测试,命令行输入:
curl http://localhost:8080 -H “Host: www.ityouknow.com”
curl http://localhost:8080 -H “Host: md.ityouknow.com”
经测试以上两种 host 均可匹配到 host_route 路由,去掉 host 参数则会报 404 错误。
- 通过请求方式匹配
可以通过是 POST、GET、PUT、DELETE 等不同的请求方式来进行路由。
spring:
cloud:
gateway:
routes:
- id: method_route
uri: http://ityouknow.com
predicates:
- Method=GET
使用 curl 测试,命令行输入:
curl 默认是以 GET 的方式去请求**
curl http://localhost:8080
测试返回页面代码,证明匹配到路由,我们再以 POST 的方式请求测试。
curl 默认是以 GET 的方式去请求
curl -X POST http://localhost:8080
返回 404 没有找到,证明没有匹配上路由
- 通过请求路径匹配
Path Route Predicate 接收一个匹配路径的参数来判断是否走路由。
spring:
cloud:
gateway:
routes:
- id: host_route
uri: http://ityouknow.com
predicates:
- Path=/foo/{segment}
如果请求路径符合要求,则此路由将匹配,例如:/foo/1 或者 /foo/bar。
使用 curl 测试,命令行输入:
curl http://localhost:8080/foo/1
curl http://localhost:8080/foo/xx
curl http://localhost:8080/boo/xx
经过测试第一和第二条命令可以正常获取到页面返回值,最后一个命令报 404,证明路由是通过指定路由来匹配。
- 通过请求参数匹配
Query Route Predicate 支持传入两个参数,一个是属性名一个为属性值,属性值可以是正则表达式。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://ityouknow.com
predicates:
- Query=smile
这样配置,只要请求中包含 smile 属性的参数即可匹配路由。
使用 curl 测试,命令行输入:
curl localhost:8080?smile=x&id=2
经过测试发现只要请求汇总带有 smile 参数即会匹配路由,不带 smile 参数则不会匹配。
还可以将 Query 的值以键值对的方式进行配置,这样在请求过来时会对属性值和正则进行匹配,匹配上才会走路由。
spring:
cloud:
gateway:
routes:
- id: query_route
uri: http://ityouknow.com
predicates:
- Query=keep, pu.
这样只要当请求中包含 keep 属性并且参数值是以 pu 开头的长度为三位的字符串才会进行匹配和路由。
使用 curl 测试,命令行输入:
curl localhost:8080?keep=pub
测试可以返回页面代码,将 keep 的属性值改为 pubx 再次访问就会报 404, 证明路由需要匹配正则表达式才会进行路由。
- 通过请求 ip 地址进行匹配
Predicate 也支持通过设置某个 ip 区间号段的请求才会路由,RemoteAddr Route Predicate 接受 cidr 符号 (IPv4 或 IPv6) 字符串的列表(最小大小为 1),例如 192.168.0.1/16 (其中 192.168.0.1 是 IP 地址,16 是子网掩码)。
spring:
cloud:
gateway:
routes:
- id: remoteaddr_route
uri: http://ityouknow.com
predicates:
- RemoteAddr=192.168.1.1/24
可以将此地址设置为本机的 ip 地址进行测试。
果请求的远程地址是 192.168.1.10,则此路由将匹配。
组合使用
上面为了演示各个 Predicate 的使用,我们是单个单个进行配置测试,其实可以将各种 Predicate 组合起来一起使用。
例如:
spring:
cloud:
gateway:
routes:
- id: host_foo_path_headers_to_httpbin
uri: http://ityouknow.com
predicates:
- Host=**.foo.org
- Path=/headers
- Method=GET
- Header=X-Request-Id, \d+
- Query=foo, ba.
- Query=baz
- Cookie=chocolate, ch.p
- After=2018-01-20T06:06:06+08:00[Asia/Shanghai]
各种 Predicates 同时存在于同一个路由时,请求必须同时满足所有的条件才被这个路由匹配。