redis的简单与集群搭建
wptr33 2024-12-31 15:02 21 浏览
Redis是什么?
是开源免费用c语言编写的单线程高性能的(key-value形式)内存数据库,基于内存运行并支持持久化的nosql数据库
作用
主要用来做缓存,单不仅仅是做缓存,比如:redis的计数器生成分布式的唯一主键,redis实现分布式锁、队列、会话缓存等等。
redis数据类型及api操作(http://redisdoc.com/)
key
keys * 查询所有的键
scan 0 match * count 1 查询所有键从下角标0开始,返回1个键数据的结果集
exists key 判断某个key是否存在
move key db 当前库就没有了,到指定的库中去了
expire key 为给定的key设置过期时间
ttl key 查看还有多少时间过期 -1表示永不过期 -2表示已过期
type key 查看key是什么类型
1.string
一般使用场景:单值缓存、对象缓存、分布式锁、计数器、websession共享、分布式全局序列号
string是redis最基本的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个redis中字符串value最多可以是512M
set key value 设置key value
get key 查看当前key的值
del key 删除key
append key value 如果key存在,则在指定的key末尾添加,如果key存在则类似set
strlen key 返回此key的长度
以下几个命令只有在key值为数字的时候才能正常操作
incr key 为执定key的值加一
decr key 为指定key的值减一
incrby key 数值 为指定key的值增加数值
decrby key 数值 为指定key的值减数值
getrange key 0(开始位置) -1(结束位置) 获取指定区间范围内的值,类似between......and的关系 (0 -1)表示全部
setrange key 1(开始位置,从哪里开始设置) 具体值 设置(替换)指定区间范围内的值
setex 键 秒值 真实值 设置带过期时间的key,动态设置。
setnx key value 只有在 key 不存在时设置 key 的值。
mset key1 value key2 value 同时设置一个或多个 key-value 对。
mget key1 key 2 获取所有(一个或多个)给定 key 的值。
msetnx key1 value key2 value 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。
getset key value 将给定 key 的值设为 value ,并返回 key 的旧值(old value)。
2.list
一般使用场景:公众号消息流、
它是一个字符串链表,left、right都可以插入添加;如果键不存在,创建新的链表;如果键已存在,新增内容;如果值全移除,对应的键也就消失了。链表的操作无论是头和尾效率都极高,但假如是对中间元素进行操作,效率就很惨淡了。
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。它的底层实际是个链表
lpush key value1 value2 将一个或多个值加入到列表头部
rpush key value1 value2 将一个或多个值加入到列表底部
lrange key start end 获取列表指定范围的元素 (0 -1)表示全部
lpop key 移出并获取列表第一个元素
rpop key 移出并获取列表最后一个元素
lindex key index 通过索引获取列表中的元素
llen 获取列表长度
lrem key 0(数量) 值,表示删除全部给定的值。零个就是全部值 从left往right删除指定数量个值等于指定值的元素,返回的值为实际删除的数量
ltrim key start(从哪里开始截) end(结束位置) 截取指定索引区间的元素,格式是ltrim list的key 起始索引 结束索引
3.set
一般使用场景:微信抽奖程序、微信点赞、收藏标签、关注模型
Redis的Set是string类型的无序,不能重复的集合。
sadd key value1 value 2 向集合中添加一个或多个成员
smembers key 返回集合中所有成员
sismembers key member 判断member元素是否是集合key的成员
scard key 获取集合里面的元素个数
srem key value 删除集合中指定元素
srandmember key 数值 从set集合里面随机取出指定数值个元素 如果超过最大数量就全部取出,
spop key 随机移出并返回集合中某个元素
smove key1 key2 value(key1中某个值) 作用是将key1中执定的值移除 加入到key2集合中
sdiff key1 key2 在第一个set里面而不在后面任何一个set里面的项(差集)
sinter key1 key2 在第一个set和第二个set中都有的 (交集)
sunion key1 key2 两个集合所有元素(并集)
4.hash
一般使用场景:对象缓存、电商购物车、
Redis hash 是一个键值对集合。Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。
kv模式不变,但v是一个键值对
类似Java里面的Map
优点:同类数据归类整合存储,方便数据管理;相对于string消耗的内存与cpu更小;相比于string更省存储空间
缺点:过期功能不能用于field(里层)只能作用于key(外层)上;redis集群架构下不适合大规模使用
hset key (key value) 向hash表中添加一个元素
hget key key 向hash表中获取一个元素
hmset key key1 value1 key2 value2 key3 value3 向集合中添加一个或多个元素
hmget key key1 key2 key3 向集合中获取一个或多个元素
hgetall key 获取在hash列表中指定key的所有字段和值
hdel key key1 key2 删除一个或多个hash字段
hlen key 获取hash表中字段数量
hexits key key 查看hash表中,指定key(字段)是否存在
hkeys key 获取指定hash表中所有key(字段)
hvals key 获取指定hash表中所有value(值)
hincrdy key key1 数量(整数) 执定hash表中某个字段加 数量 ,和incr一个意思
hincrdyfloat key key1 数量(浮点数,小数) 执定hash表中某个字段加 数量 ,和incr一个意思
hsetnx key key1 value1 与hset作用一样,区别是不存在赋值,存在了无效。
5.zset
一般使用场景:排行榜
Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zadd key score 值 score 值 向集合中添加一个或多个成员
zrange key 0 -1 表示所有 返回指定集合中所有value
zrange key 0 -1 withscores 返回指定集合中所有value和score
zrangebyscore key 开始score 结束score 返回指定score间的值
zrem key score某个对应值(value),可以是多个值 删除元素
zcard key 获取集合中元素个数
zcount key 开始score 结束score 获取分数区间内元素个数
zrank key vlaue 获取value在zset中的下标位置(根据score排序)
zscore key value 按照值获得对应的分数
conf文件的注意点
1.bind可以使用0.0.0.0或者机器的IP地址
2.protected-mode在使用bind或者设置了密码时是无效的
3.protected-mode默认是开启的,不能被外网访问的
4.当redis以守护进程方式运行时,redis默认会把pid写入redis.pid文件,可以使用pidfile指定(只适用于linux版本,window不适用)
5.logfile指定日志文件的位置
6.databases是数据库的数目,默认是16
redis的单线程和多性能
redis是单线程吗:redis的单线程只要是指redis的网络io和键值对读写是由一个线程来完成的,这也是redis对外提供键值存储服务的主要流程。但是redis的其他功能,如持久化、异步删除、数据同步等都是由额外的线程执行的。
redis单线程为什么还能这么快:因为它所有的数据都在内存中,所有的运算都是内存级别的运算,并且单线程避免了线程切换的损耗问题。正是由于redis是单线程所以要小心使用redis指令,对于那些耗时的指令(如keys),需要谨慎使用,不小心会导致redis卡顿
redis单线程如何处理多个并发客户端的连接:redis的io多路复用,redis利用epoll来实现io多路复用,将连接信息和事件放到队列总,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
持久化RDB(丢失数据较多)
- 原理:redis会单独创建(fork)一个与当前线程一样的子线程来进行持久化,这子线程的所有数据都和原线程一模一样,会先将数据希尔到一个临时的文件中,等到持久化结束了再用这个临时文件替换上次持久化好的文件,整个过程中,主线程不进行任何的io操作,这就确保了极高的性能。
- 持久化文件存放的位置:由dbfilename指定
- 什么时候fork子进程,或者什么时候触发rdb持久化机制:
shutdow时,没有开启aof会触发
配置文件中默认的快照配置
执行命令save或者bgsave,save是主线程操作会阻塞,save只管保存,其余不管,bgsave是新fork线程后台操作
执行flushall命令(清空所有数据),无意义
- 怎么开启rdb持久化
配置文件中配置save命令:save 900 1;表示900秒由一条数据更新会触发rdb的持久化机制
可以写多个save条件
当配置主从复制集群时,是关闭不了rdb的
持久化aof(丢失数据较少)
- 原理:将redis的操作日志以追加的方式写入文件,读操作是不记录的
- 怎么开启aof:配置文件中appendonly 设置为yes;命令行:config set appendonly yes,然后还是需要修改配置文件
- 持久化文件在哪里:配置文件中 appendfilename指定
- 触发机制(配置文件中appendfsync指定):no表示等操作系统进行数据缓存同步到磁盘(快,没保障);always同步持久化,每次发送数据变更时,立即记录到磁盘(慢,安全);everysec表示美妙同步一次(默认值,快,可能会丢失一秒以内的数据)
- aof文件中日志解析:*2代表是2组命令;$6,$3代表是这个命令有6个,3个字符组成(命令长度);
- aof的重写机制:当aof增长到一定大小时候redis可以调用bgreweiteaof命令对日志文件进行重写;当文件大小的增长率大于配置项时自动开启重写,是配置文件中auto-aof-rewrite-percentage指定表示超过源文件大小的百分之多少,案例:50;当文件大小大于配置项的大小时自动开启重写,是配置文件中的auto-aof-rewrite-min-size指定,案例:64mb
rdb与aof是可以同时开启时,默认使用aof持久化,所以切换rdb与aof时,最好通过命令行的方式启动aof,这样不会有数据丢失
混合持久化
- 4.0版本的混合持久化是默认关闭的,可以通过配置文件中的aof-use-rdb-preamble来控制,yes代表开启,no表示禁用,5.0之后默认开启
- 混合持久化是通过bgrewriteaof完成的,当开启混合持久化时,fork的子线程先将共享的内存副本全量的以rdb的方式写入aof文件,然后再将重写缓存区的增量命令以aof 的方式写入文件,完成后通知主线程更新统计信息,将新的aof文件替换旧的aof文件
- 优点:加载速度快,同时结合aof 的方式保存,数据丢失少
- 缺点:兼容性差,一旦开启混合持久化,在4.0版本之前都不识别该aof文件,阅读性差。
缓存穿透问题
缓存穿透是指查询一个一定不存在的数据,由于缓存不命中,并且出于容错考虑, 如果从存储层查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
可能造成原因:
1.业务代码自身问题2.恶意攻击。爬虫等等
危害
对底层数据源压力过大,有些底层数据源不具备高并发性。 例如mysql一般来说单台能够扛1000-QPS就已经很不错了
解决方案
1.缓存空对象
缺点:可能会缓存大量空对象,当缓存过期时仍然会有缓存穿透的问题
public class NullValueResultDO implements Serializable{
private static final long serialVersionUID = -6550539547145486005L;
}
public class UserManager {
UserDAO userDAO;
LocalCache localCache;
public UserDO getUser(String userNick) {
Object object = localCache.get(userNick);
if(object != null) {
if(object instanceof NullValueResultDO) {
return null;
}
return (UserDO)object;
} else {
User user = userDAO.getUser(userNick);
if(user != null) {
localCache.put(userNick,user);
} else {
localCache.put(userNick, new NullValueResultDO());
}
return user;
}
}
}
2.布隆过滤器
缺点:添加数据时,布隆过滤器也需要添加数据,但是不能删除数据,所以要定时维护更新布隆过滤里面的数据
1)、Google布隆过滤器的缺点
基于JVM内存的一种布隆过滤器重启即失效本地内存无法用在分布式场景不支持大数据量存储
2)、Redis布隆过滤器
可扩展性Bloom过滤器:一旦Bloom过滤器达到容量,就会在其上创建一个新的过滤器不存在重启即失效或者定时任务维护的成本:基于Google实现的布隆过滤器需要启动之后初始化布隆过滤器缺点:需要网络IO,性能比Google布隆过滤器低
缓存击穿.热点key重建缓存问题
缓存击穿是指缓存中没有但数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力
我们知道,使用缓存,如果获取不到,才会去数据库里获取。但是如果是热点 key,访问量非常的大,数据库在重建缓存的时候,会出现很多线程同时重建的情况。因为高并发导致的大量热点的 key 在重建还没完成的时候,不断被重建缓存的过程,由于大量线程都去做重建缓存工作,导致服务器拖慢的情况。
解决方案
1.互斥锁
第一次获取缓存的时候,加一个锁,然后查询数据库,接着是重建缓存。这个时候,另外一个请求又过来获取缓存,发现有个锁,这个时候就去等待,之后都是一次等待的过程,直到重建完成以后,锁解除后再次获取缓存命中。
public String getKey(String key){
String value = redis.get(key);
if(value == null){
String mutexKey = "mutex:key:"+key; //设置互斥锁的key
if(redis.set(mutexKey,"1","ex 180","nx")){ //给这个key上一把锁,ex表示只有一个线程能执行,过期时间为180秒
value = db.get(key);
redis.set(key,value);
redis.delete(mutexKety);
}else{
// 其他的线程休息100毫秒后重试
Thread.sleep(100);
getKey(key);
}
}
return value;
}
互斥锁的优点是思路非常简单,具有一致性,但是互斥锁也有一定的问题,就是大量线程在等待的问题。存在死锁的可能性。
缓存雪崩问题
缓存雪崩是指机器宕机或在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬时压力过重雪崩。
1:在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
2:做二级缓存,A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
3:不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
4:如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
redis的集群
- 主从架构
原理:若为master配置了一个slave,不管这个slave是否是第一次连接上master,它都会发送psync命令给master请求复制数据。master收到命令后,会在后台进行数据持久化通过bgsave上次最新的rdb快照文件,持久化时,master会继续接受客户端请求,他会把可能修改的数据及的请求缓存在内存中。当持久化进行完毕之后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后在加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master与slave之间的连接由于某些原因断开时,slave能过自动连接master,如果master收到了多个salve并发连接请求,他只会进行一次持久化,而不是一个连接一次,然后再问吧这份持久化数据发送给多个并发连接的slave。
解决主从风暴的问题方法:配置阶梯式主从架构
配置从节点实例,复制conf文件:
port 6389
pidfile /var/run/redis_6380.pid #把pid进程号写入pidfile配置文件
logfile "6380.log"
dir /usr/local/redis-5.0.3/data/6389 #指定数据存放目录
#配置主从复制
replicaof 127.0.0.1:6379 #从本机的6379redis实例复制数据,redis5.0之前用slaveof
replica-read-only yes #配置从节点只读
#启动从节点
redis-server redis.conf
#连接从节点
redis-cli -p 6380
#测试在6379实例上写数据,6380实例是否能及时同步数据
同步骤配置下一个从节点
- 哨兵模式的主从架构
原理:哨兵是特殊的redis服务,不提供读写服务,主要来健康redis实例的节点,哨兵架构下的client端第一次从哨兵中找出redis的主节点,后续就直接访问redis的主节点,不是每次都通过哨兵进行代理访问redis的主节点,当redis的主节点变化时,哨兵会第一时间感知到,并且将新的redis节点通知给client端(这里的redis的client端一般都实现了订阅功能,订阅哨兵发布的节点变动消息)
配置搭建:
复制一份sentinel.conf文件
修改相关配置:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
#sentinel monitor <master-name> <ip> <redis-port> <quorum>
#quorum是数字,指明当前有多少sentinel认为master失效时(一般是 总数/2+1),master才算真正失效
sentinel monitor mymaster 192.168.0.60:6379 2
注释掉bind IP那行代码
protected-mode no 关闭自我保护模式,开启只有本机可以访问
启动哨兵实例
redis-sentinel redis-sentinel-26379.conf
查看sentinel的info信息
redis-cli -p 26379
info
可以看到sentinel的info里已经识别出redis的主从
同步骤可以配置其余的哨兵
- redis高可用集群cluster(最少需要三个主节点)
原理:redis cluster将所有数据划分为16384个槽位,每个节点负责其中一部分槽位,槽位的信息存储在每个节点中。
当集群的客户端连接集群时,他会得到一份集群的槽位配置信息并将其缓存在客户端本地,这样当客户端要查找某个key时,可以直接定位目标节点,同时因为信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
跳转重定向:当客户端向一个错误的节点发出指令,该节点会发现指令大key所在的槽位并不归自己管理,这时她会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连接这个节点获取数据,客户端收到指令后处理跳转到正确的节点上操作,还会同步更新纠正本地的槽位映射表缓存,后续所有可以将使用新的槽位表映射表。
配置:
daemonize yes 后台启动
protect-mode no 关闭保护模式,开启的话本机才可以访问redis
注释掉bind
redis集群至少需要三个master节点,我们搭建三个master并且给每个master搭建一个slave节点
在第一台机器的/usr/local下创建文件夹redis-cluster,然后在其下面分别创建2个文件夹
mkdir -p /usr/local/redis-cluster
mkdir 8001 8004
把之前的redis.conf配置文件copy到8001下,修改内容:
daemonize yes
port 8001
dir /usr/local/redis-cluster/8001/ (指定数据文件位置,必须要指定不同的目录位置,不然会丢失数据)
cluster-enabled yes 启动集群模式
cluster-config-file nodes-8001.conf集群节点信息文件
cluster-node-timeout 5000
protected-mode no
appendonly yes
如果要设置密码需要如下配置
requirepass mima redis的访问密码
masterauth mima 集群的访问密码,与上面一致
上述步骤配置其他节点,端口号不同
#启动从节点
redis-server redis.conf
构建集群命令(最后的数字是一个主节点对应多少个从节点的意思),只需构建一次,服务器重启不需要重启构建
redis5.0使用/usr/local/bin/redis-cli --cluster create 192.168.0.104:7000 192.168.0.104:7001 192.168.0.104:7002 192.168.0.104:7003 192.168.0.104:7004 192.168.0.104:7005 --cluster-replicas 1
相关推荐
- 如果手机显示无SIM卡,到底是什么意思呢?
-
一般手机显示无可用SIM卡,可能是如下原因造成的,大家可以了解下,并且进行解决。第一个,SIM卡未正确插入:我们需要检查SIM卡是否已正确插入手机。如果SIM卡没有完全插入,或者插反了,手机可能会显...
- 赶紧设置!工信部提醒设置手机SIM卡密码
-
【赶紧设置!工信部提醒设置手机SIM卡密码】平安法治2020近日,针对网友反映的手机失窃导致信息泄露事件,国家工信部立即组织核查处理,要求电信企业加强安全防护,并提醒手机用户设置SIM卡密码。...
- 手机突然显示无SIM卡?这样做就能恢复~
-
大家有没经历过明明SIM卡在卡槽里放得好好的,手机却突然显示无SIM卡的状况?没有了SIM卡手机就失去了灵魂,打电话、上网的功能都不能用了。这到底是怎么一回事儿?让小翼帮你来解答~什么是SIM卡?SI...
- SK电讯首尔门店遭“围攻”,SIM卡更换服务陷混乱
-
据yna.co.kr网4月28日报道,28日,韩国SK电讯推出免费SIM卡更换服务以应对网络安全风险,首尔光化门店门前清晨8点便已排起长队。原定于上午10点开始的服务因企业员工需求激增,提前至9点启动...
- Springboot特性、快速创建SpringBoot应用、Starter简介
-
SpringBoot基础本章我们将揭开SpringBoot的神秘面纱。...
- Springboot2的熔断、限流和降级讲解
-
高可用的三大利器是熔断、限流和降级。它们都是在分布式系统中用于保障系统稳定性和可用性的重要策略。熔断(CircuitBreaker):熔断是一种防止故障扩散的机制。当一个服务出现故障或超时,熔断器会...
- Spring Cloud 全面解析:分布式系统开发的魔法工具包
-
SpringCloud全面解析:分布式系统开发的魔法工具包SpringCloud是Java开发者构建分布式系统的得力助手。它基于SpringBoot,为开发者提供了强大的微服务架构支持...
- 真香!GitHub开源SpringCloud Alibaba全解(全彩版)先到先得!
-
SpringCloudAliababa简介SpringCloudAlibaba是阿里巴巴集团开源的一套微服务架构解决方案。...
- 聊聊langchain4j-spring的1.0.0-beta版本的更新
-
序本文主要研究一下langchain4j-spring的1.0.0-beta版本的更新1.0.0-beta1...
- Java异步编程(5种异步实现方式详解)
-
Java面试经常会问到:异步操作?什么是异步?与同步有什么区别?Java异步的是如何实现?有哪些异步实现方式?下面我一一来详解异步@mikechen什么是异步?...
- 全部开源的快速开发平台-开源字节
-
《硕宇精选》专注于探索、发现、分享开源技术应用和优质开源项目。本期推荐的优质项目是开源字节是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。该平台基于SpringBoot+MyBat...
- Trip.com launches 700 products in 15 countries
-
OnlinetravelagencyTrip.comhasofferedmorethan700newoverseasproducts,visiting15countries...
- Spring事务
-
使用Spring事务Spring事务介绍Spring事务的特点:1.多种事务API,Spring事务都可兼容;2.程序接入简单;3.与已有的Spring框架集成。...
- 牛刀小试——五分钟入门Spring Boot
-
万物皆可HelloWorld在一个程序员的眼里,万物皆可HelloWorld。SpringBoot当然也不例外。下面一起来完成我们的第一个SpringBoot程序。...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
-
- 如果手机显示无SIM卡,到底是什么意思呢?
- 赶紧设置!工信部提醒设置手机SIM卡密码
- 手机突然显示无SIM卡?这样做就能恢复~
- SK电讯首尔门店遭“围攻”,SIM卡更换服务陷混乱
- Spring新闻汇总:Framework、Data、Security、Integration和Modulith发布里程碑版本
- Springboot特性、快速创建SpringBoot应用、Starter简介
- Springboot2的熔断、限流和降级讲解
- Spring Cloud 全面解析:分布式系统开发的魔法工具包
- 真香!GitHub开源SpringCloud Alibaba全解(全彩版)先到先得!
- 聊聊langchain4j-spring的1.0.0-beta版本的更新
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mysql max (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)