基于redis的分布式锁 RedissonLock实现分析

2015/07/05 21:34:01 No Comments

在分布式锁的实现当中,都是通过另一个中央服务来存储相应的状态,来达到一个应用分布处理的目的。这里分析了一种通过redis来进行锁实现的细节,以描述在整个实现细节中的处理点。

通常在锁实现当中, 都要实现获取锁,等待锁,释放锁这几种关键的业务场景。然后在这几种场景的基础之上,还需要实现更多的语义,比如过期时间,等待时间等。通过redis的setNx可以达到获取锁的语义,因此大多数的实现均是采用这种手法来进行锁判断和处理(类似的手法还包括concurrentHashMap的putIfAbsent)

本文基于redission版本1.2.0,类RedissionLock.

线程间协作

1. 获取锁

获取锁即通过redis的setNx命令来实现,此命令的意义即仅当相应的值不存在时,才能设置成功。如果设置成功的话,即认为当前能够获取到相应的锁了。相应的主要代码如下所示:

Boolean res = connection.setnx(getName(), currentLock);

其中name即可认为是lock的一个内部表示名字,其中多个线程共享同一个lock,即在操作命令时会使用同一个name值。

read more… »

java内部类final语义实现

2015/05/31 11:29:39 No Comments

本文描述在java内部类中,经常会引用外部类的变量信息。但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索。本文从字节码层描述在内部类中是如何实现这些语义的。

本地临时变量 基本类型

final int x = 10;

new Runnable() {
    @Override
    public void run() {
        System.out.println(x);
    }
}.run();

当输出内部类字节码(javap -p -s -c -v)时,如下所示:

         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: bipush        10
         5: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
         8: return

可以看出,此常量值直接被写在内部类的临时变量中,即相当于进行了一次变量copy。

read more… »

spring 多placeHolder问题的解决方案

2015/04/15 17:13:05 No Comments

当前的spring版本为: 4.0.6.RELEASE
问题官方地址:https://jira.spring.io/browse/SPR-9989

问题重现

@Component
public class Tb {
    @Value("${tb.username:abcd}")
    private String username;

    public String getUsername() {
        return username;
    }
}

以上为定义bean,其中属性表示需要去获取属性为tb.username的属性定义,默认值为abcd。然后在spring分别如下配置

<context:property-placeholder location="classpath:springa.properties" ignore-unresolvable="true" ignore-resource-not-found="true"/>
<context:property-placeholder location="classpath:springb.properties" ignore-unresolvable="true" ignore-resource-not-found="true"/>

配置文件的值如下所示:
配置文件一:tb.username1=spring1
配置文件二:tb.username=spring2

因为在配置文件2中,有相应的配置定义,因此我们期望其返回username的值为 spring2。但是在实际运行中,此值即是abcd,如下输出所示:

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        Tb tb = context.getBean(Tb.class);
        System.out.println("->" + tb.getUsername());
    }

//输出值
->abcd

本文即通过更换相应的属性解析器,用于解决此问题,以让spring能够正常的解析并输出我们需要的值。

read more… »

redisson的理解和使用-调用流程

2015/03/29 21:11:39 No Comments

redisson是一个用于连接redis的java客户端工作,相对于jedis,是一个采用异步模型,大量使用netty promise编程的客户端框架。

0     代码示例

        //创建配置信息
        Config config = new Config();
        config.useSingleServer().setAddress("localhost:6379").setConnectionPoolSize(5);

        Redisson redisson = Redisson.create(config);

        //测试 list
        List<String> strList = redisson.getList("strList");
        strList.clear(); //清除所有数据
        strList.add("测试中文1");
        strList.add("test2");

        redisson.shutdown();

从代码上来看,其基本的使用非常简单,在最后的使用当中。除与redisson打交道之外(获取各种数据结构),完全感觉不到与redis的信息连接。甚至于返回于上层直接不需要考虑下层的实现,一切均由redisson进行了封装。

read more… »

分布式cookie-session的实现(spring-session)

2015/03/17 18:39:10 No Comments

本文使用的spring-session版本为 1.0.0,地址为: https://github.com/spring-projects/spring-session

1     session存储策略

存储,即在后台使用session的setAttribute,getAttribute等方法时,这些内部存放的数据最终存储至什么位置。比如在默认的tomcat实现中,相应的数据即存储在内存中,并在停止之后会序列化至磁盘中。
可以使用内存存储和第三方存储,如redis。对于集群式的session存储,那么肯定会使用第三方存储的实现。

在spring-session中,对session存储单独作了一个定义,但定义上基本保证与http session一致,主要的目的在于它可以支持在非http的环境中模拟使用。因此不直接使用http session接口。
先看定义:

public interface Session {
     /** 获取惟一的id,可以理解为即将每个session当作一个实体对象 */
    String getId();
   <T> T getAttribute(String attributeName);
    Set<String> getAttributeNames();
    void setAttribute(String attributeName, Object attributeValue);
    void removeAttribute(String attributeName);
}

从这个定义来看,如果要使用httpSession,如果实现了这个session接口,那么实现上就可以全部实现httpSession对于存储的要求。对于非httpSession场景,使用这个也可以达到session存储的目的。
当然,为了保证在会话场景中会使用到失效,最后访问时间,最大不活跃时间的目的,spring-session也有一个继承于session接口的expiringSession接口。

1.0    id主键的生成

对于id,即理解为session实体对象惟一键,可以采用任意的一种惟一key生成策略。比如,使用uuid来生成惟一键。同时,也可以将这个id认为就是在http中sessionCookie的值

read more… »

使用spring typeConverter造成的数据错乱(多线程环境)

2015/02/10 17:00:23 No Comments

在线上环境碰到一个问题,经由数据库查询并进行数据处理转换之后的数据在界面显示时随机出现数据错位。由于该问题不可重现(重新清除缓存并操作一次问题即解决),并且由于缓存的存在(缓存了错误的数据),导致此问题严重并且很难查找。

业务场景描述如下:

数据库查询数据->一次处理->类型转换->二次处理->界面显示

由于每个步骤都涉及到很多代码,因此在处理时通过在不同的点设定潜在出错点,并通过判断数据变化进行log。尝试在本地环境重现此问题。经过一次偶然的场景,发现一个本该是8位数字的字符串在转换过程中报错,由此找出了真正的问题。

//报错的业务代码 假定数据为 20141111
str = s.substring(0,4) + "/" + s.substring(4,6) + "/" + s.substring(6,8)
//报错异常 indexOutofBound,即字符串不足8位...

经过断点,加层层回溯,终于发现数据在经过一次类型转换之后被修改了。转换代码如下所示:

private static TypeConverter typeConverter = new SimpleTypeConverter();
        public <T> T convertValue(Object value) {
            return (T)typeConverter.convertIfNecessary(value, Integer.class);
        }

初看起来,这个方法调用并没有什么问题,即将数据值转换为整数类型.重点的问题在于在这个方法(由spring提供)的内部实现并不是线程安全的。附API说明:

public interface TypeConverter
Interface that defines type conversion methods. Typically (but not necessarily) implemented in conjunction with the PropertyEditorRegistry interface.
Note: Since TypeConverter implementations are typically based on PropertyEditors which aren't thread-safe, TypeConverters themselves are not to be considered as thread-safe either.

typeConverter内部会使用propertyEditor来进行类型转换。这种转换在spring3.0之后已不再推荐使用。见 使用线程安全的spring类型转换器ConversionService VS TypeConverter

解决方法也简单,将其更换为conversionService即可(为什么不自己写?不想重复发明轮子)。spring3.0之后,为conversionService内置了常见类型之间的转换,相当于整个类型转换子框架被完全重写了,重写的转换是状态无关的,即线程安全。至于原来的typeConverter及propertyEditor,忘掉它吧,遗留的东西。

附:jdk自带的simpleDateFormat也存在相同的问题,用这个类需要小心处理线程问题。