核心
rocketmq
redis
集合有哪些,有什么区别
ArrayList LinkedList Vector
HashSet TreeSet
HashMap LinkedHashMap TreeMap ConcurrentHashMap HashTable
hashmap底层原理,hashmap线程不安全 怎么办
哈希 链表、红黑树 (来处理极端情况下的哈希碰撞)
数组+(链表或红黑树)Node类来存储Key、Value
:hashMap什么时候扩容?
注意,是在put的时候才会扩容,在容量超过四分之三的时候就会扩容
:hashMap的key可以为空吗
可以,Null值会作为key来存储
:key重复了,会被覆盖吗?
会的
HashMap扩容为什么总是2的次幂?
HashMap扩容主要是给数组扩容的,因为数组长度不可变,而链表是可变长度的。从HashMap的源码中可以看到HashMap在扩容时选择了位运算,向集合中添加元素时,会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置。只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞
当HashMap的容量是16时,它的二进制是10000,(n-1)的二进制是01111
JDk1.7HashMap扩容死循环问题
HashMap是一个线程不安全的容器,在最坏的情况下,所有元素都定位到同一个位置,形成一个长长的链表,这样get一个值时,最坏情况需要遍历所有节点,性能变成了O(n)。
JDK1.7中HashMap采用头插法拉链表,所谓头插法,即在每次都在链表头部(即桶中)插入最后添加的数据。
死循环问题只会出现在多线程的情况下。
假设在原来的链表中,A节点指向了B节点。
在线程1进行扩容时,由于使用了头插法,链表中B节点指向了A节点。
在线程2进行扩容时,由于使用了头插法,链表中A节点又指向了B节点。
在线程n进行扩容时,…
这就容易出现问题了。。在并发扩容结束后,可能导致A节点指向了B节点,B节点指向了A节点,链表中便有了环
死循环
解决 方案:
1)、使用线程安全的ConcurrentHashMap替代HashMap,个人推荐使用此方案。
2)、使用线程安全的容器Hashtable替代,但它性能较低,不建议使用。
3)、使用synchronized或Lock加锁之后,再进行操作,相当于多线程排队执行,也会影响性能,不建议使用。
为了解决JDK1.7死循环问题,JDK1.8引入了红黑树
即在数组长度大于64,同时链表长度大于8的情况下,链表将转化为红黑树。同时使用尾插法。当数据的长度退化成6时,红黑树转化为链表。
从JDK1.8开始,在HashMap里面定义了一个常量TREEIFY_THRESHOLD,默认为8。当链表中的节点数量大于TREEIFY_THRESHOLD时,链表将会考虑改为红黑树
使用线程安全如Concurrenthashmap、HashTable
OOP与AOP的区别
几个微服务 15-20
主要 report collect
SpringCloud优缺点
优点:
● 1.耦合度比较低。不会影响其他模块的开发。
● 2.减轻团队的成本,可以并行开发,不用关注其他人怎么开发,先关注自己的开发。
● 3.配置比较简单,基本用注解就能实现,不用使用过多的配置文件。
● 4.微服务跨平台的,可以用任何一种语言开发。
● 5.每个微服务可以有自己的独立的数据库也有用公共的数据库。
● 6.直接写后端的代码,不用关注前端怎么开发,直接写自己的后端代码即可,然后暴露接口,通过组件进行服务通信。
缺点:
● 1.部署比较麻烦,给运维工程师带来一定的麻烦。
● 2.针对数据的管理比麻烦,因为微服务可以每个微服务使用一个数据库。
● 3.系统集成测试比较麻烦
● 4.性能的监控比较麻烦。【最好开发一个大屏监控系统】
Spring Cloud底层原理
Eureka、Ribbon、Feign、Hystrix、Zuul、Spring Cloud Config:分布式统一配置管理 等二十几个组件一直在更新中
https://blog.csdn.net/qq_42046105/article/details/83793787
Eureka注册中心
服务注册与发现
Ribbon:负载均衡 多个请求到服务的负载均衡 客户端负载均衡
Nginx负载均衡
这里的负载并不一定是通常意义上我们说的“CPU负载”,而是系统当前的压力,可以用CPU负载来衡量,也可以用连接数、I/O使用率、网卡吞吐量等来衡量系统的压力
1.服务器端负载均衡Nginx
nginx是客户端所有请求统一交给nginx,由nginx进行实现负载均衡请求转发,属于服务器端负载均衡。
既在客户端实现负载均衡。
应用场景的区别:
Nginx适合于服务器端实现负载均衡 比如Tomcat ,Ribbon适合与在微服务中RPC远程调用实现本地服务负载均衡,比如Dubbo、SpringCloud中都是采用本地负载均衡。
既请求有nginx服务器端进行转发。
客户端负载均衡Ribbon
Ribbon是从eureka注册中心服务器端上获取服务注册信息列表,缓存到本地,让后在本地实现轮训负载均衡策略。
Feign:服务和服务之间的调用 声明性的Web服务客户端
Zuul:服务网关:把多个微服务公共部分拿出来,提供给不同的app/h5/pc/平台调用
单点入口 路由转发 限流熔断 日志监控 安全认证
跨域问题怎么解决
解决跨域,原因:域名不同,域名相同端口不同;二级域名不同
什么是跨域?
就是两个项目之间通讯,如果访问的域名与ajax访问的地址不一致情况,默认情况浏览器有一个安全机制。
postman不一定能测试出来,因为postman是存调用,没有浏览器那种安全机制也叫安全策略。
报错可能为
No 'Access-Control-Allow-Origin' header is present on the requested resource.
如果a,b页面的协议、域名、端口、子域名不同,或是a页面为ip地址,b页面为域名地址,所进行的访问行动都是跨域的,而浏览器为了安全问题一般都限制了跨域访问,也就是说不允许跨域请求资源。
因为浏览器收到不同的域名,不同端口,不同协议不允许共享资源的,保障浏览器安全。同源策略是针对浏览器设置的门槛。
解决的方式有很多,包括网上的解决方案有很多,比较高级的微服务解决方案可能是网关和nginx层面了
设置响应头允许跨域(可以推荐,适合于小公司快速解决问题)
使用nginx搭建api网关接口(强烈推荐)因为保证域名和端口都一致
使用Zuul微服务搭建API网关(强烈推荐)SpringCloud
1、网关 市场流行的gateway zuul
2、implements WebMvcConfigurer 如下面代码
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("")
.allowedMethods(“POST”, “GET”)
.maxAge(168000)
.allowedHeaders("")
.allowCredentials(true);
}
}
3、nginx配置跨域
nginx需要后端、前端一块来配置,广泛应用在微服务 分布式 高并发的业务场景中
原理很简单,就是中介带有网关,客户端去请求网关,而不是直接请问接口,
网关有点像代理,代理第一层请求的压力,然后转发给不同的项目或者服务。
原理还是保证域名和端口号都是相同的。
根据项目不同名称使用nginx进行转发到服务器地址中。
同时解决了安全问题,是微服务常用的手段之一。
配置nginx
server {
listen 80;
##授权从www.xxx.com的请求
server_name www.xxx.com;
###A项目
location /a {
proxy_pass http://a.xxx.com:8080/;
index index.html index.htm;
}
###B项目
location /b {
proxy_pass http://b.xxx.com:8081/;
index index.html index.htm;
}
}
前端代码
4、 设置响应头跨域
@RequestMapping("/ajaxB")
public Map<String, Object> ajaxB(HttpServletResponse response) {
response.setHeader(“Access-Control-Allow-Origin”, “*”);
Map<String, Object> result = new HashMap<String, Object>();
result.put(“errorCode”, “200”);
result.put(“errorMsg”, “登陆成功”);
return result;
}
5、单个接口实现的也有@CrossOrigin 加注解 局域与某个controller或接口
6、其他方案有很多
比如自定义过滤器,设置过滤跨域命题
比如HandlerInterceptor方法拦截器跨域解决
当然前端层面也是可以解决的哈
注册中心
https://blog.csdn.net/qq_36893229/article/details/120201306
SpringCloud与SpringBoot区别
● SpringBoot专注于快速方便的开发单个个体微服务。
● SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,
● 为各个微服务之间提供,配置管理、服务发现、断路器、路由、微代理、事件总线、全局锁、决策竞选、分布式会话等等集成服务
● SpringBoot可以离开SpringCloud独立使用开发项目, 但是SpringCloud离不开SpringBoot ,属于依赖的关系
● SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。
springboot了解 约定大于配置
spring boot和maven的约定大于配置体现 点:
1.maven的目录文件结构
1)默认有resources文件夹,存放资源配置文件。src-main-resources,src-main-java
默认的编译生成的类都在targetwen。
2)默认有target文件夹,将生成class文件盒编程生成的jar存放在target文件夹下
2.spring boot默认的配置文件必须是,也只能是application.命名的yml文件或者properties文件,且唯一
1)spring boot默认只会去src-main-resources文件夹下去找application配置文件
springboot启动流程
一、SpringBoot启动的时候,会构造一个SpringApplication的实例,构造SpringApplication的时候会进行初始化的工作,初始化的时候会做以下几件事:
1、把参数sources设置到SpringApplication属性中,这个sources可以是任何类型的参数.
2、判断是否是web程序,并设置到webEnvironment的boolean属性中.
3、创建并初始化ApplicationInitializer,设置到initializers属性中 。
4、创建并初始化ApplicationListener,设置到listeners属性中 。
5、初始化主类mainApplicatioClass。
二、SpringApplication构造完成之后调用run方法,启动SpringApplication,run方法执行的时候会做以下几件事:
1、构造一个StopWatch计时器,用来记录SpringBoot的启动时间 。
2、初始化监听器,获取SpringApplicationRunListeners并启动监听,用于监听run方法的执行。
3、创建并初始化ApplicationArguments,获取run方法传递的args参数。
4、创建并初始化ConfigurableEnvironment(环境配置)。封装main方法的参数,初始化参数,写入到 Environment中,发布 ApplicationEnvironmentPreparedEvent(环境事件),做一些绑定后返回Environment。
5、打印banner和版本。
6、构造Spring容器(ApplicationContext)上下文。先填充Environment环境和设置的参数,如果application有设置beanNameGenerator(bean)、resourceLoader(加载器)就将其注入到上下文中。调用初始化的切面,发布ApplicationContextInitializedEvent(上下文初始化)事件。
7、SpringApplicationRunListeners发布finish事件。
8、StopWatch计时器停止计时,日志打印总共启动的时间。
9、发布SpringBoot程序已启动事件(started())
10、调用ApplicationRunner和CommandLineRunner
11、最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())
#{}与KaTeX parse error: Expected 'EOF', got '#' at position 7: {}的区别 #̲{}是预编译处理(底层使用的是…{}是字符串替换。
在使用排序时使用ORDER BY ${id},如果使用#{id},则会被解析成ORDER BY “id”
mybatis xml常用的关键字
select mapper resultType insert update resultMap sql delete
mybatisplus与mybatis有什么区别
mybatisplus 可以说是 mybatis 的加强版
ISevice Mapper封装的类
如何获取自动生成主键
insert里useGeneratedKeys=“true”
Spring的两大核心依赖注入、AOP
ioc:什么是 Spring IOC 容器?
Spring 框架的核心是 Spring 容器。容器创建对象,将它们装配在一起,配置它们并管理它们的完整生命周期。Spring 容器使用依赖注入来管理组成应用程序的组件。容器通过读取提供的配置元数据来接收对象进行实例化,配置和组装的指令。该元数据可以通过 XML,Java 注解或 Java 代码提供。
什么是依赖注入?
在依赖注入中,您不必创建对象,但必须描述如何创建它们。您不是直接在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务。由 IoC 容器将它们装配在一起。
如何实现AOP
利用动态代理来实现AOP,比如JDK动态代理或Cglib动态代理,利用动态代理技术,可以针对某个类生成代理对象,当调用代理对象的某个方法时,可以任意控制该方法的执行,比如可以先打印执行时间,再执行该方法,并且该方法执行完成后,再次打印执行时间。项目中,比如事务、权限控制、方法执行时长日志都是通过AOP技术来实现,凡是需要对某些方法做统一处理的都可以用AOP来实现,利用AOP可以做到业务无侵入
SpringMVC理解和流程
1、 用户向服务端发送一次请求,这个请求会先到前端控制器DispatcherServlet(也叫中央控制器)。
2、DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器。由此得知,该请求该由哪个Controller来处理(并未调用Controller,只是得知)
3、DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller
4、HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
5、DispatcherServlet将ModelAndView交给ViewReslover视图解析器解析,然后返回真正的视图。
6、DispatcherServlet将模型数据填充到视图中
7、DispatcherServlet将结果响应给用户
Mybatis工作原理
1.首先要建立一个sqlSessionFactory:
建一个工具类,在里面引入核心配置文件
将核心配置文件转化成流文件
利用sqlSessionFactoryBuiler这个类调用build方法将材料(核心配置文件流)实例化一个sqlSessionfactor(sqlSessiongong工厂,用来生产sqls
Session),
最后写一个getSqlSession方法
2 创建核心配置文件:
官网里有模板,里面主要包括连接数据库所需要的几个配置(driver,url,username,pwd),和注册机(每个mapper都要在注册机中注册)
3 最后crud操作:调用工具类的getSqlSession方法–\u003e获得sqlSession(相当于connection)
利用sqlSession去连接指定mapper接口
最后执行sql
Mybatis核心类:
SqlSessionFactory:每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或通过Java的方式构建出 SqlSessionFactory 的实例。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,建议使用单例模式或者静态单例模式。一个SqlSessionFactory对应配置文件中的一个环境(environment),如果你要使用多个数据库就配置多个环境分别对应一个SqlSessionFactory。
SqlSession:SqlSession是一个接口,它有2个实现类,分别是DefaultSqlSession(默认使用)以及SqlSessionManager。SqlSession通过内部存放的执行器(Executor)来对数据进行CRUD。此外SqlSession不是线程安全的,因为每一次操作完数据库后都要调用close对其进行关闭,官方建议通过try-finally来保证总是关闭SqlSession。
Executor:Executor(执行器)接口有两个实现类,其中BaseExecutor有三个继承类分别是BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statement,跟Simple的唯一区别就是内部缓存statement),SimpleExecutor(默认,每次都会创建新的statement)。以上三个就是主要的Executor。通过下图可以看到Mybatis在Executor的设计上面使用了装饰器模式,我们可以用CachingExecutor来装饰前面的三个执行器目的就是用来实现缓存。
MappedStatement:MappedStatement就是用来存放我们SQL映射文件中的信息包括sql语句,输入参数,输出参数等等。一个SQL节点对应一个MappedStatement对象。
dao流程
Dao接口即Mapper接口。接口的全限名,就是映射文件中的namespace的值;接口的方法名,就是映射文件中Mapper的Statement的id值;接口方法内的参数,就是传递给sql的参数。
Mapper接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key值,可唯一定位一个MapperStatement。在Mybatis中,每一个、、、标签,都会被解析为一个MapperStatement对象。
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespace为com.mybatis3.mappers.StudentDao下面 id 为 findStudentById 的 MapperStatement。
Mapper 接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Mapper接口生成代理对象proxy,代理对象会拦截接口方法,转而执行MapperStatement所代表的sql,然后将sql执行结果返回。
Git提交规范、怎么解决冲突
idea或专门可视化工具
MySQL索引
一、单列索引
一个索引只包含单个列,但一个表中可以有多个单列索引。 这里不要搞混淆了
1、普通索引:MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值,纯粹为了查询数据更快一 点。
2、唯一索引:索引列中的值必须是唯一的,但是允许为空值。
3、主键索引:是一种特殊的唯一索引,不允许有空值。(主键约束,就是一个主键索引)。
二、组合索引
在表中的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用,使用组合索引时遵循最左前缀集合。
三、全文索引
全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引,介绍了要求,说说什么是全文索引,就是在一堆文字中,通过其中的某个关键字等,就能找到该字段所属的记录行。
索引的数据结构使用的是B+树
B+树,作为B树的升级版,在B树基础上,MySQL在B树的基础上继续改造,使用B+树构建索引。B+树和B树最主要的区别在于非叶子节点是否存储数据的问题
B树:非叶子节点和叶子节点都会存储数据。
B+树:只有叶子节点才会存储数据,非叶子节点至存储键值。叶子节点之间使用双向指针连接,最底层的叶子节点形成了一个双向有序链表。
B+树的最底层叶子节点包含了所有的索引项。从图上可以看到,B+树在查找数据的时候,由于数据都存放在最底层的叶子节点上,所以每次查找都需要检索到叶子节点才能查询到数据。所以在需要查询数据的情况下每次的磁盘的IO跟树高有直接的关系,但是从另一方面来说,由于数据都被放到了叶子节点,所以放索引的磁盘块锁存放的索引数量是会跟这增加的,所以相对于B树来说,B+树的树高理论上情况下是比B树要矮的。也存在索引覆盖查询的情况,在索引中数据满足了当前查询语句所需要的全部数据,此时只需要找到索引即可立刻返回,不需要检索到最底层的叶子节点。
https://blog.csdn.net/weixin_52967653/article/details/125229129
mysql锁
表锁:不会出现死锁,发生锁冲突几率高,并发低。MYISAM不支持行锁
行锁:会出现死锁,发生锁冲突几率低,并发高。InnoDB支持行锁和表锁
行锁分 共享锁 和 排它锁。
共享锁又称:读锁。当一个事务对某几行上读锁时,允许其他事务对这几行进行读操作,但不允许其进行写操作,也不允许其他事务给这几行上排它锁,但允许上读锁。
排它锁又称:写锁。当一个事务对某几个上写锁时,不允许其他事务写,但允许读。更不允许其他事务给这几行上任何锁。包括写锁。
1.行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
2.两个事务不能锁同一个索引
mysql事务
原子性 一致性 隔离性 持久性
脏读:对于两个事务T1,T2,T1读取了已经被T2更新但还没有被提交的字段之后,若T2回滚,T1读取的内容就是临时且无效的
🌴不可重复读 :对于两个事务T1,T2,T1读取了一个字段,然后T2更新了该字段之后,T1在读取同一个字段,值就不同了
🌴 幻读:对于两个事务T1,T2,T1在A表中读取了一个字段,然后T2又在A表中插入了一些新的数据时,T1再读取该表时,就会发现神不知鬼不觉的多出几行了…
原文链接:https://blog.csdn.net/qq_56880706/article/details/122653735
隔离级别:可提交读 不可提交读 可串行化
- read uncommitted(读未提交数据):允许事务读取未被其他事务提交的变更。(脏读、不可重复读和幻读的问题都会出现)。
- read committed(读已提交数据):只允许事务读取已经被其他事务提交的变更。(可以避免脏读,但不可重复读和幻读的问题仍然可能出现)
3.repeatable read(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新(update)。(可以避免脏读和不可重复读,但幻读仍然存在) - serializable(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可避免,但性能十分低下(因为你不完成就都不可以弄,效率太低)
select @@tx_isolation
怎么提高SQL性能优化
执行计划 索引设置合理不合理
应尽量避免在where子句中使用or来连接条件(用联合查询union all )使用or可能会使索引失效
如果知道查询结果只有一条或者只要最大/最小一条记录,建议用limit 1
查询SQL尽量不要使用select *,而是select具体字段。
优化limit分页
//方案一 :返回上次查询的最大记录(偏移量)
select id,name from employee where id>10000 limit 10.
//方案二:order by + 索引
select id,name from employee order by id limit 10000,10
//方案三:在业务允许的情况下限制页数
使用where条件限定要查询的数据,避免返回多余的行
索引列上使用mysql的内置函数,索引失效
应尽量避免在 where 子句中对字段进行表达式操作,这将导致系统放弃使用索引而进行全表扫
把%放前面,并不走索引
把% 放关键字后面,还是会走索引的
Inner join 、left join、right join,优先使用Inner join,如果是left join,左边表结果尽量小
应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
对查询进行优化,应考虑在 where 及 order by 涉及的列上建立索引,尽量避免全表扫描。
慎用distinct关键字
distinct 关键字一般用来过滤重复记录,以返回不重复的记录。在查询一个字段或者很少字段的情况下使用时,给查询带来优化效果。但是在字段很多的时候使用,却会大大降低查询效率。
删除冗余和重复索引
如果数据量较大,优化你的语句
避免同时修改或删除或添加过多数据,因为会造成cpu利用率过高,从而影响别人对数据库的访问。
where子句中考虑使用默认值代替null。
不要有超过5个以上的表连接,如果一定需要连接很多表才能得到数据,那么意味着糟糕的设计了。
索引不宜太多,一般5个以内
索引虽然提高了查询的效率,但是也降低了插入和更新的效率
尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型
索引不适合建在有大量重复数据的字段上,如性别这类型数据库字段。
当在SQL语句中连接多个表时,请使用表的别名,并把别名前缀于每一列上,这样语义更加清晰。
尽可能使用varchar/nvarchar 代替 char/nchar。
为了提高group by 语句的效率,可以在执行到该语句前,把不需要的记录过滤掉。
select job,avg(salary) from employee where job =‘president’
or job = ‘managent’ group by job;
如果字段类型是字符串,where时一定用引号括起来,否则索引失效
使用explain 分析你SQL的计划
explain select * from user where userid =10086 or age =18;
id:select的执行顺序,都相同时从上往下执行,当不相同的时,值越大越先执行。
type:表示连接类型,由好到差 null >system >const >eq_ref >ref >range >index >all 。
possible_key:可能用到的索引。
key:实际使用的索引。
key_len:索引长度。
filtered:返回数据的行数占总记录数的百分比,越大越好。
MySQL编写场景
Redis基本类型,如何CRUD
string hash set zset list
set 设置过期时间
get
del
append 追加字符串
strlen 字符串长度
数字value的加减
incr (key) :value + 1
decr (key) :value - 1
incrby (key) (number):value + number
decrby (key) (number):value - number
获取或者设置指定范围内的值
getrange (key) (begin) (end) :获取[begin,end]下标范围内的值,如果是(0,1)就是获取所有值
setrange (key) (begin) (xxxx) :从begin下标开始设置xxx值,将原有的替换掉
设置键值过期时间
setex (key) (seconds) expire:设置键过期时间
ttl (key) :查看key剩余存活时间
同时设置或获取多个key-value
met (key1) (value1) (key2) (value2):用于同时设置一个或多个 key-value 对
mget (key1) (key2) :返回所有(一个或多个)给定 key 的值(如果某个key不存在,不存在的key返回null)
msetnx(key1) (value1) (key2) (value2):当所有 key 都成功设置,返回 1 。 如果有一个key设置失败,所有的key设置都会失败,返回 0 。原子操作
redis场景
排行榜
登陆
分布式锁
计数器
分布式全局唯一id(string)
redis常见问题
1、缓存雪崩
数据未加载到缓存或者缓存大面积失效,或者缓存故障,所有请求直接落到数据库。数据库扛不住。
解决方案:
事前:redis高可用,避免全盘崩溃。失效时间加个随机数。本地 ehcache 缓存。
事中:是否能够快速从RDB或者AOF文件快速恢复缓存。是否能够限流或者降级避免数据库被打死。
事后:检查缓存数据恢复情况。问题期间影响的业务数据恢复。
2、缓存击穿
热点key失效的瞬间,有大量的请求拥入数据库。
解决方案:
1)热点key长时间有效,然后定时同步或者使用发布订阅机制。
2)key失效,构建加锁,从数据库里面获取数据,其他请求隔段时间访问该key,直到获取数据之后,锁失效。
3、缓存穿透
redis尽管没有挂掉,但是查询太多不存在的数据,就会导致会有太多的无效请求进入数据库,也会导致数据库超负荷。
解决方案:
1)不存在的key也维护起来,并且维护过期时间。
2)对于相对稳定不变的数据,可以使用布隆过滤器,但是要有过期时间。
3)在入口校验掉不合法的请求。
4、缓存并发
主要是写竞争,get和set分开执行了。
解决方案:放在队列中或者加锁。
5、缓存预热
系统上线时,先把数据库的数据,加载到缓存中。避免上线瞬间,大量的请求拥入数据库。
https://blog.csdn.net/qq_21561501/article/details/103145155
Redis哨兵模式(现在公司中所有的集群都用哨兵模式)
Redis高可用架构—哨兵(sentinel)
redis集群
redis哨兵的作用:
- 监控主数据库和从数据库是否正常运行。
- 主数据库出现故障时,可以自动将从数据库转换为主数据库,实现自动切换。
使用rocketmq如何保证消息不丢失(生产者、消费者)
Producer发送消息阶段
发送消息阶段涉及到Producer到broker的网络通信,因此丢失消息的几率一定会有,那RocketMQ在此阶段用了哪些手段保证消息不丢失了(或者说降低丢失的可能性)。
手段一:提供SYNC的发送消息方式,等待broker处理结果。
RocketMQ提供了3种发送消息方式,分别是:
同步发送:Producer 向 broker 发送消息,阻塞当前线程等待 broker 响应 发送结果。
异步发送:Producer 首先构建一个向 broker 发送消息的任务,把该任务提交给线程池,等执行完该任务时,回调用户自定义的回调函数,执行处理结果。
Oneway发送:Oneway 方式只负责发送请求,不等待应答,Producer只负责把请求发出去,而不处理响应结果。
我们在调用producer.send方法时,不指定回调方法,则默认采用同步发送消息的方式,这也是丢失几率最小的一种发送方式(但是效率比较低)。
手段二:发送消息如果失败或者超时,则重新发送。
发送重试源码如下,本质其实就是一个for循环,当发送消息发生异常或超时的时候重新循环发送。默认重试3次,重试次数可以通过producer指定。
手段三:broker提供多master模式
即使某台broker宕机了,保证消息可以投递到另外一台正常的broker上。
如果broker只有一个节点,则broker宕机了,即使producer有重试机制,也没用(Broker都挂了,哪来的重试机制),因此利用多主模式,当某台broker宕机了,换一台broker进行投递,保持高可用。
总结
producer消息发送方式虽然有3种,但为了减小丢失消息的可能性尽量采用同步的发送方式,同步等待发送结果,利用同步发送+重试机制+多个master节点,尽可能减小消息丢失的可能性。
Broker处理消息阶段
手段四:提供同步刷盘的策略【等待刷盘成功才会返回producer成功】
public enum FlushDiskType { SYNC_FLUSH, //同步刷盘 ASYNC_FLUSH//异步刷盘(默认) }
我们知道,当消息投递到broker之后,会先存到page cache【页面缓存】,然后根据broker设置的刷盘策略是否立即刷盘,也就是如果刷盘策略为异步,broker并不会等待消息落盘才返回producer一个成功的消息,也就是说当broker所在的服务器突然宕机,则会丢失部分页的消息。同步刷盘的策略【等待刷盘成功才会返回给producer一个成功的消息】
解释:
同步刷盘:当数据写入到内存中之后立刻刷盘(同步的将内存中的数据持久化到磁盘上),在保证刷盘成功的前提下响应一个消息给Producer。
异步刷盘:数据写入内存后,直接响应一个消息给Producer。异步将内存中的数据持久化到磁盘上。
手段五:提供主从模式,同时主从支持同步双写
即使broker设置了同步刷盘,如果主broker磁盘损坏,也是会导致消息丢失。 因此可以给broker指定slave,同时设置master为SYNC_MASTER,然后将slave设置为同步刷盘策略。
此模式下,producer每发送一条消息,都会等消息投递到master和slave都落盘成功了,broker才会当作消息投递成功,从而保证休息不丢失。
总结
在broker端,消息丢失的可能性主要在于刷盘策略和同步机制。
RocketMQ默认broker的刷盘策略为异步刷盘,如果有主从,同步策略也默认的是异步同步,这样子可以提高broker处理消息的效率,但是会有丢失的可能性。因此可以通过同步刷盘策略+同步slave策略(slave也可以进行刷盘)+主从双写的方式解决丢失消息的可能。
Consumer消费消息阶段
手段六:consumer默认提供的是At least Once机制
从producer投递消息到broker,即使前面这些过程保证了消息正常持久化,但如果consumer消费消息没有消费到也算是消息的丢失。因此RockerMQ默认提供了At least Once机制保证消息可靠消费。
何为At least Once?
Consumer先pull【主动拉取Broker中的信息】 消息到本地,消费完成后,才向服务器返回ack(消费成功的消息–acknowledge)。
通常消费消息的ack机制一般分为两种思路:
1、先提交后消费;
2、先消费,消费成功后再提交【这个更稳当】;
思路一可以解决重复消费的问题但是会丢失消息,因此Rocketmq默认实现的是思路二,由各自consumer业务方保证幂等(通过给每个消息携带一个唯一标识信息,去数据库进行判断。或者在producer的时候就存储一个唯一标识(消息),消费成功删除redis中的消息确保不被重复消费。)来解决重复消费问题。
手段七:消费消息重试机制
当消费消息失败了,如果不提供重试消息的能力,则也不能算完全的可靠消费,因此RocketMQ本身提供了重新消费消息的能力。
总结
consumer端要保证消费消息的可靠性,主要通过At least Once+消费重试机制保证。
归纳
MQ主要包含了4个组件 nameserver broker producer consumer
然后如何保证消息不丢失又需要对三个消息阶段进行保证
Producer发送消息阶段
1通过采用同步发送消息到broker,等待broker接收到消息过后返回的一个确认消息,虽然效率低,但是时丢失几率最小的方式,异步1和单向消息发送丢失的几率比同步消息丢失的几率大。
2发送消息失败或超时则进行重试。
3broker提供多master模式【即使某台broker宕机了,换一台broker进行投递,保持高可用】
===》采用同步消息和失败重试和多master模式
Broker处理消息阶段
手段四:提供同步刷盘的策略【等待刷盘成功才会返回producer成功】
当数据写入到内存中之后立刻刷盘(同步的将内存中的数据持久化到磁盘上),
手段五:提供主从模式,同时主从支持同步双写
主从broker都同步刷盘成功,才返回producer一个确认消息。
===》采用同步刷盘+broker主从模式,支持同步双写
Consumer消费消息阶段
consumer默认提供的是At least Once机制
手段6 broker队列中的消息消费成功,才返回一个确认消息给broker。
手段7 当消息消费失败了,进行消费消息重试机制(保证幂等就行了。)
===》采用先消费,在返回一个确认消息+消息重试。
————————————————
版权声明:本文为CSDN博主「MXin5」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_64210833/article/details/126426814
rocketmq场景
还可以配置延时机制
应用解耦
流量削峰
数据分发
rocketmq问题 消息丢失 消费不到
RocketMQ内部封装了消息重试的处理流程,无需开发人员手动处理,并且支持了生产端、消费端两端的重试机制。
rocketmq与kafaka区别
性能
kafka单机写入TPS号称在百万条/秒;
RocketMQ大约在10万条/秒;
结论:追求性能的话,kafka单机性能更高
可靠性
RocketMQ支持异步/同步刷盘;异步/同步 Replication;
Kafka使用异步刷盘方式,异步Replication;
结论:RocketMQ所支持的同步方式提升了数据的可靠性。
消费失败重试机制
Kafka消费失败不支持重试
RocketMQ消费失败支持定时重试,每次重试间隔时间顺延
定时/延时消息
Kafka不支持定时消息;
RocketMQ支持定时消息
分布式事务消息
Kafka不支持分布式事务消息
RocketMQ支持分布式事务消息
https://www.cnblogs.com/yssd/p/15195106.html
Nacos注册中心是什么,实现原理是什么
注册中心是微服务架构中的纽带,类似于“通讯录”,它记录了服务和服务地址的映射关系。
动态加入 动态发现服务 统一配置 动态调整 统一管理
https://blog.csdn.net/a745233700/article/details/122915663
Nacos配置有哪些
mysql redis rocketmq 短信 注册中心 钉钉
配置中心 阿里云服务器 feign url 快速注册初始化数据
锁有哪些、为什么要用锁
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新时会判断此期间数据是否被更新
采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作 java中的乐观锁基本通过 CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block 直到拿到锁 Java 中的悲观锁就是Synchronized
AQS 框架下的锁则是先尝试 cas 乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock
自旋锁
原理
自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需自旋,等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
线程自旋需消耗 cup的,如果一直获取不到锁,则线程长时间占用CPU自旋,需要设定一个自旋等待最大事件在最大等待时间内仍未获得锁就会停止自旋进入阻塞状态。
—
原文链接:https://blog.csdn.net/qq_45399396/article/details/126705577
数据库是多用户共享资源的,在并发环境下会破坏数据库的一致性,所以需要锁来控制它们。
高并发场景
jvm组成结构
类加载子系统
负责从文件系统或是网络中加载class信息,加载的信息存放在一个称之为方法区的内存空间
方法区
用于存放类的信息、常量信息、常量池信息、包括字符串字面量和数字常量。我们常用的反射就是从这个方法区里读取的类信息
Java堆
堆空间是jvm启动的时候创建的一块内存区域,几乎所有的对象实例都放在这个空间里(可以理解成new 出来的那些对象)。
这个区域被划分为新生代和老年的,之后重点讲解,我们常说的GC垃圾回收机制,就是主要回收堆空间的垃圾数据。
堆空间里的数据,是被所有线程共享的,所以会存在线程安全的问题。所以那些锁就是为了解决堆空间数据线程安全的问题而生的。
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但这部分也是被频繁的读写使用,也可能会导致OutOfMemoryError异常的出现。
Java的NIO中的allocateDirect方法是可以直接使用直接内存的,能显著的提高读写的速度。
Java栈
就是我们常说的堆栈两兄弟之一的栈,所有线程共享堆空间里的数据,但是栈空间是每个线程独有的,互相直接不能访问。
栈空间是线程创建的时候所创建的一份内存空间,栈里主要保存一些局部变量、方法参数、Java方法调用,返回值等信息。
本地方法栈
本地方法栈和Java栈不同之处在于,可以直接调用Java本地方法,即JDK中用native修饰的方法。
垃圾收集系统
GC垃圾回收,是一个非常重要的知识点,保证我们程序能够有足够的内存空间运行,回收掉内存中已经无效的数据,大家就可以理解成我们日常中活中的垃圾回收。
回收算法一般有标记清除算法、复制算法、标记整理算法等等,之后的文章,我们会详解讲解每一种算法。
PC寄存器
它是每个线程私有的空间,JVM会为每个线程创建单独的PC寄存器,在任意时刻,一个Java线程总是在执行一个方法,这个方法被称为当前方法,如果当前方法不是本地方法,PC寄存器会执行当前正在被执行的指令,如果是本地方法,则PC寄存器值为undefined,寄存器存放如当前环境指针、程序计数器、操作栈指针、计算的变量指针等信息。
执行引擎
是jvm非常核心的组件,它负责执行jvm的字节码,一般先会编译成机器码后执行。
1.JVM一个类的加载过程?
一个类从加载到jvm内存,到从jvm内存卸载,它的整个生命周期会经历7个阶段:
1、加载(Loading):
classpath、jar包、网络、某个磁盘位置下的类的class二进制字节流读进来,在内存中生成一个代表这个类的java.lang.Class对象放入元空间,此阶段我们程序员可以干预,我们可以自定义类加载器来实现类的加载;
2、验证(Verification):
验证:验证Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束要求,保证虚拟机的安全;
3、准备(Preparation):
类变量赋默认初始值,int为0,long为0L,boolean为false,引用类型为null;常量赋正式值;
4、解析(Resolution):把符号引用翻译为直接引用;
5、初始化(Initialization):当我们new一个类的对象,访问一个类的静态属性,修改一个类的静态属性,调用一个类的静态方法,用反射API对一个类进行调用,初始化当前类,其父类也会被初始化… 那么这些都会触发类的初始化;
6、使用(Using):使用这个类;
7、卸载(Unloading):
(1).该类所有的实例都已经被GC,也就是JVM中不存在该Class的任何实例;
(2).加载该类的ClassLoader已经被GC;
(3).该类的java.lang.Class 对象没有在任何地方被引用,如不能在任何地方通过反射访问该类的方法;
站在Java虚拟机的角度来看,只存在两种不同的类加载器:
方法返回地址:
1、启动类加载器(Bootstrap ClassLoader),使用C++语言实现,是虚拟机自身的一部分;
2、其他所有的类加载器,由Java语言实现,独立存在于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader;
————————————————
版权声明:本文为CSDN博主「weixin_47785874」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_47785874/article/details/122584476
类加载机制—双亲委派机制
jvm对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。
在加载类的时候,是采用的双亲委派机制,即把请求交给父类处理的一种任务委派模式。
● 工作原理
(1)如果一个类加载器接收到了类加载的请求,它自己不会先去加载,会把这个请求委托给父类加载器去执行。
(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader
(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常,这就是双亲委派模式
解析: loadClass()的剖析
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 在缓存中检查该类是否已经加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
//获取当前类加载器的上级加载器
if (parent != null) {
// 2. 有上级的话,委派上级 loadClass
c = parent.loadClass(name, false);
} else {
// 3. parent为null,父类加载器是引导类加载器 BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
}
if (c == null) {
long t1 = System.nanoTime();
// 4. 每一层找不到,调用 findClass 方法(每个类加载器自己扩展)来加载
c = findClass(name);
// 5. 记录耗时
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 -
t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass©;
}
return c;
}
}
jvm 指令
jvm调优
单例模式 工厂模式
linux修改文件内容、查看日志、查看cpu
负载均衡怎么实现
Docker是什么、
Jenkins是什么
nginx是什么
vue是什么
铺位评估业务,rocketmq怎么补偿,怎么预防重复消费
数据字典是什么
如何实现登录?如何实现注册?
如何实现扫码登录
如何实现全局日志
如何实现加密
定时器和Redisson如何实现
如果数据量太多了,怎么处理这种批量、导入的
线程池用什么,参数有哪些,怎么设置参数,为什么
jmeter压力测试关注哪些指标
Feigin是什么 怎么请求
常用 GC 调优策略有哪些?
GC 调优目的
将转移到老年代的对象数量降低到最小; 减少 GC 的执行时间。
GC 调优策略
策略 1:将新对象预留在新生代,由于 Full GC 的成本远高于 Minor GC,因此尽可能将对象分配在新生代是明智的做法,实际项目中根据 GC 日志分析新生代空间大小分配是否合理,适当通过“-Xmn”命令调节新生代大小,最大限度降低新对象直接进入老年代的情况。
策略 2:大对象提前老年代,虽然大部分情况下,将对象分配在新生代是合理的。但是对于大对象这种做法却值得商榷,大对象如果首次在新生代分配可能会出现空间不足导致很多年龄不够的小对象被分配的老年代,破坏新生代的对象结构,可能会出现频繁的 full gc。因此,对于大对象,可以设置直接进入老年代(当然短命的大对象对于垃圾回收来说简直就是噩梦)。-XX:PretenureSizeThreshold 可以设置直接进入老年代的对象大小。
策略 3:合理设置进入老年代对象的年龄,-XX:MaxTenuringThreshold 设置对象进入老年代的年龄大小,减少老年代的内存占用,降低 full gc 发生的频率。
策略 4:设置稳定的堆大小,堆大小设置有两个参数:-Xms 初始化堆大小,-Xmx 最大堆大小。
策略5:注意: 如果满足下面的指标,则一般不需要进行 GC 优化:
MinorGC 执行时间不到50ms; Minor GC 执行不频繁,约10秒一次; Full GC 执行时间不到1s; Full GC 执行频率不算频繁,不低于10分钟1次。
策略5:使用缓存尽量不要使用Map 使用第三方的缓存 比如redis
● static Map map = 使用软弱引用
● 软
● 弱
● 第三方缓存实现
案例
案例1 Full GC和Minor GC频繁
问题分析:
说明空间紧张 如果是新生代空间紧张 高峰期来了 大量对象被创建 新生代被塞满了 幸存区空间紧张了 晋升阈值降低 本来生存周期很短的对象 也会被晋升代老年代去 情况就恶化了 老年代存了很多生存周期很短的对象 触发full GC 新生代内存太小了 解决方法:
增大新生代内存大小 同时增大了幸存区的空间以及新生代对象的阈值 这样让生命周期较短的对象 尽量留在新生代 不进入老年代
案例2请求高峰期发生Full Gc,单次暂停时间特别长(CMS)
CMS重新标记时会扫描整个堆内存 在进行Full Gc 重新标记之前 对新生代的对象 先做一次垃圾回收 减少新生代垃圾数量 可用减少重新标记的时间
案例3老年代充裕情况下,发生FullGC (1.7)
jdk1.7是使用永久代的,1.8后使用元空间,是使用操作系统的空间的,所以内存充足。 所以是永久代空间不足,可以增加永久代和初始值和最大值。
线程池
参数怎么设置的
● corePoolSize:核心线程数。如果等于0,则任务执行完后,没有任务请求进入时销毁线程池中的线程。如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。设置过大会浪费系统资源,设置过小导致线程频繁创建。
● maximumPoolSize:最大线程数。必须大于等于1,且大于等于corePoolSize。如果与corePoolSize相等,则线程池大小固定。如果大于corePoolSize,则最多创建maximumPoolSize个线程执行任务
● keepAliveTime:线程空闲时间。线程池中线程空闲时间达到keepAliveTime值时,线程会被销毁,只到剩下corePoolSize个线程为止。默认情况下,线程池的最大线程数大于corePoolSize时,keepAliveTime才会起作用。如果allowCoreThreadTimeOut被设置为true,即使线程池的最大线程数等于corePoolSize,keepAliveTime也会起作用(回收超时的核心线程)。
● unit:TimeUnit表示时间单位。
● workQueue:缓存队列。当请求线程数大于corePoolSize时,线程进入BlockingQueue阻塞队列。
● threadFactory:线程工厂。用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等。设置有意义的名称前缀有利于在进行虚拟机分析时,知道线程是由哪个线程工厂创建的。
● handler:执行拒绝策略对象。当达到任务缓存上限时(即超过workQueue参数能存储的任务数),执行拒接策略,可以看作简单的限流保护。
线程池相关类结构
ExecutorService接口继承了Executor接口,定义了管理线程任务的方法。
ExecutorService的抽象类AbstractExecutorService提供了submit、invokeAll()等部分方法实现,但是核心方法Executor.execute()并没有实现。
因为所有任务都在这个方法里执行,不同的线程池实现策略会有不同,所以交由具体的线程池来实现。
线程池种类
阿里规范我们用TreadPoolExecutor
● newFixedThreadPool:创建固定线程数的线程池。核心线程数等于最大线程数,不存在空闲线程,keepAliveTime为0。
● newSingleThreadExecutor:创建单线程的线程池,核心线程数和最大线程数都为1,相当于串行执行。
● newCachedThreadPool:核心线程数为0,最大线程数为Integer.MAX_VALUE,是一个高度可伸缩的线程池。存在OOM风险。keepAliveTime为60,工作线程处于空闲状态超过keepAliveTime会回收线程。
● newWorkStealingPool:JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列减少竞争。
禁止直接使用Executors创建线程池原因:
Executors.newCachedThreadPool和Executors.newScheduledThreadPool两个方法最大线程数为Integer.MAX_VALUE,如果达到上限,没有任务服务器可以继续工作,肯定会抛出OOM异常。
Executors.newSingleThreadExecutor和Executors.newFixedThreadPool两个方法的workQueue参数为new LinkedBlockingQueue(),容量为Integer.MAX_VALUE,如果瞬间请求非常大,会有OOM风险。
以上5个核心方法除Executors.newWorkStealingPool方法之外,其他方法都有OOM风险。
如果线程池满了怎么办
会执行线程拒绝策略
ThreadPoolExecutor提供了四个公开的内部静态类:
● AbortPolicy:默认,丢弃任务并抛出RejectedExecutionException异常。
● DiscardPolicy:丢弃任务,但是不抛出异常(不推荐)。
● DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中。
● CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行。
友好的拒绝策略:
● 保存到数据库进行削峰填谷。在空闲时再提出来执行。
● 转向某个提示页面
● 打印日志
如何自定义拒绝策略:
为什么要用线程池?
池化技术相比大家已经屡见不鲜了,线程池、数据库连接池、Http 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。
线程池提供了一种限制和管理资源(包括执行一个任务)。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。
这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:
● 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
● 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
● 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
/**
- 线程池执行耗时的操作 不要加大核心线程和最大线程数
*/
private static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(1,
1,
120,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1),
new RegionCenterUtilThreadFactory(“InitTemplateUtilThreadPool”),
new ThreadPoolExecutor.DiscardOldestPolicy());
/**
-
线程池工厂实现类
*/
public static class RegionCenterUtilThreadFactory implements ThreadFactory {
private final String name;RegionCenterUtilThreadFactory(String name) {
this.name = name;
}@Override
public Thread newThread(Runnable r) {
return new Thread(r, this.name);
}
}
private void generateMerchantShopTemplate(Long merchantId, Long userId, String userName) {
BaseReq base = new BaseReq();
base.setMerchantId(merchantId);
base.setUserId(userId);
base.setUserName(userName);
log.info(“initializeTemplateDataUtil start”);
try {
EXECUTOR_SERVICE.execute(() -> {
// 新建商户时 初始化标准
Boolean data = this.collectionTemplateV2RemoteService.saveMerchantInitTemplate(base).getData();
log.info("==data:{}", data);
});
} catch (Exception e) {
log.error(“initializeTemplateDataUtil fail: e =”, e);
}
log.info(“initializeTemplateDataUtil finfish”);
}
java线程池详解
java线程池详解_不会秃头的小齐的博客-CSDN博客
线程池详解
7000字+24张图带你彻底弄懂线程池_Java烟雨的博客-CSDN博客
JVM内存泄漏
https://blog.csdn.net/wenyiCodeDog/article/details/119722179
在浏览器中输入url地址到显示页面的过程?
总体来说分为以下几个过程:
● DNS解析
● TCP连接
● 发送HTTP请求
● 服务器处理请求并返回HTTP报文
● 浏览器解析渲染页面
● 连接结束
三次握手过程理解
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手过程理解
第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态
第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态
第三次挥手:客户端收到服务器确认结果后, 进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)
第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。
设计模式(单例、工厂、策略)
单例模式
public class Singleton {
private static Singleton uniqueInstance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
return uniqueInstance;
}
}
ThreadFactory 线程池本身就是工厂模式
日志级别及大对象
可通过修改conf/logging.properties日志配置文件来屏蔽掉这部分的日志信息。
catalina.org.apache.juli.FileHandler.level = WARNING
catalina.org.apache.juli.FileHandler.directory = ${catalina.base}/logs
catalina.org.apache.juli.FileHandler.prefix = catalina.
将level级别设置成WARNING就可以大量减少日志的输出,当然也可以设置成OFF,直接禁用掉。
一般日志的级别有:
SEVERE (highest value) > WARNING > INFO > CONFIG > FINE > FINER > FINEST (lowest value)
————————————————
版权声明:本文为CSDN博主「weixin_39661129」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39661129/article/details/114926490
快速排序
1、快速排序的基本思想:
快速排序使用分治的思想,通过一趟排序将待排序列分割成两部分,其中一部分记录的关键字均比另一部分记录的关键字小。之后分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
2、快速排序的三个步骤:
(1)选择基准:在待排序列中,按照某种方式挑出一个元素,作为 “基准”(pivot)
(2)分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大
(3)递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。
3、选择基准的方式
对于分治算法,当每次划分时,算法若都能分成两个等长的子序列时,那么分治算法效率会达到最大。也就是说,基准的选择是很重要的。选择基准的方式决定了两个分割后两个子序列的长度,进而对整个算法的效率产生决定性影响。
最理想的方法是,选择的基准恰好能把待排序序列分成两个等长的子序列
我们介绍三种选择基准的方法
方法(1):固定位置
思想:取序列的第一个或最后一个元素作为基准
基本的快速排序
JMeter报告
Label:每个JMeter的element的Name值,例如HTTP Request的Name;
Samples(样本):发出请求数量;模拟20个用户,循环100次,所以显示了2000;
Average(平均值):平均响应时间(单位:ms);默认是单个Request的平均响应时间,当使用了Transaction Controller时,也可以以 Transaction为单位显示平均响应时间;
Median(中位数):50%的用户响应时间小于这个值;
90%Line(90%百分位):90%的用户响应时间小于这个值;
95%LIne(95%百分位):95%的用户响应时间小于这个值;
99%LIne(99%百分位):99%的用户响应时间小于这个值;
Min(最小值):用户响应时间最小值;
Max(最大值):用户响应时间最大值;
Eorror%(异常%):测试出现的错误请求数量百分比;请求的错误率 = 错误请求的数量/请求的总数;若出现错误就要看服务端的日志查找定位原因;
Throughput(吞吐量):简称TPS,吞吐量,默认情况下表示每秒处理的请求数,也就是指服务器处理能力,TPS越高说明服务器处理能力越好;
接收KB/sec:每秒从服务器端接收到的数据量;
发送KB/sec:每秒发送的数据量;
————————————————
版权声明:本文为CSDN博主「3L_csdn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35061334/article/details/123758333
AES加密
//1、过滤请求:路径
//2、环境过滤
//3、响应处理 包装响应对象 res 并缓存响应数据
//4、执行业务逻辑 交给下一个过滤器或servlet处理
//5、设置响应内容格式,防止解析响应内容时出错
//6、加密响应报文并响应 AES算法
//7、打印并且关闭
/*
AES,加解密算法
public static String encrypt(String content, String encryptKey) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance(“AES”);
kgen.init(128);
Cipher cipher = Cipher.getInstance(algorithmstr);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), “AES”));
byte[] b = cipher.doFinal(content.getBytes(“utf-8”));
return Base64.encodeBase64String(b);
}
基本原理
AES属于分组加密算法,属于对称加密,故在传递密钥途中有密钥泄露的风险
明文plainText和密文cipherText固定长度为128bit(16B)
密钥长度为128,192,256bit,密钥长度会改变算法循环次数,分别对应10/12/14轮循环
为什么AES算法安全性高?
AES的区块长度固定为128位,密钥长度则可以是128 bit,192 bit 或256位 bit 。换算成字节长度,就是密码必须是 16个字节,24个字节,32个字节。AES密码的长度更长了,破解难度就增大了,所以就更安全。
对称加密 : 也就是加密秘钥和解密秘钥是一样的。