分类目录Java

docker-elk

github源码
Bringing up the stack
Clone this repository onto the Docker host that will run the stack, then start services locally using Docker Compose:
$ docker-compose up

5000: Logstash TCP input
9200: Elasticsearch HTTP user:elastic password:changeme
9300: Elasticsearch TCP transport 集群通信保持心跳
5601: Kibana      user:elastic password:changeme

LogStash 离散的日志收集,处理,过滤,格式转换
ElasticSearch 搜索引擎,进行索引,方便检索
Kibana 展示数据

springboot集成logbacku将日志输出到logstash

<!-- https://mvnrepository.com/artifact/net.logstash.logback/logstash-logback-encoder -->
<dependency>
    <groupId>net.logstash.logback</groupId>
    <artifactId>logstash-logback-encoder</artifactId>
    <version>6.3</version>
</dependency>

grok表达式

logstash.config
filter{
    grok{
        match => {
            "message" => "%{TIMESTAPM_ISO8601:logTime} %{GREEDYDATA:logThread} %{LOGLEVEL:logLevel} %{GREEDYDATA:loggerClass} - %{GREEDYDATA:logContent}"
        }
    }
}

在ELK前使用消息队列提高可用性,可分享性,如Kafka
配制LogStash从kafka接收
配制springboot项目发送到kafka

<!-- https://mvnrepository.com/artifact/com.github.danielwegener/logback-kafka-appender -->
<dependency>
    <groupId>com.github.danielwegener</groupId>
    <artifactId>logback-kafka-appender</artifactId>
    <version>0.2.0-RC2</version>
</dependency>
建立日志配制文件logback-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration>
<configuration>

    <!--将日志发送到LOGSTASH-->
   <!-- <appender name="LOGSTASH" class="net.logstash.logback.appender.LogstashAccessTcpSocketAppender">
        <destination>localhost:5000</destination>-->
        <!--整个日志信息转为一个JSON发送-->
       <!-- <encoder charset="UTF-8" class="net.logstash.logback.encoder.LogstashEncoder"/>-->
        <!--自定义一个发送格式-->
       <!-- <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>-->

    <!--将日志发送到kafka-->
    <appender name="KAFKA" class="com.github.danielwegener.logback.kafka.KafkaAppender">
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <topic>test</topic>
        <keyingStrategy class="com.github.danielwegener.logback.kafka.keying.NoKeyKeyingStrategy"/>
        <deliveryStrategy class="com.github.danielwegener.logback.kafka.delivery.AsynchronousDeliveryStrategy"/>
        <producerConfig>bootstrap.servers=192.168.88.108:9092</producerConfig>
        <appender-ref ref="CONSOLE"/>
    </appender>

    <include resource="org/springframework/boot/logging/logback/base.xml"/>

    <root level="INFO">
        <!--<appender-ref ref="LOGSTASH"/>-->
        <appender-ref ref="KAFKA"/>
        <appender-ref ref="CONSOLE"/>
    </root>

</configuration>

Prometheus

官网 https://prometheus.io/
github源码:https://github.com/prometheus/prometheus
docker镜像:https://hub.docker.com/r/prom/prometheus

适用范围:
Prometheus使用定时采样,常常用于在服务集群中采集实时监控数据进行图表分析

不适范围:
不能保证100%的数据完整,不适用于高精度要求的统计分析。

四种指标模型
Counter:只增不减的计数器,非常适合请求的个数,任务计数器, 出现的错误个数,产品的统计数等
Gauge:可增可减的仪表盘,可用于即时测量值指标
Histogram:自带buckets区间分布统计图,还提供了所有观测值的和
Summary:客户端定义的数据分布统计图,计算滑动时间窗口上的可配置分位数

Prometheus使用PromQL查询表达式进行数据查询
rate(is_request_counter{service=”/datav/ks”,method=”GET”}[1m])
单位时间内的计数

Prometheus可自定义告警规则进行告警。告警的触发条件是一个查询表达式。告警规则可配置在Prometheus服务端的配置文件中。

Prometheus 采取拉取数据进行数据采集.
其中pushgateway中间数据处理接收数据供server拉取
自带简单的webUI,有更高级图表Grafana,也可自写API调取数据

推送->Alertmanager->pagerduty/mail/etc

Docker方式安装
docker run -d –name prometheus -p 9090:9090 -v ~/prometheus/:/etc/prometheus/ prom/prometheus:V2.4.3

docker run -d –name grafana -p 3000:3000 -v ~/grafana/config/grafana.ini:/etc/grafana/grafana.ini -v ~/grafana/provisioning/:/etc/grafana/provisioning/ -env_file ~/grafana/config.monitoring grafana/grafana:5.2.4

default admin user is admin/admin.
提供数据

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
            <!--<version>2.1.6.RELEASE</version>-->
        </dependency>
         <dependency>
            <groupId>io.micrometer</groupId>
            <artifactId>micrometer-registry-prometheus</artifactId>
        </dependency>

management:
  endpoint:
    prometheus: #启用prometheus
      enabled: true
  endpoints:
    web:
      exposure:
        include: #只对外显示以下信息
          - prometheus
          - info
          - health

放行不用认证
@Configuration
public class ActuatorSecurityConfig extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 所有的端点状态信息不需要身份认证
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                // 其它的都需要认证访问
                .anyRequest().authenticated();
    }
}

Zookeeper

官网

https://zookeeper.apache.org/
下载包安装先修改配制文件
cp -f zoo_sample.cfg zoo.cfg
./zkServer.sh start/stop
./zkCli.sh 客户端工具,默认连接本机2181端口

建议docker pull zookeeper
https://hub.docker.com/_/zookeeper

docker run --name zookeeper -p 2181:2181 -p 2888:2888 -p 3888:3888 -p 8080:8080 --restart always -d zookeeper

Sentinel  限流

增加依赖
<!--阿里Sentinel限流-->
        <!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-core -->
        <dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-core</artifactId>
            <version>1.7.1</version>
        </dependency>
声明资源
try(Entry entry = SphU.entry("getRole")) {
            //Sentinel要保护的资源定义
            response = restTemplate.postForObject("http://RBAC/role/test",  Arrays.asList("157875196366160022"),String.class);
            log.info("response={}", response);
        }catch (BlockException e) {
            log.info("限流");
            e.printStackTrace();
        }
定义规则

针对资源定义流控,限流,降级,熔断的规则

Component
public class SentinelConfig implements ApplicationListener<ContextRefreshedEvent> {

    /**
     * 系统启动完成后就执行的代码,在这里声明流控规则
     * @param contextRefreshedEvent
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
        FlowRule rule = new FlowRule();
        rule.setRefResource("getRole");
        //QPS第秒请求的数量
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        //每秒10个
        rule.setCount(10);
        //添加到FlowRuleManager
        List<FlowRule> rules = new ArrayList<FlowRule> ();
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}
日志

当前用户下~/logs/csp/

DASHBOARD

https://github.com/alibaba/Sentinel
默认用户名密码都是sentinel
how to download
docker pull bladex/sentinel-dashboard:tagname
how to start
docker run –name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard:tagname
how to login web-dashboard
visit: http://localhost:8858/
account and password: [sentinel sentinel]
just enjoy 🙂
github
https://github.com/chillzhuang/SpringBlade
website
https://bladex.vip

docker install dashboard
docker run --name sentinel  -d -p 8858:8858  bladex/sentinel-dashboard:1.7.1
springboot
<dependency>
            <groupId>com.alibaba.csp</groupId>
            <artifactId>sentinel-transport-simple-http</artifactId>
            <version>1.7.1</version>
        </dependency>
项目启动参数
-Dcsp.sentinel.dashboard.server=DASHBOARD的服务地址
使用Spring Cloud Starter Alibaba Sentinel
1. 依赖
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-alibaba-sentinel -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2.2.0.RELEASE</version>
</dependency>
2. 配制
spring:
  application:
    #应用名
    name: client
  cloud:
    #sentinel流控
    sentinel:
      transport:
        port: 8719
        dashboard: http://192.168.88.108:9001
3. 使用注解定义资源
@GetMapping("/role3")
    //Sentinel要保护的资源定义
    @SentinelResource(value = "getRole",blockHandler = "doOnBlock")
    public Result role3(@AuthenticationPrincipal String userName){
        String response = response = restTemplate.postForObject("http://RBAC/role/test",  Arrays.asList("157875196366160022"),String.class);
        return Result.success(response);
    }
    public Result doOnBlock(@AuthenticationPrincipal String userName,BlockException exception) throws InterruptedException {
        log.info("userName [{}] doOnBlock By [{}]",userName,exception.getClass().getSimpleName());
        return Result.success(userName);
    }
在面版中配制规则更方便
数据持久化

可用zookeeper,apollo,nacos按自己需求配制
1. 下载dashboard源码,选择分支,导入项目,
– 修改pom中相应的 publisher sample 去掉scope
– 实现rule下的数据接口,样列在test包下的rule中,COPY
– 修改controller/v2/FlowControllerV2.java
ruleProvider的注入改flowRuleZookeeperProvider
rulePublisher的注入改flowRuleZookeeperPublisher
– 修改配制文件
application.properties更改默认的用户名及密码,服务器运行端口等
– 修改表态页面
webapp/resources/app/scripts/directives/sidebar/sidebar.html 流控规则的dashboard.flowv1->flow
– 修改数据地址
NacosConfig ConfigService

  1. 修改应用项目
    增加数据源依赖
<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-zookeeper -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-zookeeper</artifactId>
    <version>1.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.csp/sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
    <version>1.8.1</version>
    <scope>test</scope>
</dependency>

增加配制文件
#sentinel规则zoopeeker数据源地址

sentinel:
  zookeeper:
    address: 192.168.88.108:2781
    path: /sentinel_rule_config

去掉手动的规则SentinelConfig,改读取配制文件

@Component
public class SentinelConfig {

    @Value("${sentinel.zookeeper.address}")
    private String zkServer;

    @Value("${sentinel.zookeeper.path}")
    private String zkPath;

    @Value("${spring.application.name}")
    private String appName;
    ......
}

@Valid@Validated数据校验

@Valid是使用hibernate validation的时候使用
@valid,java的jsr303声明了这类接口,hibernate-validator对其进行了实现

@Validated 是只用spring Validator 校验机制使用

空检查

@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
用在基本类型上 不能为null,但可以为empty,没有Size的约束
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
只用于String,不能为null且trim()之后size>0
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)

Booelan检查

@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false

长度检查

@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) Validates that the annotated string is between min and max included.

日期检查

@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则

数值检查,

建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为””,Integer为null

@Min 验证 Number 和 String 对象是否大等于指定的值
@Max 验证 Number 和 String 对象是否小等于指定的值
@DecimalMax 被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin 被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits 验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=) 验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=) Checks whether the annotated value lies between (inclusive) the specified minimum and maximum.
@Range(min=10000,max=50000,message=”range.bean.wage”)
private BigDecimal wage;
@Valid 递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email 验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

@NotNull(message = "appId不能为空", groups = {AccountGroup.Update.class})
private Integer appId;

BindingResult bindingResult

Pointcut 切面

<!--未使用Springcloud时使用AOP增加下面的引用-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

切面描述

Advice(通知、切面): 某个连接点所采用的处理逻辑,也就是向连接点注入的代码, AOP在特定的切入点上执行的增强处理。
@Before: 标识一个前置增强方法,相当于BeforeAdvice的功能.
@After: final增强,不管是抛出异常或者正常退出都会执行.
@AfterReturning: 后置增强,似于AfterReturningAdvice, 方法正常退出时执行.
@AfterThrowing: 异常抛出增强,相当于ThrowsAdvice.
@Around: 环绕增强,相当于MethodInterceptor.
JointPoint(连接点):程序运行中的某个阶段点,比如方法的调用、异常的抛出等。
Pointcut(切入点): JoinPoint的集合,是程序中需要注入Advice的位置的集合,指明Advice要在什么样的条件下才能被触发,在程序中主要体现为书写切入点表达式。
Advisor(增强): 是PointCut和Advice的综合体,完整描述了一个advice将会在pointcut所定义的位置被触发。
@Aspect(切面): 通常是一个类的注解,里面可以定义切入点和通知
AOP Proxy:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类。

JoinPoint常用的方法:

Object[] getArgs:返回目标方法的参数
Signature getSignature:返回目标方法的签名
Object getTarget:返回被织入增强处理的目标对象
Object getThis:返回AOP框架为目标对象生成的代理对象
当使用@Around处理时,需要将第一个参数定义为ProceedingJoinPoint类型,该类是JoinPoint的子类。

优先级

Spring提供了如下两种解决方案指定不同切面类里的增强处理的优先级:
1. 让切面类实现org.springframework.core.Ordered接口:实现该接口的int getOrder()方法,该方法返回值越小,优先级越高
2. 直接使用@Order注解来修饰一个切面类:使用这个注解时可以配置一个int类型的value属性,该属性值越小,优先级越高
3. 同一个切面类里的两个相同类型的增强处理在同一个连接点被织入时,Spring AOP将以随机的顺序来织入这两个增强处理,没有办法指定它们的织入顺序。即使给这两个 advice 添加了 @Order 这个注解,也不行!

表达式

Wildcard
*: 匹配任意数量的字符
+:匹配制定数量的类及其子类
..:一般用于匹配任意数量的子包或参数
详细示例见后面的例子
Operators

&&:与操作符
||:或操作符
!:非操作符
Designators

1. within()

//匹配productService类中的所有方法
@pointcut("within(com.sample.service.productService)")
public void matchType()

//匹配sample包及其子包下所有类的方法
@pointcut("within(com.sample..*)")
public void matchPackage()
2. 匹配对象(this, target, bean)

this(AType) means all join points where this instanceof AType is true. So this means that in your case once the call reaches any method of Service this instanceof Service will be true.

this(Atype)意思是连接所有this.instanceof(AType) == true的点,所以这意味着,this.instanceof(Service) 为真的Service实例中的,所有方法被调用时。

target(AType) means all join points where anObject instanceof AType . If you are calling a method on an object and that object is an instanceof Service, that will be a valid joinpoint.

target(AType)意思是连接所有anObject.instanceof(AType)。如果你调用一个Object中的一个方法,且这个object是Service的实例,则这是一个合法的切点。
To summarize a different way - this(AType) is from a receivers perspective, and target(AType) is from a callers perspective.

总结:this(AType)同接受者方面描述,target(AType)则从调用者方面描述。
this
@pointcut("this(com.sample.demoDao)")
public void thisDemo()
target
@pointcut("target(com.sample.demoDao)")
public void targetDemo()
bean
//匹配所有以Service结尾的bean里面的方法
@pointcut("bean(*Service)")
public void beanDemo
3. 参数匹配

bean
//匹配所有以find开头,且只有一个Long类型参数的方法
@pointcut("execution(* *..find*(Long))")
public void argDemo1()

//匹配所有以find开头,且第一个参数类型为Long的方法
@pointcut("execution(* *..find*(Long, ..))")
public void argDemo2()
arg
@pointcut("arg(Long)")
public void argDemo3()

@pointcut("arg(Long, ..)")
public void argDemo4()
4. 匹配注解

@annotation
//匹配注解有AdminOnly注解的方法
@pointcut("@annotation(com.sample.security.AdminOnly)")
public void demo1()
@within
//匹配标注有admin的类中的方法,要求RetentionPolicy级别为CLASS
@pointcut("@within(com.sample.annotation.admin)")
public void demo2()
@target
//注解标注有Repository的类中的方法,要求RetentionPolicy级别为RUNTIME
@pointcut("target(org.springframework.stereotype.Repository)")
public void demo3()
@args
//匹配传入参数的类标注有Repository注解的方法
@pointcut("args(org.springframework.stereotype.Repository)")
public void demo3()
5. execution()

格式:

execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
标注❓的项目表示着可以省略
execution(
    modifier-pattern?  //修饰符
    ret-type-pattern  //返回类型
    declaring-type-pattern?  //方法模式
    name-pattern(param-pattern)  //参数模式
    throws-pattern?  //异常模式
)

/*
整个表达式可以分为五个部分:

 1、execution(): 表达式主体。

 2、第一个*号:表示返回类型,*号表示所有的类型。

 3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。

 4、第二个*号:表示类名,*号表示所有的类。

 5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
*/
@pointcut("execution(* com.sample.service.impl..*.*(..))")

异常处理 Exception

spring mvc提供三种异常处理器:
DefaultHandlerExceptionResolver, ResponseStatusExceptionResolver, ExceptionHandlerExceptionResolver

为了统一异常处理,我们可以自定义一个HandlerExceptionResolver,所有的异常均由自定义的异常处理, 由此我们可以重写DispatcherServlet类的
processHandlerException()方法:

  1. @ExceptionHandler
    使用@ExceptionHandler注解作用在方法上面,参数是具体的异常类型。一旦系统抛出这种类型的异常时,会引导到该方法来处理。但是它的缺陷很明显,处理异常的方法和出错的方法(或者异常最终抛出来的地方)必须在同一个controller,不能全局控制。

  2. @ControllerAdvice + @ExceptionHandler
    使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。

自定异常
@Data
public class UserNotExitsException extends RuntimeException{
    private Object id;
    public UserNotExitsException(Object id){
        super("用户不存在");
        this.id = id;
    }
}
全局控制异常
@ControllerAdvice
@Slf4j
public class ControllerExceptionHandler {

    /**
     * 用户不存在
     * 待处理异常的类 UserNotExitsException
     * @return 将返回的信息转为JSON,同时返回HTTP状态码500
     */
    @ExceptionHandler(UserNotExitsException.class)
    @ResponseBody
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public JsonResult handleUserNotExitException(UserNotExitsException ex){
        log.info("handleUserNotExitException:"+String.valueOf(ex.getId()));
        return JsonResult.error(ex.getMessage(),ex.getId());
    }
}

优先级

既然在SpringMVC中有两种处理异常的方式,那么就存在一个优先级的问题:

当发生异常的时候,SpringMVC会如下处理:

(1)SpringMVC会先从配置文件找异常解析器HandlerExceptionResolver

(2)如果找到了异常异常解析器,那么接下来就会判断该异常解析器能否处理当前发生的异常

(3)如果可以处理的话,那么就进行处理,然后给前台返回对应的异常视图

(4)如果没有找到对应的异常解析器或者是找到的异常解析器不能处理当前的异常的时候,就看当前的Controller中有没有提供对应的异常处理器,如果提供了就由Controller自己进行处理并返回对应的视图

(5)如果配置文件里面没有定义对应的异常解析器,而当前Controller中也没有定义的话,就看有没有全局ControllerAdvice提供的全局异常处理器,如果没有那么该异常就会被抛出来。