1.官网下载Redis
Redis官网(有时候需要)
2.上传下载好的包到Centos上并解压(本人上传到了usr/local目录下)
3.进入redis-5.0.3目录里,执行编译命令
make
4.编译完成之后,将redis安装到指定目录
make PREFIX=/usr/local/redis install
5.此时/usr/local/redis下生成了一个bin目录
至此安装完成;
1.启动方式一
前台启动,不推荐使用,进入/usr/local/redis/bin里执行启动命令(默认端口号:6379)
./redis-server
2.启动方式二
后台启动,推荐使用,将redis-5.0.3目录下的redis.conf文件复制到 /usr/local/redis/bin 下
cp redis.conf /usr/local/redis/bin/
修改redis.conf 设置为后台启动,将daemonize no改为daemonize yes即可(redis.conf文件内容较多,全局搜索:/daemonize 然后回车,再不断敲N即可)
启动Redis:进入/usr/local/redis/bin目录,执行下边命令
./redis-server /usr/local/redis/bin/redis.conf
由于redis.conf与redis-serve在同一级目录,所以执行下边命令即可
./redis-server redis.conf
关闭Redis:进入/usr/local/redis/bin目录,执行下边命令
./redis-cli shutdown
3.查看redis进程:
ps -ef | grep -i redis
看到如下信息:
root 1218 1 0 12:04 ? 00:00:00 /usr/local/redis/bin/redis-server 127.0.0.1:6379
root 1257 1186 0 12:11 pts/0 00:00:00 grep --color=auto -i redis
可以杀死Redis进程:
kill -9 1218
4.客户端操作
进入 /usr/local/redis/bin/目录:
./redis-cli
此时就可以设置值并取值了:
1.允许远程连接Redis
redis 默认只允许自己的电脑(127.0.0.1)连接。如果想要其他电脑进行远程连接,将配置文件 redis.conf 中的 bind 127.0.0.1 注释掉(之前没注释,需要改为将其注释掉,默认只能连接本地)。
找到配置文件redis.conf中protected mode,默认protected mode yes,需要将其改为protected mode no
此时,远程就可以连接Redis了;
redis自带客户端连接远程redis服务端:
# 进入到redis安装目录的bin目录里,执行如下命令
./redis-cli -h 192.168.237.129 -p 6379
连接上以后就可以向服务端发送命令了,Redis提供了PING命令来测试客户端与Redis的连接是否正常,如果连接正常会收到回复PONG
使用set和get可以向redis设置数据、获取数据:
del删除指定key的内容:
del name
查看当前库中所有的key值:
key *
1、引入依赖
org.springframework.boot
spring-boot-starter-data-redis
2、配置文件里配置redis
spring:
application:
name: order-service
redis:
database: 0
#远程redis地址
host: 192.168.237.128
#redis默认端口就是6379
port: 6379
timeout: 2000
3、使用RedisTemplate操作Redis
@Autowired
private StringRedisTemplate redisTemplate;
...........................................
private Object saveOrderFail(int userId, int productId, HttpServletRequest request){
//监控报警 start
String saveOrderKey="save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
String ip=request.getRemoteAddr();
//注意,这里需要异步,不能同步,不然此处阻塞的话(发短息耗时的话)不会继续向下走
new Thread(()->{
if(StringUtils.isBlank(sendValue)){
System.out.println("调用发短信接口去发送短信;用户下单失败,请马上处理;"+ip);
//此时调用发短信接口去发送短信,略过
/**
* 在Redis里放入数据,防止一直不停的发短信
* 间隔时间20秒(正常比这个数据久,这里为了测试先设置20秒)
*/
redisTemplate.opsForValue().set(saveOrderKey,"save-order-fail",20, TimeUnit.SECONDS);
}else{
System.out.println("已经发生过短信了,20秒内不要发了");
}
}).start();
//监控报警 end
Map<String,Object> msg=new HashMap<>();
msg.put("code",-1);
msg.put("msg","抢购人数太多,稍后重试");
return msg;
}
使用RedisTemplate从redis里取数据
//从缓存里取数据
public Article findById(String id) {
//从缓存中提取
Article article=(Article)redisTemplate.opsForValue().get("article_"+id);
// 如果缓存没有则到数据库查询并放入缓存
if(article==null) {
article = articleDao.findById(id).get();
redisTemplate.opsForValue().set("article_" + id, article);
}
return article;
}
修改或删除数据库时使用RedisTemplate清空缓存
/**
* 修改
* @param article
*/
public void update(Article article) {
redisTemplate.delete( "article_" + article.getId() );//删除缓存
articleDao.save(article);
}
/**
* 删除
* @param id
*/
public void deleteById(String id) {
redisTemplate.delete( "article_" + id );//删除缓存
articleDao.deleteById(id);
}
使用RedisTemplate设置缓存过期时间
//过期时间为一天
redisTemplate.opsForValue().set("article_" + id, article,1,
TimeUnit.DAYS);
//过期时间为10秒
redisTemplate.opsForValue().set("article_" + id, article,10,
TimeUnit.SECONDS);
下载安装包
官网下载
解压
tar -xvf redis-4.0.9.tar.gz
mv redis-4.0.9 redis
cd redis
make && make install
修改安装目录下的redis.conf文件
vim redis.conf
修改以下配置:
#bind 127.0.0.1 # 将这行代码注释,监听所有的ip地址,外网可以访问
protected-mode no # 把yes改成no,允许外网访问
daemonize yes # 把no改成yes,后台运行
redis提供了服务端命令和客户端命令:
vim /etc/init.d/redis
输入下面内容:
#!/bin/sh
# chkconfig: 2345 90 10
# description: Redis is a persistent key-value database
PATH=/usr/local/bin:/sbin:/usr/bin:/bin
REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli
PIDFILE=/var/run/redis.pid
CONF="/home/leyou/redis/redis.conf"
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed"
else
echo "Starting Redis server..."
$EXEC $CONF
fi
if [ "$?"="0" ]
then
echo "Redis is running..."
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE does not exist, process is not running"
else
PID=$(cat $PIDFILE)
echo "Stopping ..."
$REDIS_CLI -p $REDISPORT SHUTDOWN
while [ -x ${PIDFILE} ]
do
echo "Waiting for Redis to shutdown ..."
sleep 1
done
echo "Redis stopped"
fi
;;
restart|force-reload)
${0} stop
${0} start
;;
*)
echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
exit 1
esac
然后保存退出
注意:以下信息需要根据安装目录进行调整:
EXEC=/usr/local/bin/redis-server # 执行脚本的地址
REDIS_CLI=/usr/local/bin/redis-cli # 客户端执行脚本的地址
PIDFILE=/var/run/redis.pid # 进程id文件地址
CONF="/usr/local/src/redis-3.0.2/redis.conf" #配置文件地址
2)设置权限
chmod 755 /etc/init.d/redis
3)启动测试
/etc/init.d/redis start
启动成功会提示如下信息:
Starting Redis server...
Redis is running...
4)设置开机自启动
chkconfig --add /etc/init.d/redis
chkconfig redis on
cd 6379
./redis-server . /redis.conf
cd 6380
./redis-server . /redis.conf
此时,查看redis进程,发现有两个redis进程在运行
一个redis进程就是一个redis实例,一台服务器可以同时有多个redis实例,不同的redis实例提供不同的服务端口对外提供服务,每个redis实例之间互相影响。每个redis实例都包括自己的数据库,数据库中可以存储自己的数据。
一个Redis实例可以包括多个数据库,客户端可以指定连接某个redis实例的哪个数据库,就好比一个mysql中创建多个数据库,客户端连接时指定连接哪个数据库。
一个redis实例最多可提供16个数据库,下标从0到15,客户端默认连接第0号数据库,也可以通过select选择连接哪个数据库,如下连接1号库:
在1号库中查询上节设置的数据,结果查询不到:
重新选择第0号数据库,查询数据:
如果选择一个不存在的数据库则会报错:
注意:redis不支持修改数据库的名称,只能通过select 0、select 1…选择数据库
在0号数据库存储数据,在1号数据库执行清空数据命令却把0号数据库的数据给清空了:
建议:不同的应用系统要使用不同的redis实例,而不是使用同一个redis实例下的不同数据库。
Redis不仅是使用命令来操作,现在基本上主流的语言都有客户端支持,比如java、C、C#、C++、php、Node.js、Go等。
在官方网站里列一些Java的客户端,有Jedis、Redisson、Jredis、JDBC-Redis等,其中官方推荐使用Jedis和Redisson。 在企业中用的最多的就是Jedis,下面我们就重点学习下Jedis。 Jedis同样也是托管在github上,地址:https://github.com/xetorthio/jedis
添加依赖
redis.clients
jedis
2.7.0
通过创建单实例jedis对象连接redis服务,如下代码
// 单实例连接redis
@Test
public void testJedisSingle() {
Jedis jedis = new Jedis("192.168.237.129", 6379);
jedis.set("name", "bar");
String name = jedis.get("name");
System.out.println(name);
jedis.close();
}
连接超时解决
由于linux防火墙默认开启,redis的服务端口6379并不在开放规则之内,所有需要将此端口开放访问或者关闭防火墙。关闭防火墙命令:
sevice iptables stop
如果是修改防火墙规则,可以修改:/etc/sysconfig/iptables文件
通过单实例连接redis不能对redis连接进行共享,可以使用连接池对redis连接进行共享,提高资源利用率,使用jedisPool连接redis服务,如下代码:
@Test
public void pool() {
JedisPoolConfig config = new JedisPoolConfig();
//最大连接数
config.setMaxTotal(30);
//最大连接空闲数
config.setMaxIdle(2);
JedisPool pool = new JedisPool(config, "192.168.237.129", 6379);
Jedis jedis = null;
try {
jedis = pool.getResource();
jedis.set("name", "lisi");
String name = jedis.get("name");
System.out.println(name);
}catch(Exception ex){
ex.printStackTrace();
}finally{
if(jedis != null){
//关闭连接
jedis.close();
}
}
}
配置spring配置文件applicationContext.xml
测试代码:
private ApplicationContext applicationContext;
@Before
public void init() {
applicationContext = new ClassPathXmlApplicationContext(
"classpath:applicationContext.xml");
}
@Test
public void testJedisPool() {
JedisPool pool = (JedisPool) applicationContext.getBean("jedisPool");
try {
Jedis jedis = pool.getResource();
jedis.set("name", "lisi");
String name = jedis.get("name");
System.out.println(name);
}catch(Exception ex){
ex.printStackTrace();
}finally{
if(jedis != null){
//关闭连接
jedis.close();
}
}
}
Redis支持五种数据类型:
string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
赋值:
SET key value
127.0.0.1:6379> set test 123
OK
取值:
# 当键不存在时返回空结果。
GET key
127.0.0.1:6379> get test
"123“
取值时同时对key进行赋值操作
GETSET key value
删除:
Del key
127.0.0.1:6379> del test
(integer) 1
数值增减:
127.0.0.1:6379> incr num
(integer) 1
127.0.0.1:6379> incr num
(integer) 2
127.0.0.1:6379> incr num
(integer) 3
增加指定的整数
INCRBY key increment
示例:
127.0.0.1:6379> incrby num 2
(integer) 5
127.0.0.1:6379> incrby num 2
(integer) 7
127.0.0.1:6379> incrby num 2
(integer) 9
127.0.0.1:6379> decr num
(integer) 6
127.0.0.1:6379> decr num
(integer) 5
127.0.0.1:6379> decrby num 3
(integer) 2
127.0.0.1:6379> decrby num 3
(integer) -1
向尾部追加值
语法:APPEND key value
APPEND的作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于 SET key value。返回值是追加后字符串的总长度。
127.0.0.1:6379> set str hello
OK
127.0.0.1:6379> append str " world!"
(integer) 12
127.0.0.1:6379> get str
"hello world!"
获取字符串长度
语法:STRLEN key
STRLEN命令返回键值的长度,如果键不存在则返回0。
127.0.0.1:6379> strlen str
(integer) 0
127.0.0.1:6379> set str hello
OK
127.0.0.1:6379> strlen str
(integer) 5
同时设置/获取多个键值
MSET key value [key value …]
MGET key [key …]
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> get k1
"v1"
127.0.0.1:6379> mget k1 k3
1) "v1"
2) "v3"
String类型应用场景:
自增主键:商品编号、订单号采用string的递增数字特性生成。
定义商品编号key:items:id
192.168.101.3:7003> INCR items:id
(integer) 2
192.168.101.3:7003> INCR items:id
(integer) 3
redis使用string类型存在的问题:
假设有User对象以JSON序列化的形式存储到Redis中,User对象有id,username、password、age、name等属性,存储的过程如下:
保存、更新:
User对象 ——>json(string) ——> redis
如果在业务上只是更新age属性,其他的属性并不做更新我应该怎么做呢? 如果仍然采用上边的方法在传输、处理时会造成资源浪费,下边讲的hash可以很好的解决这个问题。
redis hash介绍
hash叫散列类型,它提供了字段和字段值的映射。字段值只能是字符串类型,不支持散列类型、集合类型等其它类型。如下:
Hash命令
赋值:
HSET key field value 一次只能设置一个字段值
127.0.0.1:6379> hset user username zhangsan
(integer) 1
HMSET key field value [field value …] 一次可以设置多个字段值
127.0.0.1:6379> hmset user age 20 username lisi
OK
取值:
HGET key field 一次只能获取一个字段值
127.0.0.1:6379> hget user username
"zhangsan“
HMGET key field [field …] 一次可以获取多个字段值
127.0.0.1:6379> hmget user age username
1) "20"
2) "lisi"
HGETALL key
127.0.0.1:6379> hgetall user
1) "age"
2) "20"
3) "username"
4) "lisi"
HSET命令不区分插入和更新操作,当执行插入操作时HSET命令返回1,当执行更新操作时返回0.
删除:
可以删除一个或多个字段,返回值是被删除的字段个数
HDEL key field [field …]
127.0.0.1:6379> hdel user age
(integer) 1
127.0.0.1:6379> hdel user age name
(integer) 0
127.0.0.1:6379> hdel user age username
(integer) 1
增加数字:
HINCRBY key field increment
127.0.0.1:6379> hincrby user age 2 将用户的年龄加2
(integer) 22
127.0.0.1:6379> hget user age 获取用户的年龄
"22“
其他命令:
判断字段是否存在
HEXISTS key field
127.0.0.1:6379> hexists user age 查看user中是否有age字段
(integer) 1
127.0.0.1:6379> hexists user name 查看user中是否有name字段
(integer) 0
HSETNX key field value
当字段不存在时赋值,类似HSET,区别在于如果字段已经存在,该命令不执行任何操作。
127.0.0.1:6379> hsetnx user age 30 如果user中没有age字段则设置age值为30,否则不做任何操作
(integer) 0
只获取字段名或字段值
HKEYS key
HVALS key
127.0.0.1:6379> hmset user age 20 name lisi
OK
127.0.0.1:6379> hkeys user
1) "age"
2) "name"
127.0.0.1:6379> hvals user
1) "20"
2) "lisi"
获取字段数量
HLEN key
127.0.0.1:6379> hlen user
(integer) 2
Hash类型应用场景:
商品id、商品名称、商品描述、商品库存、商品好评
定义商品信息的key:
商品1001的信息在 redis中的key为:items:1001
存储商品信息
192.168.101.3:7003> HMSET items:1001 id 3 name apple price 999.9
OK
获取商品信息
192.168.101.3:7003> HGET items:1001 id
"3"
192.168.101.3:7003> HGETALL items:1001
1) "id"
2) "3"
3) "name"
4) "apple"
5) "price"
6) "999.9"
ArrayList与LinkedList的区别
ArrayList使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者删除元素时需要设计到位移操作,所以比较慢。
LinkedList使用双向链接方式存储数据,每个元素都记录前后元素的指针,所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快,然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或后几个元素速度比较快。
redis list介绍
列表类型(list)可以存储一个有序的字符串列表,常用的操作是向列表两端添加元素,或者获得列表的某一个片段。
列表类型内部是使用双向链表(double linked list)实现的,所以向列表两端添加元素的时间复杂度为0(1),获取越接近两端的元素速度就越快。这意味着即使是一个有几千万个元素的列表,获取头部或尾部的10条记录也是极快的。
list类型命令
向列表两端增加元素
LPUSH key value [value …]
RPUSH key value [value …]
向列表左边增加元素
127.0.0.1:6379> lpush list:1 1 2 3
(integer) 3
向列表右边增加元素
127.0.0.1:6379> rpush list:1 4 5 6
(integer) 3
查看列表
LRANGE key start stop
LRANGE命令是列表类型最常用的命令之一,获取列表中的某一片段,将返回start、stop之间的所有元素(包含两端的元素),索引从0开始。索引可以是负数,如:“-1”代表最后边的一个元素。
127.0.0.1:6379> lrange list:1 0 2
1) "2"
2) "1"
3) "4"
从列表两端弹出元素
LPOP key
RPOP key
LPOP命令从列表左边弹出一个元素,会分两步完成,第一步是将列表左边的元素从列表中移除,第二步是返回被移除的元素值。
127.0.0.1:6379> lpop list:1
"3“
127.0.0.1:6379> rpop list:1
"6“
获取列表中元素的个数
LLEN key
127.0.0.1:6379> llen list:1
(integer) 2
删除列表中指定的值
LREM key count value
LREM命令会删除列表中前count个值为value的元素,返回实际删除的元素个数。根据count值的不同,该命令的执行方式会有所不同:
当count>0时, LREM会从列表左边开始删除。
当count<0时, LREM会从列表后边开始删除。
当count=0时, LREM删除所有值为value的元素。
获得/设置指定索引的元素值
LINDEX key index
LSET key index value
127.0.0.1:6379> lindex l:list 2
"1"
127.0.0.1:6379> lset l:list 2 2
OK
127.0.0.1:6379> lrange l:list 0 -1
1) "6"
2) "5"
3) "2"
4) "2"
只保留列表指定片段,指定范围和LRANGE一致
LTRIM key start stop
127.0.0.1:6379> lrange l:list 0 -1
1) "6"
2) "5"
3) "0"
4) "2"
127.0.0.1:6379> ltrim l:list 0 2
OK
127.0.0.1:6379> lrange l:list 0 -1
1) "6"
2) "5"
3) "0"
向列表中插入元素
LINSERT key BEFORE|AFTER pivot value
该命令首先会在列表中从左到右查找值为pivot的元素,然后根据第二个参数是BEFORE还是AFTER来决定将value插入到该元素的前面还是后面。
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> linsert list after 3 4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
4) "1"
将元素从一个列表转移到另一个列表中
RPOPLPUSH source destination
127.0.0.1:6379> rpoplpush list newlist
"1"
127.0.0.1:6379> lrange newlist 0 -1
1) "1"
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "4"
3) "2"
list类型应用场景
在redis中创建商品评论列表
用户发布商品评论,将评论信息转成json存储到list中。
用户在页面查询评论列表,从redis中取出json数据展示到页面。
定义商品评论列表key:
商品编号为1001的商品评论key:items: comment:1001
192.168.237.129:7001> LPUSH items:comment:1001 '{"id":1,"name":"商品不错,很好!!","date":1430295077289}'
set类型介绍
在set集合中的每个元素都是不同的,且没有顺序。
集合类型和列表类型的对比:
集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型的Redis内部是使用值为空的散列表实现,所有这些操作的时间复杂度都为0(1)。
Redis还提供了多个集合之间的交集、并集、差集的运算。
set类型常用命令
增加/删除元素
SADD key member [member …]
SREM key member [member …]
127.0.0.1:6379> sadd set a b c
(integer) 3
127.0.0.1:6379> sadd set a
(integer) 0
127.0.0.1:6379> srem set c d
(integer) 1
获得集合中的所有元素
SMEMBERS key
127.0.0.1:6379> smembers set
1) "b"
2) "a”
判断元素是否在集合中,无论集合中有多少元素都可以极速的返回结果。
SISMEMBER key member
127.0.0.1:6379> sismember set a
(integer) 1
127.0.0.1:6379> sismember set h
(integer) 0
集合的差集运算 A-B
属于A并且不属于B的元素构成的集合。
SDIFF key [key …]
127.0.0.1:6379> sadd setA 1 2 3
(integer) 3
127.0.0.1:6379> sadd setB 2 3 4
(integer) 3
127.0.0.1:6379> sdiff setA setB
1) "1"
127.0.0.1:6379> sdiff setB setA
1) "4"
集合的交集运算 A ∩ B
属于A且属于B的元素构成的集合
SINTER key [key …]
127.0.0.1:6379> sinter setA setB
1) "2"
2) "3"
集合的并集运算 A ∪ B
属于A或者属于B的元素构成的集合
SUNION key [key …]
127.0.0.1:6379> sunion setA setB
1) "1"
2) "2"
3) "3"
4) "4"
获得集合中元素的个数
SCARD key
127.0.0.1:6379> smembers setA
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> scard setA
(integer) 3
从集合中弹出一个元素
SPOP key
127.0.0.1:6379> spop setA
"1“
注意:由于集合是无序的,所有SPOP命令会从集合中随机选择一个元素弹出
sorted set类型介绍
在集合类型的基础上,有序集合类型(sorted set)为集合中的每个元素都关联一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在在集合中,还能够获得分数最高或最低的前N个元素、获取指定分数范围内的元素等与分数有关的操作。
在某些方面有序集合和列表类型有些相似。
1、二者都是有序的。
2、二者都可以获得某一范围的元素。
但是,二者有着很大区别:
1、列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会变慢。
2、有序集合类型使用散列表实现,所有即使读取位于中间部分的数据也很快。
3、列表中不能简单的调整某个元素的位置,但是有序集合可以(通过更改分数实现)
4、有序集合要比列表类型更耗内存。
sorted set类型常用命令
增加元素
向有序集合中加入一个元素和该元素的分数,如果该元素已经存在则会用新的分数替换原有的分数。返回值是新加入到集合中的元素个数,不包含之前已经存在的元素。
ZADD key score member [score member …]
127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu
(integer) 3
127.0.0.1:6379> zadd scoreboard 97 lisi
(integer) 0
获取元素的分数
ZSCORE key member
127.0.0.1:6379> zscore scoreboard lisi
删除元素
ZREM key member [member …]
移除有序集key中的一个或多个成员,不存在的成员将被忽略。
当key存在但不是有序集类型时,返回一个错误。
127.0.0.1:6379> zrem scoreboard lisi
(integer) 1
获得排名在某个范围的元素列表
获得排名在某个范围的元素列表
ZRANGE key start stop [WITHSCORES] 照元素分数从小到大的顺序返回索引从start到stop之间的所有元素(包含两端的元素)
127.0.0.1:6379> zrange scoreboard 0 2
1) "zhangsan"
2) "wangwu"
3) "lisi“
ZREVRANGE key start stop [WITHSCORES] 照元素分数从大到小的顺序返回索引从start到stop之间的所有元素(包含两端的元素)
127.0.0.1:6379> zrevrange scoreboard 0 2
1) " lisi "
2) "wangwu"
3) " zhangsan “
如果需要获得元素的分数的可以在命令尾部加上WITHSCORES参数
127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES
1) "zhangsan"
2) "80"
3) "wangwu"
4) "94"
获得指定分数范围的元素
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES
1) "wangwu"
2) "94"
3) "lisi"
4) "97"
127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2
1) "wangwu"
2) "lisi"
增加某个元素的分数,返回值是更改后的分数。
ZINCRBY key increment member
给lisi加4分
127.0.0.1:6379> ZINCRBY scoreboard 4 lisi
"101“
获得集合中元素的数量
ZCARD key
127.0.0.1:6379> ZCARD scoreboard
(integer) 3
获得指定分数范围内的元素个数
ZCOUNT key min max
127.0.0.1:6379> ZCOUNT scoreboard 80 90
(integer) 1
按照排名范围删除元素
ZREMRANGEBYRANK key start stop
127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1
(integer) 2
127.0.0.1:6379> ZRANGE scoreboard 0 -1
1) "lisi"
ZREMRANGEBYSCORE key min max
按照分数范围删除元素
127.0.0.1:6379> zadd scoreboard 84 zhangsan
(integer) 1
127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100
(integer) 1
获取元素的排名
ZRANK key member
ZREVRANK key member
从小到大
127.0.0.1:6379> ZRANK scoreboard lisi
(integer) 0
从大到小
127.0.0.1:6379> ZREVRANK scoreboard zhangsan
(integer) 1
应用场景
根据商品销售量对商品进行排行显示,定义sorted set集合,商品销售量为元素的分数。
定义商品销售排行榜key:items:sellsort
写入商品销售量:
商品编号1001的销量是9,商品编号1002的销量是10
192.168.101.3:7007> ZADD items:sellsort 9 1001 10 1002
商品编号1001的销量加1
192.168.101.3:7001> ZINCRBY items:sellsort 1 1001
商品销量前10名:
192.168.101.3:7001> ZRANGE items:sellsort 0 9 withscores
Redis在实际使用过程中更多的用作缓存,然而缓存的数据一般都是需要设置生存时间的,即:到期后数据销毁。
例子:
192.168.101.3:7002> set test 1 设置test的值为1
OK
192.168.101.3:7002> get test 获取test的值
"1"
192.168.101.3:7002> EXPIRE test 5 设置test的生存时间为5秒
(integer) 1
192.168.101.3:7002> TTL test 查看test的生于生成时间还有1秒删除
(integer) 1
192.168.101.3:7002> TTL test
(integer) -2
192.168.101.3:7002> get test 获取test的值,已经删除
(nil)
redis 127.0.0.1:6379> keys mylist*
1) "mylist"
2) "mylist5"
3) "mylist6"
4) "mylist7"
5) "mylist8"
redis 127.0.0.1:6379> exists HongWan
(integer) 0
redis 127.0.0.1:6379> exists age
(integer) 1
redis 127.0.0.1:6379>
从结果来数据库中不存在HongWan 这个key,但是age 这个key 是存在的
redis 127.0.0.1:6379> del age
(integer) 1
redis 127.0.0.1:6379> exists age
(integer) 0
redis 127.0.0.1:6379>
从结果来数据库中不存在HongWan 这个key,但是age 这个key 是存在的
redis 127.0.0.1:6379[1]> keys *
1) "age"
redis 127.0.0.1:6379[1]> rename age age_new
OK
redis 127.0.0.1:6379[1]> keys *
1) "age_new"
redis 127.0.0.1:6379[1]>
age 成功的被我们改名为age_new 了
redis 127.0.0.1:6379> type addr
string
redis 127.0.0.1:6379> type myzset2
zset
redis 127.0.0.1:6379> type mylist
list
redis 127.0.0.1:6379>
这个方法可以非常简单的判断出值的类型
ping
测试连接是否存活(返回PONG代表存活)
redis 127.0.0.1:6379> ping
PONG
//执行下面命令之前,我们停止redis 服务器
redis 127.0.0.1:6379> ping
Could not connect to Redis at 127.0.0.1:6379: Connection refused
//执行下面命令之前,我们启动redis 服务器
not connected> ping
PONG
redis 127.0.0.1:6379>
第一个ping 时,说明此连接正常
第二个ping 之前,我们将redis 服务器停止,那么ping 是失败的
第三个ping 之前,我们将redis 服务器启动,那么ping 是成功的
echo
在命令行打印一些内容
redis 127.0.0.1:6379> echo HongWan
"HongWan"
redis 127.0.0.1:6379>
select
选择数据库。Redis 数据库编号从0~15,我们可以选择任意一个数据库来进行数据的存取。
redis 127.0.0.1:6379> select 1
OK
redis 127.0.0.1:6379[1]> select 16
(error) ERR invalid DB index
redis 127.0.0.1:6379[16]>
当选择16 时,报错,说明没有编号为16 的这个数据库
quit
退出连接。
redis 127.0.0.1:6379> quit
dbsize
返回当前数据库中key 的数目。
redis 127.0.0.1:6379> dbsize
(integer) 18
redis 127.0.0.1:6379>
结果说明此库中有18 个key
info
获取服务器的信息和统计。
redis 127.0.0.1:6379> info
redis_version:2.2.12
redis_git_sha1:00000000
redis_git_dirty:0
arch_bits:32
multiplexing_api:epoll
process_id:28480
uptime_in_seconds:2515
uptime_in_days:0
。。。。
。。。。
flushdb
删除当前选择数据库中的所有key。
redis 127.0.0.1:6379> dbsize
(integer) 18
redis 127.0.0.1:6379> flushdb
OK
redis 127.0.0.1:6379> dbsize
(integer) 0
redis 127.0.0.1:6379>
在本例中我们将0 号数据库中的key 都清除了。
flushall
删除所有数据库中的所有key。
redis 127.0.0.1:6379[1]> dbsize
(integer) 1
redis 127.0.0.1:6379[1]> select 0
OK
redis 127.0.0.1:6379> flushall
OK
redis 127.0.0.1:6379> select 1
OK
redis 127.0.0.1:6379[1]> dbsize
(integer) 0
redis 127.0.0.1:6379[1]>
在本例中我们先查看了一个1 号数据库中有一个key,然后我切换到0 号库执行flushall 命令,结果1 号库中的key 也被清除了,说是此命令工作正常。
Redis的高性能是由于其将所有数据都存储在了内存中,为了使Redis在重启之后仍能保证数据不丢失,需要将数据从内存中同步到硬盘中,这一过程就是持久化。
Redis支持两种方式的持久化,一种是RDB方式,一种是AOF方式。可以单独使用其中一种或将二者结合使用。
RDB方式的持久化是通过快照(snapshotting)完成的,当符合一定条件时Redis会自动将内存中的数据进行快照并持久化到硬盘。
RDB是Redis默认采用的持久化方式,在redis.conf配置文件中默认有此下配置:
save 900 1
save 300 10
save 60 10000
save 开头的一行就是持久化配置,可以配置多个条件(每行配置一个条件),每个条件之间是“或”的关系,“save 900 1”表示15分钟(900秒钟)内至少1个键被更改则进行快照,“save 300 10”表示5分钟(300秒)内至少10个键被更改则进行快照。
在redis.conf中:
配置dir指定rdb快照文件的位置
配置dbfilenam指定rdb快照文件的名称
Redis启动后会读取RDB快照文件,将数据从硬盘载入到内存。根据数据量大小与结构和服务器性能不同,这个时间也不同。通常将记录一千万个字符串类型键、大小为1GB的快照文件载入到内存中需要花费20~30秒钟。
问题总结:
通过RDB方式实现持久化,一旦Redis异常退出,就会丢失最后一次快照以后更改的所有数据。这就需要开发者根据具体的应用场合,通过组合设置自动快照条件的方式来将可能发生的数据损失控制在能够接受的范围。如果数据很重要以至于无法承受任何损失,则可以考虑使用AOF方式进行持久化。
默认情况下Redis没有开启AOF(append only file)方式的持久化,可以通过appendonly参数开启:
appendonly yes
开启AOF持久化后每执行一条会更改Redis中的数据的命令,Redis就会将该命令写入硬盘中的AOF文件。AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改:appendfilename appendonly.aof
持久化保证了即使redis服务重启也会丢失数据,因为redis服务重启后会将硬盘上持久化的数据恢复到内存中,但是当redis服务器的硬盘损坏了可能会导致数据丢失,如果通过redis的主从复制机制就可以避免这种单点故障,如下图:
说明:
主redis配置
无需特殊配置。
从redis配置
修改从redis服务器上的redis.conf文件,添加slaveof 主redisip 主redis端口
上边的配置说明当前该从redis服务器所对应的主redis是192.168.101.3,端口是6379
在redis2.8版本之前主从复制过程如下图:
复制过程说明:
1、slave 服务启动,slave 会建立和master 的连接,发送sync 命令。
2、master启动一个后台进程将数据库快照保存到RDB文件中
注意:此时如果生成RDB文件过程中存在写数据操作会导致RDB文件和当前主redis数据不一致,所以此时master 主进程会开始收集写命令并缓存起来。
3、master 就发送RDB文件给slave
4、slave 将文件保存到磁盘上,然后加载到内存恢复
5、master把缓存的命令转发给slave
注意:后续master 收到的写命令都会通过开始建立的连接发送给slave。
当master 和slave 的连接断开时slave 可以自动重新建立连接。如果master 同时收到多个slave 发来的同步连接命令,只会启动一个进程来写数据库镜像,然后发送给所有slave。
完整复制的问题:
在redis2.8之前从redis每次同步都会从主redis中复制全部的数据,如果从redis是新创建的从主redis中复制全部的数据这是没有问题的,但是,如果当从redis停止运行,再启动时可能只有少部分数据和主redis不同步,此时启动redis仍然会从主redis复制全部数据,这样的性能肯定没有只复制那一小部分不同步的数据高。
部分复制说明:
从机连接主机后,会主动发起 PSYNC 命令,从机会提供 master的runid(机器标识,随机生成的一个串) 和 offset(数据偏移量,如果offset主从不一致则说明数据不同步),主机验证 runid 和 offset 是否有效, runid 相当于主机身份验证码,用来验证从机上一次连接的主机,如果runid验证未通过则,则进行全同步,如果验证通过则说明曾经同步过,根据offset同步部分数据。
Redis事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断,Redis事务的主要作用就是串联多个命令防止别的命令插队。
从输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行,直到输入Exec后,Redis会将之前的命令队列中的命令依次执行。组队的过程中可以通过discard命令来放弃组队。
组队中某个命令出现了报告错误,执行时整个的所有队列会都会被取消。
执行阶段某个命令报出了错误,则只有报错的命令不会被执行,而其他的命令都会执行,不会回滚。
场景:
你的账户里有10000块钱,有三人有你的账户,同时去参加双十一抢购:
一个请求想给金额减8000
一个请求想给金额减5000
一个请求想给金额减1000
Redis事物锁
悲观锁
顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。
有了版本控制,每次执行操作的时候,都先查询版本,版本不连续的话,再去查redis里的数据和版本,查完之后,版本连续的话再继续做下一步的"增删更新"等操作;
即关系型数据库一般采用的是悲观锁,而非关系型数据库使用的是乐观锁
语法
WATCH key [key ...]
在执行multi之前,先执行watch key1 [key2],可以监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。
取消锁
unwatch命令
取消 WATCH 命令对所有 key 的监视。
如果在执行 WATCH命令之后, EXEC命令或 DISCARD命令先被执行了的话,那么就不需要再执行 UNWATCH了。
单独的隔离操作
事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
没有隔离级别的概念
队列中的命令没有提交之前都不会实际的被执行,因为事务提交前任何指令都不会被实际执行,也就不存在“事务内的查询要看到事务里的更新,在事务外查询不能看到”这个让人万分头痛的问题 。
不保证原子性
Redis同一个事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚 。
介绍
主从复制,就是主机数据更新后根据配置和策略,自动同步到备机的master/slaver机制,Master以写为主,Slave以读为主,一个主机可以有多个从机,一个从机只能有一个主机;
优势
读写分离,性能扩展
容灾快速恢复
配置从机即可,不需要配置主机(下边的才操作都是在从机里进行的配置,主机不用动)
# 新建redis6379.conf文件
vim redis6379.conf
在新建的文件里写入如下内容
# 包含之前的配置信息:redis.conf
include /root/myredis/redis.conf
开启守护进程(后台运行),如果include后的redis.conf文件里已经开启了,则新建的redis6379.conf文件里就不用再开启了;
# 从redis.conf里复制即可
pidfile /var/run/redis_6379.pid
port 6379
主机里配置过了的话,这里就不用再配置了
Dump.rdb名字改为自己的名字
dbfilename dump6379.rdb
Appendonly 关掉或者换名字
主机里配置过了的话,这里就不用再配置了
配置完成后的redis6379.conf文件如下:
include /root/myredis/redis.conf
pidfile /var/run/redis_6379.pid
port 6379
dbfilename dump6379.rdb
然后在多台从机里配置上边的文件(记得改端口号)
打印主从复制的相关信息
# 启动redis,进入客户端后(redis-cli -p 6379)执行如下命令,查看当前机器是主还是从
info replication
配置当前从机的主机信息
# 配置主机的地址端口信息
slaveof ip port
此时,主机里写入信息后,从机就可以查到了,从机只能获取信息,不可以写入信息;
主机宕机
这种情况下,一旦主机宕机的话,就没有可以写入信息的redis机器了(从机不可以自动上位为主机),从机执行info replication命令,也可以查看主机状态:
此时,主机恢复正常以后:
主机恢复后,从机依然可以获取到主机宕机前的数据;
从机宕机
从机宕机,再恢复后,还得重新找一下主机,即需要重新执行slaveof ip port命令来找主机;
切入点问题:slave1、slave2是从头开始复制还是从切入点开始复制?比如从k4进来,那之前的123是否也可以复制
从头开始复制,不是从切入点开始;
从机是否可以写?set可否?
不可以
原地待命
可以顺利复制
不可以跟上大部队,只能重新找主机,然后再复制主机里的信息;
复制原理
每次从机联通后,都会给主机发送sync指令
主机立刻进行存盘操作,发送RDB文件,给从机
从机收到RDB文件后,进行全盘加载
之后每次主机的写操作,都会立刻发送给从机,从机执行相同的命令
假如主机宕机,哨兵检测到后,根据投票数自动将从机转换为主机
配置哨兵
自定义的/myredis目录下新建sentinel.conf文件,在配置文件中填写内容:
#127.0.0.1 6379指的是当前主机的信息(还没宕机的主机) 1指的是主机宕机的话,至少有一台机器同意,则将从机变为主机
sentinel monitor mymaster 127.0.0.1 6379 1
配置从机变成为主机的优先级
在从机配置文件redis_6381.conf里进行配置
# 主机宕机后,当前从机成为主机的优先级,值越小,优先级越高
slave-priority 10
启动哨兵
执行redis-sentinel /myredis/sentinel.conf
此时,主机宕机的话,会根据优先级将从机自动生成新主机,并且其他从机立刻自动将新主机作为主机
选择新主机的条件
选择优先级靠前的:优先级配置在redis.conf中slave-priority 100
选择偏移量最大的:偏移量是指获得原主数据最多的
选择runid最小的从服务:每个redis实例启动后都会随机生成一个40位的runid
挑选出新的主服务之后,sentinel 向原主服务的从服务发送 slaveof 新主服务 的命令,复制新master;
当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主的从;
问题
容量不够,redis如何进行扩容?
并发写操作, redis如何分摊?
分布式与集群
如果一个项目里有多个信息:用户信息、订单信息、商品信息
分布式:
将用户信息、订单信息、商品信息分别存储在三台机器上(每台机器放不一样的内容)
集群:
将每一个信息平均分成三份,再将三份信息分别放在三台机器里
什么是redis集群
Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N。
Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。
安装ruby环境
注意:安装ruby之前,要把前边配置的rdb文件删除,不然会有很多问题出现
yum install ruby;
yum install rubygems;
下载redis-3.2.0.gem
打开一遍的网址:https://rubygems.org/gems/redis/versions/
进入3.2.0版本后,点击右下角的下载即可
安装redis-3.2.0.gem
# 将上边下载下来的文件复制到/opt目录,然后执行下边的命令进行安装
gem install --local redis-3.2.0.gem
制作六个redis实例(用来模拟集群)
端口分别是6379、6380、6381、6389、6390、6391,其中6389是6379的从机,8390是6380的从机,6391是6381的从机,然后参考14.2章节进行配置
安装redis cluster配置修改
vim redis6379.conf
# 在配置文件里打开集群模式
cluster-enabled yes
# 设定节点配置文件名
cluster-config-file nodes-6379.conf
# 设定节点失联时间,超过该时间(毫秒),集群自动进行主从切换。
cluster-node-timeout 15000
然后将redis6379.conf文件复制六份即可(内容记得做相应的修改,6379的地方都改为自己的端口号)
将六个节点合成一个集群
组合之前,请确保所有redis实例启动后,nodes-xxxx.conf文件都生成正常(即所有实例启动后,会生成各自对应的nodes-xxxx.conf文件)。
进入到redis解压后的目录的src里
cd /opt/redis-3.2.5/src
执行如下命令
# 切记:此处使用真实ip,不可以使用127.0.0.1
./redis-trib.rb create --replicas 1 192.168.1.100:6379 192.168.1.100:6380 192.168.1.100:6381 192.168.1.100:6389 192.168.1.100:6390 192.168.1.100:6391
输入yes后:
进入6379机器
redis-cli -p 6379
执行如下命令查看集群信息
cluster nodes
此时,在6379上写入数据,会有如下提示
MOVED 12706 192.168.1.100:6381
提示需要移动到6381机器上进行操作,这是因为redis一共16384个插槽,通过计算我们要插入的key的hash值(上边的截图可以看出来,计算后的key的hash值是12706),会将对应的值插入相应的插槽里
我们登陆的是6379机器,遇到上边的情况,本来会自动将数据插入到6381机器里,但是因为我们进入redis的方式不对,所以会出现MOVED 12706 192.168.1.100:6381提示
正确进入6379(集群)的方式:
# -c:自动重定向 -p:端口
redis-cli -c -p 6379
可以看到,自动切换到6381机器了;
一个集群至少要有三个主节点
选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。
一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。
集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:
节点 A 负责处理 0 号至 5500 号插槽。
节点 B 负责处理 5501 号至 11000 号插槽。
节点 C 负责处理 11001 号至 16383 号插槽。
在集群中录入值
在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口。
redis-cli客户端提供了 –c 参数实现自动重定向。如 redis-cli -c –p 6379 登入后,再录入、查询键值对可以自动重定向。
不在一个slot下的键值,是不能使用mget,mset等多键操作。
可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去。
查询集群中的值
CLUSTER KEYSLOT 计算键 key 应该被放置在哪个槽上。
CLUSTER COUNTKEYSINSLOT 返回槽 slot 目前包含的键值对数量。
CLUSTER GETKEYSINSLOT 返回 count 个 slot 槽中的键。
•如果主节点下线?从节点能否自动升为主节点?
•主节点恢复后,主从关系会如何?
•如果所有某一段插槽的主从节点都宕掉,redis服务是否还能继续?
•redis.conf中的参数 cluster-require-full-coverage
配置故障恢复参数
# redis.conf中的参数
cluster-require-full-coverage
public class JedisClusterTest {
public static void main(String[] args ) {
Set<HostAndPort> set =new HashSet<HostAndPort>();
set.add(new HostAndPort("192.168.1.100",6379));
set.add(new HostAndPort("192.168.1.100",6380));
set.add(new HostAndPort("192.168.1.100",6381));
set.add(new HostAndPort("192.168.1.100",6389));
set.add(new HostAndPort("192.168.1.100",6390));
set.add(new HostAndPort("192.168.1.100",6391));
//注意:这里new的是集群JedisCluster
JedisCluster jedisCluster=new JedisCluster(set);
jedisCluster.set("k1", "v1");
System.out.println(jedisCluster.get("k1"));
}
}
集群优势
实现扩容
分摊压力
无中心配置相对简单
集群不足
多键操作是不被支持的
多键的Redis事务是不被支持的。lua脚本不被支持。
由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。