[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/MQ/kafka-remark.html
kafka为什么快
1. 通过生产和缓冲区减少网络开销
生产者发送消息时,会将多条消息打包为一个batch(发送缓冲区)一起发送,等缓冲区大小达到阈值或者一定时间,批量发送
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/MQ/kafka-remark.html
生产者发送消息时,会将多条消息打包为一个batch(发送缓冲区)一起发送,等缓冲区大小达到阈值或者一定时间,批量发送
[半转载]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/dubbo/dubbo-call-flow.html
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/MQ/mq-kafka.html
Kafka 的生产者发送消息时,会将多条消息打包为一个 batch(发送缓冲区)一起发送
,等缓冲区大小达到阈值或者一定时间,批量发送,从而减少网络开销
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/MQ/mq-rabbitmq.html
RabbitMQ是一款开源的消息中间件,采用AMQP(高级消息队列协议)作为底层协议,提供了可靠的消息传递机制、灵活的路由方式以及多种消息发布/订阅模式等特性,被广泛应用于分布式系统、微服务架构等场景中
RabbitMQ支持多种消息发布方式,主要包括以下几种:
[转载]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Spring/Spring-events.html
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Spring/Spring-life-cycle.html
通过loadBeanDefinitions() -> 读取配置文件 -> 解析配置文件 -> 封装成BeanDefinition -> 注册到BeanDefinitionRegistry的beanDefinitionMap中
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Spring/Spring-whiteboard.html
这本经常出错的辣鸡书的读后整理:《Spring源码深度解析》
仅记录,日后有空加描述,嗯,有空的话,有空再说。。。
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Spring/Spring-Circular-Dependencies.html
看源码的同学可以查看我的GitHub 上面在官方的基础上加入了
大量中文注释
,帮助理解
[原创]个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/MQ/mq-common-problem.html
基于Rabbitmq:
[原创]这篇只是做点记录备忘,个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Make-A-Little-Progress-Every-Day/Make-A-Little-Progress-Every-Day.html
优化: 缓存为基础数据,数据量稳定,目前采用CMS回收器,堆空间8g,缓存存于堆中,占约4g,平时MajorGC达4~6s,曾出现高峰gc达52s,
应考虑将缓存存于堆外,减少GC的压力,提升性能(风险点,可能会导致内存溢出)后面自己论证时行不通...因为java对象存到堆外时需要额外进行序列化,经测试,这会导致对象明显变大,浪费的内存有点多,在降本增效的背景下是行不通的
为让Hystrix的熔断降级配置更加合理,会议讨论结果需进行如下优化,
QPS和RT的关系:
对于单线程:QPS=1000/P99
对于多线程:QPS=1000线程数量/P99
对于多线程多接点:QPS=1000单节点线程数量*节点数量/P99
后端对CorsConfiguration配置Access-Control-Max-Age,前端请求时接收到Access-Control-Max-Age,在该有效时间内不会再发出Option请求
CorsConfiguration config = new CorsConfiguration();
config.setMaxAge(600L);
后端返回的Access-Control-Max-Age 大于浏览器支持的最大值 那么取浏览器最大值作为缓存时间
否则取后端返回的Access-Control-Max-Age作为缓存时间
缓存时间内不会再发option请求
源码
- 实现Serializable接口
- 将属性标记为private
- 使用 getter/setter 方法来访问属性
DTO 没有任何显式行为。它基本上有助于通过将域模型与表示层解耦来使代码松散耦合
通常情况都以 keyworkd 字段进行搜索,因为全文索引的分词器不一定能够完全分词,可能会导致搜索不准确,所以一般都是用 keyword 字段进行搜索
HBase是一种面向列的数据库,以row+列名作为key,data作为value,依次存放 假如某一行的某一个列没有数据,则直接跳过该列。对于稀疏矩阵的大表,HBase能节省空间
单机情况下,MySQL的innodb通过redo log和checkpoint机制来保证数据的完整性。因为怕log越写越大,占用过多磁盘,而且当log特别大的时候,恢复起来也比较耗时。而checkpoint的出现就是为了解决这些问题。
描述起来比较麻烦 大概是把下图左边的变成变成右边的
订单–>可以对BSP客户进行分类, 对不同类型客户,可以特别推荐一些增值服务或产品
----->根据寄件商品的类型为其推荐增值服务
扩展信息…没啥用
增值服务
订单状态<—监控? 存在很多很久不揽收的 进行告警通知小哥? 让其决定是取消,还是让其再设定一个较远的预约时间
FVP所有状态<–
运单号生成
运单<—
产品变更<— 变更监控? 至少可以记录一下产品变化以及运费变化
简单来说,就是客户端启动后,会在zk注册一个watcher监听某个我们关心的节点Node的变化;
同时客户端会把这个watcher存到本地的WatcherManager里;
当这个节点出现变化,zk会通知到对应的客户端,调用该watcher的回调方法(process方法)。以此方式实现动态配置平台的配置刷新下发、分布式锁等功能
term 查询是如何工作的? Elasticsearch 会在倒排索引中查找包括某 term 的所有文档
Segments 是不可变的(immutable):
Segments Delete?当删除发生时,Lucene 做的只是将其标志位置为删除,但是文件还是会在它原来的地方,不会发生改变。
Segments Update?所以对于更新来说,本质上它做的工作是:先删除,然后重新索引(Re-index)
随处可见的压缩:Lucene 非常擅长压缩数据,基本上所有教科书上的压缩方式,都能在 Lucene 中找到
缓存所有的所有:Lucene 也会将所有的信息做缓存,这大大提高了它的查询效率
Cluster由多个Node节点组成
每个Node节点由多个索引Index组成
每个索引由多个Share组成
每个Share(又叫Lucene Index)存在于集群中多个Node中,具体有多少个Share,看你索引的配置,由多个Segment组成
每个Segment(又称Mini索引),每个Segment都是不可变的,只会生成一个增量Segment(含修改后的/新增的数据),原来的数据只能标记为删除,当Segment多了之后会做merge合并操作;
进行索引文档后,看是否有达到flush条件的Segment,存在就flush该Segment将该数据刷到硬盘中,没找到就创建一个Segment??
参考 -> ES lucene写入流程,segment产生机制源码分析
–以下为内连接,驱动表为t1,如果t1通过where过滤完还有2条数据,那么会去t2表查询2次
select * from t1 join t2 where ***;
select * from t1 inner join t2 where ***;
select * from t1 cross join t2 where ***;
(以上等价于)select * from t1,t2 where ***;
select * from t1 left join t2 on t1.a=t2.a where ***; – 为外连接
on实际是给外连接用的,在内连接使用的话和where的作用是一样的;
在外连接中使用,如果匹配不上,不会过滤掉驱动表原有的值;如果要过滤掉这种连接不上的值,可以再加个where条件过滤
驱动表t1只会被访问一次,被驱动表t2会被访问多次
因类型转换导致不走索引
create table t (
id int(20) primary key AUTO_INCREMENT,
cell varchar(20) unique
)engine=innodb;
建表的时候cell定义的是字符串类型
通过explain,基本已经可以判断:
update t set cell=456 where cell=55555555555;
并没有和我们预想一样,走cell索引进行查询,而是走了PK索引进行了全表扫描。
where语句cell类型与索引的不匹配,不会走索引,最终会走全表;
类型转换,会导致全表扫描,出现锁升级,锁住全部记录
select_type:SIMPLE
这是一个简单类型的SQL语句,不含子查询或者UNION。
type:index
访问类型,即找到所需数据使用的遍历方式,潜在的方式有:
(1)ALL(Full Table Scan):全表扫描;
(2)index:走索引的全表扫描;
(3)range:命中where子句的范围索引扫描;
(4)ref/eq_ref:非唯一索引/唯一索引单值扫描;
(5)const/system:常量扫描;
(6)NULL:不用访问表;
上述扫描方式,ALL最慢,逐步变快,NULL最快。
possible_keys:NULL
可能在哪个索引找到记录。
key:PRIMARY
实际使用索引。
ref:NULL
哪些列,或者常量用于查找索引上的值。
rows:5
找到所需记录,预估需要读取的行数。
show engine innodb status;
学习自: ES 在数据量很大的情况下(数十亿级别)如何提高查询效率
filesystem cache
中filesystem cache
,数据存HBase;通过ES进行条件查询,获取docId,用该docId去查HBaseindex -> type -> mapping -> document -> field
实例: order~2020-08-02/order/_mapping/记录/字段
翻译: 索引名称/表名/表结构/记录/字段
Topic&消费组:
一个Topic的一个Partition只能一个Consumer Group的一个节点消费
一个【Topic】对应多个【Partition】(文件)
消息大小限制:
一条消息 默认最大只能为1000000B(976.56 kB),所以一般规定不允许发送>900k的消息
0.8版本 (相对历史版本 支持了Replication高可用 )
当时只有Consumer Coordinator
coordinator需要依赖于ZK,通过zk监听/consumers//ids变化 与 brokers/topic的数据变化决定是否要 rebalanced
rebalanced后,consumer自己决定自己要消费哪些Partition,然后抢先在/consumers//owners/ / 下注册(通过这种方式实现一个Topic的一个Partition只能一个Consumer Group的一个节点消费`)
同时,各个Consumer Coordinator还需要进行位移的提交弊端: 消费者自己决定消费哪些分区,各个Consumer Coordinator还需要进行位移的提交
并且分区的决定与位移的提交都需要依赖于ZK0.8.2版本
0.8.2版本开始同时支持将 offset 存于 Zookeeper 中与将offset 存于专用的Kafka Topic 中,但是需要High Level API的支持,且BUG较多,目前公司用的还是Low Level Api0.9.x版本
新增Group Coordinator,存在于Broker端
代替了0.8.x版本的zk,每个消费组对应一个,负责每个消费者位移的提交&分区消费的决策0.10+
消息结构添加了时间戳,可根据这个时间戳实现延迟队列0.11.x版本
新增了对【幂等】、【事务】的支持(依赖于Producer幂等) (exactly-once)
3.High Level和Low Level
将仅支持zookeeper维护offset方式的高级抽象的API称为 Low Level Api,高度抽象,将支持kafka broker 维护offset方式抽象低的API的称为 High Level API ,
High level consumer vs. Low level consumer
官方解释(看最下面的描述)
每个Topic的每个Partition对每个生产者都维护了一套ID(UUID)
生产者每次发送消息时候,消息体都带上这个ID+1,以此Broker可得知:
- 当消息的squence number等于broker维护的squence number + 1,表示消息有序且第一次消费
- 当消息的squence number小于或等于broker维护的squence number,表示重复消费额
- 当消息的squence number等于broker维护的squence number + n(n > 1),表示存在消息丢失
参考1:Kafka Producer 幂等的原理
参考2:上半场的幂等性设计
一条消息会根据Key被路由到某一【Partition】(key=0对应分区0);如果没有指定key,消息会被均匀的分配到所有分区;目前我们封装的方案是,不管有没有Key,都会被随机打乱到每个分区)
每隔 topic.metadata.refresh.interval.ms 的时间,随机选择一个partition。这个时间窗口内的所有记录发送到这个partition。发送数据出错后也会重新选择一个partition
对key求hash,然后对partition数量求模: Utils.abs(key.hashCode) % numPartitions
代码: kafka.producer.async.DefaultEventHandler#handle
At most once 消息可能会丢,但绝不会重复传输(例:读到先Commit,再处理)
At least one 消息绝不会丢,但可能会重复传输(例:读到先处理,再Commit)
Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的 (0.8.2版本还不支持)
高可用
kafka默认会重试3次
零碎小点
SF-Sentinel中配置Redis链(mymaster1,mymaster2,mymaster3),然后获取每一条链的Master,进行初始化Redis连接池
原生的Sentinel中配置Redis链,然后获取该链的Master,进行初始化Redis连接池
Jedis初始化时会初始化160个虚拟节点,160个虚拟节点通过Map(Map<ShardInfo
, R> resources)映射到实际的Redis-Master节点
Jedis在Set key时会对Key分片计算(计算落在160个节点的哪一个),然后再根据虚拟节点与实际节点的映射,把指令发给实际的节点
参考代码:
redis.clients.util.Sharded#initialize
redis.clients.util.Sharded#getShard(byte[])
空
pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d)
inverse只有在非many方才有,也就是many-to-many或者one-to-many的set,List等
Returns a count of the number of non-NULL values of expr in the rows retrieved by a SELECT statement. The result is a BIGINT value.
返回行中 expr 的非 NULL 值的计数
5.7.18以后,两个函数执行计划都是一样的
- 如果该表没有任何索引,那么会扫描全表,统计行数
- 如果该表只有一个主键索引,没有任何二级索引的情况下,那么通过主键索引来统计行数的
- 如果该表有二级索引,则会通过占用空间最小的字段的二级索引进行统计
如果字段定义为not null,则按行累加,如果允许有null,则会把值取出来判断一下是不是null,将不是null的值累加返回。
count(*)=count(1)>count(primary key)>count(column)
MySQL原理:count(*)为什么这么慢,带你重新认识count的方方面面
通过减少数据量,提升性能
(base表)
(ext表)
(备选)
- base表和ext表不能Join,因为一旦Join了,那么两张表就出现了耦合,这不利于日后拆表到别的数据库实例上
- Join很消耗数据库的性能
(分布式场景下,瓶颈往往是数据库)
(降低每行记录大小)
因为减少单表数据量还可以充分利用数据库缓存,减少磁盘IO
MyISAM:
InnoDB:
(为主键索引,没主键时会用第一个非空普通索引,都没有会生成一个基于行号的聚集索引)
select * from t where name=‘lisi’;
会先通过name辅助索引定位到B+树的叶子节点得到id=5,再通过聚集索引定位到行记录
违反唯一索引场景:
MyISAM会出现一个update语句,部分执行成功,部分执行失败(因为不支持事务)
3台机器的一个集群 ,shardingCount=10 ,分片结果为:1=[0,1,2,9], 2=[3,4,5], 3=[6,7,8] (参考
AverageAllocationJobShardingStrategy
)
如果本机的数据分片分到了多个分片(即一个JVM进程分到了多个分片),则Elastic-Job会为每一个分片去启动一个线程来执行分片任务
每个任务对应一个线程池,其默认线程数为: 2*逻辑核心数(参考
DefaultExecutorServiceHandler
)
线程池配置为:new ThreadPoolExecutor(threadSize, threadSize, 5L, TimeUnit.MINUTES, workQueue, new BasicThreadFactory.Builder().namingPattern(Joiner.on("-").join(namingPattern, "%s")).build());
(参考ExecutorServiceObject
)
要注意单机线程数要 大于 单机获取到的分片数 - 参考 《Elastic job 线程模型 源码分析》
一个jvm实例 处理多个 job , 每个job 在该实例上分片数又大于逻辑核心数*2 的数量
随着job不断增加 , 单个job任务执行时间可能会变长 ,有可能超过平时的任务完成超时时间 ,造成任务失败举个例子:
如果一台机器 处理器数 2 , 线程池 就是 4 , 如果 分片是 5 , 就是说 一个分片会被排队 ,实际完成时间 >2 个分片 完成时间
【简单的HA】版失效转移 (默认)
在作业节点下线,或者zk的session超时(默认60s)时,会在下一轮任务分片时,把这个该问题节点的分片分给别的正常节点进行作业 (可能会存在作业重复处理的问题
)
【真正的】'失败’转移 (需要开启)
当failover(默认值为false)
配置为true
时,才会启动真正的失效转移;
当failover(默认值为false)
和 monitorExecution(默认值是true)
这两个配置都为true时 只有对monitorExecution
为true
的情况下才可以开启失效转移;
如果任务1在A节点执行【失败】,那么会【转移】给别的存活的节点【竞争】执行这个任务1;
- InnoDB锁机制是基于索引建立的
- 如果SQL语句中匹配不到索引,那么就会升级为表锁
1 | -- id 列为主键列或唯一索引列 |
通过唯一索引实现的记录锁,只会锁住当前记录(必须为
=
不然会退化为临键锁
)
- 间隙锁只有在事务隔离级别 RR(可重复读)中才会生效.
- 为非唯一索引组成(如class,age等)
1 | select student where age>26 and age<28 lock in share mode ; -- 这里以读锁为例 |
唯一索引
,也可以是非唯一索引
,对其都以间隙锁的形式进行锁定(以唯一索引匹配,并且只匹配到一条数据除外
)tno(唯一索引) | tname | tsex | tbirthday | prof | depart | age(非唯一索引) |
---|---|---|---|---|---|---|
858 | 张旭 | 1 | 1969-03-12 | 讲师 | 电子工程系 | 25 |
857 | 张旭 | 女1 | 1969-03-12 | 讲师 | 电子工程系 | 25 |
856 | 张旭 | 男 | 1969-03-12 | 讲师 | 电子工程系 | 25 |
831 | 刘冰 | 女 | 1977-08-14 | 助教 | 电子工程系 | 29 |
825 | 王萍 | 女 | 1972-05-05 | 助教 | 计算机系 | 28 |
804 | 李诚 | 男 | 1958-12-02 | 副教授 | 计算机系 | 26 |
其中有唯一索引的
临键
为:
(-∞,804]
(804,825]
(825,831]
(831,856]
(856,857]
(857,858]
(858,+∞]
其中有非唯一索引的
临键
为:
(-∞,25]
(25,26]
(26,28]
(28,29]
(29,+∞]
非唯一索引
临键锁验证1 | -- session1 |
这时候会锁定非唯一索引的
临键
(25,29]
所以我们测试更新age=25–>成功 插入age=27阻塞 更新age=29阻塞 插入age=30成功即可验证
1 | -- session2 |
唯一索引
临键锁验证1 | -- session1 |
根据上面的sql,我们匹配到
唯一索引
临键锁为:(825,857]
所以我们测试更新tno=825–>成功 更新tno=857阻塞 更新age=858成功即可验证
1 | -- 更新tno="825"-->成功 |
@EnableAspectJAutoProxy(exposeProxy = true)
AopContext.serCurrentProxy(proxy)
把当前代理设置到ThreadLocal中((AService)AopContext.currentProxy()).b()
进行调用了(应该不能叫做代理)
- 需要添加配置:
- 代码添加:
@EnableLoadTimeWeaving(aspectjWeaving=ENABLED)
或<context:load-time-weaver aspectj-weaving="enable" />
- 添加JVM参数
-javaagent:类加载器代理路径
LTW(LoadTime Weaving)
加载时织入。在通过JVM加载类时候会先调用ClassTransformer
的transform()
进行字节码替换后才会进行加载。
静态AOP
通过LTW
可以实现静态AOP
增强,加载到的类就是已经增强后的代码。这样我们调用方法的时候,直接就是调用了增强后的方法,比起动态代理的调用,更加地高效。
上述流程大致如下所示:
graph TD A[Target] B[增强后的字节码] C[加载后的代码] D[注入后的Bean] E[调用方] A--ClassTransformer的transform方法进行字节码植入-->B B--JVM加载-->C C--Spring使用,创建/注入Bean-->D E--方法调用-->D
对于this.b()这些类实例的内部调用,b()实际上是无事务的
但是可以用((AService)AopContext.currentProxy()).b()
结合@EnableAspectJAutoProxy(exposeProxy = true)
这样b()就包裹在事务里了
由于使用final,static,private修饰符的方法都不能被子类覆盖,相应的,这些方法将不能被实施的AOP增强
@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。
【Time: user=0.71 sys=0.01 real=0.02 secs】
- user表示:本次GC过程中【所有线程】在用户态消耗的时间总和
- sys表示: 本次GC过程中 【所有线程】在内核态所消耗的时间总和
- real表示:本次GC过程中,实际GC消耗的时间
- MVCC:多版本并发控制(Multi-Version Concurrency Control)
- 优势:查询速度快,并发环境尤是。对于大多数读操作,我们只需要通过MVCC进行简单的查询操作,而不需要获取任何一个
锁
。- 劣势:需要多存储数据。对每一条记录都需要存储所有版本的数据
- MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下
- READ UNCOMMITED不是MVCC兼容:因为这个模式只能读取到最新的数据
- SERIABLABLE也不与MVCC兼容:因为每个读操作都需要为读到的数据上锁
- 以下摘自《五分钟搞清楚 MVCC 机制》
每一条数据库表记录,都隐藏2个字段
执行insert语句插入的时候,会把当前的事务ID写到该记录的数据行的版本号 (DB_TRX_ID)中:
1 | begin;-- 获取到全局事务ID 假设为2 |
id | test_id | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|
5 | 68 | 2 | NULL |
6 | 78 | 1 | 3 |
修改数据库记录的时候
1 | begin;-- 获取全局系统事务ID 假设为 10 |
id | test_id | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|
5 | 68 | 2 | 10 |
6 | 78 | 1 | 3 |
5 | 22 | 10 | NULL |
查询的时候需要根据数据行的版本号 (DB_TRX_ID)
和 删除版本号 (DB_ROLL_PT)
二者进行数据数据筛选,需要同时满足以下规则:
数据行的版本号 (DB_TRX_ID)
<= 当前事务删除版本号 (DB_ROLL_PT)
> 当前事务1 | begin;-- 假设拿到的系统事务ID为 10 |
id | test_id | DB_TRX_ID | DB_ROLL_PT |
---|---|---|---|
6 | 22 | 10 | NULL |
Spring会拿到所有Lifecycle实现类,然后委托DefaultLifecycleProcessor进行逐个处理
1 | com.fpx.wms.service.impl.InstockServiceImpl |
在单例A里 可能依赖到原型类型B,这时候如果用普通的Autowrite不能拿到原型的B,这时候就需要使用@Lockup了
即,合适优于先进,简单优于复杂,演化优于一步到位
→能不分,尽可能不分
1 | 策略模式针对一个命令,多种实现方式 |
1 | 命令模式针对多个命令,每种命令都有各自的实现 |
1 | 命令模式等于菜单中的复制,移动,压缩等,而策略模式是其中一个菜单的例如复制到不同算法实现。 |
1 | 需要调用方告知具体的策略 |
1 | 需要调用方告知使用哪个[代理类] |
1 | 需要调用方告知[被代理类]及其接口 |
1 | 以上模式都需要客户端告知具体的[策略]/[代理]/[被代理者] |
场景现有3个类相互依赖,依赖关系分别为:
graph LR A-->B B-->C C-->A
场景细分为3种
根据Spring初始化方式,Spring容器会按照顺序创建"无属性"的A放到“当前创建Bean池”中,同理再B、C、A,但是在再次创建A的时候发现“当前创建Bean池”已经存在A了,那么这时候会报错循环依赖
单例
)没毛病,在set的时候对象ABC都已经实例化放在Spring缓存了好了
prototype
)
prototype
修饰的bean不会被Spring缓存,都是使用的时候当场创建的
结合上面的循环依赖问题,Setter出现问题的概率会低一些 推荐使用Setter注入
正例:
以List为例子,先得获取他的Iterator,通过iterator来进行修改操作
反例:
使用增强型foreach进行add/remove操作:
因为增强型foreach实际上是使用iterator实现的java语法糖:
1 | List<String> userNames = new ArrayList<String>() {{ |
编译后
1 | List<String> userNames = new ArrayList<String>() { |
1 | 所以实际上for (String userName : userNames) 这里每次都会去调用itertor.next() |
fail-fast:
防止多线程同时对集合修改的一种机制
modCount:
****List**中的一个成员变量。它表示该集合实际被修改的次数
expectedModCount:
是 ****List**中的一个内部类——Itr中的成员变量
tomcat的请求线程会交给线程池的线程处理
超过线程池会排队或者降级,一个线程池对应的服务挂了,不会影响别的线程池的服务
只作为开关
并发数超过X服务的信号量,多出来的Tomcat请求将会被拒绝
StringBuilder在高性能场景下的正确用法(文中代码打错了一些字…)
从需求上说,分布式锁要求是不一样的:
- 如果是用于聊天等社交场景,那么可以使用AP的分布式锁:Redis
- 如果是用于交易等不允许极端情况下获取锁不一致的,那么AP的Redis锁是不能接受的,这时候一定得用CP的分布式锁,如:etcd Zookeeper这一类
每个线程都有一个ThreadLocalMap,ThreadLocalMap以Entry的形式保存着各个线程自己的数据
Entry为一个WeakReference,以你new的ThreadLocal为Key
基于2.当你new的ThreadLocal没被外部强引用时,线程该Thread下对应该ThreadLocal的Entry会在下次GC被回收
当一条线程创建了多个ThreadLocal,多个ThreadLocal放入ThreadLocalMap 会极大地增加冲突概率
ThreadLocalMap对冲突的处理方式与普通HashMap的链表处理不一样,而是以原来的位置+1,一直寻找到没有冲突的地方存入
ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的
ThreadLocal.remove(),移除ThreadLocalMap与Entry的关系,释放内存
常量池包含:
graph LR A[编译后的class文件中的class常量池] B[运行时常量池*N] C[字符串常量池] D[Java代码运行] A-->B D-->B B-->C
(范围为一个SqlSession)
有Session/STATEMENT级别:
基于mapper
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享
举个例子:
- 流量充值涉及到订单支付,金钱交易严格用tcc;
- 订单支付完后要给用户增加积分,这个必要成功,用最终消息一致性方案;
- 订单支付完后还要给用户发送一条短信,短信一般是跟电信运营商的第三方接口对接,有可能成功有可能失败,用最大努力通知方案