0%

Java CPU上下文切换、用户态和内核态

1、概述

JDK源码中很多Native方法,特别是多线程、NIO部分,很多功能需要操作系统功能支持,作为Java程序员,如果要理解和掌握多线程和NIO等原理,就需要对操作系统的原理有所了解。

2、CPU 上下文切换

多任务操作系统中,多于CPU个数的任务同时运行就需要进行任务调度,从而多个任务轮流使用CPU。

从用户角度看好像所有的任务同时在运行,实际上是多个任务你运行一会,我运行一会,任务切换的速度很快,我们感觉不到而已。

而每个任务运行前,CPU需要知道从哪里加载这个任务的程序,还需要知道从程序哪行开始执行,这就要求OS事先帮任务设置好CPU的 寄存器程序计数器

CPU执行任务必须依赖的环境称为 CPU上下文

CPU 上下文切换,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。

CPU的上下文切换分为几种场景:进程上下文切换、线程上下文切换、中断上下文切换

2.1、用户态、内核态

Linux按特权等级,将进程的运行空间分为 内核空间用户空间

在这里插入图片描述

Intel x86架构使用了4个级别来标明不同的特权级权限。

R0实际就是内核态,拥有最高权限,可以直接访问所有资源(包括外围设备,例如硬盘,网卡等)。而一般应用程序处于R3状态–用户态

进程在用户空间运行时,被称为进程的 用户态,而陷入内核空间的时候,被称为进程的 内核态

R0最高可以读取R0-3所有的内容,R1可以读R1-3的,R2以此类推,R3只能读自己的数据。

2.2、为什么分内核态和用户态

假设没有这种内核态和用户态之分,程序随随便便就能访问硬件资源,比如说分配内存,程序能随意的读写所有的内存空间,如果程序员一不小心将不适当的内容写到了不该写的地方,就很可能导致系统崩溃。

用户程序进行系统调用后,操作系统执行一系列的检查验证,确保这次调用是安全的,再进行相应的资源访问操作。内核态能有效保护硬件资源的安全。

2.3、系统调用

从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。

系统调用会将CPU从用户态切换到核心态,以便 CPU 访问受到保护的内核内存。

系统调用的过程会发生 CPU 上下文的切换,CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。

而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。

注意:系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。

系统调用过程通常称为特权模式切换,而不是进程上下文切换。

3、进程上下文切换

进程上下文切换跟系统调用又有什么区别呢?

首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。

因此,进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、用户栈等保存下来;而加载下一个进程的内核态后,还需要加载这个进程的虚拟内存和用户栈。

根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间,在进程上下文切换次数较多的情况下,这个时间对于CPU来说是相当可观的,会大大缩短CPU真正用于运行进程的时间。

3.1、什么时候会切换进程上下文?

只有在进程调度的时候,才需要切换上下文。Linux 为每个 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。

新进程在什么时候才会被调度到 CPU 上运行呢?
1.运行中的进程执行完终止了,CPU 会释放出来,新的基础进程就可以被调度到CPU上运行了。
2. 运行中的进程时间片用完,进程被挂起
3. 运行中的进程资源不足,进程被挂起
4. 运行中的进程执行Sleep方法主动挂起
5. 新进程优先级更高,运行中的进程被挂起
6. 发生硬件中断,运行中的进程会被中断挂起,转而执行内核中的中断服务程序。

4、线程上下文切换

线程是调度的基本单位,而进程则是资源拥有的基本单位。

所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。

当进程只有一个线程时,可以认为进程就等于线程,当进程拥有多个线程时,这些线程会共享进程的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。

线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。

线程的上下文切换其实就可以分为两种情况:

  1. 两个线程属于不同进程,因为资源不共享,切换过程和进程上线文切换一样
  2. 两个线程属于同一个进程,只需要切换线程的私有数据、寄存器等不共享的数据

5、总结

CPU上线文切换,切换寄存器、程序计数器

进程上线文切换,切换虚拟内存、用户栈

线程上下文切换,2种情况:(1)线程私有数据(比如线程栈、程序计数器等);(2)、(1)+ 线程资源 ;

系统调用:需要进行线程上下文切换,但不是进程上下文切换

R0实际就是内核态,拥有最高权限,可以直接访问所有资源(包括外围设备,例如硬盘,网卡等)。

应用程序处于R3状态–用户态

系统调用会进行 内核态用户态 转换

参考资料

Linux性能优化实战-03讲 CPU 上下文切换
许式伟的架构课-08讲 操作系统内核与编程接口
操作系统为什么要分用户态和内核态
用户态和内核态的区别

转载自 微服务容错 - 隔离熔断限流

在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错机制。常用的的容错技术如:隔离,降级,熔断,限流等策略,本文将详细的介绍微服务中的容错机制。

隔离机制

为什么要隔离? 比如我们现在某个接口所在的服务A需要调用服务B,而服务B同时需要调用C服务,此时服务C突然宕机同时此时流量暴涨,调用全部打到服务B上,此时B服务调用C超时大量的线程资源被该接口所占全部hang住,慢慢服务B中的线程数量则会持续增加直致CPU资源耗尽到100%,整个服务对外不可用渐渐蔓延到B服务集群中的其他节点,导致服务级联故障。

1570592685522.png

此时我们就需要对服务出现异常的情况进行隔离,防止级联故障效应,常用的隔离策略有线程池隔离和信号量隔离

线程池隔离

线程池隔离顾名思义就是通过Java的线程池进行隔离,B服务调用C服务给予固定的线程数量比如10个线程,如果此时C服务宕机了就算大量的请求过来,调用C服务的接口只会占用10个线程不会占用其他工作线程资源,因此B服务就不会出现级联故障

1570593867373.png

信号量隔离

另一种隔离信号量隔离是使用JUC下的Semaphore来实现的,当拿不到信号量的时候直接拒接因此不会出现超时占用其他工作线程的情况。

1
2
3
4
5
6
Semaphore semaphore = new Semaphore(10,true);
//获取信号量
semaphore.acquire();
//do something here
//释放信号量
semaphore.release();

比较

​线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。而信号量隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以 overhead 比较小,但是效果不错,也支持超时失败。

比较项 线程池隔离 信号量隔离
线程 与调用线程不同,使用的是线程池创建的线程 与调用线程相同
开销 排队,切换,调度等开销 无线程切换性能更高
是否支持异步 支持 不支持
是否支持超时 支持超时 支持超时(新版本支持)
并发支持 支持通过线程池大小控制 支持通过最大信号量控制

降级熔断机制

​ 什么是降级和熔断?降级和熔断有什么区别?虽然很多人把降级熔断当着一个词来说的,但是降级和熔断是完全不同的概念的,看看下面几种场景:

1
2
场景一:比如我们每天上班坐公交,1路和2路公交都能到公司,但是2路公交需要下车走点路,所以平时都是坐1路公交,
突然有一天等了1路公交好久都没来,于是就坐了2路公交作为替代方案总不能迟到吧!下次再等1路车。
1
2
场景二:第二天,第三天 ... 已经一个星期了都没看到1路公交,心里觉得可能是1路公交改路线了,
于是直接坐2路公交了,在接下来的日子里都是直接忽略1路车直接坐2路车
1
场景三:突然有一天在等2路车的时候看到了1路车,是不是1路车现在恢复了,于是天天开心的坐着1路车上班去了,领导再也不担心我迟到了

场景一 在1路车没等到的情况下采取降级方案坐2路车,这就是降级策略,
场景二 如果多次都没有等到1路车就直接不等了下次直接坐2路车,这就是熔断策略,
场景三 如果过段时间1路车恢复了就使用2路车,这就是熔断恢复!

降级机制

常用的降级策略如:熔断器降级,限流降级,超时降级,异常降级,平均响应时间降级等

1570591437265.png

  • **熔断器降级:**即熔断器开启的时间直接熔断走降级的策略
  • **限流降级:**对流量进行限制达到降级的效果,如:Hystrix中的线程池,信号量都能达到限流的效果
  • **超时降级:**课时设置对应的超时时间如果服务调用超时了就执行降级策略,如:Hystrix中默认为1s
  • **异常降级:**异常降级很简单就是服务出现异常了执行降级策略
  • **平均响应时间降级:**服务响应时间持续飙高的时候实现降级策略,如Sentinel中默认的RT 上限是 4900 ms

熔断机制

​ 熔断其实是一个框架级的处理,那么这套熔断机制的设计,基本上业内用的是Martin Fowler提出的断路器模式,断路器的基本原理非常简单。
您将受保护的函数调用包装在断路器对象中,该对象将监视故障。一旦故障达到某个阈值,断路器将跳闸,并且所有进一步的断路器调用都会返回错误,而根本不会进行受保护的调用。常见的断路器模式有基本模式和扩展模式。

基本模式:

  • 如果断路器状态为close,则调用断路器将调用supplier服务模块;
  • 如果断路器状态为open则直接返回错误;
  • 如果超时,我们将增加失败计数器,成功的调用会将其重置为零;
  • 通过比较故障计数和阈值来确定断路器的状态;

20191024163112.png

扩展模式:

基础模式的断路器避免了在电路断开时发出受保护的呼叫,但是当情况恢复正常时,将需要外部干预才能将其重置。对于建筑物中的电路断路器,这是一种合理的方法,但是对于软件断路器,我们可以让断路器本身检测基础调用是否再次正常工作。我们可以通过在适当的时间间隔后再次尝试受保护的调用来实现这种自我重置行为,并在成功后重置断路器。于是就出现了扩展模式:

20191024163133.png

  • 最开始处于closed状态,一旦检测到错误到达一定阈值,便转为open状态;
  • 这时候会有个 reset timeout,到了这个时间了,会转移到half open状态;
  • 尝试放行一部分请求到后端,一旦检测成功便回归到closed状态,即恢复服务;

熔断策略

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

  • 平均响应时间:如Sentinel中的熔断就使用了平均响应时间,当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口之内,对这个方法的调用都会自动地熔断。
  • 异常比例 :主流的容错框架Hystrixsentinel中都使用了异常比例熔断策略,比如当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值之后,资源进入熔断状态,即在接下的时间窗口之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数:如Sentinel中的熔断就使用了异常数熔断策略,当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

限流机制

​ 限流也是提高系统的容错性的一种方案,不同的场景对“流”的定义也是不同的,可以是网络流量,带宽,每秒处理的事务数 (TPS),每秒请求数 (hits per second),并发请求数,甚至还可能是业务上的某个指标,比如用户在某段时间内允许的最多请求短信验证码次数。我们常说的限流都是限制每秒请求数,从分布式角度来看,限流可分为 分布式限流 (比如基于Sentinel或者Redis的集群限流)和 单机限流 。从算法实现角度来看,限流算法可分为 漏桶算法令牌桶算法滑动时间窗口算法

单机限流

漏桶算法

1570701032430.png

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

令牌桶算法

1570700342271.png

  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;
  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

固定时间窗口算法
20191024163948.png

这种实现计数器限流方式由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的,但是将间隔临界的一段时间内的请求就会超过系统限制,可能导致系统被压垮。

滑动时间窗口算法

1571219763433.png

  • 0、初始化,设置时间窗口,设置时间窗口时间点间隔长度;
  • 1、判断请求时间点是否在时间窗口中,在进入步骤2,否则进入步骤3;
  • 2、判断是否超过时间窗口限流值,是->进行限流,否->对应时间窗口计数器+1;
  • 3、移动当时时间窗口,移动方式是:起始时间点变为时间列表中的第二时间点,结束时间增加一个时间点。重新步骤一的判断 。

分布式限流

当应用为单点应用时,只要应用进行了限流,那么应用所依赖的各种服务也都得到了保护。 但线上业务出于各种原因考虑,多是分布式系统,单节点的限流仅能保护自身节点,但无法保护应用依赖的各种服务,并且在进行节点扩容、缩容时也无法准确控制整个服务的请求限制。

1571903140149.png

如果实现了分布式限流,那么就可以方便地控制整个服务集群的请求限制,且由于整个集群的请求数量得到了限制,因此服务依赖的各种资源也得到了限流的保护。

1571904304647.png

分布式限流方案

分布式限流的思想我列举下面三个方案:

1,Redis令牌桶

这种方案是最简单的一种集群限流思想。在本地限流中,我们使用Long的原子类作令牌桶,当实例数量超过1,我们就考虑将Redis用作公共内存区域,进行读写。涉及到的并发控制,也可以使用Redis实现分布式锁。

**缺点:**每取一次令牌都会进行一次网络开销,而网络开销起码是毫秒级,所以这种方案支持的并发量是非常有限的。

2,QPS统一分配

这种方案的思想是将集群限流最大程度的本地化。

举个例子,我们有两台服务器实例,对应的是同一个应用程序(Application.name相同),程序中设置的QPS为100,将应用程序与同一个控制台程序进行连接,控制台端依据应用的实例数量将QPS进行均分,动态设置每个实例的QPS为50,若是遇到两个服务器的配置并不相同,在负载均衡层的就已经根据服务器的优劣对流量进行分配,例如一台分配70%流量,另一台分配30%的流量。面对这种情况,控制台也可以对其实行加权分配QPS的策略。

缺点:

这也算一种集群限流的实现方案,但依旧存在不小的问题。该模式的分配比例是建立在大数据流量下的趋势进行分配,实际情况中可能并不是严格的五五分或三七分,误差不可控,极容易出现用户连续访问某一台服务器遇到请求驳回而另一台服务器此刻空闲流量充足的尴尬情况。

3,发票服务器

这种方案的思想是建立在Redis令牌桶方案的基础之上的。如何解决每次取令牌都伴随一次网络开销,该方案的解决方法是建立一层控制端,利用该控制端与Redis令牌桶进行交互,只有当客户端的剩余令牌数不足时,客户端才向该控制层取令牌并且每次取一批。

缺点:
这种思想类似于Java集合框架的数组扩容,设置一个阈值,只有当超过该临界值时,才会触发异步调用。其余存取令牌的操作与本地限流无二。虽然该方案依旧存在误差,但误差最大也就一批次令牌数而已。

参考

1,https://www.cnblogs.com/rjzhe...
2,https://www.martinfowler.com/...
3,https://www.cnblogs.com/babyc...
4,https://github.com/alibaba/Se...
5,https://www.jishuwen.com/d/2TX1
6,https://juejin.im/post/5c74a2...
7,https://www.jianshu.com/p/259...
8,https://zhuanlan.zhihu.com/p/...

转载自 微服务容错 - 隔离熔断限流

在高并发访问下,系统所依赖的服务的稳定性对系统的影响非常大,依赖有很多不可控的因素,比如网络连接变慢,资源突然繁忙,暂时不可用,服务脱机等。我们要构建稳定、可靠的分布式系统,就必须要有这样一套容错机制。常用的的容错技术如:隔离,降级,熔断,限流等策略,本文将详细的介绍微服务中的容错机制。

隔离机制

为什么要隔离? 比如我们现在某个接口所在的服务A需要调用服务B,而服务B同时需要调用C服务,此时服务C突然宕机同时此时流量暴涨,调用全部打到服务B上,此时B服务调用C超时大量的线程资源被该接口所占全部hang住,慢慢服务B中的线程数量则会持续增加直致CPU资源耗尽到100%,整个服务对外不可用渐渐蔓延到B服务集群中的其他节点,导致服务级联故障。

1570592685522.png

此时我们就需要对服务出现异常的情况进行隔离,防止级联故障效应,常用的隔离策略有线程池隔离和信号量隔离

线程池隔离

线程池隔离顾名思义就是通过Java的线程池进行隔离,B服务调用C服务给予固定的线程数量比如10个线程,如果此时C服务宕机了就算大量的请求过来,调用C服务的接口只会占用10个线程不会占用其他工作线程资源,因此B服务就不会出现级联故障

1570593867373.png

信号量隔离

另一种隔离信号量隔离是使用JUC下的Semaphore来实现的,当拿不到信号量的时候直接拒接因此不会出现超时占用其他工作线程的情况。

1
2
3
4
5
6
Semaphore semaphore = new Semaphore(10,true);
//获取信号量
semaphore.acquire();
//do something here
//释放信号量
semaphore.release();

比较

​线程池隔离针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。而信号量隔离非常轻量级,仅限制对某个资源调用的并发数,而不是显式地去创建线程池,所以 overhead 比较小,但是效果不错,也支持超时失败。

比较项 线程池隔离 信号量隔离
线程 与调用线程不同,使用的是线程池创建的线程 与调用线程相同
开销 排队,切换,调度等开销 无线程切换性能更高
是否支持异步 支持 不支持
是否支持超时 支持超时 支持超时(新版本支持)
并发支持 支持通过线程池大小控制 支持通过最大信号量控制

降级熔断机制

​ 什么是降级和熔断?降级和熔断有什么区别?虽然很多人把降级熔断当着一个词来说的,但是降级和熔断是完全不同的概念的,看看下面几种场景:

1
2
场景一:比如我们每天上班坐公交,1路和2路公交都能到公司,但是2路公交需要下车走点路,所以平时都是坐1路公交,
突然有一天等了1路公交好久都没来,于是就坐了2路公交作为替代方案总不能迟到吧!下次再等1路车。
1
2
场景二:第二天,第三天 ... 已经一个星期了都没看到1路公交,心里觉得可能是1路公交改路线了,
于是直接坐2路公交了,在接下来的日子里都是直接忽略1路车直接坐2路车
1
场景三:突然有一天在等2路车的时候看到了1路车,是不是1路车现在恢复了,于是天天开心的坐着1路车上班去了,领导再也不担心我迟到了

场景一 在1路车没等到的情况下采取降级方案坐2路车,这就是降级策略,
场景二 如果多次都没有等到1路车就直接不等了下次直接坐2路车,这就是熔断策略,
场景三 如果过段时间1路车恢复了就使用2路车,这就是熔断恢复!

降级机制

常用的降级策略如:熔断器降级,限流降级,超时降级,异常降级,平均响应时间降级等

1570591437265.png

  • **熔断器降级:**即熔断器开启的时间直接熔断走降级的策略
  • **限流降级:**对流量进行限制达到降级的效果,如:Hystrix中的线程池,信号量都能达到限流的效果
  • **超时降级:**课时设置对应的超时时间如果服务调用超时了就执行降级策略,如:Hystrix中默认为1s
  • **异常降级:**异常降级很简单就是服务出现异常了执行降级策略
  • **平均响应时间降级:**服务响应时间持续飙高的时候实现降级策略,如Sentinel中默认的RT 上限是 4900 ms

熔断机制

​ 熔断其实是一个框架级的处理,那么这套熔断机制的设计,基本上业内用的是Martin Fowler提出的断路器模式,断路器的基本原理非常简单。
您将受保护的函数调用包装在断路器对象中,该对象将监视故障。一旦故障达到某个阈值,断路器将跳闸,并且所有进一步的断路器调用都会返回错误,而根本不会进行受保护的调用。常见的断路器模式有基本模式和扩展模式。

基本模式:

  • 如果断路器状态为close,则调用断路器将调用supplier服务模块;
  • 如果断路器状态为open则直接返回错误;
  • 如果超时,我们将增加失败计数器,成功的调用会将其重置为零;
  • 通过比较故障计数和阈值来确定断路器的状态;

20191024163112.png

扩展模式:

基础模式的断路器避免了在电路断开时发出受保护的呼叫,但是当情况恢复正常时,将需要外部干预才能将其重置。对于建筑物中的电路断路器,这是一种合理的方法,但是对于软件断路器,我们可以让断路器本身检测基础调用是否再次正常工作。我们可以通过在适当的时间间隔后再次尝试受保护的调用来实现这种自我重置行为,并在成功后重置断路器。于是就出现了扩展模式:

20191024163133.png

  • 最开始处于closed状态,一旦检测到错误到达一定阈值,便转为open状态;
  • 这时候会有个 reset timeout,到了这个时间了,会转移到half open状态;
  • 尝试放行一部分请求到后端,一旦检测成功便回归到closed状态,即恢复服务;

熔断策略

我们通常用以下几种方式来衡量资源是否处于稳定的状态:

  • 平均响应时间:如Sentinel中的熔断就使用了平均响应时间,当 1s 内持续进入 5 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口之内,对这个方法的调用都会自动地熔断。
  • 异常比例 :主流的容错框架Hystrixsentinel中都使用了异常比例熔断策略,比如当资源的每秒请求量 >= 5,并且每秒异常总数占通过量的比值超过阈值之后,资源进入熔断状态,即在接下的时间窗口之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
  • 异常数:如Sentinel中的熔断就使用了异常数熔断策略,当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。

限流机制

​ 限流也是提高系统的容错性的一种方案,不同的场景对“流”的定义也是不同的,可以是网络流量,带宽,每秒处理的事务数 (TPS),每秒请求数 (hits per second),并发请求数,甚至还可能是业务上的某个指标,比如用户在某段时间内允许的最多请求短信验证码次数。我们常说的限流都是限制每秒请求数,从分布式角度来看,限流可分为 分布式限流 (比如基于Sentinel或者Redis的集群限流)和 单机限流 。从算法实现角度来看,限流算法可分为 漏桶算法令牌桶算法滑动时间窗口算法

单机限流

漏桶算法

1570701032430.png

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;
  • 如果桶是空的,则不需流出水滴;
  • 可以以任意速率流入水滴到漏桶;
  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

令牌桶算法

1570700342271.png

  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;
  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;
  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;
  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

固定时间窗口算法
20191024163948.png

这种实现计数器限流方式由于是在一个时间间隔内进行限制,如果用户在上个时间间隔结束前请求(但没有超过限制),同时在当前时间间隔刚开始请求(同样没超过限制),在各自的时间间隔内,这些请求都是正常的,但是将间隔临界的一段时间内的请求就会超过系统限制,可能导致系统被压垮。

滑动时间窗口算法

1571219763433.png

  • 0、初始化,设置时间窗口,设置时间窗口时间点间隔长度;
  • 1、判断请求时间点是否在时间窗口中,在进入步骤2,否则进入步骤3;
  • 2、判断是否超过时间窗口限流值,是->进行限流,否->对应时间窗口计数器+1;
  • 3、移动当时时间窗口,移动方式是:起始时间点变为时间列表中的第二时间点,结束时间增加一个时间点。重新步骤一的判断 。

分布式限流

当应用为单点应用时,只要应用进行了限流,那么应用所依赖的各种服务也都得到了保护。 但线上业务出于各种原因考虑,多是分布式系统,单节点的限流仅能保护自身节点,但无法保护应用依赖的各种服务,并且在进行节点扩容、缩容时也无法准确控制整个服务的请求限制。

1571903140149.png

如果实现了分布式限流,那么就可以方便地控制整个服务集群的请求限制,且由于整个集群的请求数量得到了限制,因此服务依赖的各种资源也得到了限流的保护。

1571904304647.png

分布式限流方案

分布式限流的思想我列举下面三个方案:

1,Redis令牌桶

这种方案是最简单的一种集群限流思想。在本地限流中,我们使用Long的原子类作令牌桶,当实例数量超过1,我们就考虑将Redis用作公共内存区域,进行读写。涉及到的并发控制,也可以使用Redis实现分布式锁。

**缺点:**每取一次令牌都会进行一次网络开销,而网络开销起码是毫秒级,所以这种方案支持的并发量是非常有限的。

2,QPS统一分配

这种方案的思想是将集群限流最大程度的本地化。

举个例子,我们有两台服务器实例,对应的是同一个应用程序(Application.name相同),程序中设置的QPS为100,将应用程序与同一个控制台程序进行连接,控制台端依据应用的实例数量将QPS进行均分,动态设置每个实例的QPS为50,若是遇到两个服务器的配置并不相同,在负载均衡层的就已经根据服务器的优劣对流量进行分配,例如一台分配70%流量,另一台分配30%的流量。面对这种情况,控制台也可以对其实行加权分配QPS的策略。

缺点:

这也算一种集群限流的实现方案,但依旧存在不小的问题。该模式的分配比例是建立在大数据流量下的趋势进行分配,实际情况中可能并不是严格的五五分或三七分,误差不可控,极容易出现用户连续访问某一台服务器遇到请求驳回而另一台服务器此刻空闲流量充足的尴尬情况。

3,发票服务器

这种方案的思想是建立在Redis令牌桶方案的基础之上的。如何解决每次取令牌都伴随一次网络开销,该方案的解决方法是建立一层控制端,利用该控制端与Redis令牌桶进行交互,只有当客户端的剩余令牌数不足时,客户端才向该控制层取令牌并且每次取一批。

缺点:
这种思想类似于Java集合框架的数组扩容,设置一个阈值,只有当超过该临界值时,才会触发异步调用。其余存取令牌的操作与本地限流无二。虽然该方案依旧存在误差,但误差最大也就一批次令牌数而已。

参考

1,https://www.cnblogs.com/rjzhe...
2,https://www.martinfowler.com/...
3,https://www.cnblogs.com/babyc...
4,https://github.com/alibaba/Se...
5,https://www.jishuwen.com/d/2TX1
6,https://juejin.im/post/5c74a2...
7,https://www.jianshu.com/p/259...
8,https://zhuanlan.zhihu.com/p/...

1 Druid

1.1 什么样的业务适合用 Druid?

建议如下:

时序化数据:Druid 可以理解为时序数据库,所有的数据必须有时间字段。
实时数据接入可容忍丢数据(tranquility): tranquility 有丢数据的风险,所以建议实时和离线一起用,实时接当天数据,离线第二天把今天的数据全部覆盖,保证数据完备性。
OLAP 查询而不是 OLTP 查询:Druid 查询并发有限,不适合 OLTP 查询。
非精确的去重计算:目前 Druid 的去重都是非精确的。
无 Join 操作:Druid 适合处理星型模型的数据,不支持关联操作。
数据没有 update 更新操作,只对 segment 粒度进行覆盖:由于时序化数据的特点,Druid 不支持数据的更新

1.2 离线批量入库脚本

1.2.1 druid indexing on spark

https://github.com/Fokko/druid-indexing-on-spark.git

1.2.2 pyspark

Druid是一款高性能的列式存储时序数据库,其支持实时数据分析并在OLAP数据分析领域有其特有的优势。Druid除了支持实时摄入数据外也支持离线批量导入数据,主要通过离线MR任务去HDFS上拉取数据并做聚合roll up处理入库。
该脚本可作为通用的druid入库离线任务脚本,方便在配置离线任务流即数据写到HDFS后起对应的入库任务。该脚本可运行在tesla平台作为pyspark任务执行。

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python

from __future__ import print_function
from pyspark import SparkContext

import json
import re
import sys
import time
import urllib2
import urlparse
import datetime

def read_task_file(filename):
with open(filename, 'r') as f:
contents = f.read()
# We don't use the parsed data, but we want to throw early if it's invalid
try:
json.loads(contents)
except Exception, e:
print('Invalid JSON in task file "{0}": {1}\n'.format(filename, repr(e)))
sys.exit(1)
return contents

# Keep trying until timeout_at, maybe die then
def post_task(url, task_json, timeout_at):
try:
task_url = url.rstrip("/") + "/druid/indexer/v1/task"
req = urllib2.Request(task_url, task_json, {'Content-Type' : 'application/json'})
timeleft = timeout_at - time.time()
response_timeout = min(max(timeleft, 5), 10)
response = urllib2.urlopen(req, None, response_timeout)
return response.read().rstrip()
except urllib2.URLError as e:
if isinstance(e, urllib2.HTTPError) and e.code >= 400 and e.code <= 500:
# 4xx (problem with the request) or 500 (something wrong on the server)
raise_friendly_error(e)
elif time.time() >= timeout_at:
# No futher retries
raise_friendly_error(e)
elif isinstance(e, urllib2.HTTPError) and e.code in [301, 302, 303, 305, 307] and \
e.info().getheader("Location") is not None:
# Set the new location in args.url so it can be used by await_task_completion and re-issue the request
location = urlparse.urlparse(e.info().getheader("Location"))
url = "{0}://{1}".format(location.scheme, location.netloc)
print("Redirect response received, setting url to [{0}]\n".format(url))
return post_task(url, task_json, timeout_at)
else:
# If at first you don't succeed, try, try again!
sleep_time = 30
extra = ''
if hasattr(e, 'read'):
extra = e.read().rstrip()
print("Waiting up to {0}s for indexing service to become available. [Got: {1} {2}]".format(max(sleep_time, int(timeout_at - time.time())), str(e), extra).rstrip())
print("\n")
time.sleep(sleep_time)
return post_task(url, task_json, timeout_at)

# Keep trying until timeout_at, maybe die then
def await_task_completion(url, task_id, timeout_at):
while True:
task_url = url.rstrip("/") + "/druid/indexer/v1/task/{0}/status".format(task_id)
req = urllib2.Request(task_url)
timeleft = timeout_at - time.time()
response_timeout = min(max(timeleft, 5), 30)
response = urllib2.urlopen(req, None, response_timeout)
response_obj = json.loads(response.read())
response_status_code = response_obj["status"]["status"]
if response_status_code in ['SUCCESS', 'FAILED']:
return response_status_code
else:
if time.time() < timeout_at:
print("Task {0} still running...".format(task_id))
timeleft = timeout_at - time.time()
time.sleep(min(30, timeleft))
else:
raise Exception("Task {0} did not finish in time!".format(task_id))

def raise_friendly_error(e):
if isinstance(e, urllib2.HTTPError):
text = e.read().strip()
reresult = re.search(r'<pre>(.*?)</pre>', text, re.DOTALL)
if reresult:
text = reresult.group(1).strip()
raise Exception("HTTP Error {0}: {1}, check overlord log for more details.\n{2}".format(e.code, e.reason, text))
raise e

def get_task_json(content, hdfspath, data_source, date, segment, query):
input_json = json.loads(content)
input_json["spec"]["ioConfig"]["inputSpec"]["paths"] = hdfspath + "/" + date
input_json["spec"]["dataSchema"]["dataSource"] = data_source

date_array = []
date_time = datetime.datetime(int(date[0:4]),int(date[4:6]),int(date[6:8]))
date_time_next = date_time + datetime.timedelta(days=1)

date_array.append(date_time.strftime('%Y-%m-%dT%H:%M:%S+08:00') + "/" + date_time_next.strftime('%Y-%m-%dT%H:%M:%S+08:00'))
input_json["spec"]["dataSchema"]["granularitySpec"]["segmentGranularity"] = segment
input_json["spec"]["dataSchema"]["granularitySpec"]["queryGranularity"] = query
input_json["spec"]["dataSchema"]["granularitySpec"]["intervals"] = date_array
return json.dumps(input_json, indent=2)

def main():
"""
Usage: druid_task.py <url> <task_file> <date> <submit_timeout> <complete_timeout> <hdfs_path> <data_source>
"""
if len(sys.argv) < 10:
print("Usage: druid_task.py <url> <task_file> <date> <submit_timeout> <complete_timeout> <hdfs_path> <data_source> <segment> <query>")
exit(1)
print(sys.argv)

url = sys.argv[1].strip()
task_file = sys.argv[2].strip()
date = sys.argv[3].strip()
submit_timeout = sys.argv[4].strip()
complete_timeout = sys.argv[5].strip()
hdfspath = sys.argv[6].strip()
data_source = sys.argv[7].strip()
date_segment = sys.argv[8].strip()
date_query = sys.argv[9].strip()

# data path
datapath = hdfspath + "/" + date

# init spark context
sc = SparkContext(appName="druid_index_task_day")

datafiles_rdd = sc.wholeTextFiles(datapath)
is_empty = datafiles_rdd.isEmpty()
print(is_empty)
print("datapath:" + datapath)
if is_empty == False:
submit_timeout_at = time.time() + float(submit_timeout)
complete_timeout_at = time.time() + float(complete_timeout)
task_json = get_task_json(read_task_file(task_file), hdfspath, data_source, date, date_segment, date_query)
print(task_json)

task_id = json.loads(post_task(url, task_json, submit_timeout_at))["task"]
sys.stderr.write('\033[1m' + "Task started: " + '\033[0m' + "{0}\n".format(task_id))
sys.stderr.write('\033[1m' + "Task log: " + '\033[0m' + "{0}/druid/indexer/v1/task/{1}/log\n".format(url.rstrip("/"),task_id))
sys.stderr.write('\033[1m' + "Task status: " + '\033[0m' + "{0}/druid/indexer/v1/task/{1}/status\n".format(url.rstrip("/"),task_id))

task_status = await_task_completion(url, task_id, complete_timeout_at)
print("Task finished with status: {0}\n".format(task_status))
if task_status != 'SUCCESS':
sys.exit(1)
else:
print("Task finished with no data.")

if __name__ == "__main__":
main()

下钻和聚合

注:以下参考答案都经过简单数据场景进行测试通过,但并未测试其他复杂情况。本文档的SQL主要使用Hive SQL。

一、行列转换

描述:表中记录了各年份各部门的平均绩效考核成绩。
表名:t1
表结构:

a -- 年份 b -- 部门 c -- 绩效得分

表内容

a b c 2014 B 9 2015 A 8 2014 A 10 2015 B 7

问题一:多行转多列

问题描述:将上述表内容转为如下输出结果所示:

a col_A col_B 2014 10 9 2015 8 7

参考答案

select a, max(case when b="A" then c end) col_A, max(case when b="B" then c end) col_B from t1 group by a;

问题二:如何将结果转成源表?(多列转多行)

问题描述:将问题一的结果转成源表,问题一结果表名为t1_2

参考答案

select a, b, c from ( select a,"A" as b,col_a as c from t1_2 union all select a,"B" as b,col_b as c from t1_2 )tmp;

问题三:同一部门会有多个绩效,求多行转多列结果

问题描述:2014年公司组织架构调整,导致部门出现多个绩效,业务及人员不同,无法合并算绩效,源表内容如下:

2014 B 9 2015 A 8 2014 A 10 2015 B 7 2014 B 6

输出结果如下所示

a col_A col_B 2014 10 6,9 2015 8 7

参考答案:

select a, max(case when b="A" then c end) col_A, max(case when b="B" then c end) col_B from ( select a, b, concat_ws(",",collect_set(cast(c as string))) as c from t1 group by a,b )tmp group by a;

二、排名中取他值

表名t2
表字段及内容

a b c 2014 A 3 2014 B 1 2014 C 2 2015 A 4 2015 D 3

问题一:按a分组取b字段最小时对应的c字段

输出结果如下所示

a min_c 2014 3 2015 4

参考答案:

select a, c as min_c from ( select a, b, c, row_number() over(partition by a order by b) as rn from t2 )a where rn = 1;

问题二:按a分组取b字段排第二时对应的c字段

输出结果如下所示

a second_c 2014 1 2015 3

参考答案

select a, c as second_c from ( select a, b, c, row_number() over(partition by a order by b) as rn from t2 )a where rn = 2;

问题三:按a分组取b字段最小和最大时对应的c字段

输出结果如下所示

a min_c max_c 2014 3 2 2015 4 3

参考答案:

select a, min(if(asc_rn = 1, c, null)) as min_c, max(if(desc_rn = 1, c, null)) as max_c from ( select a, b, c, row_number() over(partition by a order by b) as asc_rn, row_number() over(partition by a order by b desc) as desc_rn from t2 )a where asc_rn = 1 or desc_rn = 1 group by a;

问题四:按a分组取b字段第二小和第二大时对应的c字段

输出结果如下所示

a min_c max_c 2014 1 1 2015 3 4

参考答案

select ret.a ,max(case when ret.rn_min = 2 then ret.c else null end) as min_c ,max(case when ret.rn_max = 2 then ret.c else null end) as max_c from ( select * ,row_number() over(partition by t2.a order by t2.b) as rn_min ,row_number() over(partition by t2.a order by t2.b desc) as rn_max from t2 ) as ret where ret.rn_min = 2 or ret.rn_max = 2 group by ret.a;

问题五:按a分组取b字段前两小和前两大时对应的c字段

注意:需保持b字段最小、最大排首位

输出结果如下所示

a min_c max_c 2014 3,1 2,1 2015 4,3 3,4

参考答案

select tmp1.a as a, min_c, max_c from ( select a, concat_ws(',', collect_list(c)) as min_c from ( select a, b, c, row_number() over(partition by a order by b) as asc_rn from t2 )a where asc_rn <= 2 group by a )tmp1 join ( select a, concat_ws(',', collect_list(c)) as max_c from ( select a, b, c, row_number() over(partition by a order by b desc) as desc_rn from t2 )a where desc_rn <= 2 group by a )tmp2 on tmp1.a = tmp2.a;

三、累计求值

表名t3
表字段及内容

a b c 2014 A 3 2014 B 1 2014 C 2 2015 A 4 2015 D 3

问题一:按a分组按b字段排序,对c累计求和

输出结果如下所示

a b sum_c 2014 A 3 2014 B 4 2014 C 6 2015 A 4 2015 D 7

参考答案

select a, b, c, sum(c) over(partition by a order by b) as sum_c from t3;

问题二:按a分组按b字段排序,对c取累计平均值

输出结果如下所示

a b avg_c 2014 A 3 2014 B 2 2014 C 2 2015 A 4 2015 D 3.5

参考答案

select a, b, c, avg(c) over(partition by a order by b) as avg_c from t3;

问题三:按a分组按b字段排序,对b取累计排名比例

输出结果如下所示

a b ratio_c 2014 A 0.33 2014 B 0.67 2014 C 1.00 2015 A 0.50 2015 D 1.00

参考答案

select a, b, c, round(row_number() over(partition by a order by b) / (count(c) over(partition by a)),2) as ratio_c from t3 order by a,b;

问题四:按a分组按b字段排序,对b取累计求和比例

输出结果如下所示

a b ratio_c 2014 A 0.50 2014 B 0.67 2014 C 1.00 2015 A 0.57 2015 D 1.00

参考答案

select a, b, c, round(sum(c) over(partition by a order by b) / (sum(c) over(partition by a)),2) as ratio_c from t3 order by a,b;

四、窗口大小控制

表名t4
表字段及内容

a b c 2014 A 3 2014 B 1 2014 C 2 2015 A 4 2015 D 3

问题一:按a分组按b字段排序,对c取前后各一行的和

输出结果如下所示

a b sum_c 2014 A 1 2014 B 5 2014 C 1 2015 A 3 2015 D 4

参考答案

select a, b, lag(c,1,0) over(partition by a order by b)+lead(c,1,0) over(partition by a order by b) as sum_c from t4;

问题二:按a分组按b字段排序,对c取平均值

问题描述:前一行与当前行的均值!

输出结果如下所示

a b avg_c 2014 A 3 2014 B 2 2014 C 1.5 2015 A 4 2015 D 3.5

参考答案

select a, b, case when lag_c is null then c else (c+lag_c)/2 end as avg_c from ( select a, b, c, lag(c,1) over(partition by a order by b) as lag_c from t4 )temp;

五、产生连续数值

输出结果如下所示

1 2 3 4 5 ... 100

参考答案
不借助其他任何外表,实现产生连续数值
此处给出两种解法,其一:

select id_start+pos as id from( select 1 as id_start, 1000000 as id_end ) m lateral view posexplode(split(space(id_end-id_start), '')) t as pos, val

其二:

select row_number() over() as id from (select split(space(99), ' ') as x) t lateral view explode(x) ex;

那如何产生1至1000000连续数值?

参考答案

select row_number() over() as id from (select split(space(999999), ' ') as x) t lateral view explode(x) ex;

六、数据扩充与收缩

表名t6
表字段及内容

a 3 2 4

问题一:数据扩充

输出结果如下所示

a b 3 3、2、1 2 2、1 4 4、3、2、1

参考答案

select t.a, concat_ws('、',collect_set(cast(t.rn as string))) as b from ( select t6.a, b.rn from t6 left join ( select row_number() over() as rn from (select split(space(5), ' ') as x) t -- space(5)可根据t6表的最大值灵活调整 lateral view explode(x) pe ) b on 1 = 1 where t6.a >= b.rn order by t6.a, b.rn desc ) t group by t.a;

问题二:数据扩充,排除偶数

输出结果如下所示

a b 3 3、1 2 1 4 3、1

参考答案

select t.a, concat_ws('、',collect_set(cast(t.rn as string))) as b from ( select t6.a, b.rn from t6 left join ( select row_number() over() as rn from (select split(space(5), ' ') as x) t lateral view explode(x) pe ) b on 1 = 1 where t6.a >= b.rn and b.rn % 2 = 1 order by t6.a, b.rn desc ) t group by t.a;

问题三:如何处理字符串累计拼接

问题描述:将小于等于a字段的值聚合拼接起来

输出结果如下所示

a b 3 2、3 2 2 4 2、3、4

参考答案

select t.a, concat_ws('、',collect_set(cast(t.a1 as string))) as b from ( select t6.a, b.a1 from t6 left join ( select a as a1 from t6 ) b on 1 = 1 where t6.a >= b.a1 order by t6.a, b.a1 ) t group by t.a;

问题四:如果a字段有重复,如何实现字符串累计拼接

输出结果如下所示

a b 2 2 3 2、3 3 2、3、3 4 2、3、3、4

参考答案

select a, b from ( select t.a, t.rn, concat_ws('、',collect_list(cast(t.a1 as string))) as b from ( select a.a, a.rn, b.a1 from ( select a, row_number() over(order by a ) as rn from t6 ) a left join ( select a as a1, row_number() over(order by a ) as rn from t6 ) b on 1 = 1 where a.a >= b.a1 and a.rn >= b.rn order by a.a, b.a1 ) t group by t.a,t.rn order by t.a,t.rn ) tt;

问题五:数据展开

问题描述:如何将字符串”1-5,16,11-13,9”扩展成”1,2,3,4,5,16,11,12,13,9”?注意顺序不变。

参考答案

select concat_ws(',',collect_list(cast(rn as string))) from ( select a.rn, b.num, b.pos from ( select row_number() over() as rn from (select split(space(20), ' ') as x) t -- space(20)可灵活调整 lateral view explode(x) pe ) a lateral view outer posexplode(split('1-5,16,11-13,9', ',')) b as pos, num where a.rn between cast(split(num, '-')[0] as int) and cast(split(num, '-')[1] as int) or a.rn = num order by pos, rn ) t;

七、合并与拆分

表名t7
表字段及内容

a b 2014 A 2014 B 2015 B 2015 D

问题一:合并

输出结果如下所示

2014 A、B 2015 B、D

参考答案:

select a, concat_ws('、', collect_set(t.b)) b from t7 group by a;

问题二:拆分

问题描述:将分组合并的结果拆分出来

参考答案

select t.a, d from ( select a, concat_ws('、', collect_set(t7.b)) b from t7 group by a )t lateral view explode(split(t.b, '、')) table_tmp as d;

八、模拟循环操作

表名t8
表字段及内容

a 1011 0101

问题一:如何将字符’1’的位置提取出来

输出结果如下所示:

1,3,4 2,4

参考答案

select a, concat_ws(",",collect_list(cast(index as string))) as res from ( select a, index+1 as index, chr from ( select a, concat_ws(",",substr(a,1,1),substr(a,2,1),substr(a,3,1),substr(a,-1)) str from t8 ) tmp1 lateral view posexplode(split(str,",")) t as index,chr where chr = "1" ) tmp2 group by a;

九、不使用distinct或group by去重

表名t9
表字段及内容

a b c d 2014 2016 2014 A 2014 2015 2015 B

问题一:不使用distinct或group by去重

输出结果如下所示

2014 A 2016 A 2014 B 2015 B

参考答案

select t2.year ,t2.num from ( select * ,row_number() over (partition by t1.year,t1.num) as rank_1 from ( select a as year, d as num from t9 union all select b as year, d as num from t9 union all select c as year, d as num from t9 )t1 )t2 where rank_1=1 order by num;

十、容器–反转内容

表名t10
表字段及内容

a AB,CA,BAD BD,EA

问题一:反转逗号分隔的数据:改变顺序,内容不变

输出结果如下所示

BAD,CA,AB EA,BD

参考答案

select a, concat_ws(",",collect_list(reverse(str))) from ( select a, str from t10 lateral view explode(split(reverse(a),",")) t as str ) tmp1 group by a;

问题二:反转逗号分隔的数据:改变内容,顺序不变

输出结果如下所示

BA,AC,DAB DB,AE

参考答案

select a, concat_ws(",",collect_list(reverse(str))) from ( select a, str from t10 lateral view explode(split(a,",")) t as str ) tmp1 group by a;

十一、多容器–成对提取数据

表名t11
表字段及内容

a b A/B 1/3 B/C/D 4/5/2

问题一:成对提取数据,字段一一对应

输出结果如下所示

a b A 1 B 3 B 4 C 5 D 2

参考答案:

select a_inx, b_inx from ( select a, b, a_id, a_inx, b_id, b_inx from t11 lateral view posexplode(split(a,'/')) t as a_id,a_inx lateral view posexplode(split(b,'/')) t as b_id,b_inx ) tmp where a_id=b_id;

十二、多容器–转多行

表名t12
表字段及内容

a b c 001 A/B 1/3/5 002 B/C/D 4/5

问题一:转多行

输出结果如下所示

a d e 001 type_b A 001 type_b B 001 type_c 1 001 type_c 3 001 type_c 5 002 type_b B 002 type_b C 002 type_b D 002 type_c 4 002 type_c 5

参考答案:

select a, d, e from ( select a, "type_b" as d, str as e from t12 lateral view explode(split(b,"/")) t as str union all select a, "type_c" as d, str as e from t12 lateral view explode(split(c,"/")) t as str ) tmp order by a,d;

十三、抽象分组–断点排序

表名t13
表字段及内容

a b 2014 1 2015 1 2016 1 2017 0 2018 0 2019 -1 2020 -1 2021 -1 2022 1 2023 1

问题一:断点排序

输出结果如下所示

a b c 2014 1 1 2015 1 2 2016 1 3 2017 0 1 2018 0 2 2019 -1 1 2020 -1 2 2021 -1 3 2022 1 1 2023 1 2

参考答案:

select a, b, row_number() over( partition by b,repair_a order by a asc) as c--按照b列和[b的组首]分组,排序 from ( select a, b, a-b_rn as repair_a--根据b列值出现的次序,修复a列值为b首次出现的a列值,称为b的[组首] from ( select a, b, row_number() over( partition by b order by a asc ) as b_rn--按b列分组,按a列排序,得到b列各值出现的次序 from t13 )tmp1 )tmp2--注意,如果不同的b列值,可能出现同样的组首值,但组首值需要和a列值 一并参与分组,故并不影响排序。 order by a asc;

十四、业务逻辑的分类与抽象–时效

日期表d_date
表字段及内容

date_id is_work 2017-04-13 1 2017-04-14 1 2017-04-15 0 2017-04-16 0 2017-04-17 1

工作日:周一至周五09:30-18:30

客户申请表t14
表字段及内容

a b c 1 申请 2017-04-14 18:03:00 1 通过 2017-04-17 09:43:00 2 申请 2017-04-13 17:02:00 2 通过 2017-04-15 09:42:00

问题一:计算上表中从申请到通过占用的工作时长

输出结果如下所示

a d 1 0.67h 2 10.67h

参考答案:

select a, round(sum(diff)/3600,2) as d from ( select a, apply_time, pass_time, dates, rn, ct, is_work, case when is_work=1 and rn=1 then unix_timestamp(concat(dates,' 18:30:00'),'yyyy-MM-dd HH:mm:ss')-unix_timestamp(apply_time,'yyyy-MM-dd HH:mm:ss') when is_work=0 then 0 when is_work=1 and rn=ct then unix_timestamp(pass_time,'yyyy-MM-dd HH:mm:ss')-unix_timestamp(concat(dates,' 09:30:00'),'yyyy-MM-dd HH:mm:ss') when is_work=1 and rn!=ct then 9*3600 end diff from ( select a, apply_time, pass_time, time_diff, day_diff, rn, ct, date_add(start,rn-1) dates from ( select a, apply_time, pass_time, time_diff, day_diff, strs, start, row_number() over(partition by a) as rn, count(*) over(partition by a) as ct from ( select a, apply_time, pass_time, time_diff, day_diff, substr(repeat(concat(substr(apply_time,1,10),','),day_diff+1),1,11*(day_diff+1)-1) strs from ( select a, apply_time, pass_time, unix_timestamp(pass_time,'yyyy-MM-dd HH:mm:ss')-unix_timestamp(apply_time,'yyyy-MM-dd HH:mm:ss') time_diff, datediff(substr(pass_time,1,10),substr(apply_time,1,10)) day_diff from ( select a, max(case when b='申请' then c end) apply_time, max(case when b='通过' then c end) pass_time from t14 group by a ) tmp1 ) tmp2 ) tmp3 lateral view explode(split(strs,",")) t as start ) tmp4 ) tmp5 join d_date on tmp5.dates = d_date.date_id ) tmp6 group by a;

十五、时间序列–进度及剩余

表名t15
表字段及内容

date_id is_work 2017-07-30 0 2017-07-31 1 2017-08-01 1 2017-08-02 1 2017-08-03 1 2017-08-04 1 2017-08-05 0 2017-08-06 0 2017-08-07 1

问题一:求每天的累计周工作日,剩余周工作日

输出结果如下所示

date_id week_to_work week_left_work 2017-07-31 1 4 2017-08-01 2 3 2017-08-02 3 2 2017-08-03 4 1 2017-08-04 5 0 2017-08-05 5 0 2017-08-06 5 0

参考答案:
此处给出两种解法,其一:

select date_id ,case date_format(date_id,'u') when 1 then 1 when 2 then 2 when 3 then 3 when 4 then 4 when 5 then 5 when 6 then 5 when 7 then 5 end as week_to_work ,case date_format(date_id,'u') when 1 then 4 when 2 then 3 when 3 then 2 when 4 then 1 when 5 then 0 when 6 then 0 when 7 then 0 end as week_to_work from t15

其二:

select date_id, week_to_work, week_sum_work-week_to_work as week_left_work from( select date_id, sum(is_work) over(partition by year,week order by date_id) as week_to_work, sum(is_work) over(partition by year,week) as week_sum_work from( select date_id, is_work, year(date_id) as year, weekofyear(date_id) as week from t15 ) ta ) tb order by date_id;

十六、时间序列–构造日期

问题一:直接使用SQL实现一张日期维度表,包含以下字段:

date string 日期 d_week string 年内第几周 weeks int 周几 w_start string 周开始日 w_end string 周结束日 d_month int 第几月 m_start string 月开始日 m_end string 月结束日 d_quarter int 第几季 q_start string 季开始日 q_end string 季结束日 d_year int 年份 y_start string 年开始日 y_end string 年结束日

参考答案

``drop table if exists dim_date;
create table if not exists dim_date(
date string comment ‘日期’,
d_week string comment ‘年内第几周’,
weeks string comment ‘周几’,
w_start string comment ‘周开始日’,
w_end string comment ‘周结束日’,
d_month string comment ‘第几月’,
m_start string comment ‘月开始日’,
m_end string comment ‘月结束日’,
d_quarter int comment ‘第几季’,
q_start string comment ‘季开始日’,
q_end string comment ‘季结束日’,
d_year int comment ‘年份’,
y_start string comment ‘年开始日’,
y_end string comment ‘年结束日’
);
–自然月: 指每月的1号到那个月的月底,它是按照阳历来计算的。就是从每月1号到月底,不管这个月有30天,31天,29天或者28天,都算是一个自然月。

insert overwrite table dim_date
select date
, d_week –年内第几周
, case weekid
when 0 then ‘周日’
when 1 then ‘周一’
when 2 then ‘周二’
when 3 then ‘周三’
when 4 then ‘周四’
when 5 then ‘周五’
when 6 then ‘周六’
end as weeks – 周
, date_add(next_day(date,’MO’),-7) as w_start –周一
, date_add(next_day(date,’MO’),-1) as w_end – 周日_end
– 月份日期
, concat(‘第’, monthid, ‘月’) as d_month
, m_start
, m_end

 -- 季节  
 , quarterid as d_quart  
 , concat(d_year, '-', substr(concat('0', (quarterid - 1) * 3 + 1), -2), '-01') as q_start --季开始日  
 , date_sub(concat(d_year, '-', substr(concat('0', (quarterid) * 3 + 1), -2), '-01'), 1) as q_end   --季结束日  
 -- 年  
 , d_year  
 , y_start  
 , y_end  

from (
select date
, pmod(datediff(date, ‘2012-01-01’), 7) as weekid –获取周几
, cast(substr(date, 6, 2) as int) as monthid –获取月份
, case
when cast(substr(date, 6, 2) as int) <= 3 then 1
when cast(substr(date, 6, 2) as int) <= 6 then 2
when cast(substr(date, 6, 2) as int) <= 9 then 3
when cast(substr(date, 6, 2) as int) <= 12 then 4
end as quarterid –获取季节 可以直接使用 quarter(date)
, substr(date, 1, 4) as d_year – 获取年份
, trunc(date, ‘YYYY’) as y_start –年开始日
, date_sub(trunc(add_months(date, 12), ‘YYYY’), 1) as y_end –年结束日
, date_sub(date, dayofmonth(date) - 1) as m_start –当月第一天
, last_day(date_sub(date, dayofmonth(date) - 1)) m_end –当月最后一天
, weekofyear(date) as d_week –年内第几周
from (
– ‘2021-04-01’是开始日期, ‘2022-03-31’是截止日期
select date_add(‘2021-04-01’, t0.pos) as date
from (
select posexplode(
split(
repeat(‘o’, datediff(
from_unixtime(unix_timestamp(‘2022-03-31’, ‘yyyy-mm-dd’),
‘yyyy-mm-dd’),
‘2021-04-01’)), ‘o’
)
)
) t0
) t1
) t2;
``

十七、时间序列–构造累积日期

表名t17
表字段及内容

date_id 2017-08-01 2017-08-02 2017-08-03

问题一:每一日期,都扩展成月初至当天

输出结果如下所示

date_id date_to_day 2017-08-01 2017-08-01 2017-08-02 2017-08-01 2017-08-02 2017-08-02 2017-08-03 2017-08-01 2017-08-03 2017-08-02 2017-08-03 2017-08-03

这种累积相关的表,常做桥接表。

参考答案:

select date_id, date_add(date_start_id,pos) as date_to_day from ( select date_id, date_sub(date_id,dayofmonth(date_id)-1) as date_start_id from t17 ) m lateral view posexplode(split(space(datediff(from_unixtime(unix_timestamp(date_id,'yyyy-MM-dd')),from_unixtime(unix_timestamp(date_start_id,'yyyy-MM-dd')))), '')) t as pos, val;

十八、时间序列–构造连续日期

表名t18
表字段及内容

a b c 101 2018-01-01 10 101 2018-01-03 20 101 2018-01-06 40 102 2018-01-02 20 102 2018-01-04 30 102 2018-01-07 60

问题一:构造连续日期

问题描述:将表中数据的b字段扩充至范围[2018-01-01, 2018-01-07],并累积对c求和。
b字段的值是较稀疏的。

输出结果如下所示

a b c d 101 2018-01-01 10 10 101 2018-01-02 0 10 101 2018-01-03 20 30 101 2018-01-04 0 30 101 2018-01-05 0 30 101 2018-01-06 40 70 101 2018-01-07 0 70 102 2018-01-01 0 0 102 2018-01-02 20 20 102 2018-01-03 0 20 102 2018-01-04 30 50 102 2018-01-05 0 50 102 2018-01-06 0 50 102 2018-01-07 60 110

参考答案:

select a, b, c, sum(c) over(partition by a order by b) as d from ( select t1.a, t1.b, case when t18.b is not null then t18.c else 0 end as c from ( select a, date_add(s,pos) as b from ( select a, '2018-01-01' as s, '2018-01-07' as r from (select a from t18 group by a) ta ) m lateral view posexplode(split(space(datediff(from_unixtime(unix_timestamp(r,'yyyy-MM-dd')),from_unixtime(unix_timestamp(s,'yyyy-MM-dd')))), '')) t as pos, val ) t1 left join t18 on t1.a = t18.a and t1.b = t18.b ) ts;

十九、时间序列–取多个字段最新的值

表名t19
表字段及内容

date_id a b c 2014 AB 12 bc 2015 23 2016 d 2017 BC

问题一:如何一并取出最新日期

输出结果如下所示

date_a a date_b b date_c c 2017 BC 2015 23 2016 d

参考答案:
此处给出三种解法,其一:

SELECT max(CASE WHEN rn_a = 1 THEN date_id else 0 END) AS date_a ,max(CASE WHEN rn_a = 1 THEN a else null END) AS a ,max(CASE WHEN rn_b = 1 THEN date_id else 0 END) AS date_b ,max(CASE WHEN rn_b = 1 THEN b else NULL END) AS b ,max(CASE WHEN rn_c = 1 THEN date_id else 0 END) AS date_c ,max(CASE WHEN rn_c = 1 THEN c else null END) AS c FROM ( SELECT date_id ,a ,b ,c --对每列上不为null的值 的 日期 进行排序 ,row_number()OVER( PARTITION BY 1 ORDER BY CASE WHEN a IS NULL THEN 0 ELSE date_id END DESC) AS rn_a ,row_number()OVER(PARTITION BY 1 ORDER BY CASE WHEN b IS NULL THEN 0 ELSE date_id END DESC) AS rn_b ,row_number()OVER(PARTITION BY 1 ORDER BY CASE WHEN c IS NULL THEN 0 ELSE date_id END DESC) AS rn_c FROM t19 ) t WHERE t.rn_a = 1 OR t.rn_b = 1 OR t.rn_c = 1;

其二:

SELECT a.date_id ,a.a ,b.date_id ,b.b ,c.date_id ,c.c FROM ( SELECT t.date_id, t.a FROM ( SELECT t.date_id ,t.a ,t.b ,t.c FROM t19 t INNER JOIN t19 t1 ON t.date_id = t1.date_id AND t.a IS NOT NULL ) t ORDER BY t.date_id DESC LIMIT 1 ) a LEFT JOIN ( SELECT t.date_id ,t.b FROM ( SELECT t.date_id ,t.b FROM t19 t INNER JOIN t19 t1 ON t.date_id = t1.date_id AND t.b IS NOT NULL ) t ORDER BY t.date_id DESC LIMIT 1 ) b ON 1 = 1 LEFT JOIN ( SELECT t.date_id ,t.c FROM ( SELECT t.date_id ,t.c FROM t19 t INNER JOIN t19 t1 ON t.date_id = t1.date_id AND t.c IS NOT NULL ) t ORDER BY t.date_id DESC LIMIT 1 ) c ON 1 = 1;

其三:

`select

from
(
select t1.date_id as date_a,t1.a from (select t1.date_id,t1.a from t19 t1 where t1.a is not null) t1
inner join (select max(t1.date_id) as date_id from t19 t1 where t1.a is not null) t2
on t1.date_id=t2.date_id
) t1
cross join
(
select t1.date_b,t1.b from (select t1.date_id as date_b,t1.b from t19 t1 where t1.b is not null) t1
inner join (select max(t1.date_id) as date_id from t19 t1 where t1.b is not null)t2
on t1.date_b=t2.date_id
) t2
cross join
(
select t1.date_c,t1.c from (select t1.date_id as date_c,t1.c from t19 t1 where t1.c is not null) t1
inner join (select max(t1.date_id) as date_id from t19 t1 where t1.c is not null)t2
on t1.date_c=t2.date_id
) t3;
`

二十、时间序列–补全数据

表名t20
表字段及内容

date_id a b c 2014 AB 12 bc 2015 23 2016 d 2017 BC

问题一:如何使用最新数据补全表格

输出结果如下所示

date_id a b c 2014 AB 12 bc 2015 AB 23 bc 2016 AB 23 d 2017 BC 23 d

参考答案:

select date_id, first_value(a) over(partition by aa order by date_id) as a, first_value(b) over(partition by bb order by date_id) as b, first_value(c) over(partition by cc order by date_id) as c from ( select date_id, a, b, c, count(a) over(order by date_id) as aa, count(b) over(order by date_id) as bb, count(c) over(order by date_id) as cc from t20 )tmp1;

二十一、时间序列–取最新完成状态的前一个状态

表名t21
表字段及内容

date_id a b 2014 1 A 2015 1 B 2016 1 A 2017 1 B 2013 2 A 2014 2 B 2015 2 A 2014 3 A 2015 3 A 2016 3 B 2017 3 A

上表中B为完成状态

问题一:取最新完成状态的前一个状态

输出结果如下所示

date_id a b 2016 1 A 2013 2 A 2015 3 A

参考答案:
此处给出两种解法,其一:

select t21.date_id, t21.a, t21.b from ( select max(date_id) date_id, a from t21 where b = 'B' group by a ) t1 inner join t21 on t1.date_id -1 = t21.date_id and t1.a = t21.a;

其二:

select next_date_id as date_id ,a ,next_b as b from( select *,min(nk) over(partition by a,b) as minb from( select *,row_number() over(partition by a order by date_id desc) nk ,lead(date_id) over(partition by a order by date_id desc) next_date_id ,lead(b) over(partition by a order by date_id desc) next_b from( select * from t21 ) t ) t ) t where minb = nk and b = 'B';

问题二:如何将完成状态的过程合并

输出结果如下所示:

a b_merge 1 A、B、A、B 2 A、B 3 A、A、B

参考答案

select a ,collect_list(b) as b from( select * ,min(if(b = 'B',nk,null)) over(partition by a) as minb from( select *,row_number() over(partition by a order by date_id desc) nk from( select * from t21 ) t ) t ) t where nk >= minb group by a;

二十二、非等值连接–范围匹配

表f是事实表,表d是匹配表,在hive中如何将匹配表中的值关联到事实表中?

表d相当于拉链过的变化维,但日期范围可能是不全的。

表f

date_id p_id 2017 C 2018 B 2019 A 2013 C

表d

d_start d_end p_id p_value 2016 2018 A 1 2016 2018 B 2 2008 2009 C 4 2010 2015 C 3

问题一:范围匹配

输出结果如下所示

date_id p_id p_value 2017 C null 2018 B 2 2019 A null 2013 C 3

**参考答案:
此处给出两种解法,其一:

select f.date_id, f.p_id, A.p_value from f left join ( select date_id, p_id, p_value from ( select f.date_id, f.p_id, d.p_value from f left join d on f.p_id = d.p_id where f.date_id >= d.d_start and f.date_id <= d.d_end )A )A ON f.date_id = A.date_id;

其二:

select date_id, p_id, flag as p_value from ( select f.date_id, f.p_id, d.d_start, d.d_end, d.p_value, if(f.date_id between d.d_start and d.d_end,d.p_value,null) flag, max(d.d_end) over(partition by date_id) max_end from f left join d on f.p_id = d.p_id ) tmp where d_end = max_end;

二十三、非等值连接–最近匹配

表t23_1和表t23_2通过a和b关联时,有相等的取相等的值匹配,不相等时每一个a的值在b中找差值最小的来匹配。

t23_1和t23_2为两个班的成绩单,t23_1班的每个学生成绩在t23_2班中找出成绩最接近的成绩。

表t23_1:a中无重复值

a 1 2 4 5 8 10

表t23_2:b中无重复值

b 2 3 7 11 13

问题一:单向最近匹配

输出结果如下所示
注意:b的值可能会被丢弃

a b 1 2 2 2 4 3 5 3 5 7 8 7 10 11

参考答案

`select

from
(
select
ttt1.a,
ttt1.b
from
(
select
tt1.a,
t23_2.b,
dense_rank() over(partition by tt1.a order by abs(tt1.a-t23_2.b)) as dr
from
(
select
t23_1.a
from t23_1
left join t23_2 on t23_1.a=t23_2.b
where t23_2.b is null
) tt1
cross join t23_2
) ttt1
where ttt1.dr=1
union all
select
t23_1.a,
t23_2.b
from t23_1
inner join t23_2 on t23_1.a=t23_2.b
) result_t
order by result_t.a;
`

二十四、N指标–累计去重

假设表A为事件流水表,客户当天有一条记录则视为当天活跃。

表A

time_id user_id 2018-01-01 10:00:00 001 2018-01-01 11:03:00 002 2018-01-01 13:18:00 001 2018-01-02 08:34:00 004 2018-01-02 10:08:00 002 2018-01-02 10:40:00 003 2018-01-02 14:21:00 002 2018-01-02 15:39:00 004 2018-01-03 08:34:00 005 2018-01-03 10:08:00 003 2018-01-03 10:40:00 001 2018-01-03 14:21:00 005

假设客户活跃非常,一天产生的事件记录平均达千条。

问题一:累计去重

输出结果如下所示

日期 当日活跃人数 月累计活跃人数_截至当日 date_id user_cnt_act user_cnt_act_month 2018-01-01 2 2 2018-01-02 3 4 2018-01-03 3 5

参考答案

SELECT tt1.date_id ,tt2.user_cnt_act ,tt1.user_cnt_act_month FROM ( -- ④ 按照t.date_id分组求出user_cnt_act_month,得到tt1 SELECT t.date_id ,COUNT(user_id) AS user_cnt_act_month FROM ( -- ③ 表a和表b进行笛卡尔积,按照a.date_id,b.user_id分组,保证截止到当日的用户唯一,得出表t。 SELECT a.date_id ,b.user_id FROM ( -- ① 按照日期分组,取出date_id字段当主表的维度字段 得出表a SELECT from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') AS date_id FROM test.temp_tanhaidi_20211213_1 GROUP BY from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') ) a INNER JOIN ( -- ② 按照date_id、user_id分组,保证每天每个用户只有一条记录,得出表b SELECT from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') AS date_id ,user_id FROM test.temp_tanhaidi_20211213_1 GROUP BY from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') ,user_id ) b ON 1 = 1 WHERE a.date_id >= b.date_id GROUP BY a.date_id ,b.user_id ) t GROUP BY t.date_id ) tt1 LEFT JOIN ( -- ⑥ 按照date_id分组求出user_cnt_act,得到tt2 SELECT date_id ,COUNT(user_id) AS user_cnt_act FROM ( -- ⑤ 按照日期分组,取出date_id字段当主表的维度字段 得出表a SELECT from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') AS date_id ,user_id FROM test.temp_tanhaidi_20211213_1 GROUP BY from_unixtime(unix_timestamp(time_id),'yyyy-MM-dd') ,user_id ) a GROUP BY date_id ) tt2 ON tt2.date_id = tt1.date_id

GraalVM: run programs faster anywhere

Yudi Zheng 郑雨迪

Graal Compiler Team, Oracle Labs

为什么快?

支持哪些程序

跑在哪里?

compiler optimization , performace tuning, X64 backend

ahead-of-time
experimental java-based JIT Compiler

https://www.graalvm.org/docs/getting-started-with-graalvm/

https://medium.com/graalvm/simplifying-native-image-generation-with-maven-plugin-and-embeddable-configuration-d5b283b92f57

01.正则表达式

To use regular expressions effectively in Java, you need to know the syntax. The syntax is extensive, enabling you to write very advanced regular expressions. It may take a lot of exercise to fully master the syntax.

In this text I will go through the basics of the syntax with examples. I will not cover every little detail of the syntax, but focus on the main concepts you need to understand, in order to work with regular expressions. For a full explanation, see the Pattern class JavaDoc page.

Before showing all the advanced options you can use in Java regular expressions, I will give you a quick run-down of the Java regular expression syntax basics.

The most basic form of regular expressions is an expression that simply matches certain characters. Here is an example:

1

This simple regular expression will match occurences of the text “John” in a given input text.

You can use any characters in the alphabet in a regular expression.

You can also refer to characters via their octal, hexadecimal or unicode codes. Here are two examples:

1

These three expressions all refer to the uppercase A character. The first uses the octal code (101) for A, the second uses the hexadecimal code (41) and the third uses the unicode code (0041).

Character classes are constructst that enable you to specify a match against multiple characters instead of just one. In other words, a character class matches a single character in the input text against multiple allowed characters in the character class. For instance, you can match either of the characters a, b or c like this:

1

Character classes are nested inside a pair of square brackets []. The brackets themselves are not part of what is being matched.

You can use character classes for many things. For instance, this example finds all occurrences of the word John, with either a lowercase or uppercase J:

1

The character class [Jj] will match either a J or a j, and the rest of the expression will match the characters ohn in that exact sequence.

There are several other character classes you can use. See the character class table later in this text.

The Java regular expression syntax has a few predefined character classes you can use. For instance, the \d character class matches any digit, the \s character class matches any white space character, and the \w character matches any word character.

The predefined character classes do not have to be enclosed in square brackets, but you can if you want to combine them. Here are a few examples:

1

The first example matches any digit character. The second example matches any digit or any white space character.

The predefined character classes are listed in a table later in this text.

The syntax also include matchers for matching boundaries, like boundaries between words, the beginning and end of the input text etc. For instance, the \w matches boundaries between words, the ^ matches the beginning of a line, and the $ matches the end of a line.

Here is a boundary matcher example:

1

This expression matches a line of text with only the text This is a single line. Notice the start-of-line and end-of-line matchers in the expression. These state that there can be nothing before or after the text, except the beginning and end of a line.

There is a full list of boundary matchers later in this text.

Quantifiers enables you to match a given expression or subexpression multiple times. For instance, the following expression matches the letter A zero or more times:

1

The * character is a quantifier that means “zero or more times”. There is also a + quantifier meaning “one or more times”, a ? quantifier meaning “zero or one time”, and a few others which you can see in the quantifier table later in this text.

Quantifiers can be either “reluctant”, “greedy” or “possesive”. A reluctant quantifier will match as little as possible of the input text. A greedy quantifier will match as much as possible of the input text. A possesive quantifier will match as much as possible, even if it makes the rest of the expression not match anything, and the expression to fail finding a match.

I will illustrate the difference between reluctant, greedy and possesive quantifiers with an example. Here is an input text:

1

Then look at the following expression with a reluctant quantifier:

1

This expression will match the word John followed by zero or more characters The . means “any character”, and the * means “zero or more times”. The ? after the * makes the * a reluctant quantifier.

Being a reluctant quantifier, the quantifier will match as little as possible, meaning zero characters. The expression will thus find the word John with zero characters after, 3 times in the above input text.

If we change the quantifier to a greedy quantifier, the expression will look like this:

1

The greedy quantifier will match as many characters as possible. Now the expression will only match the first occurrence of John, and the greedy quantifier will match the rest of the characters in the input text. Thus, only a single match is found.

Finally, lets us change the expression a bit to contain a possesive quantifier:

1

The + after the * makes it a possesive quantifier.

This expression will not match the input text given above, even if both the words John and hurt are found in the input text. Why is that? Because the .*+ is possesive. Instead of matching as much as possible to make the expression match, as a greedy quantifier would have done, the possesive quantifier matches as much as possible, regardless of whether the expression will match or not.

The .*+ will match all characters after the first occurrence of John in the input text, including the word hurt. Thus, there is no hurt word left to match, when the possesive quantifier has claimed its match.

If you change the quantifier to a greedy quantifier, the expression will match the input text one time. Here is how the expression looks with a greedy quantifier:

1

You will have to play around with the different quantifiers and types to understand how they work. See the table later in this text for a full list of quantifiers.

The Java regular expression syntax also has support for a few logical operators (and, or, not).

The and operator is implicit. When you write the expression John, then it means “J and o and h and n“.

The or operator is explicit, and is written with a |. For instance, the expression John|hurt will match either the word John, or the word hurt.

Construct Matches
x The character x. Any character in the alphabet can be used in place of x.
\\ The backslash character. A single backslash is used as escape character in conjunction with other characters to signal special matching, so to match just the backslash character itself, you need to escape with a backslash character. Hence the double backslash to match a single backslash character.
\0n The character with octal value 0n. n has to be between 0 and 7.
\0nn The character with octal value 0nn. n has to be between 0 and 7.
\0mnn The character with octal value 0mnn. m has to be between 0 and 3, n has to be between 0 and 7.
\xhh The character with the hexadecimal value 0xhh.
\uhhhh The character with the hexadecimal value 0xhhhh. This construct is used to match unicode characters.
\t The tab character.
\n The newline (line feed) character (unicode: '\u000A').
\r The carriage-return character (unicode: '\u000D').
\f The form-feed character (unicode: '\u000C').
\a The alert (bell) character (unicode: '\u0007').
\e The escape character (unicode: '\u001B').
\cx The control character corresponding to x
``
Construct Matches
[abc] Matches a, or b or c. This is called a simple class, and it matches any of the characters in the class.
[^abc] Matches any character except a, b, and c. This is a negation.
[a-zA-Z] Matches any character from a to z, or A to Z, including a, A, z and Z. This called a range.
[a-d[m-p]] Matches any character from a to d, or from m to p. This is called a union.
[a-z&&[def]] Matches d, e, or f. This is called an intersection (here between the range a-z and the characters def).
[a-z&&[^bc]] Matches all characters from a to z except b and c. This is called a subtraction.
[a-z&&[^m-p]] Matches all characters from a to z except the characters from m to p. This is also called a subtraction.
Construct Matches
. Matches any single character. May or may not match line terminators, depending on what flags were used to compile the Pattern.
\d Matches any digit [0-9]
\D Matches any non-digit character [^0-9]
\s Matches any white space character (space, tab, line break, carriage return)
\S Matches any non-white space character.
\w Matches any word character.
\W Matches any non-word character.
Construct Matches
^ Matches the beginning of a line.
$ Matches then end of a line.
\b Matches a word boundary.
\B Matches a non-word boundary.
\A Matches the beginning of the input text.
\G Matches the end of the previous match
\Z Matches the end of the input text except the final terminator if any.
\z Matches the end of the input text.
Greedy Reluctant Possessive Matches
X? X?? X?+ Matches X once, or not at all (0 or 1 time).
X* X*? X*+ Matches X zero or more times.
X+ X+? X++ Matches X one or more times.
X{n} X{n}? X{n}+ Matches X exactly n times.
X{n,} X{n,}? X{n,}+ Matches X at least n times.
X{n, m) X{n, m)? X{n, m)+ Matches X at least n time, but at most m times.
Construct Matches
XY Matches X and Y (X followed by Y).
`X Y`

Linux-shell

curl

curl是一个很棒的命令.
例如目标网站Url:

127.0.0.1:8080/check_your_status?user=Summer&passwd=12345678

通过Get方法请求:

curl protocol://address:port/url?args
curl http://127.0.0.1:8080/check_your_status?user=Summer&passwd=12345678

发送数组参数

curl http://127.0.0.1:8080/check_yours_status?user[]=avery&user[]=zhangsan

发送包含特殊字符的参数

curl -G --data-urlencode 'user[]=avery@tencent#td' --data-urlencode 'user[]=zhangsan@tencent#bf' -d 'tableId=913' http://127.0.0.1:8080/check_yours_status

“-G”等价于”—get”,”-d”等价于”—data”

从文件中获取参数

curl --get --data @data.txt http://aiezu.com/test.php

通过Post方法请求:

curl -d “args” “protocol://address:port/url”
curl -d “user=Summer&passwd=12345678” “http://127.0.0.1:8080/check_your_status“

自定义header

这种方法是参数直接在header里面的
如需将输出指定到文件可以通过重定向进行操作.

curl -H "Content-Type:application/json" -X POST --data (json.data) URL
curl -H "Content-Type:application/json" -X POST --data '{"message": "sunshine"}' http://localhost:8000/

这种方法是json数据直接在body里面的

输出到文件

1
curl -o tank_pos_meta.json 'http://9.22.24.233:8080/ec/v1/listTable?pageNum=1&pageSize=999&type=hippo&dbName=bank-pos-info&name=t_bank_pos_yyyymmdd'

URL必须带分号

date

两天前

1

sed

MacOS sed替换为gnu-sed

1
2
3
brew install gnu-sed

alias sed=gsed

MySQL

因为苹果在OS X 10.11中引入的SIP特性使得即使加了sudo(也就是具有root权限)也无法修改系统级的目录,其中就包括了/usr/bin。要解决这个问题有两种做法:

一种是比较不安全的就是关闭SIP,也就是rootless特性;

另一种是将本要链接到/usr/bin下的改链接到/usr/local/bin下就好了。

解决办法

sudo ln -s /usr/local/mysql/bin/mysql /usr/local/bin

mysql 启动

1
mysqld --user mysql