※快速掌握key-value数据库
一.Redis概述
(一)什么是Redis
Redis是一个开源的使用ANSI C语言编写、支持网络、 可基于内存亦可持久化的日志型、Key-Value型 NoSQL数据库,并提供多种语言的API。从2010年3 月15日起,Redis的开发工作由VMware主持。从 2013年5月开始,Redis的开发由Pivotal赞助。
(二)Redis的特点
速度快 广泛的语言支持 持久化 多种数据结构 主从复制 高可用与分布式
(三)Redis数据结构
1.String
1.1使用情景:
缓存 秒杀 分布式锁 配置中心 对象序列化 计数器
1.2用法
2.List(字符串列表)
2.1 可用于有顺序要求的场景,如时间轴
2.2 用法
3.Set(字符串集合,通过哈希表实现的)
3.1应用场景
集合的交集,并集那些,还有随机抽出抽奖的人
3.2用法
4.ZSet(有序字符串集合)
4.1用用场景:排行榜
4.2用法
zcount player:rank 700 900
zrangebyscore player:rank 700 900
zrangebyscore player:rank 700 900 withscores
5.Hash(键值对)
5.1应用场景:存储对象
5.2用法
(四)Redis原理
(五)Redis持久化-RDB数据文件保存
(六)Redis持久化-AOF日志保存
- AOF的追加(fsync)策略 ualways - 随时写入(不推荐) ueverysec - 每秒写入(推荐) uno -
依赖os规则写入(不推荐)
rewrite说明 u aof 文件小于64mb,不重写 u aof第一次大于64mb时自动重写,压缩至33mb u aof再次增长至66mb时,相较上次重写后33mb增长率 =100%且大于64mb最小值,自动重写,压缩至50mb u 依次类推,下次再次达到100%增长率,即100mb时自动 重写,压缩.
RDB与AOF的选择 u 当RDB与AOF都开启后,Redis官方默认采用AOF恢复 数据 u 在日常开发中,我们也推荐AOF来持久化数据 u Redis主从数据同步底层采用RDB实现
(七)Redis内存管理与回收策略
二.单点搭建Redis
(一)安装步骤
(二)自定义配置文件
save,bgsave手动保存到硬盘
(三)(命令行客户端通用命令)
flushdb(删除本数据库) flushall(删除所有数据库)
三.集群搭建Redis
1.Redis部署采用主从复制集群部署,由于环境设备限制,故采用伪集群部署,将多个Redis实例安装在阿里云东上119.23.60.34。通过Redis提供的主从复制解决方案,采用一主机两从机,主机进行数据更新,从机复制数据并只能查询;设立三个哨兵(Sentinel)监控三台Redis主机的情况和互相监督哨兵的情况。
2.搭建步骤如下:
(1)从中文官网http://www.redis.cn/下载最新版Redis5.0.4.tar.gz,上传到服务器
/usr/local
(2)在windows电脑创建3个启动Redis配置文件,三个Sentinel启动日志
(A)redis-7000.conf
port 7000
daemonize yes
logfile “7000.log”
dir “./”
bind 0.0.0.0
(B)redis-7001.conf
port 7001
daemonize yes
logfile “7001.log”
dir “./”
slaveof 119.23.60.34 7000
bind 0.0.0.0
(C)redis-7002.conf
port 7002
daemonize yes
logfile “7002.log”
dir “./”
slaveof 119.23.60.34 7000
bind 0.0.0.0
(D)sentinel-26379.conf
port 26379
daemonize yes
logfile “26379.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(E)sentinel-26380.conf
port 26380
daemonize yes
logfile “26380.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(F)sentinel-26381.conf
port 26381
daemonize yes
logfile “26381.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(3)解压缩redis5.0.4文件,将上面配置文件复制到Redis5.0.4中,登陆阿里云服务器,操作以下命令,并在操作命令前开启7000,7001,7002,26379,26380,26381端口
[root@izwz98b396i3cl6vnnpqc1z ~]# yum install gcc
[root@izwz98b396i3cl6vnnpqc1z local]# cd /usr/local/redis-5.0.4
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7000.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7001.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7002.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26379.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26380.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26381.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# netstat -tulpn
最后修改项目的配置文件
#配置访问Redis集群 Sentinel
spring.redis.database=0
#spring.redis.host=119.23.60.34
#spring.redis.port=26379
spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10
spring.redis.jedis.pool.max-wait=1000ms
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=119.23.60.34:26379,119.23.60.34:26380,119.23.60.34:26381
四.Redis Desktop Manager使用
1.Redis默认不允许外网访问,需要在自定义配置文件中增加 bind 0.0.0.0
2.开发RedisLinux端口
五.Jedis使用
(一)创建Maven工程,导入jar包
<dependency>
<groupId>com.yugabyte</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0-yb-11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
(二)jedis直连用法
import org.junit.Test;
import redis.clients.jedis.Jedis;
import java.util.List;
import java.util.Map;
public class JedisTestor {
@Test
public void testJedis() throws Exception{
//创建一个Redis通道
Jedis jedis = new Jedis("192.168.132.128", 6666, 1000);
try {
jedis.auth("123456");//设置登录密码
jedis.select(3);//选择第四个数据库,数据库下标从0开始
jedis.flushDB();//清空第四个数据库
jedis.set("hello", "world"); //jedis.xxx方法名字就是命令
System.out.println( jedis.get("hello"));
jedis.mset(new String[]{"a", "1", "b", "2", "c" ,"3"});
List<String> strs = jedis.mget(new String[]{"a", "c"});
System.out.println(strs);
System.out.println(jedis.incr("c"));
System.out.println(jedis.del("b"));;
} catch (Exception e) {
throw e;
}finally {
jedis.close();//释放链接
}
}
@Test
public void testHash() throws Exception {
Jedis jedis = new Jedis("192.168.132.128", 6666, 1000);
try {
jedis.auth("123456");//设置登录密码
jedis.select(3);//选择第四个数据库,数据库下标从0开始
jedis.flushDB();//清空第四个数据库
jedis.hset("user:1:info", "name", "齐毅");
jedis.hset("user:1:info", "age", "35");
jedis.hset("user:1:info", "height", "180");
String name = jedis.hget("user:1:info", "name");
System.out.println(name);
Map user1 = jedis.hgetAll("user:1:info");
System.out.println(user1);
} catch (Exception e) {
throw e;
}finally {
jedis.close();//释放链接
}
}
}
(三)Jedis线程池用法
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
/**
* 连接池测试类
*/
public class JedisPoolTestor {
@Test
public void testJedisPool(){
GenericObjectPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(100); //设置连接池中最多允许放100个Jedis对象
//我的建议maxidle与maxTotal一样,相当于储备干部
config.setMaxIdle(100);//设置连接池中最大允许的空闲连接
config.setMinIdle(10);//设置连接池中最小允许的连接数
config.setTestOnBorrow(false); //借出连接的时候是否测试有效性,推荐false
config.setTestOnReturn(false); //归还时是否测试,推荐false
//远程Redis配置默认有protected-mode yes,所以这里设置false
config.setTestOnCreate(false); //创建时是否测试有效
config.setBlockWhenExhausted(true);//当连接池内jedis无可用资源时,是否等待资源 ,true
config.setMaxWaitMillis(1000); //没有获取资源时最长等待1秒,1秒后还没有的话就报错
//创建jedis,这句话运行后就自动根据上面的配置来初始化jedis资源了
JedisPool pool = new JedisPool(config , "192.168.132.128" , 6666);
Jedis jedis = null;
try {
jedis = pool.getResource(); //从连接池中"借出(borrow)"一个jedis对象
jedis.auth("123456");
jedis.set("abc" , "bbb");
System.out.println(jedis.get("abc"));
} catch (Exception e) {
e.printStackTrace();
} finally {
if(jedis != null){
jedis.close();//在使用连接池的时候,close()方法不再是关闭,而是归还(return)
}
}
}
}
六.SSM下使用Jedis
(一)创建Maven工程,导入依赖
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
<dependency>
<groupId>com.yugabyte</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0-yb-11</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- json序列化工具-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<!--对象转化Map-->
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
(二)增加配置文件applicationContext.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--连接池配置对象-->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="100"></property>
<property name="maxIdle" value="100"></property>
<property name="minIdle" value="10"></property>
<property name="maxWaitMillis" value="1000"></property>
<property name="blockWhenExhausted" value="true"></property>
<property name="testOnBorrow" value="false"></property>
<property name="testOnReturn" value="false"></property>
<property name="testOnCreate" value="false"></property>
</bean>
<!-- usePool = false Jedis直连, true 连接池 -->
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:usePool="true" p:poolConfig-ref="poolConfig" p:hostName="192.168.132.128" p:port="6666" p:password="123456" p:database="10">
</bean>
<!--redisTemplate本质就是Jedis的封装,在Jedis基础上进行了大幅度的简化,并且对连接池友好,允许自动回收连接.
PS:前面课程自己写的JedisPool如果没有调用close方法则会出现连接泄露的问题,但在Spring-Data-Redis中则不会-->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnFactory">
<!-- keySerializer key序列化采用String原文保存 -->
<property name="keySerializer">
<!-- StringRedisSerializer 字符串原文序列化保存 -->
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="valueSerializer">
<!--//利用Jackson进行对象序列化-->
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"></bean>
</property>
<property name="hashKeySerializer">
<!-- StringRedisSerializer 字符串原文序列化保存 -->
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
</property>
<property name="hashValueSerializer">
<!--//利用Jackson进行对象序列化-->
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"></bean>
</property>
</bean>
</beans>
(三)测试使用
import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
//启动时初始化Spring IOC容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringJedisTestor {
@Resource
private RedisTemplate<String,Object> redisTemplate;
@Test
public void testSetGet(){
//opsForValue()是对String字符串操作的类
//默认情况下,Spring-Data-Redis 会采用JDK 序列化的方式将所有key value进行二进制序列化
//这会导致redis数据库的值可读性极差,通常我们需要用字符串或JSON的形式来保存对象
redisTemplate.opsForValue().set("a" , "b1111");
String b = (String)redisTemplate.opsForValue().get("a");
System.out.println(b);
}
@Test
public void testObjectSerializer(){
User u1 = new User("u1" , "p1");
User u2 = new User("u2" , "p2");
redisTemplate.opsForValue().set("user:u1" , u1);
redisTemplate.opsForValue().set("user:u2" , u2);
}
@Test
public void testObjectDeserializer(){
User u1 = (User)redisTemplate.opsForValue().get("user:u1");
System.out.println(u1.getUsername());
}
@Test
public void testHashSerializer() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
User u = new User("u3" , "p3");
//关键是如何将JavaBean转为Map
Map m1 = BeanUtils.describe(u);
redisTemplate.opsForHash().putAll("user:m1" , m1);
}
@Test
public void testHashDeserializer() throws InvocationTargetException, IllegalAccessException {
Map m1 = (Map)redisTemplate.opsForHash().entries("user:m1");
User u1 = new User();
BeanUtils.populate(u1 , m1);
System.out.println(u1.getUsername());
}
@Test
public void testList(){
for(int i = 0 ; i < 10 ; i++){
User u = new User("l" + i , "p" + i);
redisTemplate.opsForList().rightPush("VipUserRank" , u);
}
List users = redisTemplate.opsForList().range("VipUserRank" ,1 , 5);
System.out.println(users);
}
@Test
//数据库层面的命令要是用execute接口实现
public void testFlushDB(){
redisTemplate.execute(new RedisCallback() {
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
redisConnection.flushDb();
return null;
}
});
}
}
七.Spring Boot下整合spring cache和Redis
(一)创建工程,使用Initize方法,选择cache,Redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
(二)在启动中加入
@EnableCaching //启用SpringCache缓存 , SpringCache会根据底层引用的Jar包决定缓存的类型
public class Ch14SpringcacheApplication {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(){
//加载redis缓存的默认配置
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return configuration;
}
(三)在配置文件中
spring:
redis:
database: 9
host: 192.168.132.128
port: 6666
password: 123456
jedis:
pool:
max-active: 100
max-idle: 100
min-idle: 10
max-wait: 1000ms
(四)创建实体类
package com.itlaoqi.redis.ch14springcache.entity;
import java.io.Serializable;
import java.util.Date;
public class Emp implements Serializable{
private Integer empno;
private String name;
private Date birthday;
private Float salary;
private String department;
public Emp(){ //必须要有默认构造函数
}
public Emp(Integer empno, String name, Date birthday, Float salary, String department) {
this.empno = empno;
this.name = name;
this.birthday = birthday;
this.salary = salary;
this.department = department;
}
public Integer getEmpno() {
return empno;
}
public void setEmpno(Integer empno) {
this.empno = empno;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public Float getSalary() {
return salary;
}
public void setSalary(Float salary) {
this.salary = salary;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
(五)写service方法,加上声明式缓存注解
package com.itlaoqi.redis.ch14springcache.service;
import com.itlaoqi.redis.ch14springcache.entity.Emp;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Service
public class EmpService {
//对于默认情况下, Redis对象的序列化使用的是JDK序列化.必须要求实体类实现Serili..接口
//Cacheable会将方法的返回值序列化后存储到redis中,key就是参数执行的字符串
//Cacheable的用途就是在执行方法前检查对应的key是否存在,存在则直接从redis中取出不执行方法中的代码
//没有对应的key则执行方法代码,并将返回值序列化保存到redis中
//condition代表条件成立的时候才执行缓存的数据 , 反之有一个unless ,代表条件不成立的时候才获取缓存
@Cacheable(value = "emp" , key = "#empId" , condition = "#empId != 1000")
public Emp findById(Integer empId) {
System.out.println("执行了FindById方法:EmpId:" + empId);
return new Emp(empId , "itlaoqi" , new Date() , 1000f ,"RESEARCH");
}
//冒号分割
@Cacheable(value = "emp:rank:salary")
public List<Emp> getEmpRank() {
List list = new ArrayList();
for(int i = 0 ; i < 10 ; i++) {
list.add(new Emp(i , "emp" + i , new Date() , 5000 + i * 100f , "RESEARCH"));
}
return list;
}
//@CachePut 作用是不管redis是否存在key, 都对数据进行更新
@CachePut(value="emp" , key = "#emp.empno")
public Emp create(Emp emp){
System.out.println("正在创建" + emp.getEmpno() + "员工信息");
return emp;
}
//@CachePut 作用是不管redis是否存在key, 都对数据进行更新
//Update也是用CachePut
@CachePut(value="emp" , key = "#emp.empno")
public Emp update(Emp emp){
System.out.println("正在更新" + emp.getEmpno() + "员工信息");
return emp;
}
@CacheEvict(value="emp" , key = "#empno")
public void delete(Integer empno){
System.out.println("删除" + empno + "员工信息");
}
}
八.Redis开发规范