Redis基础入门

一 基本介绍

1.1 介绍

1.redis是一种NoSql(非关系型)数据库,我们平常用的比较多的就是像MySql和SQLServer这种关型数据库,但关型数据库在大数据高并发场景下表现不是那么好,是关型数据库的一个补充。

2.redis由C语言开发的开源的高性能键值对(key-value)数据库(nosql),所以在linux安装时需要使用gcc编译器。

3. redis命令不区分大小写,但是key区分的

4. redis中的数据都是字符串。

5. redis是单线程,(不适合存储比较大的数据)

1.2 补充

NoSql数据库类型相关信息

  • 键值(Key-Value)存储数据库

       相关产品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB。

       典型应用:内容缓存,主要用于处理大量数据的高访问负载。

       数据模型:一系列键值对

       优势:快速查询

       劣势:存储的数据缺少结构化

  • 列存储数据库

       相关产品:Cassandra, HBase, Riak

       典型应用:分布式的文件系统

       数据模型:以列簇式存储,将同一列数据存在一起

       优势:查找速度快,可扩展性强,更容易进行分布式扩展

       劣势:功能相对局限

  • 文档型数据库

       相关产品:CouchDB、MongoDB

       典型应用:Web应用(与Key-Value类似,Value是结构化的)

       数据模型:一系列键值对

       优势:数据结构要求不严格

       劣势:查询性能不高,而且缺乏统一的查询语法

 

  • 图形(Graph)数据库

       相关数据库:Neo4J、InfoGrid、Infinite Graph

       典型应用:社交网络

       数据模型:图结构

       优势:利用图结构相关算法。

       劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。

二 应用场景

  1. 缓存。
  2. 分布式集群架构中的session分离。
  3. 任务队列。(秒杀、抢购、12306等等)
  4. 应用排行榜。(SortedSet)
  5. 网站访问统计。
  6. 数据过期处理。(expire)  在SSO系统中模拟session

三 基本数据类型和常用命令

3.1字符串类型 (String:key-value)

 redis中所有的数据都是字符串。(笔者曾用于缓存分类信息,和expire使用模拟session)

 set key value 设置值

 get key 获取值

 incr key 加一   使用incr  命令,如果key 不存在,会自动创建key 并自动+1.

 decr key 减一

3.2散列类型(hash: key-field-value)

相当于一个key 对应一个map (map中又是key- value)(笔者曾用于缓存购物车信息)

    应用于归类

    hset  key field value  设置值

    hget  key field       获取值

    hincrby key field num  设置增数量

3.3列表类型(List)

    List是有顺序可重复(数据结构中的:双链表,队列)

    可作为链表 ,从左添加元素  也可以从右添加元素。

       lpush list a b c d    (从左添加元素)

       rpush list 1 2 3 4    (从右边添加元素)

       lrange list 0 -1 (从0 到 -1 元素查看:也就表示查看所有)

       lpop list (从左边取,删除)

       rpop list  (从右边取,删除)

 

3.4集合类型(set)

 Set无顺序,不能重复

  sadd set1 a b c d d (向set1中添加元素) 元素不重复

  smembers set1 (查询元素)

  srem set1 a (删除元素)

3.5有序集合类型(SortedSet)

有顺序,不能重复

适合做排行榜 排序需要一个分数属性

zadd zset1 9 a 8 c 10 d 1 e   (添加元素 zadd key score member )

(ZRANGE key start stop [WITHSCORES])(查看所有元素:zrange key  0  -1  withscores)

如果要查看分数,加上withscores.

zrange zset1 0 -1 (从小到大)

zrevrange zset1 0 -1 (从大到小)

zincrby zset2 score member (对元素member 增加 score)

3.6其他常用命令

1. select  n     选择仓库  

在一个redis实例中,含有多个数据仓库(最多16个,下表从0-15),默认连接的是0号仓库。

2. 在使用Jedis操作redis数据库时调用的方法名和redis内部操作的命令基本相同

3. expire key second  (设置key的过期时间)

4. ttl key (查看剩余时间)(-2 表示不存在,-1 表示已被持久化,正数表示剩余的时间)

5. persist key (清除过期时间,也即是持久化 持久化成功体提示 1 不成功0)。

6. del key: 删除key 

7.EXISTS key(若key存在,返回1,否则返回0)

8.使用cli客户端程序 输入ping判断redis是否启动(回复pong表示启动成功)

9.quit 退出连接

10. flushdb 删除当前数据库所有key   flushall 删除所有数据仓库中的key

11. move newkey n:将当前库中的key移动到1号库中

四 持久化方案

redis将数据存储在内存中,在使用过程中速度会比直接操作磁盘快很多,但存储在内存中的数据会随着设备的关闭而消失,redis对数据的持久化方案有以下两种。

4.1 RDB(快照形式)

指定时间间隔达到一定数据量就将内存中的数据集快照写入磁盘,保存在dump.rdb中。 时间间隔可以在配置文件中配置,redis默认使用的就是这种方式,配置文件详情如下,第一个数字表示时间(秒) 第二个表示数据量。(会存在数据丢失)

4.2 AOF(日志文件)

记录Server收到的写操作到日志文件,在Server重启时通过回放这些写操作来重建数据集,配置方式

1)修改redis.config配置文件,找到appendonly默认是appendonly no。改成appendonly yes

2)再找到appendfsync 。默认是 appendfsync everysec

  appendfsync always    #每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用  

  appendfsync everysec    #每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐  

  appendfsync no     #完全依赖os性能最好,持久化没保证 

五 使用案例

5.1 Jedis的入门使用

1. 导入jar包

或者使用maven

<dependency>
	<groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
	<version>${jedis.version}</version>
</dependency>

2.单机版连接实例

@Test
public void testJedis() throws Exception {
		// 第一步:创建一个Jedis对象。需要指定服务端的ip及端口。
		Jedis jedis = new Jedis("192.168.25.153", 6379);
		// 第二步:使用Jedis对象操作数据库,每个redis命令对应一个方法。
		String result = jedis.get("hello");
		// 第三步:打印结果。
		System.out.println(result);
		// 第四步:关闭Jedis
		jedis.close();
}
@Test   //连接池版本
public void testJedisPool() throws Exception {
		// 第一步:创建一个JedisPool对象。需要指定服务端的ip及端口。
		JedisPool jedisPool = new JedisPool("192.168.25.153", 6379);
		// 第二步:从JedisPool中获得Jedis对象。
		Jedis jedis = jedisPool.getResource();
		// 第三步:使用Jedis操作redis服务器。
		jedis.set("jedis", "test");
		String result = jedis.get("jedis");
		System.out.println(result);
		// 第四步:操作完毕后关闭jedis对象,连接池回收资源。
		jedis.close();
		// 第五步:关闭JedisPool对象。
		jedisPool.close();
}

3.集群版连接实例

@Test  //JedisCluster连接版本
public void testJedisCluster() throws Exception {
		// 第一步:使用JedisCluster对象。需要一个Set<HostAndPort>参数。Redis节点的列表。
		Set<HostAndPort> nodes = new HashSet<>();
		nodes.add(new HostAndPort("192.168.25.153", 7001));
		nodes.add(new HostAndPort("192.168.25.153", 7002));
		nodes.add(new HostAndPort("192.168.25.153", 7003));
		nodes.add(new HostAndPort("192.168.25.153", 7004));
		nodes.add(new HostAndPort("192.168.25.153", 7005));
		nodes.add(new HostAndPort("192.168.25.153", 7006));
		JedisCluster jedisCluster = new JedisCluster(nodes);
		// 第二步:直接使用JedisCluster对象操作redis。在系统中单例存在。
		jedisCluster.set("hello", "100");
		String result = jedisCluster.get("hello");
		// 第三步:打印结果
		System.out.println(result);
		// 第四步:系统关闭前,关闭JedisCluster对象。
		jedisCluster.close();
}

5.2 redis在SSM框架中的应用

1. spring配置文件(一般是在业务层调用redis)

<!-- 配置单机版的 -->
<bean class="redis.clients.jedis.JedisPool">
		<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
		<constructor-arg name="port" value="6379"></constructor-arg>
</bean>
<bean class="com.taotao.content.jedis.JedisClientPool"></bean>
<!-- 配置集群版  两种配置模式不能同时使用 -->
<!-- <bean class="redis.clients.jedis.JedisCluster">
	<constructor-arg name="nodes">
	<set>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7001"></constructor-arg>
		</bean>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7002"></constructor-arg>
		</bean>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7003"></constructor-arg>
		</bean>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7004"></constructor-arg>
		</bean>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7005"></constructor-arg>
		</bean>
		<bean class="redis.clients.jedis.HostAndPort">
			<constructor-arg name="host" value="192.168.25.153"></constructor-arg>
					<constructor-arg name="port" value="7006"></constructor-arg>
		</bean>
	</set>
	</constructor-arg>
	</bean>-->
<!-- 为了方便集群版和本地单击版的切换,使用了统一的接口访问 -->
<bean class="com.taotao.content.jedis.JedisClientCluster"></bean> 

2. 访问redis工具类

接口

public interface JedisClient {
	String set(String key, String value);
	String get(String key);
	Boolean exists(String key);
	Long expire(String key, int seconds);
	Long ttl(String key);
	Long incr(String key);
	Long hset(String key, String field, String value);
	String hget(String key, String field);	
	Long hdel(String key,String... field);//删除hkey	
}

单机版本

public class JedisClientCluster implements JedisClient {
	@Autowired
	private JedisCluster jedisCluster;
	@Override
	public String set(String key, String value) {
		return jedisCluster.set(key, value);
	}
	@Override
	public String get(String key) {
		return jedisCluster.get(key);
	}
	@Override
	public Boolean exists(String key) {
		return jedisCluster.exists(key);
	}
	@Override
	public Long expire(String key, int seconds) {
		return jedisCluster.expire(key, seconds);
	}
	@Override
	public Long ttl(String key) {
		return jedisCluster.ttl(key);
	}
	@Override
	public Long incr(String key) {
		return jedisCluster.incr(key);
	}
	@Override
	public Long hset(String key, String field, String value) {
		return jedisCluster.hset(key, field, value);
	}
	@Override
	public String hget(String key, String field) {
		return jedisCluster.hget(key, field);
	}
	@Override
	public Long hdel(String key, String... field) {
		return jedisCluster.hdel(key, field);
	}
}

集群版本

public class JedisClientPool implements JedisClient {
	@Autowired
	private JedisPool jedisPool;
	@Override
	public String set(String key, String value) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.set(key, value);
		jedis.close();
		return result;
	}
	@Override
	public String get(String key) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.get(key);
		jedis.close();
		return result;
	}
	@Override
	public Boolean exists(String key) {
		Jedis jedis = jedisPool.getResource();
		Boolean result = jedis.exists(key);
		jedis.close();
		return result;
	}
	@Override
	public Long expire(String key, int seconds) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.expire(key, seconds);
		jedis.close();
		return result;
	}
	@Override
	public Long ttl(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.ttl(key);
		jedis.close();
		return result;
	}
	@Override
	public Long incr(String key) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.incr(key);
		jedis.close();
		return result;
	}
	@Override
	public Long hset(String key, String field, String value) {
		Jedis jedis = jedisPool.getResource();
		Long result = jedis.hset(key, field, value);
		jedis.close();
		return result;
	}
	@Override
	public String hget(String key, String field) {
		Jedis jedis = jedisPool.getResource();
		String result = jedis.hget(key, field);
		jedis.close();
		return result;
	}
	@Override
	public Long hdel(String key, String... field) {
		Jedis jedis = jedisPool.getResource();
		Long hdel = jedis.hdel(key, field);
		jedis.close();
		return hdel;
	}	
}

3.项目中的实际应用(存储购物车和分类信息)

缓存分类信息

@Autowired
private JedisClient client;
@Autowired
private TbContentMapper mapper;
@Value("${CONTENT_KEY}")
private String CONTENT_KEY;
@Override
public TaotaoResult saveContent(TbContent content) {
		//1.注入mapper
		//2.补全其他的属性
		content.setCreated(new Date());
		content.setUpdated(content.getCreated());
		//3.插入内容表中
		mapper.insertSelective(content);
		//当添加内容的时候,需要清空此内容所属的分类下的所有的缓存
		try {
			client.hdel(CONTENT_KEY, content.getCategoryId()+"");
			System.out.println("当插入时,清空缓存!!!!!!!!!!");
		} catch (Exception e) {
			e.printStackTrace();
		}	
		return TaotaoResult.ok();
}
@Override
public List<TbContent> getContentListByCatId(Long categoryId) {
		//添加缓存不能影响正常的业务逻辑		
		//判断 是否redis中有数据  如果有   直接从redis中获取数据 返回		
	try {
            //从redis数据库中获取内容分类下的所有的内容
		    String jsonstr = client.hget(CONTENT_KEY, categoryId+"");
			//如果存在,说明有缓存
			if(StringUtils.isNotBlank(jsonstr)){
			System.out.println("这里有缓存啦!!!!!");
				return JsonUtils.jsonToList(jsonstr, TbContent.class);
			}
		} catch (Exception e1) {
			e1.printStackTrace();
		}		
		//1.注入mapper
		//2.创建example
		TbContentExample example = new TbContentExample();
		//3.设置查询的条件
		example.createCriteria().andCategoryIdEqualTo(categoryId);
		//4.执行查询
		List<TbContent> list = mapper.selectByExample(example);
		//返回
		//将数据写入到redis数据库中   
		// 注入jedisclient 
		// 调用方法写入redis   key - value
		try {
			System.out.println("没有缓存!!!!!!");
			client.hset(CONTENT_KEY, categoryId+"", JsonUtils.objectToJson(list));
		} catch (Exception e) {
			e.printStackTrace();
		}		
		return list;
	}

存储购物车

@Override  //编辑购物车
public void updateNumByCart(Long userId, Long itemId, Integer num) {
		// 从redis中查询购物车数据。redis中保存的是购物车的json格式数据
		List<Cart> list = this.queryCartByUserId(userId);
		// 遍历购物车,商品是否存在,存在则更新,不存在什么都不做
		for (Cart cart : list) {
			if (cart.getItemId().longValue() == itemId.longValue()) {
				// 如果存在,则更新购物车商品数量。这里是更新,不是相加
				cart.setNum(num);
				cart.setUpdated(new Date());
				try {
					// 把添加后的购物车保存在redis中
this.redisUtils.set(this.CART_TAOTAO_CART_KEY + userId, MAPPER.writeValueAsString(list));
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
}

5.3 对于javaBean的处理

针对java对象存储到redis的处理方式,根据笔者本人做的项目,一般是使用JSON工具转化为字符串,再把字符串存储到Redis中,

个人认为使用这种方式还有一部分原因是去除无效字段节约内存,比如你值需要存储商品信息中的ID和数量,直接把对象全部存进redis是一种很消耗内存且不明智的行为。

下面是直接存储序列化对象的演示

      其实两种方式的本质是一样的,一个是用户手动实现JAVABean-->String的转化,一个是使用Jedis完成,都需要把数据处理成string,因为redis只能存储string类型的数据,重要的是看你有没有去掉对象中一些无用却又很占内存的字段。

六 其他属性

6.1 消息订阅和发布

窗口1中通过输入:subscribe mychat 订阅一个名称为mychat的频道

窗口2中通过输入:publish mychat ‘111’在频道mychat中发布消息111

窗口3中通过输入:psubscribe my* 批量订阅以my开头的频道

窗口2中通过输入:

  publish mychat ‘333’ 在频道mychat和my*频道中发布消息

  Publish mychat02 ‘444’ 在频道my*中发布消息

6.2 事务的支持

 

1_redis中通过multi/exec/discard命令来解决开发中遇到的事务问题

2_Redis事务特点

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Jedis使用总结 前段时间细节的了解了Jedis的使用,Jedisredis的java版本的客户端实现。 本文做个总结,主要分享如下内容: 【pipeline】【分布式的id生成器】【分布式锁【watch】【multi】】【redis分布式】 好了,一个一个来。 一、 Pipeline 官方的说明是:starts a pipeline,which is a very efficient way to send lots of command and read all the responses when you finish sending them。简单点说pipeline适用于批处理。当有大量的操作需要一次性执行的时候,可以用管道。 示例: Jedis jedis = new Jedis(String, int); Pipeline p = jedis.pipelined(); p.set(key,value);//每个操作都发送请求给redis-server p.get(key,value); p.sync();//这段代码获取所有的response 这里我进行了20w次连续操作(10w读,10w写),不用pipeline耗时:187242ms,用pipeline耗时:1188ms,可见使用管道后的性能上了一个台阶。看了代码了解到,管道通过一次性写入请求,然后一次性读取响应。也就是说jedis是:request response,request response,...;pipeline则是:request request... response response的方式。这样无需每次请求都等待server端的响应。 二、 跨jvm的id生成器 谈到这个话题,首先要知道redis-server端是单线程来处理client端的请求的。 这样来实现一个id生成器就非常简单了,只要简单的调用jdeis.incr(key);就搞定了。 你或许会问,incr是原子操作吗,能保证不会出现并发问题吗,不是说了吗,server端是单线程处理请求的。 三、 【跨jvm的锁实现【watch】【multi】】 首先说下这个问题的使用场景,有些时候我们业务逻辑是在不同的jvm进程甚至是不同的物理机上的jvm处理的。这样如何来实现不同jvm上的同步问题呢,其实我们可以基于redis来实现一个锁。 具体事务和监听请参考文章:redis学习笔记之事务 暂时找到三种实现方式: 1. 通过jedis.setnx(key,value)实现 import java.util.Random; import org.apache.commons.pool.impl.GenericObjectPool.Config; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; import redis.clients.jedis.Transaction; /** * @author Teaey */ public class RedisLock { //加锁标志 public static final String LOCKED = "TRUE"; public static final long ONE_MILLI_NANOS = 1000000L; //默认超时时间(毫秒) public static final long DEFAULT_TIME_OUT = 3000; public static JedisPool pool; public static final Random r = new Random(); //锁的超时时间(秒),过期删除 public static final int EXPIRE = 5 * 60; static { pool = new JedisPool(new Config(), "host", 6379); } private Jedis jedis; private String key; //锁状态标志 private boolean locked = false; public RedisLock(String key) { this.key = key; this.jedis = pool.getResource(); } public boolean lock(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { if (jedis.setnx(key, LOCKED) == 1) { jedis.expire(key, EXPIRE); locked = true; return locked; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } public boolean lock() { return lock(DEFAULT_TIME_OUT); } // 无论是否加锁成功,必须调用 public void unlock() { try { if (locked) jedis.del(key); } finally { pool.returnResource(jedis); } } } 2. 通过事务(multi)实现 由于采纳第一张方法,第二种跟第三种实现只贴了关键代码,望谅解。^_^ public boolean lock_2(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { Transaction t = jedis.multi(); // 开启事务,当server端收到multi指令 // 会将该client的命令放入一个队列,然后依次执行,知道收到exec指令 t.getSet(key, LOCKED); t.expire(key, EXPIRE); String ret = (String) t.exec().get(0); if (ret == null || ret.equals("UNLOCK")) { return true; } // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 3. 通过事务+监听实现 public boolean lock_3(long timeout) { long nano = System.nanoTime(); timeout *= ONE_MILLI_NANOS; try { while ((System.nanoTime() - nano) < timeout) { jedis.watch(key); // 开启watch之后,如果key的值被修改,则事务失败,exec方法返回null String value = jedis.get(key); if (value == null || value.equals("UNLOCK")) { Transaction t = jedis.multi(); t.setex(key, EXPIRE, LOCKED); if (t.exec() != null) { return true; } } jedis.unwatch(); // 短暂休眠,nano避免出现活锁 Thread.sleep(3, r.nextInt(500)); } } catch (Exception e) { } return false; } 最终采用第一种实现,因为加锁只需发送一个请求,效率最高。 四、 【redis分布式】 最后一个话题,jedis的分布式。在jedis的源码里发现了两种hash算法(MD5,MURMUR Hash(默认)),也可以自己实现redis.clients.util.Hashing接口扩展。 List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>(); //server1 JedisShardInfo host1 = new JedisShardInfo("", 6380, 2000); //server2 JedisShardInfo host2 = new JedisShardInfo("", 6381, 2000); hosts.add(host1); hosts.add(host2); ShardedJedis jedis = new ShardedJedis(hosts); jedis.set("key", "");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值