(未完成)【Redis专题】一线大厂Redis高并发缓存架构实战与性能优化

news2024/10/5 21:25:37

前言

在本章内容里,我希望大家还是要先看看【前置知识】的内容。按照我的大纲设计,我是想先给大家抛出一些大家比较陌生的,关于【Redis缓存问题以及缓存方案】的一些名词概念,再然后在正文【课程内容】里面给大家使用源码案例,然后优化演进的方式,逐步、尽可能地将【前置知识】中提到的这些内容给大家结合案例解释一下,帮助大家加深理解印象。
另外,说实在对于这个推演的过程理解还是有点门槛的,对于没有【并发意识】的同学来说,真的有点难度。我只能说我尽可能地,简单地给大伙讲解一下。
对了,我在推演过程中用到了【Redis分布式锁】,非常推荐大家看看我前面的文章《【Redis专题】大厂生产级Redis高并发分布式锁实战》,没有【并发意识】和【分布式锁】经验的同学墙裂建议看看,获取能得到那么一些灵感吧。

为什么要用Redis

听说,Redis、数据库、JVM级别扛并发能力如下:

层级RedisMysqlJVM
扛并发能力Redis官宣单个节点10W+主流说法数千到数万之间百万级

解释一下:

  1. 这里说的扛并发能力是指,每秒同时做【读】操作的次数
  2. JVM级别其实是指内存级别,比如比较有名的:ecache

所以,你知道为什么要学,或者要用Redis了吗?

前置知识

在说缓存问题跟缓存方案之前,需要跟大家声明一句:没有绝对完美的方案,只有相对可靠的方案。别钻牛角尖哦同学们

一、缓存问题

1.1 缓存击穿

Q1:什么是缓存击穿?
答:也叫缓存失效。击穿,是指击穿了缓存,使得请求直达数据库,进而造成了数据库瞬间压力过大甚至挂掉。
记忆点:缓存中没有但是数据库有的数据

Q2:导致缓存击穿可能的原因是什么?
答:大量已存在的key同时失效。比如在电商场景下, 以前存在的做法是:批量上架一些热点商品到缓存中,由于批量操作,所以过期时间设置的一样。所以,在某些情况下造成:热点数据同时到达过期时间,接着大量用户进来。

Q3:缓存击穿如何解决?
答:批量添加缓存时,分散缓存过期时间(设置为一个时间段内的不同时间),避免相同时间段大量缓存失效。
给一个伪代码示例:

String get(String key) {

    // 从缓存中获取数据
    String cacheValue = cache.get(key);
    
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
    
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        
        //***设置一个过期时间(300到600之间的一个随机数)***
        int expireTime = new Random().nextInt(300)  + 300;
        if (storageValue == null) {
            cache.expire(key, expireTime);
        }
        return storageValue;
    } else {
    
        // 缓存非空
        return cacheValue;
    }
}

注意上面的过期时间。

1.2 缓存穿透

Q1:什么是缓存穿透?
答:缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上造成数据库短时间内承受大量请求而崩掉。
记忆点:缓存和数据库中都没有的数据

Q2:导致缓存穿透可能的原因是什么?
答:通常处于容错的考虑,存储层找不到数据不会写入都缓存层。正常情况是没问题的,可能导致缓存穿透的的情况如下:

  1. 自身业务代码或数据出现问题。这个比较难解释,但是楼主曾经在生产遇到过我一个同事,在一个定时器写了一个while操作,反复获取一个不存在的数据(实际上是新增功能,上线时忘了准备一些初始化数据),导致死循环CPU超负荷运行,直接把系统搞炸了。(属于是自己玩自己了)
  2. 一些恶意攻击、爬虫等造成大量空命中。通常是遭受外部攻击

Q3:缓存穿透如何解决?
答:有如下几点:

  1. 接口层增加校验,如用户鉴权校验,id做基础校验,id<=0的直接拦截
  2. 从缓存取不到的数据,在数据库中也没有取到,这时也可以将keyvalue对写为key-空对象(不是null),缓存有效时间可以设置短点,如30秒(设置太长会导致正常情况也没法使用)。这样可以防止攻击用户反复用同一个id暴力攻击。伪代码如下:
// 从缓存中获取数据
String cacheValue = cache.get(key);
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
        
        // 如果存储数据为空, 需要设置一个过期时间(300秒)
        if (storageValue == null) {
        
			// 缓存空对象
         	cache.set(key, new TargetObject());
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    } else {
        // 缓存非空
        return cacheValue;
    }
} 

(注意:不同于网上其他版本,这里的建议是缓存一个空对象。为啥?说不出很好的理由。只能说,这算是一种比较好的规范。对于某些变量,给个有意义的初始值远比Null安全的多)

  1. 采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

对于恶意攻击,向服务器请求大量不存在的数据造成的缓存穿透,还可以用布隆过滤器先做一次过滤,对于不存在的数据布隆过滤器一般都能够过滤掉,不让请求再往后端发送。当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。
在这里插入图片描述
布隆过滤器就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀.
向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都为 1,只要有一个位为 0,那么说明布隆过滤器中这个key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组长度比较大,存在概率就会很大,如果这个位数组长度比较小,存在概率就会降低。(PS:总结一下:布隆过滤器认为这个key不存在那就一定不存在;布隆过滤器认为key存在,却不一定真的存在,只是极有可能存在。记住这一点,你基本上算是理解了布隆过滤器!!!
这种方法适用于数据命中不高、 数据相对固定、 实时性低(通常是数据集较大) 的应用场景, 代码维护较为复杂, 但是缓存空间占用很少。

(PS:重要的知识点说三遍)
(PS:重要的知识点说三遍)
(PS:重要的知识点说三遍)
布隆过滤器认为这个key不存在那就一定不存在;布隆过滤器认为key存在,却不一定真的存在,只是极有可能存在
在项目中使用Redisson实现布隆过滤器,引入依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.6.5</version>
</dependency>

测试代码:

package com.redisson;

import org.redisson.Redisson;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonBloomFilter {

    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        //构造Redisson
        RedissonClient redisson = Redisson.create(config);

        RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
        //初始化布隆过滤器:预计元素为100000000L,误差率为3%,根据这两个参数会计算出底层的bit数组大小
        bloomFilter.tryInit(100000000L,0.03);
        //将test插入到布隆过滤器中
        bloomFilter.add("test");

        //判断下面号码是否在布隆过滤器中
        System.out.println(bloomFilter.contains("test_1"));//false
        System.out.println(bloomFilter.contains("test_2"));//false
        System.out.println(bloomFilter.contains("test"));//true
    }
}

但是正常来说,使用布隆过滤器需要把所有数据提前放入布隆过滤器,并且在增加数据时也要往布隆过滤器里放,这样才能起到过滤作用。布隆过滤器缓存过滤伪代码:(注意:布隆过滤器不能删除数据,如果要删除得重新初始化数据

// 第一步:初始化布隆过滤器
// 第一步:初始化布隆过滤器
// 第一步:初始化布隆过滤器
RBloomFilter<String> bloomFilter = redisson.getBloomFilter("nameList");
bloomFilter.tryInit(100000000L,0.03); //初始化布隆过滤器:预计元素为100000000L,误差率为3%

// 第二步:预先把所有数据存入布隆过滤器        
// 第二步:预先把所有数据存入布隆过滤器        
// 第二步:预先把所有数据存入布隆过滤器        
void init(){
    for (String key: keys) {
        bloomFilter.put(key);
    }
}

// 第三步:真正的使用场景了。Redis查询数据
// 第三步:真正的使用场景了。Redis查询数据
// 第三步:真正的使用场景了。Redis查询数据
String get(String key) {

    // 先从布隆过滤器这一级缓存判断下key是否存在,防止缓存穿透
    Boolean exist = bloomFilter.contains(key);
    if(!exist){
        return "";
    }
    // 从缓存中获取数据
    String cacheValue = cache.get(key);
    
    // 缓存为空
    if (StringUtils.isBlank(cacheValue)) {
    
        // 从存储中获取
        String storageValue = storage.get(key);
        cache.set(key, storageValue);
       
        // 如果存储数据为空, 需要设置一个过期时间(300秒)
        if (storageValue == null) {
        
        	// 缓存空对象
         	cache.set(key, new TargetObject());
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    } else {
        // 缓存非空
        return cacheValue;
    }
}

1.3 缓存雪崩

Q1:什么是缓存雪崩?
答:缓存雪崩指的是缓存层支撑不住或宕掉后,流量会像奔逃的野牛一样,打向后端存储层,然后一层一层的滚雪球导致各个层宕机。由于缓存层承载着大量请求,有效地保护了存储层,但是如果缓存层由于某些原因不能提供服务(比如超大并发过来,缓存层支撑不住,或者由于缓存设计不好,类似大量请求访问bigkey,导致缓存能支撑的并发急剧下降),于是大量请求都会打到存储层,存储层的调用量会暴增,造成存储层也会级联宕机的情况。
记忆点:缓存层撑不住,大量流量打向服务层,导致整体拒绝服务,或者级联宕机

Q2:导致缓存雪崩可能的原因是什么?
答:缓存层没有起到缓存作用,或者缓存作用没有达到预期。比如:

  1. 大量大key操作,长时间阻塞了Redis请求

Q3:缓存雪崩如何解决?
答:有如下几种方式:

  1. 保证缓存服务的高可用。比如搭建redis集群,使用Redis Sentinel或Redis Cluster
  2. 使用隔离组件为后端限流熔断并降级。比如使用Sentinel或Hystrix组件,区分非核心数据和核心数据的处理方式
    • 比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取
  3. 提前演练。在项目上线前, 演练缓存层宕掉后, 应用以及后端的负载情况以及可能出现的问题, 在此基础上做一些预案设定

二、缓存方案

2.1 普通缓存

基本介绍
查询数据时先查找缓存,如果有,则延长缓存时间并返回;如果没有,再去查找数据库,将查询的数据再写到缓存,同时设置过期时间。如果是静态热点数据,可以不设置缓存失效时间。

对应的伪代码:

public Target getTartget(String id) {
	String key = getRedisKey(id);
	String json = redisUtil.get(key);
	if (StrUtil.isNotEmpty(json)) {
		
		// 延长缓存时间
		redisUtil.expire(key, 新的缓存时间);
		return JSONObject.parse(json, Target.class);
	}

	Target target = dbUtil.get(id);
	if (target != null) {
		
		// 这里根据是否为热点数据,可以考虑是否要设置过期时间
		redisUtil.set(key, JSONObject.toJSONString(target), 缓存时间);
	}

	return target;
}

2.2 冷热分离

基本介绍
在服务降级时,根据冷热数据做不同的处理。这个方案其实我们在上面【缓存雪崩】讲过大致的过程了。但是对于冷热数据需要大家自己根据业务去区分。这里给一点思路:

  • 所谓冷数据:我们不常点击,不常使用的数据。例如美团APP,用户个人信息,就是冷数据;
  • 所谓热数据:我们常点击,常使用的数据。还是美团APP。首页推广信息肯定是热数据,无论谁点开美团APP首页,显示的数据就是那些。如果这些数据每次都从数据库拿,那得多慢…

比如服务降级,我们可以针对不同的数据采取不同的处理方式。当业务应用访问的是非核心数据(例如电商商品属性,用户信息等)时,暂时停止从缓存中查询这些数据,而是直接返回预定义的默认降级信息、空值或是错误提示信息;当业务应用访问的是核心数据(例如电商商品库存)时,仍然允许查询缓存,如果缓存缺失,也可以继续通过数据库读取

2.3 多级缓存

基本介绍
比如在redis前再加一级缓存JVM,一般是通过map存储数据。可以类似redis方案更新缓存,也可以使用redis的发布订阅功能、MQ、canal来实现与数据库的同步。也可以单独部署热点缓存系统,监测到热点数据主动同步到分布式系统中。
值得注意的是,因为多引入了一级缓存,那在分布式的情况下,势必需要通过通信的方式更新新的一级缓存(就像Redis集群使用gossip协议更新节点一样)。通信就势必存在时间消耗,进而导致:短期的数据不一致性。所以建议大家使用多级缓存时,就不要就觉绝对一致了,否则会增加更大的维护成本。

2.4 缓存预热

基本介绍
热点重建缓存时,通过双检锁重建缓存:先查询,不存在需要重建缓存,重建缓存逻辑加入分布式锁,仅有一个请求能重建缓存,重建完成后,后面的请求都能获取到数据了。

对应的伪代码:

public Target getTartget(String id) {
	String key = getRedisKey(id);
	String json = redisUtil.get(key);
	if (StrUtil.isNotEmpty(json)) {
		
		// 延长缓存时间
		redisUtil.expire(key, 新的缓存时间);
		return JSONObject.parse(json, Target.class);
	}

	// 获取分布式锁
	RLock rLock = redisson.getLock(lockKey);
	rLock.lock();
	try {
	
		// 由于我们在外层已经设置了分布式锁,所以我们知道的
		// 只要有一个去查库,然后设置到缓存,其他的没必要再去查库了
		// 通过双重检查,我们把原本n个查库的请求,转变成了1个查库,n-1查缓存
		// 再次检查缓存
		if (StrUtil.isNotEmpty(json)) {
		
			// 延长缓存时间
			redisUtil.expire(key, 新的缓存时间);
			return JSONObject.parse(json, Target.class);
		}
		
		Target target = dbUtil.get(id);
		if (target != null) {
			
			// 这里根据是否为热点数据,可以考虑是否要设置过期时间
			redisUtil.set(key, JSONObject.toJSONString(target), 缓存时间);
		}
	} finaly {
		rLock.unlock();
	}
	
	return target;
}

通过上面的例子我们可以看到,通过【缓存预热】,或者说【双检索】思想,我们把原本n个查库的请求,转变成了1个查库,n-1个查缓存

*课程内容

同学们有没有一种感觉,目前我们Java技术栈,出现的各种中间件也好,框架也好,似乎都是为了【电商场景】服务的。太难了…

一、一个案例引发的思考(电商场景)

public class ProductService {
    @Autowired
    private ProductDao productDao;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private Redisson redisson;

	public static final Integer PRODUCT_CACHE_TIMEOUT = 60 * 60 * 24;

	@Transactional
    public Product create(Product product) {
        Product productResult = productDao.create(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult), PRODUCT_CACHE_TIMEOUT , TimeUnit.SECONDS);
        return productResult;
    }

	@Transactional
    public Product update(Product product) {
		Product productResult = productDao.update(product);
        redisUtil.set(RedisKeyPrefixConst.PRODUCT_CACHE + productResult.getId(), JSON.toJSONString(productResult), PRODUCT_CACHE_TIMEOUT , TimeUnit.SECONDS);
	}

	public Product get(Long productId) throws InterruptedException {
		Product product = null;
        String productCacheKey = RedisKeyPrefixConst.PRODUCT_CACHE + productId;
        
        // 先从缓存拿数据,有就直接返回
        product = getProductFromCache(productCacheKey);
        if (product != null) {
            return product;
        }

		product = productDao.get(productId);
        if (product != null) {
             redisUtil.set(productCacheKey, JSON.toJSONString(product), PRODUCT_CACHE_TIMEOUT, TimeUnit.SECONDS);
        }
        return product;
	}
}

上面的例子很简单,就是CRUD而已。我想,大家对于Redis缓存,很多人的使用都止于上述几个getcreateupdate方法的使用了吧?反正博主我就是,啊哈哈
在这里插入图片描述
如果你也是这么写的话,我想问问大伙儿,你们有没有思考过这里面会存在什么问题没有?可以好好拿笔写一下。尽可能地写出来,也对自己是一种锻炼了(大家也是看过我【前置知识】的各种缓存问题跟方案介绍了,多少能写一点吧)。我个人能看到的问题有如下:(百万流量下)

  1. create方法的缓存时间是固定的,在批量插入场景下,可能会出现批量缓存同时过期,造成【缓存击穿(失效)】现象
  2. get方法在成功拿到缓存数据返回之后,没有延长缓存时间。有可能出现【热点数据】,或者最近使用过的数据缓存过期,造成【缓存击穿(失效)】现象
  3. get方法在没有拿到缓存,去查询数据库的时候,可能存在大量请求同时请求数据库,数据库压力暴增
  4. get方法对于Redis跟数据库都不存在的数据没有防备,可能会造成【缓存穿透】现象
  5. … … (目前我能想到的就这些了)

好了,问题已经发现了,那让我们一起学习一下,在大厂里面,是如何写这些代码的!
首先,还是希望大家时刻对背景有个简单的印象:

  1. 分布式,多个tomcat
  2. 多线程,高并发,动不动就百万流量,吹牛逼都很爽

在这里插入图片描述
然后再附上一张,JD的主页:
在这里插入图片描述
你们看上面吧,我们前面有简单分析过,像这种电商首页的数据,正常来说一定会缓存到Redis里面的,而且首页数据是超热数据,所以添加缓存的时候甚至不会设置过期时间。

二、优化方案的演进

上面那个版本

2.1 冷热分离方案推演

我们前面有大致介绍过【冷热分离】,这里重点在【冷】、【热】数据的区分。

学习总结

感谢

感谢我东哥的文章《一线大厂Redis高并发缓存架构实战与性能优化》

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

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

相关文章

SQL9 查找除复旦大学的用户信息

描述 题目&#xff1a;现在运营想要查看除复旦大学以外的所有用户明细&#xff0c;请你取出相应数据 示例&#xff1a;user_profile iddevice_idgenderageuniversityprovince12138male21北京大学Beijing23214male复旦大学Shanghai36543female20北京大学Beijing42315female23浙…

postman连接websocket, 建立连接、聊天测试(v8.5.1)

1. postman v8.5版本 以上支持 websocket。 2. 选择websocket请求模块File - New... 3. WebSocketServer.java import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.Server…

ChatGPT提示词(prompt)资源汇总

文章目录 awesome-chatgpt-promptsLearn PromptingSnack PromptFlow GPTPrompt VineChatGPT 指令大全AI Toolbox HubAI Short ChatGPT是一种强大的生成式AI模型&#xff0c;而提示词&#xff08;prompt&#xff09;则是与ChatGPT一起使用的指导性文本&#xff0c;用于引导模型生…

Redis的用法及面试题(删除策略、企业级解决方案)

目录 一、Redis删除策略 &#xff08;1&#xff09;过期数据 &#xff08;2&#xff09;数据删除策略 1.定时删除 2.惰性删除 &#xff08;3&#xff09;逐出算法 二、企业级解决方案 &#xff08;1&#xff09;缓存预热 &#xff08;2&#xff09;缓存雪崩 &…

【Spring面试】八、事务相关

文章目录 Q1、事务的四大特性是什么&#xff1f;Q2、Spring支持的事务管理类型有哪些&#xff1f;Spring事务实现方式有哪些&#xff1f;Q3、说一下Spring的事务传播行为Q4、说一下Spring的事务隔离Q5、Spring事务的实现原理Q6、Spring事务传播行为的实现原理是什么&#xff1f…

神经网络 07(正则化)

一、正则化 在设计机器学习算法时不仅要求在训练集上误差小&#xff0c;而且希望在新样本上的泛化能力强。许多机器学习算法都采用相关的策略来减小测试误差&#xff0c;这些策略被统称为正则化。因为神经网络的强大的表示能力经常遇到过拟合&#xff0c;所以需要使用不同形式的…

第4章_freeRTOS入门与工程实践之开发板使用

本教程基于韦东山百问网出的 DShanMCU-F103开发板 进行编写&#xff0c;需要的同学可以在这里获取&#xff1a; https://item.taobao.com/item.htm?id724601559592 配套资料获取&#xff1a;https://rtos.100ask.net/zh/freeRTOS/DShanMCU-F103 freeRTOS系列教程之freeRTOS入…

Charles的Map Remote功能

1、charles的Map Remote功能&#xff08;指定的网络请求重定向到另一个网址&#xff09;&#xff0c;说白了就是你本来要请求A接口拿数据&#xff0c;重定向后&#xff0c;你实际请求的是B接口&#xff0c;拿到的是B接口返回的数据。 入口Tools->Map Remote 本次测试过程中…

【教程】IDEA操作GIT

不小心推送代码之后 进行回退 1 找到需要回退的记录 比如要回退13分钟之前提交的代码 选中 右键还原提交 最后再重新推送被还原的提交 就可以了

78基于matlab的BiLSTM分类算法,输出迭代曲线,测试集和训练集分类结果和混淆矩阵,程序有详细注释,数据可更换自己的,程序已调通,可直接运行。

基于matlab的BiLSTM分类算法&#xff0c;输出迭代曲线&#xff0c;测试集和训练集分类结果和混淆矩阵&#xff0c;程序有详细注释&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 78 matlabBiLSTM模式识别混淆矩阵 (xiaohongshu.com)https://www.…

PAT(Advanced Level) Practice(with python)——1118 Birds in Forest

code N int(input())birds [[] for i in range(10001)] vis [0 for i in range(10001)] max_bird 0 tree_id 0# 染色问题&#xff0c;并查集&#xff0c;如果没染色&#xff0c;深度搜索并染色 def dfs(cur):vis[cur] tree_idfor b in birds[cur]:if vis[b]0:dfs(b)for …

kubernetes进阶 (三) 基础练习

前两天朋友给了我几道题&#xff0c;看着挺简单的&#xff0c;但实际做的时候发现坑不少&#xff0c;这里做下笔记 一、镜像构建部署lnmp 1、构建镜像 nginx、php、mysql 要求使用centos7作为基础镜像 2、使用deployment部署上面的容器,要求3个服务要放到一个pod中(虽然这样是…

基于STM32CUBEMX驱动TOF模块VL53l0x(2)----修改设备地址

基于STM32CUBEMX驱动TOF模块VL53l0x----2.修改设备地址 概述视频教学样品申请修改设备地址主程序测试结果 概述 本章主要介绍如何修改VL53L0X传感器的I2C地址&#xff0c;并成功驱动设备以使用新的地址。VL53L0X是一种多功能、高性能的接近和环境光传感器&#xff0c;常用于测…

UMA 2 - 创建自己的UMA模型⭐二.给模型绑定骨骼 , 并建立符合UMA的骨骼结构

文章目录 🟥 寻找或者制作合适果模🟧 使用ActorCore AccuRig🟩 替换骨骼名称🟦 增加Global骨骼🟪 增加Position骨骼🟫 设置骨骼结构⬛ 删除无用骨骼⬜最终骨骼效果🟥 寻找或者制作合适果模 找到一个果模,需要导出fbx格式. 🟧 使用ActorCore AccuRig 将fbx导…

JVM 虚拟机 ----> Java 内存模型(JMM)

文章目录 Java 内存模型&#xff08;JMM&#xff09;一、运行时数据区域划分二、程序计数器&#xff08;Program Counter Register&#xff09;计数器的作用 三、Java 虚拟机栈&#xff08;VM Stack&#xff09;四、本地方法栈&#xff08;Native Method Stack&#xff09;五、…

fcpx视频编辑处理 Final Cut Pro for Mac

Final Cut Pro是一款专业的视频剪辑软件&#xff0c;适用于Mac操作系统。Final Cut Pro X版本在视频剪辑方面进行了大规模的更新和改进&#xff0c;下面将介绍Final Cut Pro X中的一些主要功能和特性&#xff1a; Magnetic Timeline。这个新功能使得多条剪辑片段如同磁铁般吸合…

Databend 开源周报第 110 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 使用 BendSQL 管…

班主任须知,查询发布技巧

Hey&#xff0c;亲爱的班主任们&#xff01;今天小红书博主要跟你们分享一个超级实用的技巧&#xff0c;让你们的查询发布变得轻松又高效&#xff01;它能帮你们把查询结果变成在线查询&#xff0c;让家长们随时随地都能轻松查询各种信息&#xff0c;简直就是教师“神器”。 通…

Fiddler抓包工具(详细讲解)

序章 Fiddler是一个蛮好用的抓包工具&#xff0c;可以将网络传输发送与接受的数据包进行截获、重发、编辑、转存等操作。也可以用来检测网络安全。反正好处多多&#xff0c;举之不尽呀&#xff01;当年学习的时候也蛮费劲&#xff0c;一些蛮实用隐藏的小功能用了之后就忘记了&…

SQL11 高级操作符练习(1)

描述 题目&#xff1a;现在运营想要找到男性且GPA在3.5以上(不包括3.5)的用户进行调研&#xff0c;请你取出相关数据。 示例&#xff1a;user_profile iddevice_idgenderageuniversitygpa12138male21北京大学3.423214male复旦大学4.036543female20北京大学3.242315female23浙…