Nginx
JVM
所有的对象实例以及数组都要在堆上分配。堆是垃圾收集器管理的主要区域,也被称为GC
堆”;也是我们优化最多考虑的地方。
堆可以细分为:
- 新生代
- Eden 空间
- From Survivor 空间
- To Survivor 空间
- 老年代
- 永久代/元空间
- Java8 以前永久代,受 jvm 管理,java8 以后元空间,直接使用物理内存。因此,
默认情况下,元空间的大小仅受本地内存限制
缓存&分布式锁
整合redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
spring:
redis:
host: ***.***.***.***
缓存穿透
查询一个一定不存在的数据
,缓存中没有所有去查询数据库,但数据库也没有,将查询的null写入缓存中,导致以后的结果全是null
风险
利用不存在的数据进行攻击,数据库压力增大,导致崩溃
缓存雪崩
在设置key时使用了相同的过期时间
,导致缓存中某一时刻全部失效,请求全部查询数据库
解决
设置key的过期时间为随机
缓存击穿 –热点key失效
对于设置了过期时间的key,如果key可能会在某个时间点被高频访问,称为热点数据
如果这个key正好在大量请求同时进来的时候正好失效,则会大量访问数据库
解决
加锁
时序问题
分布式锁
//分布式锁
public Map<String, List<Catelog2Vo>> getCatalogJsonFromDBWithRedisLock() {
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,20,TimeUnit.SECONDS);
if(lock){
//加锁成功
Map<String, List<Catelog2Vo>> dataFromDB;
try{
dataFromDB = getDataFromDB();
}finally {
//lua脚本解锁
String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Integer execute = redisTemplate.execute(new DefaultRedisScript<Integer>(script, Integer.class),
Arrays.asList("lock"), uuid);
}
//删锁必须是原子操作
// String lockValue = redisTemplate.opsForValue().get("lock");
// if(uuid.equals(lockValue)){
// redisTemplate.delete("lock");
// }
return dataFromDB;
}else {
//重试 自旋锁
return getCatalogJsonFromDBWithRedisLock();
}
}
Redisson
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.0</version>
</dependency>
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() throws IOException{
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.***.***:6379");
return Redisson.create(config);
}
}
@Autowired
RedissonClient redissonClient;
@Test
void testRedisson(){
System.out.println(redissonClient);
}
Redisson的锁为 阻塞时等待,默认30S
lock.lock()
自己未指定锁的超时时间
占锁成功后,会启动定时任务,重新给锁设置过期时间(看门狗的默认时间)(每10s续期)
如果业务超长,锁会自动续期,自动续期30s
加锁的业务一完成,就不会续期,即使不手动解锁,也会在30s内自动过期
Redisson不会出现死锁
RLock lock = redissonClient.getLock("lock");
lock.lock();
try{
//执行业务
}catch(Exception e){
//异常处理
}finally{
//解锁
lock.unlock();
}
lock.lock(10,TimeUnit.SECOUNDS);
自己指定时间时,底层时给redis发送lua脚本进行占锁,且锁到期后,不会自动续期
读写锁
保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
写锁没有释放时读锁必须等待
读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
写 + 读 :必须等待写锁释放
写 + 写 :阻塞方式
读 + 写 :有读锁。写也需要等待
只要有读或者写的存都必须等待
public void write(){
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
Rlock rLock = readWriteLock.writeLock();
try{
rLock.lock();
//写入数据
}finally{
rLock.unlock();
}
}
public void read(){
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
Rlock rLock = readWriteLock.readLock();
try{
rLock.lock();
//读数据
}finally{
rLock.unlock();
}
}
Semaphore
信号量也可以做分布式限流
public void park(){
RSemaphore park = redisson.getSemaphore("park");
park.acquire();//获取信号量,信号量-1,等待式获取
}
public void go(){
RSemaphore park = redisson.getSemaphore("park");
park.relase();//释放信号量,信号量+1
}
boolean park.tryAcquire()
尝试获取信号量,如果获取不到则立刻返回false
CountDownLatch
public void lock(){
RCountDownLatch latch = redisson.getCountDownLatch("latch");
latch.trySetCount(5);
latch.await();
}
public void down(){
RCountDownLatch latch = redisson.getCountDownLatch("latch");
latch.countDown();//count -1 需五个都减完
}
缓存数据一致性
无论是双写模式还是失效模式,都会导致缓存的不一致问题。即多个实例同时更新会出事。怎么办?
1、如果是用户纬度数据(订单数据、用户数据),这种并发几率非常小,不用考虑这个问题,缓存数据加 上过期时间,每隔一段时间触发读的主动更新即可
2、如果是菜单,商品介绍等基础数据,也可以去使用canal订阅binlog的方式。
3、缓存数据+过期时间也足够解决大部分业务对于缓存的要求。
4、通过加锁保证并发读写,写写的时候按顺序排好队。读读无所谓。所以适合使用读写锁。(业务不关心 脏数据,允许临时脏数据可忽略);
总结:
我们能放入缓存的数据本就不应该是实时性、一致性要求超高的。所以缓存数据的时候加上过期时间,保 证每天拿到当前最新数据即可。
我们不应该过度设计,增加系统的复杂性
遇到实时性、一致性要求高的数据,就应该查数据库,即使慢点。
Spring Cache
spring cache + redis
spring-boot-starter-cache
spring-boot-starter-data-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
spring:
cache:
type: redis
在启动类上加上 @EnableCaching
使用注解
当前如果缓存中存在为 name 的缓存,则不会调用方法直接返回缓存数据
@Cacheable("name")
public void get(){
//查询数据库
}