暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基础概念及问答(四)

萌小璐 2021-12-14
139
1: 您使用了哪些starter maven依赖项
使用了下面的一些依赖项:
spring-boot-starter-activemq
spring-boot-starter-security
这有助于增加更少的依赖关系,并减少版本的冲突。
  • Spring Boot中starter到底是什么?

首先,这个Starter并非什么新的技术点,基本上还是基于Spring已有功能来实现的。首先它提供了一个自动化配置类,一般命名为XXXAutoConfiguration,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。当然,开发者也可以自定义 Starter
  • spring-boot-starter-parent有什么用?

我们都知道,新创建一个Spring Boot项目,默认都是有parent的,这个parent就是spring-boot-starter-parent ,spring-boot-starter-parent 主要有如下作用:
  1. 定义了Java编译版本为1.8 。
  2. 使用UTF-8格式编码。
  3. 继承自spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
  4. 执行打包操作的配置。
  5. 自动化的资源过滤。
  6. 自动化的插件配置。
  7. 针对application.properties和application.yml的资源过滤,包括通过 profile定义的不同环境的配置文件,例如applicationdev.properties和 application-dev.yml。
  • Spring Boot打成的jar和普通的jar有什么区别 ?

Spring Boot项目终打包成的jar是可执行jar ,这种jar可以直接通过java jar xxx.jar命令来运行,这种jar不可以作为普通的jar被其他项目依赖,即使依赖了也无法使用其中的类。Spring Boot的jar无法被其他项目依赖,主要还是他和普通jar的结构不同。普通的jar包,解压后直接就是包名,包里就是我们的代码,而Spring Boot打包成的可执行jar解压后,在\BOOT-INF\classes目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在pom.xml 文件中增加配置,将Spring Boot项目打包成两个jar ,一个可执行,一个可引用。

2: Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面
虽然Mybatis解析Xml映射文件是按照顺序解析的,但是,被引用的B标签依然可以定义在任何地方,Mybatis都可以正确识别。
原理是:Mybatis解析A标签,发现A标签引用了B标签,但是B标签尚未解析到,尚不存在,此时,Mybatis会将A标签标记为未解析状态,然后继续解析余下的标签,包含B标签,待所有标签解析完毕,Mybatis会重新解析那些被标记为未解析的标签,此时再解析A标签时,B标签已经存在,A标签也就可以正常解析完成了。

3: Spring 框架中用到了哪些设计模式
代理模式—在AOP和remoting中被用的比较多。
单例模式—在spring配置文件中定义的bean默认为单例模式。
模板方法—用来解决代码重复的问题。比如RestTemplate, JmsTemplate, JpaTemplate。
前端控制器—Spring提供了DispatcherServlet来对请求进行分发。
视图帮助(View Helper)—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。
依赖注入—贯穿于BeanFactory/ApplicationContext接口的核心理念。
工厂模式—BeanFactory用来创建对象的实例。

4: Mybatis是否支持延迟加载?如果支持,它的实现原理是什么
1)Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法, 比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。当然了,不光是Mybatis,几乎所有的包括Hibernate,支持延迟加载的原理都是一样的。 
2)#{}和${}的区别
#{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
Mybatis在处理${}时,是原值传入,就是把{}替换成变量的值,相当于JDBC中的Statement编译。
变量替换后,#{}对应的变量自动加上单引号 ‘’;变量替换后,${}对应的变量不会加上单引号 ‘’。
#{}可以有效的防止SQL注入,提高系统安全性;${}不能防止SQL注入。
#{}的变量替换是在DBMS中;${}的变量替换是在DBMS外。
3)模糊查询like语句该怎么写
(1)’%${question}%’ 可能引起SQL注入,不推荐
(2)"%"#{question}"%" 注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’‘,不然会查不到任何结果。
(3)CONCAT(’%’,#{question},’%’) 使用CONCAT()函数,推荐。
(4)使用bind标签
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password from person 
where username LIKE #{pattern}
</select>
4)在mapper中如何传递多个参数
方法1:顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>
#{}里面的数字代表传入参数的顺序。
这种方法不建议使用,sql层表达不直观,且一旦顺序调整容易出错。
方法2:@Param注解传参法
public User selectUser(@Param("userName") String name,@Param("deptId"int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是注解@Param括号里面修饰的名称。
这种方法在参数不多的情况还是比较直观的,推荐使用。
方法3:Map传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是Map里面的key名称。
这种方法适合传递多个参数,且参数易变能灵活传递的情况。
方法4:Java Bean传参法
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是User类里面的成员属性。
这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑 处理方便,推荐使用。

5: 使用MyBatis的mapper接口调用时有哪些要求
1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
4、Mapper.xml文件中的namespace即是mapper接口的类路径。

6: 如何应对Hot Key和Big Key引发的问题

Hot key

  • 问题描述:

    对于大多数互联网系统,数据是分冷热的。在突发事件发生时,大量用户同时去访问突发热点信息( Hot key ),这个突发热点信息所在的缓存节点就很容易出现过载和卡顿现象,甚至会被Crash。

  • 业务场景:

    明星结婚、离婚、出轨这种特殊突发事件,奥运、春节这些重大活动或节日,秒杀、双12、618 等线上促销活动。

  • 解决方案:

    1.利用大数据找出Hot key,将这些Hotkey进行分散处理,比如一个Hot key名字叫hotkey,可以被分散为hotkey#1、hotkey#2、hotkey#3,……hotkey#n,这n个key分散存在多个缓存节点,然后 client端请求时,随机访问其中某个后缀的hotkey,这样就可以把热key 的请求打散,避免一个缓存节点过载。

    2. key的名字不变,对缓存提前进行多副本+多级结合的缓存架构设计。

    3. 如果热key较多,还可以通过监控体系对缓存的SLA实时监控,通过快速扩容来减少Hot key的冲击。

    4. 业务端还可以使用本地缓存,将这些Hot key记录在本地缓存,来减少对远程缓存的冲击。

Big key

  • 问题描述:

    在缓存访问时,部分Key的Value过大,读写、加载易超时的现象。

  • 业务场景:

    互联网系统中需要保存用户最新1万个粉丝的业务,一个用户个人信息缓存,包括基本资料、关系图谱计数、发feed统计等,微博用户发表1千字甚至更长的微博内容。

  • 解决方案

    1.如果数据存在memcached中,可以设计一个缓存阀值,当value的长度超过阀值,则对内容启用压缩。

    2.如果数据存在Redis中,对Big key拆分,如下big list:list1、list2、...listN;big hash:可以做二次的hash,例如hash%100;日期:key20190320、key20190321、key_201903223。对Big key设置较长的过期时间,缓存内部在淘汰key时,同等条件下,尽量不淘汰这些大 key。


7: 运行Spring Boot有哪几种方式
1) 打包用命令或者放到容器中运行
2)用Maven/Gradle插件运行
3)直接执行main方法运行

8: Spring Boot自动配置原理是什么
注解@EnableAutoConfiguration, @Configuration, @ConditionalOnClass就是自动配置的核心,@EnableAutoConfiguration给容器导入META-INF/spring.factories里定义的自动配置类。筛选有效的自动配置类。每一个自动配置类结合对应的xxxProperties.java读取配置文件进行自动配置功能。

9: JDK8中的ConcurrentHashMap有一个CounterCell,你是如何理解的
CounterCell是JDK8中用来统计ConcurrentHashMap中所有元素个数的,在统计ConcurentHashMap时,不能直接对ConcurrentHashMap对象进行加锁然后再去统计,因为这样会影响ConcurrentHashMap的put等操作的效率,在JDK8的实现中使用CounterCell+baseCount来辅助进行统计,baseCount是ConcurrentHashMap中的一个属性,某个线程在调用ConcurrentHashMap对象的put操作时,会先通过CAS去修改baseCount的值,如果CAS修改成功,就计数成功,如果CAS修改失败,则会从CounterCell数组中随机选出一个CounterCell对象,然后利用CAS去修改CounterCell对象中的值,因为存在CounterCell数组,所以,当某个线程想要计数时,先尝试通过CAS去修改baseCount的值,如果没有修改成功,则从CounterCell数组中随机取出来一个CounterCell对象进行CAS计数,这样在计数时提高了效率。
所以ConcurrentHashMap在统计元素个数时,就是baseCount加上所有CountCeller中的value值,所得的和就是所有的元素个数。

10: Mapper编写有哪几种方式
第一种:接口实现类继承SqlSessionDaoSupport:
使用此种方法需要编写mapper接口,mapper接口实现类、mapper.xml文件。
(1)在sqlMapConfig.xml中配置mapper.xml的位置
<mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>
(2)定义mapper接口
(3)实现类继承SqlSessionDaoSupport
mapper方法中可以this.getSqlSession()进行数据增删改查。
(4)spring配置
<bean id=" " class="mapper接口的实现">
<property name="sqlSessionFactory" ref="sqlSessionFactory">
</property>
</bean>
第二种:使用org.mybatis.spring.mapper.MapperFactoryBean:
(1)在sqlMapConfig.xml中配置mapper.xml位置,如果mapper.xml和mappre接口的名称相同且在同一个目录,这里可以不用配置
<mappers>
<mapper resource="mapper.xml 文件的地址" />
<mapper resource="mapper.xml 文件的地址" />
</mappers>
(2)定义mapper 接口:
(3)mapper.xml中的namespace为mapper接口的地址
(4)mapper接口中的方法名和mapper.xml中定义的statement的id保持一致
(5)Spring 中定义
<bean id="" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="mapper接口地址" />
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
第三种:使用mapper扫描器:
(1)mapper.xml文件编写:
mapper.xml中的namespace为mapper接口的地址;
mapper接口中的方法名和mapper.xml中的定义的statement的id保持一致;
如果将mapper.xml和mapper接口的名称保持一致则不用在sqlMapConfig.xml中进行配置。
(2)定义mapper接口:
注意mapper.xml的文件名和mapper的接口名称保持一致,且放在同一个目录。
 (3)配置mapper扫描器:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="mapper接口包地址"></property>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
(4)使用扫描器后从spring容器中获取mapper的实现对象

11: 为什么HashMap的数组的大小是2的幂次方数
JDK7的HashMap是数组+链表实现的;
JDK8的HashMap是数组+链表+红黑树实现的;
当某个key-value对需要存储到数组中时,需要先生成一个数组下标index,并且这个index不能越界。
在HashMap中,先得到key的hashcode,hashcode是一个数字,然后通过 hashcode & (table.length - 1) 运算得到一个数组下标index,是通过与运算计算出来一个数组下标的,而不是通过取余,与运算相比于取余运算速度更快,但是也有一个前提条件,就是数组的长度得是一个2的幂次方数。

12: 开启Spring Boot特性有哪几种方式
1) 继承spring-boot-starter-parent项目
2) 导入spring-boot-dependencies项目依赖

13: 红黑树的定义
红黑树是一种二叉查找树,但在每个结点上增加了一个存储位表示结点的颜色, 可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的。当二叉查找树的高度较低时,这些操作执行的比较快,但是当树的高度较高时, 这些操作的性能可能不比用链表好。红黑树(red-black tree)是一种平衡的二叉查找树,它能保证在坏情况下,基本的动态操作集合运行时间为O(lgn)。红黑树必须要满足的五条性质:
  • 性质一:节点是红色或者是黑色;在树里面的节点不是红色的就是黑色的,没有其他颜色。
  • 性质二:根节点是黑色;根节点总是黑色的。它不能为红色。
  • 性质三:每个叶节点(NIL或空节点)是黑色;
  • 性质四:每个红色节点的两个子节点都是黑色的(也就是说不存在两个连续的红色节点);就是连续的两个节点不能是连续的红色,连续的两个节点的意思就是父节点与子节点不能是连续的红色。
  • 性质五:从任一节点到其每个叶节点的所有路径都包含相同数目的黑色节点。从根节点到每一个NIL节点的路径中,都包含了相同数量的黑色节点。
红黑树的应用场景:红黑树是一种不是非常严格的平衡二叉树,没有AVLtree那么严格的平衡要求,所以它的平均查找,增添删除效率都还不错。广泛用在C++的STL中。如map和set都是用红黑树实现的。

14: 简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系
Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。<resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个<select>、<insert>、<update>、 <delete>标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql 对象。

15: JDK8中的ConcurrentHashMap为什么使用synchronized来进行加锁
JDK8中使用synchronized加锁时,是对链表头结点和红黑树根结点来加锁的,而ConcurrentHashMap会保证,数组中某个位置的元素一定是链表的头结点或红黑树的根结点,所以JDK8中的ConcurrentHashMap在对某个桶进行并发安全控制时,只需要使用synchronized对当前那个位置的数组上的元素进行加锁即可,对于每个桶,只有获取到了第一个元素上的锁,才能操作这个桶,不管这个桶是一个链表还是红黑树。
相比于JDK7中使用ReentrantLock来加锁,因为JDK7中使用了分段锁,所以对于一个ConcurrentHashMap对象而言,分了几段就得有几个ReentrantLock对象,表示得有对应的几把锁。
而JDK8中使用synchronized关键字来加锁就会更节省内存,并且jdk也已经对synchronized的底层工作机制进行了优化,效率更好。

16: Spring中的@Transactional注解的工作原理是怎样的
在使用Spring框架时,可以有两种使用事务的方式,一种是编程式的,一种是申明式的,@Transactional注解就是申明式的。
首先,事务这个概念是数据库层面的,Spring只是基于数据库中的事务进行了扩展,以及提供了一些能让程序员更加方便操作事务的方式。
比如我们可以通过在某个方法上增加@Transactional注解,就可以开启事务,这个方法中所有的sql都会在一个事务中执行,统一成功或失败。
在一个方法上加了@Transactional注解后,Spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当在使用这个代理对象的方法时,如果这个方法上存在@Transactional注解,那么代理逻辑会先把事务的自动提交设置为false,然后再去执行原本的业务逻辑方法,如果执行业务逻辑方法没有出现异常,那么代理逻辑中就会将事务进行提交,如果执行业务逻辑方法出现了异常,那么则会将事务进行回滚。当然,针对哪些异常回滚事务是可以配置的,可以利用@Transactional注解中的rollbackFor属性进行配置,默认情况下会对RuntimeException和Error进行回滚。

17: HashMap扩容流程是怎样的
HashMap的扩容指的就是数组的扩容, 因为数组占用的是连续内存空间,所以数组的扩容其实只能新开一个新的数组,然后把老数组上的元素转移到新数组上来,这样才是数组的扩容;
在HashMap中也是一样,先新建一个2倍数组大小的数组,然后遍历老数组上的每一个位置,如果这个位置上是一个链表,就把这个链表上的元素转移到新数组上去;在这个过程中就需要遍历链表,当然jdk7,和jdk8在这个实现时是有不一样的,jdk7就是简单的遍历链表上的每一个元素,然后按每个元素的hashcode结合新数组的长度重新计算得出一个下标,而重新得到的这个数组下标很可能和之前的数组下标是不一样的,这样子就达到了一种效果,就是扩容之后,某个链表会变短,这也就达到了扩容的目的,缩短链表长度,提高了查询效率;
而在jdk8中,因为涉及到红黑树,这个其实比较复杂,jdk8中其实还会用到一个双向链表来维护红黑树中的元素,所以jdk8中在转移某个位置上的元素时,会去判断如果这个位置是一个红黑树,那么会遍历该位置的双向链表,遍历双向链表统计哪些元素在扩容完之后还是原位置,哪些元素在扩容之后在新位置,这样遍历完双向链表后,就会得到两个子链表,一个放在原下标位置,一个放在新下标位置,如果原下标位置或新下标位置没有元素,则红黑树不用拆分,否则判断这两个子链表的长度,如果超过八,则转成红黑树放到对应的位置,否则把单向链表放到对应的位置。元素转移完了之后,在把新数组对象赋值给HashMap的table属性,老数组会被回收掉。

18: Spring Boot是否可以使用XML配置
1 Spring Boot推荐使用Java配置而非XML配置,但是Spring Boot中也可以使用 XML配置,通过@ImportResource注解可以引入一个XML配置。
2 spring boot核心配置文件是什么?
bootstrap.properties和application.properties有何区别 ?
单纯做Spring Boot开发,可能不太容易遇到bootstrap.properties配置文件,但是在结合Spring Cloud时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。
spring boot核心的两个配置文件:
  • bootstrap (. yml 或者 . properties):由父ApplicationContext 加载的,比applicaton优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在Spring Cloud Config或者Nacos中会用到它。且 boostrap里面的属性不能被覆盖;

  • application (. yml 或者 . properties):由ApplicatonContext加载,用于 spring boot项目的自动化配置。


19: 最佳实践中,通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗
Dao接口,就是人们常说的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法内的参数,就是传递给sql的参数。Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MappedStatement,举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面id = findStudentById的MappedStatement。在Mybatis中,每一个<select>、<insert>、<update>、<delete>标签,都会被解析为 一个MappedStatement对象。
Dao接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

20: 你如何理解Spring Boot配置加载顺序
在Spring Boot里面,可以使用以下几种方式来加载配置。
1) properties文件;
2) YAML文件;
3) 系统环境变量;
等等……

21:rdd怎么区分宽依赖和窄依赖

宽依赖:父RDD的分区被子RDD的多个分区使用sortByKey等操作会产生宽依赖,会产生 shuffle;例如 groupByKey、reduceByKey;

窄依赖:父RDD的每个分区都只被子RDD的一个分区使用;例如 map、filter、union等操作会产生窄依赖。


22:spark streaming读取kafka数据的两种方式

这两种方式分别是:

1 Receiver-base

使用Kafka的高层次Consumer API来实现。receiver从Kafka中获取的数据都存储在Spark Executor的内存中,然后Spark Streaming启动的job会去处理那些数据。然而,在默认的配置下,这种方式可能会因为底层的失败而丢失数据。如果要启用高可靠机制,让数据零丢失,就必须启用Spark Streaming 的预写日志机制(Write Ahead Log,WAL)。该机制会同步地将接收到的Kafka数据写入分布式文件系统(比如 HDFS)上的预写日志中。所以,即使底层节点出现了失败,也可以使用预写日志中的数据进行恢复。

2 Direct

Spark1.3中引入Direct方式,用来替代掉使用Receiver接收数据,这种方式会周期性地查询Kafka,获得每个topic+partition的最新的offset,从而定义每个batch的offset的范围。当处理数据的job启动时,就会使用Kafka的简单 consumer api来获取Kafka指定offset范围的数据。

3 Kafka最核心的思想是使用磁盘,而不是使用内存,可能所有人都会认为,内存的速度一定比磁盘快,我也不例外。在看了Kafka 的设计思想,查阅了相应资料再加上自己的测试后, 发现磁盘的顺序读写速度和内存持平。

而且Linux对于磁盘的读写优化也比较多,包括read-ahead和write-behind,磁盘缓存等。如果在内存做这些操作的时候,一个是JAVA对象的内存开销很大,另一个是随着堆内存数据的增多,JAVA的GC时间会变得很长,使用磁盘操作有以下几个好处:

  • 磁盘缓存由Linux系统维护,减少了程序员的不少工作。磁盘顺序读写速度超过内存随机读写。

  • JVM的GC效率低,内存占用大。使用磁盘可以避免这一问题。系统冷启动后,磁盘缓存依然可用。


23:怎么解决 kafka 的数据丢失

  • producer端:

宏观上看保证数据的可靠安全性,肯定是依据分区数做好数据备份,设立副本数。

  • broker端:

topic设置多分区,分区自适应所在机器,为了让各分区均匀分布在所在的 broker中,分区数要大于broker数。分区是kafka进行并行读写的单位,是提升kafka速度的关键。

  • Consumer端

consumer端丢失消息的情形比较简单:如果在消息处理完成前就提交了offset,那么就有可能造成数据的丢失。由于Kafka consumer默认是自动提交位移的,所以在后台提交位移前一定要保证消息被正常处理了,因此不建议采用很重的处理逻辑,如果处理耗时很长, 则建议把逻辑放到另一个线程中去做。为了避免数据丢失,现给出两点建议: 

1)enable.auto.commit=false 关闭自动提交位移

2)在消息被完整处理之后再手动提交位移


24:fsimage和edit的区别?

大家都知道namenode与secondary namenode的关系,当他们要进行数据同步时叫做checkpoint时就用到了fsimage与edit,fsimage是保存最新的元数据的信息,当fsimage数据到一定的大小是会去生成一个新的文件来保存元数据的信息,这个新的文件就是edit,edit会回滚最新的数据。


25:列举几个配置文件优化?

1)Core-site.xml文件的优化

a、fs.trash.interval,默认值: 0;说明: 这个是开启hdfs文件删除自动转移到垃圾箱的选项,值为垃圾箱文件清除时间。一般开启这个会比较好,以防错误删除重要文件。单位是分钟。

b、dfs.namenode.handler.count,默认值:10;说明:hadoop系统里启动的任务线程数,这里改为40,同样可以尝试该值大小对效率的影响变化进行最合适的值的设定。

c、mapreduce.tasktracker.http.threads,默认值:40;说明:map和reduce是通过http进行数据传输的,这个是设置传输的并行线程数。


26:datanode首次加入cluster的时候,如果log报告不兼容文件版本,那需要namenode执行格式化操作,这样处理的原因是?

1)这样处理是不合理的,因为那么namenode格式化操作,是对文件系统进行格式化, namenode格式化时清空dfs/name两个目录下的所有文件,之后,会在目录dfs.name.dir下创建文件。

2)文本不兼容,有可能是namenode与datanode的数据里的namespaceID、 clusterID不一致,找到两个ID 位置,修改为一样即可解决。


27:MapReduce中排序发生在哪几个阶段?这些排序是否可以避免?为什么?

1)一个MapReduce作业由Map阶段和Reduce阶段两部分组成,这两阶段会对数据排序,从这个意义上说,MapReduce框架本质就是一个Distributed Sort。

2)在Map阶段,Map Task会在本地磁盘输出一个按照key排序(采用的是快速排序)的文件(中间可能产生多个文件,但最终会合并成一个),在Reduce阶段,每个Reduce Task会对收到的数据排序,这样,数据便按照Key分成了若干组,之后以组为单位交给reduce()处理。

3)很多人的误解在Map阶段,如果不使用Combiner便不会排序,这是错误的,不管你用不用Combiner,Map Task均会对产生的数据排序(如果没有 Reduce Task,则不会排序,实际上Map阶段的排序就是为了减轻Reduce端排序负载)。

4)由于这些排序是MapReduce自动完成的,用户无法控制,因此,在 hadoop 1.x中无法避免,也不可以关闭,但hadoop2.x是可以关闭的。


28:hadoop 的优化?

1)优化的思路:

可以从配置文件和系统以及代码的设计思路来优化 ;

2)配置文件的优化:

调节适当的参数,在调参数时要进行测试 ;

3)代码的优化:

combiner的个数尽量与reduce的个数相同,数据的类型保持一致,可以减少拆包与封包的进度;

4)系统的优化:

可以设置linux系统打开最大的文件数预计网络的带宽MTU的配置 ;

5)为job添加一个Combiner,可以大大的减少shuffer阶段的maoTask拷贝过来给远程的reduce task的数据量,一般而言combiner与reduce相同。

6)在开发中尽量使用stringBuffer而不string,string 的模式是read-only的,如果对它进行修改,会产生临时的对象,而stringBuffer是可修改的,不会产生临时对象。

7)修改一下配置:

以下是修改mapred-site.xml 文件;

a、修改最大槽位数:槽位数是在各个tasktracker上的mapred-site.xml上设置的, 默认都是2;

<property>
<name>mapred.tasktracker.map.tasks.maximum</name>
<value>2</value>
</property>
<property>
<name>mapred.tasktracker.reduce.tasks.maximum</name> <value>2</value>
</property>

b、调整心跳间隔:集群规模小于300时,心跳间隔为300毫秒;

c、启动带外心跳mapreduce.tasktracker.outofband.heartbeat默认是false;

d、配置多块磁盘mapreduce.local.dir;

e、配置RPC hander数目,mapred.job.tracker.handler.count 默认是10,可以改成 50,根据机器的能力;

f、配置HTTP线程数目tasktracker.http.threads 默认是40,可以改成100根据机器的能力;

g、选择合适的压缩方式,以snappy为例: 

<property>
<name>mapred.compress.map.output</name>
<value>true</value>
</property>
<property>
<name>mapred.map.output.compression.codec</name>
<value>org.apache.hadoop.io.compress.SnappyCodec</value>
</property>


29:设计题

采集nginx产生的日志,日志的格式为user ip time url htmlId每天产生的文件的数据量上亿条,请设计方案把数据保存到HDFS上,并提供一下实时查询的功能(响应时间小于 3s)

A、某个用户某天访问某个URL的次数

B、某个URL某天被访问的总次数

实时思路:

使用Logstash + Kafka + Spark-streaming + Redis + 报表展示平台 ;

离线的思路:

Logstash + Kafka + Elasticsearch + Spark-streaming + 关系型数据库;A、B、数据在进入到Spark-streaming中进行过滤,把符合要求的数据保存到 Redis 中。


30:有10个文件,每个文件1G,每个文件的每一行存放的都是用户的 query,每个文件的query都可能重复。要求你按照query的频度排序。还是典型的TOP K算法。

解决方案如下:

1)方案 1:

顺序读取10个文件,按照 hash(query)%10 的结果将query写入到另外10个文件中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。找 一台内存在2G左右的机器,依次对用hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件。对这10个文件进行归并排序(内排序与外排序相结合)。

2)方案 2:

一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的 query,一次性就可以加入到内存了。这样,我们就可以采用trie 树/hash_map等直接来统计每 个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。

3)方案 3:

与方案 1 类似,但在做完hash,分成多个文件后,可以交给多个文件来处理,采用分 布式的架构来处理(比如MapReduce),最后再进行合并。

文章转载自萌小璐,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论