分布式对象

RBucket<Object>

通用对象桶都是以String的方式存入到redis中的。Redisson还提供了地理位置桶RGeo和位向量RBitSet用于位置空间的计算,我们可以使用RBucket来存放任意类型的对象。

简单操作

RedissonClient client = Redisson.create(config);
RBucket<Object> bucket = client.getBucket("city");
bucket.set("nanjing");
Object o = bucket.get();
System.out.println(o.getClass());
System.out.println(o);
class java.lang.String
nanjing
//可选编码类型
org.redisson.codec.JsonJacksonCodec Jackson JSON 编码 默认编码
org.redisson.codec.AvroJacksonCodec Avro 一个二进制的JSON编码
org.redisson.codec.SmileJacksonCodec Smile 另一个二进制的JSON编码
org.redisson.codec.CborJacksonCodec CBOR 又一个二进制的JSON编码
org.redisson.codec.MsgPackJacksonCodec MsgPack 再来一个二进制的JSON编码
org.redisson.codec.IonJacksonCodec Amazon Ion 亚马逊的Ion编码,格式与JSON类似
org.redisson.codec.KryoCodec Kryo 二进制对象序列化编码
org.redisson.codec.SerializationCodec JDK序列化编码
org.redisson.codec.FstCodec FST 10倍于JDK序列化性能而且100%兼容的编码
org.redisson.codec.LZ4Codec LZ4 压缩型序列化对象编码
org.redisson.codec.SnappyCodec Snappy 另一个压缩型序列化对象编码
org.redisson.client.codec.JsonJacksonMapCodec 基于Jackson的映射类使用的编码。可用于避免序列化类的信息,以及用于解决使用byte[]遇到的问题。
org.redisson.client.codec.StringCodec 纯字符串编码(无转换)
org.redisson.client.codec.LongCodec 纯整长型数字编码(无转换)
org.redisson.client.codec.ByteArrayCodec 字节数组编码
org.redisson.codec.CompositeCodec 用来组合多种不同编码在一起
RBucket<Object> bucket = client.getBucket("city", new StringCodec("utf-8"));
bucket.compareAndSet(new AnyObject(4), new AnyObject(5));
bucket.getAndSet(new AnyObject(6));

批量操作

RBuckets buckets = redisson.getBuckets();
List<RBucket<V>> foundBuckets = buckets.find("myBucket*");
Map<String, V> loadedBuckets = buckets.get("myBucket1", "myBucket2", "myBucket3");

异步操作

//异步,几乎所有的Redisson对象都实现了一个异步接口,异步接口提供的方法名称与其同步接口的方法名称相互匹配
RBucket<String> bucket2 = client.getBucket("name2");
bucket2.setAsync("赵云2").get();
bucket2.getAsync().thenAccept(System.out::println);

操作基础类型

String对象

RBucket<Object> bucket = client.getBucket("city");
City city = new City(); //对象必须实现序列化接口
city.name = "hangzhou";
city.province = "zhejiang";
bucket.set(city);
City c1 = (City)bucket.get();
System.out.println(c1.province);
//查看服务器上的数据类型
10.150.27.139:6380> get city
"\x00\x01\x04City\xfc\bhangzhou\xfc\bzhejiang\x00"
10.150.27.139:6380> type city
string

操作数字对象

在jdk8中,增加了LongAdder,该类在高并发的环境下性能更优于RAtomicLong,Redisson同样也有该类的实现RLongAdder count = client.getLongAdder("count");在java中并没有提供AtomicDouble,Redisson为我们提供了:RAtomicDouble d = client.getAtomicDouble("double");我们就可以使用该类存储或计算浮点数据。

//我们有时候需要一个全局的计数器,那么就可以使用原子长整型。
RedissonClient client = Redisson.create(config);
RAtomicLong count = client.getAtomicLong("count");
long l = count.incrementAndGet();
System.out.println(l);

操作集合类型

map

RedissonClient client = Redisson.create(config);
RMap<Object, Object> cities = client.getMap("cities");
City c1 = new City("南京", "江苏");
City c2 = new City("杭州", "浙江");
cities.put(1,c1);
cities.put(2,c2);
City c = (City)cities.get(2);
System.out.println(c.name +"-"+ c.province);
//服务器存储结构
10.150.27.139:6381> type cities
hash
10.150.27.139:6381> hgetall cities
1) "\xf7\x01"
2) "\x00\x01\x04City\xfc\x02\xffWS\xff\xacN\xfc\x02\xff_l\xff\xcf\x82\x00"
3) "\xf7\x02"
4) "\x00\x01\x04City\xfc\x02\xffmg\xff\xde]\xfc\x02\xffYm\xff_l\x00"

list

RedissonClient client = Redisson.create(config);
RList<String> list = client.getList("list",new StringCodec("utf-8"));
list.add("北京");
list.add("济南");

queue

RedissonClient client = Redisson.create(config);
RQueue<String> qq = client.getQueue("qq");
qq.add("12");
qq.offer("34");

set

public void set() {
RSet<String> set = client.getSet("set");
set.add("赵云");
set.add("张飞");
set.forEach(System.out::println);
}

stream

public void stream() throws Exception {
RBinaryStream stream = client.getBinaryStream("stream");
stream.set("赵云".getBytes());
OutputStream outputStream = stream.getOutputStream();
outputStream.write("张飞".getBytes());
InputStream inputStream = stream.getInputStream();
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int len;
while ((len = inputStream.read(b)) != -1) {
result.write(b, 0, len);
}
System.out.println(result.toString());
}

bitSet

也就是位图,由于可以用非常紧凑的格式来表示给定范围的连续数据而经常出现在各种算法设计中。基本原理是,用1位来表示一个数据是否出现过,0为没有出现过,1表示出现过。使用用的时候既可根据某一个是否为0表示此数是否出现过。一个1G的空间,有 8*1024*1024*1024=8.58*10^9bit,也就是可以表示85亿个不同的数。

RBitSet set = redisson.getBitSet("simpleBitset");
set.set(0, true);
set.set(1812, false);
set.clear(0);
set.addAsync("e");
set.xor("anotherBitset");

话题(订阅分发)

发布

//发布代码
RedissonClient client = Redisson.create(config);
RTopic topic = client.getTopic("anyTopic");
DemoMessage message = new DemoMessage();
message.setTitle("震惊,一女子深夜竟然做出这种事情!");
message.setArticle("阿巴阿巴阿巴");
topic.publish(message);

订阅

//订阅代码
RedissonClient client = Redisson.create(config);
RTopic topic = client.getTopic("anyTopic");
topic.addListenerAsync(DemoMessage.class, new MessageListener<DemoMessage>() {
@Override
public void onMessage(CharSequence channel, DemoMessage msg) {
System.out.println(msg.getTitle());
}
});

Map元素淘汰机制

元素淘汰功能(Eviction)

redis并没有实现对hash元素过期时间的设置。Redisson通过在初始化RedissonMapCache时,设置了一个EvictionScheduler,这个类通过netty的EventloopGroup线程池周期地向以redisson_map_cache_expired前缀名的频道发布消息。RedissonMapCache会订阅这个频道来处理消息。它一次可移除 100 条过期项。

任务的调度时间会根据上次任务中删除的过期项数量自动调整,时间在 1 秒到 2 个小时内。因此若清理任务每次删除了100项数据,它将每秒钟执行一次(最小的执行延迟)。但如果当前过期项数量比前一次少,则执行延迟将扩大为 1.5 倍。

//可以通过client.getKey() 来设定key的存活时间,另外可以使用RMapCache控制每一条数据的过期时间,
RedissonClient client = Redisson.create(config);
RMapCache<Object, Object> cities = client.getMapCache("cities", new StringCodec("utf-8"));
cities.put(1,new City("成都","四川"),60,TimeUnit.SECONDS);
cities.put(2,new City("深圳","广东"),30, TimeUnit.SECONDS);

本地缓存功能(Local Cache)

本地缓存机制,其中有一个参数LocalCachedMapOptions,这个参数可以自定义缓存的淘汰机制。EvictionPolicy可以选择使用LRU,LFU或者通过GC过程清除元素,SyncStrategy实现了本地缓存的同步机制。

//本地缓存的机制
RLocalCachedMap<Object, Object> cities = client.getLocalCachedMap("cities", LocalCachedMapOptions.defaults());
City c1 = new City("武汉", "湖北");
cities.put(1,c1);
City c = (City)cities.get(1);
System.out.println(c.name+"-"+c.province);

分布式锁与同步器

Redisson的强大之处在于完美的实现了分布式锁和同步器,不需要我们再考虑怎么设计分布式锁的可重入?怎么保证分布式锁的公平性?如何实现一个分布式读写锁?怎么实现分布式的信号量和闭锁?

删除的时候肯定不是任何一个线程都可以删除,只有加锁的线程可以删除,这也就明白了上面用hash存储并且key设计为UUID:ThreadId的原因,是为了在删除的时候便于判断删除的线程是否是加锁时候的线程。

非公平锁

//Redisson默认的锁的过期时间为30s
@RestController
public class TestController {
@Autowired
private RedissonClient client;
@RequestMapping("/test")
public String test(){
RLock anyLock = client.getLock("anyLock");
anyLock.lock();
return "success";
}
}

上面的demo获取到一个lock不去释放。我们打开一个浏览器请求这个controller返回success后,再打开一个窗口重新请求,发现一直等待无法返回结果.

10.150.27.139:6380> hgetall anyLock

1) "c5745dc6-3105-4d60-9d5d-e39258714c31:38"

2) "1"

//可以尝试一定时间去获取锁,返回Boolean值
boolean b = lock1.tryLock(7, TimeUnit.SECONDS);
// 尝试获取锁7s, 最多占有锁2s,超过后自动释放,调用unlock可以提前释放。
boolean b = lock1.tryLock(7, 2, TimeUnit.SECONDS);

公平锁

//默认非公平锁,下面方式获取到的是非公平锁
RLock lock1 = redissonClient.getLock(KEY_LOCKED);
//公平锁
RLock lock1 = redissonClient.getFairLock(KEY_LOCKED);

读写锁

log.info(Thread.currentThread().getName() + " \t 运行");
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(KEY_LOCKED);
readWriteLock.readLock().lock();
log.info(Thread.currentThread().getName() + " \t 获取读锁");
try {
// 模拟处理逻辑用时5s
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
readWriteLock.readLock().unlock();
log.info(Thread.currentThread().getName() + " \t 释放读锁");
readWriteLock.writeLock().lock();
log.info(Thread.currentThread().getName() + " \t 获取写锁");
try {
// 模拟处理逻辑用时5s
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
readWriteLock.writeLock().unlock();
log.info(Thread.currentThread().getName() + " \t 释放写锁");
}

框架级别应用

限流器

public void rateLimiter() throws InterruptedException {
RRateLimiter rateLimiter = client.getRateLimiter("rateLimiter");
//初始化 最大流速:每1秒钟产生5个令牌
rateLimiter.trySetRate(RateType.OVERALL, 5, 1, RateIntervalUnit.SECONDS);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
int i = 0;
@Override
public void run() {
while(true) {
rateLimiter.acquire(1);
System.out.println(Thread.currentThread() + "-" + System.currentTimeMillis() + "-" + i++);
}
}
}).start();
}
Thread.sleep(1000 * 5);
}