# SpringCloud Netfliex
**Repository Path**: jiang-qikun/spring-cloud-netfliex
## Basic Information
- **Project Name**: SpringCloud Netfliex
- **Description**: No description available
- **Primary Language**: Java
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-03-11
- **Last Updated**: 2022-03-13
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 1:注册中心eureka
```
# 官网地址
https://github.com/Netflix/eureka/wiki
```
### 单节点搭建
1:引入 pom 依赖
```xml
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
```
2:启动类上添加注解
```
@EnableEurekaServer
```
3:配置配置文件
```yaml
server:
port: 7900
spring:
application:
name: eureka-server
eureka:
client:
#是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
register-with-eureka: false
#是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
fetch-registry: true
#设置服务注册中心的URL,用于client和server端交流
service-url:
defaultZone: http://localhost:7900/eureka/
server:
# 自我保护看自己情况
enable-self-preservation: true
# 续约阈值,和自我保护相关
renewal-percent-threshold: 0.85
# server剔除过期服务的时间间隔
eviction-interval-timer-in-ms: 1000
# 是否开启readOnly读缓存
use-read-only-response-cache: true
# 关闭 readOnly
response-cache-update-interval-ms: 1000
```
### euraka 集群
无法达到强数据一致性,为了保证可用性
有两种方式:
方式一:eureka 中的节点互为客户端,互相拉取数据。
ps:注册的时候向一个注册就行,一般搭建这个
方式二:不互相交流,服务独立开,服务注册的时候需要向所有的服务器注册
ps: 很清晰,简单。但是在拉取服务列表的时候需要拉取所有 eureka 节点的,请求变多了。
spring 官网有很好的例子,可以参考:
```shell
https://docs.spring.io/spring-cloud-netflix/docs/current/reference/html/#registering-with-eureka
```
#### 两节点
```yaml
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: https://peer2/eureka/
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: https://peer1/eureka/
```
#### 三节点
```yaml
eureka:
client:
serviceUrl:
defaultZone: https://peer1/eureka/,http://peer2/eureka/,http://peer3/eureka/
---
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
---
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
---
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
```
## 2:上报节点信息 Actuator
微服务只要给 Eureka 发送心跳包,Eureka 就认为服务是可用的,但是服务如果内部出现了异常,其实已经不可用了,但是Eureka 不会让服务下线
可以通过节点信息去手动控制上下线 (适用于服务可用,但是业务不可用)
1:eureka client 端引入 maven 依赖,eureka server 端默认带了这个依赖,不需要引入
```xml
org.springframework.boot
spring-boot-starter-actuator
```
2: 服务的配置文件需要进行配置
```yaml
eureka:
client:
service-url:
defaultZone: http://localhost:7900/eureka/
# 客户端配置健康检查,将自己真正的服务状态传给 eureka server
healthcheck:
enabled: true
management:
## 可以手动将服务的状态置为down
endpoint:
shutdown:
enabled: true
## 开启所有端点
endpoints:
web:
exposure:
include: '*'
```
3:编写手动下线的处理类
```java
@Service
public class HealthStatusService implements HealthIndicator {
private Boolean status = true;
public void setStatus(Boolean status) {
this.status = status;
}
@Override
public Health health() {
if (status) {
return new Health.Builder().up().build();
}
return new Health.Builder().down().build();
}
public String getStatus() {
return this.status.toString();
}
}
```
4:在controller 类里面提供调用接口
```
/**
* 根据服务状态:控制服务上下线
* localhost:端口/health?status=true 上线
* localhost:端口/health?status=false 下线
*/
@GetMapping("/health")
public String health(@RequestParam("status") Boolean status) {
healthStatusSrv.setStatus(status);
return healthStatusSrv.getStatus();
}
```
5:浏览器调用接口手动将服务下线
```
# 上线
localhost:端口/health?status=true
# 下线
localhost:端口/health?status=false
# 上上线完成后,可以查看节点的健康状态;也可以去 Eureka 端查看服务状态
localhost:端口/actuator/health
```
## 3:登录验证 Security (非重点)
1:Eureka server 端加上 maven 依赖,不让随意访问
```xml
org.springframework.boot
spring-boot-starter-security
```
2:需要在 配置文件配置账号密码
```yaml
spring:
security:
user:
name: admin
password: admin
```
3:在访问 Eureka 的服务端的时候,localhost:7900 的时候,出现一个登录界面,输入账号密码即可。
4:注意(重要):如果server 端开启了登录验证,client 注册的时候也是需要在配置文件配置账号密码的,否则注册不上去
```yaml
# 需要在注册的时候加上账号密码,这个是简单的使用,复杂的需要重写方法
eureka:
client:
#是否将自己注册到Eureka Server,默认为true,由于当前就是server,故而设置成false,表明该服务不会向eureka注册自己的信息
register-with-eureka: false
#是否从eureka server获取注册信息,由于单节点,不需要同步其他节点数据,用false
fetch-registry: true
#设置服务注册中心的URL,用于client和server端交流
service-url:
defaultZone: http://admin:admin@localhost:7900/eureka/
```
如果 client 端启动报错,注册失败
```
Root name 'timestamp' does not match expected ('instance') for type [simple
```
是默认开启了防止跨域攻击,需要在 server 端增加配置
```java
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
super.configure(http);
}
}
```
## 4:服务提供者
1:引入 maven 依赖
```
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
```
2: 配置文件配置,为了测试负载均衡,配置了两个服务
```yaml
spring:
application:
name: service-provider
eureka:
client:
service-url:
defaultZone: http://localhost:7900/eureka/
---
spring:
config:
activate:
on-profile: provider1
server:
#服务端口
port: 8001
---
spring:
config:
activate:
on-profile: provider2
server:
#服务端口
port: 8002
```
3:controller 类提供服务
```java
@RestController
public class ProviderController {
@Value("${server.port}")
private String port;
@GetMapping("/send-message")
public String sendMessage(){
return "port: "+port;
}
@GetMapping("test-hystrix")
public String testHystrix(){
int i=1/0;
return "port: "+port;
}
}
```
## 5:服务调用方 RestTempte && Feign && Ribbon
1:添加 maven 依赖
```
org.springframework.cloud
spring-cloud-starter-openfeign
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.boot
spring-boot-starter-web
```
2:启动类添加注解(不适用 Feign 可以省掉这一步)
```
@EnableFeignClients
```
3:写配置类,注入 RestTemplate ,并加上 ribbon (不使用 RestTemplate,可以省掉这一步 )
```java
@Component
public class Configuration {
/**
* 创建 RestTemplate 并使用 Ribbon 负载均衡
*/
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
```
4: 编写 Feign 调用接口(不使用 Feign,可以省掉这一步)
```java
@FeignClient(name = "service-provider")
public interface ProviderFeign {
@GetMapping(value = "/send-message")
String sendMessage();
@GetMapping(value="/test-hystrix")
String testHystrix();
}
```
5:编写 Controller 类,调用服务方
```java
@RestController
@Log4j2(topic = "ConsumerController")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private ProviderFeign providerFeign;
/**
* RestTemplate 调用
*/
@GetMapping("/rest-message")
public String getMessageByRestTemplate(){
String serviceName ="service-provider";
String url ="http://"+ serviceName +"/send-message";
String result = restTemplate.getForObject(url, String.class);
log.debug("使用 RestTemplate + ribbon 掉用服务的结果是:{}",result);
return result;
}
/**
* Feign 调用
*/
@GetMapping("/feign-message")
public String getMessageByFeign(){
String result = providerFeign.sendMessage();
log.debug("feign 掉用服务的结果是:{}",result);
return result;
}
}
```
5:调用方可以设置超时重试(与版本有关,有的不一定好用,建议使用限流组件)
```yaml
ribbon:
#连接超时
ConnectTimeout: 1000
#业务超时,3秒没有结果,会再次尝试
ReadTimeout: 3000
#同一台实例最大重试次数,不包括首次调用
MaxAutoRetries: 1
#重试负载均衡其他的实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1
#是否所有操作都重试
OkToRetryOnAllOperations: false
```
## 6:服务熔断 Hystrix
1: 调用方加上 maven 依赖,本案例加在 api-passener
```xml
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
2:启动类上加注解
```
@EnableCircuitBreaker
```
### 4.1:RestTemplate 集成 Hystrix
在 Controller 类中的调用方法上写上 @HystrixCommand(fallbackMethod="sendFail"),并写一个 sendFail 方法
我这个案例是在 ConsumerController 新写一个方法
```j
/**
* RestTemplate 调用 Hystrix+
*/
@GetMapping("/rest-message")
@HystrixCommand(fallbackMethod = "sendFail")
public String getMessage2ByRestTemplate(){
String serviceName ="xxx";
String url ="http://"+ serviceName +"/send-message";
String result = restTemplate.getForObject(url, String.class);
log.debug("使用 RestTemplate + ribbon 掉用服务的结果是:{}",result);
return result;
}
/**
* 降级方法:此方法的 请求参数和 返回参数 要和原方法一致。
*/
private String sendFail(){
return "调用服务失败,熔断";
}
```
### 4.2:Feign 集成 Hystrix
默认情况下hystrix使用线程池控制请求隔离,针对不同的服务使用不同的线程池。并发数量超过线程池数量,不再调用服务。
1:feign 自带 Hystrix,但是默认没有打开,需要在配置文件配置
```yaml
feign:
circuitbreaker:
enabled: true
```
2: 修改 Feign 调用接口,注意后面的快速失败类
```
@FeignClient(name = "service-provider",fallbackFactory = ProviderFallBackFactory.class)
```
3:新建快速失败类,实现 Feign 接口
```java
@Component
public class ProviderFallBackFactory implements FallbackFactory {
/**
* 服务提供方 catch 住异常,返回错误码,这边拿到错误码,去做返回
*/
@Override
public ProviderFeign create(Throwable cause) {
return new ProviderFeign() {
@Override
public String sendMessage() {
return "调用 sendMessage 方法失败";
}
@Override
public String testHystrix() {
return "调用 testHystrix 方法失败";
}
};
}
}
```
4: 是在 ConsumerController 新写一个方法
```
/**
* Feign 测试 Hystrix
*/
@GetMapping("/feign-Hystrix")
public String getMessage2ByFeign(){
String result = providerFeign.testHystrix();
log.debug("feign 掉用服务的结果是:{}",result);
return result;
}
```
5:可以配置信号量隔离与线程隔离(面试多问)
线程池隔离技术,是用 Hystrix 自己的线程去执行调用;针对不同的服务使用不同的线程池。并发数量超过线程池数量,不再调用服务。
信号量隔离技术,是直接让 tomcat 线程去调用依赖服务。信号量隔离,只是一道关卡,信号量有多少,就允许多少个 tomcat 线程通过它,然后去执行。
信号量隔离主要维护的是Tomcat的线程,不需要内部线程池,更加轻量级。可以做限流
```yaml
hystrix:
command:
default:
execution:
isolation:
#隔离策略,默认是Thread, 可选可选THREAD|SEMAPHORE
strategy: SEMAPHORE
# thread:
# #命令执行超时时间,默认1000ms
# timeoutInMilliseconds: 1000
# #发生超时是是否中断,默认true
# interruptOnTimeout: true
# SEMAPHORE:
# #最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。
# maxConcurrentRequests: 10
# #执行是否启用超时,默认启用true
# timeout: true
```
### 4.3:开启 Hystrix dashboard ,需要引入 actuator
1:引入依赖
```
org.springframework.cloud
spring-cloud-starter-netflix-hystrix-dashboard
org.springframework.boot
spring-boot-starter-actuator
```
2: 启动类加上注解
```
@EnableHystrixDashboard
```
3:访问浏览器
```
# 健康上报,查看前需要调用一下接口访问,因为这个是懒加载
http://localhost:端口/actuator/hystrix.stream
http://localhost:端口/hystrix
```
注意:如果访问 dashboard 不显示,在配置文件添加下配置
```yaml
# 解决 dashboard 不显示的问题
hystrix:
dashboard:
proxy-stream-allow-list: "*"
```
## 7:网关 zuul(不太好用)
1: 新建一个服务,加上 maven 依赖
```
org.springframework.cloud
spring-cloud-starter-netflix-zuul
2.2.5.RELEASE
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
2:配置文件进行配置
```yaml
spring:
application:
name: cloud-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:7900/eureka/
server:
port: 8004
#开启路由断点
management:
## 可以手动将服务的状态置为down
endpoint:
routes:
enabled: true
health:
enabled: true
show-details: always
## 开启所有端点
endpoints:
web:
exposure:
include: '*'
```
3:启动类上加上注解
```
@EnableZuulProxy
```
4:注意:版本问题:高版本的cloud版本不支持zuul了
```
# 使用以下版本
SpringBoot 2.3.2.RELEASE
SpringCloud Hoxton.SR8
SpringCloudAlibaba 2.2.3.RELEASE
```
### 经典问题1:token 不向后传
前端调用网关,网关不把前端传来的 token 透传给后面的服务
目的:把功能提到网关做,避免重复工作,符合阿里的最小知道原则.但是如果你想做一个单独的服务来完成这个工作,那么token 需要后传
```yaml
# 解决办法。配置文件添加配置。以下配置为空表示:所有请求头都透传到后面微服务
zuul:
sensitive-headers:
```
### 经典问题2:服务改造,url 转变成 另外的 url
前端请求的 url 进行转化成其他的 url
步骤:开发之前,想清楚步骤
1:准备 router 过滤器
2:接收请求过来的 url
3:将 url 和要转发的目的地url 映射
4:网关本来就有转发的功能。设置 RequestContext 中的 serviceId,url
```
```java
@Component
public class RibbonFilter extends ZuulFilter {
/**
* 过滤器类型
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 优先级,数字越小,越先执行
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 是否执行此过滤器
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的业务逻辑
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
String url = ctx.getRequest().getRequestURI();
Map forwardMap = getForwardMap(url);
if(forwardMap != null){
String fowardUrl = forwardMap.get(url);
String serviceId = getServiceId(fowardUrl);
// ps:如果用的到不是 eureka,不需要设置 serviceId
String requestUrl = getRequestUrl(fowardUrl,serviceId);
//1.设置目标service的Controller的路径
ctx.put(FilterConstants.REQUEST_URI_KEY,requestUrl);
//2.设置目标service的serviceId
ctx.put(FilterConstants.SERVICE_ID_KEY,serviceId);
}
return null;
}
private String getServiceId(String url){
if(url.startsWith("/")){
String temp = url.substring(1);
return temp.split("/")[0];
}else{
return null;
}
}
private String getRequestUrl(String url,String serviceId){
return url.substring(serviceId.length() +1);
}
private Map getForwardMap(String originalUrl){
//todo:这里是返回一个map,传入一个originUrl,返回一个要转发的url
return null;
}
}
```
### 经典问题3:动态路由
比如 根据不同的用户调用不同的服务,入口都是网关,但是不同用户可能根据地区不同调用所在地区服务,不同地区服务业务有差别
解决方法:
1:网关灰度的思路,但是要求服务名称一致,如果是不同的服务,不可取
2:直接连接服务地址
```yaml
zuul:
routes:
# zuul 请求路径
xxxx: /zuul-api-driver/**
# 将请求路径和 服务地址关联
custom-zuul-name:
path: /zuul-api-driver/**
url: http://localhost:8080/
```
```
# 与上面filter 类似
RequestContext ctx = RequestContext.getCurrentContext();
// 中间省略 根据用户选择服务
...
// 匹配服务
ctx.setRouteHost(new URI("http://localhost:8003/test/ses-s").toURL());
```
### 网关高可用
可以通过 nginx 对 多个网关服务做负载,为了防止 nginx 的单点问题,可以对nginx 做一个主备或者做集群
nginx 做集群,前面可以 通过 keepalived 来转发请求,提供统一的访问地址。
### 网关的其他问题
#### 网关过滤器为什么也有访问顺序?
要考虑到过滤器的执行顺序对计算资源的节省,要把小的逻辑放在前面,比如 ip 黑名单,用户黑白名单放在前面,鉴权放在后面。
当系统面临大流量的时候,不至于措手不及
#### 网关的接口容错
ps:网关调用后面服务的接口出错了,网关应该怎么办?
```java
@Component
public class ZuulFailback implements FallbackProvider {
/**
* 我这里是对所有的服务,可以修改成对应的服务名称
*/
@Override
public String getRoute() {
return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST.value();
}
@Override
public String getStatusText() throws IOException {
return "message";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
// 最好和通用的返回值结构一样
return new ByteArrayInputStream(JSONObject.toString("fail","-1").getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
};
}
}
```
#### 网关的多过滤器如何控制开关
生产中有个小技巧,可以将过滤器开关存储在数据库中,修改数据库的值即可控制过滤器的开关
#### 查看网关中有多少个过滤器
需要在网关中配置 Actuator ,访问 http://网关ip:端口/actuator/filters 即可查看
ps:建议每个微服务都配置 Actuator ,方便查看节点信息
#### ip限制,ip调用次数限制,设备号过滤
使用 pre 过滤器类型,对某个服务的访问进行限制,不让频繁访问,使用 redis 的过期时间或者访问次数
```java
public class IPFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// 1:获取ip 地址
// 2:访问 redis ,拿到访问次数,做对比
// 3:如果达到了访问次数,设置不向后面的过滤器转发,返回 null;否则继续执行
// ps:后面的过滤器可以根据 RequestContext.getCurrentContext().sendZuulResponse();
// 不向后面的 router 过滤器转发,但是还是会把所有的 pre 过滤器执行一遍
context.setSendZuulResponse(false);
return null;
}
}
```
#### 网关限流
限流包括:网关的限流,每个服务的限流
```java
@Component
public class LimitFilter extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
// -10 是最小的了
return -10;
}
@Override
public boolean shouldFilter() {
return true;
}
// 每秒向令牌桶中放入两个令牌,设置每秒的请求数
public static final RateLimiter RATE_LIMITER=RateLimiter.create(50);
@Override
public Object run() throws ZuulException {
RequestContext context = RequestContext.getCurrentContext();
if(RATE_LIMITER.tryAcquire()){
System.out.println("通过");
return null;
}else {
System.out.println("被限流");
// 为了控制不向下面所有的过滤器走了,设置条件 ,其他过滤器 (Boolean) RequestContext.getCurrentContext().get("limit");
context.set("limit",false);
// 只能控制不向 router 过滤器走了
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
return null;
}
}
```
```java
// 其他的过滤器,这个不重要
@Component
public class LimitFilter2 extends ZuulFilter {
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
return -9;
}
@Override
public boolean shouldFilter() {
Object limit = RequestContext.getCurrentContext().get("limit");
return Objects.isNull(limit)? true:(Boolean)limit;
}
@Override
public Object run() throws ZuulException {
System.out.println("限流之后的过滤器");
return null;
}
}
```
#### 服务限流
```java
@Component
public class LimitFilter implements Filter {
// 2=每秒2个;0.1 = 10秒1个
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 限流的业务逻辑
if (RATE_LIMITER.tryAcquire()){
filterChain.doFilter(servletRequest,servletResponse);
}else {
servletResponse.setCharacterEncoding("utf-8");
servletResponse.setContentType("text/html; charset=utf-8");
PrintWriter pw = servletResponse.getWriter();
pw.write("限流了");
pw.close();
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void destroy() {
}
}
```
## 8:链路跟踪 Sleuth(不太好用)
1:每个要跟踪的服务都加上 maven 依赖
```xml
org.springframework.cloud
spring-cloud-starter-sleuth
```
2:启动服务,查看控制台日志即可
## 9:链路跟踪 zipkin(不太好用)
1:每个要跟踪的服务都加上 maven 依赖
```xml
org.springframework.cloud
spring-cloud-starter-zipkin
2.2.8.RELEASE
```
2:每个要跟踪的服务配置文件添加配置,每个服务都向 zipkin 上报信息
```yaml
spring:
#zipkin
zipkin:
base-url: http://localhost:9411/
#采样比例1
sleuth:
sampler:
rate: 1
```
3:下载 zipkin
到下面的地址去下载
```
https://repo1.maven.org/maven2/io/zipkin/zipkin-server/
```
4:启动 zipkin
```
java -jar zipkin.jar
```
5:查看前端页面,http://localhost:9411/ ,需要先访问接口,在前端界面输入服务名称
## 10:健康检查 admin-服务 down 了,发送钉钉消息
1:新建一个服务,添加 maven 依赖
```
de.codecentric
spring-boot-admin-starter-server
de.codecentric
spring-boot-admin-server-ui
2.2.1
com.alibaba
fastjson
1.2.79
```
2:启动类加上注解
```
@EnableAdminServer
```
3:配置文件
```yaml
server:
port: 8005
spring:
application:
name: admin-server
```
4:其他服务加上 maven 依赖
```
de.codecentric
spring-boot-admin-starter-client
2.6.2
org.springframework.boot
spring-boot-starter-actuator
```
5:其他服务配置文件添加配置
```yaml
spring:
# 需要向这个地址上报服务信息
boot:
admin:
client:
url: http://localhost:8005
management:
## 可以手动将服务的状态置为down
endpoint:
routes:
enabled: true
health:
enabled: true
show-details: always
## 开启所有端点
endpoints:
web:
exposure:
include: '*'
```
6:服务上下线:发送钉钉消息
新建一个消息类
```java
@Data
public class Message {
private String msgtype;
private String text;
}
```
新建一个钉钉工具类
```java
public class DingDingMessageUtil {
//注意:打开钉钉一个群聊,打开群设置,打开智能群助手,添加机器人 安全设置:选择 自定义关键词 确定后,保存 token
public static String access_token = "f54f611b869ad8e82ec8250f85f5e4bb6a06146c4dc7046d4ae9217fec95ef17";
public static void sendTextMessage(String msg) {
try {
Message message = new Message();
message.setMsgtype("text");
message.setText(msg);
URL url = new URL("https://oapi.dingtalk.com/robot/send?access_token=" + access_token);
// 建立 http 连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Charset", "UTF-8");
conn.setRequestProperty("Content-Type", "application/Json; charset=UTF-8");
conn.connect();
OutputStream out = conn.getOutputStream();
String textMessage = JSONObject.toJSONString(message);
byte[] data = textMessage.getBytes();
out.write(data);
out.flush();
out.close();
InputStream in = conn.getInputStream();
byte[] data1 = new byte[in.available()];
in.read(data1);
System.out.println(new String(data1));
} catch (Exception e) {
e.printStackTrace();
}
}
}
```
新建一个过滤器类,继承 AbstractStatusChangeNotifier 抽象类
```java
public class DingDingNotifier extends AbstractStatusChangeNotifier {
public DingDingNotifier(InstanceRepository repository) {
super(repository);
}
@Override
protected Mono doNotify(InstanceEvent event, Instance instance) {
String serviceName = instance.getRegistration().getName();
String serviceUrl = instance.getRegistration().getServiceUrl();
String status = instance.getStatusInfo().getStatus();
Map details = instance.getStatusInfo().getDetails();
StringBuilder str = new StringBuilder();
str.append("服务预警 : 【" + serviceName + "】");
str.append("【服务地址】" + serviceUrl);
str.append("【状态】" + status);
str.append("【详情】" + JSONObject.toJSONString(details));
return Mono.fromRunnable(() -> {
DingDingMessageUtil.sendTextMessage(str.toString());
});
}
}
```
在启动类里面注入过滤器类
```
@Bean
public DingDingNotifier dingDingNotifier(InstanceRepository repository) {
return new DingDingNotifier(repository);
}
```
7:启动 admin-server 服务,可以到前端 ui 界面查看 ,并手动关掉服务,查看钉钉信息
## 11:邮件发送 mail -本例配合 admin 使用
服务 down 了,发送钉钉消息
1:需要在 admin-server 中引入 maven 依赖
```xml
org.springframework.boot
spring-boot-starter-mail
```
2:配置文件添加配置,授权码需要到邮箱里面去获取
```yaml
spring:
application:
name: admin-server
security:
user:
name: root
password: root
# 邮件设置
mail:
host: smtp.qq.com
username: 1677081700
# 授权码:从 qq 邮箱->账户->STMP 开启->需要发短信,会得到一串密钥,填写在这里
password: wcyyhhrdveuybcgd
properties:
mail:
smpt:
auth: true
starttls:
enable: true
required: true
boot:
admin:
notify:
mail:
to: 1677081700@qq.com
from: 1677081700@qq.com
```
3:手动关掉服务,查看邮箱信息
## 12:配置中心 config 动态配置
1:新建一个微服务,添加 maven 依赖
```
org.springframework.cloud
spring-cloud-config-server
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
2:配置配置文件
```yaml
server:
port: 8006
spring:
application:
name: config-server
# 配置 git 仓库的路径和分支
cloud:
config:
server:
git:
uri: https://gitee.com/jiang-qikun/spring-cloud-config-center.git
label: master
eureka:
client:
service-url:
defaultZone: http://localhost:7900/eureka/
```
3:启动类添加注解
```
@EnableConfigServer
```
4:在 git 仓库新建仓库 https://gitee.com/jiang-qikun/spring-cloud-config-center.git,
添加一个配置文件,命名规则是 服务名-环境名称.yml
ps:环境名称: dev/test/pre/ft/uat 等
5:启动服务,去读取 git 仓库的文件,浏览器访问 http://localhost:8006/master/service-provider-dev.yml
6: 我们这个例子使用的是 service-provider 服务,因此需要现在 service-provider 的 maven 依赖里面添加
```
org.springframework.cloud
spring-cloud-config-client
org.springframework.cloud
spring-cloud-starter-bootstrap
3.1.1
```
7:修改 service-provider 服务的配置文件名称,变成 bootstrap.yml,并添加内容
```yaml
spring:
cloud:
config:
# 直接URL方式查找 或者配置中心 两种方式二选一
# uri: http://localhost:8006/
discovery:
enabled: true
service-id: config-server
profile: dev
label: master
```
8:手动刷新-针对单服务:编写 Controller ,能够访问 git 仓库的配置, 类上添加 @RefreshScope 注解
```java
@RestController
@RefreshScope
public class ConfigController {
@Value("${myConfig}")
private String myConfig;
@GetMapping("/test-config")
public String testConfig(){
return "myConfig: "+ myConfig;
}
}
```
9:先访问一下接口,然后修改 git 仓库中的配置内容,向浏览器发送刷新请求 http://localhost:8001/actuator/refresh,
刷新的很慢,基本是把所有的配置都重启了。刷新完成后,再次访问接口,查看内容是否发生了变化
ps: 只能一个一个服务的刷新,有点麻烦,可以通过下面的消息总线 bus 来刷新
## 13:消息总线 bus
所有服务都接入 bus ,与eureka 不同的是,这个是主动推消息的。
底层是一个消息中间件,需要一个 mq 和 bus. SpringCloud 里面的 bus 要求 mq 支持 amqp 协议,可以使用 kafka, RabbitMQ,
另外的 mq 也可以接进来,但是比较麻烦,这两个可以无缝集成。kafka 面向大的数据中转开发,RabbitMQ 面向应用级开发,我们这里使用 RabbitMQ。
这个组件有很多功能,下面简单使用一个功能
### 自动刷新:通过消息总线,配合 config 动态刷新
1:erlang安装,下载地址: http://www.erlang.org/downloads ,下载完成后,需要像jdk 一样配置环境变量。
配置完成后,在命令行里面输入 erl , 能显示版本说明成功
2: RabbitMQ 安装,下载地址: http://www.rabbitmq.com/install-windows.html
安装完成后,在 sbin 窗口执行以下命令
```shell
# 开启RabbitMQ节点
rabbitmqctl start_app
# 开启RabbitMQ管理模块的插件,并配置到RabbitMQ节点上
rabbitmq-plugins enable rabbitmq_management
```
3:启动 RabbitMQ ,上面步骤完成后,可以在菜单栏-所有程序 里面点开 RabbitMQ Server,双击 RabbitMQ Server -start 启动即可。
启动完成后,可以通过浏览器访问 http://localhost:15672 ,查看管理界面,用户名密码均为guest
ps:遇到报错可以参考这篇博客 : https://blog.csdn.net/lezeqe/article/details/106098082
4:所有服务 配置文件添加对 mq 的配置,除了 eureka,admin,zuul也可以不配置,只配置自己想动态改变的,
我这里只在 config-server,service-provider 中添加了
```yaml
spring:
# 消息总线配置,注意端口是 5672
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
```
5:只在 config-server,service-provider 服务中添加 maven 依赖
```xml
org.springframework.cloud
spring-cloud-starter-bus-amqp
```
6:前面没有在 config-server 添加 actuator 的配置,这里需要在 maven 添加依赖
```xml
org.springframework.boot
spring-boot-starter-actuator
```
在配置文件添加配置
```yaml
management:
## 可以手动将服务的状态置为down
endpoint:
routes:
enabled: true
health:
enabled: true
show-details: always
## 开启所有端点
endpoints:
web:
exposure:
include: '*'
```
7:启动所有服务,修改 git 仓库中的内容,可以调用配置中心的 http://localhost:8006/actuator/busrefresh ,post 请求,还是慢
ps: 有的版本是 http://localhost:8006/actuator/bus-refresh ,这个可以通过 http://localhost:8006/actuator 查看
这样几乎所有的服务都跟着更新了