Redis学习随记

                       Redis学习随记

 

一、Redis特点及安装

1、特点

       Redis是开源BD许可高级的key-value存储系统(NoSQL)。

       可以用来存储字符串,哈希结构,链表,集合,因此常用来提供数据结构服务。

       Redis和memcaached相比的独特之处:

      ①、Redis可以用来做存储(storage),而memcached是用来做缓存(cache),这个特点主要因为Redis有持久化功能。

      ②、Redis中存储的数据有多种结构,而memcached存储的数据只有一种类型,字符串。

2、安装

学习Redis之前,需要先安装Redis,下载 Redis-x64-3.2.100.zip 该压缩包,解压即可。然后启动服务,如下所示:

3、应用场景

  • 取最新N个数据的操作   

         比如典型的取你网站的最新文章,通过下面命令

  • 排行榜应用,取TOP N操作
  • 需要精准设定过期时间的应用
  • 计数器应用
  • uniq操作,获取某段时间所有数据排重值
  • 实时系统,反垃圾系统
  • pub/sub构建实时消息系统
  • 构建排队系统
  • 缓存

二、Redis数据类型

Redis数据类型:string,list,set,sorted-sets,hash。

1、string

字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或JSON对象描述信息等。在Redis中字符串类型的value最多可以容纳的的数据长度是512M。

2、list

在Redis中,list类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。list中可以包含的最大元素数量是4294967295。

从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或者删除,这将会是非常高效的操作,即使链表中以及存储了百万条记录,该操作也可以在常量时间内完成。然后,需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。

3、set

在Redis中,我们可以将set类型看做是一个没有排序的字符集合,和list一样,我们也可以在该类型的数值上执行添加、删除或者判断某一元素是否存在等操作。需要说明的是,这些操作的时间复杂度为o(1),即常量时间内完成操作。set可包含的最大元素数量是4294967295.

和list类型不同的是,set集合中不允许出现重复的元素。换句话说,如果多次添加相同的元素,set中将仅保留该元素的一份拷贝。和list类型相比,set类型在功能上还存在着一个重要的特性,即在服务器端完成多个sets之间的聚合计算操作,如unions(交集),intersections(并集)、differences(差集)。由于这些操作在服务端完成,因此效率极高,而且也节省了大量的网络IO开销。

4、sorted-sets

sorted-sets和set类型极为相似,他们都是字符串的集合,都不允许重复的成员出现在一个set中。他们之间主要差别是sorted-sets中的每一个成员都会有一个分数(score)与之关联。Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管sorted-sets中的成员必须是唯一的,但是分数却可以是重复的。

在sorted-sets中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合成员数量的对数。由于sorted-sets中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也依然非常高效的。事实上,Redis所具有的这一特征在很多其他类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis同样的高效,在其它数据库中进行建模是非常困难的。

5、hash

我们可以将Redis中的hash类型看成是具有string key和string value的map容器。所以该类型非常适合于存储对象的信息。如username,password,age等。如果hash中包含很少的字段,那么该类型的数据也将仅占很少的磁盘空间。每一个hash可以存储4294967295个键值对。

三、不同数据类型的相关命令

1、string

set   key value       get  key       append  key value        decr  key    incr  ey         decrby key decrement            incrby key increment

getset key value           strlen key           setex key   seconds vlaue          setnx key value  

setrange key offset  value           getrange key start end              setbit key offset vlaue          getbit key offset

mget key [key.....]                 mset key value

2、list

3、set

4、sorted-sets

5、hash

6、key的相关命令

四、事务

和众多其他数据库一样,Redis作为nosql数据库也同样提供了事务机制。在Redis中,multi、exec、discard、watch 这四个命令是实现事务的基石。

Redis事务的特征:

  • 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其他客户端的请求提供任何服务,从而保证了事务中的所有命令都被原子执行。
  • 和关系型数据库中的事务相比,在Redis事务中如果有一条命令执行失败,其后的命令仍然会被继续执行。
  • 我们可以通过multi命令开启一个事务,该语句之后的命令都将被视为事务之内的操作,最后我们可以通过执行 exec / discard命令来提交/回滚该事务内的所有操作。
  • 在事务开启之前,如果客户端与服务器好自己出现通讯故障 并导致网络断开,其后所有待执行的语句都不会被服务器执行。然后如果网络中断事务事件是发生在客户端执行exec命令之后,那么该事务中的所有命令都会被服务器执行。
  • 当使用append-only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的Redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。

相关命令:

multi:开启一个事务,表示一个事务的开始

exec:执行在一个事务内命令队列中的所有命令

discard:回滚事务队列中的所有命令,同时将当前连接状态恢复为正常状态,即非事务状态。

watch  key:在multi命令执行之前,可以指定待监控的keys,然后在执行exec之前,如果被监控的keys发生修改,exec将放弃执行该事务队列中的所有命令。

unwatch:取消当前事务中指定监控的key,如果执行了exec或discard命令,则无需再手工执行该命令了,因为在此之后,事务中所有被监控的keys都将自动取消。

 

Redis的对事务的控制很弱,如果事务的命令队列中,有一条命令发生运行时错误,其后的命令依然可以执行成功,没有发生回滚;如果事务命令队列中,有一条命令发生语法错误等非运行错误,其后的命令不会执行,才发发生回滚。

五、持久化

1、Redis提供了那些持久化机制?

  • RDB持久化   

      该机制是指在指定的时间间隔内将内存中的数据集快照写入磁盘。

  • AOF持久化   

     该机制将以日志的形式记录服务器所处理的每一个写操作,在Redis服务器启动之初会读取该文件来重新构建数据库,以保证启动后数据库中的数据是完整的。

  • 无持久化

         我们可以通过配置的方式禁用Redis服务器的持久化功能,这样我们就可以将Redis视为一个加强版的memcached了。

  • 同时应用AOF和RDB

        

  2、RDB机制的优势和劣势

1)、RDB的优势

  • 一旦采用该方式,那么你的整个Redis数据库将只包含一个文件,这对于文件备份而言是非常完美的。比如,你可能打算每个小时归档一次最近24小时的数据,同时还要每天归档一次最近30天的数据,通过这样的备份策略,一旦系统出现灾难性故障,我们可以非常容易进行恢复。
  • 对于灾难恢复而言,RDB是非常不错的选择。因为我们可以非常轻松的将一个单独的文件压缩后再转义到其它存储介质上。
  • 性能最大化。对于Redis的服务进程而言,在开始持久化时,它唯一需要做的只是fork出子进程,之后再由子进程完成这些持久化工作,这样就可以极大的避免服务进程执行IO操作了。
  • 相比于AOF机制,如果数据集很大,RDB的启动效率会更高。

2)、RDB的劣势

  • 如果你想抱着数据的高可用性,即最大限度的避免数据丢失,那么RDB将不是一个很好的选择。因为系统一旦在定时持久化之前出现宕机现象,此前没有来得及写入磁盘的数据都将丢失。
  • 由于RDB是通过fork子进程来协助完成数据持久化工作的,因此,如果当数据集较大时,可能会导致真个服务器停止服务几百毫秒,甚至是1秒钟。

3、AOF机制的优势和劣势 

1)、AOF的优势

  • 该机制可以带来更高的数据安全性。即数据持久性。Redis中提供了3种同步策略,即每秒同步、每修改同步和不同步。事实上,每秒同步也是异步完成的,其效率也是非常高的,所差的是一旦系统出现宕机现象,那么这一秒钟之内修改的数据将会丢失。而每修改同步,我们可以将其视为同步持久化,即每次发生的数据变化都会被立即记录到磁盘中。可以预见,这种方式在效率上是最低的。至于无同步,无需多言,我想大家都能理解它。
  • 由于该机制对日志文件的写入操作采用的是append模式,因此在写入过程中即使出现宕机现象,也不会破坏日志文件中已经存在的内容。然后如果我们本次操作只是写入了一半数据就出现了系统崩溃问题,不用担心,在Redis下一次启动之前,我们可以通过rdis-check-aof工具来帮助我们解决数据一致性的问题。
  • 如果日志过大,Redis可以自动启用rewirte机制。即Redis以append模式不断的将修改数据写入到老的磁盘文件中,同时Redis还会创建一个新的文件用于记录此期间有哪些修改命令被执行。因此在进行rewrite切换时可以更好地保证数据安全性。
  • AOF包含一个格式清晰、易于理解的日志文件用于记录所有的修改操作。事实上,我们也可以通过该文件完成数据的重建。

2)、AOF的劣势

  • 对于相同数量的数据集而言,AOF文件通常要大于RDB文件。
  • 根据同步策略的不同,AFO在运行效率上往往会慢与RDB。总之,每秒同步策略的效率是比较高的,同步禁用策略的效率和RDB一样高效。

六、集群配置(主从复制)

1、主从复制的特点和优势

  • 同一个master可以同步多个slaves。
  • slave同样可以接收其他slave的连接和同步请求,这样可以有效的分载master的同步压力。因此我们可以将Redis的集群架构视为图结构。
  • master server是以非阻塞的方式为slave提供服务。所以在master-slave同步期间,客户端仍然可以提交查询或修改请求。
  • slave server同样是以非阻塞的方式完成数据同步。在同步期间,如果有客户端提交查询请求,Redis则返回同步之前的数据。如果master和slave直接的链接出现断链现象,slave可以自动重连master,但是在连接成功之后,一次完全同步将被自动执行。

2、如何配置主从连接

本人是在同一台机器上启动了两个Redis服务器,分别监听不同的端口,如6379,6380,以此为例说明。6379位主,6380位从。

修改Redis_6380额的配置文件6380.conf,将 #slaveof <masterip> <masterport> 修改为 slaveof  127.0.0.1  6379,保存退出即可。这样就可以保证Redis_6380服务器程序在每次启动后都会主动建立与redis_6379的主从连接了。如果设置了密码,就要设置:masterauth <master-password>

七、运维

Redis持久化方式:1、rdb快照方式     2、aof日志方式

rdb快照方式配置:

save 900 1

save 300 10

save 60 10000

stop-writes-on-bgsave-error  yes

rdbcompression  yes

rdbchecksum  yes

dbfilename dump.rdb

dir   /var/rdb/

aof方式配置:

appendonly  no #是否打开aof日志功能

appendfsync  always  #每个命令都立即同步到aof,安全速度慢

appendfsync  ecerysec  #

appendfsync no   #写入工作交给操作系统,由操作系统判定缓冲区大小,统一写入到aof  不同频率低 速度快

no-appendfsync-on-write   yes  #正在导出rdb快照时不要写aof

auto-aof-rewrite-percentage   100 

auto-aof-rewrite-min-size    64mb

./bin/redis-benchmark    -n   20000

------------------------------------------------------------

主从复制配置:

master配置:

1、关闭rdb快照(备份工作交给slave)     2、可以开启aof

slave配置:

1、声明slave-of        2、某一个slave打开rdb快照功能        3、配置是否只读     slave-read-only

4、可选配置密码

在主服务器配置中修改  requirepass   123456 ,客户端连上后需要  auth  123456 才能访问,但是从服务器已经连不成功master,需要在从服务器配置上  masterauth  123456

注:每次slave断开后,再次连接master时,都要master全部dump出来rdb再aof,即同步的过程需要重新来一遍,所以要记住,多台slave不要一下全部启起来。

------------------------------------------------------------------------------

Redis运维:

time 查看时间戳与微秒数

dbsize   查看当前库中的key数量

bgrewriteaof    后台进程重写aof

bgsave  后台保存rdb快照

save   保存rdb快照

lastsave   上次保存时间

slaveof  设为slave服务器

flushall  清空所有db

flushdb  清空当前db

shutdown save|nosave   断开连接关闭服务器

slowlog 显示慢查询:

slowlog-log-slower-than  10000   多慢才算慢

slowlog-max-len   128  存多少条

slowlog  get   查看慢日志

config  get   slowlog-log-slower-than 

config set  slowlog-log-slower-than  10

info  显示服务器信息

config get 获取配置信息

config set 设置配置信息

monitor  打开控制台

sync  主从同步

client list  客户端列表

client kill  关闭某个客户端

client setname  为客户端设置名字

client getname  获取客户端名字

----------------------------------------------------

如果不小心flushall了,应该首先 shutdown nosave,然后编辑aof(把最后一条flushall命令删除掉),然后重启

八、Java客户端jedis

测试例子:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.cjl</groupId>
  <artifactId>Maven-Redis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  
  <dependencies>
  	<dependency>
  		<groupId>redis.clients</groupId>
  		<artifactId>jedis</artifactId>
  		<version>2.9.0</version>
  	</dependency>
  	
  	<dependency>
  		<groupId>junit</groupId>
  		<artifactId>junit</artifactId>
  		<version>4.12</version>
      	<scope>test</scope>
  	</dependency>
  </dependencies>
</project>

测试代码:

package com.cjl.test;


public class JedisUtilTest {

	//JedisPool jedisPool;
	
	Jedis jedis;
	
	@Before
	public void setUp()
	{
		//jedisPool = new JedisPool(new JedisPoolConfig(), "127.0.0.1");
		//jedis =jedisPool.getResource();
		jedis = new Jedis("localhost");
	}
	
	@Test
	public void testGet(){
		System.out.println(jedis.get("lu"));
	}
	
	/**
	 * Redis 存储初级字符串
	 */
	@Test
	public void testBasicString(){
		//添加数据
		jedis.set("name", "jeck");
		System.out.println("testBasicString key name,value = "+jedis.get("name"));
		
		//修改数据
		//1、在原来基础上修改
		jedis.append("name", "hahahaha...");
		System.out.println("append name: "+jedis.get("name"));
		
		//2、直接覆盖
		jedis.set("name", "被我覆盖了吧");
		System.out.println("set name :"+jedis.get("name"));
		
		//3、删除key对应的记录
		jedis.del("name");
		System.out.println("del name :"+jedis.get("name"));
		
		//4、mset 相当于set 多个
		jedis.mset("name1","msetname1","name2","msetname2");
		System.out.println("mset :"+jedis.mget("name1","name2"));
		
		jedis.set("xiao", "xiao");
	}
	
	/**
	 * jedis 操作map
	 */
	@Test
	public void testMap(){
		Map<String,String> user = new HashMap<String, String>();
		user.put("name", "hehe");
		user.put("pwd", "7777");
		user.put("sc", "shanchu");
		jedis.hmset("user", user);
		//第一个参数 先说取哪个map,第二个及以后的参数,说取该map里哪个key对应的值
		List<String> resultMap = jedis.hmget("user", "name","pwd","sc");
		System.out.println("map :"+resultMap);
		
		//删除 删除哪个map里的哪个key及值
		jedis.hdel("user", "sc");
		List<String> resultMap2 = jedis.hmget("user", "name","pwd","sc");
		System.out.println("map del :"+resultMap2);
		
		
		System.out.println("hlen:"+jedis.hlen("user"));//map里有几对键值对
		System.out.println("exists user "+jedis.exists("user"));//map 是否存在
		System.out.println("hkeys "+jedis.hkeys("user"));//map 里所有的key
		System.out.println("hvals "+jedis.hvals("user"));//map 里所有的值
		
		Iterator<String> iter = jedis.hkeys("user").iterator();
		while(iter.hasNext()){
			String key = iter.next();
			System.out.println("key : "+key+"-- value : "+jedis.hmget("user", key));
		}
							
	}
	
	/**
	 * jedis 操作 List
	 */
	@Test
	public void testList(){
		System.out.println("------------testList--------------");
		//开始前,先移除所有的内容
		jedis.del("java framework");
		System.out.println(jedis.lrange("java framework", 0, -1));
		
		//先向key java framework中存放三条数据
		jedis.lpush("java framework", "spring");
		jedis.lpush("java framework", "struts");
		jedis.lpush("java framework", "hibernate");
		//取出所有数据,jedis.lrange是按范围取出 
		//第一个是key,第二个是开始位置,第三个结束位置 jedis.llen获取长度  -1表示所有
		System.out.println("java framework ...... "+jedis.lrange("java framework", 0, -1));
	}
	
	@Test
	public void testSet()
	{
		System.out.println("------------testSet--------------");
		//添加
		jedis.sadd("setName", "xiaomin");
		jedis.sadd("setName", "java");
		jedis.sadd("setName", "haha");
		jedis.sadd("setName", "hehe");
		
		System.out.println("setName: "+jedis.smembers("setName"));
		
		//移除hehe
		jedis.srem("setName", "hehe");
		System.out.println("setName rem : "+jedis.smembers("setName"));
		
		//判断hehe是否是setName集合中的元素
		System.out.println("sismemer hehe: "+jedis.sismember("setName", "hehe"));
		
		//随记返回一个元素
		System.out.println("srandmember setName: "+jedis.srandmember("setName"));
		
		//返回集合元素的个数
		System.out.println("setName  scard  "+jedis.scard("setName"));
	}
	
	
	@Test
	public void test() throws InterruptedException
	{
		System.out.println("-----------test-------------");
		//keys 中传入的可以是通配符
		//返回当前库中所有的key
		System.out.println("all keys : "+jedis.keys("*"));
		
		//前匹配 区分大小写
		System.out.println("*name: "+jedis.keys("*name"));
		
		//后匹配
		System.out.println("name*: "+jedis.keys("name*"));
		
		//删除key为user的元素,删除成功返回1  删除失效(或者不存在)返回0
		System.out.println("delete key is user :"+jedis.del("user"));
		System.out.println("delete key is auser :"+jedis.del("auser"));
		
		//返回指定key的有效时间,如果是-1则表示永久有效
		System.out.println("key keep time :"+jedis.ttl("setName"));
		
		//指定key的存活时间 及值,时间单位为秒
		jedis.setex("name1", 10, "min");
		Thread.sleep(5000);
		System.out.println("key name1 tiem: "+jedis.ttl("name1"));
		
		//重命名
		System.out.println("rename xiao --> da :"+jedis.rename("xiao", "da"));
		
		//打印name1,看重命名后他是否还存在
		System.out.println("xiao :"+jedis.get("xiao"));
		
		//打印name3
		System.out.println("da :"+jedis.get("da"));
		
		//排序 list
		jedis.del("a");
		jedis.rpush("a", "1");//rpush 把一个或多个值插入到列表尾部
		jedis.lpush("a", "6");//lpush 把一个或多个值插入到列表头部
		jedis.lpush("a", "4");
		jedis.lpush("a", "7");
		jedis.lpush("a", "3");
		jedis.lpush("a", "8");
		jedis.lpush("a", "2");
		
		System.out.println("print a all values :"+jedis.lrange("a", 0, -1));
		
		System.out.println("a sort :"+jedis.sort("a"));//排序
		
		System.out.println("print a all values :"+jedis.lrange("a", 0, -1));
		
	}
	
}

 

猜你喜欢

转载自blog.csdn.net/xingyuncaojun/article/details/80762975