目录
Redis简介(个人理解):
Redis持久化方案(两种):
RDB(快照)持久化:
快照保存过程
AOF(只追加)文件:
RedisCluster集群
Redis Cluster介绍:
RedisCluster失效检测:
slave rank(次序)
集群创建(windows环境):
Redis Sentinel(哨兵机制,提供高可用方案)介绍
Redis锁实现
减少内存方法
发布订阅与模拟
将java对象存储到redis数据库
不做任何商业用途,集合各种资源整理的入门知识点,仅分享
个人整理redis,文件下载地址:http://download.csdn.net/detail/qq_26881739/9737177
redis是一个key-value存储系统,相比传统的关系型数据库,拥有高性能的特点,源于redis大部分数据缓存在内存中,读写性能非常高。但数据安全性较关系型数据库低,在存储系统发生故障时会丢失一部分数据,且不可恢复。所以redis不能提供强一致性,只能保证最终一致性
Redis支持的数据类型(五种):String(字符串、整数、浮点数)、List(列表)、Set(无序集合,不可重复)、Hash(无序散列表)、Zset(有序集合)
主要运用场景:登录服务和cookies缓存、购物车、投票(社交点赞)、共享session
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置
save 900 1 #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000
ps:client 也可以使用save或者bgsave命令通知redis做一次快照持久化。save操作是在主线程中保存快照的,由于redis是用一个主线程来处理所有client的请求,这种方式会阻塞所有client请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步脏数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘io操作,可能会严重影响性能。
另外由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用aof持久化方式。
aof 比快照方式有更好的持久化性,是由于在使用aof持久化方式时,redis会将每一个收到的“写命令”都通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的“写命令”,在内存中重建整个数据库的内容。当然由于os会在内核中缓存 write做的修改,所以可能不是立即写到磁盘上。这样aof方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉redis我们想要通过fsync函数强制os写入到磁盘的时机。有三种方式如下(默认是:每秒fsync一次)
appendonly yes //启用aof持久化方式
# appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
appendfsync everysec //每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中,推荐
# appendfsync no //完全依赖os自主同步,性能最好,但持久化没保证
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。
例如:我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要恢复数据库的状态其实文件中保存一条set test 100就够了。为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照类似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件。具体过程如下
RedisCluster:用于实现redis服务端自动分片以及高可用、线性扩展方案
Redis Cluster只需要运行一个进程,不过需要两个端口,第一个用来服务client,第二个用于节点间的通讯(TCP协议),如错误监控,failover,重新分片等。
节点间通讯的端口=服务client的端口 + 10000
使用集群时redis会,将16384个Hash Slot(槽)自动分配到各个master,分配的方法采用CRC-16 hash方法,所有的master分担16384个固定slot的一部分。没有分配slot的master不能存数据,连接到这个master的client会重定向到其它master 。所有分配的slot的总和必须为16384 。slot的重新分布必须手工做。
Cluster中实现了一个称为“hash tags”的概念,每个key都可以包含一个自定义的“tags”,那么在存储时将根据tags计算此key应该分布在哪个nodes上(而不是使用key计算,但是存储层面仍然是key);此特性,可以强制某些keys被保存在同一个节点上,以便于进行“multikey”操作,比如“foo”和“{foo}.student”将会被保存在同一个node上。Cluster环境下,将不支持SELECT命令,所有的key都将保存在默认的database中。
HASH_SLOT= CRC16(key) mod 16384
RedisCluster需要至少3个master,所有的数据在各个master间分片,并复制到slave。建议每一个master都配一个slave,否则发生故障的节点数据会丢失。
客户端可以连接集群中的任意节点执行查询(包括replica),但是这个节点未必有需要的数据,因此客户端需要负责去定位key所在的节点,然后再重定向到正确的节点。而这可以做到的原因,是因为通过hash计算可以得到slot,而每个实例的slot是固定的。
当master处于FAIL状态时,将会触发slave的选举。slaves都希望将自己提升为master,此master的所有slaves都可以开启选举,不过最终只有一个slave获胜。当如下情况满足时,slave将会开始选举:
为了选举,第一步,就是slave自增它的currentEpoch值,然后向其他masters请求投票(需求支持,votes)。slave通过向其他masters传播“FAILOVER_AUTH_REQUEST”数据包,然后最长等待2倍的NODE_TIMEOUT时间,来接收反馈。一旦一个master向此slave投票,将会响应“FAILOVER_AUTH_ACK”,此后在2 * NODE_TIMOUT时间内,它将不会向同一个master的slaves投票;虽然这对保证安全上没有必要,但对避免多个slaves同时选举时是有帮助的。slave将会丢弃那些epoch值小于自己的currentEpoch的AUTH_ACK反馈,即不会对上一次选举的投票计数(只对当前轮次的投票计数)。一旦此slave获取了大多数master的ACKs,它将在此次选举中获胜;否则如果大多数master不可达(在2 * NODE_TIMEOUT)或者投票额不足,那么它的选举将会被中断,那么其他的slave将会继续尝试。
当master处于FAIL状态时,slave将会随机等待一段时间,然后才尝试选举,等待的时间:
DELAY = 500ms +random(0 ~ 500ms) + SLAVE_RANK * 1000ms
一定的延迟确保我们等待FAIL状态在集群中传播,否则slave立即尝试选举(不进行等待的话),不过此时其他masters或许尚未意识到FAIL状态,可能会拒绝投票。延迟的时间是随机的,这用来“去同步”(desynchronize),避免slaves同时开始选举。SLAVE_RANK表示此slave已经从master复制数据的总量的rank。当master失效时,slaves之间交换消息以尽可能的构建rank,持有replication offset最新的rank为0,第二最新的为1,依次轮推。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。当然rank顺序也不是严格执行的,如果一个持有较小rank的slave选举失败,其他slaves将会稍后继续。
一旦,slave选举成功,它将获取一个新的、唯一的、自增的configEpoch值,此值比集群中任何masters持有的都要大,它开始宣称自己是master,并通过ping、pong数据包传播,并提供自己的新的configEpoch以及持有的slots列表。为了加快其他nodes的重新配置,pong数据包将会在集群中广播。当前node不可达的那些节点,它们可以从其他节点的ping或者pong中获知信息(gossip),并重新配置。
其他节点也会检测到这个新的master和旧master持有相同的slots,且持有更高的configEpoch,此时也会更新自己的配置(epoch,以及master);旧master的slaves不仅仅更新配置信息,也会重新配置并与新的master跟进(slave of)。
Redis Cluster节点down掉重启(自己写的,没有测试性能与稳定性):使用Java代码读取节点记录文件,与通过jedis获取的nodesMap进行对比,发现有节点失效,调用bat脚本进行重启,每两秒(redis的failover机制,在节点down掉之后并不能及时发现节点失效,时间过短会导致资源浪费)进行一次检测检测(在执行cmd脚本重启服务时无法得到返回的流,改用等待两秒让脚本进行执行,不然脚本会失效或者只能执行一次便死锁)
节点监控代码:
packagecom.book.controller;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileInputStream;
import java.io.IOException;
importjava.io.InputStreamReader;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.Map;
importorg.apache.commons.pool2.impl.GenericObjectPoolConfig;
importredis.clients.jedis.HostAndPort;
importredis.clients.jedis.JedisCluster;
importredis.clients.jedis.JedisPool;
public classTestMain {
publicstatic void main(String[] args) {
Set nodes =newHashSet<>();
HostAndPortnode1 =newHostAndPort("127.0.0.1", 7001);
HostAndPortnode2= newHostAndPort("127.0.0.1", 7002);
HostAndPortnode3= newHostAndPort("127.0.0.1", 7003);
HostAndPortnode4= newHostAndPort("127.0.0.1", 7004);
HostAndPortnode5= newHostAndPort("127.0.0.1", 7005);
HostAndPortnode6= newHostAndPort("127.0.0.1", 7006);
nodes.add(node1);
nodes.add(node2);
nodes.add(node3);
nodes.add(node4);
nodes.add(node5);
nodes.add(node6);
GenericObjectPoolConfigpoolConfig = new GenericObjectPoolConfig();
Runtimeruntime = Runtime.getRuntime();
finalString cmd = "cmd /c start D:/redis/";
Listlist = new ArrayList<>();
try{
Stringencoding = "GBK";
Filefile = new File("D://redis//redisCluster.txt");
if(file.isFile() && file.exists()) { // 判断文件是否存在
InputStreamReaderread = new InputStreamReader(new FileInputStream(file),encoding);// 考虑到编码格式
BufferedReaderbufferedReader = new BufferedReader(read);
StringlineTxt = null;
while((lineTxt = bufferedReader.readLine()) !=null) {
list.add(lineTxt);
System.out.println(lineTxt);
}
read.close();
newThread(new Runnable() {
@Override
publicvoid run() {
inti = 0;
while(true) {
JedisClusterjedisCluster =new JedisCluster(nodes, 10, 10, 10, "123456",poolConfig);
Map nodes = jedisCluster.getClusterNodes();
System.out.println(nodes.toString());
if(nodes.size() < list.size()) {
for(String port : list) {
if(!nodes.containsKey(port)) {
String[]a = port.split(":");
Stringportcmd = cmd + a[1] + "/start.bat";
Processprocess;
try{
System.out.println("执行cmd");
process= runtime.exec(portcmd);
/*BufferedReaderbr = new BufferedReader(
newInputStreamReader(process.getInputStream()));
Stringline = null;
while((line = br.readLine()) != null) {
System.out.println(line);
}*/
Thread.sleep(2000);
if(null != process) {
process.destroy();
process=null;
}
}catch (Exception e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
try{
jedisCluster.close();
Thread.sleep(2000);
System.out.println("集群检测完毕"+ (i++));
}catch (Exception e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
}else {
System.out.println("找不到指定的文件");
}
}catch (Exception e) {
System.out.println("读取文件内容出错");
e.printStackTrace();
}
}
}
从官网下载3.0版本之后的redis,cluster是redis3.0的新特性,之前的版本不支持
解压编译,下载下来的一般都是C源码,需要编译为可执行的exe文件(或直接下载exe文件)
创建建立集群所需要的文件目录并复制文件到各个文件夹
修改每个redis实例配置文件*.conf文件
例:
Port 7000
daemonizeyes
cluster-enabledyes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
5、启动实例
6、安装ruby、安装ruby环境(windows下为RubyDevkit)、安装gem、使用gem安装redis的ruby接口(redis官方客户端使用ruby语言编写)
示例代码:gem install redis
7、执行redis的创建集群的命令
示例代码:redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004127.0.0.1:7005 (replicas参数指定之后的1是为每个主节点创建一个从节点,本实例中前三个为主节点,后三个为从节点)
8、使用redis客户端连接集群:redis-cli-c -p 7000(登录任意的redis实例,其他的实例有redis自动寻找)
9、可以对集群添加或删除节点
Sentinel是一个运行的redis实例。通过订阅与发布实现对master与从节点的监控。sentinel以每10秒一次的频率向master发送info命令,通过info的回复来分析master信息,master的回复主要包含了两部分信息,一部分是master自身的信息,一部分是master所有的slave(从)的信息,所以sentinel可以自动发现master的从服务。sentinel从master那儿获取到的master自身信息以及master所有的从信息,将会更新到sentinel的sentinelState中及masters(sentinelRedisInstance结构)中的slaves字典中。
当sentinel发现master有新的从服务时,不但为从服务创建相信的实例结构,而且还会创建连接到该从服务的命令连接和订阅连接,创建命令连接后,sentinel会10秒每次的向从服务发送info命令,并从回复信息中提取从服务ID、从服务角色、从服务所属的主服务的ip及端口、主从服务的连接状态、从服务的优先级、从服务的复制偏移量等信息;创建或者更新到从服务的sentinelRedisInstance结构。
sentinel会以每两秒一次的频率向所有的被监视服务器(master和从服务)发送询问命令,命令格式如下:
publish ___sentinel___:hello s_ip s_port s_runid s_epoch m_namem_ip m_port m_epoch
各个参数的解析如下
s_ip:sentinel的ip
s_port:sentinel的端口
s_runid:sentinel运行id
s_epoch:sentinel当前的配置纪元
m_name:主服务器名字
m_ip:主服务器ip
m_port:主服务器端口
m_epoch:主服务器纪元
sentinel与被监视的服务之间,一方面,sentinel通过命令链接发送信息到频道,另一方面,通过订阅连接从频道中接收信息。对于同一服务的多个sentinel,一个sentinel发送的信息,会被其他sentinel收到,用于更新对该sentinel以及被监视服务的认知,用于更新sentinelRedisInstance的sentinels字典信息(请看sentinelRedisInstance的数据结构)及master信息。当sentinel通过频道发现新的sentinel时,不但会更新sentinel字典,同时会与新的sentinel建立命令连接(不是建立订阅连接,因为sentinel与master及从建立订阅连接,是用来发现新的sentinel,而sentinel之间是已知的,所以不需要订阅连接),最终,监视同一个服务的多个sentinel会互联形成一个网络
sentinel会以每秒一次的频率向所有与其建立了命令连接的实例(master,从服务,其他sentinel)发ping命令,通过判断ping回复是有效回复,还是无效回复来判断实例是否在线(对该sentinel来说是“主观在线”,即单个sentinel认为实例在线,否则为主观下线)。
当sentinel监视的某个服务主观下线后,sentinel会询问其它监视该服务的sentinel,看它们是否也认为该服务主观下线,接收到足够数量(这个值可以配置)的sentinel判断为主观下线,既任务该服务客观下线,并对其做故障转移操作
询问命令:sentinel通过发送 SENTINEL is-master-down-by-addrip port current_epoch runid,(ip:主观下线的服务id,port:主观下线的服务端口,current_epoch:sentinel的纪元,runid:*表示检测服务下线状态,如果是sentinel 运行id,表示用来选举领头sentinel)来询问其它sentinel是否同意服务下线。
一个sentinel接收另一个sentinel发来的is-master-down-by-addr后,提取参数,根据ip和端口,检测该服务时候在该sentinel主观下线,并且回复is-master-down-by-addr,回复包含三个参数:down_state(1表示已下线,0表示未下线),leader_runid(领头sentinal id),leader_epoch(领头sentinel纪元)。
sentinel接收到回复后,根据配置设置的下线最小数量,达到这个值,既认为该服务客观下线
选举领头sentinel过程:
一个redis服务被判断为客观下线时,多个监视该服务的sentinel协商,选举一个领头sentinel,对该redis服务进行古战转移操作。选举领头sentinel遵循以下规则:
所有的sentinel都有公平被选举成领头的资格
所有的sentinel都有且只有一次将某个sentinel选举成领头的机会(在一轮选举中),一旦选举某个sentinel为领头,不能更改
sentinel设置领头sentinel是先到先得,一旦当前sentinel设置了领头sentinel,以后要求设置sentinel为领头请求都会被拒绝
每个发现服务客观下线的sentinel,都会要求其他sentinel将自己设置成领头
当一个sentinel(源sentinel)向另一个sentinel(目sentinel)发送is-master-down-by-addr ip portcurrent_epoch runid命令的时候,runid参数不是*,而是sentinel运行id,就表示源sentinel要求目标sentinel选举其为领头
源sentinel会检查目标sentinel对其要求设置成领头的回复,如果回复的leader_runid和leader_epoch为源sentinel,表示目标sentinel同意将源sentinel设置成领头
如果某个sentinel被半数以上的sentinel设置成领头,那么该sentinel既为领头
如果在限定时间内,没有选举出领头sentinel,暂定一段时间,再选举
进行故障转移
sentinel状态数据结构中保存了主服务的所有从服务信息,领头sentinel按照如下的规则从从服务列表中挑选出新的主服务
删除列表中处于下线状态的从服务
删除最近5秒没有回复过领头sentinelinfo信息的从服务
删除与已下线的主服务断开连接时间超过 down-after-milliseconds*10毫秒的从服务,这样就能保留从的数据比较新(没有过早的与主断开连接)
领头sentinel从剩下的从列表中选择优先级高的,如果优先级一样,选择偏移量最大的(偏移量大说明复制的数据比较新),如果偏移量一样,选择运行id最小的从服务
挑选出新的主服务之后,领头sentinel 向原主服务的从服务发送 slaveof 新主服务 的命令,复制新master。同理,当已下线的服务重新上线时,sentinel会向其发送slaveof命令,让其成为新主节点的从节点
Cluster和 Sentinel区别
Sentinel的目标是提供可靠的master/slave自动切换,无需sharding数据;而Cluster的目标是在多个实例分布数据,并在master发生故障时自动切换。
和Sentinel不一样,当failover发生时,只有失效的master上的key不可用,直到它的slave被升级为master。
客户端分片
将数据按照一定的策略"分散"存储在集群中不同的物理server上,本质上实现了"大数据"分布式存储,以及体现了"集群"的高可用性.比如1亿数据,我们按照数据的hashcode散列存储在5个server上.补充: Jedis sharding模式下,如果某个server失效,客户端并不会删除此shard,所以如果访问此shard将会抛出异常。这是为了保持所有的客户端数据视图一致性。你可能希望动态的一致性hash拓扑结构(即如果某个shard失效,shard结构则重新调整,失效的shard上的数据则被hash到其他shard上),但是很遗憾,SharedJedis客户端无法支持,如果非要支持,则需要巨大的代码调整,而且还需要引入额外的拓扑自动发现机制。(参看:redis cluster架构,已提供此问题的完善解决方案)
实现方法有三种:1、直接使用ShardedJedis 2、ShardedJedisPool 3、ShardedJedisPipeline
客户端分片代码:
1) hashcode取值:源码来自redis.clients.util.Hashing,Jedis中默认的hash值计算采取了MD5作为辅助
public class SerializeUtils {
public ThreadLocal md5Holder = new ThreadLocal();
public static final Hashing MD5 = new Hashing() {
public long hash(String key) {
return hash(SafeEncoder.encode(key));
}
public long hash(byte[] key) {
try {
if (md5Holder.get() == null) {
md5Holder.set(MessageDigest.getInstance("MD5"));
}
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("++++ no md5 algorythmfound");
}
MessageDigest md5 = md5Holder.get();
md5.reset();
md5.update(key);
byte[] bKey = md5.digest();//获得MD5字节序列
//前四个字节作为计算参数,最终获得一个32位int值.
//此种计算方式,能够确保key的hash值更加“随即”/“离散”
//如果hash值过于密集,不利于一致性hash的实现(特别是有“虚拟节点”设计时)
long res = ((long) (bKey[3] & 0xFF) << 24)
| ((long) (bKey[2] & 0xFF) << 16)
| ((long) (bKey[1] & 0xFF) << 8) | (long) (bKey[0] & 0xFF);
return res;
}
};
}
2) node构建过程(redis.clients.util.Sharded):
private void initialize(List shards) {
nodes = new TreeMap();//虚拟节点,采取TreeMap存储:排序,二叉树
for (int i = 0; i != shards.size(); ++i) {
final S shardInfo = shards.get(i);
if (shardInfo.getName() == null)
//当没有设置“name”是,将“SHARD-NODE”作为“虚拟节点”hash值计算的参数
//"逻辑区间步长"为160,为什么呢??
//最终多个server的“虚拟节点”将会交错布局,不一定非常均匀。
{
for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);
}
} else {
for (int n = 0; n < 160 * shardInfo.getWeight(); n++) {
nodes.put(this.algo.hash(shardInfo.getName() + "*" + shardInfo.getWeight() + n), shardInfo);
}
}
resources.put(shardInfo, shardInfo.createResource());
}
}
3) node选择方式:
public RgetShard(String key) {
returnresources.get(getShardInfo(key));
}
//here:
public SgetShardInfo(byte[] key) {
//获取>=key的“虚拟节点”的列表
SortedMap tail = nodes.tailMap(algo.hash(key));
//如果不存在“虚拟节点”,则将返回首节点。
if (tail.size() == 0) {
returnnodes.get(nodes.firstKey());
}
//如果存在,则返回符合(>=key)条件的“虚拟节点”的第一个节点
return tail.get(tail.firstKey());
}
使用压缩列表:配置限制条件,超过限制条件的数据将会使用底层结构类型.
配置信息:
List-max-ziplist-entries512
List-max-ziplist-value64
hash-max-ziplist-entries512
hash-max-ziplist-value64
zset-max-ziplist-entries128
zset-max-ziplist-value64
超过配置信息则使用链表进行存储:
讲压缩列表的长度限制在500-2000个元素之内,并将每个元素的体积限制在128字节或以下,那么压缩的性能就会在合理范围之内。一般做法是将压缩列表限制在1024个元素之内,并且每个元素体积不超过64字节,对于大多数散列应用来说,可以兼顾低内存和高性能两个优点。设置的数值过大会影响性能。
使用Redis可以实现消息订阅与发布,使用有序集合可以实现群组聊天系统。但原生的发布与订阅在客户端不在线的时候,收不到消息,当客户端上线时,不会收到已经发送但未接受的消息。需要使用双集合来实现。
使用序列化和反序列化。Redis不支持直接将Java对象存储到数据库中,所以需要将java对象进行序列化得到字节数组,然后将字节数组存入到redis中,需要数据的时候就从redis数据库中取出字节数组,再经过反序列化将自己数组转换成对象使用
代码(使用JDK的SerializeUtils )
package com.tujia.common.unit;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializeUtils {
public static byte[] serialize(Object obj) {
byte[] bytes = null;
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
bytes = baos.toByteArray();
baos.close();
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
public static Object deSerialize(byte[] bytes) {
Object obj = null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
obj = ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
推荐使用Protobuf,性能比jdk自带的号,序列化后字节长度小
依赖:
com.dyuproject.protostuff
protostuff-core
1.0.8
com.dyuproject.protostuff
protostuff-runtime
1.0.8
package com.tujia.common.unit;
import io.protostuff.LinkedBuffer;
import io.protostuff.ProtobufIOUtil;
import io.protostuff.runtime.RuntimeSchema;
public class SerializeUtils {
public static byte[] serialize(T t, Class clazz) {
return ProtobufIOUtil
.toByteArray(t, RuntimeSchema.createFrom(clazz), LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
}
public static T decodeSerialize(byte[] data, Class clazz) {
RuntimeSchema runtimeSchema = RuntimeSchema.createFrom(clazz);
T t = runtimeSchema.newMessage();
ProtobufIOUtil.mergeFrom(data, t, runtimeSchema);
return t;
}
}