"pate"(越南有什么特色美食)
资讯
2023-11-01
378
1. pate,越南有什么特色美食?
1 |国民面条——Pho
没吃过河粉不算到过越南,但来了越南却欣赏不来一碗正宗的 Pho,才是真正的原罪所在。
首先,把 Pho 读成 “fou”,你就已经露馅了!人家的正确发音念 “fuh~”,大致是英音、上扬版的 “fur” 的读法。
如今越南青年全民护肤,个个白嫩水灵,你一声降调的 “fou”,原本把你当自己人一通寒暄的餐厅阿姨,会产生巨大心理落差,悻悻地丢你一本英文菜单。
就算成功伪装到点菜阶段,你还有很多关要过——
汤头该什么颜色?牛肉点哪个部位的?端上来一大盘娇艳欲滴的香草放多少?海鲜酱和是拉差酱,加了会不会被邻座鄙视?......
一碗 Pho 的正确打开姿势
【汤】
*牛肉粉配牛骨、牛尾汤,鸡肉粉配整鸡汤,要加桂皮八角丁香小豆蔻等熬制近十小时,再以焦糖化的洋葱、姜和鱼露增味...琥珀色、甘甜回味、透亮见底的汤,是好粉的第一要义&灵魂,一碗 Pho 上桌,先尝汤!
【肉】
*牛肉(Bo):各类部位轮番上阵,生熟肉,瘦油腩,筋腱骨丸……翻谁牌子随你喜好
*鸡肉(Ga):除了鸡丝,也可以是一整条鸡大腿儿
【粉】
*粗细不是关键,当日现备、雪白透光的新鲜大米河粉,出锅要滑而不烂!
【配菜&香草】
*豆芽,青柠,青红辣椒...除了你熟悉的带梗罗勒、薄荷,常常会出现味道更重、跟香菜差了一个字母的刺芹,Culantro!
*每种香草各取几片即可,不要整片叶子往汤里放,掰碎有助于香气的释放。
【酱】
*一般有海鲜酱、是拉差辣酱和鱼露——前两种,建议汤头靠谱时别挤在汤里,但要给鱼露疯狂打 call!
2 |Banh Mi (越南法包)
法国人在越南殖民了大半个世纪,即便西贡城早就改姓了胡,但越式法包 Banh Mi,还是在越南人的“本土化”之后,留下来成为了当地首屈一指的美味。
到胡志明市的头一件事,自然是开启疯狂寻找 Banh Mi 模式!
你也许不屑,越南法包,还能好吃过巴黎的?
说越南法包正宗,法国人肯定会摇头,但任何一种食物传入异邦,免不了入乡随俗接受改造,日本的意面如此,越南法包亦不例外。
有别于印象中硬朗的传统法包,越南人在面粉里掺入大米粉,外壳依旧脆韧,内里口感却蓬松轻盈,更受本地人欢迎,同时还省了不少钱!
一枚典型的 Banh Mi 里,先要厚涂一层法风浓重的 Pate(猪、鸡鸭肝酱),然后夹进三两片 Headcheese(猪皮、猪耳和各种杂碎做成的冻肉块)、叉烧、纯肉火腿片……还需备齐肉松、酸黄瓜条、腌红白萝卜,最后往里恶狠狠挤一坨混着黄油的蛋黄酱,才算功德圆满。
对了,要不是因为 Banh Mi 的存在,此生从没觉得香菜这么好吃过!
小吃,多到能写字典!
3.越南的街头巷尾,多得是能写成百科全书的稀奇小吃,会让你觉得发现了新大陆!
Bun Cha,河内名吃,前两年奥巴马上 CNN 安东尼波登的节目,俩人相约去河内小馆子里吸的,就是这碗越北最经典的猪肉蘸汤米线。
在西贡的 145 Bùi Viện,你也能找到一碗从河内穿越来的地道 Bun Cha。老板英文说得溜,热情招呼你点这点那,没带现金还特意关照吃完再取。
上了菜,他又主动示范米粉的正确打开方式:用手拆散米粉团,分几次投入浓口的酸甜蘸汤,就着撕碎的菜叶、辣椒碎和蒜末、烤得酥润的猪颈肉和绞肉饼(跟 Banh Mi 37 里的味道相近)一起送下肚,用英文夸张比喻,就是“it’s to die for!”
还被安利了炸春卷,吃法同样是浸到这鱼露汁,美味!
Cơm tấm An Dương Vương
其实 Com Tam 就是个炭烤猪排盖浇饭。虽鲜为外人知晓,但 Com Tam 可谓越南的隐世美味!
对于烤猪肉这件事,越南人算登峰造极了。谁让你们家城管允许沿街架一台大烤炉,随时往炭上扔超大块的厚切猪排呢?
反复刷酱跟鱼露那是必须的,但最幸福的莫过于,吃的时候再来一碟鱼露,拌饭!
4越式田螺塞肉,个头颇为壮硕,秒杀一众杭帮菜馆。猪肉和螺肉打得既粗粒又筋道,每只里面还塞了根香茅提味,不由得感叹,越南人天生五行不缺香草,太会玩!
肋排的肉头无比紧致却能一秒脱骨,粗粒海盐和鱼露搭档的料碟,虽是头一次见,但混着迷人的炙烤香气,确认鱼露是百搭之王无误了!
2. 海洋奇缘的所有歌曲?
《海洋奇缘》是一部由迪士尼于2016年推出的动画电影。电影的音乐由Musker、Clements以及Lin-Manuel Miranda等人创作。以下是电影《海洋奇缘》中的主要歌曲列表:
1. "Tulou Tagaloa"(电影开头歌曲)
2. "An Innocent Warrior"
3. "知道谁是你" ("Where You Are")
4. "你知道回家的路" ("How Far I'll Go")
5. "We Know the Way"
6. "Moana, Make War" (未在最终电影中使用)
7. "How Far I'll Go" (享有广泛的知名度和家喻户晓的歌曲)
8. "You're Welcome" (为毛伊(Maui)的特色歌曲,表达了他自豪和乐于助人的性格)
9. "Shiny" (为电影的反派克拉肯(Tamatoa)演唱的歌曲)
10. "Logo Te Pate"
11. "I Am Moana (Song of the Ancestors)"
12. "Know Who You Are"
13. "We Know The Way" (Finale)
14. "How Far I'll Go" (Alessia Cara版本,片尾曲)
15. "You're Welcome" (Jordan Fisher/Lin-Manuel Miranda版本)
以上就是《海洋奇缘》的主要歌曲列表,其中"你知道回家的路"和"You're Welcome"这两首歌在全球范围内都非常有影响力。
3. 现在学习分布式锁还来得及吗?
来得及,学无止境!
多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。常见的分布式锁实现一般是基于DB、Redis、zookeeper。下面笔者会按照顺序分析下这3种分布式锁的设计与实现,想直接看分布式锁总结的小伙伴可直接翻到文档末尾处。分布式锁的实现由多种方式,但是不管怎样,分布式锁一般要有以下特点:排他性:任意时刻,只能有一个client能获取到锁容错性:分布式锁服务一般要满足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃或者网络不可达除了以上特点之外,分布式锁最好也能满足可重入、高性能、阻塞锁特性(AQS这种,能够及时从阻塞状态唤醒)等,下面就话不多说,赶紧上(开往分布式锁的设计与实现的)车~DB锁在数据库新建一张表用于控制并发控制,表结构可以如下所示:CREATE TABLE `lock_table` ( `id` int(11) unsigned NOT NULL COMMENT '主键', `key_id` bigint(20) NOT NULL COMMENT '分布式key', `memo` varchar(43) NOT NULL DEFAULT '' COMMENT '可记录操作内容', `update_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`,`key_id`), UNIQUE KEY `key_id` (`key_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;key_id作为分布式key用来并发控制,memo可用来记录一些操作内容(比如memo可用来支持重入特性,标记下当前加锁的client和加锁次数)。将key_id设置为唯一索引,保证了针对同一个key_id只有一个加锁(数据插入)能成功。此时lock和unlock伪代码如下:def lock : exec sql: insert into lock_table(key_id, memo, update_time) values (key_id, memo, NOW()) if result == true : return true else : return falsedef unlock : exec sql: delete from lock_table where key_id = 'key_id' and memo = 'memo'注意,伪代码中的lock操作是非阻塞锁,也就是tryLock,如果想实现阻塞(或者阻塞超时)加锁,只修反复执行lock伪代码直到加锁成功为止即可。基于DB的分布式锁其实有一个问题,那就是如果加锁成功后,client端宕机或者由于网络原因导致没有解锁,那么其他client就无法对该key_id进行加锁并且无法释放了。为了能够让锁失效,需要在应用层加上定时任务,去删除过期还未解锁的记录,比如删除2分钟前未解锁的伪代码如下:def clear_timeout_lock : exec sql : delete from lock_table where update_time < ADDTIME(NOW(),'-00:02:00')因为单实例DB的TPS一般为几百,所以基于DB的分布式性能上限一般也是1k以下,一般在并发量不大的场景下该分布式锁是满足需求的,不会出现性能问题。不过DB作为分布式锁服务需要考虑单点问题,对于分布式系统来说是不允许出现单点的,一般通过数据库的同步复制,以及使用vip切换Master就能解决这个问题。以上DB分布式锁是通过insert来实现的,如果加锁的数据已经在数据库中存在,那么用select xxx where key_id = xxx for udpate方式来做也是可以的。顺便在此给大家推荐一个Java架构方面的交流学习群:698581634,进群即可免费获取Java架构学习资料:里面有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,群里一定有你需要的资料,大家赶紧加群吧。Redis锁Redis锁是通过以下命令对资源进行加锁:set key_id key_value NX PX expireTime其中,set nx命令只会在key不存在时给key进行赋值,px用来设置key过期时间,key_value一般是随机值,用来保证释放锁的安全性(释放时会判断是否是之前设置过的随机值,只有是才释放锁)。由于资源设置了过期时间,一定时间后锁会自动释放。set nx保证并发加锁时只有一个client能设置成功(Redis内部是单线程,并且数据存在内存中,也就是说redis内部执行命令是不会有多线程同步问题的),此时的lock/unlock伪代码如下:def lock: if (redis.call('set', KEYS[1], ARGV[1], 'ex', ARGV[2], 'nx')) then return true end return falsedef unlock: if (redis.call('get', KEYS[1]) == ARGV[1]) then redis.call('del', KEYS[1]) return true end return false分布式锁服务中的一个问题如果一个获取到锁的client因为某种原因导致没能及时释放锁,并且redis因为超时释放了锁,另外一个client获取到了锁,此时情况如下图所示: 那么如何解决这个问题呢,一种方案是引入锁续约机制,也就是获取锁之后,释放锁之前,会定时进行锁续约,比如以锁超时时间的1/3为间隔周期进行锁续约。关于开源的redis的分布式锁实现有很多,比较出名的有redisson、百度的dlock,关于分布式锁,笔者也写了一个简易版的分布式锁redis-lock,主要是增加了锁续约和可同时针对多个key加锁的机制。对于高可用性,一般可以通过集群或者master-slave来解决,redis锁优势是性能出色,劣势就是由于数据在内存中,一旦缓存服务宕机,锁数据就丢失了。像redis自带复制功能,可以对数据可靠性有一定的保证,但是由于复制也是异步完成的,因此依然可能出现master节点写入锁数据而未同步到slave节点的时候宕机,锁数据丢失问题。顺便在此给大家推荐一个Java架构方面的交流学习群:698581634,进群即可免费获取Java架构学习资料:里面有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,群里一定有你需要的资料,大家赶紧加群吧。zookeeper分布式锁ZooKeeper是一个高可用的分布式协调服务,由雅虎创建,是Google Chubby的开源实现。ZooKeeper提供了一项基本的服务:分布式锁服务。zookeeper重要的3个特征是:zab协议、node存储模型和watcher机制。通过zab协议保证数据一致性,zookeeper集群部署保证可用性,node存储在内存中,提高了数据操作性能,使用watcher机制,实现了通知机制(比如加锁成功的client释放锁时可以通知到其他client)。zookeeper node模型支持临时节点特性,即client写入的数据时临时数据,当客户端宕机时临时数据会被删除,这样就不需要给锁增加超时释放机制了。当针对同一个path并发多个创建请求时,只有一个client能创建成功,这个特性用来实现分布式锁。注意:如果client端没有宕机,由于网络原因导致zookeeper服务与client心跳失败,那么zookeeper也会把临时数据给删除掉的,这时如果client还在操作共享数据,是有一定风险的。基于zookeeper实现分布式锁,相对于基于redis和DB的实现来说,使用上更容易,效率与稳定性较好。curator封装了对zookeeper的api操作,同时也封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等,使用curator进行分布式加锁示例如下: org.apache.curator curator-framework 2.12.0 org.apache.curator curator-recipes 2.12.0public static void main(String[] args) throws Exception { String lockPath = "/curator_recipes_lock_path"; CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.193.128:2181") .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build(); client.start(); InterProcessMutex lock = new InterProcessMutex(client, lockPath); Runnable task = () -> { try { lock.acquire(); try { System.out.println("zookeeper acquire success: " + Thread.currentThread().getName()); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } finally { lock.release(); } } catch (Exception ex) { ex.printStackTrace(); } }; ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { executor.execute(task); } LockSupport.park();}总结从上面介绍的3种分布式锁的设计与实现中,我们可以看出每种实现都有各自的特点,针对潜在的问题有不同的解决方案,归纳如下:性能:redis > zookeeper > db。避免死锁:DB通过应用层设置定时任务来删除过期还未释放的锁,redis通过设置超时时间来解决,而zookeeper是通过临时节点来解决。可用性:DB可通过数据库同步复制,vip切换master来解决,redis可通过集群或者master-slave方式来解决,zookeeper本身自己是通过zab协议集群部署来解决的。注意,DB和redis的复制一般都是异步的,也就是说某些时刻分布式锁发生故障可能存在数据不一致问题,而zookeeper本身通过zab协议保证集群内(至少n/2+1个)节点数据一致性。锁唤醒:DB和redis分布式锁一般不支持唤醒机制(也可以通过应用层自己做轮询检测锁是否空闲,空闲就唤醒内部加锁线程),zookeeper可通过本身的watcher/notify机制来做。使用分布式锁,安全性上和多线程(同一个进程内)加锁是没法比的,可能由于网络原因,分布式锁服务(因为超时或者认为client挂了)将加锁资源给删除了,如果client端继续操作共享资源,此时是有隐患的。因此,对于分布式锁,一个是尽量提高分布式锁服务的可用性,另一个就是要部署同一内网,尽量降低网络问题发生几率。这样来看,貌似分布式锁服务不是“完美”的(PS:技术貌似也不好做到十全十美 :( ),那么开发人员该如何选择分布式锁呢?最好是结合自己的业务实际场景,来选择不同的分布式锁实现,一般来说,基于redis的分布式锁服务应用较多。
本站涵盖的内容、图片、视频等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系我们删除!联系邮箱:ynstorm@foxmail.com 谢谢支持!
1. pate,越南有什么特色美食?
1 |国民面条——Pho
没吃过河粉不算到过越南,但来了越南却欣赏不来一碗正宗的 Pho,才是真正的原罪所在。
首先,把 Pho 读成 “fou”,你就已经露馅了!人家的正确发音念 “fuh~”,大致是英音、上扬版的 “fur” 的读法。
如今越南青年全民护肤,个个白嫩水灵,你一声降调的 “fou”,原本把你当自己人一通寒暄的餐厅阿姨,会产生巨大心理落差,悻悻地丢你一本英文菜单。
就算成功伪装到点菜阶段,你还有很多关要过——
汤头该什么颜色?牛肉点哪个部位的?端上来一大盘娇艳欲滴的香草放多少?海鲜酱和是拉差酱,加了会不会被邻座鄙视?......
一碗 Pho 的正确打开姿势
【汤】
*牛肉粉配牛骨、牛尾汤,鸡肉粉配整鸡汤,要加桂皮八角丁香小豆蔻等熬制近十小时,再以焦糖化的洋葱、姜和鱼露增味...琥珀色、甘甜回味、透亮见底的汤,是好粉的第一要义&灵魂,一碗 Pho 上桌,先尝汤!
【肉】
*牛肉(Bo):各类部位轮番上阵,生熟肉,瘦油腩,筋腱骨丸……翻谁牌子随你喜好
*鸡肉(Ga):除了鸡丝,也可以是一整条鸡大腿儿
【粉】
*粗细不是关键,当日现备、雪白透光的新鲜大米河粉,出锅要滑而不烂!
【配菜&香草】
*豆芽,青柠,青红辣椒...除了你熟悉的带梗罗勒、薄荷,常常会出现味道更重、跟香菜差了一个字母的刺芹,Culantro!
*每种香草各取几片即可,不要整片叶子往汤里放,掰碎有助于香气的释放。
【酱】
*一般有海鲜酱、是拉差辣酱和鱼露——前两种,建议汤头靠谱时别挤在汤里,但要给鱼露疯狂打 call!
2 |Banh Mi (越南法包)
法国人在越南殖民了大半个世纪,即便西贡城早就改姓了胡,但越式法包 Banh Mi,还是在越南人的“本土化”之后,留下来成为了当地首屈一指的美味。
到胡志明市的头一件事,自然是开启疯狂寻找 Banh Mi 模式!
你也许不屑,越南法包,还能好吃过巴黎的?
说越南法包正宗,法国人肯定会摇头,但任何一种食物传入异邦,免不了入乡随俗接受改造,日本的意面如此,越南法包亦不例外。
有别于印象中硬朗的传统法包,越南人在面粉里掺入大米粉,外壳依旧脆韧,内里口感却蓬松轻盈,更受本地人欢迎,同时还省了不少钱!
一枚典型的 Banh Mi 里,先要厚涂一层法风浓重的 Pate(猪、鸡鸭肝酱),然后夹进三两片 Headcheese(猪皮、猪耳和各种杂碎做成的冻肉块)、叉烧、纯肉火腿片……还需备齐肉松、酸黄瓜条、腌红白萝卜,最后往里恶狠狠挤一坨混着黄油的蛋黄酱,才算功德圆满。
对了,要不是因为 Banh Mi 的存在,此生从没觉得香菜这么好吃过!
小吃,多到能写字典!
3.越南的街头巷尾,多得是能写成百科全书的稀奇小吃,会让你觉得发现了新大陆!
Bun Cha,河内名吃,前两年奥巴马上 CNN 安东尼波登的节目,俩人相约去河内小馆子里吸的,就是这碗越北最经典的猪肉蘸汤米线。
在西贡的 145 Bùi Viện,你也能找到一碗从河内穿越来的地道 Bun Cha。老板英文说得溜,热情招呼你点这点那,没带现金还特意关照吃完再取。
上了菜,他又主动示范米粉的正确打开方式:用手拆散米粉团,分几次投入浓口的酸甜蘸汤,就着撕碎的菜叶、辣椒碎和蒜末、烤得酥润的猪颈肉和绞肉饼(跟 Banh Mi 37 里的味道相近)一起送下肚,用英文夸张比喻,就是“it’s to die for!”
还被安利了炸春卷,吃法同样是浸到这鱼露汁,美味!
Cơm tấm An Dương Vương
其实 Com Tam 就是个炭烤猪排盖浇饭。虽鲜为外人知晓,但 Com Tam 可谓越南的隐世美味!
对于烤猪肉这件事,越南人算登峰造极了。谁让你们家城管允许沿街架一台大烤炉,随时往炭上扔超大块的厚切猪排呢?
反复刷酱跟鱼露那是必须的,但最幸福的莫过于,吃的时候再来一碟鱼露,拌饭!
4越式田螺塞肉,个头颇为壮硕,秒杀一众杭帮菜馆。猪肉和螺肉打得既粗粒又筋道,每只里面还塞了根香茅提味,不由得感叹,越南人天生五行不缺香草,太会玩!
肋排的肉头无比紧致却能一秒脱骨,粗粒海盐和鱼露搭档的料碟,虽是头一次见,但混着迷人的炙烤香气,确认鱼露是百搭之王无误了!
2. 海洋奇缘的所有歌曲?
《海洋奇缘》是一部由迪士尼于2016年推出的动画电影。电影的音乐由Musker、Clements以及Lin-Manuel Miranda等人创作。以下是电影《海洋奇缘》中的主要歌曲列表:
1. "Tulou Tagaloa"(电影开头歌曲)
2. "An Innocent Warrior"
3. "知道谁是你" ("Where You Are")
4. "你知道回家的路" ("How Far I'll Go")
5. "We Know the Way"
6. "Moana, Make War" (未在最终电影中使用)
7. "How Far I'll Go" (享有广泛的知名度和家喻户晓的歌曲)
8. "You're Welcome" (为毛伊(Maui)的特色歌曲,表达了他自豪和乐于助人的性格)
9. "Shiny" (为电影的反派克拉肯(Tamatoa)演唱的歌曲)
10. "Logo Te Pate"
11. "I Am Moana (Song of the Ancestors)"
12. "Know Who You Are"
13. "We Know The Way" (Finale)
14. "How Far I'll Go" (Alessia Cara版本,片尾曲)
15. "You're Welcome" (Jordan Fisher/Lin-Manuel Miranda版本)
以上就是《海洋奇缘》的主要歌曲列表,其中"你知道回家的路"和"You're Welcome"这两首歌在全球范围内都非常有影响力。
3. 现在学习分布式锁还来得及吗?
来得及,学无止境!
多线程情况下对共享资源的操作需要加锁,避免数据被写乱,在分布式系统中,这个问题也是存在的,此时就需要一个分布式锁服务。常见的分布式锁实现一般是基于DB、Redis、zookeeper。下面笔者会按照顺序分析下这3种分布式锁的设计与实现,想直接看分布式锁总结的小伙伴可直接翻到文档末尾处。分布式锁的实现由多种方式,但是不管怎样,分布式锁一般要有以下特点:排他性:任意时刻,只能有一个client能获取到锁容错性:分布式锁服务一般要满足AP,也就是说,只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃或者网络不可达除了以上特点之外,分布式锁最好也能满足可重入、高性能、阻塞锁特性(AQS这种,能够及时从阻塞状态唤醒)等,下面就话不多说,赶紧上(开往分布式锁的设计与实现的)车~DB锁在数据库新建一张表用于控制并发控制,表结构可以如下所示:CREATE TABLE `lock_table` ( `id` int(11) unsigned NOT NULL COMMENT '主键', `key_id` bigint(20) NOT NULL COMMENT '分布式key', `memo` varchar(43) NOT NULL DEFAULT '' COMMENT '可记录操作内容', `update_time` datetime NOT NULL COMMENT '更新时间', PRIMARY KEY (`id`,`key_id`), UNIQUE KEY `key_id` (`key_id`) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;key_id作为分布式key用来并发控制,memo可用来记录一些操作内容(比如memo可用来支持重入特性,标记下当前加锁的client和加锁次数)。将key_id设置为唯一索引,保证了针对同一个key_id只有一个加锁(数据插入)能成功。此时lock和unlock伪代码如下:def lock : exec sql: insert into lock_table(key_id, memo, update_time) values (key_id, memo, NOW()) if result == true : return true else : return falsedef unlock : exec sql: delete from lock_table where key_id = 'key_id' and memo = 'memo'注意,伪代码中的lock操作是非阻塞锁,也就是tryLock,如果想实现阻塞(或者阻塞超时)加锁,只修反复执行lock伪代码直到加锁成功为止即可。基于DB的分布式锁其实有一个问题,那就是如果加锁成功后,client端宕机或者由于网络原因导致没有解锁,那么其他client就无法对该key_id进行加锁并且无法释放了。为了能够让锁失效,需要在应用层加上定时任务,去删除过期还未解锁的记录,比如删除2分钟前未解锁的伪代码如下:def clear_timeout_lock : exec sql : delete from lock_table where update_time < ADDTIME(NOW(),'-00:02:00')因为单实例DB的TPS一般为几百,所以基于DB的分布式性能上限一般也是1k以下,一般在并发量不大的场景下该分布式锁是满足需求的,不会出现性能问题。不过DB作为分布式锁服务需要考虑单点问题,对于分布式系统来说是不允许出现单点的,一般通过数据库的同步复制,以及使用vip切换Master就能解决这个问题。以上DB分布式锁是通过insert来实现的,如果加锁的数据已经在数据库中存在,那么用select xxx where key_id = xxx for udpate方式来做也是可以的。顺便在此给大家推荐一个Java架构方面的交流学习群:698581634,进群即可免费获取Java架构学习资料:里面有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,群里一定有你需要的资料,大家赶紧加群吧。Redis锁Redis锁是通过以下命令对资源进行加锁:set key_id key_value NX PX expireTime其中,set nx命令只会在key不存在时给key进行赋值,px用来设置key过期时间,key_value一般是随机值,用来保证释放锁的安全性(释放时会判断是否是之前设置过的随机值,只有是才释放锁)。由于资源设置了过期时间,一定时间后锁会自动释放。set nx保证并发加锁时只有一个client能设置成功(Redis内部是单线程,并且数据存在内存中,也就是说redis内部执行命令是不会有多线程同步问题的),此时的lock/unlock伪代码如下:def lock: if (redis.call('set', KEYS[1], ARGV[1], 'ex', ARGV[2], 'nx')) then return true end return falsedef unlock: if (redis.call('get', KEYS[1]) == ARGV[1]) then redis.call('del', KEYS[1]) return true end return false分布式锁服务中的一个问题如果一个获取到锁的client因为某种原因导致没能及时释放锁,并且redis因为超时释放了锁,另外一个client获取到了锁,此时情况如下图所示: 那么如何解决这个问题呢,一种方案是引入锁续约机制,也就是获取锁之后,释放锁之前,会定时进行锁续约,比如以锁超时时间的1/3为间隔周期进行锁续约。关于开源的redis的分布式锁实现有很多,比较出名的有redisson、百度的dlock,关于分布式锁,笔者也写了一个简易版的分布式锁redis-lock,主要是增加了锁续约和可同时针对多个key加锁的机制。对于高可用性,一般可以通过集群或者master-slave来解决,redis锁优势是性能出色,劣势就是由于数据在内存中,一旦缓存服务宕机,锁数据就丢失了。像redis自带复制功能,可以对数据可靠性有一定的保证,但是由于复制也是异步完成的,因此依然可能出现master节点写入锁数据而未同步到slave节点的时候宕机,锁数据丢失问题。顺便在此给大家推荐一个Java架构方面的交流学习群:698581634,进群即可免费获取Java架构学习资料:里面有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系,群里一定有你需要的资料,大家赶紧加群吧。zookeeper分布式锁ZooKeeper是一个高可用的分布式协调服务,由雅虎创建,是Google Chubby的开源实现。ZooKeeper提供了一项基本的服务:分布式锁服务。zookeeper重要的3个特征是:zab协议、node存储模型和watcher机制。通过zab协议保证数据一致性,zookeeper集群部署保证可用性,node存储在内存中,提高了数据操作性能,使用watcher机制,实现了通知机制(比如加锁成功的client释放锁时可以通知到其他client)。zookeeper node模型支持临时节点特性,即client写入的数据时临时数据,当客户端宕机时临时数据会被删除,这样就不需要给锁增加超时释放机制了。当针对同一个path并发多个创建请求时,只有一个client能创建成功,这个特性用来实现分布式锁。注意:如果client端没有宕机,由于网络原因导致zookeeper服务与client心跳失败,那么zookeeper也会把临时数据给删除掉的,这时如果client还在操作共享数据,是有一定风险的。基于zookeeper实现分布式锁,相对于基于redis和DB的实现来说,使用上更容易,效率与稳定性较好。curator封装了对zookeeper的api操作,同时也封装了一些高级特性,如:Cache事件监听、选举、分布式锁、分布式计数器、分布式Barrier等,使用curator进行分布式加锁示例如下: org.apache.curator curator-framework 2.12.0 org.apache.curator curator-recipes 2.12.0public static void main(String[] args) throws Exception { String lockPath = "/curator_recipes_lock_path"; CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.193.128:2181") .retryPolicy(new ExponentialBackoffRetry(1000, 3)).build(); client.start(); InterProcessMutex lock = new InterProcessMutex(client, lockPath); Runnable task = () -> { try { lock.acquire(); try { System.out.println("zookeeper acquire success: " + Thread.currentThread().getName()); Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } finally { lock.release(); } } catch (Exception ex) { ex.printStackTrace(); } }; ExecutorService executor = Executors.newFixedThreadPool(10); for (int i = 0; i < 1000; i++) { executor.execute(task); } LockSupport.park();}总结从上面介绍的3种分布式锁的设计与实现中,我们可以看出每种实现都有各自的特点,针对潜在的问题有不同的解决方案,归纳如下:性能:redis > zookeeper > db。避免死锁:DB通过应用层设置定时任务来删除过期还未释放的锁,redis通过设置超时时间来解决,而zookeeper是通过临时节点来解决。可用性:DB可通过数据库同步复制,vip切换master来解决,redis可通过集群或者master-slave方式来解决,zookeeper本身自己是通过zab协议集群部署来解决的。注意,DB和redis的复制一般都是异步的,也就是说某些时刻分布式锁发生故障可能存在数据不一致问题,而zookeeper本身通过zab协议保证集群内(至少n/2+1个)节点数据一致性。锁唤醒:DB和redis分布式锁一般不支持唤醒机制(也可以通过应用层自己做轮询检测锁是否空闲,空闲就唤醒内部加锁线程),zookeeper可通过本身的watcher/notify机制来做。使用分布式锁,安全性上和多线程(同一个进程内)加锁是没法比的,可能由于网络原因,分布式锁服务(因为超时或者认为client挂了)将加锁资源给删除了,如果client端继续操作共享资源,此时是有隐患的。因此,对于分布式锁,一个是尽量提高分布式锁服务的可用性,另一个就是要部署同一内网,尽量降低网络问题发生几率。这样来看,貌似分布式锁服务不是“完美”的(PS:技术貌似也不好做到十全十美 :( ),那么开发人员该如何选择分布式锁呢?最好是结合自己的业务实际场景,来选择不同的分布式锁实现,一般来说,基于redis的分布式锁服务应用较多。
本站涵盖的内容、图片、视频等数据系网络收集,部分未能与原作者取得联系。若涉及版权问题,请联系我们删除!联系邮箱:ynstorm@foxmail.com 谢谢支持!