redis Java客户端(jedis):
->使用set(String key, String Value)将大量的key-value写入redis中,受限于网络IO,400万的数据需要2个小时写入;
->hmset(String key, Map<String, String>)对于Java客户端来说能够很快的进行写入(对于vaid下的fields-values和oaid下的fields-values则很快的就能够写入Redis,其实只是两个key-value的写入,之所以耗费一定的时间,是因为fields-values很大,网络传输需要一定时间,但没有网络IO的问题)
->使用mset(String... vars)遇到的问题:
redis.clients.jedis.exceptions.JedisClusterOperationException: No way to dispatch this command to Redis Cluster because keys have different slots.
原因:哈希槽(hash slot)是Redis Cluster的概念,哈希槽是一个key的集合,Redis集群共有16384个哈希槽,每个key通过CRC16散列然后对16384进取模来决定该key应当被放到哪个槽中,集群中的每个节点负责一部分哈希槽。这里需要注意的是需要组成并集的各个集合的key必须是对应到redis集群中的同一个slot上,否则将会出现异常:...
所以redis提供了一种特定的标签{},这个{}内的字符串才参与计算hash slot。例如:{user}:aaa, {user}:bbb这两个集合可以确保在同一个slot上,可以使用zunionstore求它们的并集。如果是在redis集群环境下,需要特别注意。
解决:
在操作redis集群之前先想好后期是否会涉及对集群中存的数据进行批操作,如果只是做缓存,单个K-V存取可以不考虑key在一个hash slot中,这样也有利于数据均匀散列到不通的槽中。
链接:Redis集群()
链接:zunionstore异常:CROSSSLOT Keys in request don't hash to the same slot()
——>Lettuce和Jedis:
Lettuce和Jedis的定位都是Redis的client,所以他们当然可以直接连接redis server;
Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接;
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,即其是线程安全的
->Lettuce(Redis Java客户端Lettuce):
是什么:Lettuce支持完整的Redis API的同步和异步通信使用,包括其数据结构,发布/订阅消息传递和高可用性服务器连接;
为什么:
它与Jedis的最重要的区别是它通过Java 8的CompletionState接口提供异步支持,并支持Reactive Streams;
Lettuce提供了一个自然界面,用于从Redis数据库服务器发出异步请求以及创建流;
它还使用Netty与服务器通信,虽然这使得API变得更加笨重,但也使它更适合多个线程共享连接。
如何使用:
设置:
依赖:
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>5.0.1.RELEASE</version>
</dependency>
Redis安装:...
连接:
连接到服务器:
RedisClient redisClient = RedisClient.create("host:prot");
StatefulRedisConnection<String, String> connection = client.connect();
RedisCommands<String, String> commands = connection.sync();
String value = commands.get("foo");
...
connection.close();
client.shutdown();
...
Batching:
正常情况下,Lettuce会在API客户端调用命令后立即执行命令,这是大多数普通应用程序所需要的,特别是如果它们依赖于串行接收命令结果。
但是,如果应用程序不立即需要结果或批量上载大量数据,则此行为效率不高;
异步应用程序可以解决上述问题:
RedisClient client = RedisClient.create("hsot");
RedisAsyncCommands<String, String> commands = client.connect().async();
commands.setAutoFlushCommands(false);
List<RedisFuture<?>> futures = new ArrayList<>();
for (int i = 0; i < iterations; i++) {
futures.add(commands.set("key-" + i, "value-" + i);
}
commands.flushCommands();
boolean result = LettuceFutures.awaitAll(5, TimeUnit.SECONDS,
futures.toArray(new RedisFuture[0]));
将setAutoFlushCommands设置为false时,应用程序必须手动调用flushCommands。在此示例中,我们将多个set命令排队,然后刷新通道,AwaitAll等待所有RedisFutures完成。
Lettuce Examples:
RedisURI node1 = RedisURI.create("node1", 6379);
RedisURI node2 = RedisURI.create("node2", 6379);
RedisClusterClient clusterClient = RedisClusterClient.create(Arrays.asList(node1, node2));
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();
RedisAdvancedClusterCommands<String, String> syncCommands = conenction.sync();
...
connection.close();
clusterClient.shutdown();
Redis遍历所有key的两个命令--KEYS和SCAN:
KEYS pattern: KEYS的速度非常快,但由于KEYS命令一次性返回所有匹配的key,所以当redis中的key非常多时,对于内存的消耗和redis服务器都是一个隐患
对于Redis2.8以上版本给我们提供了一个更好的遍历key的命令SCAN,该命令的基本格式:
SCAN cursor [MATCH pattern] [COUNT count]
SCAN每次执行都只会返回少量元素,所以可以用于生产环境,而不会出现像KEYS或SMEMBERS命令带来的可能会阻塞服务器的问题;
SCAN命令是一个基于游标的迭代器。这意味着命令每次被调用都需要使用上一次这个调用返回的游标作为该次调用的游标参数,以此来延续之前的迭代过程;
当SCAN命令的游标参数(即cursor)被设置为0时,服务器将开始一次新的迭代,当服务器向用户返回值为0的游标时,表示迭代已结束;
SCAN增量式迭代命令并不保证每次执行都返回某个给定数量的元素,甚至可能会返回零个元素,但只要命令返回的游标不是0,应用程序就不应该将迭代视为结束;
命令返回的元素数量总是符合一定规则的,对于一个大数据集来说,增量式迭代命令每次最多可能会返回数十个元素;而对于一个足够小的数据集来说,可能会一次迭代返回所有的key。
lettuce介绍:
lettuce是一个可扩展且线程安全的redis客户端,它提供了同步(synchronous)、异步(asynchronous)和反应式reactive的API。
为什么使用lettuce:
最初是因为jedis在mset时只支持将同一槽位中的key放在一起设置,所以选择了lettuce;
Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池,为每个redis实例增加物理连接;
Lettuce是一种可伸缩,线程安全,完全非阻塞的Redis客户端,多个线程可以共享一个RedisConnection,它利用Netty NIO框架来高效地管理多个连接,从而提供了异步和同步数据访问方式,用于构建非阻塞的反应性应用程序;
Spring Boot1.5.x默认使用的是Jedis,Spring Boot2.0.x默认使用的是
如何使用:
连接redis单机、哨兵或者集群需要详细的连接说明,我们统一使用 RedisURI 。你可以在 RedisURI 中提供database,password,和timeout。可以通过如下的方式创建 RedisURI:
1.使用一个URI:
RedisURI.create("redis://localhost/");
2.使用Builder:
RedisURI.Builder.redis("localhost", 6379).auth("password").database(1).build();
3.直接在 RedisURI 中设置值:
new RedisURI("localhost", 6379, 60, TimeUnit.SECONDS);
基础实例:
单台redis机器访问连接:
RedisClient client = RedisClient.create("redis://localhost");(1)
StatefulRedisConnection<String, String> connection = client.connect();(2)
RedisCommands<String, String> commands = connection.sync();(3)
String value = commands.get("foo");(4)
...
connection.close();(5)
client.shutdown();(6)
1.创建redis客户端实例,并产生一个指向localhost的redis URI,端口默认是6379
2.开启一个redis的单连接,终端使用的是上面初始化好的redis客户端
3.获取可以异步执行的command API. Lettuce supports asynchronous and reactive execution models, too.
4.使用GET命令获取key foo
...
5.当你完成工作时,关闭连接。通常连接都是long-lived的。
6.关闭客户端实例来释放线程和资源
访问redis集群执行命令:
...
clusterNodeList.add(RedisURI.craete("host", Integer.pareInt("port")));(1)
RedisClusterClient clusterClient = RedisClusterClient.create(clusterNodeList);(2)
StatefulRedisClusterConnection<String, String> connection = clusterClient.connect();(3)
RedisAdvancedClusterCommands<String, String> syncCommands = connection.sync();(4)
syncCommands.mset(map);(5)
...
connection.close();(6)
clusterClient.shutdown();
1.将集群中的各节点添加到列表中
2.以集群列表创建redis集群客户端
......
——>Lambda表达式与匿名内部类:
Lambda表达式是Java8的重要更新,Lambda表达式支持将代码块作为参数,Lambda表达式允许使用更简洁的代码来创建只有一个抽象方法的接口(这种接口被称为函数式接口)的实例。
Lambda表达式完全可以用于简化创建匿名内部类对象,例子对比如下:
public interface MyInter {
public String getName();
}
public class AnonymousTest {
public void test(MyInter myInter) {
sout....
}
public static void main(String args[]) {
//anonyous inner class
AnonymousTest anonymousTest;
anonymousTest.test(new MyInteger() {
public String getName() {
.....
}
});
//lambda expression
anonymousTest.test(() -> new String("..."));
}
}
从上面的程序之中可以看出,这段代码中的Lambda表达式所实现的test方法和匿名内部类所实现的test方法是完全等价的,只是不再需要一个繁琐的代码块重新声明一个匿名类,不需要重新指出所重写的方法的名字,也不需要给出重写的方法的返回值类型。
Lambda表达式的语法主要由三部分构成:
1.形参列表,如果只有一个参数可以省略括号,当无参数类型时可以使用()或者obj来代替;
2.箭头 (->)
3.代码块部分,如果代码只有一行则可以省略括号,不然使用括号将lambda表达式的代码部分标记出来。