如何架构一个新的综合金融交易系统

 

技术产品需求

客户端网站系统(app),业务系统(接口),运维平台

网站系统:

基础系统:

1.用户(帐户)管理系统 2.商品(行情)管理系统 3.订单(交易管理)系统 4.支付系统5.风控系统

增值系统:

客户服务系统,筛选筛选系统,资讯系统。。。

业务系统:

管理系统:

用户管理系统

运营支撑系统

核心系统:

订单交易系统

查询系统

行情系统

增值系统:

数据统计(报表)系统

客户服务系统

 

技术栈选型

1.前端系统

2.后端系统

2.1 负载均衡及http 服务器接入:nginx

2.2 web 应用容器 SOA 服务 Tomcat + dubbo

2.3 文件服务器

        2.3.1 储存方式
        2.3.2 储存容量
        2.3.3 安全性与存取权限控管
        2.3.4 存取效能

2.4 缓存服务器
         2.4.1 分布式Redis缓存
         2.4.2 Memcache缓存

2.5 消息系统
         2.5.1 ActiveMQ
         2.5.2 分布式消息系统Kafka、Rocketmq等

2.6 数据持久层
         2.6.1 关系型数据库
              (1). Mysql

        2.6.2 Nosql
              (1). MongoDB

mysql优化系列(一)

使用新数据库,玩一段时间后,我想大家就应该会碰到一系列的小问题,那是正常的,因为就像处女朋友一样,作为一个负责的男人,不能随便玩一玩了事。

当然,本人也不是泡妞高手,所以也需要不断学习充电,当然老毛子说理论实践是要结合的,在此留下一记。

    1.全表扫描

查询一定要避免全表扫描,如果你有前任,不管她的名字是不是叫oracle,你一定吃过全表扫描的亏,如果很不幸,你一直是只单身狗,那么你也不要沮丧,这里有现成的理论,单身狗们快拿去实践一下:

  • 在查询条件字段建立索引,这是最基本的常识,如果你不记牢你女朋友的电话号码,你大冬天的每次都去她楼下找她么!当然索引也不是越多越好,就像衣服穿多了,你何时才能上三垒!insert update 看到索引就说不要不要的!
  • 在查询条件中,不要拿null值进行判断,既然这样,如果可以,请把你的字段都设置成非NULL,跟默认值进行判断,0 就是0,NULL是什么鬼,你女朋友一定会河东狮吼!
  • 对于 like 这类关键字,大家也尽量少用为好,不要问我为什么,你女朋友真的要大海捞针!
  • 不要给你女朋友太多的选择条件,直接告诉她怎么做,所以 or 这种条件字段也尽量不要用了!
  • 如果字段可以使用 int 类型,那么就不要使用 字符串类型,你女朋友的脑子有时候没有你想象中的那么好,你把字符串给她一个个对比,你们约好的出门时间应该又要推迟了,电影也只能看后半场了,所以给她处理的东西也越简单越好.

当然,mysql 既然是你的新女朋友,那么她也会有自己的一些特性,请在查询的时候限制她查询的范围,limit n,你只需要 n个记录,那么就不要让她找出 n+m来,不然她真的会这么干!

有时候,即使你给了限制,还是会出错!

请看

select * from order where kj_customer_acco =

@kj_customer_acco and position_int > @position_int order by position_int asc limit 0,@request_num

上面给出的示范,除了

select *

这个是不好的外(增加多余的网络传输压力等),其他应该都是几乎完美的一个语句,但是就是如此,有时候你女朋友还是爽约了,那一天你在雨中苦等了2小时,以为会有一场雨中浪漫的约会,结果是你淋成了落汤鸡!因为你没有从实际国情出发,我们是一国两制的社会,胡乱套用也会水土不服,所以我们一定要具体问题具体分析,那么就先来看下国情:

CREATE TABLE order
(
	position_int                   bigint          NOT NULL AUTO_INCREMENT,
	kj_customer_acco               varchar(10)     DEFAULT ' '        ,
	update_date                    int             DEFAULT 0          ,
	update_time                    int             DEFAULT 0          ,
	PRIMARY KEY(position_int)
);
CREATE INDEX idx_position_int ON order(position_int ASC );

然后我们造了测试数据进去

begin
    declare i int default 1000;
    
	while i < 1300 do
    BEGIN
    declare j int default 1;
		while j < 9060 do
       insert into `order` values(DEFAULT,i,20151203,111111);
       set j=j+1;
    end while; 
    set i=i+1;
    END;
   end while;
   commit;
end

我们使用上面的语句对这个表进行翻页式查询,在查询中,每当如下语句

select * from order where kj_customer_acco = @kj_customer_acco  and position_int > @position_int

查询出来的数据不足 @request_num,的时候,就会特别的慢,使用 EXPLAIN 关键字查看下执行计划

image

我们发现查询搜索的边界在我们的意料之外,它的搜索范围是1231780行数据,当剩余查询的数据条数大于@request_num,因为我们加了limit 的限制,所以查询搜索到@request_num 条数据的时候就结束搜索了,但是当剩余查询的有效数据量不足@request_num时,就会继续往下查询把rows 条记录都会搜一遍!

spring_mvc_mybatis傻瓜入门篇之二spring与spring-mvc

上篇我们已经弄好了spring 与mybatis,即搞定了数据model层的东西,已经可以对外提供数据服务了,那么现在就要将spring-mvc加上去,增加逻辑与展示!

一.增加依赖包

在pom.xml中增加配置项

<dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency>

增加spring_mvc 的配置文件(servlet-context.xml)如下

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

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<!-- SpringMVC上传文件时,需要配置MultipartResolver处理器 -->
	<beans:bean id="multipartResolver"
		class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
		<beans:property name="defaultEncoding" value="utf-8" />
		<beans:property name="maxUploadSize" value="10485760000" />
		<beans:property name="maxInMemorySize" value="40960" />
	</beans:bean>
    
	<context:component-scan base-package="com.test.controller" />
	
</beans:beans>

将该 配置文件路径添加到web.xml上:

<!-- Processes application requests -->
	<servlet>
		<servlet-name>appServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>
		
	<servlet-mapping>
		<servlet-name>appServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

配置好了,然后就可以开始新建个页面测试下了

新建展示页面showUserInfo.jsp

<%@ page language="java" contentType="text/html; charset=utf-8"
    pageEncoding="utf-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Insert title here</title>
</head>
<body>
${userinfo.name}:${userinfo.passwd}
</body>
</html>

新建逻辑控制页面代码:

package com.test.controller;

import javax.annotation.Resource;  
import javax.servlet.http.HttpServletRequest;  
import org.springframework.stereotype.Controller;  
import org.springframework.ui.Model;  
import org.springframework.web.bind.annotation.RequestMapping;  
import com.test.model.UserInfo;  
import com.test.service.IUserInfoService;  


@Controller  
@RequestMapping("/user") 
public class queryUserController {

	 @Resource  
	 private IUserInfoService userService;  
	  
	    @RequestMapping("/showUserInfo")  
	    public String toIndex(HttpServletRequest request, Model model) {  
	        long pos_int = Long.parseLong(request.getParameter("id"));  
	        UserInfo userinfo = this.userService.getUserInfoById(pos_int);  
	        model.addAttribute("userinfo", userinfo);  
	        return "showUserInfo"; 
	    }
}

好了,就可以运行调试:http://localhost:8080/smvc/user/showUserInfo?id=1

QA:

有很大几率你是不能一次性就运行成功的,会报很多错误,那么耐性看他报错信息,然后找谷歌问一问你就可以得到答案!当然如果你怕麻烦,那么也可以预先做下如下处理以避免各种报错:

1.在项目属性中,添加如下图红框中的路径配置

image

其他的一时也记不起来了,出问题了百度吧,或者留言!

Linux IO模式及 select、poll、epoll详解(转载)

这是目前入门级别感觉比较清晰又详尽的IO模式讲解,值得一看。

注:本文是对众多博客的学习和总结,可能存在理解错误。请带着怀疑的眼光,同时如果有错误希望能指出。

同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。

本文讨论的背景是Linux环境下的network IO。

一 概念说明

在进行解释之前,首先要说明几个概念:
– 用户空间和内核空间
– 进程切换
– 进程的阻塞
– 文件描述符
– 缓存 I/O

用户空间与内核空间

现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。

进程切换

为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。

从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
1. 保存处理机上下文,包括程序计数器和其他寄存器。
2. 更新PCB信息。
3. 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。
4. 选择另一个进程执行,并更新其PCB。
5. 更新内存管理的数据结构。
6. 恢复处理机上下文。

注:总而言之就是很耗资源,具体的可以参考这篇文章:进程切换

进程的阻塞

正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的

文件描述符fd

文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。

缓存 I/O

缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。

缓存 I/O 的缺点:
数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。

二 IO模式

刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)

正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。

同步:
– 阻塞 I/O(blocking IO)
– 非阻塞 I/O(nonblocking IO)
– I/O 多路复用( IO multiplexing)
– 信号驱动 I/O( signal driven IO)

异步:
– 异步 I/O(asynchronous IO)

注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。

阻塞 I/O(blocking IO)

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

非阻塞 I/O(nonblocking IO)

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。

I/O 多路复用( IO multiplexing)

IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。

所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

信号驱动式U/O模型:

         可以用信号,让内核在描述符就绪时发送SIGIO信号通知我们。称为信号驱动式I/O

        我们首先开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数。该系统调用将立即返回,我们的进程继续工作,也就是说它没有被阻塞。当数据报准备好读取时,内核就为该进程产生一个SIGIO信号。我们随后既可以在信号处理函数中调用recvfrom读取数据报,并通知主循环数据已准备好待处理。也可以立即通知循环,让它读取数据报。

         无论如何处理SIGIO信号,这种模型的优势在于等待数据报到达期间进程不被阻塞。主循环可以继续执行,只要等待来自信号处理函数的通知:既可以是数据已准备好被处理,也可以是数据报已准备好被读取。

异步 I/O(asynchronous IO)

inux下的asynchronous IO其实用得很少。先看一下它的流程:

用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

总结

blocking和non-blocking的区别

调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。

synchronous IO和asynchronous IO的区别

在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的:
– A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
– An asynchronous I/O operation does not cause the requesting process to be blocked;

两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。

有人会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。

而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。

各个IO Model的比较如图所示:

通过上面的图片,可以发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

三 I/O 多路复用之select、poll、epoll详解

select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。(这里啰嗦下)

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。

struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ };

pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

epoll

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

一 epoll操作过程

epoll操作过程需要三个接口,分别如下:

int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议
当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
函数是对指定描述符fd执行op操作。
– epfd:是epoll_create()的返回值。
– op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
– fd:是需要监听的fd(文件描述符)
– epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //events可以是以下几个宏的集合: EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待epfd上的io事件,最多返回maxevents个事件。
参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

二 工作模式

epoll对文件描述符的操作有两种模式:LT(level trigger)ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

1. LT模式

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。

2. ET模式

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)

ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

3. 总结

假如有这样一个例子:
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
2. 这个时候从管道的另一端被写入了2KB的数据
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
4. 然后我们读取了1KB的数据
5. 调用epoll_wait(2)……

LT模式:
如果是LT模式,那么在第5步调用epoll_wait(2)之后,仍然能受到通知。

ET模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。

当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:

while(rs){ buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0); if(buflen < 0){ // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读 // 在这里就当作是该次事件已处理处. if(errno == EAGAIN){ break; } else{ return; } } else if(buflen == 0){ // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf){ rs = 1; // 需要再次读取 } else{ rs = 0; } }

Linux中的EAGAIN含义

Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。
从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。

例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。
又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。

三 代码演示

下面是一段不完整的代码且格式不对,意在表述上面的过程,去掉了一些模板代码。

#define IPADDRESS "127.0.0.1" #define PORT 8787 #define MAXSIZE 1024 #define LISTENQ 5 #define FDSIZE 1000 #define EPOLLEVENTS 100 listenfd = socket_bind(IPADDRESS,PORT); struct epoll_event events[EPOLLEVENTS]; //创建一个描述符 epollfd = epoll_create(FDSIZE); //添加监听描述符事件 add_event(epollfd,listenfd,EPOLLIN); //循环等待 for ( ; ; ){ //该函数返回已经准备好的描述符事件数目 ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1); //处理接收到的连接 handle_events(epollfd,events,ret,listenfd,buf); } //事件处理函数 static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf) { int i; int fd; //进行遍历;这里只要遍历已经准备好的io事件。num并不是当初epoll_create时的FDSIZE。 for (i = 0;i < num;i++) { fd = events[i].data.fd; //根据描述符的类型和事件类型进行处理 if ((fd == listenfd) &&(events[i].events & EPOLLIN)) handle_accpet(epollfd,listenfd); else if (events[i].events & EPOLLIN) do_read(epollfd,fd,buf); else if (events[i].events & EPOLLOUT) do_write(epollfd,fd,buf); } } //添加事件 static void add_event(int epollfd,int fd,int state){ struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); } //处理接收到的连接 static void handle_accpet(int epollfd,int listenfd){ int clifd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen); if (clifd == -1) perror("accpet error:"); else { printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //添加一个客户描述符和事件 add_event(epollfd,clifd,EPOLLIN); } } //读处理 static void do_read(int epollfd,int fd,char *buf){ int nread; nread = read(fd,buf,MAXSIZE); if (nread == -1) { perror("read error:"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLIN); //删除监听 } else if (nread == 0) { fprintf(stderr,"client close.\n"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLIN); //删除监听 } else { printf("read message is : %s",buf); //修改描述符对应的事件,由读改为写 modify_event(epollfd,fd,EPOLLOUT); } } //写处理 static void do_write(int epollfd,int fd,char *buf) { int nwrite; nwrite = write(fd,buf,strlen(buf)); if (nwrite == -1){ perror("write error:"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLOUT); //删除监听 }else{ modify_event(epollfd,fd,EPOLLIN); } memset(buf,0,MAXSIZE); } //删除事件 static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); } //修改事件 static void modify_event(int epollfd,int fd,int state){ struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); } //注:另外一端我就省了

四 epoll总结

在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。)

epoll的优点主要是一下几个方面:
1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对 于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。

  1. IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。

如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。

Java nio和多路复用

java 1.4 nio提供的select,这是一种多路复用I/O(multiplexed non-blocking I/O)模型,底层是使用select或者poll。I/O复用就是,阻塞在select或者poll系统调用的某一个之上,而不是阻塞在真正的I/O系统调用之上。JDK 5.0 update 9和JDK 6.0在linux下支持使用epoll,可以提高并发idle connection的性能(http://blogs.sun.com/alanb/entry/epoll)。
“BIO是指阻塞IO方式,即读和写必须为同步方式,NIO是指异步(用户进程未被IO阻塞)读,同步(用户进程被IO阻塞)写的方式,AIO是指异步读,异步写的方式。
在网络协议上java对于TCP/IP和UDP/IP均支持,在网络IO的操作上,目前java仅支持BIO和NIO两种方式。”

(读:读取内核状态是否准备好写:数据内核态->用户态的拷贝

Reactor 和 Proactor

这是目前网络编程中经常用到两种事件驱动的设计模式。

其实结合上面来看,reactor 模式思想就是基于非阻塞同步IO的方式,也即I/O 多路复用。

其流程如下:

  • 步骤 1) 等待事件 (Reactor 的工作)
  • 步骤 2) 发”已经可读”事件发给事先注册的事件处理者或者回调 ( Reactor 要做的)
  • 步骤 3) 读数据 (用户代码要做的)
  • 步骤 4) 处理数据 (用户代码要做的)

而proactor模式思想基于完全异步IO方式。

  • 步骤 1) 等待事件 (Proactor 的工作)
  • 步骤 2) 读数据(看,这里变成成了让 Proactor 做这个事情)
  • 步骤 3) 把数据已经准备好的消息给用户处理函数,即事件处理者(Proactor 要做的)
  • 步骤 4) 处理数据 (用户代码要做的)

区别就在第三步骤,所以如果我们的网络库使用了Reactor设计模式,使用的是IO复用,那么可以通过封装第三个步骤,变成proactor 模式,这样可以让用户进程对读数据这一步无感知,那么使用了 Proactor设计模式封装过的IO多路复用模型在使用上与异步IO模型无差异,为什么这样做,虽然不会提升我们系统的性能,我们的服务在跨平台的时候可以让用户代码脱离 IO模型可能不一样的限制,降低耦合,提高生产率。

参考

用户空间与内核空间,进程上下文与中断上下文[总结]
进程切换
维基百科-文件描述符
Linux 中直接 I/O 机制的介绍
IO – 同步,异步,阻塞,非阻塞 (亡羊补牢篇)
Linux中select poll和epoll的区别
IO多路复用之select总结
IO多路复用之poll总结
IO多路复用之epoll总结

(转载自:https://segmentfault.com/a/1190000003063859,作者:人云思云

spring_mvc_mybatis傻瓜入门篇之spring与mybatis

  • 一.介绍

  • spring
  • 目前编程,一般我们都会在一个框架一下进行编程,而目前J2EE比较流行的框架,SPRING 当居其首。当然我们可以先撇开JAVA,撇开SPRING,一般进行编程都会运用到GOF中设计模式思想使得我们的代码程序可读可扩展解耦等等,那么在设计框架的时候,不管啥语言啥框架,他的中心思想就是屏蔽细节让我们用起来简单方便,实现这个目标有很多方式,那么作为GOD设计模式的延伸,一般的服务框架目前都已经运用IOC->AOP->SOA思想进行框架的设计实现!最基础的是 IOC,在此基础上实现AOP,SOA!关于其中的关系传送门在此!SPIRNG只是众多框架中的一种,但好用方便,所以用户众多!

  • SPRING_MVC

  • MVC是一种3层设计模式,web mvc 顾名思义就是web端的mvc,SPRING_MVC是对MVC的一种web实现!

  • Mybatis

  • mybaits封装了后台与数据库持久层的一个框架!

相互关系

spring作为核心可以自由的与各种持久层框架,web框架组合出不同的架构,如ssh(web框架struct,持久层框架hiberante)等!所以其他都可换,核心 spring 就不要换了,一个好的芯还是很重要的! 今天要入门的就是 spring + spring-mvc+mybatis

二.工具安装

工欲善其事必先利其器,spring 的开发都会有一个 sts的套件,其实 就是帮你安装好方便使用spring 相关插件的 eclipse !那就下载过来安装起来就好了!

三.创建项目

我们项目使用maven 管理,如果你还不知道maven ,那么找wikipedia 详细了解下,如果你只想知道在eclipse下的安装使用,那么这里你可以快速入门。 安装好之后之后,就可以建立maven工程,按说明next你就成功了:

image

通过pom.xml 配置文件引入各种需要的jar包具体看下面各个模块的需要引入!一般我们会引入日志,所以再配置下日志的配置文件如下(log4j.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

	<!-- Appenders -->
	<appender name="console" class="org.apache.log4j.ConsoleAppender">
		<param name="Target" value="System.out" />
		<layout class="org.apache.log4j.PatternLayout">
			<param name="ConversionPattern" value="%-5p: %c - %m%n" />
		</layout>
	</appender>
	
	<!-- Application Loggers -->
	<logger name="com.hundsun.smvc">
		<level value="info" />
	</logger>
	
	<!-- 3rdparty Loggers -->
	<logger name="org.springframework.core">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.beans">
		<level value="info" />
	</logger>
	
	<logger name="org.springframework.context">
		<level value="info" />
	</logger>

	<logger name="org.springframework.web">
		<level value="info" />
	</logger>

	<!-- Root Logger -->
	<root>
		<priority value="warn" />
		<appender-ref ref="console" />
	</root>
	
</log4j:configuration>

四 .spring

spring 框架只需要在 pom.xml上配置引入spring 的jar,如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.hundsun</groupId>
	<artifactId>smvc</artifactId>
	<name>smvc</name>
	<packaging>war</packaging>
	<version>1.0.0-BUILD-SNAPSHOT</version>
	<properties>
		<java-version>1.6</java-version>
		<org.springframework-version>4.2.0.RELEASE</org.springframework-version>
		<org.aspectj-version>1.6.10</org.aspectj-version>
		<org.slf4j-version>1.6.6</org.slf4j-version>
	</properties>
	<dependencies>
	<!-- Spring -->
	<!-- spring核心包 -->  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-core</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-web</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-oxm</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-tx</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-jdbc</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-webmvc</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-aop</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-context-support</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
  
    <dependency>  
        <groupId>org.springframework</groupId>  
        <artifactId>spring-test</artifactId>  
        <version>${org.springframework-version}</version>  
    </dependency>  
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${org.springframework-version}</version>
			<exclusions>
				<!-- Exclude Commons Logging in favor of SLF4j -->
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				 </exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>

	</dependencies>
</project>

五.在Spring中加入mybatis

先在pom.xml配置上mybatis 用到的引入包信息

<!-- jackson 包 -->
    <dependency>  
        <groupId>org.codehaus.jackson</groupId>  
        <artifactId>jackson-mapper-asl</artifactId>  
        <version>1.9.13</version>  
    </dependency>
		
		<!-- mybatis 包 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.2.8</version>
		</dependency>

		<!--mybatis spring 插件 -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>1.2.2</version>
		</dependency>
		
		<!-- mysql连接 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.34</version>
		</dependency>
		
		<!-- dbcp的jar包,用来在applicationContext.xml中配置数据库 -->  
   		<dependency>  
           <groupId>commons-dbcp</groupId>  
           <artifactId>commons-dbcp</artifactId>  
           <version>1.2.2</version>  
   		</dependency>

配置jdbc 配置文件 (jdbc.properties)

url=jdbc:mysql://115.29.151.158:3306/mytrade?useUnicode=true&characterEncoding=utf8 
driver=com.mysql.jdbc.Driver
username=********
password=*******
#定义初始连接数  
initialSize=0 
#定义最大连接数  
maxActive=20  
#定义最大空闲  
maxIdle=20 
#定义最小空闲  
minIdle=1  
#定义最长等待时间  
maxWait=60000  
  • 配置spring 引入mybatis 的配置文件(spring-mybatis.xml)

  • <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"  
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  
        xmlns:context="http://www.springframework.org/schema/context"  
        xmlns:mvc="http://www.springframework.org/schema/mvc"  
        xsi:schemaLocation="http://www.springframework.org/schema/beans    
      http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
      http://www.springframework.org/schema/context    
      http://www.springframework.org/schema/context/spring-context-3.1.xsd    
      http://www.springframework.org/schema/mvc    
      http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">  
      
        <!-- 自动扫描 -->  
        <context:component-scan base-package="com.test" />  
      
        <!-- 引入配置文件 -->  
        <bean id="propertyConfigurer"  
            class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
            <property name="location" value="classpath:jdbc.properties" />  
        </bean>  
      
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"  
            destroy-method="close">  
            <property name="driverClassName" value="${driver}" />  
            <property name="url" value="${url}" />  
            <property name="username" value="${username}" />  
            <property name="password" value="${password}" />  
            <!-- 初始化连接大小 -->  
            <property name="initialSize" value="${initialSize}"></property>  
            <!-- 连接池最大数量 -->  
            <property name="maxActive" value="${maxActive}"></property>  
            <!-- 连接池最大空闲 -->  
            <property name="maxIdle" value="${maxIdle}"></property>  
            <!-- 连接池最小空闲 -->  
            <property name="minIdle" value="${minIdle}"></property>  
            <!-- 获取连接最大等待时间 -->  
            <property name="maxWait" value="${maxWait}"></property>  
        </bean>  
      
        <!-- Spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->  
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
            <property name="dataSource" ref="dataSource" />  
            <!-- 自动扫描mapping.xml文件 -->  
            <property name="mapperLocations" value="classpath:com/test/mapping/*.xml"></property>  
        </bean>  
      
        <!-- DAO接口所在包名,Spring会自动查找其下的类 -->  
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
            <property name="basePackage" value="com.test.Dao" />  
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>  
        </bean>  
      
        <!-- 事务管理 -->  
        <bean id="transactionManager"  
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">  
            <property name="dataSource" ref="dataSource" />  
        </bean>  
      
    </beans>  
  • 将该配置文件添加到web.xml中
    	<!-- The definition of the Root Spring Container shared by all Servlets and Filters -->
    	<context-param>
    		<param-name>contextConfigLocation</param-name>
    		<param-value>
    		/WEB-INF/spring/root-context.xml;
    		classpath:spring-mybatis.xml;
    		</param-value>
    到这里,我们需要的包都已经添加好了,然后我们就需要测试下我们的spring 通过mybatis与mysql 数据库是不是通的,当然我们得先把数据库,表都准备好了,这个就不说了!接下来你们可能觉得需要写代码,写一些model层出来通过mybatis与mysql进行联通了?不需要,我们只要借助一个mybatis中的神器 mybatis-generator 用来自动生成代码,当然你需要把mysql 的数据连接地址,需要生成的model 的表等一些信息配置起来!具体还是看前面链接里面的教程吧,就不重复说了:自动生成以下类:
    package com.test.Dao;
    
    import com.test.model.UserInfo;
    
    public interface UserInfoMapper {
        int deleteByPrimaryKey(Long positionInt);
    
        int insert(UserInfo record);
    
        int insertSelective(UserInfo record);
    
        UserInfo selectByPrimaryKey(Long positionInt);
    
        int updateByPrimaryKeySelective(UserInfo record);
    
        int updateByPrimaryKey(UserInfo record);
    }
                      然后把接口及服务层实现写出来,就写个简单的获取用户信息吧:

                      image

                      package com.test.service;
                      
                      import com.test.model.UserInfo;
                      
                      public interface IUserInfoService {
                      	public UserInfo getUserInfoById(long pos_int);
                      }
                      
                      package com.test.service.impl;
                      
                      import javax.annotation.Resource;  
                      import org.springframework.stereotype.Service;  
                      import com.test.model.UserInfo;  
                      import com.test.Dao.UserInfoMapper;  
                      import com.test.service.IUserInfoService; 
                      
                      @Service("userinfoService") 
                      public class UserInfoServiceImpl implements IUserInfoService {
                      	@Resource  
                          private UserInfoMapper userinfoDao;  
                      	
                      	@Override  
                          public UserInfo getUserInfoById(long pos_int) {  
                              return this.userinfoDao.selectByPrimaryKey(pos_int);  
                          }  
                      }
                      

                      然后写个 jtest:

                      package smvc;
                      
                      import javax.annotation.Resource;  
                      import org.apache.log4j.Logger;  
                      import org.junit.Test;  
                      import org.junit.runner.RunWith;  
                      import org.springframework.test.context.ContextConfiguration;  
                      import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;  
                      import com.alibaba.fastjson.JSON;  
                      import com.test.model.UserInfo;  
                      import com.test.service.IUserInfoService;  
                      
                      
                      @RunWith(SpringJUnit4ClassRunner.class)  
                      @ContextConfiguration(locations = { "classpath:spring-mybatis.xml" })  
                      public class TestMyBatis {
                      	private static Logger logger = Logger.getLogger(TestMyBatis.class);  
                      	
                      	 @Resource  
                      	 private IUserInfoService userinfoService = null;  
                      	 
                      	 @Test  
                      	    public void test1() {  
                      	        UserInfo userinfo = userinfoService.getUserInfoById((long) 1);  
                      	        logger.info(JSON.toJSONString(userinfo));  
                      	    }  
                      }

                      运行下,我们就可以愉快的得到数据了!

                      FTP服务器及账号访问设置

                      背景

                      最近做个项目与其他系统对接,接口数据他们非要以文件格式获取,好吧,

                      按照他们的需求要弄个FTP服务器,然后他们的系统还卖了好多地方(好有钱),

                      然后他们都要来取数据,有些数据公用的,有些数据还是私有的,好吧还要整好多

                      账号起来!

                      安装

                      FTP服务器是部署在linux系统上的,所以很自然就想到了用vsftpd,其他都不多说了,

                      直接把FTP服务安装起来再说了!安装还是直接使用yum(真神器!):

                      yum install vsftpd

                      就这么简单,然后啥都装好了!然后就启动服务就可以了:

                      service vsftpd start

                      配置

                      vsftpd.conf

                      vsftpd.conf 是vsftpd的配置文件,用来控制vsftpd的各项功能。默认状态下,它的位置

                      是/etc/vsftpd.conf或者在/etc/vsftpd/vsftpd.conf。里面的配置项的含义还是需要了解一番,

                      这里我就不多做介绍,你们直接传送门走起!

                      虚拟用户

                      看完上面的配置,那么我们就来进行本次重点内容虚拟用户的配置过程:

                      基本思路就是先创建一个用户,然后在这个用户下面挂多个虚拟的用户

                      进行!

                      mysql编译安装

                      以前的项目都用Oracle,新项目准备使用Mysql数据库,所以就先装个玩一下,把安装过程记录一下,当 是学习mysql 的开篇了!mysql是开源的,作为一只有那么点GEEK情结的超猿类,除了安装外,还是要编译一把才过瘾的!

                      编译

                      • 基础工具准备:

                        要编译,首先需要准备好编译的环境工具,此次我的linux环境还是CentOS6.5版本,通过以下脚本安装好最新的编译工具与库文件:

                      [root@iZ28gxqlqfsZ ~]# yum install gcc gcc-c++ ncurses-devel perl&lt;

                      同时我们需要安装cmake,我们使用当前最新的稳定版本V3.2,命令如下:

                      [root@iZ28gxqlqfsZ ~]# wget http://www.cmake.org/files/v3.2/cmake-3.2.0.tar.gz 
                      [root@iZ28gxqlqfsZ ~]# tar -xzvf cmake-3.2.0.tar.gz 
                      [root@iZ28gxqlqfsZ ~]# cd cmake-3.2.0
                      • 源码准备

                      正常进入mysql官网下载页面,找不到源码的下载按钮(听说是中国被墙),进入如下页面后选择一个国家的FTP资源去下载,我选择了鸟国的!

                      http://dev.mysql.com/downloads/mirrors.html
                      [root@iZ28gxqlqfsZ ~]# wgetftp:ftp://ftp.jaist.ac.jp/pub/mysql/Downloads/MySQL-5.5/mysql-5.5.44.tar.gz
                      [root@iZ28gxqlqfsZ ~]# tar -zxv -f mysql-5.5.44.tar.gz
                      • 用户环境准备:

                      数据库用户组与用户名

                      [root@iZ28gxqlqfsZ ~]# groupadd mysql
                      [root@iZ28gxqlqfsZ ~]# useradd -r -g mysql mysql

                      mysql安装目录

                      [root@iZ28gxqlqfsZ local]# mkdir <span style="color: #ff0000;">/usr/local/mysql</span>
                      [root@iZ28gxqlqfsZ local]# cd /usr/local/mysql
                      [root@iZ28gxqlqfsZ mysql]# chown -R mysql:mysql .
                      

                      mysql数据库文件目录

                      [root@iZ28gxqlqfsZ ~]# mkdir <span style="color: #ff0000;">/data/mysql</span>
                      [root@iZ28gxqlqfsZ ~]# cd /data/mysql
                      [root@iZ28gxqlqfsZ ~]# chown -R mysql:mysql .
                      

                      环境变形执行路径更改

                      vim /etc/profile 
                      #mysql path
                      PATH=/usr/local/mysql/bin:/usr/local/mysql/lib:$PATH
                      export PATH
                      
                      source /etc/profile

                      设置源码编译配置脚本:

                      [root@iZ28gxqlqfsZ mysql-5.5.44]# cmake \
                      > -DCMAKE_INSTALL_PREFIX=/usr/local/mysql \
                      > -DDEFAULT_CHARSET=utf8 \
                      > -DDEFAULT_COLLATION=utf8_general_ci \
                      > -DWITH_INNOBASE_STORAGE_ENGINE=1 \
                      > -DWITH_ARCHIVE_STORAGE_ENGINE=1 \
                      > -DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
                      > -DMYSQL_DATADIR=/data/mysql \
                      > -DMYSQL_TCP_PORT=3306 \
                      > -DEXTRA_CHARSETS=all
                      • 编译mysql

                      删除CMakeCache.txt,后重新编译:

                      [root@iZ28gxqlqfsZ mysql-5.5.44]# rm CMakeCache.txt
                      [root@iZ28gxqlqfsZ mysql-5.5.44]# make
                      • 安装MYSQL:
                      [root@iZ28gxqlqfsZ mysql-5.5.44]# make install

                      到这一步mysql 数据库就已经编译安装好了!

                      初始化

                      • 初始化数据库
                      [root@iZ28gxqlqfsZ ~]# cd /usr/local/mysql
                      [root@iZ28gxqlqfsZ ~]# scripts/mysql_install_db --user=mysql --datadir=/data/mysql

                      配置文件修改

                      [root@iZ28gxqlqfsZ mysql]# cp /usr/local/mysql/support-files/my-medium.cnf /etc/my.cnf
                      [root@iZ28gxqlqfsZ mysql]# cp support-files/mysql.server /etc/init.d/mysqld
                      [root@iZ28gxqlqfsZ mysql]# cp /usr/local/mysql/support-files/my-medium.cnf  /usr/local/mysql/my.cnf
                      

                      修改启动参数:
                      my.cnf 配置的[mysqld]项新增两个目录配置

                      [root@iZ28gxqlqfsZ mysql]# vi /usr/local/mysql/my.cnf
                      
                      [mysqld]
                      ...
                      basedir = /usr/local/mysql
                      datadir = /data/mysql
                      
                      • 启动数据库

                      启动数据库并让数据库开机自动启动

                      [root@iZ28gxqlqfsZ ~]# /usr/local/mysql/scripts/mysql_install_db --user=mysql --datadir=/data/mysql

                      [root@iZ28gxqlqfsZ mysql]# chkconfig –level 35 mysqld on

                      检查mysql服务是否启动:查看3306端口是否由mysqld 进程正在监听,因为没有设置密码,直接打mysql 命令进入看是否能进入root用户

                      [root@iZ28gxqlqfsZ mysql]# netstat -tulnp | grep 3306
                      [root@iZ28gxqlqfsZ mysql]# mysql

                      到此,mysql 数据库已经编译安装完成!小伙伴可以愉快进去玩耍了!当然mysql 数据库跟Oracle 数据库的上层的使用基本差不多,只是有些细微的结构上的差别,下面就列出最基本的操作!

                       

                      mysql5.6安装

                      RPM方式安装MySQL5.6

                      a. 检查MySQL及相关RPM包,是否安装,如果有安装,则移除(rpm –e 名称)

                      1
                      [root@localhost ~]# rpm -qa | grep -i mysql

                      2
                      mysql-libs-5.1.66-2.el6_3.x86_64

                      3
                      [root@localhost ~]# yum -y remove mysql-libs*

                      b. 下载Linux对应的RPM包,如:CentOS6.4_64对应的RPM包,如下:

                      1
                      [root@localhost rpm]# ll

                      2
                      total 74364

                      3
                      -rw-r--r--. 1 root root 18442536 Dec 11 20:19 MySQL-client-5.6.15-1.el6.x86_64.rpm

                      4
                      -rw-r--r--. 1 root root  3340660 Dec 11 20:06 MySQL-devel-5.6.15-1.el6.x86_64.rpm

                      5
                      -rw-r--r--. 1 root root 54360600 Dec 11 20:03 MySQL-server-5.6.15-1.el6.x86_64.rpm

                      c. 安装MySQL

                      1
                      [root@localhost rpm]# rpm -ivh MySQL-server-5.6.15-1.el6.x86_64.rpm

                      2
                      [root@localhost rpm]# rpm -ivh MySQL-devel-5.6.15-1.el6.x86_64.rpm

                      3
                      [root@localhost rpm]# rpm -ivh MySQL-client-5.6.15-1.el6.x86_64.rpm

                      4
                      #修改配置文件位置

                      5
                      [root@localhost rpm]# cp /usr/share/mysql/my-default.cnf /etc/my.cnf

                      d. 初始化MySQL及设置密码

                      1
                      [root@localhost rpm]# /usr/bin/mysql_install_db

                      2
                      [root@localhost rpm]# service mysql start

                      3
                      [root@localhost rpm]# cat /root/.mysql_secret  #查看root账号密码

                      4
                      # The random password set for the root user at Wed Dec 11 23:32:50 2013 (local time): qKTaFZnl

                      5
                      [root@localhost ~]# mysql -uroot –pqKTaFZnl

                      6
                      mysql> SET PASSWORD = PASSWORD('123456');    #设置密码为123456

                      7
                      mysql> exit

                      8
                      [root@localhost ~]# mysql -uroot -p123456

                      e. 允许远程登陆

                      01
                      mysql> use mysql;

                      02
                      mysql> select host,user,password from user;

                      03
                      +-----------------------+------+-------------------------------------------+

                      04
                      | host                  | user | password                                  |

                      05
                      +-----------------------+------+-------------------------------------------+

                      06
                      | localhost             | root | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 |

                      07
                      | localhost.localdomain | root | *1237E2CE819C427B0D8174456DD83C47480D37E8 |

                      08
                      | 127.0.0.1             | root | *1237E2CE819C427B0D8174456DD83C47480D37E8 |

                      09
                      | ::1                   | root | *1237E2CE819C427B0D8174456DD83C47480D37E8 |

                      10
                      +-----------------------+------+-------------------------------------------+

                      11

                      12
                      mysql> update user set password=password('123456') where user='root';

                      13
                      mysql> update user set host='%' where user='root' and host='localhost';

                      14
                      mysql> flush privileges;

                      15
                      mysql> exit

                      f. 设置开机自启动

                      1
                      [root@localhost ~]# chkconfig mysql on

                      2
                      [root@localhost ~]# chkconfig --list | grep mysql

                      3
                      mysql           0:off   1:off   2:on    3:on    4:on    5:on    6:off

                      g. MySQL的默认安装位置

                      1
                      /var/lib/mysql/               #数据库目录

                      2
                      /usr/share/mysql              #配置文件目录

                      3
                      /usr/bin                     #相关命令目录

                      4
                      /etc/init.d/mysql              #启动脚本

                      修改字符集和数据存储路径

                      配置/etc/my.cnf文件,修改数据存放路径、mysql.sock路径以及默认编码utf-8.

                      view plain copy

                      在CODE上查看代码片派生到我的代码片

                      1. [client]  
                      2. password        = 123456  
                      3. port            = 3306  
                      4. default-character-set=utf8
                      5. [mysqld]  
                      6. port            = 3306  
                      7. character_set_server=utf8
                      8. character_set_client=utf8
                      9. collation-server=utf8_general_ci
                      10. #(注意linux下mysql安装完后是默认:表名区分大小写,列名不区分大小写; 0:区分大小写,1:不区分大小写)  
                      11. lower_case_table_names=1
                      12. #(设置最大连接数,默认为 151,MySQL服务器允许的最大连接数16384; )  
                      13. max_connections=1000
                      14. [mysql]  
                      15. default-character-set = utf8

                      查看字符集

                      Better understanding Linux secondary dependencies solving with examples

                      除了man 资料外觉得是挺详细的资料,收藏!

                      http://www.kaizou.org/2015/01/linux-libraries/

                      08 Jan 2015 by David Corvoysier

                      A few months ago I stumbled upon a linking problem with secondary dependencies I couldn’t solved without overlinking the corresponding libraries.

                      I only realized today in a discussion with my friend Yann E. Morin that not only did I use the wrong solution for that particular problem, but that my understanding of the gcc linking process was not as good as I had imagined.

                      This blog post is to summarize what I have now understood.

                      There is also a small repository on github with the mentioned samples.

                      A few words about Linux libraries

                      This paragraph is only a brief summary of what is very well described in The Linux Documentation Project library howto.

                      Man pages for the linux linker and loader are also a good source of information.

                      There are three kind of libraries in Linux: static, shared and dynamically loaded (DL).

                      Dynamically loaded libraries are very specific to some use cases like plugins, and would deserve an article on their own. I will only focus here on static and shared libraries.

                      Static libraries

                      A static library is simply an archive of object files conventionally starting with thelib prefix and ending with the .a suffix.

                      Example:

                      libfoobar.a

                      Static libraries are created using the ar program:

                      $ ar rcs libfoobar.a foo.o bar.o

                      Linking a program with a static library is as simple as adding it to the link command either directly with its full path:

                      $ gcc -o app main.c /path/to/foobar/libfoobar.a

                      or indirectly using the -l/L options:

                      $ gcc -o app main.c -lfoobar -L/path/to/foobar

                      Shared libraries

                      A shared library is an ELF object loaded by programs when they start.

                      Shared libraries follow the same naming conventions as static libraries, but with the .sosuffix instead of .a.

                      Example:

                      libfoobar.so

                      Shared library objects need to be compiled with the -fPIC option that produces position-independent code, ie code that can be relocated in memory.

                      $ gcc -fPIC -c foo.c
                      $ gcc -fPIC -c bar.c

                      The gcc command to create a shared library is similar to the one used to create a program, with the addition of the -shared option.

                      $ gcc -shared -o libfoobar.so foo.o bar.o

                      Linking against a shared library is achieved using the exact same commands as linking against a static library:

                      $ gcc -o app main.c libfoobar.so

                      or

                      $ gcc -o app main.c -lfoobar -L/path/to/foobar

                      Shared libraries and undefined symbols

                      An ELF object maintains a table of all the symbols it uses, including symbols belonging to another ELF object that are marked as undefined.

                      At compilation time, the linker will try to resolve an undefined symbol by linking it either statically to code included in the overall output ELF object or dynamically to code provided by a shared library.

                      If an undefined symbol is found in a shared library, a DT_NEEDED entry is created for that library in the output ELF target.

                      The content of the DT_NEEDED field depends on the link command: – the full path to the library if the library was linked with an absolute path, – the library name otherwise (or the library soname if it was defined).

                      You can check the dependencies of an ELF object using the readelf command:

                      $ readelf -d main

                      or

                      $ readelf -d libbar.so

                      When producing an executable a symbol that remains undefined after the link will raise an error: all dependencies must therefore be available to the linker in order to produce the output binary.

                      For historic reason, this behavior is disabled when building a shared library: you need to specify the --no-undefined (or -z defs) flag explicitly if you want errors to be raised when an undefined symbol is not resolved.

                      $ gcc -Wl,--no-undefined -shared -o libbar.so -fPIC bar.c

                      or

                      $ gcc -Wl,--no-undefined -shared -o libbar.so -fPIC bar.c

                      Note that when producing a static library, which is just an archive of object files, no actual ‘linking’ operation is performed, and undefined symbols are kept unchanged.

                      Library versioning and compatibility

                      Several versions of the same library can coexist in the system.

                      By conventions, two versions of the same library will use the same library name with a different version suffix that is composed of three numbers:

                      • major revision,
                      • minor revision,
                      • build revision.

                      Example:

                      libfoobar.so.1.2.3

                      This is often referred as the library real name.

                      Also by convention, the library major version should be modified every time the library binary interface (ABI) is modified.

                      Following that convention, an executable compiled with a shared library version is theoretically able to link with another version of the same major revision.

                      This concept if so fundamental for expressing compatibility between programs and shared libraries that each shared library can be associated a soname, which is the library name followed by a period and the major revision:

                      Example:

                      libfoobar.so.1

                      The library soname is stored in the DT_SONAME field of the ELF shared object.

                      The soname has to be passed as a linker option to gcc.

                      $ gcc -shared -Wl,-soname,libfoobar.so.1 -o libfoobar.so foo.o bar.o

                      As mentioned before, whenever a library defines a soname, it is that soname that is stored in the DT_NEEDED field of ELF objects linked against that library.

                      Solving versioned libraries dependencies at build time

                      As mentioned before, libraries to be linked against can be specified using a shortened name and a path:

                      $ gcc -o app main.c -lfoobar -L/path/to/foobar

                      When installing a library, the installer program will typically create a symbolic link from the library real name to its linker name to allow the linker to find the actual library file.

                      Example:

                      /usr/lib/libfoobar.so -> libfoobar.so.1.5.3

                      The linker uses the following search paths to locate required shared libraries:

                      • directories specified by -rpath-link options (more on that later)
                      • directories specified by -rpath options (more on that later)
                      • directories specified by the environment variable LD_RUN_PATH
                      • directories specified by the environment variable LD_LIBRARY_PATH
                      • directories specified in DT_RUNPATH or DT_RPATH of a shared library are searched for shared libraries needed by it
                      • default directories, normally /lib and /usr/lib
                      • directories listed inthe /etc/ld.so.conf file

                      Solving versioned shared libraries dependencies at runtime

                      On GNU glibc-based systems, including all Linux systems, starting up an ELF binary executable automatically causes the program loader to be loaded and run.

                      On Linux systems, this loader is named /lib/ld-linux.so.X (where X is a version number). This loader, in turn, finds and loads recursively all other shared libraries listed in theDT_NEEDED fields of the ELF binary.

                      Please note that if a soname was specified for a library when the executable was compiled, the loader will look for the soname instead of the library real name. For that reason, installation tools automatically create symbolic names from the library soname to its real name.

                      Example:

                      /usr/lib/libfoobar.so.1 -> libfoobar.so.1.5.3

                      When looking fo a specific library, if the value described in the DT_NEEDED doesn’t contain a /, the loader will consecutively look in:

                      • directories specified at compilation time in the ELF object DT_RPATH (deprecated),
                      • directories specified using the environment variable LD_LIBRARY_PATH,
                      • directories specified at compile time in the ELF object DT_RUNPATH,
                      • from the cache file /etc/ld.so.cache, which contains a compiled list of candidate libraries previously found in the augmented library path (can be disabled at compilation time),
                      • in the default path /lib, and then /usr/lib (can be disabled at compilation time).

                      Proper handling of secondary dependencies

                      As mentioned in the introduction, my issue was related to secondary dependencies, ie shared libraries dependencies that are exported from one library to a target.

                      Let’s imagine for instance a program main that depends on a library libbar that itself depends on a shared library libfoo.

                      We will use either a static libbar.a or a shared libbar.so.

                      foo.c

                      int foo()
                      {
                          return 42;
                      }

                      bar.c

                      int foo();
                      
                      int bar()
                      {
                          return foo();
                      }

                      main.c

                      int bar();
                      
                      int main(int argc, char** argv)
                      {
                          return bar();
                      }

                      Creating the libfoo.so shared library

                      libfoo has no dependencies but the libc, so we can create it with the simplest command:

                      $ gcc -shared -o libfoo.so -fPIC foo.c

                      Creating the libbar.a static library

                      As said before, static libraries are just archives of object files, without any means to declare external dependencies.

                      In our case, there is therefore no explicit connection whatsoever between libbar.a and libfoo.so.

                      $ gcc -c bar.c
                      $ ar rcs libbar.a bar.o

                      Creating the libbar.so dynamic library

                      The proper way to create the libbar.so shared library it by explicitly specifying it depends on libfoo:

                      $ gcc -shared -o libbar2.so -fPIC bar.c -lfoo -L$(pwd)

                      This will create the library with a proper DT_NEEDED entry for libfoo.

                      $ readelf -d libbar.so
                      Dynamic section at offset 0xe08 contains 25 entries:
                        Tag        Type                         Name/Value
                       0x0000000000000001 (NEEDED)             Shared library: [libfoo.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
                      ...

                      However, since undefined symbols are not by default resolved when building a shared library, we can also create a “dumb” version without any DT_NEEDED entry:

                      $ gcc -shared -o libbar_dumb.so -fPIC bar.c

                      Note that it is very unlikely that someone actually chooses to create such an incomplete library on purpose, but it may happen that by misfortune you encounter one of these beasts in binary form and still need to link against it (yeah, sh… happens !).

                      Creating the main executable

                      Linking against the libbar.a static library

                      As mentioned before, when linking an executable, the linker must resolve all undefined symbols before producing the output binary.

                      Trying to link only with libbar.a produces an error, since it has an undefined symbol and the linker has no clue where to find it:

                      $ gcc -o app_s main.c libbar.a
                      libbar.a(bar.o): In function `bar':
                      bar.c:(.text+0xa): undefined reference to `foo'
                      collect2: error: ld returned 1 exit status

                      Adding libfoo.so to the link command solves the problem:

                      $ gcc -o app main.c libbar.a -L$(pwd) -lfoo

                      You can verify that the app binary now explicitly depends on libfoo:

                      $ readelf -d app
                      Dynamic section at offset 0xe18 contains 25 entries:
                        Tag        Type                         Name/Value
                       0x0000000000000001 (NEEDED)             Shared library: [libfoo.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
                      ...

                      At run-time, the dynamic linker will look for libfoo.so, so unless you have installed it in standard directories (/lib or /usr/lib) you need to tell it where it is:

                      LD_LIBRARY_PATH=$(pwd) ./app

                      To summarize, when linking an executable against a static library, you need to specify explicitly all dependencies towards shared libraries introduced by the static library on the link command.

                      Note however that expressing, discovering and adding implicit static libraries dependencies is typically a feature of your build system (autotools, cmake).

                      Linking against the libbar.so shared library

                      As specified in the linker documentation, when the linker encounters an input shared library it processes all its DT_NEEDED entries as secondary dependencies:

                      • if the linker output is a shared relocatable ELF object (ie a shared library), it will add all DT_NEEDED entries from the input library as new DT_NEEDED entries in the output,
                      • if the linker ouput is a non-shared, non-relocatable link (our case), it will automatically add the libraries listed in the DT_NEEDED of the input library on the link command line, producing an error if it can’t locate them.

                      So, let’s see what happens when dealing with our two shared libraries.

                      Linking against the “dumb” library

                      When trying to link an executable against the “dumb” version of libbar.so, the linker encounters undefined symbols in the library itself it cannot resolve since it lacks theDT_NEEDED entry related to libfoo:

                      $ gcc -o app main.c -L$(pwd) -lbar_dumb
                      libbar_dumb.so: undefined reference to `foo'
                      collect2: error: ld returned 1 exit status

                      Let’s see how we can solve this.

                      Adding explicitly the libfoo.so dependency

                      Just like we did when we linked against the static version, we can just add libfoo to the link command to solve the problem:

                      $ gcc -o app main.c -L$(pwd) -lbar_dumb -lfoo

                      It creates an explicit dependency in the app binary:

                      $ readelf -d app
                      Dynamic section at offset 0xe18 contains 25 entries:
                        Tag        Type                         Name/Value
                       0x0000000000000001 (NEEDED)             Shared library: [libbar_dumb.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libfoo.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
                      ...

                      Again, at runtime you may need to tell the dynamic linker where libfoo.so is:

                      $ LD_LIBRARY_PATH=$(pwd) ./app

                      Note that having an explicit dependency to libfoo is not quite right, since our application doesn’t use directly any symbols from libfoo. What we’ve just done here is called overlinking, and it is BAD.

                      Let’s imagine for instance that in the future we decide to provide a newer version oflibbar that uses the same ABI, but based on a new version of libfoo with a different ABI: we should theoretically be able to use that new version of libbar without recompiling our application, but what would really happen here is that the dynamic linker would actually try to load the two versions of libfoo at the same time, leading to unpredictable results. We would therefore need to recompile our application even if it is still compatible with the newest libbar.

                      As a matter of fact, this actually happened in the past: a libfreetype update in the debian distro caused 583 packages to be recompiled, with only 178 of them actually using it.

                      Ignoring libfoo dependency

                      There is another option you can use when dealing with the “dumb” library: tell the linker to ignore its undefined symbols altogether:

                      $ gcc -o app main.c -L$(pwd) -lbar_dumb -Wl,--allow-shlib-undefined

                      This will produce a binary that doesn’t declare its hidden dependencies towards libfoo:

                      $ readelf -d app
                      Dynamic section at offset 0xe18 contains 25 entries:
                        Tag        Type                         Name/Value
                       0x0000000000000001 (NEEDED)             Shared library: [libbar_dumb.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
                      ...

                      This isn’t without consequences at runtime though, since the dynamic linker is now unable to resolve the executable dependencies:

                      $ ./app: symbol lookup error: ./libbar_dumb.so: undefined symbol: foo

                      Your only option is then to load libfoo explicitly (yes, this is getting uglier and uglier):

                      $ LD_PRELOAD=$(pwd)/libfoo.so LD_LIBRARY_PATH=$(pwd) ./app
                      Linking against the “correct” library
                      Doing it the right way

                      As mentioned before, when linking against the correct shared library, the linker encounters the libfoo.so DT_NEEDED entry, adds it to the link command and finds it at the path specified by -L, thus solving the undefined symbols … or at least that is what I expected:

                      $ gcc -o app main.c -L$(pwd) -lbar
                      /usr/bin/ld: warning: libfoo.so, needed by libbar.so, not found (try using -rpath or -rpath-link)
                      /home/diec7483/dev/linker-example/libbar.so: undefined reference to `foo'
                      collect2: error: ld returned 1 exit status

                      Why the error ? I thought I had done everything by the book !

                      Okay, let’s take a look at the ld man page again, looking at the -rpath-link option. This says:

                      When using ELF or SunOS, one shared library may require another. This happens when an “ld -shared” link includes a shared library as one of the input files. When the linker encounters such a dependency when doing a non-shared, non-relocatable link, it will automatically try to locate the required shared library and include it in the link, if it is not included explicitly. In such a case, the -rpath-link option specifies the first set of directories to search. The -rpath-link option may specify a sequence of directory names either by specifying a list of names separated by colons, or by appearing multiple times.

                      Ok, this is not crystal-clear, but what it actually means is that when specifying the path for a secondary dependency, you should not use -L but -rpath-link:

                      $ gcc -o app main.c -L$(pwd) -lbar -Wl,-rpath-link=$(pwd)

                      You can now verify that app depends only on libbar:

                      $ readelf -d app
                      Dynamic section at offset 0xe18 contains 25 entries:
                        Tag        Type                         Name/Value
                       0x0000000000000001 (NEEDED)             Shared library: [libbar.so]
                       0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
                      ...

                      And this is finally how things should be done.

                      You may also use -rpath instead of -rpath-link but in that case the specified path will be stored in the resulting executable, which is not suitable if you plan to relocate your binaries. Tools like cmake use the -rpath during the build phase (make), but remove the specified path from the executable during the installation phase(make install).

                      Conclusion

                      To summarize, when linking an executable against:

                      • a static library, you need to specify all dependencies towards other shared libraries this static library depends on explicitly on the link command.

                      • a shared library, you don’t need to specify dependencies towards other shared libraries this shared library depends on, but you may need to specify the path to these libraries on the link command using the -rpath/-rpath-link options.

                      Note however that expressing, discovering and adding implicit libraries dependencies is typically a feature of your build system (autotools, cmake), as demonstrated in my samples.

                      Memcache初探(二)分布式集群

                      要探讨memcache 的集群,那么就要先理解其特征,伪分布式。

                      这个要作两层理解:1.服务端数据可以进行分布式部署,但是需要依靠客户端的将数据根据算法进行分布式存储。

                      2.伪,上面的分布式需要依靠客户端实现,这是它第一个伪,还一个是 memcache 本身做不到可靠性,因为服务端相互之间不通信,不能同步数据,所以如果某一点挂了,就该缓存就失效了,并且如果客户端的算法是直接HASH的话,可能还会 影响后续缓存的命中。

                      magent

                      如果让客户端去实现分布式,我觉得这个会麻烦,同时而magent 这个代理就解决了memcashe上述的不足,客户端就只需要存入缓存,取缓存就够了,把一切什么麻烦的分布式,数据同步,可靠性,可用性全都交给magent 。所以一般我们需要集群方式使用memcache 的时候,一般都会增加magent 这个代理节点。当然客户端是少不了,既然提到了那也就讲一下,一般的使用memcache 的流程:

                      客户端(我们的应用服务) 通过memcache 提供的客户端(SDK)与memcache服务端连接,进行缓存数据的读写操作,如果需要集群,那么我们一般连接到magent,通过magent进行缓存数据读写,具体读写到哪个memcache节点,magent替我们做主,可靠性,可用性等也通过magent帮我们解决,其分布式使用.

                      magent wiki 给我们的简单示意图,这样memcached 就有主备,可以解决单点问题。

                      magent is a proxy server sitting between memcache clients(such as php programs) and memcached servers. magent have several advantages over direct connections
                      
                                      Client
                                        |
                                      Magent
                                        |
                          |--|--|-------------------|--|--|
                          |  |  |                   |  |  |
                        Normal Servers Farm      Backup Servers Farm
                      magent安装

                      1.mkdir magent 
                      2.cd magent/ 
                      3.wget http://memagent.googlecode.com/files/magent-0.5.tar.gz
                      4.tar zxvf magent-0.5.tar.gz 
                      5./sbin/ldconfig 
                      6.sed -i “s#LIBS = -levent#LIBS = -levent -lm#g” Makefile 
                      7.make 
                      8.cp magent /usr/bin/magent 
                      9.cd ../

                      一步步来,当然安装还是要有root 权限,有啥问题就google 吧,貌似ketama.h

                      的头文件需要添加以下预定义宏

                      #ifndef SSIZE_MAX  
                      #define SSIZE_MAX      32767  
                      #endif

                      算上前面一篇这样子我们就安装好了magent,memcached。我们就来个最简单的集群吧:

                      1.启动memcashe与magent

                      memcached -m 1 -u weekcashe -d -l 127.0.0.1 -p 11222
                      
                      memcached -m 1 -u weekcashe -d -l 127.0.0.1 -p 11223
                      
                      
                      memcached -m 1 -u weekcashe -d -l 127.0.0.1 -p 11224
                      magent -u weekcashe -n 51200 -l 127.0.0.1 -p 10000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                      
                      magent -u weekcashe -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                      

                      2.可用性测试

                      [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 10000 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. set key 0 0 8 888888 STORED quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 11222 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 11223 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. get key END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 11224 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host. [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 11000 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. get key VALUE key 0 8 888888 END quit Connection closed by foreign host.

                      3.可靠性测试

                      [weekcashe@iZ28gxqlqfsZ ~]$ ps x
                        PID TTY      STAT   TIME COMMAND
                       1600 ?        Ssl    0:00 memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/memcached.pid
                       1615 ?        Ssl    0:00 memcached -d -u weekcashe -p 11223 -c 256 -P /tmp/memcached.pid
                       1630 ?        Ssl    0:00 memcached -d -u weekcashe -p 11224 -c 256 -P /tmp/memcached.pid
                       1658 ?        Ss     0:00 magent -u weekcashe -n 51200 -l 127.0.0.1 -p 10000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                       1660 ?        Ss     0:00 magent -u weekcashe -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                       1726 pts/2    R+     0:00 ps x
                      [weekcashe@iZ28gxqlqfsZ ~]$ kill 1600
                      [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 11222
                      Trying 127.0.0.1...
                      telnet: connect to address 127.0.0.1: Connection refused
                      
                      [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 10000
                      Trying 127.0.0.1...
                      Connected to 127.0.0.1.
                      Escape character is '^]'.
                      get key
                      VALUE key 0 8
                      
                      888888
                      END
                      quit
                      Connection closed by foreign host.
                      
                      [weekcashe@iZ28gxqlqfsZ ~]$ memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/memcached.pid
                      [weekcashe@iZ28gxqlqfsZ ~]$ ps x
                        PID TTY      STAT   TIME COMMAND
                       1365 ?        S      0:00 sshd: weekcashe@pts/0
                       1366 pts/0    Ss     0:00 -bash
                       1487 ?        S      0:00 sshd: weekcashe@pts/2
                       1488 pts/2    Ss     0:00 -bash
                       1615 ?        Ssl    0:00 memcached -d -u weekcashe -p 11223 -c 256 -P /tmp/memcached.pid
                       1630 ?        Ssl    0:00 memcached -d -u weekcashe -p 11224 -c 256 -P /tmp/memcached.pid
                       1658 ?        Ss     0:00 magent -u weekcashe -n 51200 -l 127.0.0.1 -p 10000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                       1660 ?        Ss     0:00 magent -u weekcashe -n 51200 -l 127.0.0.1 -p 11000 -s 127.0.0.1:11222 -s 127.0.0.1:11223 -b 127.0.0.1:11224
                       1730 ?        Ssl    0:00 memcached -d -u weekcashe -p 11222 -c 256 -P /tmp/memcached.pid
                       1744 pts/2    R+     0:00 ps x
                      
                      [weekcashe@iZ28gxqlqfsZ ~]$ telnet 127.0.0.1 10000
                      Trying 127.0.0.1...
                      Connected to 127.0.0.1.
                      Escape character is '^]'.
                      get key
                      END

                      我可以看出,当11222挂掉的时候,magent依然可以从备机器中取到缓存数据。

                      但是当11222被重新启动后,因为数据丢失,但是magent 未更新hash 对,依然命中到 11222去取数据,导致取不到数据。备机数据失效。这个问题留待解决。

                      你好通过 magent 端口 10000 写入的数据存储在了 memcashe 端口11222及备份memcashe 11224上,并且可以在 magent 11000 上获取到。并且设置获取数据magent 与直接在memcashe 端口上的接口是一致的。

                      OK,现在后台模拟集群的memcashe环境已经搭建完毕,通过magent 也已经解决了伪分布式带来的一些麻烦,那么接下就可以直接开始使用用客户端接口进行实际编码工作了。

                      xmemcached 客户端SDK使用了 NIO 机制,可以方便实现大并发。如果一般玩票性质就直接上 memcached client for java ,虽然其通信方式是传统的阻塞IO模型但足够稳定,不是超大型的已经够用。