个人笔记1.0

Kafka如何保证消息不丢失

普通说法 消息要持久化 添加消息确认机制

我的看法 我从三个点来阐述一下自己的理解 第一点 先从咱们的服务端开始 设置broker中的配置unclean.leader.enable= false, 来保证咱们的一个所有副本同步 同时 生产者将消息投递到服务器的时候 需要把消息进行持久化 也就是说会进行同步到磁盘 这个过程中 存在同步刷盘和异步刷盘 如果选择同步刷盘 那是一定会保证消息不会失败 就算刷盘失败 也能及时进行补偿 反之采用异步的话 消息有一定的概率会丢失 网上有一种说法 说Kafka并不支持同步刷盘 这种说法也不能说是错误的 咱们可以通过参数的配置变成同步 可以设置 当咱们的一个消息数量达到一定的数量时 会将数据flush到日志文件中

log.flush.interval.messages=10000

也可以设置一个特定的事件 执行一次强制的flush操作

以上是可以达到同步刷盘的效果

第二点是生产者 使用带回调同志的send(msg,callback)方法 并且设置acks = all 它的消息投递采用一个同步的方式 生产者要保证消息到达服务器 就需要用到消息的一个确认机制 也就是说 必须确保消息投递到服务端 并且得到投递成功的响应 那如果生产者将消息投递到broker broker没来得及接收消息 就已经宕机了 那投递过来的消息 怎么办 不用慌 在生产者投递发送消息的时候 都会进行日志记录 在将消息投递到broker 就算服务器宕机了 等服务器重启了 可以根据日志信息 完成消息补偿 确保消息不会丢失

第三点 首先需要将消费者的提交方式改成手动提交

在Kafka中 消息消费完成后 并不会立即删除 二十使用定时清除策略 也就是说 我们消费者要确保消息消费成功后 手动ack提交 如果出现消费失败的情况 需要不断的进行重试 消费端不要设置自动提交 一定是手动提交 才能确保消息不丢失

最后总结一下 Kafka要严格意义上保证消息不丢失 需要从三个方面来设置

一 服务器端持久化设置为同步刷盘

二 生产者设置同步投递

三 消费端设置手动提交

MySQL中的myisam 和innodb的区别?

  1. 存储结构不同  mysiam有三个文件 .frm存放的是表结构文件 .myd存储数据文件 myi(myindex) 存储的是索引文件 而innodb是只有俩个文件 和mysiam一样 在.frm存放的都是表结构文件 而不同的是 innodb把存储数据文件和存储索引文件放到了一个文件 .ibd
  2. 存储空间消耗不同  mysiam所消耗的空间相对于来说 比较小 因为它的叶子节点存放的是数据的地址 并不是和innodb一样把数据存放到叶子节点上 mysiam有三个存储格式 静态表 动态表 压缩表 innodb是把数据存放到内存中 然后在主内存中有一个缓冲池用于高速缓存和索引 innodb所在的表都保存在一个数据文件中(也可能是多个文件 也可能是一个独立的一个表空间) innodb的表大小只受限于操作文件系统的大小 一般为2g
  3. 支持锁不同 如果是排除增删改的话 只是大量的查询 这样的话 推荐使用mysiam 但如果涉及到了删除 修改 添加的话 因为mysiam会锁定整个表 会影响性能 有读有写的情况下 推荐采用innodb 因为innodb是锁定操作行 性能会比mysiam快
  4. 支持事务不同 mysiam不支持事务 而innodb支持事务 比如commit(提交) roollback(事务回滚) 和数据崩溃回复
  5. 支持不同外键 mysiam不支持外键 但innodb支持

重载是什么 重写又是什么?

  1. 重载是在一个类中 方法名必须相同 但参数 返回值可以不同
  2. 一般来说 重写是子类或者接口继承某个方法 然后使用@overrid进行方法的重写 或者子类继承父类 重写父类plubic的属性和公共方法

Jdk jre是什么?

  1. jdk是java提供的开发工具包
  2. Jre是运行Java所需的运行环境

== 和equals有什么区别?

  1. ==比较的是堆内存的地址
  2. Equals比较的是两者的一个内容

Redis和mysql 如何保证数据一致性?

  1. 先查询数据库的数据 在把查询到的数据备份一份到redis 出现更新失败 可用日志记录 后续补偿
  2. 先删除缓存 在更新缓存 在别的线程进入时 访问该功能 会面临缓存击穿 从而缓存数据失效 在查数据库
  3. 利用消息中间件进行发送消息 比如在添加的时候 利用Kafka发送消息 然后利用它的监听器 根据key获取到它对应的一个value值 就是我们发送消息的一个数据 然后在此处写一个消息幂等性 防止消息丢失 在进行添加 这样能最大程度防止消息丢失 可以的话 用日志记录 不过这种概率出现的极低

缓存雪崩 缓存穿透 缓存击穿

  1. 缓存雪崩指的是用户访问某个功能时 缓存数据大面积失效 从而把压力给到了数据库 导致了缓存雪崩
  2. 缓存穿透指的是 用户访问某个功能的时候 缓存没有 数据库都失效了
  3. 缓存击穿指的是用户访问某个功能 缓存数据失效 从而把压力给到了数据库

解决缓存雪崩 缓存穿透 缓存击穿

缓存雪崩

  1. 可以把热点数据设置成永不过期
  2. 缓存数据时间设置成随机 避免在同一时间内 大批数据同时失效
  3. 如果访问量并不是特别高的情况下 可以加入队列 或者锁
  4. 如果缓存数据库是分布式部署的 可以将热点数据打散均匀的分布到各个节点上

缓存穿透

  1. 接口层添加校验 比如用户鉴权校验
  2. 将空数据存放到缓存 设置key-null 同时设置短时间(30s) 防止用户使用同一个id恶意暴力攻击

缓存击穿

  1. 热点数据永不过期
  2. 避免大量请求查询数据库
  3. 做好熔断 降级 防止系统崩溃

spring boot自动装配原理

@springbootapplication这个注解是暴露给用户使用的入口 它的底层是由@enableautoconfiguration这个注解来实现的 它的一个自动装配的实现 有三个核心步骤

  1. 组件中必须要包含@configuration配置类
  2. 第三方jar包在/meta-inf/目录下添加spring.factories文件
  3. Spring调用importselector接口完成动态加载

第一步:启动依赖组件的时候 组件中必须要包含@configuration的配置类 在这个配置类里面声明为@bean注解 就将此方法的返回值或者属性值 注入到ioc容器中

第二步:如果是使用第三方jar包 spring boot采用spi机制 只需要在mata-inf目录下添加springfactories配置文件 然后spring boot会根据约定规则 自动使用 springfactoriesloader来加载配置文件 中的内容

第三步:spring获取到第三方jar包中的配置以后 会使用调用importselector接口来完成动态加载

···这样的设计好处在于 大幅减少了臃肿的配置文件 而且各个模块之间的依赖实现了深度解耦 比如我们使用spring创建web程序时需要导入非常多的maven依赖 而spring boot只需要一个maven依赖来创建web程序 并且spring boot还把我们最常用的依赖都放到了一起 我们只需要引入spring-boot-starter-web这一个依赖就可以完成一个简单的web应用

以前用spring的时候需要用到xml文件配置开启一些功能 现在spring boot不用xml配置 只需呀写一个加入configuration注解  或者实现对应接口的配置类就可以了

··· 最后总结一下 spring boot自动装配是spring的完善和扩展 就是为我们便捷开发 方便测试和部署 提高效率而诞生的框架技术

单例模式

单例模式的定义 确保一个类在任何情况下都绝对只有一个实例 并提供一个全局访问点

破坏单例模式的五大场景

  1. 多线程破坏单例 在多线程环境下 线程的时间片是由cpu自由分配的 具有随机性 而单例对象作为共享资源可能会同时被多个线程同时操作 从而导致同时创建多个对象 当然这种情况只出现在懒汉式单例中 如果是饿汉式单例 子啊线程启动前就被初始化了 就不存在线程再创建对象的情况了

解决方案:当懒汉式到哪里出现多线程破坏的情况下 我给出两个解决方案 第一个是改为dcl的双重检查锁的写法 第二是使用静态内部类的写法 性能更高

  1. 指令重排破坏单例
  2. 克隆破坏单例
  3. 反序列化破坏单例
  4. 反射破坏单例

CountDownLatch协调同步工具

理解:

CountDownLatch是一个同步工具类 用来协调多个线程之间的同步 在继续执行 使用一个计数器进行实现 计数器初始值为线程的数量 当每一个线程完成自己任务后 计数器的值会减1 当计数器的值为0时 表示所有的线程都已经完成一些任务 然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务

CountDownLatch用法:

CountDownLatch典型用法 1.某线程在开始运行前等待线程执行完毕 将CountDownLatch的计数器初始化为new CountDownLatch(n) 每当一个任务线程执行完毕 就将计数器减1

CountDownLatch.countDown() 当计数器的值变为0时 在CountDownLatch上await()的线程就会被唤醒 一个典型的应用场景就是启动一个服务时 主线需要等待多个组件加载完毕 之后再继续执行

CountDownLatch典型用法:2.实现多线程开始执行任务的最大并行性 注意是并行性 不是并发 强调的是多个线程在某个时刻同时开始执行 类似于赛跑 将多个线程放到起点 等待发令枪响 然后开始同时跑 做法是初始化一个共享的CountDownLatch(1) 将计数器初始化为1 多个线程在开始执行任务前首先CountDownLatch.await() 当主线程调用countDown()时 计数器变为0 多个线程被同时唤醒

CountDownLatch的不足

CountDownLatch是一次性的 计算器的值只能在构造方法中初始化一次 之后没有任何机制再次对其设置值 当countDownLatch使用完毕后 它不能再次被使用 可用性差

ConcurrentHashMap

ConcurrentHashMap是hashmap的升级版 hashmap是线程不安全的 而concurrenthashmap是线程安全的 而其它功能和实现原理和hashmap类似

Hashtable也是线程安全 但每次操作都需要锁住整个结构 并发性低 相比之下 concurrenthashmap获取size时才锁整个对象

Hashtable对get/put/remove都使用了同步操作 concurrenthashmap只对put/remove同步

Hashtable是快速失败的 遍历时改变结构会报concurrentModificationException concurrenthashmap是安全失败 允许并发检索和更新

jdk8的concurrenthashmap和jdk7的concurrenthashmap有什么区别

  1. jdk8中新增了红黑树
  2. Jdk7使用的是头插法 jdk8使用的是尾插法
  3. Jdk7使用的是分段锁 而jdk8中没有使用分段锁
  4. Jdk7使用的锁是ReentrantLock jdk8使用的是Synchronized
  5. Jdk7中的扩容是每个Segment内部进行扩容 不会影响其他Segment 而jdk中的扩容和hashmap的扩容相似 只不过支持了多线程扩容 并且保证了线程安全

concurrenthashmap是如何保证并发安全的

Jdk7中的concurrenthashamp是通过ReentrantLock+CAS+分段思想来保证的并发安全的 concurrenthashmap的put方法会通过CAS的方式 把一个Segment对象存到Segment数组中 一个Segment内部存在一个hashEntry数组 相当于分段的hashmap Segment继承了ReentrantLock 每段put开始会加锁

在jdk7的concurrenthashmap中 首先有一个Segment数组 村的是Segment对象 Segment相当于是一个小hashmap Segment内部有一个hashEntry的数组 也有扩容的阈值 同时Segment继承了ReentrantLock类 同时在Segment还提供了put get等方法 比如Segment的put方法在一开始就会去加锁 加到锁之后才会把key-value存到Segment中去 然后在释放锁 同时在concurrenthashmap的put方法中 会通过CAS的方式把一个Segment存到Segment数组的某个位置中 同时因为一个Segment内部存在一个hashEntry数组 所以和hashmap对比来看 相当于分段了 每段里面是一个小的hashmap 每段公用一把锁 同时在concurrenthashmap的构造方法是可以设置分段数量的 叫做并发级别的concurrencyLevel

Jdk8中的concurrenthashmap是通过synchronized+cas来实现了 子啊jdk8中只有一个数组 就是node数组 node就是把key-value hashcode封装出来的对象 和hashmap中的Entry一样 在jdk8中通过node数组的某个index位置的元素进行同步 达到index位置的并发安全 同时内部也利用了cas对数组的某个位置进行并发安全的赋值

jdk8中的ConcurrentHashMap为什么使用synchronized来进行加锁

Jdk8中使用synchronized加锁时 是对链表头节点和红黑树根节点来加锁的 而ConcurrentHashMap会保证 数组中的某个位置的元素一定是链表的头节点或红黑树的根节点 所以jdk8中的ConcurrentHashMap在对某个桶进行并发安全控制时 只需要使用synchronized对当前那个位置的数组上的元素进行加锁即可 对于每个桶 只有获取到了第一个元素上的锁 才能操作这个桶 不管这个桶是一个链表还是红黑树

相比于jdk7中使用ReentrantLock来加锁 因为jdk7使用了分段锁 所以对于一个ConcurrentHashMap对象而言 分了段就得有几个ReentrantLock对象 表示得有对应得几把锁

而jdk8使用synchronized关键字来加锁就会更节省内存 并且jdk也已经对synchronized得底层工作机制进行优化 效率会更好

Jdk7中得ConcurrentHashMap是如何让扩容的

Jdk7中的concurrenthashmap和jdk7的hashmap的扩容不太一样 首先jdk7中也是支持多线程扩容 原因是 jdk7中的concurrenthashmap分段了 每一段叫做Segment对象 每个Segment对象就相当于一个hashmap 分段之后 对于concurrenthashmap而言 能同时支持多个线程进行操作 前提是这些操作的是不同的Segment 而concurrenthashmap的扩容仅限于本Segment 也就是对于的小型Shashmap进行扩容 所以是可以多线程扩容的

每个Segment内部的扩容逻辑和hashmap中一样的  

ConcurrentHashMap与hashmap类非常相似 不同之处在于ConcurrentHashMap提供内部维护的并发性 这意味着在多线程应用程序中访问ConcurrentHashMap时 不需要具有同步块

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值