分布式锁-看一眼就能懂的-原理 (分布式锁看门狗怎么实现)

教程大全 2025-07-18 01:17:21 浏览

看一眼就能懂的“分布式锁”原理

2019-06-10 08:04:26分布式锁和我们平常讲到的锁原理基本一样,目的就是确保在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法、变量。

分布式锁和我们平常讲到的锁原理基本一样,目的就是确保在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法、变量。

在一个进程中,也就是一个 JVM 或者说应用中,我们很容易去处理控制,在 jdk java.util 并发包中已经为我们提供了这些方法去加锁,比如 Synchronized 关键字或者 Lock 锁,都可以处理。

但是我们现在的应用程序如果只部署一台 服务器 ,那并发量是很差的,如果同时有上万的请求,很有可能造成服务器压力过大而瘫痪。

想想双十一和大年三十晚上十点,瓜分支付宝红包等业务场景,自然需要用到多台服务器去同时处理这些业务,这些服务可能会有上百台同时处理。

但是我们想一想,如果有 100 台服务器要处理分红包的业务,现在假设有 1 亿的红包,1 千万个人分,金额随机,那么这个业务场景下,是不是必须确保这 1 千万个人***分的红包金额总和等于 1 亿?

如果处理不好~~每人分到 100 万,那马云爸爸估计大年初一,就得宣布破产了~~

常规锁会造成什么情况?

首先说一下我们为什么要搞集群。简单理解就是,需求量(请求并发量)变大了,一个工人处理能力有限,那就多招一些工人来一起处理。

假设 1 千万个请求平均分配到 100 台服务器上,每个服务器接收 10w 的请求。

这 10w 个请求并不是在同一秒中来的,可能是在 1-2 个小时内,可以联想下我们三十晚上开红包,等到 10:20 开始,有的人立马开了,有的人等到 12 点才想起来。

那这样的话,平均到每一秒上的请求也就不到 1 千个,这种压力一般的服务器还是可以承受的:

等于是在每个服务器中去分 1 亿,也就是 10w 个用户分了 1 亿,***总计有 100 个服务器,要分 100 亿。

如果真这样了,虽说马云爸爸不会破产(据***统计马云有 2300 亿人民币),那分红包的开发项目组,以及产品经理,可以 GG了~

简化结构图如下:

那么为了解决这个问题,让 1000 万用户只分 1 亿,而不是 100 亿,这个时候分布式锁就派上用处了。

分布式锁可以把整个集群就当作是一个应用一样去处理,那么也就需要这个锁独立于每一个服务之外,而不是在服务里面。

假设***个服务器接收到用户 1 的请求后,不能只在自己的应用中去判断还有多少钱可以分了,而需要去外部请求专门负责管理这 1 亿红包的人(服务),问他:哎,我这里要分 100 块,给我 100。

管理红包的妹子(服务)一看,还有 1 个亿,那好,给你 100 块,然后剩下 99999900 块。

第二个请求到来后,被服务器 2 获取,继续去询问,管理红包的妹子,我这边要分 10 块,管理红包的妹子先查了下还有 99999900,那就说:好,给你 10 块,那就剩下 99999890 块。

等到第 1000w 个请求到来后,服务器 100 拿到请求,继续去询问,管理红包的妹子,我要 100,妹子翻了翻白眼,对你说,就剩 1 块了,爱要不要,那这个时候就只能给你 1 块了(1 块也是钱啊,买根辣条还是可以的)。

这些请求编号 1,2 不代表执行的先后顺序,正式的场景下,应该是 100 台服务器每个服务器持有一个请求去访问负责管理红包的妹子(服务)。

那在管红包的妹子那里同时会接收到 100 个请求,这个时候就需要在负责红包的妹子那里加个锁就可以了(抛绣球),你们 100 个服务器谁拿到锁(抢到绣球),谁就进来和我谈,我给你分,其他人就等着去吧。

经过上面的分布式锁的处理后,马云爸爸终于放心了,决定给红包团队每人加一个鸡腿。

简化的结构图如下:

分布式锁的实现有哪些?

说到分布式锁的实现,还是有很多的,有数据库方式的,有 Redis 分布式锁,有 Zookeeper 分布式锁等等。

我们如果采用 Redis 作为分布式锁,那么上图中负责“红包的妹子(服务)”,就可以替换成 Redis,请自行脑补。

①为什么 Redis 可以实现分布式锁?

首先 Redis 是单线程的,这里的单线程指的是网络请求模块使用了一个线程(所以不需考虑并发安全性),即一个线程处理所有网络请求,其他模块仍用了多个线程。

在实际的操作中过程大致是这样子的:服务器 1 要去访问发红包的妹子,也就是 Redis,那么它会在 Redis 中通过”setnx key value” 操作设置一个 Key 进去,Value 是啥不重要,重要的是要有一个 Key,也就是一个标记。

而且这个 Key 你爱叫啥叫啥,只要所有的服务器设置的 Key 相同就可以。

假设我们设置一个,如下图:

那么我们可以看到会返回一个 1,那就代表了成功。

如果再来一个请求去设置同样的 Key,如下图:

这个时候会返回 0,那就代表失败了。

那么我们就可以通过这个操作去判断是不是当前可以拿到锁,或者说可以去访问“负责发红包的妹子”,如果返回 1,那我就开始去执行后面的逻辑,如果返回 0,那就说明已经被人占用了,我就要继续等待。

当服务器 1 拿到锁之后,进行了业务处理,完成后,还需要释放锁,如下图所示:

删除成功返回 1,那么其他的服务器就可以继续重复上面的步骤去设置这个 Key,以达到获取锁的目的。

当然以上的操作是在 Redis 客户端直接进行的,通过程序调用的话,肯定就不能这么写,比如 Java 就需要通过 Jedis 去调用,但是整个处理逻辑基本都是一样的。

通过上面的方式,我们好像是解决了分布式锁的问题,但是想想还有没有什么问题呢?

对,问题还是有的,可能会有死锁的问题发生,比如服务器 1 设置完之后,获取了锁之后,忽然发生了宕机。

那后续的删除 Key 操作就没法执行,这个 Key 会一直在 Redis 中存在,其他服务器每次去检查,都会返回 0,他们都会认为有人在使用锁,我需要等。

为了解决这个死锁的问题,我们就需要给 Key 设置有效期了。设置的方式有 2 种:

***种就是在 Set 完 Key 之后,直接设置 Key 的有效期 “EXPire key timeout” ,为 Key 设置一个超时时间,单位为 Second,超过这个时间锁会自动释放,避免死锁。

这种方式相当于,把锁持有的有效期,交给了 Redis 去控制。如果时间到了,你还没有给我删除 Key,那 Redis 就直接给你删了,其他服务器就可以继续去 Setnx 获取锁。

第二种方式,就是把删除 Key 权利交给其他的服务器,那这个时候就需要用到 Value 值了,比如服务器 1,设置了 Value 也就是 Timeout 为当前时间 +1 秒 。

这个时候服务器 2 通过 Get 发现时间已经超过系统当前时间了,那就说明服务器 1 没有释放锁,服务器 1 可能出问题了,服务器 2 就开始执行删除 Key 操作,并且继续执行 Setnx 操作。

但是这块有一个问题,也就是不光你服务器 2 可能会发现服务器 1 超时了,服务器 3 也可能会发现,如果刚好服务器 2 Setnx 操作完成,服务器 3 就接着删除,是不是服务器 3 也可以 Setnx 成功了?

那就等于是服务器 2 和服务器 3 都拿到锁了,那就问题大了。这个时候怎么办呢?

这个时候需要用到“GETSET key value”命令了。这个命令的意思就是获取当前 Key 的值,并且设置新的值。

假设服务器 2 发现 Key 过期了,开始调用 getset 命令,然后用获取的时间判断是否过期,如果获取的时间仍然是过期的,那就说明拿到锁了。

如果没有,则说明在服务 2 执行 getset 之前,服务器 3 可能也发现锁过期了,并且在服务器 2 之前执行了 getset 操作,重新设置了过期时间。

那么服务器 2 就需要放弃后续的操作,继续等待服务器 3 释放锁或者去监测 Key 的有效期是否过期。

这块其实有一个小问题是,服务器 3 已经修改了有效期,拿到锁之后,服务器 2 也修改了有效期,但是没能拿到锁。

但是这个有效期的时间已经被在服务器 3 的基础上又增加一些,但是这种影响其实还是很小的,几乎可以忽略不计。

②为什么 Zookeeper 可实现分布式锁?

百度百科是这么介绍的:ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件

那对于我们初次认识的人,可以理解成 ZooKeeper 就像是我们的电脑文件系统,我们可以在 d 盘中创建文件夹 a,并且可以继续在文件夹 a 中创建文件夹 a1,a2。

那我们的文件系统有什么特点?那就是同一个目录下文件名称不能重复,同样 ZooKeeper 也是这样的。

在 ZooKeeper 所有的节点,也就是文件夹称作 Znode,而且这个 Znode 节点是可以存储数据的。

我们可以通过“ create /zkjjj nice”来创建一个节点,这个命令就表示,在根目录下创建一个 zkjjj 的节点,值是 nice。

同样这里的值,和我在前面说的 Redis 中的一样,没什么意义,你随便给。

另外 ZooKeeper 可以创建 4 种类型的节点,分别是:

首先说下持久性节点和临时性节点的区别:

Zookeeper 有一个监听机制,客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、被删除、子目录节点增加删除)等,Zookeeper 会通知客户端。

下面我们继续结合我们上面的分红包场景,描述下在 Zookeeper 中如何加锁。

假设服务器 1,创建了一个节点 /zkjjj,成功了,那服务器 1 就获取了锁,服务器 2 再去创建相同的锁,就会失败,这个时候就只能监听这个节点的变化。

等到服务器 1 处理完业务,删除了节点后,他就会得到通知,然后去创建同样的节点,获取锁处理业务,再删除节点,后续的 100 台服务器与之类似。

注意这里的 100 台服务器并不是挨个去执行上面的创建节点的操作,而是并发的,当服务器 1 创建成功,那么剩下的 99 个就都会注册监听这个节点,等通知,以此类推。

但是大家有没有注意到,这里还是有问题的,还是会有死锁的情况存在,对不对?

当服务器 1 创建了节点后挂了,没能删除,那其他 99 台服务器就会一直等通知,那就完蛋了。

这个时候就需要用到临时性节点了,我们前面说过了,临时性节点的特点是客户端一旦断开,就会丢失。

也就是当服务器 1 创建了节点后,如果挂了,那这个节点会自动被删除,这样后续的其他服务器,就可以继续去创建节点,获取锁了。

但是我们可能还需要注意到一点,就是惊群效应:举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到…

就是当服务器 1 节点有变化,会通知其余的 99 个服务器,但是最终只有 1 个服务器会创建成功,这样 98 还是需要等待监听,那么为了处理这种情况,就需要用到临时顺序性节点。

大致意思就是,之前是所有 99 个服务器都监听一个节点,现在就是每一个服务器监听自己前面的一个节点。

假设 100 个服务器同时发来请求,这个时候会在 /zkjjj 节点下创建 100 个临时顺序性节点 /zkjjj/000000001,/zkjjj/000000002,一直到 /zkjjj/000000100,这个编号就等于是已经给他们设置了获取锁的先后顺序了。

当 001 节点处理完毕,删除节点后,002 收到通知,去获取锁,开始执行,执行完毕,删除节点,通知 003~以此类推。

原理

人与动物、动物与动物之间的感人故事(作文) 急需

我的故事可能很平凡,很平淡,但我想起来却```眼睛微红了、 我是一个很平凡的农村家庭的孩子,虽然不是小康家庭,但也其乐融融 我家一般白天没人在家,爸爸妈妈出去做事,我和姐姐们一起去上学,爸爸怕白天没人在会遭小偷,就在家养了条狗狗。 刚开始我不怎么喜欢它的,黑黑的瘦瘦的,我喜欢的狗狗一直是白白的毛里有胖胖的肉肉的,所以我不喜欢它,起名是我就叫他“黄嘴”,呵呵,因为它的嘴巴是黄色的嘛 就这样,黄嘴开始和我们一起生活了 每天在我上学的时候它恭送我(有时还跟着我去)放学的时候它好象高兴死了,飞快的摇着尾巴,如果不是我长的高,它就可能会用舌头添我的脸了。 刚开始我不大习惯,总是在它快扑在我身上的时候就开始骂它,它也像是听懂了似的,虽然不在扑了但尾巴还是不停的摇着。 时间久了我也麻木了,反正它是畜生我是人,让让它算了(不跟狗计较嘛!呵呵) 我家直到去年还养这它呢!可是事情如我想的一样, 它还是有被杀掉的可能,因为2008年快来了,爸爸就让叔叔把狗狗杀掉,本来我还不知道的,我是看到爸爸在那个地方才想去看看有什么好玩的的,没想到就让我看到了我家的黄嘴被一条绳子钓在一棵树上,那一刻我只知道我的眼泪在流,看着它被吊在树上够不到地的在挣扎,我的心揪住了。 我想去救它,可是我不能不是吗!爸爸可能看出了我的难过,就找借口让我去买烟包我支开了。 做之前我看见爸爸也是难过的呢!呵呵,既然不舍得为什么要杀掉呢!那一刻我终于体会到原来我所讨厌的狗狗现在我有多么的喜欢它,不知道以后没有它的生活我习不习惯呢!把狗狗杀掉的那会儿我决定以后都不吃狗肉了!现在我家也有狗,我想我是不可能在喜欢了,即使是我喜欢的白白的毛里有胖胖的肉的狗了,因为已经受伤一次的心已经承受不起在一次的伤害了 现在我能做的只是希望它如果在投胎,能够做一只漂亮的宠物狗了,能够讨主人的欢心,能够带给身边的人欢笑!!!现在的我也只能思念丑丑的它了,想起它时也许我会微微笑的农历二〇〇三年八月十一晚间,俄罗斯西部城市别尔哥罗德(Belgorod)的街头上,1只忠心耿耿的狗儿为了解救主人和路人,奋不顾身扑上1枚即将爆炸的手榴弹,被炸得血肉模糊,牺牲了生命,而它的主人以及周围的路人,由於它以自己身体阻挡了手榴弹爆炸的威力,仅仅表皮擦伤而已。 当天晚上,一个名叫谢尔盖?库利科夫的18岁青年,喝得烂醉在街道上横冲直撞,行人纷纷避而远之,一位老人实在看不下去,走上前指责他,库利科夫一怒之下冲著老人破口大骂,还挥起拳头狂揍老人,并掏出1颗手榴弹抛向人群,瞬间人们瞪大眼睛呆住了,一场惨剧即将上演。 此时,人群不远处,1只正和主人一起散步的狗儿,突然意识到将会发生什麼事,猛然挣脱主人手中的绳子,钻进人群,一跃扑上正在冒烟的手榴弹,刹那间爆炸声响起,众人吓了半天才回过神来,看到这只仁勇的狗儿血肉模糊地躺在地上,而人们安然无恙,大家既惊愕又感动,默默无语。 轻的泪,是人的泪,而动物的泪,却是有重量的泪。 那是一种发自生命深处的泪,是一种比金属还要重的泪。 也许人的泪中还含有虚伪,也许人的泪里还有个人恩怨,而动物的泪里却只有真诚,也只有动物的泪,才更是震撼人们魂魄的泪。 我第一次看到动物的泪,那是我家一只老猫的泪,这只老猫已经在我家许多许多年了,也不知它生下了多少子女,也不知它已是多大的年纪。 只是知道它已经成了我们家庭的一个成员,我们全家人每天生活的一项重要内容,就是和她一起戏耍。 在它还是一只小猫的时候,我们引得它在地上滚来滚去,后来它渐渐地长大了,我们又把它抱在怀里好长好长时间地抚摸它那软软的绒毛。 也是我们和它亲热得太多了,它已经一天也离不开我们的抚爱,无论是谁,只要这一天没有摸它一下,就是到了晚上,它也要找到那个人,然后就无声地卧在他的身边,等着他的亲昵,直到那个人终于抚摸了它,哪怕只是一下,这时它也会心满意足地慢慢走开,就好象是它为此感到充实,也为此感到幸福。 只是,多少年过去,这只老猫已经的太老了,一副老态龙钟的样子,行动已经变得缓慢;尽管到这时我们全家人还是对它极为友善,但,也不知是一种什么感应,这只老猫渐渐地就和我们疏远了。 它每天只是在屋檐下卧着,无论我们如何在下面引逗它,它也不肯下来,有时它也懒懒地向我们看上一眼,但随后就毫无表情地又闭上了眼睛。 母亲说,这只猫的寿限就要到了,也是人类的无情,我们一家最担心的,却是怕它死在一个不为人知的角落。 我们怕它会给我们带来麻烦。 就这样每天每天地观察,我们只是看到这只老猫确实是一天一天地更加无精打采了,但它还是就在屋檐下,窗沿上静静地卧着,似在睡,又似在等着那即将到来的最后日子。 也是无意间的发现,那是我到院里去做什么事情的时候,我只是看见这只老猫在窗沿上卧得太久了,就过去想看看它是睡着,还是和平时一样地在晒太阳。 但在我靠近它走过去的时候,我却突然发现,就在这只老猫的眼角处,凝着一滴泪珠。 看来这滴泪已经在它的眼角驻留得太久了,那一滴了泪已经被阳光晒成活像是一颗琥珀,一动不动,就凝在眼角边,还在阳光下闪出点点光斑。 “猫哭了。 ”不由己地,我向房里的母亲喊了一声,立刻,母亲就走了出来,她似是要给这只老猫一点最后的安慰。 谁料这只老猫一看到母亲向它走了过来,立刻强挣扎着站了起来,用出最后一点力气,一步一步地向屋顶爬了上去。 这时,母亲还尽力想把它引下来,也许是想给它一点最后的食物,但这只老猫头也不回地,就一步一步地向远处走去了,走得那样的沉重。 直到这时,我才发现,是我们太冷酷了,它在我们家活了一生,我们还是怕它就在我们家里终结生命,我们总是盼着它自己在生命的最后时刻,能够自己走开,无论是走到哪里,也比留在我们家里强。 最先我们还以为是它不肯走,怕它要向我们索要最后的温暖,但是我们把它估计错了,它只是在等我们最后的送别;而在它发现我们已经知道它要离开我们的时候,她只是留下了一滴泪,然后就悄无声息地走了,走到不知什么地方去了。 很久很久,我总是不能忘记那滴眼泪,那是一种留恋生命,又感知到大限到来的泪水。 动物有它们自己的情感,它们只给人们留下自己的情爱,然后就含着一滴永远的泪珠向人们告别,而又把最后的痛苦由自己远远的带走。 动物的泪是圣洁的泪,它们不向人类索求回报的狗昨天被车撞了,脑海里总回想起它被撞的那一幕.我走不出阴影.好伤心,养了它快一年,寂寞的时候是朋友.上班的时候帮我看门.出差了还帮我陪着了一半.....太多太多的好了.它已经是我家庭里的一员.可是昨天它离开了我.不知道为什么我当时蒙了.居然连尸都没敢给它收.我从来不怕街上撞死的猫狗,压死的老鼠那种血肉模糊.可是我就是不忍心看它那个惨状.多看一眼都受不了.事发的时候什么都不知道了,就傻傻的在路上来回走.不敢看,也不舍得离开.半个小时后我才相信它死了,才知道悲痛.可是眼泪也没有用,怎么样它都不会回来了.长这么大,除了欠爸妈的.我谁都不欠,谁都对得起,可是今天我感觉我欠它的.我对不起它...我好难过,今天是平安夜..它为什么就不能平安呢?就不能活到平安夜呢?我想它开开心心的过完它一生,哪怕以后生老病死.起码也是寿终正寝.那怕是跟我失散了,也好过现在.起码还活着.家里好多它的东西,吃饭的碗睡觉的窝.玩具,被它咬坏的鞋子.舍不得丢.....舍不得,舍不得,舍不得.....我欠它的债,每次都带着狗绳,昨天没带.每次都只带到公园,昨天却走到了公路附近.我对不起它......朋友劝我说只不过是畜生,鸡鸭还不是自己养自己杀.何况你也没亏待它.可是我觉得它不同.它有灵性,它懂我的心情.我现在的心情它会懂吗?我只想对它说对不起,来生如果它还做狗.我会像今生这样对它好.我还做它的主人它愿意吗? 我只是想上来诉说.它走了.留给我的是遗憾和思念.我会像朋友们所说的,永远为它在心里留个位置.痛过之后,我去寻找过它,可是老天没给我机会.没能找到.问了清洁阿姨,她们说,这一块清洁都她们做.而且当天扫的东西都送去附近一个压缩站烧掉了.还好能给我一丝安慰,那个压缩站刚好就在它生前最喜欢的公园旁边.圣诞节的晚上,我买了它最爱吃的零食,也做了它最喜欢吃的菜连同它所有用的,玩的送去公园埋掉了.真的很不舍.不过我我相信它就在附近......想它早日投胎,我抄了往生咒烧给了它.希望它来生能做人.能够自己保护自己.也许真的跟它只有八个月的缘分吧.如果还有缘,如果来生它还做狗.我相信一定还会再碰到它,然后又抱回家里..........

天枰座女生喜欢一个人的表现

我也是天枰座的女生,我个人比较喜欢干净,大方的男孩,不能小气,但也不要不懂装懂,不会装会,喜欢的男生类型应该是阳光、活泼、向上的那种,遇事要积极对待,不能消极,而且天秤座的女生一般会比较现实,但是又比较喜欢听甜言蜜语,哈哈,加油吧。

有没有. 很美很美的文章.网右们提供些美的文章丫``

<没有宋词的年代> 青藤静静攀在漆迹斑斑的旧墙边,空气里漂浮着不知名的清香,一个罗衫淡淡的女子,提一盏小小的荷花灯,寻着词人的韵脚婉约地走来. 那是多雨的季节,多忧愁的《雨霖铃》,是被花落和笙萧包围的《满庭芳》;是精致不可临摹的《钗头风》,是靡丽凄婉,让我伤感的——宋王朝。整个下午,我素手为桨,宣纸为舟,泛波于千年前宋词的烟波浩渺中。 曾经,想过羽扇纶巾间穿行的女子,在西子湖畔独品晓风残月,绿柳如烟中墨笔添香,做那宋朝华美锦缎上韵短而味长的一笔。曾经,想学闲雅清丽的同叔,凄婉优柔的易安,随手从词里斟杯清酒,便能盛满离人的眼泪;书页间点曲轻歌,便能唱尽万古的痴情。 只是,这是一个没有宋词的年代。人类文明的进步,数字时代的到来,那个精致玲珑的朝代,早已浓缩成淡淡的身影,在墨香古卷的文字中沉淀下去。在那本该丝弦弄音,霓裳轻舞的七夕之夜,没有谁肯对着秋月伤情悲怀。素笺成灰,相思成灾,赤裸裸的爱恨恩仇随意抛出,还有哪家女子“和羞走,却把青梅嗅”? 这是一个欲望充斥灵魂的年代,充足的物质让我们安逸,流行的泛滥,语言的苍白晦暗,让豪放、婉约成为已逝的背景。黯然回首间,钢筋水泥的丛林,市井巷陌的攘攘冠盖,使暗香疏影早已成为沧海桑田,还有谁肯闲情雅致地栏杆拍便,欲说还休? 于是我喂叹,不能像清新婉丽的晏几道,可以惆怅忆着心宇落衣的小萍,寻着旧日的谢桥;不能如柔弱无力的秦少游,徘徊在清烟小楼里数绯红万点纤云弄巧,看自在飞花与无边丝雨‘更不可能似娇花照水弱柳扶风的李清照,守着满地黄花,为绿肥红瘦的雨后海棠叹惋楚江里,一篙独去。 每当月照轩窗,我惟有在秦悲柳切,伤花惜春的缠绵悱恻里,化身成崖草边的一朵红菡芍,让长长的雨,把整颗心沁得透湿。我惟有怀想、感伤那个风雨飘摇的宋王朝,那个造就文学奇葩的时代。 感谢,感谢这段距离——沉淀了千年的孤独,让我足以远远地、静静地拈花微笑。

本文版权声明本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请联系本站客服,一经查实,本站将立刻删除。

发表评论

热门推荐