• 完蛋,公司被一条 update 语句干趴了!

    大家好,我是小林。昨晚在群划水的时候,看到有位读者说了这么一件事。大概就是,在线上执行一条 update 语句修改数据库数据的时候,where 条件没有带上索引,导致业务直接崩了,被老板教训了一波这次我们就来看看:为什么会发生这种的事故?又该如何避免这种事故的发生?说个前提,接下来说的案例都是基于 InnoDB 存储引擎,且事务的隔离级别是可重复读。1 为什么会发生这种的事故?InnoDB 存储引擎的默认事务隔离级别是「可重复读」,但是在这个隔离级别下,在多个事务并发的时候,会出现幻读的问题,所谓的幻读是指在同一事务下,连续执行两次同样的查询语句,第二次的查询语句可能会返回之前不存在的行。因此 InnoDB 存储引擎自己实现了行锁,通过 next-key 锁(记录锁和间隙锁的组合)来锁住记录本身和记录之间的“间隙”,防止其他事务在这个记录之间插入新的记录,从而避免了幻读现象。当我们执行 update 语句时,实际上是会对记录加独占锁(X 锁)的,如果其他事务对持有独占锁的记录进行修改时是会被阻塞的。另外,这个锁并不是执行完 update 语句就会释放的,而是会等事务结束时才会释放。在 InnoDB 事务中,对记录加锁带基本单位是 next-key 锁,但是会因为一些条件会退化成间隙锁,或者记录锁。加锁的位置准确的说,锁是加在索引上的而非行上。比如,在 update 语句的 where 条件使用了唯一索引,那么 next-key 锁会退化成记录锁,也就是只会给一行记录加锁。这里举个例子,这里有一张数据库表,其中 id 为主键索引。假设有两个事务的执行顺序如下:可以看到,事务 A 的 update 语句中 where 是等值查询,并且 id 是唯一索引,所以只会对 id = 1 这条记录加锁,因此,事务 B 的更新操作并不会阻塞。但是,在 update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 间隙锁),相当于把整个表锁住了。假设有两个事务的执行顺序如下:可以看到,这次事务 B 的 update 语句被阻塞了。这是因为事务 A的 update 语句中 where 条件没有索引列,所有记录都会被加锁,也就是这条 update 语句产生了 4 个记录锁和 5 个间隙锁,相当于锁住了全表。因此,当在数据量非常大的数据库表执行 update 语句时,如果没有使用索引,就会给全表的加上 next-key 锁, 那么锁就会持续很长一段时间,直到事务结束。而这期间除了 select ... from语句,其他语句都会被锁住不能执行,业务会因此停滞,接下来等着你的,就是老板的挨骂。那 update 语句的 where 带上索引就能避免全表记录加锁了吗?并不是。关键还得看这条语句在执行过程中,优化器最终选择的是索引扫描,还是全表扫描,如果走了全表扫描,就会对全表的记录加锁了。2 又该如何避免这种事故的发生?我们可以将 MySQL 里的 sql_safe_updates 参数设置为 1,开启安全更新模式。官方的解释:If set to 1, MySQL aborts UPDATE or DELETE statements that do not use a key in the WHERE clause or a LIMIT clause. (Specifically, UPDATE statements must have a WHERE clause that uses a key or a LIMIT clause, or both. DELETE statements must have both.) This makes it possible to catch UPDATE or DELETE statements where keys are not used properly and that would probably change or delete a large number of rows. The default value is 0.大致的意思是,当 sql_safe_updates 设置为 1 时。update 语句必须满足如下条件之一才能执行成功:使用 where,并且 where 条件中必须有索引列;使用 limit;同时使用 where 和 limit,此时 where 条件中可以没有索引列;delete 语句必须满足如下条件之一才能执行成功:使用 where,并且 where 条件中必须有索引列;同时使用 where 和 limit,此时 where 条件中可以没有索引列;如果 where 条件带上了索引列,但是优化器最终扫描选择的是全表,而不是索引的话,我们可以使用 force index([index_name]) 可以告诉优化器使用哪个索引,以此避免有几率锁全表带来的隐患。3 总结不要小看一条 update 语句,在生产机上使用不当可能会导致业务停滞,甚至崩溃。当我们要执行 update 语句的时候,确保 where 条件中带上了索引列,并且在测试机确认该语句是否走的是索引扫描,防止因为扫描全表,而对表中的所有记录加上锁。我们可以打开 MySQL 里的 sql_safe_updates 参数,这样可以预防 update 操作时 where 条件没有带上索引列。如果发现即使在 where 条件中带上了列索引列,优化器走的还是全标扫描,这时我们就要使用 force index([index_name]) 可以告诉优化器使用哪个索引。这次就说到这啦,下次要小心点,别再被老板挨骂啦。

    小林coding update

  • 无题

    大家好,我是小林。不知道大家平时喜欢看什么类型电影,我个人比较喜欢看悬疑和科幻片,豆瓣上的高分悬疑电影我基本都看过了。我看悬疑电影不是为了锻炼脑力,而是为了图个看完电影久久不能忘怀的感觉,因为通常悬疑结尾都有个意想不到的结局。昨晚正好闲着没事,在豆瓣上找了个 8.5 评分的科幻片《火星救援》,应该有不少人已经看过了,毕竟是 2015 年的电影。我简单介绍下这电影的大概剧情,有 6 位宇航员在火星上采集样本,突然一场火星风暴袭来,尘土飞扬,他们被迫返回宇宙飞船里,而在返回的过程中,有位宇航员(主角)被一块飞在空中的太阳能板打到了,直接被拍飞,但是由于当时风暴太大,能见度很低,而且宙飞船有随时被吹翻的风险,所以剩下的宇航员只能放弃救援,返回宙飞船,飞回地球。这位主角宇航员没死,不然也不会有接下来的故事了。自己队友都走了,所以就剩他一个人在火星上了,于是整个电影主要就围绕主角是如何在火星上生存 500 多天,直到被队友救回地球的故事线。主角在火星上生存的时候,遇到了 N 多问题,而主角靠自己的生物学、化学、物理学、计算机学等知识一个一个将问题解决,最终才得以生存那么久。其中有一个地方我觉得很有意思,也是一个很关键的转折点。当时主角没办法跟地球上联络,然后他就去寻找 1996 年留在火星上的火星探测器,这台探测器以前是用来拍火星的地理环境的,所以这台机器是可以与地球联络。找到后,主角就尝试用火星探测器与地球联络。这台火星探测器的通信方式不是传输文字的,只能拍照和转动摄像头。主角与地球传输信息就很简单,只需要在摄像头前面的摆上画板文字,然后地球上的人控制这台火星探测器来拍摄照片,但是地球上的人无法通过一样的方式与主角通信,他们只能转动摄像头,向左转表示 yes,向右转表示 no。后面主角就开始思考,怎么利用会转动的摄像头来完成信息传递工作?一开始主角想到的是做个字母表,围着火星探测器画一个圈,放上 26 字母牌子,让地球上的人类转动摄像头,然后把摄像头转动指向的字母组成单词,就能知道地球上的人想表达什么了。但是这种方式平均圆周角度差只有 13 度,用 26 个字母围成圈太过于拥挤了,很难分辨出摄像头到底指哪儿。后面,主角后重新思考,想到了用 16 进制!围着火星探测器放上这些字母牌子,然后地球上的人类通过转动摄像头来传递信息。2 个 16 进制数就代表一个字母,比如十六进制 48,就代表字母 H,看到这是不是觉得很熟悉,没错这个就是 ASCII 编码。后面我查这个电影原版小说的作者原来也是码农,怪不得会设计这个场景呢。可能有的小伙伴会说,用十进制的 ASCII 编码不行吗,这样牌子不就放的更少了?十进制的 ASCII 编码范围是 0~126,也就是有时候需要 1 位数,或者 2 位数, 或者 3 位数才能表示一个 ASCII 码,这样主角是难分辨出到底是用几位十进制数来组合成一个 ASCII 码。而用了十六进制的 ASCII 编码,范围是 00~7E,始终都是两位数来表示一个 ASCII 码。这篇没什么技术文,就跟大家闲聊下电影观感。下次见啦。

    小林coding

  • 那些同事看了想骂人的代码!

    大家好,我是小林,先跟大家扯几句。卖书的地方大多数书名都是《21天精通C 》之类的书,其实我觉得根本不用 21 天,在这快节奏的时代,21 天学一个东西谁受得了?这些书能卖出去那才叫怪。我觉得真要认真学 C 话,一节课就够了!哈哈,上面只是调侃了下 C 。接下来,教大家怎么写出让同事无法维护的代码,甚至想骂人。咳咳,写烂代码都能写得这么有创意,这也不失为一种能力啊(手动狗头)!链接| http://mindprod.com/jgloss/unmain.html译者| 陈皓(@CoolShell)一、程序命名容易输入的变量名 。比如:Fred,asdf单字母的变量名 。比如:a,b,c, x,y,z(如果不够用,可以考虑a1,a2,a3,a4,….)有创意地拼写错误 。比如:SetPintleOpening, SetPintalClosing。这样可以让人很难搜索代码。抽象 。比如:ProcessData, DoIt, GetData… 抽象到就跟什么都没说一样。缩写 。比如:WTF,RTFSC …… (使用拼音缩写也同样给力,比如:BT,TMD,TJJTDS)随机大写字母 。比如:gEtnuMbER..重用命名 。在内嵌的语句块中使用相同的变量名有奇效。使用重音字母 。比如:int  ínt(第二个 ínt不是int)使用下划线 。比如:_, __, ___。使用不同的语言 。比如混用英语,德语,或是中文拼音。使用字符命名 。比如:slash, asterix, comma…使用无关的单词 。比如:god, superman, iloveu….混淆l和1 。字母l和数字1有时候是看不出来的。 二、伪装欺诈把注释和代码交织在一起。for(j=0; j{    total  = array[j 0 ];    total  = array[j 1 ];    total  = array[j 2 ]; /* Main body of    total  = array[j 3]; * loop is unrolled    total  = array[j 4]; * for greater speed.    total  = array[j 5]; */    total  = array[j 6 ];    total  = array[j 7 ];}代码和显示不一致 。比如,你的界面显示叫postal code,但是代码里确叫 zipcode.隐藏全局变量 。把使用全局变量以函数参数的方式传递给函数,这样可以让人觉得那个变量不是全局变量。使用相似的变量名 。如:单词相似,swimmer 和 swimner,字母相似:ilI1| 或 oO08。parselnt 和 parseInt, D0Calc 和 DOCalc。还有这一组:xy_Z, xy__z, _xy_z, _xyz, XY_Z, xY_z, Xy_z。重载函数 。使用相同的函数名,但是其功能和具体实现完全没有关系。操作符重载 。重载操作符可以让你的代码变得诡异,感谢CCTV,感谢C 。这个东西是可以把混乱代码提高到一种艺术的形式。比如:重载一个类的 ! 操作符,但实际功能并不是取反,让其返回一个整数。于是,如果你使用 ! ! 操作符,那么,有意思的事就发生了—— 先是调用类的重载 ! 操作符,然后把其返回的整数给 ! 成了 布尔变量,如果是 !!! 呢?呵呵。 三、文档和注释在注释中撒谎 。你不用真的去撒谎,只需在改代码的时候不要更新注释就可以了。注释里面写废话 。比如:/* add 1 to i */只注释是什么,而不是为什么 。不要注释秘密 。如果你开发一个航班系统,请你一定要保证每有一个新的航班被加入,就得要修改25个以上的位置的程序。千万别把这个事写在文档中。注重细节 。当你设计一个很复杂的算法的时候,你一定要把所有的详细细设计都写下来,没有100页不能罢休,段落要有5级以上,段落编号要有500个以上,例如:1.2.4.6.3.13 – Display all impacts for activity where selected mitigations can apply (short pseudocode omitted). 这样,当你写代码的时候,你就可以让你的代码和文档一致,如:Act1_2_4_6_3_13()千万不要注释度衡单位。比如时间用的是秒还是毫秒,尺寸用的是像素还是英寸,大小是MB还是KB。等等。另外,在你的代码里,你可以混用不同的度衡单位,但也不要注释。Gotchas 。陷阱 ,千万不要注释代码中的陷阱。在注释和文档中发泄不满 。 四、程序设计Java Casts 。Java的类型转型是天赐之物。每一次当你从Collection里取到一个object的时候,你都需要把其转回原来的类型。因些,这些转型操作会出现在N多的地方。如果你改变了类型,那么你不一定能改变所有的地方。而编译器可能能检查到,也可能检查不到。利用Java的冗余 。比如:Bubblegum b = new Bubblegom(); 和 swimmer = swimner 1; 注意变量间的细微差别。从不验证 。从不验证输入的数据,从不验证函数的返回值。这样做可以向大家展示你是多么的信任公司的设备和其它程序员不要封装 。调用者需要知道被调用的所有的细节。克隆和拷贝 。为了效率,你要学会使用copy paste。你几乎都不用理解别人的代码,你就可以高效地编程了。巨大的listener 。写一个listener,然后让你的所有的button类都使用这个listener,这样你可以在这个listener中整出一大堆if…else…语句,相当的刺激。使用三维数组 。如果你觉得三维还不足够,你可以试试四维。混用 。同时使用类的get/set方法和直接访问那个public变量。这样做的好处是可以极大的挫败维护人员。包装,包装,包装 。把你所有的API都包装上6到8遍,包装深度多达4层以上。然后包装出相似的功能。没有秘密 。把所有的成员都声明成public的。这样,你以后就很难限制其被人使用,而且这样可以和别的代码造成更多的耦合度,可以让你的代码存活得更久。排列和阻碍 。把drawRectangle(height, width) 改成 drawRectangle(width, height),等release了几个版本后,再把其改回去。这样维护程序的程序员们很快就不明白哪一个是对的。把变量改在名字上 。例如,把setAlignment(int alignment)改成,setLeftAlignment, setRightAlignment, setCenterAlignment。保留你所有的没有使用的和陈旧的变量,方法和代码 。Final你所有的子结点的类 ,这样,当你做完这个项目后,没有人可以通过继承来扩展你的类。java.lang.String不也是这样吗?避免使用layout 。这样就使得我们只能使用绝对坐标。如果你的老大强制你使用layout,你可以考虑使用GridBagLayout,然后把grid坐标hard code.环境变量 。如果你的代码需要使用环境变量。那么,你应该把你的类的成员的初始化使用环境变量,而不是构造函数。使用全局变量 。1)把全局变量的初始化放在不同的函数中,就算这个函数和这个变量没有任何关系,这样能够让我们的维护人员就像做侦探工作一样。2)使用全局变量可以让你的函数的参数变得少一些。配置文件 。配置文件主要用于一些参数的初始化。在编程中,我们可以让配置文件中的参数名和实际程序中的名字不一样。膨胀你的类 。让你的类尽可能地拥有各种臃肿和晦涩的方法。比如,你的类只实现一种可能性,但是你要提供所有可能性的方法。不要定义其它的类,把所有的功能都放在一个类中。使用子类 。面向对象是写出无法维护代码的天赐之物。如果你有一个类有十个成为(变量和方法)你可以考虑写10个层次的继承,然后把这十个属性分别放在这十个层次中。如果可能的话,把这十个类分别放在十个不同的文件中。混乱你的代码。 使用XML。XML的强大是无人能及的。使用XML你可以把本来只要10行的代码变成100行。而且,还要逼着别人也有XML。(参看,信XML得永生,信XML得自信)分解条件表达式 。如:把 a==100分解成,a>99

    小林coding 代码

  • 作为程序员,你还在用B站学习?别做梦了

    人工智能薪资高、人才缺口大,2021年AI岗的增长率预计达344%,平均月薪14K,现在入行AI也不算晚。 但是,往往我们在自学AI的时候,常常会遇到一些问题: · 网上资料太多,不知如何取舍? · 初学不懂法门,无法自测查验? · 光学Python编程,不懂模型原理? 这些问题的根本就是,你没有掌握正确的学习路径。 不得法门,就算AI算法岗位高薪也会与你无缘。想要掌握AI的核心技能,不仅仅是掌握了多少模型,更多的是在于你的算法分析和设计能力、工程实践能力、算法模型优化能力。 对于这些,特整理了9本AI经典好书 150道AI自测题,该资料适合本科、硕士以及刚接触机器学习的博士,还有一些想要转行AI的小伙伴。看完这些资料以后,预祝你顺利敲开大厂的大门。 本号主给大家争取了一些名额 赶快报名参加吧 扫描下方二维码 添加课程顾问才能领取哟,并 备注【AI书籍】添加 01 9本AI经典好书 这9本是人工智能经典学习书籍,全部都是英文原版,省去你自己搜索和下载的烦恼。这些原版书籍都比较贵,现在全部免费送给大家。 02 150道AI自测题 这些机器学习与深度学习的自测题非常实用,包含了机器学习理论类、特征工程类、深度学习、自然语言处理、推荐系统、计算机视觉等类型。 仅展示部分题目:

    小林coding

  • 林哥,幻读是怎么被解决的?

    ‍‍大家好,我是小林。我之前写过一篇数据库事务的文章「 事务、事务隔离级别和MVCC」,这篇我说过什么是幻读。幻读的定义我这里还得补充一句,幻读仅专指“新插入的行”,中途通过 update 更新数据而出现同一个事务前后两次查询的「结果集合」不一样,这种不算幻读。然后前几天有位读者跟我说,这个幻读例子不是已经被「可重复读」隔离级别解决了吗?为什么还要有 next-key 呢?他有这个质疑,是因为他做了这个实验。实验的数据库表 t_stu 如下,其中 id 为主键。然后在可重复读隔离级别下,有两个事务的执行顺序如下:从这个实验结果可以看到,即使事务 B 中途插入了一条记录,事务 A 前后两次查询的结果集都是一样的,并没有出现所谓的幻读现象。读者做的实验之所以看不到幻读现象,是因为在可重复读隔离级别下,普通的查询是快照读,是不会看到别的事务插入的数据的。可重复读隔离级是由 MVCC(多版本并发控制)实现的,实现的方式是启动事务后,在执行第一个查询语句后,会创建一个视图,然后后续的查询语句都用这个视图,「快照读」读的就是这个视图的数据,视图你可以理解为版本数据,这样就使得每次查询的数据都是一样的。MySQL 里除了普通查询是快照度,其他都是当前读,比如update、insert、delete,这些语句执行前都会查询最新版本的数据,然后再做进一步的操作。这很好理解,假设你要 update 一个记录,另一个事务已经 delete 这条记录并且 提交事务了,这样不是会产生冲突吗,所以 update 的时候肯定要知道最新的数据。另外,select ... for update 这种查询语句是当前读,每次执行的时候都是读取最新的数据。 因此,要讨论「可重复读」隔离级别的幻读现象,是要建立在「当前读」的情况下。接下来,我们假设select ... for update当前读是不会加锁的(实际上是会加锁的),在做一遍读者那个实验。这时候,事务 B 插入的记录,就会被事务 A 的第二条查询语句查询到(因为是当前读),这样就会出现前后两次查询的结果集合不一样,这就出现了幻读。所以,Innodb 引擎为了解决「可重复读」隔离级别使用「当前读」而造成的幻读问题,就引出了 next-key 锁,就是记录锁和间隙锁的组合。记录锁,锁的是记录本身;间隙锁,锁的就是两个值之间的空隙,以防止其他事务在这个空隙间插入新的数据,从而避免幻读现象。比如,下面事务 A 查询语句会锁住(2, ∞]范围的记录,然后期间如果有其他事务在这个锁住的范围插入数据就会被阻塞。next-key 锁的加锁规则其实挺复杂的,在一些场景下会退化成记录锁或间隙锁,我之前也写一篇加锁规则,详细可以看这篇「我做了一天的实验!」需要注意的是,next-key lock 锁的是索引,而不是数据本身,所以如果 update 语句的 where 条件没有用到索引列,那么就会全表扫描,在一行行扫描的过程中,不仅给行加上了行锁,还给行两边的空隙也加上了间隙锁,相当于锁住整个表,然后直到事务结束才会释放锁。所以在线上千万不要执行没有带索引条件的 update 语句,不然会造成业务停滞,我有个读者就因为干了这个事情,然后被老板教育了一波,详细可以看这篇「完蛋,公司被一条 update 语句干趴了!」好了,这次就聊到这啦,有不明白的地方欢迎讨论!‍‍

    小林coding

  • YYDS,17年了,Netty终于成为了王者...

    震惊!2004年6月发生了一件震惊IT圈的大事 这项技术的诞生以其综合性能最优,稳定修复了NIO出现的所有Bug一举成为java网络编程框架里面的王者(没有之一) 到如今,时隔17年 经久不衰 这就是 Java社区中第一个基于事件驱动的 应用网络框架Netty ↓↓ 由于它API使用简单,开发门槛低 并且支持多种协议开发 定制能力还特别强 所以在互联网、大数据网络游戏、企业应用等技术领域迅速得到大规模的使用 例如:在微服务的大潮之中 架构师把系统拆分成了多个服务 根据需要部署在多个机器上 这些服务非常灵活 可以随着访问量弹性扩展 但问题也随之而来 拆分成多个“微服务”以后虽然增加了弹性 但也带来了一个巨大的挑战: 服务之间互相调用的开销 ↑↑ 这时候使用Netty就是绝佳选择 因为Netty本身就是一个基于NIO的网络框架 封装了Java NIO那些复杂的底层细节 提供简单好用的抽象概念来编程 注意关键词,首先它是个框架 是个“半成品”,不能开箱即用 你必须得拿过来做定制 利用它开发自己的程序,然后才能运行 一个更加知名的例子 就是阿里巴巴的Dubbo了 这个RPC框架的底层用的就是Netty 目前,京东、美团、华为等 都在自家的架构中使用了Netty 想进这些公司 如果你的简历里没有展示Netty这项技能 那么你可能连初筛都过不了 就是这么炙手可热 聊了这么多Netty,我们也聊聊它的兄弟Tomcat 很多同学搞不懂Netty和Tomcat的区别 其实最大的区别主要有两方面 Tomcat是基于http协议的web容器 处理http请求,执行Servlet和jsp 而且Tomcat6.x之前是BIO通信 直到Tomcat8以后默认才是NIO模型 Netty则可以通过编程自定义各种协议 并且Netty是基于Java NIO开发的 Netty更底层,可以用Netty来创建一个Web服务器 前者更像一个中间件框架,后者更像一个工具编程 你如果可以对这些技术娓娓道来 薪资上调个5-8K轻而易举 为了升职加薪,迎娶白富美 去年我花了小金库(把医bao套出来了) 特意学习了一下 效果嘛,总体来说就是 年终奖比去年多拿的1倍 然而,让我抓心挠肝的事情 就这么猝不及防的发生了 .... 去年小编报的 前阿里、美团资深架构师黄俊老师出品的 Java高级网络编程与Tomcat原理深度解析 现在赶上金九银十的活动 现在居然只要0.02元? (靠,心疼我的医bao) 花0.02元 2天时间! 就能让你彻底搞定netty技术 (还要啥自行车?) 训练营精髓 学完收获夯实基础:掌握Netty 的基础知识,了解底层原理;序线程并发执行; 深入学习:深入到源码级别,带你彻底理解Netty高并发高性能的架构设计思想; 积累经验:在金九银十的面试中,能够很好的应对关于NIO与Netty的面试问题 。 为什么推荐你学? 1、架构师必备技术栈深度解析 本次训练营针对大厂招聘需求,深度解析:Java高级网络编程与Tomcat原理,想要当一个架构师,这些都是你要掌握的。 2、金九银十面试真题疑难解答众所周知,如今的面试环节动不动就是“八股文”“造火箭”,针对这种情况,讲师会带你聊聊大厂经常喜欢问的那些“经典面试题”。 3、工作遇到瓶颈如何快速破局工作遇到瓶颈,职业迷茫,不知道该怎么办?在直播间打出你的问题:例,城市、学历、工作年限、目前遇到的问题等…… 我在直播间等你~ 官网价值 ¥99本号粉丝专享 ¥0.02 即可学习! 长按扫码,锁定 ¥0.02 名额 还可免费领取面试真题扫码立即参与,仅剩53个名额 01PART超强讲师团队 互联网教育豪华阵容十余年一线大厂经验 教学经验P8架构师,清北博士后算法大神…… 真实学员反馈02PART 真实学员反馈,好评无数,腾讯课堂学习人次累计200w ,好评率99%!成功辅导数万 学员涨薪,offer拿到手软~ 03PART课程福利 1.课前福利: 报名即可领取训练营配套预习资料 2.到课福利: 最新整理大厂面试真题合集及详解 3.课后福利: 不用记笔记,你只管好好学习,下课讲师会分享笔记~ 适合人群04PART

    小林coding

  • 电磁兼容(EMC)基础知识

    配图 By 网友小野智本文思维导图:01EMC(Electro Magnetic Compatibility,电磁兼容)是指电子、电气设备或系统在预期的电磁环境中,不会因为周边的电磁环境而导致性能降低、功能丧失或损坏,也不会在周边环境中产生过量的电磁能量,以致影响周边设备的正常工作。EMI(Electro Magnetic Interference,电磁干扰):自身产生的电磁干扰不能超过一定的限值。EMS(Electro Magnetic Susceptibility,电磁抗扰度):自身承受的电磁干扰在一定的范围内。电磁环境:同种类的产品,不同的环境就有着不同的标准。需要说明的是,以上都基于一个前提:一定环境里,设备或系统都在正常运行下。02电磁干扰的产生原因:电压/电流的变化中不必要的部分。电磁干扰的耦合途径有两种:导线传导和空间辐射。导线传导干扰原因是电流总是走“最小阻抗”路径。以屏蔽线为例,低频(f10kHz)时,环路屏蔽层的感抗小于导线的阻抗,因此信号电流从屏蔽层上流过。干扰电流在导线上传输有两种方式:共模和差模。一般有用的信号为差模信号,因此共模电流只有转变为差模电流才能对有用信号产生干扰。阻抗平衡防止共模电流向差模转变,可以通过多点接地用来降低地线公共阻抗,减小共地线阻抗干扰。空间辐射干扰分近场和远场。近场又称为感应场,与场源的性质密切相关。当场源为高电压小电流时,主要表现为电场;当场源为低电压大电流时,主要表现为磁场。无论是电场还是磁场,当距离大于λ/2π时都变成了远场。远场又称为辐射场。远场属于平面波,容易分析和测量,而近场存在电场和磁场的相互转换问题,比较复杂。这里面有问题的是如果导线变成天线,有时候就分不清是传导干扰还是辐射干扰?低频带下特别是30 MHz以下的主要是传导干扰。或者可以估算当设备和导线的长度比波长短时,主要问题是传导干扰,当它们的尺寸比波长长时,主要问题是辐射干扰。干扰信号以平面电磁波形式向外辐射电磁场能量,再以泄漏和耦合形式,通过绝缘支撑物等(包括空气)为媒介,经公共阻抗的耦合进入被干扰的线路、设备或系统。举例:900MHz,平面波的转折点在50 mm电磁波辐射有两个必要条件:变化的电压/电流和辐射天线。两者缺一,都不会产生大量的辐射干扰。有些资料会给出瞬态干扰的概念,顾名思义:时间很短但幅度较大的电磁干扰。瞬态干扰一般指各类电快速脉冲瞬变(EFT)、各类浪涌(SURGE)、静电放电(ESD)等三种。03重点:消除其中任何一个因素就可以满足电磁兼容设计的要求。切断耦合途径是最有效的电磁兼容处理措施。了解下传播路径:电磁干扰可以通过电源线、信号线、地线、大地等途径传播的传导干扰,也有通过空间直接传播的空间辐射干扰。这些干扰或者噪声并不是独立存在的,在传播过程中又会出现新的复杂噪声,这种问题叠加问题才是解决问题的难点。04近场区,波阻抗与辐射源的位置、阻抗、频率及辐射源周围的介质有关;远场区,波阻抗等于电磁波传播介质的特性阻抗;在真空中,波阻抗为377Ω。由377Ω想到自由空间的特性阻抗:有个基础概念需要讲一讲:dB

    小麦大叔 电磁兼容 EMC

  • Cortex-M裸机环境下临界区保护的三种实现

    大家好,我是痞子衡,是正经搞技术的痞子。今天痞子衡给大家分享的是Cortex-M裸机环境下临界区保护的三种实现。搞嵌入式玩过 RTOS 的朋友想必都对 OS_ENTER_CRITICAL()、OS_EXIT_CRITICAL() 这个功能代码对特别眼熟,在 RTOS 里常常会有多任务(进程)处理,有些情况下一些特殊操作(比如 XIP 下 Flash 擦写、低功耗模式切换)不能被随意打断,或者一些共享数据区不能被无序访问(A 任务正在读,B 任务却要写),这时候就要用到临界区保护策略了。所谓临界区保护策略,简单说就是系统中硬件临界资源或者软件临界资源,多个任务必须互斥地对它们进行访问。RTOS 环境下有现成的临界区保护接口函数,而裸机系统里其实也有这种需求。在裸机系统里,临界区保护主要就是跟系统全局中断控制有关。痞子衡之前写过一篇 《嵌入式MCU中通用的三重中断控制设计》,文中介绍的第三重也是最顶层的中断控制是系统全局中断控制,今天痞子衡就从这个系统全局中断控制使用入手给大家介绍三种临界区保护做法:一、临界区保护测试场景关于临界区保护的测试场景无非两种。第一种场景是受保护的多个任务间并无关联,也不会互相嵌套,如下面的代码所示,task1 和 task2 是按序被保护的,因此 enter_critical() 和 exit_critical() 这两个临界区保护函数总是严格地成对执行:void critical_section_test(void){    // 进入临界区    enter_critical();    // 做受保护的任务1    do_task1();    // 退出临界区    exit_critical();    // 进入临界区    enter_critical();    // 做受保护的任务2,与任务1无关联    do_task2();    // 退出临界区    exit_critical();}第二种场景就是多个任务间可能有关联,会存在嵌套情况,如下面的代码所示,task2 是 task1 的一个子任务,这种情况下,你会发现实际上是先执行两次 enter_critical(),然后再执行两次 exit_critical()。需要注意的是 task1 里面的子任务 task3 虽然没有像子任务 task2 那样被主动加一层保护,但由于主任务 task1 整体是受保护的,因此子任务 task3 也应该是受保护的。void do_task1(void){    // 进入临界区    enter_critical();    // 做受保护的任务2,是任务1中的子任务    do_task2();    // 退出临界区    exit_critical();     // 做任务3    do_task3();}void critical_section_test(void){    // 进入临界区    enter_critical();    // 做受保护的任务1    do_task1();    // 退出临界区    exit_critical();}二、临界区保护三种实现上面的临界区保护测试场景很清楚了,现在到 enter_critical()、exit_critical() 这对临界区保护函数的实现环节了:2.1 入门做法首先是非常入门的做法,直接就是对系统全局中断控制函数 __disable_irq()、__enable_irq() 的封装。回到上一节的测试场景里,这种实现可以很好地应对非嵌套型任务的保护,但是对于互相嵌套的任务保护就失效了。上一节测试代码里,task3 应该也要受到保护的,但实际上并没有被保护,因为紧接着 task2 后面的 exit_critical() 直接就打开了系统全局中断。void enter_critical(void){    // 关闭系统全局中断    __disable_irq();}void exit_critical(void){    // 打开系统全局中断    __enable_irq();}2.2 改进做法针对入门做法,可不可以改进呢?当然可以,我们只需要加一个全局变量 s_lockObject 来实时记录当前已进入的临界区保护的次数,即如下代码所示。每调用一次 enter_critical() 都会直接关闭系统全局中断(保证临界区一定是受保护的),并记录次数,而调用 exit_critical() 时仅当当前次数是 1 时(即当前不是临界区保护嵌套情况),才会打开系统全局中断,否则只是抵消一次进入临界区次数而已。改进后的实现显然可以保护上一节测试代码里的 task3 了。static uint32_t s_lockObject;void init_critical(void){    __disable_irq();    // 清零计数器    s_lockObject = 0;    __enable_irq();}void enter_critical(void){    // 关闭系统全局中断    __disable_irq();    // 计数器加 1     s_lockObject;}void exit_critical(void){    if (s_lockObject 

    小麦大叔 Cortex-M

  • 阿里面试这样问:Nacos配置中心交互模型是 push 还是 pull ?(原理 源码分析)

    本文来源:公众号「 程序员内点事」 对于Nacos大家应该都不太陌生,出身阿里名声在外,能做动态服务发现、配置管理,非常好用的一个工具。然而这样的技术用的人越多面试被问的概率也就越大,如果只停留在使用层面,那面试可能要吃大亏。比如我们今天要讨论的话题,Nacos在做配置中心的时候,配置数据的交互模式是服务端推过来还是客户端主动拉的?这里我先抛出答案:客户端主动拉的!接下来咱们扒一扒Nacos的源码,来看看它具体是如何实现的?配置中心聊Nacos之前简单回顾下配置中心的由来。简单理解配置中心的作用就是对配置统一管理,修改配置后应用可以动态感知,而无需重启。因为在传统项目中,大多都采用静态配置的方式,也就是把配置信息都写在应用内的yml或properties这类文件中,如果要想修改某个配置,通常要重启应用才可以生效。但有些场景下,比如我们想要在应用运行时,通过修改某个配置项,实时的控制某一个功能的开闭,频繁的重启应用肯定是不能接受的。尤其是在微服务架构下,我们的应用服务拆分的粒度很细,少则几十多则上百个服务,每个服务都会有一些自己特有或通用的配置。假如此时要改变通用配置,难道要我挨个改几百个服务配置?很显然这不可能。所以为了解决此类问题配置中心应运而生。配置中心推与拉模型客户端与配置中心的数据交互方式其实无非就两种,要么推push,要么拉pull。推模型客户端与服务端建立TCP长连接,当服务端配置数据有变动,立刻通过建立的长连接将数据推送给客户端。优势:长链接的优点是实时性,一旦数据变动,立即推送变更数据给客户端,而且对于客户端而言,这种方式更为简单,只建立连接接收数据,并不需要关心是否有数据变更这类逻辑的处理。弊端:长连接可能会因为网络问题,导致不可用,也就是俗称的假死。连接状态正常,但实际上已无法通信,所以要有的心跳机制KeepAlive来保证连接的可用性,才可以保证配置数据的成功推送。拉模型客户端主动的向服务端发请求拉配置数据,常见的方式就是轮询,比如每3s向服务端请求一次配置数据。轮询的优点是实现比较简单。但弊端也显而易见,轮询无法保证数据的实时性,什么时候请求?间隔多长时间请求一次?都是不得不考虑的问题,而且轮询方式对服务端还会产生不小的压力。长轮询开篇我们就给出了答案,nacos采用的是客户端主动拉pull模型,应用长轮询(Long Polling)的方式来获取配置数据。额?以前只听过轮询,长轮询又是什么鬼?它和传统意义上的轮询(暂且叫短轮询吧,方便比较)有什么不同呢?短轮询不管服务端配置数据是否有变化,不停的发起请求获取配置,比如支付场景中前段JS轮询订单支付状态。这样的坏处显而易见,由于配置数据并不会频繁变更,若是一直发请求,势必会对服务端造成很大压力。还会造成推送数据的延迟,比如:每10s请求一次配置,如果在第11s时配置更新了,那么推送将会延迟9s,等待下一次请求。为了解决短轮询的问题,有了长轮询方案。长轮询长轮询可不是什么新技术,它不过是由服务端控制响应客户端请求的返回时间,来减少客户端无效请求的一种优化手段,其实对于客户端来说与短轮询的使用并没有本质上的区别。客户端发起请求后,服务端不会立即返回请求结果,而是将请求挂起等待一段时间,如果此段时间内服务端数据变更,立即响应客户端请求,若是一直无变化则等到指定的超时时间后响应请求,客户端重新发起长链接。Nacos初识为了后续演示操作方便我在本地搭了个Nacos。注意: 运行时遇到个小坑,由于Nacos默认是以cluster集群的方式启动,而本地搭建通常是单机模式standalone,这里需手动改一下启动脚本startup.X中的启动模式。直接执行/bin/startup.X就可以了,默认用户密码均是nacos。几个概念Nacos配置中心的几个核心概念:dataId、group、namespace,它们的层级关系如下图:dataId:是配置中心里最基础的单元,它是一种key-value结构,key通常是我们的配置文件名称,比如:application.yml、mybatis.xml,而value是整个文件下的内容。目前支持JSON、XML、YAML等多种配置格式。group:dataId配置的分组管理,比如同在dev环境下开发,但同环境不同分支需要不同的配置数据,这时就可以用分组隔离,默认分组DEFAULT_GROUP。namespace:项目开发过程中肯定会有dev、test、pro等多个不同环境,namespace则是对不同环境进行隔离,默认所有配置都在public里。架构设计下图简要描述了nacos配置中心的架构流程。客户端、控制台通过发送Http请求将配置数据注册到服务端,服务端持久化数据到Mysql。客户端拉取配置数据,并批量设置对dataId的监听发起长轮询请求,如服务端配置项变更立即响应请求,如无数据变更则将请求挂起一段时间,直到达到超时时间。为减少对服务端压力以及保证配置中心可用性,拉取到配置数据客户端会保存一份快照在本地文件中,优先读取。这里我省略了比较多的细节,如鉴权、负载均衡、高可用方面的设计(其实这部分才是真正值得学的,后边另出文讲吧),主要弄清客户端与服务端的数据交互模式。“下边我们以Nacos 2.0.1版本源码分析,2.0以后的版本改动较多,和网上的很多资料略有些不同 地址:https://github.com/alibaba/nacos/releases/tag/2.0.1客户端源码分析Nacos配置中心的客户端源码在nacos-client项目,其中NacosConfigService实现类是所有操作的核心入口。说之前先了解个客户端数据结构cacheMap,这里大家重点记住它,因为它几乎贯穿了Nacos客户端的所有操作,由于存在多线程场景为保证数据一致性,cacheMap采用了AtomicReference原子变量实现。/** * groupKey -> cacheData. */private final AtomicReference cacheMap = new AtomicReference(new HashMap());cacheMap是个Map结构,key为groupKey,是由dataId, group, tenant(租户)拼接的字符串;value为CacheData对象,每个dataId都会持有一个CacheData对象。获取配置Nacos获取配置数据的逻辑比较简单,先取本地快照文件中的配置,如果本地文件不存在或者内容为空,则再通过HTTP请求从远端拉取对应dataId配置数据,并保存到本地快照中,请求默认重试3次,超时时间3s。获取配置有getConfig()和getConfigAndSignListener()这两个接口,但getConfig()只是发送普通的HTTP请求,而getConfigAndSignListener()则多了发起长轮询和对dataId数据变更注册监听的操作addTenantListenersWithContent()。@Overridepublic String getConfig(String dataId, String group, long timeoutMs) throws NacosException {    return getConfigInner(namespace, dataId, group, timeoutMs);}@Overridepublic String getConfigAndSignListener(String dataId, String group, long timeoutMs, Listener listener)        throws NacosException {    String content = getConfig(dataId, group, timeoutMs);    worker.addTenantListenersWithContent(dataId, group, content, Arrays.asList(listener));    return content;}注册监听客户端注册监听,先从cacheMap中拿到dataId对应的CacheData对象。public void addTenantListenersWithContent(String dataId, String group, String content,                                          List

    架构师社区 os 模型 源码

  • 读 MySQL 源码再看 INSERT 加锁流程

    来源:https://www.aneasystone.com/archives/2018/06/insert-locks-via-mysql-source-code.html在之前的博客中,我写了一系列的文章,比较系统的学习了 MySQL 的事务、隔离级别、加锁流程以及死锁,我自认为对常见 SQL 语句的加锁原理已经掌握的足够了,但看到热心网友在评论中提出的一个问题,我还是彻底被问蒙了。他的问题是这样的:加了插入意向锁后,插入数据之前,此时执行了 select…lock in share mode 语句(没有取到待插入的值),然后插入了数据,下一次再执行 select…lock in share mode(不会跟插入意向锁冲突),发现多了一条数据,于是又产生了幻读。会出现这种情况吗?这个问题初看上去很简单,在 RR 隔离级别下,假设要插入的记录不存在,如果先执行 select...lock in share mode 语句,很显然会在记录间隙之间加上 GAP 锁,而 insert 语句首先会对记录加插入意向锁,插入意向锁和 GAP 锁冲突,所以不存在幻读;如果先执行 insert 语句后执行 select...lock in share mode 语句,由于 insert 语句在插入记录之后,会对记录加 X 锁,它会阻止 select...lock in share mode 对记录加 S 锁,所以也不存在幻读。两种情况如下所示:先执行 INSERT 后执行 SELECT:先执行 SELECT 后执行 INSERT:但是我们仔细想一想就会发现哪里有点不对劲,我们知道 insert 语句会先在插入间隙上加上插入意向锁,然后开始写数据,写完数据之后再对记录加上 X 记录锁(这里简化了,关于 insert 语句的加锁流程,可以参考我之前写的常见 SQL 语句的加锁分析)。那么问题就来了,如果在 insert 语句加插入意向锁之后,写数据之前,执行了 select...lock in share mode语句,这个时候 GAP 锁和插入意向锁是不冲突的,查询出来的记录数为 0,然后 insert 语句写数据,加 X 记录锁,因为记录锁和 GAP 锁也是不冲突的,所以 insert 成功插入了一条数据,这个时候如果事务提交,select...lock in share mode 语句再次执行查询出来的记录数就是 1,岂不是就出现了幻读?整个流程如下所示(我们把 insert 语句的执行分成两个阶段,INSERT 1 加插入意向锁,还没写数据,INSERT 2 写数据,加记录锁):一、INSERT 加锁的困惑在得出上面的结论时,我也感到很惊讶。按理是不可能出现这种情况的,只可能是我对这两个语句的加锁过程还没有想明白。于是我又去复习了一遍 MySQL 官方文档,Locks Set by Different SQL Statements in InnoDB 这篇文档对各个语句的加锁有详细的描述,其中对 insert 的加锁过程是这样说的(这应该是网络上介绍 MySQL 加锁机制被引用最多的文档,估计也是被误解最多的文档):INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.Prior to inserting the row, a type of gap lock called an insert intention gap lock is set. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6 each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.If a duplicate-key error occurs, a shared lock on the duplicate index record is set. This use of a shared lock can result in deadlock should there be multiple sessions trying to insert the same row if another session already has an exclusive lock. This can occur if another session deletes the row.讲到了 insert 会对插入的这条记录加排他记录锁,在加记录锁之前还会加一种 GAP 锁,叫做插入意向锁,如果出现唯一键冲突,还会加一个共享记录锁。这和我之前的理解是完全一样的,那么究竟是怎么回事呢?难道 MySQL 的 RR 真的会出现幻读现象?在 Google 上搜索了很久,并没有找到 MySQL 幻读的问题,百思不得其解之际,遂决定从 MySQL 的源码中一探究竟。二、编译 MySQL 源码编译 MySQL 的源码非常简单,但是中间也有几个坑,如果能绕过这几个坑,在本地调试 MySQL 是一件很容易的事(当然能调试源码是一回事,能看懂源码又是另一回事了)。我的环境是 Windows 10 x64,系统上安装了 Visual Studio 2012,如果你的开发环境和我不一样,编译步骤可能也会不同。在开始之前,首先要从官网下载 MySQL 源码:这里我选择的是 5.6.40 版本,操作系统下拉列表里选 Source Code,OS Version 选择 Windows(Architecture Independent),然后就可以下载打包好的 zip 源码了。将源码解压缩到 D:\mysql-5.6.40 目录,在编译之前,还需要再安装几个必要软件:CMake:CMake 本身并不是编译工具,它是通过编写一种平台无关的 CMakeList.txt 文件来定制编译流程的,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程;Bison:MySQL 在执行 SQL 语句时,必然要对 SQL 语句进行解析,一般来说语法解析器会包含两个模块:词法分析和语法规则。词法分析和语法规则模块有两个较成熟的开源工具 Flex 和 Bison 分别用来解决这两个问题。MySQL 出于性能和灵活考虑,选择了自己完成词法解析部分,语法规则部分使用了 Bison,所以这里我们还要先安装 Bison。Bison 的默认安装路径为 C:\Program Files\GnuWin32,但是千万不要这样,一定要记得选择一个不带空格的目录,譬如 C:\GnuWin32 要不然在后面使用 Visual Studio 编译 MySQL 时会卡死;Visual Studio:没什么好说的,Windows 环境下估计没有比它更好的开发工具了吧。安装好 CMake 和 Bison 之后,记得要把它们都加到 PATH 环境变量中。做好准备工作,我们就可以开始编译了,首先用 CMake 生成 Visual Studio 的工程文件:1D:\mysql-5.6.40> mkdir project2D:\mysql-5.6.40> cd project3D:\mysql-5.6.40\project> cmake -G "Visual Studio 11 2012 Win64" ..cmake 的-G 参数用于指定生成哪种类型的工程文件,这里是 Visual Studio 2012,可以直接输入 cmake -G 查看支持的工程类型。如果没问题,会在 project 目录下生成一堆文件,其中 MySQL.sln 就是我们要用的工程文件,使用 Visual Studio 打开它。打开 MySQL.sln 文件,会在 Solution Explorer 看到 130 个项目,其中有一个叫 ALL_BUILD,这个时候如果直接编译,编译会失败,在这之前,我们还要对代码做点修改:首先是 sql\sql_locale.cc 文件,看名字就知道这个文件用于国际化与本土化,这个文件里有各个国家的语言字符,但是这个文件却是 ANSI 编码,所以要将其改成 Unicode 编码;打开 sql\mysqld.cc 文件的第 5239 行,将 DBUG_ASSERT(0) 改成 DBUG_ASSERT(1),要不然调试时会触发断言;现在我们可以编译整个工程了,选中 ALL_BUILD 项目,Build,然后静静的等待 5 到 10 分钟,如果出现了 Build: 130 succeeded, 0 failed 这样的提示,那么恭喜,你现在可以尽情的调试 MySQL 了。我们将 mysqld 设置为 Startup Project,然后加个命令行参数 --console,这样可以在控制台里查看打印的调试信息:另外, client\Debug\mysql.exe 这个文件是对应的 MySQL 的客户端,可以直接双击运行,默认使用的用户为 ODBC@localhost,如果要以 root 用户登录,可以执行 mysql.exe -u root,不需要密码。三、调试 INSERT 加锁流程首先我们创建一个数据库 test,然后创建一个测试表 t,主键为 id,并插入测试数据:1use test;2create table t(id int NOT NULL AUTO_INCREMENT , PRIMARY KEY (id));3insert into t(id) values(1),(10),(20),(50);然后我们开两个客户端会话,一个会话执行 insert into t(id) value(30),另一个会话执行 select * from t where id = 30 lock in share mode。很显然,如果我们能在 insert 语句加插入意向锁之后写数据之前下个断点,再在另一个会话中执行 select 就可以模拟出这种场景了。那么我们来找下 insert 语句是在哪加插入意向锁的。第一次看 MySQL 源码可能会有些不知所措,调着调着就会迷失在深深的调用层级中,我们看 insert 语句的调用堆栈,一开始时还比较容易理解,从 mysql_parse -> mysql_execute_command -> mysql_insert -> write_record -> handler::ha_write_row -> innobase::write_row -> row_insert_for_mysql,这里就进入 InnoDb 引擎了。然后继续往下跟:row_ins_step -> row_ins -> row_ins_index_entry_step -> row_ins_index_entry -> row_ins_clust_index_entry -> row_ins_clust_index_entry_low -> btr_cur_optimistic_insert -> btr_cur_ins_lock_and_undo -> lock_rec_insert_check_and_lock。一路跟下来,都没有发现插入意向锁的踪迹,直到 lock_rec_insert_check_and_lock 这里: 1if (lock_rec_other_has_conflicting( 2        static_cast( 3            LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION), 4        block, next_rec_heap_no, trx)) { 5 6    /* Note that we may get DB_SUCCESS also here! */ 7    trx_mutex_enter(trx); 8 9    err = lock_rec_enqueue_waiting(10        LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION,11        block, next_rec_heap_no, index, thr);1213    trx_mutex_exit(trx);14} else {15    err = DB_SUCCESS;16}这里是检查是否有和插入意向锁冲突的其他锁,如果有冲突,就将插入意向锁加到锁等待队列中。这很显然是先执行 select ... lock in share mode 语句再执行 insert 语句时的情景,插入意向锁和 GAP 冲突。但这不是我们要找的点,于是继续探索,但是可惜的是,直到 insert 执行结束,我都没有找到加插入意向锁的地方。跟代码非常辛苦,我担心是因为我跟丢了某块的逻辑导致没看到加锁,于是我看了看加其他锁的地方,发现在 InnoDb 里行锁都是通过调 lock_rec_add_to_queue(没有锁冲突) 或者 lock_rec_enqueue_waiting(有锁冲突,需要等待其他事务释放锁) 来实现的,于是在这两个函数上下断点,执行一条 insert 语句,依然没有断下来,说明 insert 语句没有加任何锁!到这里我突然想起之前做过的 insert 加锁的实验,执行 insert 之后,如果没有任何冲突,在 show engine innodb status 命令中是看不到任何锁的,这是因为 insert 加的是隐式锁。什么是隐式锁?隐式锁的意思就是没有锁!所以,根本就不存在之前说的先加插入意向锁,再加排他记录锁的说法,在执行 insert 语句时,什么锁都不会加。这就有点意思了,如果 insert 什么锁都不加,那么如果其他事务执行 select ... lock in share mode,它是如何阻止其他事务加锁的呢?答案就在于隐式锁的转换。InnoDb 在插入记录时,是不加锁的。如果事务 A 插入记录且未提交,这时事务 B 尝试对这条记录加锁,事务 B 会先去判断记录上保存的事务 id 是否活跃,如果活跃的话,那么就帮助事务 A 去建立一个锁对象,然后自身进入等待事务 A 状态,这就是所谓的隐式锁转换为显式锁。我们跟一下执行 select 时的流程,如果 select 需要加锁,则会走:sel_set_rec_lock -> lock_clust_rec_read_check_and_lock -> lock_rec_convert_impl_to_expl,lock_rec_convert_impl_to_expl 函数的核心代码如下: 1impl_trx = trx_rw_is_active(trx_id, NULL); 2 3if (impl_trx != NULL 4    

    架构师社区 源码

  • 数据库连接池到底应该设多大?

     本文来源:https://www.jianshu.com/p/a8f653fc0c54| 前言本文内容95%译自这篇文章:https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing我在研究HikariCP(一个数据库连接池)时无意间在HikariCP的Github wiki上看到了一篇文章(即前面给出的链接),这篇文章有力地消除了我一直以来的疑虑,看完之后感觉神清气爽。故在此做译文分享。| 正文数据库连接池的配置是开发者们常常搞出坑的地方,在配置数据库连接池时,有几个可以说是和直觉背道而驰的原则需要明确。 1万并发用户访问想象你有一个网站,压力虽然还没到Facebook那个级别,但也有个1万上下的并发访问——也就是说差不多2万左右的TPS。那么这个网站的数据库连接池应该设置成多大呢?结果可能会让你惊讶,因为这个问题的正确问法是:“这个网站的数据库连接池应该设置成多小呢?”下面这个视频是Oracle Real World Performance Group发布的,请先看完:http://www.dailymotion.com/video/x2s8uec(因为这视频是英文解说且没有字幕,我替大家做一下简单的概括:)视频中对Oracle数据库进行压力测试,9600并发线程进行数据库操作,每两次访问数据库的操作之间sleep 550ms,一开始设置的中间件线程池大小为2048:初始的配置压测跑起来之后是这个样子的:2048连接时的性能数据每个请求要在连接池队列里等待33ms,获得连接后执行SQL需要77ms。此时数据库的等待事件是这个熊样的:各种buffer busy waits各种buffer busy waits,数据库CPU在95%左右(这张图里没截到CPU)接下来,把中间件连接池减到1024(并发什么的都不变),性能数据变成了这样:连接池降到1024后获取链接等待时长没怎么变,但是执行SQL的耗时减少了。下面这张图,上半部分是wait,下半部分是吞吐量:wait和吞吐量能看到,中间件连接池从2048减半之后,吐吞量没变,但wait事件减少了一半。接下来,把数据库连接池减到96,并发线程数仍然是9600不变。96个连接时的性能数据队列平均等待1ms,执行SQL平均耗时2ms。wait事件几乎没了,吞吐量上升。没有调整任何其他东西,仅仅只是缩小了中间件层的数据库连接池,就把请求响应时间从100ms左右缩短到了3ms。 But why?为什么nginx只用4个线程发挥出的性能就大大超越了100个进程的Apache HTTPD?回想一下计算机科学的基础知识,答案其实是很明显的。即使是单核CPU的计算机也能“同时”运行数百个线程。但我们都[应该]知道这只不过是操作系统用时间分片玩的一个小把戏。一颗CPU核心同一时刻只能执行一个线程,然后操作系统切换上下文,核心开始执行另一个线程的代码,以此类推。给定一颗CPU核心,其顺序执行A和B永远比通过时间分片“同时”执行A和B要快,这是一条计算机科学的基本法则。一旦线程的数量超过了CPU核心的数量,再增加线程数系统就只会更慢,而不是更快。这 几乎 就是真理了…… 有限的资源上面的说法只能说是接近真理,但还并没有这么简单,有一些其他的因素需要加入。当我们寻找数据库的性能瓶颈时,总是可以将其归为三类:CPU、磁盘、网络。把内存加进来也没有错,但比起磁盘和网络,内存的带宽要高出好几个数量级,所以就先不加了。如果我们无视磁盘和网络,那么结论就非常简单。在一个8核的服务器上,设定连接/线程数为8能够提供最优的性能,再增加连接数就会因上下文切换的损耗导致性能下降。数据库通常把数据存储在磁盘上,磁盘又通常是由一些旋转着的金属碟片和一个装在步进马达上的读写头组成的。读/写头同一时刻只能出现在一个地方,然后它必须“寻址”到另外一个位置来执行另一次读写操作。所以就有了寻址的耗时,此外还有旋回耗时,读写头需要等待碟片上的目标数据“旋转到位”才能进行操作。使用缓存当然是能够提升性能的,但上述原理仍然成立。在这一时间段(即"I/O等待")内,线程是在“阻塞”着等待磁盘,此时操作系统可以将那个空闲的CPU核心用于服务其他线程。所以,由于线程总是在I/O上阻塞,我们可以让线程/连接数比CPU核心多一些,这样能够在同样的时间内完成更多的工作。那么应该多多少呢?这要取决于磁盘。较新型的SSD不需要寻址,也没有旋转的碟片。可别想当然地认为“SSD速度更快,所以我们应该增加线程数”,恰恰相反,无需寻址和没有旋回耗时意味着更少的阻塞,所以更少的线程[更接近于CPU核心数]会发挥出更高的性能。只有当阻塞创造了更多的执行机会时,更多的线程数才能发挥出更好的性能。网络和磁盘类似。通过以太网接口读写数据时也会形成阻塞,10G带宽会比1G带宽的阻塞少一些,1G带宽又会比100M带宽的阻塞少一些。不过网络通常是放在第三位考虑的,有些人会在性能计算中忽略它们。上图是PostgreSQL的benchmark数据,可以看到TPS增长率从50个连接数开始变缓。在上面Oracle的视频中,他们把连接数从2048降到了96,实际上96都太高了,除非服务器有16或32颗核心。 计算公式下面的公式是由PostgreSQL提供的,不过我们认为可以广泛地应用于大多数数据库产品。你应该模拟预期的访问量,并从这一公式开始测试你的应用,寻找最合适的连接数值。连接数 = ((核心数 * 2) 有效磁盘数)核心数不应包含超线程(hyper thread),即使打开了hyperthreading也是。如果活跃数据全部被缓存了,那么有效磁盘数是0,随着缓存命中率的下降,有效磁盘数逐渐趋近于实际的磁盘数。这一公式作用于SSD时的效果如何尚未有分析。按这个公式,你的4核i7数据库服务器的连接池大小应该为((4 * 2) 1) = 9。取个整就算是是10吧。是不是觉得太小了?跑个性能测试试一下,我们保证它能轻松搞定3000用户以6000TPS的速率并发执行简单查询的场景。如果连接池大小超过10,你会看到响应时长开始增加,TPS开始下降。笔者注:这一公式其实不仅适用于数据库连接池的计算,大部分涉及计算和I/O的程序,线程数的设置都可以参考这一公式。我之前在对一个使用Netty编写的消息收发服务进行压力测试时,最终测出的最佳线程数就刚好是CPU核心数的一倍。 公理你需要一个小连接池,和一个充满了等待连接的线程的队列。如果你有10000个并发用户,设置一个10000的连接池基本等于失了智。1000仍然很恐怖。即使100也太多了。你需要一个10来个连接的小连接池,然后让剩下的业务线程都在队列里等待。连接池中的连接数量应该等于你的数据库能够有效同时进行的查询任务数(通常不会高于2*CPU核心数)。我们经常见到一些小规模的web应用,应付着大约十来个的并发用户,却使用着一个100连接数的连接池。这会对你的数据库造成极其不必要的负担。| 请注意连接池的大小最终与系统特性相关。比如一个混合了长事务和短事务的系统,通常是任何连接池都难以进行调优的。最好的办法是创建两个连接池,一个服务于长事务,一个服务于短事务。再例如一个系统执行一个任务队列,只允许一定数量的任务同时执行,此时并发任务数应该去适应连接池连接数,而不是反过来。

    架构师社区 数据库

  • 真牛X!这款通用数据库连接工具DBeaver!可以连接和操作市面所有的数据库!

    来源:https://blog.csdn.net/horses/article/details/89683422在制作《SQL 入门教程》时,接触到了这款非常强大易用的数据库管理和开发工具:DBeaver,也就是上面这个可爱的小河狸。DBeaver 是一个基于 Java 开发,免费开源的通用数据库管理和开发工具,使用非常友好的 ASL 协议。可以通过官方网站或者 Github 进行下载。由于 DBeaver 基于 Java 开发,可以运行在各种操作系统上,包括:Windows、Linux、macOS 等。DBeaver 采用 Eclipse 框架开发,支持插件扩展,并且提供了许多数据库管理工具:ER 图、数据导入/导出、数据库比较、模拟数据生成等。DBeaver 通过 JDBC 连接到数据库,可以支持几乎所有的数据库产品,包括:MySQL、PostgreSQL、MariaDB、SQLite、Oracle、Db2、SQL Server、Sybase、MS Access、Teradata、Firebird、Derby 等等。商业版本更是可以支持各种 NoSQL 和大数据平台:MongoDB、InfluxDB、Apache Cassandra、Redis、Apache Hive 等。| 下载与安装DBeaver 社区版可以通过官方网站或者 Github 进行下载。两者都为不同的操作系统提供了安装包或者解压版,可以选择是否需要同时安装 JRE。另外,官方网站还提供了 DBeaver 的 Eclipse 插件,可以在 Eclipse 中进行集成。DBeaver 支持中文,安装过程非常简单,不多说,唯一需要注意的是 DBeaver 的运行依赖于 JRE。不出意外,安装完成后运行安装目录下的 dbeaver.exe 可以看到以下界面(Windows 10):这个界面其实是新建数据库连接,我们可以看到它支持的各种数据平台;先点击“取消 ”按钮,进入主窗口界面。此时,它会提示我们是否建立一个示例数据库。如果点击“是(Y)”,它会创建一个默认的 SQLite 示例数据库。下图是它的主窗口界面。DBeaver 和我们常用的软件类似,最上面是菜单项和快捷工具,左侧是已经建立的数据库连接和项目信息,右侧是主要的工作区域。| 连接数据库打开 DBeaver 之后,首先要做的就是创建数据库连接。可以通过菜单“数据库 ” -> “新建连接 ”打开新建连接向导窗口,也就是我们初次运行 DBeaver 时弹出的窗口。我们以 PostgreSQL 为例,新建一个数据库连接。选择 PostgreSQL 图标,点击“下一步(N) ”。然后是设置数据库的连接信息:主机、端口、数据库、用户、密码。“Advanced settings ”高级设置选项可以配置 SSH、SSL 以及代理等,也可以为连接指定自己的名称和连接类型(开发、测试、生产)。点击最下面的“测试链接(T) ”可以测试连接配置的正确性。初次创建某种数据库的连接时,会提示下载相应的 JDBC 驱动。它已经为我们查找到了相应的驱动,只需要点击“下载 ”即可,非常方便。下载完成后,如果连接信息正确,可以看到连接成功的提示。确认后完成连接配置即可。左侧的数据库导航中会增加一个新的数据库连接。由于某些数据库(例如 Oracle、Db2)的 JDBC 驱动需要登录后才能下载,因此可以使用手动的方式进行配置。选择菜单“数据库 ” -> “驱动管理器 ”。选择 Oracle ,点击“编辑(E)… ”按钮。通过界面提示的网址,手动下载 Oracle 数据库的 JDBC 驱动文件,例如 ojdbc8.jar。然后点击“添加文件(F) ”按钮,选择并添加该文件。下次建立 Oracle 数据库连接时即可使用该驱动。新建连接之后,就可以通过这些连接访问相应的数据库,查看和编辑数据库中的对象,执行 SQL 语句,完成各种管理和开发工作。| 生成ER图最后介绍一下如何生成数据库对象的 ER 图。点击窗口左侧“数据库导航 ”旁边的“项目 ”视图。其中有个“ER Diagrams ”,就是实体关系图。右击该选项,点击“创建新的 ER 图 ”。输入一个名称并选择数据库连接和需要展示的对象,然后点击“完成 ”,即可生成相应的 ER 图。ER 图可以进行排版和显示设置,也支持打印为图片。DBeaver 目前还不支持自己创建 ER 图,只能从现有的数据库中生成。对于图形工具,很多功能我们都可以自己去使用体会;当然,DBeaver 也提供了用户指南,自行参考。

    架构师社区 数据库

  • 为什么字节跳动全面使用 Go 语言?

    随着云计算时代的到来,Go 的应用越来越广泛,已然成为首选编程语言,而且,薪资也水涨船高。以字节跳动为例,Go 语言是字节跳动内部使用最多的编程语言。为啥?因为字节跳动更看重效率,上手简单,学习难度低。另外, Goroutine 和 Channel 这两个神器可以很好解决并发和异步编程的问题,不得不说,Go 语言是新一代的编程语言。如果你的第一语言是 PHP,或者 Python,或者 C#,并且职位是后端工程师,那我还是建议你学学 Go。不是鼓吹 Go,是我觉得这是趋势,我们不管是写程序,还是做其他事情,都应该顺应时代。这里为你整理了一套大厂 Go 工程师的高频考题和知识点,哪怕不面试,也可以先学习熟悉一下。通过这些你是可以了解现在一线市场的招聘需求,可以认识到自己的问题,丰富自己的知识宽度,熟悉 Go 高频难点,巩固 Go 相关知识...除了字节等一线大厂的 Go 开发工程师面试题,还有:Golang 必考的理论和常见语法问题;Go 并发和 Redis 高频测试题目;资深 Go 工程师毛剑大佬的面试公开课。扫码可免费领取~

    架构师社区 字节跳动

  • Redis缓存那点破事 | 绝杀面试官 25 问!

    为了便于大家查找问题,了解全貌,整理个目录,我们可以快速全局了解关于Redis 缓存,面试官一般喜欢问哪些问题?接下来,我们逐条来看看每个问题及答案Redis 有哪些特性?答案:性能高, 读的速度是100000次/s,写的速度是80000次/s数据持久化,支持RDB 、AOF支持事务。通过MULTI和EXEC指令包起来。多种数据结构类型主从复制其他特性:发布/订阅、通知、key过期等Redis 为什么这么快?答案:完全基于内存,没有磁盘IO上的开销,异步持久化除外单线程,避免多个线程切换的性能损耗非阻塞的IO多路复用机制底层的数据存储结构优化,使用原生的数据结构提升性能。Redis 底层的基础数据结构有哪些?答案:字符串。没有采用C语言的传统字符串,而是自己实现的一个简单动态字符串SDS的抽象类型,并保存了长度信息。链表(linkedlist)。双向无环链表结构,每个链表的节点由一个listNode结构来表示,每个节点都有前置和后置节点的指针字典(hashtable)。保存键值对的抽象数据结构,底层使用hash表,每个字典带有两个hash表,供平时使用和rehash时使用。跳跃表(skiplist)。跳跃表是有序集合的底层实现之一。redis跳跃表由zskiplist和zskiplistNode组成,zskiplist用于保存跳跃表 信息(表头、表尾节点、⻓度等),zskiplistNode用于表示表跳跃节点,每个跳跃表的层高都是1- 32的随机数,在同一个跳跃表中,多个节点可以包含相同的分值,但是每个节点的成员对象必须是唯一的,节点按照分值大小排序,如果分值相同,则按照成员对象的大小排序。整数集合(intset)。用于保存整数值的集合抽象数据结构,不会出现重复元素,底层实现为数组。压缩列表(ziplist)。为节约内存而开发的顺序性数据结构,可以包含多个节点,每个节点可以保存一个字节数组或者整数值。Redis 支持哪些数据类型?答案:五种常用数据类型:String、Hash、Set、List、SortedSet。三种特殊的数据类型:Bitmap、HyperLogLog、Geospatial,其中Bitmap 、HyperLogLog的底层都是 String 数据类型,Geospatial 底层是 Sorted Set 数据类型。字符串对象string:int整数、embstr编码的简单动态字符串、raw简单动态字符串列表对象list:ziplist、linkedlist哈希对象hash:ziplist、hashtable集合对象set:intset、hashtable有序集合对象zset:ziplist、skiplistRedis 常用的 5 种数据结构和应用场景?答案:String:缓存、计数器、分布式锁等List:链表、队列、微博关注人时间轴列表等Hash:用户信息、Hash 表等Set:去重、赞、踩、共同好友等Zset:访问量排行榜、点击量排行榜等为什么采用单线程?答案:官方回复,CPU不会成为Redis的制约瓶颈,Redis主要受内存、网络限制。例如,在一个普通的 Linux 系统上,使用pipelining 可以每秒传递 100 万个请求,所以如果您的应用程序主要使用 O(N) 或 O(log(N)) 命令,则几乎不会使用太多 CPU,属于IO密集型系统。Redis 6.0 之后又改用多线程呢?答案:Redis的多线程主要是处理数据的读写、协议解析。执行命令还是采用单线程顺序执行。主要是因为redis的性能瓶颈在于网络IO而非CPU,使用多线程进行一些周边预处理,提升了IO的读写效率,从而提高了整体的吞吐量。antirez 在 RedisConf 2019 分享时提到,Redis 6 引入的多线程 IO 对性能提升至少一倍以上。过期键Key 的删除策略有哪些?答案:有3种过期删除策略。惰性删除、定期删除、定时删除惰性删除。使用key时才进行检查,如果已经过期,则删除。缺点:过期的key如果没有被访问到,一直无法删除,一直占用内存,造成空间浪费。定期删除。每隔一段时间做一次检查,删除过期的key,每次只是随机取一些key去检查。定时删除。为每个key设置过期时间,同时创建一个定时器。一旦到期,立即执行删除。缺点:如果过期键比较多时,占用CPU较多,对服务的性能有很大影响。如果Redis的内存空间不足,淘汰机制?答案:volatile-lru:从已设置过期时间的key中,移出最近最少使用的key进行淘汰allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)volatile-ttl:从已设置过期时间的key中,移出将要过期的keyvolatile-random:从已设置过期时间的key中,随机选择key淘汰allkeys-random:从key中随机选择key进行淘汰no-eviction:禁止淘汰数据。当内存达到阈值的时候,新写入操作报错volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰(LFU(Least Frequently Used)算法,也就是最频繁被访问的数据将来最有可能被访问到)allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key。Redis 突然挂了怎么解决?答案:1、从系统可用性角度思考,Redis Cluster引入主备机制,当主节点挂了后,自动切换到备用节点,继续提供服务。2、Client端引入本地缓存,通过开关切换,避免Redis突然挂掉,高并发流量把数据库打挂。Redis 持久化有哪些方式?答案:1、快照RDB。将某个时间点上的数据库状态保存到RDB文件中,RDB文件是一个压缩的二进制文件,保存在磁盘上。当Redis崩溃时,可用于恢复数据。通过SAVE或BGSAVE来生成RDB文件。SAVE:会阻塞redis进程,直到RDB文件创建完毕,在进程阻塞期间,redis不能处理任何命令请求。BGSAVE:会fork出一个子进程,然后由子进程去负责生成RDB文件,父进程还可以继续处理命令请求,不会阻塞进程。2、只追加文件AOF。以日志的形式记录每个写操作(非读操作)。当不同节点同步数据时,读取日志文件的内容将写指令从前到后执行一次,即可完成数据恢复。Redis 常用场景答案:1、缓存,有句话说的好,「性能不够,缓存来凑」2、分布式锁,利用Redis 的 setnx3、分布式session4、计数器,通过incr命令5、排行榜,Redis 的 有序集合6、其他Redis 缓存要注意的七大经典问题?答案:列举了亿级系统,高访问量情况下Redis缓存可能会遇到哪些问题?以及对应的解决方案。1、缓存集中失效2、缓存穿透3、缓存雪崩4、缓存热点5、缓存大Key6、缓存数据的一致性7、数据并发竞争预热每个问题的详细解决方案,请查看  亿级系统的Redis缓存如何设计???Redis 集群方案有哪几种?答案:主从复制模式Sentinel(哨兵)模式Redis Cluster模式Redis 主从数据同步(主从复制)的过程?答案:1、slave启动后,向master发送sync命令2、master收到sync之后,执行bgsave保存快照,生成RDB全量文件3、master把slave的写命令记录到缓存4、bgsave执行完毕之后,发送RDB文件到slave,slave执行5、master发送缓冲区的写命令给slave,slave接收命令并执行,完成复制初始化。6、此后,master每次执行一个写命令都会同步发送给slave,保持master与slave之间数据的一致性主从复制的优缺点?答案:1、优点:master能自动将数据同步到slave,可以进行读写分离,分担master的读压力master、slave之间的同步是以非阻塞的方式进行的,同步期间,客户端仍然可以提交查询或更新请求缺点:不具备自动容错与恢复功能,master 节点宕机后,需要手动指定新的 mastermaster宕机,如果宕机前数据没有同步完,则切换IP后会存在数据不一致的问题难以支持在线扩容,Redis的容量受限于单机配置Sentinel(哨兵)模式的优缺点?答案:哨兵模式基于主从复制模式,增加了哨兵来监控与自动处理故障。1、优点:哨兵模式基于主从复制模式,所以主从复制模式有的优点,哨兵模式也有master 挂掉可以自动进行切换,系统可用性更高2、缺点:Redis的容量受限于单机配置需要额外的资源来启动sentinel进程Redis Cluster 模式的优缺点?答案:实现了Redis的分布式存储,即每台节点存储不同的内容,来解决在线扩容的问题。1、优点:无中心架构,数据按照slot分布在多个节点集群中的每个节点都是平等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据。可线性扩展到1000多个节点,节点可动态添加或删除能够实现自动故障转移,节点之间通过gossip协议交换状态信息,用投票机制完成slave到master的角色转换缺点:数据通过异步复制,不保证数据的强一致性slave充当 “冷备”,不对外提供读、写服务,只作为故障转移使用。批量操作限制,目前只支持具有相同slot值的key执行批量操作,对mset、mget、sunion等操作支持不友好key事务操作支持有限,只支持多key在同一节点的事务操作,多key分布在不同节点时无法使用事务功能不支持多数据库空间,一台redis可以支持16个db,集群模式下只能使用一个,即db 0。Redis Cluster模式不建议使用pipeline和multi-keys操作,减少max redirect产生的场景。Redis 如何做扩容?答案:为了避免数据迁移失效,通常使用一致性哈希实现动态扩容缩容,有效减少需要迁移的Key数量。但是Cluster 模式,采用固定Slot槽位方式(16384个),对每个key计算CRC16值,然后对16384取模,然后根据slot值找到目标机器,扩容时,我们只需要迁移一部分的slot到新节点即可。Redis 的集群原理?答案:一个redis集群由多个节点node组成,而多个node之间通过cluster meet命令来进行连接,组成一个集群。数据存储通过分片的形式,整个集群分成了16384个slot,每个节点负责一部分槽位。整个槽位的信息会同步到所有节点中。key与slot的映射关系:健值对 key,进行 CRC16 计算,计算出一个 16 bit 的值将 16 bit 的值对 16384 取模,得到 0 ~ 16383 的数表示 key 对应的哈希槽Redis 如何做到高可用?答案:哨兵机制。具有自动故障转移、集群监控、消息通知等功能。哨兵可以同时监视所有的主、从服务器,当某个master下线时,自动提升对应的slave为master,然后由新master对外提供服务。什么是 Redis 事务?答案:Redis事务是一组命令的集合,将多个命令打包,然后把这些命令按顺序添加到队列中,并且按顺序执行这些命令。Redis事务中没有像Mysql关系型数据库事务隔离级别的概念,不能保证原子性操作,也没有像Mysql那样执行事务失败会进行回滚操作Redis 事务执行流程?答案:通过MULTI、EXEC、WATCH等命令来实现事务机制,事务执行过程将一系列多个命令按照顺序一次性执行,在执行期间,事务不会被中断,也不会去执行客户端的其他请求,直到所有命令执行完毕。具体过程:服务端收到客户端请求,事务以MULTI开始如果正处于事务状态时,则会把后续命令放入队列同时返回给客户端QUEUED,反之则直接执行这 个命令当收到客户端的EXEC命令时,才会将队列里的命令取出、顺序执行,执行完将当前状态从事务状态改为非事务状态如果收到 DISCARD 命令,放弃执行队列中的命令,可以理解为Mysql的回滚操作,并且将当前的状态从事务状态改为非事务状态WATCH 监视某个key,该命令只能在MULTI命令之前执行。如果监视的key被其他客户端修改,EXEC将会放弃执行队列中的所有命令。UNWATCH 取消监视之前通过WATCH 命令监视的key。通过执行EXEC 、DISCARD 两个命令之前监视的key也会被取消监视。Redis 与 Guava 、Caffeine 有什么区别?答案:缓存分为本地缓存和分布式缓存。1、Caffeine、Guava,属于本地缓存,特点:直接访问内存,速度快,受内存限制,无法进行大数据存储。无网络通讯开销,性能更高。只支持本地应用进程访问,同步更新所有节点的本地缓存数据成本较高。应用进程重启,数据会丢失。所以,本地缓存适合存储一些不易改变或者低频改变的高热点数据。2、Redis属于分布式缓存,特点:集群模式,支持大数据量存储数据集中存储,保证数据的一致性数据跨网络传输,性能低于本地缓存。但同一个机房,两台服务器之间请求跑一个来回也就需要500微秒,比起其优势,这点损耗完全可以忽略,这也是分布式缓存受欢迎的原因。支持副本机制,有效的保证了高可用性。如何实现一个分布式锁?答案:1、数据库表,性能比较差2、使用Lua脚本 (包含 SETNX EXPIRE 两条指令)3、SET的扩展命令(SET key value [EX][PX] [NX|XX])4、Redlock 框架5、Zookeeper Curator框架提供了现成的分布式锁

    架构师社区

  • 源码解读Dubbo分层设计思想

    I作者:vivo互联网服务器团队-Wang Genfu一、Dubbo分层整体设计概述我们先从下图开始简单介绍Dubbo分层设计概念:(引用自Duboo开发指南-框架设计文档)如图描述Dubbo实现的RPC整体分10层:service、config、proxy、registry、cluster、monitor、protocol、exchange、transport、serialize。service:使用方定义的接口和实现类;config:负责解析Dubbo定义的配置,比如注解和xml配置,各种参数;proxy:主要负责生成消费者和提供者的代理对象,加载框架功能,比如提供者过滤器链,扩展点;registry:负责注册服务的定义和实现类的装载;cluster:只有消费者有这么一层,负责包装多个服务提供者成一个‘大提供者’,加载负载均衡、路有等扩展点;monitor:定义监控服务,加载监控实现提供者;protocol:封装RPC调用接口,管理调用实体的生命周期;exchange:封装请求响应模式,同步转异步;transport:抽象传输层模型,兼容netty、mina、grizzly等通讯框架;serialize:抽象序列化模型,兼容多种序列化框架,包括:fastjson、fst、hessian2、kryo、kryo2、protobuf等,通过序列化支持跨语言的方式,支持跨语言的rpc调用;Dubbo这么分层的目的在于实现层与层之间的解耦,每一层都定义了接口规范,也可以根据不同的业务需求定制、加载不同的实现,具有极高的扩展性。1.1. RPC调用过程接下来结合上图简单描述一次完整的rpc调用过程:从Dubbo分层的角度看,详细时序图如下,蓝色部分是服务消费端,浅绿色部分是服务提供端,时序图从消费端一次Dubbo方法调用开始,到服务端本地方法执行结束。从Dubbo核心领域对象的角度看,我们引用Dubbo官方文档说明,如下图所示。Dubbo核心领域对象是Invoker,消费端代理对象是proxy,包装了Invoker的调用;服务端代理对象是一个Invoker,他通过exporter包装,当服务端接收到调用请求后,通过exporter找到Invoker,Invoker去实际执行用户的业务逻辑。(引用自Dubbo官方文档)1.2 Dubbo服务的注册和发现流程下图出自开发指南-框架设计-引用服务时序,主要流程是:从注册中心订阅服务提供者,然后启动tcp服务连接远端提供者,将多个服务提供者合并成一个Invoker,用这个Invoker创建代理对象。下图出自开发指南-框架设计-暴露服务时序,主要流程是:创建本地服务的代理Invoker,启动tcp服务暴露服务,然后将服务注册到注册中心。接下来我们结合Dubbo服务的注册和发现,从配置层开始解释每一层的作用和原理。示例服务接口定义如下:public interface CouponServiceViewFacade { /** * 查询单张优惠券 */ CouponViewDTO query(String code);}二、配置层2.1. 做什么配置层提供配置处理工具类,在容器启动的时候,通过ServiceConfig.export实例化服务提供者,ReferenceConfig.get实例化服务消费者对象。Dubbo应用使用spring容器启动时,Dubbo服务提供者配置处理器通过ServiceConfig.export启动Dubbo远程服务暴露本地服务。Dubbo服务消费者配置处理器通过ReferenceConfig.get实例化一个代理对象,并通过注册中心服务发现,连接远端服务提供者。Dubbo配置可以使用注解和xml两种形式,本文采用注解的形式进行说明。2.2. 怎么做2.2.1 服务消费端的解析Spring容器启动过程中,填充bean属性时,对含有Dubbo引用注解的属性使用org.apache.dubbo.config.spring.beans.factory.annotation.ReferenceAnnotationBeanPostProcessor进行初始化。如下是ReferenceAnnotationBeanPostProcessor的构造方法,Dubbo服务消费者注解处理器处理以下三个注解:DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class修饰的类。ReferenceAnnotationBeanPostProcessor类定义:public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor implements ApplicationContextAware { public ReferenceAnnotationBeanPostProcessor() { super(DubboReference.class, Reference.class, com.alibaba.dubbo.config.annotation.Reference.class); }}Dubbo服务发现到这一层,Dubbo即将开始构建服务消费者的代理对象,CouponServiceViewFacade接口的代理实现类。2.2.2 服务提供端的解析Spring容器启动的时候,加载注解@org.apache.dubbo.config.spring.context.annotation.DubboComponentScan指定范围的类,并初始化;初始化使用dubbo实现的扩展点org.apache.dubbo.config.spring.beans.factory.annotation.ServiceClassPostProcessor。ServiceClassPostProcessor处理的注解类有DubboService.class,Service.class,com.alibaba.dubbo.config.annotation.Service.class。如下是ServiceClassPostProcessor类定义:public class ServiceClassPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware { private final static List

    架构师社区

首页  上一页  1 2 3 4 5 6 7 8 9 10 下一页 尾页
发布文章