每天进步一点点(持续更新)

[原创]这篇只是做点记录备忘,个人理解,请批判接受,有误请指正。转载请注明出处: https://heyfl.gitee.io/Make-A-Little-Progress-Every-Day/Make-A-Little-Progress-Every-Day.html

2022-10-25

目前本地缓存使用的方式

  • 订运单系统: SF自研的类Ehcache框架,存储的内容不是特别多,都是一些网点月结卡号信息,所有对象存在于Map,占200m左右
  • SISP系统: 使用了Caffeine,存储的内容,存储的内容很多,包括员工表(900m),人员表(bdus 800m) 客户表(1.2g),用户及权限相关表(500m)
    • 关联后约占4g内存,目前采用Caffeine默认存储方式,启动即全量加载(极个别采用懒加载方式加载),全部存在于Map中,即堆存储

优化: 缓存为基础数据,数据量稳定,目前采用CMS回收器,堆空间8g,缓存存于堆中,占约4g,平时MajorGC达4~6s,曾出现高峰gc达52s,
应考虑将缓存存于堆外,减少GC的压力,提升性能(风险点,可能会导致内存溢出) 后面自己论证时行不通...因为java对象存到堆外时需要额外进行序列化,经测试,这会导致对象明显变大,浪费的内存有点多,在降本增效的背景下是行不通的

参考 guava、caffeine、ohc(堆外缓存)详解

2022-10-20

Spring中最常用的11个扩展点

2022-09-02

Hystrix熔断配置

为让Hystrix的熔断降级配置更加合理,会议讨论结果需进行如下优化,

  1. 为每个已有Hystrix熔断的接口设置最高并发配置(execution.isolation.semaphore.maxConcurrentRequests),配置200~500之间,具体计算方式
    单节点线程数 = QPS /节点数/ ( 1000 / 被熔断方法的P99耗时ms )
    翻译:方法单节点线程并发数 = QPS /节点数/1s内该方法能执行次数
  2. 把Hystrix配置提取到disconf,重启生效,无需发版

QPS和RT的关系:
对于单线程:QPS=1000/P99
对于多线程:QPS=1000线程数量/P99
对于多线程多接点:QPS=1000
单节点线程数量*节点数量/P99

2022-08-11

前端跨域请求减少Option请求

后端对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请求
源码

2022-06-03

POJO、JavaBeans、BO、DTO 和 VO 、DO之间的区别

  • POJO,也称为普通旧 Java 对象,是一个普通的 Java 对象,它没有对任何特定框架的引用。
  • JavaBean/BO:有约束的POJO,国内用法一般为BO
    • 实现Serializable接口
    • 将属性标记为private
    • 使用 getter/setter 方法来访问属性
  • DTO:也称为数据传输对象,封装值以在进程或网络之间传输数据。

    DTO 没有任何显式行为。它基本上有助于通过将域模型与表示层解耦来使代码松散耦合

  • VO:外国作为值对象,不过国内用法是用来做视图对象,主要是返回前端用的对象
  • DO(Data Object) ,持久化对象,数据库对象

2021-06-01

System.arraycopy方法和Arrays.copyOf()

  • System.arraycopy方法:是本地方法,如果是数组比较大,那么使用System.arraycopy会比较有优势,因为其使用的是内存复制,省去了大量的数组寻址访问等时间
  • Arrays.copyOf()
    1. Arrays.copyOf()在System.arraycopy()实现的基础上提供了额外的功能
    2. 会创建新数组
    3. 允许与原数组类型不同,但是这样会调用JVM的反射,性能较差

2021-05-20

ES 分词

  • text:用于全文索引,该类型的字段将通过分词器进行分词,最终用于构建索引
  • keyword:不分词,只能搜索该字段的完整的值,只~~~~用于条件精准查询

通常情况都以 keyworkd 字段进行搜索,因为全文索引的分词器不一定能够完全分词,可能会导致搜索不准确,所以一般都是用 keyword 字段进行搜索

2021-02-26

HBASE 列族,RowKey

HBase是一种面向列的数据库,以row+列名作为key,data作为value,依次存放 假如某一行的某一个列没有数据,则直接跳过该列。对于稀疏矩阵的大表,HBase能节省空间

  • 表是行的集合
  • 行是列族的集合
  • 列族是列的集合
  • 列是键值对的集合

2021-02-08

最近要搞懂的事情

  1. redo log和checkpoint机制

单机情况下,MySQL的innodb通过redo log和checkpoint机制来保证数据的完整性。因为怕log越写越大,占用过多磁盘,而且当log特别大的时候,恢复起来也比较耗时。而checkpoint的出现就是为了解决这些问题。

  1. mysql主从架构
    Master-Slave(主挂了可能会丢失一部分数据)和Group Replication 的架构(mgr采用paxos协议实现了数据节点的强同步,保证了所有节点都可以写数据,并且所有节点读到的也是最新的数据)

2021-02-07

稍稍记录一下2020年干过的那些P大点的事

  1. 协助完成Redis降存储–>阉割无用字段,(没用上压缩) ,以前是存储整个对象,现在是存储个别有用的字段, 降低了60%~80%的存储
    • 综合订单、CX、操作运单、公共redis,共节省redis资源9034G
  2. 团队共同完成灰度发版–>中间加应用,数据先到分流应用,通过分流应用把对应城市、网点的数据分流到对应的应用
  3. 独立完成ES查询优化–>优化判断索引逻辑,指定查询具体某个分片,提高性能550倍
  4. 生产某个节点线程数过多及CPU高–>dump&排查源码 elasticJob的采用了流式处理,有某个节点的一些线程一直能查到数据,就一直继续工作了;
  5. elastic-job流式处理导致最终只有一个线程在跑的问题排查&修复 —> 同上
  6. 重试模块加入根据重试次数逃生逻辑,防止异常时空跑把系统跑死
  7. 优化ES存储订单数据的结构 —> 4亿+数据量减少到只剩下5kw数据量,降低了十倍左右
  • 把orderExtendInfoList类型改为keyword类型(原来为嵌套类型), 内部额外存储一个作为索引用的值为原orderExtendInfo的key和value对应的Map

描述起来比较麻烦 大概是把下图左边的变成变成右边的

es索引优化1

数据造就业务—>咋玩???

  1. 目前手上有啥数据:
  • 订单–>可以对BSP客户进行分类, 对不同类型客户,可以特别推荐一些增值服务或产品
    ----->根据寄件商品的类型为其推荐增值服务
    扩展信息…没啥用
    增值服务

  • 订单状态<—监控? 存在很多很久不揽收的 进行告警通知小哥? 让其决定是取消,还是让其再设定一个较远的预约时间

  • FVP所有状态<–

  • 运单号生成

  • 运单<—

  • 产品变更<— 变更监控? 至少可以记录一下产品变化以及运费变化

2021-01-25

一、ZK事件回调原理 – 最近用得少老是忘记,还是记录一下吧

简单来说,就是客户端启动后,会在zk注册一个watcher监听某个我们关心的节点Node的变化;
同时客户端会把这个watcher存到本地的WatcherManager里;
当这个节点出现变化,zk会通知到对应的客户端,调用该watcher的回调方法(process方法)。

以此方式实现动态配置平台的配置刷新下发、分布式锁等功能

2020-12-14

一、 ElasticSearch原理

图解ElasticSearch原理

  • 精确查询

term 查询是如何工作的? Elasticsearch 会在倒排索引中查找包括某 term 的所有文档

  • Lucene Index(包含多个Segments):

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合并操作;

  • Segments的创建&刷新 (没玩大数据 大概了解就行了)

进行索引文档后,看是否有达到flush条件的Segment,存在就flush该Segment将该数据刷到硬盘中,没找到就创建一个Segment??
参考 -> ES lucene写入流程,segment产生机制源码分析

2020-11-18

一、 MYSQL是怎么运行的 – 连接原理

–以下为内连接,驱动表为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会被访问多次

2020-09-24(好久没做记录了…)

一、 DB 看似匹配到索引,但是没有走索引的情况(注意事项)

因类型转换导致不走索引

摘自本文结论内容

  1. 建表语句cell的数据类型为Varchar

create table t (
id int(20) primary key AUTO_INCREMENT,
cell varchar(20) unique
)engine=innodb;
建表的时候cell定义的是字符串类型

  1. Explain
update索引执行计划

通过explain,基本已经可以判断:
update t set cell=456 where cell=55555555555;
并没有和我们预想一样,走cell索引进行查询,而是走了PK索引进行了全表扫描。

  1. 实际问题

where语句cell类型与索引的不匹配,不会走索引,最终会走全表;

  1. 结论

类型转换,会导致全表扫描,出现锁升级,锁住全部记录

二、 DB 执行计划查看&&死锁排查

执行计划

update索引执行计划
  • 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
    找到所需记录,预估需要读取的行数。

死锁排查

  • 有权限的mysql账户执行:

    show engine innodb status;

  • 根据查到的结果 分析LATEST DETECTED DEADLOCK里的内容

三、ES 提高查询效率

学习自: ES 在数据量很大的情况下(数十亿级别)如何提高查询效率

ES查询原理
  1. 把尽可能多的索引放在filesystem cache
  2. 不做复杂查询(Join等),如果有这样的需要,应以设计得更好的document(记录)来实现简单查询(单表)
  3. 使用ES+hbase架构:
    ES存索引,索引全放在filesystem cache,数据存HBase;通过ES进行条件查询,获取docId,用该docId去查HBase
  4. 禁止深度查询,使用scrollApi或search_after代替

四、ES存储结构

ES存储结构

index -> type -> mapping -> document -> field

实例: order~2020-08-02/order/_mapping/记录/字段
翻译: 索引名称/表名/表结构/记录/字段

2019-12-19

Kafka

  1. 基础点

Topic&消费组:
一个Topic的一个Partition只能一个Consumer Group的一个节点消费
一个【Topic】对应多个【Partition】(文件)
消息大小限制:
一条消息 默认最大只能为1000000B(976.56 kB),所以一般规定不允许发送>900k的消息

  1. 版本区别:

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还需要进行位移的提交
并且分区的决定与位移的提交都需要依赖于ZK

0.8.2版本
0.8.2版本开始同时支持将 offset 存于 Zookeeper 中与将offset 存于专用的Kafka Topic 中,但是需要High Level API的支持,且BUG较多,目前公司用的还是Low Level Api

0.9.x版本
新增Group Coordinator,存在于Broker端
代替了0.8.x版本的zk,每个消费组对应一个,负责每个消费者位移的提交&分区消费的决策

0.10+
消息结构添加了时间戳,可根据这个时间戳实现延迟队列

0.11.x版本
新增了对【幂等】、【事务】的支持(依赖于Producer幂等) (exactly-once)

3.High Level和Low Level

  1. 消息(生产)幂等

每个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:上半场的幂等性设计
  1. 消息的分区选择:

一条消息会根据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

  1. Kafka支持的消息发送模式

At most once 消息可能会丢,但绝不会重复传输(例:读到先Commit,再处理)
At least one 消息绝不会丢,但可能会重复传输(例:读到先处理,再Commit)
Exactly once 每条消息肯定会被传输一次且仅传输一次,很多时候这是用户所想要的 (0.8.2版本还不支持)

  1. 高可用
    kafka默认会重试3次

  2. 零碎小点

    1. Kafka实现的是客户端软负载: 让producer决定丢到哪个partition里
    2. Consumer端仅支持pull模式,这也有利于让Consumer端决定消费速率
    3. Consumer不能消费太久(如Sleep),因为Kafka会认为程序宕了,分区会重新进行分配,把消息分给其他的Consumer (相关配置项: max.poll.interval.ms)
    4. Consumer每次可从Kafka取max.poll.records条数据进行处理
    5. 如果想要消息有序 那么就得保证同个业务key的消息都是发到1个分区里

Redis-Sentinel&Jedis

通过Sentinel集群获取Redis主节点原理

SF-Sentinel中配置Redis链(mymaster1,mymaster2,mymaster3),然后获取每一条链的Master,进行初始化Redis连接池
原生的Sentinel中配置Redis链,然后获取该链的Master,进行初始化Redis连接池

Jedie的Key是如何被存入Redis的某个节点的

参考:Jedis之ShardedJedis一致性哈希分析

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[])

Redis-Sentinel模式是如何扩容的

Jedis一致性分析

Jedis一致性分析

2019-11-28

git rebase -i HEAD~2

pick:保留该commit(缩写:p)
reword:保留该commit,但我需要修改该commit的注释(缩写:r)
edit:保留该commit, 但我要停下来修改该提交(不仅仅修改注释)(缩写:e)
squash:将该commit和前一个commit合并(缩写:s)
fixup:将该commit和前一个commit合并,但我不要保留该提交的注释信息(缩写:f)
exec:执行shell命令(缩写:x)
drop:我要丢弃该commit(缩写:d)

Hibernate 基本知识

  • inverse属性表示本实体是否拥有主动权
    inverse只有在非many方才有,也就是many-to-many或者one-to-many的set,List等

2019-11-25

数据库count()

官方解释

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 值的计数

count(*) 和 count(1)

5.7.18以后,两个函数执行计划都是一样的

  • 如果该表没有任何索引,那么会扫描全表,统计行数
  • 如果该表只有一个主键索引,没有任何二级索引的情况下,那么通过主键索引来统计行数的
  • 如果该表有二级索引,则会通过占用空间最小的字段的二级索引进行统计

count(column)

如果字段定义为not null,则按行累加,如果允许有null,则会把值取出来判断一下是不是null,将不是null的值累加返回。

MyISAM 与 InnoDB

  • MyISAM会记录每个表的行数,count()时直接返回
  • InnoDB会通过扫描全表或索引,得到行数
  • 在使用count函数中加上where条件时,在两个存储引擎中的效果是一样的,都会扫描全表计算某字段有值项的次数

DB select count速度

count(*)=count(1)>count(primary key)>count(column)

参考

MySQL原理:count(*)为什么这么慢,带你重新认识count的方方面面

2019-11-22

[垂直]分库分表

目标

通过减少数据量,提升性能

原则

  • 长度较短,访问频率较高的属性尽量放在一个表里,我们将其称为主表(base表)
  • 字段较长,访问频率较低的属性尽量放在一个表里,我们将其称为扩展表(ext表)
  • 经常一起访问的属性,也可以放在一个表里(备选)

大数据量场景注意事项

  • 不能用Join
    解决方式: 让应用自己拆分成两次查询
  • base表和ext表不能Join,因为一旦Join了,那么两张表就出现了耦合,这不利于日后拆表到别的数据库实例上
  • Join很消耗数据库的性能(分布式场景下,瓶颈往往是数据库)

提高性能的原理

  • 减少单表的数据量,减少磁盘IO(降低每行记录大小)
  • 更好的利用缓存

因为减少单表数据量还可以充分利用数据库缓存,减少磁盘IO

2019-11-20

数据库基本知识

MyISAM与InnoDB索引的区别

MyISAM:

  • MyISAM不存在聚集索引,主键索引与普通索引没区别,叶子节点都是存储的都是数据的地址

InnoDB:

  • InnoDB必然有[一个]聚集索引(为主键索引,没主键时会用第一个非空普通索引,都没有会生成一个基于行号的聚集索引)

select * from t where name=‘lisi’;
会先通过name辅助索引定位到B+树的叶子节点得到id=5,再通过聚集索引定位到行记录

InnoDB命中普通索引获取数据方式

违反唯一索引场景:
MyISAM会出现一个update语句,部分执行成功,部分执行失败(因为不支持事务)

2019-11-11

Elastic-Job

  1. 运行规则:

    3台机器的一个集群 ,shardingCount=10 ,分片结果为:1=[0,1,2,9], 2=[3,4,5], 3=[6,7,8] (参考AverageAllocationJobShardingStrategy)
    如果本机的数据分片分到了多个分片(即一个JVM进程分到了多个分片),则Elastic-Job会为每一个分片去启动一个线程来执行分片任务

  2. 线程:

    每个任务对应一个线程池,其默认线程数为: 2*逻辑核心数(参考DefaultExecutorServiceHandler)
    线程池配置为: new ThreadPoolExecutor(threadSize, threadSize, 5L, TimeUnit.MINUTES, workQueue, new BasicThreadFactory.Builder().namingPattern(Joiner.on("-").join(namingPattern, "%s")).build()); (参考ExecutorServiceObject)

  3. 问题:

    要注意单机线程数要 大于 单机获取到的分片数 - 参考 《Elastic job 线程模型 源码分析》
    一个jvm实例 处理多个 job , 每个job 在该实例上分片数又大于逻辑核心数*2 的数量

    随着job不断增加 , 单个job任务执行时间可能会变长 ,有可能超过平时的任务完成超时时间 ,造成任务失败

    举个例子:
    如果一台机器 处理器数 2 , 线程池 就是 4 , 如果 分片是 5 , 就是说 一个分片会被排队 ,实际完成时间 >2 个分片 完成时间

Elastic-Job其他

1. 失效转移

- 参考

  1. 【简单的HA】版失效转移 (默认)
    在作业节点下线,或者zk的session超时(默认60s)时,会在下一轮任务分片时,把这个该问题节点的分片分给别的正常节点进行作业 (可能会存在作业重复处理的问题

  2. 【真正的】'失败’转移 (需要开启)
    failover(默认值为false) 配置为true时,才会启动真正的失效转移;
    failover(默认值为false)monitorExecution(默认值是true)这两个配置都为true时 只有对monitorExecutiontrue的情况下才可以开启失效转移;
    如果任务1在A节点执行【失败】,那么会【转移】给别的存活的节点【竞争】执行这个任务1;

- 参考
- 官方参考

2019-08-26

官方参考

MySQL锁

  • InnoDB锁机制是基于索引建立的
  • 如果SQL语句中匹配不到索引,那么就会升级为表锁

记录锁

1
2
3
4
-- id 列为主键列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;

update table set age=2 WHERE id = 1;

通过唯一索引实现的记录锁,只会锁住当前记录(必须为=不然会退化为临键锁)

间隙锁

  • 间隙锁只有在事务隔离级别 RR(可重复读)中才会生效.
  • 为非唯一索引组成(如class,age等)
1
select student where age>26 and age<28 lock in share mode ; -- 这里以读锁为例
使用间隙锁的条件
  • 命中普通索引锁定;
  • 使用多列唯一索引;
  • 使用唯一索引命中多行记录
临键锁(Next-key Locks)
  • 临键锁只有在事务隔离级别 RR(可重复读)中才会生效.
  • 是记录锁与间隙锁的组合
  • 可以是唯一索引,也可以是非唯一索引,对其都以间隙锁的形式进行锁定(以唯一索引匹配,并且只匹配到一条数据除外)
临键锁(Next-key Locks) 例子:
tno(唯一索引)tnametsextbirthdayprofdepartage(非唯一索引)
858张旭11969-03-12讲师电子工程系25
857张旭女11969-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
2
3
-- session1
select * from teacher WHERE age between 26 and 28 lock in share mode ;

这时候会锁定非唯一索引的临键 (25,29]
所以我们测试更新age=25–>成功 插入age=27阻塞 更新age=29阻塞 插入age=30成功即可验证

1
2
3
4
5
6
7
8
9
10
11
12
13
-- session2
-- 更新age=25-->成功
update teacher set tsex='女1' WHERE age=25;

-- 插入age=27阻塞
insert into `test`.`teacher` ( `tno`, `tname`, `tsex`, `tbirthday`, `prof`, `depart`,`age`) values ( '740', '张旭1', '12', '1969-03-12 00:00:00', '讲师', '电子工程系',27);

-- 更新age=29-->阻塞
update teacher set tsex='女1' WHERE age=29;


-- 更新age=30-->成功
insert into `test`.`teacher` ( `tno`, `tname`, `tsex`, `tbirthday`, `prof`, `depart`,`age`) values ( '740', '张旭1', '12', '1969-03-12 00:00:00', '讲师', '电子工程系',30);
唯一索引临键锁验证
1
2
-- session1
select * from teacher WHERE tno between "831" and "856" lock in share mode ;

根据上面的sql,我们匹配到唯一索引临键锁为:(825,857]
所以我们测试更新tno=825–>成功 更新tno=857阻塞 更新age=858成功即可验证

1
2
3
4
5
6
-- 更新tno="825"-->成功
update teacher set tsex='女1' WHERE tno="825";
-- 更新tno="857"-->阻塞
update teacher set tsex='女1' WHERE tno="857";
-- 更新tno="858"-->成功
update teacher set tsex='女1' WHERE tno="858";

2019-08-22

Spring事务/AOP增强

  • @EnableAspectJAutoProxy(exposeProxy = true)
  1. 进入代理时,通过AopContext.serCurrentProxy(proxy)把当前代理设置到ThreadLocal中
  2. 后续在线程销毁(请求结束)前调用代理内部之间的调用就可以通过((AService)AopContext.currentProxy()).b()进行调用了
  3. PS. 性能影响不大 不过实际上代理内部之间还需要AOP增强的场景不多,一般没必要用

Spring LTW实现的静态织入(应该不能叫做代理)

  • 需要添加配置:
  1. 代码添加: @EnableLoadTimeWeaving(aspectjWeaving=ENABLED)<context:load-time-weaver aspectj-weaving="enable" />
  2. 添加JVM参数-javaagent:类加载器代理路径
  1. LTW(LoadTime Weaving)
    加载时织入。在通过JVM加载类时候会先调用ClassTransformertransform()进行字节码替换后才会进行加载。

  2. 静态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

2019-08-01

Spring事务

对于this.b()这些类实例的内部调用,b()实际上是无事务的
但是可以用((AService)AopContext.currentProxy()).b() 结合@EnableAspectJAutoProxy(exposeProxy = true) 这样b()就包裹在事务里了

2019-7-20

seata

  • seata需要管理所有的数据库操作,不然不能通过前镜像进行回滚

2019-7-17

Spring事务/Cglib

  1. final,static,private修饰符无法被增强

由于使用final,static,private修饰符的方法都不能被子类覆盖,相应的,这些方法将不能被实施的AOP增强

  1. 增强应该作用在实现类中

@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解因为这只有在使用基于接口的代理时它才会生效

2019-5-20

【GC日志】GC耗时解析

【Time: user=0.71 sys=0.01 real=0.02 secs】

  1. user表示:本次GC过程中【所有线程】在用户态消耗的时间总和
  2. sys表示: 本次GC过程中 【所有线程】在内核态所消耗的时间总和
  3. real表示:本次GC过程中,实际GC消耗的时间

2019-5-1

数据库MVCC

  • MVCC:多版本并发控制(Multi-Version Concurrency Control)
  • 优势:查询速度快,并发环境尤是。对于大多数读操作,我们只需要通过MVCC进行简单的查询操作,而不需要获取任何一个
  • 劣势:需要多存储数据。对每一条记录都需要存储所有版本的数据
  • MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下
  • READ UNCOMMITED不是MVCC兼容:因为这个模式只能读取到最新的数据
  • SERIABLABLE也不与MVCC兼容:因为每个读操作都需要为读到的数据上锁

事务

MVVC机制:

  1. 每一条数据库表记录,都隐藏2个字段

    • 数据行的版本号 (DB_TRX_ID)
    • 删除版本号 (DB_ROLL_PT)
  2. 执行insert语句插入的时候,会把当前的事务ID写到该记录的数据行的版本号 (DB_TRX_ID)中:

    1
    2
    3
    begin;-- 获取到全局事务ID 假设为2
    insert into `test_zq` (`id`, `test_id`) values('5','68');
    commit;-- 提交事务
    idtest_idDB_TRX_IDDB_ROLL_PT
    5682NULL
    67813
  3. 修改数据库记录的时候

    1. 更新原记录的删除版本号 (DB_ROLL_PT)为当前事务ID
    2. 插入一行新的更新后的记录,且它的数据行的版本号 (DB_TRX_ID)为当前事务ID
    1
    2
    3
    begin;-- 获取全局系统事务ID 假设为 10
    update test_zq set test_id = 22 where id = 5;
    commit;
    idtest_idDB_TRX_IDDB_ROLL_PT
    568210
    67813
    52210NULL
  4. 查询的时候需要根据数据行的版本号 (DB_TRX_ID)删除版本号 (DB_ROLL_PT) 二者进行数据数据筛选,需要同时满足以下规则:

    1. 数据行的版本号 (DB_TRX_ID) <= 当前事务
    2. 删除版本号 (DB_ROLL_PT) > 当前事务
    1
    2
    3
    begin;-- 假设拿到的系统事务ID为 10
    select * from test_zq;
    commit;
    idtest_idDB_TRX_IDDB_ROLL_PT
    62210NULL

2019-04-24

Spring的Lifecycle (SpringAppilication生命周期)

Spring会拿到所有Lifecycle实现类,然后委托DefaultLifecycleProcessor进行逐个处理

  • Lifecycle 可以在SpringAppilication在初始化后执行start()方法,Spring停止的时候调用stop()方法
  • 但是单单实现该类不能实现SpringAppilication在启动后,停止时调用Lifecycle对应的方法
  • 这时候我们应该需要使用SmartLifecycle(Lifecycle的子类),重写isAutoStartup()返回true,才能产生理想效果

2019-04-23

关于测试类的规范

  1. 单元测试应该是不依赖于别的单元测试的
  2. 所有单元测试应该都得回滚,如果存在异步处理的情况,应尽可能把主线程与fork线程拆成2个测试类方法进行测试
  3. 每个测试类/测试方法应写上对应的名称@DisplayName
  4. 每个接口,都必须写一个正向测试方法
  5. 关于测试类的类名:测试类与被测试的类的路径需要一致,名字也需要对应,如:
1
2
3
com.fpx.wms.service.impl.InstockServiceImpl
↓对应↓
com.fpx.wms.service.impl.InstockServiceImplTest
  1. 关于测试类的方法名: 方法名尽可能为成功的条件如shouldSuccessAfterPay(),而方法具体用来测试哪个场景的,我们已经使用了@ DisplayName来描述,无须担心
  2. 对于结果,需要适应assert断言输出与结

2019-04-22

Spring @Lookup

作用

在单例A里 可能依赖到原型类型B,这时候如果用普通的Autowrite不能拿到原型的B,这时候就需要使用@Lockup了

使用参考

2019-04-21

架构设计三大原则

  • 合适原则
  • 简单原则
  • 演化原则

即,合适优于先进,简单优于复杂,演化优于一步到位
→能不分,尽可能不分

2019-03-20

策略模式 vs 命令模式

1. 策略模式

1
策略模式针对一个命令,多种实现方式

2. 命令模式

1
命令模式针对多个命令,每种命令都有各自的实现

3. 总结

1
命令模式等于菜单中的复制,移动,压缩等,而策略模式是其中一个菜单的例如复制到不同算法实现。

2019-03-15

策略模式 vs 代理模式

1. 策略模式

1
需要调用方告知具体的策略

2. 代理模式

1
2
需要调用方告知使用哪个[代理类]
具体的【被代理类】由【代理类】生成,客户端不知道具体被代理的是谁

2.1 动态代理

1
需要调用方告知[被代理类]及其接口

3.One More Thing

1
2
以上模式都需要客户端告知具体的[策略]/[代理]/[被代理者]
为了使实现其与调用方进行隔离,可以使用[**工厂模式**]进行隔离

2019-03-12

Spring循环依赖

场景现有3个类相互依赖,依赖关系分别为:

graph LR
A-->B
B-->C
C-->A

场景细分为3种

  1. 构造注入参数循环依赖(报错)
    报错

根据Spring初始化方式,Spring容器会按照顺序创建"无属性"的A放到“当前创建Bean池”中,同理再B、C、A,但是在再次创建A的时候发现“当前创建Bean池”已经存在A了,那么这时候会报错循环依赖

  1. Setter注入的循环依赖(单例)

没毛病,在set的时候对象ABC都已经实例化放在Spring缓存了好了

  1. Setter注入的循环依赖(prototype)
    报错

prototype修饰的bean不会被Spring缓存,都是使用的时候当场创建的

Spring注入方式选择

结合上面的循环依赖问题,Setter出现问题的概率会低一些 推荐使用Setter注入

  1. 构造注入
  2. Setter注入
  3. 接口注入(没用过)

2019-03-11

一、集合操作

遍历

  1. Enumeration(JDK1.0)
    • 只提供读集合相关功能,因为没有fail-fast,速度较快一点
  2. Iterator(推荐)
    • 除了读功能,还有删除集合元素的能力,并且支持fail-fast(防止多线程同时对集合修改的一种机制)

修改

正例:

以List为例子,先得获取他的Iterator,通过iterator来进行修改操作


反例:

使用增强型foreach进行add/remove操作:

因为增强型foreach实际上是使用iterator实现的java语法糖:

1
2
3
4
5
6
7
8
9
10
11
List<String> userNames = new ArrayList<String>() {{
add("test1");
add("test12");
add("test13");
add("test14");
}};
for (String userName : userNames) {
if (userName.equals("test12")) {
userNames.remove(userName);
}
}

编译后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<String> userNames = new ArrayList<String>() {
{
this.add("test1");
this.add("test12");
this.add("test13");
this.add("test14");
}
};
Iterator var1 = userNames.iterator();

while(var1.hasNext()) {
String userName = (String)var1.next();
if (userName.equals("test12")) {
userNames.remove(userName);
}
}
1
2
3
4
5
6
所以实际上for (String userName : userNames) 这里每次都会去调用itertor.next()
如果你在迭代期间,操作了list.add()和list.remove()等不通过Iterator的操作
next()里会去调用checkForComodification()方法
然后发现modCount != expectedModCount 抛出异常

因为list.add()和list.remove()等不通过Iterator的操作,是不会修改expectedModCount的

其它

  • fail-fast:

防止多线程同时对集合修改的一种机制

  • modCount:

****List**中的一个成员变量。它表示该集合实际被修改的次数

  • expectedModCount:

是 ****List**中的一个内部类——Itr中的成员变量

二、Hystrix

  1. Feign-starter包含Hystrix以及ribbon(只用他的均衡负载 http请求还是用feign自己的)
  2. 一个@FeignClient对应一个线程池或信号量
  3. 隔离
    • 线程池隔离

    tomcat的请求线程会交给线程池的线程处理
    超过线程池会排队或者降级,一个线程池对应的服务挂了,不会影响别的线程池的服务

    • 信号量隔离

    只作为开关
    并发数超过X服务的信号量,多出来的Tomcat请求将会被拒绝


2019-03-09

img.png

2019-03-05

一、StringBuilder在高性能场景下的正确用法

StringBuilder在高性能场景下的正确用法(文中代码打错了一些字…)

2019-03-01

一、分布式锁

从需求上说,分布式锁要求是不一样的:

  1. 如果是用于聊天等社交场景,那么可以使用AP的分布式锁:Redis
  2. 如果是用于交易等不允许极端情况下获取锁不一致的,那么AP的Redis锁是不能接受的,这时候一定得用CP的分布式锁,如:etcd Zookeeper这一类

2019-02-22

一、ThreadLocal

ThreadLocal数据结构
ThreadLocal引用关系.png

  1. 每个线程都有一个ThreadLocalMap,ThreadLocalMap以Entry的形式保存着各个线程自己的数据

  2. Entry为一个WeakReference,以你new的ThreadLocal为Key

  3. 基于2.当你new的ThreadLocal没被外部强引用时,线程该Thread下对应该ThreadLocal的Entry会在下次GC被回收

  4. 当一条线程创建了多个ThreadLocal,多个ThreadLocal放入ThreadLocalMap 会极大地增加冲突概率

  5. ThreadLocalMap对冲突的处理方式与普通HashMap的链表处理不一样,而是以原来的位置+1,一直寻找到没有冲突的地方存入

  6. ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的

  7. ThreadLocal.remove(),移除ThreadLocalMap与Entry的关系,释放内存

2019-02-17

一、常量池

常量池包含:

  1. class常量池 存在于class文件中
  2. 运行时常量池 存在于方法区中 一个类对应一个运行时常量池
  3. 字符串常量池 全局唯一 JDK6存在于方法区(独立于运行时常量池) JDK6以后存在于堆中

二、字符串加载到字符串常量池的2种方式

graph LR
A[编译后的class文件中的class常量池]
B[运行时常量池*N]
C[字符串常量池]
D[Java代码运行]
A-->B
D-->B
B-->C

2019-01-28

Mybatis

一级缓存

(范围为一个SqlSession)
有Session/STATEMENT级别:

  • 默认是SESSION 级别,即在一个MyBatis会
    话中执行的所有语句,都会共享这一个缓存。
  • 一种是STATEMENT 级别,可以理解为缓存只对当前执行的
    这一个Statement 有效

二级缓存

基于mapper
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享

  • 补充: 缓存为本地缓存, 在集群部署的系统里开启后,会导致A1查询与A2查询结果不一致的问题 看情况开启,一般为关闭;或者使用Redis等工具使用统一的第三方缓存

2018-11-06

一、 分布式事物要看场景的

举个例子:

  1. 流量充值涉及到订单支付,金钱交易严格用tcc;
  2. 订单支付完后要给用户增加积分,这个必要成功,用最终消息一致性方案;
  3. 订单支付完后还要给用户发送一条短信,短信一般是跟电信运营商的第三方接口对接,有可能成功有可能失败,用最大努力通知方案
阅读更多