Redis基础 - Java客户端

Redis基础 - Java客户端,第1张

在Redis官网中提供了各种语言的客户端。
地址:https://redis.io/clients
前三名客户端是jedis、lettuce、redisson。

jedis:以Redis命令作为方法名称,学习成本低,简单实用。但是jedis实例是线程不安全的,所以每个线程都要建立单独的jedis势力,所以多线程环境下需要基于连接池来使用。

lettuce:是基于netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。支持Redis的哨兵模式、集群模式和管道模式。

redisson:是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。包含了诸如Map、Queue、Lock、Semaphore、AtomicLong等强大功能。

使用Spring Data Redis就等于包含了jedis和lettuce。不过不少公司依然用jedis。

jedis

jedis官网:https://github.com/redis/jedis

jedis使用的基本步骤

1,引入依赖

<dependency>
	<groupId>redis.clientsgroupId>
	<artifactId>jedisartifactId>
	<version>3.7.0version>
dependency>

2,建立链接

private Jedis jedis;
@BeforeEach
void setUp() {
	// 建立链接
	jedis = new Jedis("IP地址",端口);
	// 设置密码
	jedis.auth("123123");
	// 选择库
	jedis.select(0);
}

3,测试

@Test
void testString() {
	// 插入数据,方法名称就是Redis命令名称,非常简单
	String result = jedis.set("name","asd");
	print(result);// ok
	// 获取数据
	String name = jedis.get("name");
	print(name);
}
@Test
void testHash () {
	// 插入hash数据
	jedis.hset("user:1","name","asd");
	jedis.hset("user:1","age","22");
	// 获取
	Map<String,String> map = jedis.hgetAll("user:1");
	print(map);
}
// 其他的都一样,所以省略

4,释放资源

@AfterEach
void tearDown() {
	// 释放资源
	if(jedis != null) jedis.close();
}
jedis连接池

jedis本身是线程不安全的,并且频繁的创建和销毁链接会有性能损耗,因此推荐使用jedis连接池来替代jedis直连方式。因为多线程时,由于jedis线程不安全,所以每个线程需要各自的jedis实例,因此会有频繁的创建和销毁的情况。

1,定义工具类

public class JedisConnectionFactory {
	private static final JedisPool jedisPool;// jedis连接池
	static {
		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();// 做配置的
		// 最大链接
		jedisPoolConfig.setMaxTotal(8);
		// 最大空闲链接
		jedisPoolConfig.setMaxIdle(8);
		// 最小空闲链接
		jedisPoolConfig.setMinIdle(8);
		// 设置最长等待时间,ms毫秒。连接池里没链接是,等多长时间。
		jedisPoolConfig.setMaxWaitMillis(1000);
		// 1000是超时时间?
		jedisPool = new JedisPool(jedisPoolConfig,"Redis的IP地址",端口,1000,"123123密码");
	}
	// 获取jedis对象
	public static Jedis getJedis() {
		return jedisPool.getResource();
	}
}

然后把上面的jedis = new Jedis(“IP地址”,端口); 改成如下:

jedis = JedisConnectionFactory.getJedis();

至于释放资源的jedis.close();里面的源码中可以看到有连接池的话直接归还到连接池了,所以有连接池时close方法调用后就不会销毁,而是把该链接干入到连接池。

SpringDataRedis

springdata是spring中数据 *** 作的模块,包含对各种数据库的集成,其中对Redis的集成模块就叫做springdataRedis。
官网是:https://spring.io/projects/spring-data-redis

springdataredis中提供了Redistemplate工具类,其中封装了各种对Redis的 *** 作。并且将不同数据类型的 *** 作API封装到了不同的类型中:
API-----返回值类型-----说明
redisTemplate.opsForValue()-----ValueOperations----- *** 作string类型数据
redisTemplate.opsForHash()-----HashOperations----- *** 作hash类型数据
redisTemplate.opsForList()-----ListOperations----- *** 作list类型数据
redisTemplate.opsForSet()-----SetOperations ----- *** 作set类型数据
redisTemplate.opsForZSet()-----ZSetOperations----- *** 作sortedset类型数据
redisTemplate-------------------------------------------------通用的命令

springboot提供了对springdataRedis的支持。

1,引入依赖


<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-data-redisartifactId>
dependency>

<dependency>
	<groupId>org.apache.commonsgroupId>
	<artifactId>commons-pool2artifactId>
dependency>

2,配置文件

spring:
  redis:
    host: Redis的IP地址
    port: 端口
    password: 密码
    lettuce:
      pool:
        max-active: 8 #最大链接
		max-idle: 8 #最大空闲链接
		min-idle: 0 #最小空闲链接
		max-wate: 100 #链接等待时间

3,注入RedisTemplate

@Autowired
private RedisTemplate redisTemplate;

4,编写测试

@SpringBootTest
public class RedisTest {
	@Autowired
	private RedisTemplate redisTemplate;
	@Test
	void testString() {
		// 插入一条string类型数据
		redisTemplate.opsForValue().set("name","张三");
		// 读取一条string类型数据
		Object name = redisTemplate.opsForValue().get("name");
		print(name);
	}
}

执行之前,假如在终端用命令设置name为李四。执行程序后,设置了张三,取出来也是张三。但此时到了终端再取出时依然是李四。在终端执行keys *之后,的确能看见有个key是“name”,但还能看见“\xac\xed\x00\x05t\x00\x04name”这个。在终端执行get "\xac\xed\x00\x05t\x00\x04name"时能看到这个才是张三。这就要说到序列化了,在代码中redisTemplate.opsForValue().set(“name”,“张三”);这里接受的参数类型并不是string,都是Object,这就是springdataRedis的特殊功能,她可以接收任何类型的对象,然后帮我们把他转成Redis可以处理的字节,所以我们存进去的"name"和"张三"都被当成了Java对象,而Redistemplate底层默认对这些对象的处理方式就是利用JDK的序列化工具ObjectOutputStream,这个流的作用就是把Java对象转成字节,然后把这个字节写入Redis之后,就是刚才那个结果了。

springdataRedis的序列化方式

redisTemplate可接受任意object作为值写入Redis,只不过写入前会把object序列化为字节形式,默认是采用JDK序列化,得到的结果就是刚才那样。(key也被序列化,所以刚才明明改了name,却不是改了name。内存占用也大,明明是name,但整出为\xac\xed\x00\x05t\x00\x04name,明明只是张三,却整出为一堆向name那种东西。)
缺点:可读性差、内存占用较大

所以为了实现写什么就是写什么,所以必须去改变Redistemplate的默认序列化模式。默认用的是RedisSerializer接口的JdkSerializationRedisSerializer实现类。所以可以使用其他的实现类。比如专门处理string的是StringRedisSerializer这个实现类,在key为字符串情况下,可以用这个。
如果是value,可能不只是字符串,有可能是对象,建议使用GenericJackson2JsonRedisSerializer这个实现类。所以key一般用StringRedisSerializer,value一般用GenericJackson2JsonRedisSerializer。

自定义Redistemplate的序列化方式
@Configuration
public class RedisConfig {
	@Bean
	public Redistemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throw UnknownHostException {

		// 创建template
		Redistemplate<String,Object> redisTemplate = new Redistemplate<>();

		// 设置链接工厂(Redistemplate需要链接工厂,springboot自动创建的使用即可)
		redisTemplate.setConnectionFactory(redisConnectionFactory);

		// 设置序列化工具
		GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

		// key和hashkey采用string序列化(RedisSerializer.string()返回StringRedisSerializer.UTF_8)
		redisTemplate.setKeySerializer(RedisSerializer.string());
		redisTemplate.setHashKeySerializer(RedisSerializer.string());

		// value和hashvalue采用JSON序列化
		redisTemplate.setValueSerializer(jsonRedisSerializer);
		redisTemplate.setHashValueSerializer(jsonRedisSerializer);
		return redisTemplate;
	}
}

然后如下改:

@Autowired
private RedisTemplate<String,Object> redisTemplate;

还有在pom.xml里添加jackson依赖,因为用到了JSON序列化。(因为这个例子没引用springMVC依赖,所以要手动引入Jackson依赖,如果依赖了springMVC,则不需要,因为springMVC自带她)


<dependency>
	<groupId>com.fasterxml.jackson.coregroupId>
	<artifactId>jackson-databindartifactId>
dependency>

这样的话,上面的问题就解决了。

还可以设置对象,比如有User.java。

@Test

void test() {
	redisTemplate.opsForValue().set("user:1",new User("asd",12));
	Object user = redisTemplate.opsForValue().get("user:1");//也可以强转成User
	print(user);
}

在图形界面客户端查的话,可以发现Redis里存入的值是json:

{
	"@class": "com.asd.User"
	"name": "asd"
	"age": 12
}

这说明他为我们自动化转为JSON写入到了Redis,然后取的时候又自动的反序列化成User了。

能自动做反序列化是因为有"@class": "com.asd.User"这个属性,所以知道要把结果转成哪个对象,没有这个就不行,虽然序列化好说,但反序列化就难了,所以这个玩意是自动反序列化必不可少的。

StringRedistemplate

尽管JSON的序列化方式可以满足我们的需求,但依然存在一些问题,比如"@class": "com.asd.User"这玩意也被包含在值上。为了在反序列化时知道对象的类型,JSON序列化器会将类的class类型写入到JSON结果中,存入Redis,会带来额外的内存开销(一个还行,要是成千上万个,就不得了)。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用string序列化器(也就是RedisSerializer.string()),要求只能存储string类型的key和value。当需要存储Java对象时,手动完成对象的序列化和反序列化。

不用去修改刚才自定义的RedisTemplate的设置了,因为spring提供了StringRedistemplate类,她的key和value序列化方式默认都是string方式,省去了自定义RedisTemplate的过程。

@Autowired
private StringRedisTemplate stringRedisTemplate;

// json工具(这是spring默认提供的,也可以使用其他的)
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testStringTemplate() throws JsonProcessingException {
	// 准备对象
	User user = new User("asd",19);
	// 手动序列化
	String json = mapper.writeValueAsString(user);
	// 写入一条数据到Redis
	stringRedisTemplate.opsForValue().set("user:1",json);

	// 读取数据
	String val = stringRedisTemplate.opsForValue().get("user:1");
	// 反序列化
	User user1 = mapper.readValue(val,User.class);
	print(user1);
}

@Test
void testString() {
	// 插入一条string类型数据
	stringRedisTemplate.opsForValue().set("name","张三");
	// 读取一条string类型数据
	Object name = stringRedisTemplate.opsForValue().get("name");
	print(name);
}

@Test
void testHash() {
	stringRedisTemplate.opsForHash().put("user:1","name","张三");
	stringRedisTemplate.opsForHash().put("user:1","age","21");
	Map<Object,Object> map = stringRedisTemplate.opsForHash().entries("user:1");
	print(map);
	// stringRedisTemplate.opsForHash()中的其他方法这里省略。
}
总结:RedisTemplate的两种序列化实践方案 方案1:

1,自定义RedisTemplate
2,修改RedisTemplate的序列化器为GenericJackson2JsonRedisSerializer

方案2:

1,使用StringRedisTemplate
2,写入Redis时,手动把对象序列化为JSON
3,读取Redis时,手动把读取到的JSON反序列化为对象

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/924476.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存