springcloud
项目代码地址:
gitee:https://gitee.com/yysimple/cloud-study.git
githup:https://github.com/yysimple/cloud-study.git
欢迎star、fork
maven的聚合工程(cloud2020)
dependencyManagement:
- 一般用在聚合工程的父工程中,用于同一版本号管理
- 如在父工程中定义了mysql的版本,在其他子工程中就不需要定义版本,对以后的版本升级就很方便,
如果哪个子项目需要自己的版本,可以自行定义版本 - 这个标签只是声明依赖,并不会实现引入,因此在子项目里需要自行引入,但可以不加上版本号 ga即可
- 且在子模块中不需要写gv,只需要写a即可
创建好父项目后 clean之后 install 中间跳过test 便可统一管理子模块的版本
cloud-api-commons 公共代码、模块等工程
将实体类提取出来,还有一些相关的公用依赖放在这个工程里面,然后install一下,在其他模块中就可以使用:
<dependency>
<groupId>com.jxkj.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
这样其他模块中就不需要重复写这些代码
cloud-eureka-server7001 开启eureka注册中心
主要是开启注册管理:
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com
client:
#false表示不向注册中心注册自己
register-with-eureka: false
#false 表示自己就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
defaultZone: http://eureka7002.com:7002/eureka/
这里的hostname:eureka7001.com 是修改了hosts文件 在里面做了域名映射,模拟不同的服务进行集群
然后在springboot启动类上加上@EnableEurekaServer这个注解即可,eureka的注册管理服务就开启了
在H版的cloud中,eureka的pom依赖也进行了修改,对应的注册中心是server:
<!-- euraka的服务端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
eureka的自我保护机制:在我们初次进入服务列表页面时,不会出现,但是过了一段时间再次刷新页面,会有红色的警告字体出现,
这是因为eureka的自我保护机制,eureka是通过客户端发送心跳来维持连接的,如果过了一段时间,客户端未给出任何反应,eureka不会
将该服务直接注销,而是会选择保留该微服务的信息。所以会给出警告,可以通过关闭自我保护机制实现消除警告(不推荐),这种机制属于
微服务CAP里面的Ap机制,保证其高可用。
在注册中心需要加入(eureka.instance:):
server:
# eureka的自我保护机制,默认为true
enable-self-preservation: false
# 过了客户端的等待时间后两秒后就关闭
eviction-interval-timer-in-ms: 2000
在客户端需要加入(eureka.instance:):
#客户端向注册中心间隔多久发送一次心跳,默认为30s
lease-renewal-interval-in-seconds: 30
# 客户端在不发送心跳后,注册中心需要等待的时间
lease-expiration-duration-in-seconds: 90
cloud-eureka-server7002 开启eureka注册中心
作为集群用,与7001几乎相同,yml文件里面的配置有点不同:
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com
client:
#false表示不向注册中心注册自己
register-with-eureka: false
#false 表示自己就是注册中心,职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
# 将7001注册到7002中
service-url:
defaultZone: http://eureka7001.com:7001/eureka/
这样做,就相当于开启了两个注册中心,之后服务就可以像这两个注册中心进行注册
cloud-paovider-payment8001 支付微服务
- 创建典型的mvc工程
- 两个方法
- 创建流水账
- 通过id查询
将单个项目跑通后,将该服务注册进eureka的集群只需加入依赖,加上注解,然后修改yml文件就可以完成服务注册:
这里是服务注册者,所以是客户端:
<!-- euraka的客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
引入依赖之后,只需要在主启动类上加上@EnableEurekaClient这个注解就可以了
然后在yml文件里面加上eureka的一些配置即可完成服务注册:
server:
port: 8001
# 服务名称
spring:
application:
name: cloud-payment-service
datasource:
url: jdbc:mysql://localhost:3306/db_cloud_202001?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&verifyServerCertificate=false&useSSL=false
username: root
password: 123456
driver-class-name: org.gjt.mm.mysql.Driver
type: com.alibaba.druid.pool.DruidDataSource
eureka:
instance:
# 修改eureka服务的status名称
instance-id: payment-8001
# 访问路劲可以显示当前服务的ip地址
prefer-ip-address: true
client:
# 入驻eureka
register-with-eureka: true
# 去获取已注册的服务,在集群的时候,每个微服务都需要设置为true,配合ribbon进行负载均衡
fetch-registry: true
# 将自己注册进的 eureka服务管理中心 的url
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#Mybatis配置
mybatis:
mapper-locations: classpath:mybatis/mapper/*.xml
type-aliases-package: com.jxkj.springcloud
这里需要注意的是:
- instance-id: payment-8001 这个是在查询注册了哪些服务(http://eureka7001.com:7001)里面的那个Status的值
- defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka:这个是向哪些注册中心注入自己
- spring.application.name: cloud-payment-service 这个是集群服务里对外暴露的服务名
服务发现:通过在启动类上加上注解@EnableDiscoveryClient,将DiscoveryClient注入,便可以发现已注册的服务
@RestController
@Slf4j
public class PaymentController {
// 注入服务发现客户端
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping("/payment/discovery")
public Object getDiscovery(){
// 获取到所有的已注册服务,(payment、Order)
List<String> services = discoveryClient.getServices();
for (String service : services) {
log.info("**** service: " + service);
}
// 这里通过服务名也既服务id去查询该集群里面有多少个微服务
List<ServiceInstance> serviceInstances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance serviceInstance : serviceInstances) {
log.info(serviceInstance.getInstanceId() + "\t" + serviceInstance.getHost() + "\t" + serviceInstance.getUri());
}
return this.discoveryClient;
}
}
cloud-paovider-payment8002 支付微服务2
其功能代码与8001是一样的,只需要将instance-id: payment-8001 改成8002即可
用于服务的负载均衡
cloud-consumer-order80 订单服务
首先需要将RestTemplate注入到spring容器内,然后调用payment服务里面的controller
通过getForObject时,不能传参数,需要在后面拼接 +id @PathVariable可以不加,默认会在请求路径上面加上
加上@PathVariable注解,可直接在后面路径上/id
@GetMapping("/consumer/payment/getPaymentById")
public CommentResult<Payment> getPaymentById(Long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/getPaymentById?id=" + id, CommentResult.class);
}
@GetMapping("/consumer/payment/getPaymentById/{id}")
public CommentResult<Payment> getPaymentById(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL + "/payment/getPaymentById/" + id, CommentResult.class);
}
再调用其他服务是,如果需要传的是一个对象,在被调用(provider)里面需要加上@RequestBody注解,才能正常插入数据库,
在本服务内不需要加,默认是加上的
在进行远程调用的时候,如果是单机单服务调用的话就是只需要通过指定路径就可访问:
public static final String PAYMENT_URL = “http://localhost:8001”;
如果是访问多个微服务的话需要在配置类中加上@LoadBalanced,以完成负载均衡的作用,不加直接调用的时候会报错
且访问路径需要改成微服务的名称:
public static final String PAYMENT_URL = “http://CLOUD-PAYMENT-SERVICE”;
@Configuration
public class ApplicationContextConfig {
/**
* 将 RestTemplate 注入到Spring的容器中
* @LoadBalanced: 开启负载均衡
* @return
*/
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
这里的负载均衡,默认的是轮询的算法
cloud-consumerzk-order80 将订单服务注册到zookeeper中心
首先需要在自己的本地一台虚拟机上安装zookeeper,并启动server端(安装很简单,然后改一下数据保存的路劲,方便自己找)
在pom中引入:
<!-- springboot整合zookeepr客户端 -->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<version>2.1.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
我的zookeeper使用的是3.4.14版本的,所以会与slf4j-log4j12这个jar冲突,排除一下依赖,而引入与springcloud整合的
zookeeper依赖需要排除一个beta的一个zookeeper的jar包
然后在启动类上加上@EnableDiscoveryClient这个注解
修改yml文件:
server:
port: 80
spring:
application:
name: cloud-comsumer-order
cloud:
zookeeper:
connect-string: 192.168.109.133:2181
然后启动便可在zookeeper上注册了
cloud-provider-paynebt8004 zookeeper上注册的微服务提供者
与80在zookeeper上的配置差不多没有什么大的区别
集群的话就是与eureka的差不多:
cloud:
zookeeper:
# 只需要在这里多家几个zookeeper的主机就可以了
connect-string: 192.168.109.133:2181
cloud-provider-consul-payment8006 将服务注入到consul注册中心
将consul作为注册中心也很简单,与zookeeper相似,consul可以在linux,也可以在windows上,在windows上是一个exe文件,
先配置一下consul的全局环境变量,然后使用命令:
consul agent -dev
在linux下:
# 先解压
upzip consul.tar.gz
# 在启动(后面的主机ip)
./consul agent -dev -ui -node=consul-dev -client=192.168.109.133
然后pom文件中引入:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
在启动类上加上@EnableDiscoveryClient即可,
修改yml文件:
server:
port: 8006
spring:
application:
name: consul-cloud-provider-payment
# consul 服务注册中心
cloud:
consul:
# 这里是本地,可以选择放在linux上
host: localhost
port: 8500
discovery:
# 注册的服务名
service-name: ${spring.application.name}
这样便将consul作为了服务的注册中心了,启动之后,在本地(localhost:8500)就可以看到起ui了
cloud-consumer-consul-order80
跟微服务提供者(8006)中的配置几乎一样,只需要简单的修改一下yml即可,其他的部分就跟其他的微服务消费者的代码几乎相同
将**public static final String INVOKE_URL = “http://consul-cloud-provider-payment”;/**改成yml文件中
对应的服务名即可
CAP原则:
C: Consistency(强一致性)
A: Availability(高可用性)
P: Partition tolerance(分区容错性)
在微服务中,p是必须的,而A、C不可兼得,而关系型数据库CA
CA:mysql
AP:eureka
CP:zookeeper/consul/redis/mongodb
Ribbon
在eureka的依赖中,会自带引入ribbon的依赖,无需自己另外添加,当然也可以添加:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Ribbon-LB是属于进程内的LB,而相对于Nginx则属于集中式的LB(第一道门槛)
想用Ribbon实现负载均衡,只需要在RestTemplate的Bean注入上加上一个注解@LoadBalanced即可
Ribbon实现负载均衡有很多种算法:
通过IRule,可以查看到其所有实现算法,默认是轮询算法
修改其默认算法,需要在@ComponentScan扫描之外的包,要不然就不能定制自己的算法,会被所有Ribbon客户端共享
@Configuration
public class MyRule {
@Bean
public IRule iRule(){
// 设置成随机算法
return new RandomRule();
}
}
然后在启动类上加上:@RibbonClient(name = “cloud-payment-service”, configuration = MyRule.class) 即可使用
这些都是ribbon自带的算法,也可以定制自己的算法
再测试的时候发现一个bug(自己电脑上):两个工程8001和8002使用相同代码的DAO层时,在实现轮询算法时,第一个工程
可以找到其对应的 xml sql映射文件,但是第二个工程,就会出现找不到对应的xml的情况,这个时候只需将其中的
一个工程里面的dao层接口的名字修改一下即可。
cloud-consumer-feign-order80 使用feign直接调用接口
feign其实就是对ribbon + RestTemplate进行封装,将微服务提供的接口(8001/8002的controller)封装到需要使用这些的接口的工程中
实现起来也很简单,首先是在pom中加入:
<!-- openfeign的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
因为feign也需要结合eureka使用,所以eureka的依赖也需引入
然后就是在yml里面配置,跟其他的注册差不多:
server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
ribbon:
# 指建立连接后从服务器读取到资源所用的时间
ReadTimeout: 5000
# 指的是建立连接所用的时间,适用于网络正常的情况下,两端连接所用的时间
ConnectTimeout: 5000
logging:
level:
# feign日志以什么级别监控那些借口
com.jxkj.springcloud.service.PaymentFeignService: debug
上面还有延时请求和日志的配置
然后就在主启动类上加上@EnableFeignClients启用feign
然后自定义一个service接口:
@Component
@FeignClient(value = "cloud-payment-service")
public interface PaymentFeignService {
/**
* 将url注册到feign中, 也就是封装restTemplate
* @param id
* @return
*/
@GetMapping("/payment/getPaymentById")
CommentResult<Payment> getPaymentById(@RequestParam("id") Long id);
/**
* 模拟延时操作
* @return
*/
@GetMapping("/payment/feign/timeout")
String paymentFeignTimeout();
}
这里RequestParam必须加上,如果是用路劲上直接/id则需要加上@PathVariable注解
@FeignClient(value = “cloud-payment-service”):
这个是你要调用哪个微服务里面的接口,该接口里面的方法名最好与提供接口(8001/8002)里面的名字一致,
弄完这些只需要在该工程的controller里面跟调用普通的业务层一样: return paymentFeignService.getPaymentById(id);
日志的话除了上面的yml里面的配置,还需要定义一个配置类:
@Configuration
public class FeignConfig {
/**
* 开启feign的详细日志
* @return
*/
@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
这样在控制台就可以监控这些使用的接口,会以debug的方式打印
feign 目前已经停更维护,所以使用的依赖是 openfeign 但是功能一致
cloud-provider-hystrix-payment8008 服务端熔断降级
服务降级:为了不导致因为一个服务请求时间过长,或者出现故障而一直占用资源,导致整个系统雪崩,所以需要对这些bug或者突发情况进行处理
所以引入服务降级和熔断
服务熔断:类比保险丝,拉闸限电,然后在调用降级方法并返回友好提示 (也是降级的一种),有三种状态,开,关,半开。
熔断机制概述:熔断机制是应对雪崩效应的一种微服务链路保护机制。当某个服务出错或者不可用或者响应时间过长,会进行服务降级,
进而熔断该节点微服务的调用,快速返回错误信息,当检测该节点微服务调用响应正常后,恢复调用链路。
服务降级::
也是同样步骤,pom引入(同时需要引入eureka的):
<!--Hystrix依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
然后yml里面的内容跟其他的服务提供差不多
然后在启动类加上@EnableCircuitBreaker
然后就是在业务类上加上对应的注解,以及对应的降级处理:
@Service
public class PaymentHystrixService {
/**
* 正常访问的方法
* @param id
* @return
*/
public String paymentInfoOk(Integer id){
return "线程池==:" + Thread.currentThread().getName() + "paymentInfoOk_id==: " + id + "\t" + "O(∩_∩)O哈哈~";
}
/**
* 模拟延时
* @HystrixCommand: 用来做服务请求超时的降级,出现问题通过fallbackMethod 来指定后续处理的方法
* commandProperties: 用来限制请求时间,超过就降级
* @param id
* @return
*/
@HystrixCommand(fallbackMethod = "paymentTimeoutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
})
public String paymentTimeout(Integer id){
// 程序暂停3s, 模拟大的业务,超过3s以上的业务
int timeout = 5;
try {
TimeUnit.SECONDS.sleep(timeout);
}catch (InterruptedException e){
e.printStackTrace();
}
return "线程池==:" + Thread.currentThread().getName() + "paymentTimeout_id==: " + id + "\t" + "O(∩_∩)O哈哈~ == 耗时" + timeout +"秒";
}
public String paymentTimeoutHandler(Integer id){
return "线程池==:" + Thread.currentThread().getName() + "paymentTimeoutHandler==: " + id + "┭┮﹏┭┮ == 耗时";
}
}
@HystrixCommand(fallbackMethod = “paymentTimeoutHandler”, commandProperties = {
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”, value = “3000”):
这个的意思是:该服务最长请求时间只能是3s,超过就会自动降级,会交由指定的方法进行处理
这里是交由:paymentTimeoutHandler这个方法进行处理,然后去测试,成功降级
出现什么异常:10/0的这种异常,也会熔断降级
服务熔断:
@Service
public class PaymentHystrixService {
/**
* 服务熔断:====
*/
@HystrixCommand(fallbackMethod = "paymentCircuitBreakerFallBack", commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 是否开启短路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "5"), // 请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 时间窗口期
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60") // 失败率达到什么概率跳闸
})
public String paymentCircuitBreaker(@RequestParam("id") Integer id){
if (id < 0) {
throw new RuntimeException("******* id不能为负数 ******");
}
String serialNumber = IdUtil.simpleUUID();
return Thread.currentThread().getName() + "\t" + "调用成功,流水号: " + serialNumber;
}
public String paymentCircuitBreakerFallBack(@RequestParam("id") Integer id){
return "id 不能为负数,请稍后再试, o(╥﹏╥)o ~~ id: " + id;
}
}
在访问的时候,id大于0的表示正确,小于0的表示错误,当小于0的次数也既失败次数大于6次(总共测试10次,要在10s内完成,代码里面的配置),
那服务会进行熔断,在下次访问正确的请求,也还是会提示报错信息,当多次成功请求之后,又会恢复正常。
可查阅官网,还有很多关于熔断的配置。
cloud-consumer-feign-hystrix-order80 客户端的服务降级
客户端的降级跟服务端差不多(该项目已经引入过hystrix的相关依赖)
只需要在yaml中开启降级熔断支持:
# 开启客户端的服务降级功能
feign:
hystrix:
enabled: true
然后在启动类上加上注解@EnableHystrix
在代码里面配置降级处理:
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentGlobalTimeoutHandler")
public class OrderHystrixController {
@Autowired
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/paymentInfoOk")
public String paymentInfoOk(@RequestParam("id") Integer id){
return paymentHystrixService.paymentInfoOk(id);
}
@GetMapping("/consumer/payment/hystrix/paymentTimeout")
@HystrixCommand(fallbackMethod = "paymentTimeoutHandler", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000")
})
public String paymentTimeout(@RequestParam("id") Integer id){
return paymentHystrixService.paymentTimeout(id);
}
public String paymentTimeoutHandler(@RequestParam("id") Integer id){
return "我是消费端的80接口,对方的支付系统繁忙,请稍后再试...┭┮﹏┭┮...";
}
/**
* 全局降级处理
* @return
*/
public String paymentGlobalTimeoutHandler(){
return "Global全局降级处理,请稍后再试.../(⊙︿⊙)/";
}
}
首先是:@HystrixCommand(fallbackMethod = “paymentTimeoutHandler”, commandProperties = {
@HystrixProperty(name = “execution.isolation.thread.timeoutInMilliseconds”, value = “2000”)
}):
这个跟服务端的意思是一样的,只需要指定其处理方法即可做到降级操作,但是这样会有个问题,一个方法一个处理,会造成代码耦合度过高所以开启全局配置
@DefaultProperties(defaultFallback = “paymentGlobalTimeoutHandler”):设置这个注解,然后在需要进行降级处理的方法上加上
@HystrixCommand 这个注解,如不指定具体处理方法,就会走默认的全局降级处理,这样还是会存在 处理代码和业务代码放在一块所以可以找到这些方法的源头(即调用的微服务):
@FeignClient(value = “hystrix-cloud-payment-service”, fallback = PaymentHystrixServiceImpl.class)
只需要实现public class PaymentHystrixServiceImpl implements PaymentHystrixService,然后在feign的注解里面:
加上fallback = PaymentHystrixServiceImpl.class 即可指定处理的类在哪里,即可做到一对一处理,也可以将处理的代码抽离
@Component
public class PaymentHystrixServiceImpl implements PaymentHystrixService {
@Override
public String paymentInfoOk(Integer id) {
return "PaymentHystrixServiceImpl -- fallback -- paymentInfoOk";
}
@Override
public String paymentTimeout(Integer id) {
return "PaymentHystrixServiceImpl -- fallback -- paymentTimeout";
}
}
cloud-consumer-hystrix-dashboard9001 监控面板
这个监控面板可以监控每个微服务里面的每个请求,可以监控请求的状态 成功与失败的次数统计和 曲线图等
pom引入:
<!--Hystrix依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
注意的是,actuator这个依赖是图形界面化的依赖,必须引入,才可以在界面上看到该服务
然后yml里面只需要指定端口号就行 这里是 9001,之后再启动类上加上@EnableHystrixDashboard注解 开启监控配置
我们这里是对8008 服务进行监控,所以需要对8008进行部分修改(因为springcloud的坑):
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentHystrixApplication8008 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixApplication8008.class, args);
}
/**
* 此配置是为了监控服务而配置,与服务本身无关,springcloud升级后的坑
* ServletRegistrationBean是因为springboot里面默认路径不是“/hystrix.stream”
* @return
*/
@Bean
public ServletRegistrationBean getServlet(){
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
}
然后访问 localhost:9001/hystrix 可以看到网站 ,然后在输入 http://localhost:8008/hystrix.stream 即可对8008服务进行监控
cloud-gateway-gateway9527 网关配置
zuul 1.x 已经停更, zuul 2.x 因为Netflix公司自身的原因,一直没有出,所以springcloud自己出了一套 gateway
网关的作用是在请求前进行一次路由,也既请求转发,还可以附带负载均衡作用
首先引入pom:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
启动类上需要加上Eureka的注解,因为网关也需要注册到注册中心,可以选择consul和zookeeper
然后就是在yml中加入相关的配置:
server:
port: 9527
# 服务名称
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
# 开启从注册注册中心动态创建路由的功能,利用微服务名进行路由
enabled: true
routes:
# 路由的id,没有固定要求,但要唯一,建议配合服务名
- id: payment_routh01
# 匹配后提供服务的路由地址
uri: http://localhost:8001
# 断言,路径匹配的进行路由,如果路径在对应的微服务里真的存在,返回就是true、就可以进行路由转发
predicates:
- Path=/payment/getPaymentById/**
- id: payment_routh02
# uri: http://localhost:8001
# 开启动态路由
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/**
# 配置在什么时候可以访问这个接口 还有 before between
#- After=2020-03-19T18:02:38.387+08:00[Asia/Shanghai]
# 配置Cookie curl http://localhost:9527/payment/lb --cookie "username=wcx" 需要带上cookie访问
#- Cookie=username,wcx
# 请求头要有 Header=X-Request-Id 属性并且值为整数的正则表达式
# curl http://localhost:9527/payment/lb --cookie "username=wcx" -H "X-Request-Id:123"
#- Header=X-Request-Id,\d+
eureka:
instance:
# 修改eureka服务的status名称
instance-id: cloud-gateway-9527
# 访问路劲可以显示当前服务的ip地址
prefer-ip-address: true
hostname: cloud-gateway-service
client:
# 入驻eureka
register-with-eureka: true
# 去获取已注册的服务,在集群的时候,每个微服务都需要设置为true,配合ribbon进行负载均衡
fetch-registry: true
# 将自己注册进的 eureka服务管理中心 的url
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
配置文件里面很详细的介绍了各个配置的作用,最主要的就是gateway里面的相关配置,其他相关配置可以查阅官网
再测试断言的cookie和请求头的时候,可以选择在cmd里面使用curl进行测试
这是使用配置文件实现路由转发,还有一种就是使用编码的方式,这里是对百度的新闻网页进行转发:
@Configuration
public class GatewayRouterConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder){
RouteLocatorBuilder.Builder routes = builder.routes();
// 进行路由转发
routes.route("path-baidu-guonei", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
routes.route("path-baidu-guoji", r -> r.path("/guoji").uri("http://news.baidu.com/guoji")).build();
return routes.build();
}
}
这样做跟nginx做反向代理很像,访问9527端口便可以跳转指定的网站
最后便是网关的过滤功能,只需要实现两个接口:GlobalFilter, Ordered 实现他们的方法便可以进行拦截,当然也可以在yml中配置
可以参阅官网
@Component
@Slf4j
public class MyLogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("***** come in MyLogGatewayFilter: " + new Date().toString());
String username = exchange.getRequest().getQueryParams().getFirst("username");
if (username == null) {
log.info("=========== 用户名不合法 o(╥﹏╥)o =========");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
这样在访问9527这个端口的请求的时候就需要在请求路径后面加上username,才能进行正常的访问
zuul 1.x 和 gateway: zuul使用的阻塞的io进行网关配置,而gateway则是非阻塞io,参考netty的思想做的,所以在现在非阻塞io几乎
是必备的东西,所以zuul不是老项目几乎可以不用了。
cloud-config-center-3344 微服务配置中心
配置中心的目的是为了解决,在多个微服务里面的.yml文件内容存在相同的情况,使得其能够更好的维护
这里我选择的是 gitee 为放置配置文件的远程仓库,也可以使用githup(不过很卡,有时候)
在idea中需要下载插件 gitee 然后在settings里面 Version Control里面的 gitee 中登录即可
这个很重要,如果不配置需要在yml中加入你的用户名和密码
首先需要引入pom依赖:
<!-- springcloud 的配置中心依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
这里引入的是服务端,再配置yml文件:
server:
port: 3344
spring:
application:
name: cloud-config-cneter
cloud:
config:
server:
git:
uri: https://gitee.com/yysimple/springcloud-config-atguigu
# 搜索目录
search-paths:
- springcloud-config
# 读取什么分支
label: master
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
这个配置中心也需要注册进注册中心,这里用的是eureka的注册中心
然后在主启动类上加上 @EnableConfigServer注解,开启配置中心管理,启动项目
http://config3344.com:3344/master/config-dev.yml (这里我在hosts里面做了域名映射)
这样便可以从gitee上访问到对应的文件内容,并且是以yml里面格式打印
这里采用的是 /label/{name}-{xxx}.yml的格式
- name一般就叫config
- xxx一般就分为 dev prod test等
还有其他三种配置方法可以进行访问,而且返回值也不太相同,可参阅官网
cloud-config-client 配置中心客户端的配置
在客户端里面,不会直接去访问gitee里面的文件,而是通过配置中心去获取内容
这里的pom依赖有点改变
<!-- springcloud 的客户端配置依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
这里引入的是客户端的依赖
然后就是yml的配置也有所不同:
配置文件bootstrap.yml:这里使用的是这个文件,这是系统级别的配置文件 ,application.yml是用户级别的配置,
为了让其更好的去加载配置中心里面的配置,所以采用这种文件
server:
port: 3355
spring:
application:
name: config-client
cloud:
config:
# 分支名称
label: master
# 配置文件名字
name: config
# 读取后缀名
profile: dev
# 配置中心服务端地址
uri: http://localhost:3344
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
#暴露监控点
management:
endpoints:
web:
exposure:
include: "*"
客户端采用的启动类的注解还是@EnableEurekaClient
为了方便测试,需要一个业务类:
@RestController
@Slf4j
@RefreshScope
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;
@GetMapping("/getConfigInfo")
public String getConfigInfo(){
return configInfo;
}
}
这样便可以通过 http://localhost:3355//getConfigInfo 便可以得到对应分支上的对应文件里面的内容
但是这样存在一个问题,当在gitee上修改文件内容是,配置中心可以拿到最新的数据(3344),但是客户端(3355)
则需要重启才能得到最新的数据,所以我们需要在进行配置:
- 在yml里面加入暴露监控点的配置
- 在controller里面加上@RefreshScope这个注解
- 修改之后,通过命令的方式刷新(不需要重启微服务)(curl -X POST http://localhost:3355/actuator/refresh)
这样便可以成功访问到最新的数据,但还是有问题,如果文件过多,则需要输入的命令也过多,所以要引入 bus
cloud-config-client-3366 微服务配置客户端
为了更好的演示,一次刷新,其他都刷新,所以新建 3366 工程
跟3355 配置几乎一样,只需要修改一点点内容,加入bus和mq的依赖,3344总配置中心 3355都需要加上:
<!-- 添加总线、RabbitMq的支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
然后就是修改yml里面的内容:
3344里面需要加入:
# rabbitmq的相关配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# 暴露bus刷新的端点,跟rabbitmq的刷新有关
management:
endpoints:
web:
exposure:
include: 'bus-refresh'
在3355和3366下要加上:
# rabbitmq的相关配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
这里有点需要注意的是:在本机上访问 rabbitmq时 端口是 15672 ,在程序里的配置是 5672
因为web里面的ui和java中的端口连接不一样
然后修改gitee上的版本号,通过:
curl -X POST http://localhost:3344/actuator/bus-refresh
然后就可以全局刷新,还有一个局部的刷新:
curl -X POST http://localhost:3344/actuator/bus-refresh/{微服务名 + 端口号}(config-client)刷新
cloud-stream-rabbitmq-provider 整合中间件的提供方(发送消息方)
由于市面上存在很多中间件,而springcloud推出stream作为中间件的统一处理,有四个概念:
- 首先是发送消息
- Source、Sink 消息发送者和消息接受者
- channel管道,在代码里就是MessageChannel
- 中间件
还是那几个步骤,首先是pom引入:
<!-- rabbitMq的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
这是是使用rabbitmq作为中间件,若需要使用其他的,则只需要修改rabbit 比如 kafka
然后就是yml配置,很多:
server:
port: 8801
spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的命名,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
tabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
output: #这个名字是一个通用的名称
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,文本则为 text/plain
binder: defaultRabbit #设置要绑定的消息服务的具体设置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
lease-renewal-interval-in-seconds: 2 #心跳时间间隔(默认30秒)
lease-expiration-duration-in-seconds: 5 #超时间隔(默认90秒)
instance-id: send-8801.com #在信息列表时显示主机名称
prefer-ip-address: true #访问路径变为ip地址
上面都有很详细的注解
写了一个业务接口,作为发送消息业务:
package com.jxkj.springcloud.service;
public interface IMessageProviderService {
/**
* 消息提供者
* @return
*/
String send();
}
实现类:
package com.jxkj.springcloud.service.impl;
import org.springframework.cloud.stream.messaging.Source;
@EnableBinding(Source.class)
public class IMessageProviderServiceImpl implements IMessageProviderService {
/**
* 消息发送管道 ------zhuzhzuzhuzhzuz: 这里只能写 output 要不然会报错
*/
@Resource
private MessageChannel output;
@Override
public String send() {
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("**** serial ****: " + serial);
return serial;
}
}
output: 这里需要注意的是,MessageChannel 后面的名字 只能写 output(我试了很多,但好像只有这个才行,汇报只要一个bean 却有三个的错误)
还有就是 Source包不能导错, 这里是发送者
然后就是controller:
@RestController
public class SendMessageController {
@Resource
private IMessageProviderService iMessageProviderService;
@GetMapping("/sendMessage")
public String sendMessage(){
return iMessageProviderService.send();
}
}
然后登陆rabbitmq(rabbitmq不说了,自行下载安装包,配置好环境,一直下一步就行),便可以查看到你自己定义的Exchange,就是在yml中配置的名字
然后这就成功整合了
cloud-stream-rabbitmq-consumer8802/8803 中间件消息接收方,两个工程一样 除端口等一些配置外
pom的引入跟提供方是一样的
yml文件里存在一些修改:
server:
port: 8802
spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: #在此处配置要绑定的rabbitmq的服务信息
defaultRabbit: #表示定义的命名,用于binding整合
type: rabbit #消息组件类型
environment: #设置rabbitmq的相关环境配置
spring:
tabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: #服务的整合处理
input: #这个名字是一个通用的名称
destination: studyExchange #表示要使用的Exchange名称定义
content-type: application/json #设置消息类型,本次为json,文本则为 text/plain
binder: defaultRabbit #设置要绑定的消息服务的具体设置
group: rabbit-a # 分组,持久化都是在这里配置
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
lease-renewal-interval-in-seconds: 2 #心跳时间间隔(默认30秒)
lease-expiration-duration-in-seconds: 5 #超时间隔(默认90秒)
instance-id: receive-8802.com #在信息列表时显示主机名称
prefer-ip-address: true #访问路径变为ip地址
这里修改的就是服务名,端口号,然后就是把output改成input,这就是提供方和接收方在配置上的一点点不同
如果后面不配置 group: rabbit-a 那么默认会将两个相同的功能服务分成不同的组,这样会造成重复消费,就是 提供方值给相同功能的两个接收方发送了
四条消息,但是在两个服务端内确各自都接收到了四条,这样就重复了,都是相同功能的微服务, 所以需要帮他们设置成同一个组,这样就会每个服务只会接收到
两条消息,这个也将持久化做了。
持久化: 比如8001发了4条消息(这个时候把8002的group配置去掉,就是8002 8003是两个不用的组),然后8002、8003这段时间突然宕机了,然后
8002、8003都重启了一下,发现8002没有接收到消息,而8003接收到了4条消息,这就是持久化,也只需要加上group即可。
消费端的代码,需要加上Sink 和 @StreamListener这个注解:
@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;
/**
* 前面提供的是String类型的数据所以 <String>
* @param message
*/
@StreamListener(Sink.INPUT)
public void input(Message<String> message){
// 消息提供方使用的是withPayload,所以这里使用getPayload
System.out.println("消费者1号,------>接收到消息:" + message.getPayload() + "\t port:" + serverPort);
}
}
sleuth 和 zinpkin的使用
使用这两个可以追踪到服务之间的调用
这里采用的是eureka的8001 8002 80 7001 7002 作为测试,7001 7002不需要修改
8001 8002 80里面修改pom:
<!-- 引入zipkin、sleuth依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
然后是yml:
spring:
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
# 采样率介于0 到 1之间, 1表示全部采集
probability: 1
然后进入 http://localhost:9411 查看最近的服务间的调用
cloud-alibaba-config-nacos-client3377 nocos的配置中心
nacos是阿里推出的一款注册中心,属于AP,但是也可以切换成CP
首先还是一样pom文件:
<!-- 配置中心的依赖 -->
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- nacos依赖的引入 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
</dependencies>
然后yaml文件:
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
# 配置nacos地址
server-addr: localhost:8848
config:
# 配置中心的地址
server-addr: localhost:8848
# 指定配置文件格式,相当于gitee上的那个文件
file-extension: yaml
# 分组
group: DEV_GROUP
# 命名空间
namespace: 4aca4479-cf8e-4ce2-b00d-9742e225a386
# 消费者将去访问微服务的名称
service-url:
nacos-user-service: http://nacos-cloud-payment-provider
# ${prefix}-${spring.profile.active}.${file-extension}
这里的配置都不难,官网上都很详细
这里需要去下载一个nacos的windows版,zip文件,下载下来直接进入bin目录下,双击startup.cmd便可以启动配置中心
很简单,然后去访问(http://localhost:8848/nacos)便可以进入ui界面,可以看到自己的微服务名已经注册进入了
@GetMapping("/config/info"):访问这个可以获取到对应的配置文件信息(这里注意的是需要.yaml),这里相较于使用springcloud-config和bus总线
作为加载配置文件,nacos是可以自动的刷新文件里面的内容,而bus和中间件需要去手动刷新,本地启动或者linux服务器上启动nacos,然后在yaml里面加点配置
便可以完成注册,不需要任何新建工程
其他配置之后跟后面的工程是联系的
cloud-alibaba-provider-payment9001/9002 服务提供者
很简单,跟eureka差不多,只是配置中心变了
这里只需要引入nacos的依赖便可:
<!-- nacos依赖的引入 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
然后就是yaml的配置:
server:
port: 9001
spring:
application:
name: nacos-cloud-payment-provider
cloud:
# 加入nacos的配置
nacos:
discovery:
# 配置nacos地址
server-addr: localhost:8848
# 暴露服务信息
management:
endpoints:
web:
exposure:
include: "*"
然后启动两个服务,在nacos的ui界面中就可以找到这两个服务名
这样两个服务就已经注册到nacos中了
cloud-alibaba-consumer-nacos-order93 服务消费方
跟eureka的服务差不多,也只需要引入上面一样的依赖便可
然后就是yaml(这里用yaml是因为nacos里面的文件后缀只能是yaml,如果写成yml就会报错):
server:
port: 93
spring:
application:
name: nacos-cloud-order-consumer
cloud:
nacos:
discovery:
# 配置nacos地址
server-addr: localhost:8848
# 消费者将去访问微服务的名称
service-url:
nacos-user-service: http://nacos-cloud-payment-provider
这里一定要记得加上负载均衡的注解@LoadBalanced,跟eureka是一样的,要不然会报错:找不到对应的服务,在引入nacos时候,也会把ribbon引入
nacos集群的配置
首先需要说的是nacos:
{spring.profile.active}.${file-extension} :
分别是:服务名,或者自定义prefix前缀,然后就是 dev test prod等环境,然后是文件后缀
- prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
- spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profile.active 为空时,
对应的连接符 - 也将不存在,dataId 的拼接格式变成 {file-extension} - file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。
目前只支持 properties 和 yaml 类型。
然后就是每一个文件有唯一的坐标:namespace + group + data ID
还有就是这些配置文件的数据信息,每次重新登录nacos,都活存在,是因为其自身带有一个嵌入式的数据库
而我们实际生产中需要放入高可用的mysql集群中(nacos目前只支持mysql),很简单,只需要在本地建好数据库nacos
然后导入nacos目录下的conf里面的nacos-mysql.sql文件即可(这个是nacos官方提供的),然后就是修改application.properties
里面的信息:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=root
db.password=123456
然后就可以在自己本地数据库里面看到数据,这样就完成了数据的迁移
这是在本地的操作,但是大多数生产环境下都是linux上的集群操作
首先有条件的可以选择使用多台服务器,如果没有的话这里可以使用nacos的一种方法:
- 在linux上复制一个文件cluster.conf,他自己文件不动cluster.conf.example,修改里面的配置:
192.168.109.134:3333
192.168.109.134:4444
192.168.109.134:5555
- application.properties里面跟本地数据库一样的配置
- 然后修改startup.sh:
while getopts ":m:f:s:p:" opt
do
case $opt in
m)
MODE=$OPTARG;;
f)
FUNCTION_MODE=$OPTARG;;
s)
SERVER=$OPTARG;;
p)
PORT=$OPTARG;;
?)
echo "Unknown parameter"
exit 1;;
esac
done
....
nohup $JAVA -Dserver.port=${PORT} ${JAVA_OPT} nacos.nacos >> ${BASE_DIR}/logs/start.out 2>&1 &
这里就是需要修改两个配置,使其运行的时候可以加上端口号运行,这样就可以启动三台nacos,但是有条件的话可以启动三台不同的服务器
然后就是使用nginx做负载均衡,做请求分发:
upstream cluster{
server 127.0.0.1:3333;
server 127.0.0.1:4444;
server 127.0.0.1:5555;
}
server
{
listen 1111;
server_name localhost;
location / {
proxy_pass http://cluster;
}
access_log /www/wwwlogs/access.log;
}
然后在浏览器上输入(192.168.109.134:1111/nacos/#/login)便可以进入nacos注册中心,然后在9002项目中修改yaml文件
将server-addr: 192.168.109.134:1111进行修改,就做成了nacos集群的搭建
cloud-alibaba-sentinel-service8401 sentinel的限流、降级等
首先需要去官网或者其他地方下载sentinel的jar包,然后java -jar就可以跑起来,我这里是在本地安装,没有放在服务器上
下载运行之后,在网站上打开 http://localhost:8080就可以访问到了,但是什么都没有,需要先创建工程,然后跑起来
在新建项目,pom、加入:
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
然后就是yaml的配置:
server:
port: 8401
spring:
application:
name: cloud-alibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
# 控制面板中心
dashboard: localhost:8080
# 默认端口号,如果存在则默认+1
port: 8719
management:
endpoints:
web:
exposure:
include: "*"
启动项目,访问/testA或者/testB
再次打开sentinel的网站(用户名和密码都是sentinel)
就会发现有这个服务注册到了sentinel里面,可以对这个服务里面的资源进行限流、降级等,类似于工具的使用
在sentinel的持久化中:
- resource:代表的是服务名称
- limitApp:来源应用
- grade:阈值类型,0表示线程数,1表示QPS
- count:单机阈值
- strategy:流控模式,0表示直接,1表示关联,2表示链路
- controlBehavior:流控效果,0表示快速失败,1表示Warm up,2表示排队等待
- clusterMode:是否集群
具体看官网:
sentinel:(https://github.com/alibaba/Sentinel/wiki/)
seata:https://seata.io/zh-cn/docs/overview/what-is-seata.html
TC - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
TM - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
RM - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。