大型分布式系统下缓存应该怎么玩,才能用得高效

news2024/11/19 6:22:13

大家好,今天我们来聊一聊在大型分布式系统中,缓存应该怎么玩,从毕业到现在也有三年多了,大大小小的系统也经历了几十个,今天就从各个角度来讨论一下,我们的不同的缓存应该怎么玩,才能用的高效。

我们团队现在做的是直播类的产品,就拿抖音来说,比如说要开发一个榜一大哥、榜二大哥等各类大哥的排行榜单,要怎么开发,对于抖音这个dau十亿级别的产品,缓存的设计肯定是家常便饭。

对于一个百万、千万级别的接口调用,若是没有缓存的设计,直接打到数据持久层,那将是毁灭性的灾难。之前我就经历过,一个接口一天几百万次的调用,因为缓存的设计不严谨,缓存失效后,瞬间直接打在数据库层,幸好有告警,及时修复,差点就领了p0的故障。

大体来说缓存分为客户端缓存和服务端缓存,客户端缓存我们比较常见的就是浏览器缓存,也就是通过http进行控制的缓存。

客户端缓存

基于请求-应答模式下,在大多数场景下客户端都是通过https协议,请求后台获取数据,若是高频的接口一天几百万次的调用,即使短时间的客户端缓存也会带来高效的收益。

因为客户端到服务端要经过漫长的网络链路,多变的网络环境,数据包可能小的几十K到大的数据包几十M,这样就能够省去复杂多变的网络请求的时间。

客户端缓存减少了客户端到服务端之间的通信次数以及成本,只要缓存可用,就能够及时响应数据。

客户端缓存常见的也就是浏览器缓存,简而言之也就是http缓存,不知道大家在实际开发过程中有没有用过这段代码:

ResponseEntity.ok().cacheControl(CacheControl.maxAge(3, TimeUnit.SECONDS)).body()

看他的包,他就是属于springframework框架下http包下的一个工具类。

import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;

在浏览器缓存中,http协议header有这么个key-value字段进行控制,叫做Cache-Control:max-age=30,max-age标志该资源在客户端缓存多少秒。

假如max-age=0,表示不缓存数据,除了max-age可以控制数据的缓存状态,还有以下三个属性来控制缓存状态no_store、no_cache、must-revalidate

  1. no_store表示不缓存数据,每次都去服务器获取
  2. no_cache看起来也是不缓存的意思,但是它表示的意思是可以缓存的,只不过在使用缓存之前,都要去服务器验证数据是否有效,是否过期,是否是最新版本
  3. must-revalidate和no_cache有点类似,就是缓存不过期的话可以继续使用,过期了就需要去服务器验证一下

除了Cache-Control可以使用客户端缓存,在http里面还有一个条件请求的header更加智能的使用客户端缓存。

条件请求是基于响应报文返回的“Last-modified”和“ETag”实现的。Last-modified资源最后的一次修改时间,ETag则表示资源的唯一标识,你可以理解为只要资源修改后都不一样了

再次请求的时候在请求头里面就会带上"If-None-Match:ETag返回的值",去验证资源是否有效。

假如有效的话,就会返回"304 Not Modified",表示缓存资源有效还可以继续使用。

​但是这种方式我较少使用,基本上使用Cache-Control就够了,控制好实效的时间,一般的场景都是允许短暂的不一致。

除了客户端能够发送Cache-Control之外,客户端也能够发送Cache-Control两者进行协商使用客户端缓存的方案。

像我在浏览器访问一个连接,在输入框敲一下回车,Request的Headers里面就有Cache-Control:max-age = 0,表示不使用缓存,直接去后台获取数据。

​所以Cache-Control来控制客户端缓存也不太好控制,要两者协商好,但是Cache-Control有一个好处就是可以控制CDN缓存。

服务端缓存CDN缓存

上面聊到Cache-Control来控制客户端缓存,它也同时影响CDN缓存,告诉CDN客户缓存这个接口的数据。

CDN服务一般是由第三方提供的内容分发网络服务,主要是用于缓存静态的数据,比如:图片、音频、视频,这些数据,都是不不变的,那么命中率就很高。

不用回源获取数据,效率高,毕竟使用CDN的费用高,一般小公司也不会用,可能大公司采用。

CDN厂商花费大价钱在全国各地建立CDN的服务站点,用于用户的就近访问,减少响应时间。

所以这个对于应用层的来说是0开发的,一般只要在你的服务治理平台针对某一个接口配置一下就好了。

除了缓存静态数据,想一些动态数据,但是不会经常变的数据CDN也是可以缓存的,只不过可能缓存的时间设置的比较短,那么在高并发场井下取得的效益也是比较大的。

好了,关于CDN的也没啥好说的,我还没接触过CDN开发,但是项目中使用到了,就是简单而配置一下而已,等我接触到开发,再和你们详聊,CDN其实就是代理源站服务器缓存数据而已。

Redis缓存

在服务端缓存中Redis缓存可能是我们最常见的缓存,可以说Redis已经是各大公司常用的缓存中间件选型之首,也不为过。

我们项目中也是在使用Redis,只不过在Redis的层面上进行封装,包括Redis哨兵、Redis分片,都是基于自己的业务情况下进行二次开发,然后供自己的业务使用。

在Redis的基础数据类型中,有五大类型供大家选择,包括String、List、Set、SortedSet、Hash。这五种数据类型在百分之九十五的场景下都能够解决,并且在这五种基本数据类型的底层运用了高效与省空间的数据结构,所以Redis的高性能之一也是因为有这些数据结构作为支撑。

图片来源于Redis核心技术与实战

比如:要实现一个排行榜单,凸显直播间榜一大哥以及上榜大哥财大气粗的实力。

那么这个明显是按照某个字段进行排序,比如刷的抖音币进行排序,那个在redis中List和Sorted Set都是可以实现有序的缓存。

List是按照写入List顺序进行存储,而Sorted Set是按照某一个字段的权重来排序,并且可以查询权重范围内的数据。

对于我们的场景List可能不太适合,因为是对数据每次都是新产生的,并且按照时间来顺序来写入,List集合就比较适合。

我们的场景是某个在榜大哥不断的刷礼物,就需要重新对他进行排序,并不是按照每次新增写入缓存的顺序取数,那么按照大哥刷的抖音币的多少就可以当做权重来排序,很好的服务我们的场景,按照时间来排序的场景Sorted Set都可以来做。

还有一些聚合统计的场景,比如要统计两个key数据集的交集、并集、差集可以使用set集合来做。

假如某一天你的老板让你开发一个统计每天新增的用户数据功能,其实那么也就两个集合差集,一个set集合用户保存所有用户的id,一个set用户保存当天用户的id集合,然后当天用户集合与所有用户集合的差集就是新增的用户集合。可以使用set集合中的SDIFFSTORE命令进行实现。

还有一些二值统计的场景,也就是基于redis的Bitmap来统计,他并不记录数据的本身,只能判断是否存在,有没有,Bitmap保存的是bit 位,所以亿级别数量的存储只要M级别的存储单位就可以了,所以Bitmap非常的节省空间。

Bitmap中提供SETBIT设置bit位,以及GETBIT获取某个bit位的值,还可以使用BITCOUNT统计bit位位1的值,比如可以统计某个月签到场景。

Redis高性能的缓存给我们系统带来了极大的性能提升,但是同时也会有一些类的问题,比如数据一致性的问题、缓存的三大问题(击穿、穿透、雪崩)、与Redis网络通信接口超时、Redis里面缓存的数据变多,操作时间复杂度大的导致Redis变慢。

数据的一致性

数据一致性问题指的是缓存与数据库的一致性问题,只要使用缓存就会有一致性问题,现在市场上都不会要求强一致性,都是追求最终一致性。

缓存按照是否可写分为读写缓存与只读缓存,大部分是只读缓存,现在我们来讨论一下只读缓存一致性问题。

只读缓存的一致性问题包括以下以下两种场景:

  1. 先更新数据库,然后删除缓存。

  2. 先删除缓存,然后更新数据库。

但是这两种场景在高并发场景下都会有问题,先来看看第一种场景:先更新数据库,然后删除缓存。

这种场景也会有一致性问题,当我们更新了数据库后,然后删除缓存,删除缓存失败了,此时请求读取的缓存还是旧的值。

这种情况下的解决方案就是重试,可以在应用层重试,也可以放入消息队列里面重试,当重试次数达到最大的限制,就需要发送告警进行人工排查了。

或者设置比较短的缓存失效时间,短暂的不一致性,也是可以接受的。

第二种场景:先删除缓存,然后再更新数据库。在高并发场景下也有可能数据不一致。

假如线程A删除了缓存,但是还没有更新数据库,然后线程B读取缓存发现缓存缺失,然后从数据库里面读取旧值,并且缓存到Redis中,后面的请求就会从Redis中读取旧值。

​这种场景市面上推荐使用延迟双删的方案进行解决,就是在请求A删除缓存后,更新数据库,然后等一段时间删除缓存,请求A的sleep的时间大于请求B的读取数据写入缓存的时间。

但是这种一般等的时间不调好估计,而且在高并发场景下,让线程去等无疑是降低性能,这个通常是不允许的。

所以一般建议采用第一种方案,先更新数据库,然后删除缓存的方式。

我们项目中也会用到读写缓存,之前遇到一个需求就是,在直播过程中,主播的公屏的流水,要显示用户中奖的横幅,也就是“恭喜某某在某某直播间抽中了XXX礼物”。

礼物的抽奖流水之前就已经发送消息队列了,所以只要监听对应抽奖流水topic就行了,然后将中奖的流水按照排序规则放入Redis中。然后客户端从Redis中读取,其实很简单,流水也不需要存库,只要展示就行了。

​所以Redis的应用场景还是很多的,几乎可以覆盖开发中的95%以上的需求

缓存击穿、穿透、雪崩

使用分布式缓存还会涉及到缓存的三大问题,也就是缓存击穿、缓存穿透、缓存雪崩。

缓存穿透的解决方案有两种:

  1. 缓存空对象:代码维护较简单,但是效果不好。

  2. 布隆过滤器:代码维护复杂,效果很好。

缓存空对象是指当一个请求过来缓存中和数据库中都不存在该请求的数据,第一次请求就会跳过缓存进行数据库的访问,并且访问数据库后返回为空,此时也将该空对象进行缓存。

若是再次进行访问该空对象的时候,就会直接击中缓存,而不是再次数据库,缓存空对象实现的原理图如下:

​缓存空对象的实现代码如下:

public class UserServiceImpl {
     @Autowired
     UserDAO userDAO;
     @Autowired
     RedisCache redisCache;

     public User findUser(Integer id) {
          Object object = redisCache.get(Integer.toString(id));
          // 缓存中存在,直接返回
          if(object != null) {
               // 检验该对象是否为缓存空对象,是则直接返回null
               if(object instanceof NullValueResultDO) {
                    return null;
               }
               return (User)object;
          } else {  
               // 缓存中不存在,查询数据库
               User user = userDAO.getUser(id);
               // 存入缓存
               if(user != null) {
                    redisCache.put(Integer.toString(id), user);
               } else {
                    // 将空对象存进缓存
                    redisCache.put(Integer.toString(id), new NullValueResultDO());
               }
               return user;
          }
     }          
}

布隆过滤器是一种基于概率的数据结构,主要用来判断某个元素是否在集合内,它具有运行速度快(时间效率),占用内存小的优点(空间效率),但是有一定的误识别率和删除困难的问题。它只能告诉你某个元素一定不在集合内或可能在集合内。

在计算机科学中有一种思想:空间换时间,时间换空间。一般两者是不可兼得,而布隆过滤器运行效率和空间大小都兼得,它是怎么做到的呢?

在布隆过滤器中引用了一个误判率的概念,即它可能会把不属于这个集合的元素认为可能属于这个集合,但是不会把属于这个集合的认为不属于这个集合,布隆过滤器的特点如下:

  1. 一个非常大的二进制位数组 (数组里只有0和1)
  2. 若干个哈希函数
  3. 空间效率和查询效率高
  4. 不存在漏报(False Negative):某个元素在某个集合中,肯定能报出来。
  5. 可能存在误报(False Positive):某个元素不在某个集合中,可能也被爆出来。
  6. 不提供删除方法,代码维护困难。
  7. 位数组初始化都为0,它不存元素的具体值,当元素经过哈希函数哈希后的值(也就是数组下标)对应的数组位置值改为1。

实际布隆过滤器存储数据和查询数据的原理图如下:

​缓存击穿是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,瞬间对数据库的访问压力增大。

缓存击穿这里强调的是并发,造成缓存击穿的原因有以下两个:

  1. 该数据没有人查询过 ,第一次就大并发的访问。(冷门数据)

  2. 添加到了缓存,reids有设置数据失效的时间 ,这条数据刚好失效,大并发访问(热点数据)

对于缓存击穿的解决方案:

  1. 加锁,用户出现大并发访问的时候,在查询缓存的时候和查询数据库的过程加锁,只能第一个进来的请求进行执行,当第一个请求把该数据放进缓存中,接下来的访问就会直接集中缓存,防止了缓存击穿。

  2. 不设置热点key的失效时间

缓存雪崩 是指在某一个时间段,缓存集中过期失效。此刻无数的请求直接绕开缓存,直接请求数据库。

造成缓存雪崩的可能原因有:

  1. reids宕机

  2. 大部分数据失效

对于缓存雪崩的解决方案有以下两种:

  1. 搭建高可用的集群,防止单机的redis宕机。

  2. 设置不同的过期时间,防止同一时间内大量的key失效。

接口超时&操作时间复杂度高

Redis数据第三方缓存中间件,要与Redis通信,必须经过网络,那么经过网络就有可能出现网络超时的现象。

之前我们也出现过,某个机房因为网络波动,出现了一系列的Redis查询网络超时的告警。

所以为了解决一时的网络超时,我们有可能还要做好接口重试的机制,提高接口的可用性。

并且对Redis五种基本数据类型的底层数据结构熟悉的,Redis中对集合类型的操作HGETALL、SMEMBERS,以及对集合进行聚合统计等,时间复杂度都是O(N)

那么Redis中存储的数据越多,这个N就越大,操作的复杂度就越高,这就是所谓的bidkey现象,已经出现查询阻塞了。

当然出现这种问题时,可以将bigkey按照一定规律进行拆分,这样分成多个key进行存储,查询的效率就会变高。

当然Redis的数据分片解决方案也可以,将原来一个实例中存储全量数据,按照16384进行crc16(key) % 16384决定数据存储于哪个槽中。

这样扩展性也比较好,不过一般优先推荐拆分key的方案,这样实现成本低,实现简单。

缓存消息队列玩法

有一些场景还可以使用消息队列进行更新缓存,用户更新数据,异步的发送消息队列,消费者就可以监听消息队列的消息,消费消息后更新缓存。

因为有些数据的更新是需要发送消息队列的,被其他消费者监听使用,所以你只要监听消息队列就行了。

并且消息的队列的消息由消息队列的方式来保证,包括生产者可靠的发送消息队列,通过ack以及重试保证,消息队列本身通过持久化机制来保证,而消费者也是通过消费后手动ack来确认消息消费。

消息对垒更新缓存

定时任务

定时任务其实就是本地缓存了,在分布式系统中,定时任务就是每个服务中都会缓存一份,这样数据不一致性也会加大。

但是在某些场景下,他带来的收益也是非常可观的,比如说某个场景下你要查询一些安全中台的白名单/黑名单列表,而且这些列表不会经常变,可能需求上线后只要配置一下就ok了,后面的更改频率也是非常的低。

但是你的接口可能是高流量接口,每次用户进来都会请求一次,进行判断,而且用户是千万级别的,那有可能一天的请求就是上百万次的请求。

那你有两种选择来请求安全中台的白名单/黑名单列表,要么就是实时请求,要么就是定时任务请求本地缓存一份,然后查询只要从本地获取就行了。

在这种情况下肯定是定时任务请求,带来的效益更大,在SprngBoot项目中开启定时任务很简单,只需要在你的启动主类上加上这个注解:**@EnableScheduling**

然后在需要定时任务的执行类的方法上加上这个注解:**@Scheduled(cron = "0 0 2 * * ?")**, 其实就是cron表达式,执行的规律隔多久执行一次。

只要你的时间配置的足够短,这样数据也是近实时的,不会差太远,你可以配置成30秒或者几十秒执行一次,或者几分钟执行一次都可以,这个可以和产品进行协商,看产品可以接受多久的延迟。

然后,查询的中台的列表数据缓存在本地的一个map里面,用户的uid作为map的key,然后后面需要查询的时候,直接从map里面获取。

这样就不用每次请求过来都会实时的调用中台的http/rpc接口查询数据,直接从本地获取提高效率,这也是空间换时间的思想。

接口超时

这里需要注意的是,就是要提高你的接口调用的可用性,毕竟中台属于另一个服务,那么服务之间涉及远程调用,就有可能存在超时的现象。

那么你就要确保你的接口99.9%可用,对于接口超时,你可以就要设置接口重试。

因为有时候可能是网络的原因导致的一时超时,设置被调用方一时因为网络抖动导致超时,那么重试成功的概率就可能比较高。

一般重试的次数会设置为2-3次比较合理,除非网络故障了或者接口一直调不通,这样的话就需要及时告警,通知到开发人员,及时检查到底是哪里的问题,确保好接口的兜底方案。

并且还要设置每次的超时时间,设置超时时间也是非常的重要,假如超时时间设置的太短,还没有查出来就已经超时了,这样就会导致频繁超时,浪费资源。

要是设置的超时间太长,那么线程就会一直阻塞在那里等待调用的结果返回,这样在高并发场景下,就会资源耗尽,系统崩溃。

所以我给你的建议就是可以结合线上服务所在服务器的配置以及qps进行配置,配置一个合理的超时时间,合理的时间内能够超时返回并且不会导致资源耗尽。

重试这种机制,在很多中间件的思想中都会涉及到,比如:在分布式事务中2PC和3PC。

2PC在第二阶段提交失败,那么只能不断重试,直到所有参与者都成功(回滚或者提交成功)。

​因为除了重试,没有更好的办法,只能不断重试直到都成功,而且多数情况可能都是一时的网络抖动的原因导致的,这样重试成功的概率就非常高。

批量查询数据

定时任务缓存其实也是一种集中式缓存,假如缓存的数据量也比较大,那么在接口调用时就需要批量获取,但是一次性又不能查询太多,一般严谨的中台设计,都会都传参进行参数校验。

因为对于调用方完全是透明的,不可信任的,什么参数都有可能传过来,假如调用方一下子查几万个或者是几万个数据集,那不是接口都爆了。

所以,必须要做好分批调用,调用方分批、分页调用,中台对参数做校验一次只能查询几百个,这样子去规定,保证接口的可用性。

调用方的伪代码如下:

boolean end = true;
int page = 1;
int pageSize = 500;
while (end){
    // 设置好超时,失败重试
    Data data = getData(page, pageSize);
    
 Map map =data.getDataMap();
    // data里面的字段hashMore表示查询下一个分页是否还有数据
    end = Objects.equals(data.getHasMore(),1);
    page++;
}

本地缓存@Cacheable

@Cacheable是springframework下提供的缓存注解类,在spring中定义了org.springframework.cache.Cache 和 org.springframework.cache.CacheManager接口来统一实现cache。

除了@Cacheable用户缓存数据,也可以使用@CachePut用于缓存更新,这两个是比较常用的。

他们缓存的数据也是缓存在本地和定时任务一样,除了使用@Cacheable还可使用谷歌研发的cache工具类LoadingCache,他也是本地缓存的一种,并且可以设置缓存的大小,重新刷新的时间。

相对比Cacheable会更加方便,因为你发现Cacheable还缺少缓存时间和缓存更新的属性配置实现,可能还需要自己再二级开发,比如加入缓存失效时间、多少秒后自动更新更新缓存,这样Cacheable才能更加完善。

private final LoadingCache<String, List<ParentTag>> tagCache = CacheBuilder.newBuilder()
            .concurrencyLevel(4)
            .expireAfterWrite(5, TimeUnit.SECONDS)
            .initialCapacity(8)
            .maximumSize(10000)
            .build(new CacheLoader<String, List<ParentTag>>() {

                       @Override
                       public List<ParentTag> load(String cacheKey) {
                           return get(cacheKey);
                       }

                       @Override
                       public ListenableFuture<List<ParentTag>> reload(String key, List<ParentTag> oldValue) {
                           ListenableFutureTask<List<ParentTag>> task = ListenableFutureTask.create(() -> get(key));
                           executorService.execute(task);
                           return task;
                       }
                   }
            );

相比@Cacheable就是代码比较冗余,注解方式会更加直观简洁,不过LoadingCache的灵活性更高。

我们自己对Cacheable进行了扩展,加入了实效时间以及自动更新的方案,这样的Cacheable更加适用于我们的业务。

总结

在项目中可能多种缓存并行使用,使用不同的缓存都要基于业务进行考量,包括成本,数据一致性,性能问题等,不同的缓存方式有不同的特点。

redis缓存分布式系统中共享数据,性能高效,扩展性强,redis可以基于数据分片、哨兵模式进行扩展,但是要额外的费用进行运维,并且引入第三方中间件,系统的复杂度也高,排查困难,而且每次都要经过网络调用,有可能存在网络超时的现象,数据丢失,所以要做好数据兼容,兜底方案。

本地缓存使用简单方便,低成本,每个服务实例都会冗余一份数据,一致性问题加大,但是效率非常高效,不用通过网络传输获取数据。

一般我们的项目中都会分配6-8G的内存,所以一般本地缓存都够使用的,所以一般能用本地缓存的话,都可以优先使用本地缓存。

一些场景不得不使用分布式缓存的,就是用Redis缓存来共享数据,综合使用不同的缓存来解决项目中的问题。

从上面的几种缓存方案中可以看到重试方案,重试是解决很多问题的重要手段之一,但是重试次数,重试的超时时间也要控制,防止资源耗尽,在大多数场景下,重试都可以解决,要是重试次数达到限制都不成功,就有可能是网络故障或者接口问题,此时就需要应用发送告警通知开发人员进行排查,这是兜底方案。

客户端缓存和CDN缓存这两个对于服务端来说,比较少使用,一般公司都是用不到,大家可以把关注点放在服务端缓存中。

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

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

相关文章

N皇后问题(分支限界法)

问题描述&#xff1a; 在 n * n 格的棋盘上放置彼此不受攻击的 n 个皇后。按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题等价于在 n * n 的棋盘上放置 n 个皇后&#xff0c;任何 2个皇后不放在同一行或同一列或同一斜线上…

Ansys Speos | 新型计算方法:使用 GPU 提升计算速率

前言 Speos 在2022R2版本中正式推出 GPU 计算功能&#xff0c;相比于 CPU 计算&#xff0c;相同HPC32配置&#xff0c;高性能显卡在仿真计算中将会更显计算优势&#xff0c;在仿真数据量大、材料属性复杂、光源种类多的条件下&#xff0c;Speos 视觉模拟会消耗更多仿真计算时间…

ARM异常处理(1):异常类型、优先级分组和异常向量表

本系列文章将以Cortex-M3内核为例&#xff0c;对ARM的异常(exception)进行分析。 文章目录1 异常类型2 优先级3 向量表1 异常类型 Cortex-M3提供了一个功能丰富的异常体系结构&#xff0c;它支持很多系统异常和外部中断。异常编号1-15表示系统异常&#xff0c;16及以上表示外部…

0116 查找算法 Day5

剑指 Offer 04. 二维数组中的查找 在一个 n * m 的二维数组中&#xff0c;每一行都按照从左到右 非递减 的顺序排序&#xff0c;每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该…

2022快手电商短视频运营白皮书:Q2对比Q1GMV总值增长率达12%

1、2022快手电商短视频运营白皮书&#xff1a;Q2对比Q1GMV总值增长率达12%新榜讯 12月2日&#xff0c;快手电商发布《2022快手电商短视频运营白皮书》。白皮书数据显示&#xff0c;2022年4-6月&#xff0c;随着平台商家对短视频渠道的认知提升&#xff0c;挂车短视频生产量不断…

【人脸识别】MVFace:一个优于CosFace和ArcFace的人脸识别损失

论文题目&#xff1a;《Mis-classifified Vector Guided Softmax Loss for Face Recognition》 论文地址&#xff1a;https://arxiv.org/pdf/1912.00833v1.pdf 代码地址&#xff1a;http://www.cbsr.ia.ac.cn/users/xiaobowang/ 1.背景 迄今为止&#xff0c;提出了几种基于mar…

Hashing to elliptic curve算法改进

1. 引言 前序博客有&#xff1a; ECDSA VS Schnorr signature VS BLS signature 第3节“BLS签名” 私钥pkpkpk&#xff0c;对应的公钥为PpkGPpk\times GPpkG。待签名消息mmm。 BLS signature的签名流程为&#xff1a; 1&#xff09;通过H(m)H(m)H(m)将消息mmm映射为point o…

计算机毕业论文java毕业设计选题源代码javaweb企业门户网站官网

&#x1f496;&#x1f496;更多项目资源&#xff0c;最下方联系我们✨✨✨✨✨✨ 目录 Java项目介绍 资料获取 Java项目介绍 《javaweb企业门户网站》该项目采用技术&#xff1a;jsp servlet mysqljdbccssjsjQuery等相关技术&#xff0c;项目含有源码、文档、配套开发软件…

[hadoop全分布部署]安装Hadoop、配置Hadoop 配置文件②

&#x1f468;‍&#x1f393;&#x1f468;‍&#x1f393;博主&#xff1a;发量不足 个人简介&#xff1a;耐心&#xff0c;自信来源于你强大的思想和知识基础&#xff01;&#xff01; &#x1f4d1;&#x1f4d1;本期更新内容&#xff1a;安装Hadoop、配置Hadoop 配置文件…

手把手教你SSM整合(包教包会)

SSM整合 步骤&#xff08;七步&#xff09; 新建maven项目&#xff0c;添加依赖 可以选择根据模版创建maven项目 <dependencies><!--spring-webmvc--><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc&…

OpenCV入门(C++/Python)- 使用OpenCV调整尺寸大小(三)

使用OpenCV调整图像大小。要调整图像的大小&#xff0c;可以根据每个轴&#xff08;高度和宽度&#xff09;进行缩放&#xff0c;考虑指定的缩放因素&#xff0c;或者只需设置所需的高度和宽度。 调整图像大小时&#xff1a; 如果想在调整后的图像中保持相同的宽高比&#xf…

Redis 7.0 源码调试环境搭建与源码导读技巧

天下武功&#xff0c;无坚不摧&#xff0c;唯快不破&#xff01;我的名字叫 Redis&#xff0c;全称是 Remote Dictionary Server。 有人说&#xff0c;组 CP&#xff0c;除了要了解她外&#xff0c;还要给机会让她了解你。 那么&#xff0c;作为开发工程师的你&#xff0c;是否…

物联网设备带你进入物联网时代,轻松实现数据互联互通

物联网这一词&#xff0c;我们在生活和工作中时常会接触到&#xff0c;但如果要问大家什么是物联网设备&#xff1f;物联网设备有哪些&#xff1f;很多人应该没有一个详细的概念。那么什么是物联网设备呢&#xff1f;物联网设备是指能够连接无线网络&#xff0c;并具有数据传输…

【电力系统经济调度】多元宇宙算法求解电力系统多目标优化问题(Matlab实现)【电气期刊论文复现与算例创新】

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5; &#x1f4dd;目前更新&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;电力系统相关知识&#xff0c;期刊论文&…

计算机网络复习笔记——运输层

计算机网络复习笔记——运输层 概述 在计算机网络中进行通信的真正实体是位于通信两端主机中的进程 运输层的不同端口对应不同的进程 根据应用需求不同&#xff0c;运输层为应用层提供两种不同的运输协议&#xff0c;面向连接的TCP和无连接的UDP协议 运输层端口号、复用与…

CSS-盒子模型-内容,边框,内边距,外边距,(合并,塌陷情况)

CSS-盒子模型-内容&#xff0c;边框&#xff0c;内边距&#xff0c;外边距&#xff0c;(合并&#xff0c;塌陷情况) 目标&#xff1a;能够认识 盒子模型的组成 &#xff0c;****能够掌握盒子模型 边框、内边距、外边距 的****设置方法 学习路径&#xff1a; 1. 盒子模型的介绍 …

RK3588开发板的性能参数、功耗及功能特点|飞凌动态讲解

内容来源&#xff1a;飞凌嵌入式官网-www.forlinx.com2021年12月16日第六届瑞芯微开发者大会上&#xff0c;瑞芯微发布了全新一代旗舰处理器——RK3588。 相较前一代产品&#xff0c;RK3588的性能提高20%~30%&#xff01;同时采用新一代8nm制程工艺&#xff0c;也将大幅度降低功…

超级菜鸟怎么学习数据分析?

超级菜鸟如何学习数据分析&#xff0c;如何有效的成长为专业高手。 这个问题跟把大象放进冰箱是一个道理。 菜鸟成为超级高手是只需要四个步骤&#xff0c;直接上干货。 快速上手&#xff1a;可以简单、低门槛的上手学习与使用&#xff0c;快速入门 快速上手数据分析无非就是选…

Logstash:运用 Elasticsearch filter 来丰富地理数据

我们知道丰富数据对于很多的应用来说非常重要。这涉及到访问不同的表格&#xff0c;并进行搜索匹配。找到最为相近的结果并进行丰富数据。针对 Elasticsearh 来说&#xff0c;我们可以通过 enrich processor来进行丰富。你可以阅读我之前的文章来了解更多&#xff1a; Elastics…

git commit -m 撤销操作

1. 撤销本次commit-m操作 返回到add成功状态 git reset --soft HEAD^ 2.撤销本次commitadd 返回本地未提交状态 git reset --hard HEAD^ HEAD^ 表示上一个版本&#xff0c;即上一次的commit&#xff0c;几个^代表几次提交&#xff0c;如果回滚两次就是HEAD^^。 也可以写成HEAD…