文章目录
前言
本篇文章对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 同时设置锁和过期时间