文章目錄
Linux操作系統
Redis服務
(一)事務操作
1. 概述
redis單條命令是原子性操作,但事務不保證原子性。
redis事務的本質是:一組命令的集合!
- 特點:
事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
事務是一個原子操作:事務中的命令要麼全部被執行,要麼全部都不執行。
2. 事務的執行
正常執行事務:
# 開啓事務
127.0.0.1:6379> multi
OK
# 命令入隊
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
# 執行事務
127.0.0.1:6379> exec
1) OK
2) OK
3) "v2"
# 執行完一個事務後,這個事務就停止了,再次開啓纔可以繼續下一個事務的操作!
放棄事務:
刷新一個事務中所有在排隊等待的指令,並且將連接狀態恢復到正常。
# 開啓事務
127.0.0.1:6379> multi
OK
# 命令入隊
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> get k2
(nil)
注意:
- 開啓事務後,出現錯誤的入隊命令,事務執行會報錯!所有命令都會執行失敗!
- 當開啓事務後出現如:string不能執行
incr
加一的操作,執行事務後,不會影響事務的執行,這條命令會返回ERROR,但其他命令會執行成功,不影響操作!
3. 監控操作
WATCH:標記所有指定的key 被監視起來,在事務中有條件的執行(樂觀鎖)。
正常執行:
# 創建一個存有100元的key
127.0.0.1:6379> set money 100
OK
# 創建一個花費統計key
127.0.0.1:6379> set spend 0
OK
# 監控money
127.0.0.1:6379> watch money
OK
# 開啓事務
127.0.0.1:6379> multi
OK
# 花費50元
127.0.0.1:6379> decrby money 50
QUEUED
# 花費統計加50元
127.0.0.1:6379> incrby spend 50
QUEUED
# 執行事務
127.0.0.1:6379> exec
1) (integer) 50
2) (integer) 50
# 當事務執行完後,watch就會失效
多線程插隊,事務執行失敗,此時體現了watch當作樂觀鎖:
開啓兩個服務操作上面的事務,在第一個服務沒有執行事務之前,讓第二個服務插隊修改money的值,由於watch存在,所以再次提交事務就會失敗:
# 服務一
127.0.0.1:6379> watch money # 再次監控
OK
127.0.0.1:6379> multi # 開啓事務
OK
127.0.0.1:6379> decrby money 20 # 再次花費20元
QUEUED
127.0.0.1:6379> incrby spend 20 # 花費統計加20元
QUEUED
# 服務二
127.0.0.1:6379> get money # 此時money還是50
"50"
127.0.0.1:6379> set money 200 # 在服務一執行事務之前將money設置爲200
OK
# 服務一執行事務:結果爲nil
127.0.0.1:6379> exec
(nil)
解決方法:unwatch
127.0.0.1:6379> unwatch # 先unwatch:刷新一個事務中已被監視的所有key
OK
127.0.0.1:6379> multi # 重新開啓事務
OK
...
(二)Jedis
1. 概述
Jedis是Redis官方推薦的Java連接開發工具。
這裏我們在windows環境下測試:首先保證win環境存在redis服務。
redis的zip包下載地址:https://github.com/MicrosoftArchive/redis/releases,下載解壓即可用!
2. Jedis的測試
- 創建項目,導入相關依賴:
<!-- jedis的包 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
- 測試連接redis
import redis.clients.jedis.Jedis;
public class Test{
public static void main(String[] args){
// 創建一個Jedis對象
Jedis jedis = new Jedis("127.0.0.1",6379);
// ping命令測試
system.out.println(jedis.ping());
}
}
結果:
可以看到Jedis的方法都是redis的命令:
3. Jedis事務操作測試
- 正常事務的執行
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class Test2 {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
JSONObject json = new JSONObject();
json.put("name", "zhangsan");
json.put("age", "20");
String string = json.toJSONString();
// 開啓事務
Transaction multi = jedis.multi();
try {
multi.set("user1",string);
multi.set("user2",string);
// 執行事務
multi.exec();
} catch (Exception e) {
// 出現異常則放棄事務
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
// 關閉連接
jedis.close();
}
}
}
/*
結果:
{"name":"zhangsan","age":"20"}
{"name":"zhangsan","age":"20"}
*/
- 異常事務
import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class Test2 {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1",6379);
jedis.flushDB(); // 刷新數據庫
JSONObject json = new JSONObject();
json.put("name", "zhangsan");
json.put("age", "20");
String string = json.toJSONString();
// 開啓事務
Transaction multi = jedis.multi();
try {
multi.set("user1",string);
multi.set("user2",string);
// 模擬error
int i = 1/0;
// 執行事務
multi.exec();
} catch (Exception e) {
// 出現異常則放棄事務
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
// 關閉連接
jedis.close();
}
}
}
/*
結果:
java.lang.ArithmeticException: / by zero
at com.chen.Test2.main(Test2.java:23)
null
null
*/
(三) SpringBoot整合redis
1. 創建SpringBoot項目
引入redis後的pom.xml文件依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
點進spring-boot-starter-data-redis
發現:lettuce依賴。
在SpringBoot 2.x 版本後,jedis被替換爲lettuce:
jedis:採用直連,多線程操作時,存在線性不安全,需使用jedis pool;
lettuce:採用netty,在多線程下不存在不安全。
2. 編寫application.properties文件
# 配置類
spring.redis.host=127.0.0.1
spring.redis.port=6379
SpringBoot的自動配置類:RedisAutoConfiguration
自動配置類綁定的properties類:RedisProperties
@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0; // 默認使用0號數據庫
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:[email protected]:6379
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379; // 默認端口6379
...
3. 測試
在test中注入RedisTemplate類,先來測試一下:
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("k1","v1");
redisTemplate.opsForValue().set("k2","哈哈哈 ");
System.out.println(redisTemplate.opsForValue().get("k1"));
System.out.println(redisTemplate.opsForValue().get("k2"));
}
}
運行結果:
此類中其他操作:(事務、數據庫…)
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
// 事務操作
redisTemplate.multi();
redisTemplate.exec();
// 數據庫的操作
RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
connection.flushDb();
connection.flushAll();
}
}
RedisTemplate 類的 opsForValue類似於string,還有其他的操作(hash、set、list…)
4. 對象序列化
編寫自己的序列化配置類:
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
// 爲了方便開發使用<String,Object>
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
// json序列化:用json解析任意的對象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// string序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key採用string序列化
template.setKeySerializer(stringRedisSerializer);
// hash的key採用string序列化
template.setHashKeySerializer(stringRedisSerializer);
// value採用json序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value採用json序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
測試類:
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("k1","v1");
redisTemplate.opsForValue().set("k2","哈哈哈 ");
System.out.println(redisTemplate.opsForValue().get("k1"));
System.out.println(redisTemplate.opsForValue().get("k2"));
}
@Test
public void test(){
User user = new User("zhangsan", 20);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
結果:
//下篇再見…謝謝