分布式锁-Redis红锁解决方案

news2024/11/24 14:29:24

一 分布式锁的概念

1:概念

分布式锁(多服务共享锁) 在分布式的部署环境下,通过锁机制来让多客户端互斥的对共享资源进行访问控制分布式系统不同进程共同访问共享资源的一种锁的实现。如果不同的系统或同一个系统的不同主机之间共享了某个临界资源,往往需要互斥来防止彼此干扰,以保证一致性。

2:锁/分布式锁/事务区别

  • 锁 单进程的系统中,存在多线程同时操作一个公共变量,此时需要加锁对变量进行同步操作,保证多线程的操作线性执行消除并发修改。解决的是单进程中的多线程并发问题。
  • 分布式锁 只要的应用场景是在集群模式的多个相同服务,可能会部署在不同机器上,解决进程间安全问题,防止多进程同时操作一个变量或者数据库解决的是多进程的并发问题 事务 解决一个会话过程中,上下文的修改对所有数据库表的操作要么全部成功,要不全部失败。所以应用在service层。解决的是一个会话中的操作的数据一致性。
  • 分布式事务 解决一个联动操作,比如一个商品的买卖分为添加商品到购物车、修改商品库存,此时购物车服务和商品库存服务可能部署在两台电脑,这时候需要保证对两个服务的操作都全部成功或者全部回退。解决的是组合服务的数据操作的一致性问题

3:reddison的公平锁

reddison公平锁枷锁

默认的加锁逻辑是非公平的。在加锁失败时,线程会进入 while 循环,一直尝试获得锁,这时候是多线程进行竞争。就是说谁抢到就是谁的。Redisson 提供了公平锁机制,使用方式如下

RLock fairLock = redisson.getFairLock("anyLock");
// 最常见的使用方法
fairLock.lock();

看门狗机制是在 RedissonBaseLock#scheduleExpirationRenewal 方法中,这块公平锁和非公平锁并无区别。前文已经了解到,公平锁加锁失败之后,会将当前放到等待队列中,通过 Java 代码中的循环不断尝试获得锁。

reddison公平锁释放

公平锁的释放同样分为主动释放和超时释放。

  • 主动释放,即自己调用释放锁。
  • 超时删除,则分为两种,一种是持锁线程超时删除,这种和非公平锁没有任何区别,因为这个锁也是含有超时时间+看门狗续租的。另一种则是等待队列中的超时删除,是在每次获取锁之前,判断第一个等待线程的时间戳是否超时,从而移除锁。

二 、使用的案例场景

需求
当在打车软件中,乘客下了订单。多个司机抢单,此时因为单子只有一个,多个司机对此共享资源进行抢,此处应该使用分布式锁;后台服务部署在多台服务器上;
controller层代码

 @GetMapping("/do/{orderId}")
    public String grab(@PathVariable("orderId") int orderId, int driverId){
        System.out.println("order:"+orderId+",driverId:"+driverId);
        //此处调用锁控制层代码
        grabService.grabOrder(orderId,driverId);
        return "";
    }

锁控制层代码(使用synchronized 不成功)
使用synchronized 不能保证多台服务器只有一个抢成功;因为synchronized 只能锁本服务的资源;多台服务的资源是锁不住的;

@Autowired
	OrderService orderService;
	
	@Override
	public String grabOrder(int orderId, int driverId) {
		String lock = (orderId+"");
		
		synchronized (lock.intern()) {
			try {
				System.out.println("司机:"+driverId+" 执行抢单逻辑");
				//此处调用订单业务代码
	            boolean b = orderService.grab(orderId, driverId);
	            if(b) {
	            	System.out.println("司机:"+driverId+" 抢单成功");
	            }else {
	            	System.out.println("司机:"+driverId+" 抢单失败");
	            }
	            
	        } finally {
	        	
	            
	        }
		}
		
		
		return null;
	}

调用的订单业务代码
这一层就是写的伪代码,后续并不关注他
在这里插入图片描述

三、Redis解决方案-红锁

介绍

红锁本质上就是使用多个Redis做锁。例如有5个Redis,一次锁的获取,会对每个请求都获取一遍,如果获取锁成功的数量超过一半(2.5),则获取锁成功,反之失败;
释放锁也需要对每个Redis释放

红锁原理

  1. 在Redis的分布式环境中,我们假设有5个Redis master。这些节点完全互相独立,没有主从关系
  2. 线程1向这5个redis加锁,当加到第三个的时候,4和5加不上了;但是符合红锁的n/2+1原则,所以线程1获取到了锁;
  3. 当redis3挂了,此时线程1获取到了锁,正在顺序执行,
  4. 线程2来到了redis抢占锁,因为3挂了,1,2有锁,只有4和5可以加锁,因为我们注册的时候是5台,4和5这两台不满足n/2+1原则,抢占锁失败;

当锁遇到故障转移

单实例肯定不是很可靠吧?加锁成功之后,结果 Redis 服务宕机了,这不就凉凉~
这时候会提出来将 Redis 主从部署。即使是主从,也是存在巧合的!
主从结构中存在明显的竞态:

客户端 A 从 master 获取到锁在 master 将锁同步到 slave 之前,master 宕掉了。
slave 节点被晋级为 master 节点客户端 B 取得了同一个资源被客户端 A 已经获取到的另外一个锁。安全失效!有时候程序就是这么巧,比如说正好一个节点挂掉的时候,多个客户端同时取到了锁。如果你可以接受这种小概率错误,那用这个基于复制的方案就完全没有问题。
那我使用集群呢?如果还记得前面的内容,应该是知道对集群进行加锁的时候,其实是通过 CRC16 的 hash 函数来对 key 进行取模,将结果路由到预先分配过 slot 的相应节点上。
发现其实还是发到单个节点上的
! 这时候 Redis 作者提出了 RedLock 的概念

在这里插入图片描述
总结一下就是对集群的每个节点进行加锁,如果大多数(N/2+1)加锁成功了,则认为获取锁成功。

RedLock 的问题
看着 RedLock 好像是解决问题了:

客户端 A 锁住了集群的大多数(一半以上);
客户端 B 也要锁住大多数;
这里肯定会冲突,所以 客户端 B 加锁失败。
那实际解决问题了么?

加锁 key 的问题

有一个很大的疑问,我加锁 lock1、lock2、lock3,但是 RedissonRedLock 是如何保证这三个 key 是在归属于 Redis 集群中不同的 master 呢?因为按照 RedLock 的理论,是需要在半数以上的 master 节点加锁成功。阅读完源码之后,发现 RedissonRedLock 完全是 RedissonMultiLock 的子类,只是重写了 failedLocksLimit 方法,保证半数以上加锁成功即可。所以这三个 key,是需要用户来保证分散在不同的节点上的。

红锁的争议

那我使用 5 个单节点的客户端,然后再使用红锁,听着好像是可以的,并且 RedissonRedLock 可以这样使用。但是那和 Redis 集群还有啥关系啊!所以依然没有解决我的问题,在 redis 集群下 针对master节点集群,,还是需要用户自己来“手工定位锁”,使锁的节点分散到不同的master 集群节点下。 手工定位锁,这个…… 我考虑了下,还是不用 RedLock 吧!如果master节点变动则锁也存在问题,master集群同步等等锁的同步和锁失效也是需要考虑的问题;
Redisson 的开发者认为 Redis 的红锁也存在争议(前文介绍的那个争议),但是为了保证可用性,RLock 对象执行的每个 Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。

Redisson 的开发者认为 Redis 的红锁也存在争议(前文介绍的那个争议),但是为了保证可用性,RLock 对象执行的每个
Redis 命令执行都通过 Redis 3.0 中引入的 WAIT 命令进行同步。

在这里插入图片描述
源码在这一部分。
在这里插入图片描述
看源码,同时发送了一个 WAIT 1 1000 到 Redis。

结论:Redisson RedLock 是基于联锁 MultiLock 实现的,但是使用过程中需要自己判断 key 落在哪个节点上,对使用者不是很友好。

红锁使用说明-官网介绍

基于Redis的Redisson红锁RedissonRedLock对象实现了Redlock介绍的加锁算法。该对象也可以用来将多个RLock对象关联为一个红锁,每个RLock对象实例可以来自于不同的Redisson实例。

RLock lock1 = redissonInstance1.getLock("lock1");
RLock lock2 = redissonInstance2.getLock("lock2");
RLock lock3 = redissonInstance3.getLock("lock3");

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 同时加锁:lock1 lock2 lock3
// 红锁在大部分节点上加锁成功就算成功。
lock.lock();
...
lock.unlock();

大家都知道,如果负责储存某些分布式锁的某些Redis节点宕机以后,而且这些锁正好处于锁住的状态时,这些锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。

另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。

RedissonRedLock lock = new RedissonRedLock(lock1, lock2, lock3);
// 给lock1,lock2,lock3加锁,如果没有手动解开的话,10秒钟后将会自动解开
lock.lock(10, TimeUnit.SECONDS);

// 为加锁等待100秒时间,并在加锁成功10秒钟后自动解开
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
...
lock.unlock();

红锁实战

1.注册红锁的RedissonClient

@Component
public class RedisConfig {
	@Bean(name = "redissonRed1")
    @Primary
    public RedissonClient redissonRed1(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6379").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed2")
    public RedissonClient redissonRed2(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6380").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed3")
    public RedissonClient redissonRed3(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6381").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed4")
    public RedissonClient redissonRed4(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6382").setDatabase(0);
        return Redisson.create(config);
    }
    @Bean(name = "redissonRed5")
    public RedissonClient redissonRed5(){
        Config config = new Config();
        config.useSingleServer().setAddress("127.0.0.1:6383").setDatabase(0);
        return Redisson.create(config);
    }
  }

配置方式2
基于 Redis 的 Redisson 分布式联锁 RedissonMultiLock 对象可以将多个 RLock 对象关联为一个联锁,每个 RLock 对象实例可以来自于不同的 Redisson 实例
在这里插入图片描述
按照官方文档的说法,这里 Redisson 客户端可以不是同一个。当然,一般工作中也不会说不用一个客户端吧,可以看出 遍历所有的锁,依次加锁。加锁逻辑就和可重入锁加锁并无区别了。所以 Lua 脚本就不进行分析了

2. 红锁使用

package com.online.taxi.order.service.impl;

import com.online.taxi.order.constant.RedisKeyConstant;
import com.online.taxi.order.service.GrabService;
import com.online.taxi.order.service.OrderService;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;


@Service
public class GrabRedisRedissonRedLockLockServiceImpl implements GrabService {

    // 红锁
    @Autowired
    @Qualifier("redissonRed1")
    private RedissonClient redissonRed1;
    @Autowired
    @Qualifier("redissonRed2")
    private RedissonClient redissonRed2;
    @Autowired
    @Qualifier("redissonRed3")
    private RedissonClient redissonRed3;
    @Autowired
    @Qualifier("redissonRed4")
    private RedissonClient redissonRed4;
    @Autowired
    @Qualifier("redissonRed5")
    private RedissonClient redissonRed5;


    @Autowired
	OrderService orderService;

    @Override
    public String grabOrder(int orderId , int driverId){
        System.out.println("红锁实现类");
        //生成key
        String lockKey = ("" + orderId).intern();


        //redisson锁 单节点
//        RLock rLock = redissonRed1.getLock(lockKey);

        //红锁 redis son
        RLock rLock1 = redissonRed1.getLock(lockKey);
        RLock rLock2 = redissonRed2.getLock(lockKey);
        RLock rLock3 = redissonRed3.getLock(lockKey);
        RLock rLock4 = redissonRed4.getLock(lockKey);
        RLock rLock5 = redissonRed5.getLock(lockKey);
        RedissonRedLock rLock = new RedissonRedLock(rLock1,rLock2,rLock3,rLock4,rLock5);



        try {

             /**红锁
		     * waitTimeout 尝试获取锁的最大等待时间,超过这个值,则认为获取锁失败
		     * leaseTime   锁的持有时间,超过这个时间锁会自动失效(值应设置为大于业务处理的时间,确保在锁有效期内业务能处理完)
		     */
            boolean b1 = rLock.tryLock((long)waitTimeout, (long)leaseTime, TimeUnit.SECONDS);

            if (b1){
                System.out.println("加锁成功");
                // 此代码默认 设置key 超时时间30秒,过10秒,再延时
                System.out.println("司机:"+driverId+" 执行抢单逻辑");
               
                boolean b = orderService.grab(orderId, driverId);
                if(b) {
                    System.out.println("司机:"+driverId+" 抢单成功");
                }else {
                    System.out.println("司机:"+driverId+" 抢单失败");
                }
                System.out.println("加锁成功");
            }else {
                System.out.println("加锁失败");
            }


        } finally {
        	rLock.unlock();
        }
        return null;
    }

}

四、单机版redission 使用

 <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.18.0</version>
        </dependency>

1.配置

    @Value("${spring.redis.host}")
    private String url;

    @Value("${spring.redis.port}")
    private Integer port;

    @Value("${spring.redis.password}")
    private String password;

    @Bean
    public RedissonClient redissonClient(){
        // 配置
        Config config = new Config();
        log.info("Redisson 初始化 {}",String.format("redis://%s:%d",url,port));
        config.useSingleServer().setAddress(String.format("redis://%s:%d",url,port) )
                .setPassword(password);
        // 创建RedissonClient对象
        return Redisson.create(config);
    }

2. 注入分布式锁

此处枷锁后在释放锁是解决了锁的读写机制问题

@Autowired
private RedissonClient redissonClient;


 public List<? extends Object> getCachedConfigList(String ode) {
        List<StructureVo> configuration =null;
        String key = getCacheKey(orgCode, STR_CONFIG_LIST);
        List<Object> cacheConfig = redisCache.getCacheList(key);
        if (!CollectionUtils.isEmpty(cacheConfig)) {
            return cacheConfig;
        }
        RLock lock = redissonClient.getLock(getReddisonEbomCacheKey());
        try {
            lock.lock(DEFAULT_EXPIRE_SECOND,TimeUnit.SECONDS);
            List<Object> secondCacheConfig = redisCache.getCacheList(key);
            if (!CollectionUtils.isEmpty(secondCacheConfig)) {
                log.info("second lock reddison 查询到缓存释放锁!");
                reddisnUnlock(getReddisonEbomCacheKey());
                return cacheConfig;
            }
            log.info("reddison枷锁成功!存放缓存资源!");
            configuration = this.queryAllConfiguration();
            redisCache.setCacheList(key, configuration);
            redisCache.expire(key, DEFAULT_EXPIRE_HOURS, TimeUnit.HOURS);
        } catch (Exception e) {
            log.error("Happen Exception: " + e.getMessage(),e);
        }finally {
            reddisnUnlock(getReddisonEbomCacheKey());
        }
        return configuration;
    }

释放锁方法

    private void reddisnUnlock(String key) {
        log.info(" reddison key {}锁释放!",key);
        try {
            RLock unlock = redissonClient.getLock(key);
            if (unlock != null && unlock.isHeldByCurrentThread()) {
                unlock.unlock();
            }
        } catch (IllegalMonitorStateException e) {
            log.info("reddison锁释放 Exception: " + e.getMessage(),e);
        }
    }

Redis 单实例版本锁 NX

单实例加锁

SET resource_name my_random_value NX PX 30000

对于单实例 Redis 只需要使用这个命令即可。

  • NX:仅在不存在 key 的时候才能被执行成功;
  • PX:失效时间,传入 30000,就是 30s 后自动释放锁;
  • my_random_value:就是随机值,可以是线程号之类的。主要是为了更安全的释放锁,释放锁的时候使用脚本告诉 Redis: 只有 key 存在并且存储的值和我指定的值一样才能删除成功。

简单描述即为

  1. 指定一个 key 作为锁标记,存入 Redis 中,指定一个 唯一的用户标识作为 value。
  2. 当 key 不存在时才能设置值,确保同一时间只有一个客户端进程获得锁,满足互斥性特性。
  3. 设置一个过期时间,防止因系统异常导致没能删除这个 key,满足防死锁特性。
  4. 当处理完业务之后需要清除这个 key 来释放锁,清除 key 时需要校验 value 值,需要满足只有加锁的人才能释放锁

可以通过以下 Lua 脚本实现锁释放:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

为什么要设置随机值?

主要是为了防止锁被其他客户端删除。有这么一种情况:

  1. 客户端 A 获得了锁,还没有执行结束,但是锁超时自动释放了;
  2. 客户端 B 此时过来,是可以获得锁的,加锁成功;
  3. 此时,客户端 A 执行结束了,要去释放锁,如果不对比随机值,就会把客户端 B 的锁给释放了。
    当然前面看过 Redisson 的处理,这个 my_random_value 存放的是 UUID:ThreadId 组合成的一个类似 931573de-903e-42fd-baa7-428ebb7eda80:1 的字符串。

为什么要用lua脚本操作redis数据库?

1.减少开销–减少向redis服务器的请求次数
2.原子操作–redis将lua脚本作为一个原子执行
3.可复用–其他客户端可以使用已经执行过的lua脚本
4.增加redis灵活性–lua脚本可以帮助redis做更多的事情

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

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

相关文章

Oracle数据库设置归档模式(超级简单)

1、打开监听 查看监听的状态&#xff0c;如果没打开监听需要打开监听&#xff0c;如果打开直接下一步 lsnrctl status 打开监听 lsnrctl start 2、启动数据库 首先进入数据库 sqlplus /nolog 然后连接管理员 conn / as sysdba 3、查看当前模式 archive log list 可以…

Spring5学习笔记—CGlib动态代理

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a; Spring专栏 ✨特色专栏&#xff1a; M…

Linux UWB Stack实现——FiRa会话状态机

在FiRa标准中&#xff0c;很重要的一个概念就是FiRa会话以及会话的管理&#xff0c;本文主要介绍了在Linux UWB Stack实现中&#xff0c;FiRa会话状态机管理的实现。 在FiRa中&#xff0c;会话分为INIT、DEINIT、ACTIVE、IDLE四种状态&#xff0c;其定义如下。 enum fira_sess…

2023年上半年上午易错题(软件设计师考试)

计算机中&#xff0c;系统总线用于 &#xff08;1&#xff09; 连接。 A. 接口和外设 B. 运算器、控制器和寄存器 C. CPU、主存及外设部件 D. DMA控制器和中断控制器 在由高速缓存、主存和硬盘构成的三级存储体系中&#xff0c;CPU执行指令时需要读取数据&#xff0c;那…

常用JQuery插件汇总

Jquery插件&#xff0c;数字动画特效&#xff0c;从n到m数字跳动JJ​​​​​​​CountUp.jsA javascript class that animates a numerical value by counting to it.http://inorganik.github.io/countUp.js/

pycharm 2023.2.3设置conda虚拟环境

分两步&#xff1a; &#xff08;1&#xff09;设置Virtualenv Environment &#xff08;2&#xff09;设值Conda Executable 加载conda环境&#xff0c;然后选择conda环境

系列二十三、bean的创建顺序是由什么决定的

一、bean的创建顺序是由什么决定的 bean的创建顺序是由bean的注册顺序决定的。 # 第一步&#xff1a; AnnotationConfigApplicationContext context new AnnotationConfigApplicationContext(MySpringConfig.class);# 第二步&#xff1a; refresh();# 第三步&#xff1a; fi…

绿色先行——建行江门市分行支助力补齐“三农”金融服务短板

7月的台山&#xff0c;稻谷飘香。在大耕户李胜业的农田里&#xff0c;金灿灿的稻谷翻起层层稻浪&#xff0c;收割机在稻浪里来回穿梭&#xff0c;割稻、脱粒、装车等工序一气呵成。空气中弥漫着丰收的喜悦。 夏粮迎丰收的背后&#xff0c;是中国建设银行江门市分行&#xff08;…

【计算机网络】(谢希仁第八版)第一章课后习题答案

1.计算机网络可以向用户提供哪些服务&#xff1f; 答&#xff1a;例如音频&#xff0c;视频&#xff0c;游戏等&#xff0c;但本质是提供连通性和共享这两个功能。 连通性&#xff1a;计算机网络使上网用户之间可以交换信息&#xff0c;好像这些用户的计算机都可以彼此直接连…

【机器学习可解释性】4.SHAP 值

机器学习可解释性 1.模型洞察的价值2.特征重要性排列3.部分依赖图4.SHAP 值5.SHAP 值 高级使用 正文 理解各自特征的预测结果&#xff1f; 介绍 您已经看到(并使用)了从机器学习模型中提取一般解释技术。但是&#xff0c;如果你想要打破模型对单个预测的工作原理? SHAP 值…

第三章 SysML入门|系统建模语言SysML实用指南学习

仅供个人学习记录 UML与SysML的联系 可以稍微参考UML与SysML的联系 UML&#xff08;统一建模语言&#xff09;和SysML&#xff08;系统建模语言&#xff09;是两种与建模相关的语言&#xff0c;它们之间存在联系和区别。 SysML的图分类如下图所示。 SysML 图概述 这里只…

GZ035 5G组网与运维赛题第3套

2023年全国职业院校技能大赛 GZ035 5G组网与运维赛项&#xff08;高职组&#xff09; 赛题第3套 一、竞赛须知 1.竞赛内容分布 竞赛模块1--5G公共网络规划部署与开通&#xff08;35分&#xff09; 子任务1&#xff1a;5G公共网络部署与调试&#xff08;15分&#xff09; 子…

锐捷smartWeb管理系统存在逻辑缺陷漏洞

通过弱口令进行登录 guest/guest 通过低权限用户构造payload&#xff1a; /web/xml/webuser-auth.xml访问漏洞url&#xff0c;直接获得所有账户的等级标志和base64加密的账号密码&#xff0c;解秘后即可登录后台 解密管理员的账号密码 成功登录 文笔生疏&#xff0c;措辞浅…

C++快餐——C++11(1)

文章目录 背景简介统一列表初始化{}初始化initializer_lists初始化 关键字autodecltypenullptr 范围for右值引用和移动语义左值和右值左值引用和右值引用完美转发 默认成员函数总结 背景简介 C11&#xff0c;也被称为C0x&#xff08;在它被正式标准化之前的名字&#xff09;&a…

JVM调优(10)JVM的运行时数据区

一、概述 对于 C C 来说&#xff0c;在内存管理领域&#xff0c;JVM既拥有最高的权利&#xff0c;但是同时他们又是从事最基础工作的劳动人员&#xff0c;因为他们担负着每一个对象从开始到结束的维护责任。而对于Java来说&#xff0c;再虚拟机自动内存管理的帮助下&#xff0…

Proteus仿真--花样流水灯(仿真文件+程序)

本文主要介绍基于51单片机的花样流水灯仿真&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真运行视频 Proteus仿真--花样流水灯&#xff08;仿真文件程序&#xff09; 附完整Proteus仿真资料代码资料 链接: https://pan.baidu.com/s/1coEEBQcTQSzWQiSH_nNiUQ?pw…

vm虚拟机保护技术简介EzMachine例题-vm逆向分析

文章目录 前言0x1 虚拟机保护技术原理0x1A 关于调用约定0x1B Handler0x1C 指令 0x2 vm虚拟机逆向 实战[GKCTF 2020]EzMachine题目分析&#xff0c;花指令去除Handler分析脚本编写 前言 关于虚拟机逆向的知识网上很少&#xff0c;我看了几篇感觉都看不太明白&#xff0c;最后还…

如何设置3D模型的凹凸贴图?

1、凹凸贴图的原理&#xff1f; 凹凸贴图&#xff08;bump mapping&#xff09;是一种计算机图形技术&#xff0c;用于增强表面的视觉效果&#xff0c;使其看起来具有凹凸感&#xff0c;而实际上并没有改变模型的几何形状。 凹凸贴图的原理基于光照模型。通常&#xff0c;我们…

分布式理论和分布式锁知识点总结

文章目录 (一) 分布式理论算法和协议1&#xff09;CAP理论总结 2&#xff09;BASE理论BASE 理论的核心思想基本可用软状态最终一致性 3&#xff09;Paxos算法Basic Paxos 算法4&#xff09; Raft算法1 拜占庭将军 5&#xff09;Gossip协议 (二) 分布式锁分布式锁应该具备哪些条…

U盘RAW格式怎么恢复 U盘RAW格式怎么改过来

当我们遇到U盘变成raw格式时&#xff0c;首先需要了解的是&#xff0c;U盘的raw格式通常是由于文件系统损坏或病毒感染引起的。当U盘变成raw格式时&#xff0c;将导致无法正常访问其中数据。因此&#xff0c;需要我们手动恢复U盘中的相关数据&#xff0c;那么下面就来为大家介绍…