Redis实战—黑马点评(二)缓存篇

2023-05-16

Redis实战—黑马点评(二)缓存篇

目录

  • Redis实战—黑马点评(二)缓存篇
    • 1. 什么是缓存
      • 1.1 缓存的作用和成本
    • 2. 添加 Redis 缓存
    • 3. 缓存更新策略
      • 3.1 三种更新策略
        • 3.1.1 主动更新策略
    • 4. 缓存穿透
      • 4.1 常见两种解决办法
        • 4.1.1 缓存空值
        • 4.1.2 布隆过滤器
      • 4.2 小结
    • 5. 缓存雪崩
    • 6. 缓存击穿
      • 6.1 常见解决方案两种
        • 6.1.1 互斥锁
        • 6.1.2 逻辑过期
        • 6.1.3 方案对比
      • 6.2 小收获
    • 7. 缓存工具类封装
      • 7.1 代码实现
        • 问题:刚才的代码中,锁的key不应该在代码中指定,而是作为参数传入。
      • 7.2 小收获
        • 7.2.1 时间处理
        • 7.2.2 泛型使用
        • 7.2.3 Function对象

在这里插入图片描述

1. 什么是缓存

缓存即为数据交换的缓冲区,是数据临时存储的地方,读写性能高

在这里插入图片描述

1.1 缓存的作用和成本

在这里插入图片描述

简单的说,用缓存好处多,可以提升程序性能,缺点就是麻烦。

2. 添加 Redis 缓存

不用缓存的话,我们的程序获取数据是从数据库:

在这里插入图片描述

用了缓存之后,减少了数据库的压力:

在这里插入图片描述

原本需要从数据库中查的数据现在只需要去缓存中查了(第一次除外)。

案例:添加商户缓存

在这里插入图片描述

具体代码就不写了,这个流程还存在缓存穿透的问题,后续会说明。

这里收获了一个小编码技巧,在软件工程中有“单一出口原则”的说法,在方法中就只有一个return,但有时候用多个return会大幅简洁代码,省掉大量的else。

3. 缓存更新策略

数据存到缓存后,会有一个数据一致性的人问题:

假如数据库更新了,用户还是从缓存查数据,那么查到的就是缓存中的旧数据了。

所以需要有一种更行缓存的机制。

3.1 三种更新策略

  1. 内存淘汰

    Redis自带的机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。

  2. 超时剔除

    给数据添加TTL值,到期自动删除。下次查询时更新。

  3. 主动更新

    主动编写逻辑,在数据改变时,更行缓存。

在这里插入图片描述

3.1.1 主动更新策略

在这里插入图片描述

第一种就是主动编码,在数据库更新时更新缓存(编码多,麻烦)。

第二种就是将缓存和数据库整合为一个服务,由该服务来保证一致性(编码少,缺点就是这个服务质量不好保证,不好维护)。

第三种就是只操作缓存,由其他服务来将缓存同步到数据库,保证最终一致性。(完全不用关心数据库,但是当缓存没有同步时宕机了,数据将不会同步到数据库)。

第一二种都是强一致性,第三种是保证最终一致性。

方案一强一致且可控,用的较多。

在开始编码前,还有三个问题需要考虑:

  1. 更新数据库时,缓存是更新还是删除?

    更新:每次更新数据库都更新缓存

    删除:更新时删除缓存,查询时更新缓存

    一般是选择删除的方案,因为不是每次更新都要查询的,不查询,也就省去了添加缓存的步骤,避免浪费内存,性能。

  2. 如何保证同时成功或失败?

    单体系统:将缓存和数据库操作放在同一个事务中。

    分布式系统:利用TCC等分布式事务方案。

  3. 先操作缓存还是数据库?

    本质上是线程安全问题

    看两个例子,初始状态数据库,缓存都为10

    在这里插入图片描述

    先删除缓存,在数据库更新这个耗时操作完成之前,其他线程将未更新前的数据更新到缓存,然后原来的线程再更新数据库,造成数据的不一致。

    例2:在这里插入图片描述

    假设缓存在线程一查询时恰好失效(或没有该缓存),将会查询数据库,此时数据库是老数据,在将该老数据写入缓存时,线程二进行数据库更新和缓存删除操作,最后线程一写入老数据,造成数据不一致。

    **例二这种情况发生概率很小,因为写缓存的操作是微秒级的,短时间内不太可能完成数据库更新和缓存删除的操作。例一的情况发生的概率还是比较大的。**所以一般先操作数据库再删缓存并且给数据加上一个TTL过期时间,最大限度保证线程安全。

在这里插入图片描述

4. 缓存穿透

缓存穿透是指,客户端的请求数据在数据库和缓存都不存在,但是这些请求永远会打向数据库,给数据库造成压力。

4.1 常见两种解决办法

在这里插入图片描述

4.1.1 缓存空值

即使数据库不存在,我们也在redis中缓存一个空。

优点:简单,易维护。

缺点:

  • 额外的内存消耗(可加一个TTL缓解)
  • 可能造成短期的数据不一致(当缓存空值时,该数据正好被插入数据库,客户端请求缓存获得空)

4.1.2 布隆过滤器

用算法实现的记录所请求的数据是否存在的过滤器。底层像是BitMap。当数据库中不存在该数据时,会拒绝请求,存在时才放行。

存在误判可能,有可能出现数据不存在布隆过滤器却认为存在的情况,但是布隆过滤器若是认为数据不存在,那就一定不存在。

优点就是不占用额外内存,缺点就是实现复杂,存在误判可能。

使用方案一,简单高效

所以商户缓存的流程将被优化为:

在这里插入图片描述

4.2 小结

在这里插入图片描述

5. 缓存雪崩

缓存雪崩是指,在同一时间大量的key同时失效或redis服务宕机,导致大量请求到达数据库,给数据库造成过大压力。

解决方案比较常见的有:

  1. 给不同的key的TTL添加随机值

    不在同一时间过期,添加几分钟随机值(根据业务)。

  2. Redis集群,提高可用性

    避免redis服务全部挂掉。

  3. 缓存业务降级限流策略

    亡羊补牢,数据库再崩了就是更大的损失。

  4. 添加多级缓存

    浏览器缓存,nginx缓存等,即使redis崩了,还有其他缓存可查。

6. 缓存击穿

也被称为热点key问题,即 一个被高并发访问并且缓存重建业务复杂的key突然失效,大量请求给数据库带来巨大压力。

在这里插入图片描述

在缓存重建期间大量请求到达数据库。

6.1 常见解决方案两种

在这里插入图片描述

6.1.1 互斥锁

当缓存重建业务开始时,其他线程等待并重试,保证没有多余的请求到达数据库。该互斥锁的简单时间可以用setnx命令来实现。(在后续秒杀篇中会实现该分布式锁)

在这里插入图片描述

6.1.2 逻辑过期

用一个字段来保存过期时间,查询该缓存时判断缓存是否过期,如果过期则返回旧数据,并让另一个线程异步地重建缓存,重建缓存时需要加锁,其他线程来获取锁失败时,返回旧数据。

在这里插入图片描述

6.1.3 方案对比

在这里插入图片描述

方案一牺牲性能保证一致性,方案二则反之,根据业务需求选择。

6.2 小收获

若使用方案二,需要添加一个字段,如何添加呢?

在数据库添加是最笨的方式并且不合理。

在往常,我会新建一个DTO,然后添加该字段,使用该DTO对象进行数据传输。

在该课程中,我学会了如下方案:

  1. 使用MP的注解

    在这里插入图片描述

    但是这样就破坏了该对象的结构

  2. 创建一个新的类继承该类,然后添加新字段。(DTO)

  3. 创建一个新类,添加该字段,让原类继承该类。(和一一样,破坏了对象的结构)

  4. 创建一个新类,使用泛型,该类包含新的字段和一个泛型对象。(优雅)

    @Data
    public class RedisData<T> {
        private LocalDateTime expireTime;
        private T data;
    }
    

7. 缓存工具类封装

7.1 代码实现

在这里插入图片描述

简单说就是:1、3配合解决缓存穿透,2、4配合解决缓存击穿,四个方法,两个设置两个读取。

方法一:

public <R> void set(String key, R value, Long time, TimeUnit unit) {
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
}

方法二:

public <R> void setWithExpire(String key, R value, Long time, TimeUnit unit) {
    RedisData<R> redisData = new RedisData<>();
    redisData.setData(value);
    redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
    // 写入redis
    stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}

方法三:

public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Type type, boolean ignoreError, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
    R r;
    String key = keyPrefix + id;
    // 1. 从redis查询商铺缓存
    String json = stringRedisTemplate.opsForValue().get(key);
    // 2. 判断是否存在
    if (StrUtil.isNotBlank(json)) {
        // 3.1 存在
        r = JSONUtil.toBean(json, type, ignoreError);
        return r;
    }
    // 3.2 不存在
    // 判断是否是空值(防止缓存穿透)
    if (json != null) {
        return null;
    }
    // 从数据库查询
    r = dbFallback.apply(id);

    // 4. 返回
    if (r == null) {
        // 不存在 返回错误 将空值写入redis
        stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
        return null;
    }
    // 存在 写入redis
    this.set(key, r, time, unit);
    return r;
}

方法四:

public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Type type, boolean ignoreError, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
    R r;
    String key = keyPrefix + id;
    // 1. 从redis查询商铺缓存
    String json = stringRedisTemplate.opsForValue().get(key);
    // 2. 判断是否命中
    if (StrUtil.isBlank(json)) {
        // 3.1 未命中
        return null;
    }
    // 3.2 命中
    RedisData<R> redisData = JSONUtil.toBean(json, new TypeReference<RedisData<R>>() {}.getType(), ignoreError);
    r = JSONUtil.toBean(JSONUtil.toJsonStr(redisData.getData()), type, false);
    if (LocalDateTime.now().isBefore(redisData.getExpireTime())) {
        // 4. 判断是否过期
        // 4.1 未过期 返回
        return r;
    }
    // 4.2 过期
    // 5. 缓存重建
    // 5.1 获取互斥锁
    String lockKey = LOCK_SHOP_KEY + id;
    if (tryLock(lockKey)) {
        // 5.2 成功 开启另一线程重建
        CACHE_REBUILD_EXECUTOR.submit(() -> {
            try {
                // 查数据库
                R newR = dbFallback.apply(id);
                // 更新缓存
                setWithExpire(key, newR, time, unit);
                // 模拟业务延时
                Thread.sleep(200);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                unLock(lockKey);
            }
        });
    }
    // 5.3 失败
    // 无论是否成功都返回旧数据
    return r;
}

锁的简单实现

private boolean tryLock(String key) {
    Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
   	// 当flag为null时,若直接返回将会拆箱失败,报错,所以使用hutool的BooleanUtil工具保证拆箱成功(不用也可以,用Boolean.TRUE.equals方法)
    return BooleanUtil.isTrue(flag);
}

private void unLock(String key) {
    stringRedisTemplate.delete(key);
}

问题:刚才的代码中,锁的key不应该在代码中指定,而是作为参数传入。

7.2 小收获

收获到了很多编码小技巧:

7.2.1 时间处理

  1. 判断某个值是否过期

    if (LocalDateTime.now().isBefore(redisData.getExpireTime())) {
    
    }
    

    可以用LocalDateTime的isBefore和isAfter方法

  2. 续期

    使用LocalDateTime实例中的plusxxx方法,例如:

    LocalDateTime time = LocalDateTime.now().plusSeconds(2000);
    

    即可在当前时间的基础上添加2000秒得到一个新时间

  3. 单位转换

    刚刚续期的代码中plusSeconds,不一定每次都是Seconds,很不方便,那如何同一代码呢?

    使用TimeUtil中的方法:

    System.out.println(TimeUnit.MINUTES.toSeconds(1)); // 60
    System.out.println(TimeUnit.SECONDS.toMinutes(60)); // 1
    

    所以续期的代码可以统一为:

    public void setWithExpire(Long time, TimeUnit unit) {
        LocalDateTime.now().plusSeconds(unit.toSeconds(time));
    }
    

7.2.2 泛型使用

泛型类:

Class<T> IData { 
	private T data;
}

泛型方法:

<R> void test(R r) {
    // 定义泛型后,一般会在参数列表中接受泛型来指定泛型类型。
} 

泛型的JSON转换:

将JSON字符串转换为含有自定义泛型的对象。

假设有如下类:

@Data
@AllArgsConstructor
@NoArgsConstructor
class IData<T> {
    private Long id;
    private T data;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    private String name;
    private int age;
}

一般情况下,因为有类型擦除,我们不会直接.class,我们都是使用TypeRefrence的方式:

/**
* 用哪种json解析工具用法都差不多 都是传type、class这些
*/
<R> R json2Obj3(String json, TypeReference<R> type) throws JsonProcessingException {
    R data = JSONUtil.toBean(json, type, false);
    return data;
}

@Test
void testParse() throws JsonProcessingException {
    // 创建json字符串
    User user = new User("张三", 18);
    IData<User> iData = new IData<>(1L, user);
    String json = JSONUtil.toJsonStr(iData);
    // 解析
    IData<User> data = json2Obj3(json, new TypeReference<IData<User>>() {
    });
    // 能输出name才算成功
    System.out.println(data.getData().getName());
}

若是传class,那么就指定泛型的class对象:

<R> IData<R> json2Obj2(String json, Class<R> type) throws JsonProcessingException {
    IData<R> data = JSONUtil.toBean(json, new TypeReference<IData<R>>() {}, false);
    // 这个时候就需要转换两次了 因为在该方法中R就是R类型而不是User类型
    data.setData(JSONUtil.toBean(JSONUtil.toJsonStr(data.getData()), type));
    return data;
}

@Test
void test1() throws JsonProcessingException {
    // 创建json字符串
    User user = new User("张三", 18);
    IData<User> iData = new IData<>(1L, user);
    String json = JSONUtil.toJsonStr(iData);
    // 解析
    IData<User> data = json2Obj2(json, User.class);
    // 能输出name才算成功
    System.out.println(data.getData().getName());
}

类型探究:

<R, T> void testType(TypeReference<R> type1, Class<T> type2) {
    System.out.println(type1);
    System.out.println(type2);
    System.out.println(new TypeReference<R>() {
    });
    System.out.println(new TypeReference<T>() {
    });
}

@Test
void testType1() {
    testType(new TypeReference<User>() {
    }, User.class);
}

输出:

在这里插入图片描述

JSON转换小结:在泛型方法内,R就是R对象,不是其他的什么对象,只有将具体的类型传进来才能解析。

7.2.3 Function对象

Java8的新特性,可以像js一样丝滑:

void testFunc(String param, Function<String, String> function) {
    String result = function.apply(param);
    System.out.println("result: " + result);
}

@Test
void testFunc1() {
    testFunc("hello", s -> {
        System.out.println("in func: " + s + " world!");
        return s + " result";
    });
}

黑马点评中,Redis工具类的封装,当方法中需要查询数据库,但是各个业务的查询逻辑可能不同,于是就在参数列表中加上了Function对象用于传入该逻辑。当然也可以不用Function,直接接受结果,这个结果在方法之外获得。

Function还有很多用法:

function源码

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

andThen,compose返回值都是Function,可以链式编程,自由组合各种函数。

ion源码

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

andThen,compose返回值都是Function,可以链式编程,自由组合各种函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Redis实战—黑马点评(二)缓存篇 的相关文章

  • 惯导系列(二):滤波相关的算法

    前言 我又消失了一段时间 xff0c 这段时间研究了惯性导航有关的算法 xff0c 整理了不少博客 xff0c 字数比较多 xff0c 图片比较多 学到了很多知识 目录 前言 本节介绍 一 Mahony算法 1 1 PID控制算法 1 2
  • STM32 CAN 设置多个过滤器接收多ID方法

    1 标识符列表模式 xff0c 32位模式下 void MX CAN Init void 这里是实现了两个地址的接收 一个是用来接收广播信息 一个用来接收私有地址 如果想实现多个地址可以添加多个过滤器组 stm32103 有0 13 共14
  • linux下运行动态库问题 cannot open shared object file: No such file or directory

    如果动态库不在同一级目录下 xff0c 则需要将以上文件的目录加载到动态库搜索路径中 xff0c 设置的方式有以下几种 一 将动态库路径加入到LD LIBRARY PATH环境变量 1 在终端输入 xff1a export LD LIBRA
  • 几个串口通信协议的整理

    一 UART UART是一个大家族 xff0c 其包括了RS232 RS499 RS423 RS422和RS485等接口标准规范和总线标准规范 它们的主要区别在于其各自的电平范围不相同 嵌入式设备中常常使用到的是TTL TTL转RS232的
  • 单片机中断的过程

    1 根据响应的中断源的中断优先级 使相应的优先级状态触发器置1 xff1b 2 把当前程序计数器PC的内容压入堆栈 xff0c 保护断点 xff0c 寻找中断源 xff1b 3 执行硬件中断服务子程序调用 xff1b 4 清除相应的中断请求
  • Ruby学习札记(3)- Ruby中gem的安装与卸载

    Ruby 学习札记 3 Ruby 中 gem 的安装与卸载 在 Ruby 中有 gem 包这种概念 xff0c 类似 PHP 中的 pear xff0c 相当于一种插件 具体可以 Google 一下 xff08 1 xff09 查看已经安装
  • 【linux】ubuntu20.04 运行软件 提示找不到过时的库 libQtCore.so.4、libQtGui.so.4、libpng12.so.0

    先上结果 1 nxView运行起来 环境 硬件 xff1a Jetson Xavier NX 套件 系统 xff1a Ubuntu 20 04 软件 xff1a nxView 43 libQtCore so 4 解决 0 现象 运行软件提示
  • rtt相关问题总结

    1 总结RT Thread的启动流程 xff08 启动文件部分跳过 xff09 关中断 rt hw interrupt disable 板级初始化 xff1a 需在该函数内部进行系统堆的初始化 rt hw board init 打印 RT
  • FTP 客户端C实现

    使用 Socket 通信实现 FTP 客户端程序 FTP 概述 文件传输协议 xff08 FTP xff09 作为网络共享文件的传输协议 xff0c 在网络应用软件中具有广泛的应用 FTP的目标是提高文件的共享性和可靠高效地传送数据 在传输
  • Qt编写串口通信程序全程图文讲解

    说明 我们的编程环境是windows xp下 xff0c 在Qt Creator中进行 xff0c 如果在Linux下或直接用源码编写 xff0c 程序稍有不同 xff0c 请自己改动 在Qt中并没有特定的串口控制类 xff0c 现在大部分
  • VLC播放器调试经验总结

    一 前言 在使用VS学习VLC源码时 xff0c 可以打断点分析变量数据 xff0c 跟踪代码流程 xff0c 方便我们理解源码 但是在定位音视频卡顿 延时等疑难问题时 xff0c 这一招就不管用了 xff0c 因为打上断点就会导致实时计算
  • http协议如何解决粘包问题

    在讲粘包问题之前 xff0c 首先得明白这个包是应用层的数据包 当数据在传输层时 xff0c 由于TCP是面向字节流的 xff0c 所以它看到的数据是按照顺序一个个放在缓冲区中的 xff0c 而对于应用层而言 xff0c 看到的只是一连串的
  • ROS- 解决 sudo rosdep init和update 出现的错误

    大家在使用ROS时都需要执行sudo rosdep init 方法和rosdep update方法 但是在执行rosdep init时会提示如下错误 ERROR cannot download default sources list fr
  • 如何用MQTT网关快速接入阿里云IOT

    深圳市钡铼技术有限公司推出的BL102 xff0c 是采集西门子 xff0c 欧姆龙 xff0c 三菱 xff0c 台达 xff0c AB xff0c 施耐德等主流PLC及Modbus xff0c DT L645协议设备数据 xff0c 简
  • 闫刚 qgc模块mavlinklog实现过程

    mavlink log qml部分 这样logController就和LogDownloadController进行了绑定 AnalyzeView qml Rectangle span class token punctuation spa
  • 初识TVM--TVM的编译与安装

    TVM是什么 xff1f Apache incubating TVM is an open deep learning compiler stack for CPUs GPUs and specialized accelerators It
  • iOS上简单推送通知(Push Notification)的实现

    iOS上简单推送通知 xff08 Push Notification xff09 的实现 根据这篇很好的教程 xff08 http www raywenderlich com 3443 apple push notification ser
  • Android学习记录(十三) http之digest鉴权之填坑6.0。

    背景 xff1a android 6 0 1 的手机发现使用webdav下载文件实效 xff0c httpclient execute get的时候出现 xff1a CrashHandler java lang ArrayIndexOutO
  • 开源视频播放器IjkPlayer使用记录之(三)--播放视频从上次播放的时间点播放。

    方法 xff1a 1 在关闭视频的时候 xff0c 使用getCurrentPosition 获取当前的时间点 2 使用SharedPreferences记录当前的时间点 3 重新播放时 xff0c 获取该时间点 xff0c 使用seekt
  • 开源视频播放器IjkPlayer使用记录之(四)--多音轨的探路之旅

    前言 xff1a 在视频播放中 xff0c 我们经常会遇到多音轨的资源文件 xff0c 比如某个mkv文件同时支持英语 国语 xff0c 那么最好是能够进行音轨的切换 在IjkPlayer中并没有支持多音轨的代码 xff0c 所以在移植的过

随机推荐

  • KERAS-YOLOV3的代码走读

    KERAS YOLOV3的代码走读 GITHUB地址 xff1a https github com qqwweee keras yolo3 YOLOV3的论文中文翻译 xff1a https zhuanlan zhihu com p 349
  • KERAS-YOLOV3的数据增强

    前言 上篇KERAS YOLOV3的代码走读 https blog csdn net yangchengtest article details 80664415 有数据增强的内容没有看明白 这篇来介绍一下 简介 数据增强的方法主要有 xf
  • 基于KITTI数据集的KERAS-YOLOV3实践

    数据整理 KERAS YOLOV3的GITHUB地址 xff1a https github com yangchengtest keras yolo3 该项目支持的数据结构 xff1a One row for one image Row f
  • 图像语义分割 DEEPLAB V3+的代码走读

    概述 GITHUB路径 xff1a https github com tensorflow models tree master research deeplab 论文 xff1a https arxiv org abs 1802 0261
  • android dlib 交叉编译

    继续趟NDK的坑 DLIB使用C 43 43 11的标准 但是使用gnustl static的时候 xff0c 有些c 43 43 11的特性是无法使用的 由于NCNN的库使用的是静态库 xff0c OPENCV xff0c OPENBLA
  • ANDROID CMAKE DEBUG的记录

    android 如果使用DEBUG模式 xff0c CMAKE编译的SO是DEBUG版本的 xff0c 会造成性能下降 但是使用RELEASE编译的SO xff0c 使用DEBUG模式 xff0c JNI的速度不会变化 终于知道为什么 xf
  • window下使用vnc远程登录linux图形界面和运行应用程序 和odroid Xu4开发板的使用和视频接口VGA、DVI、HDMI的联系

    注 xff1a 自己曾经尝试过很多次使用VNC远程登录odroid XU4的开发板 xff0c 但是连接后均显示未解码的连接 xff0c 刚开始烧写的是odroid官方的ubuntu系统 xff0c 我靠 xff0c 就是因为烧写了这个坑爹
  • Putty的ppk文件转成Xshell使用的key文件

    Putty的ppk文件转成Xshell使用的key文件 今天同学给我一个Putty远程登录使用的ppk文件 xff08 即后缀名为ppk xff09 让我远程登录主机 xff0c 但是我用的是Xshell xff0c 导入这个ppk文件时
  • GD32串口读取GPS模块数据并解析经纬度教程-附完整代码和资料文件

    前言 xff1a 最近入手了个GPS模块 xff0c 手上只有GD32的开发板 网上有很多使用STM32库函数的GPS驱动程序 xff0c 但是基于GD32库函数读取GPS驱动的教程居然一篇都没有 所以为了学习GD32库的同学 xff0c
  • opencv 所有lib文件

    今天在vs上写一段代码 xff0c 编译后总是显示有无法解析的函数 xff0c 又不知道该函数在哪个lib文件中 xff0c 在百度上找了半天 xff0c 也没找到 已是就将所有lib库都添加到vs链接中 如下 xff1a opencv c
  • Java多线程(含生产者消费者模式详解)

    多线程 导航 多线程1 线程 进程 多线程概述2 创建线程 xff08 重点 xff09 2 1 继承Thread类 xff08 Thread类也实现了Runnable接口 xff09 2 2 实现Runnable接口 xff08 无消息返
  • Java网络编程(两种聊天室:TCP和UDP)

    网络编程 您的导航 网络编程网络编程基础知识一 网络编程三要素IP地址端口协议 二 IP地址与InetAddress类IP地址分类InetAddress类三 端口 xff08 Port xff09 与 InetSocketAddressIn
  • 免费发布一个网站(保姆级图文教程)

    利用GitHub Pages发布一个网页 第一步 xff1a 注册一个github账户 访问官网 点这两个都可以注册 根据提示输入一些信息 xff0c 然后创建账户 xff1a 然后你会收到一封邮件 xff0c 输入验证码或是打开邮件的验证
  • 修改键盘映射、交换按键

    修改键盘映射 交换按键 导航 修改键盘映射 交换按键写在前面一 创建配置文件二 修改键盘映射三 重启四 键位表 写在前面 这两天买了个黑爵的小键盘 xff0c del和ins键是同一个键 xff0c 通过fn来区分 xff08 我的笔记本电
  • Spring Cloud Gateway(黑马springcloud笔记)

    Gateway 目录 Gateway一 为什么需要网关二 gateway入门三 断言工厂四 过滤器工厂五 全局过滤1 实现2 过滤器执行顺序 六 跨域问题 一 为什么需要网关 不能让外部能够直接访问微服务 xff0c 而是需要通过网关访问
  • Docker(黑马spring cloud笔记)

    Docker 目录 Docker一 介绍和安装1 安装2 启动3 镜像加速 二 Docker基本操作1 镜像操作2 容器操作3 数据卷操作 三 Dockerfile1 镜像结构2 Dockerfile 四 Docker Compose1 安
  • RabbitMQ(黑马spring cloud笔记)

    MQ 目录 MQ一 同步通讯和异步通讯1 同步通讯2 异步通讯 二 RabbitMQ1 部署2 架构3 常见消息模型3 1 基本消息队列 xff08 Basic Queue xff09 3 2 工作消息队列 xff08 Work Queue
  • Redis实战—黑马点评(一) 登录篇

    Redis实战 黑马点评 xff08 一 xff09 登录篇 来自黑马的redis课程的笔记 黑马程序员Redis入门到实战教程 xff0c 深度透析redis底层原理 43 redis分布式锁 43 企业解决方案 43 黑马点评实战项目
  • tigerVNC的简单使用教程(CentOS的远程桌面连接)

    tigerVNC的简单使用教程 xff08 CentOS的远程桌面连接 xff09 1 环境和软件准备 1 CentOS 6 3下 root 64 localhost rpm q tigervnc tigervnc server tiger
  • Redis实战—黑马点评(二)缓存篇

    Redis实战 黑马点评 xff08 二 xff09 缓存篇 目录 Redis实战 黑马点评 xff08 二 xff09 缓存篇1 什么是缓存1 1 缓存的作用和成本 2 添加 Redis 缓存3 缓存更新策略3 1 三种更新策略3 1 1