微服务治理:Eureka注册中心、Nacos注册中心、OpenFeign、网关Gateway、配置中心Nacos
Docker
异步通信:MQ技术、SpringAMQP、消费者限流
分布式搜索结果
微服务保护:流量控制、系统保护、熔断降级、服务授权
分布式事务:XA模式、TCC模式、AT模式、Saga模式
分布式缓存:数据持久化、Redis
多级缓存:多级缓存分级、Nginx缓存、Redis缓存、Canal数据同步
可靠消息服务:消息三方确认、惰性队列、延迟队列、镜像集群、仲裁队列
单体架构:所有业务功能集中在一个项目中开发,打包成一个包部署。优点:架构简单、部署成本低。缺点:耦合度高。
分布式架构:根据业务功能对系统进行拆分,每个业务模块作为单独模块开发,成为一个服务。优点:降低服务耦合,有利于服务升级拓展。缺点:要考虑(服务拆分粒度,服务集群地址如何维护、服务之间如何实现远程调用、服务健康状态如何感知)
微服务是一种经过良好架构设计的分布式架构方案,特征:
单一职责:微服务拆分粒度更小,每一个服务都对应唯一的业务能力,做到单一职责,避免重复业务开发
面向服务:微服务对外暴露业务接口
独立自治:团队独立、技术独立、数据独立、部署独立
强隔离性:服务调用做好隔离、容错、降级、避免出现级联问题
国内知名的微服务实现结构:SpringCloud、Dubbo、SpringCloudAlibaba
SpringCloud整合了:
注册中心:Eureka、Consul
服务远程调用:Feign(http协议)
配置中心:SpringCloudConfig
服务网关:SpringCloudGataway(主)、Zuul
服务监控和保护:Hystrix
SpringCloudAlibaba整合了:
注册中心:Eureka、Nacos
服务远程调用:Feign、Dubbo
配置中心:SpringCloudConfig、Nacos
服务网关:SpringCloudGataway(主)、Zuul
服务监控和保护:Sentinel
服务拆分注意事项
1、不同微服务,不要重复开发相同业务
2、微服务数据独立,不要访问其他微服务的数据库
3、将自己的服务暴露微接口,供其他微服务调用
发起http请求调用其他微服务的接口
SpringBoot提供了@RestTemplate注解
具体步骤
(写在配置类中)
@MapperScan("cn.itcast.order.mappper")
@RestTemplate
public class OrderApplication{
public static void main(String[] args){
SpringApplication.run(OrderApplication.class,args)
}
@Bean
public RestTemplate restTemplate(){
return new RestTemplate()
}
}
//首先自动注入RestTemplate
@Autowired
public RestTemplate restTemplate
//然后在具体业务代码中发送请求
String url="http://localhost:8080/user/"+order.getId();
User user=restTemplate.getForObject(url,User.class);//第二个参数是返回的类型,会将返回的json反编译成想要的类
上述demo存在问题:服务提供者不一定是在一个IP上,若有多个IP应该如何写呢?服务消费者符合获取服务提供者的地址信息?
服务提供者把自己的服务注册到Eureka-server(心跳机制),服务消费者找Eureka-server拉取服务,从几个服务提供者中挑一(负载均衡策略)发送http请求。
新建eureka-server项目module
1、依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>2.2.9.RELEASEversion>
dependency>
2、在启动类上加 @EnableEurakeServe 和@SpringBootApplication注解
3、配置文件yml中写(eureka本身也需要注册,方便以后eureka集群相互之间注册)
server:
port:10086 #服务端口
spring:
application: #eureka的服务名称
name: eurekaserver
eureka:
client:
service-url: #eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
注册user-service带EurekaServer步骤:
1、在user-service项目引入spring-cloud-starter-netflix-eureka-client的依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>2.2.9.RELEASEversion>
dependency>
2、在user-service项目application.yml文件编写配置
spring:
application:
name: userservive
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
注:同一服务多次启动:
1、(左下角)找到服务,右键选择“copy configuration”
2、在弹出窗口先改名字,然后点击“enviroment”,在VM potions中填写“-Dserver.Port=8082”//换一个端口才行,不然端口冲突
1、修改OrderService代码,修改访问的url路径,用服务名代替ip、端口
String url="http://userservice/user/"+order.getUserId()
2、在order-service项目的启动类OrderApplication中的RestTemplate添加 均衡负载注解
@Bean
@LoadBalanced//负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
上述的案例中,order-service发起请求“http://userservice/user/1”并不是真实的地址,会发送到Ribbon,再找Eureka-server返回服务列表到Ribbon,最后轮询一个服务提供者。
@LoadBalanced注解,说明这个请求要被Ribbon拦截处理。
原默认:ZoneAvoidanceRule,以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器金勋个分类,这个Zone可以理解为一个机房、一个机架等。然后再对同一Zone内的服务做轮询。
有两种调整方式
1、代码方式:在order-service项目中启动类OrderApplication类中,定义一个新的方法:
(这样的方式是全局的,只要是order-service发起的访问任何微服务,都会把负载均衡策略改为这样的)
@Bean
public IRule randomRule(){
return new RandomuRule();//改为随机负载
}
2、配置文件方式,在order-service项目的配置文件application.yml中添加新的配置:
(可以指定对某一个微服务的调用时均衡负载规则)
userservice: #指定服务名称
ribbon:
NFLoadBalancerRuleClassname: com.netflix.loadbalancer.RandomRule #负载均衡规则
Ribbon默认采用懒加载,即第一次访问时才会创建LoadBalanceClient,请求时间会很长,而饥饿加载则会在项目启动时创建,降低第一次访问的耗时。配置方式:
在order-service项目的配置文件application.yml中添加新的配置:
ribbon:
eager-load:
enabled: true #启用饥饿加载
clients: #指定饥饿加载的微服务
-userservice
-xxxservice
Nacos相比Eureka功能更加丰富,国内也更受欢迎。
github地址点左上角tags
下载后解压到非中文路径中,target里面是jar包,conf里面是配置文件(Nacos默认端口是8848),bin里是可执行文件。
在bin目录下的cmd窗口启动命令:startup.cmd -m standalone
登录默认账号密码都是nacos
1、在项目父工程中添加spring-cloud-alibaba的管理依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.6.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
2、注释掉order-service(服务消费者)和user-service(服务提供者)原来的Eureka依赖
3、添加nacos的客户端依赖(消费者、提供者都需要)
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
4、修改user-service和order-service中的application.yml文件,注释Eureka的地址,添加Nacos地址
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos服务端地址
5、启动并测试
(原本是两级:一层服务,一层实例)
在Nacos分级存储模型中,最上层是服务,往下是集群,最下是提供服务的实例。集群可以理解成 北京机房、上海机房等等。在服务调用时,尽可能调用本地集群。
默认服务是在集群:Default
修改服务提供者user-service的application.yml,添加内容
启动后,如果再修改并启动新的实例,则只会改变新的实例的集群信息。
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务器地址
discovery:
cluster-name: HZ #配置集群名称,可以是机房位置
想要实现 ”服务消费者调用服务提供者时,优先调用本地服务提供者“
1、需要给order-service设置集群配置,在其application.yml文件添加
spring:
cloud:
nacos:
server-addr: localhost:8848 #nacos 服务器地址
discovery:
cluster-name: HZ #配置集群名称,可以是机房位置
2、重启order-service
(这时order-service发起访问并不会优先访问同集群下的user-service,仍然采用的轮询)
3、再order-service中设置负载均衡的 IRule 为 NacosRule,这个规则优先寻找与自己同集群的服务:
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
4、重启order-service
实际部署中可能出现:服务器设备性能有差异,部分实例所在机器性能好,另一些较差,我们希望性能好的机器承担更多用户请求。
Nacos提供了权重配置来控制访问频率,权重越大访问频率越高。
可以网页上的Nacos控制台,服务列表中,点击详情进行修改。
Nacos中服务存储和数据存储的最外层都是一个名为nameSpace的东西,用来做最外层隔离
结构如下:NameSpace–Group–Service/Data–集群–实例
1、网页Nacos控制台(左侧),新建命名空间。填写新的命名空间信息,会生成一个命名空间ID
2、在order-service的application.yml文件添加:
增加的是namespace
spring:
datasource:
url: jdbc:mysql://localhost:3306/heima?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH # 上海
namespace: 492a7d5d-237b-46a1-a99a-fa8e98e4b0f9 # 命名空间,填ID
**结果:**这时候配置完了,order-service就访问不了user-service了。因此,想要服务能被访问,必须放到相同目录下。
nacos会把服务提供者划分成临时实例和非临时实例。
Nacos
临时实例(默认):心跳检测,检测不到就剔除
非临时实例:不做心跳检测,Nacos主动发请求询问,询问不到不会剔除,会标记为不健康
Eureka
心跳检测
Nacos配置非临时实例:
spring:
cloud:
nacos:
discovery:
ephemeral: flase #设置为非临时实例
Eurake:服务消费者去Eureka注册中心拉取(30秒一次)
Nacos:Nacos注册中心做推送
目的:统一修改配置、实现配置更改热更新
进入nacos控制台,点击左侧配置管理–配置列表,点击右侧➕,会弹出表单:
DataID是配置文件名称,不能叫application.yml,叫这个的话以后所有微服务都来找就分不清了。推荐:微服务名-环境,例如 userservise-dev.yaml
Group是分组,默认就好了。
配置内容: (不是放原来application.yml中的内容,因为并不是所有内容需要热更新)写开关类型的配置、模版类型,
点击发布
原:项目启动–读取本地配置文件–创建spring容器–加载bean
新:项目启动–读取nacos配置文件–读取本地配置文件–创建spring容器–加载bean
步骤:
1、引入Nacos配置管理客户端依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
2、在userservice中的resource目录中加一个bootstrap.yml文件,这个文件是引导文件,优先级比application.yml高。
服务名称+开发环境+后缀名就是我们在nacos控制台配的文件
spring:
application:
name: userservice # 服务名称
profiles:
active: dev #开发环境,这里是dev
cloud:
nacos:
server-addr: localhost:8848 # Nacos地址
config:
file-extension: yaml # 文件后缀名
Nacos中的配置文件变更后,微服务无需重启就可以感知。通过一下两种配置实现:
1、在@Value注入的变量所在的类上添加注解@RefreshScope
(在配置文件中的东西,在具体代码中用Value注解来取用)
2、使用@Configuration注解
这个用在:新建一个类专门用作属性加载。然后在这个类上加@Configuration注解。
@Data
@Configuration(prefix="patttern")//前缀名和变量名拼接之后,在配置文件中匹配上
public class PatternProperties{
private String format;
}
微服务启动时会从nacos读取多个配置文件:(都在nacos控制台)
[spring.application.name]-[spring.profiles.active].yaml
,例如:userservice-dev.yaml
[spring.application.name].yaml
,例如:userservice.yaml
而[spring.application.name].yaml
不包含环境,因此可以被多个环境共享。(环境发生改变时,第一个文件会改变,但是第二个不会)因此可以把共享的放在这个文件中。
在idea的运行日志可以看加载了那些yml配置文件
如果这些配置文件中有相同属性,会以 服务名-prifile.yaml>服务名称.yaml>本地 这个优先级。
将压缩包解压到任意非中文目录下,进入nacos的conf目录,修改配置文件cluster.conf.example,重命名为cluster.conf:
然后添加内容:
127.0.0.1:8845
127.0.0.1.8846
127.0.0.1.8847
然后修改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
将解压得到的nacos文件夹复制三份,分别命名为:nacos1、nacos2、nacos3
然后分别修改三个文件夹中的application.properties:
nacos1:
server.port=8845
nacos2:
server.port=8846
nacos3:
server.port=8847
分别启动三个nacos节点startup.cmd
解压到非中文目录下,修改conf/nginx.conf文件,配置如下:
upstream nacos-cluster {
server 127.0.0.1:8845;
server 127.0.0.1:8846;
server 127.0.0.1:8847;
}
server {
listen 80; #nacos端口
server_name localhost;
location /nacos {
proxy_pass http://nacos-cluster;
}
}
浏览器访问:http://localhost/nacos即可。
spring:
cloud:
nacos:
server-addr: localhost:80 # Nacos地址
RestTemplate方式存在的问题:
String url="http://userservice/user/"+order.getUserId();
User user=restTemplate.getForObject(url,User.class);
存在问题:
可读性差,编程体验不统一
复杂的URL难以维护(url的参数难写)
Feign介绍
Feign是一个声明式的http客户端,官方地址
其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。
Feign集成了ribbon,实现了负载均衡
① 引入依赖
② 添加@EnableFeignClients注解
③ 编写FeignClient接口
④ 使用FeignClient中定义的方法代替RestTemplate
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
<version>2.0.4.RELEASEversion>
dependency>
在服务调用者order-service的启动类傻姑娘添加 @EnableFeignClients
注解
在order-service中新建一个接口,内容如下:
package cn.itcast.order.client;
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
修改order-service中的OrderService类中的queryOrderById方法,使用Feign客户端代替RestTemplate:
@Autowired
private UserClient userClient;
@Autowired
private OrderMapper orderMapper;
public Order queryOrderById(Long orderId){
//查询订单
Order order=orderMapper.findById(orderId);
//利用Feign发起http请求
User user=userClient.findById(order.getUserId());
//封装user到order
order.setUser(user);
//返回
return order;
}
Feign可以支持很多的自定义配置,如下表所示:
类型 | 作用 | 说明 |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE、BASIC、HEADERS、FULL |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送 |
feign. Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign. Retryer | 失败重试机制 | 请求失败的重试机制,默认是没有,不过会使用Ribbon的重试 |
一般情况下,默认值就能满足我们使用,如果要自定义时,只需要创建自定义的@Bean覆盖默认Bean即可。
基于配置文件修改feign的日志级别可以针对单个服务:
feign:
client:
config:
userservice: # 针对某个微服务的配置
loggerLevel: FULL # 日志级别
也可以针对所有服务:
feign:
client:
config:
default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
loggerLevel: FULL # 日志级别
而日志的级别分为四种:
先声明一个类,然后声明一个Logger.Level的对象:
public class DefaultFeignConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC; // 日志级别为BASIC
}
}
如果要全局生效,将其放到启动类的@EnableFeignClients这个注解中:
@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration .class)
如果是局部生效,则把它放到对应的@FeignClient这个注解中:
@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration .class)
Feign底层发起http请求,依赖于其它的框架。其底层客户端实现包括:
•URLConnection:默认实现,不支持连接池
•Apache HttpClient :支持连接池
•OKHttp:支持连接池
性能优化主要分两点:
1、使用连接池代替默认的URLConnection。
2、日志级别,最好用basic或者none。
以Apache的HttpClient为例
服务消费者order-service的pom文件中引入Apache的HttpClient依赖:
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
在order-service的application.yml中添加配置:
feign:
client:
config:
default: # default全局的配置
loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 最大的连接数
max-connections-per-route: 50 # 每个路径的最大连接数