原理_原理图 - CSDN
精华内容
参与话题
  • 比特币原理详解

    万次阅读 多人点赞 2020-03-06 10:49:34
    一、什么是比特币 比特币是一种电子货币,是一种基于密码学的货币,在2008年11月1日由中本...其好处不多做赘述,这一层面介绍的文章很多,本文主要从更深层的技术原理角度进行介绍。 二、问题引入 假设现有4个人...

    一、什么是比特币

    比特币是一种电子货币,是一种基于密码学的货币,在2008年11月1日由中本聪发表比特币白皮书,文中提出了一种去中心化的电子记账系统,我们平时的电子现金是银行来记账,因为银行的背后是国家信用。去中心化电子记账系统是参与者共同记账。比特币可以防止主权危机、信用风险。其好处不多做赘述,这一层面介绍的文章很多,本文主要从更深层的技术原理角度进行介绍。

    二、问题引入

     假设现有4个人分别称之为ABCD,他们之间发起了3个交易,A转给B10个比特币,B转给C5个比特币,C转给D2个比特币。如果是传统的记账方式,这些交易会记录在银行的系统中,这些信息由银行来记录,我们相信银行不会随意添加、删除或修改一条交易记录,我们也不会关注到底有哪些交易,我们只关注自己的账户余额。而比特币的记账方式为ABCD每个人保存了这样一份账本,账本上记录了上述交易内容,如果每个人账本实时的一致,ABCD就不再需要银行。
            比特币是这样做的,每当有人发起一笔交易,他就要将一笔交易广播至全网,由全网中的某一个人,把一段时间内的交易打包好记录到一个区块上,再按照顺序把这些区块,一个一个的链接在一起,进而形成了一个链条,这就是所谓的区块链。

     

    那么问题来了

    1、我凭什么要参与这个系统,我为什么要动用自己的计算机资源来存储这些信息呢?

    2、以谁的记录为准呢?比如上面的账单顺序,A用户可能是这个顺序,但是B可能顺序不一样,甚至可能B根本就没有接收到C给D转账的这个消息。

    3、比特币如果做到支付功能,保证该是谁的钱就是谁的钱,而且只有其所有者才能花。

    4、如何防伪、防篡改以及双重支付,防伪是验证每条交易的真的是某人发出的,比如B可能杜撰一条消息,说某某给我转了一笔钱,这就是一个假消息,或者B说我给某人转了多少钱,但是实际上他并没有这么多钱,又怎么办。防篡改指的是B可能想从区块链上把自己曾经转给某人钱的记录删掉,这样他的余额就会增加。双重支付是指,B只有10比特币,他同时向C和D转10个比特币,造成双重花费。

    三、为什么要记账?

    因为记账有奖励,记账有手续费的收益,而且打包区块的人有系统奖励,奖励方案是,每十分钟生成一个区块,每生成一个区块会奖励一定数量的比特币,最开始是50个BTC,过4年会奖励25个BTC,再过4年再减少一半,以此类推。这样比特币的产生会越来越少,越来越趋近于一个最大值,计算公式是:50×6×24×365×4×(1+1/2+1/4+1/8+…)≈2100万,其中最初奖励50个比特币,每小时有6个区块,每天24小时,每年365天,前四年是如此,之后每四年减半。

    此外,记账奖励还有每笔交易的小额手续费,每个交易发起都会附带一定的手续费,这些手续费是给记账的矿工的。

    四、以谁为准?

    各个节点通过工作量证明机制来争夺记账权,他们计算一个很复杂的数学题,第一个计算出来的节点就是下一个区块的产生者。这个数学题很难,难到没有一个人能同过脑子算出来,它是基于概率的方法,矿工必须通过遍历、猜测和尝试的办法才能解开这个未知数。那么这个数学难题到底是什么呢?下面详细介绍。

    4.1哈希函数

    哈希函数又称为数字摘要或散列函数,它的特点是输入一个字符串,可以生成另外一个字符串,但是如果输入不同,输出的字符串就一定不同,而且通过输出的字符串,不能反推出输入。举个简单的例子,对1-100内的数模10,可以认为是一种哈希方法,比如98%10=8,66%10=6,98和66是输入,模10是哈希函数,8和6是输出,在这个模型中,通过6和8无法推断输入是66和98,因为还可能是56和88等,当然因为这个例子比较简单,所以会出现哈希碰撞,即66和56的结果都是6,输出的结果相同。一个优秀的哈希函数,可以做到输出一定不同,哈希碰撞的概率几乎为0。常见的哈希函数有很多,比如MD系列和SHA系列等,比特币采用的SHA256算法,即输入一个字符串,输出一个256位的二进制数。下面是程序运行的结果。

    通过程序结果可以看出,输入的源信息不同,得到的结果也不同(为了方便,结果用64位16进制表示),即使是orange多了一个句号,也会产生截然不同的结果。同时,通过输出的十六进制字符串,也无法倒推出输入。对于比特币,只要了解SHA256的功能即可,如果感兴趣可以深入了解SHA256的具体算法。需要SHA256的C++源码留言邮箱或私信。

    4.2挖矿原理

    首先介绍一下比特币每个区块的数据结构,每个区块由区块头和区块体两部分组成。

    区块体中包含了矿工搜集的若干交易信息,图中假设有8个交易被收录在区块中,所有的交易生成一颗默克尔树,默克尔树是一种数据结构,它将叶子节点两两哈希,生成上一层节点,上层节点再哈希,生成上一层,直到最后生成一个树根,称之为默克尔树根,只有树根保留在区块头中,这样可以节省区块头的空间,也便于交易的验证。

    区块头中包含父区块的哈希,版本号,当前时间戳,难度值,随机数和上面提到的默克尔树根。

     

     

    假设区块链已经链接到了某个块,有ABCD四个节点已经搜集了前十分钟内全网中的一些交易信息,他们选出其中约4k条交易,打包好,生成默克尔树根,将区块头中的信息,即发区块哈希+版本号+时间戳+难度值+随机数+默克尔树根组成一个字符串str,通过两次哈希函数得出一个256的二进制数,即SHA256(SHA256(str)) = 10010011……共256位,比特币要求,生成的结果,前n位必须是0,n就是难度值,如果现在生成的二进制数不符合要求,就必须改变随机数的值,重新计算,只到算出满足条件的结果为止。假设现在n是5,则生成的二进制数必须是00000……(共256位)。一旦挖矿成功,矿工就可以广播这个消息到全网,其他的矿工就会基于该区块继续挖矿。下一个区块头中的父区块哈希值就是上一个区块生成的00000……这个数。

    解决这个数学难题要靠运气,理论上,运气最好的矿工可能1次哈希就能算出结果,运气差的可能永远都算不出来。但是总体来看,如果一个矿工的算力越大,单位时间内进行的哈希次数就越多,就越可能在短时间内挖矿成功。

    那么n是如何确定的呢?比特币设计者希望,总体上平均每十分钟产生一个区块,总体上来看,挖矿成功的概率为1/2^n。现假设世界上有1W台矿机,每台矿机的算力是14T次/s = 1.4×10^13次/s,单位次/s称之为哈希率,10分钟是600s,所以10分钟可以做8×10^19次哈希运算,从概率角度看,想要挖矿成功需要做2^n次运算,可以列出等式2^n = 8×10^19,可以解出n约为66。所以对于这种方法,我们没有办法使得自己的运气变的更好,只能提高自己的算力,尽快的算出结果。

    另外,需要模拟挖矿过程的C++代码可以回复邮箱,代码可以通过调整难度值,模拟比特币的挖矿算法,控制区块产生的速度。

    五、如何防伪、防篡改、防双重支付等问题

    这部分是理解比特币很重要的部分。

    5.1电子签名技术

    身份认证技术在生活中很常见,可以是人脸识别、签字、指纹等,但是这些方法在数字货币领域并不安全,因为它们一旦数字化,都可以通过复制的方法伪造。所以比特币采用了电子签名的方法。

    注册成为比特币用户时,系统会根据随机数生成一个私钥,私钥会生成一个公钥,公钥又会生成一个地址,其中私钥必须保密,可以保存到硬盘里或者记到脑子里,因为这个私钥是使用相应地址上的比特币的唯一标识,一旦丢失,所有的比特币将无法使用。下面介绍具体的转换过程,不感兴趣可以不看,只要知道随机数->私钥->公钥->钱包地址这个过程,其中私钥可以对一串字符进行加密,而公钥可以对其进行解密,这就是非对称加密,这类算法总体上的功能都是一样的,只是具体算法有区别,由于这些算法比较复杂,与SHA265算法一样不多做介绍,感兴趣可以深入了解具体算法,但是对于比特币系统,只要了解其功能即可。典型的算法是RSA,比特币采用椭圆曲线加密算法。

    转换过程(选读,不影响理解)

        1、首先使用随机数发生器生成一个私钥,它是一个256位的二进制数。私钥是不能公开的,相当于银行卡的密码。

        2、私钥经过SECP256K1算法生成公钥,SECP256K1是一种椭圆曲线加密算法,功能和RSA算法类似,通过一个已知的私钥,生成一个公钥,但是通过公钥不能反推出私钥。

        3、同SHA256算法一样,RIPEMD160也是一种HASH算法,由公钥可以得到公钥的哈希值,而通过哈希值无法推出公钥。

        4、将一个字节的版本号连接到公钥哈希头部,然后对其进行两次SHA256运算,将结果的前4字节作为公钥哈希的校验值,连接在其尾部。

        5、将上一步的结果使用BASE58进行编码,就得到了钱包地址(相当于银行账户)。比如A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa

    所以,通过以上的过程我们可以总结出私钥、公钥、钱包之间的关系如下图。可以看到通过私钥可以推出所有的值,公钥哈希和钱包地址之间可以通过BASE58和BASE58解码算法相互转化。

    了解了公钥、私钥、地址的概念后,防伪验证的过程就很容易理解,当A发起一笔交易后,对消息进行哈希,生成数字摘要,对数字摘要,通过私钥加密,生成一个密码。之后A会广播这个条交易消息、公钥以及密码。收到消息的人首先对交易信息进行哈希生成摘要1,再通过公钥对密码进行解密,生成摘要2,这样,如果两个摘要相同,说明这个消息确实是A发出的。所谓的签名,就是密文。

     

    5.2余额检查

    余额的概念应该说根深蒂固,余额是伴随着称之为借贷记账法而产生的,也是目前银行普遍采用的方法,将一个人的交易记录统计好后算出一个余额,但是在比特币中没有余额这个概念,因为其采用的是UXTO模型的记账方法。比如A->B10个比特币,B->C5个比特币,对于第二笔交易来说,B在发起这笔交易时要注明第一笔交易的信息,这样就可以知道B曾经从A那里收到过10个比特币,说明满足第二笔交易发起的条件。所以比特币中余额的检查是通过追溯的方法。

    上图描述了两笔交易,交易10001中,B向C转了10个比特币,验证这笔交易的过程是:首先将B的签名通过B的公钥解密,然后再和交易的具体内容(B签名左侧)对比,如果相同,说明消息是B发出的,然后再检查10000这个交易是否真的存在以及它的内容的真实性。这两点都满足了,就说明交易10001是可以被接受的,否则拒绝接受。

    实际上,真实的交易比这个复杂的多,因为有可能是多笔交易构成了输入,比如B->C20个比特币,是由多笔交易A->B10,D->B10构成的,则前一笔交易ID就是两个ID,甚至可能更多。这里为了简单描述,只列举一笔交易。

    5.3双重支付

    A同时发了两条消息,同时给B和C转了10个比特币,实际上他只有10个会怎么样?假设D节点先收到了转给B10个BTC,然后收到了转给C10个比特币,通过上面的验证方法,自然会拒绝后面的一个,与此同时,E节点可能先收到了转给C10个BTC,然后收到了转给B10个比特币,他自然会拒绝后者。至于哪一笔交易最终会上链,就要看D和E哪个先解决难题,成功挖矿。

    5.4防止篡改

    假设A转给B10个比特币,但是他想把这个信息从区块链上删除,这样大家就都不知道这个事情存在,就可以赖账。
            首先说一下最长链原则,假设某一个区块后面有两个矿工同时挖到了矿,或者由于网络延迟等原因产生了分歧,这时,各个节点先随意根据自己认为对的区块挖矿,只到下一个区块产生,这时会有两条链,但是有一条是长的,比特币规定,以最长的链为准。如果某个节点仍然的固执的以较短的链为准,他就是在和大多数算力作对,这样做的结果是,他挖的块不被大家认可,会浪费时间和算力。

    回到上面的场景,A想赖账,就只能从记录了A->B10个比特币这个消息的区块的前一个区块开始重新挖矿,造出一个支链来,但是实际上的区块已经前进了很多,他只能不停的追赶,而且在追赶的同时,主链也在前进,他必须以比主链快的速度前进,如果他的算力足够大,理论上通过较长的时间确实可以追赶成功,就实现了对交易信息的篡改。然而其实这几乎是不可能的,因为就算算力再大,平均出块速度也是10分钟,从非技术的角度讲,一个人如果掌握了全网一半以上的算力,他为什么不在主链上继续挖矿呢?一个富可敌国的人应该不会甘愿去做一个小偷吧。 

    六、总结

    区块链并不等同于比特币,比特币也不是区块链,区块链只是比特币应用的一种技术,这个技术能给我们带来启发,比特币的伟大之处在于应用了前所未有的区块链技术。区块链技术还能在哪些方面应用还需继续探索。

    比特币是区块链技术最成功的应用,但是比特币本身也有很多问题,它想通过发行货币来挑战主权货币,这个动机有待商榷。此外,由于比特币的匿名性,只需要一个公钥或地址就能进行交易,为黑色产业提供了很好的平台。另外,比特币并不是一个成熟的支付系统,它具有吞吐率低,可拓展性差等缺点。

    可能文字还是比较苍白,可以看看李永乐老师讲解的视频,虽然没有这个详细,但是通俗易懂。

    七、代码实现

    点击这里!

    展开全文
  • 程序安装原理

    千次阅读 2016-08-13 15:29:38
    不同的操作系统,软件安装方法虽然不一样,但是基本的原理是一样的,主要的思路就是讲程序生成的二进制可执行bin文件拷贝到某个目录,设置一些路径。如果程序运行需要一些库,将这些库拷贝到系统目录即可。基本上都...

    不同的操作系统,软件安装方法虽然不一样,但是基本的原理是一样的,主要的思路就是讲程序生成的二进制可执行bin文件拷贝到某个目录,设置一些路径。如果程序运行需要一些库,将这些库拷贝到系统目录即可。

    基本上都要经过四个基本的操作步骤:

    1)将程序可执行文件从安装源位置拷贝到目标位置。
    2)如果有必要,往系统目录拷入一些必要的动态链接库(DLL)。(可选操作)
    3)向系统注册表中写入相应的设置项,注册程序或者库的安装信息。(可选操作)
    4)在开始菜单或者桌面上给程序建立快捷方式。(可选操作)

    一般我们在安装程序时,安装程序会默认给我们指定一个安装目录,如果我们需要自定义安装,可以手动设置我们的程序安装目录,设置好以后,点击安装,安装程序将会将安装文件嗖地一声拷贝到目标目录。一般所谓的绿色软件一般只有这一步,安装到了这一步,就可以直接使用该软件了。
    有的程序,比如大型的游戏,可能会需要很多动态链接库DLL文件,这时候安装程序可能会将这些动态链接库拷贝到系统的库的默认目录,像Win7下一般拷贝到C:\Windows\System32下面,如果不信的话,你打开该目录,可以看到该目录下有很多DLL文件。有些程序可能用到的DLL文件不是系统必需的,而是自己的程序单独使用的,这样子放在系统的官方目录里貌似不太合适,安装的时间长了,会造成系统臃肿,于是,这些库可能会安装在程序的安装目录下面。
    一般在安装前,用户可能会对软件做一些设置和选项,在安装时,安装程序会把这些设置或选项写到注册表里。还有就是当安装程序将DLL文件拷贝到系统目录时,一些DLL需要向系统注册,告诉系统我在这里,不然系统使用的时候可能找不到哦。
    最后一步,为了方便用户使用,可能会在桌面上或者开始菜单启动栏里设置程序的快捷方式。
    从程序的安装原理我们可以看到一个程序的总体情况了:首先,程序是要放到硬盘上存储起来的,当要运行程序时,系统会将程序二进制文件搬运到内存RAM里,接着CPU的指令控制器PC就会一行一行地从内存里去取指令并执行。这就是冯-伊诺曼计算机体系结构的核心原理。

    展开全文
  • 通信原理MATLAB仿真教程 《通信原理MATLAB仿真教程》系统地介绍了通信原理MATLAB仿真的基本思想与方法,重点讨论了MATLAB对常见信号与线性系统、模拟调制、模拟信号的数字传输、数字信号的基带传输、数字信号的频带...
  • 微服务,你得知道这些!(底层原理

    千次阅读 多人点赞 2018-12-11 16:00:33
    目录 一、业务场景介绍 二、Spring Cloud核心组件:Eureka 三、Spring Cloud核心组件:Feign 四、Spring Cloud核心组件:Ribbon 五、Spring Cloud核心组件:Hystrix ...六、Spring Cloud核心组件:Zuul ...

    目录

    一、业务场景介绍

    二、Spring Cloud核心组件:Eureka

    三、Spring Cloud核心组件:Feign

    四、Spring Cloud核心组件:Ribbon

    五、Spring Cloud核心组件:Hystrix

    六、Spring Cloud核心组件:Zuul

    七、总结

     

     

    概述

    毫无疑问,Spring Cloud是目前微服务架构领域的翘楚,无数的书籍博客都在讲解这个技术。不过大多数讲解还停留在对Spring Cloud功能使用的层面,其底层的很多原理,很多人可能并不知晓。因此本文将通过大量的手绘图,给大家谈谈Spring Cloud微服务架构的底层原理。

    实际上,Spring Cloud是一个全家桶式的技术栈,包含了很多组件。本文先从其最核心的几个组件入手,来剖析一下其底层的工作原理。也就是Eureka、Ribbon、Feign、Hystrix、Zuul这几个组件。

     

    一、业务场景介绍
    先来给大家说一个业务场景,假设咱们现在开发一个电商网站,要实现支付订单的功能,流程如下:

    创建一个订单之后,如果用户立刻支付了这个订单,我们需要将订单状态更新为“已支付”

    扣减相应的商品库存

    通知仓储中心,进行发货

    给用户的这次购物增加相应的积分

     

     

    针对上述流程,我们需要有订单服务、库存服务、仓储服务、积分服务。整个流程的大体思路如下:

    用户针对一个订单完成支付之后,就会去找订单服务,更新订单状态

    订单服务调用库存服务,完成相应功能

    订单服务调用仓储服务,完成相应功能

    订单服务调用积分服务,完成相应功能

    至此,整个支付订单的业务流程结束

     

    下图这张图,清晰表明了各服务间的调用过程:

     

    好!有了业务场景之后,咱们就一起来看看Spring Cloud微服务架构中,这几个组件如何相互协作,各自发挥的作用以及其背后的原理。

     

     

    二、Spring Cloud核心组件:Eureka

    咱们来考虑第一个问题:订单服务想要调用库存服务、仓储服务,或者是积分服务,怎么调用?

    订单服务压根儿就不知道人家库存服务在哪台机器上啊!他就算想要发起一个请求,都不知道发送给谁,有心无力!

    这时候,就轮到Spring Cloud Eureka出场了。Eureka是微服务架构中的注册中心,专门负责服务的注册与发现。

     咱们来看看下面的这张图,结合图来仔细剖析一下整个流程:

     

     

    如上图所示,库存服务、仓储服务、积分服务中都有一个Eureka Client组件,这个组件专门负责将这个服务的信息注册到Eureka Server中。说白了,就是告诉Eureka Server,自己在哪台机器上,监听着哪个端口。而Eureka Server是一个注册中心,里面有一个注册表,保存了各服务所在的机器和端口号

    订单服务里也有一个Eureka Client组件,这个Eureka Client组件会找Eureka Server问一下:库存服务在哪台机器啊?监听着哪个端口啊?仓储服务呢?积分服务呢?然后就可以把这些相关信息从Eureka Server的注册表中拉取到自己本地缓存起来。

    这时如果订单服务想要调用库存服务,不就可以找自己本地的Eureka Client问一下库存服务在哪台机器?监听哪个端口吗?收到响应后,紧接着就可以发送一个请求过去,调用库存服务扣减库存的那个接口!同理,如果订单服务要调用仓储服务、积分服务,也是如法炮制。

    总结一下:

    Eureka Client:负责将这个服务的信息注册到Eureka Server中

    Eureka Server:注册中心,里面有一个注册表,保存了各个服务所在的机器和端口号

     

    三、Spring Cloud核心组件:Feign
    现在订单服务确实知道库存服务、积分服务、仓库服务在哪里了,同时也监听着哪些端口号了。但是新问题又来了:难道订单服务要自己写一大堆代码,跟其他服务建立网络连接,然后构造一个复杂的请求,接着发送请求过去,最后对返回的响应结果再写一大堆代码来处理吗?

    这是上述流程翻译的代码片段,咱们一起来看看,体会一下这种绝望而无助的感受!!!

    友情提示,前方高能:

    看完上面那一大段代码,有没有感到后背发凉、一身冷汗?实际上你进行服务间调用时,如果每次都手写代码,代码量比上面那段要多至少几倍,所以这个事儿压根儿就不是地球人能干的。

    既然如此,那怎么办呢?别急,Feign早已为我们提供好了优雅的解决方案。来看看如果用Feign的话,你的订单服务调用库存服务的代码会变成啥样?

    看完上面的代码什么感觉?是不是感觉整个世界都干净了,又找到了活下去的勇气!没有底层的建立连接、构造请求、解析响应的代码,直接就是用注解定义一个 FeignClient接口,然后调用那个接口就可以了。人家Feign Client会在底层根据你的注解,跟你指定的服务建立连接、构造请求、发起靕求、获取响应、解析响应,等等。这一系列脏活累活,人家Feign全给你干了。

     

     

    那么问题来了,Feign是如何做到这么神奇的呢?很简单,Feign的一个关键机制就是使用了动态代理。咱们一起来看看下面的图,结合图来分析:

    首先,如果你对某个接口定义了@FeignClient注解,Feign就会针对这个接口创建一个动态代理

    接着你要是调用那个接口,本质就是会调用 Feign创建的动态代理,这是核心中的核心

    Feign的动态代理会根据你在接口上的@RequestMapping等注解,来动态构造出你要请求的服务的地址

    最后针对这个地址,发起请求、解析响应

     

     

    四、Spring Cloud核心组件:Ribbon
    说完了Feign,还没完。现在新的问题又来了,如果人家库存服务部署在了5台机器上,如下所示:

    192.168.169:9000

    192.168.170:9000

    192.168.171:9000

    192.168.172:9000

    192.168.173:9000

     

    这下麻烦了!人家Feign怎么知道该请求哪台机器呢?

    这时Spring Cloud Ribbon就派上用场了。Ribbon就是专门解决这个问题的。它的作用是负载均衡,会帮你在每次请求时选择一台机器,均匀的把请求分发到各个机器上

    Ribbon的负载均衡默认使用的最经典的Round Robin轮询算法。这是啥?简单来说,就是如果订单服务对库存服务发起10次请求,那就先让你请求第1台机器、然后是第2台机器、第3台机器、第4台机器、第5台机器,接着再来—个循环,第1台机器、第2台机器。。。以此类推。

    另外,Ribbon是和Feign以及Eureka紧密协作,完成工作的,具体如下:

    首先Ribbon会从 Eureka Client里获取到对应的服务注册表,也就知道了所有的服务都部署在了哪些机器上,在监听哪些端口号。

    然后Ribbon就可以使用默认的Round Robin算法,从中选择一台机器

    Feign就会针对这台机器,构造并发起请求。

    对上述整个过程,再来一张图,帮助大家更深刻的理解:

    五、Spring Cloud核心组件:Hystrix

    在微服务架构里,一个系统会有很多的服务。以本文的业务场景为例:订单服务在一个业务流程里需要调用三个服务。现在假设订单服务自己最多只有100个线程可以处理请求,然后呢,积分服务不幸的挂了,每次订单服务调用积分服务的时候,都会卡住几秒钟,然后抛出—个超时异常。

     

    咱们一起来分析一下,这样会导致什么问题?

    如果系统处于高并发的场景下,大量请求涌过来的时候,订单服务的100个线程都会卡在请求积分服务这块。导致订单服务没有一个线程可以处理请求

    然后就会导致别人请求订单服务的时候,发现订单服务也挂了,不响应任何请求了

    上面这个,就是微服务架构中恐怖的服务雪崩问题,如下图所示:

     

    如上图,这么多服务互相调用,要是不做任何保护的话,某一个服务挂了,就会引起连锁反应,导致别的服务也挂。比如积分服务挂了,会导致订单服务的线程全部卡在请求积分服务这里,没有一个线程可以工作,瞬间导致订单服务也挂了,别人请求订单服务全部会卡住,无法响应。

    但是我们思考一下,就算积分服务挂了,订单服务也可以不用挂啊!为什么?

    我们结合业务来看:支付订单的时候,只要把库存扣减了,然后通知仓库发货就OK了

    如果积分服务挂了,大不了等他恢复之后,慢慢人肉手工恢复数据!为啥一定要因为一个积分服务挂了,就直接导致订单服务也挂了呢?不可以接受!

    现在问题分析完了,如何解决?

    这时就轮到Hystrix闪亮登场了。Hystrix是隔离、熔断以及降级的一个框架。啥意思呢?说白了,Hystrix会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。

    打个比方:现在很不幸,积分服务挂了,会咋样?

    当然会导致订单服务里的那个用来调用积分服务的线程都卡死不能工作了啊!但是由于订单服务调用库存服务、仓储服务的这两个线程池都是正常工作的,所以这两个服务不会受到任何影响。

    这个时候如果别人请求订单服务,订单服务还是可以正常调用库存服务扣减库存,调用仓储服务通知发货。只不过调用积分服务的时候,每次都会报错。但是如果积分服务都挂了,每次调用都要去卡住几秒钟干啥呢?有意义吗?当然没有!所以我们直接对积分服务熔断不就得了,比如在5分钟内请求积分服务直接就返回了,不要去走网络请求卡住几秒钟,这个过程,就是所谓的熔断!

    那人家又说,兄弟,积分服务挂了你就熔断,好歹你干点儿什么啊!别啥都不干就直接返回啊?没问题,咱们就来个降级:每次调用积分服务,你就在数据库里记录一条消息,说给某某用户增加了多少积分,因为积分服务挂了,导致没增加成功!这样等积分服务恢复了,你可以根据这些记录手工加一下积分。这个过程,就是所谓的降级。

    为帮助大家更直观的理解,接下来用一张图,梳理一下Hystrix隔离、熔断和降级的全流程:

     

    六、Spring Cloud核心组件:Zuul
    说完了Hystrix,接着给大家说说最后一个组件:Zuul,也就是微服务网关。这个组件是负责网络路由的。不懂网络路由?行,那我给你说说,如果没有Zuul的日常工作会怎样?

    假设你后台部署了几百个服务,现在有个前端兄弟,人家请求是直接从浏览器那儿发过来的。打个比方:人家要请求一下库存服务,你难道还让人家记着这服务的名字叫做inventory-service?部署在5台机器上?就算人家肯记住这一个,你后台可有几百个服务的名称和地址呢?难不成人家请求一个,就得记住一个?你要这样玩儿,那真是友谊的小船,说翻就翻!

    上面这种情况,压根儿是不现实的。所以一般微服务架构中都必然会设计一个网关在里面,像android、ios、pc前端、微信小程序、H5等等,不用去关心后端有几百个服务,就知道有一个网关,所有请求都往网关走,网关会根据请求中的一些特征,将请求转发给后端的各个服务。

    而且有一个网关之后,还有很多好处,比如可以做统一的降级、限流、认证授权、安全,等等。

     

     

    七、总结:

    最后再来总结一下,上述几个Spring Cloud核心组件,在微服务架构中,分别扮演的角色:

    Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉取注册表,从而知道其他服务在哪里

    Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从一个服务的多台机器中选择一台

    Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求

    Hystrix:发起请求是通过Hystrix的线程池来走的,不同的服务走不同的线程池,实现了不同服务调用的隔离,避免了服务雪崩的问题

    Zuul:如果前端、移动端要调用后端系统,统一从Zuul网关进入,由Zuul网关转发请求给对应的服务

     

    以上就是我们通过一个电商业务场景,阐述了Spring Cloud微服务架构几个核心组件的底层原理。

     

    文字总结还不够直观?没问题!

    我们将Spring Cloud的5个核心组件通过一张图串联起来,再来直观的感受一下其底层的架构原理:

    转自:

    https://blog.csdn.net/qq_42046105

    展开全文
  • 快排原理与实现

    千次阅读 2018-01-04 15:44:11
    上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数...
    上一节的冒泡排序可以说是我们学习第一个真正的排序算法,并且解决了桶排序浪费空间的问题,但在算法的执行效率上却牺牲了很多,它的时间复杂度达到了O(N2)。假如我们的计算机每秒钟可以运行10亿次,那么对1亿个数进行排序,桶排序则只需要0.1秒,而冒泡排序则需要1千万秒,达到115天之久,是不是很吓人。那有没有既不浪费空间又可以快一点的排序算法呢?那就是“快速排序”啦!光听这个名字是不是就觉得很高端呢。

           假设我们现在对“6  1  2 7  9  3  4  5 10  8”这个10个数进行排序。首先在这个序列中随便找一个数作为基准数(不要被这个名词吓到了,就是一个用来参照的数,待会你就知道它用来做啥的了)。为了方便,就让第一个数6作为基准数吧。接下来,需要将这个序列中所有比基准数大的数放在6的右边,比基准数小的数放在6的左边,类似下面这种排列。
          3  1  2 5  4  6  9 7  10  8

           在初始状态下,数字6在序列的第1位。我们的目标是将6挪到序列中间的某个位置,假设这个位置是k。现在就需要寻找这个k,并且以第k位为分界点,左边的数都小于等于6,右边的数都大于等于6。想一想,你有办法可以做到这点吗?

           给你一个提示吧。请回忆一下冒泡排序,是如何通过“交换”,一步步让每个数归位的。此时你也可以通过“交换”的方法来达到目的。具体是如何一步步交换呢?怎样交换才既方便又节省时间呢?先别急着往下看,拿出笔来,在纸上画画看。我高中时第一次学习冒泡排序算法的时候,就觉得冒泡排序很浪费时间,每次都只能对相邻的两个数进行比较,这显然太不合理了。于是我就想了一个办法,后来才知道原来这就是“快速排序”,请允许我小小的自恋一下(^o^)。

    方法其实很简单:分别从初始序列“6  1  2 7  9  3  4  5 10  8”两端开始“探测”。先从找一个小于6的数,再从找一个大于6的数,然后交换他们。这里可以用两个变量ij,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵i”和“哨兵j”。刚开始的时候让哨兵i指向序列的最左边(即i=1),指向数字6。让哨兵j指向序列的最右边(即j=10),指向数字8

    094811yilrz1tkzkvlrriz.png


          首先哨兵j开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵j先出动,这一点非常重要(请自己想一想为什么)。哨兵j一步一步地向左挪动(即j--),直到找到一个小于6的数停下来。接下来哨兵i再一步一步向右挪动(即i++),直到找到一个数大于6的数停下来。最后哨兵j停在了数字5面前,哨兵i停在了数字7面前。


    095430axy0qkhxxkktkktk.png
    095437kdandfxhbtokk2qh.png


          现在交换哨兵i和哨兵j所指向的元素的值。交换之后的序列如下。

           6  1  2  5  9 3  4  7  10  8
    095448k1kevwlz41373e7k.png
    095458ejza15wscjv7iw5c.png


           到此,第一次交换结束。接下来开始哨兵j继续向左挪动(再友情提醒,每次必须是哨兵j先出发)。他发现了4(比基准数6要小,满足要求)之后停了下来。哨兵i也继续向右挪动的,他发现了9(比基准数6要大,满足要求)之后停了下来。此时再次进行交换,交换之后的序列如下。
           6  1  2 5  4  3  9  7 10  8

           第二次交换结束,“探测”继续。哨兵j继续向左挪动,他发现了3(比基准数6要小,满足要求)之后又停了下来。哨兵i继续向右移动,糟啦!此时哨兵i和哨兵j相遇了,哨兵i和哨兵j都走到3面前。说明此时“探测”结束。我们将基准数6和3进行交换。交换之后的序列如下。
    3  1 2  5  4  6  9 7  10  8
    095506uz7e1uuukcblhkxv.png
    095514cag5fumuqqg5jnsw.png
    095530e0jf6p0y6aaaw2ir.png
           到此第一轮“探测”真正结束。此时以基准数6为分界点,6左边的数都小于等于6,6右边的数都大于等于6。回顾一下刚才的过程,其实哨兵j的使命就是要找小于基准数的数,而哨兵i的使命就是要找大于基准数的数,直到i和j碰头为止。

           OK,解释完毕。现在基准数6已经归位,它正好处在序列的第6位。此时我们已经将原来的序列,以6为分界点拆分成了两个序列,左边的序列是“3  1 2  5  4”,右边的序列是“9  7  10  8”。接下来还需要分别处理这两个序列。因为6左边和右边的序列目前都还是很混乱的。不过不要紧,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理6左边和右边的序列即可。现在先来处理6左边的序列现吧。

           左边的序列是“3  1  2 5  4”。请将这个序列以3为基准数进行调整,使得3左边的数都小于等于3,3右边的数都大于等于3。好了开始动笔吧。

           如果你模拟的没有错,调整完毕之后的序列的顺序应该是。
           2  1  3  5  4

           OK,现在3已经归位。接下来需要处理3左边的序列“2 1”和右边的序列“5 4”。对序列“2 1”以2为基准数进行调整,处理完毕之后的序列为“1 2”,到此2已经归位。序列“1”只有一个数,也不需要进行任何处理。至此我们对序列“2 1”已全部处理完毕,得到序列是“1 2”。序列“5 4”的处理也仿照此方法,最后得到的序列如下。
           1  2  3 4  5  6 9  7  10  8

           对于序列“9  7  10  8”也模拟刚才的过程,直到不可拆分出新的子序列为止。最终将会得到这样的序列,如下。
           1  2  3 4  5  6  7  8 9  10

           到此,排序完全结束。细心的同学可能已经发现,快速排序的每一轮处理其实就是将这一轮的基准数归位,直到所有的数都归位为止,排序就结束了。下面上个霸气的图来描述下整个算法的处理过程。
    232129ogop8gk0r8y7l70k.png

           快速排序之所比较快,因为相比冒泡排序,每次交换是跳跃式的。每次排序的时候设置一个基准点,将小于等于基准点的数全部放到基准点的左边,将大于等于基准点的数全部放到基准点的右边。这样在每次交换的时候就不会像冒泡排序一样每次只能在相邻的数之间进行交换,交换的距离就大的多了。因此总的比较和交换次数就少了,速度自然就提高了。当然在最坏的情况下,仍可能是相邻的两个数进行了交换。因此快速排序的最差时间复杂度和冒泡排序是一样的都是O(N2),它的平均时间复杂度为O(NlogN)。其实快速排序是基于一种叫做“二分”的思想。我们后面还会遇到“二分”思想,到时候再聊。先上代码,如下。


    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
    #include <stdio.h>
    int a[101],n;//定义全局变量,这两个变量需要在子函数中使用
    void quicksort(int left,int right)
    {
        int i,j,t,temp;
        if(left>right)
           return;
                                        
        temp=a[left]; //temp中存的就是基准数
        i=left;
        j=right;
        while(i!=j)
        {
                       //顺序很重要,要先从右边开始找
                       while(a[j]>=temp && i<j)
                                j--;
                       //再找右边的
                       while(a[i]<=temp && i<j)
                                i++;
                       //交换两个数在数组中的位置
                       if(i<j)
                       {
                                t=a[i];
                                a[i]=a[j];
                                a[j]=t;
                       }
        }
        //最终将基准数归位
        a[left]=a[i];
        a[i]=temp;
                                     
        quicksort(left,i-1);//继续处理左边的,这里是一个递归的过程
        quicksort(i+1,right);//继续处理右边的 ,这里是一个递归的过程
    }


    **使用栈的非递归快速排序**/
    template<typename Comparable>
    void quicksort2(vector<Comparable> &vec,int low,int high){
        stack<int> st;
        if(low<high){
            int mid=partition(vec,low,high);
            if(low<mid-1){
                st.push(low);
                st.push(mid-1);
            }
            if(mid+1<high){
                st.push(mid+1);
                st.push(high);
            }
            //其实就是用栈保存每一个待排序子串的首尾元素下标,下一次while循环时取出这个范围,对这段子序列进行partition操作
            while(!st.empty()){
                int q=st.top();
                st.pop();
                int p=st.top();
                st.pop();
                mid=partition(vec,p,q);
                if(p<mid-1){
                    st.push(p);
                    st.push(mid-1);
                }
                if(mid+1<q){
                    st.push(mid+1);
                    st.push(q);
                }      
            }
        }
    }

    int main()
    {
        int i,j,t;
        //读入数据
        scanf("%d",&n);
        for(i=1;i<=n;i++)
                       scanf("%d",&a[i]);
        quicksort(1,n); //快速排序调用
                                     
        //输出排序后的结果
        for(i=1;i<=n;i++)
            printf("%d ",a[i]);
        getchar();getchar();
        return 0;
    }





           可以输入以下数据进行验证
    1061279345108
           运行结果是
    12345678910
           下面是程序执行过程中数组a的变化过程,带下划线的数表示的已归位的基准数。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    1 2 7 9 3 4 5 10 8
    1 2 5 4 6 9 7 10 8
    1 3 5 4 6 9 7 10 8
    2 3 5 4 6 9 7 10 8
    2 3 5 4 6 9 7 10 8
    2 3 4 5 6 9 7 10 8
    2 3 4 5 6 9 7 10 8
    2 3 4 5 6 8 7 9 10
    2 3 4 5 6 7 8 9 10
    2 3 4 5 6 7 8 9 10
    2 3 4 5 6 7 8 9 10


         快速排序由 C. A. R. Hoare(东尼霍尔,Charles Antony Richard Hoare)在1960年提出,之后又有许多人做了进一步的优化。如果你对快速排序感兴趣可以去看看东尼霍尔1962年在Computer Journal发表的论文“Quicksort”以及《算法导论》的第七章。快速排序算法仅仅是东尼霍尔在计算机领域才能的第一次显露,后来他受到了老板的赏识和重用,公司希望他为新机器设计一个新的高级语言。你要知道当时还没有PASCAL或者C语言这些高级的东东。后来东尼霍尔参加了由Edsger Wybe Dijkstra(1972年图灵奖得主,这个大神我们后面还会遇到的到时候再细聊)举办的“ALGOL 60”培训班,他觉得自己与其没有把握去设计一个新的语言,还不如对现有的“ALGOL 60”进行改进,使之能在公司的新机器上使用。于是他便设计了“ALGOL 60”的一个子集版本。这个版本在执行效率和可靠性上都在当时“ALGOL 60”的各种版本中首屈一指,因此东尼霍尔受到了国际学术界的重视。后来他在“ALGOL X”的设计中还发明了大家熟知的“case”语句,后来也被各种高级语言广泛采用,比如PASCAL、C、Java语言等等。当然,东尼霍尔在计算机领域的贡献还有很多很多,他在1980年获得了图灵奖。

    展开全文
  • Session原理

    万次阅读 多人点赞 2019-07-10 11:16:54
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...开发工具与关键技术:Java,HTTP协议,session原理 撰写时间:2019-06-17 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~...
  • ZIP压缩算法原理解析(好文推荐,看完就懂)

    万次阅读 多人点赞 2019-05-28 16:08:02
    最近自己实现了一个ZIP压缩数据的解压程序,觉得有必要把ZIP压缩格式进行一下详细总结,数据压缩是一门通信原理和计算机科学都会涉及到的学科,在通信原理中,一般称为信源编码,在计算机科学里,一般称为数据压缩,...
  • JS的底层原理

    千次阅读 2019-07-03 11:19:51
    JS 的底层运行原理 每调用一个函数就会生成一个执行环境(俗称执行上下文),执行上下文环境数量没有限制 每调用一个函数就会生成一个执行环境(俗称执行上下文),执行上下文环境数量没有限制 单线程 同步执行,只有...
  • ZooKeeper原理解析

    千次阅读 2020-02-07 09:48:03
    分布式协调服务组件zookeeper的介绍、特点及原理解析
  • hashMap的实现原理

    2020-09-15 19:56:48
    HashMap的实现原理 HashMap的数据结构 在看Hashmap的数据结构之前先来看看数组和链表的特点 数组:寻址容易插入和删除的时候比较困难(数组有下表寻址,但是插入删除的时候下表要移动,扩容的时候也很麻烦) 链表:...
  • Canny算子边缘检测原理及实现

    万次阅读 多人点赞 2019-11-05 17:19:37
    写在前面 Canny边缘检是在在1986年提出来的,到今天已经30多年过去了,但Canny算法仍然是图像边缘检测算法中最经典、先进的算法之一。 相比Sobel、Prewitt等算子,Canny算法更为优异。Sobel、Prewitt等算子有如下...
  • synchronize关键字synchronize的使用synchronize的原理 synchronize的使用 我们知道,当出现race condition的时候,应用就不会同步。 举个例子: package sync; import org.junit.Test; import java.util....
  • spring-boot-starter原理及实现方法

    万次阅读 2019-04-23 18:01:14
    文章目录spring-boot-starter原理实现pom.xmlEnableDemoConfigurationDemoPropertiesDemoAutoConfigurationDemoServicespring.factories测试pom.xml配置文件测试源码 spring-boot-starter spring-boot可以省略众多的...
  • 明天就要考试了,来一波知识点整理。都会了,期末考试你不过你来找我! 第一章 1.按微处理器的字节分类 4 位微处理器 8 位微处理器 16位微处理器 ...2.这个必须背,不是简答就是简答,肯定出简答 ...
  • linux反弹shell的原理

    万次阅读 2020-08-24 11:30:15
    完整命令 反弹shell命令: bash -i >& /dev/tcp/10.0.0.1/8080 0>&1 bash -i >...原理 bash -i > /dev/tcp/ip/port 0>&1 2>&1 bash -i 打开一个交互式的bash &是为了
  • 后续总结一篇自己的理解 参考文献与书籍: 1.https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html
  • 计算机组成原理核心知识点总结&面试笔试要点

    万次阅读 多人点赞 2020-06-13 19:52:06
    作为一名计算机专业的学生,计算机组成原理、计算机网络、操作系统这三门课程可以说是专业核心基础课,是至关重要的,其内容是一名合格的coder所必备的知识集;非科班出身的程序员要是想要有所提升,也需要认真学习...
  • 同学,你好! 若觉得有用请点赞或关注~以后会发布更多有用的内容。 2019-07-24更新:插入了一张“高分喷雾”。 20...
  • 直方图均衡化的数学原理

    万次阅读 多人点赞 2020-03-13 13:34:51
    直方图均衡化的数学原理直方图均衡化处理的“中心思想”是把原始图像的灰度直方图从比较集中的某个灰度区间变成在全部灰度范围内的均匀分布。直方图均衡化就是对图像进行非线性拉伸,重新分配图像像素值,使一定灰度...
  • Eureka工作原理

    万次阅读 多人点赞 2019-07-07 09:34:17
    Eureka 工作原理 上节内容为大家介绍了,注册中心 Eureka 产品的使用,以及如何利用 Eureka 搭建单台和集群的注册中心。这节课我们来继续学习 Eureka,了解它的相关概念、工作流程机制等。 Eureka 作为 Spring Cloud...
1 2 3 4 5 ... 20
收藏数 2,783,243
精华内容 1,113,297
关键字:

原理