目录
1.NullPointerException 空指针异常
2.ClassNotFoundException 指定类不存在
3.NumberFormatException 字符串转换为数字异常
4.IndexOutOfBoundsException 数组下标越界异常
5.ClassCastException 数据类型转换异常
6.FileNotFoundException 文件未找到异常
7.NoSuchMethodException 方法不存在异常
8.IOException IO 异常
9.SocketException Socket 异常
实现分布式锁的三种方式
1.基于数据库实现分布式锁 基于数据库的实现方式的核心思想是:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,
就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
2.基于缓存(Redis等)实现分布式锁 SETNX expire delete
(1)获取锁的时候,使用setnx加锁,并使用expire命令为锁添加一个超时时间,超过该时间则自动释放锁,锁的value值为一个随机生成的UUID,通过此在释放锁的时候进行判断。当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SET NX 不做任何动作,并返回0。
(2)获取锁的时候还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
(3)释放锁的时候,通过UUID判断是不是该锁,若是该锁,则执行delete进行锁释放。
3.基于Zookeeper实现分布式锁 zooKeeper是一个为分布式应用提供一致性服务的开源组件,它内部是一个分层的文件系统目录树结构,规定同一个目录下只能有一个唯一文件名
1、spring的事务注解@Transactional只能放在public修饰的方法上才起作用,如果放在其他非public(private,protected)方法上,虽然不报错,但是事务不起作用
2、如使用mysql且引擎是MyISAM,则事务会不起作用,原因是MyISAM不支持事务,可以改成InnoDB引擎
3、在业务代码中如果抛出RuntimeException异常,事务回滚;但是抛出Exception,事务不回滚;
4、如果在加有事务的方法内,使用了try…catch…语句块对异常进行了捕获,而catch语句块没有throw new RuntimeExecption异常,事务也不会回滚
5、在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。原因是在同一个类之中,方法互相调用,切面无效 ,而不仅仅是事务。这里事务之所以无效,是因为spring的事务是通过aop实现的。解决方法:
- @Service
- public class OrderServiceImpl implements OrderService {
-
- @Autowired
- private OrderService orderService;
-
- @Transactional
- public void update(Order order) {
- orderService.updateOrder(order);
- }
-
- @Transactional(propagation = Propagation.REQUIRES_NEW)
- public void updateOrder(Order order) {
- // update order
- }
-
- }
6.数据源没有配置事务管理器
- @ComponentScan("com.alanchen")
- @EnableTransactionManagement
- @Configuration
- public class DBConfig{
-
- @Bean
- public JdbcTemplate jdbcTemplate(){
- return new JdbcTemplate(dataSource());
- }
-
- @Bean
- public PlatformTransactionManager transactionManager() {
- return new DataSourceTransactionManager(dataSource());
- }
-
- @Bean
- public DataSource dataSource(){
- DriverManagerDataSource dataSource = new DriverManagerDataSource();
- dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test.....");
- dataSource.setUsername("test");
- dataSource.setPassword("test");
- return dataSource;
- }
- }
CAP理论是:分布式系统在设计时只能在一致性(Consistency)、可用性(Availability)、分区容忍性(Partition Tolerance)中满足两种,无法兼顾三种。
一致性(Consistency):服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
可用性(Availability):服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
分区容忍性(Partition Tolerance):分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。
分布式系统不可避免的出现了多个系统通过网络协同工作的场景,结点之间难免会出现网络中断、网延延迟等现象,这种现象一旦出现就导致数据被分散在不同的结点上,这就是网络分区
CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计。
AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP进行设计。
说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可
CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞
总结: 在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可。
1.两阶段提交协议(2PC)
2PC(Two-phase commit protocol),二阶段提交(准备、提交)。2PC引入一个事务协调者的角色来协调管理各参与者的提交和回滚,是一种强一致性设计。
第一阶段,准备阶段(投票阶段):协调者会给各参与者发送准备(准备提交事务)的命令
同步等待所有资源的响应成功之后就进入提交阶段(提交阶段不一定是提交事务,也有可能是回滚事务)。
第二阶段:提交/回滚
如果第一阶段所有参与者都返回准备成功,那么协调者则向所有参与者发送提交事务命令,然后等待所有事物都提交成功之后,返回事务执行成功。
2.事务补偿(TCC)
TCC(Try-Confirm-Cancel)又被称补偿事务,TCC与2PC的思想很相似,事务处理流程也很相似,但2PC 是应用于在DB层面,TCC则可以理解为在应用层面的2PC,是需要我们编写业务逻辑来实现。
TCC它的核心思想是:“针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)”。
Try阶段:预留,即资源的预留和锁定;
Confirm阶段:确认操作,真正的执行
Cancel阶段:撤销操作,可以理解为把预留阶段的动作撤销了
TCC的缺点:
应用侵入性强:TCC由于基于在业务层面,至使每个操作都需要有 try、confirm、cancel三个接口。
开发难度大:代码开发量很大,要保证数据一致性 confirm 和 cancel 接口还必须实现幂等性。
3.消息队列实现最终一致
本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成,如下图:
下边以下单减少库存为例来说明:
1订单服务和库存服务完成检查和预留资源。
2、订单服务在本地事务中完成添加订单表记录和添加“减少库存任务消息”。
3、由定时任务根据消息表的记录发送给MQ通知库存服务执行减库存操作。
4、库存服务执行减少库存,并且记录执行消息状态(为避免重复执行消息,在执行减库存之前查询是否执行过此消息)。
5、库存服务向MQ发送完成减少库存的消息。
接口的幂等性如何保证
1.insert前先select
2.加唯一索引
3.建防重表
4.token机制
5.状态机幂等
1.抽象类可以有构造方法,接口中不能有构造方法。
2.抽象类中可以有普通成员变量,接口中没有普通成员变量
3.抽象类中可以包含静态方法,接口中不能包含静态方法
4.一个类可以实现多个接口,但只能继承一个抽象类。
5.接口可以被多重实现,抽象类只能被单一继承
6.如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法
override(重写)
1、方法名、参数、返回值相同。
2、子类方法不能缩小父类方法的访问权限。
3、子类方法不能抛出比父类方法更多的异常(但子类方法可以不抛出异常)。
4、存在于父类和子类之间。
5、方法被定义为final不能被重写。
overload(重载)
1、参数类型、个数、顺序至少有一个不相同。
2、不能重载只有返回值不同的方法名。
3、存在于父类和子类、同类中。
1.对于==,比较的是值是否相等
如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
如果作用于引用类型的变量,则比较的是所指向的对象的地址
2.对于equals方法,注意:equals方法不能作用于基本数据类型的变量,equals继承Object类,比较的是是否是同一个对象
如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;
诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容。
static方法
static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的,想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
static变量
也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。
static静态代码块
形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
1.类:被修饰的类不能被继承
2.方法:被修饰的方法不能被重写
3.常量:被修饰的变量不能重新赋值
1)finally是try语句中的一个语句体,不能单独使用,用来释放资源
2)finalize是一个方法,当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
1.用new语句创建对象,这是最常用的创建对象的方式。
2.运用反射手段,调用java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
3.调用对象的clone()方法。
4.运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
实现对象拷贝的类,必须实现Cloneable接口,并覆写clone()方法
创建一个指向对象的引用变量的拷贝,地址值是相同的,那么它们肯定是同一个对象, 这就叫做引用拷贝。
创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝
深拷贝和浅拷贝都是对象拷贝
浅拷贝
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。 简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝
深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。 简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。
1) 利用序列化实现深拷贝
2) 重写clone()方法将引用对象改为深复制
3) 彻底深拷贝,几乎是不可能实现的
架构设计:
1)将请求拦截在系统上游,降低下游压力:秒杀系统特点是并发量极大,但实际秒杀成功的请求数量却很少,所以如果不在前端拦截很可能造成数据库读写锁冲突,甚至导致死锁,最终请求超时。
2)充分利用缓存(redis):利用缓存可极大提高系统读写速度。
3)消息中间件(ActiveMQ、Kafka等):消息队列可以削峰,将拦截大量并发请求,这也是一个异步处理过程
(消息队列作用:解耦 削峰 异步操作)
前端设计方案:
页面静态化:将活动页面上的所有可以静态的元素全部静态化
禁止重复提交:用户提交之后按钮置灰,禁止重复提交
用户限流:在某一时间段内只允许用户提交一次请求,比如可以采取IP限流
后端设计方案:
网关层:
限制uid(UserID)访问频率: 针对某些恶意攻击或其它插件,在服务端控制层需要针对同一个访问uid,限制访问频率。
服务层:
采用消息队列缓存请求:先把这些请求都写到消息队列缓存一下
利用缓存应对读请求:对典型的读多写少业务,大部分请求是查询请求。 利用缓存分担数据库压力。