Java程序猿搬砖笔记(十)
作为码农平时搜集一些小知识点个人认为是个不错的习惯,书上说
好记性不如烂笔头
我想即使是以前忽略或者新get的很简单的东西,自己动手记下来不管如何印象也会更深刻。
synchronized锁定的对象
synchronized修饰的作用:
让多个线程排队依次获取某个资源,保证数据不会出错(原子性)。
synchronized锁定的元素:
- 修饰方法
- 静态方法,锁定的是类
- 普通方法,锁定的时方法的调用者
- 修饰代码块,锁定的时传入的对象
Java线程内存模型(JMM)定义的8中原子操作
- read(读取):从主内存中读取数据
- load(载入):将主内存读取到的数据写入工作内存
- use(使用):从工作内存读取数据来计算
- assgin(赋值):将计算好的值重新赋值到工作内存中
- store(存储):将工作内存数据写入主内存,会经过总线,到总线后就开始广播其他线程
- write(写入):将store过去的变量值赋值给主内存中的变量
- lock(锁定):将主内存变量加锁,标识为线程独占状态
- unlock(解锁):将主内存变量解锁,解锁后其他线程可以锁定该变量
MESI缓存一致性协议:
多个cpu从主内存读取同一个数据到各自的高速缓存,当其中某个cpu修改了缓存里的数据,该数据会马上同步回主内存,其他cpu通过总线嗅探机制可以感知到数据的变化从而将自己缓存里的数据失效。
一般lock锁加在线程的赋值之后,存储之前,这样的锁的粒度不大。volatile保证数据可见性、synchronized保证原子性。
MySQL分区学习
根据hash中的键模糊删除
1 | /** |
union和ordery by一起使用报"Incorrect usage of UNION and ORDER BY"错误的解决方法
原因
每条字句都使用order by会报错。
解决方法
方法一:只在最后一条字句使用order by,意思是对整个结果集进行order by
参考代码如下:
1 | select * from t1 where username like 'l%' |
方法二:union后整体改为临时表
参考代码如下:
1 | select * from |
方法三:每一层都改为临时表再union
1 | select * from (select * from t1 where username like 'l%') t1 |
Spring Cloud Config多服务共享公共配置
参考代码:
1 | spring: |
该代码读取配置中心的common-test.yml和reward-innovation-v1-test.yml文件
解决SpringBoot启动报错:NoClassDefFoundError: org/springframework/data/redis/connection/RedisStreamCommands
查重管理的时候,在测试环境碰到一模一样的问题。
查看Java进程当前使用的JVM参数
1、jps命令查看当前进程id
1 | [award_dev@localhost reward_taskjob]$ jps -l |
2、jinfo -flags 进程id 命令查看当前进程使用的JVM参数
1 | [award_dev@localhost reward_taskjob]$ jinfo -flags 32496 |
RestTemplate使用Ribbon实现负载均衡的调用
1 | @Bean |
直接通过服务名调用,Eureka会去找。
1 | restTemplate.postForObject("http://webbas32-manage-v1/web/login/findemployeebyphone", formEntity, String.class); |
其中webbas32-manage-v1为接口提供方的服务名
调用方的Eureka配置需要添加,fetchRegistry: true //表示可以从Eureka Server获取注册的服务信息
RestTemplate.postForObject泛型丢失(返回LinkedHashMap)的问题解决方法
问题:
如上图所示,data类型本应该为MemberInfoVo,但是泛型丢失变为LinkedHashMap。
解决方法:
参考代码如下:
1 | ParameterizedTypeReference<ResponseMsg<MemberInfoVo>> typeRef = new ParameterizedTypeReference<ResponseMsg<MemberInfoVo>>() {}; |
成功效果:
参考链接
MyBatis批量更新
Spring Cloud Eureka自我保护机制
Spring Cloud Eureka控制台相关配置参数学习
SpringBoot配置默认sql级别info、sql打印级别为debug
SpringBoot的默认使用的日志框架为logback,且默认的日志级别均为INFO。
为了打印sql日志信息,我们只需把对应dao层包下的日志级别改为DEBUG。
示例代码如下:
1 | # log |
IDEA解决完所有冲突但是分支还是在Merging状态的解决方法
1、通过git status命令查看信息(关键):
All conflicts fixed but you are still merging(use “git commit” to conclude merge)
2、根据第一步的信息提示做操作
示例操作如下:
1 | git add -A |
SpringBoot中使用@Value读取配置文件中的list、map
配置文件代码:
1 | myList: item1,item2,item3 |
Java代码:
1 | @Value("#{'${myList}'.split(',')}") |
注意:
上面的list配置中,一定不要用""把list所有的成员value包起来,要不然解析报错。
上面的map配置中,一定要用"" 把map所对应的value包起来,要不然解析解析报错。
SpringBoot中多个@ControllerAdvice全局异常处理
多个ControllerAdvice,优先级由@Order决定,order的数值越小 则优先级越高。
1 | import org.springframework.core.Ordered; |
注意:@ControllerAdvice只能对controller抛出的异常处理
多个@ControllerAdvice全局异常处理(含多个实用场景)、
@ControllerAdvice优先级设定
SpringCloud zuul路由手动配置
示例代码:
1 | zuul: |
zuul路由配置说明:api-a、api-b是路由名称可以随便取,serviceId是服务的id(对应spring.application.name)。
上面配置表示,以/api-a/ 开头的请求都转发给service-ribbon服务;以/api-b/开头的请求都转发给service-feign服务。
SpringCloud zuul的路由默认自动配置
zuul可以自动根据Eureka服务器中所注册的服务自动完成路由映射、负载均衡。
在Eureka上注册了两个服务:
1、服务名management,端口18081
2、服务名gateway,端口7001
请求路径,127.0.0.1:7001/management/addressBook/getList,网关zuul会把请求转发给management服务,
实际请求路径127.0.0.1:18081/addressBook/getList
dependencyManagement标签的作用
在Maven中dependencyManagement的作用其实相当于一个对所依赖jar包进行版本管理的管理器。
jar的版本判断的两种途径:
- 如果dependencies里的dependency自己没有声明version元素,那么maven就会到dependencyManagement里面去找有没有对该artifactId和groupId进行过版本声明。如果有,就继承它,如果没有就会报错,告诉你必须为dependency声明一个version。
- 如果dependencies中的dependency声明了version,那么无论dependencyManagement中有无对该jar的version声明,都以dependency里的version为准。
示例代码如下:
1 | <!--只是对版本进行管理,不会实际引入jar --> |
VirtualBox网络连接方式学习笔记
VirtualBox的7种网络接入模式详解
各网络模式的默认情况下,网络连接情况
NAT模式 | 桥接模式 | Host-Only模式 | |
---|---|---|---|
虚拟机 -> 宿主机 | √ | √ | 默认不能,需要设置 |
宿主机 ——> 虚拟机 | x | √ | 默认不能,需要设置 |
虚拟机 ——> 虚拟机 | x | √ | √ |
虚拟机 ——> 同局域网下其他主机 | √ | √ | 默认不能,需要设置 |
同局域网下其他主机 ——> 虚拟机 | x | √ | 默认不能,需要设置 |
配置网络地址转换(NAT)模式
NAT模式下,宿主机访问虚拟机,虚拟机可以访问宿主机,虚拟机可以访问互联网。
访问外网
选择对应虚拟机 -> 设置 -> 网络 -> 选择连接方式为NAT -> 选择控制芯片
这样配置以后,启动虚拟机,执行ping www.baidu.com有响应说明配置成功。如果没有响应换一个控制芯片试试,上图框红的两个控制芯片是可以的。
宿主机访问虚拟机
配置步骤如下:
选择对应虚拟机 -> 设置 -> 网络 -> 端口转发(主机为222,子系统为22,其他不用填)
配置完重启虚拟机后,宿主机访问虚拟机连接如下:
注意,这种模式文件传输非常慢,本地上传一个10多KB的文件到虚拟机花费差不多2分钟。
配置桥接模式
桥接模式下,虚拟机和宿主机在同一网段,虚拟机可以访问外网,宿主机可以访问虚拟机,虚拟机可以访问宿主机同一局域网的其他机器。具体配置步骤如下所示:
网络连接方式设置
这里的MAC地址和网卡配置文件的HWADDR一致,否则会重启网络服务失败。
确定界面名称是网络的适配器,一般默认的没问题。
配置完桥接模式,启动虚拟机,网络就是通的,只是IP不固定。
修改网卡配置文件
这一步的目的是固定虚拟机的IP地址。
先查看宿主机的网络信息:
修改虚拟机的网卡配置文件:
1 | TYPE=Ethernet |
测试网络
配置正常后,直接ping www.baidu.com就可以联通了。宿主机访问虚拟机,连接方式如下:
NAT+HostOnly模式组合
设置主机网络管理器
这个是VirtualBox的默认设置,不是这个可以改为这个,后续配置文件就不需要改了。
网络连接方式设置
设置好后,启动虚拟机。
虚拟机网卡配置文件修改
Nat模式启动虚拟机就可以访问外网,直接ping www.baidu.com是可以连通的。
修改默认网卡配置文件(NAT)
先用ip route命令查看网卡配置文件,如下可以看到默认配置文件是/etc/sysconfig/network-scripts/ifcfg-enp0s17,如果该配置文件命名不一致需要修改,并修改里面内容的名称。
1 | [root@localhost ~]# ip route |
然后修改"ONBOOT=yes",修改后配置文件如下:
1 | TYPE=Ethernet |
修改网卡2配置文件(HostOnly)
网卡2为HostOnly网络连接模式的配置文件,需要设置为静态ip,修改后的配置文件如下:
1 | TYPE="Ethernet" |
宿主机通过静态ip(HostOnly方式)访问虚拟机
经测试,这种模式文件传输非常快。
NAT+HostOnly模式与桥接模式不同的是,IPADDR、网关的配置。
- NAT+HostOnly模式不用配置网关,IPADDR参考的是VirtualBox主机网络配置
- 桥接模式要配置网关,IPADDR参考的是config/all命令返回的配置
碰到问题
使用service network restart命令失败,要么配置文件问题(确定配置文件名和路由一致),要么网卡没选对。
1 | [root@localhost ~]# ip route |
ip route命令输出enp0s17 ,即网卡配置文件为/etc/sysconfig/network-scripts/ifcfg-enp0s17。如果该配置文件命名不一致需要修改,并修改里面内容的名称。
注意enp0s9不等于enp0s09,配置文件名多个0也会启动失败!
Job for network.service failed. See ‘systemctl status network.service’ and 'journalctl -xn’问题的解决方法(含重启网络管理相关命令)
ip route没有显示路由信息,检查下网络管理有没有开启
执行下面两个命令如下:
1 | chkconfig NetworkManager on |
桥接模式下,无法访问外网
防火墙开启是不影响的,检查下DNS服务器。
Jenkins学习笔记
Jenkins构建项目的几种方式
Jenkins简单的PipeLine流水线脚本
Windows:
1 | pipeline { |
Linux:
1 | 把bat改为sh |
注意:pipeline、stages、stage、steps、checkout…这些词不能改
构建遇到问题:java.lang.NoSuchMethodError: No such DSL method ‘pipeline’ found among steps [bat, build, checkout, container, containerLog, input, junit, library, libraryResource, load, milestone, node, parallel, podTemplate, powershell, properties
解决方法:成功安装PipeLine插件。
Jenkins项目构建细节(定时构建、参数化构建等)
Jenkins远程部署Jar包(非流水线方式)
安装Publish Over SSH 插件
配置Publish Over SSH
点击Manage Jenkins->Configure System
找到Publish over SSH->修改用户密码
填写服务名、服务器ip、用户名、服务器文件夹等信息
填写完成后,点击Test Configuration 返回Success说明配置没问题。
项目构建配置
在对应构建的项目配置中(非PipeLine),Post Steps选择Send files or execute commands over SSH
填写下面的配置:
其中,Remote directory是在之前的 Publish over SSH配置追加。比如之前配置了/usr/local/testJenkins/,这里填写/test/upload,最终上传的路径就会变成/usr/local/testJenkins/test/upload
最后,重新构建,看日志等着构建成功就好。这里传输如果文件过大速度会比较慢。
参考链接1、参考链接2
Jenkins远程部署Jar包(流水线方式)
测试通过的脚本如下:
1 | def CONFIG_NAME = "LocalCentos" // SSH Server配置的名字 |
Java程序猿搬砖笔记(九)
作为码农平时搜集一些小知识点个人认为是个不错的习惯,书上说
好记性不如烂笔头
我想即使是以前忽略或者新get的很简单的东西,自己动手记下来不管如何印象也会更深刻。
ZipOutputStream打包文件成一个zip,自定义写入文件夹和文件
Java实现将文件(包括压缩包)、文件夹压缩成zip,并且压缩包里面保留文件夹结构
参考链接、参考链接 、参考链接
Get请求且用?号形式传参,那么list参数必须加@RequestParam主键否则会报错
Java7文件复制方法
Java源码中有Files.copy(source, target, options)方法,options可以不传值。
例如:D:/a.txt文件要复制到E:/xx/b.txt中,先要得到得到两文件的path。
Ps:这里要求目标文件b.txt文件不存在(存在会报错),而且目标文件的路径必须要有,源代码方法中没有创建dir的方法。
参考代码如下:
1 | //要确认拷贝的路径存在 |
linux查看进程占用端口的方法
方法一:
1、netstat -lntp
找到端口对应的应用的ID
2、ps -ef |grep 进程ID
方法二:
lsof -i:端口号
输出的列表的pid就是进程号
在Java的switch中使用枚举判断
在Java的switch中使用枚举的code或者name会报错,必须使用枚举类。
需要在枚举类中加入一个根据编码获取枚举类的方法。
参考代码如下:
1 | /** |
Ps: case后面的枚举不能带类名,否则会报"An enum switch case label must be the unqualified name of an enumeration constant"错误。
参考链接
MySQL创建的临时表会全表扫描,很影响性能
Nginx配置学习
1 | location /portal { |
该配置(portal、proxy_pass后面无/)举例:
原始请求链接:http://10.12.7.182:5001/portal/carousel/getList
实际请求链接:http://10.12.7.182:7001/portal/carousel/getList
1 | location /portal/ { |
该配置(portal、proxy_pass后面有/)举例:
原始请求链接:http://10.12.7.182:5001/portal/carousel/getList
实际请求链接:http://10.12.7.182:7001/carousel/getList
Nginx配置返回文本或者JSON
参考代码如下:
1 | location /proxy_reward/web/login/findemployeebyphone { |
Nginx负载均衡配置
参考代码如下:
1 | upstream test{ |
validation相关注解速查
Git修改.gitignore文件修改后刷新
-
刷新命令
1
2git rm -r --cached .
git add . -
提交代码命令
1
2git commit -m "update .gitignore"
git push origin
ApplicationContext发布事件和处理事件示例代码
要发布的事件:
1 | @Getter |
发布事件:
1 | applicationContext.publishEvent(new TransformDocEvent(doc)); |
处理事件:
1 | @Component |
FastStone Capture有两种滚动截屏方式
1、按住CTRL键选取截取范围,再点击滚动条下面的向下箭头
2、点击鼠标左键即可自动捕捉窗口全部页面(适用于页面禁用滚动条的时候)
RestTemplate学习
1、RestTemplate是Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。
2、调用RestTemplate的默认构造函数,RestTemplate对象在底层通过使用java.net包下的实现创建HTTP 请求,可以通过使用ClientHttpRequestFactory指定不同的HTTP请求方式。
3、ClientHttpRequestFactory接口主要提供了两种实现方式
- 一种是SimpleClientHttpRequestFactory,使用J2SE提供的方式(既java.net包提供的方式)创建底层的Http请求连接。
- 另一种方式是使用HttpComponentsClientHttpRequestFactory方式,底层使用HttpClient访问远程的Http服务,使用HttpClient可以配置连接池和证书等信息。
RestTemplate的getForObject完成get请求、postForObject完成post请求、put对应的完成put请求、delete完成delete请求;还有execute可以执行任何请求的方法,需要你设置RequestMethod来指定当前请求类型。
使用RestTemplate发送post请求
示例代码:
1 | // RestTemplate也可以通过@Autowired注入到Spring容器。 |
如果直接使用在postForObject中把对象传入很容易出现no suitable HttpMessageConverter found for request type的错误,建议直接先转成字符串,见jsonObj.otString()。
使用RestTemplate调用接口确实非常优雅,几行代码就解决了。
Swagger常用注解
使用IDEA合并远程其他分支到本地
- 鼠标放到项目目录上右击
- 点击Git
- 点击Repository
- 点击pull
- 在Branchs to merge中选择远程分支
建议合并前把本地分支的代码先提交到远程仓库,方便解决冲突
@Transactional与@Async同时使用事务会失效
@Transactional与@Async同时使用事务会失效,在Transactional注解中的多线程也会导致事物失效
参考链接
事务失效问题解决:
方法一:@Async与@Transactional不使用在同一个方法上
方法二:手动提交事务及回滚事务
参考链接
方法三:将需要事务的方法放到另一个类中。
1 | @Component |
经过测试,直接在service实现类的方法同时加两个注解,事务和多线程都不会失效。
1 | achievementRepetitionService.checkAchievementRepetition(); |
经过测试,service实现类的非异步方法调用异步方法,多线程失效,事务不会失效。
1 | @Override |
几种常见限流算法(方式)
限流算法
- 固定窗口算法
固定窗口算法又叫计数器算法,是一种简单方便的限流算法。主要通过一个支持原子操作的计数器来累计 1 秒内的请求次数,当 1 秒内计数达到限流阈值时触发拒绝策略。每过 1 秒,计数器重置为 0 开始重新计数。 - 滑动窗口算法
固定窗口算法假如 QPS为2,但是当遇到时间窗口的临界突变时,如 1s 中的后 500 ms 和第 2s 的前 500ms 时,虽然是加起来是 1s 时间,却可以被请求 4 次。
滑动窗口算法是对固定窗口算法的改进,可以每 500ms 滑动一次窗口,可以发现窗口滑动的间隔越短,时间窗口的临界突变问题发生的概率也就越小,不过只要有时间窗口的存在,还是有可能发生时间窗口的临界突变问题。 - 滑动日志算法
滑动日志算法是实现限流的另一种方法,这种方法比较简单。基本逻辑就是记录下所有的请求时间点,新请求到来时先判断最近指定时间范围内的请求数量是否超过指定阈值,由此来确定是否达到限流,这种方式没有了时间窗口突变的问题,限流比较准确,但是因为要记录下每次请求的时间点,所以占用的内存较多。 - 漏桶算法
漏桶算法中的漏桶是一个形象的比喻,这里可以用生产者消费者模式进行说明,请求是一个生产者,每一个请求都如一滴水,请求到来后放到一个队列(漏桶)中,而桶底有一个孔,不断的漏出水滴,就如消费者不断的在消费队列中的内容,消费的速率(漏出的速度)等于限流阈值。即假如 QPS 为 2,则每 1s / 2= 500ms 消费一次。漏桶的桶有大小,就如队列的容量,当请求堆积超过指定容量时,会触发拒绝策略。 - 令牌桶算法
令牌桶算法同样是实现限流是一种常见的思路,最为常用的 Google 的 Java 开发工具包 Guava 中的限流工具类 RateLimiter 就是令牌桶的一个实现。令牌桶的实现思路类似于生产者和消费之间的关系。
系统服务作为生产者,按照指定频率向桶(容器)中添加令牌,如 QPS 为 2,每 500ms 向桶中添加一个令牌,如果桶中令牌数量达到阈值,则不再添加。
请求执行作为消费者,每个请求都需要去桶中拿取一个令牌,取到令牌则继续执行;如果桶中无令牌可取,就触发拒绝策略,可以是超时等待,也可以是直接拒绝本次请求,由此达到限流目的。 - Redis分布式限流
Redis 是一个开源的内存数据库,可以用来作为数据库、缓存、消息中间件等。Redis 是单线程的,IO多路复用,又在内存中操作,所以速度极快,得益于 Redis 的各种特性,所以使用 Redis 实现一个限流工具是十分方便的。
总结
- 窗口算法实现简单,逻辑清晰,可以很直观的得到当前的 QPS 情况,但是会有时间窗口的临界突变问题,而且不像桶一样有队列可以缓冲。
- 桶算法虽然稍微复杂,不好统计 QPS 情况,但是桶算法也有优势所在。
漏桶模式消费速率恒定,可以很好的保护自身系统,可以对流量进行整形,但是面对突发流量不能快速响应。
令牌桶模式可以面对突发流量,但是启动时会有缓慢加速的过程,不过常见的开源工具中已经对此优化。
SpringBoot使用Redis教程(共五步)
用RestTemplate调用对方接口一直报400 Bad Request,但是用PostMan没问题
MediaType type = MediaType.parseMediaType(“application/json; charset=UTF-8”);
把charset=utf-8去掉就可以了,对方接口解析请求头有问题
EasyExcel大数据导入导出
思路:分批次查询(分页查询)+循环写入Excel,及时清理查询的list,防止OOM
1、查询出此次要导出的数据量count,根据count和自定义的每次查询数量size(我是10000)来计算出一共要查询n次。(防止一次查询数据量过大导致OOM)
2、循环n次,每次查询size大小的数据。然后导出到同一个sheet的excel中。注意,每次循环完clear掉list数据,防止占用内存
3、循环中会有一个问题,就是你在循环过程中别人新插入了数据怎么办,我的做法是最开始查询count的时候根据创建时间排序,获取最新的创建时间,然后后面的循环都使用这个创建时间作为搜索条件。这样就只会查询到和count相符合的数据了。
注意:导出数据超过6w条的时候excel格式要用xlsx。
参考链接、参考链接(含代码)、参考链接
EasyExcel大数据导入
思路:分批次写入数据库,及时清理读取的list,防止OOM
参考链接(含代码)
Windows查看CPU核数和线程数
方法一:
第一步:开始菜单->运行->cmd->输入 wmic
第二步(方法一):
分别输入下面的命令:
1 | wmic:root\cli>cpu get NumberOfCores |
第二步(方法二)
- 输入 cpu get *
- 底部滑动条往右拖
- NumberOfCores为核数 NumberOfLogicalProcessors为逻辑cpu数(线程数)
方法二:
在任务管理器中的性能页面中查看,如下图所示:
方法三:
运行dxdiag命令
Linux查看物理CPU个数、CPU核心数、逻辑CPU的个数(线程数)、型号、内核信息
1、物理CPU数
实际机器中插槽上的CPU个数,查询命令如下:
1 | [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "physical id"|sort |uniq|wc -l |
2、CPU核心数
一块物理CPU上能处理数据的芯片组数量。也就是说一个物理CPU上可能会有多个核心,日常中说的双核、四核就是指的CPU核心。查询命令如下:
1 | [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "cores"|uniq |
3、逻辑CPU的个数(线程数)
逻辑CPU数量=物理cpu数量 x CPU核心数 x 2(如果支持并开启ht)。
查询命令如下所示:
1 | [award_dev@localhost ~]$ cat /proc/cpuinfo |grep "processor"|wc -l |
从结果可以看出,我的机器不支持(或不开启)ht。
一般情况,我们认为一颗cpu可以有多核,加上intel的超线程技术(HT),可以在逻辑上再分一倍数量的cpu core出来;所以逻辑CPU的值理论上是可以超过100%的。
4、CPU数量及型号
1 | [award_dev@localhost ~]$ cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c |
5、查看当前操作系统内核信息
1 | [award_dev@localhost ~]$ uname -a |
Ps:Linux下top查看的CPU也是逻辑CPU个数
超线程
一个CPU核可以执行一个线程,由英特尔开发超线程技术可以把一个线程模拟出两个线程来使用,使得单个核心用起来像两个核心一样,以充分发挥CPU的性能。
通常:逻辑CPU数量=物理cpu数量 x CPU核心数,若不相等则表示CPU支持超线程技术。