Redis——某马点评day03

news2024/9/29 9:33:49

全局唯一ID

创建一个工具类

@Component
public class RedisIdWorker {
    /**
     * 开始时间戳
     */
    private static final long BEGIN_TIME_STAMP=1672531200L;
    /**
     * 序列号的位数
     */
    private static final int COUNT_BITS=32;

    private StringRedisTemplate stringRedisTemplate;

    public RedisIdWorker(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public long nextId(String keyPrefix){
        //1.生成时间戳
        LocalDateTime now = LocalDateTime.now();
        long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
        long timestamp = nowSecond - BEGIN_TIME_STAMP;
        //2.生成序列号
        //2.1获取当前日期,精确到天
        String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));
        //2.2自增长
        long count = stringRedisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + date);
        //3.拼接并返回

        return timestamp<<COUNT_BITS|count;
    }

    public static void main(String[] args) {
        LocalDateTime time = LocalDateTime.of(2023, 1, 1, 0, 0, 0);
        long second = time.toEpochSecond(ZoneOffset.UTC);
        System.out.println("second= "+second);
    }
}

对工具类进行测试

这里开启了300个线程,每个线程执行100次,最后自增长会达到30000.

@SpringBootTest
class HmDianPingApplicationTests {
    @Resource
    private RedisIdWorker redisIdWorker;

    private ExecutorService es= Executors.newFixedThreadPool(500);

    @Test
    void test() throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(300);

        Runnable task=()->{
            for(int i=0;i<100;i++){
                long id = redisIdWorker.nextId("order");
                System.out.println("id="+id);
            }
            latch.countDown();
        };
        long begin=System.currentTimeMillis();
        for(int i=0;i<300;i++) {
            es.submit(task);
        }
        latch.await();
        long end=System.currentTimeMillis();
        System.out.println("time = "+(end-begin));
    }
}

结果如下,可以看见每个id都是不一样的。 

然后看见redis里面的数据,的确是30000没错. 

实现优惠券秒杀下单

 

代码实现(未考虑线程安全)

Controller层中

@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {
    @Resource
    private IVoucherOrderService voucherOrderService;

    @PostMapping("seckill/{id}")
    public Result seckillVoucher(@PathVariable("id") Long voucherId) {
        return voucherOrderService.seckillVoucher(voucherId);
    }
}

Service层中

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否已经结束
        if (voucher.getBeginTime().isBefore(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀已经结束");
        }
        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }
        //5.扣减库存
        boolean success=seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id",voucherId)
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }
        //6.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //6.1订单Id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //6.2用户Id
        Long userId = UserHolder.getUser().getId();
        voucherOrder.setUserId(userId);
        //6.3代金券Id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //7.返回订单ID
        return Result.ok(orderId);
    }
}

 

超卖问题

真实场景下肯定是1秒会有成百上千的请求同时发送到后端的,使用Jmeter进行测试得到如下.

使用50个线程抢20个资源出现超卖情况 

问题出现原因如下,多个线程读取到了同一个数据,然后进行修改。 

 解决方案分析

 这里的版本号实际可以用库存数量作为版本号进行使用。

乐观锁解决超卖问题

        //5.扣减库存
        boolean success=seckillVoucherService.update()
                .setSql("stock = stock - 1") //set stock =stock - 1
                .eq("voucher_id",voucherId).eq("stock",voucher.getStock()) //where id=? and stock =?
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }

对这部分代码使用乐观锁优化之后再次50抢20结果如下,反而只抢了13张,很多都是报库存不足.

因为多个线程抢同一张票时只有一个线程可以成功.

缺点:

成功率太低。

优化修改

将stock=?改为stock>0即可

提示:这块还是基于了数据库的update语句自带行锁,自带互斥的,库存只是在原来的基础上进行--操作,所以可以保证不会超卖

        //5.扣减库存
        boolean success=seckillVoucherService.update()
                .setSql("stock = stock - 1") //set stock =stock - 1
                .eq("voucher_id",voucherId)
                .gt("stock",0)//where id=? and stock > 0
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }

库存恰好为0 

 

去看mysql进阶和java并发之后再看多表分段锁.

一人一单

新的业务流程如下所示

初版代码

这个代码在多线程时有并发安全问题,有可能多个线程都查到了0,然后都抢到了票

    @Override
    @Transactional
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀已经结束");
        }
        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }

        //5.一人一单
        Long userId = UserHolder.getUser().getId();
        //5.1查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        //5.2判断是否存在
        if(count>1){
            //用户已经购买过了
            return Result.fail("用户已经购买过一次");
        }

        //6.扣减库存
        boolean success=seckillVoucherService.update()
                .setSql("stock = stock - 1") //set stock =stock - 1
                .eq("voucher_id",voucherId)
                .gt("stock",0)//where id=? and stock > 0
                .update();
        if(!success){
            return Result.fail("库存不足!");
        }

        //7.创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        //7.1订单Id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        //7.2用户Id
        voucherOrder.setUserId(userId);
        //7.3代金券Id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        //8.返回订单ID
        return Result.ok(orderId);
    }

优化代码

这里要对一整段代码加锁,将其抽取出来作为独立的方法,但是使用synchronized锁一个方法的话每次都只能有一个用户执行抢票,但是这里的加锁应该是针对同一个用户的多个请求.

锁id.toString()的话每次都是锁一个全新的对象. 不能起到作用。锁userId.toString().intern()的话就可以,这个是去字符串常量池找对象,常量池里每个字符串都有唯一的对象.

但是这个代码也有问题,事务注解是加在方法上的,锁的内容执行完之后到事务提交之前的这一段时间可能会有别的线程进来继续查询,查询到的也是旧数据,因为上一个事务没有提交.

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀已经结束");
        }
        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }

        return createVoucherOrder(voucherId);
    }

    @Transactional
    public   Result createVoucherOrder(Long voucherId) {
        //5.一人一单
        Long userId = UserHolder.getUser().getId();

        synchronized(userId.toString().intern()) {
            //5.1查询订单
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //5.2判断是否存在
            if (count > 1) {
                //用户已经购买过了
                return Result.fail("用户已经购买过一次");
            }

            //6.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1") //set stock =stock - 1
                    .eq("voucher_id", voucherId)
                    .gt("stock", 0)//where id=? and stock > 0
                    .update();
            if (!success) {
                return Result.fail("库存不足!");
            }

            //7.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1订单Id
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //7.2用户Id
            voucherOrder.setUserId(userId);
            //7.3代金券Id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //8.返回订单ID
            return Result.ok(orderId);
        }
    }

最终代码

如果是像下面这样,在锁里面调用加了事务的方法的话,会有事务失效的问题,

下面的调用createVoucherOrder实际是this.createVoucherOrder这样的话,事务注解拿到的是当前的VoucherOrderServiceImpl对象,而不是其代理对象.

事务能生效是因为拿到了VoucherOrderServiceImpl的代理对象,做了一个动态代理,对代理对象做了事务处理.this指的是目标对象,是没有事务功能的,这是spring事务失效的几种可能性之一.

        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()) {
            return createVoucherOrder(voucherId);
        }

这里为了拿到代理对象,需要导入一个新依赖,并在启动类上开启代理对象暴露

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

@MapperScan("com.hmdp.mapper")
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class HmDianPingApplication {
    public static void main(String[] args) {
        SpringApplication.run(HmDianPingApplication.class, args);
    }
}

 接口层里新增一个方法

public interface IVoucherOrderService extends IService<VoucherOrder> {

    Result seckillVoucher(Long voucherId);
    Result createVoucherOrder(Long voucherId);

}

 实现层里代码

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
        SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
        //2.判断秒杀是否开始
        if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀尚未开始");
        }
        //3.判断秒杀是否已经结束
        if (voucher.getEndTime().isBefore(LocalDateTime.now())) {
            //尚未开始
            return Result.fail("秒杀已经结束");
        }
        //4.判断库存是否充足
        if (voucher.getStock()<1) {
            //库存不足
            return Result.fail("库存不足");
        }

        //5.一人一单
        Long userId = UserHolder.getUser().getId();
        synchronized(userId.toString().intern()) {
            //取到了当前代理对象
            IVoucherService proxy =(IVoucherService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }

    @Transactional
    public   Result createVoucherOrder(Long voucherId) {
        Long userId = UserHolder.getUser().getId();
        //5.1查询订单
            int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
            //5.2判断是否存在
            if (count > 0) {
                //用户已经购买过了
                return Result.fail("用户已经购买过一次");
            }

            //6.扣减库存
            boolean success = seckillVoucherService.update()
                    .setSql("stock = stock - 1") //set stock =stock - 1
                    .eq("voucher_id", voucherId)
                    .gt("stock", 0)//where id=? and stock > 0
                    .update();
            if (!success) {
                return Result.fail("库存不足!");
            }

            //7.创建订单
            VoucherOrder voucherOrder = new VoucherOrder();
            //7.1订单Id
            long orderId = redisIdWorker.nextId("order");
            voucherOrder.setId(orderId);
            //7.2用户Id
            voucherOrder.setUserId(userId);
            //7.3代金券Id
            voucherOrder.setVoucherId(voucherId);
            save(voucherOrder);
            //8.返回订单ID
            return Result.ok(orderId);
    }
}

50抢20测试结果:

成功实现只能抢一张票

在代码里面,如果是大于1就是允许抢两张票,想控制用户可抢票数量可以根据这里进行修改

            //5.2判断是否存在
            if (count > 0) {
                //用户已经购买过了
                return Result.fail("用户已经购买过一次");
            }

集群下的线程并发安全问题

两个系统的代码互不干涉,所以肯定会有并发问题. 

两台tomcat,两个jvm,两个字符串常量池,两把锁。

分布式下需要使用同一把锁,由此引出分布式锁. 

分布式锁

基本原理和不同实现方式对比

 

Redis实现分布式锁的基本思路 

 代码实现

public class SimpleRedisLock implements ILock{

    private String lockname;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";

    public SimpleRedisLock(String lockname, StringRedisTemplate stringRedisTemplate) {
        this.lockname = lockname;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean trylock(long timeoutSec) {
        //获取线程标识
        long threadId = Thread.currentThread().threadId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX+lockname, threadId+"", timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success); //自动拆箱有风险
    }

    @Override
    public void unlock() {
        //释放锁完成
        stringRedisTemplate.delete(KEY_PREFIX+lockname);
    }
}

使用分布式锁对代码进行优化

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result seckillVoucher(Long voucherId) {
        //1.查询优惠券
            ...
        //2.判断秒杀是否开始
            ...
        //3.判断秒杀是否已经结束
            ...
        //4.判断库存是否充足
            ...
        //5.一人一单
        Long userId = UserHolder.getUser().getId();
        //创建锁对象
        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        //获取锁
        boolean isLock = lock.trylock(1200);
        //判断是否获取锁成功
        if(!isLock){
            //获取锁失败,返回报错
            return Result.fail("不允许重复下单");
        }
        try {
            //取到了当前代理对象
            IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }finally {
            //释放锁
            lock.unlock();
        }

    }

    @Override
    @Transactional
    public   Result createVoucherOrder(Long voucherId) {
        ...
    }
}

分布式锁误删问题

如下图所示,线程1释放了线程2的锁,然后这时候线程3过来获取了锁,然后就有两个线程在同时跑。这里可以取出锁之后判断是不是自己的标识.是才可以释放锁.

 

之前直接使用线程ID作为标识是不够的,可能在分布式时有多个线程有相同id

代码优化

public class SimpleRedisLock implements ILock{

    private String lockname;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";
    public SimpleRedisLock(String lockname, StringRedisTemplate stringRedisTemplate) {
        this.lockname = lockname;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean trylock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX+lockname, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success); //自动拆箱有风险
    }

    @Override
    public void unlock() {
        //获取线程标识
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        //获取锁中的标识
        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockname);
        //判断标识是否一致
        if(threadId.equals(id)) {
            //释放锁完成
            stringRedisTemplate.delete(KEY_PREFIX + lockname);
        }
    }
}

分布式锁的原子性问题

在执行完业务到释放锁期间可能会因为jvm的垃圾回收fullGC发生阻塞。时间一长就触发超时释放。此时别的项目里的线程2过来加锁,又因为线程1里面已经判断过锁标识一致,所以会直接释放线程2的锁。

Lua脚本解决多条命令原子性问题

使用Lua脚本改造分布式锁

要准备一个脚本文件unlock.lua,这个文件放在和application.yaml同级的目录下

-- 比较线程标识与锁中的标识是否一致
if(redis.call('get',KEYS[1])==ARGV[1]) then
    -- 释放锁 del key
    return redis.call('del',KEYS[1])
end
return 0

 改造后代码

public class SimpleRedisLock implements ILock{

    private String lockname;
    private StringRedisTemplate stringRedisTemplate;
    private static final String KEY_PREFIX="lock:";
    private static final String ID_PREFIX= UUID.randomUUID().toString(true)+"-";

    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT;
    static{
        UNLOCK_SCRIPT=new DefaultRedisScript<>();
        UNLOCK_SCRIPT.setLocation(new ClassPathResource("unlock.lua"));
        UNLOCK_SCRIPT.setResultType(Long.class);
    }
    public SimpleRedisLock(String lockname, StringRedisTemplate stringRedisTemplate) {
        this.lockname = lockname;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean trylock(long timeoutSec) {
        //获取线程标识
        String threadId = ID_PREFIX+Thread.currentThread().getId();
        //获取锁
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(KEY_PREFIX+lockname, threadId, timeoutSec, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success); //自动拆箱有风险
    }

    @Override
    public void unlock()  {
//        //获取线程标识
//        String threadId = ID_PREFIX+Thread.currentThread().getId();
//        //获取锁中的标识
//        String id = stringRedisTemplate.opsForValue().get(KEY_PREFIX + lockname);
//        //判断标识是否一致
//        if(threadId.equals(id)) {
//            //模拟此处发生阻塞导致锁超时释放
//            //Thread.sleep(1000);
//            //释放锁完成
//            stringRedisTemplate.delete(KEY_PREFIX + lockname);
//        }
        //调用lua脚本
        stringRedisTemplate.execute(
                UNLOCK_SCRIPT,
                Collections.singletonList(KEY_PREFIX+lockname),
                ID_PREFIX+Thread.currentThread().getId());
    }

}

总结

Redisson

快速入门

改造下单业务代码

经过测试也是可以正常使用。

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    @Resource
    private RedissonClient redissonClient;
    @Override
    public Result seckillVoucher(Long voucherId) {

        //创建锁对象
//        SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        //获取锁
//        boolean isLock = lock.trylock(1200);
        boolean isLock = lock.tryLock();
        //判断是否获取锁成功
        if(!isLock){
            //获取锁失败,返回报错
            return Result.fail("不允许重复下单");
        }
        try {
            //取到了当前代理对象
            IVoucherOrderService proxy =(IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }finally {
            //释放锁
            lock.unlock();
        }
    }
}

Redisson可重入锁原理

在下图左边的demo里面,是一个线程先后尝试获取锁共两次,在之前自己写的业务流程是没办法获取两次的。

参考jdk实现可重入锁的思路,同一个线程每次获取的时候记录次数加1,释放的时候次数减1.要记录锁的标识的和锁的重入次数可以使用hash结构。

每次释放锁的时候根据业务判断是减1还是删除锁。

使用Lua脚本完成可重入锁

获取锁的脚本

释放锁的脚本

Redisson的锁重试和WatchDog机制

整个流程如下所示

获取锁成功,发现锁的不过期的就利用看门狗机制一直刷新....逻辑很复杂。

 Redisson的multiLock原理(解决主从一致性问题)

主节点宕机之后锁就失效了,别的线程就可以来获取锁了。

 这个是必须所有主节点都获取到锁才算成功获取。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1283827.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

判断是否存在重复的数

系列文章目录 进阶的卡莎C_睡觉觉觉得的博客-CSDN博客数1的个数_睡觉觉觉得的博客-CSDN博客双精度浮点数的输入输出_睡觉觉觉得的博客-CSDN博客足球联赛积分_睡觉觉觉得的博客-CSDN博客大减价(一级)_睡觉觉觉得的博客-CSDN博客小写字母的判断_睡觉觉觉得的博客-CSDN博客纸币(C…

2023年哪些行业的offer发的最多?智能制造、AIGC、AI大模型位居前三

口罩过后&#xff0c;职场现状如何&#xff1f;为何211,985毕业生也找不到工作&#xff1f;哪些行业offer多多&#xff1f; 今天我以猎聘大数据《2023届高校毕业生就业数据报告》为基础&#xff0c;带大家一起来看2023年高校毕业生就业形势。热门行业岗位、18个新赛道机会、就业…

关于使用百度开发者平台处理语音朗读问题排查

错误信息&#xff1a;"convert_offline": false, "err_detail": "16: Open api characters limit reach 需要领取完 识别和合成都要有

堆排序详细解读

简介 堆排序是一种基于二叉堆数据结构的排序算法&#xff0c;它的特点是不同于传统的比较排序算法&#xff0c;它是通过建立一个堆结构来实现的。堆排序分为两个阶段&#xff0c;首先建立堆&#xff0c;然后逐步将堆顶元素与堆的最后一个元素交换并调整堆&#xff0c;使得最大…

YOLOv3 快速上手:Windows 10上的训练环境搭建

文章目录 前言一、前期准备二、基础环境准备1. 创建虚拟环境2. 打开Terminal3. 下载YOLOv3运行环境 三、PyCharm关联3.1 运行PyCharm3.2 关联Anaconda虚拟环境 四、运行环境检查1. 检查requirements.txt文件2. 安装依赖 五、运行代码5.1 运行检测代码5.2 运行训练代码 六、常见…

Python中的类(Class)和对象(Object)

目录 一、引言 二、类&#xff08;Class&#xff09; 1、类的定义 2、类的实例化 三、对象&#xff08;Object&#xff09; 1、对象的属性 2、对象的方法 四、类和对象的继承和多态性 1、继承 2、多态性 五、类与对象的封装性 1、封装的概念 2、Python中的封装实现…

cmd查看进程信息 终止进程

cmd查看进程信息 终止进程 1、cmd查看进程信息2、终止进程 1、cmd查看进程信息 tasklist命令 描述: 该工具显示在本地或远程机器上当前运行的进程列表。 tasklist /?查看本机所有进程列表 tasklist /V根据进程名 查看jmeter进程 tasklist /V |findstr /i jmeter2、终止进程…

操作系统·设备管理

I/O系统是计算机系统的重要组成部分&#xff0c;是OS中最复杂且与硬件密切相关的部分 I/O系统的基本任务是完成用户提出的I/O请求&#xff0c;提高I/O速率以及改善I/O设备的利用率&#xff0c;方便高层进程对IO设备的使用 I/O系统包括用于实现信息输入、输出和存储功能的设备和…

C++ 系列 第四篇 C++ 数据类型上篇—基本类型

系列文章 C 系列 前篇 为什么学习C 及学习计划-CSDN博客 C 系列 第一篇 开发环境搭建&#xff08;WSL 方向&#xff09;-CSDN博客 C 系列 第二篇 你真的了解C吗&#xff1f;本篇带你走进C的世界-CSDN博客 C 系列 第三篇 C程序的基本结构-CSDN博客 前言 面向对象编程(OOP)的…

[多线程]阻塞队列和生产者消费者模型

目录 1.阻塞队列 1.1引言 1.2Java标准库中的阻塞队列 1.3自主通过Java代码实现一个阻塞队列(泛型实现) 2.生产者消费者模型 1.阻塞队列 1.1引言 阻塞队列是多线程部分一个重要的概念,它相比于一般队列,有两个特点: 1.线程是安全的 2.带有阻塞功能 1) 队列为空,出队列就会阻…

Android wifi连接和获取IP分析

wifi 连接&获取IP 流程图 代码流程分析 一、关联阶段 1. WifiSettings.submit – > WifiManager WifiSettings 干的事情比较简单&#xff0c;当在dialog完成ssid 以及密码填充后&#xff0c;直接call WifiManager save 即可WifiManager 收到Save 之后&#xff0c;就开…

C++实现顺序栈的基本操作(扩展)

#include <stdio.h> typedef char ElemType; #define StackSize 100 /*顺序栈的初始分配空间*/ typedef struct { ElemType data[StackSize]; /*保存栈中元素*/int top; /*栈顶指针*/ } SqStack; void InitStack(SqStack &st) {st.top-1; } …

质量小议35 -- SQL注入

已经记不得上次用到SQL注入是什么时候了&#xff0c;一些概念和操作已经模糊。 最近与人聊起SQL注入&#xff0c;重新翻阅&#xff0c;暂记于此。 重点&#xff1a;敏感信息、权限过大、未脱敏的输入/输出、协议、框架、数据包、明文、安全意识 SQL - Structured Query La…

实战技巧:为Android应用设置独立的多语言

原文链接 实战技巧&#xff1a;为Android应用设置独立的多语言 通常情况下多语言的设置都在系统设置中&#xff0c;应用需要做的就是提供本应用所使用的字串的多语言翻译&#xff0c;使用时使用R.string.app_name类似的引用&#xff0c;然后系统会根据用户在系统设置中的选项来…

Kubernetes存储搭建NFS挂载失败处理

搞NFS存储时候发现如下问题&#xff1a; Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 5m1s default-scheduler Successful…

【hacker送书第8期】Java从入门到精通(第7版)

第8期图书推荐 内容简介编辑推荐作者简介图书目录参与方式 内容简介 《Java从入门到精通&#xff08;第7版&#xff09;》从初学者角度出发&#xff0c;通过通俗易懂的语言、丰富多彩的实例&#xff0c;详细讲解了使用Java语言进行程序开发需要掌握的知识。全书分为4篇共24章&a…

玩转大数据5:构建可扩展的大数据架构

1. 引言 随着数字化时代的到来&#xff0c;大数据已经成为企业、组织和个人关注的焦点。大数据架构作为大数据应用的核心组成部分&#xff0c;对于企业的数字化转型和信息化建设至关重要。我们将探讨大数据架构的基本要素和原则&#xff0c;以及Java在大数据架构中的角色&…

智能优化算法(二):禁忌搜索算法

文章目录 禁忌搜索算法1.禁忌搜索算法预备知识1.1 预备知识1---解空间1.2.预备知识2---邻域 2.禁忌搜索算法实现过程2.1.禁忌搜索算法思想2.2.禁忌搜索构成要素2.2.1.搜索结果表达2.2.2.邻域移动策略2.2.3.禁忌表引入2.2.4.禁忌搜索选择策略2.2.5.禁忌搜索渴望水平2.2.6.禁忌搜…

UEC++ 探索虚幻5笔记 day11

虚幻5.2.1探索 项目目录探索 C工程一定不能是中文路径&#xff0c;中文项目名&#xff0c;最好全部不要用中文&#xff0c;蓝图项目可以是中文浅浅创建一个空项目&#xff0c;讲解一下之前UE4没有讲解的项目目录文件的分布组成 .vs&#xff1a;文件夹一般是项目编译缓存文件夹…

Deployment脚本部署Tomcat集群:外部访问、负载均衡、文件共享及集群配置调整

文章目录 前置知识一、Deployment脚本部署Tomcat集群二、外部访问Tomcat集群三、利用Rinted对外提供Service负载均衡支持1、创建服务2、端口转发工具Rinetd3、定义jsp文件查看转发到哪个节点 四、部署配置挂载点五、基于NFS实现集群文件共享1、master2、node3、验证 六、集群配…