【2023】通过redis 实现分布式锁由原生到Redisson代码三种实现和介绍

目录

  • 一、简介
    • 分布式锁的实现应该具备哪些条件
    • 分布式锁的实现方式
  • 二、具体实现
    • 1、RedisTemplate的setnx方式实现
      • 1.1、基本配置
        • 1.1.1、创建spring项目添加依赖
        • 1.1.2、添加RedisTemplate配置bean
      • 1.2、编写DistributedLock类
      • 1.2、编写Controller层使用分布式锁
      • 1.3、可能出现的问题?
    • 2、Lua版本
      • 2.1、创建DistributedLuaLock类
      • 2.2、controller层接口
      • 2.3、存在的不足
    • 3、通过springboot配合Redisson
      • 3.1、依赖
      • 3.2、添加RedissonLock获取和释放锁
      • 3.3、Controller层接口

一、简介

在开发过程中当我们为了提高效率,我们往往会引用多线程并行的方式来提高访问修改并发,我们在面对并发问题时,多个线程一起修改时,如果是单应用部署,直接通过synchronized关键字、 ReetrantLock 类来控制一个JVM进程内多个线程对本地共享资源的访问。
但如果是在分布式、微服务等架构下,不同的服务/客户端通常运行在独立的JVM进程上,使用本地锁的方式就不能有效的解决这个问题啦,因此就引出了一个分布式锁的概念。

下面是我画的一张示意图:
多个服务的线程同时访问同一个共享资源时,在同一时间只会有一个线程获取到🔒,其他线程会进行阻塞(也可以直接返回获取不到锁的结果)。
在这里插入图片描述

分布式锁的实现应该具备哪些条件

基本条件:

  • 互斥:分布式锁应该确保在任意时刻只有一个客户端能够获得锁,其他客户端需要等待被释放。
  • 高可用:锁服务是高可用的,当一个锁服务出现问题时,需要能够自动释放锁,并且当释放锁的代码逻辑出现问题时,锁最终一定还是会被释放,不会影响其他线程去获取锁对共享资源进行访问。
  • 可重入:同一个客户端在持有锁的情况下,可以多次获取该锁而不会导致死锁
  • 一致性:分布式锁应该在分布式环境下保持一致性,即不同节点之间的锁状态应该一致。

额外条件:

  • 高性能:分布式锁的实现应该尽可能地减少网络通信和资源消耗,以提高性能
  • 安全性:分布式锁应该具备一定的安全性,防止恶意客户端的攻击或误操作。

分布式锁的实现方式

在使用分布式锁实现上:目前主要可以通过三个方式实现:

  • 使用关系型数据库的行锁特性来实现分布式锁
  • 使用分布式协调服务 ZooKeeper 实现分布式锁。
  • 使用分布式键值存储系统如Redis实现分布式锁。

在这里具体介绍通过redis的方式如何实现分布式锁

二、具体实现

1、RedisTemplate的setnx方式实现

1.1、基本配置

1.1.1、创建spring项目添加依赖
  <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
1.1.2、添加RedisTemplate配置bean

继续使用上一篇使用redis实现stream配置即可

   @Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new StringRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());// 这个地方不可使用 json 序列化,如果使用的是ObjectRecord传输对象时,可能会有问题,会出现一个 java.lang.IllegalArgumentException: Value must not be null! 错误redisTemplate.setHashValueSerializer(RedisSerializer.string());return redisTemplate;}

1.2、编写DistributedLock类

用于获取到分布式锁和释放分布式锁

  • 获取锁:使用redisTemplate.opsForValue().setIfAbsent(), 方法实现,该方法的底层其实就是调用了execute()方法实现了setnx命令(先进行判定键是否存在,不存在则设置key,成功后返回true;存在则直接返回false)。
  • 释放锁:直接使用redisTemplate.delete()方法删除掉该key即可释放成功。但在删除之前需要通过验证值的方式,验证是否是该线程自己的锁。
    • 因为如果不做验证,就可能会出现误删的情况(即客户端a在进行操作,服务器发生卡顿,达到key设置的过期时间,解开了锁,客户端b开始进行操作;然后在b进行操作期间,a卡顿结束,继续删锁操作,会导致误删了b的锁)所以需要设置唯一值(uuid等)用于验证;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.concurrent.TimeUnit;
/*** 使用redis data的RedisTemplate的原生方式实现加锁和释放锁*/
@Component
public class DistributedLock {private static final String LOCK_PREFIX = "lock:";private static final long DEFAULT_EXPIRE_TIME = 10; // 默认锁过期时间为60秒@Autowiredprivate RedisTemplate<String, String> redisTemplate;public boolean tryLock(String lockKey,String value) {return tryLock(lockKey,value, DEFAULT_EXPIRE_TIME);}/*** 获取锁* @param lockKey* @param value* @param expireTime 超时时间* @return*/public boolean tryLock(String lockKey,String value, long expireTime) {String key = LOCK_PREFIX + lockKey;Boolean success = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);return success != null && success;}/*** 释放锁* @param lockKey* @param value*/public void unlock(String lockKey,String value) {String key = LOCK_PREFIX + lockKey;String v = redisTemplate.opsForValue().get(key);if (!StringUtils.isEmpty(v) && value.equals(v)){redisTemplate.delete(key);}}
}

1.2、编写Controller层使用分布式锁

该处代码主要是模拟发生秒杀情况时,多个线程一起去争抢的场景,通过写了两个一样的接口用于模拟争抢,通过设置debug的断点为Thread模拟测试。
在这里插入图片描述

具体业务流程:

  1. 先设置currentTime和value等值,
  2. 然后进入循环,去获取锁,看是否可以直接获取到,如果获取到了说明当前没有线程争抢;如果没获取到,说明当前有其他线程持有了锁,在访问共享资源。
  3. 获取成功后直接执行访问资源等后续业务操作,执行完之后删除掉锁,便于下一个线程获取。
  4. 如果获取失败,则通过自旋的发生持续请求获取,避免请求过于频繁耗费资源。可以通过休眠的发生暂停一下,如果持续获取不到可以返回失败结果。
import com.zheng.redislock.setnx.service.ProductService;
import com.zheng.redislock.setnx.util.DistributedLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import java.util.UUID;/*** 测试使用redis的setnx命令实现分布式锁功能*  使用该方式,会出现两个问题:*      1、可能会出现误删的情况(即客户端a在进行操作,服务器发生卡顿,达到key设置的过期时间,解开了*      锁,客户端b开始进行操作;然后在b进行操作期间,a卡顿结束,继续删锁操作,会导致误删了b的锁)*      所以需要设置唯一值(uuid等)用于验证;*      2、该种方式实现因为设置锁和删除锁分开的,因此缺乏原子操作,可以采用lua脚步方式实现*/@RequestMapping("/index")
@RestController
@Slf4j
public class Index {@Resourceprivate ProductService productService;@Resourceprivate RedisTemplate<String,Object> redisTemplate;private final Long time = 1000L;@Resourceprivate DistributedLock distributedLock;/***@Description//TODO用户秒杀接收数据端1*@param:goodsId商品id*@param:userId用户id*@return:java.lang.String**/@GetMapping("doSeckill1")public String doSeckill1(String goodsId ,Integer userId ){Long currentTime = 0L;String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;while (currentTime <= time){if (distributedLock.tryLock(goodsId,value, 60)) {
//                获取到锁,执行业务退出try {
//                执行具体业务 ****productService.update(goodsId, userId);} finally {
//                删除keydistributedLock.unlock(goodsId,value);}log.info("一号直接获取锁成功");return "库存扣减成功";}
//            未获取到继续自旋,并且暂停100毫秒try {Thread.sleep(100);log.warn("一号自旋获取结果:失败,继续重试,时间{}",currentTime);} catch (InterruptedException e) {throw new RuntimeException(e);}currentTime+=100;}return "库存扣减失败";}/***@Description//TODO用户秒杀接收数据端2*@param:goodsId商品id*@param:userId用户id*@return:java.lang.String**/@GetMapping("doSeckill2")public String doSeckill2(String goodsId ,Integer userId ){Long currentTime = 0L;String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;while (currentTime <= time){if (distributedLock.tryLock(goodsId,value, 60)) {
//                获取到锁,执行业务退出try {
//                执行具体业务 ****productService.update(goodsId, userId);} finally {
//                删除keydistributedLock.unlock(goodsId,value);}log.info("二号直接获取锁成功");return "库存扣减成功";}
//            未获取到继续自旋,并且暂停100毫秒try {Thread.sleep(100);log.warn("二号自旋获取结果:失败,继续重试,时间{}",currentTime);} catch (InterruptedException e) {throw new RuntimeException(e);}currentTime+=100;}return "库存扣减失败";}
}

1.3、可能出现的问题?

当多个线程同时争抢同一个redis锁时,如果这些线程都是通过使用 RedisTemplate 的 delete() 方法来释放锁的话,可能会出现下面情况:

  1. 线程a成功获取到锁,并且设置了一个过期时间;

  2. 过了一段时间后,刚刚验证通过value是否一致时,锁的过期时间到了,自动过期了
    在这里插入图片描述

  3. 此时刚好线程b过来尝试获取锁,因为线程a的锁已经到期过期了,然后线程b获取锁成功。
    在这里插入图片描述

  4. 这时线程a去调用了 RedisTemplate 的 delete() 方法来释放锁,这时因为持有锁的其实已经是线程b的锁了,所以就会把线程b的锁给释放掉,从而到导致线程b到锁失效。

因此,使用 RedisTemplate 的 delete() 方法来释放锁的方式可能存在删除其他线程获取的锁的风险。如果要避免这种情况发生,可以使用Redis到lua脚步,在执行释放锁时,把进行验证锁的value值和删除操作去保证一个原子操作。从而避免误删其他线程的锁

2、Lua版本

基本配置同上

2.1、创建DistributedLuaLock类

在上面的demo中虽然获取锁使用的setnx保证了原则性,但在删除释放锁时,因为需要先验证value值,是分开两步操作的,就没法保证原子性了,所以需要引用lua脚步去保证。

在使用lua脚本的使用方式可以使用redisTemplate.execute()方法,这个方法总共三个参数。
script:用于封装脚本和执行完返回的参数;
keys:对应着redis的键名的集合;
args:数组,按照顺序对应着lua脚本中的每一个argv[]值,可以多个
在这里插入图片描述

  • 获取锁:
    • 解析步骤:先执行redis的setnx命令,如果执行后结果返回1(代表获取到锁),则继续 添加超时时间然后返回true;如果执行setnx命令返回的不是1(代表锁被别到线程获取)则直接返回false。同上面的demo的获取锁其实内部调用的命令是一样的。
    private static final String LOCK_SCRIPT ="if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return true; " +"else return false; " +"end";
  • 释放锁
    • 步骤解析: 先执行get()命令取出值,看结果是否和ARGV[1]的值相等(代表锁自己的锁),如果相等则del命令删除该key,然后返回true;如果不相等(则代表不是自己的锁)则直接返回false,不执行del删除命令。
    private static final String UNLOCK_SCRIPT ="if redis.call('get', KEYS[1]) == ARGV[1] then " +"redis.call('del', KEYS[1]); " +"return true; " +"else return false; " +"end";

具体代码:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/*** 使用redis加lua脚步方式实现加锁和释放锁*/
@Component
public class DistributedLuaLock {@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static final String LOCK_PREFIX = "luaLock:";// 锁的过期时间,单位毫秒private static final long LOCK_EXPIRE_TIME = 60000;// 获取锁的 Lua 脚本:private static final String LOCK_SCRIPT ="if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then " +"redis.call('pexpire', KEYS[1], ARGV[2]); " +"return true; " +"else return false; " +"end";// 释放锁的 Lua 脚本:private static final String UNLOCK_SCRIPT ="if redis.call('get', KEYS[1]) == ARGV[1] then " +"redis.call('del', KEYS[1]); " +"return true; " +"else return false; " +"end";public boolean luaLock(String key,String value){return luaLock(key,value, LOCK_EXPIRE_TIME);}/*** 获取分布式锁* @param key* @param value* @param lockTime 超时时间(毫秒)* @return*/public boolean luaLock(String key,String value,Long lockTime){String[] args = {value,String.valueOf(lockTime)};RedisScript<Boolean> script = new DefaultRedisScript<>(LOCK_SCRIPT, Boolean.class);Boolean result = redisTemplate.execute(script, Arrays.asList(LOCK_PREFIX+key), args);return result != null && result;}/*** 释放锁* @param key* @param value* @return*/public boolean unlock(String key,String value){String[] args = {value};DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Boolean.class);Boolean result = redisTemplate.execute(script, Arrays.asList(LOCK_PREFIX+key), args);return result != null && result;}
}

2.2、controller层接口

具体执行业务逻辑上和上面的demo逻辑都是一样的,只不过这里在释放锁的时候,使用lua脚本的方式把验证value值和删除键绑定为一个原子操作了

/***lua脚本方式实现接口*/
@RequestMapping("/indexLua")
@RestController
@Slf4j
public class IndexLua {@Resourceprivate ProductService productService;private final Long time = 1000L;@Resourceprivate DistributedLuaLock distributedLuaLock;/*** 1**/@GetMapping("doSeckill1")public String doSeckill1(String goodsId ,Integer userId ){Long currentTime = 0L;String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;while (currentTime <= time){if (distributedLuaLock.luaLock(goodsId,value, 60000l)) {
//                获取到锁,执行业务退出try {
//                执行具体业务 ****productService.update(goodsId, userId);} finally {
//                删除keydistributedLuaLock.unlock(goodsId,value);}log.info("一号直接获取锁成功");return "库存扣减成功";}
//            未获取到继续自旋,并且暂停100毫秒try {Thread.sleep(100);log.warn("一号自旋获取结果:失败,继续重试,时间{}",currentTime);} catch (InterruptedException e) {throw new RuntimeException(e);}currentTime+=100;}return "库存扣减失败";}/*** 2**/@GetMapping("doSeckill2")public String doSeckill2(String goodsId ,Integer userId ){Long currentTime = 0L;String value= UUID.randomUUID() + "-" + Thread.currentThread().getId();
//        通过key到redis中去获取锁,如果成功则返回true并且设置过期时间,如果失败则返回false;while (currentTime <= time){if (distributedLuaLock.luaLock(goodsId,value, 60000l)) {
//                获取到锁,执行业务退出try {
//                执行具体业务 ****productService.update(goodsId, userId);} finally {
//                删除keydistributedLuaLock.unlock(goodsId,value);}log.info("二号直接获取锁成功");return "库存扣减成功";}
//            未获取到继续自旋,并且暂停100毫秒try {Thread.sleep(100);log.warn("二号自旋获取结果:失败,继续重试,时间{}",currentTime);} catch (InterruptedException e) {throw new RuntimeException(e);}currentTime+=100;}return "库存扣减失败";}
}

2.3、存在的不足

虽然使用lua脚本的方式实现可以有效的解决非原子性造成的删除错锁的问题,看似是已经很完美了;但该种方式在设置锁时,设计超时时间是固定的,如上我们设置的是60秒,但实际开发过程中,我们可能因为某些特定的业务场景以及系统问题可能会有不同,所以在设置锁的过期时间上需要根据实际业务需求来进行设置,如果过短可能会导致频繁获取地去获取锁,如果过长则可能会造成失效不及时的问题。

在解决这个问题上可以采用下面的Redisson来实现,Redisson使用来一种叫看门狗的机制实现对锁的自动续期,可以有效的帮我们解决锁的超时时间设置不当的问题。

3、通过springboot配合Redisson

Redisson是一个基于Redis的分布式Java对象和服务框架,它提供了一系列的分布式对象和服务,使得在Java应用程序中使用Redis变得更加方便和高效。

3.1、依赖

<dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId><version>3.15.6</version><!--根据自己spring版本设置对应版本--></dependency>

3.2、添加RedissonLock获取和释放锁

Redisson在内部给我们提供了很多锁供我们选择。主要通过redissonClient接口去获取到不同的锁对象。

Redisson常用分布式锁对象

  • RLock lock = redissonClient.getLock(key) // 可重入锁(最常用的)
  • RLock fairLock = redissonClient.getFairLock(key);//公平锁
  • RLock spinLock = redissonClient.getSpinLock(key);//自旋锁
  • RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(key);//读写锁
  • RLock multiLock = redissonClient.getMultiLock(lock);//多重锁

获取锁方式:
获取锁的方式上主要有两种方式获取,一种上阻塞的方式去获取;一种上通过非阻塞的方式获取。

  • lock.lock():阻塞的方式获取,使用该方式去获取,如果没有获取到锁时,会一直阻塞请求获取锁,直到获取到锁为主;如果不设置超时时间则默认使用看门狗功能自动续期(一般不介意设置超时时间)。默认30秒为最大时间10秒续期一次,续期锁调用RedissonBaseLock类的renewExpirationAsync() 方法实现锁的异步续期
  • lock.tryLock(expireTime,TimeUnit.SECONDS):非阻塞的方式获取锁,最多可以设置三个参数,分别指定重试时间,锁过期时间,和时间单位;一般建议设置一个重试时间,如果指定时间没获取则返回false,因为毕竟是非阻塞执行的嘛,如果不设置则会一直去请求获取锁。

释放锁:则直接通过lock.unlock()进行释放即可

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/*** 使用redisson方式实现加锁和释放锁*/
@Component
public class RedissonLock {@Resourceprivate RedissonClient redissonClient;private static final String LOCK_PREFIX = "sonLock:";/*** 阻塞方式获取锁* @param key* @param expireTime* @return*/public RLock lock(String key,Integer expireTime){
//        可重入锁RLock lock = redissonClient.getLock(LOCK_PREFIX + key);
//        redissonClient.getFairLock(); //公平锁
//        redissonClient.getSpinLock(); //自旋锁
//        redissonClient.getReadWriteLock(); //读写锁
//        redissonClient.getMultiLock();  //多重锁//        waitTime:设置超时时间(),unit:时间单位
//          超时过期时间我们一般不需要设置redisson内部实现了看门狗自动续时功能。
//              看门狗续期前也会先判断是否需要执行续期操作,需要才会执行续期,否则取消续期操作。
//        lock.lock(expireTime, TimeUnit.SECONDS); //阻塞方式获取锁,设置过期时间lock.lock();return lock;}/*** 非阻塞方式获取锁* @param key*/public Boolean tryLock(String key,Integer expireTime){try {RLock lock = redissonClient.getLock(LOCK_PREFIX + key);//            waitTime:获取锁重试时间,leaseTime:设置超时时间,unit:时间单位
//            lock.tryLock(10,expireTime,TimeUnit.SECONDS);return lock.tryLock(10,TimeUnit.SECONDS); //非阻塞方式获取锁,设置在指定时间内失败重试获取锁} catch (InterruptedException e) {Thread.currentThread().interrupt();return false;}}/*** 释放锁* @param key*/public void unlock(String key) {RLock lock = redissonClient.getLock(LOCK_PREFIX+key);if (lock.isLocked()) {lock.unlock();}}}

3.3、Controller层接口

通过redsson实现,在业务调用上相对就比较简单了,内部已经做过相应的重试封装了。
内部两个接口分别是调用阻塞和非阻塞方式获取锁去实现分布式锁功能。

import com.zheng.redislock.setnx.service.ProductService;
import com.zheng.redislock.setnx.util.DistributedLuaLock;
import com.zheng.redislock.setnx.util.RedissonLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.UUID;@RequestMapping("/indexson")
@RestController
@Slf4j
public class Indexson {@Resourceprivate ProductService productService;private final Long time = 1000L;@Resourceprivate RedissonLock redissonLock;/***非阻塞方式获取锁*@param:goodsId商品id*@param:userId用户id*@return:java.lang.String**/@GetMapping("doSeckill1")public String doSeckill1(String goodsId ,Integer userId ){Boolean b = redissonLock.tryLock(goodsId, 30);if (b) {
//                获取到锁,执行业务退出try {
//                执行具体业务 ****productService.update(goodsId, userId);} finally {
//                删除keyredissonLock.unlock(goodsId);}log.info("一号直接获取锁成功");return "库存扣减成功";}return "库存扣减失败";}/***阻塞方式获取锁*@param:goodsId商品id*@param:userId用户id*@return:java.lang.String**/@GetMapping("doSeckill2")public String doSeckill2(String goodsId ,Integer userId ){RLock lock = redissonLock.lock(goodsId,30);try {//                执行具体业务 ****productService.update(goodsId, userId);} finally {if (lock.isLocked()){lock.unlock();}}return "库存扣减成功";}
}

使用redisson实现基本上就没有什么漏洞了,因为 Redisson 本身就是基于 Redis 的分布式锁实现。但在使用的时间需要根据转身业务情况看是使用阻塞方式获取还是非阻塞方式获取,如果是非阻塞的方式将需要去设置尝试获取锁的最大等待时间避免线程一直阻塞的去获取锁。
当然如果锁集群环境下可能会存在由于数据分片和主从复制等机制造成的节点未能及时同步等问题;这个的话可以使用Redis 的 RedLock 算法来实现分布式锁。算法可以在多个 Redis 节点之间进行协作,确保锁的正确性和可靠性。具体这里就不做讲解了

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

智慧公厕:细致入微的城市贴心服务与便捷方便的生活配套

在现代城市生活中&#xff0c;公厕作为重要的城市基础设施&#xff0c;一直是城市发展的关键环节之一。然而&#xff0c;传统的公厕常常存在着设施陈旧、管理不善和卫生状况差等问题&#xff0c;给市民的生活品质和城市形象带来了一定的影响。为了提供更好的城市公厕服务&#…

正点原子嵌入式linux驱动开发——Linux PWM驱动

PWM是很常用到功能&#xff0c;可以通过PWM来控制电机速度&#xff0c;也可以使用PWM来控制LCD的背光亮度。本章就来学习一下如何在Linux下进行PWM驱动开发。 PWM驱动解析 不在介绍PWM是什么了&#xff0c;直接进入使用。 给LCD的背光引脚输入一个PWM信号&#xff0c;这样就…

25装饰器2

有一个比较绕的地方&#xff0c;但是我相信我能理解透彻这个里面的逻辑还有原理 def check(func):print(登录验证)def inner():func()return innerdef fss():print(发说说)fss check(fss) fss()这里的fss check(fss)还有后面的fss()什么意思&#xff1f;怎么理解呢&#xff…

【微信小程序开发】小程序微信用户授权登录(用户信息手机号)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于小程序的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 授权流程讲解 一.用户信息授权登录 1.w…

记一次vue3实现TRSP大华相机拉流的经历

一、背景 业务场景&#xff0c;大华IP相机安装在A城市某建筑场所&#xff0c;工控机是内网通过4G流量卡上网&#xff0c;工控机通过相机采集数据后做故障识别并上传故障信息到地面服务器&#xff0c;地面服务器在B城市。 现需要在地面服务器提供的WEB界面上实现IP相机实时拉流…

CSS基础框盒模型:打造炙手可热的网页布局!

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、是…

逐鹿千亿市场:一碗中国面的魅力

【潮汐商业评论/原创】 根据世界方便面协会&#xff08;WINA&#xff09;发布的数据&#xff0c;2022年全球方便面总需求量同比增长4.0%至1212亿份&#xff0c;再创历史新高。从小门头商铺到大型商超&#xff0c;从线下到线上&#xff0c;方便面早就走进了千家万户&#xff0c…

使用adobe font style 工具绘制的艺术字,请鉴赏。

Adobe Fireflyhttps://firefly.adobe.com/generate/font-styles

引领位置服务驱动:腾讯地图 WebService 服务端 API 实用指南

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

马蹄集OJ赛第十三次

目录 小码哥的抽卡之旅 抽奖概率 越狱 square 矩阵乘法 找朋友 赌石 甜甜花的研究 行列式 饿饿&#xff01;饭饭&#xff01; 小码哥的抽卡之旅 难度&#xff1a;黄金 ①时间限制&#xff1a;1秒 巴占用内存&#xff1a;128M 小码哥最近迷上了一款抽卡游戏。单抽出金…

AWS Lambda 操作 RDS 示例

实现目标 创建一个 Lambda 接收调用时传入的数据, 写入 RDS 数据库 Post 表存储文章信息. 表结构如下: idtitlecontentcreate_date1我是标题我是正文内容2023-10-21 15:20:00 AWS 资源准备 RDS 控制台创建 MySQL 实例, 不允许 Public access (后面 Lambda 需要通过 VPC 访问…

基于MIMO+16QAM系统的VBLAST译码算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ........................................................................ for SNR_dBSNRS…

element-plus 自动按需引入icon unplugin-icons相关配置(有效)

1.安装的组件有这四个2.vite.config.js配置文件修改页面使用附完整vite.config.js配置 相关配置&#xff08;自行根据配置文件中的安装哈&#xff0c;我就不一 一列举了&#xff09; 1.安装的组件有这四个 2.vite.config.js配置文件修改 页面使用 <i-ep-edit />效果 附…

pytorch学习第四篇:从全连接到卷积

传统神经网络 传统神经网络nn,采用这种全连接的方式,可以看出是一列一列数据,包括起始和中间的隐层都是一列数据。 例如我们再对图像用这种方式来计算,需要把图像转为一列数据,例如图像为28x28单通道图像,需要转为28x28=784的列数据再进行神经网络训练。 input layer =…

基于 Android 的文件同步设计方案

1、背景 随着用户对自身数据保护意识的加强&#xff0c;让用户自己维护自己的数据也成了独立开发产品时的一个卖点。若只针对少量的文件进行同步&#xff0c;则实现起来比较简单。当针对一个多层级目录同步时&#xff0c;情况就复杂多了。鉴于相关的文章甚少&#xff0c;本文我…

【从删库到跑路】MySQL数据库 | 存储过程 | 存储函数(使用代码辅助理解)

&#x1f38a;专栏【MySQL】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【The Right Path】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f384;存储过程介绍&#x1f384;存储过程特点&#x1f33a;存储过…

找搭子平台小程序开发制作方案

找搭子小程序是一个基于地理位置的社交平台&#xff0c;旨在帮助用户找到附近的人&#xff0c;一起进行各种活动。的目标是打造一个安全、便捷、有趣的社交平台&#xff0c;让用户在享受活动的同时&#xff0c;也能结识新朋友。 找搭子平台小程序的目标用户主要是年轻人&#x…

「北大社送书」学习Flutter编程 — 《从零基础到精通Flutter开发》

目录 1.书籍推荐理由 2.本书特色 3.内容简介 4.书籍概览 1.书籍推荐理由 一套代码&#xff0c;构建多平台精美的应用&#xff1a;本书从真实的开发场景出发&#xff0c;完整地讲解了Flutter框架&#xff0c;帮助你快速掌握Flutter的基础知识和开发技巧&#xff0c;助你在移…

stm32外部时钟为12MHZ,修改代码适配

代码默认是8MHZ的&#xff0c;修改2个地方&#xff1a; 第一个地方是这个文件的这里&#xff1a; 第二个地方是找到这个函数&#xff1a; 修改第二个地方的这里&#xff1a;

大数据技术学习笔记(二)—— Hadoop 运行环境的搭建

目录 1 准备模版虚拟机hadoop1001.1 修改主机名1.2 修改hosts文件1.3 修改IP地址1.3.1 查看网络IP和网关1.3.2 修改IP地址 1.4 关闭防火墙1.5 创建普通用户1.6 创建所需目录1.7 卸载虚拟机自带的open JDK1.8 重启虚拟机 2 克隆虚拟机3 在hadoop101上安装JDK3.1 传输安装包并解压…