一 基本介绍
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
典型应用:社交网络
数据模型:图结构
优势:利用图结构相关算法。
劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案。
二 应用场景
- 缓存。
- 分布式集群架构中的session分离。
- 任务队列。(秒杀、抢购、12306等等)
- 应用排行榜。(SortedSet)
- 网站访问统计。
- 数据过期处理。(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事务特点