V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  fkdtz  ›  全部回复第 2 页 / 共 37 页
回复总数  725
1  2  3  4  5  6  7  8  9  10 ... 37  
我认为是的,这么说吧如果是一个全新项目不需要考虑兼容已有的代码和库,肯定是首选协程。
至少没遇到过能用协程而不用,反而选择用多线程的情况。
203 天前
回复了 lipengxs 创建的主题 MacBook 最近想买一个 mac book 用于个人开发
@HFX3389 贵是贵但也没办法,arm 版 Mac 已经不能扩容内存了,16G 绝对后悔。
204 天前
回复了 lipengxs 创建的主题 MacBook 最近想买一个 mac book 用于个人开发
别买 16G 内存就行了,根本不够用。
朋友你可能想多了,按照你这个代码出事了我给你担责任
有两种路线我比较认可:

一种是边学边用,边踩坑边理解。
这种机会一般只存在初创公司不断发展壮大的同时业务系统也在逐渐迭代升级的过程中,而且业务能在几经折腾之后还能存活下来的,只能说可遇不可求。

另一种是抓住几个经典的成熟系统深入研究横向对比反复蹂躏和被蹂躏,从中汲取系统设计的真谛。
我推荐楼主试试第二种方式。

在我看来应对高并发场景的核心要义是「消除单点」+「状态同步」两手抓。消除单点意味着分布式,这样可以提高系统可用性和吞吐量,而一旦引入了分布式就涉及到节点之间的状态同步问题,状态同步意味着系统中数据要保持某种程度上的一致。最理想的系统肯定是任何时刻都可用,且任何地点读取到的内容都一致,但鱼和熊掌不可兼得,如何在这两者之间寻找一个平衡就是不同项目的差异点,由此才引申出不同项目的特点和适用场景。

例如 InnoDB 为了支持读并发采用了 B+树、Buffer Pool 和 MVCC ,为了支持写并发采用了行级锁,而在单机性能达到瓶颈之后自然会演进到集群架构,也就是消除单点;主从架构中必然涉及到主从节点间的数据同步,如果数据同步采用全异步策略那么性能最高但安全性最差,反之如果采用全同步策略那么性能最差但安全性最高,系统设计一直都是在取舍。
再例如 Redis 全内存操作保证了其性能的底线,而为了在性能和内存占用之间作取舍,Redis 针对不同数据量级设计了不同的数据类型。当单机达到瓶颈之后也自然演进到分布式架构,集群架构思路是分片,而每一个分片之内又是一个主从来提高可用性;如果主从复制完全是异步的,那肯定存在数据丢失的问题,所以用 Redis 的 setnx 方式做分布式锁是存在隐患的,基于此 Redis 提出了 Redlock 算法处理分布式锁。另一种实现分布式锁的方式则是使用 zookeeper ,因为 zookeeper 实现的 ZAB 协议要求多数节点提交才确认成功,同时提供 sync 同步读保证顺序一致性,不过这样的话相对 Redis 性能就差一些。
除了架构上的取舍,在业务上也可以做调整,例如是否真的有必要把所有请求都打到同一组集群上吗?是否可以按业务、地区等维度拆分成不同集群,不同集群各自处理各自的流量,并发也就降下来了,也就有了更多调整空间。这符合康威法则的思想,即组织架构能够影响软件架构。
还有比如能否将数据放在离用户更近的地方?这样用户访问的路径就更短体验更好,服务也因为只需要服务一部分用户从而吞吐量更高性能更好。这也是空间换时间在分布式场景下的应用。
另外系统本身也需要保护,谁还不是个宝宝了,不能来多少流量就放多少流量进来,系统挂了谁也别玩了。这就涉及到限流、降级、熔断等保护后端的措施,所以服务状态监控、告警和自动扩缩容的需求也被提了出来,不过这里更偏向于基础设施和运维侧了。

总之面对高并发场景所面对的问题大体上都是相似的:高可用,高吞吐量,事务和一致性;而解决办法也都类似,在实现过程中不断做取舍:分层、缓存、复制和分片、共识算法思想及实现。学习过程中常常问问自己,这个系统是如何保证高可用和高吞吐的?是完整支持事务还是部分支持事务?以及如何实现事务?做出了什么级别的一致性保证并且如何实现的?

最后也推荐 ddia 《数据密集型应用系统设计》一书,我认为是程序员必读书目之一,值得多读几遍。
附上一个之前写的读书笔记 https://www.lipijin.com/reading-notes-ddia
216 天前
回复了 qinconquer 创建的主题 程序员 app 软件中的热门榜单怎么做的呢
我更倾向于不加任何干预,就是纯粹的根据用户行为做排行依据,但我是从电商角度出发,如果是内容产品可能会有跟多考虑。

如果希望做到有倾向性的内容展示,可以对某些类别或标签做降权甚至不进入榜单。

从产品角度还可以做成细分类别的热榜,这样就不会出现热榜中充斥着某一类或某几类的内容。
@waytodelay 没有人说其他接口啊,我怀疑你没搞清楚 tp999 的含义。总之这种现象出现并不奇怪,从你发出请求到你接到对方的响应,这中间涉及到很多环节,存在时间差是正常的,如果出现时间差相差非常多那一定是中间环节有异常导致的,可能是线程满了在排队,可能是 gc 了,可能是网络波动,可能是系统资源不够了等等。系统方面就得结合系统的监控看是不是配置不够了,网络丢包率之类的。服务内部的话很可能是线程问题或者 gc 了,可以加链路追踪 id ,然后日志打点收集,复现的时候对比看。
@waytodelay 我说的就是一个接口的 tp999 ,基本相当于这个接口的全部请求了。
正常吧,tp999 基本上等于把所有请求都包括了,那存在波动很正常,至于到底哪里慢了得全链路追踪。
这是一个很有趣的问题,发散开来有很多可以讨论的,我想到了一些点,内容比较多,结论是短信验证可能是现阶段最自然、成本最低的一种方案。

十年前人们可能并不会提出「为什么要搞这么多密码」这样的问题,因为那个时候网站也好移动 App 也好并不像今天这么丰富,而且用户的安全意识可能也并没有今天这么强,基本上都是一套账号密码走天下,在账号密码方面基本没啥痛点。

十年后的今天各种网站、移动 App 以及很多新兴模式的在线服务可以说数不胜数,琳琅满目,基本上人们的衣食住行日常生活都离不开在线服务,与此同时用户的安全意识也在增强,不会再使用同一套账户密码去注册所有的服务,这就带来一个现实问题,应该如何记住这些账户密码?可能有的人会用一个电子笔记本记下所有账户,也有的人设置的密码是有一定变换规则,还有人使用软件帮忙管理密码,但上述方案也各有各的问题不够完美。

提出为什么要搞这么多密码这一问题之前,让我们回头想想人们到底为什么需要密码,根本目的在于人们需要在使用服务之前证明我是我。想要证明我是我看上去很荒谬,但如果想做到绝对意义上的证明实际上是一件很困难的事,你必须依靠一些你以外的东西反过来证明你自己,不扯那么远我们只讨论现实可行的方案,之所以需要用到密码,这背后默认的前提是别人不知道我的密码,知道这个密码的一定是我。那么如果不想要密码,又想证明我是我,那是否可以找一个只有我知道而别人不知道的东西呢?

如果按这个思路去想,目前国内主流的手机短信验证码其实是最自然的一种方案:
随着移动互联网的爆发,手机已经成了人的延伸,是现实世界与虚拟世界之间的 socket ,现实与虚拟就是通过这一块小小的玻璃板双向通信,换句话说在虚拟世界里,你的手机就是你,手机短信也只有你自己知道。

这里并不是吹捧短信验证,我知道这背后还有实名制和审查的考虑,但从事实出发短信验证就是目前最自然的方案,这个自然指的是对用户、服务提供商、监管这三方的需求都能满足且不需要花很大成本。

虽然短信验证是一个比较自然的方案,但却不是一个完美的方案,如果别人拿到了我的手机卡那我就成了替身了。回到问题的本质,证明我是我更进一步的方案是生物识别,例如指纹、面容、虹膜等等,虽然这个方案比短信验证更接近问题本质,但却隐含了另一个问题,既隐私问题。可能会有人有这种顾虑:录入了面容之后我的信息会不会被滥用?会不会被出卖?有多少人愿意承担可能存在的隐私风险,来换取理论上更进一步的账户安全呢?这就好像在做架构中的 trade-off ,系统的可用性和一致性没办法既要又要,只能是牺牲一点这边从而换来提升一点那边。

那么假如有一个绝对权威公正的认证服务呢?人们只要在这里录入生物特征,之后所有的在线服务都接入到这个认证服务中验证用户身份,倒也不是没可能,SSL 证书颁发机构不就是这种形式吗,通过证书验证网站是不是真正的网站。
但方案理论可行实际上很难推广,确切的是没有动力推广,毕竟服务提供商也乐见用户使用手机号登录,毕竟有了手机号还可以做很多营销或者召回策略。
那为什么 CA 证书颁发的模式就可以推广下来呢?也是成本问题,因为早期的电商钓鱼网站和中间人给交易双方带来的损失大于成立一套 CA 认证的成本。
同样的道理在如何登录这件事上,用户属于弱势的一方,除非用户和服务商的利益受到的损失超过了建立一套新的认证体系的成本,否则很难推动,而现阶段短信验证码就是最自然可行、成本最低的一种方案。
@freakxx
请教这种命名咋了?
Redis 不是 AP 的么
225 天前
回复了 SlowDown 创建的主题 程序员 锁续期和锁归属问题?
都永久有效的锁了,还怎么自动释放?
你要不再好好看看
225 天前
回复了 SlowDown 创建的主题 程序员 锁续期和锁归属问题?
@SlowDown
这里强调的是锁带上有效期的必要性,即如果用一个永久有效的锁并且不使用续期线程会有什么问题。
另外我没看懂你的疑问是什么?
225 天前
回复了 SlowDown 创建的主题 程序员 锁续期和锁归属问题?
虽然有守护线程持续的续约锁,但还是有必要在释放锁时做校验的,关键在于「你不能假设系统任何时刻都能正常运行」
假如锁续期的守护进程出现异常而没有正常执行续期,那么直接释放锁而不做校验就会出现锁被误删除的情况。

其实你有没有想过,既然锁续期线程一直在给锁延长有效期,那不就相当于是一把永久有效的锁吗?那直接用一个不加有效期的永久锁不就好了,为什么还要弄个有效期再加一个续期线程?而且如果用一个永久有效的锁甚至连锁误删除的问题也不会存在了。

选择用带有效期的锁的原因跟前面的问题有同样的考虑:「你不能假设系统任何时刻都能正常运行」,如果主线程加锁后挂掉,那么不加有效期的锁就会一直存在,所以必须要带上有效期,带上有效期就引出了锁归属和锁续期的概念,进而才引出后面的各种方案。
228 天前
回复了 main1234 创建的主题 程序员 golang 关于 forrange 的一些疑问
我认为综合 4 、5 、6 楼的回复已经可以完整回答的楼主的问题了,不过貌似楼主有一点模糊,我根据自己的理解再具象化地补充一下,或许可以帮助楼主理解,也希望能和大家一起交流学习。
搞清楚下面 3 个 go 的特性可能有助于理解上面的代码发生了什么:
1.go 的自动引用和自动解引用; 2.defer 的求值时机和执行时机 3.for 循环变量只初始化一次之后一直在复用(go1.22 以前)

第一个特性自动引用和解引用指的是,如果一个方法是定义在指针类型上的,那么你可以通过该类型的值对象来调用方法。例如代码中 func (u *Users) GetName()定义在 *User 上,但却可以在 for 循环 users1[]Users 时通过 u.GetName() 调用。这里的完整写法其实应该是 (&u).GetName()。
自动解引用就是反过来,方法定义在值类型上,但允许你在指针类型上直接调用。

第二个特性 defer 的执行时机大家都懂,只是需要明确的是 defer 后面语句的求值时机,是在执行到这一行时就要求值,之后压栈。

第三个特性是循环变量 u 只初始化一次,即 u 的地址不会变(go1.22 之前),后面的循环是将新的列表元素值赋值给 u 。

现在回头看为什么第一个 for 打出 ccc ?
我们排除掉自动引用的干扰,还原完整写法,users1 的 for 循环中完整写法应该是 defer (&u).GetName(),执行到这里就得求值并压栈,压入的是 u 的地址,之后进入后面循环。
由于 u 只初始化一次,所以之后的循环中 u 的地址一直不变,只是在更新他的值,所以再次执行 defer (&u).GetName() 时压入的也还是 u 的地址,就这样一共循环 3 次,压了 3 次 u 的地址,最后 u 装的是 Users("c"),所以 GetName()打出三次 c 。

再看为什么第二个 for 打出 zyx ?
理解了第一个 for 也就能理解第二个 for 了,这次执行 defer u.GetName() 时不需要自动引用,因为 u 本身就是*User 类型,那么此时求值压栈,压入的就是 u 的值,注意 u 是指针,虽然 u 只初始化一次 u 的地址不变,但我们压入的并不是 u 的地址,而是 u 的值,u 的值是*User("x"),也就是 User("x")的地址,接着第二次循环,压入 User("y")地址,最后压入 User("z")地址,最终执行,得到结果就是 zyx 。

换一个角度思考,可以将 GetName 定义到 User 上,即 func (u Users) GetName(),其余代码不需要做改动,可以输出 zyxcba ,相当于 user1 循环不涉及自动引用,而 users2 循环中会自动解引用。

我想这也是很多 for 循环中如果嵌套函数或 goroutine 时,比较推荐用函数参数传值的方式而不是闭包的原因,因为 go 全都是值传递,这样就省掉了很多变量在函数内外生命周期的问题,心智负担轻了很多。

最后 4 楼提到在 go 1.22 版本做了修改,for 循环时循环变量已经改成每次循环都是一个全新变量了,这一点可以观察 u 的地址就能看到新版本确实每次循环都在发生变化,这样一来也就不存在上述问题了。
有过类似经历,我当时情况是被种了挖矿脚本导致 CPU 跑满。
解决办法是 top 找到异常进程干掉,找到异常 cron 清理掉,再把没有认证的端口都封掉。
228 天前
回复了 cuishuang 创建的主题 Redis Redis 加上密码后,整体性能下降 20%?
无论从实际使用经验,还是 Redis 的设计原则、还是 client server 通信原理来看,好像都不大可能啊。
他没说具体什么原因导致的吗?
极客时间上的课不都是付费的么,应该很严谨才对,评论中没有提出疑问的么。
个人有一个观点是不要过度设计,架构是演进出来的不是设计出来的。做一些可预见的设计优化是对的,但不要想的太多做的太早,否则你会发现要么设计的东西根本用不到、要么就是不符合业务发展需要不断重构。

回到问题,我认为封装的根本是分层思想。最基本的封装就是调用一个函数了,稍微进一步调用另一个文件中的函数,再如调用另一个包中的函数,再如调用另一个服务给你提供的函数,这些我认为都是分层,函数调用可以认为是陷入另一个层再回来,操作系统调用就是 trap 到内核层再回到用户层。
分层可以让你专注你自己的业务,其他的东西由别的层负责,例如限流、风控、鉴权、日志等都是与具体业务无关的通用的功能,就可以考虑封装为独立的一层。

到了具体实现环节我认为应该首先确定好接口,接口意味着协议,至少应该是一个在未来可以扩展的协议,这对分层来说是十分重要的,也意味着未来的修改没有历史包袱。另外应该尽量做到对业务无侵入性或尽量小,否则如果与业务有很强的耦合性就应该再思考一下是不是哪里不合理了。
1  2  3  4  5  6  7  8  9  10 ... 37  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3004 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 30ms · UTC 11:05 · PVG 19:05 · LAX 03:05 · JFK 06:05
Developed with CodeLauncher
♥ Do have faith in what you're doing.