arthinking

ChinSyun Pang's blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

未命名

发表于 2016-10-15

http://www.zhihu.com/question/26010353/answer/34468073

不走索引的情况下,尽量把条件放到程序里面

未命名

发表于 2016-10-15

http://www.cnblogs.com/kunpengit/archive/2012/03/14/2395792.html

http://blog.163.com/qiangyongbin2000@126/blog/static/775178192010102432819113/

http://blog.csdn.net/seelye/article/details/40144817

未命名

发表于 2016-10-15

18 发布与订阅

频道的订阅与退订

SUBSCRIBE

1
2
3
struct redisServce {
dict *pubsub_channels; // key 频道,values 客户端链表
}

client-10086: SUBSCRIBE “news.sport” “news.movie”

UNSUBSCRIBE

client-10086: UNSUBSCRIBE “news.sport” “news.movie”

模式的订阅与退订

1
2
3
struct redisServer{
list *pubsub_patterns; // pattern: 被订阅的模式 client: 订阅模式的客户
}

PSUBSCRIBE

client-9: PSUBSCRIBE “news.*”

PUNSUBSCRIBE

发送消息

将消息发送给频道订阅者

将消息发送给模式订阅者

查看订阅信息

  • PUBSUB CHANNELS [pattern]
  • PUBSUB NUMSUM [channel-1 channel-2 … channel-n]
  • PUBSUB NUMPAT

  • Redis订阅信息丢失问题

Redis实现简单消息队列

Jedis 与 ShardedJedis 设计

征服 Redis + Jedis + Spring (三)—— 列表操作

使用Spring Data Redis操作Redis(一)

19 事务

通过MULTI、EXEC、WATCH等命令来实现事务功能。

实现

WATCH命令的实现

事务的ACID性质

未命名

发表于 2016-10-15

并发运行共享内存地址,需要对共享变量进行协同。

dobbo序列化方式与版本升级的问题

发表于 2016-10-15   |   分类于 Dubbo

1、使用kryo序列化

1.1、先升级消费者

dubbo服务接口新增返回值和方法,消费者先升级,调用服务报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Caused by: com.alibaba.dubbo.remoting.RemotingException: Fail to decode request due to: RpcInvocation [methodName=dataGrid, parameterTypes=[class com.xxx.ProductInfoDTO, null], arguments=null, attachments={dubbo=2.8.4, input=463, path=com.xxx.product.ProductService, version=0.0.0}]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:190) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:110) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:84) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:96) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:144) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:53) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77) ~[dubbo-2.8.4.jar:2.8.4]
... 53 common frames omitted

1.2、先升级生产者

dubbo服务接口新增返回值和方法,生产者先升级,消费者调用服务报错:

消费者报错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Caused by: com.alibaba.dubbo.remoting.RemotingException: Fail to decode request due to: RpcInvocation [methodName=dataGrid, parameterTypes=[class com.xxx.ProductInfoDTO, null], arguments=null, attachments={dubbo=2.8.4, input=462, path=com.xxx.ProductService, version=0.0.0}]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.returnFromResponse(DefaultFuture.java:190) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:110) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.remoting.exchange.support.DefaultFuture.get(DefaultFuture.java:84) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker.doInvoke(DubboInvoker.java:96) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.AbstractInvoker.invoke(AbstractInvoker.java:144) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:53) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:91) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) ~[dubbo-2.8.4.jar:2.8.4]
at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77) ~[dubbo-2.8.4.jar:2.8.4]
... 53 common frames omitted

生产者报错:

1
2016-10-15 14:54:35,787 [New I/O worker #12] WARN  in [com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:120)] -  [DUBBO] Decode argument failed: com.esotericsoftware.kryo.KryoException: Unable to find class: #path, dubbo version: 2.8.4, current host: 172.22.23.214
java.io.IOException: com.esotericsoftware.kryo.KryoException: Unable to find class: #path
        at com.alibaba.dubbo.common.serialize.support.kryo.KryoObjectInput.readObject(KryoObjectInput.java:127)
        at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:116)
        at com.alibaba.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode(DecodeableRpcInvocation.java:74)
        at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody(DubboCodec.java:138)
        at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:134)
        at com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec.decode(ExchangeCodec.java:95)
        at com.alibaba.dubbo.rpc.protocol.dubbo.DubboCountCodec.decode(DubboCountCodec.java:46)
        at com.alibaba.dubbo.remoting.transport.netty.NettyCodecAdapter$InternalDecoder.messageReceived(NettyCodecAdapter.java:134)
        at org.jboss.netty.channel.SimpleChannelUpstreamHandler.handleUpstream(SimpleChannelUpstreamHandler.java:70)
        at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
        at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)
        at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
        at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
        at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:88)
        at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:109)
        at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:312)
        at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:90)
        at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:178)
        at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
        at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
        at java.lang.Thread.run(Thread.java:745)
Caused by: com.esotericsoftware.kryo.KryoException: Unable to find class: #path
        at com.esotericsoftware.kryo.util.DefaultClassResolver.readName(DefaultClassResolver.java:138)
        at com.esotericsoftware.kryo.util.DefaultClassResolver.readClass(DefaultClassResolver.java:115)
        at com.esotericsoftware.kryo.Kryo.readClass(Kryo.java:641)
        at com.esotericsoftware.kryo.Kryo.readClassAndObject(Kryo.java:752)
        at com.alibaba.dubbo.common.serialize.support.kryo.KryoObjectInput.readObject(KryoObjectInput.java:125)
        ... 22 more
Caused by: java.lang.ClassNotFoundException: #path
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1387)
        at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1233)
        at java.lang.Class.forName0(Native Method)
        at java.lang.Class.forName(Class.java:274)
        at com.esotericsoftware.kryo.util.DefaultClassResolver.readName(DefaultClassResolver.java:136)
        ... 26 more

消费方也升级之后,则可以正常调用服务。

2、使用Hessian2序列化

2.1、先升级消费者

dubbo服务接口新增返回值和方法,消费者先升级,服务可以正常调用,读取接口新增的值也正常,返回为空。

2.2、先升级生产者

dubbo服务接口新增返回值和方法,生产者先升级,消费者调用服务正常。

3、序列化协议切换

测试发现,在版本一直的情况,生产者使用kryo,消费者使用hessian2,或者生产者使用hessian2,消费者使用kryo,都可以正常调同,所以在版本一致的情况可以和好对生产者和服务者的序列化协议进行切换。

4、关于序列化性能对比

总结

可以发现,使用kryo协议,无法保证接口返回值添加属性时升级过程中的兼容,而hessian则可以兼容。

Kryo平均响应时长,每秒事务数,带宽节省等相对来说,的确是比较出众,当Kryo在Dubbo中应用足够成熟之后,使用其作为序列化的确是一个不错的选择。

References

Dubbo中使用高效的Java序列化(Kryo和FST)

发表于:dobbo序列化方式与版本升级的问题

共享对象

发表于 2016-09-10   |   分类于 Java

编写正确的并发程序的关键在于对共享的、可变的状态进行访问管理。

内存可见性,你可以通过显示的同步,或者利用内置于类库中的同步机制,来保证对象的安全发布。

1、可见性

为了确保跨线程写入的内存可见性,你必须使用同步机制。

Java Thread类的 yield()、join()

指令重排会对多线程共享变量产生影响。

只要数据需要被跨线程共享,就进行恰当的同步。

1.1、过期的数据

多线程分别执行get set,而没有对变量进行同步,指令重排导致过期的数据,改为同步:

1.2、非原子的64位操作

没有声明为volatile的64位数值变量(double和long),在多线程中共享,也可能不是安全的,(JVM运行将64位的读或写划分为两个32位的操作)。

1.3、锁和可见性

锁不仅仅是关于同步与互斥的,也是关于内存可见的,为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。

1.4、Volatile变量

加锁可以保证可见性与原子性;volatile变量只能保证可见性。

使用volatile的条件:

  • 写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值
  • 变量不需要与其他的状态变量共同参与不变约束
  • 访问变量时,没有其他的原因需要加锁

2、发布和逸出

一个对象在尚未准备好时就将它发布,这种情况称作逸出。

一旦一个对象逸出,你就要假设存在其他的类或线程可能误用它,无论是出于恶意还是粗心。这是使用封装的强制原因:封装使得程序的正确性分析变得更可行,而且更不易偶然地破坏设计约束。

2.1、安全构建的实践

不要让this引用在构造期间逸出。

3、线程封闭

3.1、Ad-hoc线程限制

3.2、栈限制

3.3、ThreadLocal

4、不可变性

4.1、Final域

4.2、示例:使用volatile发布不可变对象

5、安全发布

5.1、不正确发布:当好对象变坏时

5.2、不可变对象与初始化安全性

为了保证对象状态有一个一致性视图,我们需要同步。

不可变对象可以在没有额外同步的情况下,安全地用于任意线程;甚至发布它们时亦不需要同步。

5.3、安全发布的模式

安全发布对象的条件:

  • 通过静态初始化器初始化对象的引用;
  • 将它的引用存储到volatile域或AtomicReference;
  • 将它的引用存储到正确创建的对象的final域中;
  • 或者将它的引用存储到由锁正确保护的域中。

5.4、高效不可变对象

5.5、可变对象

5.6、安全地共享对象

线程安全性

发表于 2016-09-10   |   分类于 Java

1、什么是线程安全性

从一开始就设计一个线程安全的类,比在以后再将这个类修改为线程安全的类要容易的多。

线程安全的类中封装了必要的同步机制,因此客户端无需进一步采取同步措施。

无状态对象一定是线程安全的(不包含任何域和其他类的引用,只有线程局部变量)

2、原子性

int类型也非原子,++count包含了读取,修改,写入的操作序列。

2.1、竞态条件

UnsafeCountingFactorizer

最常见的竞态条件类型是:先检查后执行操作,即通过一个可能失效的观测结果来决定下一步的动作。

2.2、示例:惰性初始化中的竞态条件

LazyInitRace

单利模式的懒惰模式如果没有加同步,多线程可能产生不同的对象实例

2.3、复合操作

要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,也就是让其变成原子操作(两个线程互斥)

在实际情况中,应该尽可能使用现有线程安全对象如AtomicLong来管理类的状态。

3、加锁机制

为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。

3.1、内置锁

synchronized块,是互斥锁(mutual exclusion lock,也称作mutex)。

3.2、重入(Reentrancy)

内置锁是可重入的,防止一下这种情况导致的死锁:

重入锁的请求是基于每个线程的,而不是每个调用;通过为每个锁关联一个请求计数和一个占有它的线程实现的。

4、用锁来保护状态

如果用同步来协调访问变量,每次访问变量时,都需要同步,每次访问变量都需要同一个锁。

5、活跃性与性能

序列化的相关问题

发表于 2016-08-17   |   分类于 Java

Kryo bug貌似比较多,Java数据类无法兼容老版本,在系统快速迭代的项目中,这是影响比较大的;
Hessian序列化字节量和耗时稍微高一点,但尚能接受。

序列化方多一个不用的属性,反序列化方少了这个属性,可以正常反序列化;

反序列化方多了一个属性,反序列化的时候会报错;

JDK从1.6升级到1.7引发的问题

发表于 2016-08-02   |   分类于 Java

handshake alert: unrecognized_name

1
2
3
4
javax.net.ssl.SSLProtocolException: handshake alert:  unrecognized_name
at sun.security.ssl.ClientHandshaker.handshakeAlert(ClientHandshaker.java:1288)
at sun.security.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1904)
at sun.security.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:1027)

Java 7引进了SNI(Server Name Indication 是为了解决一个服务器使用多个域名和证书的SSL/TLS扩展。既是,在连接到服务器建立SSL连接之前先发送要访问站点的域名(Hostname),这样服务器根据这个域名返回一个合适的证书。)的支持,默认是开启状态的,这个会导致在建立SSL连接握手的时候需要获取到访问连接的正确的虚拟域。要使用SNI,需要客户端和服务器端同时满足条件(客户端和服务器端都支持SNI,客户端发送了正确的域名,服务器端也做了相应的SNI配置)。

为了解决这个问题,可以手动的设置一下jsse.enableSNIExtension属性,暂时把SNI禁用掉,设置方法:

  • 方法一:启动参数中添加:-Djsse.enableSNIExtension=false
  • 方法二:System.setProperty(“jsse.enableSNIExtension”, “false”);

Xstream no-args constructor error

1
com.thoughtworks.xstream.converters.ConversionException: Cannot construct ClassXXX as it does not have a no-args constructor : Cannot construct java.util.RandomAccessSubList as it does not have a no-args constructor

升级JDK版本,导致xstream不兼容,解决方法:

  • 提供一个无参构造函数
  • xstream版本升级到1.4.4以上
  • ClassXXX实现序列化接口

2016-08-03 10:21:39.972 [schedulerFactoryBean_Worker-2] WARN com.yeepay.common.utils.CallbackUtils - connection error : java.lang.RuntimeException: Could not generate DH keypair
javax.net.ssl.SSLException: java.lang.RuntimeException: Could not generate DH keypair
at sun.security.ssl.Alerts.getSSLException(Alerts.java:208)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1904)
at sun.security.ssl.SSLSocketImpl.fatal(SSLSocketImpl.java:1862)

把低版本的bcprovexclude掉:

引入新的版本:

1
2
3
4
5
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15</artifactId>
<version>1.46</version>
</dependency>

References

SSL handshake alert: unrecognized_name error since upgrade to Java 1.7.0

nginx 同一个IP上配置多个HTTPS主机

Xstream no-args constructor error

快钱报错:javax.net.ssl.SSLProtocolException: handshake alert: unrecognized_name解决

关于MySQL的sharding

发表于 2016-07-31   |   分类于 NoSQL

垂直切分:按照业务进行切分

水平切分:对于大表,进行分表分库,把数据散落在不同的数据库里面,对于数据增长相近,在业务上也比较紧密的表,两个Shard可以放到同一个数据库节点上,散列取一样的模。

垂直或水平切分之后,原来的关联关系需要打断,重新逐个查询组装数据。如何路由到Shard节点去查询或者更新数据也是关键。

数据库Sharding的基本思想和切分策略

数据库分库分表(sharding)系列(一) 拆分实施策略和示例演示

Redis的数据结构相关问题

发表于 2016-07-31   |   分类于 NoSQL

1、简单动态字符串

1.1、SDS与C字符串的不同之处,为什么Redis要使用SDS,用在了什么地方?

不同之处:

  • C中的字符串获取字符串长度复杂度为O(N),SDS的复杂度为O(1);
  • SDS杜绝缓冲区溢出,修改SDS时,API会先检查SDS空间是否满足修改所需要求,不满足则扩充至需要大小;
  • SDS减少修改字符串时带来的内存重分配次数
    空间预分配和惰性空间释放两种优化策略:修改后SDS小于1m,则不够时分配多一倍空间,如果多余1m,不够时则分配多1m空间
  • 二进制安全:所有SDS API都会以处理二进制的方式来处理SDS存放在buf数组里的数据;
  • 兼容部分C字符串函数

用于:

  • 保存数据库中的字符串值
  • 用作缓冲区(AOF缓冲区)

1.2、常用的SDS API有哪些

2、链表

2.1、用于

列表键,发布与订阅,慢查询,监视器等功能也用到了链表;

2.2、结构

2.3、Redis链表特性

  • 双端
  • 无环
  • 带表头指针和表尾指针
  • 带链表长度计数器
  • 多态,可以存储不同类型的值

2.4、链表和链表节点的API

3、字典

3.1、用于?

Redis数据库,对数据库的增删改查操作也是构建在对字典的操作之上的。字典还是哈希键的底层实现之一

3.2、底层实现?

Redis使用Hash表作为底层实现

Hash表结构

值可以是一个指针,或者是一个unit64_t整数,或者int64_t整数

字典结构

ht[1]只会在对ht[0]进行rehash的时候使用。rehashidx记录rehash的进度。

3.3、哈希算法是怎样的?

当字典被用作数据库底层实现或者哈希键的底层实现时,Redis使用了MurmurHash2算法来计算键的哈希值;

3.4、如何解决键冲突?

程序总是将新节点添加到链表的表头未知

3.5、为什么要执行rehash,Redis对字典的哈希表执行rehash的过程是怎样的?

随着哈希表的键值对主键增多,为了让负载因子维持在一个合理的范围,当键值对太多或者太少时,需要对哈希表的大小进行相应的扩展或收缩,通过rehash(重新散列)操作来完成.

rehash过程:

  • 为ht[1]分配空间(扩展:大小为第一个大于等于ht[0].used*2的2的n次方,收缩:大小为第一个大于等于ht[0].used*2的n次方)
  • 将ht[0]中所有键值对rehash到ht[1]
  • 迁移完毕之后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表

3.6、哈希表的扩展与收缩条件是怎样的?

  • 如果服务器没有在执行BGSAVE或者BGREWRITEAOF命令,并且负载因子>=1,则执行扩展操作;
  • 如果服务器目前在执行BGSAVE或者BGREWRITEAOF命令,并且负载因子>=5,则执行扩展操作;
    执行这两个命令的过程中,Redis需要创建服务器进程的子进程,服务器会提高执行扩展操作所需的负载因子,避免在子进程期间进行哈希表扩展操作;
  • 当哈希因子<0.1,对哈希表执行收缩操作;

3.7、为什么要渐进式执行rehash?

为了避免rehash对服务器性能造成影响,服务器不是一次性将ht[0]全部hash到ht[1],而是分多次,渐进式地进行。在rehash期间,删除,查找,更新会在两个哈希表上进行,而新增则写入ht[1]。

4、跳跃表

跳跃表支持平均O(logN),最坏O(N)复杂度的节点查找,大部分情况下,跳跃表的效率可以和平衡树相媲美,而跳跃表的实现比平衡树更简单。

4.1、跳跃表用于?

Redis使用跳跃表作为有序集合键的底层实现之一;集群节点中用作内部数据结构。

从表头到目标节点,所累计的跨度代表了目标节点在跳跃表中的排位;

后退指针

分值和成员:跳跃表中所有节点都是按照分值从小到大排列的;成员是一个指针,指向一个SDS字符串。

5、整数集合

整数集合是集合键的底层实现之一(当一个集合只包含整数值的时候,并且元素数量不多时)。

各个项在数组中按值的大小从小到大有序地排列,并且不包含任何重复项;

contents数组保存的类型取决于encoding中的值,而不是int8_t;

5.1、什么时候需要升级整数集合

新元素的类型比现有的所有元素的类型都要长的时候会进行升级;每次添加新元素,都有可能导致升级,所以复杂度为O(N)。

5.2、升级的好处

C语言为了避免类型错误,通常不会把不同类型的值放到同一个数据结构里面,通过升级,可以灵活添加不同类型的整数,而不必担心出错;

在适当的是时候,才升级,节约了内存;

整数集合不支持降级操作;

6、压缩列表(ziplist)

6.1、什么时候会使用压缩列表

当一个列表只包含少量列表项,并且每个列表项要么是小整数,要么是长度比较短的字符串,Redis就会使用压缩列表来做列表键的底层实现。

压缩列表是Redis为了节约内存而开发的;

6.2、什么是连锁更新

provious_entry_length记录了压缩列表前一个节点的长度,这个属性为1个字节或者五个字节,如果前一个节点长度小于254字节,则这个属性为一个字节,否则这个属性为五个字节。

如果在一个节点都为小于254字节的压缩链表中间插入一个新的大于254字节的节点A,后一个节点B的previous_entry_length就会从一个属性变为5个属性,从而导致节点B也可能超过254字节…

7、对象

Redis没有使用上面的数据结构直接实现数据库,而是基于这些数据结构创建了一个对象系统,包括以下五种类型:

  • 字符串对象
  • 列表对象
  • 哈希对象
  • 集合对象
  • 有序集合对象

我们可以针对不同的使用场景,为对象设置多种不同的数据结构实现,从而优化对象在不同场景下的使用效率。

基于引用计数技术的内存回收机制,回收不再使用的对象,在适当条件下,让多个数据库键共享一个对象来节约内存。

创建对象例子

SET msg “hello world”

会分表为键和值创建redisObject对象:

type

type: REDIS_STRING, REDIS_LIST, REDIS_HASH, REDIS_SET, REDIS_ZSET

redis> TYPE msg
string

不同类型对象的创建

SET msg “hello world” #字符串对象 string
HMSET profile name Jason age 20 career Engineer # 哈希对象hash
SADD fruits apple banana cherry # 集合对象 set
ZADD price 8.5 apple 5.0 banana 6.0 cherry # 有序集合对象 zset

encoding

查看数据库键的值对象的编码:

redis> OBJECT ENCODING msg

7.1、字符串对象

7.1.1、编码问题

字符串对象REDIS_STRING的编码可以是int,raw或者embstr。

例子:

redis> SET number 10000
redis> OBJECT ENCODING number # int
redit> SET story “data structure algorithrm operation system …”
redis> OBJECT ENCODING story # raw 长度大于32个字节
redis> SET msg “hello”
redis> OBJECT ENCODING msg # embstr 长度小于等于32个字节
redis> SET pi 3.14
redis> OBJECT ENCODING pi # embstr

7.1.2、embstr编码的好处

  • 创建字符串所需的内存分配次数从raw编码的两次降为一次;
  • 释放embstr编码字符串只需要调用一次内存释放,raw需要两次
  • embstr编码字符串对象所有数据都保存在一块连续的内存,比raw更好的利用缓存带来的优势

embstr字符串不允许修改,在执行修改命令时会先转换为raw编码。

7.2、列表对象

编码:ziplist或者linkedlist

redis> RPUSH numbers 1 “a” 2

字符串对象是Redis五种类型的对象中唯一一种会被其他四中类型对象嵌套的对象。

7.2.1、列表对象的编码转换

使用ziplist编码的条件:

  • 列表对象保存的所有字符串元素的长度都小于64字节
  • 列表元素小于512个

以上上限可修改(list-max-ziplist-value list-max-ziplist-entries)

7.3、哈希对象

编码:ziplist或者hashtable

7.3.1、ziplist格式的编码

7.3.2、hashtable格式的编码

字典的键和值都是字符串对象

7.3.3、hash对象的编码转换

使用ziplist对象的条件:

  • 哈希对象保存的所有键值字符串长度都小于64字节;
  • 哈希对象保存的键值对数量小于512;

以上上限可修改(hash-max-ziplist-value hash-max-ziplist-entries)

7.4、集合对象

集合对象的编码:intset或者hashtable

7.4.1、intset

集合对象包含的所有元素都被保存在整数集合里面;

7.4.2、hashtable

hashtable使用字典作为底层实现,字典的值全部设置为NULL。

7.4.3、集合对象的编码转换

使用intset对象的条件:

  • 集合对象保存的所有元素都是整数值;
  • 元素个数不超过512。

上限可以修改(set-max-intset-entries)

7.4、有序集合对象

编码:ziplist或者skiplist

7.4.1、ziplist

每个集合使用紧挨着的两个压缩列表节点来表示,第一个节点保存元素成员,第二个元素保存元素的分值;按分值从小到大进行排列;

7.4.2、skiplist

skiplist编码的有序集合对象使用zset结构作为底层实现,一个zset同时包含一个字典和一个跳跃表:

1
2
3
4
typedef struct zset {
zskiplist *zsl;
dict *dict;
} zset;
为什么同事使用跳跃表和字典来实现有序集合

单独通过字典或者跳跃表来实现有序集合,性能都会有所下降。

字典和跳跃表会共享元素的成员和分值,所以并不会造成任何数据重复,也不会因此而浪费内存。

7.4.3、编码转换

使用ziplist的条件:

  • 有序集合的元素数量小于128个
  • 有序集合保存的所有元素成员长度都小于64字节

修改参数:zset-max-ziplist-entries,zset-max-ziplist-value。

7.5、类型检查与命令多态

Redis用于操作键的命令可以分为两种:

  • 可以对任何类型的键执行:DEL,EXPIRE,RENAME,TYPE,OBJECT
  • 只能对特定类型的键执行:

字符串对象:SET,GET,APPEND,STRLEN
哈希对象:HDEL,HSET,HGET,HLEN
列表对象:RPUSH,LPOP,LINSERT,LLEN
集合对象:SADD,SPOP,SINSERT,SCARD
有序集合对象:ZADD,ZCARD,ZRANK,ZSCORE

对于第二类命令,在执行之前,需要先执行类型检查,通过redisObject结构的type属性来实现的。

每一种类型的对象,底层的编码是不一样的,执行同样的LLEN命令,底层是ziplist还是linkedlist编码,需要确保命令都可以正常运行,这就是命令的多态(包括可以对任何类型的键执行的命令)。

7.6、内存回收

通过Redis系统内构建的引用计数器来实现内存回收

7.7、对象共享

被共享的的对象,引用计数器会对应+1。

Redis在启动的时候会创建一万个字符串对象,这些字符串对象包含了从0到9999的所有整数值,通过共享对象方式使用这些值,而不是新创建对象。

修改初始化的共享对象个数:redis.h/REDIS_SHARED_INTEGERS

查看引用计数:

redis> OBJECT REFCOUNT obj

为什么Redis不共享包含字符串的对象?

将一个共享对象设置为键的值对象时,程序会先进行检查校验想要创建的对象和目标对象是否完全一致:

  • 如果共享对象保存整数值,验证复杂度为O(1)
  • 如果共享对象是保存字符串值的字符串对象,验证复杂度为O(N)
  • 如果共享对象是包含了多个值的对象,那么验证操作的复杂度将会是O(N^2)

收到CPU时间的限制,Redis只对包含整数值的字符串进行共享。

7.8、对象的空转时长

redisObject对象的lru属性记录了对象最后一次被命令程序访问的时间。

如果服务器打开了maxmemory选项,并且服务器用户回收内存的算法为volatile-lru或者allkeys-lru,那么服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键就会优先被服务器释放。

References

本文来源于《Redis设计与实现》,笔记摘要与相关问题思考。

Redis数据库的实现

发表于 2016-07-31   |   分类于 NoSQL

1、数据库

1.1、服务器中的数据库

1
2
3
4
5
6
7
8
9
10
struct redisServer {
// ...
// 一个数组,保存着服务器中的所有数据库
redisDb *db;

// 服务器的数据库数量
int dbnum;

// ...
}

数据库的数量由dbnum属性决定,默认情况下,Redis会创建16个数据库。

1.2、切换数据库

默认情况下,redis客户端的目标数据库为0号数据库,可以通过SELECT命令来切换目标数据库。

1
2
3
4
5
6
typde struct redisClient {

// ...
// 记录当前客户端正在使用的数据库
redisDb *db
} redisClient;

redis-cli客户端会在输入符旁边提示当前所使用的数据库:
redis[1] > SELECT 2
但是其他语言客户端却没有,为了避免操作错误数据库,执行Redis命令(如FLUSHDB)之前,最好先执行以下SELECT命令。

1.3、数据库键空间

1
2
3
4
5
6
7
typedef struct redisDb {
// ...
// 数据库键空间,保存着数据库中的所有键值对
dict *dict;

// ..
}

数据库键操作

添加新键
redis> SET data “2016.07.31”
redis> RPUSH list “a” “b” “c”
redis> HSET hashTable name “test”

删除键
redis> DEL book

更新键
redis> SET data “2016.08.01”

对键取值
redis> GET str
redis> LRANGE list 0 -1

读写键空间时的维护操作

  • 记录命中和没命中次数(INFO stat的keyspace_hits和keyspace_missess属性);
  • 更新键的LRU时间(OBJECT idletime 查看闲置时间);
  • WATCH监视的键有改动,键会被标为脏,让事务程序注意到这个键已经被修改了;
  • 服务器每修改一个键之后,都会对脏键计数值加1,这个计数器会触发服务器的持久化和复制操作;
  • 数据库通知功能相关处理。

1.4、设置键的生存时间或过期时间

EXPIRE和PEXPIRE命令

redis> EXPIRE KEY 5 // 缓存5秒
redis> EXPIREAT key 1377252100

TTL和PTTL命令

返回距离这个键被服务器自动删除还有多长时间。

redisDb中的expires字典保存了所有键的过期时间。

PERSIST

在过期字典中查找给定的键,并解除键和值在过期字典的关联。

1.5、过期键删除策略

定时删除

会对服务器的响应时间和吞吐量造成影响。

惰性删除

如果一个过期的键一直没被访问到,那么永远也不会被删除。

定期删除

每个一段时间进行一次删除操作,限制删除操作执行的时长和频率减少删除操作对CPU时间的影响。

1.6、Redis的过期键删除策略

Redis使用惰性和定期删除两种策略。

1.7、AOF、RDB和复制功能对过期键的处理

执行SAVE或者BGSAVE命令创建一个新的RDB文件时,程序会对键进行检查,已过期的键不会被保存到新创建的RDB文件中。

载入的过程中,如果是主服务器,只会载入未过期的键;如果是从服务器模式,所有键都会被载入,不过在进行数据同步的时候,从服务器的数据库就会被清空,所以过期键对RDB文件的从服务器不会造成影响。

如果以AOF模式持久化数据,当过期键被惰性或者定期删除的时候,会向AOF文件追加一条DEL命令。

复制

主服务器删除一个过期键之后,会显示地向所有从服务器发送一个DEL命令。

从服务器存在过期的键,客户端从该从服务器继续获取该键,仍可以获取到;但是从主服务器获取,主服务器发现该键已过期,向客户端返回空回复,并向从服务器发送DEL message命令。

1.8、数据通知

键空间通知

获取0号数据库中针对message键执行的所有命令:
redis> SUBSCRIBE _ keyspace@0 :message
获取0号数据库中所有执行DEL命令的键:
redis> SUBSCRIBE
keyevent@0 _:del

服务器配置的notify-keyspace-events选项决定了服务器所发送通知的类型

发送通知

void notifyKeyspaceEvent(int type, char event robj key, int dbid)

程序会根据type这个值来判断通知是否就是服务器配置notify-keyspace-events选项所选定的通知类型,如果是。

例如:notifyKeyspaceEvent(REDIS_NOTIFY_SET, “sadd”, c->argv[1], c->db->id);

当SADD命令向集合成功的添加元素,命令就会发送。

发送通知的实现

2、RDB持久化

2.1、RDB文件的创建与载入

生成RDB文件的命令:

  • SAVE:会阻塞服务器进程,直到RDB文件创建完为止,阻塞期间,服务器不能处理任何请求命令;
  • BGSAVE:会派生子进程负责创建RDB文件

实际由rdb.c/rdbSave函数完成

RDB文件的载入是在服务器启动时自动执行的,期间服务器一直会处于阻塞状态。Redis没有专门用于载入RDB文件的命令。

如何选择载入方式?

如果开启了AOF持久化功能,服务器会优先使用AOF文件还原数据;
AOF持久化功能处于关闭的情况下,服务器使用RDB文件来还原数据库状态;
相关函数:rdb.c/rdbLoad;

SAVE,BGSAVE和BGREWRITEAOF有什么区别?

  • SAVE命令执行会阻塞服务器,客户端所有命令都会被拒绝;
  • BGSAVE命令有子进程执行,创建RDB文件过程中仍然可以继续处理客户端命令请求,但是此时,客户端发送的SAVE命令会被服务器拒绝,避免父进程和子进程同事执行两个rdbSave调用,防止产生竞争条件。同事,客户端再发送BGSAVE命令也会被拒绝。
  • BGREWRITEAOFhe BGSAVE命令不能同时执行,执行BGSAVE期间发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。执行BGREWRITEAOF期间,BGSAVE命令会被服务器拒绝。(BGSAVE和BGREWRITEAOF虽然都是在子进程里面执行的,不能同时执行是出于服务器性能考虑的)

2.2、自动间隔性保存

Redis运行用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令:

save 900 1
save 300 10
save 60 10000 # 服务器在60秒内,对数据库进行了至少10000次修改,则执行BGSAVE命令

2.3、RDB文件结构

固定REDIS开头,0006是版本号,最后是文件校验和。

key_value_pairs保存了数据库中所有键值对数据。

key_value_pairs具体是如何存储键值对的?

可分为不带过期和带过期时间的键值对:


其中type表示编码类型,key是一个字符串,value因type的不同结构也会不一样。

key_value_pairs中的value是如何选择编码的

字符串对象:如果是REDIS_ENCODING_RAW,又可根据长度是否大于20字节分为压缩和不压缩两种方式。(需要打开RDB文件压缩功能:redis.conf的rdbcompression),压缩格式如下:

列表对象:

REDIS_ENCODING_LINKEDLIST:

每一个item又是一个字符串对象。

集合对象:

REDIS_ENCODING_HT:

有序集合对象:

REDIS_ENCODING_SKIPLIST:

哈希表对象:

REDIS_ENCODING_HT:

ZIPLIT编码的列表、哈希表或者有序集合

将压缩列表转换成一个字符串对象,保存到RDB文件中。

2.4、分析RDB文件

可以通过od命令来分析RDB文件

redis> od -c dump.rdb

3、AOF持久化

AOF即:Append Only File。

RDB持久化与AOF持久化有什么不同?

RDB通过保存数据库中的键值对来记录数据库状态的不同,AOF持久化通过保存Redis服务器所执行的命令来记录数据库状态。

3.1、实现

AOF持久化实现分为:命令追加(追加到aof_buf),文件写入,文件同步。

Redis是如何实现AOF文件的写入和同步的?

Redis在一个事件循环里,每次都做判断,根据服务器的appendfsync配置选择对应的策略来进行AOF文件的写入和同步。

3.2、AOF文件的载入与数据还原

  • 创建fake client
  • 从AOF文件分析并取出一条写命令
  • 使用伪客户端执行被读出的命令
  • 循环上面三步

3.3、AOF重写

什么是AOF重写,为什么要执行AOF重写?

为了节省AOF文件的空间,去除冗余的命令。

通过调用aof_rewrite函数进行重写,在子进程中进行重写,重写过程新发送的命令按如下处理:

4、事件

Redis是一个事件驱动程序,服务器处理文件事件和时间时间两类事件。

4.1、文件事件

基于Reactor模式开发的网络事件处理器。使用IO多路复用同时监听多个套接字,根据套接字执行的任务为套接字关联不同的事件处理器。

IO多路复用程序所有功能都是通过封装常见的select、epoll、evport、kqueue这些I/O多路复用函数库来实现的。

常用的文件事件处理器?

连接应答处理器:对连接服务器监听套接字的客户端进行应答,Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,引发连接应答处理器执行,并执行相应的套接字应答操作。

命令请求处理器:从套接字中读入客户端发送的命令请求内容,当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AE_READABLE事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作。

命令回复处理器:当服务器有命令回复需要传送给客户端的时候,服务器会将客户端套接字的AE_WRITEABLE事件和命令回复处理器关联起来,当客户端准备好接受服务器传回的命令回复时,就会产生AE_WRITEABLE事件,引发命令回复处理器执行,并执行相应的套接字写入操作。

请简述下一次完整的客户端与服务器连接事件

4.2、时间事件

4.3、事件的调度与执行

5、客户端

5.1、客户端属性

5.2、客户端的创建与关闭

6、服务器

6.1、命令请求的执行过程

6.2、serverCron函数

6.3、初始化服务器

Redis的应用场景

发表于 2016-07-31   |   分类于 NoSQL

某些场合需要把全量关系写到内存中,不然还是会存储缓存找不到,再次去磁盘数据库查找的情况,这种时候干脆直接用Redis作为数据存储方案了。

Redis应用场景

Redis备份恢复与集群

发表于 2016-07-31   |   分类于 NoSQL

redis数据备份恢复与持久化

http://mp.weixin.qq.com/s?src=3&timestamp=1469956665&ver=1&signature=lZDPlIEB5W*RIq6fArCWqK2Z3GPpDzEhCZsoaUiVfGXQdA*TjgwGcPiUIroMhQzLf-wG5MYXdWKSHR07ezyoZo50huWfa-IUfSI8QcZ1RwuKWFMHdVdMze7lIvTO9bJugbqEKnQKc5-BHvBx3NFlYw==

高并发下的缓存与持久化选择

发表于 2016-07-31   |   分类于 NoSQL

先弄清楚两点:

  • 缓存中的数据,无论做怎么处理,都是有可能丢失的;
  • 写到磁盘中的数据,则是可以确定业务处理成功的;

类似微博,这种社交应用,数据是不要求100%不丢失的,所以可以很好的利用缓存来解决高并发的处理问题;
而如果涉及到资金交易,像金融系统或者是电商网站的核心交易模块,数据是不运行有误的,这部分数据,如果从缓存上来考虑兼容高并发,是无法做到安全的,退一步的做法,应该是使用数据库的Sharding方式和流量漏洞过滤的方式来应对高并发的场景。而非核心交易的模块,如购物车,还未确认的订单,可以考虑系统的对错误的容忍度恰当的放到缓存里面,然后通过一定的策略做其他的业务处理,如持久化数据做MapReduce和分析作业。

缓存的设计有哪些套路呢,酷客里面的缓存更新的套路说了常用的设计思路。单靠缓存来解决高并发的数据读写又想做数据的强一致性和可靠性,是没有这样的银弹的,这个时候需要转换一下思路。

http://mp.weixin.qq.com/s?src=3&timestamp=1469959496&ver=1&signature=JlKZ0BnHUKnaKGFog3Adu-C2fgC1OAuzs5VWFnwxXGevUAPQNhB1HcFMUgGVdCNcIHamUC8kFt5tbbvmTKiwicCia*sahDW60dIIekKlmFarhyBC8rG2tUDHNLnGtLep50XdUPgKir8-0XUbOjLdghV*0A0RkyTQnbVmidKJjf0=

http://mp.weixin.qq.com/s?src=3&timestamp=1469959496&ver=1&signature=4cN2zbsuTbHDkotZNk6LgGJ3gu7wYAv0j0wN7AM-rNeSa7NZ0OG6*h8mY-PWMMAGDQC4grxu2P20SvzTtlVP6i9lCqtZ1NGYC-qvCNR9YigT26XIRUCkhAfKJ*fkfJe0KfXNKvS22Ta2ZaVhEpuv86pIHbgqq2U3*nHaZz4XjvU=

RDB持久化异常

发表于 2016-07-18   |   分类于 NoSQL

MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error

redis.conf

RDB文件名,默认为dump.rdb。

dbfilename dump.rdb

文件存放的目录,AOF文件同样存放在此目录下。默认为当前工作目录。

dir ./

查看文件写入权限,以及磁盘是否已满。

info
stop-writes-on-bgsave-error yes

查看日志

Redis持久化

deploy不上maven私服

发表于 2016-07-18   |   分类于 dev-tools

nexus Return code is: 401, ReasonPhrase: Unauthorized.

查看是否配置了认证

1
2
3
4
5
6
7
<servers>
<server>  
<id>releases</id>  
<username>admin</username>  
<password>123456</password>  
</server>    
</servers>

高并发下的MySQL事务以及多系统事务协调

发表于 2016-07-01   |   分类于 Database

使用事务

一个数据库:

user_center

里面有三个表:

t_user:用户信息表

t_order:订单表

t_user_stat:用户数据统计表

现在要求:用户每创建一个订单,不管成功与否,都给用户奖励一个金币。

sql语句如下:

1
2
3
4
5
INSERT INTO `t_order` (`user_id`,`order_id`,`status`,`amount`)
VALUES
(1, "2016062701837x9d", 0, 10000);

UPDATE `t_user_stat` SET `gold` = `gold` + 1 WHERE `user_id` = 1;

为了让这条sql语句在一个事务里面执行,我们需要开启事务,在事务里面处理,结果就如下:

1
2
3
4
5
6
7
8
9
10
set autocommit=0;
START TRANSACTION;

INSERT INTO `t_order` (`user_id`,`order_id`,`status`,`amount`)
VALUES
(1, "2016062701837x9d", 0, 10000);

UPDATE `t_user_stat` SET `gold` = `gold` + 1 WHERE `user_id` = 1;
# ROLLBACK;
COMMIT;

Spring事务管理

以下是Spring的事务管理器的相关类:

事务的提交、回滚等操作是通过直接调用数据库连接Connection的提交和回滚方法实现的。

而Spring提供的HibernateTransactionManager,真正执行提交、回滚等事务操作的还是Hibernate Transaction事务对象,Spring将其做了通用封装,更加方便使用:

阅读延伸:

Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。

项目中的事务配置

目前,项目中集成了MyBatis和Hibernate。

1
2
3
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>

Mybatis和JdbcTemplate的事务也是受spring管控的,他们都是用的JDBC的事务,所以只有他们的数据源是一样的就可以让spring来管理事务。

如果直接在项目中使用DataSourceTransactionManager的话,对于Hibernate,会出现拿不到session的异常,对于同时集成MyBatis和Hibernate的项目,最佳实践还是使用HibernateTransactionManager。

问题:对于读写分离的系统,如何设置事务呢?

在应用层的解决方案,通过spring动态数据源和AOP来解决数据库的读写分离。

  • 方案1:当只有读操作的时候,直接操作读库(从库);当在写事务(即写主库)中读时,也是读主库(即参与到主库操作),这样的优势是可以防止写完后可能读不到刚才写的数据;
    此方案其实是使用事务传播行为为:SUPPORTS解决的。

  • 方案2:当只有读操作的时候,直接操作读库(从库)当在写事务(即写主库)中读时,强制走从库,即先暂停写事务,开启读(读从库),然后恢复写事务。
    此方案其实是使用事务传播行为为:NOT_SUPPORTS解决的。

两个线程并发执行事务会怎样呢

数据库隔离级别

时间 事务一 事务二
1 START TRANSACTION;
2 INSERT INTO t_order (user_id,order_id,status,amount) VALUES (1, “2016062701837x9d”, 0, 10000); START TRANSACTION;
3 UPDATE t_user_stat SET gold = gold + 1 WHERE user_id = 1; INSERT INTO t_order (user_id,order_id,status,amount) VALUES (1, “2016062701837x9d”, 0, 10000);
4 COMMIT; 等待获取Update行锁
5 - UPDATE t_user_stat SET gold = gold + 1 WHERE user_id = 1;
6 - COMMIT;

为了方式行锁阻塞了另一个事务的处理,事务应该尽量的小,不要做过多的事情;更不要发送HTTP请求,不然,遇到高并发的请求过来,很快把数据库连接耗尽。

事务特性

事务隔离级别及其特性

怎么选择事务隔离级别

Spring的传播特性

  1. PROPAGATION_REQUIRED(加入已有事务)
    尝试加入已经存在的事务中,如果没有则开启一个新的事务。

  2. RROPAGATION_REQUIRES_NEW(独立事务)
    挂起当前存在的事务,并开启一个全新的事务,新事务与已存在的事务之间彼此没有关系。

  3. PROPAGATION_NESTED(嵌套事务)
    在当前事务上开启一个子事务(Savepoint),如果递交主事务。那么连同子事务一同递交。如果递交子事务则保存点之前的所有事务都会被递交。

  4. PROPAGATION_SUPPORTS(跟随环境)
    是指 Spring 容器中如果当前没有事务存在,就以非事务方式执行;如果有,就使用当前事务。

  5. PROPAGATION_NOT_SUPPORTED(非事务方式)
    是指如果存在事务则将这个事务挂起,并使用新的数据库连接。新的数据库连接不使用事务。

  6. PROPAGATION_NEVER(排除事务)
    当存在事务时抛出异常,否则就已非事务方式运行。

  7. PROPAGATION_MANDATORY(需要事务)
    如果不存在事务就抛出异常,否则就已事务方式运行。

PROPAGATION_REQUIRED_NEW 实现原理?

从MySQL的事务模型说起

问题

请问下面的MySQL SQL,假设like_num初始值为30,执行结果会怎样?

1
2
3
4
5
6
START TRANSACTION;
update gt_audio set like_num=like_num-10 where id=1;
START TRANSACTION;
update gt_audio set like_num=like_num-20 where id=1;
ROLLBACK;
COMMIT;
  • 嵌入式事务模型:在嵌入式事务模型中,如果你开启了一个事务,并且想在当前事务下继续开启一个新的事务,第一个事务依旧会保持正常的开启状态,也就是说,第二个事务会嵌套在第一个事务里面;
  • 平面事务模型:而在平面式事务中,是不允许事务嵌套的,如果开启了一个事务之后,继续开启另一个事务,会自动先提交第一个事务。

  • MySQL使用了平面事务模型:嵌套的事务是不允许的,在连续开启第二个事务的时候,第一个事务自动提交了。

JTA(Java Transaction API)里面就提供了suspend()和resume()的接口,用于实现这种事务使用场景:

javax.transaction.TransactionManger.suspend()
javax.transaction.TransactionManger.resume(Transation)

什么时候应该使用PROPAGATION_REQUIRED,这个会有什么问题

1
2
3
4
5
@Service("userService")
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl {

}

Spring的默认传播特性,如果通过在Class文件头部添加注解的方式,默认都使用这个事务隔离级别,很容易写出事务执行时间比较长的代码,不容易控制。

什么时候应该使用RROPAGATION_REQUIRES_NEW

1
2
3
4
5
public void addComment(){
LogInfo logInfo = new LogInfo();
logInfoService.saveLogInfo(logInfo);
...
}

不管saveLogInfo(logInfo)后续执行是否成功,saveLogInfo都要持久化到数据库,则saveLogInfo()方法需要使用RROPAGATION_REQUIRES_NEW隔离级别

什么时候应该使用PROPAGATION_NESTED

1
2
3
4
5
6
7
public void methodA() {
try {
     serviceB.methodB();
    } catch (SomeException) {
     // 执行其他业务, 如 serviceC.methodC();
    }
}

如果希望不管serviceB.methodB()方法是否执行成功还是抛出异常,都希望不影响外部调用方法数据改动的提交,则可以考虑在methodB()方法使用PROPAGATION_NESTED。

常见的场景有:

什么时候应该使用PROPAGATION_SUPPORTS

什么时候应该使用PROPAGATION_NOT_SUPPORTED

PROPAGATION_NEVER和PROPAGATION_MANDATORY是用来做什么的

跨系统如何处理事务

这里就是分布式事务的问题了,传统的解决方法有二阶段事务提交

优化,三阶段事务提交

但是,这两种方式都是效率比较低的,在互联网高并发的应用下不具有实用性,CAP定理告诉我们,无法同时满足,放弃任何一个都会带来其他的隐患,而BASE理论则给了我们很好的解决方法,只要能够保持数据的最终一致性,就可以保证系统正确的运行了。

目前主流的处理方式是通过消息补偿机制实现数据的最终一致性。

举个例子,我们的系统跟易宝系统打交道的场景。
交易系统与钱包系统怎么保证两个系统的数据一致性

dubbo幂等性;

如何处理高并发情况下具有竞争关系的数据库资源

秒杀系统数据库减压方法;

节流:仅让能成功抢购到商品的流量(可以有一定余量)进入我们的系统。

削峰:将进入系统的瞬时高流量拉平,使得系统可以在自己处理能力范围内,将所有抢购的请求处理完毕。

异步处理:

可用性:

用户体验:

分布式锁(乐观锁)

如何确保消息不丢失,zk选举原理及其数据修复。

总结

在进行功能设计的时候,必须考虑上事务处理:

  • 让事务尽可能小;
  • 一般程序里面都会存在一个Service方法调用另一个Service方法的场景,请仔细考虑每一个方法应该设置的传播特性;
  • 对于高并发场景下具有竞争关系的数据(如标的表的剩余金额,每个用户购买都会尝试对该进行扣减操作),请根据并发量大小适当做一些节流工作(乐观锁,队列控制同时创建数据库的连接数);
  • 对于分布式事务,请确保每个子系统自己的事务处理正确;对于需要协调的,引入消息补偿机制实现数据的最终一致性;
  • 对于与第三方系统调用,请实现幂等性;

《Spring技术内幕》学习笔记16——Spring具体事务处理器的实现

关于分布式系统的数据一致性问题

秒杀系统架构分析与实战

如何用消息系统避免分布式事务?

JTA 深度历险 - 原理与实现

Spring技术内幕

JDBC、JTA、Spring的事务管理

Spring,Hibernate,Mybatis,JDBC事务之间的的关系

在应用层通过spring特性解决数据库读写分离

Maven Jar包改动注意事项

发表于 2016-06-28   |   分类于 Java , Maven

对于一些其他系统依赖的Jar包,例如 service-api, app-core 之类的,假设当前版本如下:

1
2
3
4
5
<dependency>
<groupId>com.itzhai</groupId>
<artifactId>service-api</artifactId>
<version>1.0.1</version>
</dependency>

如果我们要对这个Jar包进行改造,最好是新开一个特性分支,避免影响原有其他项目组正则调试的项目。

如果需要在原有分支上改动,则需要把该分支最新的代码打成Jar包推送到Maven私服,推送完之后再升级版本号为1.0.2进行后续的调整。这样有如下作用:

  • 对于直接使用maven私服Jar包的项目,直接拉取到1.0.1版本的Jar包即是最新的,不影响到该版本下功能的开发;
  • 把1.0.1最新的代码deploy到Maven,确保了不会用到不是最新的1.0.1版本的代码;
  • 对于在项目中引入了service-api的项目,由于项目版本号升级为了1.0.2,此时,如果项目配置不变,会自动去Maven私服上拉取1.0.1的Jar包,不影响原有功能。

不过,最好的方式还是在新特性分支上进行修改,修改完确定之后,视改动大小看需不需要升级版本号,然后把代码合并回master(如果升级了版本号,在合并会master之前请记得把原来master分支的代码重新打包deploy一遍,确保maven私服上面的jar包是最新的),提交到master分支的代码记得打包deploy到Maven私服,后续改动基于最新的版本号继续进行开发。

常用数据结构和算法

发表于 2016-06-22   |   分类于 数据结构 , 算法

Top K

12…6
arthinking

arthinking

Struggle for HighPay zoom.
主站 http://www.itzhai.com/

120 日志
33 分类
51 标签
itzhai GitHub Twitter Weibo
Creative Commons
© 2016 arthinking
由 Hexo 强力驱动
主题 - NexT.Pisces