写点什么

Redis- 技术专区 - 知识问题总结大全(上篇)

发布于: 2021 年 05 月 02 日
Redis-技术专区-知识问题总结大全(上篇)

Q1:为什么 Redis 能这么快


  • Redis 完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高,将数据存储在内存中,存取均不会受到硬盘 IO 的限制

  • Redis 使用单进程单线程模型的(K,V)数据库,因此其执行速度极快,另外单线程也能处理高并发请求,还可以避免频繁上下文切换和锁的竞争,由于单线程操作,也可以避免各种锁(减少数据一致性)的使用,进一步提高效率。

  • 数据结构简单,对数据操作也简单,Redis 不使用表,不会强制用户对各个关系进行关联,不会有复杂的关系限制,其存储结构就是键值对,类似于 HashMap,HashMap 最大的优点就是存取的时间复杂度为 O(1)

  • C 语言编写,效率更高

  • Redis 使用多路 I/O 复用模型,为非阻塞 IO(Epoll)

  • 有专门设计的 RESP 协议,数据轻量级协议,传输效率以及数据传输较少。

Q2:常见的 IO 模型有四种

  • 同步阻塞 IO(Blocking IO):即传统的 IO 模型。 -> BIO

  • 同步非阻塞 IO(Non-blocking IO):默认创建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被设置为 NONBLOCK。注意这里所说的 NIO 并非 Java 的 NIO(New IO)库。 -> NIO

  • IO 多路复用(IO Multiplexing):即经典的 Reactor 设计模式,有时也称为异步阻塞 IO,Java 中的 Selector 和 Linux 中的 epoll 都是这种模型。

  • 异步 IO(Asynchronous IO):即经典的 Proactor 设计模式,也称为异步非阻塞 IO

Redis 同步阻塞 IO
  • Redis 主程序(服务端 单线程)-> 多个客户端连接(真实情况是如开发人员连接 redis,程序 redispool 连接 redis),这每一个都对应着一个客户端,假设为 100 个客户端,其中一个进行交互时候,如果采用同步阻塞式,那么剩下的 99 个都需要原地等待,这势必是不科学的。

RedisI/O 多路复用模型
  • I/O 多路复用模型中,最重要的函数调用就是 epoll,该方法的能够同时监控多个文件描述符的可读可写情况,当其中的某些文件描述符可读或者可写时,epoll 方法就会返回可读以及可写的文件描述符个数,注:redis 默认使用的是优化的算法:epoll


| 操作名称 | select | poll | epoll |

| --- | --- | --- | --- |

| 操作方式 | 遍历 | 遍历 | 回调 |

| 底层实现 | 数组 | 链表 | 哈希表 |

| IO 效率 | 每次调用都进行线性遍历,时间复杂度为 O(n) | 每次调用都进行线性遍历,时间复杂度为 O(n) | 事件通知方式,每当 fd 就绪,系统注册的回调函数就会被调用,将就绪 fd 放到 readyList 里面,时间复杂度 O(1) |

| 最大连接数 | 1024(x86)或 2048(x64) | 无上限 | 无上限 |


所以我们可以说 Redis 是这样的服务端单线程毫无疑问,多客户端连接时候,如果客户端没有发起任何动作,则服务端会把其视为不活跃的 IO 流,将其挂起,当有真正的动作时,会通过回调的方式执行相应的事件

Q3:查询前缀 Key 的 key 信息


  • 笨办法:KEYS [pattern] 注意 key 很多的话,这样做肯定会出问题,造成 redis 崩溃

  • SCAN cursor [MATCH pattern] [COUNT count] 游标方式查找

Q4:如何实现异步队列


利用 redis-list 实现队列

假设场景:

A 服务生产数据 ;

B 服务消费数据,即可利用此种模型构造-生产消费者模型

使用 Redis 中的 List 作为队列

  1. 使用 BLPOP key [key...] timeout -> LPOP key [key ...] timeout: 阻塞直到队列有消息或者超时

(方案二:解决方案一中,拿数据的时,生产者尚未生产的情况)

pub/sub:主题订阅者模式,基于 reds 的终极方案,上文有介绍,基于发布/订阅模式

缺点:消息的发布是无状态的,无法保证可达。对于发布者来说,消息是“即发即失”的,此时如果某个消费者在生产者发布消息时下线,重新上线之后,是无法接收该消息的,要解决该问题需要使用专业的消息队列

Q5:Redis 持久化


持久化 : 就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种持久化方式:RDB(默认) 和 AOF。

RDB:

rdb 是 Redis DataBase 缩写

功能核心函数 rdbSave(生成 RDB 文件)和 rdbLoad(从文件加载内存)两个函数


(盗图)

RDB: 把当前进程数据生成快照文件保存到硬盘的过程。分为手动触发和自动触发

手动触发:
  • save (不推荐,阻塞严重)

  • bgsave -> (save 的优化版,微秒级阻塞)

  • 工作原理

  • 会从当前父进程 fork 一个子进程,然后生成 rdb 文件

  • 缺点:

  • 频率低,无法做到实时持久化

  • shutdown -> 关闭服务时,如果没有配置 AOF,则会使用 bgsave 持久化数据

AOF:

Append-only file 缩写,AOF 文件存储的也是 RESP 协议

(盗图)

当执行服务器(定时)任务或者函数时->flushAppendOnlyFile 函数都会被调用,函数执行两个工作

AOF 写入保存:
  • write:根据条件,将 aof_buf 中的缓存写入到 AOF 文件

  • save:根据条件,调用 fsync fdatasync 函数,将 AOF 文件保存到磁盘中。

  • 存储结构:

  • 内容是 redis 通讯协议(RESP)格式的命令文本存储

  • 原理:

  • 相当于存储了 redis 的执行命令(类似 mysql 的 binlog 日志),数据的完整性和一致性更高。

  • 比较

  • aof 文件比 rdb 更新频率高,aof 比 rdb 更安全

  • rdb 性能更好


正确停止 redis 服务,应该基于连接命令,加再上 shutdown -> 否则数据持久化会出现问题

Q6:Redis 通讯协议(RESP)


  • Redis 即 Remote Dictionary Server (远程字典服务)

  • Redis 的协议规范是 Redis Serialization Protocol (Redis 序列化协议)

  • RESP 是 redis 客户端和服务端之前使用的一种通讯协议

  • RESP 的特点:实现简单、快速解析、可读性好

  • RESP 也用于 AOF 操作备份和执行恢复的备份文件

协议如下:

客户端以规定格式的形式发送命令给服务器,set keykey value 协议翻译如下:

* 3   //表示以下有几组命令(作为3个指令的总和)set+key+value$ 3   //表示命令长度是3(set)SET$6       //表示长度是6(keykey)keykey   $5       //表示长度是5(value)value
复制代码


服务器在执行最后一条命令后,返回结果,返回格式如下:


For Simple Strings the first byte of the reply is "+" 回复

For Errors the first byte of the reply is "-" 错误

For Integers the first byte of the reply is ":" 整数

For Bulk Strings the first byte of the reply is "$" 字符串

For Arrays the first byte of the reply is "*" 数组


伪造 6379 redis-服务端,监听 jedis 发送的协议内容

public class SocketApp {
/*** * 监听 6379 传输的数据 * JVM端口需要进行设置 */ public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(6379); Socket redis = serverSocket.accept(); byte[] result = new byte[2048]; redis.getInputStream().read(result); System.out.println(new String(result)); } catch (Exception e) { e.printStackTrace(); } }}
// jedis连接-发送命令public class App { public static void main(String[] args){ Jedis jedis = new Jedis("127.0.0.1"); jedis.set("key", "This is value."); jedis.close(); }}
复制代码

监听命令内容如下:

*3$3SET$3key$14
复制代码

Q7:Redis 常用架构有哪些

主从复制

主从复制,此种结构可以考虑关闭 slave 的持久化,只让主数据库进行持久化,另外可以通过读写分离,缓解主服务器压力。

哨兵

Redis sentinel 是一个分布式系统中监控 Redis 主从服务器,并在主服务器下线时自动进行故障转移。

其中三个特性:
  • 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。

  • 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。

  • 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作。

特点:
  1. 保证高可用

  2. 监控各个节点

  3. 自动故障迁移缺点:主从模式,切换需要时间丢数据没有解决 master 写的压力

集群


Redis3.0 之后版本支持 redis-cluster 集群,Redis-Cluster 采用无中心结构,每个节点保存数据和整个集群状态,每个节点都和其他所有节点连接。

特点:
  • 无中心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。

  • 数据按照 slot 存储分布在多个节点,节点间数据共享,可动态调整数据分布。

  • 可扩展性,可线性扩展到 16384 个 slot 槽,节点可动态添加或删除。

  • 高可用性,部分节点不可用时,集群仍可用。通过增加 Slave 做备份数据副本

  • 实现故障自动 failover,节点之间通过 gossip 协议交换状态信息,用投票机制完成 slave 到 master 的角色提升。

缺点:
  • 资源隔离性较差,容易出现相互影响的情况。

  • 数据通过异步复制,不保证数据的强一致性

Q8:Redis 集群 hash 槽算法

  • 分片按照某种规则去划分数据,分散存储在多个节点上。通过将数据分到多个 Redis 服务器上,来减轻单个 Redis 服务器的压力。

  • 一致性 Hash 算法既然要将数据进行分片,那么通常的做法就是获取节点的 Hash 值,然后根据节点数求模,但这样的方法有明显的弊端,当 Redis 节点数需要动态增加或减少的时候,会造成大量的 Key 无法被命中,以及重新维护和迁移数据操作。

  • Redis 集群中引入了 hash 槽算法。该算法对 2^32 取模,将 Hash 值空间组成虚拟的圆环,整个圆环按顺时针方向组织,每个节点依次为 0、1、2...2^32-1,之后将每个服务器进行 Hash 运算,确定服务器在这个 Hash 环上的地址,确定了服务器地址后,对数据使用同样的 Hash 算法,将数据定位到特定的 Redis 服务器上。如果定位到的地方没有 Redis 服务器实例,则继续顺时针寻找,找到的第一台服务器即该数据最终的服务器位置。

hash 环的数据倾斜问题


hash 环在服务器节点很少的时候,容易遇到服务器节点不均匀的问题,这会造成数据倾斜,数据倾斜指的是被缓存的对象大部分集中在 Redis 集群的其中一台或几台服务器上。

如上图,一致性 Hash 算法运算后的数据大部分被存放在 A 节点上,而 B 节点只存放了少量的数据,久而久之 A 节点将被撑爆引入虚拟节点


例如上图:将 NodeA 和 NodeB 两个节点分为 Node A#1-A#3 NodeB#1-B#3

Q9:缓存穿透和缓存雪崩和缓存击穿

缓存穿透

一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如 DB)。一些恶意的请求会故意查询不存在的 key,请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。

如何避免

1:查询结果为空的情况进行缓存,缓存时间设置短点,或者该 key 对应的数据 insert 了之后清理缓存。

2:对一定不存在的 key 进行过滤。可以把所有的可能存在的 key 放到一个大的 Bitmap 中,查询时通过该 bitmap 过滤。

3:由于请求参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(Bloomfilter)或压缩 filter 提前进行拦截,不合法就不让这个请求进入到数据库层。

缓存雪崩

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。

如何避免

1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个 key 只允许一个线程查询数据和写缓存,其他线程等待。

2:做二级缓存,A1 为原始缓存,A2 为拷贝缓存,A1 失效时,可以访问 A2,A1 缓存失效时间设置为短期,A2 设置为长期

3:不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

4:启用限流策略,尽量避免数据库被干掉

5:热点数据不过期机制

缓存击穿

概念一个存在的 key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB,造成瞬时 DB 请求量大、压力骤增。

A. 在访问 key 之前,采用 SETNX(set if not exists)来设置另一个短期 key 来锁住当前 key 的访问,访问结束再删除该短期 key.

B. 服务层处理 - 方法加锁 + 双重校验:

// 锁-实例
private Lock lock = new ReentrantLock();
public String getProductImgUrlById(String id){
    // 获取缓存
    String product = jedisClient.get(PRODUCT_KEY + id);
    if (null == product) {
        // 如果没有获取锁等待3秒,SECONDS代表:秒
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    // 获取锁后再查一次,查到了直接返回结果
                    product = jedisClient.get(PRODUCT_KEY + id);
                    if (null == product) {
                        // ....
                    }
                    return product;
                } catch (Exception e) {
                    product = jedisClient.get(PRODUCT_KEY + id);
                } finally {
                    // 释放锁(成功、失败都必须释放,如果是lock.tryLock()方法会一直阻塞在这)
                    lock.unlock();
                }
            } else {
                product = jedisClient.get(PRODUCT_KEY + id);
            }
        } catch (InterruptedException e) {
            product = jedisClient.get(PRODUCT_KEY + id);
        }
    }
    return product;
}


| 解释 | 基础解决方案 |

| --- | --- | --- |

| 缓存穿透 | 访问一个不存在的 key,缓存不起作用,请求会穿透到 DB,流量大时 DB 会挂掉

| 1.采用布隆过滤器,使用一个足够大的 bitmap,用于存储可能访问的 key,不存在的 key 直接被过滤;

| 2.访问 key 未在 DB 查询到值,也将空值写进缓存,但可以设置较短过期时间 |

| 缓存雪崩 | 大量的 key 设置了相同的过期时间,导致在缓存在同一时刻全部失效,造成瞬时 DB 请求量大、压力骤增,引起雪崩 | 可以给缓存设置过期时间时加上一个随机值时间,使得每个 key 的过期时间分布开来,不会集中在同一时刻失效 |

| 缓存击穿 | 一个存在的 key,在缓存过期的一刻,同时有大量的请求,这些请求都会击穿到 DB,造成瞬时 DB 请求量大、压力骤增 | 分布式锁或分布式队列、双级缓存机制、热点 Key 不过过期的机制 |

Q10:缓存与数据库双写一致

  • 如果仅仅是读数据,没有此类问题

  • 如果是新增数据,也没有此类问题

  • 当数据需要更新时,如何保证缓存与数据库的双写一致性?

三种更新策略:

  • 先更新数据库,再更新缓存

  • 先删除缓存,再更新数据库

  • 先更新数据库,再删除缓存

  • 方案一:并发的时候,执行顺序无法保证,可能 A 先更新数据库,但 B 后更新数据库但先更新缓存

加锁的话,确实可以避免,但这样吞吐量会下降,可以根据业务场景考虑

  • 方案二:该方案会导致不一致的原因是。同时有一个请求 A 进行更新操作,另一个请求 B 进行查询操作。那么会出现如下情形:

(1)请求 A 进行写操作,删除缓存

(2)请求 B 查询发现缓存不存在

(3)请求 B 去数据库查询得到旧值

(4)请求 B 将旧值写入缓存

(5)请求 A 将新值写入数据库因此采用:采用延时双删策略 即进入逻辑就删除 Key,执行完操

作,延时再删除 key

  • 方案三:更新数据库 - 删除缓存 可能出现问题的场景:

(1)缓存刚好失效

(2)请求 A 查询数据库,得一个旧值

(3)请求 B 将新值写入数据库

(4)请求 B 删除缓存

(5)请求 A 将查到的旧值写入缓存

先天条件要求:请求第二步的读取操作耗时要大于更新操作,条件较为苛刻,但如果真的发生怎么处理?

A. 键设置合理的过期时间

B. 异步延时删除 key

(延时双删机制)

Q11:何保证 Redis 中的数据都是热点数据

  • 通过手工或者主动方式,去加载热点数据

  • Redis 有其自己的数据淘汰策略:Redis 内存数据集大小上升到一定大小的时候,会施行数据淘汰策略。


Redis 提供 6 种数据淘汰策略:

对设置过期时间的进行操作清除

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用数据淘汰

  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

对未设置过期时间的进行操作清除

  • allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  • no-enviction:禁止驱逐数据,直接返回错误

Q12:Redis 的并发竞争问题

多线程同时操作统一 Key 的解决办法:

  1. Redis 为单进程单线程模式,采用队列模式将并发访问变为串行访问。

  2. Redis 本身没有锁的概念,Redis 对于多个客户端连接并不存在竞争,

  3. Jedis 客户端对 Redis 进行并发访问时会发生连接超时、数据转换错误、阻塞、客户端关闭连接等问题。

对此有多种解决方法:

  1. 条件允许的情况下,请使用 redis 自带的 incr 命令,decr 命令

  2. 乐观锁方式

  3. 事务方式(悲观锁方式)

watch priceget price $price$price = $price + 10multi  set price $priceexec
复制代码
  1. 操作同一个 key 的时候,进行加锁处理(悲观锁方式)

  2. 场景允许的话,使用 setnx 实现(乐观锁方式)

  3. Lua 脚本操作机制原子化

Q13:Redis 回收进程如何工作的? Redis 回收使用的是什么算法?

Q11 中提到过,当所需内存超过配置的最大内存时,redis 会启用数据淘汰规则

默认规则是:maxmemory-policy noeviction

即只允许读,无法继续添加 key,因此常需要配置淘汰策略,比如 LRU 算法

LRU算法最为精典的实现,就是HashMap+Double-LinkedList,时间复杂度为O(1)

Q14:Redis 大批量增加数据

使用管道模式,运行的命令如下所示:

cat data.txt | redis-cli --pipe

data.txt 文本:

SET Key0 Value0
SET Key1 Value1
...
SET KeyN ValueN

# 或者是 RESP协议内容 - 注意文件编码!!!

*8
$5
HMSET
$8
person:1
$2
id
$1
1

这将产生类似于这样的输出:

All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 1000000

redis-cli 实用程序还将确保只将从 Redis 实例收到的错误重定向到标准输出

演示:

cat redis_commands.txt | redis-cli -h 192.168.127.130 -p 6379 [-a "password"] -n 0 --pipe

All data transferred.Waiting for the last reply...
Last reply received from server.
errors:0,replies:10000000

mysql数据快速导入到redis 实战: 文件详情:可见 Redis-通道实战

博文:https://www.cnblogs.com/tommy-huang/p/4703514.html

# 1.准备一个table
create database  if not exists `test`;
use `test`;
CREATE TABLE `person` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(200) NOT NULL,
  `age` varchar(200) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

# 2.插入七八万条数据

# 3.SQL查询,将其转化为 RESP协议命令   Linux 版本: -> 不要在windows环境试,没啥意义

SELECT CONCAT(
   "*8\r\n",
   '/pre>,LENGTH(redis_cmd),'\r\n',redis_cmd,'\r\n',
   '/pre>,LENGTH(redis_key),'\r\n',redis_key,'\r\n',
   '/pre>,LENGTH(hkey1),'\r\n',hkey1,'\r\n','/pre>,LENGTH(hval1),'\r\n',hval1,'\r\n',
   '/pre>,LENGTH(hkey2),'\r\n',hkey2,'\r\n','/pre>,LENGTH(hval2),'\r\n',hval2,'\r\n',
   '/pre>,LENGTH(hkey3),'\r\n',hkey3,'\r\n','/pre>,LENGTH(hval3),'\r\n',hval3,'\r'
)FROM(
   SELECT 'HMSET' AS redis_cmd,
   concat_ws(':','person', id) AS redis_key,
   'id' AS hkey1, id AS hval1,
   'name' AS hkey2, name AS hval2,
   'age' AS hkey3, age AS hval3
   From person
)AS t

# 4.如果用的就是线上数据库+线上Linux -> 把sql存到 order.sql,进行执行

mysql -uroot -p123456 test --default-character-set=utf8 --skip-column-names --raw < order.sql  
|
redis-cli -h 127.0.0.1 -p 6379 -a 123456 --pipe

# 5.本地数据库+线上redis
利用Navicat导出数据 -> data.txt,清理格式(导出来的数据里面各种 " 符号),全局替换即可
cat data.txt | redis-cli -h 127.0.0.1 -p 6379 -a 123456  --pipe
81921条数据 一瞬间导入完成
注意事项:RESP协议要求,不要有莫名其妙的字符,注意文件类型是Unix编码类型

Q15:Lua 脚本相关

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延

  • 原子操作,redis 会将整个脚本作为一个整体执行,中间不会被其他命令插入。因此在编写脚本的过程中无需担心会出现竞态条件,无需使用事务

  • 复用,客户端发送的脚本会永久存在 redis 中,这样,其他客户端可以复用这一脚本而不需要使用代码完成相同的逻辑

@RequestMapping("/testLua")public String testLua () {    String key   = "mylock";    String value = "xxxxxxxxxxxxxxx";    //        if redis.call('get', KEYS[1]) == ARGV[1]    //            then    //                return redis.call('del', KEYS[1])    //        else    //            return 0    //        end
// lua脚本,用来释放分布式锁 - 如果使用的较多,可以封装到文件中, 再进行调用 String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; Object eval = jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value)); return eval.toString();}
复制代码

Q16:延申:布隆过滤器

redis 4.X 以上 提供 布隆过滤器插件:centos 中安装 redis 插件 bloom-filter:https://blog.csdn.net/u013030276/article/details/88350641

语法:[bf.add key options]

语法:[bf.exists key options]


注意: redis 布隆过滤器提供的是 最大内存 512M,2 亿数据,万分之一的误差率

Q17:性能相关 - Redis 慢查询分析

  • redis 命令会放在 redis 内置队列中,然后主线程一个个执行,因此 其中一个 命令执行时间过长,会造成成批量的阻塞

  • 命令:slowlog get 获取慢查询记录 slowlog len 获取慢查询记录量(慢查询队列是先进先出的,因此新的值在满载的时候,旧的会出去)

Redis 慢查询 -> 执行阶段耗时过长

  • conf文件设置:slowlog-low-slower-than 10000 -> 10000 微秒,10 毫秒 (默认)0 -> 记录所有命令-1 -> 不记录命令 slow-max-len 存放的最大条数

  • 慢查询导致原因: value 值过大,解决办法:数据分段(更细颗粒度存放数据)

Q18:如何提高 Redis 处理效率? 基于 Jedis 的批量操作 Pipelined

Jedis jedis = new Jedis("127.0.0.1", 6379);Pipeline pipelined = jedis.pipelined();for (String key : keys) {    pipelined.del(key);}pipelined.sync();jedis.close();// pipelined 实际是封装过一层的指令集 ->  实际应用的还是单条指令,但是节省了网络传输开销(服务端到Redis环境的网络开销)
复制代码


发布于: 2021 年 05 月 02 日阅读数: 49
用户头像

我们始于迷惘,终于更高水平的迷惘。 2020.03.25 加入

🏆 【酷爱计算机技术、醉心开发编程、喜爱健身运动、热衷悬疑推理的”极客狂人“】 🏅 【Java技术领域,MySQL技术领域,APM全链路追踪技术及微服务、分布式方向的技术体系等】 🤝未来我们希望可以共同进步🤝

评论

发布
暂无评论
Redis-技术专区-知识问题总结大全(上篇)