简

人生短暂,学海无边,而大道至简。


  • 首页

  • 归档

  • 分类

  • 标签

springcloud介绍

发表于 2018-08-14 | 分类于 springcloud

使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现;而服务间通过Ribbon或Feign实现服务的消费以及均衡负载;
通过Spring Cloud Config实现了应用多环境的外部化配置以及版本管理。使用Hystrix的融断机制来避免在微服务架构中个别服务出现异常时引起的故障蔓延。

为了保证对外服务的安全性,需要实现对服务访问的权限控制,如果通过开放服务权限控制的方式去实现权限控制会入侵业务逻辑,破坏了服务集群无状态的特点,还无法直接复用既有接口,所以,通过服务网关的形式来解决以上问题。

服务网关是微服务架构中一个不可缺的部分。通过服务网关统一向外系统提供REST API的过程中,除了具备服务路由、均衡负载功能之外,它还具备了权限控制等功能。
Spring Cloud Netflix中的Zuul就是为微服务架构提供了前门保护的作用,同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面,使得服务集群主体能够具备更高的可复用性和可测试性。

构建服务网关

使用zuul之前,创建好服务端和消费端分别为:zuul-eureka-consumer、zuul-eureka-previder。
创建网关模块:zuul-gateway,引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hu</groupId>
<artifactId>zuul-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
</project>

创建配置文件application.yml,并加入服务名、端口号、eureka注册中心的地址。

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7903
spring:
application:
name: zuul-gateway
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
logging:
level:
root: INFO

启动类:

1
2
3
4
5
6
7
8
//开启Zuul的功能
@EnableZuulProxy
@SpringCloudApplication
public class ZuulApplication {
public static void main(String[] args){
SpringApplication.run(ZuulApplication.class, args);
}
}

这样,基于Spring Cloud Zuul服务网关就已经构建完毕。

由于Spring Cloud Zuul在整合了Eureka之后,具备默认的服务路由功能,当zuul-gateway应用启动并注册到eureka之后,服务网关会发现已经启动的两个服务zuul-eureka-consumer、zuul-eureka-previder,这时候Zuul就会创建两个路由规则。每个路由规则都包含:外部请求的匹配规则和路由的服务ID。

这里就会创建两个规则:
zuul-eureka-consumer服务的请求规则为:/consumer-user/**
zuul-eureka-previder服务的请求规则为:/provider-user/**

通过访问7903端口的服务网关来验证上述路由的正确性:
http://localhost:7903/provider-user/user,该请求将最终被路由到zuul-eureka-previder的/user接口上。
访问成功返回数据:江小白

传统路由配置

传统路由配置方式就是在不依赖于服务发现的情况下,通过在配置文件中具体指定每个路由表达式与服务实例的映射关系来实现API网关对外部请求的路由。

单实例配置:通过一组zuul.routes..path与zuul.routes..url参数对的方式配置

1
2
zuul.routes.user-service.path=/user-service/**
zuul.routes.user-service.url=http://localhost:8080/

实现了对符合/user-service/**规则的请求路径转发到http://localhost:8080/地址的路由规则。
当有一个请求http://localhost:7903/user-service/hello被发送到API网关上,由于/user-service/hello能够被上述配置的path规则匹配,所以API网关会转发请求到http://localhost:8080/hello地址。

多实例配置:通过一组zuul.routes..path与zuul.routes..serviceId参数对的方式配置

1
2
3
4
zuul.routes.user-service.path=/user-service/**
zuul.routes.user-service.serviceId=user-service
ribbon.eureka.enabled=false
user-service.ribbon.listOfServers=http://localhost:8080/,http://localhost:8081/

实现了对符合/user-service/**规则的请求路径转发到http://localhost:8080/和http://localhost:8081/两个实例地址的路由规则。
serviceId是由用户手工命名的服务名称,配合.ribbon.listOfServers参数实现服务与实例的维护。
由于存在多个实例,API网关在进行路由转发时需要实现负载均衡策略,还需要Spring Cloud Ribbon的配合,Spring Cloud Zuul中自带了对Ribbon的依赖,只需要做一些配置即可。

ribbon.eureka.enabled:由于zuul.routes..serviceId指定的是服务名称,默认情况下Ribbon会根据服务发现机制来获取配置服务名对应的实例清单。
但是,该示例并没有整合类似Eureka之类的服务治理框架,所以需要将该参数设置为false,不然配置的serviceId是获取不到对应实例清单的。
user-service.ribbon.listOfServers:该参数内容与zuul.routes..serviceId的配置相对应,开头的user-service对应了serviceId的值,这两个参数的配置相当于在该应用内部手工维护了服务与实例的对应关系。

不论哪种方式,都需要为每一对映射关系指定一个名称,也就是上面配置中的,每一个就对应了一条路由规则。每条路由规则都需要通过path属性来定义一个用来匹配客户端请求的路径表达式,并通过url或serviceId属性来指定请求表达式映射具体实例地址或服务名。

服务路由配置

Spring Cloud Zuul通过与Spring Cloud Eureka的整合,实现了对服务实例的自动化维护,使用服务路由配置的时候,不需要向传统路由配置方式那样为serviceId去指定具体的服务实例地址,只需要通过一组zuul.routes..path与zuul.routes..serviceId参数对的方式配置即可。

例如:

1
2
zuul.routes.user-service.path=/user-service/**
zuul.routes.user-service.serviceId=user-service

实现了对符合/user-service/规则的请求路径转发到名为user-service的服务实例上去的路由规则。
还有一种更简洁的配置方式:zuul.routes.=,其中用来指定路由的具体服务名,用来配置匹配的请求表达式。
例如:zuul.routes.user-service=/user-service/

等价于上面两条的组合。

那么当采用path与serviceId以服务路由方式实现时候,没有配置任何实例地址的情况下,外部请求经过API网关的时候,它是如何被解析并转发到服务具体实例的呢?
在Spring Cloud Netflix中,Zuul巧妙的整合了Eureka来实现面向服务的路由。实际上,可以直接将API网关也看做是Eureka服务治理下的一个普通微服务应用。它除了会将自己注册到Eureka服务注册中心上之外,也会从注册中心获取所有服务以及它们的实例清单。
所以,在Eureka的帮助下,API网关服务本身就已经维护了系统中所有serviceId与实例地址的映射关系。当有外部请求到达API网关的时候,根据请求的URL路径找到最佳匹配的path规则,API网关就可以知道要将该请求路由到哪个具体的serviceId上去。由于在API网关中已经知道serviceId对应服务实例的地址清单,那么只需要通过Ribbon的负载均衡策略,直接在这些清单中选择一个具体的实例进行转发就能完成路由工作了。

过滤器

微服务应用提供的接口就可以通过统一的API网关入口被客户端访问。但是当请求时对它们的访问权限进行限制,系统不会把所有接口全部开放。当前的服务路由并没有限制权限这样的功能,实现对客户端请求的安全校验和权限控制,最简单的方法就是为每个微服务应用都实现过滤器或拦截器。但是这样并不好,增加日后的系统维护难度。所以直接把这些校验剥离出去,构建出一个独立的服务。在完成了剥离之后,还可能会在微服务应用中通过调用校验服务来实现校验,这种方式校验在本质上并没有完全脱离微服务应用,冗余的拦截器或过滤器依然会存在。

由于网关服务的加入,外部客户端访问我们的系统已经有了统一入口,通过在网关中完成校验和过滤,微服务应用端就可以去除各种复杂的过滤器和拦截器了,这使得微服务应用的接口开发和测试复杂度也得到了相应的降低。

Spring Cloud Zuul的另外一个核心功能:过滤器。允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤。只需要继承ZuulFilter抽象类并实现它定义的四个抽象函数就可以完成对请求的拦截和过滤了。

过滤器的实现

定义Zuul过滤器,请求被路由之前检查HttpServletRequest中是否有某个参数,若有就进行路由,若没有就拒绝访问,自定义过滤器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* 继承ZuulFilter抽象类并重写了下面的四个方法来实现自定义的过滤器
* 定请求被路由之前检查HttpServletRequest中是否有某个参数,若有就进行路由,若没有就拒绝访问。
*/
public class ZuulFilter extends com.netflix.zuul.ZuulFilter {

private static Logger log = LoggerFactory.getLogger(ZuulFilter.class);

/**
* 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
* @return
*/
@Override
public String filterType() {
return "pre";
}

/**
* 过滤器的执行顺序。当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来依次执行。
* @return
*/
@Override
public int filterOrder() {
return 0;
}

/**
* 判断该过滤器是否需要被执行。
* 这里直接返回了true,该过滤器对所有请求都会生效。
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}

/**
* 过滤器具体逻辑
* @return
*/
@Override
public Object run() {
//获取请求信息
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();

log.info(request.getMethod(), request.getRequestURL().toString());

Object accessToken = request.getParameter("token");
if(accessToken == null) {
log.warn("不通过");
//令zuul过滤该请求,不对其进行路由
ctx.setSendZuulResponse(false);
//设置了其返回的错误码
//还可以通过ctx.setResponseBody(body)对返回body内容进行编辑等。
ctx.setResponseStatusCode(401);
return null;
}
//TODO 还有一些校验信息略过
log.info("通过");
return null;
}

}

自定义过滤器之后,不会直接生效,创建具体的Bean才能启动该过滤器,比如在主类中增加如下内容:

1
2
3
4
@Bean
public ZuulFilter zuulFilter(){
return new ZuulFilter();
}

浏览:http://localhost:7903/consumer-user/user 日志显示

1
2
 INFO 11872 --- [nio-7903-exec-2] com.hu.filter.ZuulFilter                 : GET
WARN 11872 --- [nio-7903-exec-2] com.hu.filter.ZuulFilter : 不通过

浏览:http://localhost:7903/consumer-user/user?token=11
显示出结果,并且日志中显示出通过的信息。

Zuul常见问题

有时候当我们将Spring Cloud Zuul作为API网关接入网站类应用时,会遇到会话无法保持或者重定向后的HOST错误。
1、会话无法保持
通过跟踪一个HTTP请求经过Zuul到具体服务,再到返回结果的全过程。在传递的过程中,HTTP请求头信息中的Cookie和Authorization都没有被正确地传递给具体服务,所以最终导致会话状态没有得到保持。
从Zuul进行路由转发的过滤器(如:RibbonRoutingFilter)中进行一步步查看:
从过滤器的核心逻辑run方法看,其中调用了内部方法buildCommandContext来构建上下文,而buildCommandContext中调用了helper对象的buildZuulRequestHeaders方法来处理请求头信息,helper对象是ProxyRequestHelper类的实例;
而进入到ProxyRequestHelper类中看到,构建头信息的方法buildZuulRequestHeaders通过isIncludedHeader函数来判断当前请求的各个头信息是否在忽略的头信息清单中,如果是的话就不组织到此次转发的请求中去;
如果是忽略的头信息,则也要进行初始化,在PreDecorationFilter源码中可以看到,通过调用ProxyRequestHelper的addIgnoredHeaders方法来添加需要忽略的信息到请求上下文中,供后续ROUTE阶段的过滤器使用。
if/else块分别用来处理全局设置的敏感头信息和指定路由设置的敏感头信息。而全局的敏感头信息定义于ZuulProperties中:

1
2
3
4
5
@Data
@ConfigurationProperties("zuul")
public class ZuulProperties {
private Set<String> sensitiveHeaders = new LinkedHashSet<>(Arrays.asList("Cookie", "Set-Cookie", "Authorization"));
}

所以解决该问题的方法只需要通过设置sensitiveHeaders即可:

1
2
3
4
5
全局设置:
zuul.sensitive-headers=
指定路由设置:
zuul.routes.<routeName>.sensitive-headers=
zuul.routes.<routeName>.custom-sensitive-headers=true

2、重定向问题
这是Zuul没有正确的处理HTTP请求头信息中的Host导致。在Brixton版本中,Spring Cloud Zuul的PreDecorationFilter过滤器实现时完全没有考虑这一问题,但是在Camden版本之后,Zuul增强了该功能,只需要通过配置属性zuul.add-host-header=true就能让有问题的重定向操作得到正确的处理。

Zuu统一异常处理

1、try-catch处理
异常后通过设置上下文参数信息,回传:
error.status_code:错误编码
error.exception:Exception异常对象
error.message:错误信息

1
2
3
4
5
catch (ZuulException ex) {
context.set(ERROR_STATUS_CODE, ex.nStatusCode);
context.set("error.message", ex.errorCause);
context.set("error.exception", ex);
}

2、ErrorFilter处理
使用error类型的过滤器
请求生命周期的pre、route、post三个阶段中有异常抛出的时候都会进入error阶段的处理,可以通过创建一个error类型的过滤器来捕获这些异常信息,并根据这些异常信息在请求上下文中注入需要返回给客户端的错误描述,这里我们可以直接沿用在try-catch处理异常信息时用的那些error参数,这样就可以让这些信息被SendErrorFilter捕获并组织成消息响应返回给客户端。

参考 http://blog.didispace.com/spring-cloud-learning/

Spring Cloud Config

发表于 2018-08-14 | 分类于 springcloud

对于Spring Boot应用,我们可以将配置内容写入application.yml,设置多个profile,也可以用多个application-{profile}.properties文件配置,并在启动时指定spring.profiles.active={profile}来加载不同环境下的配置。
在Spring Cloud微服务架构中,这种方式未必适用,微服务架构对配置管理有着更高的要求,如:

  • 集中管理:成百上千(可能没这么多)个微服务需要集中管理配置,否则维护困难、容易出错;
  • 运行期动态调整:某些参数需要在应用运行时动态调整(如连接池大小、熔断阈值等),并且调整时不停止服务;
  • 自动更新配置:微服务能够在配置发生变化是自动更新配置。

Spring Cloud Config是Spring Cloud团队创建的一个全新项目,用来为分布式系统中的基础设施和微服务应用提供集中化的外部配置支持,它分为服务端与客户端两个部分。

其中服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置仓库并为客户端提供获取配置信息、加密/解密信息等访问接口;而客户端则是微服务架构中的各个微服务应用或基础设施,它们通过指定的配置中心来管理应用资源与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。

Spring Cloud Config实现的配置中心默认采用Git来存储配置信息,所以使用Spring Cloud Config构建的配置服务器,天然就支持对微服务应用配置信息的版本管理,并且可以通过Git客户端工具来方便的管理和访问配置内容。当然它也提供了对其他存储方式的支持,比如:SVN仓库、本地化文件系统。

1、准备配置仓库

https://github.com/huingsn/config-repo-demo.git
假设我们读取配置中心的应用名为config-client

那么我们可以在git仓库中该项目的默认配置文件config-client.yml:

1
2
info:
profile: default
2、创建配置中心

核心依赖:

1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>

配置信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/huingsn/config-repo-demo.git
#如果我们的Git仓库需要权限访问,那么可以通过配置下面的两个属性来实现;
#spring.cloud.config.server.git.username:访问Git仓库的用户名
#spring.cloud.config.server.git.password:访问Git仓库的用户密码
server:
port: 1200

启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//开启Spring Cloud Config的服务端功能。
@EnableConfigServer
@SpringBootApplication
public class Application {

public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).web(true).run(args);
}
}
```
到此,一个简单的Spring Cloud Config实现,并使用Git管理配置内容的分布式配置中心就已经完成了。

启动,通过浏览器工具查看配置信息如下:
<img src="data:image/png;base64,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j4iIiIjGSfIgT6vV+qbrPLN6RERERBRyXPFikMPhKCsrUygUb7/9dqT7QkRERDQucYIgTJkyRaFQJCQkxMfHe73c3t6ek5MTkZ75JZPJIt0FIiIioijATB4RERFRDGKQR0RERBSDGOQRERERxSDJn64F0NLS0traCkCj0YjP1ZrNZnHyFAAFBQVjni2vqqrqySef3Ldvn16vD1VviYiIiGKA5Jk8m8129erV8vLy0tLS7u5us9kMQKvVrlu3bt26dSUlJR9++KHNZhtb5RUVFefOnTt8+LDVag1pr4mIiIiim+SZPJVKJWbv5HJ5enq616vXrl2TyWTjWbtWrVbfcMMN4+oiERERUcwJx+1akc1ms9lsycnJ4q/iPVyZTHbXXXeNJ8hTKBQpKSkWiyU1NTVEPSUiIiKKemGaJ8/pdL755ptZWVley5cJgtDY2FhUVKTRaIKpx+88eQ6HY+3atRkZGRUVFePvKhEREVEMCEcmT4zwlEql7wK106ZNUyqV165dG3PlDodj06ZNa9euzc3NHVcviYiIiGJIOKZQOXLkiFKp9Lte7ZUrV/r7+7OyssZcud1u7+3tVavV4+ggERERUayRPJNnNpsFQQBQXV0NIC0tbfny5e+//744qYpCoVi5cqVKpZK6G0RERESTiuRBnlar9Z0GT6fT+d66HRuLxfLll1+GpCoiIiKimBHdK15UVVXNmjWruLiYj9YSEREReQrT07Wh4vfpWiIiIiLyEt2ZPCIiIiLyi0EeERERUQxikEdEREQUgxjkEREREcUgBnlEREREMYhBHhEREVEMYpBHREREFIMY5BERERHFIAZ5RERERDGIQR4RERFRDGKQR0RERBSDGOQRERERxaD/D+/KrQqbw06KAAAAAElFTkSuQmCC" />

Json中返回了应用名:config-client,环境名:dev,分支名:master,以及default环境和dev环境的配置内容。

访问配置信息的URL与配置文件的映射关系如下:
/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
上面的url会映射{application}-{profile}.properties对应的配置文件,其中{label}对应Git上不同的分支,默认为master。
<img src="data:image/png;base64,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" />

##### 3、创建客户端

创建一个微服务应用,命名为config-client,引入依赖:
```xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

创建bootstrap.yml配置,来指定获取配置文件的config-server-git位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: config-client
cloud:
config:
uri: http://localhost:1200/ #指定配置中心
profile: default
label: master
# 获取配置文件规则:/{application}/{profile}[/{label}]
# spring.application.name:对应配置文件规则中的{application}部分
# spring.cloud.config.profile:对应配置文件规则中的{profile}部分
# spring.cloud.config.label:对应配置文件规则中的{label}部分
# spring.cloud.config.uri:配置中心config-server的地址
server:
port: 1201

启动类:

1
2
3
4
5
6
@SpringBootApplication
public class ApplicationClient {
public static void main(String[] args) {
SpringApplication.run(ApplicationClient.class, args);
}
}

启动后访问结果:

一个简单的demo完成。
整体流程:

4、加密解密

Spring Cloud Config提供了对属性进行加密解密的功能,以保护配置文件中的信息安全。比如下面的例子:

1
2
spring.datasource.username=didi
spring.datasource.password={cipher}dba6505baa81d7sdfafweafaf234324ji4h32u4h32uh4u32437cfbf143695f5b

在Spring Cloud Config中通过在属性值前使用{cipher}前缀来标注该内容是一个加密值,当微服务客户端来加载配置时,配置中心会自动的为带有{cipher}前缀的值进行解密。


加密准备:
使用Spring Cloud Config的加密解密功能时,为了启用该功能,需要在配置中心的运行环境中安装不限长度的JCE版本(Unlimited Strength Java Cryptography Extension)。
虽然JCE功能在JRE中就有,但是默认使用的是有长度限制的版本。我们可以从Oracle的官方网站中下载到它,它是一个压缩包,解压后可以看到下面三个文件:
README.txt
local_policy.jar
US_export_policy.jar
将local_policy.jar和US_export_policy.jar两个文件复制到$JAVA_HOME/jre/lib/security目录下,覆盖原来的默认内容。

完成了JCE的安装后,启动配置中心。在控制台中,将会输出了一些配置中心特有的端点,主要包括:

  • /encrypt/status:查看加密功能状态的端点
  • /key:查看密钥的端点
  • /encrypt:对请求的body内容进行加密的端点
  • /decrypt:对请求的body内容进行解密的端点
    可以通过GET请求访问/encrypt/status端点,我们将得到如下内容:
    1
    2
    3
    4
    {
    "description": "No key was installed for encryption service",
    "status": "NO_KEY"
    }
    返回说明当前配置中心的加密功能还不能使用,因为没有为加密服务配置对应的密钥。

配置密钥:
通过encrypt.key属性在配置文件中直接指定密钥信息(对称性密钥),比如:

1
2
encrypt:  
key: didispace #指定密钥信息

再访问http://localhost:1200/encrypt/status得到:

1
2
3
{
"status": "OK"
}

此时,我们配置中心的加密解密功能就已经可以使用了。

还有可以通过/encrypt和/decrypt端点来进行加密和解密的功能。
这两个端点都是POST请求,加密和解密信息需要通过请求体来发送。比如,以curl命令为例,我们可以通过下面的方式调用加密与解密端点:

1
2
3
4
$ curl localhost:7001/encrypt -d didispace
3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c920480be2c99406ae28c7
$ curl localhost:7001/decrypt -d 3c70a809bfa24ab88bcb5e1df51cb9e4dd4b8fec88301eb7a18177f1769c849ae9c9f29400c920480be2c99406ae28c7
didispace

这里,我们通过配置encrypt.key参数来指定密钥的实现方式采用了对称性加密。这种方式实现比较简单,只需要配置一个参数即可。另外,我们也可以使用环境变量ENCRYPT_KEY来进行配置,让密钥信息外部化存储。

非对称加密
Spring Cloud Config的配置中心不仅可以使用对称性加密,也可以使用非对称性加密(比如:RSA密钥对)。虽然非对称性加密的密钥生成与配置相对复杂一些,但是它具有更高的安全性。

首先,我们需要通过keytool工具来生成密钥对。
keytool是JDK中的一个密钥和证书管理工具。它使用户能够管理自己的公钥/私钥对及相关证书,用于(通过数字签名)自我认证(用户向别的用户/服务认证自己)或数据完整性以及认证服务。
在JDK 1.4以后的版本中都包含了这一工具,它的位置在:%JAVA_HOME%\bin\keytool.exe。

生成密钥的具体命令如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
C:\Users\hu>keytool -genkeypair -alias config-server -keyalg RSA -keystore config-server.keystore
输入密钥库口令:
密钥库口令太短 - 至少必须为 6 个字符
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
[Unknown]: 111111
您的组织单位名称是什么?
[Unknown]: 111111
您的组织名称是什么?
[Unknown]: 111111
您所在的城市或区域名称是什么?
[Unknown]: 111111
您所在的省/市/自治区名称是什么?
[Unknown]: 111111
该单位的双字母国家/地区代码是什么?
[Unknown]: 111111
CN=111111, OU=111111, O=111111, L=111111, ST=111111, C=111111是否正确?
[否]: 是

输入 <config-server> 的密钥口令
(如果和密钥库口令相同, 按回车):
再次输入新口令:

输入 <config-server> 的密钥口令
(如果和密钥库口令相同, 按回车):
再次输入新口令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
如果我们不想逐步的输入那些提示信息,可以使用-dname来直接指定,而密钥库口令与密钥口令可使用-storepass和-keypass来直接指定。所以,我们可以通过下面的命令直接创建出与上述命令一样的密钥库:

keytool -genkeypair -alias config-server -keyalg RSA \
-dname "CN=zhaiyongchao, OU=company, O=organization, L=city, ST=province, C=china" \
-keypass 222222 \
-keystore config-server.keystore \
-storepass 111111 \

默认情况下,上述命令创建的密钥只有90天有效期。如果我们想要调整它的有效期,可以通过增加-validity参数来实现,比如我们可以通过下面的命令,让密钥的有效期延长到一年:
keytool -genkeypair -alias config-server -keyalg RSA \
-dname "CN=zhaiyongchao, OU=company, O=organization, L=city, ST=province, C=china" \
-keypass 222222 \
-keystore config-server.keystore \
-storepass 111111 \
-validity 365 \

上述的三种命令生成方式,最终都会在命令的当前执行目录下生成一个config-server.keystore文件。下面,我们需要将它保存在配置中心的文件系统中的某个位置,比如放在当前的用户目录下,然后在配置中心中加入相关的配置信息:

encrypt.key-store.location=file://${user.home}/config-server.keystore
encrypt.key-store.alias=config-server
encrypt.key-store.password=111111
encrypt.key-store.secret=222222
如果我们将config-server.keystore放在配置中心的src/main/resource目录下,也可以直接这样配置:encrypt.key-store.location=config-server.keystore。另外,非对称加密的配置信息也可以通过环境变量的方式进行配置,它们对应的具体变量名如下:

ENCRYPT_KEY_STORE_LOCATION
ENCRYPT_KEY_STORE_ALIAS
ENCRYPT_KEY_STORE_PASSWORD
ENCRYPT_KEY_STORE_SECRET
通过环境变量来配置密钥库相关信息可以获得更好的安全性,所以我们可以将敏感的口令信息存储在配置中心的环境变量中是一种不错的选择。

配置中心高可用
在生产环境,Config Server与服务注册中心一样,我们也需要将其扩展为高可用的集群。
spring cloud config 实现高可用非常简单,不需要为这些服务端做额外的配置,只需要遵守一个配置规则:将所有的Config Server都指向同一个Git仓库
这样所有的配置内容就通过统一的共享文件系统来维护,而客户端在指定Config Server位置时,只要配置Config Server外的均衡负载即可,就像如下图所示的结构:
虽然通过服务端负载均衡已经能够实现,但是作为架构内的配置管理,本身其实也是可以看作架构中的一个微服务。所以,另外一种方式更为简单的方法就是把config-server也注册为服务,这样所有客户端就能以服务的方式进行访问。
通过这种方法,只需要启动多个指向同一Git仓库位置的config-server就能实现高可用了。
在config-server-git的基础上复制一个名为:config-server-git-eureka
依赖中增加:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

作为服务放到注册中心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
spring:
application:
name: config-server
cloud:
config:
server:
git:
uri: https://github.com/huingsn/config-repo-demo.git
server:
port: 1200
# 配置服务注册中心
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka

启动类增加@EnableDiscoveryClient //用来将config-server注册到上面配置的服务注册中心上去。

启动后可以访问注册中心,看到服务已经注册到注册中心。


config-client配置和config-server-git-eureka一样,复制config-client修改名字为config-client-eureka,需要增加

1
2
3
4
   <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>

修改配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
spring:
application:
name: huyun
cloud:
config:
discovery:
enabled: true #参数设置为true,开启通过服务来访问Config Server的功能
service-id: config-server #参数来指定Config Server注册的服务名。
profile: default #如之前通过URI的方式访问时候一样,用来定位Git中的资源。
label: master
fail-fast: true
server:
port: 1201
# 配置服务注册中心
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
debug: true

启动项目:
浏览http://localhost:1201/user即可得到key对应的值
demo 见:spring-cloud-config

Spring Cloud Hystrix

发表于 2018-08-14 | 分类于 springcloud

在微服务架构中,每个单元都在不同的进程中运行,依赖远程调用的方式执行,这就有可能因为网络原因或是依赖服务自身问题出现调用故障或延迟,此时调用方的请求不断增加,最后就会出现因等待出现故障的依赖方响应而形成任务积压,线程资源无法释放,最终导致自身服务的瘫痪,甚至导致整个系统的瘫痪。
为了解决这样的问题,因此产生了断路器等一系列的服务保护机制。

Spring Cloud Hystrix中实现了线程隔离、断路器等一系列的服务保护功能。它也是基于Netflix的开源框架 Hystrix实现的,该框架目标在于通过控制那些访问远程系统、服务和第三方库的节点,从而对延迟和故障提供更强大的容错能力。Hystrix具备了服务降级、服务熔断、线程隔离、请求缓存、请求合并以及服务监控等强大功能。

Hystrix服务降级

创建一个服务消费者hystrix-eureka-consumer-ribbon,pom.xml的dependencies节点中引入spring-cloud-starter-hystrix依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>

主类中使用@EnableCircuitBreaker或@EnableHystrix注解开启Hystrix的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@EnableCircuitBreaker //或@EnableHystrix
@EnableEurekaClient
@SpringBootApplication
public class ConsumerApplication {
/**
* 通过注解 @LoadBalanced 使用Ribbon负载均衡
* Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
* 这样就可以在Controller中,去掉通过LoadBalancerClient选取实例和拼接URL的步骤,直接通过RestTemplate发起请求。
* 请求的host位置并没有使用一个具体的IP地址和端口的形式,而是采用了服务名的方式组成,不需要具体的IP和端口。因为:
* Spring Cloud Ribbon有一个拦截器,它能够在这里进行实际调用的时候,自动的去选取服务实例,并将实际要请求的IP地址和端口替换这里的服务名,从而完成服务接口的调用。
* @return
*/
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
}

还可以使用Spring Cloud应用中的@SpringCloudApplication注解来修饰应用主类,该注解的具体定义如下,包含了上述的注解。

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {

}

在服务消费者服务中,新增ConsumerService类,在方法增加@HystrixCommand注解来指定服务降级方法,在controller中直接调用服务方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    @Service
class ConsumerService {

@Autowired
RestTemplate restTemplate;

/**
* 注解:@HystrixCommand用来指定服务降级方法
*/
@HystrixCommand(fallbackMethod = "userFallback")
public String[] users() {
String url = "http://provider-hello/users";
return restTemplate.getForObject(url, String[].class);
}

/*@HystrixCommand(fallbackMethod = "fallback")
public String users(String id) {
String url = "http://provider-hello/users/" + id;
return restTemplate.getForObject(url, String.class);
}*/

//如果服务降级了,userFallback
public String[] userFallback() {
String[] arr ={"err"};
return arr;
}
/*public String fallback() {
return "fallback";
}*/
}
```
控制器直接调用:
```java
@RestController
public class ConsumerController {
@Autowired
private ConsumerService consumerService;
@Autowired
private DiscoveryClient discoveryClient;

@RequestMapping(value = "/users", method = RequestMethod.GET)
public String[] users() throws InterruptedException{
return consumerService.users();
}

/*@RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
public String users(@PathVariable String id) {
return consumerService.users(id);
}*/
}

浏览正确输出值,为了验证服务降级,对代码做以下修改:

1
2
3
4
5
6
public String[] users() throws InterruptedException{
//为了出发服务降级,返回结果为arr
Thread.sleep(6000L);
System.out.println(Arrays.toString(discoveryClient.getServices().toArray()));
return consumerService.users();
}

由于延迟了,就返回fallback

依赖隔离

Hystrix通过实现线程池的隔离,它会为每一个Hystrix命令创建一个独立的线程池,这样就算某个在Hystrix命令包装下的依赖服务出现延迟过高的情况,也只是对该依赖服务的调用产生影响,而不会拖慢其他的服务。通过对依赖服务的线程池隔离实现,可以带来如下优势:

  • 应用自身得到完全的保护,不会受不可控的依赖服务影响。即便给依赖服务分配的线程池被填满,也不会影响应用自身的额其余部分。
  • 可以有效的降低接入新服务的风险。如果新服务接入后运行不稳定或存在问题,完全不会影响到应用其他的请求。
  • 当依赖的服务从失效恢复正常后,它的线程池会被清理并且能够马上恢复健康的服务,相比之下容器级别的清理恢复速度要慢得多。
  • 当依赖的服务出现配置错误的时候,线程池会快速的反应出此问题(通过失败次数、延迟、超时、拒绝等指标的增加情况)。同时,我们可以在不影响应用功能的情况下通过实时的动态属性刷新(后续会通过Spring Cloud Config与Spring Cloud Bus的联合使用来介绍)来处理它。
  • 当依赖的服务因实现机制调整等原因造成其性能出现很大变化的时候,此时线程池的监控指标信息会反映出这样的变化。同时,我们也可以通过实时动态刷新自身应用对依赖服务的阈值进行调整以适应依赖方的改变。
    每个专有线程池都提供了内置的并发实现,可以利用它为同步的依赖服务构建异步的访问。

总之,通过对依赖服务实现线程池隔离,让我们的应用更加健壮,不会因为个别依赖服务出现问题而引起非相关服务的异常。还可以在不停止服务的情况下,配合动态配置刷新实现性能配置上的调整。

线程池隔离的方案如此多的好处,使用者可能会担心为每一个依赖服务都分配一个线程池是否会过多地增加系统的负载和开销。但是Netflix在设计Hystrix的时候,认为隔离所带来的好处大于线程池上的开销。

如何使用

使用了@HystrixCommand来将某个函数包装成了Hystrix命令,这里除了定义服务降级之外,Hystrix框架还自动为这个方法实现了(框架内部)调用的隔离。依赖隔离、服务降级是一体化实现的。

Hystrix中除了使用线程池之外,还可以使用信号量来控制单个依赖服务的并发度,信号量的开销要比线程池的小,但是不能设置超时和实现异步访问,只有在依赖服务是足够可靠的情况下才使用信号量。
在HystrixCommand和HystrixObservableCommand中2处支持信号量的使用:

  • 命令执行:如果隔离策略参数execution.isolation.strategy设置为SEMAPHORE,Hystrix会使用信号量替代线程池来控制依赖服务的并发控制。
  • 降级逻辑:当Hystrix尝试降级逻辑时候,它会在调用线程中使用信号量。
    信号量的默认值为10,也可以通过动态刷新配置的方式来控制并发线程的数量。
断路器

分布式架构中,当某个服务单元发生故障(类似用电器发生短路)之后,通过断路器的故障监控(类似熔断保险丝),直接切断原来的主逻辑调用。
在Hystrix中的断路器除了切断主逻辑的功能之外,还有更复杂的逻辑。

在服务消费端的服务降级逻辑因为hystrix命令调用依赖服务超时,触发了降级逻辑,但是即使这样,受限于Hystrix超时时间的问题,调用依然很有可能产生堆积。

这个时候断路器就会发挥作用,断路器的三个重要参数:快照时间窗、请求总数下限、错误百分比下限。这个参数的作用分别是:

  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数下限:在快照时间窗内,必须满足请求总数下限才有资格根据熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用此时不足20次,即时所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比下限:当请求总数在快照时间窗内超过了下限,比如发生了30次调用,如果在这30次调用中,有16次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%下限情况下,这时候就会将断路器打开。

在断路器未打开之前,服务会根据之前的降级在延迟时间时返回fallback,当熔断器在10秒内发现请求总数超过20,并且错误百分比超过50%,这个时候熔断器打开。
打开之后,再有请求调用的时候,将不会调用主逻辑,而是直接调用降级逻辑,这个时候就不会等待5秒之后才返回fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

在断路器打开之后,处理逻辑并没有结束,我们的降级逻辑已经被成了主逻辑,对于原来的主逻辑要恢复问题,hystrix实现了自动恢复功能。当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。

综述:hystrix的断路器实现了对依赖资源故障的端口、对降级策略的自动切换以及对主逻辑的自动恢复机制,使得微服务在依赖外部服务或资源的时候得到了保护,同时对于一些具备降级逻辑的业务需求可以实现自动化的切换与恢复,相比于设置开关由监控和运维来进行切换的传统实现方式显得更为智能和高效。

Hystrix监控面板

hystrix-eureka-consumer-ribbon中接口实现使用了@HystrixCommand修饰,HystrixCommand和HystrixObservableCommand实例在执行过程中记录这些请求情况的指标信息会在内存中汇总,并保留一段时间,以供内部或外部进行查询使用,users这个接口的调用情况会被Hystrix记录下来,以用来给断路器和Hystrix Dashboard使用。

配置监控

见项目hystrix-dashboard
引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hu</groupId>
<artifactId>Hystrix-Dashboard</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

配置:

1
2
3
4
5
6
7
8
server:
port: 7904
spring:
application:
name: hystrix-dashboard
logging:
level:
root: INFO

启动类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* EnableHystrixDashboard注解,开启Hystrix Dashboard功能
* SpringCloudApplication是 spring cloud注解,包含了如下注解:
* -@Target({ElementType.TYPE})
* -@Retention(RetentionPolicy.RUNTIME)
* -@Documented
* -@Inherited
* -@SpringBootApplication
* -@EnableDiscoveryClient Eureka客户端,和EnableEurekaClient一个意思
* -@EnableCircuitBreaker 熔断器
*/
@EnableHystrixDashboard
@SpringCloudApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}

访问:http://localhost:7904/hystrix

Hystrix Dashboard的监控首页,该页面中并没有具体的监控信息。 页面上两个参数:
Delay:该参数用来控制服务器上轮询监控信息的延迟时间,默认为2000毫秒,我们可以通过配置该属性来降低客户端的网络和CPU消耗。
Title:该参数对应了上图头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,我们可以通过配置该信息来展示更合适的标题。

从页面的文字内容中我们可以知道,Hystrix Dashboard共支持三种不同的监控方式,依次为:

  • 默认的集群监控:通过URLhttp://turbine-hostname:port/turbine.stream开启,实现对默认集群的监控。
  • 指定的集群监控:通过URLhttp://turbine-hostname:port/turbine.stream?cluster=[clusterName]开启,实现对clusterName集群的监控。
  • 单体应用的监控:通过URLhttp://hystrix-app:port/hystrix.stream开启,实现对具体某个服务实例的监控。

前两者都对集群的监控,需要整合Turbine才能实现,在这里对单个服务实例的监控。

Hystrix Dashboard监控单实例节点需要通过访问实例的/hystrix.stream接口来实现,那需要为服务实例添加这个端点,而添加该功能的步骤:

1、在服务实例hystrix-eureka-consumer-ribbon的pom.xml中新增spring-boot-starter-actuator监控模块以开启监控相关的端点并确保已经引入断路器的依赖spring-cloud-starter-hystrix

1
2
3
4
5
6
7
8
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启动类中开启了断路器功能。

2、确保在服务实例的主类中已经使用@EnableCircuitBreaker或@EnableHystrix注解,开启了断路器功能。
之后再Hystrix Dashboard的首页输入http://localhost:7902/hystrix.stream,已启动对hystrix-eureka-consumer-ribbon的监控。

监控页面介绍:
在监控信息的左上部分找到两个重要的图形信息:一个实心圆和一条曲线。
实心圆,共有两种含义: 它通过颜色的变化代表了实例的健康程度,它的健康度从绿色、黄色、橙色、红色递减。 除了颜色的变化之外,大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。 所以通过该实心圆的展示,我们就可以在大量的实例中快速的发现故障实例和高压力实例。
曲线:用来记录2分钟内流量的相对变化,我们可以通过它来观察到流量的上升和下降趋势。

Hystrix集群监控数据聚合

使用Turbine来对服务的Hystrix数据进行聚合展示,有HTTP收集聚合和通过消息代理收集聚合两种方式。

1、通过HTTP收集聚合

创建一个模块,名字为:turbine,引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>turbine</artifactId>

<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 7922
spring:
application:
name: turbine
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
logging:
level:
root: INFO
management:
port: 9999
turbine:
app-config: eureka-consumer-ribbon-hystrix #指定了需要收集监控信息的服务名
cluster-name-expression: default #指定了集群名称为default
#服务数量非常多的时候,可以启动多个Turbine服务来构建不同的聚合集群,
#而该参数可以用来区分这些不同的聚合集群,同时该参数值可以在Hystrix仪表盘中用来定位不同的聚合集群,
#只需要在Hystrix Stream的URL中通过cluster参数来指定。
combine-host-port: true #让同一主机上的服务通过主机名与端口号的组合来进行区分,默认情况下会以host来区分不同的服务

启动类:

1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableAutoConfiguration
@EnableTurbine //注解开启Turbine
@EnableDiscoveryClient
public class TurbineApplication {

public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}

}

创建完毕,启动项目:
访问Hystrix Dashboard,输入对turbine监控的url:http://localhost:7922/turbine.stream
将看到针对服务hystrix-eureka-consumer-ribbon的聚合监控数据。

2、通过消息代理收集聚合

Spring Cloud在封装Turbine的时候,还实现了基于消息代理的收集实现,可以将所有需要收集的监控信息都输出到消息代理中,然后Turbine服务再从消息代理中异步的获取这些监控信息,最后将这些监控信息聚合并输出到Hystrix Dashboard中。
创建模块turbine-amqp,添加配置和依赖,见项目turbine-amqp。
创建完成后需要对服务消费者hystrix-eureka-consumer-ribbon做修改,使其监控信息能够输出到RabbitMQ上。
在消费者服务上增加对mq的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<artifactId>turbine-amqp</artifactId>
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>Dalston.SR1</version>
<relativePath/>
</parent>

<dependencies>
<!--spring-cloud-starter-turbine-amqp-->
<!--包含了spring-cloud-starter-turbine-stream和pring-cloud-starter-stream-rabbit。-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
</project>
1
2
3
4
5
6
7
8
9
10
11
@Configuration
@EnableAutoConfiguration
@EnableTurbineStream //启用Turbine Stream的配置
@EnableDiscoveryClient
public class TurbineApplication {

public static void main(String[] args) {
SpringApplication.run(TurbineApplication.class, args);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7927
spring:
application:
name: turbine-mq
eureka:
client:
serviceUrl:
defaultZone: http://user:123456@localhost:8761/eureka
logging:
level:
root: INFO
management:
port: 9999

完成配置后,通过Hystrix Dashboard开启对turbine-amqp的监控,可以获得和之前的效果,只是这里我们的监控信息收集时是通过了消息代理异步实现的。

spring整合Oauth2

发表于 2018-07-20 | 分类于 spring boot

OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1.0。 OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。

举个例子:

在某论坛上,希望用QQ登录。点了QQ登录,那论坛就引导跳转到QQ的授权页面了,同时需要对论坛进行认证。
在这个页面里,我要登录一下QQ了。前面已经认证过论坛了,现在登录就是认证我了。
登录成功后,QQ显示出授权的内容,让我确认,我点了确认。
确认之后,事实上论坛当初转到QQ授权页面的时候,除了提供自己的身份,还有一个redirectUrl。当我点了授权后,QQ把合同给了这个Url,等于就是给了论坛了。
论坛拿到了认证信息,再把自己的登录信息给QQ再验证一下,QQ就把我的资料给了论坛了。

标准的认证过程

OAuth认证和授权的过程如下

1、用户访问第三方网站网站,想对用户存放在服务商的某些资源进行操作。
2、第三方网站向服务商请求一个临时令牌。
3、服务商验证第三方网站的身份后,授予一个临时令牌。
4、第三方网站获得临时令牌后,将用户导向至服务商的授权页面请求用户授权,然后这个过程中将临时令牌和第三方网站的返回地址发送给服务商。
5、用户在服务商的授权页面上输入自己的用户名和密码,授权第三方网站访问所相应的资源。
6、授权成功后,服务商将用户导向第三方网站的返回地址。
7、第三方网站根据临时令牌从服务商那里获取访问令牌。
8、服务商根据令牌和用户的授权情况授予第三方网站访问令牌。
9、第三方网站使用获取到的访问令牌访问存放在服务商的对应的用户资源。

一些概念

1)Third-party application:第三方应用程序(client)。
2)HTTP service:HTTP服务提供商。
3)Resource Owner:资源所有者-"用户"(user)。
4)User Agent:用户代理-浏览器。
5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

运行流程

(A)用户打开客户端,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权(一般是Code),向认证服务器申请令牌TOKEN。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源(用户信息等)。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

客户端获取授权的五种模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。

OAuth 2.0定义了五种授权方式:

关于session和token

    session是空间换时间,而token是时间换空间。session占用空间,但是可以管理过期时间,token管理部了过期时间,但是不占用空间.
    sessionId失效问题和token内包含。
    session基于cookie,app请求并没有cookie 。
    token更加安全(每次请求都需要带上)。

授权码模式(authorization code)
功能最完整、流程最严密的授权模式。
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)
扩展模式(Extension)

Java日期时间API完全解析

发表于 2018-06-22 | 分类于 java

时区

GMT(Greenwich Mean Time):格林尼治时间,格林尼治标准时间的正午是指当太阳横穿格林尼治子午线时(也就是在格林尼治上空最高点时)的时间。

UTC(Universal Time Coordinated):统一协调时间,其以原子时秒长为基础,在时刻上尽量接近于格林尼治标准时间,标准 UTC 时间格式 yyyy-MM-dd’T’HH:mm:ss.SSSXXX。

格林尼治时间已经不再被作为标准时间使用,UTC 是最主要的世界时间标准。

Java提供了获取当前时间的方法

  • System.currentTimeMillis() 返回当前时间,以毫秒为单位。
    表示是当前时刻至 1970-01-01 00:00:00.000 的毫秒差值。返回的long值可以用来初始化java.util.Date, java.sql.Date, java.sql.Timestamp和java.util.GregorianCalendar对象。

  • System.nanoTime() 返回一个时间值(系统计时器的当前值),精确到纳秒。
    它是由 JVM 提供的一个时间,主要用来精确衡量两个时间段之间的时间

时间粒度:System.currentTimeMillis()方法的时间粒度是大于1毫秒的。如果你反复执行这个方法,短时间内得到的结果是相同的,或又突然在某一次结果增加了几十毫秒,这是正常的。

旧的时间API

  • Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。
  • java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
  • 对于时间、时间戳、格式化以及解析,并没有一些明确定义的类。对于格式化和解析的需求,我们有java.text.DateFormat抽象类,但通常情况下,SimpleDateFormat类被用于此类需求。
  • 所有的日期类都是可变的,因此他们都不是线程安全的,这是Java日期类最大的问题之一。
  • 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
java.util.Date

java.util.Date类用于封装日期及时间信息,一般仅用它显示某个日期,不对他作任何操作处理,作处理推荐用Calendar类,计算方便。

构造方法

Date() :分配 Date对象并初始化此对象,以表示分配它的时间(精确到毫秒)。

Date(long date):分配 Date对象并初始化此对象,以表示自从标准基准时间(称为“历元(epoch)”,即 1970 年 1 月 1 日 00:00:00 GMT)以来的指定毫秒数。

Calendar与GregorianCalendar

java.util.Calendar类用于封装日历信息,其主作用在于其方法可以对时间分量进行运算。Calendar类是一个抽象类,它为特定瞬间与一组诸如 YEAR、MONTH、DAY_OF_MONTH、HOUR 等 日历字段之间的转换提供了一些方法,并为操作日历字段(例如获得下星期的日期)提供了一些方法。

Calendar类是抽象类,构造方法是protected的,API中提供了getInstance方法用来创建对象。

Java只提供java.util.GregorianCalendar这一种java.util.Calendar的实现类。

SimpleDateFormat

SimpleDateFormat(String pattern),pattern -为描述日期和时间格式的模式,注:此构造方法可能不支持所有语言环境。要覆盖所有语言环境,请使用 DateFormat 类中的工厂方法。

1
2
public final String format(Date date)将一个 Date 格式化为日期/时间字符串
public Date parse(String source)throws ParseException从给定字符串的开始解析文本,以生成一个日期。
DateFormat

java.text.DateFormat类(抽象类)是SimpleDateFormat类的父类

java.sql.Date

java.sql.Date继承java.util.Date,为了把前者转为后者,不支持Date参数的构造器,传入long类型的时间。

java.sql.Date是SQL中的单纯的日期类型,没有时分秒。如果同时需要日期和时间,应该使用Timestamp。它也是 java.util.Date 的子类,Timestamp 则包含时间戳的完整信息。

java.sql.Timestamp是java.util.Date的派生类(继承),所以在java.util.Date上能做的事,也可以在java.sql.Timestamp上做。

现在的Date类中大部分方法已经弃用,现在一般使用旧的API,Date只负责存储一个时间,并对Calendar和DateFormat提供操作接口。Calendar获取Date中特定的信息,对日期时间进行操作,SimpleDateFormat对日期时间进行格式化输入输出。

总的来说,Date、Calendar 和 DateFormat 已经能够处理一般的时间日期问题了。但是它们依然很繁琐,不好用并且这些日期类都是可变且线程不安全的。

Java 8 时间日期API

特性

1
2
3
4
5
不变性:新的日期/时间API中,所有的类都是不可变的,这对多线程很有好处。
关注点分离:借鉴了Joda库的一些优点,新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不同的类。
清晰:在所有的类中,方法都被明确定义用以完成相同的行为。例如要拿到当前实例我们可以使用now()方法,在所有的类中都定义了format()和parse()方法,而不是像以前那样专门有一个独立的类。为了更好的处理问题,所有的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其他类协同工作并不困难。
实用操作:所有新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。
可扩展性:新的日期/时间API是工作在ISO-8601日历系统上的,但我们也可以将其应用在非IOS的日历上。

Java8日期时间的默认格式如下:yyyy-MM-dd-HH-mm-ss.zzz

主要的 核心类:

1
2
3
4
5
6
7
8
9
10
11
12
LocalDate:日期类,不带时间
LocalTime:时间类,不带日期
LocalDateTime:日期和时间类
ZonedDateTime:时区日期时间类
OffsetDateTime:按UTC时间偏移来得到日期时间
Clock:获取某个时区下当前的瞬时时间,日期或者时间
Instant:Unix时间,代表时间戳,比如 2018-01-14T02:20:13.592Z
Duration:两个时间之间,表示一个绝对的精确跨度,使用毫秒为单位
Period:两个日期之间
ZoneId:时区
DateTimeFormatter:格式化输出
TemporalAdjusters:获得指定日期时间等,如当月的第一天、今年的最后一天等等
LocalDate、LocalTime、LocalDateTime

LocalDate是不变的日期时间对象代表一个日期,往往被视为年月日。其他日期字段,如一年中的一天,一周和一周的一天,也可以访问。

LocalTime是不变的日期时间对象代表一个时间,往往被视为小时分钟秒。时间为代表的纳秒级精度。

LocalDateTime是不变的日期时间对象代表一个日期时间,往往被视为年、月、日、时、分、秒。其他日期和时间字段,如一年中的一天,一周和一周的一天,也可以访问。时间为代表的纳秒级精度。

DateTimeFormatter

格式器用于解析日期字符串和格式化日期输出,创建格式器最简单的方法是通过 DateTimeFormatter 的静态工厂方法以及常量。创建格式器一般有如下三种方式:

1
2
3
常用 ISO 格式常量,如 ISO_LOCAL_DATE
字母模式,如 ofPattern("yyyy/MM/dd")
本地化样式,如 ofLocalizedDate(FormatStyle.MEDIUM)

使用DateTimeFormatter完成格式化

1
2
3
4
5
6
7
8
9
LocalDateTime localDateTime = LocalDateTime.now();
//创建一个格式化程序使用指定的模式。
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatDateTime = localDateTime.format(formatter);
System.out.println(formatDateTime);

//DateTimeFormatter提供了一些默认的格式化器,DateTimeFormatter.ISO_LOCAL_DATE_TIME 格式 yyyy-MM-ddTHH:mm:ss.SSS
String dateTime2 = localDateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
System.out.println(dateTime2);

使用DateTimeFormatter完成格式化

1
2
LocalDate localDate = LocalDate.parse("2018/11/11",DateTimeFormatter.ofPattern("yyyy/MM/dd"));
System.out.println(localDate); //2018-11-11

和旧的 java.util.DateFormat 相比较,所有的 DateTimeFormatter 实例都是线程安全的。

Instant

Instant 表示时间线上的一点(与 Date 类似),它只是简单地表示自 1970 年 1 月 1 日 0 时 0 分 0 秒(UTC)开始的秒数。

Instant 由两部分组成,一是从原点开始到指定时间点的秒数 s, 二是距离该秒数 s 的纳秒数。它以 Unix 时间戳的形式存储日期时间。

Instant类的工厂方法创建一个Instant实例

1
2
3
4
5
6
7
Instant now = Instant.now();
//第一个参数是秒,第二个是纳秒参数,纳秒的存储范围是0至999,999,999 2s之后的在加上100万纳秒(1s)
Instant instant = Instant.ofEpochSecond(2,1000000000);
System.out.println(instant); //1970-01-01T00:00:03Z
//java.util.Date与Instant可相互转换
Instant timestamp = new Date().toInstant();
Date.from(Instant.now());
Clock

Clock用于查找当前时刻,可以用来获取某个时区下当前的日期和时间,也可以用来代替旧的System.currentTimeMillis()方法和TimeZone.getDefault()方法。

Duration

一个Duration实例是不可变的,当创建出对象后就不能改变它的值了。你只能通过Duration的计算方法,来创建出一个新的Durtaion对象.

Period

Period 是以年月日来衡量一个时间段,用于计算两个日期间隔,所以 between() 方法只能接收 LocalDate 类型的参数。

ZonedDateTime和ZonedId

ZonedDateTime类是Java 8中日期时间功能里,用于表示带时区的日期与时间信息的类。ZonedDateTime 类的值是不可变的,所以其计算方法会返回一个新的ZonedDateTime 实例。

Java 使用 ZoneId 来标识不同的时区,从基准 UTC 开始的一个固定偏移。

TemporalAdjusters

有的时候,你需要进行一些更加复杂的操作,比如,将日期调整到下个周日、下个工作日,或者是本月的最后一天。

1
2
3
4
5
6
7
8
9
10
11
LocalDate localDate = LocalDate.now();  
// 1. 本月第一天
LocalDate firstDayOfMonth = localDate.with(TemporalAdjusters.firstDayOfMonth());
// 2. 本月最后一天
LocalDate lastDayOfMonth = localDate.with(TemporalAdjusters.lastDayOfMonth());
// 3. 本年第一天
LocalDate firstDayOfYear = localDate.with(TemporalAdjusters.firstDayOfYear());
// 4. 下个月第一天
LocalDate firstDayOfNextMonth = localDate.with(TemporalAdjusters.firstDayOfNextMonth());
// 5. 本年度最后一天
LocalDate lastDayOfYear = localDate.with(TemporalAdjusters.lastDayOfYear());

用重载版本的with方法,向其传递一个提供了更多定制化选择的TemporalAdjuster对象,更加灵活地处理日期。TemporalAdjusters类通过静态方法提供了大量的常用的TemporalAdjuster的实现供我们使用。

这些日期类之间转换

java.util.Date 与 LocalDate、LocalTime、LocalDateTime 转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//将Date转换为LocalDate,LocalTime,LocalDateTime可以借助于ZonedDateTime和Instant,实现如下:

Date date = new Date();

// Date -> LocalDateTime
LocalDateTime localDateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
System.out.println("localDateTime by Instant: " + localDateTime);

// Date -> LocalDate
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

// Date -> LocalTime
LocalTime localTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalTime();

//2. Date -> LocalDateTime
localDateTime = LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault());
System.out.println("localDateTime by ofInstant: " + localDateTime);

由于JDK8实现了向下兼容,所以Date里在JDK8版本引入了2个方法,from和toInstant,所以我们可以借助这两个方法来实现LocalDateTime到Date的转换。
将LocalDateTime转为Date如下:

1
2
3
4
5
6
7
8
LocalDateTime localDateTime = LocalDateTime.now();

// LocalDateTime -> Date
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

// LocalDate -> Date,时间默认都是00
LocalDate localDate = LocalDate.now();
date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());

日期与字符串的转换

1
2
3
4
5
6
7
8
9
10
11
12
//通过LocalDate,LocalTime,LocalDateTime的parse方法和DateTimeFormatter来实现:

//字符串->日期
LocalDate localDate = LocalDate.parse("2018-09-09", DateTimeFormatter.ofPattern("yyyy-MM-dd"));
LocalDateTime localDateTime = LocalDateTime.parse("2018-09-10 12:12:12", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

//日期->字符串
String localDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String localDateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// 也可以通过DateTimeFormatter的format方法
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
localDateTime = dateTimeFormatter.format(LocalDateTime.now());

时间戳与LocalDateTime转换

1
2
3
4
5
6
7
8
9
10
11
12
13
//时间戳->LocalDateTime
public static LocalDateTime convertToDate(long timestamp) {
// ofEpochSecond 以秒为单位, ofEpochMilli 以毫秒为单位
// Instant.ofEpochSecond(timestamp);
Instant instant = Instant.ofEpochMilli(timestamp);
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
}

//LocalDateTime->时间戳
public static long convertToTimestamp() {
LocalDateTime localDateTime = LocalDateTime.now();
return localDateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
}

新的 API 区分各种日期时间概念并且各个概念使用相似的方法定义模式,这种相似性非常有利于 API 的学习。总结一下一般的方法或者方法前缀:

1
2
3
4
5
6
7
8
9
10
11
of:静态工厂方法,用于创建实例
now:静态工厂方法,用当前时间创建实例
parse:静态工厂方法,从字符串解析得到对象实例
get:获取时间日期对象的部分状态。
is:检查某些东西的是否是 true,例如比较时间前后
with:返回一个部分状态改变了的时间日期对象拷贝
plus:返回一个时间增加了的、时间日期对象拷贝
minus:返回一个时间减少了的、时间日期对象拷贝
to:转换到另一个类型
at:把这个对象与另一个对象组合起来,例如 date.atTime(time)
format:提供格式化时间日期对象的能力

参考文章:https://www.cnblogs.com/kiwenzhang/p/10960741.html

Joda-Time

Joda-Time提供了一组Java类包用于处理包括ISO8601标准在内的date和time。可以利用它把JDK Date和Calendar类完全替换掉,而且仍然能够提供很好的集成。

Joda 与 JDK 是百分之百可互操作的,因此您无需替换所有 Java 代码,只需要替换执行日期/时间计算的那部分代码。

如果时间需要按照今天,昨天这样返回,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class DateFormatUtil {
public static String format(Date date) {
DateTime now = new DateTime();
DateTime today_start = new DateTime(now.getYear(), now.getMonthOfYear(), now.getDayOfMonth(), 0, 0, 0);
DateTime today_end = today_start.plusDays(1);
DateTime yesterday_start = today_start.minusDays(1);

if(date.after(today_start.toDate()) && date.before(today_end.toDate())) {
return String.format("今天 %s", new DateTime(date).toString("HH:mm:ss"));
} else if(date.after(yesterday_start.toDate()) && date.before(today_start.toDate())) {
return String.format("昨天 %s", new DateTime(date).toString("HH:mm:ss"));
}

return new DateTime(date).toString("yyyy-MM-dd HH:mm:ss");
}
public static String formats(String dateString) {
if(StringUtils.isEmpty(dateString)){
return "";
}
try {
SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = dateFormat.parse(dateString);
return format(date);
}catch (ParseException e){
e.printStackTrace();
}
return dateString;
}
/*public static void main(String[] args) throws ParseException {
System.out.println(formats("2019-06-01 10:12:05"));
System.out.println(formats("2019-07-12 10:12:05"));
System.out.println(formats("2019-07-11 10:12:05"));
System.out.println(formats("2019-07-10 10:12:05"));
}*/
}

需要引入包:

1
2
3
4
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>

MongoDB基本操作

发表于 2018-06-14 | 分类于 MongoDB

1、查看数据库信息

在客户端指定数据库进行连接:(默认连接本机test数据库)

[root@localhost conf]# mongo 127.0.0.1/admin

查看数据库版本

1
2
> db.version()
3.6.11

查看所有数据库

1
2
3
4
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB

切换数据库

1
2
> use test;
switched to db test

显示当前数据库

1
2
3
4
> db
test
> db.getName()
test

查看当前数据库状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
> db.stats();
{
"db" : "test",
"collections" : 0,
"views" : 0,
"objects" : 0,
"avgObjSize" : 0,
"dataSize" : 0,
"storageSize" : 0,
"numExtents" : 0,
"indexes" : 0,
"indexSize" : 0,
"fileSize" : 0,
"fsUsedSize" : 0,
"fsTotalSize" : 0,
"ok" : 1
}

查看当前数据库的连接机器地址

1
2
> db.getMongo();
connection to 127.0.0.1:27017

2、数据管理

创建数据库,当use的时候,系统就会自动创建一个数据库。
如果use之后没有创建任何集合。系统就会删除这个数据库。

1
2
> use demo;
switched to db demo

删除数据库:如果没有选择任何数据库,会删除默认的test数据库

1
2
> db.dropDatabase()
{ "ok" : 1 }

创建集合

1
2
3
4
> db.createCollection('h');
{ "ok" : 1 }
> db.createCollection('u');
{ "ok" : 1 }

显示集合

1
2
3
4
5
> show collections;
h
u
> db.getCollectionNames()
[ "h", "u" ]

当插入一个文档的时候,一个集合就会自动创建。

1
2
3
4
> db.c.insert({name:'hu'});
WriteResult({ "nInserted" : 1 })
> db.c.insert({ming:'yun'});
WriteResult({ "nInserted" : 1 })

查看创建的集合

1
2
> db.getCollectionNames()
[ "c", "h", "u" ]

查看集合内容

1
2
3
> db.c.find()
{ "_id" : ObjectId("5c84a575435a08991662e280"), "name" : "hu" }
{ "_id" : ObjectId("5c84a58d435a08991662e281"), "ming" : "yun" }

重命名集合

1
2
3
4
> db.c.renameCollection('shun');
{ "ok" : 1 }
> db.getCollectionNames()
[ "h", "shun", "u" ]

删除集合

1
2
3
4
> db.h.drop();
true
> db.getCollectionNames()
[ "shun", "u" ]

批量插入数据到集合

1
2
> for(i=0;i<1000;i++){db.h.insert({'id':i,'name':'huyun','age':100,'date':new Date()});}
WriteResult({ "nInserted" : 1 })

查询集合中的查询所有记录

1
> db.h.find()

注:默认每页显示20条记录,当显示不下的的情况下,可以用it迭代命令查询下一页数据。

每页显示50条

1
2
> DBQuery.shellBatchSize=50;
50

查询第一条

1
2
3
4
5
6
7
8
9
10
> db.h.findOne()
{
"_id" : ObjectId("5c84a69f435a08991662e282"),
"id" : 1,
"name" : "huyun",
"age" : 100,
"date" : ISODate("2019-03-10T05:54:39.904Z")
}
> db.h.count()
1000

按照id查找

1
2
3
4
5
> db.h.find({id:100});
{ "_id" : ObjectId("5c84a8a8435a08991662e6ce"), "id" : 100, "name" : "huyun", "age" : 100, "date" : ISODate("2019-03-10T06:03:20.283Z") }

> db.h.distinct("name") # 查询去掉当前集合中某列的重复数据
> db.h.remove({}) # 删除集合中所有记录

查看集合存储信息

1
2
3
4
5
> db.h.stats()          # 查看数据状态
> db.h.dataSize() # 集合中数据的原始大小
> db.h.totalIndexSize() # 集合中索引数据的原始大小
> db.h.totalSize() # 集合中索引+数据压缩存储之后的大小
> db.h.storageSize() # 集合中数据压缩存储的大小

MongoDB介绍和安装

发表于 2018-06-14 | 分类于 MongoDB

MongoDB介绍

MongoDB是跨平台的、一个基于分布式文件存储的数据库。由C++语言编写。用它创建的数据库具备性能高、可用性强、易于扩展等特点。MongoDB将数据存储为一个文档,数据结构由键值(key:value)对组成。MongoDB文档类似于 JSON 对象。字段值可以包含字符串、数字、数组、当然也可以是另一个文档。文档是可以嵌套的。

20200413174638

20200413174652

集合相当于关系型数据库中的表。集合就是一组MongoDB文档。集合存在于数据库中,集合没有固定的结构,这意味着可以对集合插入不同格式和类型的数据,但通常情况下我们插入集合的数据都会有一定的关联性。

注:

集合名不能是空字符串""。
集合名不能含有\0字符(空字符),这个字符表示集合名的结尾。
集合名不能以"system."开头,这是为系统集合保留的前缀。
集合名用户创建的集合名字不能含有保留字符。有些驱动程序的确支持在集合名里面包含,这是因为某些系统生成的集合中包含该字符。除非你要访问这种系统创建的集合,否则千万不要在名字里出现$。

文档相当于关系型数据库中的行。它一组键值对,具有动态的模式,所以文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这一点,跟关系型数据库有很大的不同。

注:

文档中的键/值对是有序的。
MongoDB区分类型和大小写。
MongoDB的文档不能有重复的键。
键不能含有\0 (空字符)。
.和$有特别的意义,只有在特定环境下才能使用。

参考:https://www.cnblogs.com/clsn/p/8214194.html#auto_id_10

MongoDB数据存储格式:JSON格式和BSON格式

BSON基于JSON格式,选择JSON进行改造的原因主要是JSON的通用性及JSON的schemaless的特性。

  二进制的JSON,JSON文档的二进制编码存储格式
  BSON有JSON没有的Date和BinData
  MongoDB中document以BSON形式存放

MongoDB适用场景

  网站数据、缓存等大尺寸、低价值的数据
  在高伸缩性的场景,用于对象及JSON数据的存储。

20200413174557

MongoDB安装

MongoDB官网:https://www.mongodb.com/

CentOS6.X版本软件下载地址:https://www.mongodb.org/dl/linux/x86_64-rhel62

步骤:

创建用户:

[root@localhost ~]# groupadd -g 800 mongod
[root@localhost ~]# useradd -u 801 -g mongod mongod

修改密码

[root@localhost ~]# echo 123456 |passwd --stdin mongod

下载安装程序:

[root@localhost ~]# mkdir -p /usr/local/mongodb/
[root@localhost ~]# cd /usr/local/mongodb/
[root@localhost mongodb]# wget http://downloads.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.6.11.tgz

解压安装

[root@localhost mongodb]# tar -xf mongodb-linux-x86_64-rhel62-3.6.11.tgz

创建目录

[root@localhost mongodb]# mkdir  -p  bin  conf  log  data

复制文件

[root@localhost mongodb]# cp mongodb-linux-x86_64-rhel62-3.6.11/bin/* bin/

添加环境变量

[root@localhost mongodb]# vim /etc/profile
添加export PATH=/usr/local/mongodb/bin:$PATH
[root@localhost mongodb]# source /etc/profile

测试:

1
2
3
4
5
6
7
8
9
10
启动:mongod --dbpath=/usr/local/mongodb/data --logpath=/usr/local/mongodb/log/mongodb.log --port=27017 --logappend --fork
关闭:mongod --shutdown --dbpath=/usr/local/mongodb/data --logpath=/usr/local/mongodb/log/mongodb.log --port=27017 --logappend --fork
--dbpath 数据存放路径
--logpath 日志文件路径
--logappend 日志输出方式
--port 启用端口号
--fork 在后台运行
--auth 是否需要验证权限登录(用户名和密码)
--bind_ip 限制访问的ip
--shutdown 关闭数据库

登录数据库:

[root@localhost bin]# mongo
MongoDB shell version v3.6.11
connecting to: mongodb://127.0.0.1:27017/?gssapiServiceName=mongodb

使用配置文件的方式启动数据库:

[root@localhost bin]# cd ../conf/
[root@localhost conf]# vim mongod.conf
      dbpath=/usr/local/mongodb/data
      logpath=/usr/local/mongodb/log/mongodb.log
      port=27017
      logappend=1
      fork=1

启动

[root@localhost conf]# mongod -f mongod.conf

关闭

[root@localhost conf]# mongod -f mongod.conf –shutdown

使用YAML格式配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost conf]# vim mongod.conf
systemLog:
destination: file
path: "/usr/local/mongodb/log/mongodb.log"
logAppend: true
storage:
journal:
enabled: true
dbPath: "/usr/local/mongodb/data"
processManagement:
fork: true
net:
port: 27017

在数据库中关闭数据库的方法:

use admin
db.shutdownServer()

用户管理

MongoDB数据库默认是没有用户名及密码的,即无权限访问限制。为了方便数据库的管理和安全,需创建数据库用户。

用户权限

Read      允许用户读取指定数据库
readWrite           允许用户读写指定数据库
dbAdmin             允许用户在指定数据库中执行管理函数,如索引创建、删除,查看统计或访问system.profile
userAdmin           允许用户向system.users集合写入,可以找指定数据库里创建、删除和管理用户
clusterAdmin        只在admin数据库中可用,赋予用户所有分片和复制集相关函数的管理权限。
readAnyDatabase     只在admin数据库中可用,赋予用户所有数据库的读权限
readWriteAnyDatabase        只在admin数据库中可用,赋予用户所有数据库的读写权限
userAdminAnyDatabase        只在admin数据库中可用,赋予用户所有数据库的userAdmin权限
dbAdminAnyDatabase          只在admin数据库中可用,赋予用户所有数据库的dbAdmin权限。
root                只在admin数据库中可用。超级账号,超级权限

创建用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
> use admin
switched to db admin

> db.createUser(
... {
... user:"root",
... pwd:'123456',
... roles:[{role:"root",db:"admin"}]
... });
Successfully added user: {
"user" : "root",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}

创建管理员角色用户的时候,必须到admin下创建。 删除的时候也要到相应的库下操作。

创建完成后查看用户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
> show tables;
system.users
system.version
> show users;
{
"_id" : "admin.root",
"user" : "root",
"db" : "admin",
"roles" : [
{
"role" : "root",
"db" : "admin"
}
]
}

验证用户是否能用

1
2
> db.auth('root','123456');
1

在配置文件中开启用户验证,修改配置文件

1
2
3
4
5
[root@localhost conf]# cat >>mongod.conf<<-'EOF'
> security:
> authorization: enabled
> EOF
[root@localhost conf]# mongod restart

重启后

mongo -uroot -p123456 admin

创建对某库的只读用户roles: [ { role: “read”, db: “test” } ]

创建某库的读写用户roles: [ { role: “readWrite”, db: “test” } ]

创建对多库不同权限的用户

roles: [ { role: "readWrite", db: "app" },
        { role: "read", db: "test" }
]

删除用户

db.dropUser("hu")

创建数据库的管理员

roles: [ { role: "dbAdmin", db: "test" } ]

创建数据库读写权限的用户并具有clusterAdmin权限:

roles: [ { role: "readWrite", db: "test" },
{ role: "clusterAdmin", db: "admin" }

MongoDB和SQL对比(转自网络)

20200413175520

20200413175548

20200413175600

安装启动服务失败,直接跳过,手动启动

.\mongod –dbpath=”D:\Program Files\MongoDB\Server\4.0\data”

Springboot中使用AOP统一处理Web请求日志异常

发表于 2018-06-14 | 分类于 spring boot

日志

面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP是Spring框架中的一个重要内容,它通过对既有程序定义一个切入点,然后在其前后切入不同的执行内容,比如常见的有:打开数据库连接/关闭数据库连接、打开事务/关闭事务、记录日志等。

基于AOP不会破坏原来程序逻辑,因此它可以很好的对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hu</groupId>
<artifactId>spring-boot-aop-logging</artifactId>
<version>1.0-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>

配置:

1
2
3
4
5
6
#引入AOP依赖包后,一般来说并不需要去做其他配置。也不用主类中增加@EnableAspectJAutoProxy来启用。
#spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。
spring:
aop:
auto: true #默认值
#proxy-target-class: true#CGLIB来实现AOP,不然默认使用的是标准Java的实现。

切面类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@Aspect //定义切面类
@Component
public class WebLogAspect {

private Logger logger = Logger.getLogger(getClass());

//如果需要统计一个方法处理请求的处理时间消耗,需要在doBefore中记录时间,在doAfterReturning中统计时间
//就需要一个对象统一处理时间,在两个方法中调用,就会涉及到同步问题,可以通过ThreadLocal来记录
ThreadLocal<Long> startTime = new ThreadLocal<>();


//定义一个切入点
@Pointcut("execution(public * com.hu.controller..*.*(..))")
public void webLog(){}

@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
startTime.set(System.currentTimeMillis());
// 接收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();

// 记录下请求内容
logger.info(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()
+">>>URL:" + request.getRequestURL().toString()+">>>HTTP_METHOD:" + request.getMethod()+">>>IP:" + request.getRemoteAddr()
+ ">>>ARGS:" + Arrays.toString(joinPoint.getArgs()));

}

@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info(">>>RESPONSE:" + ret);
logger.info(">>>SPEND TIME:" + (System.currentTimeMillis() - startTime.get()));

}
/*AOP切面的优先级
由于通过AOP实现,程序得到了很好的解耦,如果涉及有多个切面,会有切面执行顺序问题,就需要定义切面的优先级。
@Order(i)注解来标识切面的优先级。i的值越小,优先级越高。
在切入点前的操作,按order的值由小到大执行
在切入点后的操作,按order的值由大到小执行*/
}

统一异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@ControllerAdvice
public class ControllerExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(ControllerExceptionHandler.class);

/**
* 处理不可知的一些异常,包括业务上RuntimeException异常
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
@ResponseBody
public RetViewModel handlerException(Exception e, HttpServletRequest request){
logger.error(e.getMessage(),e);
return new RetViewModel(HttpStatus.INTERNAL_SERVER_ERROR.value(), Constants.INTERNAL_SERVER_ERROR,null);
}

/**
* 远程请求异常
*/
@ExceptionHandler(HttpClientErrorException.class)
@ResponseBody
public RetViewModel httpClientErrorException(HttpClientErrorException e, HttpServletRequest request){
logger.error(Constants.REMOTE_R,e.getMessage());
return new RetViewModel(e.getRawStatusCode(), Constants.REMOTE_R,null);
}

/**
* 不支持当前请求方法
* @param e
* @return
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseBody
public RetViewModel handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
logger.error(Constants.METHOD_NOT_ALLOWED, e);
return new RetViewModel(HttpStatus.METHOD_NOT_ALLOWED.value(),Constants.METHOD_NOT_ALLOWED,null);
}

/**
* 不支持当前媒体类型
* @param e
* @return
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
@ResponseBody
public RetViewModel handleHttpMediaTypeNotSupportedException(Exception e) {
logger.error(Constants.UNSUPPORTED_MEDIA_TYPE, e);
return new RetViewModel(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(),Constants.UNSUPPORTED_MEDIA_TYPE,null);
}

/**
* 数据校验
*/
@ExceptionHandler(BindException.class)
@ResponseBody
public RetViewModel methodArgumentNotValidHandler(BindException e) {
logger.error(Constants.BAD_REQUEST, e);
BindingResult bindingResult = e.getBindingResult();
Map<String, Object> map = getErrors(bindingResult);
RetViewModel responseResult = new RetViewModel(HttpStatus.BAD_REQUEST.value(),map.values().toString(),null);
return responseResult;
}
/**
* 参数类型不匹配
*/
@ExceptionHandler({TypeMismatchException.class})
@ResponseBody
public RetViewModel requestTypeMismatch(TypeMismatchException ex){
logger.error(Constants.BAD_REQUEST, ex);
return new RetViewModel(HttpStatus.BAD_REQUEST.value(),"参数类型不匹配,参数" + ex.getValue() + "类型应该为" + ex.getRequiredType(),null);
}

/**
* 缺少参数异常
*/
@ExceptionHandler({MissingServletRequestParameterException.class})
@ResponseBody
public RetViewModel requestMissingServletRequest(MissingServletRequestParameterException ex){
logger.error(Constants.BAD_REQUEST, ex);
return new RetViewModel(HttpStatus.BAD_REQUEST.value(), "缺少必要参数,参数名称为" + ex.getParameterName(),null);
}
/**
* 请求参数错误
* @param e
* @return
*/
@ExceptionHandler({HttpMessageNotReadableException.class})
@ResponseBody
public RetViewModel handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
logger.error(Constants.BAD_REQUEST, e);
return new RetViewModel(HttpStatus.BAD_REQUEST.value(),Constants.BAD_REQUEST,null);
}

private Map<String, Object> getErrors(BindingResult result) {
Map<String, Object> map = new HashMap<>();
List<FieldError> list = result.getFieldErrors();
for (FieldError error : list) {
map.put(error.getField(), error.getDefaultMessage());
}
return map;
}
}
Exception Type HTTP Status Code
ConversionNotSupportedException 500 (Internal Server Error)
HttpMediaTypeNotSupportedException 406 (Not Acceptable)
HttpMediaTypeNotSupportedException 415 (Unsupported Media Type)
HttpMessageNotReadableException 400 (Bad Request)
HttpMessageNotWritableException 500 (Internal Server Error)
HttpRequestMethodNotSupportedException 405 (Method Not Allowed)
MissingServletRequestParameterException 400 (Bad Request)
NoSuchRequestHandlingMethodException 404 (Not Found)
TypeMismatchException 400 (Bad Request)

Rest和Restful风格

发表于 2018-05-14 | 分类于 rest

网络应用程序,分为前端和后端两个部分。当前的发展趋势,就是前端设备层出不穷(手机、平板、桌面电脑、其他专用设备……)。

REST 用来规范应用如何在 HTTP 层与 API 提供方进行数据交互 。REST 是面向资源的,这个概念非常重要,而资源是通过 URI 进行暴露。

一、传统下的API接口

删除一个数据,以往的做法通常是 delete/{id} 
更新一个数据,可能是Post数据放Body,然后方法是 update/{id}, 或者是artichle/{id}?method=update 
1
2
3
4
5
6
7
8
/api/getUser
/api/createApp
/api/searchResult
/api/deleteAllUsers
/api/getUser (用来获取某个用户的信息,还需要以参数方式传入用户 id 信息)
/api/updateUser (用来更新用户信息)
/api/deleteUser/id (用来删除单个用户)
/api/resetUser (重置用户的信息)

这样的弊端在于:首先加上了动词,肯定是使 URL 更长了;其次对一个资源实体进行不同的操作就是一个不同的 URL,造成 URL 过多难以管理。

二、REST 架构的链接

1. URL 中不应该出现任何表示操作的动词,链接只用于对应资源;
2. URL 中应该单复数区分,推荐的实践是永远只用复数;比如 GET /api/users 表示获取用户的列表;如果获取单个资源,传入 ID,比如 /api/users/123 表示获取单个用户的信息;
3. 按照资源的逻辑层级,对 URL 进行嵌套,比如一个用户属于某个团队,而这个团队也是众多团队之一;那么获取这个用户的接口可能是这样:

GET /api/teams/123/members/234 表示获取 id 为 123 的小组下,id 为234 的成员信息

rest原则:

<1>网络上的所有事物都被抽象为资源
<2> 每个资源都有一个唯一的资源标识符
<3> 同一个资源具有多种表现形式(xml,json等)
<4> 对资源的各种操作不会改变资源标识符
<5> 所有的操作都是无状态的

REST是一个标准,一种规范,遵循REST风格可以使开发的接口通用,便于调用者理解接口的作用。

关于restful

基于REST构建的API就是Restful风格。

RESTful架构风格规定,数据的元操作,即CRUD(create, read, update和delete,即数据的增删查改)操作,分别对应于HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口,仅通过HTTP方法,就可以完成对数据的所有增删查改工作。

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
DELETE(DELETE):从服务器删除资源。

restful用法:

在Restful之前的操作:

http://127.0.0.1/user/query/1 GET  根据用户id查询用户数据
http://127.0.0.1/user/save POST 新增用户
http://127.0.0.1/user/update POST 修改用户信息
http://127.0.0.1/user/delete GET/POST 删除用户信息

RESTful用法:

http://127.0.0.1/user/1 GET  根据用户id查询用户数据
http://127.0.0.1/user 新增用户
http://127.0.0.1/user 修改用户信息
http://127.0.0.1/user 删除用户信息

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应,一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

使用标准的状态码

GET

安全且幂等 获取表示 变更时获取表示(缓存)

200(OK) - 表示已在响应中发出
204(无内容) - 资源有空表示
301(Moved Permanently) - 资源的URI已被更新
303(See Other) - 其他(如,负载均衡)
304(not modified)- 资源未更改(缓存)
400 (bad request)- 指代坏请求(如,参数错误)
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求

POST

不安全且不幂等 使用服务端管理的(自动产生)的实例号创建资源

创建子资源 部分更新资源

如果没有被修改,则不过更新资源(乐观锁)

200(OK)- 如果现有资源已被更改
201(created)- 如果新资源被创建
202(accepted)- 已接受处理请求但尚未完成(异步处理)
301(Moved Permanently)- 资源的URI被更新
303(See Other)- 其他(如,负载均衡)
400(bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求

PUT

不安全但幂等,用客户端管理的实例号创建一个资源,通过替换的方式更新资源,如果未被修改,则更新资源(乐观锁)

200 (OK)- 如果已存在资源被更改
201 (created)- 如果新资源被创建
301(Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他(如,负载均衡)
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
406 (not acceptable)- 服务端不支持所需表示
409 (conflict)- 通用冲突
412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
415 (unsupported media type)- 接受到的表示不受支持
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务当前无法处理请求

DELETE

不安全但幂等,删除资源。

200 (OK)- 资源已被删除
301 (Moved Permanently)- 资源的URI已更改
303 (See Other)- 其他,如负载均衡
400 (bad request)- 指代坏请求
404 (not found)- 资源不存在
409 (conflict)- 通用冲突
500 (internal server error)- 通用错误响应
503 (Service Unavailable)- 服务端当前无法处理请求

表示结构

json xml

JVM内存分配和参数

发表于 2018-05-10 | 分类于 java

打印参数

在虚拟机运行过程中,可以根据一些跟踪系统状态的参数,来排查故障或者参数设置,可以打印系统运行时的一些相关参数,分析实际问题。对参数的配置,主要也就是围绕堆、栈、方法区进行配置。

打印jvm日志的方式

eclipse

20200410181219

控制台得到参数

1
2
3
4
5
6
7
8
9
Heap
PSYoungGen total 35840K, used 2458K [0x00000000d8500000, 0x00000000dad00000, 0x0000000100000000)
eden space 30720K, 8% used [0x00000000d8500000,0x00000000d8766888,0x00000000da300000)
from space 5120K, 0% used [0x00000000da800000,0x00000000da800000,0x00000000dad00000)
to space 5120K, 0% used [0x00000000da300000,0x00000000da300000,0x00000000da800000)
ParOldGen total 81920K, used 0K [0x0000000088e00000, 0x000000008de00000, 0x00000000d8500000)
object space 81920K, 0% used [0x0000000088e00000,0x0000000088e00000,0x000000008de00000)
Metaspace used 2748K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 293K, capacity 386K, committed 512K, reserved 1048576K

IntelliJ IDEA:

于是,运行程序后,GC日志就可以打印出来了,和eclipse测试一样。

解读日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
Heap
新生代分配情况
PSYoungGen total 35840K, used 3072K [0x00000000d8500000, 0x00000000dad00000, 0x0000000100000000)
eden space 30720K, 10% used [0x00000000d8500000,0x00000000d88002b8,0x00000000da300000)
from space 5120K, 0% used [0x00000000da800000,0x00000000da800000,0x00000000dad00000)
to space 5120K, 0% used [0x00000000da300000,0x00000000da300000,0x00000000da800000)
老年代分配情况
ParOldGen total 81920K, used 0K [0x0000000088e00000, 0x000000008de00000, 0x00000000d8500000)
object space 81920K, 0% used [0x0000000088e00000,0x0000000088e00000,0x000000008de00000)
元空间分配情况,jdk8彻底去除了永久代
Metaspace used 3125K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K

堆的分配参数

1、-Xmx -Xms:指定最大堆和最小堆
    -Xmx设置程序能获得最大堆大小,-Xms设置程序启动时初始堆大小
2、-XX:+PrintGC 遇到GC就会打印日志。
3、-XX:+UseSerialGC 配置串行回收器
4、-XX:+PrintGCDetails查看GC详情,包括各个回收区的情况。
5、-XX:+PrintCommandLineFlags:可以将隐式或显示传给JVM的参数输出,就是把配置的参数所改变的信息也打印输出。

-XX这种这是是对jvm系统级别的配置,非-XX配置基本是对应用级别的设置。+启用-禁用

实际中,可以直接将初始的堆大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,从而提到性能。

默认程序堆大小,可以通过Java程序获取

1
2
3
4
5
public static void main(String[] args) {
System.out.println("Xmx=" + Runtime.getRuntime().maxMemory() / 1024.0 / 1024 + "M"); // 系统的最大空间
System.out.println("free mem=" + Runtime.getRuntime().freeMemory() / 1024.0 / 1024 + "M"); // 系统的空闲空间
System.out.println("total mem=" + Runtime.getRuntime().totalMemory() / 1024.0 / 1024 + "M"); // 当前可用的总空间
}
Xmx=1694.5M
free mem=113.19983673095703M
total mem=115.0M

Demo测试设置(多个命令一起执行): -XX:+PrintGC -Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

-XX:+PrintGC 打印GC
-Xms40M 设置堆初始大小40M
-Xmx40M 设置堆最大大小40M
-Xmn20M 设置堆中 new Generation 新生代的大小为20M
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+UseSerialGC 设置GC回收器模式是串型垃圾回收器
-XX:+PrintCommandLineFlags  配置的参数所改变的信息也打印输出

Java代码:

1
2
3
4
5
6
7
8
9
10
/**
* -XX:+PrintGC -Xms40M -Xmx40M -Xmn20M -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
* @param args
*/
public static void main(String[] args) {
byte[] b1 = new byte[4 * 1024 * 1024];
byte[] b2 = new byte[4 * 1024 * 1024];
byte[] b3 = new byte[4 * 1024 * 1024];
byte[] b4 = new byte[8 * 1024 * 1024];
}

分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

初始分配:
Java堆,共有 40M
新生代,有20M
Eden区,16M
Servivor From ,2M
Servior To ,2M
老年代,有20M
当b1/b2/b3执行完时
Eden 被分配12M
当a4执行时
因为需要分配8M,但现在Eden中只有4M空闲,无法满足b4申请;
from和to中也只有2M,也不能满足,所以要触发MinorGC;
MinorGC执行,处理b1/b2/b3的空间,他们都是4M,所以servivor也不够转移,直接回被移到老年代;
MinorGC执行后,分配如下:
新生代,total 20M
Eden区 free 16M
Servivor From free 2M
Servior To free 2M
老年代,total 20M free 8M
再次分配b4
新生代,total 20M
Eden区 free 8M
Servivor From free 2M
Servior To free 2M
老年代,total 20M free 8M

运行结果分析: 配置项大小结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-XX:InitialHeapSize=41943040 -XX:MaxHeapSize=41943040 -XX:MaxNewSize=20971520 -XX:NewSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGC -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
[GC (Allocation Failure) [DefNew: 13599K->553K(18432K), 0.0109283 secs] 13599K->12841K(38912K), 0.0118473 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
新生代
def new generation total 18432K, used 8909K [0x00000000fd800000, 0x00000000fec00000, 0x00000000fec00000)
eden space 16384K, 51% used [0x00000000fd800000, 0x00000000fe0290e0, 0x00000000fe800000)
from space 2048K, 27% used [0x00000000fea00000, 0x00000000fea8a558, 0x00000000fec00000)
to space 2048K, 0% used [0x00000000fe800000, 0x00000000fe800000, 0x00000000fea00000)
老年代
tenured generation total 20480K, used 12288K [0x00000000fec00000, 0x0000000100000000, 0x0000000100000000)
the space 20480K, 60% used [0x00000000fec00000, 0x00000000ff800030, 0x00000000ff800200, 0x0000000100000000)
元空间
Metaspace used 2749K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 293K, capacity 386K, committed 512K, reserved 1048576K

运行结果基本符合推导结果。

6、-Xmn

设置新生代大小

7、-XX:NewRatio 

新生代(eden+2*s)和老年代(不包含永久区)的比值,默认2
例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5

8、-XX:SurvivorRatio 设置两个Survivor区和eden的比值,默认8

 例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10

9、-XX:+HeapDumpOnOutOfMemoryError

OOM(Out Of Memory内存溢出)时导出堆到文件,根据这个文件,我们可以看到系统dump时发生了什么。
内存分析工具:Memory Anakyaer,eclipse插件
-XX:+HeapDumpPath        导出OOM的路径
例如:XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/a.dump
我们可以用VisualVM打开这个dump文件。
注:关于VisualVM的使用,可以参考下面这篇博客:
使用 VisualVM 进行性能分析及调优:http://www.ibm.com/developerworks/cn/java/j-lo-visualvm/
或者使用Java bin目录里自带的Java VisualVM工具也行

10、-XX:OnOutOfMemoryError

在OOM时,执行一个脚本。可以在OOM时,发送邮件,甚至是重启程序。
例:-XX:OnOutOfMemoryError=D:/..../jdk/bin/printstack.bat %p    p代表的是当前进程的pid
上方参数的意思是说,执行printstack.bat脚本,而这个脚本做的事情是:D:/..../jdk/bin/jstack -F %1 > D:/a.txt,即当程序OOM时,在D:/a.txt中将会生成线程的dump。

11、-XX:PermSize -XX:MaxPermSize

设置永久区的初始空间和最大空间。也就是说,jvm启动时,永久区一开始就占用了PermSize大小的空间,如果空间还不够,可以继续扩展,但是不能超过MaxPermSize,否则会OOM。
jdk8开始,已经移除永久代。

12、

-XX:MaxTenuringThreshold 指定新生代经过多少次回收后进入老年代,默认15
-XX:PretenureSizeThreshold 设置对象大小超过指定大小后,直接进入老年代。注意线程TLAB区优先分配空间问题。
TLAB(Thread Local Allocation Buffer)线程本地分配缓存,是为了加速对象 分配而生的。线程私有。
JVM使用这个区来避免多线程冲突问题,提高对象分配的效率,TLAB空间不会太大,当大对象无法再TLAB上分配时,才会直接分配到堆上。
-XX:+UseTLAB 使用TLAB
-XX:+TLABSize 设置大小
-XX:TLABRefillWasteFraction 设置维护进入TLAB空间的单个对象大小,他是一个比值,默认64,如果对象DAU整个空间的1/64则在堆上创建对象。
-XX:+PrintTLAB 查看TLAB信息
-XX:ResizeTLAB 自调整TLABRefillWasteFraction的阈值。

总结:实际中,根基实际情况调整新生代和幸存代的大小;在OOM时,记得把dump文件导出,可以促进排查问题。

栈的分配参数

1、-Xss 指定线程的最大栈空间,决定了函数可以调用的最大深度。

  
每个线程都有独立的栈空间;
局部变量、参数 分配在栈上;
栈空间是每个线程私有的区域。栈里面的主要内容是栈帧,而栈帧存放的是局部变量表,局部变量表的内容是:局部变量、参数。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 public class Demo2 {
private static int count = 0;
public static void recursion() {
long l1 = 1;
count++;
recursion();
}
public static void main(String[] args) {
try {
recursion();
} catch (Throwable e) {
System.out.println(count);
e.printStackTrace();
}
}
}

这段代码是没有出口的递归,肯定会出现OOM.

如果设置栈大小为:-Xss1m,方法调用26037了次

26037
java.lang.StackOverflowError
    at com.hu.demo.Demo2.recursion(Demo2.java:9)

如果设置栈大小为:-Xss2m,方法调用了48156次
48156
java.lang.StackOverflowError
    at com.hu.demo.Demo2.recursion(Demo2.java:8)

所以:-Xss设置决定函数调用的深度。

方法区参数

和堆一样,方法区是一块所有线程共享的内存,用于保存类的信息,可以保存多少信息,可以通过配置:

-XX:MaxPermSize ,默认64M,实际中,如果系统产生大量的类,就需要设置这个合适的参数,一面出现OOM。
-XX:sPermSize

直接内存配置

特别是在NIO编程中,直接内存跳过了java堆,使其java直接访问内存空间。

-XX:MaxDirechMemorySize,如果不设置,默认为最大堆空间-Xmx,直接内存到上限时就会触发垃圾回收。

垃圾回收的JVM配置

运行的垃圾回收器类型

配置

-XX:+UseSerialGC        串行垃圾回收器
-XX:+UseParallelGC        并行垃圾回收器
-XX:+UseConcMarkSweepGC        并发标记扫描垃圾回收器
-XX:ParallelCMSThreads=        并发标记扫描垃圾回收器 =为使用的线程数量
-XX:+UseG1GC        G1垃圾回收器

GC的优化配置

配置

-Xms        初始化堆内存大小
-Xmx        堆内存最大值
-Xmn        新生代大小
-XX:PermSize        初始化永久代大小
-XX:MaxPermSize        永久代最大容量

并行收集器设置

-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

并发收集器设置

-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

JDK8相关参数

1、由于jdk8开始,彻底去除永久区,所以永久区的参数就不用了。

-XX:PermSize
-XX:MaxPermSize

2、metaspace,元数据空间,专门用来存元数据的,jdk8里特有的数据结构用来替代perm

(1)CompressedClassSpaceSize参数作用是设置Klass Metaspace的大小,默认1G

Klass Metaspace就是用来存klass的,klass是的class文件在jvm里的运行时数据结构,没有开启压缩指针,就不会有CompressedClassSpaceSize这块内存,但是jdk1.8里应该是默认开启的,并且,如果这块内存会如果没有满会一直增加。

但是-Xmx超过了32G,压缩指针是默认不开启的,而这个参数也就失去了设置的意义。

(2)MaxMetaspaceSize

   默认基本是无穷大,这个参数很可能会因为没有限制而导致metaspace被无止境使用(一般是内存泄漏)而被OS Kill。这个参数会限制metaspace(包括了Klass Metaspace以及NoKlass Metaspace)被committed的内存大小,会保证committed的内存不会超过这个值,一旦超过就会触发GC,这里要注意和MaxPermSize的区别,MaxMetaspaceSize并不会在jvm启动的时候分配一块这么大的内存出来,而MaxPermSize是会分配一块这么大的内存的。

3.MaxDirectMemorySize

此参数主要影响的是非堆内存的direct byte buffer,jvm默认会设置64M,可根据功能适当加大此项参数,因为非堆内存,故而不会被GC回收掉,容易出现java.lang.OutOfMemoryError: Direct buffer memory错误
如出现以上错误,可通过以下参数打印log,之后用工具进行分析

-XX:-HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=logs/oom_dump.log

4、G1收集器参数

-XX:+UseG1GC
使用G1收集器
-XX:MaxGCPauseMillis=200
用户设定的最大gc 停顿时间,默认是200ms.
-XX:InitiatingHeapOccupancyPercent=45
默认是45,也就是heap中45%的容量被使用,则会触发concurrent gc

-XX:NewRatio=n
新生代与老生代(new/old generation)的大小比例(Ratio). 默认值为 2.
-XX:SurvivorRatio=n    eden/survivor
空间大小的比例(Ratio). 默认值为 8.
-XX:MaxTenuringThreshold=n
提升年老代的最大临界值(tenuring threshold). 默认值为 15.
-XX:ParallelGCThreads=n
设置垃圾收集器在并行阶段使用的线程数,默认值随JVM运行的平台不同而不同.
-XX:ConcGCThreads=n
并发垃圾收集器使用的线程数量. 默认值随JVM运行的平台不同而不同.
-XX:G1ReservePercent=n
设置堆内存保留为假天花板的总量,以降低提升失败的可能性. 默认值是 10.
-XX:G1HeapRegionSize=n
使用G1时Java堆会被分为大小统一的的区(region)。此参数可以指定每个heap区的大小. 默认值将根据 heap size 算出最优解. 最小值为 1Mb, 最大值为 32Mb.

目前jvm支持Client和Server两种运行模式,

使用参数-client可以指定使用client模式。

使用参数-server启动server模式。

查看当前jvm使用的模式java -version

区别:client比server启动较快,server启动较慢,如果是要对其进行复杂的系统性能信息收集和使用更复杂的算法对程序进行优化,一般都会启动server模式,长期运行的性能远远快于client模式。

上一页1…131415…25下一页
初晨

初晨

永远不要说你知道本质,更别说真相了。

249 日志
46 分类
109 标签
近期文章
  • WebSocket、Socket、TCP、HTTP区别
  • Springboot项目的接口防刷
  • 深入理解Volatile关键字及其实现原理
  • 使用vscode搭建个人笔记环境
  • HBase介绍安装与操作
© 2018 — 2020 Copyright
由 Hexo 强力驱动
|
主题 — NexT.Gemini v5.1.4