1,Redis介绍
Redis是使用C语言编写的一个key-value 数据库,key是字符串,value包括以下5中类型:
结构类型 | 存储的数据 | 针对该结构能执行的操作 |
string | 字符串、整数或浮点数 | 对整个字符串或其中的一部分执行操作; 对整数和浮点数执行自增或自减操作。 |
list | 字符串列表,可以重复 | 从链表的两端压入或弹出元素; 对链表进行修剪(trim); 获取单个或多个元素; 根据值查找或移除元素。 |
set | 字符串的无序集合,每个元素都是唯一的 | 添加、获取、移除单个元素; 检查一个元素是否存在于集合中; 计算两个集合的交集、并集、差集。 |
hash | 多个key-value的集合,key不能重复的散列表 | 添加、获取、移除单个键值对; 获取所有键值对。HashMap、JavaBean、JSON |
sorted set | 字符串的有序集合,每个元素都有一个浮点数分值,元素的排列顺序由分值大小决定 | 添加、获取、删除单个元素; 根据分值范围获取元素 |
2,下载安装包
redis 官方网站 :Redis
3,安装redis
1,安装C语言的编译环境
yum -y install gcc automake autoconf libtool make
2,解压并安装
默认在/usr/local/bin目录下安装redis服务程序redis-server和客户端程序redis-cli。
tar -xzf redis-xxx.tar.gz
cd redis-xxx
make
make install
3,启动redis服务
将redis-xxx/redis.conf拷贝到/etc目录下,启动时要指定配置文件的路径。
./redis-server /etc/redis.conf
4,启动客户端并测试
默认连接本机的6379端口,ping 命令用于验证 redis 服务是否正常,quit命令关闭连接退出客户端。启动客户端时可指定主机地址和端口:redis-cli -h 127.0.0.1 -p 6379
./redis-cli
127.0.0.1:6379>ping
PONG
127.0.0.1:6379>quit
4,redis配置
在etc/redis.conf下设置
1, bind
默认情况bind=127.0.0.1只能接受本机的访问请求,不写的情况下,无限制接受任何ip地址的访问
2,protected-mode
将本机访问保护模式设置 no
3,port
端口号,默认 6379
4,timeout
一个空闲的客户端维持多少秒会关闭,0表示关闭该功能。即永不关闭。
5,cp-keepalive
对访问客户端的一种心跳检测,每个n秒检测一次。
单位为秒,如果设置为0,则不会进行Keepalive检测,建议设置成60
6,daemonize
是否为后台进程,设置为yes
守护进程,后台启动
7,requirepass
永久设置密码,需要再配置文件中进行设置
5,redis常用操作命令
quit -------------- 退出客户端连接
shutdown --------- 在客户端关闭redis服务
ping/pong ------- 测试redis连接
select index ------ 选择数据库,Redis默认有16个数据库index从0~15,默认连接的是0号数据库
keys * --------- 查看当前数据库中所有的key
expire key 秒 --------- 设置key的有效时间(秒),过期后会自动删除key
ttl key ---------- 查看key的剩余时间
del key ---------- 删除key
dbsize ---------- 返回当前数据库中key的数量
flushdb ------- 清空当前数据库中所有的key
type key ------ 返回key的类型
rename oldKey newKey --------- 重命名key
exists key------确认某个key是否存在
1,String类型
设置值:set key value
获取值:get key
自增:incr key (适用于整数类型的value)
自减:decr key(适用于整数类型的value)
返回旧值设置新值:getset key value (原子性)
设置多值:mset k1 v1 k2 v2 ...
获取多值:mget k1 k2 ....
2,hash类型
设置hash的单个属性和值:hset key field value
获取hash单个属性值:hget key field
设置hash的多个属性和值hmset key f1 v1 f2 v2 ...
获取hash多个属性值hmget key f1 f2 ...
返回hash中所有的field:hkyes key
返回hash中所有的value:hvals key
返回hash中所有的field和value:hgetall key
3, list类型
头部添加:lpush key value...
尾部添加:rpush key value...
头部弹出(删除):lpop key
尾部弹出(删除):rpop key
返回列表的元素:lrange key start stop (lrange key 0 -1 返回列表的所有元素)
4,set类型
添加元素:sadd key value ...
返回集合元素个数:scard key
返回集合的所有元素:smembers key (无序)
5,zset类型
添加:zadd key score value
升序返回所有元素 zrange key 0 -1 (按分值升序)
降序返回所有元素 zrevrange key 0 -1 (按分值降序)
返回集合中元素个数:zcard key
6,Java连接Redis——Jedis
1,连接准备
1,注释IP绑定:在/etc/redis.conf配置文件
# bind 127.0.0.1
2,关闭保护模式
protected-mode no
3,Linux防火墙开启6379端口
firewall-cmd --zone=public --add-port=6379/tcp --permanent
firewall-cmd --reload
4,配置完重启redis服务器
2,添加Jedis依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.3.0</version>
</dependency>
3,使用Jedis操作Linux上的Redis
public class App
{
public static void main( String[] args )
{
Jedis jedis=new Jedis("192.168.40.128",6379);
jedis.set("name","张三");
System.out.println(jedis.get("name"));
}
}
4,使用Jedis操作Java对象
不管是Redis中哪种数据类型进行存储时,jedis的方法只接受String和byte[]类型的参数,所以需要在Redis中保存Java对象的思路:Java对象——>String或byte[]。
1,将对象转换为Json字符串进行存储
可以利用gson、fastjson、jackson等工具进行json格式转换以fastjson为例:
1,添加fastjson和lombok依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
2,使用jedis存取对象
public class App
{
public static void main( String[] args )
{ //连接redis
Jedis jedis = new Jedis("192.168.40.128", 6379);
//创建对象Student
Student student = new Student("张三", 23);
//转换成Jjson字符串
String jsonString = JSON.toJSONString(student);
//添加到redis
jedis.set("student", jsonString);
//获取key值为student的json字符串
String s = jedis.get("student");
System.out.println(s);
//将json字符串转换成对象
Student student1 = JSON.parseObject(s, Student.class);
System.out.println(student1);
}
}
2,将对象转换为byte[]存储
1,对象实现序列化接口
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student implements Serializable {
private Integer id;
private String name;
private Integer age;
}
2,对象的序列化和反序列化
public static void main( String[] args ) throws Exception
{
Jedis jedis=new Jedis("192.168.138.128",6379);
Student stu=new Student(2,"李四",24);
//序列化对象
ByteArrayOutputStream baos=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(baos);
oos.writeObject(stu);
byte[] bytes = baos.toByteArray();
//保存redis
jedis.set("stu".getBytes(),bytes);
//从redis中获取
byte[] data = jedis.get("stu".getBytes());
//反序列化操作
ByteArrayInputStream bais=new ByteArrayInputStream(data);
ObjectInputStream ois=new ObjectInputStream(bais);
Student student=(Student) ois.readObject();
System.out.println(student);
}
7,Redis的发布和订阅机制
举例:电商的主要应用程序负责产生订单——>订单成功后立刻返回订单信息,进行消息的发布——>让其他应用完成产生订单后的相关任务:
- 发送短信应用:给用户发送确认短信,或者发邮件
- 发送发货信息应用:给仓库人员/商家/物流发送订单信息
频道:bjpn.com
1,使用客户端进行发布和订阅
1,订阅者订阅相关频道
2, 发布者在频道发送消息
3,订阅者收到消息
2,使用Java进行发布和订阅
1,编写订阅类 MyPubSub继承JedisPubSub
public class MyPubSub extends JedisPubSub {
@Override
public void onMessage(String channel, String message) {
System.out.println("收到"+channel+"的消息:"+message);
}
@Override
public void onSubscribe(String channel, int subscribedChannels) {
System.out.println(channel+"频道订阅成功");
}
}
2,开启订阅,并模拟另外一个应用发布消息
public class AppTest
{
@Test
//开启订阅
public void start()
{
Jedis jedis = new Jedis("192.168.40.128", 6379);
MyPubSub myPubSub=new MyPubSub();
jedis.subscribe(myPubSub,"bjpn.com");
}
@Test
//模拟另外一个应用发布消息
public void end()
{
Jedis jedis = new Jedis("192.168.40.128", 6379);
jedis.publish("bjpn.com","你好!");
}
}
8,Redis的事务
multi命令用于开始一个事务;此后发送的多个命令会被Redis入队,不会马上执行;最后发送 exec命令让Redis执行队列中的所有命令。取消事务:discard 命令,其实就是清空命令队列并退出事务
1,关于乐观锁和悲观锁
在涉及到多个线程操作共享资源的情况下,为了确保多个线程有序的执行修改操作,在修改过程中不被其他的线程打断,这个时候我们就可以采用“锁”机制。
1,悲观锁
当在操作共享资源之前,先加锁,然后执行操作,操作完成后再释放锁。在Java中的同步代码块和同步方法、以及MySQL都采用了悲观锁来避免多个线程操作同时操作共享数据。
2,乐观锁
在修改数据之前,先读取数据,修改时再判断此刻的值是否和之前读取到的值一致,如果一致则说明该数据未被其他的线程修改,则直接修改;如果数据不一致了,则说明该数据已经被其他线程修改,则回到开始重复该过程——“自旋锁”。
2,使用watch命令来实现乐观锁
watch命令用来实现乐观锁,使用watch 命令监视某个key,当exec时如果该key值有变化,则事务会失败。在multi 命令之前使用 watch 命令监控某些键,然后使用multi命令开启事务,将多条命令入队。当执行 exec 命令时,Redis会比对watch命令监控的值有没有发生变化,如果没有变化就执行队列中的命令;如果发生了变化,那么它不会执行任何命令,而是返回nil。
提示:不要在multi和exec之间执行watch命令。
9,Redis持久化机制
Redis支持两种备份方式,一种是快照,这也是默认方式;另一种是Append-only File(AOF)方式。
1,快照方式
save 900 1 # 超过900秒且超过1个键被更改则进行快照
save 300 10 #超过 300秒且超过10个键被更改则进行快照
save 60 10000 # 超过60秒且超过10000个键被更改则进行快照
上面的三个条件之间是“或”的关系,只要其中一个条件满足,就会进行快照。每次保存快照都是将内存数据完整写入到磁盘,并不是保存增量数据。Redis会创建一个子进程,将内存数据写入一个临时文件,写入完成后,使用该文件替换之前的快照文件。客户端可以发送save或bgsave命令通知Redis进行一次快照。save是在Redis Server的主进程中执行快照,因此会阻塞所有客户端的请求,所以不推荐使用。bgsave命令则是在子进程中执行快照。使用shutdwon停止redis的服务,则redis会进行一次快照操作。
2,AOF方式
AOF备份方式是将Redis收到的每一个写命令都追加到一个appendonly.aof文件中,当Redis故障后重启时会重新执行该文件中的所有命令,就能在内存中重建整个数据库的内容。要启用AOF备份方式,需要配置如下:
每秒钟刷新一次缓冲区,在性能和持久化方面做了折中,推荐!!!
10,Redis主从复制集群模式
主从复制其主要的目的:为了提高Redis服务的高可用(HA),采用集群部署保证高可用(HA),采用多台服务器,分布在不同的机房或机架上,通过网络进行连接,让它们进行数据的同步,保持数据的一致性。即使一台服务器宕机,其他的服务器仍然可以提供服务,保证了高可用性。
master为主节点:可读可写
slave为从节点:只读
1,Redis主从复制的工作流程:
1,创建独立的配置文件
Master:redis.conf
Slave1:redis6380.conf
Slave2:redi6381.conf
2,修改配置文件
3,启动主节点和分节点服务器
在/usr/local/bin下执行命令
./redis-server /etc/redis.conf
./redis-server /etc/redis6380.conf
./redis-server /etc/redis6381.conf
4, 客户端连接
主:./redis-cli -p 6379
从:./redis-cli -p 6380
从:./redis-cli -p 6381
5,实现主从复制
1,命令实现
在分节点的服务器下执行命令:slaveof IP 主端口号
slaveof 192.168.40.128 6379
2,配置实现
在分界点的配置文件下修改
11, Spring Data Redis
Spring Data Redis(SDR)是Spring提供的用来访问Redis的组件, 它基于lettuce来连接Redis服务,SDR中的核心类是RedisTemplate,使用该类的实例可方便的和Redis进行交互。SpringBoot创建了StringRedisTemplate<String,String>和RedisTemplate<Object,Object>,可以直接利用这两个对象来访问Redis,只需要注入即可使用。
1, 添加spring-data-redis启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
也开在创建项目页面添加
2,在yml配置redis
spring:
redis:
host: 192.168.138.128 主节点IP
port: 6379 主节点端口号
database: 0 数据库编号
timeout: 3000 服务器超时时间
lettuce: Lettuce 连接池配置
pool:
max-active: 8 最大活动连接数
max-idle: 8 最大空闲连接数
max-wait: 3000ms 连接池中等待连接的最长时间
min-idle: 0 连接池中最小空闲连接数
3,注入StringRedisTemplate或RedisTemplate操作Redis
@SpringBootTest
class Demo2ApplicationTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads1() {
//StringRedisTemplate中的K,V都是String
stringRedisTemplate.opsForValue().set("name","张三");
String name = stringRedisTemplate.opsForValue().get("name");
System.out.println(name);
}
@Test
void contextLoads2() {
//RedisTemplate中的K,V都是Object,它会将字符串序列化成字节数组
redisTemplate.opsForValue().set("username","李四");
Object username = redisTemplate.opsForValue().get("username");
System.out.println(username);
}
}
存在的问题:
- RedisTemplate对象将key也序列化成了字节数组,给操作带来了不便。
- StringRedisTemplate的key和value都只能是字符串类型,
- 如果需要<String,Object>格式存储Reids,则需要自己编写配置类(设置序列化器)
4,编写配置类,设置对象的序列化器
@Configuration
public class RedisConfig {
@Bean("myRedisTemplate")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//将key序列化为string
redisTemplate.setKeySerializer(RedisSerializer.string());
//将value序列化为json
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
}
}
5,通过“myRedisTemplate”操作Redis
@SpringBootTest
class Demo2ApplicationTests {
@Autowired
private RedisTemplate<String,Object> myRedisTemplate;
@Test
void contextLoads3() {
User user=new User("齐天大圣",500);
myRedisTemplate.opsForValue().set("user",user);
User user1 =(User) myRedisTemplate.opsForValue().get("user");
System.out.println(user1);
}
}
6,使用RedisTemplate操作其他Redis的数据类型
RedisTemplate提供了boundValueOps(key),boundListOps(key),boundSetOps(key),boundHashOps(key)等方法,该方法返回BoundXxxOperations对象:
- BoundValueOperations:字符串类型操作
- BoundListOperations:列表类型操作
- BoundSetOperations:集合类型操作
- BoundZSetOperations:有序集合类型操作
- BoundHashOperations:散列操作
1,字符串操作
@Test
void string1() {
BoundValueOperations<String, Object> bvo = myRedisTemplate.boundValueOps("name");
bvo.set("张三");
System.out.println(bvo.get());
}
@Test
void string2() {
BoundValueOperations<String, Object> bvo = myRedisTemplate.boundValueOps("user");
bvo.set(new User("悟空",500));
System.out.println(bvo.get());
}
2,链表操作
@Test
void list1() {
BoundListOperations<String, Object> blo = myRedisTemplate.boundListOps("list1");
blo.leftPush("张三");
blo.leftPush("李四");
blo.leftPush("王五");
List<Object> list = blo.range(0, -1);
System.out.println(list);
}
@Test
void list2() {
BoundListOperations<String, Object> blo = myRedisTemplate.boundListOps("list2");
blo.rightPush(new User("张三",23));
blo.rightPush(new User("李四",24));
blo.rightPush(new User("王五",25));
List<Object> list = blo.range(0, -1);
System.out.println(list);
}
3,集合操作
@Test
void set1() {
BoundSetOperations<String, Object> bso = myRedisTemplate.boundSetOps("set1");
bso.add("悟空","八戒","沙僧","悟空");
Set<Object> set = bso.members();
System.out.println(set);
}
@Test
void set2() {
BoundSetOperations<String, Object> bso = myRedisTemplate.boundSetOps("set2");
bso.add(new User("悟空",500),new User("八戒",300),new User("沙僧",200));
Set<Object> set = bso.members();
System.out.println(set);
}
4,Hash操作
@Test
void hash() {
BoundHashOperations<String, Object, Object> bho = myRedisTemplate.boundHashOps("user1");
bho.put("username","齐天大圣");
bho.put("age",500);
bho.put("address","花果山");
System.out.println(bho.get("username"));
System.out.println(bho.get("age"));
System.out.println(bho.get("address"));
List<Object> values = bho.values();
System.out.println(values);
}
5,有序链表操作
@Test
void Zset() {
BoundZSetOperations<String, Object> bzso = myRedisTemplate.boundZSetOps("zest1");
bzso.add("张三",100);
bzso.add("李四",90);
bzso.add("王五",60);
Set<Object> zset1 = bzso.range(0, -1);
System.out.println(zset1);
Set<ZSetOperations.TypedTuple<Object>> typedTuples = bzso.rangeWithScores(0, -1);
System.out.println(typedTuples);
}
12,使用Redis作为缓存服务器
1,编写缓存配置类
@Configuration
public class RedisConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
//初始化一个RedisCacheWriter
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
//序列化方式为json序列化
RedisSerializer<Object> jsonSerializer = new GenericJackson2JsonRedisSerializer();
RedisSerializationContext.SerializationPair<Object> pair = RedisSerializationContext.SerializationPair
.fromSerializer(jsonSerializer);
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(pair);
//设置默认超过期时间是30分钟
// cacheConfig =cacheConfig.entryTtl(Duration.ofMinutes(30));
//初始化RedisCacheManager
return new RedisCacheManager(cacheWriter, cacheConfig);
}
}
2,启用缓存
@SpringBootApplication
@EnableCaching
public class Demo3Application {
public static void main(String[] args) {
SpringApplication.run(Demo3Application.class, args);
}
}
3,使用缓存的注解
@Cacheable标识需要缓存的方法,该方法的返回值会被Redis进行缓存(缓存的key=cacheNames+key);@CacheEvict用于清空缓存(key=cacheNames+key),该注解经常添加在修改和删除方法上。
@Service
public class UserService {
@Cacheable(cacheNames = "user",key = "#id")
public User findById(Integer id){
System.out.println("从数据库MySQL中查询用户...");
return new User(id,"用户"+id,23);
}
@CacheEvict(cacheNames = "user",key ="#user.id" )
public int update(User user){
System.out.println("修改数据库MySQL中的用户..."+user.getId());
return 1;
}
}
4,编写测试类
@SpringBootTest
class Demo3ApplicationTests {
@Autowired
private UserService userService;
@Test
void contextLoads() {
User user1=userService.findById(1);
System.out.println(user1);
userService.update(new User(1,null,null));
user1=userService.findById(1);
System.out.println(user1);
}
}
13,Spring Session
Spring Session主要用于分布式应用中实现Session共享。比如:微服务架构的应用程序中,有专门负责进行用户登录的模块,当用户登录后,访问别的模块时,就不再需要进行登录认证了。这也就是常说的单点登录。
高并发的解决方案:
- 集群部署:将相应的应用部署到多台服务器上,形成服务器的集群。
- 微服务:将一个项目拆分成若干个独立的模块,分别部署到不同的服务器上。集群和微服务都会带来Session的共享问题,Spring Session主要用于分布式应用上实现Session的共享。
当客户端向A模块发送登录请求,登录成功后将用户的session信息保存到Redis中,当客户端向B、C模块发送订单请求,该模块会从Redis中获取用户的session信息,从而实现了A、B和C的session共享。
1,session共享实现案例
1,添加依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
2,配置yml 文件
spring:
redis:
host: 192.168.138.128
session:
store-type: redis
server:
port: 8081
spring:
redis:
host: 192.168.138.128
session:
store-type: redis
server:
port: 8082
3,创建登录controller
@RestController
public class LoginController {
@RequestMapping("login")
public String login(HttpSession session){
//模拟登录成功后保存用户信息到session
session.setAttribute("loginUser","admin");
return "success";
}
}
4,创建订单controller
@RestController
public class OrderController {
@RequestMapping("order")
public String order(HttpSession session){
//模拟判断用户是否登录
return "登录用户=====>"+session.getAttribute("loginUser");
}
}
5,访问测试
2, Spring Session的工作过程
在SpringBoot的应用中,如果添加了Spring Session的依赖,就会自动注册一个名为SpringSessionRespositoryFilter过滤器,这个过滤器过滤客户端的所有请求,该过滤器会将HttpServletRequest对象进行包装产生了一个代理对象SessionRepositoryRequestWrapper,代理对象,SessionRepositoryRequestWrapper重写了HttpServletRequest的getSession()方法,从Redis中读写对应的Session信息。
当发送第一次登录请求时,没有提交任何Cookie信息,Spring Session会创建一个新的Session存入到Redis中,该Session对象有一个唯一的uuid作为key,在响应时将uuid进行Base64编码后再通过cookie发送给客户端。
当客户端发送订单请求时,客户端浏览器会通过Cookie再将base64编码后的uuid发送给服务器。
Spring Session根据Base64解码后的uuid(即key值)从Redis获取对应的session信息
Redis中保存session信息