redis是一个非关系型数据库,可基于内存亦可持久化的日志型、Key-Value数据库。这里需要解释redis, jedis和spring data redis是什么关系。
Redis
redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。
Jedis
Jedis是Redis官方推出的一款面向Java的客户端,提供了很多接口供Java语言调用。可以在Redis官网下载,当然还有一些开源爱好者提供的客户端,如Jredis、SRP等等,推荐使用Jedis。
Spring-data-redis
Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。
redis数据操作
转自:https://blog.csdn.net/she_lock/article/details/80621156
Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。
1、String(字符串)
一个key对应一个value。一个键最大能存储512MB。string类型是二进制安全的。
192.168.56.101:6>SET name "hello" //存值
OK
192.168.56.101:6>GET name //取值
hello
2、Hash(哈希)
Redis hash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象。每个 hash 可以存储 232 - 1 键值对(40多亿)。
192.168.56.101:6>HMSET user name "张三" age 18 //存多个值 HMSET KEY_NAME FIELD1 VALUE1 ...FIELDN VALUEN 存单个值用 HSET KEY_NAME FIELD VALUE
OK
192.168.56.101:6>HGET user age //取值 HGET KEY_NAME FIELD_NAME
18
192.168.56.101:6>HGET user name
张三
192.168.56.101:6>HGETALL user //所有的字段和值 HGETALL KEY_NAME
1) name
2) 张三
3) age
4) 18
5) sex
6) 男
192.168.56.101:6>HKEYS user //获取key HKEYS key
1) name
2) age
3) sex
192.168.56.101:6>HLEN user //获取哈希表中字段的数量
3
3、List(列表)
Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。
192.168.56.101:6>lpush users admin //在users List中插入admin
1
192.168.56.101:6>lpush users guest
2
192.168.56.101:6>lpush users zhangsan
3
192.168.56.101:6>lrange users 0 10 //在users list中取出index为0到10的数据,超出的不显示
1) zhangsan
2) guest
3) admin
192.168.56.101:6>lrange users
ERR wrong number of arguments for 'lrange' command
192.168.56.101:6>lrange users 0 3
1) zhangsan
2) guest
3) admin
4、Set(集合)
Redis的Set是string类型的无序集合。值不重复。
sadd 添加一个 string 元素到 key 对应的 set 集合中,成功返回1,如果元素已经在集合中返回 0,如果 key 对应的 set 不存在则返回错误。语法如下;
sadd key member
样例:
192.168.56.101:6>sadd tecq redis //存值
1
192.168.56.101:6>sadd tecq redis //插入重复的值,会不成功,返回0
0
192.168.56.101:6>sadd tecq moongodb
1
192.168.56.101:6>sadd tecq rabbitmq
1
192.168.56.101:6>smembers tecq //取值
1) rabbitmq
2) moongodb
3) redis
5、zset(sorted set:有序集合)
Redis zset 和 set 一样也是string类型元素的集合。且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。zset的成员是唯一的,但分数(score)却可以重复。
zadd命令添加元素到集合,元素在集合中存在则更新对应score,其语法如下:
zadd key score member
样例:
192.168.56.101:6>zadd nosql 0 redis
1
192.168.56.101:6>zadd nosql 20 moongodb
1
192.168.56.101:6>zadd nosql 0 jedis
1
192.168.56.101:6>zadd nosql 2 neo4j
1
192.168.56.101:6>ZRANGEBYSCORE nosql 0 5 //按照 score 从小到大
1) jedis
2) redis
3) neo4j
192.168.56.101:6>ZRANGEBYSCORE nosql 0 0
1) jedis
2) redis
192.168.56.101:6>ZRANGEBYSCORE nosql 0 20
1) jedis
2) redis
3) neo4j
4) moongodb
key 相关
1、EXISTS KEY_NAME 检查给定 key 是否存在。若 key 存在返回 1 ,否则返回 0 。
192.168.56.101:6>exists tecp
0
192.168.56.101:6>exists tecq
1
2、DEL KEY_NAME 删除key。被删除 key 的数量。
192.168.56.101:6>del name
1
3、Expire KEY_NAME TIME_IN_SECONDS 设置 key 的过期时间,单位为 s(秒)。key 过期后将不再可用。设置成功返回 1 。 当 key 不存在或者不能为 key 设置过期时间时(比如在低于 2.1.3 版本的 Redis 中你尝试更新 key 的过期时间)返回 0 。
192.168.56.101:6>set name zhangsan
OK
192.168.56.101:6>expire name 30
1
4、 TTL KEY_NAME 以秒为单位返回 key 的剩余过期时间。
192.168.56.101:6>set name zhangsan
OK
192.168.56.101:6>expire name 60
1
192.168.56.101:6>ttl name //已经过了6s
54
spring data redis
项目常见问题思考
项目首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。我们今天讨论第一种解决方案。
spring-data-redis针对jedis提供了如下功能:
1.连接池自动管理,提供了一个高度封装的“RedisTemplate”类
2.针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
Spring Data Redis入门小Demo
1 准备工作
(1)构建Maven工程 SpringDataRedisDemo
(2)引入Spring相关依赖、引入JUnit依赖 (内容参加其它工程)
<!-- 集中定义依赖版本号 -->
<properties>
<junit.version>4.12</junit.version>
<spring.version>4.2.4.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jms</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
</dependencies>
(3)引入Jedis和SpringDataRedis依赖
<!-- 缓存 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
(4)在src/main/resources下创建properties文件夹,建立redis-config.properties
# 主机
redis.host=127.0.0.1
# 端口号
redis.port=6379
# 密码 一般不需要
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=true
(5)在src/main/resources下创建spring文件夹 ,创建applicationContext-redis.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:property-placeholder location="classpath*:properties/*.properties" />
<!-- redis 相关配置 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory" />
</bean>
</beans>
- maxIdle :最大空闲数
- maxWaitMillis:连接时的最大等待毫秒数
- testOnBorrow:在提取一个jedis实例时,是否提前进行验证操作;如果为true,则得到的jedis实例均是可用的;
2 值类型操作
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestValue {
//泛型可以不指定
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void setValue(){
//存值
redisTemplate.boundValueOps("name").set("张三");
}
@Test
public void getValue(){
String name = redisTemplate.boundValueOps("name").get();
//如果没有 name 则会返回 null
System.out.println(name);
}
@Test
public void deleteValue(){
//移除值
redisTemplate.delete("name");
}
}
3 Set类型操作
/**
* 特点: 无序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestSet {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
public void setValue(){
redisTemplate.boundSetOps("nameset").add("曹操");
redisTemplate.boundSetOps("nameset").add("刘备");
redisTemplate.boundSetOps("nameset").add("关羽");
}
@Test
public void getValue(){
//如果没有 nameset 返回 []
Set<String> members = redisTemplate.boundSetOps("nameset").members();
// 没有循序,和存入的循序无关
// [关羽, 刘备, 曹操]
System.out.println(members);
}
@Test
public void removeValue(){
//移除一个
redisTemplate.boundSetOps("nameset").remove("刘备");
//全部移除
redisTemplate.delete("nameset");
}
}
4 List类型操作
/**
* 特点: 有序, 可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestList {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 右压栈: 后添加的元素排在后面
* 先进先出
*/
@Test
public void setValueRight(){
redisTemplate.boundListOps("name").rightPush("张三");
redisTemplate.boundListOps("name").rightPush("李四");
redisTemplate.boundListOps("name").rightPush("王五");
redisTemplate.boundListOps("name").rightPush("赵六");
}
/**
* 右压栈的输入
*/
@Test
public void getValue(){
// [张三, 李四, 王五,赵六]
List<String> name = redisTemplate.boundListOps("name").range(0, 10);
System.out.println(name);
}
/**
* 左压栈: 先进后出
*/
@Test
public void setValueLeft(){
redisTemplate.boundListOps("nameLeft").leftPush("关羽");
redisTemplate.boundListOps("nameLeft").leftPush("张飞");
redisTemplate.boundListOps("nameLeft").leftPush("诸葛亮");
redisTemplate.boundListOps("nameLeft").leftPush("刘备");
}
/**
* 左压栈的输入
*/
@Test
public void getValueLeft(){
// [刘备, 诸葛亮, 张飞, 关羽]
List<String> name = redisTemplate.boundListOps("nameLeft").range(0, 10);
System.out.println(name);
}
/**
* 按索引位置查询元素
*/
@Test
public void searchByIndex(){
// 关羽
String index = redisTemplate.boundListOps("nameLeft").index(3);
System.out.println(index);
//集合元素个数
Long size = redisTemplate.boundListOps("nameLeft").size();
System.out.println(size);
}
/**
* 移除
*/
@Test
public void removeByIndex(){
// 移除元素的个数 移除元素的name
redisTemplate.boundListOps("nameLeft").remove(1, "诸葛亮");
redisTemplate.delete("nameLeft");//移除所有值
}
}
5 Hash类型操作
/**
* 存取有序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestHash {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 存值
*/
@Test
public void setValue(){
redisTemplate.boundHashOps("hash").put("a", "唐僧");
redisTemplate.boundHashOps("hash").put("b", "八戒");
redisTemplate.boundHashOps("hash").put("c", "沙和尚");
redisTemplate.boundHashOps("hash").put("d", "孙悟空");
}
/**
* 获取所有的key
*/
@Test
public void getAllKey(){
//获取所有的key : [a, b, c, d]
Set<Object> key = redisTemplate.boundHashOps("hash").keys();
System.out.println(key);
}
/**
* 获取所有的值
*/
@Test
public void getAllValue(){
//获取所有的值
List<Object> values = redisTemplate.boundHashOps("hash").values();
System.out.println(values); //[唐僧, 八戒, 沙和尚, 孙悟空]
//根据key取值
String str = (String)redisTemplate.boundHashOps("hash").get("c");
System.out.println(str); // 沙和尚
}
/**
* 移除某个小key的值
*/
@Test
public void removeKey(){
redisTemplate.boundHashOps("hash").delete("c");
}
}
在项目中的应用
1 需求分析
项目中首页的广告每次都是从数据库读取, 这样当网站访问量达到高峰时段,对数据库压力很大,并且影响执行效率。我们需要将这部分广告数据缓存起来。
2 读取缓存
2.1 公共组件层
因为缓存对于我们整个的系统来说是通用功能。广告需要用,其它数据可能也会用到,所以我们将配置放在公共组件层中较为合理。
(1. 引入依赖
(2. 创建配置文件
2.2 后端服务实现层中添加对公共组件层的依赖
2.3 后端服务实现层中缓存的实现
思路: 在查询数据时,我们要先从缓存中查询数据,如果缓存中没有再从数据库中查询,
将从数据库中查询出来的数据存入缓存中.
@Autowired
private RedisTemplate redisTemplate;
@Override
public List<TbContent> findByCategoryId(Long categoryId) {
//从缓存中查询数据
List<TbContent> list = (List<TbContent>) redisTemplate.boundHashOps("content").get(categoryId);
if(list == null){
System.out.println("从数据库中查询数据并放入缓存");
TbContentExample example = new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);//指定分类ID
criteria.andStatusEqualTo("1");//指定条件有效的
example.setOrderByClause("sort_order");//排序
list = contentMapper.selectByExample(example);
redisTemplate.boundHashOps("content").put(categoryId, list);//放入缓存
}else{
System.out.println("从缓存中查询");
}
return list;
}
2.4 细节
问题: 当我们数据库中的数据发生改变时,从缓存中查询出的数据并没有改变.
解决: 当广告数据发生变更时,需要将缓存数据清除,这样再次查询才能获取最新的数据
我们看一下那些操作使数据库中的数据发生改变:
(1 增加
再增加之前我们要清除缓存,然后再增加,这样会重新在数据库中查询一次,那么数据库的
改变就会同步到缓存中
contentMapper.insert(content);
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
(2 删除
for(Long id:ids){
//清除缓存 先清除在删除
Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.deleteByPrimaryKey(id);
}
(3 修改
注意`: 用户可能会修改广告的分类,这样需要把原分类的缓存和新分类的缓存都清除掉。
@Override
public void update(TbContent content){
//查询修改前的分类Id
Long categoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.updateByPrimaryKey(content);
//如果分类ID发生了修改,清除修改后的分类ID的缓存
if(categoryId.longValue()!=content.getCategoryId().longValue()){
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
}
}
spring data redis设置过期时间:
redisTemplate.expire(key, timeout, TimeUnit);
其中timeUnit参数是时间单位选择: