开发中长见识

初识集合

  • List接口:实现类有ArrayList和LinkedList
  • ArrayList:底层是数组,大小是可变的;初始大小为10,增量是1.5(在每次调用add()方法时都会先去计算数组空间是否够用,如果够,直接添加,不够则扩容,使用grow(),当第11个元素进来时数组空间已满,会扩充到15,扩容完成之后调用arraycopy()方法对数组进行拷贝)
  • LinkedList:底层是链表。链表增删快,查询慢
  • Vector 底层结构是数组,线程安全的,增量是2

为什么日常开发中用的最多的是ArrayList 由底层的数据结构决定,正在日常开发中,查询比增删多,即便是增删也是往往在List的尾部添加即可,复杂度O(1)比较低,对于增删来说,底层调用的copyOf()方法优化过,因此也不会太慢

集合间的继承关系

集合间的继承关系

线程安全的list还有什么?

  • 用Collection来将ArrayList包装一下,变成线程安全。 Collections.synchronizedList(new ArrayList<>());
  • java.util.concurrent包下还有一个类,叫做CopyOnWriteArrayList :底层是通过复制数组来实现的
    add()方法实现的时候会加lock锁,复制一个新的数组,往新的数组里添加真正的元素,最后在把数组的指向变更为新的数组
    get()和size()方法只是获取数组指向的 数组的元素或者大小,读不加锁,写加锁
    其实跟文件系统的cow机制很像,就是说在修改数据的时候,不会直接在原来的数据位置进行操作,而是重新找个位置修改
    也就是说:要修改数据块A的内容,先把A读出来,写到B块里面去。如果这时候断电了,原来A的内容还在。
    这样做的好处就是可以保证数据的完整性,瞬间挂掉了容易恢复
    缺点:
    比较耗费内存(每次set()/add()都会复制出一个数组)
    只能保证数据的最终一致性,不能保证数据的实时一致性(A、B两个线程,A线程读数据还没读完,B线程清空了,此时A线程还是可以读出来的)

Map

  • 在hashMap中,是怎么判断一个元素是否相同?
    首先比较hash值,然后用==和equals判断元素是否相同。如果只有hash值相同,说明该元素哈希冲突了,如果三者比较完全一样,说明该元素是同一个
  • LinkedHashMap底层结构【数组+链表+双向链表】,保证了插入是有序的,在遍历的时候用的是双向链表遍历,不会影响遍历的性能
  • TreeMap的底层数据结构【红黑树】,key不能为null,有序(通过Comparator比较,如果comparator为null,使用自然顺序)
  • 线程安全的map?
  1. hashMap不是线程安全的,线程安全的可以使用ConcurrentHashMap
  2. ConcurrentHashMap是线程安全的Map实现类,还有一个hashTable,还可以使用Collections来包装线程安全的Map。后两者性能比较低()
  3. ConcurrentHashMap底层数据结构是【数组+链表/红黑树】,支持高并发的访问和更新;通过在部分加锁和利用CAS算法来实现同步,get不加锁
  4. Node用了volatile修饰。扩容时分配对应的空间,为了防止putVal导致数据不一致,会给所负责的区间加锁
  • Jdk7的hashMap扩容时是头插法,JDK8是尾插法

事务问题@Transactional

  1. 事务失效
    看数据库层面 数据库使用的存储引擎是否支持事务===>修改存储引擎为Innodb
    默认mysql使用的引擎是innodb,支持事务,如果修改为MyISAM,不支持事务
    业务代码层面
    使用Spring的声明式事务,看看执行的bean是否已经由Spring管理(@Service,@Component)
    @Transactional注解是否被放在了合适的位置(默认情况下无法使用注解对一个非public的方法进行事务管理)
    看看是否出现自调用,自调用时,调用的是目标类中的方法而不是代理类中的方法
  2. 事务回滚相关问题
    想回滚的时候事务却提交了
    想提交的时候被标记成只能回滚了(rollback only)
    默认情况下只有出现RuntimeException 或者Error 时才会回滚
    解决:
    1、内部事务发生异常,外部事务catch异常后,内部事务自行回滚,不影响外部事务将内部事务的传播级别设置为nested/requires_new均可
    2、内部事务发生异常时,外部事务catch异常后,内外两个事务都回滚,但是方法不抛出异常 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
  3. 读写分离跟事务结合使用时的问题
    读写分离的两种实现方式:
    配置多数据源:「如果开启了一个读写事务,那么必须使用写节点」,「如果是一个只读事务,那么可以使用读节点」
    依赖中间件(如MyCat):只要开启了事务,事务内的SQL都会使用写节点(依赖于具体中间件的实现,也有可能会允许使用读节点,具体策略需要自行跟DB团队确认)」

微服务

  • 微服务之间如何独立通讯的?
    同步:dobbo通过 RPC 远程过程调用、springcloud通过 REST 接口json调用 等。
    异步:消息队列,如:RabbitMq、ActiveM、Kafka 等。
  • SpringCloud 和 Dubbo 有哪些区别?
    两者都是分布式管理框架
    dubbo 是二进制传输,占用带宽会少一点。SpringCloud是http 传输,带宽会多一点,同时使用http协议一般会使用JSON报文,消耗会更大
    dubbo 开发难度较大,所依赖的 jar 包有很多问题大型工程无法解决。SpringCloud 对第三方的继承可以一键式生成,天然集成
    SpringCloud 接口协议约定比较松散,需要强有力的行政措施来限制接口无序升级
    Spring Cloud抛弃了Dubbo 的RPC通信,采用的是基于HTTP的REST方式
  • SpringBoot 和 SpringCloud 之间关系?
    SpringBoot:专注于快速方便的开发单个个体微服务(关注微观);
    SpringCloud:关注全局的微服务协调治理框架,将SpringBoot开发的一个个单体微服务组合并管理起来(关注宏观);
    SpringBoot可以离开SpringCloud独立使用,但是SpringCloud不可以离开SpringBoot,属于依赖关系。

关于http

TCP连接的生命周期解读

  • 正常 TCP连接
    在这里插入图片描述
    注意:
  • TCP连接每次均会经过三次握手连接后才能发送数据,经过四次挥手才能断开连接,但是每个TCP 连接在 server 返回 response 后都立马断开,则发起多个 HTTP 请求就要多次创建断开 TCP, 这在 Http 请求很多的情况下无疑是很耗性能的。
  • 如果在 server 返回 response 不立即断开 TCP 链接,而是复用这条链接进行下一次的 Http 请求,则无形中省略了很多创建 / 断开 TCP 的开销,性能上无疑会有很大提升。
  • 改良TCP连接 (左图是不复用 TCP 发起多个 HTTP 请求的情况,右图是复用 TCP 的情况)
    如下图所示,发起三次HTTP请求,复用TCP连接的话可以省去两次建立/断开TCP的开销,理论上一个应用会只需开启一个TCP连接,其他请求复用即可。
    在这里插入图片描述
  • 简单阐述一下keep-alive机制
    1. keep-alive (又称持久连接,连接复用)做的就是复用连接,保证连接持久有效。
    2. Http 1.1 之后 keep-alive 默认支持并开启,目前大部分网站都用了 http 1.1 了,也就是说大部分都默认支持连接复用了
    3. 虽然 keep-alive 省去了很多不必要的握手/挥手操作,但由于连接长期保活,如果一直没有 http 请求的话,这条连接也就长期闲着了,会占用系统资源,有时反而会比复用连接带来更大的性能消耗。
    4. 一般会为 keep-alive 设置一个 timeout, 这样如果连接在设置的 timeout 时间内一直处于空闲状态(未发生任何数据传输),经过 timeout 时间后,连接就会释放,就能节省系统开销。
    5. 但是 如果服务端关闭连接,发送 FIN 包(注:在设置的 timeout 时间内服务端如果一直未收到客户端的请求,服务端会主动发起带 FIN 标志的请求以断开连接释放资源),在这个 FIN 包发送但是还未到达客户端期间,客户端如果继续复用这个 TCP 连接发送 HTTP 请求报文的话,服务端会因为在四次挥手期间不接收报文而发送 RST 报文给客户端,客户端收到 RST 报文就会提示异常 (即 NoHttpResponseException)。
      在这里插入图片描述
    6. 解决NoHttpResponseException 有两种策略
      (a)重试,收到异常后,重试一两次,由于重试后客户端会用有效的连接去请求,所以可以避免这种情况,不过一次要注意重试次数,避免引起雪崩
      (b)设置一个定时线程,定时清理上述的闲置连接,可以将这个定时时间设置为 keep alive timeout 时间的一半以保证超时前回收。【evictExpiredConnections使用的就是这种策略:调用这个方法只会产生一个定时线程,那为啥应用中线程会一直增加呢,因为我们对每一个请求都创建了一个 HttpClient! 这样由于创建每一个 HttpClient 实例j时都会调用 evictExpiredConnections ,导致有多少请求就会创建多少个定时线程!】
  • 解决问题
    1. 首先把 HttpClient 改成了单例,这样保证服务启动后只会有一个定时清理线程
    2. 对应用的线程数做监控,如果超过某个阈值直接告警
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值