nginx主要作用
- 静态代理
- 负载均衡,请求。常用策略有轮询、权重、随机、ip_hash、least_conn等
- 限流 基础漏桶算法
- 缓存 浏览器缓存和代理层缓存
- 黑白名单 不限流白名单和黑名单
禁用e.printStackTrace()
短时间内大量请求访问此接口 -> 代码本身有问题,很多情况下抛异常 -> e.printStackTrace()
来打印异常到控制台 -> 产生错误堆栈字符串到字符串池内存空间 -> 此内存空间一下子被占满了 -> 开始在此内存空间产出字符串的线程还没完全生产完整,就没空间了 -> 大量线程产出字符串产出到一半,等在这儿(等有内存了继续搞啊)-> 相互等待,等内存,锁死了,整个应用挂掉了。
记住,不要使用 e.printStackTrace()!而且
会产生大量字符串的方法,使用时都要注意,因为字符串池所属的非堆内存空间就那么点,小心占满了。
分布式session实现方案
- 使用jwt替代session,从数据库或缓存获取其它信息
- tomcat+redis 利用TomcatRedisSessionManager,使tomcat 都将 session 数据存储到 redis
- spring session + redis session交给spring托管,从redis获取数据
乐观锁和悲观锁
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。如synchronized、lock锁等
乐观锁主要有版本号和CAS两种实现方式。
CAS操作包括了3个操作数:
-
需要读写的内存位置(V)
-
进行比较的预期值(A)
-
拟写入的新值(B)
CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。
CAS可能有ABA问题,一般需要引入版本号。java.util.concurrent.atomic包提供的原子类就是基于CAS实现的。
版本号机制的话会在更新时会携带版本号与当前数据版本做对比。
当竞争不激烈 (出现并发冲突的概率小)时,乐观锁更有优势,因为悲观锁会锁住代码块或数据,其他线程无法同时访问,影响并发,而且加锁和释放锁都需要消耗额外的资源。
当竞争激烈(出现并发冲突的概率大)时,悲观锁更有优势,因为乐观锁在执行更新时频繁失败,需要不断重试,浪费CPU资源。
ConcurrentHashMap理解
-
在1.8中ConcurrentHashMap的get操作全程不需要加锁,这也是它比其他并发集合比如hashtable、用Collections.synchronizedMap()包装的hashmap安全效率高的原因之一。
-
get操作全程不需要加锁是因为Node的成员val是用volatile修饰的,和数组用volatile修饰没有关系。
-
数组用volatile修饰主要是保证在数组扩容的时候保证可见性。