天天向上
2023-06-28 12:21:42 0 举报
AI智能生成
登录查看完整内容
知识点
作者其他创作
大纲/内容
1.项目架构
2.项目中的难点
3.介绍下项目中比较复杂的实现
自我介绍
发布计划:先进行DDL(增加字段时不要加非空约束,否则会对之前有影响),再发布代码,先发布服务提供方,再发布服务调用方
上线计划
这类资源问题,第一时间要做的是两件事:1. 问下其他人有无在这段时间做什么骚操作2. 根据监控,快速排查各指标有无异常,比如某个http接口qps异常飙升。当所有方便的手段用完之后,还是无法解决问题,就应该上机器用各种命令去查看,甚至是dump下jvm堆栈信息排查
生产环境cpu飙升
线上问题
3.手动debug
观察方法
1.数据库初始化耗时->a.数据库连接池初始化;b.获取数据库元数据初始化
2.因为分库分表都需要将以上初始化
耗时原因
解决方案
补充关于 Spring Bean 生命周期 SPI BeanPostProcessor 接口为什么不是一个很好的统计 Bean 耗时时间,主要原因在于一个 Bean 可能被多个 BeanPostProcessor 处理,所以需确保统计 BeanPostProcessor 的postProcessBeforeInitialization 必须是第一个调用,同时,确保它的 postProcessAfterInitialization 是最后一个执行,显然这是矛盾的。Spring 5.3 提供了 StartupStep API,它能够统计部分 Bean 和 IoC 容器耗时,但是还是不完整,估计作者也感受到了难度。其他类似需求相关实现开源代码核心实现代码:https://github.com/microsphere-projects/microsphere-spring-projects/blob/main/microsphere-spring/microsphere-spring-context/src/main/java/io/github/microsphere/spring/context/event/Bean 生命周期核心:BeanEventListener.java Bean 耗时统计:BeanTimeStatistics.java 单元测试:BeanTimeStatisticsTest.java
延伸
启动耗时长
线下问题
项目
ArrayList在创建时,其初始容量为0,即底层是一个空数组,而第一次往其中存入元素的时候,会进行第一次扩容,在这第一个扩容中,扩容为10,而之后的第二次,第三次,第N次扩容,均为前一次容量的1.5倍
初始化与扩容
数组赋值
查询是否存在是通过遍历
痛点
ArrayList
LinkedList
线程安全的List:Collections.synchronizedList(List< T> list)
List
为什么数组扩容后,链表长度会减半?因为我们确定桶位置是数组长度与hash进行&操作,长度扩为2倍,桶位置正好会改为2倍
HashMap
底层是HashMap
优势
HashSet
1.8中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。把之前的HashEntry改成了Node,但是作用不变,把值和next采用了volatile去修饰,保证了可见性
1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的(实现可见性)
2.禁止进行指令重排序(实现有序性)
3.volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性
volatile的特性
ConcurrentHashMap
HashTable的初始值是11,扩容 2 * n + 1。不允许null的Key和Value。直接使用给的初始值。HashTable是线程安全的(方法上都有synchronized),同步机制决定了它无法追求运行速度上的极致。在取余法的时候,使用位运算来提升效率已经意义不大了,更多的精力用在考虑解决hash冲突上。使用质数和奇数的取模运算,可以将Hash冲突的概率降低到最小。基本被淘汰
HashTable
LinkedHashMap
只允许空的 value,key 不能为空key不可以重复,value允许重复有序线程不安全传入的key进行了大小排序
TreeMap
线程安全低效
vector
线程安全
Stack
1.存储的数据都是无序
同
异
set集合和map集合的区别
面试题
数据结构
1.在实际开发中,对于需要多次或大量拼接的操作,在不考虑线程安全问题时,我们就应该尽可能使用 StringBuilder 进行 append 操作.如果提前知道需要拼接 String 的个数,就应该直接使用带参构造器指定 capacity,以减少扩容的次数
2.对于程序中大量使用存在的字符串时,尤其存在很多已经重复的字符串时,使用 intern()方法能够节省内存空间
String优化
Java基础
key
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >unique_subquery > index_subquery > range >index > ALL
结果值从最好到最坏
SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,最好是 consts级别。
type☆
key_len☆
rows:预估的需要读取的记录条数rows 值越小,代表数据越有可能在一个页里面,这样io就会更小。
rows☆
filtered 的值指返回结果的行占需要读到的行(rows 列的值)的百分比。自己的理解: 比如读了100 rows. filtered 是10% 那么就说明还要对着100条进行过滤。对于单表查询来说,这个filtered列的值没什么意义,更关注在连接查询中驱动表对应的执行计划记录的filtered值,它决定了被驱动表要执行的次数(即:rows * filtered)
filtered
是用来说明一些额外信息,包含不适合在其他列中显示但十分重要的额外信息。通过这些额外信息来更准确的理解MySQL到底将如何执行给定的查询.
当我们使用全表扫描来执行对某个表的查询,并且该语句的WHERE子句中有针对该表的搜索条件时,在Extra列中会提示上述额外信息
当条件除了索引,还有其他条件,也会是这个提示
Using where
当查询列表以及搜索条件中只包含属于某个索引的列,也就是在可以使用覆盖索引的情况下,Extra列将会提示
Using index
有些搜索条件中虽然出现了索引列,但却不能使用到索引(索引条件下推)
Using index condition
Using filesort(重点优化)
借助临时表来完成一些功能,比如去重、排序之类的,比如我们在执行许多包含DISTINCT、GROUP BY、UNION等子句的查询过程中,如果不能有效利用索引来完成查询,MySQL很有可能寻求通过建立内部的临时表来执行查询.但是建立与维护临时表要付出很大成本
Using temporary(重点优化)
extra☆
EXPLAIN
1、使用like后面紧跟着%,如‘%XXX’
2、表中数据量较少时,不会走索引,而是全表查询
3、有类型转换时索引失效(如:字符串不加单引号)
4、where中索引列使用了函数
5、where中索引列有运算
6、is null可以走索引,is not null无法使用索引
7、复合索引没有用到左列字段(左前缀法则)
8、条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)。要想使用or,又想让索引生效,只能将or条件中的每个列都加上索引
9、索引范围条件右边的列
10、使用不等于(!=、<>;为了充分利用索引,有时候可以将>、<等价转为>=、<=的形式,或者将可能会有<、>的条件的字段尽量放在关联索引靠后位置)
失效场景
对于单列索引,尽量选择针对当前query过滤性更好的索引在选择组合索引时,query过滤性最好的字段应该越靠前越好在选择组合索引时,尽量选择能包含当前query中where子句中更多字段的索引在选择组合索引时,如果某个字段可能出现范围查询,尽量将它往后放
索引失效
1.字段的数值有唯一性的限制2.频繁作为 WHERE 查询条件的字段3.经常 GROUP BY 和 ORDER BY 的列4.UPDATE、DELETE 的 WHERE 条件列5.DISTINCT 字段需要创建索引6.多表 JOIN 连接操作时,尽量不要超过 3 张,对WHERE 条件创建索引,对用于连接的字段创建索引,并且类型必须一致7.使用列的类型小的创建索引8.使用字符串前缀创建索引9.区分度高(散列性高)的列适合作为索引10.使用最频繁的列放到联合索引的左侧11.在多个字段都要创建索引的情况下,联合索引优于单值索引
适合创建索引
1. 在where中使用不到的字段,不要设置索引2. 数据量小的表最好不要使用索引3. 有大量重复数据的列上不要建立索引4.避免对经常更新的表创建过多的索引5.不建议用无序的值作为索引6.删除不再使用或者很少使用的索引7.不要定义冗余或重复的索引
不适合创建索引
设计索引原则
完整的区(1MB)
存储空间被划分为七个部分,分别是:文件头(File Header)[38字节]页头(Page Header)[56字节]最大最小记录(Infimum+supremum)[26字节]用户记录(User Records)[不确定]空闲空间(Free Space)[不确定]页目录(Page Directory)[不确定]文件尾(File Tailer)[8字节]
若干零散的数据页(16KB)
段(逻辑概念)
组成
3.一个页面最少存储2条记录
注意事项
B+Tree:非叶子节点只存key,大大减少了非叶子节点的大小,那么每个节点就可以存放更多的记录,树更矮了,I/O操作更少了。所以B+Tree拥有更好的性能。
B+树
结构
索引
调优(刷盘调优、exists与in、limit深分页优化、io优化:只查询需要的字段,减少数据传输量、索引优化:查询字段全走索引,减少回表查询 )
降低锁粒度、降低锁持有时间(insert与update)
调优
union all效率高于union
列的原子性
行的唯一性
三大范式
单库单表:原始方案单库多表:有效缩小磁盘扫描范围多库多表:提供数据库并行处理能力
单表数据超过1000W(阿里推荐行数超过500W)OR单表数据文件(.ibd)超过20G(阿里推荐单表容量超过2GB)
分库分表标准:
分库分表问题:
适合日志(只关心最近产生的)
适合场景
尾部热点、数据偏斜、资源浪费问题
问题
范围分表(1~10、10~20、20~30)
存在范围查询跨库问题
Hash分表(id % 3 =X)
分库方案
分库分表
主主(双向同步)
热备份、多活、故障切换、负载均衡、读写分离
目标
选择主从一样的机器、一主多从、从库3~5为宜
从库性能差、从库压力大、从库过多
1.减少批量处理
2.优化慢SQL语句
3.实时性要求高的业务强制走主库(或者加上事务注解,也可以强制走主库)
大事务、慢SQL
升级带宽
网络延迟
换高版本MySQL
低版本MySQL(低版本是单线程复制)
主从延迟
1.异步复制(客户端commit后不等从库返回就将结果返回客户端)
2.半同步复制(设置应答从库数量)
3.组复制(基于Paxos协议状态机复制,组内大多数同意)
主从同步数据不一致(主从数据复制方式)
主从
主备(故障切换)
架构
redo log(重做日志、事务持久性)
undo log(回滚日志、事务原子性、保存相反的操作,进行insert会存delete)
bin log(二进制日志、备份数据)
relay log(中继日志)
底层实现
日志
读未提交、读已提交、可重复读、串行化
脏写、脏读、不可重复读、幻读
隔离级别
隐藏字段(row_id、trx_id、roll_pointer)
Undo Log
Read View
实现过程
在MVCC情况下,事务A先开启事务,然后普通 select 一个不存在的值,比如id = 3;这时候是查不到的,然后事务b开启事务insert一个id等于3的记录。接着事务A去update id = 3的记录,再去select id = 3,就可以查出那条记录
另外一种场景是事务A先普通select,然后事务B插入一条数据并提交,这时事务A再来个当前读 select for update就也能查出刚才插入的记录
未解决场景
针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读。针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读。在可重复读的情况下InnoDB很大程度上避免幻读现象,但并不是完全解决。
MVCC(多版本并发控制)
1.读锁/共享锁(S)
2.写锁/排他锁(X)
按照对数据操作类型
1.表级S锁、X锁
2.意向锁
3.自增锁
4.MDL锁(元数据锁)
表级锁
1.记录锁
2.间隙锁
临键锁(Next-Key锁) = 记录锁 + 间隙锁,大致可以这样理解事务的 update 语句中 where 是等值查询,并且 id 是唯一索引,所以只会对 id = 1 这条记录加锁,因此,事务 B 的更新操作并不会阻塞。但是,在 update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。
3.临键锁
4.插入意向锁
行级锁
页级锁
锁粒度划分
悲观锁
乐观锁
对待锁态度
1.隐式锁
2.显式锁
按照加锁方式
1.全局锁
2.死锁
其他
锁
指关于数据库对象(如表、列、索引、视图、存储过程等)的信息(名字、数据类型、长度、精度等),这些信息描述了这些对象的结构和属性
元数据
如查询的两表大小相当,那么用in和exists效率差别不大。如两表中一小,一大,则子查询表大的用exists,子查询表小的用in。not in 和not exists,如查询语句使用了not in 那么内外表都进行全表扫描,无法用到索引;而not exists的子查询依然能用到表上的索引。所以无论哪个表大。用not exists都比not in要快。
exists与in
MySQL自增ID用完了该怎么办?
1.Hash索引不能进行范围查询
2.Hash索引不支持联合索引的最左索引
3.Hash索引不支持Order By排序
4.InnoDB不支持Hash索引
数据库Hash索引与B+数索引区别
MySQL
@Servicepublic class UserService { @Autowired private Wolf1Bean wolf1Bean;//通过属性注入}
基于属性注入(常用)
@Servicepublic class UserService { private Wolf3Bean wolf3Bean; @Autowired //通过setter方法实现注入 public void setWolf3Bean(Wolf3Bean wolf3Bean) { this.wolf3Bean = wolf3Bean; }}
基于 setter 方法注入
@Servicepublic class UserService { private Wolf2Bean wolf2Bean; @Autowired //通过构造器注入 public UserService(Wolf2Bean wolf2Bean) { this.wolf2Bean = wolf2Bean; }}
基于构造器注入
基于注解的3种常规注入方式
DI
IOC
环绕-->before/after-->afterThrow/afterReturn(afterThrow和afterReturn只会执行一个)
执行顺序
aroudBefore...before...add....aroudAfter...after...afterReturn...
无异常
before...after...afterThrow...
有异常无环绕
aroudBefore...before...aroudAfter...after...afterReturn...
有异常有环绕(与无异常情况执行顺序一致)
切面执行顺序
AOP
SpringBoot启动流程
SpringBoot的starter加载
autowire和resource区别
①BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口
③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢
BeanFactory与ApplicationContext
Spring同一个SpringBoot同一个SpringMVC不一定(父子容器)
controller和service实在IOC同一个容器中吗?
1.创建前准备2.创建实例3.依赖注入4.容器缓存5.销毁实例
Bean生命周期
Bean作用域
Bean
监听器 > 过滤器 > 拦截器 > servlet执行 > 拦截器 > 过滤器 > 监听器
拦截器和过滤器执行顺序
三级:存储半成品Bean,未被引用的对象二级:存储半成品,被其他Bean引用的Bean一级:存储完整的Bean对象
异常1.scope=prototype 类型的循环依赖(原型多例导致)2.无法解决构造函数注入(添加@Lazy解决)3.被 @Async 增强的 Bean 的循环依赖(普通的 AOP 代理都是通过 AbstractAutoProxyCreator 来生成代理类的,其实现了 SmartInstantiationAwareBeanPostProcessor。而 @Async 标记的类是通过 AbstractAdvisingBeanPostProcessor 来生成代理的,其没有实现 )
过程
通过三级缓存解决
循环依赖
工厂、模板、代理、单例
应用到设计模式
@EnableAutoConfiguration:@AutoConfigurationPackage --> 作用是将主配置类所在的包下面所有的组件都扫描到Spring容器中。@Import(EnableAutoConfigurationImportSelector.class) -->自动配置:通过源码查看其会加载源码中的META-INF/spring.facotries文件,这个文件中包含了很多配置类。通过加载这个文件里面的类信息,然后这些类会去自动加载配置。
@SpringBootApplication(标记当前类为引导启动类(加载很多启动配置))和三个子注解:@SpringBootConfiguration(包含configuration注解,表示当前类为配置类)@EnableAutoConfiguration@ComponentScan(扫描文件同级包以及子包中的Bean)
启动注解
Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法keyGenerator:key 生成器。 key 和 keyGenerator 二选一使用
@Cacheable
与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
@CachePut
标注在需要清除缓存元素的方法或类上。当标记在一个类上时表示其中所有的方法执行都会触发缓存的清除操作;allEntries是否需要清除缓存中的所有元素。当指定allEntries为true时,Spring Cache将忽略指定的key。需要清除所有的元素,这比单独清除元素更高效
@CacheEvict
可让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict
@Caching
缓存注解
@Autowired@Inject@Reference@Resource
引用类
@Bean@Component@Qualifier@Primary@Controller@RestController@Service@Repository
声明类
@RequestMaping@RequstParam@RequestBody@PathVariable
功能参数类
配置类
常用注解
注解
失效
1.错误的传播特性2.自己吞了异常(手动try...catch)3.手动抛了别的异常(默认只回滚RuntimeException(运行时异常)和Error(错误))4.自定义了回滚异常5.嵌套事务回滚多了
事务不回滚
事务失效
NESTED和REQUIRED_NEW的区别:REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务
NESTED和REQUIRED的区别:REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚
区别
事务传播特性
ISOLATION_DEFAULT(默认同数据库):默认值,表示使用底层数据库的默认隔离级别。大部分数据库通常是ISOLATION_READ_COMMITTED
ISOLATION_READ_UNCOMMITTED(读未提交):该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别
ISOLATION_READ_COMMITTED(读已提交):该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
ISOLATION_REPEATABLE_READ(可重复读):该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读
ISOLATION_SERIALIZABLE(串行化):所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
事务隔离级别
@Transactional
声明式事务
transactionTemplate.execute((status) => {doSameThing... return Boolean.TRUE;})
TransactionTemplate的execute方法
1.避免由于Spring aop问题,导致事务失效的问题
2.能够更小粒度的控制事务的范围,更直观
优点
编程式事务
长事务
1.一致性(Consistency):每次读操作都能保证返回的是最新数据;2.可用性(Availablity):任何一个没有发生故障的节点,会在合理的时间内返回一个正常的结果;3.分区容忍性(Partition-torlerance):当节点间出现网络分区,照样可以提供服务。
含义
CP是强一致性,AP是可用性。zk是要求强一致性,也就是主从机器数据需要一致,如果主机崩了,此时就需要停下来进行选举,选举时间需要至少30s以上,在这期间系统是不可用的,服务不能注册与查询。这对于大中厂来说是不可接受的相比之下,ap就没有主从之分,一台机器可以复制另外一台机器的数据,即使是其中一台机器宕机,其他也可以继续提供服务
实践
BASE理论
延伸出
CAP原则
节点状态
选举超时时间(控制选举过程)
心跳超时时间
领导选举
案例
日志复制
Raft算法
本地消息表
实现
2PC(XA)、3PC
@GlobalTransactional
使用
TC (Transaction Coordinator) - 事务协调者维护全局和分支事务的状态,驱动全局事务提交或回滚
TM (Transaction Manager) - 事务管理器定义全局事务的范围:开始全局事务、提交或回滚全局事务
RM (Resource Manager) - 资源管理器管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚
角色
Seata
TCC事务补偿型(柔性事务)
最大努力通知型(柔性事务、可大并发)
可靠消息+最终一致性(柔性事务、可大并发)
具体解决方案
同一个请求请求了多次,但结果要保证与只请求一次的结果相同
幂等性
悬挂
补偿(反)操作比业务逻辑(正)早到
空补偿
分布式事务
事务
Spring
数据类型
主从同步
3.合理高效的数据结构
4.采用了非阻塞IO多路复用机制.采用epoll模型
单线程为什么快
单机
主从复制
哨兵模式
Redis Cluster采用的是类一致性哈希算法实现节点选择的Cluster会分成了16384(2的14次方) 个Slot(槽位),哈希槽类似于数据分区,每个键值对都会根据它的 key,被映射到一个哈希槽中,具体执行过程分为两大步。1.根据键值对的 key,按照 CRC16 算法计算一个 16 bit 的值。2.再用 16bit 值对 16384(2的14次方) 取模,得到 0~16383 范围内的模数,每个模数代表一个相应编号的哈希槽。每个Redis节点负责处理一部分槽位
集群模式
高可用
部署方式
1.setnx + expire(分开执行)
2.setnx + value值是过期时间
3.set的扩展命令(set ex px nx)
5.Redisson
6.Redisson + RedLock
分布式锁
1. volatile-lru:从设置过期时间的数据集(server.db[i].expires)中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失
2. volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰
3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰。当内存达到限制无法写入非过期时间的数据集时,可以通过该淘汰策略在主键空间中随机移除某个key
4. allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合
5. allkeys-random:从数据集(server.db[i].dict)中选择任意数据淘汰
6. no-enviction:禁止驱逐数据,也就是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略
在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的如果所有数据访问概率大致相等时,可以选择allkeys-random如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了
策略选择
淘汰策略
1.由于Redis在写入日志之前,不对命令进行语法检查,所以只记录执行成功的命令,避免出现记录错误命令的情况
2.而且在命令执行后再写日志不会阻塞当前的写操作。
写后日志
AOF
RDB 快照(Redis DataBase):将某一个时刻的内存数据,以二进制的方式写入磁盘。物理
RDB
混合持久化方式:Redis 4.0 新增了混合持久化的方式,集成了 RDB 和 AOF 的优点。在重写AOF文件的时候生成快照RDB,把当前数据以RDB的方式进行存储,替代原本AOF文件。新增加的指令还是使用AOF追加到文件后面。
混合
持久化
什么是双写?同一份数据,需要写数据库、写缓存。双写很难保证强一致性,可以保证最终一致性。要做到强一致性就需要将所有读写请求用队列串行化,但是性能非常差,降低系统的QPS。没有完美的方案,用到缓存就会存在不一致的情况,需要根据具体业务权衡得失,选择合适业务的方案。
此方案数据不一致的几率比较低,并且实现简单。造成数据不一致的情况:在缓存刚好失效时,有线程查询数据库得到旧值,另外一个线程更新数据库并删除缓存后,前面持有旧值的线程将数据存入缓存,造成数据不一致。
先更新数据库,再删除缓存(推荐方案)
造成数据不一致的情况:删除缓存后,有线程将旧的数据重写回缓存,造成数据不一致。
先删除缓存,再更新数据库
造成数据不一致的情况:多个线程更新缓存时,由于网络问题导致更新顺序错乱,造成数据不一致。
先更新数据库,再更新缓存
先删除缓存,再更新数据库,休眠一段时间,再删除缓存。在高并发场景会影响性能,也极大降低了数据不一致的可能性。
延时双删
使用Canal中间件订阅数据库的binlog,来对缓存更新。造成数据不一致的情况:消费binlog也有一定时间的延迟,还是可能出现数据不一致。
异步更新
上述方案都可能出现更新或删除缓存失败的情况,可以另起线程重试,或加入消息队列重试。
写缓存失败的情况
双写一致性
Redis
解耦
削峰
异步
应用场景
生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可
生产者
消费者消费数据后把消费数据记录在 redis 中,下次消费时先到 redis 中查看是否存在该消息,存在则表示消息已经消费过,直接丢弃消息。
状态判断法
通常数据消费后都需要插入到数据库中,使用数据库的唯一性约束防止重复消费。每次消费直接尝试插入数据,如果提示唯一性字段重复,则直接丢失消息。一般都是通过这个业务判断的方法就可以简单高效地避免消息的重复处理了。
业务判断法
消费者
如何不重复消费
一旦消息投递到队列,队列则会向生产者发送一个通知,如果设置了消息持久化到磁盘,则会等待消息持久化到磁盘之后再发送通知。生产者在发送完消息后不会等待回应,所以confirm机制性能相对比事务机制高。还会伴有超时机制。
Confirm机制
Exchange 设置持久化
Queue 设置持久化
Message持久化发送:发送消息设置发送模式deliveryMode=2,代表持久化消息
RabbitMQ 的消息默认存放在内存上面,如果不特别声明设置,消息不会持久化保存到硬盘上面的,如果节点重启或者意外死掉,消息就会丢失。要想做到消息持久化,必须满足以下三个条件,缺一不可。
消息持久化
队列
消费端消费完成后通知服务端,服务端才把消息删除。不自动,改手动
ACK确认机制
如何不丢失消息
消息积压问题
RabbitMQ
保证严格的有序性:只能使用一个分区来接收消息,类似于MQ中的队列。也使用局部有序的方式,因为kafka主要是使用多线程进行消费。在线程消费之前,创建几个本地队列缓存,将需要按照顺序处理的消息放在同一个队列里,再由线程消费。(比如同一个订单号的消息)
kafka
同一个订单的 binlog 进入到同一个 MessageQueue 中就可以了。因为同一个 MessageQueue 内的消息是一定有序的,一个 MessageQueue 中的消息只能交给一个消费者来进行处理,所以消费者消费的时候就一定会是有序的。(和RabbitMQ差不多)
rocketMQ
无中间件的情况下可以使用数据库对消息进行存储,然后利用时间或者其他id等特性排序,然后再依次处理
消息有序性
消息队列
Sync的实现就是基于monitor 管程,monitor 对象中有owner,WaitSet,EntryList。线程会进入monitor 对象中,查看owner 是否有值,如果没有,则会写入自己的线程id。如果有,则证明已经有线程拿到了锁。就会去EntryList中等待,进入阻塞状态。持有锁的线程如果调用了wait 方法,则会进入WaitSet中,进入等待状态。等出了WaitSet,又会进入EntryList中阻塞
Sync和Reentrantlock都是可重入锁
Reentrantlock可中断
Reentrantlock可锁超时
Reentrantlock可公平
Reentrantlock可多条件变量(避免虚假唤醒)
Synchronized与Reentrantlock
内存屏障
保证变量的内存可见性
禁止指令重排序
volatile
核心线程数
任务队列
最大线程数
AbortPolicy:丢弃任务并抛异常
DiscardPolicy:丢弃任务,但是不抛异常
DiscardOldestPolicy:丢弃队列最老的任务,然后把当前任务加入队列
CallerRunsPolicy:由调用线程处理该任务
拒绝策略
线程工厂
超时时间
超时单位
参数
1.设置核心参数不知道多少合适
线程池
JMM(Java内存模型)
JMM
threadlocal
多线程
1.获取client;2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)//1,构建QueryBuilder请求对象QueryBuilder queryBuilder = QueryBuilders.matchQuery(\"title\
1.获取client;2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)//多索引查询SearchRequest searchRequest = new SearchRequest(new String[]{\"huizi\
1.获取client;2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)//批量查询MultiSearchRequest request = new MultiSearchRequest();SearchRequest firstSearchRequest = new SearchRequest(new String[]{\"huizi\
1.获取client;2.具体实现(查询条件主要封在QueryBuilder中,分页、排序、字段过滤、高亮主要封装在SourceBuilder中)//布尔组合SearchRequest searchRequest = new SearchRequest(\"huizi\");SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();//名字-小米MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery(\"title\
(1)统计某个字段的数量ValueCountBuilder vcb= AggregationBuilders.count(\"count_uid\").field(\"uid\");(2)去重统计某个字段的数量(有少量误差)CardinalityBuilder cb= AggregationBuilders.cardinality(\"distinct_count_uid\").field(\"uid\");(3)聚合过滤FilterAggregationBuilder fab= AggregationBuilders.filter(\"uid_filter\").filter(QueryBuilders.queryStringQuery(\"uid:001\"));(4)按某个字段分组,相当于sql中的group byTermsBuilder tb= AggregationBuilders.terms(\"group_name\").field(\"name\");(5)求和SumBuilder sumBuilder= AggregationBuilders.sum(\"sum_price\").field(\"price\");(6)求平均AvgBuilder ab= AggregationBuilders.avg(\"avg_price\").field(\"price\");(7)求最大值MaxBuilder mb= AggregationBuilders.max(\"max_price\").field(\"price\");(8)求最小值MinBuilder min= AggregationBuilders.min(\"min_price\").field(\"price\");(9)按日期间隔分组DateHistogramBuilder dhb= AggregationBuilders.dateHistogram(\"dh\").field(\"date\");(10)获取聚合里面的结果TopHitsBuilder thb= AggregationBuilders.topHits(\"top_result\");(11)嵌套的聚合NestedBuilder nb= AggregationBuilders.nested(\"negsted_path\").path(\"quests\");(12)反转嵌套AggregationBuilders.reverseNested(\"res_negsted\").path(\"kps \");(13)拼接.script 在后面拼接参数searchSourceBuilder.aggregation(aggregationBuilder); request.source(searchSourceBuilder);
简单使用
在进行倒排索引的时候和该字段的type有关系
倒排索引
底层结构
代表查询,搜索 类似于SQL的select关键字
全局查询,如果是多个词语,会进行分词查询。字母字母默认转换成全小写,进行匹配。
match
查询短语,会对短语进行分词,match_phrase的分词结果必须在text字段分词中都包含,而且顺序必须相同,而且必须都是连续的
match_phrase
单个词语查询,会精确匹配词语,会根据输入字段精确匹配。
term
多个词语查询,精确匹配,满足多个词语中的任何一个都会返回
terms
类似于SQL的ISNULL,字段不为空的会返回出来。
exists
类似于SQL的between and关键字,返回查询
range
一次查询多个id,批量返回。
ids
模糊查询
fuzzy
叶子查询
返回的文档必须满足must子句的条件,并且参与计算分值.
must
返回的文档必须不满足must_not定义的条件
must_ not
返回的文档可能满足should子句的条件。在一个Bool查询中,如果没有must或者filter,有一个或者多个should子句,那么只要满足一个就可以返回。minimum_should_match参数定义了至少满足几个子句
should
返回的文档必须满足filter子句的条件。不会参与计算分值,如果一个查询既有filter又有should,那么至少包含一个should子句,Filter过滤的结果会进行缓存,查询效率更高,建议使用filter。
filter
复合查询
query
代表聚合,类似于SQL的group by 关键字,对查询出来的数据进行聚合 求平均值最大值等
aggs
对搜索出来的结果中的指定字段进行高亮显示
highlight
指定字段对查询结果进行排序显示,类比SQL的order by关键字
sort
对查询结果分页,类似于SQL的limit关键字
from和size
后置过滤器,在聚合查询结果之后,再对查询结果进行过滤
post_filter
字段
匹配到的文档总数
total
_index
_type
_id
_source
衡量文档与查询的匹配程度。默认情况下,首先返回最相关的文档结果,就是说,返回的文档是按照 _score 降序排列的。如果没有指定任何查询,那么所有的文档具有相同的相关性,因此对所有的结果而言 1 是中性的 _score
_score
hits数组
与查询所匹配文档的 _score 的最大值。
max_score
hits
执行整个搜索请求耗费了多少毫秒
took
部分告诉我们在查询中参与分片的总数,以及这些分片成功了多少个失败了多少个。正常情况下我们不希望分片失败,但是分片失败是可能发生的。如果我们遭遇到一种灾难级别的故障,在这个故障中丢失了相同分片的原始数据和副本,那么对这个分片将没有可用副本来对搜索请求作出响应。假若这样,Elasticsearch 将报告这个分片是失败的,但是会继续返回剩余分片的结果。
shard
查询是否超时。默认情况下,搜索请求不会超时。如果低响应时间比完成结果更重要,你可以指定 timeout 为 10 或者 10ms(10毫秒),或者 1s(1秒)。在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。应当注意的是 timeout 不是停止执行查询,它仅仅是告知正在协调的节点返回到目前为止收集的结果并且关闭连接。在后台,其他的分片可能仍在执行查询即使是结果已经被发送了。
timeout
搜索结果
ES
-Djava.compiler=NONE 关闭JIT
JIT(即时编译)
JVM结构
- 标记清除算法的效率不算高- 在进行 GC 的时候,需要停止整个应用程序,用户体验较差- 这种方式清理出来的空闲内存是不连续的,产生内碎片,需要维护一个空闲列表
缺点
标记一清除算法(Mark-Sweep)老年代
- 没有标记和清除过程,实现简单,运行高效- 复制过去以后保证空间的连续性,不会出现“碎片”问题。
- 此算法的缺点也是很明显的,就是需要两倍的内存空间。- 对于 G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着 GC 需要维护 region 之间对象引用关系,不管是内存占用或者时间开销也不小
复制算法(copying)新生代
- 消除了标记-清除算法当中,内存区域分散的缺点,我们需要给新对象分配内存时,JVM 只需要持有一个内存的起始地址即可。- 消除了复制算法当中,内存减半的高额代价。
- 从效率上来说,标记-整理算法要低于复制算法。- 移动对象的同时,如果对象被其他对象引用,则还需要调整引用的地址- 移动过程中,需要全程暂停用户应用程序。即:STW
标记-压缩算法(Mark-Compact)老年代
GC算法
空闲列表
指针碰撞
为每一个线程预先分配一块内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用CAS进行内存分配。
TLAB
CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
CAS
并发问题解决方案
内存分配方式
标量替换:用标量值代替聚合对象的属性值
栈上分配:对于未逃逸的对象分配对象在栈而不是堆
同步消除:清除同步操作,通常指 synchronized
逃逸分析
C2 编译器优化
进入老年代年龄
调整Eden 和幸存者区比例
禁用偏向锁
调优思路
JVM
2.单一职责原则:只做一件事
3.里氏替换原则:父类与子类的行为要保持与预期相符
4.迪米特法则:与外界耦合越少越好
5.接口隔离原则:将大接口拆分成多个以某个事务为边界的小接口
6.依赖倒置原则:面向接口编程
六大设计原则
包装类、缓存、池化技术
应用
享元模式
各种IO流
装饰器模式
策略模式
工厂模式
设计模式
持久连接的特点是,只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。
持久连接
1.客户端与服务端的连接保活技术(连接复用),可以减少 TCP 连接的重复建立和断开所造成的额外开销,减轻服务器端的负载,减少响应时间2.Connection是个逐跳首部,不会被最终传递给目标,因此,如果中间有代理服务器,最终客户端会与代理服务器建立长连接而不是目标服务器
Connection: keep-alive
三次握手
粘包
TCP(传输层):端口IP(网络层):IP地址ARP(网络层):地址解析协议frame:MAC地址(通过ARP协议得到)
TCP、IP、ARP
不知道大家有没有用过2010年左右(或许更早)2G时代的手机,可以运行那种基于J2ME的QQ,能聊天,看个空间,偷个菜(文字版)什么的。这种手机一般都有个缺点就是不能后台运行,一旦去做其他事情(玩游戏,看小说等),QQ就掉线了,就不能收到QQ消息了。如果想要实时接收到女神消息,就要一直保持打开着QQ,不能去做其他事情。这就类似于BIO,阻塞的。后来QQ出了一个手机业务,叫超级QQ(每月10块呢),可以伪实时在线,同时更快的升级。之所以叫他伪实时在线,是因为它的实现方式是:当QQ收到消息时,腾讯会以短信的形式发到手机上,告诉你某某给你发消息了,请及时处理之类的(也可以直接回复短信,QQ上也会自动转发过去,不太相关暂时忽略)。此时再去登录QQ,就能立刻收到消息了。虽然手机同一时刻依然只能做一件事情,但是在没有QQ消息的时候也无需一直等待了,从而从容不铺去做别的事情。也就是非阻塞的了。这个超级QQ的业务就像是NIO:人就是Selector,监听事件。短信就像是一个事件。QQ就像是Channel,建立沟通通道。人看到短信,根据短信内容,从而决定要不要打开QQ,处理消息
理解NIO
计算机网络
切换窗口:按下⌘+Tab最大化窗口(⌃⌘+F):本窗口视觉上占满全部屏幕,存在感最大正常窗口:本窗口视觉上和其他本程序或者其他程序的窗口共用桌面最小化窗口(⌘+M):有两种设置:一种是本窗口在视觉上能见,但是最小,不占用桌面,挪动到Dock的右边,一种是“本窗口”视觉上不可见,最小化(隐藏)到Dock的程序图标中(这个隐藏和下面的⌘+H的主要区别就是⌘+M针对单独的一个窗口,⌘+H是隐藏程序的所有窗口)隐藏程序的所有窗口(⌘+H):整个程序从视觉上消失,不显示在屏幕的任何地方,但只是看不见而已,其他一切照旧关闭窗口(⌘+W):本窗口实际上被关闭,所有和本窗口相关的资源释放,如果文件有编辑会提示保存,但和本程序的其他窗口无关关闭程序(⌘+Q):程序实际上被关闭,本程序所有的窗口关闭,所有资源释放⌘(command)、⎇(option)、⌃(control)、⎋(esc)、⇧(shift)、⌅(enter)⇪(caps lock)、↩(return)、↖(home)、↘(end)、⇟(pagedown)、⇞(pageup)
窗口相关
IDEA
Mac使用
天天向上
0 条评论
回复 删除
下一页