关于事件分派器的信息

4747 742 2022-11-06

本站部分文章、图片属于网络上可搜索到的公开信息,均用于学习和交流用途,不能代表睿象云的观点、立场或意见。我们接受网民的监督,如发现任何违法内容或侵犯了您的权益,请第一时间联系小编邮箱jiasou666@gmail.com 处理。

本文目录一览:

java多线程的好处

1. 充分利用CPU资源

现在世界上大多数计算机只有一块CPU.因此,充分利用CPU资源显得尤为重要。当执行单线程程序时,由于在程序发生阻塞时CPU可能会处于空闲状态。这将造成大量的计算资源的浪费。而在程序中使用多线程可以在某一个线程处于休眠或阻塞时,而CPU又恰好处于空闲状态时来运行其他的线程。这样CPU就很难有空闲的时候。因此,CPU资源就得到了充分地利用。

2. 简化编程模型

如果程序只完成一项任务,那只要写一个单线程的程序,并且按着执行这个任务的步骤编写代码即可。但要完成多项任务,如果还使用单线程的话,那就得在在程序中判断每项任务是否应该执行以及什么时候执行。如显示一个时钟的时、分、秒三个指针。使用单线程就得在循环中逐一判断这三个指针的转动时间和角度。如果使用三个线程分另来处理这三个指针的显示,那么对于每个线程来说就是指行一个单独的任务。这样有助于开发人员对程序的理解和维护。

3. 简化异步事件的处理

当一个服务器应用程序在接收不同的客户端连接时最简单地处理方法就是为每一个客户端连接建立一个线程。然后监听线程仍然负责监听来自客户端的请求。如果这种应用程序采用单线程来处理,当监听线程接收到一个客户端请求后,开始读取客户端发来的数据,在读完数据后,read方法处于阻塞状态,也就是说,这个线程将无法再监听客户端请求了。而要想在单线程中处理多个客户端请求,就必须使用非阻塞的Socket连接和异步I/O.但使用异步I/O方式比使用同步I/O更难以控制,也更容易出错。因此,使用多线程和同步I/O可以更容易地处理类似于多请求的异步事件。

4. 使GUI更有效率

使用单线程来处理GUI事件时,必须使用循环来对随时可能发生的GUI事件进行扫描,在循环内部除了扫描GUI事件外,还得来执行其他的程序代码。如果这些代码太长,那么GUI事件就会被“冻结”,直到这些代码被执行完为止。

在现代的GUI框架(如SWING、AWT和SWT)中都使用了一个单独的事件分派线程(event dispatch thread,EDT)来对GUI事件进行扫描。当我们按下一个按钮时,按钮的单击事件函数会在这个事件分派线程中被调用。由于EDT的任务只是对GUI事件进行扫描,因此,这种方式对事件的反映是非常快的。

5. 节约成本

提高程序的执行效率一般有三种方法:

(1)增加计算机的CPU个数。

(2)为一个程序启动多个进程

(3)在程序中使用多进程。

第一种方法是最容易做到的,但同时也是最昂贵的。这种方法不需要修改程序,从理论上说,任何程序都可以使用这种方法来提高执行效率。第二种方法虽然不用购买新的硬件,但这种方式不容易共享数据,如果这个程序要完成的任务需要必须要共享数据的话,这种方式就不太方便,而且启动多个线程会消耗大量的系统资源。第三种方法恰好弥补了第一种方法的缺点,而又继承了它们的优点。也就是说,既不需要购买CPU,也不会因为启太多的线程而占用大量的系统资源(在默认情况下,一个线程所占的内存空间要远比一个进程所占的内存空间小得多),并且多线程可以模拟多块CPU的运行方式,因此,使用多线程是提高程序执行效率的最廉价的方式。

京东面试官:Redis 这些我必问

缓存好处:高性能 + 高并发

数据库查询耗费了800ms,其他用户对同一个数据再次查询 ,假设该数据在10分钟以内没有变化过,并且 10 分钟之内有 1000 个用户 都查询了同一数据,10 分钟之内,那 1000 每个用户,每个人查询这个数据都感觉很慢 800ms

比如 :某个商品信息,在 一天之内都不会改变,但是这个商品每次查询一次都要耗费2s,一天之内被浏览 100W次

mysql 单机也就 2000qps,缓存单机轻松几万几十万qps,单机 承载并发量是 mysql 单机的几十倍。

在中午高峰期,有 100W 个用户访问系统 A,每秒有 4000 个请求去查询数据库,数据库承载每秒 4000 个请求会宕机,加上缓存后,可以 3000 个请求走缓存 ,1000 个请求走数据库。

缓存是走内存的,内存天然可以支撑4w/s的请求,数据库(基于磁盘)一般建议并发请求不要超过 2000/s

redis 单线程 ,memcached 多线程

redis 是单线程 nio 异步线程模型

一个线程+一个队列

redis 基于 reactor 模式开发了网络事件处理器,这个处理器叫做文件事件处理器,file event handler,这个文件事件处理器是单线程的,所以redis 是单线程的模型,采用 io多路复用机制同时监听多个 socket,根据socket上的事件来选择对应的事件处理器来处理这个事件。

文件事件处理器包含:多个 socket,io多路复用程序,文件事件分派器,事件处理器(命令请求处理器、命令恢复处理器、连接应答处理器)

文件事件处理器是单线程的,通过 io 多路复用机制监听多个 socket,实现高性能和线程模型简单性

被监听的 socket 准备好执行 accept,read,write,close等操作的时候,会产生对应的文件事件,调用之前关联好的时间处理器处理

多个 socket并发操作,产生不同的文件事件,i/o多路复用会监听多个socket,将这些 socket放入一个队列中排队。事件分派器从队列中取出socket给对应事件处理器。

一个socket时间处理完后,事件分派器才能从队列中拿到下一个socket,给对应事件处理器来处理。

文件事件:

AE_READABLE 对应 socket变得可读(客户端对redis执行 write操作)

AE_WRITABLE 对应 socket 变得可写(客户端对 redis执行 read操作)

I/O 多路复用可以同时监听AE_REABLE和 AE_WRITABLE ,如果同时达到则优先处理 AE_REABLE 时间

文件事件处理器:

连接应答处理器 对应 客户端要连接 redis

命令请求处理器 对应 客户端写数据到 redis

命令回复处理器 对应 客户端从 redis 读数据

流程:

一秒钟可以处理几万个请求

普通的 set,get kv缓存

类型 map结构,比如一个对象(没有嵌套对象)缓存到 redis里面,然后读写缓存的时候,可以直接操作hash的字段(比如把 age 改成 21,其他的不变)

key=150

value = {

}

有序列表 ,元素可以重复

可以通过 list 存储一些列表型数据结构,类似粉丝列表,文章评论列表。

例如:微信大 V的粉丝,可以以 list 的格式放在 redis 里去缓存

key=某大 V value=[zhangsan,lisi,wangwu]

比如 lrange 可以从某个元素开始读取多少个元素,可以基于 list 实现分页查询功能,基于 redis实现高性能分页,类似微博下来不断分页东西。

可以搞个简单的消息队列,从 list头怼进去(lpush),list尾巴出来 (brpop)

无序集合,自动去重

需要对一些数据快速全局去重,(当然也可以基于 HashSet,但是单机)

基于 set 玩差集、并集、交集的操作。比如:2 个人的粉丝列表整一个交集,看看 2 个人的共同好友是谁?

把 2 个大 V 的粉丝都放在 2 个 set中,对 2 个 set做交集(sinter)

排序的 set,去重但是可以排序,写进去的时候给一个分数,自动根据分数排序

排行榜:

zadd board score username

例如:

zadd board 85 zhangsan

zadd board 72 wangwu

zadd board 96 lis

zadd board 62 zhaoliu

自动排序为:

96 lisi

85 zhangsan

72 wangwu

62 zhaoliu

获取排名前 3 的用户 : zrevrange board 0 3

96 lisi

85 zhangsan

72 wangwu

查看zhaoliu的排行 :zrank board zhaoliu 返回 4

内存是宝贵的,磁盘是廉价的

给key设置过期时间后,redis对这批key是定期删除+惰性删除

定期删除:

redis 默认每隔 100ms随机抽取一些设置了过期时间的 key,检查其是否过期了,如果过期就删除。

注意:redis是每隔100ms随机抽取一些 key来检查和删除,而不是遍历所有的设置过期时间的key(否则CPU 负载会很高,消耗在检查过期 key 上)

惰性删除:

获取某个key的时候, redis 会检查一下,这个key如果设置了过期时间那么是否过期,如果过期了则删除。

如果定期删除漏掉了许多过期key,然后你也没及时去查,也没走惰性删除,如果大量过期的key堆积在内存里,导致 redis 内存块耗尽,则走内存淘汰机制。

内存淘汰策略:

LRU 算法:

缓存架构(多级缓存架构、热点缓存)

redis 高并发瓶颈在单机,读写分离,一般是支撑读高并发,写请求少,也就 一秒一两千,大量请求读,一秒钟二十万次。

一主多从,主负责写,将数据同步复制到其他 slave节点,从节点负责读,所有读的请求全部走从节点。主要是解决读高并发。、

主从架构-读写分离-支撑10W+读QPS架构

master-slave 复制,是异步的

核心机制:

master持久化对主从架构的意义:

如果开启了主从架构,一定要开启 master node的持久化,不然 master宕机重启数据是空的,一经复制,slave的数据也丢了

主从复制原理:

第一次启动或者断开重连情况:

正常情况下:

master 来一条数据,就异步给 slave

全年 99.99%的时间,都是出于可用的状态,那么就可以称为高可用性

redis 高可用架构叫故障转移,failover,也可以叫做主备切换,切换的时间不可用,但是整体高可用。

sentinal node(哨兵)

作用:

quorum = 1 (代表哨兵最低个数可以尝试故障转移,选举执行的哨兵)

master 宕机,只有 S2 存活,因为 quorum =1 可以尝试故障转移,但是没达到 majority =2 (最低允许执行故障转移的哨兵存活数)的标准,无法执行故障转移

如果 M1 宕机了,S2,S3 认为 master宕机,选举一个执行故障转移,因为 3 个哨兵的 majority = 2,所以可以执行故障转移

丢数据:

解决方案:

sdown 主观宕机,哨兵觉得一个 master 宕机(ping 超过了 is-master-down-after-milliseconds毫秒数)

odown 客观宕机,quorum数量的哨兵都觉得 master宕机

哨兵互相感知通过 redis的 pub/sub系统,每隔 2 秒往同一个 channel里发消息(自己的 host,ip,runid),其他哨兵可以消费这个消息

以及同步交换master的监控信息。

哨兵确保其他slave修改master信息为新选举的master

当一个 master被认为 odown marjority哨兵都同意,那么某个哨兵会执行主备切换,选举一个slave成为master(考虑 1. 跟master断开连接的时长 2. slave 优先级 3.复制 offset 4. runid)

选举算法:

quorum 数量哨兵认为odown-选举一个哨兵切换-获得 majority哨兵的授权(quorum majority 需要 majority个哨兵授权,quorum = majority 需要 quorum 哨兵授权)

第一个选举出来的哨兵切换失败了,其他哨兵等待 failover-time之后,重新拿confiuration epoch做为新的version 切换,保证拿到最新配置,用于 configuration传播(通过 pu/sub消息机制,其他哨兵对比 version 新旧更新 master配置)

高并发:主从架构

高容量:Redis集群,支持每秒几十万的读写并发

高可用:主从+哨兵

持久化的意义在于故障恢复数据备份(到其他服务器)+故障恢复(遇到灾难,机房断电,电缆被切)

AOF 只有一个,Redis 中的数据是有一定限量的,内存大小是一定的,AOF 是存放写命令的,当大到一定的时候,AOF 做 rewrite 操作,就会基于当时 redis 内存中的数据,来重新构造一个更小的 AOF 文件,然后将旧的膨胀很大的文件给删掉,AOF 文件一直会被限制在和Redis内存中一样的数据。AOF同步间隔比 RDB 小,数据更完整

优点:

缺点:

AOF 存放的指令日志,数据恢复的时候,需要回放执行所有指令日志,RDB 就是一份数据文件,直接加载到内存中。

优点:

缺点:

AOF 来保证数据不丢失,RDB 做不同时间的冷备

支持 N 个 Redis master node,每个 master node挂载多个 slave node

多master + 读写分离 + 高可用

数据量很少,高并发 - replication + sentinal 集群

海量数据 + 高并发 + 高可用 - redis cluster

hash算法-一致性 hash 算法- redis cluster-hash slot算法

redis cluster :自动对数据进行分片,每个 master 上放一部分数据,提供内置的高可用支持,部分master不可用时,还是可以继续工作

cluster bus 通过 16379进行通信,故障检测,配置更新,故障转移授权,另外一种二进制协议,主要用于节点间进行高效数据交换,占用更少的网络带宽和处理时间

key进行hash,然后对节点数量取模,最大问题只有任意一个 master 宕机,大量数据就要根据新的节点数取模,会导致大量缓存失效。

key进行hash,对应圆环上一个点,顺时针寻找距离最近的一个点。保证任何一个 master 宕机,只受 master 宕机那台影响,其他节点不受影响,此时会瞬间去查数据库。

缓存热点问题:

可能集中在某个 hash区间内的值特别多,那么会导致大量的数据都涌入同一个 master 内,造成 master的热点问题,性能出现瓶颈。

解决方法:

给每个 master 都做了均匀分布的虚拟节点,这样每个区间内大量数据都会均匀的分布到不同节点内,而不是顺时针全部涌入到同一个节点中。

redis cluster 有固定 16384 个 hash slot,对每个key计算 CRC16 值,然后对16384取模,可以获取 key对应的 hash slot

redis cluster 中每个 master 都会持有部分 slot ,当一台 master 宕机时候,会最快速度迁移 hash slot到可用的机器上(只会短暂的访问不到)

走同一个 hash slot 通过 hash tag实现

集群元数据:包括 hashslot-node之间的映射表关系,master-slave之间的关系,故障的信息

集群元数据集中式存储(storm),底层基于zookeeper(分布式协调中间件)集群所有元数据的维护。好处:元数据的更新和读取,时效性好,一旦变更,其他节点立刻可以感知。缺点:所有元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。

goosip: 好处:元数据的更新比较分散,有一定的延时,降低了压力。缺点:更新有延时,集群的一些操作会滞后。(reshared操作时configuration error)

自己提供服务的端口号+ 10000 ,每隔一段时间就会往另外几个节点发送ping消息,同时其他几点接收到ping之后返回pong

故障信息,节点的增加和移除, hash slot 信息

meet:某个节点发送 meet给新加入的节点,让新节点加入集群中,然后新节点就会开始于其他节点进行通信

ping:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据

ping:返回ping和meet,包含自己的状态和其他信息

fail:某个节点判断另一个节点fail之后,就发送 fail 给其他节点,通知其他节点,指定的节点宕机了

ping 很频繁,且携带元数据,会加重网络负担

每个节点每秒会执行 10 次 ping,每次选择 5 个最久没有通信的其他节点

当如果发现某个节点通信延迟达到了 cluster_node_timeout /2 ,那么立即发送 ping, 避免数据交换延迟过长,落后时间太长(2 个节点之间 10 分钟没有交换数据,整个集群处于严重的元数据不一致的情况)。

每次ping,一个是带上自己的节点信息,还有就是带上1/10其他节点的信息,发送出去,进行数据交换

至少包含 3 个其他节点信息,最多包含总节点-2 个其他节点的信息

客户端发送到任意一个redis实例发送命令,每个redis实例接受到命令后,都会计算key对应的hash slot,如果在本地就本地处理,否则返回moved给客户端,让客户端进行重定向 (redis-cli -c)

通过tag指定key对应的slot,同一个 tag 下的 key,都会在一个 hash slot中,比如 set key1:{100} 和 set key2:{100}

本地维护一份hashslot-node的映射表。

JedisCluster 初始化的时候,随机选择一个 node,初始化 hashslot-node 映射表,同时为每个节点创建一个JedisPool连接池,每次基于JedisCluster执行操作,首先JedisCluster都会在本地计算key的hashslot,然后再本地映射表中找到对应的节点,如果发现对应的节点返回moved,那么利用该节点的元数据,更新 hashslot-node映射表(重试超过 5 次报错)

hash slot正在迁移,那么会返回ask 重定向给jedis,jedis 接受到ask重定向之后,,会重定向到目标节点去执行

判断节点宕机:

如果一个节点认为另外一个节点宕机了, 就是pfail,主观宕机

如果多个节点都认为另外一个节点宕机了,那么就是fail,客观宕机(跟哨兵原理一样)

在cluster-node-timeout内,某个节点一直没有返回 pong,那么就被认为是 pfail

如果一个节点认为某个节点pfail了,那么会在gossip消息中,ping给其他节点,如果超过半数的节点认为pfail了,那么就会变成fail。

从节点过滤:

对宕机的 mster node ,从其所有的 slave node中,选择一个切换成 master node

检查每个 slave node与master node断开连接的时间,如果超过了cluster-node-timeout * cluster-slave-validity-factor,那么就没资格切换成 master(和哨兵一致)

从节点选举:

每个从节点,根据自己对 master 复制数据的 offset,设置一个选举时间,offset越大(复制数据越多)的从节点,选举时间越靠前,所有的 master node 开始投票,给要进行选举的 slave进行投票,如果大部分 master node(N/2 +1) 都投票给某个从节点,那么选举通过,从节点执行主备切换,从节点切换成主节点

总结:和哨兵很像,直接集成了 replication 和 sentinal

方案:

事前:保证 redis 集群高可用性 (主从+哨兵或 redis cluster),避免全盘崩溃

事中:本地 ehcache 缓存 + hystrix 限流(保护数据库) 降级,避免 MySQL被打死

事后: redis持久化,快速恢复缓存数据,继续分流高并发请求

限制组件每秒就 2000 个请求通过限流组件进入数据库,剩余的 3000 个请求走降级,返回一些默认 的值,或者友情提示

好处 :

4000 个请求黑客攻击请求数据库里没有的数据

解决方案:把黑客查数据库中不存在的数据的值,写到缓存中,比如: set -999 UNKNOWN

读的时候,先读缓存,缓存没有,就读数据库,然后取出数据后放入缓存,同时返回响应

更新的时候,删除缓存,更新数据库

为什么不更新缓存:

更新缓存代价太高(更新 20 次,只读 1 次),lazy思想,需要的时候再计算,不需要的时候不计算

方案:先删除缓存,再修改数据库

方案:写,读路由到相同的一个内存队列(唯一标识,hash,取模)里,更新和读操作进行串行化(后台线程异步执行队列串行化操作),(队列里只放一个更新查询操作即可,多余的过滤掉,内存队列里没有该数据更新操作,直接返回 )有该数据更新操作则轮询取缓存值,超时取不到缓存值,直接取一次数据库的旧值

TP 99 意思是99%的请求可以在200ms内返回

注意点:多个商品的更新操作都积压在一个队列里面(太多操作积压只能增加机器),导致读请求发生大量的超时,导致大量的读请求走数据库

一秒 500 写操作,每200ms,100 个写操作,20 个内存队列,每个队列积压 5 个写操作,一般在20ms完成

方案:分布式锁 + 时间戳比较

10台机器,5 主 5 从,每个节点QPS 5W ,一共 25W QPS(Redis cluster 32G + 8 核 ,Redis 进程不超过 10G)总内存 50g,每条数据10kb,10W 条数据1g,200W 条数据 20G,占用总内存不到50%,目前高峰期 3500 QPS

作者: mousycoder

QT的事件和信号的区别

signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件进行处理。但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个QPushButton,那么我们就需要重写它的鼠标点击事件和键盘处理事件,并且在恰当的时候发出clicked()信号。

还记得我们在main函数里面创建了一个QApplication对象,然后调用了它的exec()函数吗?其实,这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt将创建一个事件对象。Qt的所有事件都继承于 QEvent类。在事件对象创建完毕后,Qt将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。

在所有组件的父类QWidget中,定义了很多事件处理函数,如keyPressEvent()、

keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent ()、mousePressEvent()、mouseReleaseEvent()等。这些函数都是protected virtual的,也就是说,我们应该在子类中重定义这些函数。

求教服务器tcp连接数被占满的有关问题

我问了在约APP的专家,修改上述限制的最简单的办法就是使用ulimit命令:

[speng@as4 ~]$ ulimit -n

上述命令中,在中指定要设置的单一进程允许打开的最大文件数。如果系统回显类似于“Operation notpermitted”之类的话,说明上述限制修改失败,实际上是因为在中指定的数值超过了Linux系统对该用户打开文件数的软限制或硬限制。因此,就需要修改Linux系统对用户的关于打开文件数的软限制和硬限制。

第一步,修改/etc/security/limits.conf文件,在文件中添加如下行:

speng soft nofile 10240

speng hard nofile 10240

其中speng指定了要修改哪个用户的打开文件数限制,可用’*'号表示修改所有用户的限制;soft或hard指定要修改软限制还是硬限制;10240则指定了想要修改的新的限制值,即最大打开文件数(请注意软限制值要小于或等于硬限制)。修改完后保存文件。

第二步,修改/etc/pam.d/login文件,在文件中添加如下行:

session required /lib/security/pam_limits.so

这是告诉Linux在用户完成系统登录后,应该调用pam_limits.so模块来设置系统对该用户可使用的各种资源数量的最大限制(包括用户可打开的最大文件数限制),而pam_limits.so模块就会从/etc/security/limits.conf文件中读取配置来设置这些限制值。修改完后保存此文件。

第三步,查看Linux系统级的最大打开文件数限制,使用如下命令:

[speng@as4 ~]$ cat /proc/sys/fs/file-max

12158

这表明这台Linux系统最多允许同时打开(即包含所有用户打开文件数总和)12158个文件,是Linux系统级硬限制,所有用户级的打开文件数限制都不应超过这个数值。通常这个系统级硬限制是Linux系统在启动时根据系统硬件资源状况计算出来的最佳的最大同时打开文件数限制,如果没有特殊需要,不应该修改此限制,除非想为用户级打开文件数限制设置超过此限制的值。修改此硬限制的方法是修改/etc/rc.local脚本,在脚本中添加如下行:

echo 22158 /proc/sys/fs/file-max

这是让Linux在启动完成后强行将系统级打开文件数硬限制设置为22158。修改完后保存此文件。

完成上述步骤后重启系统,一般情况下就可以将Linux系统对指定用户的单一进程允许同时打开的最大文件数限制设为指定的数值。如果重启后用 ulimit-n命令查看用户可打开文件数限制仍然低于上述步骤中设置的最大值,这可能是因为在用户登录脚本/etc/profile中使用ulimit -n命令已经将用户可同时打开的文件数做了限制。由于通过ulimit-n修改系统对用户可同时打开文件的最大数限制时,新修改的值只能小于或等于上次 ulimit-n设置的值,因此想用此命令增大这个限制值是不可能的。所以,如果有上述问题存在,就只能去打开/etc/profile脚本文件,在文件中查找是否使用了ulimit-n限制了用户可同时打开的最大文件数量,如果找到,则删除这行命令,或者将其设置的值改为合适的值,然后保存文件,用户退出并重新登录系统即可。

通过上述步骤,就为支持高并发TCP连接处理的通讯处理程序解除关于打开文件数量方面的系统限制。

2、修改网络内核对TCP连接的有关限制(参考对比下篇文章“优化内核参数”)

在Linux上编写支持高并发TCP连接的客户端通讯处理程序时,有时会发现尽管已经解除了系统对用户同时打开文件数的限制,但仍会出现并发TCP连接数增加到一定数量时,再也无法成功建立新的TCP连接的现象。出现这种现在的原因有多种。

第一种原因可能是因为Linux网络内核对本地端口号范围有限制。此时,进一步分析为什么无法建立TCP连接,会发现问题出在connect()调用返回失败,查看系统错误提示消息是“Can’t assign requestedaddress”。同时,如果在此时用tcpdump工具监视网络,会发现根本没有TCP连接时客户端发SYN包的网络流量。这些情况说明问题在于本地Linux系统内核中有限制。其实,问题的根本原因在于Linux内核的TCP/IP协议实现模块对系统中所有的客户端TCP连接对应的本地端口号的范围进行了限制(例如,内核限制本地端口号的范围为1024~32768之间)。当系统中某一时刻同时存在太多的TCP客户端连接时,由于每个TCP客户端连接都要占用一个唯一的本地端口号(此端口号在系统的本地端口号范围限制中),如果现有的TCP客户端连接已将所有的本地端口号占满,则此时就无法为新的TCP客户端连接分配一个本地端口号了,因此系统会在这种情况下在connect()调用中返回失败,并将错误提示消息设为“Can’t assignrequested address”。有关这些控制逻辑可以查看Linux内核源代码,以linux2.6内核为例,可以查看tcp_ipv4.c文件中如下函数:

static int tcp_v4_hash_connect(struct sock *sk)

请注意上述函数中对变量sysctl_local_port_range的访问控制。变量sysctl_local_port_range的初始化则是在tcp.c文件中的如下函数中设置:

void __init tcp_init(void)

内核编译时默认设置的本地端口号范围可能太小,因此需要修改此本地端口范围限制。

第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:

net.ipv4.ip_local_port_range = 1024 65000

这表明将系统对本地端口范围限制设置为1024~65000之间。请注意,本地端口范围的最小值必须大于或等于1024;而端口范围的最大值则应小于或等于65535。修改完后保存此文件。

第二步,执行sysctl命令:

[speng@as4 ~]$ sysctl -p

如果系统没有错误提示,就表明新的本地端口范围设置成功。如果按上述端口范围进行设置,则理论上单独一个进程最多可以同时建立60000多个TCP客户端连接。

第二种无法建立TCP连接的原因可能是因为Linux网络内核的IP_TABLE防火墙对最大跟踪的TCP连接数有限制。此时程序会表现为在 connect()调用中阻塞,如同死机,如果用tcpdump工具监视网络,也会发现根本没有TCP连接时客户端发SYN包的网络流量。由于 IP_TABLE防火墙在内核中会对每个TCP连接的状态进行跟踪,跟踪信息将会放在位于内核内存中的conntrackdatabase中,这个数据库的大小有限,当系统中存在过多的TCP连接时,数据库容量不足,IP_TABLE无法为新的TCP连接建立跟踪信息,于是表现为在connect()调用中阻塞。此时就必须修改内核对最大跟踪的TCP连接数的限制,方法同修改内核对本地端口号范围的限制是类似的:

第一步,修改/etc/sysctl.conf文件,在文件中添加如下行:

net.ipv4.ip_conntrack_max = 10240

这表明将系统对最大跟踪的TCP连接数限制设置为10240。请注意,此限制值要尽量小,以节省对内核内存的占用。

第二步,执行sysctl命令:

[speng@as4 ~]$ sysctl -p

如果系统没有错误提示,就表明系统对新的最大跟踪的TCP连接数限制修改成功。如果按上述参数进行设置,则理论上单独一个进程最多可以同时建立10000多个TCP客户端连接。

3、使用支持高并发网络I/O的编程技术

在Linux上编写高并发TCP连接应用程序时,必须使用合适的网络I/O技术和I/O事件分派机制。

可用的I/O技术有同步I/O,非阻塞式同步I/O(也称反应式I/O),以及异步I/O。在高TCP并发的情形下,如果使用同步I/O,这会严重阻塞程序的运转,除非为每个TCP连接的I/O创建一个线程。但是,过多的线程又会因系统对线程的调度造成巨大开销。因此,在高TCP并发的情形下使用同步 I/O是不可取的,这时可以考虑使用非阻塞式同步I/O或异步I/O。非阻塞式同步I/O的技术包括使用select(),poll(),epoll等机制。异步I/O的技术就是使用AIO。

从I/O事件分派机制来看,使用select()是不合适的,因为它所支持的并发连接数有限(通常在1024个以内)。如果考虑性能,poll()也是不合适的,尽管它可以支持的较高的TCP并发数,但是由于其采用“轮询”机制,当并发数较高时,其运行效率相当低,并可能存在I/O事件分派不均,导致部分TCP连接上的I/O出现“饥饿”现象。而如果使用epoll或AIO,则没有上述问题(早期Linux内核的AIO技术实现是通过在内核中为每个 I/O请求创建一个线程来实现的,这种实现机制在高并发TCP连接的情形下使用其实也有严重的性能问题。但在最新的Linux内核中,AIO的实现已经得到改进)。

综上所述,在开发支持高并发TCP连接的Linux应用程序时,应尽量使用epoll或AIO技术来实现并发的TCP连接上的I/O控制,这将为提升程序对高并发TCP连接的支持提供有效的I/O保证。

内核参数sysctl.conf的优化

/etc/sysctl.conf 是用来控制linux网络的配置文件,对于依赖网络的程序(如web服务器和cache服务器)非常重要,RHEL默认提供的最好调整。

推荐配置(把原/etc/sysctl.conf内容清掉,把下面内容复制进去):

net.ipv4.ip_local_port_range = 1024 65536

net.core.rmem_max=16777216

net.core.wmem_max=16777216

net.ipv4.tcp_rmem=4096 87380 16777216

net.ipv4.tcp_wmem=4096 65536 16777216

net.ipv4.tcp_fin_timeout = 10

net.ipv4.tcp_tw_recycle = 1

net.ipv4.tcp_timestamps = 0

net.ipv4.tcp_window_scaling = 0

net.ipv4.tcp_sack = 0

net.core.netdev_max_backlog = 30000

net.ipv4.tcp_no_metrics_save=1

net.core.somaxconn = 262144

net.ipv4.tcp_syncookies = 0

net.ipv4.tcp_max_orphans = 262144

net.ipv4.tcp_max_syn_backlog = 262144

net.ipv4.tcp_synack_retries = 2

net.ipv4.tcp_syn_retries = 2

这个配置参考于cache服务器varnish的推荐配置和SunOne 服务器系统优化的推荐配置。

varnish调优推荐配置的地址为:

不过varnish推荐的配置是有问题的,实际运行表明“net.ipv4.tcp_fin_timeout = 3”的配置会导致页面经常打不开;并且当网友使用的是IE6浏览器时,访问网站一段时间后,所有网页都会打不开,重启浏览器后正常。可能是国外的网速快吧,我们国情决定需要调整“net.ipv4.tcp_fin_timeout = 10”,在10s的情况下,一切正常(实际运行结论)。

修改完毕后,执行:

/sbin/sysctl -p /etc/sysctl.conf

/sbin/sysctl -w net.ipv4.route.flush=1

命令生效。为了保险起见,也可以reboot系统。

调整文件数:

linux系统优化完网络必须调高系统允许打开的文件数才能支持大的并发,默认1024是远远不够的。

执行命令:

Shell代码

echo ulimit -HSn 65536 /etc/rc.local

echo ulimit -HSn 65536 /root/.bash_profile

ulimit -HSn 65536

关于事件驱动架构的思考

本文只是有关事件驱动的体系结构的一些想法。 这里没有代码,只有观察和建议。 明确地说,我将使用事件驱动一词,但如果您阅读上面的Wikipedia参考,则会发现我也错误地混入了消息驱动系统。

这是关于复杂性的讨论,显然是说,强大的力量伴随着巨大的责任。

基于事件的体系结构范式的核心是事件的产生,检测,消耗和反应的解耦。它们应该在反映这一点的代码中进行组织,即与生产,检测,消耗和反应相关的代码应分别分组,并且通常还通过多个应用程序进行分发。尽管事情是有条理的,并且肯定有明确的因果关系,但通过系统的分派机制进行的每次转换都会充当信息壁垒。在许多体系结构中,如果您从第一段代码开始,则可以跟踪在给定情况下从头到尾遵循的代码路径,通常可以使用调试器实时进行。使用基于事件的系统,通过事件分配器的第一跳更有可能使您感冒。您立即面临一个问题,即许多现有的听众/订户中的哪些人将对事件做出响应,他们是否都在此过程中进行响应,是否可以保证收据,以及确定性发生的顺序?

它实际上是一个公开喊价(outcry)系统,在通常情况下,出价(通话)和要约(响应)易于观察和配对,但是在混乱的时期,以观察员的身份进行的所有呼喊变得几乎不可能。

我指的是我正在替换的当前事件驱动系统,称为弹球机,因为球会大量涌入,在周围疯狂反弹,有的会导致奖品弹出,而有的则会消失殆尽。 您必须是粒子物理学家才能认为系统是可预测的和可理解的。

我记得读过一次关于航空公司系统停机的事后调查,我相信那是英国航空公司的UPS故障,恢复工作花了几天的时间。为什么?他们的系统都是事件驱动的,并挂在一条通用的消息总线上。随着时间的流逝以及通过企业收购,IT系统的有机增长意味着他们根本不知道到底在听什么,而且系统实施在容错方面也不一致。许多系统需要重新启动以重新建立通信,并且尽管UI可以快速检测和处理,但在不能解决所有问题时,他们显然会蛮力地"重新启动所有"。但是,由于系统之间的相互依赖性以及几乎同时进行的重启,因此并非所有重启均能正常工作。只是随着时间的流逝,通过注意到非功能性功能才发现了一些问题。例如,也许您可以预订航班,选择座位,登记行李,但行李标签不会在希思罗机场的柜台打印。因此,他们必须确定应该发生什么事件链,哪些链断裂了,没有发生什么事件反应以及最后应该由哪个系统执行。

不。它们功能强大,并且在许多情况下绝对是正确的解决方案。 哎呀,我们正在用另一种事件驱动的架构替换弹球机。 什么?! 是的,这是我们方案中的正确工具。

因此,如果我不是说不使用事件驱动的体系结构,那是什么意思?

从第零天开始进行跟踪和恢复:

· 将关联标识符和发起者信息维护到事件中。

· 统一审核/记录命令和事件。

· 请勿使用Blob或任何方案文本(如JSON)。 您希望始终使用通用语言,因为许多分布式部分正在监听。 集中定义事件,并在所有地方使用这些定义。 您想知道更改对整个系统的影响。 提前计划事件的演变变化。 在可能的情况下,请避免对现有字段进行结构更改,而应采用"狂暴/吹扫"方法,在这种情况下,您仅进行累加并直到要清理。

· 研究Zipkin和监视工具之类的东西,以显示跟踪信息。

这些建议似乎过于严格,但是我一次又一次地看到人们认为他们可以在获得一定收入后再解决这些问题,然后当问题确实出现时,发现没有APM或快速解决方案可以追溯地真正修复生态系统。

(本文翻译自nwillc的文章《Thoughts on Event Driven Architectures》,参考:)

那些年一直用jQuery处理事件,这些年想了解下内部原理吗

说起jQuery的事件,不得不提一下Dean Edwards大神 addEvent库,很多流行的类库的基本思想从他那儿借来的

jQuery的事件处理机制吸取了JavaScript专家Dean Edwards编写的事件处理函数的精华,使得jQuery处理事件绑定的时候相当的可靠。

在预留退路(graceful degradation),循序渐进以及非入侵式编程思想方面,jQuery也做的非常不错

事件的流程图

总的来说对于JQuery的事件绑定

在绑定的时候做了包装处理

在执行的时候有过滤器处理

.on( events [, selector ] [, data ], handler(eventObject) )

events:事件名

selector : 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素

data :当一个事件被触发时,要传递给事件处理函数的

handler:事件被触发时,执行的函数

例如:

var body = $('body')

body.on('click','p',function(){

console.log(this)

})

用on方法给body上绑定一个click事件,冒泡到p元素的时候才出发回调函数

这里大家需要明确一点:每次在body上点击其实都会触发事件,但是只目标为p元素的情况下才会触发回调handler

通过源码不难发现,on方法实质只完成一些参数调整的工作,而实际负责事件绑定的是其内部jQuery.event.add方法

on: function( types, selector, data, fn, /*INTERNAL*/ one ) {

var origFn, type;

// Types can be a map of types/handlers

if ( typeof types === "object" ) {

// ( types-Object, selector, data )

if ( typeof selector !== "string" ) {

// ( types-Object, data )

data = data || selector;

selector = undefined;

}

for ( type in types ) {

this.on( type, selector, data, types[ type ], one );

}

return this;

}

if ( data == null fn == null ) {

// ( types, fn )

fn = selector;

data = selector = undefined;

} else if ( fn == null ) {

if ( typeof selector === "string" ) {

// ( types, selector, fn )

fn = data;

data = undefined;

} else {

// ( types, data, fn )

fn = data;

data = selector;

selector = undefined;

}

}

if ( fn === false ) {

fn = returnFalse;

} else if ( !fn ) {

return this;

}

if ( one === 1 ) {

origFn = fn;

fn = function( event ) {

// Can use an empty set, since event contains the info

jQuery().off( event );

return origFn.apply( this, arguments );

};

// Use same guid so caller can remove using origFn

fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );

}

return this.each( function() {

jQuery.event.add( this, types, fn, data, selector );

});

针对事件处理,我们可以拆分2部分:

一个事件预绑定期

一个事件执行期

本章着重讲解事件的预绑定的时候做了那些处理,为什么要这样处理?

事件底层的绑定接口无非就是用addEventListener处理的,所以我们直接定位到addEventListener下面

jQuery.event.add 中有

elem: 目标元素

type: 事件类型,如’click’

eventHandle: 事件句柄,也就是事件回调处理的内容了

false: 冒泡

现在我们把之前的案例给套一下看看

var body = document.getElementsByTagName('body')

var eventHandle = function(){

console.log(this)

}

body .addEventListener( 'click’, eventHandle, false );

明显有问题,每次在body上都触发了回调,少了个p元素的处理,当然这样的效果也无法处理

eventHandle源码

回到内部绑定的事件句柄eventHandle ,可想而知eventHandle不仅仅只是只是充当一个回调函数的角色,而是一个实现了EventListener接口的对象

if ( !(eventHandle = elemData.handle) ) {

eventHandle = elemData.handle = function( e ) {

// Discard the second event of a jQuery.event.trigger() and

// when an event is called after a page has unloaded

return typeof jQuery !== core_strundefined (!e || jQuery.event.triggered !== e.type) ?

jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :

undefined;

};

// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events

eventHandle.elem = elem;

}

可见在eventHandle中并没有直接处理回调函数,而是映射到jQuery.event.dispatch分派事件处理函数了

仅仅只是传入eventHandle.elem,arguments , 就是body元素 与事件对象

那么这里有个问题,事件回调的句柄并没有传递过去,后面的代码如何关联?

本章的一些地方可能要结合后面的dispatch处理才能理清,但是我们还是先看看做了那些处理

on内部的实现机制

我们开从头来理清下jQuery.event.add代码结构,适当的跳过这个环节中不能理解的代码,具体遇到在提出

之前就提到过jQuery从1.2.3版本引入数据缓存系统,贯穿内部,为整个体系服务,事件体系也引入了这个缓存机制

所以jQuery并没有将事件处理函数直接绑定到DOM元素上,而是通过$.data存储在缓存$.cahce上

第一步:获取数据缓存

//获取数据缓存

elemData = data_priv.get( elem );

在$.cahce缓存中获取存储的事件句柄对象,如果没就新建elemData

第二步:创建编号

if ( !handler.guid ) {

handler.guid = jQuery.guid++;

}

为每一个事件的句柄给一个标示,添加ID的目的是 用来寻找或者删除handler,因为这个东东是缓存在缓存对象上的,没有直接跟元素节点发生关联

第三步:分解事件名与句柄

if ( !(events = elemData.events) ) {

events = elemData.events= {};

}

if ( !(eventHandle = elemData.handle) ) {

eventHandle = elemData.handle = function( e ) {

// Discard the second event of a jQuery.event.trigger() and

// when an event is called after a page has unloaded

return typeof jQuery !== core_strundefined (!e || jQuery.event.triggered !== e.type) ?

jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :

undefined;

};

eventHandle.elem = elem;

}

events,eventHandle 都是elemData缓存对象内部的,可见

在elemData中有两个重要的属性,

一个是events,是jQuery内部维护的事件列队

一个是handle,是实际绑定到elem中的事件处理函数

之后的代码无非就是对这2个对象的筛选,分组,填充了

第四步: 填充事件名与事件句柄

// Handle multiple events separated by a space

// jQuery(...).bind("mouseover mouseout", fn);

// 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组

// core_rnotwhite:/\S+/g

types = ( types || "" ).match( core_rnotwhite ) || [""];

// 例如:'.a .b .c'.match(/\S+/g) → [".a", ".b", ".c"]

// 事件的个数

t = types.length;

while ( t-- ) {

// 尝试取出事件的命名空间

// 如"mouseover.a.b" → ["mouseover.a.b", "mouseover", "a.b"]

tmp = rtypenamespace.exec( types[t] ) || [];

// 取出事件类型,如mouseover

type = origType = tmp[1];

// 取出事件命名空间,如a.b,并根据"."分隔成数组

namespaces = ( tmp[2] || "" ).split( "." ).sort();

// There *must* be a type, no attaching namespace-only handlers

if ( !type ) {

continue;

}

// If event changes its type, use the special event handlers for the changed type

// 事件是否会改变当前状态,如果会则使用特殊事件

special = jQuery.event.special[ type ] || {};

// If selector defined, determine special event api type, otherwise given type

// 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type

type = ( selector ? special.delegateType : special.bindType ) || type;

// Update special based on newly reset type

// type状态发生改变,重新定义特殊事件

special = jQuery.event.special[ type ] || {};

// handleObj is passed to all event handlers

// 这里把handleObj叫做事件处理对象,扩展一些来着handleObjIn的属性

handleObj = jQuery.extend({

type: type,

origType: origType,

data: data,

handler: handler,

guid: handler.guid,

selector: selector,

needsContext: selector jQuery.expr.match.needsContext.test( selector ),

namespace: namespaces.join(".")

}, handleObjIn );

// Init the event handler queue if we're the first

// 初始化事件处理列队,如果是第一次使用,将执行语句

if ( !(handlers = events[ type ]) ) {

handlers = events[ type ] = [];

handlers.delegateCount = 0;

// Only use addEventListener if the special events handler returns false

// 如果获取特殊事件监听方法失败,则使用addEventListener进行添加事件

if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

if ( elem.addEventListener ) {

elem.addEventListener( type, eventHandle, false );

}

}

}

// 特殊事件使用add处理

if ( special.add ) {

special.add.call( elem, handleObj );

// 设置事件处理函数的ID

if ( !handleObj.handler.guid ) {

handleObj.handler.guid = handler.guid;

}

}

// Add to the element's handler list, delegates in front

// 将事件处理对象推入处理列表,姑且定义为事件处理对象包

if ( selector ) {

handlers.splice( handlers.delegateCount++, 0, handleObj );

} else {

handlers.push( handleObj );

}

// Keep track of which events have ever been used, for event optimization

// 表示事件曾经使用过,用于事件优化

jQuery.event.global[ type ] = true;

}

// Nullify elem to prevent memory leaks in IE

// 设置为null避免IE中循环引用导致的内存泄露

elem = null;

},

这段比较长了分解下,最终的目的就是为填充events,eventHandle


上一篇:软件测试培训之如何做好功能测试
下一篇:系统短信告警功能(短信报警电话)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~