java 高并发_Java开发高并发使用及分布式系统设计与原理分析

引言

高并发就是可以使用多个线程或者多个进程,同时处理(就是并发)不同的的操作。比如说一个网站,同时访问的数量很多,就是高并发。想要实现并发就有多看这方面的资料了。知道了这个,高并发就知道了呗。

分布式系统(distributed system)是建立在网络之上的软件系统。正是因为软件的特性,所以分布式系统具有高度的内聚性和透明性。因此,网络和分布式系统之间的区别更多的在于高层软件(特别是操作系统),而不是硬件。内聚性是指每一个数据库分布节点高度自治,有本地的数据库管理系统。透明性是指每一个数据库分布节点对用户的应用来说都是透明的,看不出是本地还是远程。在分布式数据库系统中,用户感觉不到数据是分布的,即用户不须知道关系是否分割、有无副本、数据存于哪个站点以及事务在哪个站点上执行等。

一丶使用缓存服务器

使用Redis作为缓存服务器的,刚开始的时候会满足需要,随着项目的增大缓存数据的增多就会查询和插入更慢这时就要考虑Redis集群方案了

使用Redis分布式要保证数据都能能够平均的缓存到每一台机器,首先想到的做法是对数据进行分片,因为Redis是key-value存储的,首先想到的是Hash分片,可能的做法是对key进行哈希运算,得到一个long值对分布式的数量取模会得到一个一个对应数据库的一个映射,没有读取就可以定位到这台数据库,那么速度但然会提升了。

但是取模的hash算法是有问题的如果集群数量不变的话没有什么问题,一旦增加一台机器或者一台机器挂掉,导致机器数量变化,就会导致计算的出的数据库映射乱掉,不能正确存取数据了。

因为这个问题引入我们说的一致性哈希算法,这个哈希算法具有的特征

1.均衡性:也有人把它定义为平衡性,是指哈希的结果能够尽可能分布到所有的节点中去,这样可以有效的利用每个节点上的资源。

2.单调性:对于单调性有很多翻译让我非常的不解,而我想要的是当节点数量变化时哈希的结果应尽可能的保护已分配的内容不会被重新分派到新的节点。

3.分散性和负载:这两个其实是差不多的意思,就是要求一致性哈希算法对 key 哈希应尽可能的避免重复。

一致性哈希就数据结构是创建一个排序的环形数据结构,有许多个区域,先让每一台服务器都分布环上,取每一个服务器的特效做哈希运行,得到的值放进环中,进行排序这样就能根据哈希特征找到对应的真是服务器,能够让把服务器平均的分布到环上。

第一个特征均衡性:就是尽量的让数据平均的分部到每一个服务器,不让某台机器压力特别打,或者干脆没活干,因为这个原因,我们的每一个服务器都添加几个虚拟服务器,比如真是服务器叫node1那么第一个服务器的虚拟服务器就叫node1-1,node1-2...,根据这些特征进行哈希运算也分布到环中,这样就能把服务器平均的分布到环中。

第二个特征单调性:因为服务器都在环中,数据的key进行哈希运算得到一个值,跟环中的服务器的哈希值进行比较,取离当前值最接近的哈希值对象的服务器,这样就是获取服务器的原理了,我们是做了一个偷懒的工作,服务器哈希进行排序,以顺时针方式得到一个刚好大于key哈希的服务器。

单调性是在不管添加节点还是删除节点,原来对应的服务器不变,因为这个环很大,服务器是零星分布的,这样增加或者删除一个节点只有受影响的都是当前节点,但是key对应的数据库是不变的,也不能说不变,是把变化变得尽可能的小。

第三个特征分散性和负载:指服务器在环中尽可能的分散,尽可能的让数据平均分布到不同的服务器,我们就是使用虚拟节点的方式解决的。

其实在Linux中(我实在不知道Windows,我猜应该差不多)进程和线程是同一个数据结构——task_struct,对于内核(kernel)来说并没有进程和线程的区别,只有进程——kernel称之为task。所以 在Linux中进程和线程并没有父子关系而是平行的结构 ,表示进程的数据结构填充的数据多一些,包括了打开文件,共享内存之类的,这个被称为主线程;其他线程的数据结构这些项目则为空,并且有一个“父进程”的指针,指向了“主线程”。

明白了这一点我们就清楚了, 操作系统调度的最小对象其实是——线程 ,但是名字叫task,教科书上叫进程。。。。有点混乱了吗?所以我们引入一个新的术语—— 并发实体 。所有CPU调度的最小单位我们统称为并发实体,无论是进程还是线程或者是其他的什么“怪胎”。(没错,我会在下一次介绍这些怪胎。)

二丶为什么要线程?

勇敢提出这个问题的人要受到表扬,他冒着被无情嘲讽的危险提出了一个很白痴的问题。(我觉得回答不出来这个问题的人,才是真正的白痴)回答这个问题要回答另一个基本的问题——为什么要并发

我们想象一下,一个Web服务器,可能是下面的代码

while (true){request = next_http_request()request_work(request)}

程序循环获取新请求(next_http_request),执行请求(request_work),然后继续下一次循环。request_work会从硬盘读取文件,然后发送给客户端作为HTTP的响应,而硬盘I/O是一个阻塞操作,也就是说request_work会一直等待读取完数据之后才能释放CPU的控制权,然后下一个请求才有机会被执行。

这就是并发要解决的问题,当request_work发起I/O之后CPU是完全空闲下来的,而可怜的新请求(next_http_request)必须等待I/O完成之后才可以获取CPU的控制权。所以CPU的利用率非常低, 并发要解决的问题就是提高CPU的利用率 。明白这一点我们也就清楚了,并发只对非CPU密集型程序管用,如果CPU利用率非常高,更多的并发只会让情况更加糟糕

那么并发为什么一定是多线程而不是多进程呢?其实在Linux下进程和线程的创建成本没有什么区别(都是task_struct),但是进程之间可以共享数据的方式只能通过非常复杂的IPC来实现, 线程之间代码都是共享的,地址空间也是共享的,所以共享数据的方式更加高效。 (进程要考虑隔离,一个进程没有办法直接访问另一个进程;线程不用隔离,线程之间共享内存)

我们修改成多线程版本的Web服务器

while (true){request = next_http_request()request_work_in_thread(request)}

request_work_in_thread方法会启动一个线程(work线程),然后CPU开始执行next_http_request获得下一个请求。

request对象是在主线程创建的,可以直接传递给request_work_in_thread中的work线程使用。

我们提高CPU利用率所以需要并行,我们要提高并发实体之间共享数据的效率所以选择了线程作为并发实体的实现

三丶Java的线程

好了,回到了Java。在Java中启动一个线程非常简单——只要new Thread就搞定了。JVM会把它变成操作系统的API,如果是Linux则会生成一个task_struct的结构。至于Runable之类的东西其实最后还是Thread,即便是Java并发包最后也还是用Thread。

所以至此,我们成功把《操作系统原理》中进程、线程和Java的线程“融汇贯通了”。下面开始另一个东西——PV操作。

竞争条件,临界区、PV信号量

恩,你这一部分估计也已经还给老师了。没关系,我们一起回忆一下。

举个例子:

public void plus(int value){count = count + value;}

当多线程同时调用plus的时候程序的逻辑是错误的。count+value并不是一个原子操作,它会被变成三个CPU指令

  • 获取count的数据到寄存器(还记得吗?CPU只访问寄存器)
  • 寄存器+value,并且写回寄存器
  • 寄存器写回到内存
  • 如果T1,T2两个线程同时执行(count=0)
  • T1 获取count的数据到寄存器
  • T1 将寄存器的值加10
  • T2 获取count的数据到寄存器
  • T2 将寄存器的值加30
  • T2 寄存器写回到内存(count=30)
  • T1 寄存器写回到内存(count=10)
  • 我们期望的可能是40,但是实际情况是10,因为T2访问数据的时候T1还没有来得及写回到内存中。当两个线程访问同一个数据,最后的结果依赖于线程的顺序这个就叫竞争条件。避免竞争条件的方法就是——通过临界区把一组动作“原子化”。例子中就是把:count = count + value,原子化。(原子化是指一次做完;其他人排队等候。)

就像你去买咖啡,收银员是所有人共享的(竞争条件),如果他经历:问杯型、种类、口味;收费;给你发票,这三个过程不能被打断否则会乱掉的。所以大家需要依次排队。(临界区)

如何实现临界区?答案是PV信号量(也叫PV操作,PV原语)。它是著名“河南籍”计算机科学家——E.W.Dijkstra设计的一套算法,老爷子这套严密的理论是现代并发的基础。简单来说他定义了两个操作

设一个计数器s

  • P s-1,如果s小于0则休眠否则继续执行
  • V s+1,如果s<=0则唤醒等待进程否则继续执行

P操作相当于使用资源,执行这个操作相当于:这个桌子我承包了,你们等我用过之后再用。(如果桌子有人那就只能乖乖等着了)

V操作相当于释放资源,执行这个操作相当于:这个桌子我不用了,下一个是谁?来用吧。(如果没有下一个人,那就直接走人了)

没错,PV就是“锁”。《操作系统原理》中竞争条件、临界区都是现实中不存在的概念,只有PV操作被具体实现了,就是我们称之为锁的东西。

四丶Java中的锁

所有的“锁”都是一种PV操作,锁的区别在于你选择的“计数器”是什么?比如你选择的计数器是当前对象那么对应的关键是“synchronized”(还有个名字叫管程,天真的科学家们觉得面向对象的锁好牛B,就赐予它一个专门的名词),如果你的计数器是一个原子类型的值那么可能就是AtomicInteger的inc或者dec操作。这个就是 锁的粒度 ,锁越小竞争条件就越小。

五丶分布式服务框架设计

分布式服务框架一般可以分为以下几个部分,

(1)RPC基础层:

包括底层通信框架,如NIO框架、通信协议,序列化和反序列化协议,

以及在这几部分上的封装,屏蔽底层通信细节和序列化方式差异

(2)服务发布/消费:

服务提供者根据消费者请求消息中的接口名,方法名,参数列表等信息,通过Java反射,调用本地的接口实现类;

服务消费者将服务提供者发布的接口封装成远程服务调用;

(3)服务调用链:

在服务调用的职责链中,通过在调用链切面的编码完成相关的监控和扩展,如负载均衡,服务调用性能统计,调用完成通知,

失败重发等功能

(4)服务注册中心:

注册中心负责服务的发布和通知,需要支持服务的平滑上线下线等

(5)服务治理中心:

服务治理中心是一个可视化的模块,提供对服务的可视化分析和维护,包括服务运行状态,调用关系和健康度等

六丶分布式系统原理

1. 哈希方式,把不同的值进行哈希运算,映射到,不同的机器或者节点。考虑冗余的时候可以把多个哈希值映射到同一个地方。哈希的实现方式,取余。其实现扩展时,比较困难,数据分散在很多机器上,扩展的时候要从个机器上获取数据。而且容易出现分布不均有的情况。

常见的哈希,用IP、URL、ID、或者固定的值进行哈希,总是得到相同的结果。

2. 按数据范围分布,比如ID在1~20的在机器A上,ID在21~40的在机器B上,ID在40~60的在机器C上实现,ID在60~100的分布在机器D上,数据分布比较均匀。如果某个节点处理能力有限,可以直接分裂该节点。维护数据分布的元信息,可能出现单点瓶颈。几千机器,每个机器又划分为N个范围,导致需要维护的数据分布范围元数据过大,导致可能需要几台机器实现。

一定要严格控制元数据量,进可能的减少元数据的存储。

3. 按数据量分布,另一类常用的数据分布方式则是按照数据量分布数据。与哈希方式和按数据范围方式不同,数据量分布数据与具体的数据特征无关,而是将数据视为一个顺序增长的文件,并将这个文件按照某一较为固定的大小划分为若干数据块(chunk),不同的数据块分布到不同的服务器上。与按数据范围分布数据的方式类似的是,按数据量分布数据也需要记录数据块的具体分布情况,并将该分布信息作为元数据使用元数据服务器管理。

由于与具体的数据内容无关,按数据量分布数据的方式一般没有数据倾斜的问题,数据总是被均匀切分并分布到集群中。当集群需要重新负载均衡时,只需通过迁移数据块即可完成。集群扩容也没有太大的限制,只需将部分数据库迁移到新加入的机器上即可以完成扩容。按数据量划分数据的缺点是需要管理较为复杂的元信息,与按范围分布数据的方式类似,当集群规模较大时,元信息的数据量也变得很大,高效的管理元信息成为新的课题。

4. 一致性哈希,构造哈希环,有哈希域[0,10],则构造3个部分,[1,4)/[4,9)/[9,10),[0,1)/分成了3个部分,这3部分是一个环状,增加机器时,变动的是其附近的节点,分担的是附近节点的压力,其元数据的维护和按数据量分布一样。其未来的扩展,可以实现多个需节点。

5. 构建映射元数据,建立映射表的方式。

6. 副本与数据分布,把一个数据副本分散到多台服务器上。比如应用A的数据,存储在A、B、C ,3台机器上,如果3台机器中,其中一台出现问题,请求被处理到其他2台机器上,如果加机器恢复,还需要从另外2台机器上,Copy数据,又增加了这2台机器的负担。如果我们有应用A和应用B,各自有3台机器,那么我们可以把A应用分散在6台机器上,B应用也分散在6台机器上,可以实现相同的数据备份,但是应用存储的数据被分散了。某台机器损害,只是把该机器所承担的负载平均分配到了,另外5台机器上。恢复数据从5台机器恢复,其速度快和给各台服务器的压力都不大,而且可以实现机器损害,更换完全不影响应用。

其原理是多个机器互为副本,是比较理想的实现负载分压的方式。

7. 分布式计算思想,移动数据不如移动计算,就进计算原则,减少跨进程、跨网络、等跨度较大的实现,把计算所需的资源尽可能的靠近。因为可能出现网络、远程机器的瓶颈。

8. 常见分布式系统数据分布方式: GFS、HDFS:按数据量分布;Map reduce 按GFS的数据分布做本地化;BigTable、HBase按数据范围分布;Pnuts按哈希方式或者数据范围分布,可以选择;Dynamo、Cassndra按一致性哈希;Mola、Armor、BigPipe按哈希方式分布;Doris按哈希方式和按数据量分布组合。

总结

以 上就是我对Java开发高并发使用及分布式系统设计与原理分析

问题及其优化总结,分享给大家,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!

最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!

进阶地址:https://ke.qq.com/course/230866?flowToken=1000327

想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:头条(06 必过)欢迎大家的加入哟!

65146d1776f1509a195e8e9949989efd.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值