- Published on
超浓缩的码农笔记
- Authors
- Name
- McDaddy(戣蓦)
语言篇
各个语言的核心差异
以Java为基准,它提供了继承、多态、反射等诸多特性,同时也是设计模式最好的践行者,相比之下其他语言
Go
- 没有类的概念,用
type struct
来定义对象结构属性,用方式实现直接来扩展类型的方法 - 用属性首字母大小写来定义属性的访问权限,大写就是公开,小写就是私有
- 没有继承,秉承组合大于继承的思维,需要实现类似继承的方式就是把一个结构体嵌入另一个结构体中
- 基于接口编程,是通过Duck Typing的思想实现,即只要两个对象拥有同名同参的方法,即可视为基于同一个接口的实现,比如只要有Read和Write方法,就可以视为是一个IO接口实现
JS
- 同样没有类的概念,class只是ES的语法糖,JS可以直接在对象上定义属性和方法
- 如果想复用一个类型,而不是每次都重新定义,也可以使用构造函数的方式,配合new关键字来创建
- 本身也没有继承,是通过原型链来将一个对象的
__proto__
指向另一个对象,来实现类似继承的效果 - 基于接口编程,在语法层面其实没有
Python
后端篇
Redis
Redis数据不一致问题
假设当前更新Redis的逻辑是,更新DB => 更新Redis缓存
假如两条线程同时更新DB,线程A的DB设置value=100 => 线程B的DB设置value=200 => 线程B的Redis更新缓存为200 => 线程A的Redis更新缓存为100.
此时正确的最终结果应该是200,但是因为执行的延迟缓存中是100,就产生了不一致的问题
解法就是Cache Aside Pattern工作流
读数据:如果缓存命中就返回数据,否则从数据库读取并更新缓存
写数据:先更新数据库,然后手动让此Key的缓存失效,即在写的过程中不去更新缓存
这样即使读请求是在AB两个线程执行更新的间隙发生,得到的数据在那个时间点也是合理的,而在两个线程都结束之后就更加没问题了
缓存穿透
一般只非正常情况下,比如遭到黑客攻击,用海量的不存在的Key来做查询,这样缓存就无法命中,只能去DB进行查询。一旦DB符合太重,就可能导致系统延迟甚至崩溃
解决问题的初级办法,是把空值也缓存起来,并设置过期时间,这样在短时间内,空值就可以命中缓存返回。但是无法应对不重复的Key
终极解决方法是使用布隆过滤器,它可以做到判断一个Key绝对不在一个集合里或者可能存在一个集合里
比如基于ID字段建立布隆过滤器,访问先到达过滤器,如果判断绝对不在集合中,那就拒绝访问,否则就放行到Redis中,进行过去的操作。从而大大减轻DB的压力
布隆过滤器的原理
假设有一段8bit的内存,每位都可以用0/1存储,初始下所以都是0,比如我的ID是"A",那么就把A做Hash运算成为一个数字之后与8取模,比如得到数字2,那么就在8位的第二个位置存1。
此时请求到来,就会用同样的方法计算下Key在此内存中的位置,如果这个位置为1就代表可能在集合里,否则必然不在集合里。
这里说的可能是,总共就8位地址,即使不同数字取模也是可能落在一个位置上的,所以不是百分百有效,但是一旦为0就代表,这个hash值肯定不在集合中
所以为了把这个可能
的精确度提升,有两种方式
- 把内存的长度扩大,这样落到同一个位置的可能性就小了
- 使用不同的hash算法对同一个Key进行计算,比如对一个Key用三种方式计算,这样就会落到三个不同的位置上,那另外一个字符会同时落在相同的三个位置的概率就非常小了
- 但如果内存用的太多就不划算了,hash算法用太多,过滤的速度肯定也会变慢,所以都不是越大越多越好
综上,布隆过滤器可以做到用少量的内存就预判断一个Key是否在集合中,是一种非常高效的防护手段
热点数据失效
当一个热点数据失效,可能会有N条线程同时访问DB要求得到新的数据,此时DB的压力就会变得很大
解决办法就是当进程发现Redis缓存失效 => 通过Redis获取分布式锁 => 如果拿锁不成功就等待一会儿再尝试看看缓存有没有,如果还是没有那就再尝试拿锁,有数据则返回,如此往复 => 拿锁成功 => 从DB获取数据 => 更新缓存释放分布式锁 => 返回数据
这样不论有多少线程同时请求这条热点数据,都只有一条线程会访问数据库
数据库
分布式下数据库怎么做读写分离的数据同步?
- 基于SQL的重放,就是把在主上执行的insert/update/delete语句在从数据库上再执行一遍。但这有一个问题就是比如update_timestamp还有random函数产生的值会在各个库中不一致
- 基于行的复制,通过日志的方式,告诉从数据库,具体发生了什么变化,然后直接复制变化的部分就行。但这里的问题就是比如全表更新一个字段的值,那可能要产生几十万条复制记录。
所以综上,两种方式需要按需混合使用
分布式下的数据延迟
即主数据库已经写入,但是还没来得及同步到从数据库,读的请求就发生了。 这就产生了数据不一致的问题。
数据同步只能做到最终一致性。如果复制也做成同步ACK,那整个吞吐也会受到影响。
一个简单的策略就是,如果一个数据主键key被更新,那就在缓存里记录一下,并设置一个较短的过期时间,如果读请求命中缓存,那请求就从主DB里读,反之就正常从从DB里读
架构篇
负载均衡
- DNS负载均衡:即多台机器IP都绑定到一个域名下,通过DNS返回不同的IP来做到负载均衡,同一个用户只会访问一个具体IP。最大的问题是,一旦一个IP实例挂了或者被主动删除,因为DNS缓存的存在,用户就会访问到失效的节点
LB
就是主动加一个中间层来接管流量,做到流量的分发,从外部看,只有一个IP入口,通过这个Load Balancer来做转发。本质就是一个网关。转发的规则有且不限于
- 轮询
- 加权轮询:让性能好的节点权重更高
- 最少连接
- 加权最少连接
假设一个请求体很大,那就会被拆分成N个数据包,如果每个包都被转发到不同的节点就乱了,所以这个LB必须建立在OSI的第四层(传输层)或以上(如果建立在第三层网络层,那就是以数据包为单位了)。一般情况下,我们分为四层负载均衡和七层负载均衡,区别就是
- 四层管到TCP这一层,一般是管粗粒度的流量切分,比如一个请求应该去中国机房还是美国机房,是流量最先遇到的负载入口,如LVS(Linux Virtual Server)
- 七层是管到HTTP应用层,可以根据URL、浏览器携带的信息来做流量分发,典型的比如通过header来做灰度,同时还有类似如处理静态文件,缓存等功能。它主要是用来把流量精确得达到具体的应用服务上,如nginx
VIP
让所有服务器都公用一个相同IP地址
微服务下的分布式事务
重复执行问题
假设在一个单机里,如果调用一个函数或者进程失败,自然就知道结果,该回滚回滚该commit就commit,所以单机事务比较简单
但是在微服务的场景下,服务与服务间都是用Restful API做相互的调用,假设一个请求是向账户里增加100元,请求发出后发生了网络问题,请求方默认对方失败,所以又发了一次请求重试,但实际上服务调用是成功的,结果账户就被增加了200元
解决这个问题的核心方法是为每个可能非幂等的服务调用添加一个事务ID
非幂等就是如创建删除数据等不能重复执行的写操作,而像读数据这种就是幂等的,一言以蔽之就是无论执行多少次都是一样结果的就是幂等操作
假设我有一个全局不重复的事务ID,那每次支持事务前都检查下DB,如果这个ID存在,就不需要执行,因为前面已经执行过了
那如何得到一个全局不重复的ID呢?
- UUID,虽然重复的可能性很小,但是它是一个128数字字母的组合,是没有规律的,所以基本没法排序。因为有序的ID比较容易定位,所以并不合适
- 用数据库的自增ID列,但是在分布式的环境里保持唯一复杂度高
- snowflake也称为雪花ID,它是由41位时间戳 + 10机器ID + 12位序列号组成,一台机器一秒可以产生400w+的ID,且保证全局唯一并且升序排列
高可用
高可用本质要解决的问题就是单点失败的问题。因为任何一个单点都用承载的上限
Nginx如果做高可用?
通过Keepalived的方式,用主从的方式部署多个实例,但是有且只有一个实例是在工作的,当主挂了之后,原地待命的从实例就开始接管。在整个过程中ngnix集群只会对外暴露一个IP作为对外的统一入口
主需要实时向从发送心跳,如果停止,从服务器就会绑定VIP来取代master
Redis如何做高可用?
- 使用Redis Cluster
- 在集群中的每组实例,都只负责一部分的数据存储,比如3组实例就是负责三分之一的数据量
- 每存储一个key,都可以通过Hash算法得到一个%16384(就是一个数字)的值,看这个值落在哪组实例的区域里面就存在具体哪个实例里面,比如1号实例负责1 ~ 5000, 2号负责5001 ~ 10000
- 当要取一个key时,可以访问任意节点,判断是不是存在自身,如果不是就转发给对应的实例组来返回
- 当添加一个新节点,每个原先的节点都会分出一部分数据交给新的实例。相反删除一个节点,也会把这个节点的数据分给活着的节点
- 最后就是故障转移,上面说的每个组,其实都可以是一个集群,每个集群内部有主从关系,实时备份,一旦主挂了,从接上
- 总结就是前面的6点主要实现,在单机内存有限(比如64G)的情况下,怎么去缓存TB/PB级的数据,最后一步是描述故障转移的实现
服务端怎么做高可用?
- 做到无状态,即stateless,这样就可以做无限水平扩展
- 把类似session等带状态的内容,存储到数据库或者Redis
- 最重要的是在nginx与服务端转发的层面,要做到负载均衡
数据库如何做高可用?
在分布式环境下,做到数据的强一致性是很难的
- 读写分离,master节点可读可写,slave节点只能读,这是根据读多写少这个规律来的,同时读写分离可以减少锁的竞争
- 主从间实时备份
- 添加一层代理层,应用层无需知道谁是主谁是从