Redis保姆级学习教程


前言

本篇文章对Redis的数据类型、基本使用以及常见的错误都有涉猎,是本人学习Redis每一阶段的记录,内容可能有点多。耐心看下去,相信你会有所收获。


一、基本命令

代码如下:

redis-server /etc/redis.conf  //后面是一个修改后的配置文件,支持后台启动

ps -ef | grep redis //查看是否启动,命令详解:
ps(Process Status)://进程查看
	-e 显示所有进程。 
	-f 全格式。 
	-h 不显示标题。 
	-l 长格式。 
	-w 宽输出。 
	-a 显示终端上的所有进程,包括其他用户的进程。 
	-r 只显示正在运行的进程。 
	-u 以用户为主的格式来显示程序状况。 
	-x 显示所有程序,不以终端机来区分。
grep //查找命令,支持正则表达式,也可用于文本搜索
| //管道命令,使ps与grep同时执行。
链接服务

redis-cli -a 密码

select num(0-16)//切换数据库

dbsize //数据库的key数量

flushdb //清空当前数据库

flushall //清空所有

keys * //查看所有的key,*用于匹配

exists key  //判断key是否存在

type key //查看key的类型

del key //删除指定key

unlink key 非堵塞删除,之后进行删除操作

expire key 10//设置key的过期时间为10秒

ttl key //查看还有多长时间过期,-1永不过期,-2已经过期

二、数据类型

1.String

添加/修改
set key value

setnx key value //只有不存在,才能添加

incr key 加一

decr key 减一

incrby/decrby key value //跨这步子加减

mset key1 value1 key2 value2...//同时设置多个

msetnx key1 value1 key2 value2...//同时设置多个,key不可存在

mget key1 key2 ...//同时获取多个

getrange key start end //获取key值范围内的值

setrange key start value //会用后面的value替换start开始之后的数据

setex key time value //设置键的同时,添加过期时间

getset key value //以新换旧,得到值

追加
append key value

获得长度
strlen key

2.List 集合

 插入
lpush/rpush key value value1 value2 //左边,右边插入值

rpoplpush key1 key2 从key1右边取值,存放到key2左边

linsert key before/after value newvalue 在 value前面、后面 插入newvalue

lrem key n value //从左到右删除n个值为value的数据

lset key index value 替换操作

获取
lrange key 0 -1 //获取所有

lpop/rpop key count //从左边、右边吐出值,值没有后key消失

lindex key index //按照索引获取元素(左到右)

llen key //获取列表的长度

3.Set 集合

添加
sadd key value value1 value2

查询
smembers key 查询键值下所有的数据

删除
srem key value(more) 删除key中值为value的数据

del key 删除该键

sismember key value 判断key值中是否含有value

scard key 返回key下集合的元素个数 

spop key 随机吐出一个

srandmember key n 随机出来n个值,但是不删除

smove source(key) destination(key) value 

sinter key1 key2 两个集合的交集

sunion key1 key2 两个集合的并集

sdiff key1 key2 两个集合的差集,包含key1中,不包含key2中

4.Hash集合

 添加
hset key field value 

hmset key field1 value1 field2 value2

hsetnx key field value 仅当不存在field时,才能够正确添加

查询
hget key field

功能
hexists key field 查看key下field是否存在

hkeys key 该key下所有的field

hvals key 该hash集合所有的value

hincrby/hdecrby  key field num 值的增加删除

5.Zset集合

添加
zadd key score1 value1 score2 value2

查询
zrange key start(0) end(-1) (withscores)查询所有(带值)

zrangebyscore topno min max (withscores) 查询min-max之间的

zrevrangebyscore topno max min   (withscores) 从大到小

功能
zincrby key num value 在key下value上增加num,不存在的话就添加

zrem key value 删除key下,value的元素

zcount key min max 统计之间的个数

zrank key value 返回排名,从0开始

6.Bitmaps

添加
setbit key index value

获取
getbit key index 返回的是值

7.HyperLogLog

用来统计不同数据的个数

添加
pfadd key value

功能
pfcount key 统计key中的数量

pfmerge newkey key1 key2 将key1与key2中的数据结合保存到newkey

8.Geospatial

添加
geoadd key 经度 维度 地区名(geoadd china:city 121.47 31.23 shanghai) 可添加多个

获取
geopos key 地区名(geopos china:city shanghai) 获得经纬度

geodist key 地区1 地区2 单位(geodist china:city shanghai beijing km)地区1与2之间的距离

georadius key 经度 维度 范围 单位(georadius china:city 110 30 1000 km) 以这个经纬度为中心,方圆1000km包含的城市

三、功能实例

1.事务处理

开启事务队列
multi + 之后进行的操作 

执行队列中的任务
exec 

终止组队
discard

2.测试工具

以下两种测试工具任选一个使用就行,ab是linux系统下的,Jmeter是Windows下的,个人感觉还是ab好用。

安装ab

yum install httpd-tools

ab -n 1000 -c 100 -p ~/postfile -T application/x-www-form-urlencoded http://192.168.133.235:8080/redis 
~/postfile根目录下的postfile文件(pid=0101&),也就是保存该接口需要的返回数据(POST类型)。1000个线程,100个并发。

安装Jmeter

这里是详细的安装步骤和使用方法,实测有效。

3.经典秒杀问题

不加处理的基础逻辑会产生超卖超时问题,以下是解决方式:
事务+乐观锁(Watch)可以解决超卖超时问题,但是还会有库存遗留和连接超时问题
连接超时可以通过连接池解决
库存遗留可以通过LUA解决

事务+乐观锁(Watch)

public static boolean doSecKill(String uid,String pid){

        if (pid==null||uid==null){
            return false;
        }
//        链接Jedis
//        Jedis jedis=new Jedis("192.168.180.98",6379);
//        使用连接池,解决连接超时问题
        JedisPool poolInstance = JedisPoolUtil.getJedisPoolInstance("192.168.133.98", 6379, 60000,"112233");
        Jedis jedis = poolInstance.getResource();
        String kcKey="sk:"+pid+":qt";
        String userKey="sk:"+pid+":user";

//        监视库存,乐观锁
        jedis.watch(kcKey);

        String kc = jedis.get(kcKey);
        if (kc==null){
            System.out.println("还没有开始");
            jedis.close();
            return false;
        }

        if (jedis.sismember(userKey, uid)){
            System.out.println("已经成功,不能再次");
            jedis.close();
            return false;
        }

        if (Integer.parseInt(kc)<=0){
            System.out.println("秒杀结束");
            jedis.close();
            return false;
        }
//        使用事务,原子性
        Transaction multi=jedis.multi();
//        组队
        multi.decr(kcKey);
        multi.sadd(userKey,uid);
//        执行
        List<Object> results = multi.exec();
        if (results==null||results.size()==0){
            System.out.println("秒杀失败");
            jedis.close();
            return false;
        }
        System.out.println("秒杀成功");
        jedis.close();
        return true;
    }

    public static String geyUid(){
        Random random=new Random();
        StringBuilder sb=new StringBuilder();
        for (int i=0;i<4;i++){
            sb.append(random.nextInt(10));
        }
        return sb.toString();
    }
自己写了一个常用的连接池,如果感觉不错的话,可以直接拿去使用:
public class JedisPoolUtil {
    //双重检验
    private static volatile JedisPool jedisPool=null;
    private static volatile JedisSentinelPool jedisSentinelPool=null;

    private JedisPoolUtil() {
    }
    public static JedisPool getJedisPoolInstance(String url,int port,int timeout,String password){
        if (null==jedisPool){
            synchronized (JedisPoolUtil.class){
                if (null==jedisPool){
                    jedisPool=new JedisPool(getJedisPoolConfig(),url,port,timeout,password);
                }
            }
        }
        return jedisPool;
    }

    //哨兵模式
    public static JedisPool getJedisPoolInstance1(Set<String> set,String password){
        if (null==jedisSentinelPool){
            synchronized (JedisPoolUtil.class){
                if (null==jedisSentinelPool){
                    jedisSentinelPool=new JedisSentinelPool("mymaster",set,getJedisPoolConfig(),password);
                }
            }
        }
        return jedisPool;
    }

//    集群
    public JedisCluster getJedisCluster(String url,int port){
        Set<HostAndPort> set=new HashSet<>();
        set.add(new HostAndPort(url,port));
        return new JedisCluster(set);
    }

    private static JedisPoolConfig getJedisPoolConfig(){
        JedisPoolConfig poolConfig=new JedisPoolConfig();
        poolConfig.setMaxTotal(200);//最大可用连接数
        poolConfig.setMaxIdle(32);//最大闲置连接数
        poolConfig.setMinIdle(32);//最小闲置连接数
        poolConfig.setMaxWaitMillis(100*1000);//等待时间
        poolConfig.setBlockWhenExhausted(true);//时间耗尽是否等待
        poolConfig.setTestOnBorrow(true);//取连接的时候ping一下
        return poolConfig;
    }

    public static void release(JedisPool jedisPool, Jedis jedis){
        if(null!=jedis){
            jedisPool.returnResource(jedis);
        }
    }
}

LUA解决库存遗留问题

static String secKellScript = "local userid=KEYS[1];\n" +
            "local prodid=KEYS[2];\n" +
            "local qtkey=\"sk:\"..prodid..\":qt\";\n" +
            "local userkey=\"sk:\"..prodid..\":usr\";\n" +
            "local userExists=redis.call(\"sismember\",userkey,userid);\n" +
            "if tonumber(userExists)==1 then\n" +
            "return 2;\n" +
            "end\n" +
            "local num=redis.call(\"get\",qtkey);\n" +
            "if tonumber(num)<=0 then \n" +
            "return 0;\n" +
            "else\n" +
            "redis.call(\"decr\",qtkey);\n" +
            "redis.call(\"sadd\",userkey,userid);\n" +
            "end \n" +
            "return 1;";

    public static  boolean doDecKill(String userid,String pid){
        JedisPool poolInstance = JedisPoolUtil.getJedisPoolInstance("192.168.133.98", 6379, 60000,"lgy112233");
        Jedis jedis = poolInstance.getResource();

        String sha1=jedis.scriptLoad(secKellScript);
        Object result = jedis.evalsha(sha1, 2, userid, pid);
        String re = String.valueOf(result);
        if ("0".equals(re)){
            System.err.println("已经抢空");
        }else if ("1".equals(re)){
            System.err.println("抢购成功");
        }else if ("2".equals(re)){
            System.err.println("已经抢过");
        }else {
            System.err.println("抢购繁忙");
        }
        jedis.close();
        return  true;
    }

4.备份redis 并恢复(AOP,RDB)

以下两种测试工具任选一个使用就行,ab是linux系统下的,Jmeter是Windows下的,个人感觉还是ab好用。

RDB

手动备份命令:
save 和 bgsave
推荐使用后者,因为此时服务器还可以提供服务。

save 300 10   //表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么自动执行BGSAVE命令 

流程
1.cp 要备份的文件 备份的文件名
2.rm -rf 要备份的文件
3.mv 备份的文件 要备份的文件名,说白了就是替换掉原文件

AOP

appendonly yes 
appendfilename "appendonly.aof"
sudo /etc/init.d/redis-server restart 重启服务

redis-check-aof --fix appendonly.aof (文件名字)  修复AOP文件

重写触发命令

BGREWRITEAOF

四、Redis进阶

1.主从复制

复制原来的配置文件到一个新的位置,同时新建几个配置文件作为从服务器的配置文件,其中只能有一个主服务器,其他的均为从服务器。例如:	

在这里插入图片描述

主服务器

include redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb

从服务器

include redis.conf
pidfile /var/run/redis_6380.pid
port 6380
dbfilename dump6380.rdb
slaveof 127.0.0.1 6379
masterauth 112233

连接服务器:redis-cli -p 6380(端口) -a 密码

查看状态:info replication

2.反客为主

在从服务器端输入 slaveof no one,当主服务器死亡后,此从服务器会成为主服务器。

3.哨兵模式(自动替换)

新建一个sentinel.conf文件

sentinel monitor mymaster 127.0.0.1 6379 1
sentinel auth-pass mymaster lgy112233

在命令行启动:redis-sentinel sentinel.conf

4.集群

配置文件

1.主配置文件不变,复制过来一份

2.各端口配置文件,不同的地方就是端口号

include redis.conf
pidfile "/var/run/redis_6379.pid"
port 6379
dbfilename "dump6379.rdb"
cluster-enabled yes
cluster-config-file nodes-6379.conf
cluster-node-timeout 15000
masterauth "112233"

在这里插入图片描述

启动命令

来到redis安装包中的src中,打开命令行

redis-cli -a lgy112233  --cluster create --cluster-replicas 1 192.168.184.98:6379 192.168.184.98:6380 192.168.184.98:6381 192.168.184.98:6389 192.168.184.98:6390 192.168.184.98:6391

注意

启动端口前把所有的缓存文件删除,保证每个端口的keys * 都是空的。不然会报错误:

Node 192.168.184.98:6379 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0

基本命令

多个:mset name{user} lucy age{user} 20 adress{user} china

查询某个键插槽值:cluster keyslot 值

查询任一插槽中值得数量:cluster countkeysinslot 插槽值

查询插槽中值:cluster getkeysinslot 插槽值 num

查看状态:cluster nodes

配置某段插槽宕机后,整个系统的状态:

redis.conf中的

cluster-require-full-coverage yes 整个集群挂掉 no 其他插槽依旧可以

常见问题

缓存穿透

redis中没有,命中率低,大量请求到数据库。

缓存击穿

redis中某个key过期,突然这个key的访问量突然加大,数据库访问量突然加大。

缓存雪崩

在极少的时间段,大量key集中过期的情况。

解决方式:随机缓存时间。

分布式锁

setnx key value 上锁

del key

expire key time 设置过期时间

set key value nx ex 12 同时设置锁和过期时间

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值