redis学习笔记

1. 概述

 首先提一下下NoSQL的概念,应读做“嗯欧SQL”,而不是“肉SQL”,等价于Not Only SQL。非关系型数据库(NoSQL)的产生于WEB2.0,传统的web1.0是不可以进行互动的,到了web2.0是可以进行互动的,比如新浪微博。。。传统关系型数据库在应对web2.0(尤其是超大规模和高并发的SNS类型)暴露出很大的问题,比如

  1. 高并发读写问题;
  2. 海量数据的高效率存储和访问;
  3. 高可扩展性和高可用性;

常见的NOSQL有redis、mongoDB等,NoSQL所具备的优势有:

  1. 易扩展性;
  2. 灵活的数据模型;
  3. 大数据量,高性能;
  4. 高可用;

NoSQL数据库的分类主要有四类:

  1. 键值存储(优点:快速查询;劣势:数据缺少结构化;如redis);
  2. 列存储(优点:查找快,可扩展性强;劣势:功能局限。如HBase);
  3. 文档数据库(优点:数据结构的要求不是特别严格;劣势:查询的速度不高,缺少统一的语法;如mongoDB、CounchDB);
  4. 图形数据库(优点:利用图做数据结构;劣势:需要对图做计算,不容易作分路式集群方案;如InfoGrid);

2. redis常用操作

 位置/user/local,启动进入redis的目录中操作即可。redis所支持的数据类型有:redis是高性能键值对数据库,支持的数据类型有字符串(String)、字符串列表(list)、有序集合(sorted set)、哈希(hash)、字符串集合(set)。redis的应用场景主要有:缓存、任务队列、应用排行榜、网站统计访问、数据过期处理、分布式集群架构中的session分离。一些命令行操作如下:

2.1 通用操作

  1. 获取所有的 key – keys *
  2. 获取所有以 my 开头的 key – keys my?(匹配单个字符)/keys my*(匹配多个字符);
  3. 删除多个 key – del key1 key2 key3(删除key值为key1、key2…的对象);
  4. 判断某个 key 是否存在(存在返回1,不存在返回0) – exists key1(判断 key 为 key1 的对象是否存在);
  5. 获取指定 key – get key1(获取 key 为 key1 的对象);
  6. 重命名指定的 key – rename oldKey newKey(将 key 为 oldKey 重命名为 newKey);
  7. 设置过期时间(单位为秒)-- expire key1 5(将 key 为 key1 的对象设置过期时间为5秒);
  8. 查看所剩的生命时长(相对设置的生命周期而言,若没有设置过期时间返回-1)-- ttl key1(查看 key 为 key1 对象的剩余生命时间);
  9. 查看指定 key 的类型 – type key1(查看 key 为 key1 的对象的类型);

2.2 string类型的操作

  1. 赋值 – set key1 value1
  2. 取值 – get key1
  3. 获取 key1 的旧值,同时将 key1 置成新值(输出原始值,然后重新赋值) – getset key1
  4. 删除(删除后在获取输出 nil,表示删除不存在了) – del key1
  5. 自增1(将 key 对应的 value 自增1并将结果返回,如果 key 不存在则自动创建该 key 默认为0,自增1变为1,如果 key 对应的 value 不能转成整型将报错) – incr key1
  6. 自减1(同上)-- decr key1
  7. 指定步长进行自加并返回(同上)-- incrby key1 increment1(即让 key1 对象转成整型并加上 increment1 之后返回);
  8. 指定步长进行自减并返回(同上)-- decrby key1 decrement1

2.3 hash类型

hash类型:string 类型的 key 和 value 的容器,适合存放对对象。通常会将对象序列化为 JSONString 再存储进来。

  1. 单个赋值 – hset hash1 property propertyValue(存储一个hash1对象,这个对象中有个 property-propertyValue 属性);
  2. 单个取值 – hget hash1 property(获取 hash1 中 property 属性的值);
  3. 多个赋值 – hmset hash1 property1 value1 property2 value2......(存储 hash1 对象,该对象中有多个key-value属性 );
  4. 多个取值 – hmget hash1 property1 property2......(获取 hash1 中多个 key 值);
  5. 获取该hash的所有属性 – hgetall hash1(获取 hash1 中的所有键值对,包含key值,而hgethmget只有value);
  6. 删除某一属性 – hdel hash1 property1
  7. 直接删除某个hashdel hash1
  8. hash中某一属性自增/自减 – hincrby/hdecrby hash1 property1 increment/decrement
  9. 判断该hash中是存在某个属性(存在返回1,不存在返回0) – hexists hash1 property1
  10. 返回该hash中属性的个数 – hlen hash1
  11. 返回该hash中所有属性名 – hkeys hash1
  12. 返回该hash中所有属性值 – hvals hash1

2.4 list类型

  1. 从左/右侧插入元素(开始list默认为空) – lpush/rpush list1 value1 value2....
  2. 从左/右弹出第一个元素 – lpop/rpop list1
  3. 查看list中元素(负数代表倒数第几个) – lrange list1 startIndex endIndex,比如0到-1表示查看整个list的元素;
  4. 从头到尾删除m个指定元素n – lrem list1 m n(当m为0时,表示删除所有的n);
  5. 修改下标为index的值为newValue – lset list1 index newVlaue
  6. 在指定元素前/后插入元素 – linsert list1 before/after 指定元素 待插入元素(若指定元素在 list 中有多个,插入行为只会发生在第一个指定元素的前后);
  7. 将list1的尾部元素返回并将其移动到list2的头部 – rpoplpush list1 list2

注:其实我实操下来,这里的list数据类型更像是一种双向队列,每次插入元素之前的元素索引都要向后挪一位,以lpush操作为例,依次插入1、2、3,实际是 3->2->1 这样的形式,此时如果lpop弹出的是3而不是1。默认也是3是头,1是尾。

2.5 set类型(无序,不重复)

  1. 添加元素 – sadd set1 value1 value2....
  2. 删除元素 – srem set1 value1 value2 .....
  3. 求差异(找出第一个set和第二个的不同,只显示第一个set中有但第二个set中没有元素) – sdiff set1 set2
    3.2 将相差的元素存到另一个set3中 – sdiffstore set3 set1 set2
  4. 求交集 – sinter set1 set2
    4.2 将交集的元素存到另一个set3中 – sinterstore set3 set1 set2
  5. 求并集 – sunion set1 set2
    5.2 将并集的元素存到另一个set3中 – sunionstore set3 set1 set2
  6. 查看set中的元素 – smembers set1"
  7. 获取set中元素的数量 – scard set1"
  8. 随意获取set中的一个元素 – srandmember set1"

2.6 sortedset类型

 有序的不重复,元素唯一但分数可重复,默认从小到大排序;

  1. 添加元素 – zadd sset1 score1 key1 score2 key2.....,如果增加已存在的元素,那么该元素的分数将会是最新的分数,注意是先分数再元素,不能搞反了,比如zadd sset2 1.0 key1 2.0 key2
  2. 获取sset中某个元素的分数 – zscore sset1 key1,查看 sset 中 key1 元素的分数;
  3. 获取sset中元素的个数(分数和元素是一个整体)-- zcard sset1
  4. 删除元素 – zrem sset1 key1
  5. 查找指定索引区间内的元素 – zrange sset1 startIndex endIndex可以追加withscores将对应的分数也返回;
  6. 从大到小(按分数)排序 – zrevrange sset1 0 -1,带上withscores可返回分数;
  7. 删除指定排名范围的元素 – zremrangebyrank sset1 开始排名(第一名是0) 末位排名
  8. 删除指定分数范围内的元素 – zremrangebyscore sset1 lowScore highScore
  9. 返回指定分数范围内的元素 – zrangebyscore sset1 lowScore highScore,当然也可以带上withscores,也可以带上limit startIndex endIndex(限制返回其中的前几名);
  10. 给指定元素增加分数 – zincrby sset1 increment key1
  11. 返回指定分数范围内元素的个数 – zcount sset1 lowScore highScore

3. redis的特性

3.1 多数据库

 一个redis中可以包含多个数据库,就像mysql中可以创建多个数据库,用户进行链接时可以指定链接具体的某一个数据据库。一个redis实例最多提供16个数据库,下标分别为0到15。客户端默认连接的是0号数据库,当然也可以用select关键字来选择连接哪一个数据库:如选择1号数据库 – select 1即可。将当前数据中的某个key移动到另一个数据库可以用move key 目标数据索引,如将当前数据库中的key1移动到0号数据中:move key1 0

3.2 Redis事务

 redis中也提供了事务的特性(multi、exec、discard),在redis中所有事务命令将会被放进队列中顺序串行化执行,并且在执行事务操作期间,redis将不会再为任何客户端提供服务,流程如下:

  1. multi后面所有的语句都会被当作一个事务按序执行,相当于开启事务;
  2. exec相当于提交事务;
  3. discard相当于回滚事务;

3.3 redis的持久化

 redis的高性能源于他的所有数据都存放在内存中,为了数据不丢失,就需要将这些数据从内存中转存到本地硬盘上,这就是持久化,redis的持久化有两种方式:RDB方式和AOF方式。

3.3.1 RDB持久化

 RDB是默认支持的,不需要配置,在指定的时间间隔内将内存中数据集快照写入到磁盘。RDB持久化的优势

  1. 采用RDB持久化方式后,整个redis数据库将只包含一个文件,对于文件备份非常方便;
  2. 对于灾难恢复而言,RDB是不错的选择,可以将单独的文件压缩后转移到其他介质上;
  3. 性能最大化,在开始持久化时,redis需要做的仅仅是分叉出一些子进程去完成这些持久化的操作,极大的避免了服务器进程进行I/O操作。如果数据集很大,相对AOF,RDB的启动效率更高;

劣势:如果想保证数据的高可用性(最大限度避免数据丢失),那RDB不是一个很好的选择,因为RDB在定时持久化的时候不可避免的会出现宕机的情况,宕机前的数据就会丢失。RDB是利用子进程来协助完成持久化,所以当数据集非常大的时候,可能会导致整个服务器停止几百毫秒甚至1秒。

配置:在redis.conf文件中这里写图片描述
这里的save 900 1表示每900秒至少有一个key发生变化时,redis就会持久化一次,即dump一次快照到磁盘上,下面的同理;dump的文件名这里写图片描述
dump文件的路径,这里写图片描述
即当前路径。

3.3.2 AOF持久化

 AOP是以日志的形式记录服务器的每一次操作,在redis启动时,就会读取该文件重新构建数据库,以保证数据库的完整。

优势:AOF可以带来更高的数据安全性,redis提供了三种同步策略:每秒同步(异步,高效,但一旦宕机,这1s秒修改的数据就会丢失)、每修改同步(同步持久化,最安全但效率最低)、不同步,对于日志的修改是append形式(追加的形式),即使出现宕机,也不会破坏之前以保存的日志,如果日志过大,redis可以启动自动重写机制。AOF有一个非常清晰的日志模式。

劣势:往往AOF的文件要比RDB文件大一些,而且运行效率比RDB低(因为每修改同步);

配置:同样在redis.conf文件中,这里写图片描述开启追加的模式将appendonly no改为yes,下面也标明了日志文件是appendonly.aof,同步策略如下这里写图片描述
其中always表示是每修改同步(最常用),everysec则表示每秒同步,no表示不同步。使用AOF的案列:将appendonly no改为appendonly yes,使用appendsync always(每修改同步)策略,保存退出,然后在redis中随便写一些数据,最后执行flushall(清空数据库数据),再断开redis,然后进入日志文件appendonly.aof将最后的flushall命令删除重启redis,redis数据即可恢复

3.3 无持久化

直接禁用redis的持久化机制,就是用来做缓存。

3.4 RDB和AOF结合

4. java对Redis的操作

 下载jedis的jar包,Linux必须在配置开放对应的6379端口,redis的配置文件中要需要将绑定的ip改成0.0.0.0,不能为127.0.0.1,否则将会出现ping通,redis却链接超时的情况(redis后台启动必须带上配置文件,否则将是前台启动),代码如下

import org.junit.Test;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class JedisDemo1 {

	/*
	 * 单个链接的方式
	 */
	@Test
	public void demo1() {
		//设置IP地址和端口
		Jedis jedis = new Jedis("10.4.62.6",6379);
		//保存数据
		jedis.set("name", "Hello Redis!");
		//获取值
		String value = jedis.get("name");
		jedis.del("name");
		System.out.println(value);
		//释放资源
		jedis.close();
	}
	
	/*
	 * 使用连接池链接
	 */
	@Test
	public void demo2() {
		//创建连接池的配置对象
		JedisPoolConfig config = new JedisPoolConfig();
		//设置最大连接数
		config.setMaxTotal(30);
		//设置最大空闲连接数
		config.setMaxIdle(10);
		
		//获得连接池
		JedisPool jedisPool = new JedisPool(config, "10.4.62.6", 6379);
		
		//获取和新对象
		Jedis jedis = null;
		try {
			//通过连接池获得jedis
			jedis = jedisPool.getResource();
			jedis.set("jedisPool", "利用jedisPool设置的值");
			System.out.println(jedis.get("jedisPool"));
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			if(jedis!=null) {
				jedis.close();
			}
			if(jedisPool!=null) {
				jedisPool.close();
			}
		}
	}
}

5. 关于redis的问题排查

 redis连接占满,服务端无法连接启动不了

# 1. 查看客户端连接概况
info clients
# 样例输出
connected_clients:730
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:15


# 2. 查看所有客户端和连接状态
client list

# 样例输出,age表示连接存在的时间(单位秒),idle表示连接空闲的时间(单位秒)
id=799726 addr=10.129.60.202:43568 fd=43 name= age=80530 idle=80530 flags=b db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=brpoplpush


# 3. 查看客户端超时时间
config get timeout
# 样例输出,0表示不开启空闲清除
 1)  "timeout"
 2)  "0"
 
 # 4. 设置空闲清理时间
 config set timeout 600
 
 
 # 5. 查看redis服务端最大允许的连接数
 config get maxclients

6. redis 特殊场景的示例

6.1 使用 jedis 批量插入的场景

 很多时候,使用List数据结构时,需要批量插入然后读取。这里面的批量插入可以利用 redis 的管道来操作,它是原子操作,示例:

        Jedis jedis = getJedis();
        Pipeline pipeline = jedis.pipelined();
        try {
            for (T d : dataList) {
                pipeline.rpush(getKey(key), HessianUtils.encode(d));
            }
            pipeline.sync();
        } finally {
            close(jedis);
        }

6.2 List数据结构按序写入和读取

 再有,就是写入的方向和读取的方向,大部分场景都是期望按序写入、按序读取,写入使用rpush,读使用lrange即可完成顺序写入、读取的行为。

6.3 List数据结构的分页读取

 可以这样来封装(pageNo从1开始):

    public <T> List<T> getList(String cacheKey, Class<T> clazz, int pageNo, int pageSize) {
        int startIndex = (pageNo - 1) * pageSize;
        return jedisSentinelPoolCacheManager.listRangeWithJsonSource(cacheKey, startIndex, startIndex + pageSize - 1,
            clazz);
    }

6.4 Jedis 客户端设置过期时间

 jedis 设置过期时间有个 jedis.expireAt(key, timestemp) 方法,这里的时间戳是指的 unix 时间戳,代码中需要将当前时间的时间戳除以1000才是正确的设置,如date.getTime()/1000

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值