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

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主页

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理