redis核心技术梳理(持续更新)

redis常用命令

get/set/exists/del/keys/rename/dbsize/type/mget/mset/expire/setnx/incr/decr/append/flushall

redis的数据类型

String/Hash/List/Set/Zset/Hyperloglog/Geo/Stream/BitMaps

Memcached和Redis的主要区别是什么

Memcached:只能存储KV、没有持久化机制、不支持主从复制、是多线程的
Redis:数据类型丰富、支持多种编程语言、功能丰富(持久化机制、内存淘汰策略、事务、发布订阅、pipeline、lua)、支持集群、分布式

Hash与String的主要区别

1.把所有相关的值聚集到一个key中,节省内存空间
2.只使用一个key,减少key冲突
3.当需要批量获取值的时候,只需要使用一个命令,减少内存/IO/CPU的消耗
4.Hash不适合的场景:Field不能单独设置过期时间、需要考虑数据量分布的问题

redis的Hash数据量大怎么取出

就看你的数据量大小了,如果太大,却是不好遍历,如果有规则的key值的话,倒是可以利用分页的方式来处理,如果没有规律的话,只有用keys*来取了,或者是通过keysa*;keysz*;keys1*;keys0*等通配符的方式来顺序的读取,读取时要把内存分配的大一些,不然容易溢出。 有3个通配符 *, ? ,[]

二进制安全SDS

C语言中,用“0”表示字符串的结束,如果字符串中本身就有“0”字符,那么这个字符串就会被截断,即非二进制安全;若通过某种机制,保证读写字符串时不损害其内容,则是二进制安全。

redis3.2版本以前:

struct sdshdr{
        int len;//buf数组中已经使用的字节的数量,也就是SDS字符串长度
        int  free;//buf数组中未使用的字节的数量
        char buf[];//字节数组,字符串就保存在这里面
};

 redis3.2版本后:

struct_attribute_((_packed_))sdshdr8{
    uint8_t len;//当前字节数组的长度
    uint8_t alloc;//当前字节数组总共分配的内存大小
    uimt8_t flag;//当前字节数组的属性,用来标识到底是sdshdr8还是sdshdr16等
    char buf[]; //字符串真正的值
}

SDS的特点

1.不用担心内存溢出问题,如果需要会对SDS进行扩容。
2.获取字符串长度时间复杂度为O(1),因为定义了len属性。
3.通过‘空间预分配’和‘惰性空间释放’,防止多次重分配内存。
4.判断是否结束的标志是len属性,可以包含‘\0’。

redis事务

redis的单个命令是原子性的(比如get/set/mget/mset),要么成功要么失败,不存在并发干扰的问题。如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,redis提供了事务的功能,可以把一组命令一起执行。
事务特点:
1.按进入队列的顺序执行
2.不会受到其它客户端的请求的影响
3.事务不能嵌套,多个multi命令效果一样
事务用法:
1.multi:开启事务
2.exec:执行事务
3.discard:取消事务
4.watch:监视
事务可能遇到的问题:
1.在执行exec之前发生错误,队列都不会被执行。
2.在执行exec之后发生的错误,只有错误的不会被执行。redis事务无法保证原子性和数据一致性。

RESP通信协议

RESP(Redis Serialization Protocol)序列化协议,容易实现、解析快、可读性强。就是把命令、长度和参数用\r\n连接起来。

package redis;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class RespTest {

    private Socket socket;
    private OutputStream write;
    private InputStream read;

    public RespTest(String host, int port) throws IOException {
        socket = new Socket(host, port);
        write = socket.getOutputStream();
        read = socket.getInputStream();
    }

    /**
     * 实现了set方法
     * @param key
     * @param val
     * @throws IOException
     */
    public void set(String key, String val) throws IOException {
        StringBuffer sb = new StringBuffer();
        // 代表3个参数(set key value)
        sb.append("*3").append("\r\n");
        // 第一个参数(set)的长度
        sb.append("$3").append("\r\n");
        // 第一个参数的内容
        sb.append("SET").append("\r\n");

        // 第二个参数key的长度(不定,动态获取)
        sb.append("$").append(key.getBytes().length).append("\r\n");
        // 第二个参数key的内容
        sb.append(key).append("\r\n");
        // 第三个参数value的长度(不定,动态获取)
        sb.append("$").append(val.getBytes().length).append("\r\n");
        // 第三个参数value的内容
        sb.append(val).append("\r\n");

        // 发送命令
        write.write(sb.toString().getBytes());
        byte[] bytes = new byte[1024];
        // 接收响应
        read.read(bytes);
        System.out.println("-------------set-------------");
        System.out.println(new String(bytes));
    }

    /**
     * 实现了get方法
     * @param key
     * @throws IOException
     */
    public void get(String key) throws IOException {
        StringBuffer sb = new StringBuffer();
        // 代表2个参数
        sb.append("*2").append("\r\n");
        // 第一个参数(get)的长度
        sb.append("$3").append("\r\n");
        // 第一个参数的内容
        sb.append("GET").append("\r\n");

        // 第二个参数key的长度
        sb.append("$").append(key.getBytes().length).append("\r\n");
        // 第二个参数内容
        sb.append(key).append("\r\n");

        write.write(sb.toString().getBytes());
        byte[] bytes = new byte[1024];
        read.read(bytes);
        System.out.println("-------------get-------------");
        System.out.println(new String(bytes));
    }

    public void passport(String password) throws IOException {
        StringBuffer sb = new StringBuffer();
        // 代表2个参数
        sb.append("*2").append("\r\n");
        // 第一个参数(get)的长度
        sb.append("$4").append("\r\n");
        // 第一个参数的内容
        sb.append("AUTH").append("\r\n");

        // 第二个参数key的长度
        sb.append("$").append(password.getBytes().length).append("\r\n");
        // 第二个参数内容
        sb.append(password).append("\r\n");

        write.write(sb.toString().getBytes());
        byte[] bytes = new byte[1024];
        read.read(bytes);
        System.out.println("-------------get-------------");
        System.out.println(new String(bytes));
    }

    public static void main(String[] args) throws IOException {
        RespTest client = new RespTest("39.100.194.200", 6379);
        client.passport("123456");
        client.set("zj", "2673");
        client.get("zj");
    }
}

Pipeline

pipeline通过一个队列把所有的命令缓存起来,然后把多个命令在一次连接中发送给服务器,需要客户端和服务端都支持。 

https://blog.csdn.net/xiaowanzi_zj/article/details/121847335

redis客户端

-Jedis、Redission、Luttuce
-Spring操作Redis提供一个模板方法,RedisTemplate
-Spring定义了一个连接工厂接口:RedisConnectionFactory,可以实现不同的客户端
-Jedis多个线程使用一个连接的时候线程不安全,可以使用连接池

 redis为什么这么快

-纯内存结构(KV结构的内存数据库,时间复杂度0(1))
-请求处理单线程(避免线程上下文切换,创建销毁和线程之间竞争的消耗,redis的瓶颈不在cpu,可能在带宽或机器内存)
-多路复用机制(同步非堵塞I/0)

jedis分布式锁

分布式锁的基本特性:
1.互斥性:只有一个客户端能够持有锁
2.不会产生死锁:即使持有锁的客户端崩溃,也能保证后续其他客户端可以获取锁
3.只有持有这把锁的客户端才能解锁

 加锁

jedis.set(String key, String value, String nxxx, String expx, int time)
- 第一个为key,我们使用key来当锁,因为key是唯一的
- 第二个为value,我们传的是requestId,requestId是客户端的唯一标志。
- 第三个为nxxx,这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经
 存在,则不做任何操作;
- 第四个为expx,这个参数我们传的是PX,意思是我们要给这个key加一个过期的设置,具体时间由第五个参数决定
- 第五个为time,与第四个参数相呼应,代表key的过期时间

解锁

public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId)
{
   //首先获取锁对应的value值,检查是否与requestId相等,如果相等则删除锁
   String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return                    redis.call('del',KEYS[1]) else return 0 end";
   Object result = jedis.eval(script, Collections.singletonList(lockKey),         
   Collections.singletonList(requestId));
  if(RELEASE_SUCCESS.equals(result)) {
     return true;
   }
   return false;
}

数据一致性

缓存主要是针对读多写少的高并发场景来提高查询速度。

 redis和数据库数据发生变化的时候有两种选择:
==================(删除)操作Redis的数据再操作数据库的数据=======================
正常情况:删除缓存成功,更新数据库成功
异常情况:
1.删除缓存失败,程序捕获异常,不会走到下一步,所以数据不会出现不一致。
2.删除缓存成功,更新数据库失败。因为以数据库的数据为准,所以不存在数据不一致的情况。
######高并发情况下操作(redis是旧数据,数据库是新数据):
1.线程A需要更新数据,首先删除了redis缓存
2.线程B查询数据,发现缓存不在,到数据库查询旧值,写入redis返回
3.线程A更新了数据库
####可以使用延时双删的策略,在写入数据之后,再删除一次缓存
======================先操作数据库的数据再(删除)操作Redis的数据==================
正常情况:更新数据库成功,删除缓存成功。
异常情况:
1.更新数据库失败,程序捕获异常,不会走到下一步,所以数据不会出现不一致。
2.更新数据库成功,删除缓存失败。数据库是新数据,缓存是旧数据,发生了不一致的情况。
---重试机制比如捕获异常把删除的key发送给消息队列,通过消费者继续尝试删除key
---异步更新缓存,通过服务监听binlog的变化(阿里canel),然后在客户端完成删除key的操作,如果失败的话,发送队列
---后删除缓存失败的情况,做法就是不断地重试删除,直到成功。无论重试还是异步删除,都是最终一致性的思想。
希望:
-数据库实时性要求不高的可以采用定时任务查询数据库数据同步到redis的方案。
-由于我们是以数据库的数据为准的,所以给缓存设置一个过期时间,是保证最终一致性的解决方案。
-不管是更新还是删除操作只要数据变化都用删除。

过期策略

redis中同时使用了惰性过期和定期过期两种过期策略,并不是实时地清除过期的key。

  立即过期(主动淘汰)

每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很    友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

 惰性过期(被动淘汰)

只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。 

定期过期

每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。  

淘汰策略

 最大内存设置

1.参数设置(redis.conf的maxmemory)
2.动态修改(先get一下,config set maxmemory 2GB)

LRU(Least Recently Used):最近最少使用。判断最近被使用的时间,距离目前时间最远的数据优先被淘汰。
LFU(Least Frequently Used):最不常用,按照使用频率删除,4.0版本新增。
random:随机删除。

 

如果没有设置ttl或者没有符合前提条件的key被淘汰,那么volatile-lru、volatile-random、volatile-ttl相当于noevication(不做内存回收)
动态修改淘汰策略(先get一下):
redis> config set maxmemory-policy volatile-lru
-建议使用volatile-lru,在保证正常服务的情况下,优先删除最近最少使用的key 

redis持久化机制

RDB(Redis Database) 通过快照的形式将数据保存到磁盘中。所谓快照,可以理解为在某一时间点将数据集拍照并保存下来。Redis 通过这种方式可以在指定的时间间隔或者执行特定命令时将当前系统中的数据保存备份,以二进制的形式写入磁盘中,默认文件名为dump.rdb。

RDB 的触发有三种机制,执行save命令;执行bgsave命令;在redis.config中配置自动化。

AOF日志存储的是Redis服务器指令序列,AOF只记录对内存进行修改的指令记录。

RDB

-RDB是redis默认的持久化方案,如果RDB与AOF同时开启,默认加载AOF的配置文件。(如果开启了AOF,优先用AOF)。当满足一定条件的时候,会把当前内存中的数据写入到磁盘,生成一个快照文件dump.rdb。redis重启会通过加载dump.rdb文件恢复数据。
-如果不需要rdb方案,注释save或者配置成空字符串""
-自动触发(redis.conf)
 配置规则触发
 save 900 1 # 900 秒内至少有一个 key 被修改(包括添加)
 save 300 10 # 300 秒内至少有 10 个 key 被修改
 save 60 10000 # 60 秒内至少有 10000 个 key 被修改
 shutdown触发,保证服务器正常关闭
 flushall,rdb文件是空的,没什么意义
-手动触发
 save:生成快照的时候会堵塞当前redis服务器,redis不能处理其他命令。
 bgsave:redis会在后台异步进行快照操作,可以同时响应客户端请求,redis进程会fork一个子进程,rdb持久化由子进程负责,完成自动结束。

AOF

redis默认不开启,AOF采用日志的形式来记录每个写操作,并追加到文件中。都是RESP协议的命令。
-AOF配置(redis.conf)
 开关(默认关闭):appendonly no/文件名:appendfilename "appendonly.aof"
-AOF触发配置方式
 appendfsync 
        always:每次收到写命令就立即强制写入磁盘,是最有保证的完全的持久化,但a速度也是最慢的,一般不推荐使用。
       everysec:每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,是受推荐的方式。
        no:完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不被推荐
-文件越来越大怎么办
  使用命令bgrewriteaof重写,aof文件重写并不是对原文件进行重新整理,而是直接读取服务器现有的键值对,然后用一条命令去代替之前记录这个键值对的多条命令,生成 一个新的文件后去替换原来的aof文件。
  #重写触发机制
  auto-aof-rewrite-percentage 100:aof文件增长比例,指当前aof文件比上次重写的增长比例大小。aof重写即在aof文件在一定大小之后,重新将整个内存写到aof文件当中,以反映最新的状态(相当于bgsave)。这样就避免了,aof文件过大而实际内存数据小的问题(频繁修改数据问题)。
  auto-aof-rewrite-min-size 64mb:aof文件重写最小的文件大小,即最开始aof文件必须要达到这个文件时才触发,后面的每次重写就不会根据这个变量了(根据上一次重写完成之后的大小).此变量仅初始化启动redis有效.如果是redis恢复时,则lastSize等于初始aof文件大小。

-AOF重写过程中的命令
 会放到一个AOF重写缓存中,等子进程重写完后再把缓存中的命令追加到aof文件中。

Redis4.0混合持久化

混合持久化同样也是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将aof_rewrite_buf重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。

简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据。

如下图:

在redis重启的时候,加载aof文件进行恢复数据:先加载rdb内容再加载剩余的aof。

混合持久化配置:

aof-use-rdb-preamble yes # yes:开启,no:关闭

热点数据发现

-redis的缓存淘汰机制,能够留下那些热点的key,不管是LRU和LFU。
-客户端Jedis的Connection类的sendCommand()里面,用一个HashMap进行key的统计
-代理层比如TwemProxy或者Codis
-服务端Redis有一个monitor的命令,可以监控到所有的Redis执行的命令。
-机器层面通过对TCP协议进行抓包,比如ELK的packetbeat插件

缓存雪崩

缓存雪崩就是Redis的大量(大范围)热点数据同时过期(失效)因为设置了相同的过期时间,刚好这个时候Redis请求的并发量又很大,就会导致所有的请求落到数据库。
解决方案:
1.加互斥锁或者使用队列,针对同一个key只允许一个线程到数据库查询
2.缓存定时预先更新,避免同时失效
3.加随机数,使key在不同的时间过期
4.缓存永不过期

缓存穿透

一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。如果key对应的value是一定不存在的,并且对该key并发请求量很大,就会对后端系统造成很大的压力。这就叫做缓存穿透。
-因为每次查询的值都不存在导致的Redis失效的情况,我们就把它叫做缓存穿透。(缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,我们数据库的id都是1开始自增上去的,如发起为id值为 -1 的数据或 id 为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大,严重会击垮数据库)
1.缓存空数据
2.缓存特殊字符串,比如&&
3.布隆过滤器

https://blog.csdn.net/xiaowanzi_zj/article/details/115839661

缓存击穿

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮(针对某一个key)
解决方案:
1.互斥锁
2.缓存永不过期

布隆过滤器

位图的容量是基于元素的个数和误判率计算出来的。
从容器的角度来说:
1、如果布隆过滤器判断元素在集合中存在,不一定存在
2、如果布隆过滤器判断不存在,一定不存在
从元素的角度来说:
3、如果元素实际存在,布隆过滤器一定判断存在
4、如果元素实际不存在,布隆过滤器可能判断存在
1Byte=8bit
1KB=1024Byte
1M=1024KB
1G=1024M

 https://blog.csdn.net/xiaowanzi_zj/article/details/115839661

redis内存满了怎么办

这个跟 Redis 的内存回收策略有关。
Redis 的默认回收策略是 noenviction,当内存用完之后,写数据会报错。
Redis的其他内存回收策略含义:
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)中,随机淘汰数据

redis性能优化

1.尽量使用短的key
2.避免使用keys *会堵塞
3.在存到Redis之前先把你的数据压缩下
4.设置key有效期
5.选择回收策略(maxmemory-policy)
6.限制redis的内存大小(64位系统不限制内存,32位系统默认最多使用3GB内存)避免发生OOM
7.当业务场景不需要数据持久化时,关闭所有的持久化方式可以获得最佳的性能
8.想要一次添加多条数据的时候可以使用管道
9.尽可能地使用hash哈希存储
10.使用bit位级别操作和byte字节级别操作来减少不必要的内存使用

缓存的类型

本地缓存(进程内缓存Ehcache)、分布式缓存、多级缓存 

Memcache的特点

1.MC处理请求时使用多线程异步IO的方式,可以合理利用CPU多核的优势,性能非常优秀
2.MC功能简单,使用内存存储数据,只支持K-V结构,不提供持久化和主从同步功能
3.MC对缓存的数据可以设置失效期,过期后的数据会被清除
4.失效的策略采用延迟失效,就是当再次使用数据时检查是否失效
5.当容量存满时,会对缓存中的数据进行剔除,剔除时除了会对过期key进行清理,还会按LRU策略对数据进行剔除
6.限制:key不能超过250个字节、value不能超过1M字节

集群模式下导致重复加锁怎么办(Redission)

Redission会自动选择同一个master加锁

业务没执行完,分布式锁到期了怎么办

watchlog(看门狗)Redission

 布隆过滤器为什么没有删除功能

因为存在Hash碰撞

redis缓存过期通知

1.事件通过Redis的订阅与发布功能(pub/sub)来进行分发,故需要开启redis的事件监听与发布
2.修改redis.conf文件,打开notify-keyspace-eventsEx的注释,开启过期通知功能
3.重启redis,即可测试失效事件的触发,监听获取的值为key

什么是I/0多路复用

Io指的是网络I/O,多路指的是多个TCP连接,复用指的是复用一个或多个线程。
基本原理就是不再由应用程序自己监视连接,而是由内核替应用程序监视文件描述符。
      客户端在操作的时候,会产生具有不同事件类型的socket,在服务端,I/O多路复用程序会把消息放入队列中,然后通过文件事件派发器转发到不同的事件处理器中。
      多路复用有很多的实现,以select为例,当用户进程调用了多路复用器,进程会被堵塞,内核会监视多路复用器负责的所有socket,当任何一个socket的数据准备好了,多路复用器就会返回。这时候用户进程再调用read操作,把数据从内核缓冲区拷贝到用户空间。
IO多路复用的特点是通过一种机制让一个进程能同时等待多个文件描述符,而这些文件描述符其中的任何一个进入读就绪状态,select()函数就可以返回。

Redis为什么是单线程

因为单线程已经够用了,CPU 不是 redis 的瓶颈。Redis 的瓶颈最有可能是机器内存或者网络带宽。

分布式的意思

高性能、高可用、扩展性需要依赖两种关键的技术,一种是分片,一种是冗余。分片的意思是把所有的数据拆分到多个节点分撒存储。冗余的意思是每个节点都有一个或者多个副本。那么,redis必须要提供数据分片和主从复制的功能。副本有不同的角色,如果主节点发生故障,则把某个从节点改成主节点,访问新的主节点,实现高可用。

主从复制原理

------------------------------主从复制分为两种--------------------------
全量复制、增量复制
------------------------------连接阶段---------------------------------
1.salve节点启动时,会在自己本地保存master节点的信息(ip、port等)
2.slave节点内部有个定时任务,每隔一秒钟检查是否有新的master节点连接和复制。
3.如果发现有master节点,就跟master节点建立连接。如果连接成功,从节点就为连接分配一个专门处理复制工作的文件事
件处理器负责后续的复制工作。为了让主节点感知到slave节点的存活,slave节点定时回个主节点发送ping请求。
------------------------------数据同步阶段------------------------------
如果新加入的master节点,那就需要全量复制。master通过bgsave命令在本地生成一份RDB快照,将RDB快照发送给salve
节点。slave如果有数据,首先清除自己的旧数据,然后用RDB文件加载数据。开始生成RDB文件时,master会把所有新的写
命令缓存在内存中,在slave节点保存RDB文件后,再把新的写命令复制给slave节点。
------------------------------命令传播阶段-----------------------------
master节点持续把写命令异步复制给slave节点
------------------------------增量复制--------------------------------
slave通过master_repl_offset记录的偏移量
------------------------------无盘复制redis6.0--------------------------------
master节点的RDB文件不保存到磁盘而是直接通过网络发送给从节点。适用于master节点磁盘性能不好但是网络好。

哨兵

-------------------------------------Sentinel原理-------------------------------
-redis的高可用是通过哨兵保证的。它的思路是通过运行监控服务器来保证服务可用性。一般是奇数节点个数防止脑裂。
-哨兵集群监控所有的redis节点,哨兵之间也相互监控没有主从之分地位平等。
-哨兵通过发布订阅功能监控所有的redis节点、哨兵之间是否在线。哨兵订阅(_sentinel_:hello)
-最大作用监控服务状态、切换主从
--------------------------------------服务下线-----------------------------------
主观下线:哨兵默认以每秒钟1次的频率向redis服务节点发送ping命令。如果在指定时间(默认30秒)内没有收到有效回复,哨兵会被标记为主观下线。
客观下线:第一次发现master下线的哨兵继续询问其他的哨兵节点,如果多数哨兵都认为master节点下线,master才会被 标记为客观下线。
--------------------------------------故障转移-----------------------------------
-哨兵集群选取reader,由reader完成故障转移,通过raft算法实现哨兵选举
-数据一致需要两个步骤:领导选举,数据复制。
-raft算法是共识算法。raft核心思想:先到先得,少数复从多数
影响选举结果因素:断开连接时长/优先级id/复制数量/进程id

Redis分布式

-数据分片:客户端、代理层、服务端
-客户端分片:ShardedJedis使用的是一致性哈希算法,如果数据分布不均使用虚拟节点
-代理层分片:Twemproxy、Codis
Redis Cluster(去中心化):
-Redis的数据分布即没有用哈希取摸,也没有用一致性算法,而是使用虚拟槽来实现的。
-redis创建16384个槽,每个节点负责一定区间Slot。
-对象分布到Redis节点上时,对Key用CRC16算法计算在%16384,得到一个slot的值,数据落到负责这个slot的redis节点上。
-Redis的每个master节点都会维护自己负责的slot,用一个bit序列实现。
-key和槽位的关系是不会变的,会变的是槽位和节点的关系。
-相关的数据落到同一个节点上可以使用{hash tag},{}这里面的字符串是相同的。
-客户端重定向:先会返回MOVED 13724 127.0.0.1:7293,再更换端口:redis-cli -p 7293。
数据迁移:因为key和slot的关系是永远不会变的,当新增节点的时候,需要把原有的slot分配的新的节点负责,并且把相关的数据迁移过来。

 数据结构和数据编码

对象对象type属性值type命令输出底层可能的存储结构object encoding
字符串对象OBJ_STRING"string"

OBJ_ENCODING_INT

OBJ_ENCODING_EMBSTR

OBJ_ENCODING_RAW

int

embstr

raw

列表对象OBJ_LIST"list"OBJ_ENCODING_QUICKLISTquicklist
哈希对象OBJ_HASH"hash"

OBJ_ENCODING_ZIPLIST

OBJ_ENCODING_HT

ziplist

hashtable

集合对象OBJ_SET"set"

OBJ_ENCODING_INTSET

OBJ_ENCODING_HT

intset

hashtable

有序集合对象OBJ_ZSET"zset"

OBJ_ENCODING_ZIPLIST

OBJ_ENCODING_SKIPLIST

ziplist

skiplist+hashtable

对象原始编码升级编码
字符串对象INTembstrraw
整数并且小于long 2^63-1超过44字节,被修改
哈希对象ziplisthashtable
键和值的长度小于64byte,键值对个数不超过512个,同时满足
列表对象quicklist
集合对象intsethashtable
元素都是整数类型,元素个数小于512个,同时满足
有序集合对象ziplistskiplist
元素个数不超过128个,任何一个member的长度小于64字节,同时满足

redis集群(主从)脑裂及解决方案 

https://blog.csdn.net/xiaowanzi_zj/article/details/123805980

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

泡^泡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值