近月学习记录(一)

最近换了一个公司,确切来说应该是进入了一个新的行业,很多东西的学习都是从0开始的,学的东西很多很杂,先分点记下,以便以后复习回忆或者近一步学习

[b]1.key-value数据库[/b]
像redis,memcached都是这样的数据库,网上有一篇小日本的文章,写得很详细很好,虽然是小日本的,但也只能将就一下了
[url="http://tech.idv2.com/2008/07/11/memcached-002/"]memcached全面剖析系列[/url]
再说一下学习中了解到它的一些特点(可能有些不准确的地方):
1.它像一个大map,所有的内容都是键值对
2.它的所有内容都是放内存中的,但可以用其他的辅助手段做持久化
3.因为是操作内存的,而且只有纯粹的操作,没有任何额外的过程(像sql的编译),所以它很快,快得难以想象,但这个快是以牺牲很多其他的东西为代价的
4.它难以维护,它没有图形化操作工具,这也很容易理解,像我们想去像维护sql数据库一样维护一个map,你能有什么好办法吗?
5.没有数据完整性之类的概念,所有逻辑上的关联都是人为地去维护的,所以让它更进一步难以维护。
6.因为它的所有数据都放在内存中的,所以数据库服务器崩溃对数据的影响几乎是致命的(即使你使用了一些持久化的办法)
7.它也算个新生的东西,所以很多东西很不完善,像memcached的协议还是只有文本形式的,二进制协议正在制定中。
8.memcached集群默认中的一致性hash的用的是一个圆圈形的连续的hash值,服务器对应的hash值一般是不连续的,动态增减服务器会变得很方便。具体可以看小日本的文章
9.数据库服务器的主要硬件消耗是内存,而非cpu,小日本的文章也说了,cpu使用率几乎为0
10.它与sql数据库的区别也是很明显的,它更注重的是简单而高效。

近日用这种数据库进行了一些开发,也获得了一些经验和收获
1.虽然数据库没有提供主键外键这样一些的关联,但事实上是需要的,这时我们会人为的去维护这样一个关系,如存入一个对象,对象中带着一个list,这个list中可以索引到另外的一些key值,我们人为地使用一些原子操作去维护他们之前的关系(可以看到它的不稳健性),实质上是将数据库的一些工作由我们人为地去维护了。
2.在分布式的架构中,对整个应用的同步操作可以通过数据库来做,在思考模式上,与传统的单机上的并发是完全不一样的。
举个例子:

for(;;)
{
int expect = atomicInteger.get();
if(atomicInteger.cas(expect,expect+1))
break;
}

像我们经常使用的一个cas操作,在分布式的同步中,是不推荐的,因为对于单机,get,cas都是对内存(或者说是对cpu)的一个操作,效率是很高效,但对于远程数据库而言,他就是2个远程访问了,上述的一个操作是会大大增加io的压力的(尤其是冲突域相对较大,对这个原子变量的修改频繁的情况更是明显)。
所以在分布式的同步中,能用append,add这样的原子操作会更好
另一点是精确模型和不精确模型(现在只是有一些感觉,其实理解还是不深刻,之后需要找点书来学习一下),举个例子,对于key-value数据库,我们存了一个整形列表,列表里面的内容是我们需要的一些数据,如果是需求精确模型的,我们需要用cas进行插入或者删除操作,如果是不精确模型,我们可能只是进行append,而不去计较是否重复等,某个时候做一个整体判断,然后set
如果不是需求很明确地要求精确,不精确模型会大大减轻数据库的压力(这也让我对之前学习并发的时候的一些想法发生了巨大的改变,因为在实际应用中,不是都必须要求绝对准确的,像incre,某些时候,我们不要求它一定会增加了1,甚至减少我们也是允许的)
3.memcached的cas用的是乐观锁,也就是通过一个版本号来进行的
4.锁操作可以通过一个add或者remove,cas来进行,但像锁的await和notify动作就无法实现了,只能通过客户端的轮训来做,这个锁的值如果是一个时间戳会有很多好处,如某个锁操作进行了,但没有释放服务器就崩溃了,我们这时可以判断这个时间戳到当前时间的时间差,如果远远超过了可能锁住的时间,我们就帮他解锁

[b]2.protobuf[/b]
公司用了protobuf,在日常工作中都有所接触,一直没有时间做一些深入一点的探究,今天忙中偷闲,深入理解了一下,以下是个人的一些的总结,如果有遗漏或者错误的地方,还请看到的tx斧正

2.1.protobuf的功能
protobuf是一个google开发的开源工具,作用正如主页上说的:serialize and retrieve structured data
说白了,其实它的功能跟java的序列化和反序列化是一样的,只不过更强大,更有效率
但是在日常使用中,还会遇到这样几个问题:
a.数据结构的增删改查对之前的数据的是否会有影响,也就是版本兼容性
b.数据的压缩比率
c.易用性
d.性能
下文主要就逐步围绕这几方面进行进一步的探究

2.2.怎么用protobuf?(简单提一下)
a.按照说明编写一个.proto文件
b.用google提供的编译工具进行编译
这样,一个专负责序列化和反序列化的类就生成了,然后你可以直接去调用这个类进行序列化和反序列化了

2.3.protobuf支持的数据类型和编码
[img]http://dl.iteye.com/upload/attachment/523885/d7f43e08-37c4-3c54-8df0-5a7532093b72.jpg[/img]
其实有用的也就4种,变长的,int32,int64和长度不限制“Length-delimited”
对于java来说,一般用的也就8种数据类型而已

下面开始对java的每种数据类型进行进一步的分析(为了方便懒得看google文档的tx,这里也稍微翻译一下)
稍微写了段测试代码

public class Student {

public static void main(String[] args) {
StudentProtoBuf.Student.Builder studentProtoBuf = StudentProtoBuf.Student.newBuilder();
//studentProtoBuf.setId(1111);
//studentProtoBuf.setName("test");
studentProtoBuf.setSex(true);
byte[] bytes = studentProtoBuf.build().toByteArray();
StringBuilder builder = new StringBuilder();
for(byte b : bytes) {
builder.append(parseToString(b));
builder.append(":");
}
System.out.println(builder.toString());
}

public static String parseToString(byte b) {
int result = b & 0xff;
String resutString = Integer.toHexString(result);
return resutString.length() == 1 ? "0" + resutString : resutString;
}


}

下面是proto文件的内容,上面的java代码引用的是编译后的代码

package tutorial;

option java_package = "com.hoolai.protobuf.test";
option java_outer_classname = "StudentProtoBuf";

message Student {
optional string name = 1;
optional int64 id = 2;
optional bool sex = 3;

}

根据文档所说的,varint的编码规则是(field_number << 3) | wire_type,
而value部分的编码则是用的是很多协议都一样的:首bit表示后面是否还有内容,后7位才是有效位
单单一个bool,输出结果是18:01,0001 1000 0000 0001,
field_number = 3,wire_type = 0,第一个byte就出来了,然后是第二个,没什么问题
一个long+一个bool,输出是10:d7:08:18:01
可以看到编码是相邻着的,没有任何的间隔,而根据协议,在解码时也可以很简单地还原为各数据类型的有效数据(即使没有.proto编译出来的文件,也就是说使用抓包工具进行直接报文分析是可以的,如果有时间,再练练手去,但到底有没有价值呢?还待商榷)

然后进一步往后推,protobuf在做版本兼容的时候,会怎么做呢?
增:增加的时候,只有读之前的数据需要处理,如果我自己来实现,先是将整个报文进行分析,解码成具体类型的一个个值,还有他们的序号,然后对号入座,发现没有的,就给他一个默认值,很简单
删:这也很简单,也就是发现不需要的,直接丢弃就ok了,而之前的数据也不会占空间,因为当我们读取后再次存取的时候,这些数据也同时清空了,唯一只有个要求:该索引不能重用
改:改其实相当于增跟删的结合,举个例子:一个数据一开始是int,现在需要改成long,但数据需要保留,
很简单,在.proto中删除旧的int,加入新的long(注意,索引不能重用),读的时候还是按正常,当拿到解码后的数据,先看一下原索引的值是否为空,不为空,则读取并放入新的数据类型中,存的时候则按正常的逻辑就可以了,这样,.proto中的东西就可以删掉了

当然,以上的东西都是猜的,google应该也是这样做的吧?
在api中找到了这样的api:getUnknownFields().getField(4).getVarintList();
有点像了,再试试
看代码

byte[] result = {0x0a,0x04,0x74,0x65,0x73,0x74,0x10,(byte) 0xd7,0x08,0x18,0x01,0x20,0x01};//之前的代码System.out出来的,后面加了,0x20,0x01,意思是序号为第四的,用varint编码,并且值为1
com.hoolai.protobuf.test.StudentProtoBuf.Student student = StudentProtoBuf.Student.parseFrom(result);
student.getUnknownFields().getField(4).getVarintList();
System.out.println(student);

果然拿到了没定义的数据

根据编码规则,很容易看出该协议的压缩算法只是到byte级的,也没有long,int,short之类的概念,对于bool这样的,就有点浪费了(之后再看看package是怎么编码的)
而对于int,short之类的,压缩比率也不大(起码没预料中的高效,因为一直认为谷歌是神一样的存在),值在0-127之前的,才是2个byte(一个作为索引和类型,一个存储值),再往上加,就需要更多的空间了,除了头个byte之外,后面的byte利用率也只有7/8,跟之前练手做的snmp协议是一样的

接下来是package了,对单个参数无所谓,但对于数组这样的,总可以优化吧
继续测试

package tutorial;

option java_package = "com.hoolai.protobuf.test";
option java_outer_classname = "StudentProtoBuf";

message Student {
repeated bool sex = 1 [packed=true];
repeated int32 id = 2 [packed=true];
}


public static void main(String[] args) throws InvalidProtocolBufferException {
StudentProtoBuf.Student.Builder studentProtoBuf = StudentProtoBuf.Student.newBuilder();
studentProtoBuf.addSex(true);
studentProtoBuf.addSex(false);
studentProtoBuf.addSex(false);
studentProtoBuf.addSex(true);
byte[] bytes = studentProtoBuf.build().toByteArray();
StringBuilder builder = new StringBuilder();
for(byte b : bytes) {
builder.append(parseToString(b));
builder.append(":");
}
System.out.println(builder.toString());
}

public static String parseToString(byte b) {
int result = b & 0xff;
String resutString = Integer.toHexString(result);
return resutString.length() == 1 ? "0" + resutString : resutString;
}


很失望~真的没做压缩,只是按照文档所说的,当成了“Length-delimited”,然后再进行编码,结果如下:
0a:04:01:00:00:01

把package去掉看看?
08:01:08:00:08:00:08:01

完全按单个的形式来编码的~~


至此,protobuf已基本明晰了,包括一些细节,如
enum:按varint编码
repeat非package:当成“Length-delimited”,再按varint进行编码
只是repeat:按正常编码,然后用同一个索引

总的来说,对文档的解读算是全部明晰了,但对google的神话也有点失望

再看看易用性

protobuf生成的类其实就是一个DTO,因为它是由编译生成的,所以为了以后扩展方便,我们不能修改它,这样想使用它就有两种方式(如果你之前设计中没有DTO的概念),一种是继承它(缺点是代码后期会稍微有点乱),一种是做一次拷贝(条理较为清晰,但效率上显然会稍慢,毕竟多一次对象拷贝的过程)
如果本身就有DTO,那就简单了,只需要替换DTO就ok了


[b]3.java与c的对接[/b]
1.c的char是一个byte的,但java的是两个
2.c中对多个byte的数据类型的编码会根据环境的不同而不同,java则默认就是网络序,而网络序就是我们正常的读取习惯(这个序特别容易搞乱,尤其是自己code的时候,建议写单元测试用ByteBuffer进行验证一下)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值