redis优化

一)优雅的key结构:

redis中的key虽然可以自定义,但是最好遵循下面的几个最佳实践约定:

1)遵循基本格式:业务名称:数据名字:ID;

2)长度不要超过44字节,key所占的字节数越小,占用空间越小,越短越好;

3)不要包含特殊字符;

例如当设计登录业务的时候,保存用户信息,其实可以这样:login:user:10

优点:

1)可读性强

2)避免key的冲突:没有直接使用用户的ID,防止不同的业务之间都使用用户的ID造成key冲突,加上业务名字可以避免业务冲突;

3)方便Key的管理,使用冒号分离,在redis中形成层级结构;

4)更节省内存:key是String类型,底层编码包括int,embstr和raw三种,embstr在小于44字节的时候进行使用,采用连续的内存空间,所占用的内存更小;

查看底层编码:object encoding+key的名字;

int编码:当key是数值的时候进行存储,占用内存空间是非常小的

embstr:是一段连续空间,编码会更加紧凑,占用空间比较小,想要使用ambstr的编码格式,key就需要小于44字节

raw:空间不是连续的,访问性能下降,内存占用会更高,还会产生内存碎片

二)BigKey

redis优化_第1张图片

redis优化_第2张图片

Big Key就是某个key对应的value很大,占用的redis空间很大,本质上是大value问题,key往往是程序可以自行设置的,value往往不受程序控制,因此可能导致value很大;

在Redis中,一个字符串最大是512MB,一个二级数据结构比如说哈希,list,set,zset可以存储大约40亿个元素,但是实际上如果出现以下几种情况,就被认为是bigKey

1)字符串类型:它的big体现在单个value置很大,一般认为超过10KB就是BigKey

2)非字符串类型:哈希,列表,集合它们的bigKey取决于元素个数太多

3)memory usage+Key的名字,查看key的大小,但是消耗的CPU资源比较多

针对于String类型来说:strlen key查看key的长度

针对于list集合来说:llen key,查看list集合的长度

redis优化_第3张图片

BigKey产生的场景:

1)Redis数据结构使用不恰当:将redis用在并不适合的场景下,造成Key的value过大,比如使用String类型的key存放大体积的二进制数据文件

2)未能及时清理垃圾数据:没有针对无效数据做定期的清理,造成比如Hash类型中的Key成员在不断的增加,就是一直向value中塞数据,但是没有删除机制,value只会越来越大

3)明星、网红的粉丝列表、某条热点新闻的评论列表

假设使用List数据结构保存某个明星/网红的粉丝,或者保存热点新闻的评论列表,因为粉丝数量巨大,热点新闻因为点击率、评论数会很多,这样List集合中存放的元素就会很多,可能导致value过大,进而产生Big Key问题

在Redis配置文件中禁用一些命令:这样以后在对应的客户端就无法使用了

redis优化_第4张图片

 redis优化_第5张图片

三)BigKey的危害: 

有时也可以考虑对 Bigkey 进行拆分,具体方法如下:对于 string 类型的 Bigkey,可以考虑拆分成多个 key-value。对于 hash 或者 list 类型,可以考虑拆分成多个 hash 或者 list

1)网络阻塞:针对于BigKey进行网络请求的时候,假设每秒钟对这个bigKey的请求达到了20个,少量的并发就很有可能导致带宽被占满,导致Redis实例乃至所在的物理机变慢,假设这太物理机除了部署redis以外还部署了一些其他的应用,那么会导致其他网络请求被阻塞

Big Key对应的value较大,我们对其进行读写的时候,需要耗费较长的时间,这样就可能阻塞后续的请求处理,Redis的核心线程是单线程,单线程中请求任务的处理是串行的,前面的任务完不成,后面的任务就处理不了

2)数据倾斜:BigKey所在的Redis实例内存利用率(数据量)远远超过其他实例,导致无法使数据分片的内存资源达到均衡;

3)Redis阻塞:针对元素比较多的哈希,list,zset等做运算的时候会耗时较久,redis还是单线程的,是主线程被阻塞,读取单value较大时会占用服务器网卡较多带宽,自身变慢的同时可能会影响该服务器上的其他Redis实例或者应用

4)CPU压力过高:针对BigKey的数据做序列化和反序列化会导致CPU的使用率飙升,影响Redis和本机其他实例的使用

5)内存溢出:读取Big Key耗费的内存比正常Key会有所增大,如果不断变大,可能会引发OOM,达到redis的最大内存maxmemory设置值引发写阻塞或重要Key被逐出

四)排查BigKey:

1)redis-cli --bigKeys,利用redis-cli提供的bigKeys参数,可以遍历分析所有的key,并返回Key的整体统计信息和每一种数据的Top1的big key;

2)scan扫描,利用scan扫描Redis中的所有key,利用strlen或者是hlen等命令来判断key的长度,此处不建议使用memory usage或者是keys *;

redis优化_第6张图片

redis优化_第7张图片

redis优化_第8张图片 redis优化_第9张图片

2.1)第1个参数是游标,也就是你从第几个位置开始进行扫描,最终redis会返回一个游标,下一次会继续从这个位置进行扫描

2.2)第二个参数你扫描哪一种类型,第三个参数是你要扫描几个

redis优化_第10张图片

redis优化_第11张图片

@Controller
public class UserController {
    @Autowired
    private Jedis jedis;

    private final int StringMax=10*1024;
    private final int HashMax=500;
    @RequestMapping("/Java100")
    @ResponseBody
    public void scan(){
        System.out.println("1");
        long MaxLen=0;
        long KeyLen=0;
        String cur="0";
       do{
           ScanResult result= jedis.scan(cur);
           //1.记录游标
            cur=result.getCursor();
            //2.获取到扫描的key
            List list=result.getResult();
            if(list==null||list.isEmpty()){
                break;
            }
            for(String key:list){
                System.out.println(key);
                switch(jedis.type(key)){

                    case "string":
                        KeyLen=jedis.strlen(key);
                        MaxLen=StringMax;
                        break;
                    case "hash":
                        KeyLen= jedis.hlen(key);
                        MaxLen=HashMax;
                        break;
                    case "list":
                        KeyLen= jedis.llen(key);
                        MaxLen=HashMax;
                        break;
                    case "set":
                        KeyLen= jedis.scard(key);
                        MaxLen=HashMax;
                        break;
                    case "zset":
                        KeyLen= jedis.zcard(key);
                        MaxLen=HashMax;
                        break;
                }
                if(KeyLen>=MaxLen){
                    System.out.println("当前key是一个bigKey");
                }
            }

        } while(!cur.equals("0"));
    }

}

redis优化_第12张图片

3)debug object key

根据传入的对象(Key的名称)来对Key进行分析并返回大量数据,其中serializedlength的值为该Key的序列化长度,需要注意的是,Key的序列化长度并不等同于它在内存空间中的真实长度,此外,debug object属于调试命令,运行代价较大,并且在其运行时,进入Redis的其余请求将会被阻塞直到其执行完毕,并且每次只能查找单个key的信息,官方不推荐使用

4)这种方式是在redis实例上执行bgsave,bgsave会触发redis的快照备份,生成rdb持久化文件,然后对dump出来的rdb文件进行分析,找到其中的大key

1)针对 BigKey 进行拆分

通过将 BigKey 拆分成多个小 Key 的键值对,并且拆分后的对应的 value 大小和拆分成的成员数量比较合理,然后进行存储即可,在获取的时候通过 get 不同的 key 或是用 mget 批量获取存储的键值对

2)清理无效的数据

这个主要是针对像是 list 和 set 这种类型,在使用的过程中,list 和 set 中对应的内容不断增加,但是由于之前存储的已经是无效的了,需要定时的对 list 和 set 进行清理。

3)压缩对应的 BigKey 的 value

可以通过序列化或者压缩的方法对 value 进行压缩,是其变为较小的 value,但是如果压缩之后如果对应的 value 还是特别大的话,就需要使用拆分的方法进行解决了。

4)监控 Redis 中内存,带宽,增长率

通过监控系统,监控 redis 中的内存占用大小和网络带宽的占用大小,以及固定时间内的内存占用增长率,当超过设定的阈值的时候,进行报警通知处理

5)删除BigKey

如何删除BigKey

因为BigKey所占用的内存比较多,那么即便即使删除这样的Key也是很消耗时间的,这样还会导致Redis的主线程阻塞,从而引发一系列问题

1)redis3.0以下版本,比如说hash类型,先借用hdel来删除一个一个的子元素,最后删除key,如果是集合类型,那么先遍历BigKey的子元素,先逐个删除子元素,最后在删除BigKey,还是不能使用keys *,还是使用scan,还是指定key返回一个游标;

redis优化_第13张图片

redis优化_第14张图片

2)redis4.0以后提供了异步删除的命令,就是unlink命令

1)针对于String类型来说,一般使用del,如果过于庞大可以使用unlink;

2)针对于hash类型来说,可以使用hscan来每一次获取少量field-value,再使用hdel来删除每一个field;

public class DelHash {
    public static void start(String host,int port,String password,String bigHashKey){
        Jedis jedis=new Jedis(host,port);
        jedis.auth(password);
        //1.先进行记录每一次返回的BigHashKey中的field的个数
        ScanParams params=new ScanParams().count(10);
        //2.初始游标设置成0
        String cursor="0";
        do{
            //3.先得到所有的bigHashKey中的field
            ScanResult> scanResult=jedis.hscan(bigHashKey,cursor,params);
            //4.遍历Map集合进行删除此次得到的所有field值和value
            List> entryList=scanResult.getResult();
            if(entryList!=null&&!entryList.isEmpty()){
                for(Map.Entry entry:entryList){
                    jedis.hdel(bigHashKey,entry.getKey());
                }
            }
        }while(!"0".equals(cursor));
        //3.删除bigHashKey中的所有field和value值之后,再来进行删除对应的key
        jedis.del(bigHashKey);
    }

    public static void main(String[] args) {
        start("124.71.136.248",6379,"12503487","student");
    }
}

3)针对于list结构来说,可以使用ltrim渐进式删除,直到全部删除完成为止,这个命令是对列表只保留指定区间内的元素,不在区间内的元素会全部被删除,下标0表示列表中的第一个元素,下标1代表列表中的第一个元素,以此类推,也可以使用负数下标,-1代表列表中的最后一个元素,-2代表列表中的倒数第二个元素,以此类推:

 public static void start(String host,int port,String password,String bigListKey){
        Jedis jedis=new Jedis(host,port);
        jedis.auth(password);
        long llen= jedis.llen(bigListKey);
        int counter=0;
        int left=100;
        while(counter

redis优化_第15张图片

4)针对于set集合来说可以使用sscan命令每一次获取部分元素,再使用zrem来删除部分元素

redis优化_第16张图片

 redis优化_第17张图片

5)对于SortedSet来说,可以先使用zscan来获取部分元素,最后使用zremrangebyrank来删除每一个元素

redis优化_第18张图片

redis优化_第19张图片

redis优化_第20张图片

redis优化_第21张图片 redis优化_第22张图片

恰当的数据类型1:

第一种字符串对象存储方式,修改对应的字段值很不方便,新增字段值也很不方便

第二种转化成更大的key进行存储

1)占用空间比较大,有几个字段,key都是user:1:name,存储了很多相同的key,浪费空间

2)想要获取用户的所有信息比较麻烦

第三种的value又是一个键值对,但是Hash结构的Entry不要超过1000

redis优化_第23张图片

恰当的数据类型2:

1)当hash的entry数量超过500的时候,会使用哈希表而不是使用ZipList,这样会使内存占用比较多

2)可以通过修改hash-max-ziplist-entries配置entry上限,但是如果entry过多就会导致bigKey问题,但是还是可以通过config set  hash-max-ziplist-entries 1000来进行修改

redis优化_第24张图片

redis优化_第25张图片

解决方案: 

1)转化成String类型进行存储:解决了BigKey的问题

1.1)String类型底层结构没有太多优化,内存占用比较多

1.2)想要批量获取这些数据比较麻烦

redis优化_第26张图片

2)拆分成小的hash,将id/100作为key,将id%100作为field,这样每100个元素为一个hash,解决了BigKey的问题;

redis优化_第27张图片

 redis优化_第28张图片

 批处理优化:

一次命令的执行时间:1次往返的网络传输耗时+1次redis的执行命令的耗时,网络传输是非常耗时的,但是也不需要在一次批处理中传输太多命令,否则单次命令占用网络带宽过多导致网络阻塞

redis优化_第29张图片

 redis优化_第30张图片

 redis优化_第31张图片

mset虽然可以进行批处理,但是却只能操作部分数据类型,如果对有复杂数据类型的批处理需要,需要使用管道来进行处理

redis优化_第32张图片

1)m操作是redis原生提供的操作,这个操作的执行是原子性的,一次性会直接全部执行完成,中间过程中不会有其他命令来进行插队

2)但是管道是直接讲这些命令发送到管道里面,但是不会一起执行,因为管道里面命令的传输是有先后顺序的,在命令传输的过程中也是可以有其他客户端来给redis传输命令的,管道的命令会进入到redis队列中排队,redis的线程会依次取出这些命令进行执行,如果有其他命令来插队,那么实际执行时长可能会比与其执行时长要长

 redis优化_第33张图片

集群下的批处理:

批处理是在一次连接中把所有的请求全部干掉,如mset或者是Pipeline这样的批处理需要在一次请求中携带多条命令,而如果此时Redis是一个集群,那么批处理得key必须落到同一个插槽中,否则就会执行失败;

mset、mget只支持在同一个槽内的key,因为不在一个槽内的key可能存在于不同节点上

redis优化_第34张图片

服务器端的优化:

1)用来做缓存的redis尽量不要使用持久化功能;

2)建议关闭RDB持久化功能,使用AOF持久化功能;

3)利用脚本定期在slave节点做RDB,来实现数据备份;

4)设置合理的rewrite阈值,避免频繁的重写

5)配置no-appendfsyc-on-rewrite:yes,禁止在AOF重写的过程中做AOF持久化,避免因为AOF引起的阻塞

redis优化_第35张图片

不建议redis和做大量CPU密集型计算的应用和高磁盘负载的应用部署到同一台服务器上

慢查询:

redis优化_第36张图片redis优化_第37张图片

redis优化_第38张图片

下面的例子就是假设执行了keys *命令,接下来就可以通过showlog get 1来查询对应的命令

redis优化_第39张图片

redis优化_第40张图片 bind表示可以访问所有可以访问redis的服务器

这个配置是把config命令替换成后面的命令 

redis内存配置:

当redis内存不足的时候,可能会导致key频繁被删除,响应时间变长等问题,当redis的内存使用频率超过90%以上就需要我们警惕,并应该快速定位到内存占用的原因

1)数据内存,这是redis最主要的部分,用来存储redis的键值信息,主要的问题是BigKey问题,内存碎片的问题,是在内存分配的过程中产生的,当向redis中存储一部分的数据的时候,假设存储10个字节,就会分配16个字节,多分配给的6个字节就是内存碎片,要想解决内存碎片问题,就可以解决内存碎片的问题;

2)进程内存:Redis主进程本身运行肯定需要占用内存,比如说代码和常量池等等,这部分内存大约几兆,在大多数生产环境中和Redis数据占用的内存相比可以忽略

3)缓冲区内存:这部分内存会受到客户端的操作而受影响

info memory

memoery stats

1)复制缓冲区配置的尽量大些,防止进行频繁全量同步;

2)AOF缓冲区:每一秒钟执行一次刷盘策略,刷盘就从AOF缓冲区中刷,AOF缓冲区不会是内存有较大的波动,况且AOF缓冲区中存放的都是用户的命令;

3)客户端缓冲区(所有和redis建立连接的客户端)的输入缓冲区,如果主线程被阻塞(慢查询导致),数据量过多命令过多直接堆满输入缓冲区超过1G,那么redis会直接主动和客户端断开连接,一般情况不会出现问题,只要防止慢查询即可,所以不用担心输入缓冲区溢出的问题

4)输出缓冲区满了:redis处理完数据之后,返回的数据量太多导致客户端无法进行处理

redis优化_第41张图片

 redis优化_第42张图片

 通过info client或者client list来查看客户端信息clusterdown the cluster is down

你可能感兴趣的:(redis,缓存,数据库)