点赞功能真的有必要上 Redis 吗?(Mongo、MySQL、Redis、MQ 实测性能对比)

目录

一、你会怎么设计一个点赞功能?

1.1、点赞实现思路

1.2、点赞功能设计

1.2.1、MySQL 单表

1.2.2、单表 + MySQL 关联表

1.2.3、MySQL 关联表 + mq

1.2.4、redis + mq

1.2.5、mongodb 关联文档

二、性能测试

2.1、前置说明 

2.2、10 万数据准备


一、你会怎么设计一个点赞功能?


1.1、点赞实现思路

我们先来想一想一个基本的点赞功能都需要哪些服务(这里以小红书系统为例):

读操作:当用户刷到一个专辑的时候,需要做以下几个操作

  1. 去查询当前用户是否有点赞.
  2. 查询当前点赞的数量.

写操作:当用户点击点赞按钮时候,需要进行以下几个操作

  1. 查询当前用户是否已经点赞.
  2. 如果点赞,就删除点赞信息,如果没有点赞,就添加点赞信息.
  3. 更新点赞数量(根据不同设计,可能会有这一步).
  4. 发布点赞消息.

Ps:之后的代码由于篇幅原因,没有任何封装

1.2、点赞功能设计

1.2.1、MySQL 单表

只设计一张 点赞信息表 来实现点赞功能.  

表中记录了哪个用户对哪篇专辑进行点赞.

读操作:

    public AlbumStatVO MySQLOne(
            @RequestParam @NotBlank String albumId
    ) {
        //为了简单,只设计了点赞表,因此这里通过四次查询点赞表来模拟
        Long pageView = Db.lambdaQuery(AlbumLike.class)
                .eq(AlbumLike::getTargetId, albumId)
                .count();
        Long likeCnt = Db.lambdaQuery(AlbumLike.class)
                .eq(AlbumLike::getTargetId, albumId)
                .count();
        Long collectCnt = Db.lambdaQuery(AlbumLike.class)
                .eq(AlbumLike::getTargetId, albumId)
                .count();
        Long commentCnt = Db.lambdaQuery(AlbumLike.class)
                .eq(AlbumLike::getTargetId, albumId)
                .count();
        if(pageView == null || likeCnt == null || commentCnt == null || collectCnt == null) {
            return null;
        }
        return AlbumStatVO.builder()
                .albumId(Long.valueOf(albumId))
                .pageView(pageView)
                .likeCnt(likeCnt)
                .collectCnt(collectCnt)
                .commentCnt(commentCnt)
                .build();
    }

写操作:

    public String MySQLOne(@RequestBody @Valid ActDTO dto) {
        synchronized (locker1) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, dto.getPostId())
                    .eq(AlbumLike::getTargetId, dto.getTargetId())
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(Long.valueOf(dto.getPostId()))
                                .targetId(Long.valueOf(dto.getTargetId()))
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .remove();
            }
        }
        return "ok";
    }

好处:

实现起来简单:一张表不仅记录了当前专辑有谁点赞,还可以通过 count(*) 查询出当前专辑的点赞量.

缺点:

速度低:如果这样设计点赞表,那么你肯定也会设计出差不多的 收藏表、评论表...(如果需要,可能还有记录访问量的表).  那么当用户看到这个专辑的时候,光是查看点赞量、访问量... 都需要至少 3 次 count(*) 操作(频繁的和数据库建立连接和断开连接都有一定的开销).  

1.2.2、单表 + MySQL 关联表

为了解决刚刚提到的问题,可以再设计一个关联表——专辑信息统计表

这个表中就记录了访问量、点赞量、收藏量... 这些信息.  同时使用 专辑id 保证和 专辑表的关联关系.

读操作:

    public AlbumStatVO MySQLTwo(
            @RequestParam @NotBlank String albumId
    ) {
        AlbumStat stat = Db.lambdaQuery(AlbumStat.class)
                .eq(AlbumStat::getAlbumId, albumId)
                .one();
        if(stat == null) {
            return null;
        }
        return AlbumStatVO.builder()
                .albumId(Long.valueOf(albumId))
                .pageView(stat.getPageView())
                .likeCnt(stat.getLikeCnt())
                .collectCnt(stat.getCollectCnt())
                .commentCnt(stat.getCommentCnt())
                .build();
    }

写操作:

    public String MySQLTwo(@RequestBody @Valid ActDTO dto) {
        synchronized (locker2) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, dto.getPostId())
                    .eq(AlbumLike::getTargetId, dto.getTargetId())
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(Long.valueOf(dto.getPostId()))
                                .targetId(Long.valueOf(dto.getTargetId()))
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
                Db.lambdaUpdate(AlbumStat.class)
                        .setSql("like_cnt = like_cnt + 1")
                        .eq(AlbumStat::getAlbumId, dto.getTargetId())
                        .update();
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .remove();
                Db.lambdaUpdate(AlbumStat.class)
                        .setSql("like_cnt = like_cnt - 1")
                        .eq(AlbumStat::getAlbumId, dto.getTargetId())
                        .update();
            }
        }
        return "ok";
    }

 好处:

读操作方便,效率相对较高:解决了频繁和数据库建立连接和断开连接的问题.  查询点赞量、收藏量...这些信息,只需要通过 专辑id 对 专辑信息统计表 进行一次查询就可以得到所有的统计信息.

缺点:

写操作麻烦,效率相对较低:每次保存了用户点赞信息,还需要更新 专辑信息统计表 中的点赞量信息.

在一致性的问题上,引入了额外开销和系统复杂度.

1.2.3、MySQL 关联表 + mq

那么此时遇到的问题就是,点赞效率低,并且 点赞的统计 和 点赞消息添加 耦合在了一起.

那么此时使用 mq 就能很好的解决上述问题,因为点赞量这种数据并不需要很强的时效性,只需要保证最终一致性即可.

读操作和 MySQL 关联表没有区别.

写操作:

    public String MqOne(@RequestBody @Valid ActDTO dto) {
        long postId = Long.parseLong(dto.getPostId());
        long targetId = Long.parseLong(dto.getTargetId());
        synchronized (locker3) {
            boolean exists = Db.lambdaQuery(AlbumLike.class)
                    .eq(AlbumLike::getPostId, postId)
                    .eq(AlbumLike::getTargetId, targetId)
                    .exists();
            if(!exists) {
                Db.save(
                        AlbumLike.builder()
                                .postId(postId)
                                .targetId(targetId)
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
            } else {
                Db.lambdaUpdate(AlbumLike.class)
                        .eq(AlbumLike::getPostId, postId)
                        .eq(AlbumLike::getTargetId, targetId)
                        .remove();
            }
            rabbitTemplate.convertAndSend(
                    MqConst.STAT_DIRECT,
                    MqConst.LIKE_MySQL_QUEUE,
                    objectMapper.writeValueAsString(
                            LikeMsg.builder()
                                    .targetId(targetId)
                                    .isLike(!exists)
                                    .build()
                    )
            );
        }
        return "ok";
    }

好处:

  • 效率相对较高,达到了削峰填谷的作用,避免大量请求同一时间打入数据库导致崩溃
  • 实现了点赞量统计和点赞消息的解耦合.

缺点:

需要引入额外的表来统计数据.

单表的读写性能在大量数据的情况下,还是会达到瓶颈.

为了保证一致性,增加系统复杂度.

1.2.4、redis + mq

Ps:此处先不考虑 雪崩、击穿、穿透 问题.

这里我只考虑使用 redis 作为缓存,不考虑作为内存数据库的情况.

原因:1. 贵  2. Redis 实例突然崩溃或遭遇其他故障,RDB和AOF机制可能无法完全保证数据的完整性,这里为了保证数据的强一致性,还需要 mysql.

mq 用来解决数据同步问题.

实现思路:

a)redis 缓存更新策略:在 redis 的配置文件中设置 maxmemory (内存使用上限). 读的时候先从 redis 中读数据,如果没有读到数据,就从 mysql 中读取数据,然后再将数据通过 mq 同步到 redis 上. 经过一段时间的 “动态平衡”, redis 中的剩余数据就是 “热点数据” 了.

redis 实现点赞功能有很多种方式,这里我讲保证强一致性的方案.

b)具体实现:点赞功能使用 set 类型是非常合适的,这样不仅表示了当前专辑有哪些用户进行点赞,还可以通过 set 的 scard 获取点赞量.

读操作:

  1. 先查 redis 上有没有点赞信息.
  2. redis 中存在:直接返回
  3. redis 中不存在:去 MySQL 查数据,同步到 redis 上.
    public AlbumStatVO redisOne(
            @RequestParam @NotBlank String albumId
    ) {
        //1.redis 是否有
        List<Object> statList = redisTemplate.executePipelined(new SessionCallback<String>() {
            @Override
            public <K, V> String execute(RedisOperations<K, V> operations) throws DataAccessException {
                RedisOperations<String, Object> template = (RedisOperations<String, Object>) operations;
                template.opsForValue().get(RedisKeyConst.ALBUM_PAGE_VIEW + albumId);
                template.opsForSet().size(RedisKeyConst.ALBUM_LIKE + albumId);
                template.opsForValue().get(RedisKeyConst.ALBUM_COLLECT + albumId);
                template.opsForValue().get(RedisKeyConst.ALBUM_COMMENT + albumId);
                return null;
            }
        });
        if(statList.get(0) != null && statList.get(1) != null
                && statList.get(2) != null && statList.get(3) != null) {
            return AlbumStatVO.builder()
                    .albumId(Long.valueOf(albumId))
                    .pageView((Long) statList.get(0))
                    .likeCnt((Long) statList.get(1))
                    .collectCnt((Long) statList.get(2))
                    .commentCnt((Long) statList.get(3))
                    .build();
        }
        //2.redis上没有,去查数据库
        AlbumStat dbStat = Db.lambdaQuery(AlbumStat.class)
                .eq(AlbumStat::getAlbumId, albumId)
                .one();
        if(dbStat == null) {
            return null;
        }
        List<Long> userIds = Db.lambdaQuery(AlbumLike.class)
                .eq(AlbumLike::getTargetId, albumId)
                .list().stream()
                .map(AlbumLike::getPostId)
                .toList();

        //3.将数据保存到 redis 上
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                RedisOperations<String, Object> temp = (RedisOperations<String, Object>) operations;
                temp.opsForValue().set(RedisKeyConst.ALBUM_PAGE_VIEW + albumId, dbStat.getPageView());
                temp.opsForSet().add(RedisKeyConst.ALBUM_LIKE + albumId, userIds.toArray()); //第二个参数是数组
                temp.opsForValue().set(RedisKeyConst.ALBUM_COLLECT + albumId, dbStat.getCollectCnt());
                temp.opsForValue().set(RedisKeyConst.ALBUM_COMMENT + albumId, dbStat.getCommentCnt());
                return null;
            }
        });

        return AlbumStatVO.builder()
                .albumId(Long.valueOf(albumId))
                .pageView(dbStat.getPageView())
                .likeCnt(dbStat.getLikeCnt())
                .commentCnt(dbStat.getCommentCnt())
                .collectCnt(dbStat.getCollectCnt())
                .build();
    }

写操作:

  1. 首先去判断 redis 上判断是否有当前用户的点赞信息
  2. redis 中存在:删除 redis 上的点赞信息. 
  3. redis 中不存在:又有两种情况
    1. 当前用户确实没有对此专辑进行过点赞.
    2. 点赞数据过期了.
  4. 无论是以上哪种情况,我为了保证强一致性,都会去 mysql 中查一下,点赞数据是否存在.(如果你不想保证强一致性,就是愿意只通过 redis 来判断,就是不怕某些特殊情况下 redis 突然崩溃,持久化文件损坏,你确实可以写个定期更新任务,定期同步 redis)
  5. mysql 中存在:redis 上啥都不用做,反正查到点赞数据存在,也是删除.
  6. mysql 中不存在:在 redis 上添加点赞数据.
  7. 根据上述所有情况,通过 mq 更新 mysql 中的 点赞量 和 点赞信息.
    public String redisOne(@RequestBody @Valid ActDTO dto) {
        synchronized (locker4) {
            boolean isExists = Boolean.TRUE.equals(redisTemplate.opsForSet().isMember(
                    RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                    dto.getPostId())
            );
            Boolean isLike = null;
            if(isExists) {
                redisTemplate.opsForSet().remove(
                        RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                        dto.getPostId()
                );
                isLike = false;
            } else {
                isExists = Db.lambdaQuery(AlbumLike.class)
                        .eq(AlbumLike::getPostId, dto.getPostId())
                        .eq(AlbumLike::getTargetId, dto.getTargetId())
                        .exists();
                if(!isExists) {
                    redisTemplate.opsForSet().add(
                            RedisKeyConst.ALBUM_LIKE + dto.getTargetId(),
                            dto.getPostId()
                    );
                    isLike = true;
                } else {
                    isLike = false;
                }
            }
            rabbitTemplate.convertAndSend(
                    MqConst.STAT_DIRECT,
                    MqConst.SYNC_LIKE_MYSQL_QUEUE,
                    objectMapper.writeValueAsString(
                            LikeMsg.builder()
                                    .isLike(isLike)
                                    .postId(Long.valueOf(dto.getPostId()))
                                    .targetId(Long.valueOf(dto.getTargetId()))
                                    .build()
                    )
            );
        }
        return "ok";
    }

好处:

redis 是在内存中操作数据,速度大大提升.

坏处:

  1. 使用 redis 真的一定快么?某些情况不一定快,因为 redis 作为客户端服务器系统,也会存在一定的网络开销.
  2. 贵!内存资源是十分珍贵的,不适合用来存储大量数据.
  3. 大大增加了系统复杂度,需要考虑数据一致性,时效性问题.

1.2.5、mongodb 关联文档

MongoDB 使用内存映射技术,将数据暂时存储在内存中,而不是直接持久化到存储设备中。由于内存读取速度远快于磁盘,因此 MongoDB 在 IO 操作上相比于 MySQL 更加高效.

MongoDB 相比于 MySQL 主要的缺点就是事务支持相对较弱,但是对于点赞这种数据,也不需要太强的事务支持,因此使用 MongoDB 来存储点赞数据是非常合适的.

读操作:

    public AlbumStatVO mongoOne(
            @RequestParam String albumId
    ) {
        AlbumStatGO statGO = mongoTemplate.findOne(
                Query.query(
                        Criteria
                                .where("_id")
                                .is(Long.valueOf(albumId)))
                , AlbumStatGO.class);
        if(statGO == null) {
            return null;
        }
        return AlbumStatVO.builder()
                .albumId(Long.valueOf(albumId))
                .pageView(statGO.getPageView())
                .likeCnt(statGO.getLikeCnt())
                .commentCnt(statGO.getCommentCnt())
                .collectCnt(statGO.getCollectCnt())
                .build();
    }

写操作:

    public String mongoOne(@RequestBody @Valid ActDTO dto) {
        long postId = Long.parseLong(dto.getPostId());
        long targetId  = Long.parseLong(dto.getTargetId());
        synchronized (locker5) {
            boolean exists = mongoTemplate.exists(
                    Query.query(
                            Criteria
                                    .where("post_id")
                                    .is(postId)
                                    .and("target_id")
                                    .is(targetId)
                    ),
                    AlbumLikeGO.class
            );
            if(!exists) {
                mongoTemplate.insert(
                        AlbumLikeGO.builder()
                                .postId(postId)
                                .targetId(targetId)
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
                mongoTemplate.updateFirst(
                        Query.query(
                                Criteria
                                        .where("_id")
                                        .is(targetId)
                        ),
                        new Update().inc("like_cnt", -1),
                        AlbumStatGO.class
                );
            } else {
                mongoTemplate.remove(
                        Query.query(
                                Criteria
                                        .where("post_id")
                                        .is(postId)
                                        .and("target_id")
                                        .is(targetId)
                        ),
                        AlbumLikeGO.class
                );
                mongoTemplate.updateFirst(
                        Query.query(
                                Criteria
                                        .where("_id")
                                        .is(targetId)
                        ),
                        new Update().inc("like_cnt", -1),
                        AlbumStatGO.class
                );
            }
        }
        return "ok";
    }

二、性能测试


2.1、前置说明 

a)业务说明

由于我们一般不会只查询点赞数量,而是后端将点赞量、评论量、收藏量一并返回,因此这里在读操作上为了实际场景,我们会将这些数据一并返回.

写操作上,只关注点赞功能即可.

b)服务器配置

4C 6G

部署了 redis、mongo、mysql、rabbitmq......

当前项目也已部署.

c)测试环境

并发用户量:100

持续时间:2min

爬坡:1min

2.2、10 万数据准备

为了减少网络通讯次数,这里使用批处理,每次添加 1000 条数据.

a)mysql 数据准备

@SpringBootTest
@Slf4j
class MySQLDataTests {

    private static final int albumNum = 1000;
    private static final int userNum = 100;
    private static int count = 0;

    public void addLike() {
        for(int userId = 1; userId <= userNum; userId++) {
            //批量添加
            List<AlbumLike> list = new ArrayList<>();
            for (int albumId = 1; albumId <= albumNum; albumId++) {
                list.add(
                        AlbumLike.builder()
                                .postId((long) userId)
                                .targetId((long) albumId)
                                .ctTime(new Date())
                                .utTime(new Date())
                                .build()
                );
                log.info("插入 {} 条", ++count);
            }
            Db.saveBatch(list);
        }
    }

    public void addStat() {
        for(int albumId = 1; albumId <= albumNum; albumId++) {
            Db.save(
                    AlbumStat.builder()
                            .albumId((long) albumId)
                            .likeCnt((long) userNum)
                            .build()
            );
            log.info("当前执行 {} 条", albumId);
        }
    }

    @Test
    public void run() throws InterruptedException {
        addLike();
        addStat();
    }

}

执行时间是 1min 12s

b)mongo 数据准备

@Slf4j
@SpringBootTest
public class MongoDataTests {

    @Autowired
    private MongoTemplate mongoTemplate;

    private static final int albumNum = 1000;
    private static final int userNum = 100;
    private static int count = 0;

    private void addLikeData() {
        //分批次插入
        for(int userId = 1; userId <= userNum; userId++) {
            List<AlbumLikeGO> list = new ArrayList<>();
            for(int albumId = 1; albumId <= albumNum; albumId++) {
                list.add(
                    AlbumLikeGO.builder()
                            .postId((long) userId)
                            .targetId((long) albumId)
                            .ctTime(new Date())
                            .utTime(new Date())
                            .build()
                );
                log.info("插入 {} 条", ++count);
            }
            mongoTemplate.insertAll(list);
        }

    }

    private void addStat() {
        for(int albumId = 1; albumId <= albumNum; albumId++) {
            mongoTemplate.insert(
                    AlbumStatGO.builder()
                            .albumId((long) albumId)
                            .pageView(0L)
                            .likeCnt((long) userNum)
                            .collectCnt(0L)
                            .commentCnt(0L)
                            .build()
            );
        }
    }

    @Test
    public void run() throws InterruptedException {
        addLikeData();
        addStat();
    }


}

执行时间大约是 3s (和 mysql 恐怖的差距)

2.3、实际结果

a)读操作

 

b)写操作

c)结论:除了单表操作比较耗时外,对于中小型项目而言,频繁的读写操作场景,使用 mongo 就够用了.  甚至都不用上 mq,更甚至有的场景下 redis 性能还不如 mongo.

对于中小型项目,能有那些项目达到每秒钟同时有 100 个用户连续请求 1 ~ 3 次接口,持续 2min 不停???

服务器能崩???平均 11ms 的响应时间你等不了???

怕是你的服务器不行吧~  

为什么我这么说,我这还有一个 2C 2G 的服务器(应用部署,环境在 4C 6G 的服务器上),来给你看看效果

同样是 100用户并发,持续 2min,爬坡 1min

a)读操作

 

b)写操作

很多请求甚至还没有来得及处理,就崩了~

Ps:本文禁止转载!!!不然 si 妈!!!

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

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

相关文章

PyTorch完整的神经网络模型训练(使用GPU训练)

1.什么是CUDA&#xff1a; CUDA&#xff08;Compute Unified Device Architecture&#xff09;是由NVIDIA开发的一种并行计算平台和编程模型。它允许开发者在NVIDIA GPU上进行通用目的的并行计算&#xff0c;包括深度学习、科学计算、图形处理和加密等任务。 CUDA通过提供一组…

vulhub中Weblogic WLS Core Components 反序列化命令执行漏洞复现(CVE-2018-2628)

Oracle 2018年4月补丁中&#xff0c;修复了Weblogic Server WLS Core Components中出现的一个反序列化漏洞&#xff08;CVE-2018-2628&#xff09;&#xff0c;该漏洞通过t3协议触发&#xff0c;可导致未授权的用户在远程服务器执行任意命令。 访问http://your-ip:7001/consol…

人工智能:探索智慧的未来

目录 前言1 人工智能的简介1.1 人工智能的定义1.2 任务范围1.3 模拟人类认知 2 人工智能发展2.1 起步阶段2.2 发展阶段2.3 繁荣阶段 3 弱人工智能和强人工智能3.1 弱人工智能&#xff08;ANI&#xff09;3.2 强人工智能&#xff08;AGI&#xff09; 4 人工智能主要技术4.1 机器…

【C++11】包装器和bind

文章目录 一. 为什么要有包装器&#xff1f;二. 什么是包装器&#xff1f;三. 包装器的使用四. bind 函数模板1. 为什么要有 bind &#xff1f;2. 什么是 bind ?3. bind 的使用场景 一. 为什么要有包装器&#xff1f; function 包装器&#xff0c;也叫作适配器。C 中的 funct…

Elastic Stack--06--JavaAPI----索引(创建-查询- 删除)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 环境准备添加依赖&#xff1a;HelloElasticsearch JavaAPI-索引1.创建2.查询3.删除 环境准备 添加依赖&#xff1a; <dependencies><dependency><g…

第G3周:CGAN入门|生成手势图像

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 一、前置知识 CGAN&#xff08;条件生成对抗网络&#xff09;的原理是在原始GAN的基础上&#xff0c;为生成器和判别器提供 额外的条件信息…

vue3 ref获取子组件显示 __v_skip : true 获取不到组件的方法 怎么回事怎么解决

看代码 问题出现了 当我想要获取这个组件上的方法时 为什么获取不到这个组件上的方法呢 原來&#xff1a; __v_skip: true 是 Vue 3 中的一个特殊属性&#xff0c;用于跳过某些组件的渲染。当一个组件被标记为 __v_skip: true 时&#xff0c;Vue 将不会对该组件进行渲染&am…

ABAP接口-RFC连接(ABAP TO ABAP)

目录 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09;创建ABAP连接RFC函数的调用 ABAP接口-RFC连接&#xff08;ABAP TO ABAP&#xff09; 创建ABAP连接 事务代码&#xff1a;SM59 点击创建&#xff0c;填写目标名称&#xff0c;选择连接类型&#xff1a; 填写主机名…

打卡--MySQL8.0 一(单机部署)

一路走来&#xff0c;所有遇到的人&#xff0c;帮助过我的、伤害过我的都是朋友&#xff0c;没有一个是敌人。如有侵权&#xff0c;请留言&#xff0c;我及时删除&#xff01; MySQL 8.0 简介 MySQL 8.0与5.7的区别主要体现在&#xff1a;1、性能提升&#xff1b;2、新的默认…

02-app端文章查看,静态化freemarker,分布式文件系统minIO

app端文章查看&#xff0c;静态化freemarker,分布式文件系统minIO 1)文章列表加载 1.1)需求分析 文章布局展示 1.2)表结构分析 ap_article 文章基本信息表 ap_article_config 文章配置表 ap_article_content 文章内容表 三张表关系分析 1.3)导入文章数据库 1.3.1)导入数据…

【vue.js】文档解读【day 2】 | 响应式基础

如果阅读有疑问的话&#xff0c;欢迎评论或私信&#xff01;&#xff01; 本人会很热心的阐述自己的想法&#xff01;谢谢&#xff01;&#xff01;&#xff01; 文章目录 响应式基础声明响应式状态(属性)响应式代理 vs 原始值声明方法深层响应性DOM 更新时机有状态方法 响应式…

电脑数据安全新防线:文件备份的终极指南

一、数据守护者的使命&#xff1a;文件备份的重要性 在数字化日益普及的今天&#xff0c;电脑已成为我们日常生活和工作的必备工具&#xff0c;文件作为我们储存、交流和处理信息的主要载体&#xff0c;其重要性不言而喻。然而&#xff0c;无论是由于硬件故障、软件崩溃&#…

Autosar教程-Mcal教程-GPT配置教程

3.3GPT配置、生成 3.3.1 GPT配置所需要的元素 GPT实际上就是硬件定时器,需要配置的元素有: 1)定时器时钟:定时器要工作需要使能它的时钟源 2)定时器分步:时钟源进到定时器后可以通过分频后再给到定时器 定时器模块选择:MCU有多个定时器模块,需要决定使用哪个定时器模块作…

【动态规划】代码随想录算法训练营第三十九天 |62.不同路径,63.不同路径II(待补充)

62.不同路径 1、题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2、文章讲解&#xff1a;代码随想录 3、题目&#xff1a; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右…

JavaScript高级Ⅱ(全面版)

接上文 JavaScript高级Ⅰ JavaScript高级Ⅰ(自认为很全面版)-CSDN博客 目录 第2章 DOM编程 2.1 DOM编程概述 2.1.4 案例演示(商品全选) 2.1.5 dom操作内容 代码演示&#xff1a; 运行效果&#xff1a; 2.1.6 dom操作属性 代码演示&#xff1a; 运行效果&#xff1a; 2…

H264/265编码参数2: Profile Level Tier

profile和level profile和level是视频编码中两个很重要的概率&#xff0c;中文一般叫做档次和级别。 在MPEG2标准里边&#xff0c;按不同的压缩比分成五个档次&#xff0c;按视频清晰度分为四个级别&#xff0c;如下图所示&#xff1a; 档次和级别共有 20 种组合&#xff0c;…

2024年【化工自动化控制仪表】考试总结及化工自动化控制仪表作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【化工自动化控制仪表】考试总结及化工自动化控制仪表作业考试题库&#xff0c;包含化工自动化控制仪表考试总结答案和解析及化工自动化控制仪表作业考试题库练习。安全生产模拟考试一点通结合国家化工自动化控…

day-18 长度最小的子数组

运用队列的思维&#xff0c;求出每种满足题意的子数组长度&#xff0c;最小的即为答案&#xff0c;否则返回0 code class Solution {public int minSubArrayLen(int target, int[] nums) {int l0,r0;int ansInteger.MAX_VALUE;int total0;while(r<nums.length){totalnums[r…

C++:类和对象(三)——拷贝构造函数和运算符重载

目录 一、拷贝构造函数 1.概念 2.特性 二、赋值运算符重载 1.运算符重载 2.赋值运算符重载 &#xff08;1&#xff09;注意的点&#xff1a; &#xff08;2&#xff09;赋值运算符不允许被重载为全局函数&#xff0c;只能重载为类的成员函数 &#xff08;3&#xff09;…

STM32单片机示例:ETH_LAN8742_DHCP_NonOS_Poll_H743

文章目录 目的基础说明关键配置关键代码示例链接总结 目的 以太网是比较常用到的功能&#xff0c;STM32系列单片机使用CubeMX配置使用以太网功能比非常方便。不过对于H7系列来说需要使能 DCache 才能启用LwIP&#xff0c;启用Cache后又会带来一些需要特别注意的事情。这篇文章…