关于java:查缺补漏巩固你的Redis知识体系

8次阅读

共计 24307 个字符,预计需要花费 61 分钟才能阅读完成。

Windows Redis

装置

链接: https://pan.baidu.com/s/1MJnz… 提取码: 2c6w 复制这段内容后关上百度网盘手机 App,操作更不便哦

无脑下一步即可

应用

呈现谬误:

creating server tcp listening socket 127.0.0.1:6379: bind No error

解决方案:

  1. redis-cli.exe
  2. shutdown
  3. exit
  4. 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 的拜访,拜访完结再删除该短期 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 之前,采纳 SETNX(set if not exists)来设置另一个短期 key 来锁住以后 key 的拜访,拜访完结再删除该短期 key

Q11: 缓存与数据库双写统一

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

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

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

三种更新策略:

  1. 先更新数据库,再更新缓存 ->
  2. 先删除缓存,再更新数据库
  3. 先更新数据库,再删除缓存

计划一:并发的时候,执行程序无奈保障,可能 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 种数据淘汰策略:

  1. volatile-lru:从已设置过期工夫的数据集(server.db[i].expires)中筛选最近起码应用的数据淘汰
  2. volatile-ttl:从已设置过期工夫的数据集(server.db[i].expires)中筛选将要过期的数据淘汰
  3. volatile-random:从已设置过期工夫的数据集(server.db[i].expires)中任意抉择数据淘汰
  4. allkeys-lru:从数据集(server.db[i].dict)中筛选最近起码应用的数据淘汰
  5. allkeys-random:从数据集(server.db[i].dict)中任意抉择数据淘汰
  6. 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 主页

正文完
 0