springcloud--Ribbon的配置和使用

:微服务 ribbon 的配置和使用,ribbon 是对消费者而言的,主要是两种,一种是 消费端ribbon+eureka , 另一种是 ribbon不依赖eureka

一. 消费端 ribbon+eureka

目录结构

  • 注册中心:eureka服务
  • 提供方:eureka客户端(user-service)
  • 消费方:eureka客户端(api-gateway)

业务说明:拿登录操作来演示服务间的通信流程

接入ribbon步骤

  • pom增加ribbon依赖,发现消费端具有eurek依赖的时候,里面包含了ribbon,所以这一步可以省略{:.warning}
  • RestTemplate添加@LoadBalanced
  • 触发服务观察

api-gateway(消费端代码片段)

  • UserController
  //----------------------------登录流程-------------------------------------------
  
  
  @RequestMapping(value="/accounts/signin",method={RequestMethod.POST,RequestMethod.GET})
  public String loginSubmit(HttpServletRequest req){
    String username = req.getParameter("username");
    String password = req.getParameter("password");
    if (username == null || password == null) {
      req.setAttribute("target", req.getParameter("target"));
      return "/user/accounts/signin";
    }
    User  user =  accountService.auth(username, password);
    if (user == null) {
      return "redirect:/accounts/signin?" + "username=" + username + "&" + ResultMsg.errorMsg("用户名或密码错误").asUrlParams();
    }else {
      UserContext.setUser(user);
      return  StringUtils.isNotBlank(req.getParameter("target")) ? "redirect:" + req.getParameter("target") : "redirect:/index";
    }
  }
  • AccountService
  /**
   * 校验用户名密码并返回用户对象
   * @param username
   * @param password
   * @return
   */
  public User auth(String username, String password) {
    if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
       return null;
    }
    User user = new User();
    user.setEmail(username);
    user.setPasswd(password);
    try {
      user = userDao.authUser(user);
    } catch (Exception e) {
      return null;
    }
    return user;
  }
  • UserDao
  @Autowired
  private GenericRest rest;
  @Value("${user.service.name}")
  private String userServiceName; 
  //调用远端的鉴权服务
  public User authUser(User user) {
    //userServiceName 是在注册中心注册的服务的名称,上面已经@Value{}获取了,这里没展示
    //通过服务名称代替之前的ip+端口,实现调用
    String url = "http://" + userServiceName + "/user/auth";
    ResponseEntity> responseEntity =  rest.post(url, user, 
               new ParameterizedTypeReference>() {});
    RestResponse response = responseEntity.getBody();
    if (response.getCode() == 0) {
      return response.getResult();
   }{
      throw new IllegalStateException("Can not add user");
   }
  }
  • RestTemplate配置(直连服务名称调用
package com.mooc.house.api.config;

import java.nio.charset.Charset;
import java.util.Arrays;

import org.apache.http.client.HttpClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter4;

@Configuration
public class RestAutoConfig {

    public static class RestTemplateConfig {

        @Bean
        @LoadBalanced //spring 对restTemplate bean进行定制,加入loadbalance拦截器进行ip:port的替换
        RestTemplate lbRestTemplate(HttpClient httpclient) {
            RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient));
            template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8")));
            template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5());
            return template;
        }
        
        @Bean
        RestTemplate directRestTemplate(HttpClient httpclient) {
            RestTemplate template = new RestTemplate(new HttpComponentsClientHttpRequestFactory(httpclient));
            template.getMessageConverters().add(0,new StringHttpMessageConverter(Charset.forName("utf-8")));
            template.getMessageConverters().add(1,new FastJsonHttpMessageConvert5());
            return template;
        }
        
         public static class FastJsonHttpMessageConvert5 extends FastJsonHttpMessageConverter4{
              
              static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
              
              public FastJsonHttpMessageConvert5(){
                setDefaultCharset(DEFAULT_CHARSET);
                setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON,new MediaType("application","*+json")));
              }

            }
    }
}

  • 封装好的服务调用类
package com.mooc.house.api.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * 既支持直连又支持服务发现的调用
 *
 */
@Service
public class GenericRest {
    
    @Autowired
    private RestTemplate lbRestTemplate;
    
    @Autowired
    private RestTemplate directRestTemplate;
    
    private static final String directFlag = "direct://";
    
    public  ResponseEntity post(String url,Object reqBody,ParameterizedTypeReference responseType){
        RestTemplate template = getRestTemplate(url);
        url = url.replace(directFlag, "");
        return template.exchange(url, HttpMethod.POST,new HttpEntity(reqBody),responseType);
    }
    
    public  ResponseEntity get(String url,ParameterizedTypeReference responseType){
        RestTemplate template = getRestTemplate(url);
        url = url.replace(directFlag, "");
        return template.exchange(url, HttpMethod.GET,HttpEntity.EMPTY,responseType);
    }

    private RestTemplate getRestTemplate(String url) {
        if (url.contains(directFlag)) {
            return directRestTemplate;
        }else {
            return lbRestTemplate;
        }
    }

}

user-service(远端的user服务,提供者代码片段)

  • controller
 //------------------------登录/鉴权-------------------------- 
  @RequestMapping("auth")
  public RestResponse auth(@RequestBody User user){
    User finalUser = userService.auth(user.getEmail(),user.getPasswd());
    return RestResponse.success(finalUser);
  }
  • UserService(直接惊动自己的mapper完成数据库查询)
  /**
   * 校验用户名密码、生成token并返回用户对象
   * @param email
   * @param passwd
   * @return
   */
  public User auth(String email, String passwd) {
    if (StringUtils.isBlank(email) || StringUtils.isBlank(passwd)) {
      throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
    }
    User user = new User();
    user.setEmail(email);
    user.setPasswd(HashUtils.encryPassword(passwd));
    user.setEnable(1);
    List list =  getUserByQuery(user);
    if (!list.isEmpty()) {
       User retUser = list.get(0);
       //校验完毕,通过JWT生成token
       onLogin(retUser);
       return retUser;
    }
    throw new UserException(Type.USER_AUTH_FAIL,"User Auth Fail");
  }
 public List getUserByQuery(User user) {
    List users = userMapper.select(user);
    users.forEach(u -> {
      u.setAvatar(imgPrefix + u.getAvatar());
    });
    return users;
  }
  private void onLogin(User user) {
    String token =  JwtHelper.genToken(ImmutableMap.of("email", user.getEmail(), "name", user.getName(),"ts",Instant.now().getEpochSecond()+""));
    renewToken(token,user.getEmail());
    user.setToken(token);
  }

上述只是为了简单体现远程调用,所以代码进行了简写,其中还有拦截器判断cookie,部分依赖没有列举出来{:.warning}

总结上述流程:

  • 消费网关API接受浏览器的请求
  • 请求到消费的service,dao
  • 消费dao通过restTemplate远程调用, http://服务名+/user/auth
  • 在服务端user-service另外添加一个application-other.properties,我们可以通过不同的配置,启动两个提供服务,进入项目根目录,启动命令是mvn springboot:run -Dspring.profile.active=other
  • 然后每次浏览器访问发现,1次是A端口,1次是B端口,轮询着来

二 . ribbon脱离eureka

说明:

  • ribbon脱离eureka,也就是消费端pom不引入eureka相关的依赖。所以就需要引入ribbon的依赖
  • 配置文件需要添加listOfServers等配置

配置:

  • application.properties
#服务列表
user.ribbon.listOfServers=127.0.0.1:8083,127.0.0.1:8082
  • ribbon配置类代码
package com.mooc.house.api.config;

import com.netflix.loadbalancer.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import com.netflix.client.config.IClientConfig;

public class NewRuleConfig {
    
    @Autowired
    private IClientConfig ribbonClientConfig;
    
    @Bean
    public IPing ribbonPing(IClientConfig config){
        //Iping ribbon每隔10秒向服务端列表发送一次ping请求,看是否成功或者失败
        //默认noOpPing不发送任何的ping命令
        //我们这里配置成Ping为health ,因为提供端加入了spring Acutor监控
        return new PingUrl(false,"/health");
    }
    
    @Bean
    public IRule ribbonRule(IClientConfig config){
         //return new RandomRule();
        //下面这个负载均衡策略更智能,loadBalance拦截之后,会对当前服务调用 成功失败结果进行记录
        //记录下一次,优先调用之前成功调用的
        return new AvailabilityFilteringRule();
        //WeightedResponseTimeRule 这个策略是根据响应时间分配权重
    }

}


  • 启动类代码
@SpringBootApplication
//@EnableDiscoveryClient
//调用名称"user"服务的时候,使用NewRuleConfig配置
@RibbonClient(name="user",configuration=NewRuleConfig.class)
public class ApiGatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(ApiGatewayApplication.class, args);
    }
    
    @Autowired
    private DiscoveryClient discoveryClient;
    
    @RequestMapping("index1")
    @ResponseBody
    public List getReister(){
      return discoveryClient.getInstances("user");
    }
}   

三. 祭出一张 eureka.ribbon.restTemplate三者关系图

图片引自 慕课网

mook.png

ending

你可能感兴趣的:(springcloud--Ribbon的配置和使用)