微服务重构步骤(微服务学习笔记)
相关技术:
微服务治理:SpringCloud包含的技术
注册发现、远程调用、负载均衡、配置管理、网关路由、系统保护、流量控制、服务授权、熔断降级、分布式事务、TCC模型、AT模型、Seata
异步通信:
MQ消息模型、SpringAMQP、消息堆积问题、消息可靠性、仲裁队列、延迟队列、镜像集群、数据持久化
缓存技术:
缓存穿透(雪崩)、SpringDataReidis、Redis主从复制、OpenResty、缓存数据同步、Nginx本地缓存、Redis持久化、多级缓存分层、Redis分片集群、Lua脚本、Redis数据结构
分布式搜索技术:
DSL语句、ES集群、Rest API、集群脑裂、竞价排名、聚合统计、自动补全、地理坐标、拼音分词
DevOps持续集成:
Dockerfile、DockerCompose、GrayLog、Jenkins、SkyWalking、Docker使用、Kubernetes
技术的阶段分类
1.基础技术
微服务治理:springcloud包含的技术
eureka、Nacos、Open Feign、网关Gateway、配置中心Nacos
DevOps:持续集成
Docker原理、Docker使用、Dockerfile、DockerCompose
异步通信:
同步和异步、MQ技术选型、SpringAMQP、消息者限流
分布式搜索:
DSL语法、竞价排名、HighLevelClient、地理搜索、拼音搜索、聚合统计、自动补全、分片集群
2.高级技术
微服务保护:
流量控制、系统保护、熔断降级、服务授权
分布式事务:
XA模式、TCC模式、AT模式、Saga模式
分布式缓存:
数据持久化、Redis主从集群、哨兵机制、Redis分片集群
多级缓存:
多级缓存分层、Nginx缓存、Redis缓存、Canal数据同步
可靠消息服务:
消息三方确认、惰性队列、延迟队列、镜像集群、仲裁队列
3.理论知识
Nacos源码:
Nacos的服务发现原理、Nacos服务注册原理、Nacos心跳机制、Nacos与Eureka差异
Sentine源码:
Sentinel滑动窗口算法、令牌桶算法、漏桶算法
Redis热点问题:
分布式锁问题、缓存穿透、缓存击穿、缓存雪崩
技术很多慢慢来
2、服务架构演变在了解微服务之前先来看一下项目的几种架构
2-1. 单体架构
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
架构简单
部署成本低
缺点:
耦合度高
说明:如果项目庞大还是单体架构,光是编译打包就会占用大量时间。模块之间的代码你中有我我中有你,边界模糊。可能你在修改一处代码,很多地方的代码也会受影响,轻易不敢动代码
2-2. 分布式架构
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。
优点:
- 降低服务耦合
- 有利于服务升级拓展
拆分后的服务治理问题:
服务拆分粒度如何?
服务集群地址如何维护?
服务之间如何实现远程调用?
服务健康状态如何感知?
目前解决上面问题最常见的方案——微服务
2-3. 微服务
微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:
1)单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
说明:上面分布式示例图中,将项目拆分成 支付模块、用户模块、商品模块、订单模块,如果用在像拼多多这样的电商项目中太过粗糙,比如:用户模块还可以拆分会员模块(不同会员享受不同折扣)、积分模块等。使每一个模块只做单一职责,好处是每个服务业务更少了,影响的范围更小了,这就是单一职责
2)面向服务:微服务对外暴露业务接口
说明:单一职责中将项目拆分成一个个单一职责的模块后,那么模块之间的调用就需要暴露接口来实现了。比如上面积分模块必须暴露出查询积分的接口,将来用户模块才能远程调用到积分模块的查询功能
3)自治:团队独立、技术独立、数据独立、部署独立
说明:团队独立,每个模块拥有专门的前端、后端、测试、运维、沟通更方便,比较像敏捷开发。技术独立,因为每个服务模块相对独立,可以根据业务需求选择更适合自己的技术,可以选择自己更擅长的技术。数据独立,每个服务可以拥有自己独立的数据库,实现了数据的解耦。部署独立,可以实现独立部署
4)隔离性强:服务调用做好隔离、容错、降级、避免出现级联问题
说明:由于是面向服务,那么服务之间相互调用时,提供者挂了,可能会对消费者产生影响,为了避免这些影响就需要隔离,隔离故障,做好容错,避免提供者宕机而导致消费者也宕机的级联影响
总结:
单体架构特点:
简单方便,高度耦合,扩展性差,适合小型项目。例如:学生管理系统
分布式架构特点:
松耦合,扩展性好,但架构复杂,难度大。适合大型互联网项目,例如:京东、淘宝
微服务:一种良好的分布式架构方案
优点:拆分粒度更小,服务更独立,耦合度更低
缺点:结构非常复杂,运维、监控、部署难度提高
3、微服务技术对比3-1. 微服务结构
微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。国内最知名的就是SpringCloud和阿里巴巴的Dubbo
简单介绍上图:
不管是哪一种技术,服务之间的调用关系错综复杂,一定需要去维护,但不可能靠人去维护调用,太复杂,所以在微服务里都会有一个注册中心,它可以去维护微服务中每一个节点的信息,并且去监控这些节点的状态
如果将来微服务越来越多,有些配置需要去修改,手动的去微服务中修改显然不现实,所以在微服务中往往会有一个配置中心,可以统一的去管理整个微服务群的配置,如果有变更,我们也可以利用通知的方式,让对应的服务监控到配置的变化,从而实现配置的热更新。
在微服务部署上线用户访问时,如此之多的微服务用户怎么知道访问哪一个,微服务群往往会有一个网关,用户访问它,然后网关通过路由到微服务群,在路由中还可以做负载均衡
路由和服务之间的调用,我们还要做好容错的处理,避免因为服务故障造成级联失败,做好保护、降级、隔离等等措施
3-2. 微服务技术对比
简单介绍:
Dubbo技术早在2012年左右就由阿里巴巴开源出来了,那时微服务技术并未普及,Dubbo也不是为了处理微服务而发明的,它的核心就是服务的远程调用,所以Dubbo的技术体系并不是特别完整。核心只有注册中心和服务远程调用,而且注册中心也不是自己实现的,而是使用zookeeper、Redis等,这些并不是专业的注册中心,Redis是做缓存的,zookeeper是做集群管理的,所以并不能做到完善的注册中心功能
springCloud并不是发明而是整合,它把全球各公司开源的微服务技术整合进来了,而后形成一套完整的微服务技术体系,成了一个集大成者。
SpringCloudAlibaba这套技术栈是相当于SpringCloud的一部分,因为它实现了SpringCloud的接口规范,并整合进了Dubbo,实现了自己的注册中心Nacos,即支持Dubbo调用也支持Feign调用,SpringCloudAlibaba兼容了SpringCloud和Dubbo两种结构,也是越来越火的原因
3-3. 企业需求在企业中微服务的使用大致分以下几种
4、SpringCloud简介
SpringCloud
1、SpringCloud是目前国内使用最广泛的微服务框架。官网地址:Spring Cloud
2、SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:
那么为什么人们不使用微服务开源技术,而使用SpringCloud?因为SpringCloud是基于SpringBoot的,而SpringBoot最擅长的是自动装配,SpringCloud就是把官方原生开源的组件给整合进来了,并且基于Spring Boot做了自动装配,而你可以拿过来就用,无需复杂配置
3、SpringCloud和SpringBoot的版本兼容关系:
二、服务拆分及远程调用1、服务拆分
服务拆分注意事项
不同微服务,不要重复开发相同业务
微服务数据独立,不要访问其他微服务的数据库
微服务可以将自己的业务暴露为接口,供其他微服务调用
服务拆分说起来很简单,一个单体架构按照功能模块进行拆分,变成多个服务就行了
以订单模块为例,查询订单,需要访问数据库,再想获取用户详情与商品详情的话,按照之前的开发思路,再直接查询用户与商品数据库就行了。如果用到什么就查询什么,那么拆分就没有意义了,而且用户模块与商品模块也再做这样的查询,是一种重复开发,微服务要避免重复开发。
为了做好这样的限制,同时也会有一定的要求,比如微服务的数据独立,每个模块使用自己的数据库,订单模块访问不到用户数据,从根源上杜绝了做这中耦合性的业务。
如果在做订单模块查询又想把用户和商品查询出来的话,如何去做呢?微服务拆分时还要做一件事,就是将自己的部分业务暴露为接口供其他微服务使用。
总结:
1、微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
2、微服务可以将业务暴露为接口,供其他微服务使用
3、不同微服务都应该有自己独立的数据库**
2、服务间调用
远程调用demo
创建项目cloud-demo
创建用户和订单两个服务模块user-service和order-service
需求:在查询订单时查询用户信息
模块之间是不能直接调用方法的,只能使用远程调用
可以使用Spring框架spring-web模块中的RestTemplate类
三、Eureka注册中心1、提供者与消费者
服务调用关系
服务提供者:暴露接口给其他微服务调用
服务消费者:调用其他微服务提供的接口
提供者与消费者角色其实是相对的
一个服务可以同时是服务提供者和服务消费者
2、Eureka介绍
问题:首先采用硬编码的方式显然不行,并且用户服务可能会是多服务的集群,访问哪个服务?服务是否健康?等都是问题
消费者该如何获取服务提供者具体信息?
服务提供者启动时向eureka注册自己的信息
eureka保存这些信息
消费者根据服务名称向eureka拉取提供者信息
如果有多个服务提供者,消费者如何选择?
服务消费者利用负载均衡算法,从服务列表中挑选一个
消费者如何感知服务提供者健康状态?
服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
eureka会更新记录服务列表信息,心跳不正常被剔除
消费者就可以拉取到最新的消息
总结
在Eureka架构中,微服务角色有两类
EurekaServer:服务端,注册中心
记录服务信息
心跳监控
EurekaClient:客户端
Provider:服务提供者,例如案例中的user-service
注册自己的信息到EurekaServer
每隔30秒向EurekaServer发送心跳
Consumer:服务消费者,例如案例中的order-service
根据服务名称从EurekaServer拉取服务列表
基于服务列表做负载均衡,选中一个微服务后发起远程调用
3、Eureka使用实例1)搭建EurekaServer
2)将user-service、order-service注册到eureka
3)在order-service中完成服务拉取、然后通过负载均衡挑选一个服务,实现远程调用
搭建Eureka服务需要一个独立的微服务
1、创建项目后,引入spring-cloud-starter-netflix-eureka-server的依赖
2、编写启动类,添加@EnableEurekaServer
3、添加application.yml文件,编写下面的配置:
启动服务,并访问
这样EurekaServer就搭建完成了
进行服务注册1、在user-service、order-service项目引入spring-cloud-starter-netflix-eureka-client
2、在application.yml文件,已user-service为例,编写下面的配置:
启动服务,并访问
也可以copy服务,模仿分布式集群效果
完成服务注册
在order-service完成服务拉取
服务拉取是基于服务名称获取服务列表,然后在服务列表做负载均衡
1、修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
2、在order-service项目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
这样就完成了服务拉取
四、Ribbon负载均衡
像这种请求http://userservice/user/1,不是真正的域名ip端口,是无法请求到服务器的,所以中间一定有人拦截处理,找到真实地址,那处理的这个人就是Ribbon
1、Ribbon负载均衡原理发起的服务请求是如何拦截的,找到这个类LoadBalancerinterceptor
他实现了ClientHttpRequestInterceptor接口,而ClientHttpRequestInterceptor就是拦截http请求的,实现了intercept方法,debug在intercept方法中断点
可以看到拦截的请求是http://userservice/user/1,通过getHost()就是获取主机名userservice,而他下一步就应该去找Eureka完成服务拉取,debug进入execute()方法中
getLoadBalancer(), 结果ILoadBalancer对象中,有allServerList,可见getLoadBalancer()通过服务名称拉取服务列表
getServer(), 就是Ribbon重要的负载均衡,获取列表后使用哪一个服务,再跟进getServer()
继续跟进chooseServer()
this.rule.choose(key);字面意思说明是根据一种规则进行负载均衡的,我们查看一下rule类型
ctrl H查看IRule的实现类,定义了负载均衡的几种规则
总结:
2、Ribbon负载均衡策略
上面可以看出Ribbon的负载均衡是IRule负载均衡规则实现的,下图为IRule接口的继承关系图
内置负载均衡策略 规则描述
了解几种负载均衡的规则,如何使用规则?
通过定义IRule实现可以修改负载均衡规则
第一种方式:
在order-service中的OrderApplication类中,定义一个新的IRule:
这种方案是作用于全局的,order-service不管是调用哪个服务的接口都是随机的
第二种方式
在application.yml文件中,添加新的配置也可以修改规则:
这种方案是针对某个服务进行配置的
3、Ribbon饥饿加载Ribbon饥饿加载策略
先来看一个现象:
重启order-service服务,发送请求
第一次请求耗时达到了581毫秒,第二次请求32毫秒
原因:Ribbon默认是采用的懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
五、Nacos注册中心1、认识安装Nacos
Nacos:Nacos是阿里巴巴的产品,现在是SpringCloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
这里以1.4.1版本为例
1、window安装:
1-1、解压nacos-server-1.4.1.zip到任意非中文目录下
- bin:启动脚本
- conf:配置文件
1-2、端口设置:
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件application.properties中的端口:
1-3、启动,进入bin目录下,
startup.cmd -m standalone
1-4、访问地址:
默认的账号和密码都是nacos,登录
2、linux安装
2-1、安装JDK
Nacos依赖于JDK运行,所以Linux上也需要安装JDK才行。
2-2、解压缩:
命令解压缩安装包:
然后删除安装包:
2-3、端口设置:
与windows中类似
2-4、启动:
在nacos/bin目录中,输入命令启动Nacos:
2、Nacos简单介绍
在使用Nacos之前
- 在springcloud组件中的Spring Cloud Commons定义了通用的接口规范,其中就有服务发现和服务注册接口,Eureka和Nacos都遵循了接口规范。
- 所以将Eureka更换为Nacos时服务的提供者和消费者代码是不需要变化的
- 需要修改的地方:
引入的依赖
1、在父工程中添加spring-cloud-alibaba的管理依赖,这样有关的所有依赖版本就不需要操心了
2、添加nacos的客户端依赖:
3、服务注册到Nacos
修改user-service和order-service中的application.yml文件,去掉之前的eureka地址,添加nacos地址
重启user-service和order-service,查看Nacos成功注册
发请求测试结果,之前的负载均衡配置也依然生效
3、Nacos服务分级存储模型
像阿里京东这样的大型互联网公司,为保护服务的安全,全国各地部署服务器,做到容灾
nacos做分级的意义是什么,比如上图如果在拉取服务时,杭州的order-service访问的上海的user-service,会有延迟,显然不如访问杭州本地的局域网内的user-service
- 服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
- 只有在本地集群不可访问时,再去访问其他集群
演示
1、下面我们创建三个user-service实例,分两个集群(杭州两个、上海一个)
2、orderservice添加到杭州HZ集群中
总结:
Nacos服务分级存储模型
- 一级是服务,例如userservice
- 二级是集群,例如杭州或上海
- 三级是实例,例如杭州机房的某台部署了userservice的服务器
配好集群后请求,发现杭州的orderservice没有只请求杭州的userservice,依然采用轮询方案
说明依然采用的ribbon配置的负载均衡规则,只需要修改配置文件:
重启orderservice服务,再发几次请求
现在orderservie只请求杭州的8082或8081的userservice了,并且在userservice集群中没有规律
说明:NacosRule采用的是先按配置的本地集群访问,然后在集群中采用随机方案
如果将8082和8083关掉会怎么样?
请求依然成功,查看一下orderservice控制台
一个警告,发生了跨集群的调用,HZ访问的SH
总结:
NacosRule负载均衡策略
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才去其他集群寻找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
根据权重负载均衡
实际部署中会出现这样的场景
NacosRule先根据集群而后随机
- 服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求
Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高
1、在Nacos控制台可以设置实例的权重值,首先选中实例后面的编辑按钮
2、将其中一个权重设置为0.1,测试可以发现8083被访问到的频率大大降低
(如果权重设置为0,那么将不会访问到此实例)
总结
权重控制
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高被访问的频率越高
- 权重设置为0则完全不会被访问
Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西,用来做最外层隔离
上图中
最外层namespace是一个隔离的空间,可以有多个,之间是相互隔离开的
在Namespace内部有Group属性,同一个隔离空间中可以分多个组
Group内就是具体的服务或数据了,而服务service内就是前面介绍的集群再到实例等东西了
所以环境隔离就是对服务或数据进行隔离,不同环境不同命名空间的服务之间不能互相访问
环境隔离的意义?为什么服务实例有了集群、服务还要再包一层环境隔离呢?
原因,之前服务划分和实例划分是基于业务或地域进行的划分,而现实中还会有开发环境、生产环境、测试环境,需要对环境的变化去进行隔离,namespace就是做这些的,Group是分组的意思,可以将业务相关度比较高的服务放在一个组中,比如订单和支付服务,但不是强制使用的
下面创建一个环境隔离示例
进入Nacos管理页面
新建一个dev命名空间
之前的服务都在public中
下面将order-service放在dev命名空间中,这里需要修改order-service的application.yml文件,写上生成的命名空间id
重启order-service
下面发送一次请求,请求失败,没有发现user-service实例
总结
- namespace用来做环境隔离
- 每个namespace都有唯一id
- 不同namespace下的服务不可见
1、Nacos注册中心细节分析
上图简单介绍:
注册中心都会有的共同点:
1、服务启动时服务提供者都会将服务信息注册到注册中心,注册中心将信息保留下来
2、当消费者消费时,就会找注册中心去要这些信息,这就是服务拉取发现。但并不是每次访问都会去注册中心拉取,它会将服务列表缓存起来,之后在缓存中获取服务列表,缓存并不是一直不变会每隔30秒去注册中心拉取一次更新,然后负载均衡做远程调用
Nacos与Eureka不同点:
1、对于服务提供者的健康检测
Nacos会将服务提供者划分成临时实例和非临时实例,默认为临时实例
临时实例和Eureka一样采用心跳,提供者每隔一段时间发一次请求到Nacos,如果发现没有心跳检测出服务异常会将服务在列表中剔除。
非临时实例是Nacos主动请求提供者发送请求,询问服务是否健康,如果服务异常,并不会将服务从列表中剔除,而是标记成不健康状态
2、对于消费者服务发现
对于Eureka消费者只会每隔一段时间去注册中心拉取一次更新列表,如果缓存列表中有服务挂掉了,则会出现问题。
Nacos则就通过pull和push两种方式,不尽自己去注册中心拉取,注册中心也会推送服务信息,如果发现哪个提供者挂掉了,会第一时间推送信息,更具时效性
2、代码测试非临时实例order-service中application.yml文件,配置ephemeral
再不加此配置时,停掉服务,发现nacos已经剔除了order-service服务
启动order-service
启动成功并且临时实例为false,然后停掉服务
停掉服务,发现已经报红,但是并不会被剔除,一直等待服务启动
总结
- Nacos与Eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
- Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
随着微服务越来越多,生产环境中可能会达到数十上百的服务,如果配置文件需要修改,而且配置文件又与每个微服务有关系,首先调整配置文件太多很麻烦,第二调整完配置文件需要重启,生产环境下重启服务影响还是挺大的。现在的需求就是,配置文件统一修改而不是逐个去修改,并且修改完立即生效无需重启做到热更新。下面介绍Nacos的配置管理
1、统一配置管理1-1、新建配置管理
新建配置,点击发布
1-2、拉取配置
在项目启动时,先读取nacos配置文件然后与application.yml配置合并,之前nacos地址都在application.yml中,现在必须将nacos地址和配置相关信息,写入到优先级比application.yml高的bootstrap.yml文件中
1)userservice中引入Nacos的配置管理客户端依赖:
2)在userservice中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml
将application.yml文件中重复的配置删掉
配置属性对应nacos配置文件名称规则
3)代码测试以下是否能够获取到配置信息
成功获取到了配置信息
总结
- 在Nacos中添加配置文件
- 在微服务中引入nacos的config依赖
- 在微服务中添加bootstrap.yml,配置nacos地址、当前环境、服务名称、文件后缀名。这些决定了程序启动时去nacos读取哪个文件
Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:
- 方式一:在@Value注入的变量所在类上添加注解@RefreshScope
方式二:使用@ConfigurationProperties注解
总结
Nacos配置更改后,微服务可以实现热更新,方式:
- 通过@Value注解注入,结合@RefreshScope来刷新
- 通过@ConfigurationProperties注入,自动刷新
注意事项:
- 不是所有的配置都适合放到配置中心,维护起来比较麻烦
- 建议将一些关键参数,需要运行时调整的参数放到nacos配置中心,一般都是自定义配置
应用场景:简单说明,有一个配置属性,它在开发、生产和测试等环境下的值是一样的,像这样的配置在每个配置文件里都去写一份,有些浪费,而且将来如果有改动,还要在每个配置文件里都去改,这样显然不合适,不管环境怎么变,这个配置都可以被加载,这就是多环境配置共享的需求了
微服务启动时会从nacos读取多个配置文件:
多种配置的优先级:
nacos中的配置优先本地配置
总结:
- [服务名]-[spring.profile.active].yaml,环境配置
- [服务名].yaml,默认配置,多环境共享
优先级:
1、[服务名]-[环境].yaml > [服务名].yaml > 本地配置
4、Nacos集群搭建1.集群结构图
其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。
我们计划的集群结构:
三个nacos节点的地址:
搭建集群的基本步骤:
2.搭建集群
- 搭建集群的基本步骤:
- 搭建数据库,初始化数据库表结构
- 下载nacos安装包
- 配置nacos
- 启动nacos集群
- nginx反向代理
2.1.初始化数据库
Nacos默认数据存储在内嵌数据库Derby中,不属于生产可用的数据库。
官方推荐的最佳实践是使用带有主从的高可用数据库集群,主从模式的高可用数据库
这里我们以单点的数据库为例来讲解。
首先新建一个数据库,命名为nacos,而后导入下面的SQL:
CREATE TABLE `config_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) DEFAULT NULL,
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
`c_desc` varchar(256) DEFAULT NULL,
`c_use` varchar(64) DEFAULT NULL,
`effect` varchar(64) DEFAULT NULL,
`type` varchar(64) DEFAULT NULL,
`c_schema` text,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_aggr */
/******************************************/
CREATE TABLE `config_info_aggr` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(255) NOT NULL COMMENT 'group_id',
`datum_id` varchar(255) NOT NULL COMMENT 'datum_id',
`content` longtext NOT NULL COMMENT '内容',
`gmt_modified` datetime NOT NULL COMMENT '修改时间',
`app_name` varchar(128) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_beta */
/******************************************/
CREATE TABLE `config_info_beta` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_info_tag */
/******************************************/
CREATE TABLE `config_info_tag` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`tag_id` varchar(128) NOT NULL COMMENT 'tag_id',
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL COMMENT 'content',
`md5` varchar(32) DEFAULT NULL COMMENT 'md5',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
`src_user` text COMMENT 'source user',
`src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = config_tags_relation */
/******************************************/
CREATE TABLE `config_tags_relation` (
`id` bigint(20) NOT NULL COMMENT 'id',
`tag_name` varchar(128) NOT NULL COMMENT 'tag_name',
`tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type',
`data_id` varchar(255) NOT NULL COMMENT 'data_id',
`group_id` varchar(128) NOT NULL COMMENT 'group_id',
`tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id',
`nid` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`nid`),
UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = group_capacity */
/******************************************/
CREATE TABLE `group_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_group_id` (`group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = his_config_info */
/******************************************/
CREATE TABLE `his_config_info` (
`id` bigint(64) unsigned NOT NULL,
`nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`data_id` varchar(255) NOT NULL,
`group_id` varchar(128) NOT NULL,
`app_name` varchar(128) DEFAULT NULL COMMENT 'app_name',
`content` longtext NOT NULL,
`md5` varchar(32) DEFAULT NULL,
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`src_user` text,
`src_ip` varchar(50) DEFAULT NULL,
`op_type` char(10) DEFAULT NULL,
`tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段',
PRIMARY KEY (`nid`),
KEY `idx_gmt_create` (`gmt_create`),
KEY `idx_gmt_modified` (`gmt_modified`),
KEY `idx_did` (`data_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造';
/******************************************/
/* 数据库全名 = nacos_config */
/* 表名称 = tenant_capacity */
/******************************************/
CREATE TABLE `tenant_capacity` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID',
`quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值',
`usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量',
`max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值',
`max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数',
`max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值',
`max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量',
`gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表';
CREATE TABLE `tenant_info` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
`kp` varchar(128) NOT NULL COMMENT 'kp',
`tenant_id` varchar(128) default '' COMMENT 'tenant_id',
`tenant_name` varchar(128) default '' COMMENT 'tenant_name',
`tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc',
`create_source` varchar(32) DEFAULT NULL COMMENT 'create_source',
`gmt_create` bigint(20) NOT NULL COMMENT '创建时间',
`gmt_modified` bigint(20) NOT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info';
CREATE TABLE `users` (
`username` varchar(50) NOT NULL PRIMARY KEY,
`password` varchar(500) NOT NULL,
`enabled` boolean NOT NULL
);
CREATE TABLE `roles` (
`username` varchar(50) NOT NULL,
`role` varchar(50) NOT NULL,
UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE
);
CREATE TABLE `permissions` (
`role` varchar(50) NOT NULL,
`resource` varchar(255) NOT NULL,
`action` varchar(8) NOT NULL,
UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE
);
INSERT INTO users (username, password, enabled) VALUES ('nacos', '$2a$10$EuWPZHzz32dJN7jexM34MOeYirDdFAZm2kuWj7VEOJhhZkDrxfvUu', TRUE);
INSERT INTO roles (username, role) VALUES ('nacos', 'ROLE_ADMIN');
2.2.下载nacos
nacos在GitHub上有下载地址:https://github.com/alibaba/nacos/tags,可以选择任意版本下载。
本例中才用1.4.1版本:
2.3.配置Nacos
将这个包解压到任意非中文目录下,如图:
目录说明:
- bin:启动脚本
- conf:配置文件
进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
然后修改application.properties文件,添加数据库配置
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123
2.4.启动
将nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的application.properties,
nacos1:
nacos2:
nacos3:
然后分别启动三个nacos节点:命令
2.5.nginx反向代理
修改conf/nginx.conf文件,配置如下:
java代码中application.yml文件配置如下:
而后在浏览器访问:http://localhost/nacos即可。
2.6.优化
- 实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置
- Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离
总结:
集群搭建步骤:
- 搭建MySQL集群并初始化数据库表
- 下载解压nacos
- 修改集群配置(节点信息)、数据库配置
- 分别启动多个nacos节点
- nginx反向代理
1-1、Feign介绍
RestTemplate方式调用存在的问题
先来看我们以前利用RestTemplate发起远程调用的代码:
这个请求是通过url地址指明访问的服务名称,请求路径,以及请求参数信息,请求方式和返回值类型,由RestTemplate发起请求,转成对应类型返回,这块代码已经在Ribbon的基础上做了优化的了,但是依然存在问题。
存在下面的问题:
- 代码可读性差,编程体验不统一
- 参数复杂URL难以维护
Feign的介绍:
Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题
1-2、Feign使用
使用步骤:
1、order-service引入依赖:
2、在order-service的启动类添加注解开启Feign的功能:@EnableFeignClients
3、编写Feign客户端:
主要是基于SpringMVC的注解来声明远程调用的信息,比如:
- 服务名称:userservice
- 请求方式:GET
- 请求路径:/user/{id}
- 请求参数:Long id
- 返回值类型:User
修改order-service:
发送请求测试,发现不仅远程调用成功,并且实现了负载均衡
查看pom依赖,说明feign已经集成了ribbon实现了负载均衡
总结:
- 引入依赖
- 添加@EnableFeignClients注解
- 编写FeignClient接口
- 使用FeignClient中定义的方法代替RestTemplate
springbooot虽然帮我们实现了自动装配,但它是允许我们覆盖默认配置的
Feign可以修改的配置如下(部分):
一般我们需要配置的就是日志级别
配置Feign日志有两种方式:
方式一:配置文件方式
1、全局生效:
2、局部生效:
方式二:java代码方式,需要先声明一个Bean:
1、全局生效:则把它放在@EnableFeignClients这个注解中
2、局部生效:则把它放在@FeignClient这个注解中
总结:
1、方式一是配置文件,feign.client.config.xxx.loggerLevel
- 如果xxx是default则代表全局
- 如果xxx是服务名称,例如userservice则代表某服务
2、方式二是java代码配置Logger.Level这个Bean
- 如果在@EnableFeignClients注解声明则代表全局
- 如果在@FeignClient注解中声明则代表某服务
Feign底层的客户端实现:
- URLConnection:默认实现,不支持连接池
- ApacheHttpClient:支持连接池
- OKHttp:支持连接池
因此优化Feign的性能主要包括:
- 使用连接池代替默认的URLConnection
- 日志级别,最好用basic或none
Feign添加HttpClient的支持
引入依赖:
配置连接池:
总结:
Feign的优化:
1、日志级别尽量用basic
2、使用HttpClient或OKHttp代替URLConnection
- 引入feign-httpClient依赖
- 配置文件开启httpClient功能,设置连接池参数
方式一(继承): 给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。
消费者的FeignClient:
提供者的controller
在消费者的FeignClient和提供者的controller中整个方法(除了名称)的声明是一样的,由于必须声明一样才能访问到,我们就可以做抽取,定义一个接口叫做UserAPI,这个接口将方法的声明写好了,那么消费者的FeignClient就可以直接继承,提供者的controller就可以直接实现这个接口,定义统一标准,这就是继承
但是这样做也有一定的问题,一般情况下服务和客户端之间不推荐去共享接口,因为它会造成紧耦合,都实现了相同的接口,在API层面都已经耦合了,所以它是紧耦合,一旦UserAPI发生改变,那么两边都要一起去修改。而且这种继承方案对springmvc不起作用,springmvc在声明GetMapping之外还有对参数的声明,而方法参数是继承不下来的,尽管存在缺点,但是相对来讲它遵循的面向契约编程的思想,在企业应用还是很多的
方式二(抽取): 将FeignClient抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用
user-service服务提供者写好接口后,每个需要调用的消费者都写UserClient,重复开发,有点浪费,所以消费者都不自己写UserClient,准备一个feign-api(独立项目模块)帮消费者写UserClient,也可以将User的实体类、feign的默认配置等等放在feign-api中,消费者在使用时,直接引用feign-api依赖,实现远程调用
但是也有缺点,就是在feign-api中将所有方法封装进来了,而order-service只需要其中的某一两个方法,而把所有方法全引进来了,有一些多余方法
总结:
Feign的最佳实践:
- 让controller和FeignClient继承同一接口
- 将FeignClient、POJO、Feign的默认配置都定义到一个项目中,供所有消费者使用
实现最佳实践方式二的步骤如下:
- 先创建一个module,命名为feign-api,然后引入feign的starter依赖
- 将order-service中编写的UserClient、User、DefaultFeignConfiguration都复制到feign-api项目中
- 在order-service中引入feign-api的依赖
- 修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包
- 重启测试
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。
方式一:指定FeignClient所在包
方式二:指定FeignClient字节码
八、Gateway服务网关1、网关作用介绍
网关功能:
- 身份认证和权限校验
- 服务路由、负载均衡
- 请求限流
网关的技术实现
在SpringCloud中网关的实现包括两种:
- gateway
- zuul
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能
2、网关搭建搭建网关服务的步骤:
1、创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖
2、编写路由配置及nacos地址
总结:
网关搭建步骤:
- 创建项目,引入nacos服务发现和gateway依赖
- 配置application.yml,包括服务基本信息、nacos地址、路由
路由配置包括:
- 路由id:路由的唯一标识
- 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
- 路由断言(predicates):判断路由的规则
- 路由过滤器(filters):对请求或响应做处理
- 我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
- 例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的
- 像这样的断言工厂在SpringCloudGateway还有十几个
Spring提供了11中基本的Predicate工厂:
springcloud官网:Spring Cloud Gateway
- PredicateFactory的作用:
读取用户定义的断言条件,对请求做出判断
4、路由过滤器GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:
给所有进入userservice的 请求添加一个请求头
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
在UserController中
结果:
配置默认过滤器,全局生效,所有微服务都将
5、全局过滤器GlobalFilter
全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要自己写代码实现。
定义方式是实现GlobalFilter接口
过滤器的执行顺序
- 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
- 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增
- 当过滤器的order值一样时,会按照defaultFilter > 路由过滤器 > GlobalFilter的顺序执行
跨域:域名不一致就是跨域,主要包括:域名不同、端口不同
跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题
解决方案:CORS
,
免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com