面试题-代码

mysql

-- 20201201至今每日订单量top10的城市及其订单量(订单量对order_id去重)
select from_unixtime(ctime,"%Y-%m-%d") 日期,city_id,order_id,sum(sale_num) as 每日订单量
from t_order
where ctime>unix_timestamp('20201201')
group by from_unixtime(ctime,"%Y-%m-%d"),city_id,order_id
order by sum(sale_num) desc;

hql

  1. 行列转换

    1. 多行转一行
    select
          id
          , concat_ws(',',collect_set(col_name)) as col_new
    from table_name
    group by id
    ;
    
    1. 一行转多行
    select
       id
       , col_name
       , split_col_name
    from table_name
    lateral view explode(split(col_name,',')) num as split_col_name
    -- group by 是为了去重,可以不要
    group by id, col_name, split_col_name
    ;
    
  2. 一个文件只有一行,但是这行有100G大小,mr会不会切分,我们应该怎么解决

public class TextInputFormat extends FileInputFormat<LongWritable, Text> {
 
  @Override
  public RecordReader<LongWritable, Text> 
    createRecordReader(InputSplit split,
                       TaskAttemptContext context) {
    String delimiter = context.getConfiguration().get(
        "textinputformat.record.delimiter");
    byte[] recordDelimiterBytes = null;
    if (null != delimiter)
      recordDelimiterBytes = delimiter.getBytes(Charsets.UTF_8);
    return new LineRecordReader(recordDelimiterBytes);
  }
//这个对文件做压缩用的
  @Override
  protected boolean isSplitable(JobContext context, Path file) {
    final CompressionCodec codec =
      new CompressionCodecFactory(context.getConfiguration()).getCodec(file);
    if (null == codec) {
      return true;
    }
    return codec instanceof SplittableCompressionCodec;
  }
 
}
//TextInputFormat类是继承自FileInputFormat  ,FileInputFormat 实现了 InputFormat接口

//TextInputFormat中可以看到,在getRecordReader函数中调用了LineRecordReader这个类。我们注意观察在传入的参数中有一个delimiter参数,这个参数就是用来指定分割符的(具体可以查看LineRecordReader中实现对文件分割的实现),所以我们可以自己定义一个类MyInputFormat继承FileInputFormat类然后将

String delimiter = context.getConfiguration().get(
        "textinputformat.record.delimiter");

//改为:String delimiter = "|-|\n";

//然后在程序中,在Job中设置InputFormat类为MyInputFormat.class即可。

job.setInputFormatClass(MyInputFormat.class);

//如果不选择重写InputFormat类,也是可以实现的,给参数Configuaration对象设置属性textinputformat.record.delimiter

//以下是伪代码示例

private int run(long cols, String data_length, String primarykey_order_id, Configuration conf)
    throws IOException, ClassNotFoundException, InterruptedException, SQLException
  {
   
    conf.set("colnumSize", String.valueOf(cols));  
    conf.set("textinputformat.record.delimiter", "|-|\n");   
    Job job = new Job(conf, JobUtil.getJobName(dataTime, interfaceCode));
    job.setJarByClass(RunTxt.class);
    job.setMapperClass(Mapper_RunTxt.class);
//    job.setInputFormatClass(MyTextFormat.class); 	
}

这样程序读取文本时不再以原来默认的"\n"逐行读取了,而是以用户自定义的行分割符来作为“行”(可能是一行或者多行)读取文件内容了。

  1. hdfs HA机制,一台namenode宕机了,joualnode,namenode,edit.log fsimage的变化
  • jounalnode
    HA是为了解决单点问题,通过JN集群共享状态,通过ZKFC 选举active,监控状态,自动备援。
    NN-HA机制
    Active Namenode将数据写入共享文件管理系统,而StandbyNamenode监听该系统,一旦发现有新数据写入,则读取这些数据,并加载到自己内存中,以保证自己内存状态与Active NameNode保持基本一致。如此,在紧急情况下standby便可快速切为active namenode。

    • 自动故障转移机制:
      1. active Namenode宕机(假死)。

      2. active Namenode zkfc检测到假死

      3. 通知另一台namenode的zkfc

      4. 另一台机机器强行杀死之前的active namenode

      5. 激活standby namenode,切为active状态

  • namenode,edit,fsimage

  • Namenode滚动当前正在写的edit文件,该文件为待合并状态,也会生成新的edits.inprogress文件,后续的修改日志将写入该文件中

  • namenode将fsimage文件和edits文件加载到内存进行合并。dump成新的fsimage文件fsimage.chkpoint。

  • namenode将fsimage.chkpoint重命名为fsimage

  1. 如何判断一个模型的好坏
  • 算法模型
    我们会用不同的度量去评估我们的模型,而度量的选择,完全取决于模型的类型和模型以后要做的事;一般基于方差,基尼系数等可以对算法进行评估的数学模型。
  • 数仓模型
  • 模型完整度:des,ads,dm层直接饮用ods的比例太大就是跨层引用率过高就不是最优
  • **复用率:dw,dws层产出的表的数量 **
  • 规范度
  • 数据可回滚,重跑数据结果不变
  • 核心模型和扩展模型分离
  1. rowkey一般如何设计,你项目中是如何设计的:保证唯一,有序,简短64kb以下
    核心数据比如userid和itemid+时间戳+随机hash

  2. 你们需求的开发流程是什么样的
    需求开发流程

    1. 需求提出:产品或者客户提出,UI,前后端开发,测试评估业务意义,成本等。
    2. 需求PRD:产品根据需求列出需求的细节,流程歧义等定义边界。并配有原型图,文字说明,主要是,需求,需求目标,业务逻辑,参与敲定版本的还是上述那些人。
    3. 交互设计:UI根据PRD设计出效果图,一般变动不大,如果大说明需求PRD没有设计好。
    4. 概要设计和详细设计:小需求改动可能直接详细设计或者确认-改动-发布完成即可。
      1. 技术是把双刃剑,你用了技术之后你是不是需要列出他的优点缺点,出问题之后的解决方案,还有可能出现的问题,注意点等等。
      2. 你需要考虑这个需求涉及到哪些服务了,需要新增哪些接口(功能列表),修改哪些接口,表有现场的还是要新建表,字段要新建么?
  3. 两个数 a=3,b=5,如何不使用中间变量不使用函数的情况下调换他们: ^按位异或,不相同二进制位异或的1
    a=a^b b=a^b a=a^b
    或者
    a=a+b b=a-b a=a-b

  4. hive常用的优化:

    1. 行列过滤,代码优化
    • 列处理:在SELECT中,只拿需要的列,如果有,尽量使用分区过滤,少用SELECT *
    • 行处理:在分区剪裁中,当使用外关联时,如果将副表的过滤条件写在Where后面,那么就会先全表关联,之后再过滤,这样效率低,可以直接先子查询后再关联。(这个非常有用,实际开发中大数据处理效率会高很多,善用子查询关联);但是这种对左外关联和右外关联有时候就不一定适用了。尤其表中有null值的时候就不适合使用了,使用时要注意。
    1. 开启JVM重用
    2. CombineHiveInputFormat具有对小文件进行合并
    3. hive.exec.parallel值为true开启并行执行
    4. Fetch抓取: Fetch抓取是指,Hive中对某些情况的查询可以不必使用MapReduce计算;hive-default.xml.template文件中hive.fetch.task.conversion默认是more,该属性修改为more以后,在全局查找、字段查找、limit查找等都不走mapreduce。
  5. hive的执行计划有看过吗,你一般会关注哪几个点:查看命令关键字Explain

EXPLAIN [EXTENDED|DEPENDENCY|AUTHORIZATION] query
  • 执行计划一共有三个部分:
    1. 这个语句的抽象语法树
      • 在语法树中的TOK_FROM中我们可以看到查询的这两张表及他们需要连接的条件
      • 在TOK_INSERT中因为我们没有指定将查询信息写入哪张表,没有插入就为空
      • 在TOK_SELECT中,显示了我们要查询的字段,逐个显示。
    2. 这个计划不同阶段之间的依赖关系:shuffle
      可以stage十分明显的看出依赖关系。
    3. 对于每个阶段的详细描述
      对每个stage都进行详细展示;
      •  TableScan 查看表
        
      •  alias: emp 所需要的表
        
      •  Statistics:Num rows: 2  这张表的基本信息
        
      •  expressions:empno (type: int) 需要输出的字段及类型
        
      •  outputColumnNames: _col0 输出字段编号
        
      •  Filter Operator 过滤操作
        
      •  Reduce Output Operator 输入到reduce阶段的操作及字段等信息
        
      •  Join Operator join操作及类型信息
        
      •  keys 连接字段
        
      •  inputformat,outputfomat 输入输出格式
        

总的来说,Hive是通过给用户提供的一系列交互接口,接收到用户的指令(SQL),使用自己的Driver,结合元数据(MetaStore),将这些指令翻译成MapReduce,提交到Hadoop中执行,最后,将执行返回的结果输出到用户交互接口
驱动器里主要有:
(1)解析器(SQL Parser):将SQL字符串转换成抽象语法树AST,这一步一般都用第三方工具库完成,比如antlr;对AST进行语法分析,比如表是否存在、字段是否存在、SQL语义是否有误。
(2)编译器(Physical Plan):将AST编译生成逻辑执行计划。
(3)优化器(Query Optimizer):对逻辑执行计划进行优化。
(4)执行器(Execution):把逻辑执行计划转换成可以运行的物理计划。对于Hive来说,就是MR/Spark。

  1. 用phenix和es作为hbase二级索引的区别,最新的hbase已经支持二级索引了,你清楚吗?
    es没有Phoenix并发高,速度快的。弄到solr/es主要目的是解决全文模糊查询.
    • Phoenix 社区好,用的比较多,sql话,索引分析支持没有ES灵活
    • ES 模糊搜索等多种功能,需要自己定制开发链接两个框架
    • Hbase的二级索引只是基于Coprocessor方案,目前市场使用的少。

JAVA

spring

  1. spring AOP应用场景
  • AOP(Aspect-OrientedProgramming,面向方面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP引入封装、继承和多态性等概念来建立一种对象层次结构,用以模拟公共行为的一个集合。当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。对于其他类型的代码,如安全性、异常处理和透明的持续性也是如此。这种散布在各处的无关的代码被称为横切(cross-cutting)代码,在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
    AOP技术则恰恰相反,它利用一种称为“横切”的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”,即方面。

  • AOP用来封装横切关注点,具体可以在下面的场景中使用:

    •  Authentication 权限
      
    •  Caching 缓存
      
    •  Context passing 内容传递
      
    •  Error handling 错误处理
      
    •  Lazy loading 懒加载
      
    •  Debugging  调试
      
    •  logging, tracing, profiling and monitoring 记录跟踪 优化 校准
      
    •  Performance optimization 性能优化
      
    •  Persistence  持久化
      
    •  Resource pooling 资源池
      
    •  Synchronization 同步
      
    •  Transactions 事务
      
  • AOP相关概念

    • 方面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用spring的 Advisor或拦截器实现。

    • 连接点(Joinpoint): 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。

    • 通知(Advice): 在特定的连接点,AOP框架执行的动作。各种类型的通知包括“around”、“before”和“throws”通知。通知类型将在下面讨论。许多AOP框架包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链。Spring中定义了四个advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

    • 切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点:例如,使用正则表达式。 Spring定义了Pointcut接口,用来组合MethodMatcher和ClassFilter,可以通过名字很清楚的理解, MethodMatcher是用来检查目标类的方法是否可以被应用此通知,而ClassFilter是用来检查Pointcut是否应该应用到目标类上

    • 引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。例如,你可以使用一个引入使任何对象实现 IsModified接口,来简化缓存。Spring中要使用Introduction, 可有通过DelegatingIntroductionInterceptor来实现通知,通过DefaultIntroductionAdvisor来配置Advice和代理类要实现的接口

    • 目标对象(Target Object): 包含连接点的对象。也被称作被通知或被代理对象。POJO

    • AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

    • 织入(Weaving): 组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

  1. 分布式锁的几种实现方式?
  • 背景

    • 单机的情况下,如果有多个线程要同时访问某个共享资源的时候,我们可以采用线程间加锁的机制;
    • 到了分布式系统的时代,这种线程之间的锁机制,就没作用了,系统可能会有多份并且部署在不同的机器上,这些资源已经不是在线程之间共享了,而是属于进程之间共享的资源。
  • 分布式锁要满足哪些要求呢?

    • 排他性:在同一时间只会有一个客户端能获取到锁,其它客户端无法同时获取
    • 避免死锁:这把锁在一段有限的时间之后,一定会被释放(正常释放或异常释放)
    • 高可用:获取或释放锁的机制必须高可用且性能佳
  • 实现方式

    • 基于数据库
      - 悲观锁
      - 乐观锁
    • 基于redis
      • SETNX
      • expire
      • delete
    • 基于zk
  • 缺点
    一、数据库分布式锁实现的缺点:

     	1、db操作性能较差,并且有锁表的风险。
    
     	2、非阻塞操作失败后,需要轮询,占用cpu资源。
    
     	3、长时间不commit或者长时间轮询,可能会占用较多连接资源。
    
    二、Redis(缓存)分布式锁实现的缺点:
    
     	1、锁删除失败,过期时间不好控制。
    
     	2、非阻塞,操作失败后,需要轮询,占用cpu资源。
    
    三、ZK分布式锁实现的缺点:
    
     	性能不如redis实现,主要原因是写操作(获取锁释放锁)都需要在Leader上执行,然后同步到follower。
    

scala

  1. 二分查找
package top.majia.example
import scala.collection.mutable.ArrayBuffer
object Dichotomy {
  def main(args: Array[String]): Unit = {
    val list = Array(1, 3, 4, 2, 5, 5, 6, 9, 3, 4, 4)
    search(list.sorted, 8)
  }

  def search(unSorted: Array[Int], find: Int): Unit = {
    val size = unSorted.size
    if (size == 0) {
      println(s"empty array")
      return
    }

    println(unSorted.toList)
    var start = 0
    var end = size - 1
    while (start <= end) {
      val index = (start + end) / 2
      if (unSorted(index) > find) {
        end = index - 1
      } else if (unSorted(index) < find) {
        start = index + 1
      } else {
        println(s"${find} in index : ${index}")
        return
      }
    }

    println(s"${find} no found")
  }
}

  1. 怎么从一个字符串中把数字拆出来?
object FindNumFromStr {
 def main(args: Array[String]): Unit = {
   val xs = List("X45C", "5K")
   val ys = xs map {x => """\d+""".r.findAllIn(x).toList}
   println(ys)
 }
}
posted @ 2021-03-28 23:57  编程未来  阅读(483)  评论(0编辑  收藏  举报