0%

前言

应用场景:自动部署系统发布后发现问题,需要回滚到某一个commit,再重新发布

原理:先将本地分支退回到某个commit,删除远程分支,再重新push本地分支

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

1. git checkout the_branch

2. git pull

3. git branch the_branch_backup # 备份一下这个分支当前的情况

4. git reset --hard the_commit_id # 把the_branch本地回滚到the_commit_id

5. git push origin :the_branch # 删除远程 the_branch

6. git push origin the_branch # 用回滚后的本地分支重新建立远程分支

7. git push origin :the_branch_backup # 如果前面都成功了,删除这个备份分支

Tips:获取the_commit_id 使用git log,然后找到需要回滚到的那个提交的id hash值。

阅读全文 »

前言

前段时间业务上有一个需求,这个需求需要查询数据库,由于单表数据比较大,导致出现超过5s的慢查询。随后,为了快速修复慢查询对整个系统带来的影响,将查询的数据源通过简单粗暴的修改配置切换到从库上。此后,增加了memcached来缓存一些case下的查询数据,但是对从库配置实现方式,并没有去调整。

最近,有其他业务的数据查询也需要切换到从库上,因此对上述简单的配置实现进行了思考。

动态数据源,其实就是根据我们的代码实现和配置来选择不同的数据源进行sql操作。一般地,我们会把读操作移到从库中,从而减轻主库的压力,也就是所谓的读写分离。

当然,对于一些使用数据库中间件来完成读写分离,而不需要业务层来做。这种方式,在大互联网公司中大量使用,比如360基于mysql-proxy的Atlas,阿里的DRDS(基于淘宝之前开源的TDDL)以及网易的分布式数据库中间件DDB等等。

对于一些未使用部署数据库中间件的公司,简单的方法就是在代码里面使用AOP方式通过对每个DAO层sql请求进行配置,来完成自定义的动态数据源。

Spring动态数据源接口

Spring提供了一个抽象类AbstractRoutingDataSource,该类可以让开发人员快速实现数据源路由完成根据不同请求使用不同数据源的需求。

AbstractRoutingDataSource抽象类,继承关系如下图:

Notes: 抽象类最终继承javax.sql.DataSource类,该数据源类提供的一些接口就是我们最终需要实现的。

DataSource接口主要提供了两个方法给开发者实现,因此实现动态数据源,我们只需要把这两个方法的实现,在调用数据库查询的时候,告知执行上下文,运行环境拿到对应的数据库连接,就可以连接到对应的数据库进行查询更新等操作。

1
2
3
4
5
6
7
8
9
10

public interface DataSource extends CommonDataSource,Wrapper {

Connection getConnection() throws SQLException;

Connection getConnection(String username, String password)
throws SQLException;

}

阅读全文 »

问题

倒序索引是一种索引数据结构,该索引存储单词(或者其他内容)到它们位于文件,档案或者数据库等位置之间的映射关系。这个通常被用来实现全文搜素服务,但是这要求在搜索之前这些文档的相关倒序索引就必须建立好。

因此,我们想要事业Redis来作为背后的存储系统来实现全文搜索服务。

解决方法

我们的实现,将为每一个单词,准备一个set集合,这些集合包含对应的文档的ID。为了允许快速搜索,我们将在开始之前为所有的文档建立索引。

搜索服务本身先分割请求为各个单词,然后获取每个单词匹配的集合set的交集,最后就可以返回包含所有我们搜索的单词的文档ID集。

讨论

建立索引

首先,让我们假设我们有一百个允许我们搜索的文档或者网页,因此需要对它们建立倒序索引。为了建立索引,我们必须分割文本为分开的单词(分词操作),在此过程中,可能需要排除stop word以及长度小于3的单词。使用Ruby脚本,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

def id_for_document(filename)
doc_id = $redis.hget("documents", filename)
if doc_id.nil?
doc_id = $redis.incr("next_document_id")
$redis.hset("documents", filename, doc_id)
$redis.hset("filenames", doc_id, filename)
end
doc_id
end

STOP_WORDS = ["the", "of", "to", "and", "a", "in", "is", "it", "you", "that"] f = File.open(filename)
doc_id = id_for_document(filename)
f.each_line do |l|
l.strip.split(/ |,|\)|\(|\;|\./).each do |word|
continue if word.size <= 3 || STOP_WORDS.include?(word)
add_word(word, doc_id)
end
end

所以,我们将过滤掉这些已经被加入到索引的单词,然后为我们的文档生成唯一的ID。此外,我们仍然需要完成上面的索引方法:

阅读全文 »

问题

想要借助 RedisPUB/SUB功能,使用node.js和Socket.io实现一个轻量级的实时聊天系统。

解决方法

由于Redis 天生就支持发布订阅(pub/sub)模式,所以我们可以很容易就使用Node.jsSocket.IO来快速创建一个实时的聊天系统。

发布订阅模式,其实就是接收者订阅某种特定模式的消息(比如,发送到某个指定channel的消息),而发送者发送一个消息到消息云上。当一个消息到达云上的时候,订阅了这一种类的客户端就会获得消息。这中发布订阅模式,然后就可以允许发送者和接收客户端在不知道彼此的情况下,亲密结对交流。而他们仅仅需要以一种既定的模式发送消息和接收匹配类型的消息即可。

Redis直接支持pub/sub模式,意味着其可以让接收客户端订阅指定的匹配消息频道channel,以及发布消息到一个给定的频道channel。这意味着,我们可以很简单地创建像chat:cars的聊车频道;或者像chat:sausage这种关于食物的谈话。此外,频道channel的命名跟Redis 的keySpace无关,所以不用担心会存在某些冲突情况。下面给出,Redis支持的一些命令:

    * PUBLISH:发布消息到指定的频道;

    * SUBSCRIBE:订阅一个指定频道的消息;

    * UNSUBSCRIBE:取消订阅一个指定频道;

    * PSUBSCRIBE:订阅一个满足给定模式的频道集;

    * PUNSUBSCRIBE:取消订阅满足指定模式的频道集。

拥有上面这些知识,为在应用程序逻辑部分之间的终端用户或者流消息实现一个聊天和统计系统,其实还是很琐碎的。
pub/sub甚至可以被用来作为一个内建的强壮阻塞队列系统。接下来看看,如何去实现这么一个消息聊天系统吧。

在服务端,Node.jsSocket.IO将来实现网络层,然后Redis将作为一个在客户端之间递交消息的pub/sub功能的实现。在客户端,我们使用jQuery来处理消息,然后发送数据到服务器上。

阅读全文 »

前言

Guava是Google开源出来的Java常用工具集库,包括集合|缓存|并发|字符串|IO操作等在Java开发过程中经常需要去实现的工具类.

显然,对于这种十分常见的需求,Guava提供了自己的工具类实现.GuavaCache 提供了一般我们使用缓存所需要的几乎所有的功能,主要有:

  • 自动将entry节点加载进缓存结构中;

  • 当缓存的数据已经超过预先设置的最大值时,使用LRU算法移除一些数据;

  • 具备根据entry节点上次被访问或者写入的时间来计算过期机制;

  • 缓存的key被封装在WeakReference引用内;

  • 缓存的value被封装在WeakReference或者SoftReference引用内;

  • 移除entry节点,可以触发监听器通知事件;

  • 统计缓存使用过程中命中率/异常率/未命中率等数据。

此外,Guava Cache其核心数据结构大体上和ConcurrentHashMap一致,具体细节上会有些区别。功能上,ConcurrentMap会一直保存所有添加的元素,直到显式地移除.相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素.在某些场景下,尽管它不回收元素,也是很有用的,因为它会自动加载缓存.

阅读全文 »

问题

存储分析或者其他基于时间序列的数据,对于传统的存储系统(比如RDBMS)来说,是有一点挑战的。可能你想要对输入流量的速率进行限制(要求快速和高并发更新)或者简单地追踪网站访问者(或者其他更复杂的度量指标),然后以图表的形式画出来。

虽然当前在其他系统中,有很多的方式存储这类数据;但是,Redis是一个非常优秀的候选者,由于它强大的数据结构。

解决方法

Redis 理念上非常适合存储这类数据,以及跟踪某种特定的事件。具有原子性的,并且非常快的(O(1)时间复杂度)HINCRHINCRBY命令,结合快速数据查找,使得它非常适合这类场景。

在Redis中一种好的高效内存存储这类数据的方式是使用hash来存储统计值,使用HINCRBY增加它们,然后使用HGETHMGET来获取这些数据。查找位于top位置的元素通过SORT命令也是很容易做到的。

阅读全文 »

说明:算法背景和解法来自https://github.com/ketao1989/The-Art-Of-Programming-By-July.git,如有版权问题,请留言告知!

前言

最近看博客,发现一些有趣的算法,很久没接触,都不清楚了。这里,把上述的github中一些算法用java实现。

旋转字符串

背景

给定一个字符串,要求把字符串前面的若干个字符移动到字符串的尾部,如把字符串“abcddg”前面的2个字符’a’和’b’移动到字符串的尾部,使得原字符串变成字符串“cdcgab”。请写一个函数完成此功能,要求对长度为n的字符串操作的时间复杂度为 O(n),空间复杂度为 O(1)。

Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

/*************************************************************************
> File Name: RotateString.java
> Author: ketao
> Mail: ketao1989@gmail.com
> Created Time: 2014年11月26日 星期三 16时33分28秒
************************************************************************/
public class RotateString{

/**
* 把一个字符数组,按照指定位置移动
*/

public static void rotateString(char[] str,int n,int len){
reverseString(str,0,n-1);
reverseString(str,n,len-1);
reverseString(str,0,len-1);
}
/**
*反转指定区间的字符数组
*
*/
private static void reverseString(char[] str,int from,int to){

while(from < to){
char ch = str[from];
str[from++] = str[to];
str[to--] = ch;
}
}

/**
*测试代码
*/
public static void main(String[] args){
char[] str = {'a','b','c','d','c','g'};
rotateString(str,2,6);
System.out.println(str);
}
}

阅读全文 »

插播广告:ubuntu系统的sublime text 3 中文无法输入,需要修复一些so库。参考:http://jingyan.baidu.com/article/f3ad7d0ff8731609c3345b3b.html

前言

虽然java开发已经快两年了,但是对于java内部一些小的技巧和坑还是会有些不了解。这里记录下。

java Integer并发问题

前段时间看书,顺带提到说Integer.valueOf( )会导致死锁问题很是惊讶。于是,查看了JDK源码,果然如此。

JDK代码对把-128 到127 之间的整数转换成Integer的时候,并不会new一个新的Integer对象,而是从 内部的IntegerCache中直接获取已经创建好的对象(第一次调用时会创建这个IntegerCache)。

由于内部IntegerCache共用,所以在不同的地方对同一个数值调用valueOf获取cache中同一个对象,这样很可能会导致死锁。此外,关于整数范围可以使用VM初始设置(-XX:AutoBoxCacheMax=,但是不能比127小)

valueOf方法实现代码

valueOf方法实际上就是调用IntegerCache获取对应下标的对象,而IntegerCache实际上就是一个Integer对象数组。关于IntegerCache实现如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
}
high = h;

cache = new Integer[(high - low) + 1];//大小为正负两边总的数量
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}

private IntegerCache() {}
}

之所以会导致死锁,主要原因是因为当两个线程不断的调用valueOf时,比如一个为 Integer a=Integer.valueOf(10) + Integer.valueOf(20),另外一个线程调用Integer b= Integer.valueOf(20) +Integer.valueOf(10),while中不停的调用,就可能出现死锁异常。

阅读全文 »

前言

最近线上遇到一个很奇怪的现象,就是一个dubbo 服务被注册到了好几个group下面,并且这些group都是我们应用中,通过dubbo:registry来配置的。但是,显然这不是我们应用所期待的结果,因此,首先,我们需要修复这个问题;其次,我们需要找出原因。

1.1 问题定位

首先看配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://code.alibabatech.com/schema/dubbo
http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

<dubbo:registry id="crm-message-registry" group="crm-message"
address="${qunar-public.zookeeper.address}" protocol="zookeeper"/>

<dubbo:protocol name="dubbo" port="${dubbo.protocol} "/>

<dubbo:service interface="com.qxx.scm.message.api.IPushMessageBiz"
ref="pushMessageBiz" timeout="6000" version="1.0.1" />

</beans>

从上面的配置可以看到,我们实际上是有dubbo:registry,并且在其中也设置了group属性。

阅读全文 »

前言

今天项目发布,上线上跟日志的时候,发现一些死锁信息的出现,查询了一下,发现日志里面虽然死锁出现很少,但是都是同一个代码sql语句产生的,如下图所示:

并且,一天产生死锁31次。

从DBA那边拿到了对应死锁的Mysql日志信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
------------------------
LATEST DETECTED DEADLOCK
------------------------
141009 12:54:59
*** (1) TRANSACTION:
TRANSACTION AEE50DCB, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 6055694, OS thread handle 0x7f4345c8d700, query id 2443700084 192.168.249.154 crm_w updating
DELETE FROM crm_business WHERE serial_number = 'CH01313320'
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 244 page no 817 n bits 824 index `uniq_serial_number_business_type` of table `crm`.`crm_business` trx id AEE50DCB lock_mode X waiting
*** (2) TRANSACTION:
TRANSACTION AEE50DCA, ACTIVE 0 sec inserting, thread declared inside InnoDB 500
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1
MySQL thread id 6055696, OS thread handle 0x7f4344941700, query id 2443700084 192.168.249.154 crm_w update
INSERT INTO crm_business(serial_number, business_type) values ('CH01313318', 2)
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 244 page no 817 n bits 824 index `uniq_serial_number_business_type` of table `crm`.`crm_business` trx id AEE50DCA lock mode S
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 244 page no 817 n bits 824 index `uniq_serial_number_business_type` of table `crm`.`crm_business` trx id AEE50DCA lock_mode X locks gap before rec insert intention waiting
*** WE ROLL BACK TRANSACTION (1)

Note: 数据库中,CH01313318CH01313320序列号是连着的两个记录。

死锁问题定位

之所以需要分析这个问题,主要原因是,这边代码并没有涉及到在一个事务内部sql操作导致死锁等常见的情况。

阅读全文 »