seata是阿里开源的分布式事务调度框架,支持TCC、AT、SAGA、XA四种模式
AT
SEATA默认是AT模式,通过@GlobalTrancation 动态代理生成全局事务ID,并通过RM来管理全局事务,如中间出错需要回滚事务时则通过数据库中的undo-log回写,undolog记录了数据提交前的状态
XA
二阶段提交,需要数据库本身支持XA协议
TCC
即 尝试(Try)-确认(Confirm)-取消(Cancel),每个事务都分成这三个阶段,在提交事务前向另一个系统发送确认消息,两边系统都确认OK了才执行提交操作,有一方出现异常则执行Cancel(补偿方法),缺点在于三个方法都需要自已手动完成
SAGA
与TCC类似,不同点在于一阶段直接提交事务,失败则执行补偿操作,无锁,因此性能相对较好,但同时由于没有事务隔离性会带来赃写
更新数据的时候带上指定版本号,如果被其他线程提前更新的版本号,则此次更新失败,缺点对数据库表侵入较大,每个表需要增加version字段,高并发下存在很多更新失败。
使用命令 SET key value NX PX milliseconds
缺点:
改进:
基于Redlock算法实现分布式锁。 redisson对Redlock算法进行了封装。
简单来说就是同时向多个节点发送锁定命令,当获取锁时由于是多个节点会有时间差问题。
**客户端计算获取锁所花的时间,只有当客户端在大多数master节点上成功获取了锁,而且总共消耗的时间不超过锁释放时间,这个锁就认为是获取成功了 **
当某些简单的定时任务需要通过后台修改执行时间时,通过spring boot自带的定时任务来实现是个不错的选择。
需要实现SchedulingConfigurer
接口
1 |
|
1 |
|
由于服务器上部署了caddy torjan 作为代理服务器,想要配置域名时发现80端口已经被caddy占用,无法使用nginx,干掉80端口代理又无法使用,于是打算直接使用caddy反向代理域名。
配置caddy时走了一些弯路,按照官方文档配置怎么都访问不了,在此记录一下最终解决方案
按照官方文档我找到了caddyFile的位置:/etc/caddy/Caddyfile
接着vi 编辑,如下配置:
1 | reiner.host { |
保存后重启caddy: systemctl restart caddy.service
访问配置的域名,结果发现域名访问不进来,官方的说法是,不配置前缀只配置域名,默认转发http://reiner.host 以及 https://reiner.host 的80和443端口,理论上这么配置应该没错。
这里我的版本是caddy 2.6.x
最终我打算不再相信官方文档,手动配置每个需要转发的端口,如下:
1 | www.reiner.host:80 www.reiner.host:443 reiner.host:80 reiner.host:443 { |
其中第一段配置是将/data/pages
文件夹下所有文件作为HTTP服务器,通过访问如 reiner.host 或者 www.reiner.host 转发到 /data/pages/index.html
第二段是配置后台接口的地址,通过访问gateway.reiner.host 转发到本地的8000端口服务
tls 的作用是帮你申请ssl证书,这一点比nginx方便很多,当配置完重启后已经可以直接通过https访问了
2023-8-26 update
针对同一个域名,根据不同的路径转换到不同的服务,配置示例如下:
1 | www.reiner.host:80 www.reiner.host:443 reiner.host:80 reiner.host:443 { |
例如访问reiner.host/api/user/xxx 就会转发到服务器的8081端口
]]>Langchain
llama_index >=0.6.5
GPT-3.5
websockets
python >=3.10
dependence
1 | pip install llama-index |
Supporting private knowledge base AI question-answering chatbot, capable of both knowledge-based Q&A and casual conversation.
1 | from llama_index import SimpleDirectoryReader, ServiceContext, GPTVectorStoreIndex, PromptHelper,StorageContext,load_index_from_storage |
Receive questions from the client and have AI generate answers.
1 | from llama_index import SimpleDirectoryReader, ServiceContext, PromptHelper,StorageContext,load_index_from_storage,GPTVectorStoreIndex |
Return AI’s response to the WebSocket client
1 | from typing import Any, Union |
随着OPENAI开放API接口后,各大厂商的AI如雨后春笋般涌现,如同十年前的互联网大火,未来的风口必然是在AI上。
当然,基于自训模型/自研AI 门槛过高,不是个人或中小厂能干的,而且即使有也与OPENAI差距不小,因此一般人能卷的也只有应用层了。
基于此背景,我开始研究基于GPT的自定义数据索引问答机器人,接着我发现了llama_index和langchain这两个框架,在此记录一下它的使用方法。
langchain: GitHub - hwchase17/langchain: ⚡ Building applications with LLMs through composability ⚡
最初我尝试使用OPENAI的模型微调,我试着弄了几百KB的文本数据喂进去,然而发现当我使用微调模型对话时AI的回复总是只言片语连一句完整的句子都无法返回。
通过搜索资料后我意识到模型微调不是几百或者几兆文本数据就能达到我想要的目地的。
最终我发现llama_index + langchain可以实现想要的效果
安装python3.10以上版本
安装依赖库:
pip install llama-index
pip install openai
pip install langchain
pip install pandas
准备OPENAI的API KEY
准备好机器人要回答的资料库,可以有PDF、HTML、WORD文档、SQL、API接口甚至GITHUB、WIKI等网络资源也可以,在本章我将使用简单的TXT文本,举例大概内容如下:
塞尔达传说:王国之泪什么时候出? 《塞尔达传说:王国之泪》将于2023年5月12日发售哦,敬请期待!
1 | from llama_index import SimpleDirectoryReader, ServiceContext, GPTVectorStoreIndex, PromptHelper,load_index_from_storage,StorageContext |
运行python代码看看是否能正常回答资料库中问题
后续可优化的点:
使用websocket + 流试输出实现类似打字机的效果,流式输出反应较快用户体验较好
记录下用户的历史对话上下文
集成资料库问答+聊天为一体的机器人,自动识别属于资料问答还是普通聊天
GPT4-FREE:GitHub - xtekky/gpt4free: decentralising the Ai Industry, just some language model api's…
OPENAI-JAVA:GitHub - TheoKanning/openai-java: OpenAI GPT-3 Api Client in Java
]]>前面使用Jenkins+docker+shell脚本可以方便的实现单体应用部署,但如果是微服务架构,工程包较多,若是为每一个服务都建一个Jenkins job 会变得很繁琐,这个时候就需要用到docker-compose 容器编排工具,它可以只需一行命令就能帮我们完成多个服务的构建、推送、重启。
考虑到如果有多个服务需要部署到多台服务器,如果每台服务器都采用发送jar包再构建镜像的方式会产生许多重复工作,因此这种情况应该使用jenkins构建镜像->推送到私库->服务器拉取->docker-compose启动 如此流程来完成部署。
需要安装如下软件:
Jenkins (包括git/svn、publish over ssh 这个插件,jenkins安装教程很多此处不再赘述)
docker
Node JS (可选,仅部署前端vue项目时需要安装)
docker compose
准备工作指路:
jenkins: https://www.jenkins.io/download/ 直接启动war包或者使用docker安装
docker: Install Docker Desktop on Linux
Node Js:Node.js
docker compose: GitHub - docker/compose: Define and run multi-container applications with Docker
或者直接使用curl下载安装,以CentOs为例执行如下代码:
1 | 1、下载docker-compose |
为了使服务一次打包多次部署,需要安装docker私库来保存镜像
首先建好映射目录的文件夹:
1 | mkdir /data/registry |
执行docker命令启动私库镜像:
1 | docker run -itd -v /data/registry:/var/lib/registry -p 5000:5000 --restart=always --name registry registry:latest |
添加docker配置:
注意:如果是通过内网访问就配内网IP否则 配公网IP
vi /etc/docker/daemon.json
添加如下:
1 | "insecure-registries": ["192.168.2.200:5000"] |
如果需要设置账号密码:
1 | docker run --rm --entrypoint htpasswd httpd:2 -Bbn yourUserName yourPwd >> ./auth/htpasswd |
一般在内网环境部署私库,拉取推送也是全程内网,所以可装可不装
默认私库无法删除镜像,执行如下命令添加配置:
1 | sudo docker exec -it registry /bin/sh |
首先需要编写Dockerfile,在项目根目录新建Dockerfile文件,告诉docker-compose如何构建镜像:
1 | FROM openjdk:8 |
注意修改spring-boot-1.1.0.jar为项目中实际的jar文件名,环境变量在maven打包阶段就要设置好,而不是通过启动命令spring.profiles.active=dev 这样来设置。
同样在项目根目录新建docker-compose.yml文件,粘入如下内容:
1 | version: '2' |
以上是单个项目的部署配置,且其它的如redis、mysql、kafka等依赖服务已存在的情况下,仅配置一个即可,如果希望一行命令将后台服务、mysql、redis等全部一并创建并启动,则配置如下:
1 | version: '2' |
以上是完整的配置,如需要构建自己的镜像,则配置build.context,并编写dockerfile,如使用官方镜像直接启动,则配置image: 镜像名 即可。
首先确保安装了publish over ssh插件,安装方法为jenkins主页->左边菜单Manage Jenkins -> Manage Plugins->点击可选插件->搜索:Publish Over SSH -> 安装此插件
1、配置源码管理,比如这里使用的是svn,点击subversion ,填入svn地址和账号密码
2、Build 项,Root POM填写pom.xml,如不在根目录,填写完整目录,Goals and options填写maven打包命令,如:clean package -Dmaven.test.skip=true -e -Ptest
3、Post Steps选择Run only if build succeeds or is unstable (构建成功则继续执行)
4、点击Add post build step,选择Execute shell,填写如下代码:
1 | cd manage-system |
首先cd进入项目的根目录(也是docker-compose.yml的目录),执行构建命令,然后使用push命令将镜像推送到私库
如果需要直接在本机部署则直接填写如下即可:
1 | cd manage-system |
如直接部署在本机,配置到此就已经结束了,直接运行Jenkins Build Now即可,如是部署在其它服务器,则需要增加一步
5、如部署在其它服务器,则在构建后操作下点击Add post build step,选择Send files or execute commands over SSH,SSH Server Name一栏选择要部署的服务器,服务器连接配置在Manage Jenkins-> Configure System -> 找到SSH Servers ,配置远程连接服务器的信息。
回到jenkins job配置,Transfer Set Source files填写要发送到远程服务器的文件,这里可将docker-compose.yml发送过去,如填写:manage-system/docker-compose.yml
Exec command填写内容如下:
1 | docker pull 192.168.1.199:5000/project/manage-system:latest |
–no-build 表示不构建,因为我们已经构建了该镜像并推送到私库了
至此spring boot项目的自动部署配置完成!
在项目根目录编写Dockerfile文件:
1 | FROM nginx |
修改其中的ip为实际项目的ip和地址
老样子,新建jenkins job ,源码管理填写git或svn地址,
Build Steps点击新增Excute Shell,填写构建命令:
1 | npm run build |
执行构建命令,再执行docker打包命令,docker会自动查找当前目前的Dockerfile文件并构建镜像。
如果是直接本机部署,则填写如下:
1 | npm run build |
本机部署配置到此结束,如需要发送到远程服务部署,则再增加一步。
构建后操作新增Send build artifacts over SSH,选择要部署的服务器,Transfer Set
Source files可填写docker-compose.yml文件的路径
前端项目的docker-compose.yml可参照springboot项目中的配置,这里我直接手动命令启动了,Exec command填写如下 :
1 | docker pull 192.168.1.199:5000/web/manage-system-front:latest |
结束!
1 | docker run -d -p 9000:9000 --restart=always \ |
1 | docker run -d -p 7001:8080 --name registry-web --restart=always --link registry:registry -e registry_url=http://registry:5000/v2 -e registry_name=localhost:5000 hyper/docker-registry-web:latest |
1 | docker run -d --name kafka -p 9092:9092 -p 9093:9093 --link zookeeper --network networks -e ALLOW_PLAINTEXT_LISTENER=yes -e KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 -e KAFKA_CFG_LISTENERS=CLIENT://:9092,EXTERNAL://:9093 -e KAFKA_CFG_ADVERTISED_LISTENERS=CLIENT://kafka:9092,EXTERNAL://192.168.1.199:9093 -e KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CLIENT:PLAINTEXT,EXTERNAL:PLAINTEXT -e KAFKA_CFG_INTER_BROKER_LISTENER_NAME=CLIENT bitnami/kafka:3.4 |
kafka特别说明:
此配置在容器内部访问时使用kafka:9092或者内部ip:9092,外部访问时使用ip:9093,记得暴露9093
关于kafka lisner 说明可参考: https://rmoff.net/2018/08/02/kafka-listeners-explained/
1 | docker run --name=kafka-ui -d --network networks -e KAFKA_CLUSTERS_0_NAME=local-kafka -e KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka:9092 -p 7002:8080 provectuslabs/kafka-ui:latest |
1 | docker run -u root -d -p 7000:8080 -p 50000:50000 --name jenkins -v jenkins-data:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkins/jenkins |
1 | docker run --restart=always -p 6379:6379 --name redis --network networks -v /home/redis/conf/redis.conf:/etc/redis/redis.conf -v /home/redis/conf/data:/data -d hub.c.163.com/library/redis /etc/redis/redis.conf --appendonly yes --requirepass 123456 |
补充:如连接redis时报no route host,执行如下:
1 | firewall-cmd --permanent --zone=public --add-rich-rule='rule family=ipv4 source address=172.18.0.0/16 accept' && firewall-cmd --reload |
1 | docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch --network networks -e ES_JAVA_OPTS="-Xms512m -Xmx512m" -e "discovery.type=single-node" elasticsearch:7.17.9 |
1 | docker run -v /data/mysql:/var/lib/mysql -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=root123456 -d mysql |
1 | docker run -d -p8891:8891 --link kafka:kafka -e plumelog.model=kafka -e plumelog.es.esHosts=elasticsearch:9200 -e plumelog.kafka.kafkaHosts=kafka:9092 -e login.username=admin -e login.password=123456 --volume=/data/plumelog-server:/plumelog-server --network=networks --name=plumelog ylyue/plumelog:v3.5.1 |
注意各个容器想要相互互通需要容器在同一个虚拟网络(network),使用--network=networks
指定,处于同一虚拟网络才能通过容器名+端口访问
网上的资料比较杂,且少有成体系的资料,因此收集资料并按照自己的理解做了一下整合,本文为自我理解与总结,不代表标准答案。
java从编写’System.out.println(“hello world”)’ 开始,并编译运行,在这期间到底发生了什么。
一个.java文件的完整周期是 java编码成jvm可识别的.class文件, 然后大致流程是: 加载–>连接–>初始化–>使用–>卸载
1、通过全类名获取定义此类的二进制字节流(类似反射)
2、将字节流所代表的静态存储结构转换为JVM方法区的运行时数据结构
3、在内存中生成一个代表该类的 java.lang.Class 对象,作为方法区这些数据的访问入口(这样代码才能通过全类名访问到该对象)
PS:数组类型不通过类加载器创建,它由JAVA虚拟机直接创建,数组的加载可通过自定义类加载器控制加载方式
JVM中有三个类加载器,除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
1、BootstrapClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,负责加载 %JAVA_HOME%/lib目录下的 jar 包和类或者被 -Xbootclasspath参数指定的路径中的所有类。
2、ExtensionClassLoader(扩展类加载器) :主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类,或被 java.ext.dirs 系统变量所指定的路径下的 jar 包。
3、AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
每一个类都有一个对应它的类加载器,在加载类时系统会先判断该类是否已经被加载过,如加载过则直接返回,否则才尝试加载,加载时会首先调用父类的loadClass()方法,当父类加载器无法处理时,才由自己来处理。
举例: 自定义加载器(判断该类是否加载过,未加载过,委托上级处理) –> AppClassLoader(应用程序类加载器,同样判断是否加载过,未加载则委托上级处理) —> ExtensionClassLoader –> BootstrapClassLoader
整个过程就是依次向上传递,(如上级无法处理)再依次向下传递
好处:
1、双亲委派模型保证了 Java 程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类)
2、保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类(即系统无法区分是jdk自带的Object对象还是用户自建的Object对象)。
自定义类加载器:继承 ClassLoader
1、文件格式验证,验证是否符合class文件规范,版本号是否支持,常量池中常量是否有不支持的类型
2、元数据验证,对字节码语义进行分析,以保存其符合java语言规范要求,例如:此类是否有父类(java.langObject除外),是否继承了被final 修饰的类等等。
3、字节码验证,比较复杂的一个阶段,会对类的方法体逻辑进行验证,例如类型转换是否有效,字节码指令跳转异常等。
4、符号引用验证,验证符号引用等否找到对应类,类权限是否能被引用等。
总结:验证编译后的代码是否合法,是否能正常运行
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,* 这个时候进行内存分配的仅包括类变量(静态变量) * ,这里初始化变量值是数据类型的零值,不是实际值,如果是final修饰,则直接赋予实际值
解析阶段时将java虚拟机中变量池中的符号引用替换为直接引用的过程。简单来说就是把类名限定转换为实际内存中的Class对象引用。
JAVA虚拟机为每个类都准备了一张方法表来存储方法的地址,当需要调用某个类时只需要知道这个方法的位置即可,通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置
总结:java通过全类名获得类对象,通过解析操作
初始化阶段是执行初始化方法
1、初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
2、只有主动去使用类才会初始化类(也就是NEW对象的时候)
3、虚拟机启动时会先初始化Main方法这个类
4、……
当类被加载完毕后如何分配内存?
虚拟机执行到new对象指令时才开始加载类并分配内存,其流程为:类加载检查 –> 判断是否已加载类(未加载类,则先加载类) –> 已加载类,开始分配内存 –> 执行初始化 –> 设置对象头 –> 执行init方法。
当主动使用一个类(new 一个对象)时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化过(也就是检查类是否已经被加载过)。如果没有,那必须先执行相应的类加载流程。
如果未被加载过,则在类加载检查通过后,接下来虚拟机将为新生对象分配内存,分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
指针碰撞 :
空闲列表 :
选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是”标记-清除”,还是”标记-整理”(也称作”标记-压缩”),值得注意的是,复制算法内存也是规整的。
PS:默认对象分配在Eden区,大对象直接分配在老年代
内存分配并发问题
虚拟机采用两种方式来保证线程安全:
也就是说每个线程都会在堆的Eden区预先分配一块内存给对象使用,当不够用时再使用乐观锁竞争分配来保证操作的原子性。
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。
在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,
建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有:使用句柄、直接指针。
句柄
如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。
直接指针
如果使用直接指针访问,reference 中存储的直接就是对象的地址。
简单来说就是当我们要使用一个类时JVM会通过这两种方式获取到对象,使用句柄的好处是中间隔了一层,对象被移动时只会改变句柄中的实例数据指针,而直接指针的优势就是速度快。
一般情况下new出来的对象都是分配在堆上的,但在满足一定的条件,会先分配在栈上。
我们知道当堆空间不足时会触发FULL GC,而FULL GC会严重影响性能。
有些对象其实是临时产生的,只在某个方法中使用,如果可以将这些对象随着栈的弹出而一同被销毁,那么就减轻了垃圾回收的压力。
为了减少临时对象在堆内分配的数量,JVM通过逃逸分析确定该对象会不会被外部访问。如果不会逃逸可以将该对象在栈上分配内存。随栈帧出栈而销毁,减轻GC的压力。
什么是逃逸分析
当创建的对象只在方法中使用,不被其它类所使用时则属于逃逸对象。如下代码:
1 | public class Test { |
test1由于该对象返回了引用地址,有被外部使用,因此不属性逃逸对象,而test里的User并没有被其它任何地方使用,因此属于逃逸对象,可以将基分配在栈中。
如何开启逃逸分析
JDK7之后默认开启逃逸分析
1 | -XX:+DoEscapeAnalysis 开启逃逸分析 |
如果一个对象通过逃逸分析能过确定他可以在栈上分配,但是我们知道一个线程栈的空间默认也就1M,栈帧空间就更小了。而对象分配需要一块连续的空间,经过计算如果这个对象可以放在栈帧上,但是栈帧的空间不是连续的,
对于一个对象来说,这样是不行的,因为对象需要一块连续的空间。那怎么办呢?这时JVM做了一个优化,即便在栈帧中没有一块连续的空间方法下这个对象,他也能够通过其他的方式,让这个对象放到栈帧里面去,这个办法就是标量替换。
标量替换不是将整个User对象放到栈帧中,而是将User中的成员变量拿出来分别放在每一块空闲空间中。这种不是放一个完整的对象,而是将对象打散成一个个的成员变量放到栈帧上,当然会有一个地方标识这个属性是属于那个对象的,这就是标量替换.
1 | -XX:+EliminateAllocations 开启标量替换 |
JDK7之后默认开启。
标量替换与聚合量是逃逸分析中对变量或对象的定义。
JAVA的基本类型就属于标量,如:int,long等基本类型,对象属于可进一步分解的量,因此属于聚合量
卸载类需要满足 3 个要求:
1、该类的所有的实例对象都已被 GC,也就是说堆不存在该类的实例对象。
2、该类没有在其他任何地方被引用
3、该类的类加载器的实例已被 GC
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡(即不能再被任何途径使用的对象)。
判断对象死亡的算法有两种:引用计数法和可达性分析算法。
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加 ,当引用失效,计数器就减 1,任何时候计数器为 0 的对象就是不可能再被使用的。
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的,需要被回收。
哪些对象可以作为 GC Roots 呢?
虚拟机栈(栈帧中的本地变量表)中引用的对象
本地方法栈(Native 方法)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
所有被同步锁持有的对象
对象可以被回收,就代表一定会被回收吗?
即使在可达性分析法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑阶段”,要真正宣告一个对象死亡,至少要经历两次标记过程;可达性分析法中不可达的对象被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize 方法。当对象没有覆盖 finalize 方法,或 finalize 方法已经被虚拟机调用过时,虚拟机将这两种情况视为没有必要执行。
被判定为需要执行的对象将会被放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
判断对象死亡的算法总结
引用计数法通过简单的计数判断,可达性分析算法通过引用链判断对象,不可达对象不一定会被回收,该对象会放在一个队列中进行第二次标记,除非这个对象与引用链上的任何一个对象建立关联,否则就会被真的回收。
垃圾回收中的引用类型
无论是通过引用计数法判断对象引用数量,还是通过可达性分析法判断对象的引用链是否可达,判定对象的存活都与“引用”有关。
代码中直接new 出来的对象就属于强引用,JVM GC不会回收它,当内存空间不足的时候,java虚拟机宁可抛出OOM异常,也不会回收具有强引用的对象来释放内存。
如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。软引用可用来实现内存敏感的高速缓存。
定义一个软引用对象:
1 | Object o=new Object(); |
无论内存是否足够,只要发生GC 弱引用的对象一定被回收.
1 | Object o=new Object(); |
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
虚引用主要用来跟踪对象被垃圾回收的活动。
虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。
虚引用与引用队列
1 | Object o=new Object(); |
标记-清除算法
该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。它是最基础的收集算法,后续的算法都是对其不足进行改进得到。这种垃圾收集算法会带来两个明显的问题:
效率问题
空间问题(标记清除后会产生大量不连续的碎片)
标记-复制算法
为了解决效率问题,“标记-复制”收集算法出现了。它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的一半进行回收。
复制算法
标记-整理算法
根据老年代的特点提出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
标记-整理算法
分代收集算法
当前虚拟机的垃圾收集都采用分代收集算法,这种算法没有什么新的思想,只是根据对象存活周期的不同将内存分为几块。一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
比如在新生代中,每次收集都会有大量对象死去,所以可以选择”标记-复制“算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保,所以我们必须选择“标记-清除”或“标记-整理”算法进行垃圾收集。
在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,
如果大于,则此次Minor GC是安全的
如果小于,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,则尝试进行一次Minor GC,但这次Minor GC依然是有风险的;如果小于或者HandlePromotionFailure=false,则改为进行一次Full GC。
空间分配担保是为了确保在 Minor GC 之前老年代本身还有容纳新生代所有对象的剩余空间。
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。
JVM有许多垃圾收集器,每种收集器都对应了不同的使用场景,收集器列表如下:
下面是网上收集的各个垃圾收集器的具体介绍:
Serial 收集器
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Serial 收集器
虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。
但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
ParNew 收集器
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
ParNew 收集器
它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。
并行和并发概念补充:
并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。
Parallel Scavenge 收集器
Parallel Scavenge 收集器也是使用标记-复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。 那么它有什么特别之处呢?
-XX:+UseParallelGC
使用 Parallel 收集器+ 老年代串行
-XX:+UseParallelOldGC
使用 Parallel 收集器+ 老年代并行
Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。
新生代采用标记-复制算法,老年代采用标记-整理算法。
Parallel Scavenge 收集器
这是 JDK1.8 默认收集器
使用 java -XX:+PrintCommandLineFlags -version 命令查看
-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version “1.8.0_211”
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能
Serial Old 收集器
Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
CMS 垃圾收集器
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:
对 CPU 资源敏感;
无法处理浮动垃圾;
它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1 收集器
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.
被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备以下特点:
并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
空间整合:与 CMS 的“标记-清理”算法不同,G1 从整体来看是基于“标记-整理”算法实现的收集器;从局部上来看是基于“标记-复制”算法实现的。
可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。
G1 收集器的运作大致分为以下几个步骤:
初始标记
并发标记
最终标记
筛选回收
G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来) 。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
ZGC 收集器
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
在 ZGC 中出现 Stop The World 的情况会更少!
总过程如下:
程序员编写JAVA代码,保存为.java文件 –> JDK再将java文件编译成.class文件 –> 虚拟机加载.class文件 –> JVM执行类加载器(参考上面的类加载过程,new一个对象时触发) –> 类加载完后会开始分配内存(Main方法类会在虚拟机启动时就初始化) –>
对象内存分配完后执行init方法(执行构造方法) –> 但对象未被引用时可能会被回收(取决于回收算法) –> 卸载(前提是满足条件)。
]]>适用于单库,日志表过大的问题,如每月产生几千万条日志,还不能清理,必须存档,其它业务表反而没这么大量时,可以使用分表来解决。
可以按实际情况来决定是按年分表还是按月分表,每年产生的数据量过亿时可以按月分表。
使用spring boot 2.x + mybatis-plus + shardingjdbc5.x + druid
shardingjdbc非常坑的一个点,每个版本变一次配置,官方文档写得不清不楚,通过GITHUB找到官方示例,发现示例也很少,最后是通过网上的博文+示例+文档才搞定。
1 | <properties> |
按月分表使用单库,所以这里只配置一个数据源
1 | spring.shardingsphere.mode.type=Standalone |
1 | server: |
记得加上mybatis plus扫描注解:@MapperScan(value = "com.reiner.sharding.test.mapper")
控制器代码:
1 |
|
附上控制器中时间参数转换器:
1 |
|
调用http://localhost:7999/orders?startTime=2021-11-01&endTime=2021-11-30 ,shardingjdbc会自动根据参数变换表名,如startTime=2021-11-01,则查询的表名为:t_order_2021-11。
插入同理,会根据createTime字段自动选择要插入的表,同时查多个月份的数据shardingjdbc会自动查询多张表。
测试用的表结构:
1 | CREATE TABLE `db0`.`Untitled` ( |
这一步不能忘,shardingjdbc不会自动创建表,因此需要我们自己写一个定时任务自动按月或者年创建表,如按月则获取当前时间并取名:t_order_2021_12
下一篇将记录一下如何使用shardingjdbc分表分库。
]]> 此文章为个人经验总结,纯当自用笔记(反正也没人看),每个人的体质和情况各不相同,不保证对每个人都有效。
某伟人曾说过:身体是革命的本钱,钱没了还可以再嫌,人病了那就人财两空了。
口腔溃疡一般是由于饮食不均衡导致的,在外漂泊的打工人多数没有条件自己做饭,吃外卖又容易营养不均衡,解决办法如下:
自此以后再也不怕口腔溃疡啦!
重要的东西放在前面讲,这个问题恐怕是所有人(不分男女)的恶梦了,本人试过很多所谓的偏方,包括什么黑芝麻、霸王、德国防脱发洗发水等等
统统没有用! 首先需要排除是由于压力过大或者斑秃导致的脱发,如果不是这两个原因导致的,那么八成是溢脂性脱发(雄脱)导致的。
溢脂性脱发多为遗传因素,缓解方法有如下:
长痘一般是皮肤出油导致,出油又容易导致面部细菌繁殖,解决办法如下:
2023-3-9更新
总结一条经验,保持面部干燥无菌就不会长痘,口罩不宜长时间戴,最好勤换,潮湿环境+油皮容易滋生细菌
早晚刷牙不用说了,如果已经蛀牙赶紧去补,如果已经没法补了,开始疼了,优先保守治疗,吃药撑一撑,然后每晚刷牙后用牙线清理蛀牙残留
如果用了牙线清理还是复发了,那没得救了,做根管治疗吧,根管治疗后医生会建议再做个牙冠,然而真的做了牙冠之后会发现那棵牙齿用又不好用,刷又不好刷。
所以最好的做法是不要做牙冠,就这样凑合着用,能用多久是多久,实在有一天牙齿裂开了拔掉再种牙,原生的牙齿才是最好用的。
2023-3-9更新:
莫名其妙的被传染了,用了一年的曲安奈德乳膏都没好,最后居然PDD买了瓶10块的狼毒喷好了,不得不说传承了千年的医术还是有点用的
持续更新……
]]>在之前的文章:使用左右值树形数据结构实现树形菜单 中记录了如何使用左右值增删改查节点,本次将记录一下如何移动节点,语言使用JAVA + MYSQL实现。
总体思路
移动节点的大致思路是记录移动节点和目标节点之间的左右值以及其子节点的左右值,移动节点及其子节点左右值和目标节点及其子节点的左右值相互加减。
举例
节点A的左右值是LEFT=1,RIGHT=2,节点B的左右值是LEFT=5,RIGHT=6,现在要将节点A移动到节点B前面,那么它的移动范围就是RIGHT - LEFT + 1,即:2 - 1 +1 = 2,也就是说节点A左右值+2,左右值小于节点B的左右值-2 。
结果:节点A的LEFT = 3 ,RIGHT = 4,节点B前面的节点左右值减了2变成了LEFT=1,RIGHT=2,相当于和节点A换了个位置,如此来达到移动节点的目的
后续的移动到节点B后面、移动到节点B作为其子节点思路相同。
更新
2021-6-22 更新
修复了BUG,最新代码请关注: https://github.com/reinershir/lui-auth 以下代码摘抄自菜单管理功能
将要移动的节点移动动目标节点的前面作为其兄弟节点,为防止在移动节点过程中左右值被修改,需要锁定表,代码示例如下 :
1 |
|
示例代码以移动菜单为例,LEFT_VALUE,RIGHT_VALUE分别对应左值和右值
lockTables(),mysql锁表代码:
1 | jdbcTemplate.update("LOCK TABLES "+tableName+" WRITE") |
unlockTable() 手动解除锁表:
1 | jdbcTemplate.update("UNLOCK TABLES "); |
之所以要锁表是因为在修改左右值过程中不能有其它事务修改数据,否则会造成左右值错乱,整张表的数据都坏掉,所以这种数据结构有好有坏,要根据场景来使用。
此代码还可以进一步优化,目前有点冗余
1 |
|
移到节点到目标节点的下面,作为其子节点,并排在其子节点的最后一位,被移动节点的子节点也会跟着一并移动,代码示例:
1 | /** |
表字段对应上一章LEFT_VALUE对应L,RIGHT_VALUE对应R,其余字段名字相同
]]>在前一章记录了如何使用netty作为TCP通信的服务端:点击前往 ,本章记录一下如何解决硬件设备在接收到服务端发送过来的消息时有连包的问题。
在通过监测硬件的收包信息时发现偶尔会出现心跳包和指令“粘”在一块的情况,例如指令的发送内容是:EEFF0103GGHH
,心跳包的回复是:EEFF0201GGHH
。
当心跳包返回信息的时候此时正好指令下发,那么设备端就会收到:EEFF0103GGHHEEFF0201GGHH
。
这是由于调用netty的writeAndFlush()
方法时并不是马上将数据发送过去,而将它放在一个缓冲池当中,而由于硬件的一发一收通信机制客户端无法对连包的数据做分割,因此这个问题要由服务端解决。
找遍了谷歌、官方文档等资料就是没有说明连包问题的相关资料,大部分资料都是说明如何解决服务端接收TCP消息时产生的粘包、半包等问题。
最终发现原来netty配置中有个ChannelOption.TCP_NODELAY
选项,官网上没有任何介绍,根据名字可知意思为“无延迟TCP”,于是启动类代码修改为如下:
1 | //创建线程组 |
关键代码在于.childOption(ChannelOption.TCP_NODELAY, true)
,最终通过抓包发现未在出现连包的情况。
由于我这边的特殊情况,在发送消息时我还加了一层保障,防止心跳回复与指令冲突,示例代码如下:
1 | /** |
HTTP属于应用层协议,TCP属于传输层协议,IP属于网络层协议,一个HTTP协议由IP协议包体包含了TCP协议内容,而TCP协议包体又包括了HTTP协议内容,就像一个洋葱。
一个HTTP请求的完整历程,首先会根据URL解析请求的地址和端口,此时的地址拿到的一般是域名,因此还需要根据DNS服务器获取域名的真实IP。
NDS服务查询完毕后,此时浏览器会调用Socket库将HTTP协议里的内容包装成TCP或者UDP协议包。
光靠TCP协议还无法具备传输功能,因此TCP还需要借助IP协议来定位目标服务器在互联网中的位置,光靠IP依旧无法准确定位,因为一个IP可能有好几台设备使用,要明确要传输的目标还得在IP头部加上目标的MAC地址。
最后由网卡将数字信息转换为光信号经网线发送出去,将服务器通过交换机和路由器接收到请求包后像剥洋葱一样一层一层解析包中内容。
小结,一个HTTP请求总共要经历如下几层协议:
应用层:定义数据格式,并按照对应的格式解读数据。 –HTTP
传输层:定义端口,确认主机上应用程序的身份,并将数据包交给对应的应用程序。 –TCP
网络层:定义IP地址,确认主机所在的网络位置,并通过IP进行MAC寻址,对外网数据包进行路由转发。 –IP
链路层:对0和1进行分组,定义数据帧,确认主机的物理地址,传输数据。 –物理传输
此外 IP 中还包括 ICMP 协议和 ARP 协议。
1、ICMP 用于告知网络包传送过程中产生的错误以及各种控制信息。
2、ARP 用于根据 IP 地址查询相应的以太网 MAC 地址。
面试中问烂的问题了,在 HTTP 传输数据之前,首先需要 TCP 建立连接,TCP 连接的建立,通常称为三次握手。这个所谓的「连接」,只是双方计算机里维护一个状态机。
一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。然后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。
服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。客户端收到服务端发送的 SYN 和 ACK 之后,发送 ACK 的 ACK,之后处于 ESTABLISHED状态,因为它一发一收成功了。
服务端收到 ACK 的 ACK 之后,处于 ESTABLISHED 状态,因为它也一发一收了。所以三次握手目的是保证双方都有发送和接收的能力。
同样是面试中经常问的问题,简单来说就是当客户端发送FIN(断开连接)请求时服务器并不是马上断开连接,因为此时对方可能还处于数据的收发中,也就是说之前发送的数据可能还未接收完。
当客户端数据接收完毕后,会返回FIN和序号+1的回复表示已经接收完毕,可以断开连接了,此时服务收到这条回复才真的断开连接,借用一张图表示如下:
通过上图可知,TCP是通过确认号(ACK)**和序号(SEQ)**,来保证数据顺序以及丢包重发,即某个带序号的包没有收到回复,那么TCP会重发该包直到确认送达。
更新日期:2021-6-7 14:07 ,1202年了,基本上所有的网站都使用了HTTPS,已经少有网站使用HTTP了,在这里总结下为什么HTTPS传输是安全的。
为何需要HTTPS?
因为HTTP是明文传输,可以被中间人攻击。
如何保证传输安全?
所有请求加密传输,客户端请求服务器获取公钥 -> 服务器返回公钥 -> 客户端生成密钥发送给服务器 -> 最终所有通信都使用该密钥加密再传输。
由于密钥只有客户端和服务端持有,中间人即使截获也无法修改内容,因此传输过程是安全的。
但是这是理想情况,中间人可以截获服务器返回的公钥并返回自己生成的私钥给服务器,因此这个时候就要引进CA证书机构。
CA证书机构保证密钥传输安全
既然直接传输无法保证密钥安全,所以引进一个可信任的第三方机构来发放证书,证书中包括公钥,浏览器从证书获取公钥,且证书使用了数字签名来防止被篡改。
浏览器通过该机构的公钥将证书内容做HASH运算并比较运算结果来检测证书是否被篡改。
证书机构的公钥从何而来?如何保证证书机构的公钥是可信的?
浏览器和操作系统会预装可信任的CA机构的根证书,根证书中附带了CA机构的公钥(这就是为什么不要随便安装证书)。
总结,HTTPS = 加密传输+CA证书机构
在浏览器地址栏输入一个URL后回车,背后会进行哪些技术步骤? - 小林coding的回答 - 知乎
]]>最近看了《白帽子讲WEB安全》,书的内容可能已经过时了,但是用来提高安全意识还是不错的,现在将它记下来做个总结。
WEB安全整体给我的感觉是虽然重要,但攻击手段有限,安全的大头还是在服务器安全这块。
先来第一个,XSS攻击,XSS攻击主要分为两类: 1、存储型XSS 2、反射型XSS ,先来看反射型XSS攻击。
所谓反射型XSS攻击其实就是利用JAVASCRIPT解析漏洞,将原本该解析成HTML的内容解析成了JS代码,那么如何实现攻击的呢? 假设某个20年前原始的网站有如下代码:
1 | 请输入搜索关键字:<input type="text" name="search" > |
此时我们输入:<script>alert('xss')</script>
,点击搜索后,发现页面上打印alert(xss)了,但是这样只是控制了自己的浏览器JS,没有意义(用浏览器控制台一样可以做到),因此要将此漏洞散布出去。
假设它的搜索请求url是:http://reiner.host/search?keyword=WEB安全总结 ,将其改为 http://reiner.host/search?keyword= ,并将链接分享出去,诱导别人去点击。
此时这个倒霉鬼如果刚好登陆了该网站,那么你就可以通过JS拿到他的sessionId,然后弄个不可见的iframe将sessionId传到自己的服务器,从而为所欲为了。
PS:担心加上JS代码后链接过长可以弄个短链接。
所以防止反射型XSS的关键点在于不能直接将用户输入的内容展现出来,绝大部分前端框架一般是解决了此问题的,即用户输入内容不会被浏览器解析为JS代码,但不排除一定就没有XSS漏洞了。
防反射型XSS攻击总结
1、对于用户输入的内容不要直接展示
2、使用稳定的开源框架,一般有自动处理
这就是为什么要注意不要乱点链接
此类漏洞危害性最大,是反射型XSS攻击的加强版,原理依旧是将用户输入内容解析为JS代码。
假设某论坛没有对用户输入内容做处理直接展示,此时我新建一个帖子,内容如下:
1 | <script> |
标题为:震惊!一对男女大白天在办公室竟干出这种事!(附图片) ,我相信会有很多人点进来,然后他们就会自动打开新窗口,他们的session id我全知道了。
如果想做得绝一点,甚至可以做个隐藏表单提交,表单请求一些删除操作的接口。。。
防存储型XSS攻击总结
1、在做好反射型XSS攻击的基础上增加XSS过滤器,github上应该有很多现成的xss过滤器可以用,将js代码转义再存储
2、设置http header里的http only 为true,这样浏览器会禁止js代码读取cookie
CSRF其基本原理是伪造请求,即对于服务器来说,请求是合法的,但是这可能并不是用户主动发起的,假设某论坛网站的删除接口URL是 https://reiner.host/delete?id={id}
通过查看帖子我知道了某用户的一个帖子ID是1,此时我给该用户发私信了,内容是: <img src="https://reiner.host/delete?id=1"/>
。
该用户打开私信时看到的只是一张加载失败的图片,但是实际上浏览器已经请求了delete接口,他的ID为1的帖子已经被删除了。
防CSRF跨域脚本攻击总结
1、 Referer check来源检查,请求不能是通过其它域名请求过来的。
2、使用TOKEN验证用户身份,这样伪造请求的接口地址中没有TOKEN就无法请求成功
以上均为示例,实际攻击手段各种各样。
这个玩意漏洞一堆,所以可以看到chrome为了安全已经把它禁用了,而且这个年代已经没有人使用FLASH了,因此FLASH直接PASS 。
这个年代SQL注入基本上已经凉了,除非某些新手同志写代码不好好用占位符,假设漏洞代码如下 :
1 |
|
在登陆框里输入用户名为:admin --
密码: 随便填 ,拼出来的SQL就变成了:SELECT * FROM USER WHERE USER_NAME = admin --AND PASSWORD=123
,后面的条件都注释掉了。
应对方法
1、使用参数占位符,举例:jdbcTemplate.query("SELECT * FROM USER WHERE USER_NAME = ? AND PASSWORD = ?",userName,password);
,使用mybatis的${}要注意,此符号是拼接SQL。
2、参数过滤,每个添加、修改请求过滤参数里可能存在的敏感字符,如UPDATE DELETE 等,网上已经有大量的开源工具可以帮我们过滤。
浏览器和服务器之间的通信内容被人抓包到了,劫持人获得了你的TOKEN以及所有请求内容
解决:
1、上HTTPS
2、每次请求返回带上下一次请求的随机数,后端验证该随机数是否正确
不在WEB安全范围内,有如下几点:
其它WEB安全:
渗透测试工具
以上工具可以帮助我们发现WEB应用中可能存在的漏洞。
]]>微信开发真乃坑中之坑,会话阻塞场景如下:
小程序,有两个用户同时访问同一个接口会阻塞,第二个请求的人必须请等一个请求的人处理完毕才会响应第二个请求。
但如果我在 PC 上用两个不同的浏览器同时请求这个接口,是并行的,从结果上来看可能是 session 阻塞,即同一个会话同时只能处理一个请求。
我的测试代码如下:
1 |
|
两个不同的小程序用户同时请求,第二个请求的人必须请等一个请求的人返回OK才会打印第二个请求的日志,理论上来说,不同用户的请求应当是并行的,但服务器似乎将它们视为同一个会话(session)了。
一开始想到了异步处理,即controller返回callable让处理不占用会话,但是发现这样做事务没法控制!
最终使用了比较粗暴的解决办法,即不使用微信浏览器生成的session id,而是每次请求都由前端生成一个随机数作为session id,这样用户的请求就不会被视为同一个会话了
]]>本文记录了在使用netty作为TCP服务端的情况下,如何解决TCP粘包、半包问题,以及如何使用16进制与客户端进行通信。
首先添加maven依赖:
1 | <dependency> |
首先创建一个将字节转换为16进制的处理类,并判断包头包尾是否正确。
1 | public class CustomDecode extends ByteToMessageDecoder { |
以上为数据处理类,首先判断包头是否为约定的16进制数据,其次判断包尾是否为约定的字节,如不是则return继续接收数据直到接收到了包尾。
工具类 StringUtil.byteToHexString
的作用是将字节转换为16进制字符串,此工具类为netty自带,**(src[0] & 0x000000ff)
表示将下标为0的字节转换为16进制**
1 | public class ServerInitializer extends ChannelInitializer<SocketChannel> { |
初始化字节处理类,并使用netty自带的粘包处理器解决粘包问题,addLast(new DelimiterBasedFrameDecoder(1024,false,Unpooled.copiedBuffer(TCPServerUtils.hexStr2bytes("EFGG"))))
表示以EFGG结尾的数据分割包
简单的说明一下半包和粘包,半包: 假设数据包以16进制 AABB开头,以EFGG结尾,中间放具体数据,但是TCP传输可能只传了AABB,EFGG可能是在第二次传输过来,因此要等一个完整的数据包传输过来后再做处理。
粘包: 假设数据包内容为”AABBHHEFGG“是一个完整的包,但是TCP在传输过程中可能是这样传的:AABBHHEFGGAABBH
,本来应该是两个包的内容给放在了一起,而且还不完整,这就是粘包。
有了上面的字节处理类,在这一层拿到的就是分割好的完整数据包了,此时可以对字节做截取,去掉包头包尾读取中间内容,代码如下:
1 | public class TCPServerHandler extends ChannelInboundHandlerAdapter{ |
注意点:一定要新建一个Map保存认证设备的唯一识别码,netty中的channel即表示一个会话,因此在请求认证时就要将识别码与channel进行绑定,这样才可以随时向客户端发送消息。
checksum工具类代码如下:
1 | public static String makeChecksum(String data) { |
由于我是使用的spring boot框架因此直接在初始化事件中创建netty服务,也可以直接在main方法中启动,代码如下 :
1 | public class ServerStarter implements ApplicationListener<ContextRefreshedEvent>{ |
OK,现在TCP服务的架子已经搭好,只需往里面填充业务代码即可。
附上可能会用到的工具类,比如16进制转10进制,16进制转2进制等
十六进制转byte[]数组
1 | public static byte[] hexStr2bytes(String hexStr) { |
大小端处理 ,低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
1 | public static int toLittleEndian(int a) { |
10进制数字转16进制数字
1 | public static String numberToHexString(Integer number) { |
checksum,上面已经贴过再贴一次
1 | public static String makeChecksum(String data) { |
截取字节 (这个可能是最常用的了,后来我才发现java自带的bytebuffer已经自带大小端转换功能)
1 | public static String catoutByte(byte[] sourceBytes,int startPos,int len) { |
更新日期:2021-7-7 ,新增使用redis作简单队列,发送指令到设备。
此方式需要可容忍指令丢失,因为有可以在取出指令后服务器挂掉或一直未获取到锁导致指令超时,可靠消息列队应当使用队列中间件
首先在你的发送指令类的构造函数中创建一个线程池,在指令并发较大时可以有效节省资源
1 | ExecutorService pool; |
1 |
|
可调用public boolean pushCommand(String deviceId,String responseData)
方法将指令放入队列,等待获取指令列队的线程执行。
指令实体类:
1 | public class Command { |
最后一个注意点,一定要做重试机制,TCP传输过程中很可能会丢包,我目前的做法是在发送消息成功后保存一个临时的缓存,当接收到客户端回复后将缓存移除,如若一直未接收到回复(比如1秒后)则再次重新发送。
]]>最近上AMAZON买个台Oculus Quest2玩玩,前前后后折腾了整整一个星期才终于搞定激活问题!因此这在里写下记录希望后来的网友们少走弯路。
找了下网上的教程发现需要借助智能路由器,而我平时不怎么需要这个东西,就为了激活而买个路由器也不划算,因此我选择了笔记本共享热点的方式。
说道Oculus quest2 我想先简单的评测下,先说说它的优点:
支持透视,找手柄时不用摘下头盔了
支持手势操作,简单的操作无需手柄了
最大的优势—–>没有乱七八糟的线!比如PSVR就要插一堆线,每次看着这么多线就没有开启的动力了
一体机,即可以直接使用又可以WIFI串连电脑玩STEAM VR游戏!
AMAZON退税后约只需1900-2000左右(不要上TB买JS的)
再说说它的不足之处:
清晰度和PSVR相差不大,至少我的眼睛感觉没有明显差距
头戴的那个带子刮耳朵
一会儿要充电了
国内用户无法直接使用
总体来说是目前VR里体验最好的了,不用插线实在是方便
需要准备以下东西:
支持共享热点的笔记本一台
V2RAY以及支持UDP转发的节点(如果不知道V2RAY是什么请先学习相关内容)
SSTAP(一个本地代理转发的软件)
(可选)支持5G信号的路由器,因为2.4G的WIFI串流会卡
1、首先配置好V2节点,再打开配置界面分别勾选上“UDP转发”以及”允许局域网连接“这两个选项,然后右键右下角的V2图标将代理模式改成全局模式
2、打开windows10自带的移动热点分享,开启热点分享此时网络适配器控制面板中会多出一个名字叫本地连接12的东西,如果系统没有自带热点功能,请使用其它WIFI共享软件代替
3、打开SSTAP,点击+号选新建SOCKET5代理,代理地址填127.0.0.1,代理端口填10808(V2RAY默认代理端口就是这个,如有修改该端口可在配置里修改),代理网卡选刚才开启分享的热点(比如名字叫本地连接12),点击启动
此时网络适配器控制面板中会多出一个名字叫SSTAP1的连接,右键属性选共享,勾上刚才开启热点产生的本地连接12。
4、手机连上共享出来的WIFI,试试该IP地址是否为V2节点的地址,或直接访问FB看看
5、最后如果一切正常,此时可以用oculus quest2连接此WIFI了,不管是激活、还是更新固件都没问题。
如何5G WIFI无线串流玩STEAM VR游戏? 有两种方式:
付费方式:virual desktop + oculus sidequest ,其中virual desktop需要付费购买,virual desktop请使用sidequest安装
免费方式: PC和VR端都安装ALVR,由于最近GITHUB被墙不方便直接给地址了,可以自行上GITHUB搜索 ALVR ,PC和VR安装好ALVR以及STEAM VR后就可以开始无线串流了。
一些注意点:
如果ALVR无法检测到VR,可以先安装Oculus Home,也就是PC端的Oculus商店
ALVR连接成功后你会发现VR界面中的STEAM库是没有游戏的,不用管直接STEAM添加本地游戏然后启动即可
记得两台设备都要连接5G WIFI
先说说为什么重复造轮子,当时的情况是只需要一个简单的延时任务,做到不丢失即可,引入分布式定时任务框架又觉得太重,因此手动造了个基于redis的简单可靠定时任务。
适用场景,此种做法在单机部署情况下是肯定没问题的,在分布式部署情况下可能会出现问题,比如同一个服务同时重启了,又刚好同时读取redis中的定时任务,还刚好就在那几十毫秒的情况下没有检测到redis锁,此时会导致任务重复读取。
因此,在定时任务的代码中需要容忍可能会出现重复执行的情况,一般做法是用订单状态判断,所以如果不是非常严格的场景,这套做法是够用了 (复杂的定时任务建议用QUARTZ)。
废话结束,接下来上代码,首先创建定时任务的实体类,保存定时任务执行时间、订单ID、订单类型等。
1 | public class DelayedInfo implements Delayed { |
其中delayType、OrderType就是订单类型的枚举标识,可自己定义标识。
再创建一个定时任务的抽象类,它用来做一些每个定时任务都会有的操作,比如添加定时任务,删除定时任务,扫描redis:
1 | public abstract class DelayService { |
OK,上面已经建好了父类,接下来可以创建执行业务代码的订单延时任务了,此处我还加了个小功能,那就是执行结果返回true才真的结束任务,如果返回false会再等待一次相同的时间再执行,最多允许失败3次。
1 |
|
定时任务创建好后需要有个东西来执行它,因此我创建一个循环执行的线程不断判断是否有任务,如果没有任务,该线程阻塞。
1 | /** |
在这里处理你的业务逻辑,代码示例:
1 | /** |
我们需要在spring启动完成后执行扫描代码,因此还需要一个配置类
1 |
|
大功造成,在代码中动态添加定时任务的用法如下,只需注入并调用add方法即可:
1 |
|
最后redis依赖别忘记加了哦
1 | <dependency> |
由于太长,分成两篇记录,第一篇地址: 自自技术笔记(一)
hashmap
数组加链表存储,通过hash计算key值直接计算出链表在数组中的下标位置,再通过遍历链表判断hash查找value
二叉树其实就是链表的加强版
Java所有集合类型
注意:共10个集合类!
Hashmap扩容机制
loadFactor默认0.75,扩容时要满足一下两个条件
1、 存放新值的时候当前已有元素的个数必须大于等于阈值
2、 存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)
扩容时一般成倍扩容,自jdk1.8后引入新机制
当hash碰撞的链表长度超过默认的8时自动由链表变为红黑树
注意点:如果map触发扩容会重新计算每个数据的位置,所以应尽量避免扩容
ArrayList和LinkedList
HashMap和HashTable
Http Tcp
为什么需要四次挥手
tcp之所以三次握手四次挥手是因为发送端的数据发送完了请求关闭连接,但接收端不一定接收完毕,因此需要来回重复确认。
为什么需要三次握手
如果只有两次握手,
tcp/udp属于传输层 基于 ip网络层 socket和http属于应用层
http的几种请求方式
get post put head delete connect options
trace
其中trace用于回显收到的请求,一般禁用
connect用于代理
http由几部分组成如head method keepAlive 请求url cookie host等
缓存穿透
当查询一个不存在的数据时,会每次都先请求缓存再请求数据库,由此造成性能浪费和攻击漏洞
解决方案1,直接缓存空数据,设置短时间过期
解决方案2,布隆过滤器,将所有可能存在的数据的哈希保存下来,每次请求时判断哈希是否存在
缓存雪崩
同一时间大量缓存失效造成服务器压力暴增,解决方案:失效时间加上随机数保证每个缓存的失效时间不一样
缓存击穿
一些热点数据可能正好在缓存失效时出现大量请求导致数据库压力剧增,解决方案:
Cap理论
1.当大量服务分散部署到各个省,且合省之间相互独立,其他省服务器宕机不影响本省服务继续提供支持时,选择ap
2.对于其他情况则只需保证ca即可,一般情况下没有分区性问题。
一致性hash
一致性hash的作用是解决数据倾斜问题,保证请求和数据分布均匀。
整个Hash空间被构建成一个首尾相接的环,使用一致性Hash时需要进行两次映射。
第一次给每个节点计算哈希并记录它们在环上的位置,第二次给每个Key计算Hash,然后沿着顺时针的方向找到环上的第一个节点,就是该Key储存对应的集群
源码问答
为了应对该死的面试而记录的
1.Spring注入私有属性开关
2.spring boot自动加载原理
3.spring扫描原理 ,读取class文件反射
spring循环注入问题
spring为避免循环注入 spring用了三级缓存分别为:
spring cloud ribbon 7种负载均衡策略
分表算法
1.按时间分表
2.按数字,如分30张表使用id %30 得出的结果为表的位置
3.使用md5值的前5位做hash
@enableEurekaClient只能用于注册中心为eureka而@enableDiscoveryClien则支持其他注册中心
高可用策略
服务降级 自动重试 快速失败(熔断)
负载均衡 健康检测 回退部署
服务降级
暂时关闭不重要的服务 分级降级 降级权重
微服务概念理解
1.将业务服务拆分成多个细小独立的服务
2.每个微服务都是独立可运行的
3.微服务本质是soa(面向服务架构)其是一种架构设计理念
微服务的好处
1.每个服务独立运行不受技术,数据库,等影响
2.由于其划分精细,小部分功能升级不影响其他功能使用
3.松耦合
4.接口通用
spring cloud组件
1.eureka
2.ribbon
3. feign
4.config
5.hystrix
6. bus消息总线
7.dashbord(hystrix仪表盘)
8.Zuul网关
9.sleuth调用链追踪(一般配合zipkin)
高并发策略
1.线程池
2.负载均衡
3.熔断
4.队列
5.服务降级
实现高并发秒杀的三种思路
1.数据库行锁(性能差)
2.使用redis的list存储秒杀数据使用lpop删除数组中的第一个下标的数据来表示减少库存,redis是单线程的 (性能最好)
3.使用redis分布式锁,通过setnx方法获取锁,setnx是设置一个值,如果它不存在则设置成功返回1,否则失败返回0,再通过expire设置锁的超时时间,避免死锁,最后需要操作完后根据客户端请求唯一标识删除锁(为了避免误删)
redis优势
redis哨兵模式有三种
故障转移 投票选举 监控
epoll
io复用模型中的一种,为每一个流增加标记,这样重缓冲区读取流时就知道当前流的状态了,在获取到流之前处于阻塞状态类似socket
select所有流轮询,限制大小为1024,其优势有:
redis数据淘汰策略
Redis持久化
懒癌末期患者整理出来的几个半小时内就行搞定的菜,不想吃外卖时过来看看菜单,本菜单长期更新。
顺带说句:外卖是真的不健康,有次吃完肚子疼了一夜,最后去了医院。。。
材料:
带皮鸡肉/鸡翅斩断/鸡腿肉
姜丝
生粉
酱油
玉米油调
香菇
米
做法
带皮鸡肉+姜丝+生粉+酱油+玉米油调和。
把米洗干净,香菇泡开切丝,一起放进电饭煲开煮。
等到第一次闻到香菇味道的时候就是电饭煲里面的温度到达了沸点,然后就打开电饭煲,把调好的鸡肉丢进去~
直接买酱料然后和豆腐一块煮就完事了
1.淘好米;鸡翅(翅中为好)、 泡发的香菇、葱姜蒜适量。
2、先把葱姜蒜切碎,香菇切小丁,把鸡翅中洗干净划上几刀,放在盘子里,撒上葱姜蒜和香菇。香菇可以铺在下边,也可洒在上边。再撒上盐,酱油,料酒,稍作搅拌。有耐心就腌制半小时,没耐心直接上锅。
3、电饭煲已经淘好米,直接把盘子放在蒸笼上,摁下开关。
洗菜快!
同上
?,这个。。。我忘记了。
材料:
一杯大米,半颗土豆,半个胡萝卜,一根烟熏火腿,玉米粒和豌豆粒适量 盐,糖,蚝油,生抽,老抽
9【做法】
1.大米洗净后放入电饭煲,按比正常水量稍少一点的水量加水
2.加入半平勺老抽,一平勺生抽,半平勺蚝油,一丢丢盐,一丢丢糖提味
3.土豆胡萝卜切丁后和玉米以及豌豆一起放入电饭煲,我为了省事是直接在超市买的速冻玉米粒和豌豆粒
4.将烟熏火腿切片后铺在最上层
5.电饭煲定时
3分钟的事
直接剁椒混生皮蛋,味道是很不错,就是有点担心细菌
虽然做起来麻烦,但是可以吃三餐。。。
切菜洗菜快,直接一锅煮,方便。
我这面和某些面馆的白开水泡面不一样,既方便又好吃。
材料: 挂面、大蒜、西红柿、任意肉类、鸡蛋、小葱
做法:面煮好用凉水冲冷放一旁,热锅放油,煎好鸡蛋放一旁,再放入大蒜和肉爆炒,差不多了后放入西红柿炒,然后放适量水,放盐,水沸腾后装碗里,把面倒进去,放葱,大功告成。
持续更新中…
]]>整理了一下记录在手机中的技术知识点,包含JAVA、MYSQL、数据结构等,自用笔记,放博客上备份以备不时之需,如若有人发现错误还请指出。
由于太长,分成两篇记录,笔记二地址: 自自技术笔记(二)
红黑树
红黑树其实就是平衡二叉树,该规则可保证每个节点都是平衡的,不会出现单个节点过长的情况
红黑树的作用,红黑树是一种平衡二叉树,不会退化为链表,多用于搜索查找,时间复杂度logn,也就是树的深度,倍数增长,红黑树是局部平衡二叉树
普通二叉树的局限性是子节点都比父节点小因此全排在右,会退化成链表
红黑树4个性质
红黑树有三种变化规则
1.红变黑 2.黑变红 3.旋转
红黑树的操作规则
右旋有3步
Mysql的五种索引
联合索引按照最左匹配原则
Mysql聚簇(cu)索引
聚簇索引是一种数据存储方式,innoDB使用b+tree存储索引和数据行,当有聚簇索引时,数据是存储在索引的叶子节点中,一张表只能有一个聚簇索引,聚簇索引一般是自增的id主键
索引的条件顺序可以打乱,mysql优化器会自动调整顺序
聚合索引是物理地址连续存放的索引
索引场景
mysql四个隔离级别
幻读,多个人员同时修改一个数据时,后操作的人员将就旧数据修改后覆盖了先操作人员修改的数据称之为幻读
mysql锁
mysql行锁 走索引 for update 不走索引则表锁
for update为排他锁 不可读 lock in share.mode为共享锁其他事务可读
msql binary log原理
binlog保存数据为事务日志和二进制日志,二进制日志索引文件后缀为.index,二进制日志文件为.00000*,记录了所有的ddl dml操作
其内容分别为事务提交前数据和事务提交后数据,binlog除了可以用来回滚事务外还可以做主从同步,读写分离
Mysql优化
msql主从复制原理
通过binlog异步同步数据,为保证复制准确,mysql在master.info和relay-log.info中保存了同步日志,主从同步有两个线程
1.io线程,监听主库数据是否发生改变,当发生修改时,将异步复制到从库中的relay log中
2. sql线程,从中继日志中读取主库修改的数据同步到从库中来完成数据同步操作。
三种复制方式
二进制binlog的格式有三种:
statement:基于sql的binlog,每条修改数据的sql都会保存到binlog里。
row:基于行级别,记录每一行数据的变化,也就是将每一行数据的变化都记录到binlog里,记录非常详细。
mixed:混合statement和row模式。
阿里的canal框架也可以实现主从复制
Msql分库分表后按时间查询思路
垂直拆分,新建一张数据索引表,保存了主表id和该表的时间数据,查询时先按时间来索引表查id,再根据id查关联数据,如果索引表也变得非常庞大,可以单开一个库按月分表,查询时先按月份找到表,再按时间从表中筛选,最后根据id查到想要的数据
Mysql保证持久化
Undo log记录修改前的数据,用于回滚
Redo log记录事物提交修改后的数据,用于数据恢复
Mysql优化笔记
查看开关
show variables like “%slow_query_log%”
打开开关,1秒超时
set global slow_query_log = on;
set global long_query_time = 1;
查看慢sql条数
show status like “%slow_queries%”
慢sql日志文件在 /data/mysql/localhost-slow.log
spring生命周期关键点:
执行initantiationAwareBeanPostProcessor的postProcessPropertyValue方法后开始 执行bean构造器 - 为bean注入属性 - beanAware的setBeanName setBeanFactoru - 调用 bean init-method 销毁时执行destory 并调用配置的destroy-method
分布式锁笔记
分布式锁有如下几种
1.使用redis做分布式锁
2.使用mysql索引锁
3.使用乐观锁
Redis锁的加锁和解锁需要两步操作,为保证原子性,需要用lua脚本一次执行,一般使用公平锁,加速队列,避免出现大量线程竞争不到锁而超时的问题
当redis为分布式环境时需要使用redlock算法,避免重复获取锁
Redlock基本原理如下
1.快速失败时间,客户端使用相同key和随机数尝试获取锁时,为每个锁设置快速失败时间
2.计算获取锁的时间,判断获取锁消耗时间小于锁的存活时间,且一半以上的master节点获得锁才算锁定成功
3.获取成功的锁不足一半以上时需要释放所有节点锁
4.加锁失败时如释放锁时出现问题需要等待所有锁自动失效
5.获取锁成功后,执行任务的时间窗口是锁的存活时间减去锁的消耗时间
jvm类的加载方式
类加载生命周期
加载~验证~准备~解析~初始化~使用~卸载
jvm三种预定义类型类加载器
类加载双亲委派机制
在某个特定的类加载器接到加载请求时先将加载任务委托给父类,依次递归,如果父类不能完成才自己去加载,类加载器均继承自ClassLoader抽象类。
jvm内存模型
JVM内存空间分为五部分,分别是:方法区、堆、Java虚拟机栈、本地方法栈、程序计数器,方法区主要用来存放类信息、类的静态变量、常量、运行时常量池等,方法区的大小是可以动态扩展的,
堆主要存放的是数组、类的实例对象、字符串常量池等。Java虚拟机栈是描述JAVA方法运行过程的内存模型,Java虚拟机栈会为每一个即将执行的方法创建一个叫做“栈帧”的区域,该区域用来存储该方法运行时需要的一些信息,
包括:局部变量表、操作数栈、动态链接、方法返回地址等。比如我们方法执行过程中需要创建变量时,就会将局部变量插入到局部变量表中,局部变量的运算、传递等在操作数栈中进行,
当方法执行结束后,这个方法对应的栈帧将出栈,并释放内存空间。栈中会发生的两种异常,StackOverFlowError和OutOfMemoryError,StackOverFlowError表示当前线程申请的栈超过了事先定好的栈的最大深度,
但内存空间可能还有很多。 而OutOfMemoryError是指当线程申请栈时发现栈已经满了,而且内存也全都用光了。
本地方法栈结构上和Java虚拟机栈一样,只不过Java虚拟机栈是运行Java方法的区域,而本地方法栈是运行本地方法的内存模型。运行本地方法时也会创建栈帧,
同样栈帧里也有局部变量表、操作数栈、动态链接和方法返回地址等,在本地方法执行结束后栈帧也会出栈并释放内存资源,也会发生OutOfMemoryError。
最后是程序计数器,程序计数器是一个比较小的内存空间,用来记录当前线程正在执行的那一条字节码指令的地址。如果当前线程正在执行的是本地方法,那么此时程序计数器为空。
程序计数器有两个作用:
1、字节码解释器通过改变程序计数器来一次读取指令,从而实现代码的流程控制,比如我们常见的顺序、循环、选择、异常处理等。
2、在多线程的情况下,程序计数器用来记录当前线程执行的位置,
当线程切换回来的时候仍然可以知道该线程上次执行到了哪里。而且程序计数器是唯一一个不会出现OutOfMeroryError的内存区域。
方法区和堆都是线程共享的,在JVM启动时创建,在JVM停止时销毁,而Java虚拟机栈、本地方法栈、程序计数器是线程私有的,随线程的创建而创建,随线程的结束而死亡。
JVM三大性能调优参数:-Xms –Xmx –Xss
-Xms –Xmx是对堆的性能调优参数,一般两个设置是一样的,如果不一样,当Heap不够用,会发生内存抖动。一般都调大这两个参数,并且两个大小一样。
-Xss是对每一个线程栈的性能调优参数,影响堆栈调用的深度
Jvm回收判断
1.引用计数法,缺点是当循环引用时会内存溢出
2.可达性分析法,存储了对象的引用链接,并向下搜索,当一个对象到root节点没有任何引用链接时,则证明此对象是可以被回收的。以下对象会被认为是root对象:
Jvm 3个回收算法
Jvm调优工具
1.jconsole,jdk1.5自带,用于对JVM中内存,线程和类等的监控,是一个基于JMX(java management extensions)的GUI性能监测工具
2.VisualVM,可监控cpu、内存、类、线程,新生代,老年代等内存变化
3.Mat eclipse的一个第三方调优工具,它可以帮助我们查找内存泄漏和减少内存消耗。使用内存分析工具从众多的对象中进行分析,快速的计算出在内存中对象的占用大小
join的作用
join的作用是等待调用该方法的线程结束后才往下执行,如主线程调用t1.join() 那么t1 执行完后才继续执行后面代码
java线程锁
以下是各种概念上锁的分类
1.悲观锁,乐观锁
2.独占锁,互斥锁
3.可重入,不可重入
4.重量级,轻量级
5.公平锁,非公平锁
各种杂项锁的概念分类
自旋锁 ,自旋锁的其他种类,阻塞锁,可重入锁 ,读写锁,互斥锁,悲观锁,乐观锁,公平锁,偏向锁, 对象锁,线程锁,锁粗化,锁消除,轻量级锁,重量级锁, 信号量,独享锁,共享锁,分段锁
java实现线程的几种方式
4种线程池
线程池的4种拒绝策略
默认策略抛异常
线程池的4种队列:
join 让父线程等待子线程结束后才运行,join可指定等待时间
java IO流
1.字节流 不缓存到缓冲区,主要操作类inputStream outputStream
2.字符流 会缓存到缓冲区,当输出内容到文件时即使不flush文件中也依旧有内容,且一个字符占两个字节,默认编码unicode 主要操作类为 Reader Writer
线程池原理
workset线程集合加队列
配置说明:
keepAlivetime排队时长,该参数只在当然线程求大于corePoolSize时有用
maxinumPoolSize最大线程执行数量,超过核心线程会自动创建非核心线程,前提是不超过maxinumPoolSize
线程池总体流程:
当用户提交线程时创建新线程执行直到线程数量等于corePoolSize,多出来的任务则加入队列,当队列满了且当前线程数小于maxinumPoolSize时创建新的线程执行任务,当队列满了且线程池执行数量超过设定上限则执行配置的拒绝策略。