Java工程师修炼之道

后端技术导言

sharding-jdbc:从数据源层面解决分库分表,读/写分离问题

solr和ElasticSearch都是基于Lucene实现的搜索引擎,ElasticSearch对集群有良好的支持,逐渐成为主流

Apache Kafka、RabbitMQ是使用比较普遍的消息队列,对消息丢失不敏感且不要求消息事务的,选择Kafka,否则选择RabbitMQ

分布式文件存储:HDFS、NFS、Samba

主流的RPC协议:Dubbo、Hession、RMI等

分布式调度(定时器):elastic-job

Java编程知识

核心语法

        jdk7中的try-with-resource、switch string、diamonds等语法

        jdk8中的Lambda、Stream等语法

集合类:HashMap、ArrayList、LinkedList、HashSet、TreeSet以及线程安全的ConcurrentHashMap、ConcurrentLinkedQueue等线程安全集合。了解其实现原理、查询、修改的性能是使用场景。

高级特性

       并发编程:Executor框架、jdk7的fork/join框架以及CountDownLatch、Semaphore、CyclicBarrier等同步工具

       网络编程:区分BIO、NIO、AIO

       序列化:除了Java自带的序列化实现,Protobuf和Kryo都是比较高效的第三方实现

       RPC的实现:Dubbo基于TCP协议、Hession基于HTTP协议

类加载机制:遵循双亲委派原则,即当前类加载器需要先去请求父加载器加载当前类,无法完成加载才自己去尝试进行加载

GC原理和调优、Java内存模型、性能调优和监控工具

系统架构的演化

单体应用:当应用、团队规模较小,一个工程包括所有的功能,部署节点少

垂直应用:将单体应用拆分成不同的互不相关的几个应用,分别对外提供服务

分布式应用:服务粒度越来越小,彼此通过某种方式进行通信。

三者层层递进

codis:redis集群方案;cobar:mysql集群方案

数据结构和算法:B树、哈希表、栈以及七大排序算法、查找算法等

知其然知其所以然:技术的深度和广度

IOC:将代码之间的依赖关系从代码中剥离出来,交由容器去维护。目前有三种基本的方式:构造器方式注入、setter注入以及字段注入方式

@Autowired和@Resource注解的区别

@Resource是先根据Bean的名字去匹配Bean,获取不到的话再根据类型去匹配;而@Autowired是根据类型匹配,通过名字则需要和@Qualifier注解配合使用

MyBatis的也可以支持注解映射Mapper

public interface TestUserMapper{
    @select("select * from test_user where id = #{id}")
    TestUser selectUser(int id);
}

MyBatis的核心是SqlSessionFactory,SqlSession是非线程安全的,而SqlSessionTemplate是线程安全的

查询缓存的使用:一级缓存和二级缓存

  • MyBatis默认开启一级缓存,其实SqlSession级别的,Session结束缓存就清空
  • MyBatis的二级缓存是Mapper级别的,需要手动开启,useCache=“true”即是开启二级缓存,flushCache=“true”使数据直接flush到数据库中防止出现脏数据

SpringMVC的请求处理流程

  • 用户发送请求到DispatchServlet前端控制器
  • 从HandlerMapping中匹配此次请求的Handler,匹配的条件包括:请求路径、请求方法、hear信息等。常用的几个HandlerMapping如下:
  1. SimpleUrlHandlerMapping:简单映射一个URL到一个Handler
  2. RequestMappingHandlerMapping:扫描RequestMapping注解,根据相关配置,绑定URL到一个Handler
  • 获取相应的Handler(Controller,控制器)后,调用相应的方法。在这里有一个非常关键的组件HandlerAdapter,是Controller的适配器,SpringMVC最终通过HandlerAdapter来调用实际的Controller方法,常用的如下:
  1. SimpleControllerHandlerAdapter:处理实现了Controller接口的Controller
  2. RequestMappingHandlerAdapter:处理类型为HandlerMethod的Handler,这里使用@RequestMapping注解的Controller就是一种HandlerMethod
  • Handler执行完毕,返回对应的ModelAndView
  • 使用配置好的ViewResolver来解析返回结果
  • 生成视图返回给用户

MySQL索引使用

常用的命令explain

EXPLAIN select * from client_info where id = '10007';
EXPLAIN select * from client_info where real_name = '江米条1';

  • type:显示了连接使用了哪种类别,有无使用索引。如果为ALL,那么说明要进行全表扫描
  • key:显示了MySQL实际决定使用的键(索引)。如果没有选择索引,键是NULL。要想强制MySQL使用或忽视possible_key列中的索引,则在查询中使用FORCE INDEX、USE INDEX或者IGNORE INDEX。如果这里为NULL,则说明没有命中索引
  • Extra:此列中如果出现Using filesort(需要额外的步骤来发现如何对返回的行排序)或者Using temporary(需要创建一个临时表来存储结果),说明查询需要优化

使用索引时的注意点

  • 最左前缀原则:索引可以以一定顺序引用多列,这种索引叫做联合索引。如name和city加联合索引就是(name,city)。而最左前缀原则是指,如果查询条件精确匹配索引的左边连续一列或几列,则可以命中索引
--可以命中索引
select * from user where name = xx and city = xx;
--可以命中索引
select * from user where name = xx;
--无法命中索引
select * from user where city = xx;
  • 避免where子句对字段施加函数,如to_date(create_time)>xxxx,这会造成无法命中索引
  • 避免索引冗余:能够命中A就肯定能命中B,那么A就是冗余索引。如(name,city)和(name)这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的。在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引
  • 删除长期未使用的索引,不用的索引的存在会造成不必要的性能损耗
  • 查询条件的字段应使用正确的数据类型,否则MySQL会自动做类型转换,导致无法命中索引。比如mobile字段为字符串,查询时没有加上'',那么就会进行强制类型转换

索引可以加快查询速度,但是索引也会有代价:索引文件本身要消耗存储空间,并且在被索引的表上insert和delete会变慢;另外MySQL在运行时也会消耗资源维护索引。

MySQL的事务机制

Atomic(原子性):即事务要么全部完成,要么全部都不做。只要其中一个操作失败,就认为事务失败,需要回滚。

Consistency(一致性):数据库要一直处于一致的状态。

Isolation(独立性):并发的事务之间不会互相影响。

Durability(持久性):一旦事务提交后,做的修改就会永久的保存到数据库中。

事务隔离级别

  • 读未提交:会产生脏读,可以读取未提交的记录,实际情况下不会使用
  • 读已提交:会存在不可重复读以及幻读的现象。即读取过的数据两次读的值不一样;幻读则侧重于记录数目变化,多次执行同一个查询返回的记录不完全相同,JDBC默认的事务级别
  • 可重复读:MySQL默认的事务级别
  • 串行:保证不同事务间的互斥。只有一个事务完成,下一个事务才会进行,效率低

锁机制

按照锁粒度分为表级锁和行级锁

表级锁:对当前操作的整张表加锁,实现简单,加锁快,不会造成死锁。并发最低。

行级锁:只针对当前操作的行进行加锁。大大减少数据库操作的冲突。开销大,加锁慢,会出现死锁。

表级锁和行级锁可进一步划分为共享锁和排他锁

共享锁(读锁):其他用户可以读取数据,可以再加共享锁,读取到的数据也是同一版本的。

排他锁(写锁):一个事务对数据加上排他锁后,其他事物不能再对此数据加任何其他类型的锁。

InnoDB还有如下的两个表级锁

意向共享锁:表示事务准备给数据行加入共享锁,事务在一个数据行加共享锁前必须先取得该表的意向共享锁。

意向排他锁:表示事务准备给数据行加入排他锁,事务在一个数据行加排他锁前必须先取得该表的意向排他锁。

如何避免死锁

  • 通过表级锁来减少死锁产生的概率
  • 多个程序尽量约定以相同的顺序访问表
  • 同一个事务尽可能做到一次锁定所需要的所有资源

大表优化

  • 限定数据的查询范围
  • 读/写分离
  • 缓存:使用MySQL的查询缓存
  • 垂直拆分:将表结构进行拆分
  • 水平拆分:不进行表结构的拆分,通过某种策略存储数据分片,将每片数据分到不同的表或库中

需要注意的是,分表仅仅解决了单一表数据过大的问题,但由于表数据还是在同一机器上,对提升MySQL并发能力并没有什么意义,所以水平拆分最好分库

数据库分片分表是客户端代理和中间件代理

客户端代理:逻辑在应用端,封装在jar包中,通过修改或封装JDBC来实现,当当网的Sharding-JDBC、阿里的TDDL是目前比较出名的

中间件代理:在应用和数据中间加了一个代理层。逻辑维护在中间件服务,阿里的Cobar,开源的MyCat、网易的DDB等

在数据查询时注意以下技巧:

  1. count(*)和count(1)是等价的,在有索引的时候都会选择合适的索引,没有则全表扫描。而count(字段)则是统计字段不为NULL的记录数目。
  2. 在查询时不要使用全属性选择器*
  3. 模糊匹配查询时,当后缀模糊查询时能够使用索引,如like'a%',而前缀不行
  4. 避免列运算,如select * user where age +1>100;
  5. 批量插入代替循环单条插入,能够减少网络I/O带来的开销
  6. 一个很耗时的SQL会堵死整个库,可以拆开多个小的语句进行,以减少锁的时间

CAP理论:核心在于一个分布式系统不可能同时满足以下三点,最多能较好地同时满足两个

  • Consistency:一致性,所有节点在同一时间具有相同的数据
  • Available:可用性,每次请求都能在期望时间内获取正确的响应,但不保证获取的数据为最新数,可以允许数据不一致
  • Partition tolerance:分区容错性,系统中任意信息的丢失或失败不会影响系统的继续运作。

可以根据CAP理论,将数据库分为如下3类

  • CA:传统关系型数据库的单点模式,但没有扩展性
  • CP:MongoDB、HBase,满足数据的一致性和分区性
  • AP:Cassendra,具有较好的性能和可扩展性

Dubbo:传输是基于TCP协议的,使用了高性能的NIO框架Netty,序列化可以有多种选择,默认使用的是Hessian的序列化实现。默认使用Zookeeper作为服务注册、管理中心。

Java编程进阶

JVM虚拟机内存

Java内存管理指的就是对JVM虚拟机的内存管理,Java运行时内存组成部分

线程私有的

  • 程序计数器:当前线程所执行的字节码的行号指示器
  • Java虚拟机栈:每个方法被执行时都会创建一个栈帧,存储局部变量表、操作栈、动态链接、方法出口等信息。每个线程都有自己独立的栈空间,线程栈只存储基本类型和对象地址,方法中局部变量存放在线程空间中
  • 本地方法栈:Native方法服务,在HotSpot虚拟机中和Java虚拟机栈合二为一

线程共享的

  • Java堆:存放对象实例,几乎所有的对象实例及其属性都在这里分配内存
  • 方法区:存储被虚拟机加载的类信息、常亮、静态变量、JIT编译后的代码
  • 直接内存:NIO、Native函数直接分配的堆外内存

内存溢出

在JVM申请内存时,遇到无法申请到足够内存的情况,从而导致内存溢出,一般有以下几种情况:

  • 虚拟机栈和本地方法栈溢出
  1. StackOverflowError:线程请求的栈深度大于虚拟机所允许的最大深度,一般可以通过循环递归会触发此现象
  2. OutOfMemoryError:虚拟机在扩展栈时无法申请到足够的内存空间,一般可以通过不停地创建线程触发此现象
  • Java堆溢出:在创建大量对象并且对象生命周期都很长的情况下,会引发此现象
  • 方法区溢出:方法区存储Class等元数据信息,如果产生大量的类,比如使用CGLIB,那么就会引发内存溢出

垃圾回收还需要搞清楚吞吐量与响应时间的含义

  • 吞吐量是对单位时间内完成的工作量的度量,如每分钟的Web服务器请求数量
  • 响应时间是提交请求和返回该请求的响应之间使用的时间,如访问Web页面花费的时间

GC流程和算法

GC的一般流程如下

  • 找出堆中活着的对象
  • 释放死对象占用的资源
  • 定期调整活对象的位置

在找出活着的对象以及释放死对象的情况下,可以使用以下算法

  • Mark-Sweep:标记-清除
  • Mark-Sweep-Compact:标记-整理
  • Copying Collector:复制算法

Mark(标记):从GCroot开始扫描(包括线程栈、静态常量),给能够沿着roots到达的对象标记为“live”,最终所有能够到达的对象都标记为“live”,而无法到达的对象则被标记为"dead"。

Sweep(清除):扫描堆,定位到所有"dead"对象,并将其清理

Compact(压缩):对于对象的清除,会产生一些内存碎片,需要对这些内存进行压缩、整理

Copy(复制):将内存分为“from”和"to"两个区域,在垃圾回收时,将from区域的存活对象整体复制到to区域中。

Copy和Mark-Sweep相比

  • 内存消耗上:Copy需要2倍的最大内存,Mark-Sweep只需要一倍
  • 效率上:Copy效率较高,Mark-Sweep效率较低

分代回收

  • 分代假设:大部分对象的寿命很短,“朝生夕死”,重点放在对新生代对象的回收,而且新生代通常只占整个空间的一小部分
  • 把新生代里活得很长的对象移动到老年代
  • 只有当老年代满了才去回收
  • 回收效率明细比不分代高

Java网络编程

同步阻塞:去餐馆吃饭,在原地一直等待直到盖浇饭好,然后去就餐。当厨师给你做饭时,顾客需要一直等待

同步非阻塞:去餐馆吃饭,点完后回位子等着,每隔一段时间去询问饭好了没,依次循环到饭做好,然后就餐。

信号驱动:去餐馆吃饭,当饭菜好后,通知自己,自己可以去做别的事情

异步非阻塞(事件驱动):去餐馆吃饭,当饭菜好后,就直接端到位子上

信号驱动和异步I/O的区别

  • 信号驱动由内核告诉我们何时可以开始一个I/O操作(数据在内核缓冲区中)
  • 异步I/O则由内核通知I/O操作何时已经完成(数据已在用户空间中)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芦蒿炒香干

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值