月度归档:2019年12月

Zookeeper集群的”脑裂”问题处理 – 运维总结

关于集群中的"脑裂"问题,之前已经在这里详细介绍过,下面重点说下Zookeeper脑裂问题的处理办法。ooKeeper是用来协调(同步)分布式进程的服务,提供了一个简单高性能的协调内核,用户可以在此之上构建更多复杂的分布式协调功能。脑裂通常会出现在集群环境中,比如ElasticSearch、Zookeeper集群,而这些集群环境有一个统一的特点,就是它们有一个大脑,比如ElasticSearch集群中有Master节点,Zookeeper集群中有Leader节点。

一、 Zookeeper 集群节点为什么要部署成奇数
zookeeper容错指的是:当宕掉几个zookeeper节点服务器之后,剩下的个数必须大于宕掉的个数,也就是剩下的节点服务数必须大于n/2,这样zookeeper集群才可以继续使用,无论奇偶数都可以选举leader。例如5台zookeeper节点机器最多宕掉2台,还可以继续使用,因为剩下3台大于5/2。那么为什么最好为奇数个节点呢?是在以最大容错服务器个数的条件下,会节省资源。比如,最大容错为2的情况下,对应的zookeeper服务数,奇数为5,而偶数为6,也就是6个zookeeper服务的情况下最多能宕掉2个服务,所以从节约资源的角度看,没必要部署6(偶数)个zookeeper服务节点。

zookeeper集群有这样一个特性:集群中只要有过半的机器是正常工作的,那么整个集群对外就是可用的。也就是说如果有2个zookeeper节点,那么只要有1个zookeeper节点死了,那么zookeeper服务就不能用了,因为1没有过半,所以2个zookeeper的死亡容忍度为0;同理,要是有3个zookeeper,一个死了,还剩下2个正常的,过半了,所以3个zookeeper的容忍度为1;同理也可以多列举几个:2->0; 3->1; 4->1; 5->2; 6->2 就会发现一个规律,2n和2n-1的容忍度是一样的,都是n-1,所以为了更加高效,何必增加那一个不必要的zookeeper呢。

根据以上可以得出结论:从资源节省的角度来考虑,zookeeper集群的节点最好要部署成奇数个!!

二、 Zookeeper 集群中的"脑裂"场景说明
对于一个集群,想要提高这个集群的可用性,通常会采用多机房部署,比如现在有一个由6台zkServer所组成的一个集群,部署在了两个机房:

正常情况下,此集群只会有一个Leader,那么如果机房之间的网络断了之后,两个机房内的zkServer还是可以相互通信的,如果不考虑过半机制,那么就会出现每个机房内部都将选出一个Leader。

这就相当于原本一个集群,被分成了两个集群,出现了两个"大脑",这就是所谓的"脑裂"现象。对于这种情况,其实也可以看出来,原本应该是统一的一个集群对外提供服务的,现在变成了两个集群同时对外提供服务,如果过了一会,断了的网络突然联通了,那么此时就会出现问题了,两个集群刚刚都对外提供服务了,数据该怎么合并,数据冲突怎么解决等等问题。刚刚在说明脑裂场景时有一个前提条件就是没有考虑过半机制,所以实际上Zookeeper集群中是不会轻易出现脑裂问题的,原因在于过半机制。

zookeeper的过半机制:在领导者选举的过程中,如果某台zkServer获得了超过半数的选票,则此zkServer就可以成为Leader了。

举个简单的例子:如果现在集群中有5台zkServer,那么half=5/2=2,那么也就是说,领导者选举的过程中至少要有三台zkServer投了同一个zkServer,才会符合过半机制,才能选出来一个Leader。

有个疑问:zookeeper选举的过程中为什么一定要有一个过半机制验证?
因为这样不需要等待所有zkServer都投了同一个zkServer就可以选举出来一个Leader了,这样比较快,所以叫快速领导者选举算法。

再来想一个问题,过半机制中为什么是大于,而不是大于等于呢?这就是更脑裂问题有关系了,比如回到上文出现脑裂问题的场景[如上图1]:

当机房中间的网络断掉之后,机房1内的三台服务器会进行领导者选举,但是此时过半机制的条件是 "节点数 > 3",也就是说至少要4台zkServer才能选出来一个Leader,所以对于机房1来说它不能选出一个Leader,同样机房2也不能选出一个Leader,这种情况下整个集群当机房间的网络断掉后,整个集群将没有Leader。

而如果过半机制的条件是 "节点数 >= 3",那么机房1和机房2都会选出一个Leader,这样就出现了脑裂。这就可以解释为什么过半机制中是大于而不是大于等于,目的就是为了防止脑裂。

如果假设我们现在只有5台机器,也部署在两个机房:

此时过半机制的条件是 "节点数 > 2",也就是至少要3台服务器才能选出一个Leader,此时机房件的网络断开了,对于机房1来说是没有影响的,Leader依然还是Leader,对于机房2来说是选不出来Leader的,此时整个集群中只有一个Leader。所以可以简单总结得出,有了过半机制,对于一个Zookeeper集群来说,要么没有Leader,要么只有1个Leader,这样zookeeper也就能避免了脑裂问题。

三、 Zookeeper 集群"脑裂"问题处理
1.  什么是脑裂?
简单点来说,脑裂(Split-Brain) 就是比如当你的 cluster 里面有两个节点,它们都知道在这个 cluster 里需要选举出一个 master。那么当它们两个之间的通信完全没有问题的时候,就会达成共识,选出其中一个作为 master。但是如果它们之间的通信出了问题,那么两个结点都会觉得现在没有 master,所以每个都把自己选举成 master,于是 cluster 里面就会有两个 master。

对于Zookeeper来说有一个很重要的问题,就是到底是根据一个什么样的情况来判断一个节点死亡down掉了。 在分布式系统中这些都是有监控者来判断的,但是监控者也很难判定其他的节点的状态,唯一一个可靠的途径就是心跳,Zookeeper也是使用心跳来判断客户端是否仍然活着。

使用ZooKeeper来做Leader HA基本都是同样的方式:每个节点都尝试注册一个象征leader的临时节点,其他没有注册成功的则成为follower,并且通过watch机制 (这里有介绍) 监控着leader所创建的临时节点,Zookeeper通过内部心跳机制来确定leader的状态,一旦leader出现意外Zookeeper能很快获悉并且通知其他的follower,其他flower在之后作出相关反应,这样就完成了一个切换,这种模式也是比较通用的模式,基本大部分都是这样实现的。但是这里面有个很严重的问题,如果注意不到会导致短暂的时间内系统出现脑裂,因为心跳出现超时可能是leader挂了,但是也可能是zookeeper节点之间网络出现了问题,导致leader假死的情况,leader其实并未死掉,但是与ZooKeeper之间的网络出现问题导致Zookeeper认为其挂掉了然后通知其他节点进行切换,这样follower中就有一个成为了leader,但是原本的leader并未死掉,这时候client也获得leader切换的消息,但是仍然会有一些延时,zookeeper需要通讯需要一个一个通知,这时候整个系统就很混乱可能有一部分client已经通知到了连接到新的leader上去了,有的client仍然连接在老的leader上,如果同时有两个client需要对leader的同一个数据更新,并且刚好这两个client此刻分别连接在新老的leader上,就会出现很严重问题。

这里做下小总结:
假死:由于心跳超时(网络原因导致的)认为leader死了,但其实leader还存活着。
脑裂:由于假死会发起新的leader选举,选举出一个新的leader,但旧的leader网络又通了,导致出现了两个leader ,有的客户端连接到老的leader,而有的客户端则连接到新的leader。

2.  zookeeper脑裂是什么原因导致的?
主要原因是Zookeeper集群和Zookeeper client判断超时并不能做到完全同步,也就是说可能一前一后,如果是集群先于client发现,那就会出现上面的情况。同时,在发现并切换后通知各个客户端也有先后快慢。一般出现这种情况的几率很小,需要leader节点与Zookeeper集群网络断开,但是与其他集群角色之间的网络没有问题,还要满足上面那些情况,但是一旦出现就会引起很严重的后果,数据不一致。

3.  zookeeper是如何解决"脑裂"问题的?
要解决Split-Brain脑裂的问题,一般有下面几种种方法:
- Quorums (法定人数) 方式: 比如3个节点的集群,Quorums = 2, 也就是说集群可以容忍1个节点失效,这时候还能选举出1个lead,集群还可用。比如4个节点的集群,它的Quorums = 3,Quorums要超过3,相当于集群的容忍度还是1,如果2个节点失效,那么整个集群还是无效的。这是zookeeper防止"脑裂"默认采用的方法。
- 采用Redundant communications (冗余通信)方式:集群中采用多种通信方式,防止一种通信方式失效导致集群中的节点无法通信。
- Fencing (共享资源) 方式:比如能看到共享资源就表示在集群中,能够获得共享资源的锁的就是Leader,看不到共享资源的,就不在集群中。
- 仲裁机制方式。
- 启动磁盘锁定方式。

要想避免zookeeper"脑裂"情况其实也很简单,在follower节点切换的时候不在检查到老的leader节点出现问题后马上切换,而是在休眠一段足够的时间,确保老的leader已经获知变更并且做了相关的shutdown清理工作了然后再注册成为master就能避免这类问题了,这个休眠时间一般定义为与zookeeper定义的超时时间就够了,但是这段时间内系统可能是不可用的,但是相对于数据不一致的后果来说还是值得的。

1) zooKeeper默认采用了Quorums这种方式来防止"脑裂"现象,即只有集群中超过半数节点投票才能选举出Leader。这样的方式可以确保leader的唯一性,要么选出唯一的一个leader,要么选举失败。在zookeeper中Quorums有3个作用:
1) 集群中最少的节点数用来选举leader保证集群可用。
2) 通知客户端数据已经安全保存前集群中最少数量的节点数已经保存了该数据。一旦这些节点保存了该数据,客户端将被通知已经安全保存了,可以继续其他任务。而集群中剩余的节点将会最终也保存了该数据。

假设某个leader假死,其余的followers选举出了一个新的leader。这时,旧的leader复活并且仍然认为自己是leader,这个时候它向其他followers发出写请求也是会被拒绝的。因为每当新leader产生时,会生成一个epoch标号(标识当前属于那个leader的统治时期),这个epoch是递增的,followers如果确认了新的leader存在,知道其epoch,就会拒绝epoch小于现任leader epoch的所有请求。那有没有follower不知道新的leader存在呢,有可能,但肯定不是大多数,否则新leader无法产生。Zookeeper的写也遵循quorum机制,因此,得不到大多数支持的写是无效的,旧leader即使各种认为自己是leader,依然没有什么作用。

zookeeper除了可以采用上面默认的Quorums方式来避免出现"脑裂",还可以可采用下面的预防措施:
2) 添加冗余的心跳线,例如双线条线,尽量减少“裂脑”发生机会。
3) 启用磁盘锁。正在服务一方锁住共享磁盘,"裂脑"发生时,让对方完全"抢不走"共享磁盘资源。但使用锁磁盘也会有一个不小的问题,如果占用共享盘的一方不主动"解锁",另一方就永远得不到共享磁盘。现实中假如服务节点突然死机或崩溃,就不可能执行解锁命令。后备节点也就接管不了共享资源和应用服务。于是有人在HA中设计了"智能"锁。即正在服务的一方只在发现心跳线全部断开(察觉不到对端)时才启用磁盘锁。平时就不上锁了。
4) 设置仲裁机制。例如设置参考IP(如网关IP),当心跳线完全断开时,2个节点都各自ping一下 参考IP,不通则表明断点就出在本端,不仅"心跳"、还兼对外"服务"的本端网络链路断了,即使启动(或继续)应用服务也没有用了,那就主动放弃竞争,让能够ping通参考IP的一端去起服务。更保险一些,ping不通参考IP的一方干脆就自我重启,以彻底释放有可能还占用着的那些共享资源。

来源: https://www.cnblogs.com/kevingrace/p/11801284.html

Spring事务失效事件

近期遇到一个spring注解事务失效的问题,先上代码(以下代码经过简化,只保留关键点)。

        没耐心的朋友可以直接看最下面结论。

第一部分:具体问题

接下来上一下代码,大致介绍一下流程。

Controller

Controller里面使用@Autowired注入了一个serviceImpl,然后调用这个serviceImpl的业务方法。

ServiceImpl的属性

自动装配了另一个service和一个dao

重点看serviceImpl的方法(经过简化,只保留了关键点)

红框是两个insert数据库操作,需要事务管理

最下面的抛出异常是为了测试事务。

其中红框里面的super.addNew方法上有@Transactional(rollbackFor = Exception.class)注解。所以看上去并没有什么问题,但是每次抛出异常,数据库的操作都没有回滚。

问题大致就是这样,本来以为事情会很容易被解决,因为在网上看过很多事务失效的例子,各种原因被各种大神列举了很多次了。

第二部分:常规问题解决


普通事务失效原因:

1.如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB

2. 如果使用了spring+mvc,则context:component-scan重复扫描问题可能会引起事务失败。 (即父子容器)

3. @Transactional 注解开启配置,必须放到listener里加载,如果放到DispatcherServlet的配置里,事务也是不起作用的。

4. @Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错,事务也会失效。

5. Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

以上五点摘抄自:Spring事务失效的原因


看到常规的原因,首先的解决思路如下

1.首先,数据库使用的是oracle,所以排除1

2.项目确实使用的是spring+mvc不过已经分开扫描了,mvc只扫描Controller和RestController,spring扫描排除这两个注解这样可以排除2,

3.事务管理器配置确实是放在spring容器里,用Listener加载的

4.方法是public

5.是在类的方法上使用的,非接口上

所以,常规的问题被我巧妙地全避开了。

第三部分:解决思路

这个事务失效问题是第一次出现,并没有广泛存在我们的系统中。于是和其他的事务方法对比,发现其他的事务方法都是通过Service.getInstance()方法获取到的实例,跟进去,发现getInstance方法是通过我们项目定义的工具类从xml里面getBean获取到的。看上去貌似没什么问题,getBean和使用@Autowired不都一样嘛,都能获取到bean。

打开日志debug级别,看能不能查到点什么细节。并且给方法入口和spring的事务管理器入口org.springframework.transaction.interceptor.TransactionInterceptor#invoke打上断点,然后运行到该事务方法。

首先在方法入口的断点处用evaluate expression执行AopUtils.isAopProxy这个方法来看一下注入的serviceImpl是否被代理了,结果是true。

继续执行,进入到spring事务管理器里面,然后一步步跟代码,获取TransactionInfo等,并没有发现什么可疑点,因为都已经进入到了spring事务管理器了,而spring几乎不会出bug,进入到方法第一行后,查看了下debug日志,发现有Creating new transaction with name [XXX]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',-java.lang.Exception,继续执行,执行到super.addNew方法时,debug日志又出现了一行 Creating new transaction with name [XXX]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; '',-java.lang.Exception,而且此XXX非刚才的XXX,意思是在第一个事务里面,又起了一个事务,但是我事务没有显示配置PROPAGATION,默认的是REQUIRED,为什么会又重新起一个事务呢,这里让我百思不得其解。于是我回过头又检查了一遍父子容器问题,是不是还是因为包扫描重复导致的呢,虽然如果是子容器(即MVC)的话,肯定不会进入到事务管理器,但我还是抱着侥幸心理检查了一遍,没有任何问题。

没找到问题所在,继续执行下去,发现跑出的异常被事务管理局接管了,debug日志也显示了transaction rollback。这就更奇怪了,既然事务都回滚了,数据是怎么插到数据库里的呢。我继续在插数据的那一行sql打上断点,重复执行,发现执行完这条sql的时候,数据就已经在数据库了,但是事务还没提交。。这又是怎么回事。这里我想到了会不会是数据库连接设置的autoCommit,于是重复执行,在spring的transaction源码里面打断点,发现事务管理器会自动把autoCommit关掉。

感觉这条追查的路已经堵死了。。反而产生了两个其他的问题,1.既然是PROPAGATION_REQUIRED,为什么会在事务里又起一个事务,2.既然事务都还没提交,你是怎么插入到数据库里的。。

想到最一开始观察到的,和其他事务起作用的方法差别在于,他们的成员变量是getInstance获得的,而这个有问题的是@Autowired注入的,那么是不是这里有问题呢,于是我把Autowired的全换成了getInstance获取在xml里面注册的bean,果然,事务起作用了。可是,这tm到底咋回事。。。Autowired和getBean不是一样么,为什么会出现两种截然不同的结果。刚好最近刚看完SpringIoc的源码,就决定追查到底。

第四部分:真相大白

理理思路,父容器先加载,子容器后加载,子容器在加载过程中会通过获取到父容器。

请求到达应用后,Controller会先被初始化,然后他的私有属性serviceImpl会被 inject,实质上是会被父容器inject,然后循环注入serviceImpl私有属性的私有属性。。一直循环直到所有的bean都被实例化。事务管理器也在父容器中定义,貌似没有问题,事实也证明了,事务管理器确实代理了目标方法。

然后深入看了下getInstance方法,这个方法是通过项目的一个工具类的getBean方法工作的,工具类的getBean方法实际上是new了一个ClassPathXmlApplicationContext,然后调用他的getBean方法,那么看到这里好像一切都明朗了。。

@Autowired自动装配的是web.xml里面定义的Listener加载的父容器里的bean,getInstance是自己定义的第二个容器获取到的bean,那么事务管理器也是一样,实际上有两个事物管理器,第一个是webxml加载父容器的,第二个是工具类加载的容器的。所以才会出现在一个事务中又起一个事务,因为容器2不知道方法已经在容器1起了一个事务,我所看到的事务没提交也是看到的容器1起的事务没提交。

如果我再仔细缕一遍全流程就会发现,dao里面还封装了一层mybatis的sqlsession,这里使用的是工具类的getBean方法。

如果再仔细看一下debug日志的话,就会发现启动应用的时候,每个xml都被加载了两次,一次是webxml的父容器,一次是工具类new加载的。

终章:结论

遇到事务失效的时候,先看一下是否被事务管理器接管了,如果有,那么多半是容器的问题,这里并不一定是父子容器,也有可能是父父容器。。

借用《黑客与画家》的一句话:如果自己就是潮水的一部分,又怎么能看见潮流的方向呢。

当遇到问题时,不要迷在某个局部,要跳出来,先把整个流程理清。

最后再说一句,开启了上帝视角之后,感觉这问题实在是很简单,不过解决过程中花费了我本人非常多时间(不少于十小时)。

希望这篇博客可以给有问题的朋友带来一点思路。

Guava平滑限流

1.常用限流方法
对于一个应用系统来说一定会有极限并发/请求数,即总有一个TPS/QPS阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此我们最好进行过载保护,防止大量请求涌入击垮系统。

常见的限流方法有:

容器限流

常见的web容器其实也具备限流的功能。

以Tomcat容器为例,其Connector 其中一种配置有如下几个参数:

acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;

maxConnections: 瞬时最大连接数,超出的会排队等待;

maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。

限流总资源数

如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用;可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出了可以等待或者抛异常。

限流某个接口的总并发/请求数

如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。可以使用Java中的AtomicLong进行限流:

try {
if(atomic.incrementAndGet() > 限流数) {
//拒绝请求
}
//处理请求
} finally {
atomic.decrementAndGet();
}

利用Netflix的hystrix限流

2.漏桶算法 VS 令牌桶算法
2.1 漏桶算法(Leaky Bucket)
漏桶算法(Leaky Bucket):水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率.示意图如下:

可见这里有两个变量,一个是桶的大小,支持流量突发增多时可以存多少的水(burst),另一个是水桶漏洞的大小(rate)。

2.2 令牌桶算法(Token Bucket)
令牌桶算法(Token Bucket)和 Leaky Bucket 效果一样但方向相反的算法,更加容易理解.随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.

令牌桶的另外一个好处是可以方便的改变速度. 一旦需要提高速率,则按需提高放入桶中的令牌的速率. 一般会定时(比如100毫秒)往桶中增加一定数量的令牌, 有些变种算法则实时的计算应该增加的令牌的数量.

2.3 漏桶算法VS令牌桶算法
令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;

漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;

令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;

两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

3.Guava实现平滑限流
3.1Guava是什么
Guava是一个开源的Java库,其中包含谷歌很多项目使用的核心库。这个库是为了方便编码,并减少编码错误。这个库提供用于集合,缓存,支持原语,并发性,常见注解,字符串处理,I/O和验证的实用方法。

3.2Guava RateLimiter实现平滑限流
Guava RateLimiter提供了令牌桶算法实现:平滑突发限流(SmoothBursty)和平滑预热限流(SmoothWarmingUp)实现。

平滑突发限流:

/**
* 平滑突发限流(SmoothBursty)
*/
public class SmoothBurstyRateLimitTest {
public static void main(String[] args) {
//QPS = 5,每秒允许5个请求
RateLimiter limiter = RateLimiter.create(5);
//limiter.acquire() 返回获取token的耗时,以秒为单位
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
System.out.println(limiter.acquire());
}
}

0.0
0.19667
0.195784
0.194672
0.195658
0.195285
/**
* 平滑突发限流(smoothbursty)
*/
public class SmoothBurstyRateLimitTest02 {

public static void main(String[] args) {
//每秒允许5个请求,表示桶容量为5且每秒新增5个令牌,即每隔0.2毫秒新增一个令牌
RateLimiter limiter = RateLimiter.create(5);
//一次性消费5个令牌
System.out.println(limiter.acquire(5));
//limiter.acquire(1)将等待差不多1秒桶中才能有令牌
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
}
}
/**
* 平滑突发限流(smoothbursty)
*/
public class SmoothBurstyRateLimitTest03 {

public static void main(String[] args) {
//每秒允许5个请求,表示桶容量为5且每秒新增5个令牌,即每隔0.2毫秒新增一个令牌
RateLimiter limiter = RateLimiter.create(5);
//第一秒突发了10个请求
System.out.println(limiter.acquire(10));
//limiter.acquire(1)将等待差不多2秒桶中才能有令牌
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
//固定速率
System.out.println(limiter.acquire(1));
}
}
平滑预热限流:

/**
* 平滑预热限流(SmoothWarmingUp)
*/
public class SmoothWarmingUp {
public static void main(String[] args) {
//permitsPerSecond:每秒新增的令牌数 warmupPeriod:从冷启动速率过渡到平均速率的时间间隔
//系统冷启动后慢慢的趋于平均固定速率(即刚开始速率慢一些,然后慢慢趋于我们设置的固定速率)
RateLimiter limiter = RateLimiter.create(10, 1000, TimeUnit.MILLISECONDS);
for(int i = 0; i < 10;i++) {
//获取一个令牌
System.out.println(limiter.acquire(1));
}
}
}
秒杀场景:

/**
* 秒杀场景模拟
*/
public class MiaoShaTest {

public static void main(String[] args) throws InterruptedException {

//限流,每秒允许10个请求进入秒杀
RateLimiter limiter = RateLimiter.create(10);

for (int i = 0; i < 100; i++) {
//100个线程同时抢购
new Thread(() -> {
//每个秒杀请求如果100ms以内得到令牌,就算是秒杀成功,否则就返回秒杀失败
if (limiter.tryAcquire(50, TimeUnit.MILLISECONDS)) {
System.out.println("恭喜您,秒杀成功");
} else {
System.out.println("秒杀失败,请继续努力");
}
}).start();
//等待新的令牌生成
Thread.sleep(10);
}
}
}
测试demo地址:https://github.com/online-demo/yunxi-guava
————————————————
版权声明:本文为CSDN博主「ShiXueTanLang」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ShiXueTanLang/article/details/80960208

----------------

三种常见的限流算法

在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

1、计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:

具体的伪代码如下:

public class CounterTest {
    public long timeStamp = getNowTime();
    public int reqCount = 0;
    public final int limit = 100; // 时间窗口内最大请求数
    public final long interval = 1000; // 时间窗口ms

    public boolean grant() {
        long now = getNowTime();
        if (now < timeStamp + interval) {
            // 在时间窗口内
            reqCount++;
            // 判断当前时间窗口内是否超过最大请求控制数
            return reqCount <= limit;
        } else {
            timeStamp = now;
            // 超时后重置
            reqCount = 1;
            return true;
        }
    }

    public long getNowTime() {
        return System.currentTimeMillis();
    }
}

这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:

从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

聪明的朋友可能已经看出来了,刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。

滑动窗口
滑动窗口,又称rolling window。为了解决这个问题,我们引入了滑动窗口算法。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。下面这张图,很好地解释了滑动窗口算法:

在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口 划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格 子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触 发了限流。

我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

2、令牌桶算法
令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)、根据限流大小,设置按照一定的速率往桶里添加令牌;
3)、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;

3、漏桶算法
漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。

如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【关注我】。

如果,想给予我更多的鼓励,求打

因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Ruthless】!

联系或打赏博主【Ruthless】!https://www.cnblogs.com/ujq3/p/7221582.html

 

来源: https://www.cnblogs.com/linjiqin/p/9707713.html

spring security安全防护

前言

xss攻击(跨站脚本攻击):攻击者在页面里插入恶意脚本代码,用户浏览该页面时,脚本代码就会执行,达到攻击者的目的。原理就是:攻击者对含有漏洞的服务器注入恶意代码,引诱用户浏览受到攻击的服务器,并打开相关页面,执行恶意代码。
xss攻击方式:一、反射性攻击,脚本代码作为url参数提交给服务器,服务器解析执行后,将脚本代码返回给浏览器,最后浏览器解析执行攻击代码;二、存储性攻击,和发射性攻击的区别是,脚本代码存储在服务器,下次在请求时,不用再提交脚本代码。其中一个示例图如下所示:

CSRF攻击:跨站请求伪造攻击,CSRF是一种欺骗受害者提交恶意请求的攻击,并劫持受害者的身份和特权,并以受害者的身份访问未授权的信息和功能。序列图如下所示:

同步器Token

解决XSS攻击和CSRF攻击的一个推荐方法就是同步器Token,就是在post的请求中,增加一个token,每次请求到来,服务器都会验证请求中的token和服务器期望的值是否一致,如果不一致,服务器将请求视为非法的,整个过程的示例图如下所示:

在spring security中如果使用的是 @EnableWebMvcSecurity而不是@EnableWebSecurity,同步器Token是默认打开的,通过http().csrf().disable()可以关闭同步器token功能。spring security发现token无效后,会返回一个403的访问拒绝,不过可以通过配置AccessDeniedHandler类处理InvalidCsrfTokenException异常来定制行为。

spring security虽然默认是打开同步器token保护的,但是也提供了一个显示打开的行为即http().csrf(),同时需要在html的form表单中添加以“<input type="hidden"name="${_csrf.parameterName}" value="${_csrf.token}"/>”

如果请求的是json或ajax请求,如何使用同步器token防护那?
json请求的话,我们可以在header的元数据中添加token防护,代码如下所示:

<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
...
</head>

ajax请求的话,可是使用如下代码:

$(function () {
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
});

Synchronizer Token的常见问题
1.超时
因为token是存储在httpsession中的,所以token存在超时的问题,一旦超时,则配置的accessDeniedHandler将接受一个异常,或者spring security直接拒绝访问;
2.登录
为了防止伪造的登录请求,在登录的form中也需要添加token,而token又是存储在HttpSession中,也就是说一旦发现token属性,就会创建一个HttpSession,这和无状态架构模式可能会产生一些冲突;
3.注销
添加CSRF将更新LogoutFilter过滤器,使其只使用HTTP POST。这确保了注销需要一个CSRF令牌,并且恶意用户不能强制注销您的用户。也就是说,注销不再是一个get请求,而是一个post请求;

spring security支持的安全response header

spring security 支持的response header 包括:Cache-Control,Content-type options,HTTP Strict Transport Security,X-Frame-Options,X-XSS-Protection 如果你使用的是WebSecurityConfigurerAdapter 配置方式,则这些头都是默认支持的,你可以通过调用 http.header().disable()关闭这些缺省的头。当然你也可以单独配置,但是如果你单独配置的话,只有你配置的头会起作用,其他的头都不再默认支持了。下面分别来讨论一下这些响应header的含义;

Cache-Control
如果不加任何控制,浏览器通常会缓存你的认证相关信息,这在一定程度上会造成你的信息泄露,因此在默认情况下,spring security会在响应头里加入缓存控制如下所示:

Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0

当然你也可以通过 http.header().cacheControl()方法单独打开缓存控制,你也可以通过HttpServletResponse.setHeader(String,String)方法对特殊的响应做处理,从而确保css js 图片能够合适地被缓存,在spring mvc中,可以通过配置进行设置,下面的代码对所有的资源都增加了缓存:

@EnableWebMvc
public class WebMvcConfiguration
     extends WebMvcConfigurerAdapter {
        @Override
        public void addResourceHandlers(
        ResourceHandlerRegistry registry) {
        registry
        .addResourceHandler("/resources/**")
        .addResourceLocations("/resources/")
        .setCachePeriod(3_155_6926);
        }
// ...
}

Content-type Options

历史上很多很多浏览器都能可以通过对请求类型的content进行嗅探,猜测请求类型,从而提高用户体验,但是这会带来XSS攻击。例如有些站点允许用户向其提交有效的postScript脚本,并查看他。恶意用户可能提交一个有效的js文件,并使用它执行XSS攻击。spring security默认是禁止进行嗅探的。

HTTP Strict Transport Security
当你访问一个站点是,例如www.baidu.com,省略了https协议,潜在的你会受到中间人攻击,为了减少这种情况,就是告知浏览器,当你输入某个地址时,应该使用https协议。如何让浏览器该host是一个hsts站点那?其中一种方法就是,增加Strict-Transport-Security 到响应头中,如下例所示:

Strict-Transport-Security: max-age=31536000 ; includeSubDomains

上述例子告知浏览器,该站点在一年内访问都使用https协议,includeSubDomains说明该站点的子域名也使用https协议访问。

X-frame-optiont

X-Frame-Options HTTP 响应头是用来给浏览器指示允许一个页面可否在 <frame>, <iframe> 或者 <object> 中展现的标记。网站可以使用此功能,来确保自己网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。
X-Frame-Options 有三个值: DENY 表示该页面不允许在 frame 中展示,即便是在相同域名的页面中嵌套也不允许。SAMEORIGIN 表示该页面可以在相同域名页面的 frame 中展示。ALLOW-FROM uri表示该页面可以在指定来源的 frame 中展示。
Spring security默认是使用DENY,即拒绝在frame中展示,从而避免点击劫持的攻击,当然你也可以进行单独设置或对X-Frame-Option设置特殊的值。

Customer Header

Spring Security有一些机制,可以方便地向应用程序添加更常见的安全头。但是,它还提供了允许添加自定义头的钩子。有时,您希望将自定义安全头插入到您的应用程序中,而该特性并没有被支持。例如,您可能希望尽早支持内容安全策略,以确保资源只能从相同的源加载。由于对内容安全策略的支持还没有最终确定,浏览器使用两个常见的扩展头之一来实现这个特性。这意味着我们将需要注入两次策略。下面的代码片段中可以看到一个头的示例:

X-Content-Security-Policy: default-src 'self'
X-WebKit-CSP: default-src 'self'
@Override
protected void configure(HttpSecurity http) throws Exception {
        http.headers()
        .addHeaderWriter(
        new StaticHeadersWriter(
        "X-Content-Security-Policy",
        "default-src 'self'"))
        .addHeaderWriter(
        new StaticHeadersWriter(
        "X-WebKit-CSP",
        "default-src 'self'"));
}

当名称空间或Java配置不支持您想要的头部时,您可以创建一个自定义的HeadersWriter实例,甚至可以提供HeadersWriter的自定义实现。让我们看一个使用XFrameOptionsHeaderWriter的自定义实例的示例。也许您希望允许为相同的起源构建内容框架。将策略属性设置为SAMEORIGIN很容易支持这一点:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.headers()
    .addHeaderWriter(
    new XFrameOptionsHeaderWriter(
    XFrameOptionsMode.SAMEORIGIN));
}

有时,您可能只想为某些请求编写一个header。例如,您可能只想保护您的登录页面。您可以使用DelegatingRequestMatcherHeaderWriter类来这样做。

@Override
protected void configure(HttpSecurity http) throws Exception {
    DelegatingRequestMatcherHeaderWriter headerWriter =
    new DelegatingRequestMatcherHeaderWriter(
    new AntPathRequestMatcher("/login"),
    new XFrameOptionsHeaderWriter());
    http.headers()
    .addHeaderWriter(headerWriter);
}
阅读 1.4k发布于 2018-07-25
来源: https://segmentfault.com/a/1190000015767740

 

tomcat中server.xml中Connector各个参数的意义

1. port

    tomcat作为一个网络server端,它需要暴露一个socket端口来accept客户端的链接,可以通过port指定.

2. protocol

使用的网络协议,表示tomcat使用何种方式来接受和处理client端请求,"HTTP/1.1"是默认值,等效于"org.apache.coyote.http11.Http11Protocol";还有熟悉的"AJP/1.3";关于HTTP和AJP两种方式的区别和性能优劣可以参见其他文档。【文档】

在Tomcat 6.0之后,还提供了NIO的方式,可以有效的提升性能,特别是在大量长连接/数据上传+下载等web应用中.此时portocal="org.apache.coyote.http11.Http11NioProtocol".

tomcat目前支持:BIO、NIO、NIO2、APR四种IO模型,默认为BIO。对于互联网应用,我们应该在NIO、NIO2之间做选择,因为它能够有效的提升性能(主要是并发能力),其中NIO2即为AIO,需要JDK 1.7+、Linux 2.6+才能支持。

BIO:JDK 1.5+,tomcat 5.x+

NIO:JDK 1.6+,tomcat 6.x+

NIO2:JDK 1.7+,tomcat 7.x+

为了保守起见,我们暂且基于NIO模式。

3. connectionTimeout

当client与tomcat建立连接之后,在"connectionTimeout"时间之内,仍然没有得到client的请求数据,此时连接将会被断开.此值的设定需要考虑到网络稳定型,同时也有性能的考虑.它和tcp的配置选项中的"socket_timeout"仍有区别,connectionTimeout只会在链接建立之后,得到client发送http-request信息前有效.

默认值为60000,即60秒;对于互联网应用,此值我们应该设置合理,比如20000。

4. maxHeaderCount

http请求中header的最大个数,默认为100,"-1"表示不限制,通常不会关注此属性,不过在一些设计"扭曲"的web应用中,使用header传递大量参数(:post)和校验信息时,可能需要调整此值.如果请求中的header个数超过此限定值,请求将会被拒绝.(避免恶性攻击,建议此值设置为符合application的实际需要)

5. maxParameterCount

http-get请求中允许传递的查询字符串的最大个数,尽管各种http浏览器(proxy工具)都会对http-get请求的长度和查询字符串的个数有限制,你仍然可以通过tomcat再次设定合适的值.parameter个数越多,事实上对tomcat的内存开支更大,很多时候处于安全或者实用的角度考虑,maxParameterCount的值都不会太大.默认值为10000,"-1"表示无限制.如果请求中参数的个数超过限定值,请求将会被拒绝.(为了避免恶性攻击,请根据application实际需要设定此值。)

为了安全和规范,maxHeaderCount和maxParamterCount通常应该合理,建议设置为100等;如果请求参数再多,那么就建议使用post body发送或者拆分请求。

6. maxPostSize

http-post请求中数据(body)的最大尺寸,单位:byte,默认值为2M.这对一些表单提交(较多文本域)有影响.可以适度调整此值,大文件上传一般会在client拆分成小文件,而不是直接发送.

7. URIEncoding

http-get请求中,使用何种字符集对查询字符串进行编码,默认为"iso-8859-1".

8. useBodyEncodingForURI

是否使用"Content-type"中指定的编码方式对http-get请求中查询字符串进行编码.如果为"true",将会忽略"URIEncoding"配置项,转而使用header中"content-Type"指定的编码方式.

9. maxThreads

用于接收和处理client端请求的最大线程数,tomcat底层将采取线程池的方式来处理客户端请求,此参数标识这线程池的尺寸.maxThreads意味着tomcat能够并发执行request的个数.此值默认为200.一般情况下,在production环境中(根据物理机器配置,或者虚拟机的限制来做参考值),通常会有微调.较大的值并不能提升tomcat的负载能力,事实上"200"个线程数,已经足够大了.本人的线上环境为maxThreads=120.

对于NIO模式下,maxThreads参数应该由CPU核心数决定,乐观起见,此值为:cpu核数 * 2。太大的值,并不能提升NIO性能,反而会使性能下降,因为线程切换(CS)将会占据CPU的大量时间。

10. compression

是否对http相应数据启用Gzip压缩,可选值为"off"或者"on";这是一个值得商榷的参数;如果开启压缩,意味着较少的网络传输量,但是将消耗一定的CPU.如果你的应用有较高的CPU性能结余,且响应数据均是一些文本字符串,那么开启压缩,会有较大的收益.(并不是所有的浏览器都能够合理的支持gzip压缩,特别是低版本)

11. acceptCount

当tomcat请求处理线程池中的所有线程都处于忙碌状态时,此时新建的链接将会被放入到pending队列,acceptCount即是此队列的容量,如果队列已满,此后所有的建立链接的请求(accept),都将被拒绝。默认为100。在高并发/短链接较多的环境中,可以适当增大此值;当长链接较多的场景中,可以将此值设置为0.

这个参数将会在创建ServerSocket时带入,为TCP底层参数。如果请求均为短连接、请求耗时较短,我们可以适当增加此值。
12. address

当物理server上绑定了多个IP地址时,可以通过“address”来指定tomcat-server需要bind的地址.默认将port关联到所有的ip上。
13. bufferSize

链接在读取stream时,buffer数据的尺寸。(非socket buffer)
14. connectionLinger

socket linger参数值。当socket即将关闭时(前)阻塞的时间,单位:秒。如果设置为-1,表示关闭linger。在BIO(Blocking IO)和AJP链接中默认为100,NIO中默认为25.
15. keepAliveTimeout

当无实际数据交互时,链接被保持的时间,单位:毫秒。在未指定此属性时,将使用connectionTimeout作为keepAliveTimeout。通常和“HTTP keepAlive”选项协调工作。对于HTTP请求,server端为了支撑较高的吞吐量,不可能无限制的keepAlive一个链接(在设计要求上,这个和TCP通讯有本质的区别),keepAliveTimeout超时后,将会导致链接关闭。如果此tomcat设计为“长链接”服务,可以适当增加keepAliveTimeout值,否则无需设置此值。

 

不过我们通常在tomcat上层还有nginx等代理服务器,我们通常希望链接keepAlive的机制由代理服务器控制,比如nginx来决定链接是否需要“保持活性”(注意,与keep_alive不同),当然nginx服务器只会保留极少的长连接,几乎所有的链接都会在使用结束后主动close;因为链接复用层,将有nginx与client保持,而不再是tomcat与client保持。太多的keepAlive链接,尽管提高了链接使用效率,但是对负载均衡不利。
16. maxKeepAliveRequests

tomcat需要保持的最大请求数,即处于keepAlive状态的请求的个数,建议此值为maxThreads * 0.5,不得大于maxThreads,否则将得不到预期的效果。-1表示不限制,1表示关闭keepAlive机制。

17、maxConnections

tomcat允许接收和处理的最大链接数,对于BIO而言此值默认与maxThreads参数一样,对于NIO而言此值默认为10000。对于tomcat已经接受和正在处理的线程数达到此值,server将允许继续accept新链接但是不会处理它们,这些链接将会被阻塞直到连接数降低到此值(server将不会从这些socket中读取数据,而是将它们的句柄buffer起来)。最终server是否可以继续accept新的链接,取决于acceptCount值,因为此值是在创建ServerSocket时传递的参数,超过此值后,链接请求将会被拒绝。

此值还受限于系统的ulimit、CPU、内存等配置。

18、acceptorThreadCount:默认为1,表示用于accept新链接的线程个数,如果在多核CPU架构下,此值可以设置为2,官方不建议设定超过2个的值。

19、maxHttpHeaderSize:http头的最大尺寸,默认为8192,单位为“字节”。

20、minSpareThreads:线程池中,保持活跃的线程的最小数量,默认为10。

21、SSLEnabled:是否开启ssl支持,默认为false;通常SSL应该在nginx等代理层,我们不应该让tomcat直接接入。

 

【如下为NIO配置】

22、pollerThreadCount:表示用于polling IO事件的线程个数,默认为1。在多核CPU架构下,我们可以设置为2来提高polling的能力,官方不建议设置大于2的值,因为锁的竞争会导致性能下降,事实上一个线程也足够快速。

23、useSendfile:是否开启sendfile特性,默认为true。对于web应用而言,通常project中还会包含一定数量的静态资源,比如图片、CSS、js、html等,sendfile在一定程度上可以提高性能。

24、selectorTimeout:选择器阻塞的时间,如果你进行过NIO开发,应该知道此参数的含义,默认值为1000毫秒。此值不要太大,因为selector线程本身还需要用来清理已关闭的链接等。

25、selectorPool.maxSelectors:NIO中选择的个数,默认值为200。NIO中我们可以使用多个selector,每个selector负责注册一定数量的NIOChannel,这样可以有效的提高selector选择效率;通常我们建议此值与maxThreads值一致,或者小于maxThreads,但是大于maxThreads其实意义并不大。

为了开启此特性,我们需要在catalina.sh中增加一个启动命令参数:

Java代码

收藏代码

  1. CATALINA_OPTS="-Dorg.apache.tomcat.util.net.NioSelectorShared=false"

官方的意思是使用command-line-options="-Dorg.apache.tomcat.util.net.NioSelectorShared=false",但是经过测试发现并不能生效。这个命令参数的意思是“不使用共享的selector,而是每个thread单独使用各自的selector。”

 

Java代码

收藏代码

  1. <Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol"
  2.                connectionTimeout="20000"
  3.                maxHeaderCount="64"
  4.                maxParameterCount="64"
  5.                maxHttpHeaderSize="8192"
  6.                URIEncoding="UTF-8"
  7.                useBodyEncodingForURI="false"
  8.                maxThreads="128"
  9.                minSpareThreads="12"
  10.                acceptCount="1024"
  11.                connectionLinger="-1"
  12.                keepAliveTimeout="60"
  13.                maxKeepAliveRequests="32"
  14.                maxConnections="10000"
  15.                acceptorThreadCount="1"
  16.                pollerThreadCount="2"
  17.                selectorTimeout="1000"
  18.                useSendfile="true"
  19.                selectorPool.maxSelectors="128"
  20.                redirectPort="8443" />

 

参考:

https://tomcat.apache.org/tomcat-7.0-doc/config/http.html

https://tomcat.apache.org/tomcat-8.0-doc/config/http.html

 

 

来源: https://blog.csdn.net/m0_37797416/article/details/83505663

 

https://blog.csdn.net/m0_37797416/article/details/83505663