mirror of
https://github.com/cheetahlou/CategoryResourceRepost.git
synced 2025-11-16 06:03:45 +08:00
mod
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<audio id="audio" title="第36讲 | 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/d1/39b4b6580b81b69aca9a92904ec593d1.mp3"></audio>
|
||||
|
||||
在日常开发中,尤其是业务开发,少不了利用Java对数据库进行基本的增删改查等数据操作,这也是Java工程师的必备技能之一。做好数据操作,不仅仅需要对Java语言相关框架的掌握,更需要对各种数据库自身体系结构的理解。今天这一讲,作为补充Java面试考察知识点的完整性,关于数据库的应用和细节还需要在实践中深入学习。
|
||||
|
||||
今天我要问你的问题是,谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景?
|
||||
|
||||
## 典型回答
|
||||
|
||||
所谓隔离级别([Isolation Level](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels)),就是在数据库事务中,为保证并发数据读写的正确性而提出的定义,它并不是MySQL专有的概念,而是源于[ANSI](https://en.wikipedia.org/wiki/American_National_Standards_Institute)/[ISO](https://en.wikipedia.org/wiki/International_Organization_for_Standardization)制定的[SQL-92](https://en.wikipedia.org/wiki/SQL-92)标准。
|
||||
|
||||
每种关系型数据库都提供了各自特色的隔离级别实现,虽然在通常的[定义](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Isolation_levels)中是以锁为实现单元,但实际的实现千差万别。以最常见的MySQL InnoDB引擎为例,它是基于 [MVCC](https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html)(Multi-Versioning Concurrency Control)和锁的复合实现,按照隔离程度从低到高,MySQL事务隔离级别分为四个不同层次:
|
||||
|
||||
<li>
|
||||
读未提交(Read uncommitted),就是一个事务能够看到其他事务尚未提交的修改,这是最低的隔离水平,允许[脏读](https://en.wikipedia.org/wiki/Isolation_(database_systems)#Dirty_reads)出现。
|
||||
</li>
|
||||
<li>
|
||||
读已提交(Read committed),事务能够看到的数据都是其他事务已经提交的修改,也就是保证不会看到任何中间性状态,当然脏读也不会出现。读已提交仍然是比较低级别的隔离,并不保证再次读取时能够获取同样的数据,也就是允许其他事务并发修改数据,允许不可重复读和幻象读(Phantom Read)出现。
|
||||
</li>
|
||||
<li>
|
||||
可重复读(Repeatable reads),保证同一个事务中多次读取的数据是一致的,这是MySQL InnoDB引擎的默认隔离级别,但是和一些其他数据库实现不同的是,可以简单认为MySQL在可重复读级别不会出现幻象读。
|
||||
</li>
|
||||
<li>
|
||||
串行化(Serializable),并发事务之间是串行化的,通常意味着读取需要获取共享读锁,更新需要获取排他写锁,如果SQL使用WHERE语句,还会获取区间锁(MySQL以GAP锁形式实现,可重复读级别中默认也会使用),这是最高的隔离级别。
|
||||
</li>
|
||||
|
||||
至于悲观锁和乐观锁,也并不是MySQL或者数据库中独有的概念,而是并发编程的基本概念。主要区别在于,操作共享数据时,“悲观锁”即认为数据出现冲突的可能性更大,而“乐观锁”则是认为大部分情况不会出现冲突,进而决定是否采取排他性措施。
|
||||
|
||||
反映到MySQL数据库应用开发中,悲观锁一般就是利用类似SELECT … FOR UPDATE这样的语句,对数据加锁,避免其他事务意外修改数据。乐观锁则与Java并发包中的AtomicFieldUpdater类似,也是利用CAS机制,并不会对数据加锁,而是通过对比数据的时间戳或者版本号,来实现乐观锁需要的版本判断。
|
||||
|
||||
我认为前面提到的MVCC,其本质就可以看作是种乐观锁机制,而排他性的读写锁、双阶段锁等则是悲观锁的实现。
|
||||
|
||||
有关它们的应用场景,你可以构建一下简化的火车余票查询和购票系统。同时查询的人可能很多,虽然具体座位票只能是卖给一个人,但余票可能很多,而且也并不能预测哪个查询者会购票,这个时候就更适合用乐观锁。
|
||||
|
||||
## 考点分析
|
||||
|
||||
今天的问题来源于实际面试,这两部分问题反映了面试官试图考察面试者在日常应用开发中,是否学习或者思考过数据库内部的机制,是否了解并发相关的基础概念和实践。
|
||||
|
||||
我从普通数据库应用开发者的角度,提供了一个相对简化的答案,面试官很有可能进一步从实例的角度展开,例如设计一个典型场景重现脏读、幻象读,或者从数据库设计的角度,可以用哪些手段避免类似情况。我建议你在准备面试时,可以在典型的数据库上试验一下,验证自己的观点。
|
||||
|
||||
其他可以考察的点也有很多,在准备这个问题时你也可以对比Java语言的并发机制,进行深入理解,例如,随着隔离级别从低到高,竞争性(Contention)逐渐增强,随之而来的代价同样是性能和扩展性的下降。
|
||||
|
||||
数据库衍生出很多不同的职责方向:
|
||||
|
||||
<li>
|
||||
数据库管理员(DBA),这是一个单独的专业领域。
|
||||
</li>
|
||||
<li>
|
||||
数据库应用工程师,很多业务开发者就是这种定位,综合利用数据库和其他编程语言等技能,开发业务应用。
|
||||
</li>
|
||||
<li>
|
||||
数据库工程师,更加侧重于开发数据库、数据库中间件等基础软件。
|
||||
</li>
|
||||
|
||||
后面两者与Java开发更加相关,但是需要的知识和技能是不同的,所以面试的考察角度也有区别,今天我会分析下对相关知识学习和准备面试的看法。
|
||||
|
||||
另外,在数据库相关领域,Java工程师最常接触到的就是O/R Mapping框架或者类似的数据库交互类库,我会选取最广泛使用的框架进行对比和分析。
|
||||
|
||||
## 知识扩展
|
||||
|
||||
首先,我来谈谈对数据库相关领域学习的看法,从最广泛的应用开发者角度,至少需要掌握:
|
||||
|
||||
<li>
|
||||
数据库设计基础,包括数据库设计中的几个基本范式,各种数据库的基础概念,例如表、视图、索引、外键、序列号生成器等,清楚如何将现实中业务实体和其依赖关系映射到数据库结构中,掌握典型实体数据应该使用什么样的数据库数据类型等。
|
||||
</li>
|
||||
<li>
|
||||
每种数据库的设计和实现多少会存在差异,所以至少要精通你使用过的数据库的设计要点。我今天开篇谈到的MySQL事务隔离级别,就区别于其他数据库,进一步了解MVCC、Locking等机制对于处理进阶问题非常有帮助;还需要了解,不同索引类型的使用,甚至是底层数据结构和算法等。
|
||||
</li>
|
||||
<li>
|
||||
常见的SQL语句,掌握基础的SQL调优技巧,至少要了解基本思路是怎样的,例如SQL怎样写才能更好利用索引、知道如何分析[SQL执行计划](https://dev.mysql.com/doc/workbench/en/wb-performance-explain.html)等。
|
||||
</li>
|
||||
<li>
|
||||
更进一步,至少需要了解针对高并发等特定场景中的解决方案,例如读写分离、分库分表,或者如何利用缓存机制等,目前的数据存储也远不止传统的关系型数据库了。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ae/9d/ae0959aafa30d1530ad4bdf7b1a8a19d.png" alt="" />
|
||||
|
||||
上面的示意图简单总结了我对数据库领域的理解,希望可以给你进行准备时提供个借鉴。当然在准备面试时并不是一味找一堆书闷头苦读,我还是建议从实际工作中使用的数据库出发,侧重于结合实践,完善和深化自己的知识体系。
|
||||
|
||||
接下来我们还是回到Java本身,目前最为通用的Java和数据库交互技术就是JDBC,最常见的开源框架基本都是构建在JDBC之上,包括我们熟悉的[JPA](https://www.tutorialspoint.com/jpa/jpa_introduction.htm)/[Hibernate](https://en.wikipedia.org/wiki/Hibernate_(framework))、[MyBatis](http://www.mybatis.org/mybatis-3/)、Spring JDBC Template等,各自都有独特的设计特点。
|
||||
|
||||
Hibernate是最负盛名的O/R Mapping框架之一,它也是一个JPA Provider。顾名思义,它是以对象为中心的,其强项更体现在数据库到Java对象的映射,可以很方便地在Java对象层面体现外键约束等相对复杂的关系,提供了强大的持久化功能。内部大量使用了[Lazy-load](https://en.wikipedia.org/wiki/Lazy_loading)等技术提高效率。并且,为了屏蔽数据库的差异,降低维护开销,Hibernate提供了类SQL的HQL,可以自动生成某种数据库特定的SQL语句。
|
||||
|
||||
Hibernate应用非常广泛,但是过度强调持久化和隔离数据库底层细节,也导致了很多弊端,例如HQL需要额外的学习,未必比深入学习SQL语言更高效;减弱程序员对SQL的直接控制,还可能导致其他代价,本来一句SQL的事情,可能被Hibernate生成几条,隐藏的内部细节也阻碍了进一步的优化。
|
||||
|
||||
而MyBatis虽然仍然提供了一些映射的功能,但更加以SQL为中心,开发者可以侧重于SQL和存储过程,非常简单、直接。如果我们的应用需要大量高性能的或者复杂的SELECT语句等,“半自动”的MyBatis就会比Hibernate更加实用。
|
||||
|
||||
而Spring JDBC Template也是更加接近于SQL层面,Spring本身也可以集成Hibernate等O/R Mapping框架。
|
||||
|
||||
关于这些具体开源框架的学习,我的建议是:
|
||||
|
||||
<li>
|
||||
从整体上把握主流框架的架构和设计理念,掌握主要流程,例如SQL解析生成、SQL执行到结果映射等处理过程到底发生了什么。
|
||||
</li>
|
||||
<li>
|
||||
掌握映射等部分的细节定义和原理,根据我在准备专栏时整理的面试题目,发现很多题目都是偏向于映射定义的细节。
|
||||
</li>
|
||||
<li>
|
||||
另外,对比不同框架的设计和实现,既有利于你加深理解,也是面试考察的热点方向之一。
|
||||
</li>
|
||||
|
||||
今天我从数据库应用开发者的角度,分析了MySQL数据库的部分内部机制,并且补充了我对数据库相关面试准备和知识学习的建议,最后对主流O/R Mapping等框架进行了简单的对比。
|
||||
|
||||
## 一课一练
|
||||
|
||||
关于今天我们讨论的题目你做到心中有数了吗? 今天的思考题是,从架构设计的角度,可以将MyBatis分为哪几层?每层都有哪些主要模块?
|
||||
|
||||
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
|
||||
|
||||
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
|
||||
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
<audio id="audio" title="第37讲 | 谈谈Spring Bean的生命周期和作用域?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d4/c7/d4a97ab8ff75f23c8902b08880c204c7.mp3"></audio>
|
||||
|
||||
在企业应用软件开发中,Java是毫无争议的主流语言,开放的Java EE规范和强大的开源框架功不可没,其中Spring毫无疑问已经成为企业软件开发的事实标准之一。今天这一讲,我将补充Spring相关的典型面试问题,并谈谈其部分设计细节。
|
||||
|
||||
今天我要问你的问题是,谈谈Spring Bean的生命周期和作用域?
|
||||
|
||||
## 典型回答
|
||||
|
||||
Spring Bean生命周期比较复杂,可以分为创建和销毁两个过程。
|
||||
|
||||
首先,创建Bean会经过一系列的步骤,主要包括:
|
||||
|
||||
<li>
|
||||
实例化Bean对象。
|
||||
</li>
|
||||
<li>
|
||||
设置Bean属性。
|
||||
</li>
|
||||
<li>
|
||||
如果我们通过各种Aware接口声明了依赖关系,则会注入Bean对容器基础设施层面的依赖。具体包括BeanNameAware、BeanFactoryAware和ApplicationContextAware,分别会注入Bean ID、Bean Factory或者ApplicationContext。
|
||||
</li>
|
||||
<li>
|
||||
调用BeanPostProcessor的前置初始化方法postProcessBeforeInitialization。
|
||||
</li>
|
||||
<li>
|
||||
如果实现了InitializingBean接口,则会调用afterPropertiesSet方法。
|
||||
</li>
|
||||
<li>
|
||||
调用Bean自身定义的init方法。
|
||||
</li>
|
||||
<li>
|
||||
调用BeanPostProcessor的后置初始化方法postProcessAfterInitialization。
|
||||
</li>
|
||||
<li>
|
||||
创建过程完毕。
|
||||
</li>
|
||||
|
||||
你可以参考下面示意图理解这个具体过程和先后顺序。<br />
|
||||
<img src="https://static001.geekbang.org/resource/image/3a/7e/3a51f06f56b905b8fbf1661359e1727e.png" alt="" />
|
||||
|
||||
第二,Spring Bean的销毁过程会依次调用DisposableBean的destroy方法和Bean自身定制的destroy方法。
|
||||
|
||||
Spring Bean有五个作用域,其中最基础的有下面两种:
|
||||
|
||||
<li>
|
||||
Singleton,这是Spring的默认作用域,也就是为每个IOC容器创建唯一的一个Bean实例。
|
||||
</li>
|
||||
<li>
|
||||
Prototype,针对每个getBean请求,容器都会单独创建一个Bean实例。
|
||||
</li>
|
||||
|
||||
从Bean的特点来看,Prototype适合有状态的Bean,而Singleton则更适合无状态的情况。另外,使用Prototype作用域需要经过仔细思考,毕竟频繁创建和销毁Bean是有明显开销的。
|
||||
|
||||
如果是Web容器,则支持另外三种作用域:
|
||||
|
||||
<li>
|
||||
Request,为每个HTTP请求创建单独的Bean实例。
|
||||
</li>
|
||||
<li>
|
||||
Session,很显然Bean实例的作用域是Session范围。
|
||||
</li>
|
||||
<li>
|
||||
GlobalSession,用于Portlet容器,因为每个Portlet有单独的Session,GlobalSession提供一个全局性的HTTP Session。
|
||||
</li>
|
||||
|
||||
## 考点分析
|
||||
|
||||
今天我选取的是一个入门性质的高频Spring面试题目,我认为相比于记忆题目典型回答里的细节步骤,理解和思考Bean生命周期所体现出来的Spring设计和机制更有意义。
|
||||
|
||||
你能看到,Bean的生命周期是完全被容器所管理的,从属性设置到各种依赖关系,都是容器负责注入,并进行各个阶段其他事宜的处理,Spring容器为应用开发者定义了清晰的生命周期沟通界面。
|
||||
|
||||
如果从具体API设计和使用技巧来看,还记得我在[专栏第13讲](http://time.geekbang.org/column/article/8471)提到过的Marker Interface吗,Aware接口就是个典型应用例子,Bean可以实现各种不同Aware的子接口,为容器以Callback形式注入依赖对象提供了统一入口。
|
||||
|
||||
言归正传,还是回到Spring的学习和面试。关于Spring,也许一整本书都无法完整涵盖其内容,专栏里我会有限地补充:
|
||||
|
||||
<li>
|
||||
Spring的基础机制。
|
||||
</li>
|
||||
<li>
|
||||
Spring框架的涵盖范围。
|
||||
</li>
|
||||
<li>
|
||||
Spring AOP自身设计的一些细节,前面[第24讲](http://time.geekbang.org/column/article/10076)偏重于底层实现原理,这样还不够全面,毕竟不管是动态代理还是字节码操纵,都还只是基础,更需要Spring层面对切面编程的支持。
|
||||
</li>
|
||||
|
||||
## 知识扩展
|
||||
|
||||
首先,我们先来看看Spring的基础机制,至少你需要理解下面两个基本方面。
|
||||
|
||||
- 控制反转(Inversion of Control),或者也叫依赖注入(Dependency Injection),广泛应用于Spring框架之中,可以有效地改善了模块之间的紧耦合问题。
|
||||
|
||||
从Bean创建过程可以看到,它的依赖关系都是由容器负责注入,具体实现方式包括带参数的构造函数、setter方法或者[AutoWired](https://docs.spring.io/spring-framework/docs/5.0.3.RELEASE/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html)方式实现。
|
||||
|
||||
- AOP,我们已经在前面接触过这种切面编程机制,Spring框架中的事务、安全、日志等功能都依赖于AOP技术,下面我会进一步介绍。
|
||||
|
||||
第二,Spring到底是指什么?
|
||||
|
||||
我前面谈到的Spring,其实是狭义的[Spring Framework](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java),其内部包含了依赖注入、事件机制等核心模块,也包括事务、O/R Mapping等功能组成的数据访问模块,以及Spring MVC等Web框架和其他基础组件。
|
||||
|
||||
广义上的Spring已经成为了一个庞大的生态系统,例如:
|
||||
|
||||
<li>
|
||||
Spring Boot,通过整合通用实践,更加自动、智能的依赖管理等,Spring Boot提供了各种典型应用领域的快速开发基础,所以它是以应用为中心的一个框架集合。
|
||||
</li>
|
||||
<li>
|
||||
Spring Cloud,可以看作是在Spring Boot基础上发展出的更加高层次的框架,它提供了构建分布式系统的通用模式,包含服务发现和服务注册、分布式配置管理、负载均衡、分布式诊断等各种子系统,可以简化微服务系统的构建。
|
||||
</li>
|
||||
<li>
|
||||
当然,还有针对特定领域的Spring Security、Spring Data等。
|
||||
</li>
|
||||
|
||||
上面的介绍比较笼统,针对这么多内容,如果将目标定得太过宽泛,可能就迷失在Spring生态之中,我建议还是深入你当前使用的模块,如Spring MVC。并且,从整体上把握主要前沿框架(如Spring Cloud)的应用范围和内部设计,至少要了解主要组件和具体用途,毕竟如何构建微服务等,已经逐渐成为Java应用开发面试的热点之一。
|
||||
|
||||
第三,我们来探讨一下更多有关Spring AOP自身设计和实现的细节。
|
||||
|
||||
先问一下自己,我们为什么需要切面编程呢?
|
||||
|
||||
切面编程落实到软件工程其实是为了更好地模块化,而不仅仅是为了减少重复代码。通过AOP等机制,我们可以把横跨多个不同模块的代码抽离出来,让模块本身变得更加内聚,进而业务开发者可以更加专注于业务逻辑本身。从迭代能力上来看,我们可以通过切面的方式进行修改或者新增功能,这种能力不管是在问题诊断还是产品能力扩展中,都非常有用。
|
||||
|
||||
在之前的分析中,我们已经分析了AOP Proxy的实现原理,简单回顾一下,它底层是基于JDK动态代理或者cglib字节码操纵等技术,运行时动态生成被调用类型的子类等,并实例化代理对象,实际的方法调用会被代理给相应的代理对象。但是,这并没有解释具体在AOP设计层面,什么是切面,如何定义切入点和切面行为呢?
|
||||
|
||||
Spring AOP引入了其他几个关键概念:
|
||||
|
||||
<li>
|
||||
Aspect,通常叫作方面,它是跨不同Java类层面的横切性逻辑。在实现形式上,既可以是XML文件中配置的普通类,也可以在类代码中用“@Aspect”注解去声明。在运行时,Spring框架会创建类似[Advisor](https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/Advisor.java)来指代它,其内部会包括切入的时机(Pointcut)和切入的动作(Advice)。
|
||||
</li>
|
||||
<li>
|
||||
Join Point,它是Aspect可以切入的特定点,在Spring里面只有方法可以作为Join Point。
|
||||
</li>
|
||||
<li>
|
||||
[](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java)[Advice](https://github.com/spring-projects/spring-framework/blob/67ea4b3a050af3db5545f58ff85a0d132ee91c2a/spring-aop/src/main/java/org/aopalliance/aop/Advice.java),它定义了切面中能够采取的动作。如果你去看Spring源码,就会发现Advice、Join Point并没有定义在Spring自己的命名空间里,这是因为他们是源自[AOP联盟](http://aopalliance.sourceforge.net/),可以看作是Java工程师在AOP层面沟通的通用规范。
|
||||
</li>
|
||||
|
||||
Java核心类库中同样存在类似代码,例如Java 9中引入的Flow API就是Reactive Stream规范的最小子集,通过这种方式,可以保证不同产品直接的无缝沟通,促进了良好实践的推广。
|
||||
|
||||
具体的Spring Advice结构请参考下面的示意图。<br />
|
||||
<img src="https://static001.geekbang.org/resource/image/5b/ba/5b6955b4757c1a5fd0ecacdaf835e3ba.png" alt="" />
|
||||
|
||||
其中,BeforeAdvice和AfterAdvice包括它们的子接口是最简单的实现。而Interceptor则是所谓的拦截器,用于拦截住方法(也包括构造器)调用事件,进而采取相应动作,所以Interceptor是覆盖住整个方法调用过程的Advice。通常将拦截器类型的Advice叫作Around,在代码中可以使用“@Around”来标记,或者在配置中使用“<aop:around>”。
|
||||
|
||||
如果从时序上来看,则可以参考下图,理解具体发生的时机。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/85/cb/85205c0c0ddcdafd2fad4ff5a53af0cb.png" alt="" />
|
||||
|
||||
- Pointcut,它负责具体定义Aspect被应用在哪些Join Point,可以通过指定具体的类名和方法名来实现,或者也可以使用正则表达式来定义条件。
|
||||
|
||||
你可以参看下面的示意图,来进一步理解上面这些抽象在逻辑上的意义。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/de/4a/dee96c33619d76d33281332bb3d2494a.png" alt="" />
|
||||
|
||||
<li>
|
||||
Join Point仅仅是可利用的机会。
|
||||
</li>
|
||||
<li>
|
||||
Pointcut是解决了切面编程中的Where问题,让程序可以知道哪些机会点可以应用某个切面动作。
|
||||
</li>
|
||||
<li>
|
||||
而Advice则是明确了切面编程中的What,也就是做什么;同时通过指定Before、After或者Around,定义了When,也就是什么时候做。
|
||||
</li>
|
||||
|
||||
在准备面试时,如果在实践中使用过AOP是最好的,否则你可以选择一个典型的AOP实例,理解具体的实现语法细节,因为在面试考察中也许会问到这些技术细节。
|
||||
|
||||
如果你有兴趣深入内部,最好可以结合Bean生命周期,理解Spring如何解析AOP相关的注解或者配置项,何时何地使用到动态代理等机制。为了避免被庞杂的源码弄晕,我建议你可以从比较精简的测试用例作为一个切入点,如[CglibProxyTests](https://github.com/spring-projects/spring-framework/blob/da80502ea6ed4860f5bf7b668300644cdfe3bb5a/spring-context/src/test/java/org/springframework/aop/framework/CglibProxyTests.java)。
|
||||
|
||||
另外,Spring框架本身功能点非常多,AOP并不是它所支持的唯一切面技术,它只能利用动态代理进行运行时编织,而不能进行编译期的静态编织或者类加载期编织。例如,在Java平台上,我们可以使用Java Agent技术,在类加载过程中对字节码进行操纵,比如修改或者替换方法实现等。在Spring体系中,如何做到类似功能呢?你可以使用AspectJ,它具有更加全面的能力,当然使用也更加复杂。
|
||||
|
||||
今天我从一个常见的Spring面试题开始,浅谈了Spring的基础机制,探讨了Spring生态范围,并且补充分析了部分AOP的设计细节,希望对你有所帮助。
|
||||
|
||||
## 一课一练
|
||||
|
||||
关于今天我们讨论的题目你做到心中有数了吗?今天的思考题是,请介绍一下Spring声明式事务的实现机制,可以考虑将具体过程画图。
|
||||
|
||||
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
|
||||
|
||||
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
|
||||
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
<audio id="audio" title="第38讲 | 对比Java标准NIO类库,你知道Netty是如何实现更高性能的吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3e/01/3e1abfe62177d4b41b43c4b675a62201.mp3"></audio>
|
||||
|
||||
今天我会对NIO进行一些补充,在[专栏第11讲](http://time.geekbang.org/column/article/8369)中,我们初步接触了Java提供的几种IO机制,作为语言基础类库,Java自身的NIO设计更偏底层,这本无可厚非,但是对于一线的应用开发者,其复杂性、扩展性等方面,就存在一定的局限了。在基础NIO之上,Netty构建了更加易用、高性能的网络框架,广泛应用于互联网、游戏、电信等各种领域。
|
||||
|
||||
今天我要问你的问题是,对比Java标准NIO类库,你知道Netty是如何实现更高性能的吗?
|
||||
|
||||
## 典型回答
|
||||
|
||||
单独从性能角度,Netty在基础的NIO等类库之上进行了很多改进,例如:
|
||||
|
||||
<li>
|
||||
更加优雅的Reactor模式实现、灵活的线程模型、利用EventLoop等创新性的机制,可以非常高效地管理成百上千的Channel。
|
||||
</li>
|
||||
<li>
|
||||
充分利用了Java的Zero-Copy机制,并且从多种角度,“斤斤计较”般的降低内存分配和回收的开销。例如,使用池化的Direct Buffer等技术,在提高IO性能的同时,减少了对象的创建和销毁;利用反射等技术直接操纵SelectionKey,使用数组而不是Java容器等。
|
||||
</li>
|
||||
<li>
|
||||
使用更多本地代码。例如,直接利用JNI调用Open SSL等方式,获得比Java内建SSL引擎更好的性能。
|
||||
</li>
|
||||
<li>
|
||||
在通信协议、序列化等其他角度的优化。
|
||||
</li>
|
||||
|
||||
总的来说,Netty并没有Java核心类库那些强烈的通用性、跨平台等各种负担,针对性能等特定目标以及Linux等特定环境,采取了一些极致的优化手段。
|
||||
|
||||
## 考点分析
|
||||
|
||||
这是一个比较开放的问题,我给出的回答是个概要性的举例说明。面试官很可能利用这种开放问题作为引子,针对你回答的一个或者多个点,深入探讨你在不同层次上的理解程度。
|
||||
|
||||
在面试准备中,兼顾整体性的同时,不要忘记选定个别重点进行深入理解掌握,最好是进行源码层面的深入阅读和实验。如果你希望了解更多从性能角度Netty在编码层面的手段,可以参考Norman在Devoxx上的[分享](https://speakerdeck.com/normanmaurer/writing-highly-performant-network-frameworks-on-the-jvm-a-love-hate-relationship),其中的很多技巧对于实现极致性能的API有一定借鉴意义,但在一般的业务开发中要谨慎采用。
|
||||
|
||||
虽然提到Netty,人们会自然地想到高性能,但是Netty本身的优势不仅仅只有这一个方面,
|
||||
|
||||
下面我会侧重两个方面:
|
||||
|
||||
<li>
|
||||
对Netty进行整体介绍,帮你了解其基本组成。
|
||||
</li>
|
||||
<li>
|
||||
从一个简单的例子开始,对比在[第11讲](http://time.geekbang.org/column/article/8369)中基于IO、NIO等标准API的实例,分析它的技术要点,给你提供一个进一步深入学习的思路。
|
||||
</li>
|
||||
|
||||
## 知识扩展
|
||||
|
||||
首先,我们从整体了解一下Netty。按照官方定义,它是一个异步的、基于事件Client/Server的网络框架,目标是提供一种简单、快速构建网络应用的方式,同时保证高吞吐量、低延时、高可靠性。
|
||||
|
||||
从设计思路和目的上,Netty与Java自身的NIO框架相比有哪些不同呢?
|
||||
|
||||
我们知道Java的标准类库,由于其基础性、通用性的定位,往往过于关注技术模型上的抽象,而不是从一线应用开发者的角度去思考。我曾提到过,引入并发包的一个重要原因就是,应用开发者使用Thread API比较痛苦,需要操心的不仅仅是业务逻辑,而且还要自己负责将其映射到Thread模型上。Java NIO的设计也有类似的特点,开发者需要深入掌握线程、IO、网络等相关概念,学习路径很长,很容易导致代码复杂、晦涩,即使是有经验的工程师,也难以快速地写出高可靠性的实现。
|
||||
|
||||
Netty的设计强调了 “**Separation Of Concerns**”,通过精巧设计的事件机制,将业务逻辑和无关技术逻辑进行隔离,并通过各种方便的抽象,一定程度上填补了了基础平台和业务开发之间的鸿沟,更有利于在应用开发中普及业界的最佳实践。
|
||||
|
||||
另外,**Netty > java.nio + java. net!**
|
||||
|
||||
从API能力范围来看,Netty完全是Java NIO框架的一个大大的超集,你可以参考Netty官方的模块划分。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/f5/d8/f5de2483afd924b90ea09b656f4fced8.png" alt="" />
|
||||
|
||||
除了核心的事件机制等,Netty还额外提供了很多功能,例如:
|
||||
|
||||
<li>
|
||||
从网络协议的角度,Netty除了支持传输层的UDP、TCP、[SCTP](https://en.wikipedia.org/wiki/Stream_Control_Transmission_Protocol)协议,也支持HTTP(s)、WebSocket等多种应用层协议,它并不是单一协议的API。
|
||||
</li>
|
||||
<li>
|
||||
在应用中,需要将数据从Java对象转换成为各种应用协议的数据格式,或者进行反向的转换,Netty为此提供了一系列扩展的编解码框架,与应用开发场景无缝衔接,并且性能良好。
|
||||
</li>
|
||||
<li>
|
||||
它扩展了Java NIO Buffer,提供了自己的ByteBuf实现,并且深度支持Direct Buffer等技术,甚至hack了Java内部对Direct Buffer的分配和销毁等。同时,Netty也提供了更加完善的Scatter/Gather机制实现。
|
||||
</li>
|
||||
|
||||
可以看到,Netty的能力范围大大超过了Java核心类库中的NIO等API,可以说它是一个从应用视角出发的产物。
|
||||
|
||||
当然,对于基础API设计,Netty也有自己独到的见解,未来Java NIO API也可能据此进行一定的改进,如果你有兴趣可以参考[JDK-8187540](https://bugs.openjdk.java.net/browse/JDK-8187540)。
|
||||
|
||||
接下来,我们一起来看一个入门的代码实例,看看Netty应用到底是什么样子。
|
||||
|
||||
与[第11讲](http://time.geekbang.org/column/article/8369)类似,同样是以简化的Echo Server为例,下图是Netty官方提供的Server部分,完整用例请点击[链接](http://netty.io/4.1/xref/io/netty/example/echo/package-summary.html)。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/97/42/97f1f65e7277681a9e6da818832c8342.png" alt="" />
|
||||
|
||||
上面的例子,虽然代码很短,但已经足够体现出Netty的几个核心概念,请注意我用红框标记出的部分:
|
||||
|
||||
<li>
|
||||
[ServerBootstrap](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/bootstrap/ServerBootstrap.java),服务器端程序的入口,这是Netty为简化网络程序配置和关闭等生命周期管理,所引入的Bootstrapping机制。我们通常要做的创建Channel、绑定端口、注册Handler等,都可以通过这个统一的入口,以**Fluent** API等形式完成,相对简化了API使用。与之相对应, [Bootstrap](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/bootstrap/Bootstrap.java)则是Client端的通常入口。
|
||||
</li>
|
||||
<li>
|
||||
[Channel](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/Channel.java),作为一个基于NIO的扩展框架,Channel和Selector等概念仍然是Netty的基础组件,但是针对应用开发具体需求,提供了相对易用的抽象。
|
||||
</li>
|
||||
<li>
|
||||
[EventLoop](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/EventLoop.java),这是Netty处理事件的核心机制。例子中使用了EventLoopGroup。我们在NIO中通常要做的几件事情,如注册感兴趣的事件、调度相应的Handler等,都是EventLoop负责。
|
||||
</li>
|
||||
<li>
|
||||
[ChannelFuture](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/ChannelFuture.java),这是Netty实现异步IO的基础之一,保证了同一个Channel操作的调用顺序。Netty扩展了Java标准的Future,提供了针对自己场景的特有[Future](https://github.com/netty/netty/blob/eb7f751ba519cbcab47d640cd18757f09d077b55/common/src/main/java/io/netty/util/concurrent/Future.java)定义。
|
||||
</li>
|
||||
<li>
|
||||
ChannelHandler,这是应用开发者**放置业务逻辑的主要地方**,也是我上面提到的“Separation Of Concerns”原则的体现。
|
||||
</li>
|
||||
<li>
|
||||
[ChannelPipeline](https://github.com/netty/netty/blob/2c13f71c733c5778cd359c9148f50e63d1878f7f/transport/src/main/java/io/netty/channel/ChannelPipeline.java),它是ChannelHandler链条的容器,每个Channel在创建后,自动被分配一个ChannelPipeline。在上面的示例中,我们通过ServerBootstrap注册了ChannelInitializer,并且实现了initChannel方法,而在该方法中则承担了向ChannelPipleline安装其他Handler的任务。
|
||||
</li>
|
||||
|
||||
你可以参考下面的简化示意图,忽略Inbound/OutBound Handler的细节,理解这几个基本单元之间的操作流程和对应关系。
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/77/fa/77452800d6567dbf202583a9308421fa.png" alt="" />
|
||||
|
||||
对比Java标准NIO的代码,Netty提供的相对高层次的封装,减少了对Selector等细节的操纵,而EventLoop、Pipeline等机制则简化了编程模型,开发者不用担心并发等问题,在一定程度上简化了应用代码的开发。最难能可贵的是,这一切并没有以可靠性、可扩展性为代价,反而将其大幅度提高。
|
||||
|
||||
我在[专栏周末福利](http://time.geekbang.org/column/article/12188)中已经推荐了Norman Maurer等编写的《Netty实战》(Netty In Action),如果你想系统学习Netty,它会是个很好的入门参考。针对Netty的一些实现原理,很可能成为面试中的考点,例如:
|
||||
|
||||
<li>
|
||||
Reactor模式和Netty线程模型。
|
||||
</li>
|
||||
<li>
|
||||
Pipelining、EventLoop等部分的设计实现细节。
|
||||
</li>
|
||||
<li>
|
||||
Netty的内存管理机制、[引用计数](http://netty.io/wiki/reference-counted-objects.html)等特别手段。
|
||||
</li>
|
||||
<li>
|
||||
有的时候面试官也喜欢对比Java标准NIO API,例如,你是否知道Java NIO早期版本中的Epoll[空转问题](http://www.10tiao.com/html/308/201602/401718035/1.html),以及Netty的解决方式等。
|
||||
</li>
|
||||
|
||||
对于这些知识点,公开的深入解读已经有很多了,在学习时希望你不要一开始就被复杂的细节弄晕,可以结合实例,逐步、有针对性的进行学习。我的一个建议是,可以试着画出相应的示意图,非常有助于理解并能清晰阐述自己的看法。
|
||||
|
||||
今天,从Netty性能的问题开始,我概要地介绍了Netty框架,并且以Echo Server为例,对比了Netty和Java NIO在设计上的不同。但这些都仅仅是冰山的一角,全面掌握还需要下非常多的功夫。
|
||||
|
||||
## 一课一练
|
||||
|
||||
关于今天我们讨论的题目你做到心中有数了吗?今天的思考题是,Netty的线程模型是什么样的?
|
||||
|
||||
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
|
||||
|
||||
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
<audio id="audio" title="第39讲 | 谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c7/89/c732a54f9bf356b24cf0dfa377b5c889.mp3"></audio>
|
||||
|
||||
专栏的绝大部分主题都侧重于Java语言和虚拟机,基本都是单机模式下的问题,今天我会补充一个分布式相关的问题。严格来说,分布式并不算是Java领域,而是一个单独的大主题,但确实也会在Java技术岗位面试中被涉及。在准备面试时,如果有丰富的分布式系统经验当然好;如果没有,你可以选择典型问题和基础技术进行适当准备。关于分布式,我自身的实战经验也非常有限,专栏里就谈谈从理论出发的一些思考。
|
||||
|
||||
今天我要问你的问题是,谈谈常用的分布式ID的设计方案?Snowflake是否受冬令时切换影响?
|
||||
|
||||
## 典型回答
|
||||
|
||||
首先,我们需要明确通常的分布式ID定义,基本的要求包括:
|
||||
|
||||
<li>
|
||||
全局唯一,区别于单点系统的唯一,全局是要求分布式系统内唯一。
|
||||
</li>
|
||||
<li>
|
||||
有序性,通常都需要保证生成的ID是有序递增的。例如,在数据库存储等场景中,有序ID便于确定数据位置,往往更加高效。
|
||||
</li>
|
||||
|
||||
目前业界的方案很多,典型方案包括:
|
||||
|
||||
<li>
|
||||
基于数据库自增序列的实现。这种方式优缺点都非常明显,好处是简单易用,但是在扩展性和可靠性等方面存在局限性。
|
||||
</li>
|
||||
<li>
|
||||
基于Twitter早期开源的[Snowflake](https://github.com/twitter/snowflake)的实现,以及相关改动方案。这是目前应用相对比较广泛的一种方式,其结构定义你可以参考下面的示意图。
|
||||
</li>
|
||||
|
||||
<img src="https://static001.geekbang.org/resource/image/ff/ad/ffd41494a39ef737b3c1151929c3c4ad.png" alt="">
|
||||
|
||||
整体长度通常是64 (1 + 41 + 10+ 12 = 64)位,适合使用Java语言中的long类型来存储。
|
||||
|
||||
头部是1位的正负标识位。
|
||||
|
||||
紧跟着的高位部分包含41位时间戳,通常使用System.currentTimeMillis()。
|
||||
|
||||
后面是10位的WorkerID,标准定义是5位数据中心 + 5位机器ID,组成了机器编号,以区分不同的集群节点。
|
||||
|
||||
最后的12位就是单位毫秒内可生成的序列号数目的理论极限。
|
||||
|
||||
Snowflake的[官方版本](https://github.com/twitter/snowflake)是基于Scala语言,Java等其他语言的[参考实现](https://github.com/relops/snowflake)有很多,是一种非常简单实用的方式,具体位数的定义是可以根据分布式系统的真实场景进行修改的,并不一定要严格按照示意图中的设计。
|
||||
|
||||
<li>
|
||||
Redis、ZooKeeper、MongoDB等中间件,也都有各种唯一ID解决方案。其中一些设计也可以算作是Snowflake方案的变种。例如,MongoDB的[ObjectId](http://mongodb.github.io/node-mongodb-native/2.0/tutorials/objectid/)提供了一个12 byte(96位)的ID定义,其中32位用于记录以秒为单位的时间,机器ID则为24位,16位用作进程ID,24位随机起始的计数序列。
|
||||
</li>
|
||||
<li>
|
||||
国内的一些大厂开源了其自身的部分分布式ID实现,InfoQ就曾经介绍过微信的[seqsvr](http://www.infoq.com/cn/articles/wechat-serial-number-generator-architecture),它采取了相对复杂的两层架构,并根据社交应用的数据特点进行了针对性设计,具体请参考相关[代码实现](https://github.com/nebula-im/seqsvr)。另外,[百度](https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md)、美团等也都有开源或者分享了不同的分布式ID实现,都可以进行参考。
|
||||
</li>
|
||||
|
||||
关于第二个问题,**Snowflake是否受冬令时切换影响?**
|
||||
|
||||
我认为没有影响,你可以从Snowflake的具体算法实现寻找答案。我们知道Snowflake算法的Java实现,大都是依赖于System.currentTimeMillis(),这个数值代表什么呢?从Javadoc可以看出,它是返回当前时间和1970年1月1号UTC时间相差的毫秒数,这个数值与夏/冬令时并没有关系,所以并不受其影响。
|
||||
|
||||
## 考点分析
|
||||
|
||||
今天的问题不仅源自面试的热门考点,并且也存在着广泛的应用场景,我前面给出的回答只是一个比较精简的典型方案介绍。我建议你针对特定的方案进行深入分析,以保证在面试官可能会深入追问时能有充分准备;如果恰好在现有系统使用分布式ID,理解其设计细节是很有必要的。
|
||||
|
||||
涉及分布式,很多单机模式下的简单问题突然就变得复杂了,这是分布式天然的复杂性,需要从不同角度去理解适用场景、架构和细节算法,我会从下面的角度进行适当解读:
|
||||
|
||||
<li>
|
||||
我们的业务到底需要什么样的分布式ID,除了唯一和有序,还有哪些必须要考虑的要素?
|
||||
</li>
|
||||
<li>
|
||||
在实际场景中,针对典型的方案,有哪些可能的局限性或者问题,可以采取什么办法解决呢?
|
||||
</li>
|
||||
|
||||
## 知识扩展
|
||||
|
||||
如果试图深入回答这个问题,首先需要明确业务场景的需求要点,我们到底需要一个什么样的分布式ID?
|
||||
|
||||
除了唯一和有序,考虑到分布式系统的功能需要,通常还会额外希望分布式ID保证:
|
||||
|
||||
<li>
|
||||
有意义,或者说包含更多信息,例如时间、业务等信息。这一点和有序性要求存在一定关联,如果ID中包含时间,本身就能保证一定程度的有序,虽然并不能绝对保证。ID中包含额外信息,在分布式数据存储等场合中,有助于进一步优化数据访问的效率。
|
||||
</li>
|
||||
<li>
|
||||
高可用性,这是分布式系统的必然要求。前面谈到的方案中,有的是真正意义上的分布式,有得还是传统主从的思路,这一点没有绝对的对错,取决于我们业务对扩展性、性能等方面的要求。
|
||||
</li>
|
||||
<li>
|
||||
紧凑性,ID的大小可能受到实际应用的制约,例如数据库存储往往对长ID不友好,太长的ID会降低MySQL等数据库索引的性能;编程语言在处理时也可能受数据类型长度限制。
|
||||
</li>
|
||||
|
||||
在具体的生产环境中,还有可能提出对QPS等方面的具体要求,尤其是在国内一线互联网公司的业务规模下,更是需要考虑峰值业务场景的数量级层次需求。
|
||||
|
||||
第二,**主流方案的优缺点分析**。
|
||||
|
||||
对于数据库自增方案,除了实现简单,它生成的ID还能够保证固定步长的递增,使用很方便。
|
||||
|
||||
但是,因为每获取一个ID就会触发数据库的写请求,是一个代价高昂的操作,构建高扩展性、高性能解决方案比较复杂,性能上限明显,更不要谈扩容等场景的难度了。与此同时,保证数据库方案的高可用性也存在挑战,数据库可能发生宕机,即使采取主从热备等各种措施,也可能出现ID重复等问题。
|
||||
|
||||
实际大厂商往往是构建了多层的复合架构,例如美团公开的数据库方案[Leaf-Segment](https://tech.meituan.com/MT_Leaf.html),引入了起到缓存等作用的Leaf层,对数据库操作则是通过数据库中间件提供的批量操作,这样既能保证性能、扩展性,也能保证高可用。但是,这种方案对基础架构层面的要求很多,未必适合普通业务规模的需求。
|
||||
|
||||
与其相比,Snowflake方案的好处是算法简单,依赖也非常少,生成的序列可预测,性能也非常好,比如Twitter的峰值超过10万/s。
|
||||
|
||||
但是,它也存在一定的不足,例如:
|
||||
|
||||
- 时钟偏斜问题(Clock Skew)。我们知道普通的计算机系统时钟并不能保证长久的一致性,可能发生时钟回拨等问题,这就会导致时间戳不准确,进而产生重复ID。
|
||||
|
||||
针对这一点,Twitter曾经在文档中建议开启[NTP](http://doc.ntp.org/4.1.0/ntpd.htm),毕竟Snowflake对时间存在依赖,但是也有人提议关闭NTP。我个人认为还是应该开启NTP,只是可以考虑将stepback设置为0,以禁止回调。
|
||||
|
||||
从设计和具体编码的角度,还有一个很有效的措施就是缓存历史时间戳,然后在序列生成之前进行检验,如果出现当前时间落后于历史时间的不合理情况,可以采取相应的动作,要么重试、等待时钟重新一致,或者就直接提示服务不可用。
|
||||
|
||||
<li>
|
||||
另外,序列号的可预测性是把双刃剑,虽然简化了一些工程问题,但很多业务场景并不适合可预测的ID。如果你用它作为安全令牌之类,则是非常危险的,很容易被黑客猜测并利用。
|
||||
</li>
|
||||
<li>
|
||||
ID设计阶段需要谨慎考虑暴露出的信息。例如,[Erlang版本](https://github.com/boundary/flake)的flake实现基于MAC地址计算WorkerID,在安全敏感的领域往往是不可以这样使用的。
|
||||
</li>
|
||||
<li>
|
||||
从理论上来说,类似Snowflake的方案由于时间数据位数的限制,存在与[2038年问题](https://en.wikipedia.org/wiki/Year_2038_problem)相似的理论极限。虽然目前的系统设计考虑数十年后的问题还太早,但是理解这些可能的极限是有必要的,也许会成为面试的过程中的考察点。
|
||||
</li>
|
||||
|
||||
如果更加深入到时钟和分布式系统时序的问题,还有与分布式ID相关但又有所区别的问题,比如在分布式系统中,不同机器的时间很可能是不一致的,如何保证事件的有序性?Lamport在1978年的论文([Time, Clocks, and the Ording of Events in a Distributed System](https://amturing.acm.org/p558-lamport.pdf))中就有很深入的阐述,有兴趣的同学可以去查找相应的翻译和解读。
|
||||
|
||||
最后,我再补充一些当前分布式领域的面试热点,例如:
|
||||
|
||||
<li>
|
||||
分布式事务,包括其产生原因、业务背景、主流的解决方案等。
|
||||
</li>
|
||||
<li>
|
||||
理解[CAP](https://en.wikipedia.org/wiki/CAP_theorem)、[BASE](https://en.wikipedia.org/wiki/Eventual_consistency)等理论,懂得从最终一致性等角度来思考问题,理解[Paxos](https://en.wikipedia.org/wiki/Paxos_(computer_science))、[Raft](https://raft.github.io/)等一致性算法。
|
||||
</li>
|
||||
<li>
|
||||
理解典型的分布式锁实现,例如最常见的[Redis分布式锁](https://redis.io/topics/distlock)。
|
||||
</li>
|
||||
<li>
|
||||
负载均衡等分布式领域的典型算法,至少要了解主要方案的原理。
|
||||
</li>
|
||||
|
||||
这些方面目前都已经有相对比较深入的分析,尤其是来自于一线大厂的实践经验。另外,在[左耳听风专栏的“程序员练级攻略”](http://time.geekbang.org/column/48)里,提供了非常全面的分布式学习资料,感兴趣的同学可以参考。
|
||||
|
||||
今天我简要梳理了当前典型的分布式ID生成方案,并探讨了ID设计的一些考量,尤其是应用相对广泛的Snowflake的不足之处,希望对你有所帮助。
|
||||
|
||||
## 一课一练
|
||||
|
||||
关于今天我们讨论的题目你做到心中有数了吗?今天的思考题是,从理论上来看,Snowflake这种基于时间的算法,从形式上天然地限制了ID的并发生成数量,如果在极端情况下,短时间需要更多ID,有什么办法解决呢?
|
||||
|
||||
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
|
||||
|
||||
你的朋友是不是也在准备面试呢?你可以“请朋友读”,把今天的题目分享给好友,或许你能帮到他。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user