a. 更多的数据结构;
b. 可持久化;
c. 计数器;
d. 发布-订阅功能;
e. 事务功能;
f. 过期回调功能;
g. 队列功能;
h. 排序、聚合查询功能。
a. RDB:快照形式是直接把内存中的数据保存到⼀个 dump ⽂件中,定时保存,保存策略。(会丢数据)
b. AOF:把所有的对Redis的服务器进⾏修改的命令都存到⼀个⽂件⾥,命令的集合。(影响性能)
a. explain select语句;
b. 当只要⼀条数据时使⽤limit 1;
c. 为搜索字段建索引;
d. 避免select *;
e. 字段尽量使⽤not null;
f. 垂直分割;
g. 拆分⼤的delete和insert语句:delete和insert会锁表;
h. 分表分库分区。
1、虚拟机:我们传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有⾃⼰的操作系统,虚拟机⼀旦被开启,预分配
给他的资源将全部被占⽤。,每⼀个虚拟机包括应⽤,必要的⼆进制和库,以及⼀个完整的⽤户操作系统。
2、Docker:容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。
容器包含应⽤和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在⽤户空间以分离的进程运⾏。
3、对⽐:
1、这⾥同主机不同容器之间通信主要使⽤Docker桥接(Bridge)模式。
2、不同主机的容器之间的通信可以借助于 pipework 这个⼯具。
2、队列:
3. 说说rabbitmq的结构。
a. 消息处理过程:
b. 四种交换机:
i. 直连交换机,Direct exchange:带路由功能的交换机,根据routing_key(消息发送的时候需要指定)直接绑定到队列,
⼀个交换机也可以通过过个routing_key绑定多个队列。
ii. 扇形交换机,Fanout exchange:⼴播消息。
iii. 主题交换机,Topic exchange:发送到主题交换机上的消息需要携带指定规则的routing_key,主题交换机会根据这个规
则将数据发送到对应的(多个)队列上。
iv. ⾸部交换机,Headers exchange:⾸部交换机是忽略routing_key的⼀种路由⽅式。路由器和交换机路由的规则是通过
Headers信息来交换的,这个有点像HTTP的Headers。将⼀个交换机声明成⾸部交换机,绑定⼀个队列的时候,定义⼀
个Hash的数据结构,消息发送的时候,会携带⼀组hash数据结构的信息,当Hash的内容匹配上的时候,消息就会被写⼊队
列。
4. rabbitmq队列与消费者的关系?
a. ⼀个队列可以绑定多个消费者;
b. 消息分发:若该队列⾄少有⼀个消费者订阅,消息将以循环(round-robin)的⽅式发送给消费者。每条消息只会分发给⼀个
订阅的消费者(前提是消费者能够正常处理消息并进⾏确认)。
5. rabbitmq交换器种类。
a. 场景:
i. ⼤数据部⻔流数据处理;
ii. elk;
b. 特性:
它被设计为⼀个分布式系统,易于向外扩展;
它同时为发布和订阅提供⾼吞吐量;
它⽀持多订阅者,当失败时能⾃动平衡消费者;
它将消息持久化到磁盘,因此可⽤于批量消费,例如ETL,以及实时应⽤程序。
M.
springcloud由以下⼏个核⼼组件构成:
Eureka:各个服务启动时,Eureka Client都会将服务注册到Eureka Server,并且Eureka Client还可以反过来从Eureka Server拉
取注册表,从⽽知道其他服务在哪⾥
Ribbon:服务间发起请求的时候,基于Ribbon做负载均衡,从⼀个服务的多台机器中选择⼀台
Feign:基于Feign的动态代理机制,根据注解和选择的机器,拼接请求URL地址,发起请求
Hystrix:发起请求是通过Hystrix的线程池来⾛的,不同的服务⾛不同的线程池,实现了不同服务调⽤的隔离,避免了服务雪崩
的问题
Zuul:如果前端、移动端要调⽤后端系统,统⼀从Zuul⽹关进⼊,由Zuul⽹关转发请求给对应的服务
特性 ActiveMQ RabbitMQ RocketMQ kafka
开发语⾔ java erlang java scala
单机吞吐量 万级 万级 10万级 10万级
时效性 ms级 us级 ms级 ms级以内
可⽤性 ⾼(主从架构) ⾼(主从架构) ⾮常⾼(分布式架构) ⾮常⾼(分布式架构)
功能特性
成熟的产品,在很多公司
得到应⽤;有较多的⽂
档;各种协议⽀持较好
基于erlang开发,所以并
发能⼒很强,性能极其
好,延时很低;管理界⾯较
丰富
MQ功能⽐较完备,扩展
性佳
只⽀持主要的MQ功能,
像⼀些消息查询,消息回
溯等功能没有提供,毕竟
是为⼤数据准备的,在⼤
数据领域应⽤⼴。
a. 当⼀个服务实例启动,会将它的ip地址等信息注册到eureka;
b. 当a服务调⽤b服务,a服务会通过Ribbon检查本地是否有b服务实例信息的缓存;
c. Ribbon会定期从eureka刷新本地缓存。
a. 某个服务不可⽤时,各个Eureka Client不能及时的知道,需要1~3个⼼跳周期才能感知,但是,由于基于Netflix的服务调⽤端
都会使⽤Hystrix来容错和降级,当服务调⽤不可⽤时Hystrix也能及时感知到,通过熔断机制来降级服务调⽤,因此弥补了基于
客户端服务发现的时效性的缺点。
a. 第⼀层缓存:readOnlyCacheMap,本质上是ConcurrentHashMap:这是⼀个JVM的CurrentHashMap只读缓存,这个主要是
为了供客户端获取注册信息时使⽤,其缓存更新,依赖于定时器的更新,通过和readWriteCacheMap 的值做对⽐,如果数据不
⼀致,则以readWriteCacheMap 的数据为准。readOnlyCacheMap 缓存更新的定时器时间间隔,默认为30秒
b. 第⼆层缓存:readWriteCacheMap,本质上是Guava缓存:此处存放的是最终的缓存, 当服务下线,过期,注册,状态变
更,都会来清除这个缓存⾥⾯的数据。 然后通过CacheLoader进⾏缓存加载,在进⾏readWriteCacheMap.get(key)的时候,⾸
先看这个缓存⾥⾯有没有该数据,如果没有则通过CacheLoader的load⽅法去加载,加载成功之后将数据放⼊缓存,同时返回数
据。 readWriteCacheMap 缓存过期时间,默认为 180 秒 。
c. 缓存机制:设置了⼀个每30秒执⾏⼀次的定时任务,定时去服务端获取注册信息。获取之后,存⼊本地内存。
a. 区别:
传输协议
RPC,可以基于TCP协议,也可以基于HTTP协议
HTTP,基于HTTP协议
传输效率
RPC,使⽤⾃定义的TCP协议,可以让请求报⽂体积更⼩,或者使⽤HTTP2协议,也可以很好的减少报⽂的体积,提
⾼传输效率
HTTP,如果是基于HTTP1.1的协议,请求中会包含很多⽆⽤的内容,如果是基于HTTP2.0,那么简单的封装以下是可
以作为⼀个RPC来使⽤的,这时标准RPC框架更多的是服务治理
性能消耗,主要在于序列化和反序列化的耗时
RPC,可以基于thrift实现⾼效的⼆进制传输
HTTP,⼤部分是通过json来实现的,字节⼤⼩和序列化耗时都⽐thrift要更消耗性能
负载均衡
RPC,基本都⾃带了负载均衡策略
HTTP,需要配置Nginx,HAProxy来实现
服务治理(下游服务新增,重启,下线时如何不影响上游调⽤者)
RPC,能做到⾃动通知,不影响上游
HTTP,需要事先通知,修改Nginx/HAProxy配置
b. 总结:RPC主要⽤于公司内部的服务调⽤,性能消耗低,传输效率⾼,服务治理⽅便。HTTP主要⽤于对外的异构环境,浏览
器接⼝调⽤,APP接⼝调⽤,第三⽅接⼝调⽤等。
a. 优点:
i. 模块解耦:把模块拆分,使⽤接⼝通信,降低模块之间的耦合度.
ii. 项⽬拆分,不同团队负责不同的⼦项⽬:把项⽬拆分成若⼲个⼦项⽬,不同的团队负责不同的⼦项⽬.
iii. 提⾼项⽬扩展性:增加功能时只需要再增加⼀个⼦项⽬,调⽤其他系统的接⼝就可以。
iv. 分布式部署:可以灵活的进⾏分布式部署.
v. 提⾼代码的复⽤性:⽐如service层,如果不采⽤分布式rest服务⽅式架构就会在⼿机wap商城,微信商城,pc,android,ios每
个端都要写⼀个service层逻辑,开发量⼤,难以维护⼀起升级,这时候就可以采⽤分布式rest服务⽅式,公⽤⼀个service层。
b. 缺点:
i. 系统之间的交互要使⽤远程通信,接⼝开发增⼤⼯作量;
ii. ⽹络请求有延时;
iii. 事务处理⽐较麻烦,需要使⽤分布式事务。
4、jvm:
1、JVM中堆空间可以分成三个⼤区,新⽣代、⽼年代、永久代
2、新⽣代可以划分为三个区,Eden区,两个Survivor区,在HotSpot虚拟机Eden和Survivor的⼤⼩⽐例为8c1
@RestController :@ResponseBody和@Controller的合集。
@EnableAutoConfiguration :尝试根据你添加的jar依赖⾃动配置你的Spring应⽤。
@ComponentScan:表示将该类⾃动发现(扫描)并注册为Bean,可以⾃动收集所有的Spring组件,包括@Configuration类。
@ImportResource :⽤来加载xml配置⽂件。
@Configuration :相当于传统的xml配置⽂件,如果有些第三⽅库需要⽤到xml⽂件,建议仍然通过@Configuration类作为项⽬
的配置主类——可以使⽤@ImportResource注解加载xml配置⽂件。
@SpringBootApplication:相当于@EnableAutoConfiguration、@ComponentScan和@Configuration的合集。
1、数据结构:
以下是ConcurrentHashMap的类图:
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是⼀种可重⼊锁ReentrantLock,在
ConcurrentHashMap中扮演锁的⻆⾊,HashEntry则⽤于存储键值对数据。⼀个ConcurrentHashMap中包含⼀个Segment数组,
Segment的结构和HashMap类似,是⼀种数组和链表结构。⼀个Segment⾥⾯包含⼀个HashEntry数组,每个HashEntry是⼀个链表的元
素。每个Segment拥有⼀个锁,当对HashEntry数组的数据进⾏修改时,必须先获得对应的Segment锁,如图所示:
2、concurrenthashmap 1.7和1.8区分:
去除 Segment + HashEntry + Unsafe的实现,
改为 Synchronized + CAS + Node + Unsafe的实现
其实 Node 和 HashEntry 的内容⼀样,但是HashEntry是⼀个内部类。
⽤ Synchronized + CAS 代替 Segment ,这样锁的粒度更⼩了,并且不是每次都要加锁了,CAS尝试失败了在加锁。
put()⽅法中 初始化数组⼤⼩时,1.8不⽤加锁,因为⽤了个 sizeCtl变量,将这个变量置为-1,就表明table正在初始化。
详⻅:https://my.oschina.net/u/566591/blog/1548176
详⻅“⾯试题库/java基础”
当在对红⿊树进⾏插⼊和删除等操作时,对树做了修改可能会破坏红⿊树的性质。为了继续保持红⿊树的性质,可以通过对结点
进⾏重新着⾊,以及对树进⾏相关的旋转操作,即通过修改树中某些结点的颜⾊及指针结构,来达到对红⿊树进⾏插⼊或删除结点等操作
后继续保持它的性质或平衡的⽬的。
树的旋转分为左旋和右旋,下⾯借助图来介绍⼀下左旋和右旋这两种操作。
1.左旋
如上图所示,当在某个结点pivot上,做左旋操作时,我们假设它的右孩⼦y不是NIL[T],pivot可以为任何不是NIL[T]的左⼦结
点。左旋以pivot到Y之间的链为“⽀轴”进⾏,它使Y成为该⼦树的新根,⽽Y的左孩⼦b则成为pivot的右孩⼦。
1 LeftRoate(T, x) 2 y ← x.right //定义y:y是x的右孩⼦
3 x.right ← y.left //y的左孩⼦成为x的右孩⼦
4 if y.left ≠ T.nil
5 y.left.p ← x
6 y.p ← x.p //x的⽗结点成为y的⽗结点
7 if x.p = T.nil
8 then T.root ← y
9 else if x = x.p.left
10 then x.p.left ← y
11 else x.p.right ← y
12 y.left ← x //x作为y的左孩⼦
13 x.p ← y
2.右旋
右旋与左旋差不多,再此不做详细介绍。
树在经过左旋右旋之后,树的搜索性质保持不变,但树的红⿊性质则被破坏了,所以,红⿊树插⼊和删除数据后,需要利⽤旋转
与颜⾊重涂来重新恢复树的红⿊性质。
7、并发:
1.CountDownLatch:api解释:⼀个同步辅助类,在完成⼀组正在其他线程中执⾏的操作之前,它允许⼀个或多个线程⼀直等待。
个⼈理解是CountDownLatch让可以让⼀组线程同时执⾏,然后在这组线程全部执⾏完前,可以让另⼀个线程等待。
2.ReentrantLock:可重⼊互斥锁
3.Condition:此类是同步的条件对象,每个Condition实例绑定到⼀个ReetrantLock中,以便争⽤同⼀个锁的多线程之间可以通
过Condition的状态来获取通知。
注意:使⽤Condition前,⾸先要获得ReentantLock,当条件不满⾜线程1等待时,ReentrantLock会被释放,以能让其他线程争
⽤,其他线程获得reentrantLock,然后满⾜条件,唤醒线程1继续执⾏。
1、Zookeeper:基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下(该图来⾃于IBM⽹站)。⼤致思想即为:每个客户端对
某个功能加锁时,在zookeeper上的与该功能对应的指定节点的⽬录下,⽣成⼀个唯⼀的瞬时有序节点。判断是否获取锁的⽅式很简单,只需要判断
有序节点中序号最⼩的⼀个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁⽆法释放,⽽产⽣的死锁问题。
2、优点
锁安全性⾼,zk可持久化,且能实时监听获取锁的客户端状态。⼀旦客户端宕机,则瞬时节点随之消失,zk因⽽能第⼀时间释放
锁。这也省去了⽤分布式缓存实现锁的过程中需要加⼊超时时间判断的这⼀逻辑。
3、缺点
性能开销⽐较⾼。因为其需要动态产⽣、销毁瞬时节点来实现锁功能。所以不太适合直接提供给⾼并发的场景使⽤。
4、实现
可以直接采⽤zookeeper第三⽅库curator即可⽅便地实现分布式锁。
5、适⽤场景
对可靠性要求⾮常⾼,且并发程度不⾼的场景下使⽤。如核⼼数据的定时全量/增量同步等。
2、memcached:memcached带有add函数,利⽤add函数的特性即可实现分布式锁。add和set的区别在于:如果多线程并发set,则每
个set都会成功,但最后存储的值以最后的set的线程为准。⽽add的话则相反,add会添加第⼀个到达的值,并返回true,后续的添加则都会返回
false。利⽤该点即可很轻松地实现分布式锁。
2、优点
并发⾼效
3、缺点
memcached采⽤列⼊LRU置换策略,所以如果内存不够,可能导致缓存中的锁信息丢失。
memcached⽆法持久化,⼀旦重启,将导致信息丢失。
4、使⽤场景
⾼并发场景。需要 1)加上超时时间避免死锁; 2)提供⾜够⽀撑锁服务的内存空间; 3)稳定的集群化管理。
3、redis:redis分布式锁即可以结合zk分布式锁锁⾼度安全和memcached并发场景下效率很好的优点,其实现⽅式和memcached类
似,采⽤setnx即可实现。需要注意的是,这⾥的redis也需要设置超时时间。以避免死锁。可以利⽤jedis客户端实现。
1 ICacheKey cacheKey = new ConcurrentCacheKey(key, type); 2 return RedisDao.setnx(cacheKey, “1”); 8、线程池:
JDK7 提供了 7 个阻塞队列。分别是
ArrayBlockingQueue :⼀个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :⼀个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue :⼀个⽀持优先级排序的⽆界阻塞队列。
DelayQueue:⼀个使⽤优先级队列实现的⽆界阻塞队列。
SynchronousQueue:⼀个不存储元素的阻塞队列。
LinkedTransferQueue:⼀个由链表结构组成的⽆界阻塞队列。
LinkedBlockingDeque:⼀个由链表结构组成的双向阻塞队列。
看Executors源码可知:newFixedThreadPool使⽤了LinkedBlockingQueue。 1 public static ExecutorService newFixedThreadPool(int var0) { 2 return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); 3 } 9、数据库:
a. InnoDB:
1、选择最合适的字段属性:类型、⻓度、是否允许NULL等;尽量把字段设为not null,⼀⾯查询时对⽐是否为null;
2.要尽量避免全表扫描,⾸先应考虑在 where 及 order by 涉及的列上建⽴索引。
3.应尽量避免在 where ⼦句中对字段进⾏ null 值判断、使⽤!= 或 <> 操作符,否则将导致引擎放弃使⽤索引⽽进⾏全表扫描
4.应尽量避免在 where ⼦句中使⽤ or 来连接条件,如果⼀个字段有索引,⼀个字段没有索引,将导致引擎放弃使⽤索引⽽进⾏全
表扫描
5.in 和 not in 也要慎⽤,否则会导致全表扫描
6.模糊查询也将导致全表扫描,若要提⾼效率,可以考虑字段建⽴前置索引或⽤全⽂检索;
7.如果在 where ⼦句中使⽤参数,也会导致全表扫描。因为SQL只有在运⾏时才会解析局部变量,但优化程序不能将访问计划的选择
推迟到运⾏时;它必须在编译时进⾏选择。然 ⽽,如果在编译时建⽴访问计划,变量的值还是未知的,因⽽⽆法作为索引选择的输⼊项。
9.应尽量避免在where⼦句中对字段进⾏函数操作,这将导致引擎放弃使⽤索引⽽进⾏全表扫描。
10.不要在 where ⼦句中的“=”左边进⾏函数、算术运算或其他表达式运算,否则系统将可能⽆法正确使⽤索引。
11.在使⽤索引字段作为条件时,如果该索引是复合索引,那么必须使⽤到该索引中的第⼀个字段作为条件时才能保证系统使⽤该索
引,否则该索引将不会被使⽤,并且应尽可能的让字段顺序与索引顺序相⼀致。
12.不要写⼀些没有意义的查询,如需要⽣成⼀个空表结构:
13.Update 语句,如果只更改1、2个字段,不要Update全部字段,否则频繁调⽤会引起明显的性能消耗,同时带来⼤量⽇志。
14.对于多张⼤数据量(这⾥⼏百条就算⼤了)的表JOIN,要先分⻚再JOIN,否则逻辑读会很⾼,性能很差。
15.select count() from table;这样不带任何条件的count会引起全表扫描,并且没有任何业务意义,是⼀定要杜绝的。
16.索引并不是越多越好,索引固然可以提⾼相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert
或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况⽽定。⼀个表的索引数最好不要超过6个,若太多则应考虑⼀些不常使
⽤到的列上建的索引是否有 必要。
17.应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,⼀旦该列值改
变将导致整个表记录的顺序的调整,会耗费相当⼤的资源。若应⽤系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为
clustered 索引。
18.尽量使⽤数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为
引擎在处理查询和连 接时会逐个⽐较字符串中每⼀个字符,⽽对于数字型⽽⾔只需要⽐较⼀次就够了。
19.尽可能的使⽤ varchar/nvarchar 代替 char/nchar ,因为⾸先变⻓字段存储空间⼩,可以节省存储空间,其次对于查询来
说,在⼀个相对较⼩的字段内搜索效率显然要⾼些。
20.任何地⽅都不要使⽤ select * from t ,⽤具体的字段列表代替“”,不要返回⽤不到的任何字段。
21.尽量使⽤表变量来代替临时表。如果表变量包含⼤量数据,请注意索引⾮常有限(只有主键索引)。
22. 避免频繁创建和删除临时表,以减少系统表资源的消耗。临时表并不是不可使⽤,适当地使⽤它们可以使某些例程更有效,例如,
当需要重复引⽤⼤型表或常⽤表中的某个数据集时。但是,对于⼀次性事件, 最好使⽤导出表。
23.在新建临时表时,如果⼀次性插⼊数据量很⼤,那么可以使⽤ select into 代替 create table,避免造成⼤量 log ,以提
⾼速度;如果数据量不⼤,为了缓和系统表的资源,应先create table,然后insert。
24.如果使⽤到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可
以避免系统表的较⻓时间锁定。
25.尽量避免使⽤游标,因为游标的效率较差,如果游标操作的数据超过1万⾏,那么就应该考虑改写。
26.使⽤基于游标的⽅法或临时表⽅法之前,应先寻找基于集的解决⽅案来解决问题,基于集的⽅法通常更有效。
27.与临时表⼀样,游标并不是不可使⽤。对⼩型数据集使⽤ FAST_FORWARD 游标通常要优于其他逐⾏处理⽅法,尤其是在必须引⽤
⼏个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要⽐使⽤游标执⾏的速度快。如果开发时 间允许,基于游标的⽅法和基于集的⽅法
都可以尝试⼀下,看哪⼀种⽅法的效果更好。
28.在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。⽆需在执⾏存储过程和触
发器的每个语句后向客户端发送 DONE_IN_PROC 消息。
29.尽量避免⼤事务操作,提⾼系统并发能⼒。
30.尽量避免向客户端返回⼤数据量,若数据量过⼤,应该考虑相应需求是否合理。
1)原⼦性(Atomic):事务中各项操作,要么全做要么全不做,任何⼀项操作的失败都会导致整个事务的失败;
2)⼀致性(Consistent):事务结束后系统状态是⼀致的;
3)隔离性(Isolated):并发执⾏的事务彼此⽆法看到对⽅的中间状态;
4)持久性(Durable):事务完成后所做的改动都会被持久化,即使发⽣灾难性的失败。通过⽇志和同步备份可以在故障发⽣后重建数
据。
redis cluster基于smart client和⽆中⼼的设计,client必须按key的哈希将请求直接发送到对应的节点。这意味着:使⽤官⽅
cluster必须要等对应语⾔的redis driver对cluster⽀持的开发和不断成熟;client不能直接像单机⼀样使⽤pipeline来提⾼效率,想同时执
⾏多个请求来提速必须在client端⾃⾏实现异步逻辑。 ⽽codis因其有中⼼节点、基于proxy的设计,对client来说可以像对单机redis⼀样
去操作proxy(除了⼀些命令不⽀持),还可以继续使⽤pipeline并且如果后台redis有多个的话速度会显著快于单redis的pipeline。同时
codis使⽤zookeeper来作为辅助,这意味着单纯对于redis集群来说需要额外的机器搭zk,不过对于很多已经在其他服务上⽤了zk的公司
来说这不是问题)
10、设计:
41. 要缓存⽹站登录的⽤户信息,你有⼏种⽅式?
cookie+session、cookie+redis。
42. 让你设计⼀套分布式缓存,如何设计可以同时更新所有服务器的缓存?
43. 说说你在⼯作中遇到的困难或者挑战。