美文网首页
高并发基础知识(一)

高并发基础知识(一)

作者: 木石前盟_429a | 来源:发表于2020-05-12 00:25 被阅读0次

    Netty之所以这么火,与它的巨大优点是密不可分的,大致可以总结如下:

    · API使用简单,开发门槛低。· 功能强大,预置了多种编解码功能,支持多种主流协议。

    · 定制能力强,可以通过ChannelHandler对通信框架进行灵活扩展。· 性能高,与其他业界主流的NIO框架对比,Netty的综合性能最优。

    · 成熟、稳定,Netty修复了已经发现的所有JDK NIO中的BUG,业务开发人员不需要再为NIO的BUG而烦恼。

    · 社区活跃,版本迭代周期短,发现的BUG可以被及时修复。



    Redis的主要应用场景:缓存(数据查询、短连接、新闻内容、商品内容等)、分布式会话(Session)、聊天室的在线好友列表、任务队列(秒杀、抢购、12306等)、应用排行榜、访问统计、数据过期处理(可以精确到毫秒)。


    相对于其他的键-值对(Key-Value)内存数据库(如Memcached)而言,Redis具有如下特点:

    (1)速度快 不需要等待磁盘的IO,在内存之间进行的数据存储和查询,速度非常快。当然,缓存的数据总量不能太大,因为受到物理内存空间大小的限制。

    (2)丰富的数据结构 除了string之外,还有list、hash、set、sortedset,一共五种类型。

    (3)单线程,避免了线程切换和锁机制的性能消耗。

    (4)可持久化 支持RDB与AOF两种方式,将内存中的数据写入外部的物理存储设备。

    (5)支持发布/订阅。

    (6)支持Lua脚本。

    (7)支持分布式锁 在分布式系统中,如果不同的节点需要访同到一个资源,往往需要通过互斥机制来防止彼此干扰,并且保证数据的一致性。在这种情况下,需要使用到分布式锁。分布式锁和Java的锁用于实现不同线程之间的同步访问,原理上是类似的。

    (8)支持原子操作和事务Redis事务是一组命令的集合。一个事务中的命令要么都执行,要么都不执行。如果命令在运行期间出现错误,不会自动回滚。

    (9)支持主-从(Master-Slave)复制与高可用(Redis Sentinel)集群(3.0版本以上)

    (10)支持管道Redis管道是指客户端可以将多个命令一次性发送到服务器,然后由服务器一次性返回所有结果。管道技术的优点是:在批量执行命令的应用场景中,可以大大减少网络传输的开销,提高性能。



    ZooKeeper的核心优势是,实现了分布式环境的数据一致性,简单地说:每时每刻我们访问ZooKeeper的树结构时,不同的节点返回的数据都是一致的。也就是说,对ZooKeeper进行数据访问时,无论是什么时间,都不会引起脏读、重复读。



    read系统调用,并不是直接从物理设备把数据读取到内存中;write系统调用,也不是直接把数据写入到物理设备。上层应用无论是调用操作系统的read,还是调用操作系统的write,都会涉及缓冲区。具体来说,调用操作系统的read,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。



    有了内存缓冲区,上层应用使用read系统调用时,仅仅把数据从内核缓冲区复制到上层应用的缓冲区(进程缓冲区);上层应用使用write系统调用时,仅仅把数据从进程缓冲区复制到内核缓冲区中。



    在Java服务器端,完成一次socket请求和响应,完整的流程如下:

    · 客户端请求:Linux通过网卡读取客户端的请求数据,将数据读取到内核缓冲区。

    · 获取请求数据:Java服务器通过read系统调用,从Linux内核缓冲区读取数据,再送入Java进程缓冲区。

    · 服务器端业务处理:Java服务器在自己的用户空间中处理客户端的请求。

    · 服务器端返回数据:Java服务器完成处理后,构建好的响应数据,将这些数据从用户缓冲区写入内核缓冲区。这里用到的是write系统调用。

    · 发送给客户端:Linux内核通过网络IO,将内核缓冲区中的数据写入网卡,网卡通过底层的通信协议,会将数据发送给目标客户端。



    阻塞IO,指的是需要内核IO操作彻底完成后,才返回到用户空间执行用户的操作。阻塞指的是用户空间程序的执行状态。传统的IO模型都是同步阻塞IO。在Java中,默认创建的socket都是阻塞的



    在Java应用程序进程中,默认情况下,所有的socket连接的IO操作都是同步阻塞IO(BlockingIO)



    总之,阻塞IO的特点是:在内核进行IO执行的两个阶段,用户线程都被阻塞了。



    阻塞IO的缺点是:一般情况下,会为每个连接配备一个独立的线程;反过来说,就是一个线程维护一个连接的IO操作。在并发量小的情况下,这样做没有什么问题。但是,当在高并发的应用场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上阻塞IO模型在高并发应用场景下是不可用的。



    异步IO模型的特点:在内核等待数据和复制数据的两个阶段,用户线程都不是阻塞的。用户线程需要接收内核的IO操作完成的事件,或者用户线程需要注册一个IO操作完成的回调函数。正因为如此,异步IO有的时候也被称为信号驱动IO。



    对于高并发、高负载的应用,就必须要调整这个系统参数,以适应处理并发处理大量连接的应用场景。可以通过ulimit来设置这两个参数。方法如下:

    ulimit -n  1000000000

    在上面的命令中,n的设置值越大,可以打开的文件句柄数量就越大。建议以root用户来执行此命令。


    终极解除Linux系统的最大文件打开数量的限制,可以通过编辑Linux的极限配置文件/etc/security/limits.conf来解决,修改此文件,加入如下内容:

    soft  nofile  1000000

    hard nofile 1000000

    soft nofile表示软性极限,hard nofile表示硬性极限。


    如果想永久地把设置值保存下来,可以编辑/etc/rc.local开机启动文件,在文件中添加如下内容:

    ulimit -SHn  1000000


    Java NIO由以下三个核心组件组成:· Channel(通道)· Buffer(缓冲区)· Selector(选择器)需要强调的是:Buffer类是一个非线程安全类。



    NIO的Buffer(缓冲区)本质上是一个内存块,既可以写入数据,也可以从中读取数据。NIO的Buffer类,是一个抽象类,位于java.nio包中,其内部是一个内存块(数组)。





    在调用allocate方法分配内存、返回了实例对象后,缓冲区实例对象处于写模式,可以写入对象。要写入缓冲区,需要调用put方法。put方法很简单,只有一个参数,即为所需要写入的对象。不过,写入的数据类型要求与缓冲区的类型保持一致。



    对flip()方法的从写入到读取转换的规则,详细的介绍如下:

    首先,设置可读的长度上限limit。将写模式下的缓冲区中内容的最后写入位置position值,作为读模式下的limit上限值。

    其次,把读的起始位置position的值设为0,表示从头开始读。最后,清除之前的mark标记,因为mark保存的是写模式下的临时位置。在读模式下,如果继续使用旧的mark标记,会造成位置混乱。




    调用flip方法,将缓冲区切换成读取模式。这时,可以开始从缓冲区中进行数据读取了。读数据很简单,调用get方法,每次从position的位置读取一个数据,并且进行相应的缓冲区属性的调整。



    已经读完的数据,如果需要再读一遍,可以调用rewind()方法。rewind()也叫倒带,就像播放磁.



    Buffer.mark()方法的作用是将当前position的值保存起来,放在mark属性中,让mark属性记住这个临时位置;之后,可以调用Buffer.reset()方法将mark的值恢复到position中。



    在读取模式下,调用clear()方法将缓冲区切换为写入模式。此方法会将position清零,limit设置为capacity最大容量值,可以一直写入,直到缓冲区写满。



    总体来说,使用Java NIO Buffer类的基本步骤如下:

    (1)使用创建子类实例对象的allocate()方法,创建一个Buffer类的实例对象。

    (2)调用put方法,将数据写入到缓冲区中。

    (3)写入完成后,在开始读取数据前,调用Buffer.flip()方法,将缓冲区转换为读模式。

    (4)调用get方法,从缓冲区中读取数据。

    (5)读取完成后,调用Buffer.clear() 或Buffer.compact()方法,将缓冲区转换为写入模式。


    相关文章

      网友评论

          本文标题:高并发基础知识(一)

          本文链接:https://www.haomeiwen.com/subject/qyqmnhtx.html