并发优化 高并发 锁

高并发限制因素:

网络,cpu,内存,硬盘io
优化:1.优化业务代码,使用合理的算法,减少计算量或者使用的空间,减少数据库的查询2.使用缓存,如果单体应用,可以直接使用map(本地缓存)。如果是分布式,用缓存中间件,保持数据一致性3.前后端分离,tomcat不需要处理静态资源请求4.优化中间件(影响没那么大)5.jvm调优

Jmeter 压力测试,可以看:https://www.bilibili.com/video/BV1np4y1C7Yf?p=142

jvm调优:【使用jvisualvm】

1.主要是在堆上面调优,堆分为 Eden,Survivor 0 ,Survivor 1,Old,Metaspace

​ 先在Eden分配内存,要是Eden不够,小规模在Eden GC,再不够,大规模GC

​ 大规模GC时间久,所有提高性能需要加大 Eden,减少大规模GC次数

不适合放在缓存中的数据:修改较多,实时性要求高【缓存一致性问题】

加锁:

锁的基本结构,以简单的分布式锁为例:

【过期时间,操作原子性,删自己的锁,自旋】

// 加锁:解决缓存击穿
public Map<String, List<Catelog2Vo>> getCatelogJsonFromDb() {


    // 解决缓存击穿,加锁
    // TODO 本机锁:synchronized JUC(lock)  在分布式情况下,想锁住所有,用分布式锁


    // 1.加单机同步锁
    // synchronized是单机同步锁
    // springboot中,对象一般都是单例的,synchronized (this) 可以锁住【适合单机,分布式也行,压力也不会太大】
    //synchronized (this) {
    //    return getDataFromDb();
    //}


    // 2.分布式锁-1.去redis占坑【在redis设置一个值,设置成功相当于获得锁】
    // 优化:
    //      设置值时加过期时间【需要原子操作,防止出现以为加不了过期时间】。
    //      使用uuid,防止删掉别人的锁[因为可能业务超出锁的过期时间,别人也获取到了锁]【也需要原子操作】
    String uuid = UUID.randomUUID().toString();
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,10,TimeUnit.SECONDS);
    if(lock){
        // 加锁成功,执行业务
        Map<String, List<Catelog2Vo>> dataFromDb;
        // 要是执行业务出异常,解锁
        try{
            dataFromDb= getDataFromDb();
        }finally {
            // 获取数据后,解锁(删除掉redis中的lock kv)【查询数据以及删除需要原子性!!!使用lua脚本】
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);
        }
        //String lock1 = redisTemplate.opsForValue().get("lock");
        //if(uuid.equals(lock1)){
        //    redisTemplate.delete("lock");
        //}
        return dataFromDb;
    }else{
        // 加锁失败,重试
        // 休眠200毫秒
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return getCatelogJsonFromDb(); // 自旋
        // 个人感觉其实可以用while循环,循环里面一直查缓存,直到从缓存中查出
        // 要是超过一定时间找不出,查数据库,加缓存
        // 因为加锁之后只能有一个线程查询,一直拿锁很影响效率,最好还是一个人获得锁之后,其他人不拿锁,等结果
    }
}

分布式锁

【redis基础上,使用Redisson】【GitHub有中文文档,可参考】【Redisson还有分布式对象等功能】

能够实现分布式的原因:值不是保存在本地,而是保存在redis中,比如信号量,比如锁的值

【锁的名字不同,锁就不同。最好使锁的粒度最小。比如某种商品就加某种商品的锁,不加所有商品的锁】

配置:

<!-- 以后使用Redisson作为所有分布式锁,分布式对象 -->
<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson</artifactId>
   <version>3.12.0</version>
</dependency>


配置类:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;

@Configuration
public class MyRedissonConfig {

    /**
     * 所有对Redisson的使用都是通过RedissonClient
     */
    @Bean(destroyMethod="shutdown")
    public RedissonClient redissonClient() throws IOException {
        //1、创建配置【单节点配置】
        Config config = new Config();
        config.useSingleServer().setAddress("redis://192.168.190.150:6379");

        //2、根据Config创建出RedissonClient实例
        //Redis url should start with redis:// or rediss://
        RedissonClient redissonClient = Redisson.create(config);
        return redissonClient;
    }

}

Redisson锁:

底层其实也是在redis里面设置一个 keyvalue,占坑

redisson的锁,比上面自己写的锁好:

1.阻塞式等待,不需要像上面那样写,循环调用自己

2.解决锁的自动续期。不用担心业务时间长,锁自动过期失效

\3. 如果我们传递了锁的超时时间,就发送给redis执行脚本,进行占锁,默认超时就是我们指定的时间

​ 如果我们未指定锁的超时时间,就使用38*1800【LockWatchdogTimeout看门狗的默认时间】;

​ 只要占锁成功,就会启动一个定时任务【重新给锁设置过期时间,新的过期时间就是看门狗的默认时间】:internalLockLeaseTime【看门狗时间】/3,10s

普通锁:【可重入锁】(不设置超时时间,设置超时时间,尝试获取锁,公平锁)
@GetMapping("/hello")
@ResponseBody
public String hello(){
    // 只要名字一样,就是同一把锁
    RLock lock = redisson.getLock("my-lock");

    // 1.加锁。不设置过期时间,自动续期。业务完成自动过期
    lock.lock();
    // 2.加锁。设置10秒过期时间。不会自动续期,要确保锁的过期时间比业务运行时间长
    //lock.lock(10, TimeUnit.SECONDS);
    // 3.尝试加锁,最多等待100秒,上锁以后10秒自动解锁
    //boolean res = lock.tryLock(100,10,TimeUnit.SECONDS);
    // 4.公平锁,按照一开始先来后到的顺序获取锁【获取锁之后的加锁操作也是 fairLock.lock();】
    //RLock fairLock redisson.getFairLock("anyLock");
    
    try{
        System.out.println("加锁成功,执行业务。。。"+Thread.currentThread().getName());
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        System.out.println("释放锁。。。"+Thread.currentThread().getName());
        lock.unlock();
    }
    return "hello";
}
读写锁
使用读写锁:
//保证一定能读到最新数据,修改期间,写锁是一个排他锁(互斥锁、独享锁),读锁是一个共享锁
//写锁没释放读就必须等待
//读+读:相当于无锁,并发读,只会在redis中记录好,所有当前的读锁。他们都会同时加锁成功
//写+读:等待写锁释放
//写+写:阻塞方式
//读+写:有读锁。写也需要等待。
//只要有写的存在,都必须等待
@GetMapping("/write")
@ResponseBody
public String writeValue(){

    // 获得读写锁
    RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
    // 获得写锁
    RLock rLock = lock.writeLock();

    // 获得写锁+加锁,可以这样写:lock.writeLock().lock();

    String s = "";

    
    try {
        // 改数据加写锁,读数据加读锁
        rLock.lock();
        s = UUID.randomUUID().toString();
        Thread.sleep(3000);
        redisTemplate.opsForValue().set("writeValue",s);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        // 释放锁
        rLock.unlock();
    }

    return s;
}


@GetMapping("/read")
@ResponseBody
public String readValue(){
    String s = "";
    // 获得读写锁
    RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");

    // 获得读锁
    RLock rLock = lock.readLock();

    // 加锁
    rLock.lock();

    // 获得写锁+加锁,可以这样写:lock.readLock().lock();

    try{
        redisTemplate.opsForValue().get("writeValue");
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        // 释放锁
        rLock.unlock();
    }

    return s;
}

信号量【可以用来进行限流操作:先在redis设置一个键值,键为信号量名字。请求尝试获取信号量,获取不到返回false,不进行等待】
/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {

    【在redis里面,设置一个键为park,值为3RSemaphore park = redisson.getSemaphore("park");
    //获取一个信号、获取一个值,占一个车位
    //park.acquire();
    // 尝试获取一个信号量,获取不到返回false,不会阻塞等待
    boolean flag = park.tryAcquire();

    if (flag) {
        //执行业务
    } else {
        return "当前停车位不足,请稍后再尝试";
    }

    return "ok=>" + flag;
}


@GetMapping(value = "/go")
@ResponseBody
public String go() {
    RSemaphore park = redisson.getSemaphore("park");
    park.release();     //释放一个车位
    return "ok";
}
闭锁:等所有的条件都完成才执行
  /**
     * 放假、锁门
     * 1班没人了
     * 5个班,全部走完,我们才可以锁大门
     * 分布式闭锁
     */
    @GetMapping(value = "/lockDoor")
    @ResponseBody
    public String lockDoor() throws InterruptedException {

        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.trySetCount(5);
        door.await();       //等待闭锁完成

        return "放假了...";
    }

    @GetMapping(value = "/gogogo/{id}")
    @ResponseBody
    public String gogogo(@PathVariable("id") Long id) {
        RCountDownLatch door = redisson.getCountDownLatch("door");
        door.countDown();       //计数-1


        return id + "班的人都走了...";
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沛权

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值