Windows Redis
装置
链接: https://pan.baidu.com/s/1MJnz… 提取码: 2c6w 复制这段内容后关上百度网盘手机App,操作更不便哦
无脑下一步即可
应用
呈现谬误:
creating server tcp listening socket 127.0.0.1:6379: bind No error
解决方案:
- redis-cli.exe
- shutdown
- exit
- redis-server.exe redis.windows.conf
启动:redis-server.exe redis.windows.conf
客户端启动:redis-cli.exe (不批改配置的话默认即可)
redis-cli.exe -h 127.0.0.1 -p 6379 -a password
根本文件阐明
可执行文件 | 作用阐明 |
---|---|
redis-server | redis服务 |
redis-cli | redis命令行工具 |
redis-benchmark | 基准性能测试工具 |
redis-check-aof | AOF长久化文件检测和修复工具 |
redis-check-dump | RDB长久化文件检测和修复工具 |
redis-sentinel | 启动哨兵 |
redis-trib | cluster集群构建工具 |
根底命令
命令 | 阐明 |
---|---|
keys * | redis容许含糊查问key 有3个通配符 *、?、[] |
del key | 删除key |
exists kxm | 判断是否存在 |
expire key 20 | 设置过期工夫 – 秒 |
pexpire key 20000 | 设置过期工夫 – 毫秒 |
move kxm 2 | 挪动key到指定地位库中 2号库 |
persist key | 移除过期工夫,key将会永恒存在 胜利设置返回1 否则返回0 |
pttl key | 以毫秒为单位返回 key 的残余的过期工夫 |
ttl key | 以秒为单位,返回给定 key 的残余生存工夫 |
randomkey | 从以后数据库中随机返回一个 key |
rename key newkxy | 更改key的名字,如果反复了会笼罩 |
renamenx kxm key | 仅当 newkey 不存在时,将 key 改名为 newkey |
type key | 返回 key 所贮存的值的类型 |
select 0 | 抉择第一个库 |
ping | 返回PONG 示意连贯失常 |
quit | 敞开以后连贯 |
字符串命令
命令 | 阐明 |
---|---|
set key aaa | 设置指定 key 的值 |
get key | 获取指定 key 的值 |
getrange key 0 1 | 返回 key 中字符串值的子字符 蕴含 0 和 1 蕴含关系 |
getset key aaaaaaaa | 将给定 key 的值设为 value ,并返回 key 的旧值(old value) |
mget key kxm | 获取所有(一个或多个)给定 key 的值 |
setex test 5 “this is my test” | 将值 value 关联到 key ,并将 key 的过期工夫设为 seconds (以秒为单位) |
setnx test test | 只有在 key 不存在时设置 key 的值 (用于分布式锁) |
strlen test | 返回 key 所贮存的字符串值的长度 |
mset key1 “1” key2 “2” | 同时设置一个或多个 key-value 对 |
msetnx key3 “a” key2 “b” | 同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在 其中一个失败则全副失败 |
incr key | 将 key 中贮存的数字值增一 -> key的值 比方为 数字类型字符串 返回减少后的后果 |
incrby num 1000 | 将 key 中贮存的数字值增指定的值 -> key的值 比方为 数字类型字符串 返回减少后的后果 |
decr key | 同 -> 减一 |
decrby num 500 | 同 -> 减指定值 |
append key 1123123 | 如果 key 曾经存在并且是一个字符串, APPEND 命令将指定的 value 追加到该 key 原来值(value)的开端 返回字符串长度 |
哈希(Hash)命令
命令 | 阐明 |
---|---|
hdel key field1 [field2] | 删除一个或多个哈希表字段 |
hexistskey field | 查看哈希表 key 中,指定的字段是否存在 |
hget key field | 获取存储在哈希表中指定字段的值 |
hgetall key | 获取在哈希表中指定 key 的所有字段和值 |
hincrby hash yeary 1 | 为哈希表 key 中的指定字段的整数值加上增量 increment |
hkeys hash | 获取所有哈希表中的字段 |
hlen hash | 获取哈希表中字段的数量 |
hmget hash name year | 获取所有给定字段的值 |
hmset hash name “i am kxm” year 24 | 同时将多个 field-value (域-值)对设置到哈希表 key 中 |
hset hash name kxm | 将哈希表 key 中的字段 field 的值设为 value |
hsetnx key field value | 只有在字段 field 不存在时,设置哈希表字段的值 |
hvals hash | 获取哈希表中所有值 |
hexists hash name | 是否存在 |
编码: field value 值由 ziplist 及 hashtable 两种编码格局
字段较少的时候采纳ziplist,字段较多的时候会变成hashtable编码
列表(List)命令
Redis列表是简略的字符串列表,依照插入程序排序。你能够增加一个元素到列表的头部(右边)或者尾部(左边)
一个列表最多能够蕴含 232 – 1 个元素 (4294967295, 每个列表超过40亿个元素)
容量 -> 汇合,有序汇合也是如此
命令 | 阐明 |
---|---|
lpush list php | 将一个值插入到列表头部 返回列表长度 |
lindex list 0 | 通过索引获取列表中的元素 |
blpop key1 [key2 ] timeout | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到期待超时或发现可弹出元素为止 |
brpop key1 [key2 ] timeout | 移出并获取列表的最初一个元素, 如果列表没有元素会阻塞列表直到期待超时或发现可弹出元素为止 |
linsert list before 3 4 | 在值 3 前插入 4 前即为顶 |
linsert list after 4 5 | 在值4 后插入5 |
llen list | 获取列表长度 |
lpop list | 移出并获取列表的第一个元素 |
lpush list c++ c | 将一个或多个值插入到列表头部 |
lrange list 0 1 | 获取列表指定范畴内的元素 蕴含0和1 -1 代表所有 (lrange list 0 -1) |
lrem list 1 c | 移除list 汇合中 值为 c 的 一个元素, 1 代表count 即移除几个 |
lset list 0 “this is update” | 通过索引设置列表元素的值 |
ltrim list 1 5 | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 |
rpop list | 移除列表的最初一个元素,返回值为移除的元素 |
rpush list newvalue3 | 从底部增加新值 |
rpoplpush list list2 | 转移列表的数据 |
汇合(Set)命令
Set 是 String 类型的无序汇合。汇合成员是惟一的,这就意味着汇合中不能呈现反复的数据
命令 | 阐明 | ||
---|---|---|---|
sadd set java php c c++ python | 向汇合增加一个或多个成员 | ||
scard set | 获取汇合的成员数 | ||
sdiff key1 [key2] | 返回给定所有汇合的差集 数学含意差集 | ||
sdiffstore curr set newset (sdiffstore destination key1 [key2]) | 把set和 newset的差值存储到curr中 | ||
sinter set newset | 返回给定所有汇合的交加 | ||
sinterstore curr set newset (sinterstoredestination key1 [key2]) | 同 | ||
sismember set c# | 判断 member 元素是否是汇合 key 的成员 | ||
smembers set | 返回汇合中的所有成员 | ||
srandmember set 2 | 随机抽取两个key (抽奖实现美滋滋) | ||
smove set newtest java (smove source destination member) | 将 member 元素从 source 汇合挪动到 destination 汇合 | ||
sunion set newset | 返回所有给定汇合的并集 | ||
srem set java | 删除 | ||
spop set | 从汇合中弹出一个元素 | ||
sdiff \ | sinter \ | sunion | 操作:汇合间运算:差集 |
有序汇合(sorted set)命令
Redis 有序汇合和汇合一样也是string类型元素的汇合,且不容许反复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为汇合中的成员进行从小到大的排序。
有序汇合的成员是惟一的,但分数(score)却能够反复。
命令 | 阐明 |
---|---|
zadd sort 1 java 2 python | 向有序汇合增加一个或多个成员,或者更新已存在成员的分数 |
zcard sort | 获取有序汇合的成员数 |
zcount sort 0 1 | 计算在有序汇合中指定区间分数的成员数 |
zincrby sort 500 java | 有序汇合中对指定成员的分数加上增量 increment |
zscore sort java | 返回有序集中,成员的分数值 |
zrange sort 0 -1 | 获取指定序号的值,-1代表全副 |
zrangebyscore sort 0 5 | 分数合乎范畴的值 |
zrangebyscore sort 0 5 limit 0 1 | 分页 limit 0代表页码,1代表每页显示数量 |
zrem sort java | 移除元素 |
zremrangebyrank sort 0 1 | 依照排名范畴删除元素 |
zremrangebyscore sort 0 1 | 依照分数范畴删除元素 |
zrevrank sort c# | 返回有序汇合中指定成员的排名,有序集成员按分数值递加(从大到小)排序 |
公布订阅
开启两个客户端
A客户端订阅频道: subscribe redisChat (频道名字为 redisChat)
B客户端公布内容: publish redisChat “Hello, this is my wor” (内容是 hello….)
A客户端即为主动收到内容, 原理图如下:
命令 | 阐明 |
---|---|
pubsub channels | 查看以后redis 有多少个频道 |
pubsub numsub chat1 | 查看某个频道的订阅者数量 |
unsubscrible chat1 | 退订指定频道 |
psubscribe java.* | 订阅一组频道 |
Redis 事务
Redis 事务能够一次执行多个命令, 并且带有以下三个重要的保障:
- 批量操作在发送 EXEC 命令前被放入队列缓存
- 收到 EXEC 命令后进入事务执行,事务中任意命令执行失败,其余的命令仍然被执行
- 在事务执行过程,其余客户端提交的命令申请不会插入到事务执行命令序列中
一个事务从开始到执行会经验以下三个阶段:
- 开始事务
- 命令入队
- 执行事务
留神:redis事务和数据库事务不同,redis事务出错后最大的特点是,一剩下的命令会继续执行,二出错的数据不会回滚
命令 | 阐明 |
---|---|
multi | 标记一个事务开始 |
exec | 执行事务 |
discard | 事务开始后输出命令入队过程中,停止事务 |
watch key | 监督一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其余命令所改变,那么事务将被打断 |
unwatch | 勾销 WATCH 命令对所有 key 的监督 |
Redis 服务器命令
命令 | 阐明 |
---|---|
flushall | 删除所有数据库的所有key |
flushdb | 删除以后数据库的所有key |
save | 同步保留数据到硬盘 |
Redis 数据备份与复原
Redis SAVE 命令用于创立以后数据库的备份
如果须要复原数据,只需将备份文件 (dump.rdb) 挪动到 redis 装置目录并启动服务即可。获取 redis 目录能够应用 CONFIG 命令
Redis 性能测试
redis 性能测试的根本命令如下:
redis目录执行:redis-benchmark [option] [option value]
// 会返回各种操作的性能报告(100连贯,10000申请)
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
// 100个字节作为value值进行压测
redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
Java Redis
Jedis
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
Jedis配置
############# redis Config #############
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=120.79.88.17
# Redis服务器连贯端口
spring.redis.port=6379
# Redis服务器连贯明码(默认为空)
spring.redis.password=123456
# 连接池中的最大闲暇连贯
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小闲暇连贯
spring.redis.jedis.pool.min-idle=0
JedisConfig
@Configuration
public class JedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.min-idle}")
private Integer minIdle;
@Bean
public JedisPool redisPoolFactory(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(3000L);
int timeOut = 3;
return new JedisPool(jedisPoolConfig, host, port, timeOut, password);
}
}
根底应用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = KerwinBootsApplication.class)
public class ApplicationTests {
@Resource
JedisPool jedisPool;
@Test
public void testJedis () {
Jedis jedis = jedisPool.getResource();
jedis.set("year", String.valueOf(24));
}
}
SpringBoot redis staeter RedisTemplate
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis 2.X 更换为commons-pool2 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
############# redis Config #############
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=120.79.88.17
# Redis服务器连贯端口
spring.redis.port=6379
# Redis服务器连贯明码(默认为空)
spring.redis.password=123456
# 连接池最大连接数(应用负值示意没有限度)
spring.redis.jedis.pool.max-active=200
# 连接池最大阻塞等待时间(应用负值示意没有限度)
spring.redis.jedis.pool.max-wait=1000ms
# 连接池中的最大闲暇连贯
spring.redis.jedis.pool.max-idle=8
# 连接池中的最小闲暇连贯
spring.redis.jedis.pool.min-idle=0
# 连贯超时工夫(毫秒)
spring.redis.timeout=1000ms
// Cache注解配置类
@Configuration
public class RedisCacheConfig {
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认策略,未配置的 key 会应用这个
this.getRedisCacheConfigurationWithTtl(15),
// 指定 key 策略
this.getRedisCacheConfigurationMap()
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
redisCacheConfigurationMap.put("redisTest", this.getRedisCacheConfigurationWithTtl(15));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
// RedisAutoConfiguration
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采纳String的序列化形式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采纳String的序列化形式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化形式采纳jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化形式采纳jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
// 根底应用
@Resource
RedisTemplate<String,Object> redisTemplate;
redisTemplate.opsForList().rightPush("user:1:order", dataList.get(3).get("key").toString());
// 注解应用
@Cacheable(value = "redisTest")
public TestBean testBeanAnnotation () {}
Redis应用场景
类型 | 实用场景 |
---|---|
String | 缓存,限流,计数器,分布式锁,分布式session |
Hash | 存储用户信息,用户主页访问量,组合查问 |
List | 微博关注人时间轴列表,简略队列 |
Set | 赞,踩,标签,好友关系 |
Zset | 排行榜 |
或者简略音讯队列,公布订阅施行音讯零碎等等
String – 缓存
// 1.Cacheable 注解
// controller 调用 service 时主动判断有没有缓存,如果有就走redis缓存间接返回,如果没有则数据库而后主动放入redis中
// 能够设置过期工夫,KEY生成规定 (KEY生成规定基于 参数的toString办法)
@Cacheable(value = "yearScore", key = "#yearScore")
@Override
public List<YearScore> findBy (YearScore yearScore) {}
// 2.手动用缓存
if (redis.hasKey(???) {
return ....
}
redis.set(find from DB)...
String – 限流 | 计数器
// 注:这只是一个最简略的Demo 效率低,耗时旧,但外围就是这个意思
// 计数器也是利用单线程incr...等等
@RequestMapping("/redisLimit")
public String testRedisLimit(String uuid) {
if (jedis.get(uuid) != null) {
Long incr = jedis.incr(uuid);
if (incr > MAX_LIMITTIME) {
return "Failure Request";
} else {
return "Success Request";
}
}
// 设置Key 起始申请为1,10秒过期 -> 理论写法必定封装过,这里就是轻易一写
jedis.set(uuid, "1");
jedis.expire(uuid, 10);
return "Success Request";
}
String – 分布式锁 (重点)
/***
* 外围思路:
* 分布式服务调用时setnx,返回1证实拿到,用完了删除,返回0就证实被锁,等...
* SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
* EX second:设置键的过期工夫为second秒
* PX millisecond:设置键的过期工夫为millisecond毫秒
* NX:只在键不存在时,才对键进行设置操作
* XX:只在键曾经存在时,才对键进行设置操作
*
* 1.设置锁
* A. 分布式业务对立Key
* B. 设置Key过期工夫
* C. 设置随机value,利用ThreadLocal 线程公有存储随机value
*
* 2.业务解决
* ...
*
* 3.解锁
* A. 无论如何必须解锁 - finally (超时工夫和finally 双保障)
* B. 要比照是否是本线程上的锁,所以要比照线程公有value和存储的value是否统一(防止把他人加锁的货色删除了)
*/
@RequestMapping("/redisLock")
public String testRedisLock () {
try {
for(;;){
RedisContextHolder.clear();
String uuid = UUID.randomUUID().toString();
String set = jedis.set(KEY, uuid, "NX", "EX", 1000);
RedisContextHolder.setValue(uuid);
if (!"OK".equals(set)) {
// 进入循环-能够短时间休眠
} else {
// 获取锁胜利 Do Somethings....
break;
}
}
} finally {
// 解锁 -> 保障获取数据,判断统一以及删除数据三个操作是原子的, 因而如下写法是不合乎的
/*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {
jedis.del(KEY);
}*/
// 正确姿态 -> 应用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(RedisContextHolder.getValue()));
}
return "锁创立胜利-业务解决胜利";
}
String – 分布式Session(重点)
// 1.首先明确为什么须要分布式session -> nginx负载平衡 散发到不同的Tomcat,即便利用IP散发,能够利用request获取session,然而其中一个挂了,怎么办?? 所以须要分布式session
留神了解其中的区别 A服务-用户校验服务 B服务-业务层
状况A:
A,B 服务单机部署:
cookie:登录胜利后,存储信息到cookie,A服务本身通过request设置session,获取session,B服务通过惟一key或者userid 查询数据库获取用户信息
cookie+redis:登录胜利后,存储信息到cookie,A服务本身通过request设置session,获取session,B服务通过惟一key或者userid 查问redis获取用户信息
状况B:
A服务多节点部署,B服务多节点部署
B服务获取用户信息的形式其实是不重要的,必然要查,要么从数据库,要么从cookie
A服务:登录胜利后,存储惟一key到cookie, 与此同时,A服务须要把session(KEY-UserInfo)同步到redis中,不能存在单纯的request(否则nginx散发到另一个服务器就完犊子了)
官网实现:
spring-session-data-redis
有一个内置拦截器,拦挡request,session通过redis交互,一般应用代码仍然是request.getSession.... 然而实际上这个session的值曾经被该组件拦挡,通过redis进行同步了
List 简略队列-栈
// 说白了利用redis - list数据结构 反对从左从右push,从左从右pop
@Component
public class RedisStack {
@Resource
Jedis jedis;
private final static String KEY = "Stack";
/** push **/
public void push (String value) {
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
return jedis.lpop(KEY);
}
}
@Component
public class RedisQueue {
@Resource
JedisPool jedisPool;
private final static String KEY = "Queue";
/** push **/
public void push (String value) {
Jedis jedis = jedisPool.getResource();
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
Jedis jedis = jedisPool.getResource();
return jedis.rpop(KEY);
}
}
List 社交类APP – 好友列表
依据工夫显示好友,多个好友列表,求交加,并集 显示独特好友等等...
疑难:难道大厂真的用redis存这些数据吗???多大的量啊... 我集体认为理论是数据库存用户id,而后用算法去解决,更省空间
Set 抽奖 | 好友关系(合,并,交加)
// 插入key 及用户id
sadd cat:1 001 002 003 004 005 006
// 返回抽奖参加人数
scard cat:1
// 随机抽取一个
srandmember cat:1
// 随机抽取一人,并移除
spop cat:1
Zset 排行榜
依据分数实现有序列表
微博热搜:每点击一次 分数+1 即可
--- 不必数据库目标是因为防止order by 进行全表扫描
常见面试题
Q1:为什么Redis能这么快
1.Redis齐全基于内存,绝大部分申请是纯正的内存操作,执行效率高。
2.Redis应用单过程单线程模型的(K,V)数据库,将数据存储在内存中,存取均不会受到硬盘IO的限度,因而其执行速度极快,另外单线程也能解决高并发申请,还能够防止频繁上下文切换和锁的竞争,同时因为单线程操作,也能够防止各种锁的应用,进一步提高效率
3.数据结构简略,对数据操作也简略,Redis不应用表,不会强制用户对各个关系进行关联,不会有简单的关系限度,其存储构造就是键值对,相似于HashMap,HashMap最大的长处就是存取的工夫复杂度为O(1)
5.C语言编写,效率更高
6.Redis应用多路I/O复用模型,为非阻塞IO
7.有专门设计的RESP协定
针对第四点进行阐明 ->
常见的IO模型有四种:
- 同步阻塞IO(Blocking IO):即传统的IO模型。
- 同步非阻塞IO(Non-blocking IO):默认创立的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。留神这里所说的NIO并非Java的NIO(New IO)库。
- IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
- 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO
同步异步,阻塞非阻塞的概念:
假如Redis采纳同步阻塞IO:
Redis主程序(服务端 单线程)-> 多个客户端连贯(真实情况是如开发人员连贯redis,程序 redispool连贯redis),这每一个都对应着一个客户端,假如为100个客户端,其中一个进行交互时候,如果采纳同步阻塞式,那么剩下的99个都须要原地期待,这势必是不迷信的。
IO多路复用
Redis 采纳 I/O 多路复用模型
I/O 多路复用模型中,最重要的函数调用就是
select
,该办法的可能同时监控多个文件描述符的可读可写状况,当其中的某些文件描述符可读或者可写时,select
办法就会返回可读以及可写的文件描述符个数
注:redis默认应用的是更加优化的算法:epoll
select poll epoll 操作形式 遍历 遍历 回调 底层实现 数组 链表 哈希表 IO效率 每次调用都进行线性遍历,工夫复杂度为O(n) 每次调用都进行线性遍历,工夫复杂度为O(n) 事件告诉形式,每当fd就绪,零碎注册的回调函数就会被调用,将就绪fd放到readyList外面,工夫复杂度O(1) 最大连接数 1024(x86)或2048(x64) 无下限 无下限 所以咱们能够说Redis是这样的:服务端单线程毫无疑问,多客户端连贯时候,如果客户端没有发动任何动作,则服务端会把其视为不沉闷的IO流,将其挂起,当有真正的动作时,会通过回调的形式执行相应的事件
Q2:从海量Key里查问出某一个固定前缀的Key
A. 笨办法:KEYS [pattern] 留神key很多的话,这样做必定会出问题,造成redis解体
B. SCAN cursor [MATCH pattern] [COUNT count] 游标形式查找
Q3:如何通过Redis实现分布式锁
见上文
Q4:如何实现异步队列
上文说到利用 redis-list 实现队列
假如场景:A服务生产数据 - B服务生产数据,即可利用此种模型结构-生产消费者模型
1. 应用Redis中的List作为队列
2.应用BLPOP key [key...] timeout -> LPOP key [key ...] timeout:阻塞直到队列有音讯或者超时
(计划二:解决方案一中,拿数据的时,生产者尚未生产的状况)
3.pub/sub:主题订阅者模式
基于reds的终极计划,上文有介绍,基于公布/订阅模式
毛病:音讯的公布是无状态的,无奈保障可达。对于发布者来说,音讯是“即发即失”的,此时如果某个消费者在生产者公布音讯时下线,从新上线之后,是无奈接管该音讯的,要解决该问题须要应用业余的音讯队列
Q5:Redis反对的数据类型?
见上文
Q6:什么是Redis长久化?Redis有哪几种长久化形式?优缺点是什么?
长久化就是把内存的数据写到磁盘中去,避免服务宕机了内存数据失落。
Redis 提供了两种长久化形式:RDB(默认) 和AOF
RDB:
rdb是Redis DataBase缩写
性能外围函数rdbSave(生成RDB文件)和rdbLoad(从文件加载内存)两个函数
RDB: 把以后过程数据生成快照文件保留到硬盘的过程。分为手动触发和主动触发
手动触发 -> save (不举荐,阻塞重大) bgsave -> (save的优化版,微秒级阻塞)
shutdowm 敞开服务时,如果没有配置AOF,则会应用bgsave长久化数据
bgsave – 工作原理
会从以后父过程fork一个子过程,而后生成rdb文件
毛病:频率低,无奈做到实时长久化
AOF:
Aof是Append-only file缩写,AOF文件存储的也是RESP协定
每当执行服务器(定时)工作或者函数时flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作
aof写入保留:
WRITE:依据条件,将 aof_buf 中的缓存写入到 AOF 文件
SAVE:依据条件,调用 fsync 或 fdatasync 函数,将 AOF 文件保留到磁盘中。
存储构造:
内容是redis通信协定(RESP )格局的命令文本存储
原理:
相当于存储了redis的执行命令(相似mysql的sql语句日志),数据的完整性和一致性更高
比拟:
1、aof文件比rdb更新频率高
2、aof比rdb更平安
3、rdb性能更好
PS:正确进行redis服务 应该基于连贯命令 加再上 shutdown -> 否则数据长久化会呈现问题
Q7:redis通信协定(RESP)
Redis 即 REmote Dictionary Server (近程字典服务);
而Redis的协定标准是 Redis Serialization Protocol (Redis序列化协定)
RESP 是redis客户端和服务端之前应用的一种通信协定;
RESP 的特点:实现简略、疾速解析、可读性好
协定如下:
客户端以规定格局的模式发送命令给服务器
set key value 协定翻译如下: * 3 -> 示意以下有几组命令 $ 3 -> 表示命令长度是3 SET $6 -> 示意长度是6 keykey $5 -> 示意长度是5 value 残缺即: * 3 $ 3 SET $6 keykey $5 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
$3
SET
$3
key
$14
Q8:redis架构有哪些
单节点
主从复制
Master-slave 主从赋值,此种构造能够思考敞开master的长久化,只让从数据库进行长久化,另外能够通过读写拆散,缓解主服务器压力
哨兵
Redis sentinel 是一个分布式系统中监控 redis 主从服务器,并在主服务器下线时主动进行故障转移。其中三个个性:
监控(Monitoring): Sentinel 会一直地查看你的主服务器和从服务器是否运作失常。
揭示(Notification): 当被监控的某个 Redis 服务器呈现问题时, Sentinel 能够通过 API 向管理员或者其余应用程序发送告诉。
主动故障迁徙(Automatic failover): 当一个主服务器不能失常工作时, Sentinel 会开始一次主动故障迁徙操作。
特点:
1、保障高可用
2、监控各个节点
3、主动故障迁徙
毛病:主从模式,切换须要工夫丢数据
没有解决 master 写的压力
集群
从redis 3.0之后版本反对redis-cluster集群,Redis-Cluster采纳无核心构造,每个节点保留数据和整个集群状态,每个节点都和其余所有节点连贯。
特点:
1、无核心架构(不存在哪个节点影响性能瓶颈),少了 proxy 层。
2、数据依照 slot 存储散布在多个节点,节点间数据共享,可动静调整数据分布。
3、可扩展性,可线性扩大到 1000 个节点,节点可动静增加或删除。
4、高可用性,局部节点不可用时,集群仍可用。通过减少 Slave 做备份数据正本
5、实现故障主动 failover,节点之间通过 gossip 协定替换状态信息,用投票机制实现 Slave到 Master 的角色晋升。
毛病:
1、资源隔离性较差,容易呈现相互影响的状况。
2、数据通过异步复制,不保证数据的强一致性
Q9:Redis集群-如何从海量数据里疾速找到所需?
-
分片
依照某种规定去划分数据,扩散存储在多个节点上。通过将数据分到多个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。
Q10:什么是缓存穿透?如何防止?什么是缓存雪崩?如何防止?什么是缓存击穿?如何防止?
缓存穿透
个别的缓存零碎,都是依照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:启用限流策略,尽量避免数据库被干掉
缓存击穿
概念
一个存在的key,在缓存过期的一刻,同时有大量的申请,这些申请都会击穿到DB,造成刹时DB申请量大、压力骤增。解决方案
A. 在拜访key之前,采纳SETNX(set if not exists)来设置另一个短期key来锁住以后key的拜访,拜访完结再删除该短期keyB. 服务层解决 – 办法加锁 + 双重校验:
// 锁-实例 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之前,采纳SETNX(set if not exists)来设置另一个短期key来锁住以后key的拜访,拜访完结再删除该短期key |
Q11:缓存与数据库双写统一
如果仅仅是读数据,没有此类问题
如果是新增数据,也没有此类问题
当数据须要更新时,如何保障缓存与数据库的双写一致性?
三种更新策略:
- 先更新数据库,再更新缓存 ->
- 先删除缓存,再更新数据库
- 先更新数据库,再删除缓存
计划一:并发的时候,执行程序无奈保障,可能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
Q12:何保障Redis中的数据都是热点数据
A. 能够通过手工或者被动形式,去加载热点数据
B. 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(驱赶):禁止驱赶数据
Q13:Redis的并发竞争问题如何解决?
即多线程同时操作对立Key的解决办法:
Redis为单过程单线程模式,采纳队列模式将并发拜访变为串行拜访。Redis自身没有锁的概念,Redis对于多个客户端连贯并不存在竞争,然而在Jedis客户端对Redis进行并发拜访时会产生连贯超时、数据转换谬误、阻塞、客户端敞开连贯等问题,这些问题均是因为客户端连贯凌乱造成
对此有多种解决办法:
A:条件容许的状况下,请应用redis自带的incr命令,decr命令
B:乐观锁形式
watch price
get price $price
$price = $price + 10
multi
set price $price
exec
C:针对客户端,操作同一个key的时候,进行加锁解决
D:场景容许的话,应用setnx 实现
Q14:Redis回收过程如何工作的? Redis回收应用的是什么算法?
Q12 中提到过,当所需内存超过配置的最大内存时,redis会启用数据淘汰规定
默认规定是:# maxmemory-policy noeviction
即只容许读,无奈持续增加key
因而常须要配置淘汰策略,比方LRU算法
LRU算法最为精典的实现,就是HashMap+Double LinkedList,工夫复杂度为O(1)
Q15:Redis大批量减少数据
参考文章:https://www.cnblogs.com/Patri…
应用管道模式,运行的命令如下所示:
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…
# 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", '$',LENGTH(redis_cmd),'\r\n',redis_cmd,'\r\n', '$',LENGTH(redis_key),'\r\n',redis_key,'\r\n', '$',LENGTH(hkey1),'\r\n',hkey1,'\r\n','$',LENGTH(hval1),'\r\n',hval1,'\r\n', '$',LENGTH(hkey2),'\r\n',hkey2,'\r\n','$',LENGTH(hval2),'\r\n',hval2,'\r\n', '$',LENGTH(hkey3),'\r\n',hkey3,'\r\n','$',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编码类型
Q16:延申:布隆过滤器
数据结构及算法篇 / 布隆过滤器
Redis 实现
redis 4.X 以上 提供 布隆过滤器插件
centos中装置redis插件bloom-filter:https://blog.csdn.net/u013030…
语法:[bf.add key options]
语法:[bf.exists key options]
留神:
redis 布隆过滤器提供的是 最大内存512M,2亿数据,万分之一的误差率
Q17:Lua脚本相干
应用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();
}
Q18:性能相干 – Redis慢查问剖析
redis 命令会放在redis内置队列中,而后主线程一个个执行,因而 其中一个 命令执行工夫过长,会造成成批量的阻塞
命令:
slowlog get 获取慢查问记录slowlog len 获取慢查问记录量
(慢查问队列是先进先出的,因而新的值在满载的时候,旧的会进来)
Redis 慢查问 -> 执行阶段耗时过长
conf文件设置:
slowlog-low-slower-than 10000 -> 10000微秒,10毫秒 (默认)0 -> 记录所有命令 -1 -> 不记录命令
slow-max-len 寄存的最大条数
慢查问导致起因: value 值过大,解决办法:
数据分段(更细颗粒度存放数据)
Q19:如何进步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环境的网络开销)
最初
本篇是一篇大合集,两头必定参考了许多其他人的文章内容或图片,但因为工夫比拟长远,过后并没有一一记录,为此表示歉意,如果有作者发现了本人的文章或图片,能够私聊我,我会进行补充。
如果你发现写的还不错,能够搜寻公众号「是Kerwin啊」,一起提高!
也能够查看Kerwin的GitHub主页
发表回复