轻松打卡:使用Spring Boot和Redis Bitmap构建高效签到系统【redis实战 四】

欢迎来到我的博客,代码的世界里,每一行都是一个故事


在这里插入图片描述

轻松打卡:使用Spring Boot和Redis Bitmap构建高效签到系统【redis实战 四】

    • 引言(redis实战)
    • 前言
    • 回顾bitmap
      • 基本概念
      • 核心特性
      • 使用场景
    • 为什么使用redis中的bitmap实现?
      • 1. 存储效率
      • 2. 性能
      • 3. 扩展性和可用性
      • 4. 位操作的优势
      • 5. 灵活性和简易性
      • 6. 成本效益
    • 构思与实现逻辑
      • 基础逻辑
      • Key的组成
      • 与MySQL的交互
      • 优势和考虑
    • 签到系统实现
      • maven依赖
      • service实现
      • controller实现
      • 前端页面简化版
      • 功能展现图

引言(redis实战)

Redis List:打造高效消息队列的秘密武器【redis实战 一】

Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】

Spring Boot和Redis Geo实现附近的人【redis实战 三】

前言

在数字化时代,签到系统已成为许多应用的标准功能,它不仅帮助我们追踪参与度,还激励着用户的日常活动。但如何在保证响应速度和数据准确性的同时处理成千上万的用户请求呢?这正是Spring Boot遇上Redis Bitmap的舞台。通过这篇文章,我们将踏上一个既充满挑战又充满创新的旅程,一起探索如何利用这两个强大工具打造出一个既高效又可靠的签到系统。

回顾bitmap

Redis Bitmap是一种特殊的数据结构,它使用连续的内存来存储和操作位数据(bit data),每个位可以是0或1。这种结构特别适合于那些需要处理大量布尔值的场景,比如在线状态、签到系统等。Bitmap由于其高效的存储和计算能力,在处理大规模数据时特别有优势。下面是Redis Bitmap的一些基本概念和特性:

基本概念

  1. 位操作:Bitmap的核心是位操作。它允许你设置、获取和计数单个位或一系列位的值。
  2. 存储效率:在Bitmap中,每个布尔值只占用一个位,而不是使用一个完整的字节或更多的空间,这使得它在存储大量布尔值时非常节省空间。
  3. 操作精确性:你可以精确地操作Bitmap中的每一位,这为处理复杂的数据结构提供了可能。

核心特性

  1. SETBIT & GETBIT

    • SETBIT 用于在指定偏移量处设置位的值。
    • GETBIT 用于获取指定偏移量处的位的值。
    • 这两个命令使得单个位的读写操作变得简单快速。
  2. BITCOUNT

    • BITCOUNT 命令用于计数Bitmap中设置为1的位的数量。
    • 可以对整个Bitmap或其特定范围进行计数。
    • 这对于统计和分析大量数据非常有用。
  3. BITOP

    • BITOP 命令用于对两个或多个Bitmap执行位运算(AND、OR、XOR和NOT)。
    • 这可以用来组合多个Bitmaps,计算交集、并集等。
  4. BITFIELD

    • BITFIELD 命令用于对Bitmap执行更复杂的操作,如将位作为整数进行递增。
    • 它提供了更高级别的控制,使得Bitmap可以用于更复杂的场景。
  5. 性能

    • Bitmap操作通常非常快,能够处理每秒数百万次的读写。
    • 它们特别适合高并发环境,如大型网站的在线状态跟踪。
  6. 空间效率

    • Bitmap在存储大量布尔值时极为节省空间。
    • 它特别适合那些值大部分为0的场景,因为Redis内部会进行优化以减少存储需求。

使用场景

  • 在线状态跟踪:跟踪数以百万计用户的在线或活跃状态。
  • 签到系统:记录用户每日的签到情况。
  • 访问统计:统计网站或应用的每日访问量。
  • 特征标记:记录用户或事物的特定特征,如权限、喜好等。

总结来说,Redis的Bitmap提供了一种高效、灵活的方法来处理大量的位级数据。它在存储效率和性能方面都有显著优势,特别适合于那些需要处理大量布尔值的场景。

为什么使用redis中的bitmap实现?

使用Redis Bitmap相比于使用MySQL或其他关系型数据库实现签到系统有一系列的优势。这些优势不仅在于性能上的提升,还包括了存储效率、扩展性和特定场景下的操作便利性。下面详细说明这些优势:

1. 存储效率

  • Bitmap结构:在Redis中,Bitmap以非常紧凑的方式存储数据。每个签到只需要一个位,而在关系型数据库中,你可能需要一个完整的表行来记录同样的信息。这在用户量庞大时尤其节省空间。
  • 内存存储:Redis作为内存数据库,其读写速度远超基于磁盘的关系型数据库。

2. 性能

  • 快速读写:对于Bitmap,无论是设置还是读取位值,时间复杂度都是O(1),这意味着操作的速度极快,几乎不受数据规模影响。
  • 减少I/O操作:由于Redis是内存数据库,所有操作都在内存中完成,避免了磁盘I/O,这在高并发场景下尤为重要。

3. 扩展性和可用性

  • 水平扩展:Redis支持主从复制、持久化、分区等多种特性,使得它可以很好地扩展并保持高可用性。
  • 成熟的集群支持:Redis集群提供了一种易于扩展和具有容错能力的方式,可以在多个节点上平衡负载。

4. 位操作的优势

  • 内置位操作:Redis提供了丰富的位操作命令(如SETBITGETBITBITCOUNT等),使得处理像签到这样的布尔值数据更加方便和高效。
  • 批量操作和原子性:可以原子性地对多个位进行操作,而在关系型数据库中实现同样的效果可能需要复杂的事务管理。

5. 灵活性和简易性

  • 简单的数据模型:相比关系型数据库复杂的表结构和关系,Redis的数据模型简单直观,易于理解和使用。
  • 快速开发和部署:对于开发人员来说,使用Redis通常意味着更少的设置和配置,可以更快速地开发和部署应用。

6. 成本效益

  • 减少硬件需求:由于Redis的高效率,相同负载下可能需要更少的硬件资源。
  • 降低维护成本:Redis简单的架构和少量的依赖使得维护工作相对更简单。

构思与实现逻辑

在上面提到的实现中,签到系统的基础逻辑是使用Redis的Bitmap来跟踪用户的每日签到状态。这种实现方式利用了Bitmap的高效存储和快速位操作特性,适合处理大量用户和高频率的签到事件。以下是对这个实现的基础逻辑和关键组件的详细介绍:

基础逻辑

  1. 每日签到

    • 每当用户签到时,系统会在Bitmap中的特定位置设置一个位(将其设为1)。
    • 位置通常由日期决定,例如,可以使用年内的天数作为偏移量。
  2. 统计查询

    • 系统可以通过计算Bitmap中设置为1的位的数量来统计签到次数。
    • 可以计算总签到次数、指定日期范围内的签到次数等。

Key的组成

在实现中,关键的是如何构造用于Bitmap的key。一个好的key设计既能确保访问的效率,又能方便与其他系统(如MySQL中的用户信息)进行交互。

  1. 用户标识

    • 每个用户应有唯一标识,如用户ID。这个标识会成为key的一部分,确保每个用户的签到信息是隔离的。
  2. 时间信息

    • 为了便于管理和查询,通常会在key中包含时间信息,如年份。这样可以每年为用户创建新的Bitmap,也便于历史数据的归档和查询。
  3. 前缀或命名空间

    • 为了更好的组织和区分不同的数据,可以在key前加上前缀或命名空间,如signin:

示例Key: signin:userId:2023

与MySQL的交互

  1. 用户详细信息

    • 在MySQL中维护用户的详细信息,如姓名、邮箱、注册日期等。
    • 每个用户记录都有一个唯一的ID,这个ID与Redis中使用的用户ID相对应。
  2. 数据关联

    • 当需要获取用户的签到信息以及详细信息时,可以先从Redis获取签到数据,然后使用从Redis得到的用户ID去MySQL中查询详细信息。
    • 这样的关联查询使得我们既能利用Redis的高性能,又能保持复杂数据的完整性和丰富性。
  3. 数据一致性

    • 确保在用户信息更新时(如用户ID变更),Redis中相应的key也同步更新,以维护数据的一致性。

优势和考虑

  • 高效存储:使用Bitmap存储签到信息极大地减少了所需的存储空间。
  • 快速操作:位操作的时间复杂度为O(1),即使是在大量数据面前也能保持高性能。
  • 灵活扩展:通过适当的key设计,系统可以灵活地进行扩展,如按年归档数据。
  • 与关系型数据库交互:通过在Redis和MySQL之间建立良好的数据关联,系统可以同时利用两者的优势。

通过以上的基础逻辑和key组成策略,你的签到系统将能够高效、灵活地处理用户的签到数据,并且能够方便地与存储在MySQL中的用户详细信息进行交互。

签到系统实现

maven依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
</dependency>

service实现

package fun.bo.service;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.BitSet;

/**
 * @author xiaobo
 */
@Service
public class SignInService {
    // 这里可以不写构造方法,直接用@RequiredArgsConstructor实现,可参考<a href="https://blog.csdn.net/Mrxiao_bo/article/details/135113649">一行注解,省却百行代码:深度解析@RequiredArgsConstructor的妙用</>

    private final RedisTemplate<String, byte[]> redisTemplate;

    public SignInService(RedisTemplate<String, byte[]> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 签到
    public void signIn(long userId) {
        LocalDate today = LocalDate.now();
        int dayOfYear = today.getDayOfYear();

        // 使用用户ID和年份作为key,确保每年的数据是独立的
        String key = "sign_in:" + userId + ":" + today.getYear();
        redisTemplate.opsForValue().setBit(key, dayOfYear, true);
    }
    
    // 获取今天签到的人数
    public long countSignInToday() {
        LocalDate today = LocalDate.now();
        return countSignInOnDate(today);
    }

    // 获取本周签到的人数
    public long countSignInThisWeek() {
        LocalDate today = LocalDate.now();
        // 获取本周的开始和结束日期
        LocalDate startOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
        LocalDate endOfWeek = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
        return countSignInBetweenDates(startOfWeek, endOfWeek);
    }

    // 获取本月签到的人数
    public long countSignInThisMonth() {
        // 获取本月的开始和结束日期
        LocalDate startOfMonth = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
        LocalDate endOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
        return countSignInBetweenDates(startOfMonth, endOfMonth);
    }

    // 获取特定日期签到的人数
    private long countSignInOnDate(LocalDate date) {
        int dayOfYear = date.getDayOfYear();
        String keyPattern = "sign_in:*:" + date.getYear();
        return redisTemplate.keys(keyPattern).stream()
                .filter(key -> redisTemplate.opsForValue().getBit(key, dayOfYear))
                .count();
    }

    // 获取日期范围内签到的人数
    private long countSignInBetweenDates(LocalDate start, LocalDate end) {
        long count = 0;
        // 遍历日期范围
        for (LocalDate date = start; !date.isAfter(end); date = date.plusDays(1)) {
            count += countSignInOnDate(date);
        }
        return count;
    }

    // 获取用户当月签到次数
    public long countUserSignInThisMonth(long userId) {
        LocalDate startOfMonth = LocalDate.now().with(TemporalAdjusters.firstDayOfMonth());
        LocalDate endOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
        String key = "sign_in:" + userId + ":" + LocalDate.now().getYear();
        byte[] bitmap = redisTemplate.opsForValue().get(key);
        BitSet bitSet = BitSet.valueOf(bitmap);
        long count = 0;
        for (int day = startOfMonth.getDayOfYear(); day <= endOfMonth.getDayOfYear(); day++) {
            count += bitSet.get(day) ? 1 : 0;
        }
        return count;
    }
}

有坑注意:RedisTemplate<String, byte[]>,这里要留意,如果你的bean中序列化Value的时候用的非字节数组,可能会报错如下

2023-12-25 11:12:55.163 ERROR 56398 --- [nio-7004-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.data.redis.serializer.SerializationException: Could not read JSON: Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: (byte[])"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001"; line: 1, column: 2]; nested exception is com.fasterxml.jackson.core.JsonParseException: Illegal character ((CTRL-CHAR, code 0)): only regular white space (\r, \n, \t) is allowed between tokens
 at [Source: (byte[])"\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0001"; line: 1, column: 2]] with root cause

controller实现

package fun.bo.controller;

import fun.bo.service.SignInService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SignInController {

    private final SignInService signInService;

    public SignInController(SignInService signInService) {
        this.signInService = signInService;
    }

    // ... 保留之前的signIn和count方法 ...

    @GetMapping("/signin/count/today")
    public String countSignInToday() {
        long count = signInService.countSignInToday();
        return "Total sign-ins today: " + count;
    }

    @GetMapping("/signin/count/week")
    public String countSignInThisWeek() {
        long count = signInService.countSignInThisWeek();
        return "Total sign-ins this week: " + count;
    }

    @GetMapping("/signin/count/month")
    public String countUserSignInThisMonth(@RequestParam long userId) {
        long count = signInService.countUserSignInThisMonth(userId);
        return "Total sign-ins this month for user " + userId + ": " + count;
    }
    @PostMapping("/signin")
    public String signIn(@RequestParam("userId") Long userId) {
        // 调用签到服务来处理签到
        signInService.signIn(userId);
        return "User " + userId + " signed in successfully.";
    }
}

前端页面简化版

<!DOCTYPE html>
<html>
<head>
    <title>签到系统</title>
    <meta charset="UTF-8"> <!-- 指定页面字符集为UTF-8 -->
</head>
<body>
<h1>欢迎来到签到系统</h1>

<div>
    <h2>用户签到</h2>
    用户ID:<input type="number" id="userIdInput" placeholder="输入用户ID">
    <button onclick="signIn()">签到</button>
</div>

<div>
    <h2>今日签到人数</h2>
    <button onclick="countSignInToday()">查询</button>
    <p id="todayCount"></p>
</div>

<div>
    <h2>本周签到人数</h2>
    <button onclick="countSignInThisWeek()">查询</button>
    <p id="weekCount"></p>
</div>

<div>
    <h2>本月用户签到次数</h2>
    用户ID:<input type="number" id="userId">
    <button onclick="countUserSignInThisMonth()">查询</button>
    <p id="monthCount"></p>
</div>

<script>
    function signIn() {
        // 请替换为你的实际用户ID和API端点
        let userId = document.getElementById('userIdInput').value;
        if (!userId) {
            alert("请先输入用户ID!");
            return;
        }
        fetch('/signin?userId=' + userId, { method: 'POST' })
            .then(response => alert('签到成功!'))
            .catch(error => console.error('Error:', error));
    }

    function countSignInToday() {
        fetch('/signin/count/today')
            .then(response => response.text())
            .then(data => document.getElementById('todayCount').innerText = data)
            .catch(error => console.error('Error:', error));
    }

    function countSignInThisWeek() {
        fetch('/signin/count/week')
            .then(response => response.text())
            .then(data => document.getElementById('weekCount').innerText = data)
            .catch(error => console.error('Error:', error));
    }

    function countUserSignInThisMonth() {
        let userId = document.getElementById('userId').value;
        fetch('/signin/count/month?userId=' + userId)
            .then(response => response.text())
            .then(data => document.getElementById('monthCount').innerText = data)
            .catch(error => console.error('Error:', error));
    }
</script>
</body>
</html>

功能展现图

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

探索全球DNS体系 | 从根服务器到本地解析

DNS 发展 DNS&#xff08;Domain Name System&#xff09;的起源可以追溯到互联网早期。 早期的挑战&#xff1a; 早期互联网主要通过IP地址进行通信&#xff0c;用户需要记住复杂的数字串来访问网站。 需求的催生&#xff1a; 随着互联网的扩大&#xff0c;更简单、易记的…

binary_search_tree的介绍与实现(二叉搜索树精美图示详解哦)

二叉搜搜索树 引言二叉搜索树的介绍二叉搜索树的实现框架默认成员函数构造析构赋值重载 InsertR&#xff08;插入&#xff09;EraseR&#xff08;删除&#xff09;SearchR&#xff08;查找&#xff09; 源码概览总结 引言 在C语言部分&#xff0c;我们已经认识了树与二叉树的结…

如何进行正确的 CodeReview

软件开发生命周期中至关重要的一步是代码审查。它使开发人员能够显著提升代码质量。它类似于书籍的创作过程。首先&#xff0c;作者写故事&#xff0c;然后经过编辑以确保不会出现诸如混淆“you’re”和“yours”之类的错误。在这个语境中&#xff0c;代码审查指的是检查和评估…

GPT应用程序的行业应用

GPT&#xff08;Generative Pre-trained Transformer&#xff09;应用程序在各个行业都有广泛的应用潜力&#xff0c;其自然语言生成的能力使其适用于多种场景。以下是一些行业中常见的GPT应用&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件…

【明道云】学习笔记1-了解APaaS

【背景】 APaaS (Application Platform As A Service) &#xff0c;即应用程序平台即服务&#xff0c;这是基于PaaS&#xff08;平台即服务&#xff09;的一种解决方案&#xff0c;支持应用程序在云端的开发、部署和运行&#xff0c;提供软件开发中的基础工具给用户&#xff0…

React Hooks 源码解析:useEffect

React Hooks 源码解析&#xff08;4&#xff09;&#xff1a;useEffect React 源码版本: v16.11.0源码注释笔记&#xff1a;airingursb/react 1. useEffect 简介 1.1 为什么要有 useEffect 我们在前文中说到 React Hooks 使得 Functional Component 拥有 Class Component 的…

三维重建(4)--三维重建与极几何

目录 一、三维重建基础 1、线性解法 2、非线性解法 3、多视图几何的关键问题 二、极几何 1、极几何关系 2、极几何特例 3、本质矩阵 4、本质矩阵的性质 5、基础矩阵 6、基础矩阵的性质 7、基础矩阵的作用 三、基础矩阵估计 1、八点法 2、归一化的八点法 四…

138.随机链表的复制(附带源码)

目录 一、思路分析 二、如何操作 三、源码 深拷贝&#xff1a;原封不动的拷贝一份 一、思路分析&#xff1a; 这一题&#xff0c;偏向于技巧性。如果是按照工科思维硬推&#xff0c;那会非常头大&#xff0c;脑袋瓜疼。 这一题目的核心难点在于&#xff1a;处理random指针…

BP蓝图映射到C++笔记1

教程链接&#xff1a;示例1&#xff1a;CompleteQuest - 将蓝图转换为C (epicgames.com) 1.常用的引用需要记住&#xff0c;如图所示。 2.蓝图中可以调用C函数&#xff0c;也可以实现C函数 BlueprintImplementableEvent:C只创建&#xff0c;不实现&#xff0c;在蓝图中实现 B…

日常常见应用组件升级记录

一、前言 因近期安全扫描&#xff0c;发现java后端应用涉及多个引用组件版本过低&#xff0c;涉及潜在漏洞利用风险&#xff0c;特记录相关处理升级处理过程&#xff0c;以备后续确认&#xff1b; 二、升级处理过程 2.1、Java类应用内置Spring Boot版本升级 Spring Boot是一…

python实现图片式PDF转可搜索word文档[OCR](已打包exe文件)

目录 1、介绍 1.1、痛点 1.2、程序介绍 2、安装方式 2.1、&#x1f53a;必要环节 2.2、脚本安装 2.2.1、不太推荐的方式 2.2.2、节约内存的方式 2.3、⭐完整版安装 3、使用 3.1、最终文件目录 3.2、主程序 3.2.1、绝对路径 3.2.2、是否为书籍 3.2.3、⭐截取区域 …

SpringCloudConfig+SpringCloudBus+Actuator+Git实现Eureka关键配置属性热更新(全程不重启服务)

文章目录 前言1.痛点2.解决方案3.具体实现3.1搭建热配置服务3.2编写配置文件3.3搭建版本控制仓库3.4Eureka-Client引入以下依赖3.5Eureka-Client微服务编写以下配置bootstrap.yml提前加载3.6分别编写测试Controller3.7测试效果3.8下线场景压测 4.SpringCloudBus优化 前言 在上…

【Kafka】Kafka介绍、架构和概念

目录 Kafka介绍Kafka优势Kafka应用场景Kafka基本架构和概念ProducerConsumer/Consumer GroupBrokerZooKeeperTopicPartitionReplicasOffsetsegment Kafka介绍 Kafka是是一个优秀的分布式消息中间件&#xff0c;关于常用的消息中间件对比可参考文章&#xff1a;消息中间件概述。…

Linux用户空间和内核空间所有15种内存分配方法

在Linux操作系统中&#xff0c;内存管理是一个关键的系统功能。用户空间和内核空间分别使用不同的函数来申请内存。以下是用户空间和内核空间内存申请函数的详细列表&#xff1a; Linux用户空间内存申请函数 1. malloc() 函数&#xff1a; void* malloc(size_t size); 用于…

Android OpenGL EGL使用——自定义相机

如果要使用OpenGl来自定义相机&#xff0c;EGL还是需要了解下的。 可能大多数开发者使用过OpengGL但是不知道EGL是什么&#xff1f;EGL的作用是什么&#xff1f;这其实一点都不奇怪&#xff0c;因为Android中的GlSurfaceView已经将EGL环境都给配置好了&#xff0c;你一直在使用…

100天精通Python(实用脚本篇)——第113天:基于Tesseract-OCR实现OCR图片文字识别实战

文章目录 专栏导读1. OCR技术介绍2. 模块介绍3. 模块安装4. 代码实战4.1 英文图片测试4.2 数字图片测试4.3 中文图片识别 书籍分享 专栏导读 &#x1f525;&#x1f525;本文已收录于《100天精通Python从入门到就业》&#xff1a;本专栏专门针对零基础和需要进阶提升的同学所准…

Flowable 生成流程图

/*** 生成流程图** param processId 任务ID*/ RequestMapping("/diagram/{processId}") public void genProcessDiagram(HttpServletResponse response,PathVariable("processId") String processId) {InputStream inputStream flowTaskService.diagram(p…

redis优化系列(六)

本期分享redis内存过期策略&#xff1a;过期key的处理 Redis之所以性能强&#xff0c;最主要的原因就是基于内存存储。然而单节点的Redis其内存大小不宜过大&#xff0c;会影响持久化或主从同步性能。 可以通过修改配置文件来设置Redis的最大内存&#xff1a; maxmemory 1gb …

[翻译] Vulkan-Sample-MSAA (Multisample anti-aliasing)

原文 Aliasing是以低于原始信号采样率的采样率进行采样导致的。在图形学中&#xff0c;这个过程可以描述为&#xff1a;基于一个会产生artifacts的分辨率去计算像素值&#xff0c;从而在模型边缘产生锯齿。多重采样抗锯齿&#xff08;Multisample anti-aliasing&#xff0c;MS…

vivado 接口、端口映射

接口 重要&#xff01;接口只能在“fpga”类型的&#xff1c;component&#xff1e;中定义。接口部分提供了<component>上所有可用物理接口的列表。<interfaces>部分包含嵌套在其中的一个或多个<interface>标记。一个接口是通过使用<port_map>标记由多…